diff --git a/AUTHORS b/AUTHORS
index 29011ff..61e471e 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -260,6 +260,7 @@
 Deokjin Kim <deokjin81.kim@samsung.com>
 Derek Halman <d.halman@gmail.com>
 Devlin Cronin <rdevlin.cronin@gmail.com>
+Dhi Aurrahman <dio@rockybars.com>
 Diana Suvorova <diana.suvorova@gmail.com>
 Diego Ferreiro Val <elfogris@gmail.com>
 Dillon Sellars <dill.sellars@gmail.com>
diff --git a/DEPS b/DEPS
index 65b47cb..6cd0aeb 100644
--- a/DEPS
+++ b/DEPS
@@ -200,11 +200,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '39b4c86fe9146508a2d92d5775331c8aa1a04693',
+  'skia_revision': '48a99420a2495a665a732a7fe335e93ec7a83418',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': 'fe46597df7664f8fe317c2363cfad28007283e75',
+  'v8_revision': '8040ad59abd7119457d5acbde8602fde8a5291e5',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -212,11 +212,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '939fcd1a14f5ddc43e300bcb2d5d1f680c50ab85',
+  'angle_revision': '8420c5acaa568cacb7283116fce3ad283075166e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': '43bb60e1fa119d80b449c6550dd2b72328b101b9',
+  'swiftshader_revision': '9dff6a3bb5950edd890e5141f2d3d2f3b4ef351e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -259,7 +259,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling HarfBuzz
   # and whatever else without interference from each other.
-  'harfbuzz_revision': 'c39ab82c90479341dcf28eaa8174af6f08c0d7ae',
+  'harfbuzz_revision': '53806e5b83cee0e275eac038d0780f95ac56588c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Emoji Segmenter
   # and whatever else without interference from each other.
@@ -275,7 +275,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': 'abee7cf3a9897cce6d02571c4aa37a06509c2358',
+  'devtools_frontend_revision': '3383020812d40c5b2839233c6817d2d8f84eff43',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -327,7 +327,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '9175f00eec91c6c9b3c3e264d6d8ec7879881844',
+  'dawn_revision': '71279dcde7cd1b718b137b9ed0384803f347a359',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -899,7 +899,7 @@
   # Tools used when building Chrome for Chrome OS. This affects both the Simple
   # Chrome workflow, as well as the chromeos-chrome ebuild.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '9ed30bc3ed292b02d85fde89c64207484b7a3aa4',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'fc23bc1c96acb0fa2fcc526204333fb9a373ecb6',
       'condition': 'checkout_chromeos',
   },
 
@@ -1369,7 +1369,7 @@
       'packages': [
           {
               'package': 'fuchsia/third_party/aemu/linux-amd64',
-              'version': 'pYEJYcwmc7764Ioa_c8OSqFs8pflIE1Y-yEvVdFb3zUC'
+              'version': 'Ts7JI7CLxH4-h5lAQEsDnba0CD6d4ACU00UDPQXaW0wC'
           },
       ],
       'condition': 'host_os == "linux" and checkout_fuchsia',
@@ -1551,7 +1551,7 @@
   },
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'b6b599fc588e59142df343d2d2897203468aacea',
+    Var('webrtc_git') + '/src.git' + '@' + '0d863f72a8c747c1b41f2798e5201e1abcdaec2b',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1623,7 +1623,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@dee1cdf147f77fcf5597d87931226c519f49c66c',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@d90991c959bad057c3109394079ce08097d66e43',
     'condition': 'checkout_src_internal',
   },
 
@@ -1642,7 +1642,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'UEDryZf_VuC_LfcEXlTJ_7tEzVfL6qKZIy8qC-wjmbIC',
+        'version': '2vgKL8p-SUW0S-OFCowtelp1xuW-s-25Zm0z7AwTFPMC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/ash/login/ui/login_user_view.cc b/ash/login/ui/login_user_view.cc
index 1e46272..95589bc 100644
--- a/ash/login/ui/login_user_view.cc
+++ b/ash/login/ui/login_user_view.cc
@@ -17,6 +17,7 @@
 #include "ash/public/cpp/login_constants.h"
 #include "ash/public/cpp/session/user_info.h"
 #include "ash/resources/vector_icons/vector_icons.h"
+#include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_provider.h"
 #include "base/bind.h"
@@ -171,6 +172,7 @@
     enterprise_icon_->SetVisible(false);
     AddChildView(enterprise_icon_);
   }
+
   ~UserImage() override = default;
 
   void UpdateForUser(const LoginUserInfo& user) {
@@ -461,6 +463,9 @@
   hover_notifier_ = std::make_unique<HoverNotifier>(
       this,
       base::BindRepeating(&LoginUserView::OnHover, base::Unretained(this)));
+
+  if (ash::Shell::HasInstance())
+    display_observation_.Observe(ash::Shell::Get()->display_configurator());
 }
 
 LoginUserView::~LoginUserView() = default;
@@ -538,6 +543,12 @@
                                         : FocusBehavior::NEVER);
 }
 
+void LoginUserView::OnPowerStateChanged(
+    chromeos::DisplayPowerState power_state) {
+  bool is_display_on = power_state != chromeos::DISPLAY_POWER_ALL_OFF;
+  user_image_->SetAnimationEnabled(is_display_on && is_opaque_);
+}
+
 const char* LoginUserView::GetClassName() const {
   return kUserViewClassName;
 }
diff --git a/ash/login/ui/login_user_view.h b/ash/login/ui/login_user_view.h
index a333c75..f3bfa31 100644
--- a/ash/login/ui/login_user_view.h
+++ b/ash/login/ui/login_user_view.h
@@ -11,6 +11,8 @@
 #include "ash/login/ui/login_user_menu_view.h"
 #include "ash/public/cpp/login_types.h"
 #include "base/macros.h"
+#include "base/scoped_observation.h"
+#include "ui/display/manager/display_configurator.h"
 #include "ui/views/view.h"
 
 namespace ash {
@@ -20,7 +22,8 @@
 
 // Display the user's profile icon, name, and a menu icon in various layout
 // styles.
-class ASH_EXPORT LoginUserView : public views::View {
+class ASH_EXPORT LoginUserView : public views::View,
+                                 public display::DisplayConfigurator::Observer {
  public:
   // TestApi is used for tests to get internal implementation details.
   class ASH_EXPORT TestApi {
@@ -71,6 +74,9 @@
   // Enables or disables tapping the view.
   void SetTapEnabled(bool enabled);
 
+  // DisplayConfigurator::Observer
+  void OnPowerStateChanged(chromeos::DisplayPowerState power_state) override;
+
   const LoginUserInfo& current_user() const { return current_user_; }
 
   // views::View:
@@ -128,6 +134,10 @@
   // state.
   bool force_opaque_ = false;
 
+  base::ScopedObservation<display::DisplayConfigurator,
+                          display::DisplayConfigurator::Observer>
+      display_observation_{this};
+
   DISALLOW_COPY_AND_ASSIGN(LoginUserView);
 };
 
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 5830912..05fb2f7 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -3865,6 +3865,7 @@
     ]
 
     sources = [
+      "test/android/javatests/src/org/chromium/base/test/BaseActivityTestRule.java",
       "test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java",
       "test/android/javatests/src/org/chromium/base/test/BaseChromiumRunnerCommon.java",
       "test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java",
diff --git a/base/allocator/partition_allocator/partition_bucket.cc b/base/allocator/partition_allocator/partition_bucket.cc
index a2570a5..be169da 100644
--- a/base/allocator/partition_allocator/partition_bucket.cc
+++ b/base/allocator/partition_allocator/partition_bucket.cc
@@ -55,12 +55,11 @@
 
   root->total_size_of_direct_mapped_pages.fetch_add(reserved_size,
                                                     std::memory_order_relaxed);
-  root->IncreaseCommittedPages(slot_size);
 
   char* slot = ptr + PartitionPageSize();
   RecommitSystemPages(ptr + SystemPageSize(), SystemPageSize(), PageReadWrite,
                       PageUpdatePermissions);
-  RecommitSystemPages(slot, slot_size, PageReadWrite, PageUpdatePermissions);
+  root->RecommitSystemPagesForData(slot, slot_size, PageUpdatePermissions);
 
   auto* metadata = reinterpret_cast<PartitionDirectMapMetadata<thread_safe>*>(
       PartitionSuperPageToMetadataArea(ptr));
@@ -220,9 +219,8 @@
 
   // System pages in the super page come in a decommited state. Commit them
   // before vending them back.
-  RecommitSystemPages(ret, slot_span_committed_size, PageReadWrite,
-                      PageUpdatePermissions);
-  root->IncreaseCommittedPages(slot_span_committed_size);
+  root->RecommitSystemPagesForData(ret, slot_span_committed_size,
+                                   PageUpdatePermissions);
   root->next_partition_page += slot_span_reserved_size;
   // Double check that we had enough space in the super page for the new slot
   // span.
@@ -569,9 +567,9 @@
       PA_DCHECK(new_slot_span->is_decommitted());
       decommitted_slot_spans_head = new_slot_span->next_slot_span;
       void* addr = SlotSpanMetadata<thread_safe>::ToPointer(new_slot_span);
-      root->RecommitSystemPages(addr,
-                                new_slot_span->bucket->get_bytes_per_span(),
-                                PageKeepPermissionsIfPossible);
+      root->RecommitSystemPagesForData(
+          addr, new_slot_span->bucket->get_bytes_per_span(),
+          PageKeepPermissionsIfPossible);
       new_slot_span->Reset();
       *is_already_zeroed = kDecommittedPagesAreAlwaysZeroed;
     }
diff --git a/base/allocator/partition_allocator/partition_page.cc b/base/allocator/partition_allocator/partition_page.cc
index e65c795..aafb7ba0 100644
--- a/base/allocator/partition_allocator/partition_page.cc
+++ b/base/allocator/partition_allocator/partition_page.cc
@@ -41,6 +41,7 @@
     extent->next_extent->prev_extent = extent->prev_extent;
   }
 
+  // The actual decommit is deferred, when releasing the reserved memory region.
   root->DecreaseCommittedPages(slot_span->bucket->slot_size);
 
   size_t reserved_size =
@@ -166,8 +167,8 @@
   PA_DCHECK(is_empty());
   PA_DCHECK(!bucket->is_direct_mapped());
   void* addr = SlotSpanMetadata::ToPointer(this);
-  root->DecommitSystemPages(addr, bucket->get_bytes_per_span(),
-                            PageKeepPermissionsIfPossible);
+  root->DecommitSystemPagesForData(addr, bucket->get_bytes_per_span(),
+                                   PageKeepPermissionsIfPossible);
 
   // We actually leave the decommitted slot span in the active list. We'll sweep
   // it on to the decommitted list when we next walk the active list.
diff --git a/base/allocator/partition_allocator/partition_root.cc b/base/allocator/partition_allocator/partition_root.cc
index 95243580..fb63b42a 100644
--- a/base/allocator/partition_allocator/partition_root.cc
+++ b/base/allocator/partition_allocator/partition_root.cc
@@ -456,15 +456,16 @@
 
     // Shrink by decommitting unneeded pages and making them inaccessible.
     size_t decommit_size = current_slot_size - new_slot_size;
-    DecommitSystemPages(char_ptr + new_slot_size, decommit_size,
-                        PageUpdatePermissions);
+    DecommitSystemPagesForData(char_ptr + new_slot_size, decommit_size,
+                               PageUpdatePermissions);
   } else if (new_slot_size <=
              DirectMapExtent::FromSlotSpan(slot_span)->map_size) {
     // Grow within the actually allocated memory. Just need to make the
     // pages accessible again.
     size_t recommit_slot_size_growth = new_slot_size - current_slot_size;
-    RecommitSystemPages(char_ptr + current_slot_size, recommit_slot_size_growth,
-                        PageUpdatePermissions);
+    RecommitSystemPagesForData(char_ptr + current_slot_size,
+                               recommit_slot_size_growth,
+                               PageUpdatePermissions);
 
 #if DCHECK_IS_ON()
     memset(char_ptr + current_slot_size, kUninitializedByte,
@@ -596,10 +597,12 @@
 template <bool thread_safe>
 void PartitionRoot<thread_safe>::PurgeMemory(int flags) {
   // TODO(chromium:1129751): Change to LIKELY once PCScan is enabled by default.
-  if (UNLIKELY(IsScanEnabled()) && (flags & PartitionPurgeForceAllFreed)) {
-    PCScan::Instance().PerformScanIfNeeded(PCScan::InvocationMode::kBlocking);
+  if (UNLIKELY(IsScanEnabled())) {
+    if (flags & PartitionPurgeForceAllFreed)
+      PCScan::Instance().PerformScan(PCScan::InvocationMode::kBlocking);
+    else
+      PCScan::Instance().PerformScanIfNeeded(PCScan::InvocationMode::kBlocking);
   }
-
   {
     ScopedGuard guard{lock_};
     if (flags & PartitionPurgeDecommitEmptySlotSpans)
diff --git a/base/allocator/partition_allocator/partition_root.h b/base/allocator/partition_allocator/partition_root.h
index fe5008e0..5406b25d 100644
--- a/base/allocator/partition_allocator/partition_root.h
+++ b/base/allocator/partition_allocator/partition_root.h
@@ -212,12 +212,12 @@
 
   ALWAYS_INLINE void IncreaseCommittedPages(size_t len);
   ALWAYS_INLINE void DecreaseCommittedPages(size_t len);
-  ALWAYS_INLINE void DecommitSystemPages(
+  ALWAYS_INLINE void DecommitSystemPagesForData(
       void* address,
       size_t length,
       PageAccessibilityDisposition accessibility_disposition)
       EXCLUSIVE_LOCKS_REQUIRED(lock_);
-  ALWAYS_INLINE void RecommitSystemPages(
+  ALWAYS_INLINE void RecommitSystemPagesForData(
       void* address,
       size_t length,
       PageAccessibilityDisposition accessibility_disposition)
@@ -856,21 +856,21 @@
 }
 
 template <bool thread_safe>
-ALWAYS_INLINE void PartitionRoot<thread_safe>::DecommitSystemPages(
+ALWAYS_INLINE void PartitionRoot<thread_safe>::DecommitSystemPagesForData(
     void* address,
     size_t length,
     PageAccessibilityDisposition accessibility_disposition) {
-  ::base::DecommitSystemPages(address, length, accessibility_disposition);
+  DecommitSystemPages(address, length, accessibility_disposition);
   DecreaseCommittedPages(length);
 }
 
 template <bool thread_safe>
-ALWAYS_INLINE void PartitionRoot<thread_safe>::RecommitSystemPages(
+ALWAYS_INLINE void PartitionRoot<thread_safe>::RecommitSystemPagesForData(
     void* address,
     size_t length,
     PageAccessibilityDisposition accessibility_disposition) {
-  ::base::RecommitSystemPages(address, length, PageReadWrite,
-                              accessibility_disposition);
+  RecommitSystemPages(address, length, PageReadWrite,
+                      accessibility_disposition);
   IncreaseCommittedPages(length);
 }
 
diff --git a/base/allocator/partition_allocator/pcscan.cc b/base/allocator/partition_allocator/pcscan.cc
index 05825b2..49ccdc0 100644
--- a/base/allocator/partition_allocator/pcscan.cc
+++ b/base/allocator/partition_allocator/pcscan.cc
@@ -533,13 +533,11 @@
   auto task = std::make_unique<PCScanTask>(*this);
 
   // Post PCScan task.
-  const auto callback = [](PCScanTask task) { std::move(task).RunOnce(); };
-  if (UNLIKELY(invocation_mode == InvocationMode::kBlocking)) {
-    // Blocking is only used for testing.
-    callback(std::move(*task));
-  } else {
-    PA_DCHECK(InvocationMode::kNonBlocking == invocation_mode);
+  if (LIKELY(invocation_mode == InvocationMode::kNonBlocking)) {
     PCScanThread::Instance().PostTask(std::move(task));
+  } else {
+    PA_DCHECK(InvocationMode::kBlocking == invocation_mode);
+    std::move(*task).RunOnce();
   }
 }
 
diff --git a/base/allocator/partition_allocator/pcscan.h b/base/allocator/partition_allocator/pcscan.h
index 8bc1410..dca4d9b 100644
--- a/base/allocator/partition_allocator/pcscan.h
+++ b/base/allocator/partition_allocator/pcscan.h
@@ -66,6 +66,9 @@
 
   ALWAYS_INLINE void MoveToQuarantine(void* ptr, SlotSpan* slot_span);
 
+  // Performs scanning unconditionally.
+  void PerformScan(InvocationMode invocation_mode);
+  // Performs scanning only if a certain quarantine threshold was reached.
   void PerformScanIfNeeded(InvocationMode invocation_mode);
 
   void ClearRootsForTesting();
@@ -135,8 +138,6 @@
 
   constexpr PCScan() = default;
 
-  void PerformScan(InvocationMode invocation_mode);
-
   static PCScan instance_ PA_CONSTINIT;
 
   Roots roots_{};
diff --git a/base/allocator/partition_allocator/thread_cache.cc b/base/allocator/partition_allocator/thread_cache.cc
index 1ca1fe1..ebe3d58 100644
--- a/base/allocator/partition_allocator/thread_cache.cc
+++ b/base/allocator/partition_allocator/thread_cache.cc
@@ -148,6 +148,24 @@
 ThreadCache::ThreadCache(PartitionRoot<ThreadSafe>* root)
     : buckets_(), stats_(), root_(root), next_(nullptr), prev_(nullptr) {
   ThreadCacheRegistry::Instance().RegisterThreadCache(this);
+
+  for (int index = 0; index < kBucketCount; index++) {
+    const auto& root_bucket = root->buckets[index];
+    // Invalid bucket.
+    if (!root_bucket.active_slot_spans_head)
+      continue;
+
+    // Smaller allocations are more frequent, and more performance-sensitive.
+    // Cache more small objects, and fewer larger ones, to save memory.
+    size_t element_size = root_bucket.slot_size;
+    if (element_size <= 128) {
+      buckets_[index].limit = 128;
+    } else if (element_size <= 256) {
+      buckets_[index].limit = 64;
+    } else {
+      buckets_[index].limit = 32;
+    }
+  }
 }
 
 ThreadCache::~ThreadCache() {
diff --git a/base/allocator/partition_allocator/thread_cache.h b/base/allocator/partition_allocator/thread_cache.h
index 10805e7..3d4f3ffd 100644
--- a/base/allocator/partition_allocator/thread_cache.h
+++ b/base/allocator/partition_allocator/thread_cache.h
@@ -161,8 +161,9 @@
 
  private:
   struct Bucket {
-    size_t count;
     PartitionFreelistEntry* freelist_head;
+    uint16_t count;
+    uint16_t limit;
   };
 
   explicit ThreadCache(PartitionRoot<ThreadSafe>* root);
@@ -172,16 +173,13 @@
 
   // TODO(lizeb): Optimize the threshold.
   static constexpr size_t kSizeThreshold = 512;
-  static constexpr size_t kBucketCount =
+  static constexpr uint16_t kBucketCount =
       ((ConstexprLog2(kSizeThreshold) - kMinBucketedOrder + 1)
        << kNumBucketsPerOrderBits) +
       1;
   static_assert(
       kBucketCount < kNumBuckets,
       "Cannot have more cached buckets than what the allocator supports");
-  // TODO(lizeb): Tune this constant, and adapt it to the bucket size /
-  // allocation patterns.
-  static constexpr size_t kMaxCountPerBucket = 100;
 
   std::atomic<bool> should_purge_;
   Bucket buckets_[kBucketCount];
@@ -208,7 +206,7 @@
 
   INCREMENT_COUNTER(stats_.cache_fill_count);
 
-  if (bucket_index >= kBucketCount) {
+  if (UNLIKELY(bucket_index >= kBucketCount)) {
     INCREMENT_COUNTER(stats_.cache_fill_misses);
     return false;
   }
@@ -225,8 +223,8 @@
   INCREMENT_COUNTER(stats_.cache_fill_hits);
 
   // Batched deallocation, amortizing lock acquisitions.
-  if (bucket.count >= kMaxCountPerBucket) {
-    ClearBucket(bucket, kMaxCountPerBucket / 2);
+  if (UNLIKELY(bucket.count >= bucket.limit)) {
+    ClearBucket(bucket, bucket.limit >> 1);
   }
 
   return true;
@@ -235,7 +233,7 @@
 ALWAYS_INLINE void* ThreadCache::GetFromCache(size_t bucket_index) {
   INCREMENT_COUNTER(stats_.alloc_count);
   // Only handle "small" allocations.
-  if (bucket_index >= kBucketCount) {
+  if (UNLIKELY(bucket_index >= kBucketCount)) {
     INCREMENT_COUNTER(stats_.alloc_miss_too_large);
     INCREMENT_COUNTER(stats_.alloc_misses);
     return nullptr;
@@ -243,7 +241,7 @@
 
   auto& bucket = buckets_[bucket_index];
   auto* result = bucket.freelist_head;
-  if (!result) {
+  if (UNLIKELY(!result)) {
     PA_DCHECK(bucket.count == 0);
     INCREMENT_COUNTER(stats_.alloc_miss_empty);
     INCREMENT_COUNTER(stats_.alloc_misses);
diff --git a/base/allocator/partition_allocator/thread_cache_unittest.cc b/base/allocator/partition_allocator/thread_cache_unittest.cc
index cf40720..b92d817d 100644
--- a/base/allocator/partition_allocator/thread_cache_unittest.cc
+++ b/base/allocator/partition_allocator/thread_cache_unittest.cc
@@ -299,19 +299,20 @@
 
   tcache->Purge();
   cache_fill_counter.Reset();
-  // Bucket are never full, fill always succeeds.
-  size_t bucket_index = FillThreadCacheAndReturnIndex(
-      kTestSize, ThreadCache::kMaxCountPerBucket + 10);
-  EXPECT_EQ(ThreadCache::kMaxCountPerBucket + 10, cache_fill_counter.Delta());
+  constexpr size_t kMaxCountForBucket = 128;
+  // Buckets are never full, fill always succeeds.
+  size_t bucket_index =
+      FillThreadCacheAndReturnIndex(kTestSize, kMaxCountForBucket + 10);
+  EXPECT_EQ(kMaxCountForBucket + 10, cache_fill_counter.Delta());
   EXPECT_EQ(0u, cache_fill_misses_counter.Delta());
 
   // Memory footprint.
   ThreadCacheStats stats;
   ThreadCacheRegistry::Instance().DumpStats(true, &stats);
   // Bucket was cleared (count halved, then refilled).
-  EXPECT_EQ(g_root->buckets[bucket_index].slot_size *
-                (ThreadCache::kMaxCountPerBucket / 2 + 10),
-            stats.bucket_total_memory);
+  EXPECT_EQ(
+      g_root->buckets[bucket_index].slot_size * (kMaxCountForBucket / 2 + 10),
+      stats.bucket_total_memory);
   EXPECT_EQ(sizeof(ThreadCache), stats.metadata_overhead);
 }
 
diff --git a/base/android/java/src/org/chromium/base/JNIUtils.java b/base/android/java/src/org/chromium/base/JNIUtils.java
index 1b53b9f..44c6a83 100644
--- a/base/android/java/src/org/chromium/base/JNIUtils.java
+++ b/base/android/java/src/org/chromium/base/JNIUtils.java
@@ -4,6 +4,9 @@
 
 package org.chromium.base;
 
+import android.content.Context;
+import android.text.TextUtils;
+
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.MainDex;
 
@@ -20,14 +23,24 @@
      * is needed for the few cases where the JNI mechanism is unable to automatically determine the
      * appropriate ClassLoader instance.
      */
-    @CalledByNative
-    public static Object getClassLoader() {
+    private static ClassLoader getClassLoader() {
         if (sJniClassLoader == null) {
             return JNIUtils.class.getClassLoader();
         }
         return sJniClassLoader;
     }
 
+    /** Returns a ClassLoader which can load Java classes from the specified split. */
+    @CalledByNative
+    public static ClassLoader getSplitClassLoader(String splitName) {
+        Context context = ContextUtils.getApplicationContext();
+        if (!TextUtils.isEmpty(splitName)
+                && BundleUtils.isIsolatedSplitInstalled(context, splitName)) {
+            return BundleUtils.createIsolatedSplitContext(context, splitName).getClassLoader();
+        }
+        return getClassLoader();
+    }
+
     /**
      * Sets the ClassLoader to be used for loading Java classes from native.
      * @param classLoader the ClassLoader to use.
diff --git a/base/android/jni_android.cc b/base/android/jni_android.cc
index 679a7d2..1f02bbd0 100644
--- a/base/android/jni_android.cc
+++ b/base/android/jni_android.cc
@@ -11,19 +11,22 @@
 
 #include "base/android/java_exception_reporter.h"
 #include "base/android/jni_string.h"
+#include "base/android/jni_utils.h"
+#include "base/containers/flat_map.h"
 #include "base/debug/debugging_buildflags.h"
 #include "base/lazy_instance.h"
 #include "base/logging.h"
+#include "base/no_destructor.h"
+#include "base/synchronization/lock.h"
 #include "base/threading/thread_local.h"
 
+namespace base {
+namespace android {
 namespace {
-using base::android::GetClass;
-using base::android::MethodID;
-using base::android::ScopedJavaLocalRef;
 
 JavaVM* g_jvm = NULL;
-base::LazyInstance<base::android::ScopedJavaGlobalRef<jobject>>::Leaky
-    g_class_loader = LAZY_INSTANCE_INITIALIZER;
+base::LazyInstance<ScopedJavaGlobalRef<jobject>>::Leaky g_class_loader =
+    LAZY_INSTANCE_INITIALIZER;
 jmethodID g_class_loader_load_class_method_id = 0;
 
 #if BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
@@ -33,10 +36,61 @@
 
 bool g_fatal_exception_occurred = false;
 
-}  // namespace
+// Returns a ClassLoader instance which will be able to load classes from the
+// specified split.
+jobject GetCachedClassLoader(JNIEnv* env, const std::string& split_name) {
+  DCHECK(!split_name.empty());
+  static base::NoDestructor<base::Lock> lock;
+  static base::NoDestructor<
+      base::flat_map<std::string, ScopedJavaGlobalRef<jobject>>>
+      split_class_loader_map;
 
-namespace base {
-namespace android {
+  base::AutoLock guard(*lock);
+  auto it = split_class_loader_map->find(split_name);
+  if (it != split_class_loader_map->end()) {
+    return it->second.obj();
+  }
+
+  ScopedJavaGlobalRef<jobject> class_loader(
+      GetSplitClassLoader(env, split_name));
+  jobject class_loader_obj = class_loader.obj();
+  split_class_loader_map->insert({split_name, std::move(class_loader)});
+  return class_loader_obj;
+}
+
+ScopedJavaLocalRef<jclass> GetClassInternal(JNIEnv* env,
+                                            const char* class_name,
+                                            jobject class_loader) {
+  jclass clazz;
+  if (class_loader != nullptr) {
+    // ClassLoader.loadClass expects a classname with components separated by
+    // dots instead of the slashes that JNIEnv::FindClass expects. The JNI
+    // generator generates names with slashes, so we have to replace them here.
+    // TODO(torne): move to an approach where we always use ClassLoader except
+    // for the special case of base::android::GetClassLoader(), and change the
+    // JNI generator to generate dot-separated names. http://crbug.com/461773
+    size_t bufsize = strlen(class_name) + 1;
+    char dotted_name[bufsize];
+    memmove(dotted_name, class_name, bufsize);
+    for (size_t i = 0; i < bufsize; ++i) {
+      if (dotted_name[i] == '/') {
+        dotted_name[i] = '.';
+      }
+    }
+
+    clazz = static_cast<jclass>(
+        env->CallObjectMethod(class_loader, g_class_loader_load_class_method_id,
+                              ConvertUTF8ToJavaString(env, dotted_name).obj()));
+  } else {
+    clazz = env->FindClass(class_name);
+  }
+  if (ClearException(env) || !clazz) {
+    LOG(FATAL) << "Failed to find class " << class_name;
+  }
+  return ScopedJavaLocalRef<jclass>(env, clazz);
+}
+
+}  // namespace
 
 JNIEnv* AttachCurrentThread() {
   DCHECK(g_jvm);
@@ -109,41 +163,44 @@
   g_class_loader.Get().Reset(class_loader);
 }
 
-ScopedJavaLocalRef<jclass> GetClass(JNIEnv* env, const char* class_name) {
-  jclass clazz;
-  if (!g_class_loader.Get().is_null()) {
-    // ClassLoader.loadClass expects a classname with components separated by
-    // dots instead of the slashes that JNIEnv::FindClass expects. The JNI
-    // generator generates names with slashes, so we have to replace them here.
-    // TODO(torne): move to an approach where we always use ClassLoader except
-    // for the special case of base::android::GetClassLoader(), and change the
-    // JNI generator to generate dot-separated names. http://crbug.com/461773
-    size_t bufsize = strlen(class_name) + 1;
-    char dotted_name[bufsize];
-    memmove(dotted_name, class_name, bufsize);
-    for (size_t i = 0; i < bufsize; ++i) {
-      if (dotted_name[i] == '/') {
-        dotted_name[i] = '.';
-      }
-    }
-
-    clazz = static_cast<jclass>(
-        env->CallObjectMethod(g_class_loader.Get().obj(),
-                              g_class_loader_load_class_method_id,
-                              ConvertUTF8ToJavaString(env, dotted_name).obj()));
-  } else {
-    clazz = env->FindClass(class_name);
-  }
-  if (ClearException(env) || !clazz) {
-    LOG(FATAL) << "Failed to find class " << class_name;
-  }
-  return ScopedJavaLocalRef<jclass>(env, clazz);
+ScopedJavaLocalRef<jclass> GetClass(JNIEnv* env,
+                                    const char* class_name,
+                                    const std::string& split_name) {
+  return GetClassInternal(env, class_name,
+                          GetCachedClassLoader(env, split_name));
 }
 
-jclass LazyGetClass(
-    JNIEnv* env,
-    const char* class_name,
-    std::atomic<jclass>* atomic_class_id) {
+ScopedJavaLocalRef<jclass> GetClass(JNIEnv* env, const char* class_name) {
+  return GetClassInternal(env, class_name, g_class_loader.Get().obj());
+}
+
+// This is duplicated with LazyGetClass below because these are performance
+// sensitive.
+jclass LazyGetClass(JNIEnv* env,
+                    const char* class_name,
+                    const std::string& split_name,
+                    std::atomic<jclass>* atomic_class_id) {
+  const jclass value = std::atomic_load(atomic_class_id);
+  if (value)
+    return value;
+  ScopedJavaGlobalRef<jclass> clazz;
+  clazz.Reset(GetClass(env, class_name, split_name));
+  jclass cas_result = nullptr;
+  if (std::atomic_compare_exchange_strong(atomic_class_id, &cas_result,
+                                          clazz.obj())) {
+    // We intentionally leak the global ref since we now storing it as a raw
+    // pointer in |atomic_class_id|.
+    return clazz.Release();
+  } else {
+    return cas_result;
+  }
+}
+
+// This is duplicated with LazyGetClass above because these are performance
+// sensitive.
+jclass LazyGetClass(JNIEnv* env,
+                    const char* class_name,
+                    std::atomic<jclass>* atomic_class_id) {
   const jclass value = std::atomic_load(atomic_class_id);
   if (value)
     return value;
diff --git a/base/android/jni_android.h b/base/android/jni_android.h
index 0e8e322..41ab9c3 100644
--- a/base/android/jni_android.h
+++ b/base/android/jni_android.h
@@ -96,6 +96,9 @@
 // This method triggers a fatal assertion if the class could not be found.
 // Use HasClass if you need to check whether the class exists.
 BASE_EXPORT ScopedJavaLocalRef<jclass> GetClass(JNIEnv* env,
+                                                const char* class_name,
+                                                const std::string& split_name);
+BASE_EXPORT ScopedJavaLocalRef<jclass> GetClass(JNIEnv* env,
                                                 const char* class_name);
 
 // The method will initialize |atomic_class_id| to contain a global ref to the
@@ -104,6 +107,10 @@
 // The caller is responsible to zero-initialize |atomic_method_id|.
 // It's fine to simultaneously call this on multiple threads referencing the
 // same |atomic_method_id|.
+BASE_EXPORT jclass LazyGetClass(JNIEnv* env,
+                                const char* class_name,
+                                const std::string& split_name,
+                                std::atomic<jclass>* atomic_class_id);
 BASE_EXPORT jclass LazyGetClass(
     JNIEnv* env,
     const char* class_name,
diff --git a/base/android/jni_generator/golden/SampleForTestsWithSplit_jni.golden b/base/android/jni_generator/golden/SampleForTestsWithSplit_jni.golden
new file mode 100644
index 0000000..c7bd774
--- /dev/null
+++ b/base/android/jni_generator/golden/SampleForTestsWithSplit_jni.golden
@@ -0,0 +1,527 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+
+// This file is autogenerated by
+//     base/android/jni_generator/jni_generator.py
+// For
+//     org/chromium/example/jni_generator/SampleForTests
+
+#ifndef org_chromium_example_jni_generator_SampleForTests_JNI
+#define org_chromium_example_jni_generator_SampleForTests_JNI
+
+#include <jni.h>
+
+#include "base/android/jni_generator/jni_generator_helper.h"
+
+
+// Step 1: Forward declarations.
+
+JNI_REGISTRATION_EXPORT extern const char
+    kClassPath_org_chromium_example_jni_1generator_SampleForTests[];
+const char kClassPath_org_chromium_example_jni_1generator_SampleForTests[] =
+    "org/chromium/example/jni_generator/SampleForTests";
+
+JNI_REGISTRATION_EXPORT extern const char
+    kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA[];
+const char kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA[] =
+    "org/chromium/example/jni_generator/SampleForTests$InnerStructA";
+
+JNI_REGISTRATION_EXPORT extern const char
+    kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB[];
+const char kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB[] =
+    "org/chromium/example/jni_generator/SampleForTests$InnerStructB";
+// Leaking this jclass as we cannot use LazyInstance from some threads.
+JNI_REGISTRATION_EXPORT std::atomic<jclass>
+    g_org_chromium_example_jni_1generator_SampleForTests_clazz(nullptr);
+#ifndef org_chromium_example_jni_1generator_SampleForTests_clazz_defined
+#define org_chromium_example_jni_1generator_SampleForTests_clazz_defined
+inline jclass org_chromium_example_jni_1generator_SampleForTests_clazz(JNIEnv* env) {
+  return base::android::LazyGetClass(env,
+      kClassPath_org_chromium_example_jni_1generator_SampleForTests, "sample",
+      &g_org_chromium_example_jni_1generator_SampleForTests_clazz);
+}
+#endif
+// Leaking this jclass as we cannot use LazyInstance from some threads.
+JNI_REGISTRATION_EXPORT std::atomic<jclass>
+    g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz(nullptr);
+#ifndef org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz_defined
+#define org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz_defined
+inline jclass org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz(JNIEnv*
+    env) {
+  return base::android::LazyGetClass(env,
+      kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA, "sample",
+      &g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz);
+}
+#endif
+// Leaking this jclass as we cannot use LazyInstance from some threads.
+JNI_REGISTRATION_EXPORT std::atomic<jclass>
+    g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz(nullptr);
+#ifndef org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz_defined
+#define org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz_defined
+inline jclass org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz(JNIEnv*
+    env) {
+  return base::android::LazyGetClass(env,
+      kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB, "sample",
+      &g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz);
+}
+#endif
+
+
+// Step 2: Constants (optional).
+
+
+// Step 3: Method stubs.
+namespace base {
+namespace android {
+
+static jlong JNI_SampleForTests_Init(JNIEnv* env, const base::android::JavaParamRef<jobject>&
+    caller,
+    const base::android::JavaParamRef<jstring>& param);
+
+JNI_GENERATOR_EXPORT jlong
+    Java_org_chromium_base_natives_GEN_1JNI_org_1chromium_1example_1jni_11generator_1SampleForTests_1init(
+    JNIEnv* env,
+    jclass jcaller,
+    jobject caller,
+    jstring param) {
+  return JNI_SampleForTests_Init(env, base::android::JavaParamRef<jobject>(env, caller),
+      base::android::JavaParamRef<jstring>(env, param));
+}
+
+JNI_GENERATOR_EXPORT void
+    Java_org_chromium_base_natives_GEN_1JNI_org_1chromium_1example_1jni_11generator_1SampleForTests_1destroy(
+    JNIEnv* env,
+    jclass jcaller,
+    jlong nativeCPPClass,
+    jobject caller) {
+  CPPClass* native = reinterpret_cast<CPPClass*>(nativeCPPClass);
+  CHECK_NATIVE_PTR(env, jcaller, native, "Destroy");
+  return native->Destroy(env, base::android::JavaParamRef<jobject>(env, caller));
+}
+
+static jdouble JNI_SampleForTests_GetDoubleFunction(JNIEnv* env, const
+    base::android::JavaParamRef<jobject>& caller);
+
+JNI_GENERATOR_EXPORT jdouble
+    Java_org_chromium_base_natives_GEN_1JNI_org_1chromium_1example_1jni_11generator_1SampleForTests_1getDoubleFunction(
+    JNIEnv* env,
+    jclass jcaller,
+    jobject caller) {
+  return JNI_SampleForTests_GetDoubleFunction(env, base::android::JavaParamRef<jobject>(env,
+      caller));
+}
+
+static jfloat JNI_SampleForTests_GetFloatFunction(JNIEnv* env);
+
+JNI_GENERATOR_EXPORT jfloat
+    Java_org_chromium_base_natives_GEN_1JNI_org_1chromium_1example_1jni_11generator_1SampleForTests_1getFloatFunction(
+    JNIEnv* env,
+    jclass jcaller) {
+  return JNI_SampleForTests_GetFloatFunction(env);
+}
+
+static void JNI_SampleForTests_SetNonPODDatatype(JNIEnv* env, const
+    base::android::JavaParamRef<jobject>& caller,
+    const base::android::JavaParamRef<jobject>& rect);
+
+JNI_GENERATOR_EXPORT void
+    Java_org_chromium_base_natives_GEN_1JNI_org_1chromium_1example_1jni_11generator_1SampleForTests_1setNonPODDatatype(
+    JNIEnv* env,
+    jclass jcaller,
+    jobject caller,
+    jobject rect) {
+  return JNI_SampleForTests_SetNonPODDatatype(env, base::android::JavaParamRef<jobject>(env,
+      caller), base::android::JavaParamRef<jobject>(env, rect));
+}
+
+static base::android::ScopedJavaLocalRef<jobject> JNI_SampleForTests_GetNonPODDatatype(JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& caller);
+
+JNI_GENERATOR_EXPORT jobject
+    Java_org_chromium_base_natives_GEN_1JNI_org_1chromium_1example_1jni_11generator_1SampleForTests_1getNonPODDatatype(
+    JNIEnv* env,
+    jclass jcaller,
+    jobject caller) {
+  return JNI_SampleForTests_GetNonPODDatatype(env, base::android::JavaParamRef<jobject>(env,
+      caller)).Release();
+}
+
+JNI_GENERATOR_EXPORT jint
+    Java_org_chromium_base_natives_GEN_1JNI_org_1chromium_1example_1jni_11generator_1SampleForTests_1method(
+    JNIEnv* env,
+    jclass jcaller,
+    jlong nativeCPPClass,
+    jobject caller) {
+  CPPClass* native = reinterpret_cast<CPPClass*>(nativeCPPClass);
+  CHECK_NATIVE_PTR(env, jcaller, native, "Method", 0);
+  return native->Method(env, base::android::JavaParamRef<jobject>(env, caller));
+}
+
+JNI_GENERATOR_EXPORT jdouble
+    Java_org_chromium_base_natives_GEN_1JNI_org_1chromium_1example_1jni_11generator_1SampleForTests_1methodOtherP0(
+    JNIEnv* env,
+    jclass jcaller,
+    jlong nativePtr,
+    jobject caller) {
+  CPPClass::InnerClass* native = reinterpret_cast<CPPClass::InnerClass*>(nativePtr);
+  CHECK_NATIVE_PTR(env, jcaller, native, "MethodOtherP0", 0);
+  return native->MethodOtherP0(env, base::android::JavaParamRef<jobject>(env, caller));
+}
+
+JNI_GENERATOR_EXPORT void
+    Java_org_chromium_base_natives_GEN_1JNI_org_1chromium_1example_1jni_11generator_1SampleForTests_1addStructB(
+    JNIEnv* env,
+    jclass jcaller,
+    jlong nativeCPPClass,
+    jobject caller,
+    jobject b) {
+  CPPClass* native = reinterpret_cast<CPPClass*>(nativeCPPClass);
+  CHECK_NATIVE_PTR(env, jcaller, native, "AddStructB");
+  return native->AddStructB(env, base::android::JavaParamRef<jobject>(env, caller),
+      base::android::JavaParamRef<jobject>(env, b));
+}
+
+JNI_GENERATOR_EXPORT void
+    Java_org_chromium_base_natives_GEN_1JNI_org_1chromium_1example_1jni_11generator_1SampleForTests_1iterateAndDoSomethingWithStructB(
+    JNIEnv* env,
+    jclass jcaller,
+    jlong nativeCPPClass,
+    jobject caller) {
+  CPPClass* native = reinterpret_cast<CPPClass*>(nativeCPPClass);
+  CHECK_NATIVE_PTR(env, jcaller, native, "IterateAndDoSomethingWithStructB");
+  return native->IterateAndDoSomethingWithStructB(env, base::android::JavaParamRef<jobject>(env,
+      caller));
+}
+
+JNI_GENERATOR_EXPORT jstring
+    Java_org_chromium_base_natives_GEN_1JNI_org_1chromium_1example_1jni_11generator_1SampleForTests_1returnAString(
+    JNIEnv* env,
+    jclass jcaller,
+    jlong nativeCPPClass,
+    jobject caller) {
+  CPPClass* native = reinterpret_cast<CPPClass*>(nativeCPPClass);
+  CHECK_NATIVE_PTR(env, jcaller, native, "ReturnAString", NULL);
+  return native->ReturnAString(env, base::android::JavaParamRef<jobject>(env, caller)).Release();
+}
+
+
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_javaMethod(nullptr);
+static jint Java_SampleForTests_javaMethod(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+    JniIntWrapper foo,
+    JniIntWrapper bar) {
+  jclass clazz = org_chromium_example_jni_1generator_SampleForTests_clazz(env);
+  CHECK_CLAZZ(env, obj.obj(),
+      org_chromium_example_jni_1generator_SampleForTests_clazz(env), 0);
+
+  jni_generator::JniJavaCallContextChecked call_context;
+  call_context.Init<
+      base::android::MethodID::TYPE_INSTANCE>(
+          env,
+          clazz,
+          "javaMethod",
+          "(II)I",
+          &g_org_chromium_example_jni_1generator_SampleForTests_javaMethod);
+
+  jint ret =
+      env->CallIntMethod(obj.obj(),
+          call_context.base.method_id, as_jint(foo), as_jint(bar));
+  return ret;
+}
+
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_staticJavaMethod(nullptr);
+static jboolean Java_SampleForTests_staticJavaMethod(JNIEnv* env) {
+  jclass clazz = org_chromium_example_jni_1generator_SampleForTests_clazz(env);
+  CHECK_CLAZZ(env, clazz,
+      org_chromium_example_jni_1generator_SampleForTests_clazz(env), false);
+
+  jni_generator::JniJavaCallContextChecked call_context;
+  call_context.Init<
+      base::android::MethodID::TYPE_STATIC>(
+          env,
+          clazz,
+          "staticJavaMethod",
+          "()Z",
+          &g_org_chromium_example_jni_1generator_SampleForTests_staticJavaMethod);
+
+  jboolean ret =
+      env->CallStaticBooleanMethod(clazz,
+          call_context.base.method_id);
+  return ret;
+}
+
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_packagePrivateJavaMethod(nullptr);
+static void Java_SampleForTests_packagePrivateJavaMethod(JNIEnv* env, const
+    base::android::JavaRef<jobject>& obj) {
+  jclass clazz = org_chromium_example_jni_1generator_SampleForTests_clazz(env);
+  CHECK_CLAZZ(env, obj.obj(),
+      org_chromium_example_jni_1generator_SampleForTests_clazz(env));
+
+  jni_generator::JniJavaCallContextChecked call_context;
+  call_context.Init<
+      base::android::MethodID::TYPE_INSTANCE>(
+          env,
+          clazz,
+          "packagePrivateJavaMethod",
+          "()V",
+          &g_org_chromium_example_jni_1generator_SampleForTests_packagePrivateJavaMethod);
+
+     env->CallVoidMethod(obj.obj(),
+          call_context.base.method_id);
+}
+
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_methodWithGenericParams(nullptr);
+static void Java_SampleForTests_methodWithGenericParams(JNIEnv* env, const
+    base::android::JavaRef<jobject>& obj, const base::android::JavaRef<jobject>& foo,
+    const base::android::JavaRef<jobject>& bar) {
+  jclass clazz = org_chromium_example_jni_1generator_SampleForTests_clazz(env);
+  CHECK_CLAZZ(env, obj.obj(),
+      org_chromium_example_jni_1generator_SampleForTests_clazz(env));
+
+  jni_generator::JniJavaCallContextChecked call_context;
+  call_context.Init<
+      base::android::MethodID::TYPE_INSTANCE>(
+          env,
+          clazz,
+          "methodWithGenericParams",
+          "(Ljava/util/Map;Ljava/util/LinkedList;)V",
+          &g_org_chromium_example_jni_1generator_SampleForTests_methodWithGenericParams);
+
+     env->CallVoidMethod(obj.obj(),
+          call_context.base.method_id, foo.obj(), bar.obj());
+}
+
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_Constructor(nullptr);
+static base::android::ScopedJavaLocalRef<jobject> Java_SampleForTests_Constructor(JNIEnv* env,
+    JniIntWrapper foo,
+    JniIntWrapper bar) {
+  jclass clazz = org_chromium_example_jni_1generator_SampleForTests_clazz(env);
+  CHECK_CLAZZ(env, clazz,
+      org_chromium_example_jni_1generator_SampleForTests_clazz(env), NULL);
+
+  jni_generator::JniJavaCallContextChecked call_context;
+  call_context.Init<
+      base::android::MethodID::TYPE_INSTANCE>(
+          env,
+          clazz,
+          "<init>",
+          "(II)V",
+          &g_org_chromium_example_jni_1generator_SampleForTests_Constructor);
+
+  jobject ret =
+      env->NewObject(clazz,
+          call_context.base.method_id, as_jint(foo), as_jint(bar));
+  return base::android::ScopedJavaLocalRef<jobject>(env, ret);
+}
+
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_methodThatThrowsException(nullptr);
+static void Java_SampleForTests_methodThatThrowsException(JNIEnv* env, const
+    base::android::JavaRef<jobject>& obj) {
+  jclass clazz = org_chromium_example_jni_1generator_SampleForTests_clazz(env);
+  CHECK_CLAZZ(env, obj.obj(),
+      org_chromium_example_jni_1generator_SampleForTests_clazz(env));
+
+  jni_generator::JniJavaCallContextUnchecked call_context;
+  call_context.Init<
+      base::android::MethodID::TYPE_INSTANCE>(
+          env,
+          clazz,
+          "methodThatThrowsException",
+          "()V",
+          &g_org_chromium_example_jni_1generator_SampleForTests_methodThatThrowsException);
+
+     env->CallVoidMethod(obj.obj(),
+          call_context.method_id);
+}
+
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_javaMethodWithAnnotatedParam(nullptr);
+static void Java_SampleForTests_javaMethodWithAnnotatedParam(JNIEnv* env, const
+    base::android::JavaRef<jobject>& obj, JniIntWrapper foo,
+    JniIntWrapper bar,
+    JniIntWrapper baz,
+    JniIntWrapper bat) {
+  jclass clazz = org_chromium_example_jni_1generator_SampleForTests_clazz(env);
+  CHECK_CLAZZ(env, obj.obj(),
+      org_chromium_example_jni_1generator_SampleForTests_clazz(env));
+
+  jni_generator::JniJavaCallContextChecked call_context;
+  call_context.Init<
+      base::android::MethodID::TYPE_INSTANCE>(
+          env,
+          clazz,
+          "javaMethodWithAnnotatedParam",
+          "(IIII)V",
+          &g_org_chromium_example_jni_1generator_SampleForTests_javaMethodWithAnnotatedParam);
+
+     env->CallVoidMethod(obj.obj(),
+          call_context.base.method_id, as_jint(foo), as_jint(bar), as_jint(baz), as_jint(bat));
+}
+
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_create(nullptr);
+static base::android::ScopedJavaLocalRef<jobject> Java_InnerStructA_create(JNIEnv* env, jlong l,
+    JniIntWrapper i,
+    const base::android::JavaRef<jstring>& s) {
+  jclass clazz = org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz(env);
+  CHECK_CLAZZ(env, clazz,
+      org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz(env), NULL);
+
+  jni_generator::JniJavaCallContextChecked call_context;
+  call_context.Init<
+      base::android::MethodID::TYPE_STATIC>(
+          env,
+          clazz,
+          "create",
+          "(JILjava/lang/String;)Lorg/chromium/example/jni_generator/SampleForTests$InnerStructA;",
+          &g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_create);
+
+  jobject ret =
+      env->CallStaticObjectMethod(clazz,
+          call_context.base.method_id, l, as_jint(i), s.obj());
+  return base::android::ScopedJavaLocalRef<jobject>(env, ret);
+}
+
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_addStructA(nullptr);
+static void Java_SampleForTests_addStructA(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+    const base::android::JavaRef<jobject>& a) {
+  jclass clazz = org_chromium_example_jni_1generator_SampleForTests_clazz(env);
+  CHECK_CLAZZ(env, obj.obj(),
+      org_chromium_example_jni_1generator_SampleForTests_clazz(env));
+
+  jni_generator::JniJavaCallContextChecked call_context;
+  call_context.Init<
+      base::android::MethodID::TYPE_INSTANCE>(
+          env,
+          clazz,
+          "addStructA",
+          "(Lorg/chromium/example/jni_generator/SampleForTests$InnerStructA;)V",
+          &g_org_chromium_example_jni_1generator_SampleForTests_addStructA);
+
+     env->CallVoidMethod(obj.obj(),
+          call_context.base.method_id, a.obj());
+}
+
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_iterateAndDoSomething(nullptr);
+static void Java_SampleForTests_iterateAndDoSomething(JNIEnv* env, const
+    base::android::JavaRef<jobject>& obj) {
+  jclass clazz = org_chromium_example_jni_1generator_SampleForTests_clazz(env);
+  CHECK_CLAZZ(env, obj.obj(),
+      org_chromium_example_jni_1generator_SampleForTests_clazz(env));
+
+  jni_generator::JniJavaCallContextChecked call_context;
+  call_context.Init<
+      base::android::MethodID::TYPE_INSTANCE>(
+          env,
+          clazz,
+          "iterateAndDoSomething",
+          "()V",
+          &g_org_chromium_example_jni_1generator_SampleForTests_iterateAndDoSomething);
+
+     env->CallVoidMethod(obj.obj(),
+          call_context.base.method_id);
+}
+
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_getKey(nullptr);
+static jlong Java_InnerStructB_getKey(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
+  jclass clazz = org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz(env);
+  CHECK_CLAZZ(env, obj.obj(),
+      org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz(env), 0);
+
+  jni_generator::JniJavaCallContextChecked call_context;
+  call_context.Init<
+      base::android::MethodID::TYPE_INSTANCE>(
+          env,
+          clazz,
+          "getKey",
+          "()J",
+          &g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_getKey);
+
+  jlong ret =
+      env->CallLongMethod(obj.obj(),
+          call_context.base.method_id);
+  return ret;
+}
+
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_getValue(nullptr);
+static base::android::ScopedJavaLocalRef<jstring> Java_InnerStructB_getValue(JNIEnv* env, const
+    base::android::JavaRef<jobject>& obj) {
+  jclass clazz = org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz(env);
+  CHECK_CLAZZ(env, obj.obj(),
+      org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz(env), NULL);
+
+  jni_generator::JniJavaCallContextChecked call_context;
+  call_context.Init<
+      base::android::MethodID::TYPE_INSTANCE>(
+          env,
+          clazz,
+          "getValue",
+          "()Ljava/lang/String;",
+          &g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_getValue);
+
+  jstring ret =
+      static_cast<jstring>(env->CallObjectMethod(obj.obj(),
+          call_context.base.method_id));
+  return base::android::ScopedJavaLocalRef<jstring>(env, ret);
+}
+
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_getInnerInterface(nullptr);
+static base::android::ScopedJavaLocalRef<jobject> Java_SampleForTests_getInnerInterface(JNIEnv* env)
+    {
+  jclass clazz = org_chromium_example_jni_1generator_SampleForTests_clazz(env);
+  CHECK_CLAZZ(env, clazz,
+      org_chromium_example_jni_1generator_SampleForTests_clazz(env), NULL);
+
+  jni_generator::JniJavaCallContextChecked call_context;
+  call_context.Init<
+      base::android::MethodID::TYPE_STATIC>(
+          env,
+          clazz,
+          "getInnerInterface",
+          "()Lorg/chromium/example/jni_generator/SampleForTests$InnerInterface;",
+          &g_org_chromium_example_jni_1generator_SampleForTests_getInnerInterface);
+
+  jobject ret =
+      env->CallStaticObjectMethod(clazz,
+          call_context.base.method_id);
+  return base::android::ScopedJavaLocalRef<jobject>(env, ret);
+}
+
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_getInnerEnum(nullptr);
+static base::android::ScopedJavaLocalRef<jobject> Java_SampleForTests_getInnerEnum(JNIEnv* env) {
+  jclass clazz = org_chromium_example_jni_1generator_SampleForTests_clazz(env);
+  CHECK_CLAZZ(env, clazz,
+      org_chromium_example_jni_1generator_SampleForTests_clazz(env), NULL);
+
+  jni_generator::JniJavaCallContextChecked call_context;
+  call_context.Init<
+      base::android::MethodID::TYPE_STATIC>(
+          env,
+          clazz,
+          "getInnerEnum",
+          "()Lorg/chromium/example/jni_generator/SampleForTests$InnerEnum;",
+          &g_org_chromium_example_jni_1generator_SampleForTests_getInnerEnum);
+
+  jobject ret =
+      env->CallStaticObjectMethod(clazz,
+          call_context.base.method_id);
+  return base::android::ScopedJavaLocalRef<jobject>(env, ret);
+}
+
+}  // namespace android
+}  // namespace base
+
+#endif  // org_chromium_example_jni_generator_SampleForTests_JNI
diff --git a/base/android/jni_generator/jni_generator.py b/base/android/jni_generator/jni_generator.py
index 6de4ea7..743e694 100755
--- a/base/android/jni_generator/jni_generator.py
+++ b/base/android/jni_generator/jni_generator.py
@@ -363,6 +363,7 @@
         'Ljava/lang/Object',
         'Ljava/lang/String',
         'Ljava/lang/Class',
+        'Ljava/lang/ClassLoader',
         'Ljava/lang/CharSequence',
         'Ljava/lang/Runnable',
         'Ljava/lang/Throwable',
@@ -969,10 +970,12 @@
 class HeaderFileGeneratorHelper(object):
   """Include helper methods for header generators."""
 
-  def __init__(self, class_name, fully_qualified_class, use_proxy_hash):
+  def __init__(self, class_name, fully_qualified_class, use_proxy_hash,
+               split_name):
     self.class_name = class_name
     self.fully_qualified_class = fully_qualified_class
     self.use_proxy_hash = use_proxy_hash
+    self.split_name = split_name
 
   def GetStubName(self, native):
     """Return the name of the stub function for this native method.
@@ -1043,7 +1046,7 @@
 #define ${JAVA_CLASS}_clazz_defined
 inline jclass ${JAVA_CLASS}_clazz(JNIEnv* env) {
   return base::android::LazyGetClass(env, kClassPath_${JAVA_CLASS}, \
-&g_${JAVA_CLASS}_clazz);
+${MAYBE_SPLIT_NAME_ARG}&g_${JAVA_CLASS}_clazz);
 }
 #endif
 """
@@ -1059,7 +1062,10 @@
 
     for full_clazz in classes.values():
       values = {
-          'JAVA_CLASS': EscapeClassName(full_clazz),
+          'JAVA_CLASS':
+          EscapeClassName(full_clazz),
+          'MAYBE_SPLIT_NAME_ARG':
+          (('"%s", ' % self.split_name) if self.split_name else '')
       }
       # Since all proxy methods use the same class, defining this in every
       # header file would result in duplicated extern initializations.
@@ -1083,8 +1089,10 @@
     self.constant_fields = constant_fields
     self.jni_params = jni_params
     self.options = options
-    self.helper = HeaderFileGeneratorHelper(
-        self.class_name, fully_qualified_class, self.options.use_proxy_hash)
+    self.helper = HeaderFileGeneratorHelper(self.class_name,
+                                            fully_qualified_class,
+                                            self.options.use_proxy_hash,
+                                            self.options.split_name)
 
   def GetContent(self):
     """Returns the content of the JNI binding file."""
@@ -1578,6 +1586,9 @@
       action='store_true',
       help='Hashes the native declaration of methods used '
       'in @JniNatives interface.')
+  parser.add_argument(
+      '--split_name',
+      help='Split name that the Java classes should be loaded from.')
   args = parser.parse_args()
   input_files = args.input_files
   output_files = args.output_files
diff --git a/base/android/jni_generator/jni_generator_tests.py b/base/android/jni_generator/jni_generator_tests.py
index fbaf9fb..934dea9 100755
--- a/base/android/jni_generator/jni_generator_tests.py
+++ b/base/android/jni_generator/jni_generator_tests.py
@@ -52,6 +52,7 @@
     self.enable_tracing = False
     self.use_proxy_hash = False
     self.always_mangle = False
+    self.split_name = None
 
 
 class BaseTest(unittest.TestCase):
@@ -1264,6 +1265,15 @@
                                                     TestOptions())
     self.AssertGoldenTextEquals(jni_from_java.GetContent())
 
+  def testSplitNameExample(self):
+    opts = TestOptions()
+    opts.split_name = "sample"
+    generated_text = self._CreateJniHeaderFromFile(
+        os.path.join(_JAVA_SRC_DIR, 'SampleForTests.java'),
+        'org/chromium/example/jni_generator/SampleForTests', opts)
+    self.AssertGoldenTextEquals(
+        generated_text, golden_file='SampleForTestsWithSplit_jni.golden')
+
 
 class ProxyTestGenerator(BaseTest):
 
diff --git a/base/android/jni_generator/jni_registration_generator.py b/base/android/jni_generator/jni_registration_generator.py
index 7665671..66fa71cf 100755
--- a/base/android/jni_generator/jni_registration_generator.py
+++ b/base/android/jni_generator/jni_registration_generator.py
@@ -326,7 +326,7 @@
     self.class_name = self.fully_qualified_class.split('/')[-1]
     self.main_dex = main_dex
     self.helper = jni_generator.HeaderFileGeneratorHelper(
-        self.class_name, fully_qualified_class, use_proxy_hash)
+        self.class_name, fully_qualified_class, use_proxy_hash, None)
     self.use_proxy_hash = use_proxy_hash
     self.registration_dict = None
 
diff --git a/base/android/jni_utils.cc b/base/android/jni_utils.cc
index 7ca8a64c..ebcd1e1 100644
--- a/base/android/jni_utils.cc
+++ b/base/android/jni_utils.cc
@@ -4,6 +4,7 @@
 
 #include "base/android/jni_utils.h"
 
+#include "base/android/jni_string.h"
 #include "base/android/scoped_java_ref.h"
 
 #include "base/base_jni_headers/JNIUtils_jni.h"
@@ -12,7 +13,14 @@
 namespace android {
 
 ScopedJavaLocalRef<jobject> GetClassLoader(JNIEnv* env) {
-  return Java_JNIUtils_getClassLoader(env);
+  return Java_JNIUtils_getSplitClassLoader(env,
+                                           ConvertUTF8ToJavaString(env, ""));
+}
+
+ScopedJavaLocalRef<jobject> GetSplitClassLoader(JNIEnv* env,
+                                                const std::string& split_name) {
+  return Java_JNIUtils_getSplitClassLoader(
+      env, ConvertUTF8ToJavaString(env, split_name));
 }
 
 bool IsSelectiveJniRegistrationEnabled(JNIEnv* env) {
diff --git a/base/android/jni_utils.h b/base/android/jni_utils.h
index c626ba4..d5e08e16 100644
--- a/base/android/jni_utils.h
+++ b/base/android/jni_utils.h
@@ -18,6 +18,12 @@
 // via JNI from Java.
 BASE_EXPORT ScopedJavaLocalRef<jobject> GetClassLoader(JNIEnv* env);
 
+// Gets a ClassLoader instance which can load Java classes from the specified
+// split.
+BASE_EXPORT ScopedJavaLocalRef<jobject> GetSplitClassLoader(
+    JNIEnv* env,
+    const std::string& split_name);
+
 // Returns true if the current process permits selective JNI registration.
 BASE_EXPORT bool IsSelectiveJniRegistrationEnabled(JNIEnv* env);
 
diff --git a/base/files/important_file_writer.cc b/base/files/important_file_writer.cc
index 36bc092..351a079 100644
--- a/base/files/important_file_writer.cc
+++ b/base/files/important_file_writer.cc
@@ -371,8 +371,15 @@
 
 void ImportantFileWriter::DoScheduledWrite() {
   DCHECK(serializer_);
-  std::unique_ptr<std::string> data(new std::string);
+  auto data = std::make_unique<std::string>();
+
+  // Pre-allocate previously needed memory plus 1kB for potential growth of
+  // data. Reduces the number of memory allocations to grow |data| step by step
+  // from tiny to very large.
+  data->reserve(previous_data_size_ + 1024);
+
   if (serializer_->SerializeData(data.get())) {
+    previous_data_size_ = data->size();
     WriteNow(std::move(data));
   } else {
     DLOG(WARNING) << "failed to serialize data to be saved in "
diff --git a/base/files/important_file_writer.h b/base/files/important_file_writer.h
index 1accf1d8..6358d5b 100644
--- a/base/files/important_file_writer.h
+++ b/base/files/important_file_writer.h
@@ -117,6 +117,10 @@
   // Overrides the timer to use for scheduling writes with |timer_override|.
   void SetTimerForTesting(OneShotTimer* timer_override);
 
+#if defined(UNIT_TEST)
+  size_t previous_data_size() const { return previous_data_size_; }
+#endif
+
  private:
   const OneShotTimer& timer() const {
     return timer_override_ ? *timer_override_ : timer_;
@@ -169,6 +173,11 @@
   // Custom histogram suffix.
   const std::string histogram_suffix_;
 
+  // Memorizes the amount of data written on the previous write. This helps
+  // preallocating memory for the data serialization. It is only used for
+  // scheduled writes.
+  size_t previous_data_size_ = 0;
+
   SEQUENCE_CHECKER(sequence_checker_);
 
   WeakPtrFactory<ImportantFileWriter> weak_factory_{this};
diff --git a/base/files/important_file_writer_unittest.cc b/base/files/important_file_writer_unittest.cc
index baa1d5d..00dcadb 100644
--- a/base/files/important_file_writer_unittest.cc
+++ b/base/files/important_file_writer_unittest.cc
@@ -234,6 +234,7 @@
   MockOneShotTimer timer;
   ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get(),
                              kCommitInterval);
+  EXPECT_EQ(0u, writer.previous_data_size());
   writer.SetTimerForTesting(&timer);
   EXPECT_FALSE(writer.HasPendingWrite());
   DataSerializer serializer("foo");
@@ -247,6 +248,7 @@
   RunLoop().RunUntilIdle();
   ASSERT_TRUE(PathExists(writer.path()));
   EXPECT_EQ("foo", GetFileContent(writer.path()));
+  EXPECT_EQ(3u, writer.previous_data_size());
 }
 
 TEST_F(ImportantFileWriterTest, DoScheduledWrite) {
diff --git a/base/task/sequence_manager/sequence_manager_impl_unittest.cc b/base/task/sequence_manager/sequence_manager_impl_unittest.cc
index 12986dc..ff2394b 100644
--- a/base/task/sequence_manager/sequence_manager_impl_unittest.cc
+++ b/base/task/sequence_manager/sequence_manager_impl_unittest.cc
@@ -236,10 +236,11 @@
                         .SetRandomisedSamplingEnabled(false)
                         .SetTickClock(mock_tick_clock())
                         .Build();
-    sequence_manager_ = SequenceManagerForTest::Create(
+    auto thread_controller =
         std::make_unique<ThreadControllerWithMessagePumpImpl>(std::move(pump),
-                                                              settings),
-        std::move(settings));
+                                                              settings);
+    sequence_manager_ = SequenceManagerForTest::Create(
+        std::move(thread_controller), std::move(settings));
     sequence_manager_->SetDefaultTaskRunner(MakeRefCounted<NullTaskRunner>());
 
     // The SequenceManager constructor calls Now() once for setting up
diff --git a/base/test/android/javatests/src/org/chromium/base/test/BaseActivityTestRule.java b/base/test/android/javatests/src/org/chromium/base/test/BaseActivityTestRule.java
new file mode 100644
index 0000000..af570fb
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/BaseActivityTestRule.java
@@ -0,0 +1,94 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.support.test.runner.lifecycle.Stage;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Assert;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.Log;
+import org.chromium.base.test.util.ApplicationTestUtils;
+
+/**
+ * A replacement for ActivityTestRule, designed for use in Chromium. This implementation supports
+ * launching the target activity through a launcher or redirect from another Activity.
+ *
+ * @param <T> The type of Activity this Rule will use.
+ */
+public class BaseActivityTestRule<T extends Activity> implements TestRule {
+    private static final String TAG = "BaseActivityTestRule";
+
+    private final Class<T> mActivityClass;
+    private boolean mFinishActivity = true;
+    private T mActivity;
+
+    /**
+     * @param activityClass The Class of the Activity the TestRule will use.
+     */
+    public BaseActivityTestRule(Class<T> activityClass) {
+        mActivityClass = activityClass;
+    }
+
+    @Override
+    public Statement apply(final Statement base, final Description desc) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                base.evaluate();
+                if (mFinishActivity && mActivity != null) {
+                    ApplicationTestUtils.finishActivity(mActivity);
+                }
+            }
+        };
+    }
+
+    /**
+     * @param finishActivity Whether to finish the Activity between tests. This is only meaningful
+     *     in the context of {@link Batch} tests. Non-batched tests will always finish Activities
+     *     between tests.
+     */
+    public void setFinishActivity(boolean finishActivity) {
+        mFinishActivity = finishActivity;
+    }
+
+    /**
+     * @return The activity under test.
+     */
+    public T getActivity() {
+        return mActivity;
+    }
+
+    /**
+     * Set the Activity to be used by this TestRule.
+     */
+    public void setActivity(T activity) {
+        mActivity = activity;
+    }
+
+    /**
+     * Launches the Activity under test using the provided intent.
+     */
+    public void launchActivity(@NonNull Intent startIntent) {
+        String packageName = ContextUtils.getApplicationContext().getPackageName();
+        Assert.assertTrue(TextUtils.equals(startIntent.getPackage(), packageName)
+                || TextUtils.equals(startIntent.getComponent().getPackageName(), packageName));
+
+        startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        Log.d(TAG, String.format("Launching activity %s", mActivityClass.getName()));
+
+        mActivity = ApplicationTestUtils.waitForActivityWithClass(mActivityClass, Stage.CREATED,
+                () -> ContextUtils.getApplicationContext().startActivity(startIntent));
+    }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/ApplicationTestUtils.java b/base/test/android/javatests/src/org/chromium/base/test/util/ApplicationTestUtils.java
index 799c803..c044cb4 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/util/ApplicationTestUtils.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/ApplicationTestUtils.java
@@ -5,6 +5,9 @@
 package org.chromium.base.test.util;
 
 import android.app.Activity;
+import android.content.Intent;
+import android.os.Build;
+import android.provider.Settings;
 import android.support.test.runner.lifecycle.ActivityLifecycleCallback;
 import android.support.test.runner.lifecycle.ActivityLifecycleMonitor;
 import android.support.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
@@ -26,9 +29,16 @@
 
     /** Waits until the given activity transitions to the given state. */
     public static void waitForActivityState(Activity activity, Stage stage) {
-        CriteriaHelper.pollUiThread(() -> {
-            return sMonitor.getLifecycleStageOf(activity) == stage;
-        }, ScalableTimeout.scaleTimeout(10000), CriteriaHelper.DEFAULT_POLLING_INTERVAL);
+        waitForActivityState(null, activity, stage);
+    }
+
+    /** Waits until the given activity transitions to the given state. */
+    public static void waitForActivityState(String failureReason, Activity activity, Stage stage) {
+        CriteriaHelper.pollUiThread(
+                ()
+                        -> { return sMonitor.getLifecycleStageOf(activity) == stage; },
+                failureReason, ScalableTimeout.scaleTimeout(10000),
+                CriteriaHelper.DEFAULT_POLLING_INTERVAL);
     }
 
     /** Finishes the given activity and waits for its onDestroy() to be called. */
@@ -38,7 +48,24 @@
                 activity.finish();
             }
         });
-        waitForActivityState(activity, Stage.DESTROYED);
+        try {
+            waitForActivityState(
+                    "Failed to finish the Activity. Did you start a second Activity and not finish"
+                            + " it?",
+                    activity, Stage.DESTROYED);
+        } catch (Throwable e) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) throw e;
+
+            // On L, there's a framework bug where Activities sometimes just don't get finished
+            // unless you start another Activity.
+            Intent intent = new Intent(Settings.ACTION_SETTINGS);
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            activity.startActivity(intent);
+            waitForActivityState(
+                    "Failed to finish the Activity. Did you start a second Activity and not finish"
+                            + " it?",
+                    activity, Stage.DESTROYED);
+        }
     }
 
     /**
@@ -48,11 +75,25 @@
      * @return The newly created Activity.
      */
     public static <T extends Activity> T recreateActivity(T activity) {
-        final Class<?> activityClass = activity.getClass();
+        return waitForActivityWithClass(
+                activity.getClass(), Stage.RESUMED, () -> activity.recreate());
+    }
+
+    /**
+     * Waits for an activity of the specified class to reach the specified Activity {@link Stage},
+     * triggered by running the provided trigger.
+     *
+     * @param activityClass The class type to wait for.
+     * @param state The Activity {@link Stage} to wait for an activity of the right class type to
+     * reach.
+     * @param trigger The Runnable that will trigger the state change to wait for.
+     */
+    public static <T extends Activity> T waitForActivityWithClass(
+            Class<? extends Activity> activityClass, Stage stage, Runnable trigger) {
         final CallbackHelper activityCallback = new CallbackHelper();
         final AtomicReference<T> activityRef = new AtomicReference<>();
-        ActivityLifecycleCallback stateListener = (Activity newActivity, Stage stage) -> {
-            if (stage == Stage.RESUMED) {
+        ActivityLifecycleCallback stateListener = (Activity newActivity, Stage newStage) -> {
+            if (newStage == stage) {
                 if (!activityClass.isAssignableFrom(newActivity.getClass())) return;
 
                 activityRef.set((T) newActivity);
@@ -62,8 +103,8 @@
         sMonitor.addLifecycleCallback(stateListener);
 
         try {
-            ThreadUtils.runOnUiThreadBlocking(() -> activity.recreate());
-            activityCallback.waitForCallback("Activity did not start as expected", 0);
+            ThreadUtils.runOnUiThreadBlocking(() -> trigger.run());
+            activityCallback.waitForCallback("No Activity reached target state.", 0);
             T createdActivity = activityRef.get();
             Assert.assertNotNull("Activity reference is null.", createdActivity);
             return createdActivity;
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index c6cd584c..a453337 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -192,6 +192,9 @@
           args += [ "-n ${invoker.namespace}" ]
         }
       }
+      if (defined(invoker.split_name)) {
+        args += [ "--split_name=${invoker.split_name}" ]
+      }
 
       outputs = []
       foreach(_name, _input_names) {
diff --git a/build/config/mac/base_rules.gni b/build/config/mac/base_rules.gni
index 4678e95..d767ce82 100644
--- a/build/config/mac/base_rules.gni
+++ b/build/config/mac/base_rules.gni
@@ -24,7 +24,7 @@
 #     string, path to the converted plist, must be under $root_build_dir
 #
 #   format:
-#     string, the format to `plutil -convert` the plist to.
+#     string, the format to convert the plist to. Either "binary1" or "xml1".
 template("convert_plist") {
   assert(defined(invoker.source), "source must be defined for $target_name")
   assert(defined(invoker.output), "output must be defined for $target_name")
@@ -38,17 +38,12 @@
                              "deps",
                            ])
 
-    script = "//build/gn_run_binary.py"
+    script = "//build/config/mac/plist_util.py"
     sources = [ invoker.source ]
     outputs = [ invoker.output ]
-
-    # /usr/bin/plutil is present on all images of macOS. If at some point in the
-    # future we need to ship our own copy [e.g. for cross-compile], we can add a
-    # layer of indirection.
     args = [
-      "/usr/bin/plutil",
-      "-convert",
-      invoker.format,
+      "merge",
+      "--format=${invoker.format}",
       "-o",
       rebase_path(invoker.output, root_build_dir),
       rebase_path(invoker.source, root_build_dir),
diff --git a/build/config/mac/plist_util.py b/build/config/mac/plist_util.py
index 7c03780..e975083 100644
--- a/build/config/mac/plist_util.py
+++ b/build/config/mac/plist_util.py
@@ -109,15 +109,15 @@
 
 def SavePList(path, format, data):
   """Saves |data| as a Plist to |path| in the specified |format|."""
+  # The below does not replace the destination file but update it in place,
+  # so if more than one hardlink points to destination all of them will be
+  # modified. This is not what is expected, so delete destination file if
+  # it does exist.
+  if os.path.exists(path):
+    os.unlink(path)
   if sys.version_info.major == 2:
     fd, name = tempfile.mkstemp()
     try:
-      # "plutil" does not replace the destination file but update it in place,
-      # so if more than one hardlink points to destination all of them will be
-      # modified. This is not what is expected, so delete destination file if
-      # it does exist.
-      if os.path.exists(path):
-        os.unlink(path)
       with os.fdopen(fd, 'wb') as f:
         plistlib.writePlist(data, f)
       subprocess.check_call(['plutil', '-convert', format, '-o', path, name])
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index b1e0b1d..3208790 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-0.20201125.2.1
+0.20201126.0.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index b1e0b1d..3208790 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-0.20201125.2.1
+0.20201126.0.1
diff --git a/build/sanitizers/tsan_suppressions.cc b/build/sanitizers/tsan_suppressions.cc
index 26b42ef..982e79d 100644
--- a/build/sanitizers/tsan_suppressions.cc
+++ b/build/sanitizers/tsan_suppressions.cc
@@ -45,9 +45,6 @@
     // http://crbug.com/244856
     "race:libpulsecommon*.so\n"
 
-    // http://crbug.com/246968
-    "race:webrtc::VideoCodingModuleImpl::RegisterPacketRequestCallback\n"
-
     // http://crbug.com/258479
     "race:g_trace_state\n"
 
@@ -73,10 +70,6 @@
     // http://crbug.com/328868
     "race:PR_Lock\n"
 
-    // http://crbug.com/348982
-    "race:cricket::P2PTransportChannel::OnConnectionDestroyed\n"
-    "race:cricket::P2PTransportChannel::AddConnection\n"
-
     // http://crbug.com/348984
     "race:sctp_express_handle_sack\n"
     "race:system_base_info\n"
diff --git a/cc/layers/layer.cc b/cc/layers/layer.cc
index aca2eaf..3f187ae 100644
--- a/cc/layers/layer.cc
+++ b/cc/layers/layer.cc
@@ -59,7 +59,6 @@
   int int_fields[6];
   gfx::Vector2dF offset;
   unsigned bitfields;
-  SkColor safe_opaque_background_color;
   void* debug_info;
 };
 
@@ -87,19 +86,13 @@
 Layer::Inputs::~Inputs() = default;
 
 Layer::LayerTreeInputs::LayerTreeInputs()
-    : mask_layer(nullptr),
-      opacity(1.f),
-      blend_mode(SkBlendMode::kSrcOver),
-      masks_to_bounds(false),
+    : masks_to_bounds(false),
       is_fast_rounded_corner(false),
       user_scrollable_horizontal(true),
       user_scrollable_vertical(true),
       trilinear_filtering(false),
       hide_layer_and_subtree(false),
-      scrollable(false),
-      backdrop_filter_quality(1.0f),
-      mirror_count(0),
-      corner_radii({0, 0, 0, 0}) {}
+      scrollable(false) {}
 
 Layer::LayerTreeInputs::~LayerTreeInputs() = default;
 
@@ -128,8 +121,7 @@
       needs_show_scrollbars_(false),
       has_transform_node_(false),
       has_clip_node_(false),
-      subtree_has_copy_request_(false),
-      safe_opaque_background_color_(0) {}
+      subtree_has_copy_request_(false) {}
 
 Layer::~Layer() {
   // Our parent should be holding a reference to us so there should be no
@@ -496,25 +488,41 @@
 
 void Layer::SetSafeOpaqueBackgroundColor(SkColor background_color) {
   DCHECK(IsPropertyChangeAllowed());
-  SkColor opaque_color = SkColorSetA(background_color, 255);
-  if (safe_opaque_background_color_ == opaque_color)
+  SkColor opaque_color = SkColorSetA(background_color, SK_AlphaOPAQUE);
+  auto& inputs = EnsureLayerTreeInputs();
+  if (inputs.safe_opaque_background_color == opaque_color)
     return;
-  safe_opaque_background_color_ = opaque_color;
+  inputs.safe_opaque_background_color = opaque_color;
   SetNeedsPushProperties();
 }
 
 SkColor Layer::SafeOpaqueBackgroundColor() const {
   if (contents_opaque()) {
-    // TODO(936906): We should uncomment this DCHECK, since the
-    // |safe_opaque_background_color_| could be transparent if it is never set
-    // (the default is 0). But to do that, one test needs to be fixed.
-    // DCHECK_EQ(SkColorGetA(safe_opaque_background_color_), SK_AlphaOPAQUE);
-    return safe_opaque_background_color_;
+    if (!layer_tree_host_ || !layer_tree_host_->IsUsingLayerLists()) {
+      // In layer tree mode, PropertyTreeBuilder should have calculated the safe
+      // opaque background color and called SetSafeOpaqueBackgroundColor().
+      DCHECK(layer_tree_inputs());
+      DCHECK_EQ(SkColorGetA(layer_tree_inputs()->safe_opaque_background_color),
+                SK_AlphaOPAQUE);
+      return layer_tree_inputs()->safe_opaque_background_color;
+    }
+    // In layer list mode, the PropertyTreeBuilder algorithm doesn't apply
+    // because it depends on the layer tree hierarchy. Instead we use
+    // background_color() if it's not transparent, or layer_tree_host_'s
+    // background_color(), with the alpha channel forced to be opaque.
+    SkColor color = background_color() == SK_ColorTRANSPARENT
+                        ? layer_tree_host_->background_color()
+                        : background_color();
+    return SkColorSetA(color, SK_AlphaOPAQUE);
   }
-  SkColor color = background_color();
-  if (SkColorGetA(color) == 255)
-    color = SK_ColorTRANSPARENT;
-  return color;
+  if (SkColorGetA(background_color()) == SK_AlphaOPAQUE) {
+    // The layer is not opaque while the background color is, meaning that the
+    // background color doesn't cover the whole layer. Use SK_ColorTRANSPARENT
+    // to avoid intrusive checkerboard where the layer is not covered by the
+    // background color.
+    return SK_ColorTRANSPARENT;
+  }
+  return background_color();
 }
 
 void Layer::SetMasksToBounds(bool masks_to_bounds) {
@@ -1305,7 +1313,7 @@
   layer->SetElementId(inputs_.element_id);
   layer->SetHasTransformNode(has_transform_node_);
   layer->SetBackgroundColor(inputs_.background_color);
-  layer->SetSafeOpaqueBackgroundColor(safe_opaque_background_color_);
+  layer->SetSafeOpaqueBackgroundColor(SafeOpaqueBackgroundColor());
   layer->SetBounds(inputs_.bounds);
   layer->SetTransformTreeIndex(transform_tree_index());
   layer->SetEffectTreeIndex(effect_tree_index());
diff --git a/cc/layers/layer.h b/cc/layers/layer.h
index 6991597..3f4189e 100644
--- a/cc/layers/layer.h
+++ b/cc/layers/layer.h
@@ -151,22 +151,22 @@
   virtual void SetBackgroundColor(SkColor background_color);
   SkColor background_color() const { return inputs_.background_color; }
 
-  // Internal to property tree generation. Sets an opaque background color for
-  // the layer, to be used in place of the background_color() if the layer says
-  // contents_opaque() is true.
+  // For layer tree mode only. In layer list mode, client doesn't need to set
+  // it. Sets an opaque background color for the layer, to be used in place of
+  // the background_color() if the layer says contents_opaque() is true.
   void SetSafeOpaqueBackgroundColor(SkColor background_color);
-  // Returns a background color with opaque-ness equal to the value of
+
+  // Returns a background color with opaqueness equal to the value of
   // contents_opaque().
-  // If the layer says contents_opaque() is true, this returns the value set by
-  // SetSafeOpaqueBackgroundColor() which should be an opaque color. Otherwise,
-  // it returns something non-opaque. It prefers to return the
+  // If the layer says contents_opaque() is true, in layer tree mode, this
+  // returns the value set by SetSafeOpaqueBackgroundColor() which should be an
+  // opaque color, and in layer list mode, returns an opaque color calculated
+  // from background_color() and layer_tree_host()->background_clor().
+  // Otherwise, it returns something non-opaque. It prefers to return the
   // background_color(), but if the background_color() is opaque (and this layer
-  // claims to not be), then SK_ColorTRANSPARENT is returned.
+  // claims to not be), then SK_ColorTRANSPARENT is returned to avoid intrusive
+  // checkerboard where the layer is not covered by the background_color().
   SkColor SafeOpaqueBackgroundColor() const;
-  // For testing, return the actual stored value.
-  SkColor ActualSafeOpaqueBackgroundColorForTesting() const {
-    return safe_opaque_background_color_;
-  }
 
   // For layer tree mode only.
   // Set and get the position of this layer, relative to its parent. This is
@@ -862,10 +862,10 @@
 
     // If not null, points to one of child layers which is set as mask layer
     // by SetMaskLayer().
-    PictureLayer* mask_layer;
+    PictureLayer* mask_layer = nullptr;
 
-    float opacity;
-    SkBlendMode blend_mode;
+    float opacity = 1.0f;
+    SkBlendMode blend_mode = SkBlendMode::kSrcOver;
 
     bool masks_to_bounds : 1;
 
@@ -889,12 +889,14 @@
     gfx::Transform transform;
     gfx::Point3F transform_origin;
 
+    SkColor safe_opaque_background_color = SK_ColorTRANSPARENT;
+
     FilterOperations filters;
     FilterOperations backdrop_filters;
     base::Optional<gfx::RRectF> backdrop_filter_bounds;
-    float backdrop_filter_quality;
+    float backdrop_filter_quality = 1.0f;
 
-    int mirror_count;
+    int mirror_count = 0;
 
     gfx::ScrollOffset scroll_offset;
     // Size of the scroll container that this layer scrolls in.
@@ -945,8 +947,6 @@
   // This value is valid only when LayerTreeHost::has_copy_request() is true
   bool subtree_has_copy_request_ : 1;
 
-  SkColor safe_opaque_background_color_;
-
   std::unique_ptr<LayerDebugInfo> debug_info_;
 
   static constexpr gfx::Transform kIdentityTransform{};
diff --git a/cc/layers/layer_impl.cc b/cc/layers/layer_impl.cc
index 460019a..920a3f5 100644
--- a/cc/layers/layer_impl.cc
+++ b/cc/layers/layer_impl.cc
@@ -555,20 +555,6 @@
   safe_opaque_background_color_ = background_color;
 }
 
-SkColor LayerImpl::SafeOpaqueBackgroundColor() const {
-  if (contents_opaque()) {
-    // TODO(936906): We should uncomment this DCHECK, since the
-    // |safe_opaque_background_color_| could be transparent if it is never set
-    // (the default is 0). But to do that, one test needs to be fixed.
-    // DCHECK_EQ(SkColorGetA(safe_opaque_background_color_), SK_AlphaOPAQUE);
-    return safe_opaque_background_color_;
-  }
-  SkColor color = background_color();
-  if (SkColorGetA(color) == 255)
-    color = SK_ColorTRANSPARENT;
-  return color;
-}
-
 void LayerImpl::SetContentsOpaque(bool opaque) {
   contents_opaque_ = opaque;
   contents_opaque_for_text_ = opaque;
diff --git a/cc/layers/layer_impl.h b/cc/layers/layer_impl.h
index 6af8b62..66c0b80 100644
--- a/cc/layers/layer_impl.h
+++ b/cc/layers/layer_impl.h
@@ -163,9 +163,12 @@
   void SetBackgroundColor(SkColor background_color);
   SkColor background_color() const { return background_color_; }
   void SetSafeOpaqueBackgroundColor(SkColor background_color);
-  // If contents_opaque(), return an opaque color else return a
-  // non-opaque color.  Tries to return background_color(), if possible.
-  SkColor SafeOpaqueBackgroundColor() const;
+  SkColor safe_opaque_background_color() const {
+    // Layer::SafeOpaqueBackgroundColor() should ensure this.
+    DCHECK_EQ(contents_opaque(),
+              SkColorGetA(safe_opaque_background_color_) == SK_AlphaOPAQUE);
+    return safe_opaque_background_color_;
+  }
 
   // See Layer::SetContentsOpaque() and SetContentsOpaqueForText() for the
   // relationship between the two flags.
diff --git a/cc/layers/picture_layer_impl.cc b/cc/layers/picture_layer_impl.cc
index c26d781..2f6a867 100644
--- a/cc/layers/picture_layer_impl.cc
+++ b/cc/layers/picture_layer_impl.cc
@@ -511,7 +511,7 @@
 
     if (!has_draw_quad) {
       // Checkerboard.
-      SkColor color = SafeOpaqueBackgroundColor();
+      SkColor color = safe_opaque_background_color();
       if (ShowDebugBorders(DebugBorderType::LAYER)) {
         // Fill the whole tile with the missing tile color.
         color = DebugColors::DefaultCheckerboardColor();
diff --git a/cc/test/test_layer_tree_host_base.cc b/cc/test/test_layer_tree_host_base.cc
index 96b1b7a..26b5ef5 100644
--- a/cc/test/test_layer_tree_host_base.cc
+++ b/cc/test/test_layer_tree_host_base.cc
@@ -123,6 +123,7 @@
     pending_layer_->SetDrawsContent(true);
     // LCD-text tests require the layer to be initially opaque.
     pending_layer_->SetContentsOpaque(true);
+    pending_layer_->SetSafeOpaqueBackgroundColor(SK_ColorWHITE);
 
     pending_tree->SetElementIdsForTesting();
     SetupRootProperties(pending_root);
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinatorTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinatorTest.java
index 80b4009..2717c72 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinatorTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinatorTest.java
@@ -23,10 +23,9 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.verify;
 
-import static org.chromium.chrome.browser.autofill_assistant.AutofillAssistantUiTestUtil.waitUntil;
 import static org.chromium.chrome.browser.autofill_assistant.AutofillAssistantUiTestUtil.waitUntilViewMatchesCondition;
 
-import android.app.Activity;
+import android.support.test.runner.lifecycle.Stage;
 import android.text.Spanned;
 import android.text.style.ClickableSpan;
 import android.view.View;
@@ -43,10 +42,10 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
-import org.chromium.base.ActivityState;
-import org.chromium.base.ApplicationStatus;
 import org.chromium.base.Callback;
+import org.chromium.base.test.util.ApplicationTestUtils;
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.chrome.autofill_assistant.R;
 import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayCoordinator;
@@ -57,7 +56,6 @@
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.util.ChromeTabUtils;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.widget.scrim.ScrimCoordinator;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
@@ -295,8 +293,11 @@
                                     spannedMessage.getSpanEnd(spans[0]))
                             .toString());
         });
-        spans[0].onClick(termsMessage);
-        waitUntil(() -> getOpenedUrlSpec().equals(expectedTermsUrl));
+        CustomTabActivity activity = ApplicationTestUtils.waitForActivityWithClass(
+                CustomTabActivity.class, Stage.RESUMED, () -> spans[0].onClick(termsMessage));
+        CriteriaHelper.pollUiThread(
+                () -> activity.getActivityTab().getUrlString().equals(expectedTermsUrl));
+        activity.finish();
     }
 
     @Test
@@ -356,12 +357,13 @@
                             .toString()
                             .replaceAll("\\s+", " "));
         });
-        spans[0].onClick(termsMessage);
-        waitUntil(()
-                          -> getOpenedUrlSpec().equals(
-                                  mActivity.getResources()
-                                          .getText(R.string.autofill_assistant_google_terms_url)
-                                          .toString()));
+        CustomTabActivity activity = ApplicationTestUtils.waitForActivityWithClass(
+                CustomTabActivity.class, Stage.RESUMED, () -> spans[0].onClick(termsMessage));
+        String url = mActivity.getResources()
+                             .getText(R.string.autofill_assistant_google_terms_url)
+                             .toString();
+        CriteriaHelper.pollUiThread(() -> activity.getActivityTab().getUrlString().equals(url));
+        activity.finish();
     }
 
     /** Trigger onboarding and wait until it is fully displayed. */
@@ -370,21 +372,4 @@
         TestThreadUtils.runOnUiThreadBlocking(() -> coordinator.show(callback));
         waitUntilViewMatchesCondition(withId(R.id.button_init_ok), isCompletelyDisplayed());
     }
-
-    // Get the newly opened Activity (through CustomTabActivity.showInfoPage) that happens on
-    // terms click. Return the URL of the current tab on that activity.
-    private String getOpenedUrlSpec() {
-        for (Activity runningActivity : ApplicationStatus.getRunningActivities()) {
-            if (runningActivity instanceof CustomTabActivity
-                    && ApplicationStatus.getStateForActivity(runningActivity)
-                            == ActivityState.RESUMED) {
-                return ChromeTabUtils
-                        .getUrlOnUiThread(((CustomTabActivity) runningActivity)
-                                                  .getTabModelSelector()
-                                                  .getCurrentTab())
-                        .getSpec();
-            }
-        }
-        return "";
-    }
 }
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
index 51b95d2..a3ae2c4 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
@@ -65,6 +65,7 @@
 import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Restriction;
+import org.chromium.base.test.util.ScalableTimeout;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.compositor.layouts.Layout;
 import org.chromium.chrome.browser.compositor.layouts.LayoutManagerChromePhone;
@@ -172,7 +173,7 @@
         Intent intent = new Intent(Intent.ACTION_MAIN);
         intent.addCategory(Intent.CATEGORY_LAUNCHER);
         mActivityTestRule.prepareUrlIntent(intent, null);
-        mActivityTestRule.startActivityCompletely(intent);
+        mActivityTestRule.launchActivity(intent);
     }
 
     public static Bitmap createThumbnailBitmapAndWriteToFile(int tabId) {
@@ -271,7 +272,8 @@
         TestThreadUtils.runOnUiThreadBlocking(
                 () -> mActivityTestRule.getActivity().startDelayedNativeInitializationForTests());
         CriteriaHelper.pollUiThread(
-                mActivityTestRule.getActivity().getTabModelSelector()::isTabStateInitialized);
+                mActivityTestRule.getActivity().getTabModelSelector()::isTabStateInitialized,
+                ScalableTimeout.scaleTimeout(10000L), CriteriaHelper.DEFAULT_POLLING_INTERVAL);
         Assert.assertTrue(LibraryLoader.getInstance().isInitialized());
     }
 
@@ -568,6 +570,7 @@
             RecyclerView.ViewHolder viewHolder = recyclerView.findViewHolderForAdapterPosition(i);
             if (viewHolder != null) {
                 ImageView thumbnail = viewHolder.itemView.findViewById(R.id.tab_thumbnail);
+                if (!(thumbnail.getDrawable() instanceof BitmapDrawable)) return false;
                 BitmapDrawable drawable = (BitmapDrawable) thumbnail.getDrawable();
                 Bitmap bitmap = drawable.getBitmap();
                 if (bitmap == null) return false;
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceNoTabsTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceNoTabsTest.java
index 19a30c7..260b84d6 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceNoTabsTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceNoTabsTest.java
@@ -91,7 +91,7 @@
         Intent intent = new Intent(Intent.ACTION_MAIN);
         intent.addCategory(Intent.CATEGORY_LAUNCHER);
         mActivityTestRule.prepareUrlIntent(intent, null);
-        mActivityTestRule.startActivityCompletely(intent);
+        mActivityTestRule.launchActivity(intent);
     }
 
     @Before
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
index 9d29913..c7b94a6 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
@@ -811,7 +811,7 @@
     @CommandLineFlags.Add({BASE_PARAMS + "/single/exclude_mv_tiles/true"
             + "/show_last_active_tab_only/true/show_stack_tab_switcher/true"})
     public void
-    testShow_SingleAsHomepageV2_FromResumeShowStart() throws ExecutionException {
+    testShow_SingleAsHomepageV2_FromResumeShowStart() throws Exception {
         // clang-format on
         if (!mImmediateReturn) return;
 
@@ -831,7 +831,7 @@
         pressHome();
 
         // Simulates pressing Chrome's icon and launching Chrome from warm start.
-        startMainActivityFromLauncher();
+        mActivityTestRule.resumeMainActivityFromLauncher();
 
         CriteriaHelper.pollUiThread(
                 () -> cta.getLayoutManager() != null && cta.getLayoutManager().overviewVisible());
diff --git a/chrome/android/java/res/layout/custom_tabs_toolbar.xml b/chrome/android/java/res/layout/custom_tabs_toolbar.xml
index fac032c..83c8fb6 100644
--- a/chrome/android/java/res/layout/custom_tabs_toolbar.xml
+++ b/chrome/android/java/res/layout/custom_tabs_toolbar.xml
@@ -14,15 +14,16 @@
         style="@style/ToolbarButton"
         android:layout_gravity="start|center_vertical"
         android:contentDescription="@string/close_tab" />
-    <org.chromium.ui.widget.ChromeImageButton
-        android:id="@+id/incognito_cct_logo_button"
+    <org.chromium.ui.widget.ChromeImageView
+        android:id="@+id/incognito_cct_logo_image_view"
         style="@style/LocationBarButton"
         android:layout_width="@dimen/location_bar_icon_width"
         android:layout_height="match_parent"
         android:layout_gravity="start"
         android:scaleType="center"
         app:srcCompat="@drawable/ic_incognito_cct_24dp"
-        android:visibility="gone" />
+        android:visibility="gone"
+        android:contentDescription="@string/accessibility_incognito_badge"/>
     <FrameLayout
         android:id="@+id/location_bar_frame_layout"
         android:layout_width="match_parent"
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
index 5ab3865..84509ce 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
@@ -1097,7 +1097,7 @@
                             .setRedirectHandler(mRedirectHandler)
                             .setIsMainFrame(navigationParams.isMainFrame)
                             .build();
-            if (externalNavHandler.shouldOverrideUrlLoading(params)
+            if (externalNavHandler.shouldOverrideUrlLoading(params).getResultType()
                     != OverrideUrlLoadingResultType.NO_OVERRIDE) {
                 return false;
             }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
index 24150a9e..18c6d73 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
@@ -30,6 +30,7 @@
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 import android.widget.ImageButton;
+import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
@@ -131,7 +132,7 @@
     private View mLiteStatusView;
     private View mLiteStatusSeparatorView;
     private TextView mTitleBar;
-    private ImageButton mIncognitoButton;
+    private ImageView mIncognitoImageView;
     private ImageButton mSecurityButton;
     private LinearLayout mCustomActionButtons;
     private ImageButton mCloseButton;
@@ -184,7 +185,7 @@
         mLocationBarFrameLayout = findViewById(R.id.location_bar_frame_layout);
         mTitleUrlContainer = findViewById(R.id.title_url_container);
         mTitleUrlContainer.setOnLongClickListener(this);
-        mIncognitoButton = findViewById(R.id.incognito_cct_logo_button);
+        mIncognitoImageView = findViewById(R.id.incognito_cct_logo_image_view);
         mSecurityButton = findViewById(R.id.security_button);
         mCustomActionButtons = findViewById(R.id.action_buttons);
         mCloseButton = findViewById(R.id.close_button);
@@ -376,7 +377,7 @@
 
     private void updateToolbarLayoutMargin() {
         // We show the Incognito logo for Incognito CCT case
-        if (getToolbarDataProvider().isIncognito()) mIncognitoButton.setVisibility(VISIBLE);
+        if (getToolbarDataProvider().isIncognito()) mIncognitoImageView.setVisibility(VISIBLE);
 
         int startMargin = calculateStartMarginWhenCloseButtonVisibilityGone();
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationDelegateImpl.java
index 01441c0..9297716 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationDelegateImpl.java
@@ -35,7 +35,7 @@
 import org.chromium.components.external_intents.ExternalNavigationDelegate;
 import org.chromium.components.external_intents.ExternalNavigationDelegate.StartActivityIfNeededResult;
 import org.chromium.components.external_intents.ExternalNavigationHandler;
-import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResultType;
+import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResult;
 import org.chromium.components.external_intents.ExternalNavigationParams;
 import org.chromium.components.external_intents.RedirectHandler;
 import org.chromium.content_public.browser.LoadUrlParams;
@@ -149,14 +149,13 @@
     }
 
     @Override
-    public @OverrideUrlLoadingResultType int handleIncognitoIntentTargetingSelf(
+    public OverrideUrlLoadingResult handleIncognitoIntentTargetingSelf(
             final Intent intent, final String referrerUrl, final String fallbackUrl) {
         String primaryUrl = intent.getDataString();
         boolean isUrlLoadedInTheSameTab = ExternalNavigationHandler.loadUrlFromIntent(
                 referrerUrl, primaryUrl, fallbackUrl, this, false, true);
-        return (isUrlLoadedInTheSameTab)
-                ? OverrideUrlLoadingResultType.OVERRIDE_WITH_CLOBBERING_TAB
-                : OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT;
+        return (isUrlLoadedInTheSameTab) ? OverrideUrlLoadingResult.forClobberingTab()
+                                         : OverrideUrlLoadingResult.forExternalIntent();
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/ProfileDataCache.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/ProfileDataCache.java
index 3991bede..a1e05b8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/ProfileDataCache.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/ProfileDataCache.java
@@ -45,8 +45,7 @@
  * should be provided by calling {@link #update(List)}
  */
 @MainThread
-public class ProfileDataCache implements ProfileDownloader.Observer, ProfileDataSource.Observer,
-                                         IdentityManager.Observer {
+public class ProfileDataCache implements ProfileDataSource.Observer, IdentityManager.Observer {
     /**
      * Observer to get notifications about changes in profile data.
      */
@@ -151,7 +150,7 @@
 
         for (int i = 0; i < accounts.size(); i++) {
             if (mCachedProfileData.get(accounts.get(i)) == null) {
-                ProfileDownloader.startFetchingAccountInfoFor(
+                ProfileDownloader.get().startFetchingAccountInfoFor(
                         mContext, accounts.get(i), mImageSize, true);
             }
         }
@@ -204,7 +203,7 @@
                 mProfileDataSource.addObserver(this);
                 updateCacheFromProfileDataSource();
             } else {
-                ProfileDownloader.addObserver(this);
+                ProfileDownloader.get().addObserver(this);
             }
             mIdentityManager.addObserver(this);
         }
@@ -221,7 +220,7 @@
             if (mProfileDataSource != null) {
                 mProfileDataSource.removeObserver(this);
             } else {
-                ProfileDownloader.removeObserver(this);
+                ProfileDownloader.get().removeObserver(this);
             }
             mIdentityManager.removeObserver(this);
         }
@@ -242,11 +241,9 @@
     }
 
     @Override
-    public void onProfileDownloaded(
-            String accountEmail, String fullName, String givenName, Bitmap bitmap) {
+    public void onProfileDataUpdated(ProfileDataSource.ProfileData profileData) {
         ThreadUtils.assertOnUiThread();
-        updateCachedProfileDataAndNotifyObservers(new DisplayableProfileData(
-                accountEmail, prepareAvatar(bitmap, accountEmail), fullName, givenName));
+        updateCachedProfileDataAndNotifyObservers(createDisplayableProfileData(profileData));
     }
 
     @Override
@@ -255,13 +252,18 @@
         ProfileDataSource.ProfileData profileData =
                 mProfileDataSource.getProfileDataForAccount(accountEmail);
         if (profileData == null) {
-            mCachedProfileData.remove(accountEmail);
-            notifyObservers(accountEmail);
+            removeProfileData(accountEmail);
         } else {
-            updateCachedProfileDataAndNotifyObservers(createDisplayableProfileData(profileData));
+            onProfileDataUpdated(profileData);
         }
     }
 
+    @Override
+    public void removeProfileData(String accountEmail) {
+        mCachedProfileData.remove(accountEmail);
+        notifyObservers(accountEmail);
+    }
+
     /**
      * Implements {@link IdentityManager.Observer}.
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/ProfileDownloader.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/ProfileDownloader.java
index 2cb164a..804be94 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/ProfileDownloader.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/ProfileDownloader.java
@@ -13,6 +13,7 @@
 import org.chromium.base.annotations.NativeMethods;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.components.signin.AccountTrackerService;
+import org.chromium.components.signin.ProfileDataSource;
 
 import java.util.ArrayList;
 
@@ -21,38 +22,38 @@
  * The native ProfileDownloader requires its access to be in the UI thread.
  * See chrome/browser/profiles/profile_downloader.h/cc for more details.
  */
-public class ProfileDownloader {
-    private static final ObserverList<Observer> sObservers = new ObserverList<Observer>();
+class ProfileDownloader {
+    private static ProfileDownloader sInstance;
 
-    /**
-     * Interface for receiving notifications on account information updates.
-     */
-    public interface Observer {
-        /**
-         * Notifies that an account data in the profile has been updated.
-         * @param accountEmail An account email.
-         * @param fullName A full name.
-         * @param givenName A given name.
-         * @param bitmap A user picture.
-         */
-        void onProfileDownloaded(
-                String accountEmail, String fullName, String givenName, Bitmap bitmap);
+    private final ObserverList<ProfileDataSource.Observer> mObservers = new ObserverList<>();
+
+    static synchronized ProfileDownloader get() {
+        if (sInstance == null) {
+            sInstance = new ProfileDownloader();
+        }
+        return sInstance;
     }
 
     /**
      * Add an observer.
      * @param observer An observer.
      */
-    public static void addObserver(Observer observer) {
-        sObservers.addObserver(observer);
+    public void addObserver(ProfileDataSource.Observer observer) {
+        mObservers.addObserver(observer);
     }
 
     /**
      * Remove an observer.
      * @param observer An observer.
      */
-    public static void removeObserver(Observer observer) {
-        sObservers.removeObserver(observer);
+    public void removeObserver(ProfileDataSource.Observer observer) {
+        mObservers.removeObserver(observer);
+    }
+
+    private void notifyObservers(ProfileDataSource.ProfileData profileData) {
+        for (ProfileDataSource.Observer observer : mObservers) {
+            observer.onProfileDataUpdated(profileData);
+        }
     }
 
     /**
@@ -74,7 +75,7 @@
             mImageSidePixels = new ArrayList<>();
         }
 
-        public static PendingProfileDownloads get(Context context) {
+        static PendingProfileDownloads get(Context context) {
             ThreadUtils.assertOnUiThread();
             if (sPendingProfileDownloads == null) {
                 sPendingProfileDownloads = new PendingProfileDownloads();
@@ -120,7 +121,7 @@
      * @param accountEmail Account email to fetch the information for
      * @param imageSidePixels Request image side (in pixels)
      */
-    public static void startFetchingAccountInfoFor(
+    public void startFetchingAccountInfoFor(
             Context context, String accountEmail, int imageSidePixels, boolean isPreSignin) {
         ThreadUtils.assertOnUiThread();
         Profile profile = Profile.getLastUsedRegularProfile();
@@ -137,11 +138,10 @@
 
     @CalledByNative
     private static void onProfileDownloadSuccess(
-            String accountEmail, String fullName, String givenName, Bitmap bitmap) {
+            String accountEmail, String fullName, String givenName, Bitmap avatar) {
         ThreadUtils.assertOnUiThread();
-        for (Observer observer : sObservers) {
-            observer.onProfileDownloaded(accountEmail, fullName, givenName, bitmap);
-        }
+        ProfileDownloader.get().notifyObservers(
+                new ProfileDataSource.ProfileData(accountEmail, avatar, fullName, givenName));
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
index 2d53db6..55cc509 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
@@ -130,10 +130,10 @@
 
     @Override
     public boolean hasTab() {
-        // TODO(dtrainor, tedchoc): Remove the isInitialized() check when we no longer wait for
-        // TAB_CLOSED events to remove this tab.  Otherwise there is a chance we use this tab after
-        // {@link ChromeTab#destroy()} is called.
-        return mTab != null && mTab.isInitialized();
+        // TODO(https://crbug.com/1147131): Remove the isInitialized() and isDestroyed checks when
+        // we no longer wait for TAB_CLOSED events to remove this tab.  Otherwise there is a chance
+        // we use this tab after {@link Tab#destroy()} is called.
+        return mTab != null && mTab.isInitialized() && !mTab.isDestroyed();
     }
 
     @Override
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/CopylessPasteTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/CopylessPasteTest.java
index 279eb8d..d2e0260 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/CopylessPasteTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/CopylessPasteTest.java
@@ -15,7 +15,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.test.util.ApplicationTestUtils;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
@@ -72,7 +71,6 @@
     @After
     public void tearDown() throws Exception {
         AppIndexingUtil.setCallbackForTesting(null);
-        ApplicationTestUtils.finishActivity(mActivityTestRule.getActivity());
     }
 
     private static class CopylessHelper extends CallbackHelper {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/MainActivityWithURLTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/MainActivityWithURLTest.java
index cee2800..723653c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/MainActivityWithURLTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/MainActivityWithURLTest.java
@@ -8,13 +8,11 @@
 
 import androidx.test.filters.SmallTest;
 
-import org.junit.After;
 import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.test.util.ApplicationTestUtils;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
@@ -36,11 +34,6 @@
     @Rule
     public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
 
-    @After
-    public void tearDown() throws Exception {
-        ApplicationTestUtils.finishActivity(mActivityTestRule.getActivity());
-    }
-
     /**
      * Verify launch the activity with URL.
      */
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkPersonalizedSigninPromoDismissTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkPersonalizedSigninPromoDismissTest.java
index 2d586b71..8640a48 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkPersonalizedSigninPromoDismissTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkPersonalizedSigninPromoDismissTest.java
@@ -11,8 +11,11 @@
 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 
+import static org.hamcrest.CoreMatchers.allOf;
 import static org.junit.Assert.assertEquals;
 
+import static org.chromium.chrome.test.util.ViewUtils.onViewWaiting;
+
 import android.support.test.InstrumentationRegistry;
 
 import androidx.test.filters.MediumTest;
@@ -81,7 +84,7 @@
     @MediumTest
     public void testPromoNotShownAfterBeingDismissed() {
         mBookmarkTestRule.showBookmarkManager(mSyncTestRule.getActivity());
-        onView(withId(R.id.signin_promo_view_container)).check(matches(isDisplayed()));
+        onViewWaiting(allOf(withId(R.id.signin_promo_view_container), isDisplayed()));
         onView(withId(R.id.signin_promo_close_button)).perform(click());
         onView(withId(R.id.signin_promo_view_container)).check(doesNotExist());
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java
index 645fad0..733c40d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java
@@ -204,8 +204,9 @@
     }
 
     @After
-    public void tearDown() {
+    public void tearDown() throws Exception {
         if (mTestServer != null) mTestServer.stopAndDestroyServer();
+        if (mBookmarkActivity != null) ApplicationTestUtils.finishActivity(mBookmarkActivity);
     }
 
     @AfterClass
@@ -283,7 +284,7 @@
         // Click the star button again to launch the edit activity.
         MenuUtils.invokeCustomMenuActionSync(InstrumentationRegistry.getInstrumentation(),
                 mActivityTestRule.getActivity(), R.id.bookmark_this_page_id);
-        waitForEditActivity();
+        waitForEditActivity().finish();
     }
 
     @Test
@@ -314,8 +315,9 @@
             currentSnackbar.getController().onAction(null);
         });
 
-        waitForEditActivity();
+        BookmarkEditActivity activity = waitForEditActivity();
         SnackbarManager.setDurationForTesting(0);
+        activity.finish();
     }
 
     @Test
@@ -1731,12 +1733,13 @@
         RecyclerViewTestUtils.waitForStableRecyclerView(mItemsContainer);
     }
 
-    private void waitForEditActivity() {
+    private BookmarkEditActivity waitForEditActivity() {
         CriteriaHelper.pollUiThread(() -> {
             Criteria.checkThat(ApplicationStatus.getLastTrackedFocusedActivity(),
                     IsInstanceOf.instanceOf(BookmarkEditActivity.class));
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        return (BookmarkEditActivity) ApplicationStatus.getLastTrackedFocusedActivity();
     }
 
     private ChromeTabbedActivity waitForTabbedActivity() {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataFragmentBasicTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataFragmentBasicTest.java
index 6fc157e..c6d9b533 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataFragmentBasicTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataFragmentBasicTest.java
@@ -17,6 +17,7 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
@@ -44,14 +45,18 @@
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 public class ClearBrowsingDataFragmentBasicTest {
-    @Rule
     public final ChromeTabbedActivityTestRule mActivityTestRule =
             new ChromeTabbedActivityTestRule();
-    @Rule
     public final SettingsActivityTestRule<ClearBrowsingDataFragmentBasic>
             mSettingsActivityTestRule =
                     new SettingsActivityTestRule<>(ClearBrowsingDataFragmentBasic.class);
 
+    // SettingsActivity has to be finished before the outer CTA can be finished or trying to finish
+    // CTA won't work.
+    @Rule
+    public final RuleChain mRuleChain =
+            RuleChain.outerRule(mActivityTestRule).around(mSettingsActivityTestRule);
+
     @Rule
     public final AccountManagerTestRule mAccountManagerTestRule = new AccountManagerTestRule();
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityIncognitoTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityIncognitoTest.java
index 1225843..193430a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityIncognitoTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityIncognitoTest.java
@@ -196,7 +196,7 @@
     public void toolbarHasIncognitoLogo() throws Exception {
         Intent intent = createMinimalIncognitoCustomTabIntent();
         launchIncognitoCustomTab(intent);
-        Espresso.onView(withId(R.id.incognito_cct_logo_button)).check(matches(isDisplayed()));
+        Espresso.onView(withId(R.id.incognito_cct_logo_image_view)).check(matches(isDisplayed()));
     }
 
     @Test
@@ -205,7 +205,8 @@
     public void toolbarDoesNotHaveIncognitoLogo() throws Exception {
         Intent intent = createMinimalIncognitoCustomTabIntent();
         launchIncognitoCustomTab(intent);
-        Espresso.onView(withId(R.id.incognito_cct_logo_button)).check(matches(not(isDisplayed())));
+        Espresso.onView(withId(R.id.incognito_cct_logo_image_view))
+                .check(matches(not(isDisplayed())));
     }
 
     @Test
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
index 8101eec..96b2323a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
@@ -707,7 +707,7 @@
         IntentFilter filter = new IntentFilter(Intent.ACTION_VIEW);
         filter.addDataScheme(Uri.parse(mTestServer.getURL("/")).getScheme());
         final ActivityMonitor monitor =
-                InstrumentationRegistry.getInstrumentation().addMonitor(filter, null, false);
+                InstrumentationRegistry.getInstrumentation().addMonitor(filter, null, true);
         openAppMenuAndAssertMenuShown();
         PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, () -> {
             MenuItem item =
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTestRule.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTestRule.java
index ff48c21..e832ecf 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTestRule.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTestRule.java
@@ -4,29 +4,20 @@
 
 package org.chromium.chrome.browser.customtabs;
 
-import android.app.Activity;
 import android.content.Intent;
-import android.support.test.InstrumentationRegistry;
 
-import org.hamcrest.Matchers;
+import androidx.annotation.NonNull;
+
 import org.junit.Assert;
 
-import org.chromium.base.ApplicationStatus;
 import org.chromium.base.FeatureList;
-import org.chromium.base.test.util.CallbackHelper;
-import org.chromium.base.test.util.Criteria;
-import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.ScalableTimeout;
-import org.chromium.chrome.browser.DeferredStartupHandler;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
-import org.chromium.chrome.browser.tab.EmptyTabObserver;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabTestUtils;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 
 import java.util.Collections;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
 
 /**
  * Custom ActivityTestRule for all instrumentation tests that require a {@link CustomTabActivity}.
@@ -54,29 +45,13 @@
     }
 
     @Override
-    public void startActivityCompletely(Intent intent) {
+    public void launchActivity(@NonNull Intent intent) {
         if (!FeatureList.hasTestFeatures()) {
             FeatureList.setTestFeatures(
                     Collections.singletonMap(ChromeFeatureList.SHARE_BY_DEFAULT_IN_CCT, true));
         }
         putCustomTabIdInIntent(intent);
-        int currentIntentId = getCustomTabIdFromIntent(intent);
-
-        Activity activity = InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
-        Assert.assertNotNull("Main activity did not start", activity);
-        CriteriaHelper.pollUiThread(() -> {
-            for (Activity runningActivity : ApplicationStatus.getRunningActivities()) {
-                if (runningActivity instanceof CustomTabActivity) {
-                    CustomTabActivity customTabActivity = (CustomTabActivity) runningActivity;
-                    final int customTabIdInActivity =
-                            getCustomTabIdFromIntent(customTabActivity.getIntent());
-                    if (currentIntentId != customTabIdInActivity) continue;
-                    setActivity(customTabActivity);
-                    return true;
-                }
-            }
-            return false;
-        });
+        super.launchActivity(intent);
     }
 
     /**
@@ -84,33 +59,8 @@
      * initialized.
      */
     public void startCustomTabActivityWithIntent(Intent intent) {
-        DeferredStartupHandler.setExpectingActivityStartupForTesting();
         startActivityCompletely(intent);
-        waitForActivityNativeInitializationComplete();
-        CriteriaHelper.pollUiThread(() -> {
-            Criteria.checkThat(getActivity().getActivityTab(), Matchers.notNullValue());
-        });
         final Tab tab = getActivity().getActivityTab();
-        final CallbackHelper pageLoadFinishedHelper = new CallbackHelper();
-        tab.addObserver(new EmptyTabObserver() {
-            @Override
-            public void onLoadStopped(Tab tab, boolean toDifferentDocument) {
-                pageLoadFinishedHelper.notifyCalled();
-            }
-        });
-        try {
-            if (tab.isLoading()) {
-                pageLoadFinishedHelper.waitForCallback(
-                        0, 1, LONG_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            }
-        } catch (TimeoutException e) {
-            Assert.fail();
-        }
-        Assert.assertTrue("Deferred startup never completed",
-                DeferredStartupHandler.waitForDeferredStartupCompleteForTesting(
-                        STARTUP_TIMEOUT_MS));
-        Assert.assertNotNull(tab);
-        Assert.assertNotNull(tab.getView());
         Assert.assertTrue(TabTestUtils.isCustomTab(tab));
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabExternalNavigationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabExternalNavigationTest.java
index 7a6fbb1a..a39d907 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabExternalNavigationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabExternalNavigationTest.java
@@ -27,6 +27,7 @@
 import org.chromium.chrome.browser.tab.TabTestUtils;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.components.external_intents.ExternalNavigationHandler;
+import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResult;
 import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResultType;
 import org.chromium.components.external_intents.ExternalNavigationParams;
 import org.chromium.net.test.EmbeddedTestServer;
@@ -107,9 +108,9 @@
         final String testUrl = "customtab://customtabtest/intent";
         ExternalNavigationParams params = new ExternalNavigationParams.Builder(testUrl, false)
                 .build();
-        @OverrideUrlLoadingResultType
-        int result = mUrlHandler.shouldOverrideUrlLoading(params);
-        Assert.assertEquals(OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT, result);
+        OverrideUrlLoadingResult result = mUrlHandler.shouldOverrideUrlLoading(params);
+        Assert.assertEquals(
+                OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT, result.getResultType());
         Assert.assertTrue("A dummy activity should have been started to handle the special url.",
                 mNavigationDelegate.hasExternalActivityStarted());
     }
@@ -124,9 +125,8 @@
         final String testUrl = "http://customtabtest.com";
         ExternalNavigationParams params = new ExternalNavigationParams.Builder(testUrl, false)
                 .build();
-        @OverrideUrlLoadingResultType
-        int result = mUrlHandler.shouldOverrideUrlLoading(params);
-        Assert.assertEquals(OverrideUrlLoadingResultType.NO_OVERRIDE, result);
+        OverrideUrlLoadingResult result = mUrlHandler.shouldOverrideUrlLoading(params);
+        Assert.assertEquals(OverrideUrlLoadingResultType.NO_OVERRIDE, result.getResultType());
         Assert.assertFalse("External activities should not be started to handle the url",
                 mNavigationDelegate.hasExternalActivityStarted());
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabFromChromeExternalNavigationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabFromChromeExternalNavigationTest.java
index a07414a..3821874 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabFromChromeExternalNavigationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabFromChromeExternalNavigationTest.java
@@ -111,8 +111,7 @@
     @Test
     @Feature("CustomTabFromChrome")
     @LargeTest
-    public void
-    testIntentWithRedirectToApp() {
+    public void testIntentWithRedirectToApp() {
         final String redirectUrl = "https://maps.google.com/maps?q=1600+amphitheatre+parkway";
         final String initialUrl =
                 mServerRule.getServer().getURL("/chrome/test/data/android/redirect/js_redirect.html"
@@ -123,7 +122,7 @@
                         + Base64.encodeToString(
                                 ApiCompatibilityUtils.getBytesUtf8(redirectUrl), Base64.URL_SAFE));
 
-        mActivityRule.startActivityCompletely(getCustomTabFromChromeIntent(initialUrl, true));
+        mActivityRule.launchActivity(getCustomTabFromChromeIntent(initialUrl, true));
         mActivityRule.waitForActivityNativeInitializationComplete();
 
         final AtomicReference<InterceptNavigationDelegateImpl> navigationDelegate =
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/display_cutout/DisplayCutoutTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/display_cutout/DisplayCutoutTest.java
index 0899713..af9727f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/display_cutout/DisplayCutoutTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/display_cutout/DisplayCutoutTest.java
@@ -9,12 +9,10 @@
 
 import androidx.test.filters.LargeTest;
 
-import org.junit.After;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.test.util.ApplicationTestUtils;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.MinAndroidSdkLevel;
@@ -37,11 +35,6 @@
     public DisplayCutoutTestRule mTestRule =
             new DisplayCutoutTestRule<ChromeActivity>(ChromeActivity.class);
 
-    @After
-    public void tearDown() throws Exception {
-        ApplicationTestUtils.finishActivity(mTestRule.getActivity());
-    }
-
     /**
      * Test that no safe area is applied when we have viewport fit auto
      */
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
index 3dc82eb..cc76495e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
@@ -13,6 +13,7 @@
 import android.net.Uri;
 import android.os.SystemClock;
 import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.lifecycle.Stage;
 import android.text.TextUtils;
 import android.util.Base64;
 
@@ -28,10 +29,14 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.ApiCompatibilityUtils;
+import org.chromium.base.ContextUtils;
+import org.chromium.base.test.util.ApplicationTestUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.base.test.util.ScalableTimeout;
+import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.document.ChromeLauncherActivity;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.tab.EmptyTabObserver;
@@ -424,24 +429,36 @@
 
     @Test
     @SmallTest
-    public void testRedirectionFromIntent() {
-        // Test cold-start.
+    public void testRedirectionFromIntentCold() throws Exception {
+        Context context = ContextUtils.getApplicationContext();
         Intent intent = new Intent(Intent.ACTION_VIEW,
                 Uri.parse(mTestServer.getURL(NAVIGATION_FROM_JAVA_REDIRECTION_PAGE)));
-        Context targetContext = InstrumentationRegistry.getTargetContext();
-        intent.setClassName(targetContext, ChromeLauncherActivity.class.getName());
+        intent.setClassName(context, ChromeLauncherActivity.class.getName());
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
+
+        ChromeTabbedActivity activity = ApplicationTestUtils.waitForActivityWithClass(
+                ChromeTabbedActivity.class, Stage.CREATED, () -> context.startActivity(intent));
+        mActivityTestRule.setActivity(activity);
+
+        CriteriaHelper.pollUiThread(() -> {
+            Criteria.checkThat(mActivityMonitor.getHits(), Matchers.is(1));
+        }, ScalableTimeout.scaleTimeout(10000L), CriteriaHelper.DEFAULT_POLLING_INTERVAL);
+        ApplicationTestUtils.waitForActivityState(activity, Stage.STOPPED);
+    }
+
+    @Test
+    @SmallTest
+    public void testRedirectionFromIntentWarm() throws Exception {
+        Context context = ContextUtils.getApplicationContext();
+        mActivityTestRule.startMainActivityOnBlankPage();
+        Intent intent = new Intent(Intent.ACTION_VIEW,
+                Uri.parse(mTestServer.getURL(NAVIGATION_FROM_JAVA_REDIRECTION_PAGE)));
+        intent.setClassName(context, ChromeLauncherActivity.class.getName());
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        context.startActivity(intent);
 
         CriteriaHelper.pollUiThread(
                 () -> Criteria.checkThat(mActivityMonitor.getHits(), Matchers.is(1)));
-
-        // Test warm start.
-        mActivityTestRule.startMainActivityOnBlankPage();
-        targetContext.startActivity(intent);
-
-        CriteriaHelper.pollUiThread(
-                () -> Criteria.checkThat(mActivityMonitor.getHits(), Matchers.is(2)));
     }
 
     @Test
@@ -489,7 +506,6 @@
         String originalUrl = mTestServer.getURL(NAVIGATION_TO_FILE_SCHEME_FROM_INTENT_URI);
         loadUrlAndWaitForIntentUrl(originalUrl, true, false, false, null, false, "null_scheme");
     }
-
     @Test
     @LargeTest
     public void testIntentURIWithEmptySchemeDoesNothing() throws TimeoutException {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/homepage/HomepagePolicyIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/homepage/HomepagePolicyIntegrationTest.java
index 7934a16..3579486 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/homepage/HomepagePolicyIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/homepage/HomepagePolicyIntegrationTest.java
@@ -16,6 +16,7 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
 import org.chromium.base.ActivityState;
@@ -64,12 +65,16 @@
 
     private EmbeddedTestServer mTestServer;
 
-    @Rule
     public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
-    @Rule
     public SettingsActivityTestRule<HomepageSettings> mSettingsActivityTestRule =
             new SettingsActivityTestRule<>(HomepageSettings.class);
 
+    // SettingsActivity has to be finished before the outer CTA can be finished or trying to finish
+    // CTA won't work.
+    @Rule
+    public final RuleChain mRuleChain =
+            RuleChain.outerRule(mActivityTestRule).around(mSettingsActivityTestRule);
+
     @Rule
     public HomepageTestRule mHomepageTestRule = new HomepageTestRule();
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/homepage/settings/HomepageSettingsFragmentTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/homepage/settings/HomepageSettingsFragmentTest.java
index 703521d..a273db8 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/homepage/settings/HomepageSettingsFragmentTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/homepage/settings/HomepageSettingsFragmentTest.java
@@ -14,6 +14,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.test.util.ApplicationTestUtils;
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.UserActionTester;
@@ -104,9 +105,8 @@
         Assert.assertNotNull("Custom URI radio button is null.", mCustomUriRadioButton);
     }
 
-    private void finishSettingsActivity() {
-        mTestRule.getActivity().finish();
-        mTestRule.waitTillActivityIsDestroyed();
+    private void finishSettingsActivity() throws Exception {
+        ApplicationTestUtils.finishActivity(mTestRule.getActivity());
     }
 
     @Test
@@ -394,7 +394,7 @@
     @Test
     @SmallTest
     @Feature({"Homepage"})
-    public void testCheckRadioButtons() {
+    public void testCheckRadioButtons() throws Exception {
         mHomepageTestRule.useCustomizedHomepageForTest(TEST_URL_FOO);
         launchSettingsActivity();
         LocationChangedCounter counter = new LocationChangedCounter();
@@ -446,7 +446,7 @@
     @Test
     @SmallTest
     @Feature({"Homepage"})
-    public void testChangeCustomized() {
+    public void testChangeCustomized() throws Exception {
         mHomepageTestRule.useChromeNTPForTest();
         launchSettingsActivity();
         LocationChangedCounter actionCounter = new LocationChangedCounter();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java
index 5c0af74..98254843 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java
@@ -218,7 +218,7 @@
             // mSlowPage will hang for 2 seconds before sending a response. It should be enough to
             // put Chrome in background before the page is committed.
             mTabbedActivityTestRule.prepareUrlIntent(intent, mSlowPage);
-            mTabbedActivityTestRule.startActivityCompletely(intent);
+            mTabbedActivityTestRule.launchActivity(intent);
 
             // Put Chrome in background before the page is committed.
             ChromeApplicationTestUtils.fireHomeScreenIntent(
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineIndicatorControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineIndicatorControllerTest.java
index cdbf6b5..8c2cbab 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineIndicatorControllerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineIndicatorControllerTest.java
@@ -250,6 +250,7 @@
 
         // Offline indicator should not be shown.
         checkOfflineIndicatorVisibility(downloadActivity, false);
+        downloadActivity.finish();
     }
 
     @Test
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentUiTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentUiTest.java
index 4485b23..012b466 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentUiTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentUiTest.java
@@ -13,6 +13,8 @@
 
 import static org.chromium.chrome.browser.preferences.ChromePreferenceKeys.ASSISTANT_VOICE_SEARCH_ENABLED;
 
+import android.support.test.runner.lifecycle.Stage;
+
 import androidx.test.filters.MediumTest;
 
 import org.junit.After;
@@ -26,6 +28,7 @@
 import org.mockito.junit.MockitoRule;
 
 import org.chromium.base.Callback;
+import org.chromium.base.test.util.ApplicationTestUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
@@ -33,6 +36,7 @@
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
+import org.chromium.chrome.browser.settings.SettingsActivity;
 import org.chromium.chrome.browser.settings.SettingsLauncherImpl;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
@@ -130,16 +134,19 @@
     public void testDialogInteractivity_LearnMoreButton() {
         showConsentUi();
 
-        TestThreadUtils.runOnUiThreadBlocking(() -> {
-            ClickUtils.clickButton(mAssistantVoiceSearchConsentUi.getContentView().findViewById(
-                    R.id.avs_consent_ui_learn_more));
-            mBottomSheetTestSupport.endAllAnimations();
-        });
+        SettingsActivity activity = ApplicationTestUtils.waitForActivityWithClass(
+                SettingsActivity.class, Stage.RESUMED, () -> {
+                    ClickUtils.clickButton(
+                            mAssistantVoiceSearchConsentUi.getContentView().findViewById(
+                                    R.id.avs_consent_ui_learn_more));
+                    mBottomSheetTestSupport.endAllAnimations();
+                });
 
         onView(withText(mActivityTestRule.getActivity().getResources().getString(
                        R.string.avs_setting_category_title)))
                 .check(matches(isDisplayed()));
         Mockito.verify(mCallback, Mockito.times(0)).onResult(/* meaningless value */ true);
+        activity.finish();
     }
 
     @Test
@@ -156,4 +163,4 @@
         });
         Mockito.verify(mCallback, Mockito.timeout(1000)).onResult(false);
     }
-}
\ No newline at end of file
+}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoViewTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoViewTest.java
index 61515dd..419276e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoViewTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoViewTest.java
@@ -108,7 +108,7 @@
 
     private void loadUrlAndOpenPageInfo(String url) {
         mActivityTestRule.loadUrl(url);
-        onView(withId(R.id.location_bar_status_icon)).perform(click());
+        onViewWaiting(allOf(withId(R.id.location_bar_status_icon), isDisplayed())).perform(click());
     }
 
     private View getPageInfoView() {
@@ -187,8 +187,6 @@
         // Choose a fixed, "random" port to create stable screenshots.
         mTestServerRule.setServerPort(424242);
         mTestServerRule.setServerUsesHttps(true);
-
-        mActivityTestRule.startMainActivityOnBlankPage();
     }
 
     @After
@@ -212,6 +210,7 @@
     @Feature({"RenderTest"})
     @Features.DisableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testShowOnInsecureHttpWebsite() throws IOException {
+        mActivityTestRule.startMainActivityOnBlankPage();
         mTestServerRule.setServerUsesHttps(false);
         loadUrlAndOpenPageInfo(mTestServerRule.getServer().getURL(sSimpleHtml));
         mRenderTestRule.render(getPageInfoView(), "PageInfo_HttpWebsite");
@@ -225,6 +224,7 @@
     @Feature({"RenderTest"})
     @Features.DisableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testShowOnSecureWebsite() throws IOException {
+        mActivityTestRule.startMainActivityOnBlankPage();
         loadUrlAndOpenPageInfo(mTestServerRule.getServer().getURL(sSimpleHtml));
         mRenderTestRule.render(getPageInfoView(), "PageInfo_SecureWebsite");
     }
@@ -238,6 +238,7 @@
     @DisabledTest(message = "https://crbug.com/1133770")
     @Features.DisableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testShowOnExpiredCertificateWebsite() throws IOException {
+        mActivityTestRule.startMainActivityOnBlankPage();
         mTestServerRule.setCertificateType(ServerCertificate.CERT_EXPIRED);
         loadUrlAndOpenPageInfo(mTestServerRule.getServer().getURL(sSimpleHtml));
         mRenderTestRule.render(getPageInfoView(), "PageInfo_ExpiredCertWebsite");
@@ -251,6 +252,7 @@
     @Feature({"RenderTest"})
     @Features.DisableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testChromePage() throws IOException {
+        mActivityTestRule.startMainActivityOnBlankPage();
         loadUrlAndOpenPageInfo("chrome://version/");
         mRenderTestRule.render(getPageInfoView(), "PageInfo_InternalSite");
     }
@@ -264,6 +266,7 @@
     @Feature({"RenderTest"})
     @Features.DisableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testShowWithPermissions() throws IOException {
+        mActivityTestRule.startMainActivityOnBlankPage();
         mIsSystemLocationSettingEnabled = false;
         addSomePermissions(mTestServerRule.getServer().getURL("/"));
         loadUrlAndOpenPageInfo(mTestServerRule.getServer().getURL(sSimpleHtml));
@@ -278,6 +281,7 @@
     @Feature({"RenderTest"})
     @Features.DisableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testShowWithCookieBlocking() throws IOException {
+        mActivityTestRule.startMainActivityOnBlankPage();
         setThirdPartyCookieBlocking(CookieControlsMode.BLOCK_THIRD_PARTY);
         loadUrlAndOpenPageInfo(mTestServerRule.getServer().getURL(sSimpleHtml));
         mRenderTestRule.render(getPageInfoView(), "PageInfo_CookieBlocking");
@@ -291,6 +295,7 @@
     @Feature({"RenderTest"})
     @Features.DisableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testShowWithPermissionsAndCookieBlocking() throws IOException {
+        mActivityTestRule.startMainActivityOnBlankPage();
         addSomePermissions(mTestServerRule.getServer().getURL("/"));
         setThirdPartyCookieBlocking(CookieControlsMode.BLOCK_THIRD_PARTY);
         loadUrlAndOpenPageInfo(mTestServerRule.getServer().getURL(sSimpleHtml));
@@ -305,6 +310,7 @@
     @Feature({"RenderTest"})
     @Features.DisableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testShowWithDefaultSettingPermissions() throws IOException {
+        mActivityTestRule.startMainActivityOnBlankPage();
         addDefaultSettingPermissions(mTestServerRule.getServer().getURL("/"));
         loadUrlAndOpenPageInfo(mTestServerRule.getServer().getURL(sSimpleHtml));
         mRenderTestRule.render(getPageInfoView(), "PageInfo_DefaultSettingPermissions");
@@ -318,6 +324,7 @@
     @Feature({"RenderTest"})
     @Features.EnableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testShowOnSecureWebsiteV2() throws IOException {
+        mActivityTestRule.startMainActivityOnBlankPage();
         loadUrlAndOpenPageInfo(mTestServerRule.getServer().getURL(sSimpleHtml));
         mRenderTestRule.render(getPageInfoView(), "PageInfo_SecureWebsiteV2");
     }
@@ -346,6 +353,7 @@
     @Feature({"RenderTest"})
     @Features.EnableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testShowConnectionInfoSubpage() throws IOException {
+        mActivityTestRule.startMainActivityOnBlankPage();
         loadUrlAndOpenPageInfo(mTestServerRule.getServer().getURL(sSimpleHtml));
         onView(withId(R.id.page_info_connection_row)).perform(click());
         mRenderTestRule.render(getPageInfoView(), "PageInfo_ConnectionInfoSubpage");
@@ -359,6 +367,7 @@
     @Feature({"RenderTest"})
     @Features.EnableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testShowPermissionsSubpage() throws IOException {
+        mActivityTestRule.startMainActivityOnBlankPage();
         addSomePermissions(mTestServerRule.getServer().getURL("/"));
         loadUrlAndOpenPageInfo(mTestServerRule.getServer().getURL(sSimpleHtml));
         onView(withId(R.id.page_info_permissions_row)).perform(click());
@@ -373,6 +382,7 @@
     @Feature({"RenderTest"})
     @Features.EnableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testShowCookiesSubpage() throws IOException {
+        mActivityTestRule.startMainActivityOnBlankPage();
         setThirdPartyCookieBlocking(CookieControlsMode.BLOCK_THIRD_PARTY);
         loadUrlAndOpenPageInfo(mTestServerRule.getServer().getURL(sSimpleHtml));
         onView(withId(R.id.page_info_cookies_row)).perform(click());
@@ -387,6 +397,7 @@
     @MediumTest
     @Features.EnableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testNoPermissionsSubpage() throws IOException {
+        mActivityTestRule.startMainActivityOnBlankPage();
         loadUrlAndOpenPageInfo(mTestServerRule.getServer().getURL(sSimpleHtml));
         View dialog = (View) getPageInfoView().getParent();
         onView(withId(R.id.page_info_permissions_row))
@@ -401,6 +412,7 @@
     @Features.EnableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     @FlakyTest(message = "https://crbug.com/1147236")
     public void testClearCookiesOnSubpage() throws Exception {
+        mActivityTestRule.startMainActivityOnBlankPage();
         mActivityTestRule.loadUrl(mTestServerRule.getServer().getURL(sSiteDataHtml));
         // Create cookies.
         expectHasCookies(false);
@@ -426,6 +438,7 @@
     @MediumTest
     @Features.EnableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testResetPermissionsOnSubpage() throws Exception {
+        mActivityTestRule.startMainActivityOnBlankPage();
         mActivityTestRule.loadUrl(mTestServerRule.getServer().getURL(sSiteDataHtml));
         String url = mTestServerRule.getServer().getURL("/");
         // Create permissions.
@@ -453,6 +466,7 @@
     @MediumTest
     @Features.EnableFeatures(PageInfoFeatureList.PAGE_INFO_V2)
     public void testPaintPreview() {
+        mActivityTestRule.startMainActivityOnBlankPage();
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             final ChromeActivity activity = mActivityTestRule.getActivity();
             final Tab tab = activity.getActivityTab();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTestRule.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTestRule.java
index 3b5375a..2c90ae8 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTestRule.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTestRule.java
@@ -1245,6 +1245,7 @@
         @Override
         public void create(PaymentAppFactoryDelegate delegate) {
             Runnable createApp = () -> {
+                if (delegate.getParams().hasClosed()) return;
                 boolean canMakePayment =
                         delegate.getParams().getMethodData().containsKey(mAppMethodName);
                 delegate.onCanMakePaymentCalculated(canMakePayment);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/settings/MainSettingsFragmentTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/settings/MainSettingsFragmentTest.java
index aa1888d..947221f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/settings/MainSettingsFragmentTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/settings/MainSettingsFragmentTest.java
@@ -15,6 +15,7 @@
 import static org.mockito.Mockito.verify;
 
 import android.app.Activity;
+import android.support.test.InstrumentationRegistry;
 import android.text.TextUtils;
 import android.view.View;
 
@@ -101,7 +102,7 @@
     private final SyncTestRule mSyncTestRule = new SyncTestRule();
 
     private final SettingsActivityTestRule<MainSettings> mSettingsActivityTestRule =
-            new SettingsActivityTestRule<>(MainSettings.class, true);
+            new SettingsActivityTestRule<>(MainSettings.class);
 
     // SettingsActivity needs to be initialized and destroyed with the mock
     // signin environment setup in SyncTestRule
@@ -133,6 +134,7 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        InstrumentationRegistry.getInstrumentation().setInTouchMode(true);
         PasswordCheckFactory.setPasswordCheckForTesting(mPasswordCheck);
         SigninActivityLauncherImpl.setLauncherForTest(mMockSigninActivityLauncherImpl);
         DeveloperSettings.setIsEnabledForTests(true);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountPickerBottomSheetRenderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountPickerBottomSheetRenderTest.java
index 516bc1db0..6783423 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountPickerBottomSheetRenderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountPickerBottomSheetRenderTest.java
@@ -22,7 +22,6 @@
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.test.filters.MediumTest;
 
-import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -34,7 +33,6 @@
 import org.chromium.base.Callback;
 import org.chromium.base.test.params.ParameterAnnotations;
 import org.chromium.base.test.params.ParameterizedRunner;
-import org.chromium.base.test.util.ApplicationTestUtils;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.CriteriaHelper;
@@ -120,11 +118,6 @@
         mActivityTestRule.startMainActivityOnBlankPage();
     }
 
-    @After
-    public void tearDown() throws Exception {
-        ApplicationTestUtils.finishActivity(mActivityTestRule.getActivity());
-    }
-
     @AfterClass
     public static void tearDownAfterActivityDestroyed() {
         ChromeNightModeTestUtils.tearDownNightModeAfterChromeActivityDestroyed();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/ManageSpaceActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/ManageSpaceActivityTest.java
index 16095c4..5383798 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/ManageSpaceActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/ManageSpaceActivityTest.java
@@ -100,7 +100,7 @@
     @Test
     @SmallTest
     public void testLaunchActivity() {
-        startManageSpaceActivity();
+        startManageSpaceActivity().finish();
     }
 
     @Test
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/AccountManagementFragmentTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/AccountManagementFragmentTest.java
index b42cd0d..d0a1c47 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/AccountManagementFragmentTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/AccountManagementFragmentTest.java
@@ -18,6 +18,7 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
 import org.chromium.base.test.util.CommandLineFlags;
@@ -40,14 +41,18 @@
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 public class AccountManagementFragmentTest {
-    @Rule
     public final SettingsActivityTestRule<AccountManagementFragment> mSettingsActivityTestRule =
             new SettingsActivityTestRule<>(AccountManagementFragment.class);
 
-    @Rule
     public final ChromeTabbedActivityTestRule mActivityTestRule =
             new ChromeTabbedActivityTestRule();
 
+    // SettingsActivity has to be finished before the outer CTA can be finished or trying to finish
+    // CTA won't work.
+    @Rule
+    public final RuleChain mRuleChain =
+            RuleChain.outerRule(mActivityTestRule).around(mSettingsActivityTestRule);
+
     @Rule
     public final AccountManagerTestRule mAccountManagerTestRule = new AccountManagerTestRule();
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/GoogleServicesSettingsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/GoogleServicesSettingsTest.java
index dc9d76a..9b38421 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/GoogleServicesSettingsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/GoogleServicesSettingsTest.java
@@ -16,6 +16,7 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
 import org.chromium.base.test.util.CommandLineFlags;
@@ -51,13 +52,17 @@
     @Rule
     public final AccountManagerTestRule mAccountManagerTestRule = new AccountManagerTestRule();
 
-    @Rule
     public final ChromeTabbedActivityTestRule mActivityTestRule =
             new ChromeTabbedActivityTestRule();
 
-    @Rule
     public final SettingsActivityTestRule<GoogleServicesSettings> mSettingsActivityTestRule =
-            new SettingsActivityTestRule<>(GoogleServicesSettings.class, true);
+            new SettingsActivityTestRule<>(GoogleServicesSettings.class);
+
+    // SettingsActivity has to be finished before the outer CTA can be finished or trying to finish
+    // CTA won't work.
+    @Rule
+    public final RuleChain mRuleChain =
+            RuleChain.outerRule(mActivityTestRule).around(mSettingsActivityTestRule);
 
     @Before
     public void setUp() {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/ManageSyncSettingsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/ManageSyncSettingsTest.java
index 9c90fe6..2acd095 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/ManageSyncSettingsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/ManageSyncSettingsTest.java
@@ -90,7 +90,7 @@
     private final SyncTestRule mSyncTestRule = new SyncTestRule();
 
     private final SettingsActivityTestRule<ManageSyncSettings> mSettingsActivityTestRule =
-            new SettingsActivityTestRule<>(ManageSyncSettings.class, true);
+            new SettingsActivityTestRule<>(ManageSyncSettings.class);
 
     // SettingsActivity needs to be initialized and destroyed with the mock
     // signin environment setup in SyncTestRule
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/SyncErrorCardPreferenceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/SyncErrorCardPreferenceTest.java
index 76da0b0..559a75f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/SyncErrorCardPreferenceTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/SyncErrorCardPreferenceTest.java
@@ -62,7 +62,7 @@
 
     @Rule
     public final SettingsActivityTestRule<ManageSyncSettings> mSettingsActivityTestRule =
-            new SettingsActivityTestRule<>(ManageSyncSettings.class, true);
+            new SettingsActivityTestRule<>(ManageSyncSettings.class);
 
     @Rule
     public final ChromeRenderTestRule mRenderTestRule =
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/SyncTestRule.java b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/SyncTestRule.java
index cfd5d94..458cdfa 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/SyncTestRule.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/SyncTestRule.java
@@ -4,6 +4,12 @@
 
 package org.chromium.chrome.browser.sync;
 
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.content.Context;
@@ -13,6 +19,7 @@
 
 import androidx.annotation.Nullable;
 import androidx.preference.TwoStatePreference;
+import androidx.test.espresso.contrib.RecyclerViewActions;
 
 import org.junit.Assert;
 import org.junit.runner.Description;
@@ -31,6 +38,7 @@
 import org.chromium.chrome.test.util.browser.signin.AccountManagerTestRule;
 import org.chromium.chrome.test.util.browser.signin.SigninTestUtil;
 import org.chromium.chrome.test.util.browser.sync.SyncTestUtil;
+import org.chromium.components.browser_ui.widget.R;
 import org.chromium.components.signin.base.CoreAccountInfo;
 import org.chromium.components.sync.ModelType;
 import org.chromium.components.sync.protocol.AutofillWalletSpecifics;
@@ -410,12 +418,9 @@
 
     // UI interaction convenience methods.
     public void togglePreference(final TwoStatePreference pref) {
-        TestThreadUtils.runOnUiThreadBlocking(() -> {
-            boolean newValue = !pref.isChecked();
-            pref.getOnPreferenceChangeListener().onPreferenceChange(pref, newValue);
-            pref.setChecked(newValue);
-        });
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        onView(withId(R.id.recycler_view))
+                .perform(RecyclerViewActions.actionOnItem(
+                        hasDescendant(withText(pref.getTitle().toString())), click()));
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateTest.java
index 41f7667..ba3b0bb 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateTest.java
@@ -76,10 +76,9 @@
         }
 
         @Override
-        public @OverrideUrlLoadingResultType int shouldOverrideUrlLoading(
-                ExternalNavigationParams params) {
+        public OverrideUrlLoadingResult shouldOverrideUrlLoading(ExternalNavigationParams params) {
             mExternalNavParamHistory.add(params);
-            return OverrideUrlLoadingResultType.NO_OVERRIDE;
+            return OverrideUrlLoadingResult.forNoOverride();
         }
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/LevelDBPersistedTabDataStorageFactoryTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/LevelDBPersistedTabDataStorageFactoryTest.java
index 2611d9a5..c715a63 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/LevelDBPersistedTabDataStorageFactoryTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/LevelDBPersistedTabDataStorageFactoryTest.java
@@ -64,9 +64,11 @@
         LevelDBPersistedTabDataStorage.setSkipNativeAssertionsForTesting(true);
     }
 
+    @UiThreadTest
     @SmallTest
     @Test
     public void testFactoryMethod() {
+        Profile realProfile = Profile.getLastUsedRegularProfile();
         LevelDBPersistedTabDataStorageFactory factory = new LevelDBPersistedTabDataStorageFactory();
         Profile.setLastUsedProfileForTesting(mProfile1);
         LevelDBPersistedTabDataStorage profile1Storage = factory.create();
@@ -76,6 +78,8 @@
         LevelDBPersistedTabDataStorage profile1StorageAgain = factory.create();
         Assert.assertEquals(profile1Storage, profile1StorageAgain);
         Assert.assertNotEquals(profile1Storage, profile2Storage);
+        // Restore the original profile so the Activity can shut down correctly.
+        Profile.setLastUsedProfileForTesting(realProfile);
     }
 
     @UiThreadTest
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tasks/ReturnToChromeTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tasks/ReturnToChromeTest.java
index 12907e8..cf1a94e0 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tasks/ReturnToChromeTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tasks/ReturnToChromeTest.java
@@ -597,7 +597,7 @@
         intent.addCategory(Intent.CATEGORY_LAUNCHER);
         mActivityTestRule.prepareUrlIntent(intent, url);
         Assert.assertFalse(mInflated.get());
-        mActivityTestRule.startActivityCompletely(intent);
+        mActivityTestRule.launchActivity(intent);
         if (mUseInstantStart) {
             CriteriaHelper.pollUiThread(mInflated::get);
         } else {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappNavigationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappNavigationTest.java
index 33af832..fc5fc02 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappNavigationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappNavigationTest.java
@@ -263,7 +263,7 @@
         IntentFilter filter = new IntentFilter(Intent.ACTION_VIEW);
         filter.addDataScheme("https");
         final ActivityMonitor monitor =
-                InstrumentationRegistry.getInstrumentation().addMonitor(filter, null, false);
+                InstrumentationRegistry.getInstrumentation().addMonitor(filter, null, true);
 
         RevampedContextMenuUtils.selectContextMenuItem(InstrumentationRegistry.getInstrumentation(),
                 null /* activity to check for focus after click */,
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 6facf83b..8e92d67 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -2277,7 +2277,6 @@
     "//net",
     "//net:extras",
     "//ppapi/buildflags",
-    "//ppapi/host",
     "//printing",
     "//printing/buildflags",
     "//rlz/buildflags",
@@ -5988,15 +5987,12 @@
       "plugins/reload_plugin_infobar_delegate.h",
       "renderer_host/pepper/chrome_browser_pepper_host_factory.cc",
       "renderer_host/pepper/chrome_browser_pepper_host_factory.h",
-      "renderer_host/pepper/device_id_fetcher.cc",
-      "renderer_host/pepper/device_id_fetcher.h",
       "renderer_host/pepper/pepper_isolated_file_system_message_filter.cc",
       "renderer_host/pepper/pepper_isolated_file_system_message_filter.h",
     ]
     deps += [
       "//components/pdf/browser",
       "//media:media_buildflags",
-      "//ppapi/buildflags",
       "//ppapi/host",
       "//ppapi/proxy:ipc",
       "//services/device/public/mojom",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 774391e..fdbf07f 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -3685,6 +3685,10 @@
      flag_descriptions::kImeAssistAutocorrectName,
      flag_descriptions::kImeAssistAutocorrectDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(chromeos::features::kAssistAutoCorrect)},
+    {"enable-cros-ime-assist-multi-word",
+     flag_descriptions::kImeAssistMultiWordName,
+     flag_descriptions::kImeAssistMultiWordDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(chromeos::features::kAssistMultiWord)},
     {"enable-cros-ime-assist-personal-info",
      flag_descriptions::kImeAssistPersonalInfoName,
      flag_descriptions::kImeAssistPersonalInfoDescription, kOsCrOS,
diff --git a/chrome/browser/android/contextualsearch/contextual_search_delegate.cc b/chrome/browser/android/contextualsearch/contextual_search_delegate.cc
index ee626e5..2754381 100644
--- a/chrome/browser/android/contextualsearch/contextual_search_delegate.cc
+++ b/chrome/browser/android/contextualsearch/contextual_search_delegate.cc
@@ -77,7 +77,7 @@
 
 const int kContextualSearchMaxSelection = 1000;
 const char kXssiEscape[] = ")]}'\n";
-const char kDiscourseContextHeaderPrefix[] = "X-Additional-Discourse-Context: ";
+const char kDiscourseContextHeaderName[] = "X-Additional-Discourse-Context";
 const char kDoPreventPreloadValue[] = "1";
 
 const int kResponseCodeUninitialized = -1;
@@ -162,8 +162,7 @@
 
   // Populates the discourse context and adds it to the HTTP header of the
   // search term resolution request.
-  resource_request->headers.AddHeadersFromString(
-      GetDiscourseContext(*context_));
+  resource_request->headers.CopyFrom(GetDiscourseContext(*context_));
 
   // Disable cookies for this request.
   resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
@@ -396,7 +395,7 @@
                                  selection_end);
 }
 
-std::string ContextualSearchDelegate::GetDiscourseContext(
+const net::HttpRequestHeaders ContextualSearchDelegate::GetDiscourseContext(
     const ContextualSearchContext& context) {
   discourse_context::ClientDiscourseContext proto;
   discourse_context::Display* display = proto.add_display();
@@ -419,7 +418,10 @@
   // The server memoizer expects a web-safe encoding.
   std::replace(encoded_context.begin(), encoded_context.end(), '+', '-');
   std::replace(encoded_context.begin(), encoded_context.end(), '/', '_');
-  return kDiscourseContextHeaderPrefix + encoded_context;
+
+  net::HttpRequestHeaders headers;
+  headers.SetHeader(kDiscourseContextHeaderName, encoded_context);
+  return headers;
 }
 
 // Decodes the given response from the search term resolution request and sets
diff --git a/chrome/browser/android/contextualsearch/contextual_search_delegate.h b/chrome/browser/android/contextualsearch/contextual_search_delegate.h
index 4137d68..5b21dca 100644
--- a/chrome/browser/android/contextualsearch/contextual_search_delegate.h
+++ b/chrome/browser/android/contextualsearch/contextual_search_delegate.h
@@ -17,6 +17,7 @@
 #include "base/values.h"
 #include "chrome/browser/android/contextualsearch/contextual_search_context.h"
 #include "chrome/browser/android/contextualsearch/resolved_search_term.h"
+#include "net/http/http_request_headers.h"
 
 namespace content {
 class WebContents;
@@ -127,7 +128,8 @@
       uint32_t end_offset);
 
   // Populates and returns the discourse context.
-  std::string GetDiscourseContext(const ContextualSearchContext& context);
+  const net::HttpRequestHeaders GetDiscourseContext(
+      const ContextualSearchContext& context);
 
   // Builds a Resolved Search Term by decoding the given JSON string.
   std::unique_ptr<ResolvedSearchTerm> GetResolvedSearchTermFromJson(
diff --git a/chrome/browser/autofill/autofill_browsertest.cc b/chrome/browser/autofill/autofill_browsertest.cc
index bd93553a..b730d63 100644
--- a/chrome/browser/autofill/autofill_browsertest.cc
+++ b/chrome/browser/autofill/autofill_browsertest.cc
@@ -117,11 +117,6 @@
     // Don't want Keychain coming up on Mac.
     test::DisableSystemServices(browser()->profile()->GetPrefs());
 
-    // Load the MatchingPattern definitions.
-    base::RunLoop run_loop;
-    field_type_parsing::PopulateFromResourceBundle(run_loop.QuitClosure());
-    run_loop.Run();
-
     ASSERT_TRUE(embedded_test_server()->Start());
   }
 
diff --git a/chrome/browser/autofill/autofill_interactive_uitest.cc b/chrome/browser/autofill/autofill_interactive_uitest.cc
index b20f0ab..d3513ccd 100644
--- a/chrome/browser/autofill/autofill_interactive_uitest.cc
+++ b/chrome/browser/autofill/autofill_interactive_uitest.cc
@@ -302,11 +302,6 @@
         &AutofillInteractiveTestBase::HandleTestURL, base::Unretained(this)));
     embedded_test_server()->StartAcceptingConnections();
 
-    // Load the MatchingPattern definitions.
-    base::RunLoop run_loop;
-    field_type_parsing::PopulateFromResourceBundle(run_loop.QuitClosure());
-    run_loop.Run();
-
     // By default, all SSL cert checks are valid. Can be overriden in tests if
     // needed.
     cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
diff --git a/chrome/browser/autofill/form_structure_browsertest.cc b/chrome/browser/autofill/form_structure_browsertest.cc
index ed3c554..5debbc4 100644
--- a/chrome/browser/autofill/form_structure_browsertest.cc
+++ b/chrome/browser/autofill/form_structure_browsertest.cc
@@ -195,11 +195,6 @@
 void FormStructureBrowserTest::SetUpOnMainThread() {
   InProcessBrowserTest::SetUpOnMainThread();
 
-  // Load the MatchingPattern definitions.
-  base::RunLoop run_loop;
-  field_type_parsing::PopulateFromResourceBundle(run_loop.QuitClosure());
-  run_loop.Run();
-
   embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
       &FormStructureBrowserTest::HandleRequest, base::Unretained(this)));
   ASSERT_TRUE(embedded_test_server()->Start());
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 9c711e3..2b45773 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -345,7 +345,6 @@
 #include "net/ssl/ssl_cert_request_info.h"
 #include "pdf/buildflags.h"
 #include "ppapi/buildflags/buildflags.h"
-#include "ppapi/host/ppapi_host.h"
 #include "printing/buildflags/buildflags.h"
 #include "sandbox/policy/features.h"
 #include "sandbox/policy/sandbox_type.h"
diff --git a/chrome/browser/chromeos/login/enrollment/auto_enrollment_check_screen.cc b/chrome/browser/chromeos/login/enrollment/auto_enrollment_check_screen.cc
index 5eaca153..90f90cf 100644
--- a/chrome/browser/chromeos/login/enrollment/auto_enrollment_check_screen.cc
+++ b/chrome/browser/chromeos/login/enrollment/auto_enrollment_check_screen.cc
@@ -30,13 +30,6 @@
 
 }  // namespace
 
-// static
-AutoEnrollmentCheckScreen* AutoEnrollmentCheckScreen::Get(
-    ScreenManager* manager) {
-  return static_cast<AutoEnrollmentCheckScreen*>(
-      manager->GetScreen(AutoEnrollmentCheckScreenView::kScreenId));
-}
-
 AutoEnrollmentCheckScreen::AutoEnrollmentCheckScreen(
     AutoEnrollmentCheckScreenView* view,
     ErrorScreen* error_screen,
diff --git a/chrome/browser/chromeos/login/enrollment/auto_enrollment_check_screen.h b/chrome/browser/chromeos/login/enrollment/auto_enrollment_check_screen.h
index ba5c9ff..fb29d7b 100644
--- a/chrome/browser/chromeos/login/enrollment/auto_enrollment_check_screen.h
+++ b/chrome/browser/chromeos/login/enrollment/auto_enrollment_check_screen.h
@@ -21,7 +21,6 @@
 namespace chromeos {
 
 class ErrorScreensHistogramHelper;
-class ScreenManager;
 
 // Handles the control flow after OOBE auto-update completes to wait for the
 // enterprise auto-enrollment check that happens as part of OOBE. This includes
@@ -33,13 +32,13 @@
       public BaseScreen,
       public NetworkPortalDetector::Observer {
  public:
+  using TView = AutoEnrollmentCheckScreenView;
+
   AutoEnrollmentCheckScreen(AutoEnrollmentCheckScreenView* view,
                             ErrorScreen* error_screen,
                             const base::RepeatingClosure& exit_callback);
   ~AutoEnrollmentCheckScreen() override;
 
-  static AutoEnrollmentCheckScreen* Get(ScreenManager* manager);
-
   // Clears the cached state causing the forced enrollment check to be retried.
   void ClearState();
 
diff --git a/chrome/browser/chromeos/login/enrollment/enrollment_local_policy_server_browsertest.cc b/chrome/browser/chromeos/login/enrollment/enrollment_local_policy_server_browsertest.cc
index 32aa1a5..181523ef 100644
--- a/chrome/browser/chromeos/login/enrollment/enrollment_local_policy_server_browsertest.cc
+++ b/chrome/browser/chromeos/login/enrollment/enrollment_local_policy_server_browsertest.cc
@@ -112,8 +112,8 @@
   AutoEnrollmentCheckScreen* auto_enrollment_screen() {
     EXPECT_NE(WizardController::default_controller(), nullptr);
     AutoEnrollmentCheckScreen* auto_enrollment_screen =
-        AutoEnrollmentCheckScreen::Get(
-            WizardController::default_controller()->screen_manager());
+        WizardController::default_controller()
+            ->GetScreen<AutoEnrollmentCheckScreen>();
     EXPECT_NE(auto_enrollment_screen, nullptr);
     return auto_enrollment_screen;
   }
diff --git a/chrome/browser/chromeos/login/wizard_controller.cc b/chrome/browser/chromeos/login/wizard_controller.cc
index 75b4c77..9df1100 100644
--- a/chrome/browser/chromeos/login/wizard_controller.cc
+++ b/chrome/browser/chromeos/login/wizard_controller.cc
@@ -832,8 +832,7 @@
 }
 
 void WizardController::ShowAutoEnrollmentCheckScreen() {
-  AutoEnrollmentCheckScreen* screen =
-      AutoEnrollmentCheckScreen::Get(screen_manager());
+  AutoEnrollmentCheckScreen* screen = GetScreen<AutoEnrollmentCheckScreen>();
   if (retry_auto_enrollment_check_)
     screen->ClearState();
   screen->set_auto_enrollment_controller(GetAutoEnrollmentController());
diff --git a/chrome/browser/chromeos/policy/dlp/data_transfer_dlp_controller_browsertest.cc b/chrome/browser/chromeos/policy/dlp/data_transfer_dlp_controller_browsertest.cc
new file mode 100644
index 0000000..bef0996
--- /dev/null
+++ b/chrome/browser/chromeos/policy/dlp/data_transfer_dlp_controller_browsertest.cc
@@ -0,0 +1,188 @@
+// // Copyright 2020 The Chromium Authors. All rights reserved.
+// // Use of this source code is governed by a BSD-style license that can be
+// // found in the LICENSE file.
+
+#include <memory>
+
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/chromeos/policy/dlp/data_transfer_dlp_controller.h"
+#include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager.h"
+#include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager_test_utils.h"
+#include "chrome/browser/policy/policy_test_utils.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/policy_constants.h"
+#include "content/public/test/browser_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/clipboard/clipboard.h"
+#include "ui/base/clipboard/clipboard_buffer.h"
+#include "ui/base/clipboard/scoped_clipboard_writer.h"
+#include "ui/base/data_transfer_policy/data_transfer_endpoint.h"
+#include "url/origin.h"
+
+namespace policy {
+
+namespace {
+
+constexpr char kClipboardText[] = "Hello World";
+
+}  // namespace
+
+class DataTransferDlpBrowserTest : public PolicyTest {
+ public:
+  DataTransferDlpBrowserTest() = default;
+};
+
+IN_PROC_BROWSER_TEST_F(DataTransferDlpBrowserTest, EmptyPolicy) {
+  PolicyMap policies;
+  policies.Set(key::kDataLeakPreventionRulesList, POLICY_LEVEL_MANDATORY,
+               POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD, /*value=*/base::nullopt,
+               nullptr);
+  UpdateProviderPolicy(policies);
+
+  {
+    ui::ScopedClipboardWriter writer(ui::ClipboardBuffer::kCopyPaste);
+    writer.WriteText(base::UTF8ToUTF16(kClipboardText));
+  }
+  ui::DataTransferEndpoint data_dst(
+      url::Origin::Create(GURL("https://google.com")));
+  base::string16 result;
+  ui::Clipboard::GetForCurrentThread()->ReadText(
+      ui::ClipboardBuffer::kCopyPaste, &data_dst, &result);
+  EXPECT_EQ(base::UTF8ToUTF16(kClipboardText), result);
+}
+
+IN_PROC_BROWSER_TEST_F(DataTransferDlpBrowserTest, RestrictedUrl) {
+  const std::string kUrl1 = "https://mail.google.com";
+  const std::string kUrl2 = "https://docs.google.com";
+  const std::string kUrl3 = "https://example.com";
+
+  base::Value rules(base::Value::Type::LIST);
+
+  base::Value src_urls1(base::Value::Type::LIST);
+  src_urls1.Append(kUrl1);
+  base::Value dst_urls1(base::Value::Type::LIST);
+  dst_urls1.Append("*");
+  base::Value restrictions1(base::Value::Type::LIST);
+  restrictions1.Append(dlp_test_util::CreateRestrictionWithLevel(
+      dlp::kClipboardRestriction, dlp::kBlockLevel));
+  rules.Append(dlp_test_util::CreateRule(
+      "rule #1", "Block Gmail", std::move(src_urls1), std::move(dst_urls1),
+      /*dst_components=*/base::Value(base::Value::Type::LIST),
+      std::move(restrictions1)));
+
+  base::Value src_urls2(base::Value::Type::LIST);
+  src_urls2.Append(kUrl1);
+  base::Value dst_urls2(base::Value::Type::LIST);
+  dst_urls2.Append(kUrl2);
+  base::Value restrictions2(base::Value::Type::LIST);
+  restrictions2.Append(dlp_test_util::CreateRestrictionWithLevel(
+      dlp::kClipboardRestriction, dlp::kAllowLevel));
+  rules.Append(dlp_test_util::CreateRule(
+      "rule #2", "Allow Gmail for work purposes", std::move(src_urls2),
+      std::move(dst_urls2),
+      /*dst_components=*/base::Value(base::Value::Type::LIST),
+      std::move(restrictions2)));
+
+  PolicyMap policies;
+  policies.Set(key::kDataLeakPreventionRulesList, POLICY_LEVEL_MANDATORY,
+               POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD, std::move(rules),
+               nullptr);
+  UpdateProviderPolicy(policies);
+
+  {
+    ui::ScopedClipboardWriter writer(ui::ClipboardBuffer::kCopyPaste,
+                                     std::make_unique<ui::DataTransferEndpoint>(
+                                         url::Origin::Create(GURL(kUrl1))));
+    writer.WriteText(base::UTF8ToUTF16(kClipboardText));
+  }
+  ui::DataTransferEndpoint data_dst1(url::Origin::Create(GURL(kUrl1)));
+  base::string16 result1;
+  ui::Clipboard::GetForCurrentThread()->ReadText(
+      ui::ClipboardBuffer::kCopyPaste, &data_dst1, &result1);
+  EXPECT_EQ(base::UTF8ToUTF16(kClipboardText), result1);
+
+  ui::DataTransferEndpoint data_dst2(url::Origin::Create(GURL(kUrl2)));
+  base::string16 result2;
+  ui::Clipboard::GetForCurrentThread()->ReadText(
+      ui::ClipboardBuffer::kCopyPaste, &data_dst2, &result2);
+  EXPECT_EQ(base::UTF8ToUTF16(kClipboardText), result2);
+
+  ui::DataTransferEndpoint data_dst3(url::Origin::Create(GURL(kUrl3)));
+  base::string16 result3;
+  ui::Clipboard::GetForCurrentThread()->ReadText(
+      ui::ClipboardBuffer::kCopyPaste, &data_dst3, &result3);
+  EXPECT_EQ(base::string16(), result3);
+
+  {
+    ui::ScopedClipboardWriter writer(ui::ClipboardBuffer::kCopyPaste,
+                                     std::make_unique<ui::DataTransferEndpoint>(
+                                         url::Origin::Create(GURL(kUrl3))));
+    writer.WriteText(base::UTF8ToUTF16(kClipboardText));
+  }
+  ui::DataTransferEndpoint data_dst4(url::Origin::Create(GURL(kUrl1)));
+  base::string16 result4;
+  ui::Clipboard::GetForCurrentThread()->ReadText(
+      ui::ClipboardBuffer::kCopyPaste, &data_dst1, &result4);
+  EXPECT_EQ(base::UTF8ToUTF16(kClipboardText), result4);
+}
+
+IN_PROC_BROWSER_TEST_F(DataTransferDlpBrowserTest, RestrictedComponent) {
+  const std::string kUrl1 = "https://mail.google.com";
+
+  base::Value rules(base::Value::Type::LIST);
+
+  base::Value src_urls(base::Value::Type::LIST);
+  src_urls.Append(kUrl1);
+  base::Value dst_components(base::Value::Type::LIST);
+  dst_components.Append(dlp::kArc);
+  dst_components.Append(dlp::kPluginVm);
+  base::Value restrictions(base::Value::Type::LIST);
+  restrictions.Append(dlp_test_util::CreateRestrictionWithLevel(
+      dlp::kClipboardRestriction, dlp::kBlockLevel));
+  rules.Append(dlp_test_util::CreateRule(
+      "rule #1", "Block Gmail", std::move(src_urls),
+      /*dst_urls=*/base::Value(base::Value::Type::LIST),
+      std::move(dst_components), std::move(restrictions)));
+
+  PolicyMap policies;
+  policies.Set(key::kDataLeakPreventionRulesList, POLICY_LEVEL_MANDATORY,
+               POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD, std::move(rules),
+               nullptr);
+  UpdateProviderPolicy(policies);
+
+  {
+    ui::ScopedClipboardWriter writer(ui::ClipboardBuffer::kCopyPaste,
+                                     std::make_unique<ui::DataTransferEndpoint>(
+                                         url::Origin::Create(GURL(kUrl1))));
+    writer.WriteText(base::UTF8ToUTF16(kClipboardText));
+  }
+  ui::DataTransferEndpoint data_dst1(ui::EndpointType::kDefault);
+  base::string16 result1;
+  ui::Clipboard::GetForCurrentThread()->ReadText(
+      ui::ClipboardBuffer::kCopyPaste, &data_dst1, &result1);
+  EXPECT_EQ(base::UTF8ToUTF16(kClipboardText), result1);
+
+  // `notify_if_restricted` should be set false, otherwise the test would fail,
+  // because no guest os is actually running.
+  ui::DataTransferEndpoint data_dst2(ui::EndpointType::kArc,
+                                     /*notify_if_restricted=*/false);
+  base::string16 result2;
+  ui::Clipboard::GetForCurrentThread()->ReadText(
+      ui::ClipboardBuffer::kCopyPaste, &data_dst2, &result2);
+  EXPECT_EQ(base::string16(), result2);
+
+  // `notify_if_restricted` should be set false, otherwise the test would fail,
+  // because no guest os is actually running.
+  ui::DataTransferEndpoint data_dst3(ui::EndpointType::kGuestOs,
+                                     /*notify_if_restricted=*/false);
+  base::string16 result3;
+  ui::Clipboard::GetForCurrentThread()->ReadText(
+      ui::ClipboardBuffer::kCopyPaste, &data_dst3, &result3);
+  EXPECT_EQ(base::string16(), result3);
+}
+
+// TODO(crbug.com/1139884): Add browsertests for the clipboard notifications.
+
+}  // namespace policy
diff --git a/chrome/browser/chromeos/preferences.cc b/chrome/browser/chromeos/preferences.cc
index 8c5e520e..9cc52c4 100644
--- a/chrome/browser/chromeos/preferences.cc
+++ b/chrome/browser/chromeos/preferences.cc
@@ -491,6 +491,10 @@
   registry->RegisterBooleanPref(
       chromeos::prefs::kLauncherResultEverLaunched, false,
       user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
+
+  registry->RegisterBooleanPref(
+      chromeos::prefs::kHasCameraAppMigratedToSWA, false,
+      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
 }
 
 void Preferences::InitUserPrefs(sync_preferences::PrefServiceSyncable* prefs) {
@@ -1108,9 +1112,8 @@
   rate.repeat_interval_in_ms = xkb_auto_repeat_interval_pref_.GetValue();
   DCHECK(rate.initial_delay_in_ms > 0);
   DCHECK(rate.repeat_interval_in_ms > 0);
-  input_method::InputMethodManager::Get()
-      ->GetImeKeyboard()
-      ->SetAutoRepeatRate(rate);
+  input_method::InputMethodManager::Get()->GetImeKeyboard()->SetAutoRepeatRate(
+      rate);
 
   user_manager::known_user::SetIntegerPref(user_->GetAccountId(),
                                            ash::prefs::kXkbAutoRepeatDelay,
diff --git a/chrome/browser/chromeos/web_applications/camera_system_web_app_info.cc b/chrome/browser/chromeos/web_applications/camera_system_web_app_info.cc
index bf9b674..1bbd113 100644
--- a/chrome/browser/chromeos/web_applications/camera_system_web_app_info.cc
+++ b/chrome/browser/chromeos/web_applications/camera_system_web_app_info.cc
@@ -14,7 +14,7 @@
 std::unique_ptr<WebApplicationInfo> CreateWebAppInfoForCameraSystemWebApp() {
   auto info = std::make_unique<WebApplicationInfo>();
   info->start_url = GURL(chromeos::kChromeUICameraAppMainURL);
-  info->scope = GURL(chromeos::kChromeUICameraAppURL);
+  info->scope = GURL(chromeos::kChromeUICameraAppScopeURL);
 
   info->title = l10n_util::GetStringUTF16(IDS_NAME);
   web_app::CreateIconInfoForSystemWebApp(
diff --git a/chrome/browser/extensions/api/passwords_private/password_check_delegate.cc b/chrome/browser/extensions/api/passwords_private/password_check_delegate.cc
index 1d86988..f373950 100644
--- a/chrome/browser/extensions/api/passwords_private/password_check_delegate.cc
+++ b/chrome/browser/extensions/api/passwords_private/password_check_delegate.cc
@@ -12,6 +12,7 @@
 #include <memory>
 #include <utility>
 
+#include "base/bind.h"
 #include "base/containers/flat_set.h"
 #include "base/memory/ref_counted.h"
 #include "base/numerics/safe_conversions.h"
@@ -346,9 +347,13 @@
     return;
   }
 
+  // In case the Weakness Check feature is enabled start the check, and notify
+  // observers once done.
   if (base::FeatureList::IsEnabled(
           password_manager::features::kPasswordsWeaknessCheck)) {
-    insecure_credentials_manager_.StartWeakCheck();
+    insecure_credentials_manager_.StartWeakCheck(base::BindOnce(
+        &PasswordCheckDelegate::RecordAndNotifyAboutCompletedWeakPasswordCheck,
+        weak_ptr_factory_.GetWeakPtr()));
   }
 
   auto progress = base::MakeRefCounted<PasswordCheckProgress>();
@@ -378,13 +383,15 @@
 PasswordCheckDelegate::GetPasswordCheckStatus() const {
   api::passwords_private::PasswordCheckStatus result;
 
-  // Obtain the timestamp of the last completed check. This is 0.0 in case the
-  // check never completely ran before.
-  const double last_check_completed = profile_->GetPrefs()->GetDouble(
-      password_manager::prefs::kLastTimePasswordCheckCompleted);
-  if (last_check_completed) {
-    result.elapsed_time_since_last_check = std::make_unique<std::string>(
-        FormatElapsedTime(base::Time::FromDoubleT(last_check_completed)));
+  // Obtain the timestamp of the last completed password or weak check. This
+  // will be null in case no check has completely ran before.
+  base::Time last_check_completed =
+      std::max(base::Time::FromTimeT(profile_->GetPrefs()->GetDouble(
+                   password_manager::prefs::kLastTimePasswordCheckCompleted)),
+               last_completed_weak_check_);
+  if (!last_check_completed.is_null()) {
+    result.elapsed_time_since_last_check =
+        std::make_unique<std::string>(FormatElapsedTime(last_check_completed));
   }
 
   State state = bulk_leak_check_service_adapter_.GetBulkLeakCheckState();
@@ -455,18 +462,7 @@
   if (state == State::kIdle && std::exchange(is_check_running_, false)) {
     // When the service transitions from running into idle it has finished a
     // check.
-    profile_->GetPrefs()->SetDouble(
-        password_manager::prefs::kLastTimePasswordCheckCompleted,
-        base::Time::Now().ToDoubleT());
-
-    // In case the check run to completion delay the last Check Status update by
-    // a second. This avoids flickering of the UI if the full check ran from
-    // start to finish almost immediately.
-    base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
-        FROM_HERE,
-        base::BindOnce(&PasswordCheckDelegate::NotifyPasswordCheckStatusChanged,
-                       weak_ptr_factory_.GetWeakPtr()),
-        base::TimeDelta::FromSeconds(1));
+    RecordAndNotifyAboutCompletedCompromisedPasswordCheck();
     return;
   }
 
@@ -514,6 +510,29 @@
   return insecure_credential;
 }
 
+void PasswordCheckDelegate::
+    RecordAndNotifyAboutCompletedCompromisedPasswordCheck() {
+  profile_->GetPrefs()->SetDouble(
+      password_manager::prefs::kLastTimePasswordCheckCompleted,
+      base::Time::Now().ToDoubleT());
+
+  // Delay the last Check Status update by a second. This avoids flickering of
+  // the UI if the full check ran from start to finish almost immediately.
+  base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(&PasswordCheckDelegate::NotifyPasswordCheckStatusChanged,
+                     weak_ptr_factory_.GetWeakPtr()),
+      base::TimeDelta::FromSeconds(1));
+}
+
+void PasswordCheckDelegate::RecordAndNotifyAboutCompletedWeakPasswordCheck() {
+  last_completed_weak_check_ = base::Time::Now();
+  // Note: In contrast to the compromised password check we do not does not
+  // artificially delay the response, Since this check is expected to complete
+  // quickly.
+  NotifyPasswordCheckStatusChanged();
+}
+
 void PasswordCheckDelegate::NotifyPasswordCheckStatusChanged() {
   if (auto* event_router =
           PasswordsPrivateEventRouterFactory::GetForProfile(profile_)) {
diff --git a/chrome/browser/extensions/api/passwords_private/password_check_delegate.h b/chrome/browser/extensions/api/passwords_private/password_check_delegate.h
index ab7abc2fc..257c99a 100644
--- a/chrome/browser/extensions/api/passwords_private/password_check_delegate.h
+++ b/chrome/browser/extensions/api/passwords_private/password_check_delegate.h
@@ -10,6 +10,7 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/scoped_observer.h"
+#include "base/time/time.h"
 #include "chrome/browser/extensions/api/passwords_private/passwords_private_delegate.h"
 #include "chrome/browser/extensions/api/passwords_private/passwords_private_utils.h"
 #include "chrome/common/extensions/api/passwords_private.h"
@@ -122,6 +123,14 @@
   FindMatchingInsecureCredential(
       const api::passwords_private::InsecureCredential& credential) const;
 
+  // Invoked when a compromised password check completes. Records the current
+  // timestamp in `kLastTimePasswordCheckCompleted` pref.
+  void RecordAndNotifyAboutCompletedCompromisedPasswordCheck();
+
+  // Invoked when a weak password check completes. Records the current timestamp
+  // in `last_completed_weak_check_`.
+  void RecordAndNotifyAboutCompletedWeakPasswordCheck();
+
   // Tries to notify the PasswordsPrivateEventRouter that the password check
   // status has changed. Invoked after OnSavedPasswordsChanged and
   // OnStateChanged.
@@ -162,6 +171,9 @@
   // Remembers whether a password check is running right now.
   bool is_check_running_ = false;
 
+  // Store when the last weak check was completed.
+  base::Time last_completed_weak_check_;
+
   // A scoped observer for |saved_passwords_presenter_|.
   ScopedObserver<password_manager::SavedPasswordsPresenter,
                  password_manager::SavedPasswordsPresenter::Observer>
diff --git a/chrome/browser/extensions/api/storage/setting_sync_data.cc b/chrome/browser/extensions/api/storage/setting_sync_data.cc
index 4dce6ce..ce7dd47 100644
--- a/chrome/browser/extensions/api/storage/setting_sync_data.cc
+++ b/chrome/browser/extensions/api/storage/setting_sync_data.cc
@@ -22,7 +22,7 @@
 }
 
 SettingSyncData::SettingSyncData(const syncer::SyncData& sync_data)
-    : change_type_(syncer::SyncChange::ACTION_INVALID) {
+    : change_type_(base::nullopt) {
   ExtractSyncData(sync_data);
 }
 
diff --git a/chrome/browser/extensions/api/storage/setting_sync_data.h b/chrome/browser/extensions/api/storage/setting_sync_data.h
index 6522f30..fca45377 100644
--- a/chrome/browser/extensions/api/storage/setting_sync_data.h
+++ b/chrome/browser/extensions/api/storage/setting_sync_data.h
@@ -9,6 +9,7 @@
 #include <string>
 
 #include "base/macros.h"
+#include "base/optional.h"
 #include "base/values.h"
 #include "components/sync/model/sync_change.h"
 
@@ -25,7 +26,7 @@
   // Creates from a sync change.
   explicit SettingSyncData(const syncer::SyncChange& sync_change);
 
-  // Creates from sync data. |change_type| will be ACTION_INVALID.
+  // Creates from sync data. |change_type| will be base::nullopt.
   explicit SettingSyncData(const syncer::SyncData& sync_data);
 
   // Creates explicitly.
@@ -36,9 +37,10 @@
 
   ~SettingSyncData();
 
-  // May return ACTION_INVALID if this object represents sync data that isn't
+  // May return base::nullopt if this object represents sync data that isn't
   // associated with a sync operation.
-  syncer::SyncChange::SyncChangeType change_type() const {
+  const base::Optional<syncer::SyncChange::SyncChangeType>& change_type()
+      const {
     return change_type_;
   }
   const std::string& extension_id() const { return extension_id_; }
@@ -55,7 +57,7 @@
   // either an extension or app settings data type.
   void ExtractSyncData(const syncer::SyncData& sync_data);
 
-  syncer::SyncChange::SyncChangeType change_type_;
+  base::Optional<syncer::SyncChange::SyncChangeType> change_type_;
   std::string extension_id_;
   std::string key_;
   std::unique_ptr<base::Value> value_;
diff --git a/chrome/browser/extensions/api/storage/syncable_settings_storage.cc b/chrome/browser/extensions/api/storage/syncable_settings_storage.cc
index 9291132..3457a1f 100644
--- a/chrome/browser/extensions/api/storage/syncable_settings_storage.cc
+++ b/chrome/browser/extensions/api/storage/syncable_settings_storage.cc
@@ -289,7 +289,9 @@
 
     syncer::SyncError error;
 
-    switch (sync_change->change_type()) {
+    DCHECK(sync_change->change_type().has_value());
+
+    switch (*sync_change->change_type()) {
       case syncer::SyncChange::ACTION_ADD:
         if (!current_value.get()) {
           error = OnSyncAdd(key, std::move(change_value), &changes);
@@ -324,9 +326,6 @@
               extension_id_ << "/" << key;
         }
         break;
-
-      default:
-        NOTREACHED();
     }
 
     if (error.IsSet()) {
diff --git a/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc b/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc
index f52ce98..07ba5a3 100644
--- a/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc
+++ b/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc
@@ -407,6 +407,9 @@
                           base::FeatureList::IsEnabled(
                               chromeos::features::kHandwritingGestureEditing)));
   features->AppendString(GenerateFeatureFlag(
+      "multiword",
+      base::FeatureList::IsEnabled(chromeos::features::kAssistMultiWord)));
+  features->AppendString(GenerateFeatureFlag(
       "floatingkeyboarddefault",
       base::FeatureList::IsEnabled(
           chromeos::features::kVirtualKeyboardFloatingDefault)));
diff --git a/chrome/browser/extensions/component_loader.cc b/chrome/browser/extensions/component_loader.cc
index cdfc420..d082efa 100644
--- a/chrome/browser/extensions/component_loader.cc
+++ b/chrome/browser/extensions/component_loader.cc
@@ -53,6 +53,7 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/keyboard/ui/grit/keyboard_resources.h"
 #include "chromeos/constants/chromeos_features.h"
+#include "chromeos/constants/chromeos_pref_names.h"
 #include "chromeos/constants/chromeos_switches.h"
 #include "components/user_manager/user_manager.h"
 #include "content/public/browser/site_instance.h"
@@ -396,7 +397,15 @@
 }
 
 void ComponentLoader::AddChromeCameraApp() {
-  if (base::FeatureList::IsEnabled(chromeos::features::kCameraSystemWebApp)) {
+  // TODO(crbug.com/1135280): Remove all the logic here once CCA is fully
+  // migrated to SWA.
+
+  // If users should use the SWA version of CCA and the status from the platform
+  // app version is already migrated, there is no need to install the platform
+  // version of CCA.
+  if (base::FeatureList::IsEnabled(chromeos::features::kCameraSystemWebApp) &&
+      profile_->GetPrefs()->GetBoolean(
+          chromeos::prefs::kHasCameraAppMigratedToSWA)) {
     return;
   }
 
diff --git a/chrome/browser/extensions/corb_and_cors_extension_browsertest.cc b/chrome/browser/extensions/corb_and_cors_extension_browsertest.cc
index d4fe18e..a970c52 100644
--- a/chrome/browser/extensions/corb_and_cors_extension_browsertest.cc
+++ b/chrome/browser/extensions/corb_and_cors_extension_browsertest.cc
@@ -71,18 +71,6 @@
 
 namespace {
 
-enum TestParam {
-  // Whether the extension under test is "allowlisted" (see
-  // GetExtensionsAllowlist in
-  // //extensions/browser/url_loader_factory_manager.cc).
-  kAllowlisted = 1 << 0,
-
-  // Whether network::features::
-  // kDeriveOriginFromUrlForNeitherGetNorHeadRequestWhenHavingSpecialAccess is
-  // enabled.
-  kDeriveOriginFromUrl = 1 << 2,
-};
-
 const char kCorsErrorWhenFetching[] = "error: TypeError: Failed to fetch";
 
 // The manifest.json used by tests uses |kExpectedKey| that will result in the
@@ -197,7 +185,7 @@
 
 class CorbAndCorsExtensionBrowserTest
     : public CorbAndCorsExtensionTestBase,
-      public ::testing::WithParamInterface<TestParam> {
+      public ::testing::WithParamInterface<bool> {
  public:
   using Base = CorbAndCorsExtensionTestBase;
 
@@ -206,17 +194,6 @@
     std::vector<base::test::ScopedFeatureList::FeatureAndParams>
         enabled_features;
 
-    if (DeriveOriginFromUrl()) {
-      enabled_features.emplace_back(
-          network::features::
-              kDeriveOriginFromUrlForNeitherGetNorHeadRequestWhenHavingSpecialAccess,
-          base::FieldTrialParams());
-    } else {
-      disabled_features.push_back(
-          network::features::
-              kDeriveOriginFromUrlForNeitherGetNorHeadRequestWhenHavingSpecialAccess);
-    }
-
     if (IsExtensionAllowlisted()) {
       base::FieldTrialParams field_trial_params;
       field_trial_params.emplace(
@@ -240,13 +217,7 @@
         &policy_provider_);
   }
 
-  bool IsExtensionAllowlisted() {
-    return (GetParam() & TestParam::kAllowlisted) != 0;
-  }
-
-  bool DeriveOriginFromUrl() {
-    return (GetParam() & TestParam::kDeriveOriginFromUrl) != 0;
-  }
+  bool IsExtensionAllowlisted() { return GetParam(); }
 
   const Extension* InstallExtension(
       GURL resource_to_fetch_from_declarative_content_script = GURL()) {
@@ -1288,7 +1259,7 @@
 };
 INSTANTIATE_TEST_SUITE_P(Allowlisted_AllowlistForCors,
                          TrustTokenExtensionBrowserTest,
-                         ::testing::Values(TestParam::kAllowlisted));
+                         ::testing::Values(true));
 IN_PROC_BROWSER_TEST_P(
     TrustTokenExtensionBrowserTest,
     FromProgrammaticContentScript_TrustTokenRedemptionAllowed) {
@@ -2296,23 +2267,16 @@
 
 INSTANTIATE_TEST_SUITE_P(Allowlisted,
                          CorbAndCorsExtensionBrowserTest,
-                         ::testing::Values(TestParam::kAllowlisted));
+                         ::testing::Values(true));
 INSTANTIATE_TEST_SUITE_P(NotAllowlisted,
                          CorbAndCorsExtensionBrowserTest,
-                         ::testing::Values(0));
+                         ::testing::Values(false));
 
 INSTANTIATE_TEST_SUITE_P(Allowlisted_LegacyOriginHeaderBehavior,
                          OriginHeaderExtensionBrowserTest,
-                         ::testing::Values(TestParam::kAllowlisted));
-INSTANTIATE_TEST_SUITE_P(Allowlisted_NewOriginHeaderBehavior,
-                         OriginHeaderExtensionBrowserTest,
-                         ::testing::Values(TestParam::kAllowlisted |
-                                           TestParam::kDeriveOriginFromUrl));
+                         ::testing::Values(true));
 INSTANTIATE_TEST_SUITE_P(NotAllowlisted_LegacyOriginHeaderBehavior,
                          OriginHeaderExtensionBrowserTest,
-                         ::testing::Values(0));
-INSTANTIATE_TEST_SUITE_P(NotAllowlisted_NewOriginHeaderBehavior,
-                         OriginHeaderExtensionBrowserTest,
-                         ::testing::Values(TestParam::kDeriveOriginFromUrl));
+                         ::testing::Values(false));
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/extension_samesite_cookies_browsertest.cc b/chrome/browser/extensions/extension_samesite_cookies_browsertest.cc
index 6cc76d4..6cb45ce 100644
--- a/chrome/browser/extensions/extension_samesite_cookies_browsertest.cc
+++ b/chrome/browser/extensions/extension_samesite_cookies_browsertest.cc
@@ -362,8 +362,9 @@
 
 // Extension is site_for_cookies, initiator and requested URL are permitted,
 // initiator and requested URL are same-site => SameSite cookies are sent.
+// crbug.com/1153083: flaky on linux, win, and mac
 IN_PROC_BROWSER_TEST_P(ExtensionSameSiteCookiesTest,
-                       OnePermittedSameSiteFrame_Navigation) {
+                       DISABLED_OnePermittedSameSiteFrame_Navigation) {
   SetCookies(kPermittedHost);
   content::RenderFrameHost* main_frame = NavigateMainFrameToExtensionPage();
   content::RenderFrameHost* child_frame =
diff --git a/chrome/browser/extensions/fetch_apitest.cc b/chrome/browser/extensions/fetch_apitest.cc
index bfeac5a..b40a047 100644
--- a/chrome/browser/extensions/fetch_apitest.cc
+++ b/chrome/browser/extensions/fetch_apitest.cc
@@ -287,28 +287,7 @@
   EXPECT_EQ("basic", ExecuteScriptInBackgroundPage(extension->id(), script));
 }
 
-class ExtensionFetchPostOriginTest : public ExtensionFetchTest,
-                                     public testing::WithParamInterface<bool> {
- protected:
-  void SetUp() override {
-    if (GetParam()) {
-      scoped_feature_list_.InitAndEnableFeature(
-          network::features::
-              kDeriveOriginFromUrlForNeitherGetNorHeadRequestWhenHavingSpecialAccess);
-    } else {
-      scoped_feature_list_.InitAndDisableFeature(
-          network::features::
-              kDeriveOriginFromUrlForNeitherGetNorHeadRequestWhenHavingSpecialAccess);
-    }
-    ExtensionFetchTest::SetUp();
-  }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
-IN_PROC_BROWSER_TEST_P(ExtensionFetchPostOriginTest,
-                       OriginOnPostWithPermissions) {
+IN_PROC_BROWSER_TEST_F(ExtensionFetchTest, OriginOnPostWithPermissions) {
   TestExtensionDir dir;
   dir.WriteManifest(R"JSON(
      {
@@ -324,15 +303,12 @@
   GURL destination_url =
       embedded_test_server()->GetURL("example.com", "/echo-origin");
   std::string script = content::JsReplace(kFetchPostScript, destination_url);
-  std::string origin_string =
-      GetParam() ? url::Origin::Create(destination_url).Serialize()
-                 : url::Origin::Create(extension->url()).Serialize();
+  std::string origin_string = url::Origin::Create(extension->url()).Serialize();
   EXPECT_EQ(origin_string,
             ExecuteScriptInBackgroundPage(extension->id(), script));
 }
 
-IN_PROC_BROWSER_TEST_P(ExtensionFetchPostOriginTest,
-                       OriginOnPostWithoutPermissions) {
+IN_PROC_BROWSER_TEST_F(ExtensionFetchTest, OriginOnPostWithoutPermissions) {
   TestExtensionDir dir;
   dir.WriteManifest(R"JSON(
      {
@@ -352,14 +328,6 @@
             ExecuteScriptInBackgroundPage(extension->id(), script));
 }
 
-INSTANTIATE_TEST_SUITE_P(UseExtensionOrigin,
-                         ExtensionFetchPostOriginTest,
-                         testing::Values(false));
-
-INSTANTIATE_TEST_SUITE_P(UseDestinationUrlOrigin,
-                         ExtensionFetchPostOriginTest,
-                         testing::Values(true));
-
 // An extension background script should be able to fetch resources contained in
 // the extension, and those resources should not be opaque.
 IN_PROC_BROWSER_TEST_F(ExtensionFetchTest, ExtensionResourceShouldNotBeOpaque) {
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 2d7886c..184be0b 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1395,6 +1395,11 @@
     "expiry_milestone": 90
   },
   {
+    "name": "enable-cros-ime-assist-multi-word",
+    "owners": [ "curtismcmullan", "essential-inputs-team@google.com" ],
+    "expiry_milestone": 95
+  },
+  {
     "name": "enable-cros-ime-assist-personal-info",
     "owners": [ "jiwan", "essential-inputs-team@google.com" ],
     "expiry_milestone": 90
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 3cb1921..4fa6c8d 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -4281,6 +4281,11 @@
 const char kImeAssistAutocorrectDescription[] =
     "Enable assistive auto-correct features for native IME";
 
+const char kImeAssistMultiWordName[] =
+    "Enable assistive multi word suggestions";
+const char kImeAssistMultiWordDescription[] =
+    "Enable assistive multi word suggestions for native IME";
+
 const char kImeAssistPersonalInfoName[] = "Enable assistive personal info";
 const char kImeAssistPersonalInfoDescription[] =
     "Enable auto-complete suggestions on personal infomation for native IME.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 36e207b..667be75 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -2498,6 +2498,9 @@
 extern const char kImeAssistAutocorrectName[];
 extern const char kImeAssistAutocorrectDescription[];
 
+extern const char kImeAssistMultiWordName[];
+extern const char kImeAssistMultiWordDescription[];
+
 extern const char kImeAssistPersonalInfoName[];
 extern const char kImeAssistPersonalInfoDescription[];
 
diff --git a/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/services/PaintPreviewTabServiceTest.java b/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/services/PaintPreviewTabServiceTest.java
index 0e65d8c..cb72f9c 100644
--- a/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/services/PaintPreviewTabServiceTest.java
+++ b/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/services/PaintPreviewTabServiceTest.java
@@ -99,6 +99,8 @@
         activity.getWindow().setLocalFocus(true, true);
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             InstrumentationRegistry.getInstrumentation().callActivityOnRestart(activity);
+            InstrumentationRegistry.getInstrumentation().callActivityOnStart(activity);
+            InstrumentationRegistry.getInstrumentation().callActivityOnResume(activity);
         });
 
         TestThreadUtils.runOnUiThreadBlocking(() -> {
diff --git a/chrome/browser/permissions/prediction_based_permission_ui_selector.cc b/chrome/browser/permissions/prediction_based_permission_ui_selector.cc
index 2cddc45..50b4d53 100644
--- a/chrome/browser/permissions/prediction_based_permission_ui_selector.cc
+++ b/chrome/browser/permissions/prediction_based_permission_ui_selector.cc
@@ -51,6 +51,8 @@
     return;
   }
 
+  last_request_grant_likelihood_ = base::nullopt;
+
   DCHECK(!request_);
   permissions::PredictionService* service =
       PredictionServiceFactory::GetForProfile(profile_);
@@ -67,6 +69,11 @@
   callback_.Reset();
 }
 
+base::Optional<permissions::PermissionUmaUtil::PredictionGrantLikelihood>
+PredictionBasedPermissionUiSelector::PredictedGrantLikelihoodForUKM() {
+  return last_request_grant_likelihood_;
+}
+
 permissions::PredictionRequestFeatures
 PredictionBasedPermissionUiSelector::BuildPredictionRequestFeatures(
     permissions::PermissionRequest* request) {
@@ -129,6 +136,9 @@
     return;
   }
 
+  last_request_grant_likelihood_ =
+      response->suggestion(0).grant_likelihood().discretized_likelihood();
+
   if (response->suggestion(0).grant_likelihood().discretized_likelihood() ==
       VeryUnlikely) {
     std::move(callback_).Run(Decision(
diff --git a/chrome/browser/permissions/prediction_based_permission_ui_selector.h b/chrome/browser/permissions/prediction_based_permission_ui_selector.h
index d8511911..20781d3 100644
--- a/chrome/browser/permissions/prediction_based_permission_ui_selector.h
+++ b/chrome/browser/permissions/prediction_based_permission_ui_selector.h
@@ -38,6 +38,9 @@
 
   void Cancel() override;
 
+  base::Optional<permissions::PermissionUmaUtil::PredictionGrantLikelihood>
+  PredictedGrantLikelihoodForUKM() override;
+
  private:
   permissions::PredictionRequestFeatures BuildPredictionRequestFeatures(
       permissions::PermissionRequest* request);
@@ -49,6 +52,9 @@
 
   Profile* profile_;
   std::unique_ptr<PredictionServiceRequest> request_;
+  base::Optional<permissions::PermissionUmaUtil::PredictionGrantLikelihood>
+      last_request_grant_likelihood_;
+
   DecisionMadeCallback callback_;
 };
 
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 1cfa576..8c4da83 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -194,7 +194,6 @@
 #if BUILDFLAG(ENABLE_PLUGINS)
 #include "chrome/browser/plugins/plugin_info_host_impl.h"
 #include "chrome/browser/plugins/plugins_resource_service.h"
-#include "chrome/browser/renderer_host/pepper/device_id_fetcher.h"
 #endif
 
 #if BUILDFLAG(ENABLE_SUPERVISED_USERS)
@@ -491,6 +490,9 @@
 const char kSettingsLaunchedPasswordChecks[] =
     "profile.settings_launched_password_checks";
 
+// Deprecated 11/2020
+const char kDRMSalt[] = "settings.privacy.drm_salt";
+
 // Register local state used only for migration (clearing or moving to a new
 // key).
 void RegisterLocalStatePrefsForMigration(PrefRegistrySimple* registry) {
@@ -568,6 +570,7 @@
 #endif
 
   registry->RegisterIntegerPref(kSettingsLaunchedPasswordChecks, 0);
+  registry->RegisterStringPref(kDRMSalt, "");
 }
 
 }  // namespace
@@ -866,7 +869,6 @@
 #endif
 
 #if BUILDFLAG(ENABLE_PLUGINS)
-  DeviceIDFetcher::RegisterProfilePrefs(registry);
   PluginInfoHostImpl::RegisterUserPrefs(registry);
 #endif
 
@@ -1172,4 +1174,7 @@
 
   // Added 11/2020
   profile_prefs->ClearPref(kSettingsLaunchedPasswordChecks);
+
+  // Added 11/2020
+  profile_prefs->ClearPref(kDRMSalt);
 }
diff --git a/chrome/browser/profiles/profile_metrics.h b/chrome/browser/profiles/profile_metrics.h
index 044deee..440fc1c 100644
--- a/chrome/browser/profiles/profile_metrics.h
+++ b/chrome/browser/profiles/profile_metrics.h
@@ -79,10 +79,10 @@
   // These values are persisted to logs. Entries should not be renumbered and
   // numeric values should never be reused.
   enum ProfileSync {
-    SYNC_CUSTOMIZE = 0,       // User decided to customize sync
-    SYNC_CHOOSE,              // User chose what to sync
-    SYNC_ENCRYPT,             // User has chosen to encrypt all data
-    SYNC_PASSPHRASE,          // User is using a passphrase
+    SYNC_CUSTOMIZE = 0,           // User decided to customize sync
+    SYNC_CHOOSE,                  // User chose what to sync
+    SYNC_CREATED_NEW_PASSPHRASE,  // User created a passphrase to encrypt data
+    SYNC_ENTERED_EXISTING_PASSPHRASE,  // User entered an existing passphrase
     NUM_PROFILE_SYNC_METRICS
   };
 
diff --git a/chrome/browser/renderer_host/pepper/device_id_fetcher.cc b/chrome/browser/renderer_host/pepper/device_id_fetcher.cc
deleted file mode 100644
index ac59ea46..0000000
--- a/chrome/browser/renderer_host/pepper/device_id_fetcher.cc
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/renderer_host/pepper/device_id_fetcher.h"
-
-#include "chrome/common/pref_names.h"
-#include "components/pref_registry/pref_registry_syncable.h"
-
-// static
-void DeviceIDFetcher::RegisterProfilePrefs(
-    user_prefs::PrefRegistrySyncable* prefs) {
-  prefs->RegisterBooleanPref(prefs::kEnableDRM, true);
-  prefs->RegisterStringPref(prefs::kDRMSalt, "");
-}
diff --git a/chrome/browser/renderer_host/pepper/device_id_fetcher.h b/chrome/browser/renderer_host/pepper/device_id_fetcher.h
deleted file mode 100644
index f5892c0..0000000
--- a/chrome/browser/renderer_host/pepper/device_id_fetcher.h
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_RENDERER_HOST_PEPPER_DEVICE_ID_FETCHER_H_
-#define CHROME_BROWSER_RENDERER_HOST_PEPPER_DEVICE_ID_FETCHER_H_
-
-namespace user_prefs {
-class PrefRegistrySyncable;
-}
-
-// This class has been followed out and now simply hosts a profile pref.
-// TODO(crbug.com/1152871): Remove or migrate those, too.
-class DeviceIDFetcher {
- public:
-  DeviceIDFetcher() = delete;
-
-  // Called to register the |kEnableDRM| and |kDRMSalt| preferences.
-  static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* prefs);
-};
-
-#endif  // CHROME_BROWSER_RENDERER_HOST_PEPPER_DEVICE_ID_FETCHER_H_
diff --git a/chrome/browser/resources/chromeos/login/BUILD.gn b/chrome/browser/resources/chromeos/login/BUILD.gn
index 5db088b..d3d4e10a 100644
--- a/chrome/browser/resources/chromeos/login/BUILD.gn
+++ b/chrome/browser/resources/chromeos/login/BUILD.gn
@@ -43,6 +43,7 @@
     ":oobe_network",
     ":oobe_reset",
     ":oobe_screen_assistant_optin_flow",
+    ":oobe_screen_auto_enrollment_check",
     ":oobe_screen_enable_debugging",
     ":oobe_select",
     ":oobe_supervision_transition",
@@ -99,7 +100,7 @@
 js_library("active_directory_password_change") {
   deps = [
     "components/oobe_dialog:oobe_dialog",
-    "components/oobe_i18n_behavior:oobe_i18n_behavior"
+    "components/oobe_i18n_behavior:oobe_i18n_behavior",
   ]
 }
 
@@ -255,8 +256,8 @@
   deps = [
     ":web_view_helper",
     "components:login_screen_behavior",
-    "components/oobe_dialog:oobe_dialog",
     "components:oobe_dialog_host_behavior",
+    "components/oobe_dialog:oobe_dialog",
     "components/oobe_i18n_behavior:oobe_i18n_behavior",
   ]
 }
@@ -290,6 +291,13 @@
   ]
 }
 
+js_library("oobe_screen_auto_enrollment_check") {
+  deps = [
+    "components:login_screen_behavior",
+    "components/oobe_i18n_behavior:oobe_i18n_behavior",
+  ]
+}
+
 js_library("oobe_screen_enable_debugging") {
   deps = [
     "components:login_screen_behavior",
diff --git a/chrome/browser/resources/chromeos/login/md_login.html b/chrome/browser/resources/chromeos/login/md_login.html
index 045e89a..43df1b66 100644
--- a/chrome/browser/resources/chromeos/login/md_login.html
+++ b/chrome/browser/resources/chromeos/login/md_login.html
@@ -55,7 +55,6 @@
 <script defer src="chrome://oobe/test_api.js"></script>
 <link rel="stylesheet" href="api_keys_notice.css">
 <link rel="stylesheet" href="oobe_screen_autolaunch.css">
-<link rel="stylesheet" href="oobe_screen_auto_enrollment_check.css">
 <link rel="stylesheet" href="screen_error_message.css">
 
 <script src="chrome://oobe/keyboard_utils.js"></script>
diff --git a/chrome/browser/resources/chromeos/login/oobe.html b/chrome/browser/resources/chromeos/login/oobe.html
index 36890e6..d095710 100644
--- a/chrome/browser/resources/chromeos/login/oobe.html
+++ b/chrome/browser/resources/chromeos/login/oobe.html
@@ -60,7 +60,6 @@
 <link rel="stylesheet" href="api_keys_notice.css">
 
 <link rel="stylesheet" href="oobe_screen_autolaunch.css">
-<link rel="stylesheet" href="oobe_screen_auto_enrollment_check.css">
 
 <link rel="stylesheet" href="screen_error_message.css">
 
diff --git a/chrome/browser/resources/chromeos/login/oobe.js b/chrome/browser/resources/chromeos/login/oobe.js
index 2115305..ce4dd5c 100644
--- a/chrome/browser/resources/chromeos/login/oobe.js
+++ b/chrome/browser/resources/chromeos/login/oobe.js
@@ -28,7 +28,6 @@
 // <include src="screen_multidevice_setup.js">
 
 // <include src="../../gaia_auth_host/authenticator.js">
-// <include src="oobe_screen_auto_enrollment_check.js">
 // <include src="multi_tap_detector.js">
 // <include src="web_view_helper.js">
 
@@ -41,7 +40,6 @@
        */
       initialize() {
         cr.ui.login.DisplayManager.initialize();
-        login.AutoEnrollmentCheckScreen.register();
         login.AutolaunchScreen.register();
         login.AccountPickerScreen.register();
         login.ErrorMessageScreen.register();
diff --git a/chrome/browser/resources/chromeos/login/oobe_screen_auto_enrollment_check.css b/chrome/browser/resources/chromeos/login/oobe_screen_auto_enrollment_check.css
deleted file mode 100644
index f718523..0000000
--- a/chrome/browser/resources/chromeos/login/oobe_screen_auto_enrollment_check.css
+++ /dev/null
@@ -1,16 +0,0 @@
-/* Copyright 2014 The Chromium Authors. All rights reserved.
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file. */
-
-#auto-enrollment-check {
-  min-height: 395px;
-  padding: 70px 17px 21px;
-}
-
-#auto-enrollment-check #auto-enrollment-check-progress {
-  color: #9c9c9c;
-  display: flex;
-  justify-content: center;
-  margin-top: 130px;
-  min-height: 0;
-}
diff --git a/chrome/browser/resources/chromeos/login/oobe_screen_auto_enrollment_check.html b/chrome/browser/resources/chromeos/login/oobe_screen_auto_enrollment_check.html
index 3990cab..cbaf814 100644
--- a/chrome/browser/resources/chromeos/login/oobe_screen_auto_enrollment_check.html
+++ b/chrome/browser/resources/chromeos/login/oobe_screen_auto_enrollment_check.html
@@ -1,9 +1,33 @@
-<div class="step hidden" id="auto-enrollment-check" role="group"
-     i18n-values="aria-label:autoEnrollmentCheckScreenHeader" hidden>
-  <div class="step-contents">
-    <div id="auto-enrollment-check-progress" aria-live="polite">
-      <div class="throbber"></div>
-      <div i18n-content="autoEnrollmentCheckMessage"></div>
+<!-- Copyright 2020 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+
+<dom-module id="auto-enrollment-check-element">
+  <template>
+    <link rel="stylesheet" href="chrome://resources/css/throbber.css">
+    <link rel="stylesheet" href="../../../../../ui/login/oobe.css">
+    <style>
+      #auto-enrollment-check {
+        min-height: 395px;
+        padding: 70px 17px 21px;
+      }
+
+      #auto-enrollment-check #auto-enrollment-check-progress {
+        color: #9c9c9c;
+        display: flex;
+        justify-content: center;
+        margin-top: 130px;
+        min-height: 0;
+      }
+    </style>
+    <div id="auto-enrollment-check" role="group"
+        i18n-values="aria-label:autoEnrollmentCheckScreenHeader">
+      <div class="step-contents">
+        <div id="auto-enrollment-check-progress" aria-live="polite">
+          <div class="throbber"></div>
+          [[i18nDynamic(locale, 'autoEnrollmentCheckMessage')]]
+        </div>
+      </div>
     </div>
-  </div>
-</div>
+  </template>
+</dom-module>
diff --git a/chrome/browser/resources/chromeos/login/oobe_screen_auto_enrollment_check.js b/chrome/browser/resources/chromeos/login/oobe_screen_auto_enrollment_check.js
index 1065eb88..70eea17 100644
--- a/chrome/browser/resources/chromeos/login/oobe_screen_auto_enrollment_check.js
+++ b/chrome/browser/resources/chromeos/login/oobe_screen_auto_enrollment_check.js
@@ -6,7 +6,14 @@
  * @fileoverview Oobe Auto-enrollment check screen implementation.
  */
 
-login.createScreen(
-    'AutoEnrollmentCheckScreen', 'auto-enrollment-check', function() {
-      return {EXTERNAL_API: []};
+Polymer({
+  is: 'auto-enrollment-check-element',
+
+  behaviors: [OobeI18nBehavior, LoginScreenBehavior],
+
+  ready() {
+    this.initializeLoginScreen('AutoEnrollmentCheckScreen', {
+      resetAllowed: true,
     });
+  },
+});
diff --git a/chrome/browser/resources/chromeos/login/structure/components_oobe.html b/chrome/browser/resources/chromeos/login/structure/components_oobe.html
index 2618d65..1689a189 100644
--- a/chrome/browser/resources/chromeos/login/structure/components_oobe.html
+++ b/chrome/browser/resources/chromeos/login/structure/components_oobe.html
@@ -6,6 +6,7 @@
 
 <include src="../oobe_hid_detection.html">
 <include src="../oobe_i18n_dropdown.html">
+<include src="../oobe_screen_auto_enrollment_check.html">
 <include src="../oobe_screen_enable_debugging.html">
 <include src="../oobe_welcome_dialog.html">
 <include src="../oobe_welcome.html">
diff --git a/chrome/browser/resources/chromeos/login/structure/components_oobe.js b/chrome/browser/resources/chromeos/login/structure/components_oobe.js
index 0f246fd..1f4b99d 100644
--- a/chrome/browser/resources/chromeos/login/structure/components_oobe.js
+++ b/chrome/browser/resources/chromeos/login/structure/components_oobe.js
@@ -4,6 +4,7 @@
 
 // <include src="../oobe_hid_detection.js">
 // <include src="../oobe_i18n_dropdown.js">
+// <include src="../oobe_screen_auto_enrollment_check.js">
 // <include src="../oobe_screen_enable_debugging.js">
 // <include src="../oobe_welcome_dialog.js">
 // <include src="../oobe_welcome.js">
diff --git a/chrome/browser/resources/chromeos/login/structure/screens_oobe.html b/chrome/browser/resources/chromeos/login/structure/screens_oobe.html
index a6c4b83..17e8c2f 100644
--- a/chrome/browser/resources/chromeos/login/structure/screens_oobe.html
+++ b/chrome/browser/resources/chromeos/login/structure/screens_oobe.html
@@ -18,7 +18,8 @@
 </oobe-eula-element>
 <oobe-update-element id="oobe-update" class="step hidden" hidden>
 </oobe-update-element>
-<include src="../oobe_screen_auto_enrollment_check.html">
+<auto-enrollment-check-element id="auto-enrollment-check" class="step hidden" hidden>
+</auto-enrollment-check-element>
 <demo-preferences-element id="demo-preferences" class="step hidden" hidden>
 </demo-preferences-element>
 <enterprise-enrollment-element id="enterprise-enrollment" class="step hidden"
diff --git a/chrome/browser/resources/settings/autofill_page/password_check.js b/chrome/browser/resources/settings/autofill_page/password_check.js
index f193afc..c8c52b1 100644
--- a/chrome/browser/resources/settings/autofill_page/password_check.js
+++ b/chrome/browser/resources/settings/autofill_page/password_check.js
@@ -530,8 +530,10 @@
    * @private
    */
   showsTimestamp_() {
-    return this.status.state === CheckState.IDLE &&
-        !!this.status.elapsedTimeSinceLastCheck;
+    return !!this.status.elapsedTimeSinceLastCheck &&
+        (this.status.state === CheckState.IDLE ||
+         (this.status.state === CheckState.SIGNED_OUT &&
+          this.passwordsWeaknessCheckEnabled));
   },
 
   /**
diff --git a/chrome/browser/resources/settings/people_page/sync_browser_proxy.js b/chrome/browser/resources/settings/people_page/sync_browser_proxy.js
index eb9e0ef9..a3f5574 100644
--- a/chrome/browser/resources/settings/people_page/sync_browser_proxy.js
+++ b/chrome/browser/resources/settings/people_page/sync_browser_proxy.js
@@ -72,7 +72,6 @@
    *   extensionsRegistered: boolean,
    *   extensionsSynced: boolean,
    *   fullEncryptionBody: string,
-   *   passphrase: (string|undefined),
    *   passphraseRequired: boolean,
    *   passwordsRegistered: boolean,
    *   passwordsSynced: boolean,
@@ -81,7 +80,6 @@
    *   preferencesSynced: boolean,
    *   readingListRegistered: boolean,
    *   readingListSynced: boolean,
-   *   setNewPassphrase: (boolean|undefined),
    *   syncAllDataTypes: boolean,
    *   tabsRegistered: boolean,
    *   tabsSynced: boolean,
@@ -198,13 +196,20 @@
     setSyncDatatypes(syncPrefs) {}
 
     /**
-     * Sets the sync encryption options.
-     * @param {!settings.SyncPrefs} syncPrefs
-     * @return {!Promise<!settings.PageStatus>}
+     * Attempts to set up a new passphrase to encrypt Sync data.
+     * @param {string} passphrase
+     * @return {!Promise<boolean>} Whether the passphrase was successfully set.
+     * The call can fail, for example, if encrypting the data is disallowed.
      */
-    // TODO(crbug.com/1139060): Use a clear signature which doesn't rely on
-    // syncPrefs.
-    setSyncEncryption(syncPrefs) {}
+    setEncryptionPassphrase(passphrase) {}
+
+    /**
+     * Attempts to set the passphrase to decrypt Sync data.
+     * @param {string} passphrase
+     * @return {!Promise<boolean>} Whether the passphrase was successfully set.
+     * The call can fail, for example, if the passphrase is incorrect.
+     */
+    setDecryptionPassphrase(passphrase) {}
 
     /**
      * Start syncing with an account, specified by its email.
@@ -312,9 +317,13 @@
     }
 
     /** @override */
-    setSyncEncryption(syncPrefs) {
-      return cr.sendWithPromise(
-          'SyncSetupSetEncryption', JSON.stringify(syncPrefs));
+    setEncryptionPassphrase(passphrase) {
+      return cr.sendWithPromise('SyncSetupSetEncryptionPassphrase', passphrase);
+    }
+
+    /** @override */
+    setDecryptionPassphrase(passphrase) {
+      return cr.sendWithPromise('SyncSetupSetDecryptionPassphrase', passphrase);
     }
 
     /** @override */
diff --git a/chrome/browser/resources/settings/people_page/sync_encryption_options.js b/chrome/browser/resources/settings/people_page/sync_encryption_options.js
index 98d8f48..0708d6c 100644
--- a/chrome/browser/resources/settings/people_page/sync_encryption_options.js
+++ b/chrome/browser/resources/settings/people_page/sync_encryption_options.js
@@ -68,6 +68,13 @@
   },
 
   /**
+   * Whether there's a setEncryptionPassphrase() call pending response, in which
+   * case the component should wait before making a new call.
+   * @private {boolean}
+   */
+  isSettingEncryptionPassphrase_: false,
+
+  /**
    * Returns the encryption options CrRadioGroupElement.
    * @return {?CrRadioGroupElement}
    */
@@ -137,24 +144,26 @@
   saveNewPassphrase_() {
     assert(this.creatingNewPassphrase_);
     chrome.metricsPrivate.recordUserAction('Sync_SaveNewPassphraseClicked');
-    // Might happen within the transient time between the request to
-    // |setSyncEncryption| and receiving the response.
-    if (this.syncPrefs.setNewPassphrase) {
+
+    if (this.isSettingEncryptionPassphrase_) {
       return;
     }
+
     // If a new password has been entered but it is invalid, do not send the
     // sync state to the API.
     if (!this.validateCreatedPassphrases_()) {
       return;
     }
 
-    this.syncPrefs.setNewPassphrase = true;
-    this.syncPrefs.passphrase = this.passphrase_;
-
+    this.isSettingEncryptionPassphrase_ = true;
     settings.SyncBrowserProxyImpl.getInstance()
-        .setSyncEncryption(this.syncPrefs)
-        .then(pageStatus => {
-          this.fire('passphrase-changed', pageStatus);
+        .setEncryptionPassphrase(this.passphrase_)
+        .then(successfullySet => {
+          // TODO(crbug.com/1139060): Rename the event, there is no change if
+          // |successfullySet| is false. It should also mention 'encryption
+          // passphrase' in its name.
+          this.fire('passphrase-changed', {didChange: successfullySet});
+          this.isSettingEncryptionPassphrase_ = false;
         });
   },
 
diff --git a/chrome/browser/resources/settings/people_page/sync_page.js b/chrome/browser/resources/settings/people_page/sync_page.js
index 6469b5f..f31b881 100644
--- a/chrome/browser/resources/settings/people_page/sync_page.js
+++ b/chrome/browser/resources/settings/people_page/sync_page.js
@@ -491,22 +491,23 @@
       return;
     }
 
-    this.syncPrefs.setNewPassphrase = false;
+    this.browserProxy_.setDecryptionPassphrase(this.existingPassphrase_)
+        .then(
+            sucessfullySet => this.handlePageStatusChanged_(
+                sucessfullySet ? settings.PageStatus.DONE :
+                                 settings.PageStatus.PASSPHRASE_FAILED));
 
-    this.syncPrefs.passphrase = this.existingPassphrase_;
     this.existingPassphrase_ = '';
-
-    this.browserProxy_.setSyncEncryption(this.syncPrefs)
-        .then(this.handlePageStatusChanged_.bind(this));
   },
 
   /**
    * @private
-   * @param {!CustomEvent<!settings.PageStatus>} e
+   * @param {!CustomEvent<!{didChange: boolean}>} e
    */
   onPassphraseChanged_(e) {
     this.handlePageStatusChanged_(
-        /** @type {!settings.PageStatus} */ (e.detail));
+        e.detail.didChange ? settings.PageStatus.DONE :
+                             settings.PageStatus.PASSPHRASE_FAILED);
   },
 
   /**
diff --git a/chrome/browser/search_engines/template_url_service_sync_unittest.cc b/chrome/browser/search_engines/template_url_service_sync_unittest.cc
index e2d2c10..27a851f 100644
--- a/chrome/browser/search_engines/template_url_service_sync_unittest.cc
+++ b/chrome/browser/search_engines/template_url_service_sync_unittest.cc
@@ -143,7 +143,8 @@
 
   change_map_.erase(change_map_.begin(), change_map_.end());
   for (auto iter = change_list.begin(); iter != change_list.end(); ++iter)
-    change_map_[GetGUID(iter->sync_data())] = *iter;
+    change_map_.emplace(GetGUID(iter->sync_data()), *iter);
+
   return base::nullopt;
 }
 
diff --git a/chrome/browser/settings/android/java/src/org/chromium/chrome/browser/settings/SettingsActivityTestRule.java b/chrome/browser/settings/android/java/src/org/chromium/chrome/browser/settings/SettingsActivityTestRule.java
index 250dbd2e..22bcb58 100644
--- a/chrome/browser/settings/android/java/src/org/chromium/chrome/browser/settings/SettingsActivityTestRule.java
+++ b/chrome/browser/settings/android/java/src/org/chromium/chrome/browser/settings/SettingsActivityTestRule.java
@@ -8,17 +8,15 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.support.test.InstrumentationRegistry;
-import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.lifecycle.Stage;
 
 import androidx.fragment.app.Fragment;
 
-import org.hamcrest.Matchers;
 import org.junit.Assert;
 
-import org.chromium.base.ActivityState;
-import org.chromium.base.ApplicationStatus;
-import org.chromium.base.test.util.Criteria;
-import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.base.test.BaseActivityTestRule;
+import org.chromium.base.test.util.ApplicationTestUtils;
+
 /**
  * Activity test rule that launch {@link SettingsActivity} in tests.
  *
@@ -28,7 +26,7 @@
  * @param <T> Fragment that will be attached to the SettingsActivity.
  */
 public class SettingsActivityTestRule<T extends Fragment>
-        extends ActivityTestRule<SettingsActivity> {
+        extends BaseActivityTestRule<SettingsActivity> {
     private final Class<T> mFragmentClass;
 
     /**
@@ -36,16 +34,7 @@
      * @param fragmentClass Fragment that will be attached after the activity starts.
      */
     public SettingsActivityTestRule(Class<T> fragmentClass) {
-        this(fragmentClass, false);
-    }
-
-    /**
-     * Create the settings activity test rule with an specific fragment class.
-     * @param fragmentClass Fragment that will be attached after the activity starts.
-     * @param initialTouchMode Whether in touch mode after the activity starts.
-     */
-    public SettingsActivityTestRule(Class<T> fragmentClass, boolean initialTouchMode) {
-        super(SettingsActivity.class, initialTouchMode, false);
+        super(SettingsActivity.class);
         mFragmentClass = fragmentClass;
     }
 
@@ -67,35 +56,9 @@
         SettingsLauncher settingsLauncher = new SettingsLauncherImpl();
         Intent intent = settingsLauncher.createSettingsActivityIntent(
                 context, mFragmentClass.getName(), fragmentArgs);
-        SettingsActivity activity = super.launchActivity(intent);
-        Assert.assertNotNull(activity);
-
-        return activity;
-    }
-
-    /**
-     * We need to ensure that SettingsActivity gets destroyed in the TestRule because sometimes
-     * it uses the mock signin environment like fake AccountManagerFacade, if the activity starts
-     * with the stub then it also needs to finish with it. That's why we need to wait till the
-     * activity state becomes destroyed before tearing down the mock signin environment.
-     */
-    @Override
-    protected void afterActivityFinished() {
-        super.afterActivityFinished();
-        waitTillActivityIsDestroyed();
-    }
-
-    /**
-     * Block the execution till the SettingsActivity is destroyed.
-     */
-    public void waitTillActivityIsDestroyed() {
-        SettingsActivity activity = getActivity();
-        if (activity != null) {
-            CriteriaHelper.pollUiThread(() -> {
-                Criteria.checkThat(ApplicationStatus.getStateForActivity(activity),
-                        Matchers.is(ActivityState.DESTROYED));
-            });
-        }
+        launchActivity(intent);
+        ApplicationTestUtils.waitForActivityState(getActivity(), Stage.RESUMED);
+        return getActivity();
     }
 
     /**
diff --git a/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc b/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc
index 887452b..57cfd3c 100644
--- a/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc
+++ b/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc
@@ -401,8 +401,6 @@
         dictionary_change->RemoveWord(word);
         break;
       case syncer::SyncChange::ACTION_UPDATE:
-        // Intentionally fall through.
-      case syncer::SyncChange::ACTION_INVALID:
         return syncer::ConvertToModelError(
             sync_error_handler_->CreateAndUploadError(
                 FROM_HERE, "Processing sync changes failed on change type " +
diff --git a/chrome/browser/supervised_user/supervised_user_allowlist_service.cc b/chrome/browser/supervised_user/supervised_user_allowlist_service.cc
index 2d8454a3..f03cf92 100644
--- a/chrome/browser/supervised_user/supervised_user_allowlist_service.cc
+++ b/chrome/browser/supervised_user/supervised_user_allowlist_service.cc
@@ -258,10 +258,6 @@
         allowlists_removed = true;
         break;
       }
-      case syncer::SyncChange::ACTION_INVALID: {
-        NOTREACHED();
-        break;
-      }
     }
   }
 
diff --git a/chrome/browser/supervised_user/supervised_user_settings_service.cc b/chrome/browser/supervised_user/supervised_user_settings_service.cc
index 0460c14d..4682b00 100644
--- a/chrome/browser/supervised_user/supervised_user_settings_service.cc
+++ b/chrome/browser/supervised_user/supervised_user_settings_service.cc
@@ -351,10 +351,6 @@
         dict->RemoveKey(key);
         break;
       }
-      case SyncChange::ACTION_INVALID: {
-        NOTREACHED();
-        break;
-      }
     }
   }
   store_->ReportValueChanged(kAtomicSettings,
diff --git a/chrome/browser/ui/prefs/prefs_tab_helper.cc b/chrome/browser/ui/prefs/prefs_tab_helper.cc
index 4a60728..1d2032c 100644
--- a/chrome/browser/ui/prefs/prefs_tab_helper.cc
+++ b/chrome/browser/ui/prefs/prefs_tab_helper.cc
@@ -357,6 +357,7 @@
       prefs::kEnableReferrers,
       !base::FeatureList::IsEnabled(features::kNoReferrers));
   registry->RegisterBooleanPref(prefs::kEnableEncryptedMedia, true);
+  registry->RegisterBooleanPref(prefs::kEnableDRM, true);
   registry->RegisterBooleanPref(prefs::kScrollToTextFragmentEnabled, true);
 #if defined(OS_ANDROID)
   registry->RegisterDoublePref(prefs::kWebKitFontScaleFactor, 1.0);
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view.cc
index 06693bc..0a3adea 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view.cc
@@ -303,13 +303,13 @@
   }
 
   bool should_leave_to_top_container = false;
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
   // In immersive mode, the caption buttons container is reparented to the
   // TopContainerView and hence |rect| should not be claimed here.  See
-  // BrowserNonClientFrameViewAsh::OnImmersiveRevealStarted().
+  // BrowserNonClientFrameViewChromeOS::OnImmersiveRevealStarted().
   should_leave_to_top_container =
       browser_view_->immersive_mode_controller()->IsRevealed();
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif
 
   if (!browser_view_->IsTabStripVisible()) {
     // Claim |rect| if it is above the top of the topmost client area view.
diff --git a/chrome/browser/ui/views/profiles/profile_picker_interactive_uitest.cc b/chrome/browser/ui/views/profiles/profile_picker_interactive_uitest.cc
new file mode 100644
index 0000000..0410261
--- /dev/null
+++ b/chrome/browser/ui/views/profiles/profile_picker_interactive_uitest.cc
@@ -0,0 +1,196 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/profiles/profile_picker_view.h"
+
+#include "base/check.h"
+#include "base/run_loop.h"
+#include "base/test/mock_callback.h"
+#include "build/build_config.h"
+#include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/lifetime/browser_shutdown.h"
+#include "chrome/browser/ui/profile_picker.h"
+#include "chrome/browser/ui/views/profiles/profile_picker_test_base.h"
+#include "chrome/test/base/interactive_test_utils.h"
+#include "content/public/browser/notification_source.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_utils.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/keycodes/dom/dom_key.h"
+#include "ui/views/view.h"
+#include "ui/views/view_observer.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_observer.h"
+
+namespace {
+
+// Waits until a view is deleted.
+class ViewDeletedWaiter : public views::ViewObserver {
+ public:
+  explicit ViewDeletedWaiter(views::View* view) {
+    DCHECK(view);
+    observation_.Observe(view);
+  }
+  ~ViewDeletedWaiter() override = default;
+
+  // Waits until the view is deleted.
+  void Wait() { run_loop_.Run(); }
+
+ private:
+  // ViewObserver:
+  void OnViewIsDeleting(views::View* observed_view) override {
+    run_loop_.Quit();
+  }
+
+  base::RunLoop run_loop_;
+  base::ScopedObservation<views::View, views::ViewObserver> observation_{this};
+};
+
+// Waits until the widget bounds change.
+class WidgetBoundsChangeWaiter : public views::WidgetObserver {
+ public:
+  explicit WidgetBoundsChangeWaiter(views::Widget* widget) {
+    DCHECK(widget);
+    observation_.Observe(widget);
+  }
+
+  // Waits until the widget bounds change.
+  void Wait() { run_loop_.Run(); }
+
+ private:
+  // WidgetObserver:
+  void OnWidgetBoundsChanged(views::Widget* widget,
+                             const gfx::Rect& new_bounds) override {
+    run_loop_.Quit();
+  }
+
+  base::RunLoop run_loop_;
+  base::ScopedObservation<views::Widget, views::WidgetObserver> observation_{
+      this};
+};
+
+}  // namespace
+
+class ProfilePickerInteractiveUiTest : public ProfilePickerTestBase {
+ public:
+  ProfilePickerInteractiveUiTest() = default;
+  ~ProfilePickerInteractiveUiTest() override = default;
+
+  void SendCloseWindowKeyboardCommand() {
+    // Close window using keyboard.
+#if defined(OS_MAC)
+    // Use Cmd-W on Mac.
+    bool control = false;
+    bool shift = false;
+    bool command = true;
+#else
+    // Use Ctrl-Shift-W on other platforms.
+    bool control = true;
+    bool shift = true;
+    bool command = false;
+#endif
+    ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
+        widget()->GetNativeWindow(), ui::VKEY_W, control, shift, /*alt=*/false,
+        command));
+  }
+
+  void WaitForPickerClosed() {
+    if (!ProfilePicker::IsOpen())
+      return;
+    ViewDeletedWaiter(view()).Wait();
+    ASSERT_FALSE(ProfilePicker::IsOpen());
+  }
+};
+
+// Checks that the main picker view can be closed with keyboard shortcut.
+IN_PROC_BROWSER_TEST_F(ProfilePickerInteractiveUiTest, CloseWithKeyboard) {
+  // Open a new picker.
+  ProfilePicker::Show(ProfilePicker::EntryPoint::kProfileMenuManageProfiles);
+  WaitForNewWebView();
+  WaitForFirstPaint(web_contents(), GURL("chrome://profile-picker"));
+  EXPECT_TRUE(ProfilePicker::IsOpen());
+  SendCloseWindowKeyboardCommand();
+  WaitForPickerClosed();
+  // Closing the picker does not exit Chrome.
+  EXPECT_FALSE(browser_shutdown::IsTryingToQuit());
+}
+
+#if defined(OS_MAC)
+// Checks that Chrome be closed with keyboard shortcut. Only MacOS has a
+// keyboard shortcut to exit Chrome.
+IN_PROC_BROWSER_TEST_F(ProfilePickerInteractiveUiTest, ExitWithKeyboard) {
+  // Open a new picker.
+  ProfilePicker::Show(ProfilePicker::EntryPoint::kProfileMenuManageProfiles);
+  WaitForNewWebView();
+  WaitForFirstPaint(web_contents(), GURL("chrome://profile-picker"));
+  EXPECT_TRUE(ProfilePicker::IsOpen());
+
+  content::WindowedNotificationObserver terminate_observer(
+      chrome::NOTIFICATION_APP_TERMINATING,
+      content::NotificationService::AllSources());
+  // Send Cmd-Q.
+  ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
+      widget()->GetNativeWindow(), ui::VKEY_Q, /*control=*/false,
+      /*shift=*/false, /*alt=*/false, /*command=*/true));
+  // Check that Chrome is quitting.
+  terminate_observer.Wait();
+  WaitForPickerClosed();
+  EXPECT_TRUE(browser_shutdown::IsTryingToQuit());
+}
+#endif
+
+// Checks that the main picker view can switch to full screen.
+IN_PROC_BROWSER_TEST_F(ProfilePickerInteractiveUiTest, FullscreenWithKeyboard) {
+  // Open a new picker.
+  ProfilePicker::Show(ProfilePicker::EntryPoint::kProfileMenuManageProfiles);
+  WaitForNewWebView();
+  WaitForFirstPaint(web_contents(), GURL("chrome://profile-picker"));
+  EXPECT_TRUE(ProfilePicker::IsOpen());
+
+  EXPECT_FALSE(widget()->IsFullscreen());
+  WidgetBoundsChangeWaiter bounds_waiter(widget());
+
+  // Toggle fullscreen with keyboard.
+#if defined(OS_MAC)
+  // Use Cmd-Ctrl-F on Mac.
+  bool control = true;
+  bool command = true;
+  ui::KeyboardCode key_code = ui::VKEY_F;
+#else
+  // Use F11 on other platforms.
+  bool control = false;
+  bool command = false;
+  ui::KeyboardCode key_code = ui::VKEY_F11;
+#endif
+  ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
+      widget()->GetNativeWindow(), key_code, control, /*shift=*/false,
+      /*alt=*/false, command));
+  // Fullscreen causes the bounds of the widget to change.
+  bounds_waiter.Wait();
+  EXPECT_TRUE(widget()->IsFullscreen());
+}
+
+// Checks that the signin web view is able to process keyboard events.
+IN_PROC_BROWSER_TEST_F(ProfilePickerInteractiveUiTest,
+                       CloseSigninWithKeyboard) {
+  ProfilePicker::Show(ProfilePicker::EntryPoint::kProfileMenuAddNewProfile);
+  WaitForNewWebView();
+
+  // Simulate a click on the signin button.
+  base::MockCallback<base::OnceClosure> switch_failure_callback;
+  EXPECT_CALL(switch_failure_callback, Run()).Times(0);
+  ProfilePicker::SwitchToSignIn(SK_ColorRED, switch_failure_callback.Get());
+
+  // Switch to the signin webview.
+  WaitForNewWebView();
+  WaitForFirstPaint(web_contents(),
+                    GaiaUrls::GetInstance()->signin_chrome_sync_dice());
+
+  // Close the picker with the keyboard.
+  EXPECT_TRUE(ProfilePicker::IsOpen());
+  SendCloseWindowKeyboardCommand();
+  WaitForPickerClosed();
+}
diff --git a/chrome/browser/ui/views/profiles/profile_picker_test_base.cc b/chrome/browser/ui/views/profiles/profile_picker_test_base.cc
new file mode 100644
index 0000000..ac452a3
--- /dev/null
+++ b/chrome/browser/ui/views/profiles/profile_picker_test_base.cc
@@ -0,0 +1,122 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/profiles/profile_picker_test_base.h"
+
+#include "base/callback.h"
+#include "base/run_loop.h"
+#include "base/scoped_observation.h"
+#include "chrome/browser/ui/profile_picker.h"
+#include "chrome/browser/ui/ui_features.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/views/controls/webview/webview.h"
+#include "ui/views/view.h"
+#include "ui/views/view_observer.h"
+#include "url/gurl.h"
+
+namespace {
+
+// Waits until a web view is added as a child view of the given view.
+class WebViewAddedWaiter : public views::ViewObserver {
+ public:
+  WebViewAddedWaiter(
+      views::View* top_view,
+      base::RepeatingCallback<views::WebView*()> current_web_view_getter)
+      : current_web_view_getter_(current_web_view_getter) {
+    observation_.Observe(top_view);
+  }
+  ~WebViewAddedWaiter() override = default;
+
+  void Wait() { run_loop_.Run(); }
+
+ private:
+  // ViewObserver:
+  void OnChildViewAdded(views::View* observed_view,
+                        views::View* child) override {
+    if (child == current_web_view_getter_.Run()) {
+      ASSERT_TRUE(child);
+      run_loop_.Quit();
+    }
+  }
+
+  base::RunLoop run_loop_;
+  base::RepeatingCallback<views::WebView*()> current_web_view_getter_;
+  base::ScopedObservation<views::View, views::ViewObserver> observation_{this};
+};
+
+// Waits until a first non empty paint for given `url`.
+class FirstVisuallyNonEmptyPaintObserver : public content::WebContentsObserver {
+ public:
+  explicit FirstVisuallyNonEmptyPaintObserver(content::WebContents* contents,
+                                              const GURL& url)
+      : content::WebContentsObserver(contents), url_(url) {}
+
+  // Waits for the first paint.
+  void Wait() {
+    if (IsExitConditionSatisfied()) {
+      return;
+    }
+    run_loop_.Run();
+    EXPECT_TRUE(IsExitConditionSatisfied())
+        << web_contents()->GetVisibleURL() << " != " << url_;
+  }
+
+ private:
+  // WebContentsObserver:
+  void DidFirstVisuallyNonEmptyPaint() override {
+    if (web_contents()->GetVisibleURL() == url_)
+      run_loop_.Quit();
+  }
+
+  bool IsExitConditionSatisfied() {
+    return (web_contents()->GetVisibleURL() == url_ &&
+            web_contents()->CompletedFirstVisuallyNonEmptyPaint());
+  }
+
+  base::RunLoop run_loop_{base::RunLoop::Type::kNestableTasksAllowed};
+  GURL url_;
+};
+
+}  // namespace
+
+ProfilePickerTestBase::ProfilePickerTestBase() {
+  feature_list_.InitAndEnableFeature(features::kNewProfilePicker);
+}
+
+ProfilePickerTestBase::~ProfilePickerTestBase() = default;
+
+views::View* ProfilePickerTestBase::view() {
+  return ProfilePicker::GetViewForTesting();
+}
+
+views::Widget* ProfilePickerTestBase::widget() {
+  return view() ? view()->GetWidget() : nullptr;
+}
+
+views::WebView* ProfilePickerTestBase::web_view() {
+  return ProfilePicker::GetWebViewForTesting();
+}
+
+void ProfilePickerTestBase::WaitForNewWebView() {
+  ASSERT_TRUE(view());
+  WebViewAddedWaiter(view(),
+                     base::BindRepeating(&ProfilePickerTestBase::web_view,
+                                         base::Unretained(this)))
+      .Wait();
+  EXPECT_TRUE(web_view());
+}
+
+void ProfilePickerTestBase::WaitForFirstPaint(content::WebContents* contents,
+                                              const GURL& url) {
+  DCHECK(contents);
+  FirstVisuallyNonEmptyPaintObserver(contents, url).Wait();
+}
+
+content::WebContents* ProfilePickerTestBase::web_contents() {
+  if (!web_view())
+    return nullptr;
+  return web_view()->GetWebContents();
+}
diff --git a/chrome/browser/ui/views/profiles/profile_picker_test_base.h b/chrome/browser/ui/views/profiles/profile_picker_test_base.h
new file mode 100644
index 0000000..7176045
--- /dev/null
+++ b/chrome/browser/ui/views/profiles/profile_picker_test_base.h
@@ -0,0 +1,50 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_PROFILES_PROFILE_PICKER_TEST_BASE_H_
+#define CHROME_BROWSER_UI_VIEWS_PROFILES_PROFILE_PICKER_TEST_BASE_H_
+
+#include "base/test/scoped_feature_list.h"
+#include "chrome/test/base/in_process_browser_test.h"
+
+namespace content {
+class WebContents;
+}
+
+namespace views {
+class View;
+class WebView;
+class Widget;
+}  // namespace views
+
+class GURL;
+
+class ProfilePickerTestBase : public InProcessBrowserTest {
+ public:
+  ProfilePickerTestBase();
+  ~ProfilePickerTestBase() override;
+
+  // Returns the ProfilePickerView that is currently displayed.
+  views::View* view();
+
+  // Returns the widget associated with the profile picker.
+  views::Widget* widget();
+
+  // Returns the internal web view for the profile picker.
+  views::WebView* web_view();
+
+  // Waits until a new internal web view has been added to the main picker view.
+  void WaitForNewWebView();
+
+  // Waits until the web contents does the first non-empty paint for `url`.
+  void WaitForFirstPaint(content::WebContents* contents, const GURL& url);
+
+  // Gets the picker's web contents.
+  content::WebContents* web_contents();
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_PROFILES_PROFILE_PICKER_TEST_BASE_H_
diff --git a/chrome/browser/ui/views/profiles/profile_picker_view.cc b/chrome/browser/ui/views/profiles/profile_picker_view.cc
index 4e27252..3c0a000 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_view.cc
+++ b/chrome/browser/ui/views/profiles/profile_picker_view.cc
@@ -10,8 +10,10 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
+#include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/extensions/chrome_extension_web_contents_observer.h"
+#include "chrome/browser/lifetime/application_lifetime.h"
 #include "chrome/browser/profiles/profile_attributes_entry.h"
 #include "chrome/browser/profiles/profile_attributes_storage.h"
 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
@@ -24,11 +26,13 @@
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_navigator.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
+#include "chrome/browser/ui/views/accelerator_table.h"
 #include "chrome/browser/ui/views/profiles/profile_picker_view_sync_delegate.h"
 #include "chrome/browser/ui/webui/signin/dice_turn_sync_on_helper.h"
 #include "chrome/browser/ui/webui/signin/profile_picker_ui.h"
 #include "chrome/browser/ui/webui/signin/signin_web_dialog_ui.h"
 #include "chrome/browser/ui/webui/signin/sync_confirmation_ui.h"
+#include "chrome/common/pref_names.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/grit/chromium_strings.h"
 #include "chrome/grit/google_chrome_strings.h"
@@ -51,6 +55,10 @@
 #include "ui/views/win/hwnd_util.h"
 #endif
 
+#if defined(OS_MAC)
+#include "chrome/browser/global_keyboard_shortcuts_mac.h"
+#endif
+
 namespace {
 
 ProfilePickerView* g_profile_picker_view = nullptr;
@@ -61,6 +69,10 @@
 constexpr base::TimeDelta kExtendedAccountInfoTimeout =
     base::TimeDelta::FromSeconds(10);
 
+constexpr int kSupportedAcceleratorCommands[] = {
+    IDC_CLOSE_TAB, IDC_CLOSE_WINDOW, IDC_EXIT, IDC_FULLSCREEN,
+    IDC_MINIMIZE_WINDOW};
+
 GURL CreateURLForEntryPoint(ProfilePicker::EntryPoint entry_point) {
   GURL base_url = GURL(chrome::kChromeUIProfilePickerUrl);
   switch (entry_point) {
@@ -144,6 +156,7 @@
   SetButtons(ui::DIALOG_BUTTON_NONE);
   SetTitle(IDS_PRODUCT_NAME);
   set_use_custom_frame(false);
+  ConfigureAccelerators();
   // TODO(crbug.com/1063856): Add |RecordDialogCreation|.
 }
 
@@ -201,16 +214,11 @@
 void ProfilePickerView::Init(ProfilePicker::EntryPoint entry_point,
                              Profile* system_profile) {
   DCHECK_EQ(state_, kInitializing);
-  auto web_view = std::make_unique<views::WebView>(system_profile);
-  web_view->GetWebContents()->SetDelegate(this);
+  CreateWebView(system_profile);
+  DCHECK(web_view_);
   // To record metrics using javascript, extensions are needed.
   extensions::ChromeExtensionWebContentsObserver::CreateForWebContents(
-      web_view->GetWebContents());
-  // Set the member before adding to the hieararchy to make it easier for tests
-  // to detect that a new WebView has been created.
-  web_view_ = web_view.get();
-  AddChildView(std::move(web_view));
-  SetLayoutManager(std::make_unique<views::FillLayout>());
+      web_view_->GetWebContents());
 
   CreateDialogWidget(this, nullptr, nullptr);
 
@@ -309,13 +317,7 @@
   // Rebuild the view.
   // TODO(crbug.com/1126913): Add the simple toolbar with the back button.
   RemoveAllChildViews(true);
-  auto web_view = std::make_unique<views::WebView>(profile);
-  web_view->GetWebContents()->SetDelegate(this);
-  // Set the member before adding to the hieararchy to make it easier for tests
-  // to detect that a new WebView has been created.
-  web_view_ = web_view.get();
-  AddChildView(std::move(web_view));
-  SetLayoutManager(std::make_unique<views::FillLayout>());
+  CreateWebView(profile);
   web_view_->LoadInitialURL(GaiaUrls::GetInstance()->signin_chrome_sync_dice());
   web_view_->RequestFocus();
 }
@@ -359,6 +361,43 @@
   return minimum_size;
 }
 
+bool ProfilePickerView::AcceleratorPressed(const ui::Accelerator& accelerator) {
+  // Ignore presses of the Escape key. The profile picker may be Chrome's only
+  // top-level window, in which case we don't want presses of Esc to maybe quit
+  // the entire browser. This has higher priority than the default dialog Esc
+  // accelerator (which would otherwise close the window).
+  if (accelerator.key_code() == ui::VKEY_ESCAPE &&
+      accelerator.modifiers() == ui::EF_NONE) {
+    return true;
+  }
+
+  const auto& iter = accelerator_table_.find(accelerator);
+  DCHECK(iter != accelerator_table_.end());
+  int command_id = iter->second;
+  switch (command_id) {
+    case IDC_CLOSE_TAB:
+    case IDC_CLOSE_WINDOW:
+      // kEscKeyPressed is used although that shortcut is disabled (this is
+      // Ctrl-Shift-W instead).
+      GetWidget()->CloseWithReason(views::Widget::ClosedReason::kEscKeyPressed);
+      break;
+    case IDC_EXIT:
+      chrome::AttemptUserExit();
+      break;
+    case IDC_FULLSCREEN:
+      GetWidget()->SetFullscreen(!GetWidget()->IsFullscreen());
+      break;
+    case IDC_MINIMIZE_WINDOW:
+      GetWidget()->Minimize();
+      break;
+    default:
+      NOTREACHED() << "Unexpected command_id: " << command_id;
+      break;
+  }
+
+  return true;
+}
+
 bool ProfilePickerView::HandleContextMenu(
     content::RenderFrameHost* render_frame_host,
     const content::ContextMenuParams& params) {
@@ -366,6 +405,15 @@
   return true;
 }
 
+bool ProfilePickerView::HandleKeyboardEvent(
+    content::WebContents* source,
+    const content::NativeWebKeyboardEvent& event) {
+  // Forward the keyboard event to AcceleratorPressed() through the
+  // FocusManager.
+  return unhandled_keyboard_event_handler_.HandleKeyboardEvent(
+      event, GetFocusManager());
+}
+
 void ProfilePickerView::AddNewContents(
     content::WebContents* source,
     std::unique_ptr<content::WebContents> new_contents,
@@ -487,6 +535,10 @@
   entry->SetIsEphemeral(false);
   entry->SetLocalProfileName(name_for_signed_in_profile_);
 
+  // Skip the FRE for this profile as it's replaced by profile creation flow.
+  signed_in_profile_being_created_->GetPrefs()->SetBoolean(
+      prefs::kHasSeenWelcomePage, true);
+
   // TODO(crbug.com/1126913): Change the callback of
   // profiles::OpenBrowserWindowForProfile() to be a OnceCallback as it is only
   // called once.
@@ -521,3 +573,44 @@
   DCHECK(browser);
   std::move(finish_flow_callback).Run(browser);
 }
+
+void ProfilePickerView::ConfigureAccelerators() {
+  // By default, dialog views close when pressing escape. Override this
+  // behavior as the profile picker should not close in that case.
+  AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE));
+
+  const std::vector<AcceleratorMapping> accelerator_list(GetAcceleratorList());
+  for (const auto& entry : accelerator_list) {
+    if (!base::Contains(kSupportedAcceleratorCommands, entry.command_id))
+      continue;
+    ui::Accelerator accelerator(entry.keycode, entry.modifiers);
+    accelerator_table_[accelerator] = entry.command_id;
+    AddAccelerator(accelerator);
+  }
+
+#if defined(OS_MAC)
+  // Check Mac-specific accelerators. Note: Chrome does not support dynamic or
+  // user-configured accelerators on Mac. Default static accelerators are used
+  // instead.
+  for (int command_id : kSupportedAcceleratorCommands) {
+    ui::Accelerator accelerator;
+    bool mac_accelerator_found =
+        GetDefaultMacAcceleratorForCommandId(command_id, &accelerator);
+    if (mac_accelerator_found) {
+      accelerator_table_[accelerator] = command_id;
+      AddAccelerator(accelerator);
+    }
+  }
+#endif  // OS_MAC
+}
+
+void ProfilePickerView::CreateWebView(Profile* profile) {
+  auto web_view = std::make_unique<views::WebView>(profile);
+  web_view->GetWebContents()->SetDelegate(this);
+  web_view->set_allow_accelerators(true);
+  // Set the member before adding to the hieararchy to make it easier for tests
+  // to detect that a new WebView has been created.
+  web_view_ = web_view.get();
+  AddChildView(std::move(web_view));
+  SetLayoutManager(std::make_unique<views::FillLayout>());
+}
diff --git a/chrome/browser/ui/views/profiles/profile_picker_view.h b/chrome/browser/ui/views/profiles/profile_picker_view.h
index 7c470c2..9141802 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_view.h
+++ b/chrome/browser/ui/views/profiles/profile_picker_view.h
@@ -11,6 +11,7 @@
 #include "components/keep_alive_registry/scoped_keep_alive.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "content/public/browser/web_contents_delegate.h"
+#include "ui/views/controls/webview/unhandled_keyboard_event_handler.h"
 #include "ui/views/controls/webview/webview.h"
 #include "ui/views/window/dialog_delegate.h"
 
@@ -72,6 +73,7 @@
 
   // views::View;
   gfx::Size GetMinimumSize() const override;
+  bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
 
   // content::WebContentsDelegate:
   bool HandleContextMenu(content::RenderFrameHost* render_frame_host,
@@ -83,6 +85,9 @@
                       const gfx::Rect& initial_rect,
                       bool user_gesture,
                       bool* was_blocked) override;
+  bool HandleKeyboardEvent(
+      content::WebContents* source,
+      const content::NativeWebKeyboardEvent& event) override;
 
   // IdentityManager::Observer:
   void OnRefreshTokenUpdatedForAccount(
@@ -104,9 +109,22 @@
                        Profile* profile,
                        Profile::CreateStatus profile_create_status);
 
+  // Register basic keyboard accelerators such as closing the window (Alt-F4
+  // on Windows).
+  void ConfigureAccelerators();
+
+  // Creates and configures the internal web view, and adds it as a child view.
+  void CreateWebView(Profile* profile);
+
   ScopedKeepAlive keep_alive_;
   State state_ = State::kNotStarted;
 
+  // A mapping between accelerators and command IDs.
+  std::map<ui::Accelerator, int> accelerator_table_;
+
+  // Handler for unhandled key events from renderer.
+  views::UnhandledKeyboardEventHandler unhandled_keyboard_event_handler_;
+
   // The current WebView object, owned by the view hierarchy.
   views::WebView* web_view_ = nullptr;
 
diff --git a/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc b/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc
index c22da81..f7f4af1 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc
+++ b/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc
@@ -25,6 +25,7 @@
 #include "chrome/browser/ui/sync/profile_signin_confirmation_helper.h"
 #include "chrome/browser/ui/tab_dialogs.h"
 #include "chrome/browser/ui/ui_features.h"
+#include "chrome/browser/ui/views/profiles/profile_picker_test_base.h"
 #include "chrome/browser/ui/webui/signin/login_ui_service.h"
 #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
 #include "chrome/test/base/in_process_browser_test.h"
@@ -38,12 +39,12 @@
 #include "components/sync/driver/sync_driver_switches.h"
 #include "components/sync/driver/sync_service.h"
 #include "components/sync/driver/sync_user_settings.h"
-#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "google_apis/gaia/gaia_urls.h"
 #include "third_party/skia/include/core/SkColor.h"
-#include "ui/views/view_observer.h"
+#include "url/gurl.h"
 
 namespace {
 
@@ -69,64 +70,6 @@
   return account_info;
 }
 
-// Waits until a first non empty paint for given `url`.
-class FirstVisuallyNonEmptyPaintObserver : public content::WebContentsObserver {
- public:
-  explicit FirstVisuallyNonEmptyPaintObserver(content::WebContents* contents,
-                                              const GURL& url)
-      : content::WebContentsObserver(contents), url_(url) {}
-
-  void DidFirstVisuallyNonEmptyPaint() override {
-    if (web_contents()->GetVisibleURL() == url_)
-      run_loop_.Quit();
-  }
-
-  void Wait() {
-    if (IsExitConditionSatisfied()) {
-      return;
-    }
-    run_loop_.Run();
-    EXPECT_TRUE(IsExitConditionSatisfied())
-        << web_contents()->GetVisibleURL() << " != " << url_;
-  }
-
- private:
-  bool IsExitConditionSatisfied() {
-    return (web_contents()->GetVisibleURL() == url_ &&
-            web_contents()->CompletedFirstVisuallyNonEmptyPaint());
-  }
-
-  base::RunLoop run_loop_{base::RunLoop::Type::kNestableTasksAllowed};
-  GURL url_;
-};
-
-class WebViewAddedWaiter : public views::ViewObserver {
- public:
-  WebViewAddedWaiter(
-      views::View* top_view,
-      base::RepeatingCallback<views::WebView*()> current_web_view_getter)
-      : current_web_view_getter_(current_web_view_getter) {
-    observation_.Observe(top_view);
-  }
-  ~WebViewAddedWaiter() override = default;
-
-  void Wait() { run_loop_.Run(); }
-
- private:
-  // ViewObserver:
-  void OnChildViewAdded(views::View* observed_view,
-                        views::View* child) override {
-    if (child == current_web_view_getter_.Run()) {
-      ASSERT_TRUE(child);
-      run_loop_.Quit();
-    }
-  }
-
-  base::RunLoop run_loop_;
-  base::RepeatingCallback<views::WebView*()> current_web_view_getter_;
-  base::ScopedObservation<views::View, views::ViewObserver> observation_{this};
-};
-
 class BrowserAddedWaiter : public BrowserListObserver {
  public:
   explicit BrowserAddedWaiter(size_t total_count) : total_count_(total_count) {
@@ -256,15 +199,14 @@
   base::RunLoop* run_loop_;
 };
 
-class ProfilePickerCreationFlowBrowserTest : public InProcessBrowserTest {
+class ProfilePickerCreationFlowBrowserTest : public ProfilePickerTestBase {
  public:
   ProfilePickerCreationFlowBrowserTest() {
-    feature_list_.InitWithFeatures(
-        {features::kProfilesUIRevamp, features::kNewProfilePicker}, {});
+    feature_list_.InitAndEnableFeature(features::kProfilesUIRevamp);
   }
 
   void SetUpInProcessBrowserTestFixture() override {
-    InProcessBrowserTest::SetUpInProcessBrowserTestFixture();
+    ProfilePickerTestBase::SetUpInProcessBrowserTestFixture();
     create_services_subscription_ =
         BrowserContextDependencyManager::GetInstance()
             ->RegisterCreateServicesCallbackForTesting(
@@ -279,26 +221,6 @@
         context, base::BindRepeating(&FakeUserPolicySigninService::Build));
   }
 
-  views::View* view() { return ProfilePicker::GetViewForTesting(); }
-
-  views::WebView* web_view() { return ProfilePicker::GetWebViewForTesting(); }
-
-  void WaitForNewWebView() {
-    ASSERT_TRUE(view());
-    WebViewAddedWaiter(
-        view(),
-        base::BindRepeating(&ProfilePickerCreationFlowBrowserTest::web_view,
-                            base::Unretained(this)))
-        .Wait();
-    EXPECT_TRUE(web_view());
-  }
-
-  content::WebContents* web_contents() {
-    if (!web_view())
-      return nullptr;
-    return web_view()->GetWebContents();
-  }
-
  private:
   base::CallbackListSubscription create_services_subscription_;
   base::test::ScopedFeatureList feature_list_;
@@ -324,9 +246,8 @@
   ProfilePicker::Show(ProfilePicker::EntryPoint::kProfileMenuAddNewProfile);
   WaitForNewWebView();
   EXPECT_TRUE(ProfilePicker::IsOpen());
-  FirstVisuallyNonEmptyPaintObserver(
-      web_contents(), GURL("chrome://profile-picker/new-profile"))
-      .Wait();
+  WaitForFirstPaint(web_contents(),
+                    GURL("chrome://profile-picker/new-profile"));
 }
 
 IN_PROC_BROWSER_TEST_F(ProfilePickerCreationFlowBrowserTest,
@@ -344,9 +265,8 @@
   // The DICE navigation happens in a new web view (for the profile being
   // created), wait for it.
   WaitForNewWebView();
-  FirstVisuallyNonEmptyPaintObserver(
-      web_contents(), GaiaUrls::GetInstance()->signin_chrome_sync_dice())
-      .Wait();
+  WaitForFirstPaint(web_contents(),
+                    GaiaUrls::GetInstance()->signin_chrome_sync_dice());
 
   // Add an account - simulate a successful Gaia sign-in.
   Profile* profile_being_created =
@@ -365,18 +285,14 @@
 
   // Wait for the sign-in to propagate to the flow, resulting in sync
   // confirmation screen getting displayed.
-  FirstVisuallyNonEmptyPaintObserver(web_contents(),
-                                     GURL("chrome://sync-confirmation/"))
-      .Wait();
+  WaitForFirstPaint(web_contents(), GURL("chrome://sync-confirmation/"));
 
   // Simulate closing the UI with "Yes, I'm in".
   LoginUIServiceFactory::GetForProfile(profile_being_created)
       ->SyncConfirmationUIClosed(LoginUIService::SYNC_WITH_DEFAULT_SETTINGS);
   Browser* new_browser = BrowserAddedWaiter(2u).Wait();
-  FirstVisuallyNonEmptyPaintObserver(
-      new_browser->tab_strip_model()->GetActiveWebContents(),
-      GURL("chrome://newtab/"))
-      .Wait();
+  WaitForFirstPaint(new_browser->tab_strip_model()->GetActiveWebContents(),
+                    GURL("chrome://newtab/"));
 
   // Check expectations when the profile creation flow is done.
   EXPECT_FALSE(ProfilePicker::IsOpen());
@@ -411,9 +327,8 @@
   // The DICE navigation happens in a new web view (for the profile being
   // created), wait for it.
   WaitForNewWebView();
-  FirstVisuallyNonEmptyPaintObserver(
-      web_contents(), GaiaUrls::GetInstance()->signin_chrome_sync_dice())
-      .Wait();
+  WaitForFirstPaint(web_contents(),
+                    GaiaUrls::GetInstance()->signin_chrome_sync_dice());
 
   // Disable sync by setting the device as managed in prefs.
   Profile* profile_being_created =
@@ -437,10 +352,8 @@
   // Wait for the sign-in to propagate to the flow, resulting in new browser
   // getting opened.
   Browser* new_browser = BrowserAddedWaiter(2u).Wait();
-  FirstVisuallyNonEmptyPaintObserver(
-      new_browser->tab_strip_model()->GetActiveWebContents(),
-      GURL("chrome://newtab/"))
-      .Wait();
+  WaitForFirstPaint(new_browser->tab_strip_model()->GetActiveWebContents(),
+                    GURL("chrome://newtab/"));
 
   EXPECT_FALSE(ProfilePicker::IsOpen());
 
@@ -478,9 +391,8 @@
   // The DICE navigation happens in a new web view (for the profile being
   // created), wait for it.
   WaitForNewWebView();
-  FirstVisuallyNonEmptyPaintObserver(
-      web_contents(), GaiaUrls::GetInstance()->signin_chrome_sync_dice())
-      .Wait();
+  WaitForFirstPaint(web_contents(),
+                    GaiaUrls::GetInstance()->signin_chrome_sync_dice());
 
   // Add an account - simulate a successful Gaia sign-in.
   Profile* profile_being_created =
@@ -497,18 +409,14 @@
 
   // Wait for the sign-in to propagate to the flow, resulting in sync
   // confirmation screen getting displayed.
-  FirstVisuallyNonEmptyPaintObserver(web_contents(),
-                                     GURL("chrome://sync-confirmation/"))
-      .Wait();
+  WaitForFirstPaint(web_contents(), GURL("chrome://sync-confirmation/"));
 
   // Simulate closing the UI with "Yes, I'm in".
   LoginUIServiceFactory::GetForProfile(profile_being_created)
       ->SyncConfirmationUIClosed(LoginUIService::CONFIGURE_SYNC_FIRST);
   Browser* new_browser = BrowserAddedWaiter(2u).Wait();
-  FirstVisuallyNonEmptyPaintObserver(
-      new_browser->tab_strip_model()->GetActiveWebContents(),
-      GURL("chrome://settings/syncSetup"))
-      .Wait();
+  WaitForFirstPaint(new_browser->tab_strip_model()->GetActiveWebContents(),
+                    GURL("chrome://settings/syncSetup"));
 
   // Check expectations when the profile creation flow is done.
   EXPECT_FALSE(ProfilePicker::IsOpen());
@@ -544,9 +452,8 @@
   // The DICE navigation happens in a new web view (for the profile being
   // created), wait for it.
   WaitForNewWebView();
-  FirstVisuallyNonEmptyPaintObserver(
-      web_contents(), GaiaUrls::GetInstance()->signin_chrome_sync_dice())
-      .Wait();
+  WaitForFirstPaint(web_contents(),
+                    GaiaUrls::GetInstance()->signin_chrome_sync_dice());
 
   // Simulate clicking on a link that opens in a new window.
   const GURL kURL("https://foo.google.com");
@@ -561,9 +468,8 @@
   // A new pppup browser is displayed (with the specified URL).
   Browser* new_browser = BrowserAddedWaiter(2u).Wait();
   EXPECT_EQ(new_browser->type(), Browser::TYPE_POPUP);
-  FirstVisuallyNonEmptyPaintObserver(
-      new_browser->tab_strip_model()->GetActiveWebContents(), kURL)
-      .Wait();
+  WaitForFirstPaint(new_browser->tab_strip_model()->GetActiveWebContents(),
+                    kURL);
 }
 
 // TODO(crbug.com/1144065): Flaky on multiple platforms.
@@ -606,9 +512,8 @@
   // The DICE navigation happens in a new web view (for the profile being
   // created), wait for it.
   WaitForNewWebView();
-  FirstVisuallyNonEmptyPaintObserver(
-      web_contents(), GaiaUrls::GetInstance()->signin_chrome_sync_dice())
-      .Wait();
+  WaitForFirstPaint(web_contents(),
+                    GaiaUrls::GetInstance()->signin_chrome_sync_dice());
 
   // Add an account - simulate a successful Gaia sign-in.
   Profile* profile_being_created =
@@ -625,10 +530,8 @@
 
   // Instead of sync confirmation, a browser is displayed (with a login error).
   Browser* new_browser = BrowserAddedWaiter(2u).Wait();
-  FirstVisuallyNonEmptyPaintObserver(
-      new_browser->tab_strip_model()->GetActiveWebContents(),
-      GURL("chrome://newtab/"))
-      .Wait();
+  WaitForFirstPaint(new_browser->tab_strip_model()->GetActiveWebContents(),
+                    GURL("chrome://newtab/"));
 
   // Check expectations when the profile creation flow is done.
   EXPECT_FALSE(ProfilePicker::IsOpen());
@@ -659,9 +562,8 @@
   // The DICE navigation happens in a new web view (for the profile being
   // created), wait for it.
   WaitForNewWebView();
-  FirstVisuallyNonEmptyPaintObserver(
-      web_contents(), GaiaUrls::GetInstance()->signin_chrome_sync_dice())
-      .Wait();
+  WaitForFirstPaint(web_contents(),
+                    GaiaUrls::GetInstance()->signin_chrome_sync_dice());
 
   Profile* profile_being_created =
       static_cast<Profile*>(web_view()->GetBrowserContext());
@@ -680,18 +582,14 @@
 
   // Wait for the sign-in to propagate to the flow, resulting in sync
   // confirmation screen getting displayed.
-  FirstVisuallyNonEmptyPaintObserver(web_contents(),
-                                     GURL("chrome://sync-confirmation/"))
-      .Wait();
+  WaitForFirstPaint(web_contents(), GURL("chrome://sync-confirmation/"));
 
   // Simulate closing the UI with "Yes, I'm in".
   LoginUIServiceFactory::GetForProfile(profile_being_created)
       ->SyncConfirmationUIClosed(LoginUIService::SYNC_WITH_DEFAULT_SETTINGS);
   Browser* new_browser = BrowserAddedWaiter(2u).Wait();
-  FirstVisuallyNonEmptyPaintObserver(
-      new_browser->tab_strip_model()->GetActiveWebContents(),
-      GURL("chrome://newtab/"))
-      .Wait();
+  WaitForFirstPaint(new_browser->tab_strip_model()->GetActiveWebContents(),
+                    GURL("chrome://newtab/"));
 
   // Check expectations when the profile creation flow is done.
   EXPECT_FALSE(ProfilePicker::IsOpen());
@@ -740,9 +638,8 @@
   // The DICE navigation happens in a new web view (for the profile being
   // created), wait for it.
   WaitForNewWebView();
-  FirstVisuallyNonEmptyPaintObserver(
-      web_contents(), GaiaUrls::GetInstance()->signin_chrome_sync_dice())
-      .Wait();
+  WaitForFirstPaint(web_contents(),
+                    GaiaUrls::GetInstance()->signin_chrome_sync_dice());
 
   // Add an account - simulate a successful Gaia sign-in.
   Profile* profile_being_created =
@@ -775,10 +672,8 @@
   // The picker should be closed even before the enterprise confirmation but it
   // is closed asynchronously after opening the browser so after the NTP
   // renders, it is safe to check.
-  FirstVisuallyNonEmptyPaintObserver(
-      new_browser->tab_strip_model()->GetActiveWebContents(),
-      GURL("chrome://newtab/"))
-      .Wait();
+  WaitForFirstPaint(new_browser->tab_strip_model()->GetActiveWebContents(),
+                    GURL("chrome://newtab/"));
   EXPECT_FALSE(ProfilePicker::IsOpen());
 
   // Now the sync consent screen is shown, simulate closing the UI with "Yes,
@@ -814,9 +709,8 @@
   // The DICE navigation happens in a new web view (for the profile being
   // created), wait for it.
   WaitForNewWebView();
-  FirstVisuallyNonEmptyPaintObserver(
-      web_contents(), GaiaUrls::GetInstance()->signin_chrome_sync_dice())
-      .Wait();
+  WaitForFirstPaint(web_contents(),
+                    GaiaUrls::GetInstance()->signin_chrome_sync_dice());
 
   // Add an account - simulate a successful Gaia sign-in.
   Profile* profile_being_created =
@@ -849,10 +743,8 @@
   // "Configure sync".
   LoginUIServiceFactory::GetForProfile(profile_being_created)
       ->SyncConfirmationUIClosed(LoginUIService::CONFIGURE_SYNC_FIRST);
-  FirstVisuallyNonEmptyPaintObserver(
-      new_browser->tab_strip_model()->GetActiveWebContents(),
-      GURL("chrome://settings/syncSetup"))
-      .Wait();
+  WaitForFirstPaint(new_browser->tab_strip_model()->GetActiveWebContents(),
+                    GURL("chrome://settings/syncSetup"));
 
   // Check expectations when the profile creation flow is done.
   EXPECT_FALSE(ProfilePicker::IsOpen());
diff --git a/chrome/browser/ui/webui/settings/people_handler.cc b/chrome/browser/ui/webui/settings/people_handler.cc
index 66cfbd2b..b31cbad 100644
--- a/chrome/browser/ui/webui/settings/people_handler.cc
+++ b/chrome/browser/ui/webui/settings/people_handler.cc
@@ -84,8 +84,6 @@
   bool sync_everything;
   syncer::UserSelectableTypeSet selected_types;
   bool payments_integration_enabled;
-  std::string passphrase;
-  bool set_new_passphrase;
 };
 
 bool IsSyncSubpage(const GURL& current_url) {
@@ -93,9 +91,7 @@
 }
 
 SyncConfigInfo::SyncConfigInfo()
-    : sync_everything(false),
-      payments_integration_enabled(false),
-      set_new_passphrase(false) {}
+    : sync_everything(false), payments_integration_enabled(false) {}
 
 SyncConfigInfo::~SyncConfigInfo() {}
 
@@ -132,13 +128,6 @@
       config->selected_types.Put(type);
   }
 
-  // Passphrase settings.
-  if (result->GetString("passphrase", &config->passphrase) &&
-      !config->passphrase.empty() &&
-      !result->GetBoolean("setNewPassphrase", &config->set_new_passphrase)) {
-    DLOG(ERROR) << "GetConfiguration() not passed a set_new_passphrase value";
-    return false;
-  }
   return true;
 }
 
@@ -276,8 +265,12 @@
       base::BindRepeating(&PeopleHandler::HandleSetDatatypes,
                           base::Unretained(this)));
   web_ui()->RegisterMessageCallback(
-      "SyncSetupSetEncryption",
-      base::BindRepeating(&PeopleHandler::HandleSetEncryption,
+      "SyncSetupSetEncryptionPassphrase",
+      base::BindRepeating(&PeopleHandler::HandleSetEncryptionPassphrase,
+                          base::Unretained(this)));
+  web_ui()->RegisterMessageCallback(
+      "SyncSetupSetDecryptionPassphrase",
+      base::BindRepeating(&PeopleHandler::HandleSetDecryptionPassphrase,
                           base::Unretained(this)));
   web_ui()->RegisterMessageCallback(
       "SyncSetupShowSetupUI",
@@ -527,79 +520,72 @@
 #endif
 }
 
-void PeopleHandler::HandleSetEncryption(const base::ListValue* args) {
-  SyncConfigInfo configuration;
-  const base::Value* callback_id = nullptr;
-  ParseConfigurationArguments(args, &configuration, &callback_id);
+void PeopleHandler::HandleSetEncryptionPassphrase(const base::ListValue* args) {
+  const base::Value& callback_id = args->GetList()[0];
 
-  // Start configuring the SyncService using the configuration passed to us from
-  // the JS layer.
-  syncer::SyncService* service = GetSyncService();
-
-  // If the sync engine has shutdown for some reason, just close the sync
-  // dialog.
-  if (!service || !service->IsEngineInitialized()) {
+  // Check the SyncService is up and running before retrieving SyncUserSettings,
+  // which contains the encryption-related APIs.
+  if (!GetSyncService() || !GetSyncService()->IsEngineInitialized()) {
+    // TODO(crbug.com/1139060): HandleSetDatatypes() also returns a success
+    // status in this case. Consider returning a failure in both methods. Maybe
+    // the CloseSyncSetup() call can also be removed.
     CloseSyncSetup();
-    ResolveJavascriptCallback(*callback_id, base::Value(kDonePageStatus));
+    ResolveJavascriptCallback(callback_id, base::Value(true));
     return;
   }
+  syncer::SyncUserSettings* sync_user_settings =
+      GetSyncService()->GetUserSettings();
 
-  if (service->GetUserSettings()->IsEncryptEverythingAllowed()) {
-    ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_ENCRYPT);
+  const std::string& passphrase = args->GetList()[1].GetString();
+  bool successfully_set = false;
+  if (passphrase.empty()) {
+    successfully_set = false;
+  } else if (!sync_user_settings->IsEncryptEverythingAllowed()) {
+    successfully_set = false;
+  } else if (sync_user_settings->IsUsingSecondaryPassphrase()) {
+    // In case a passphrase is already being used, changing to a new one isn't
+    // currently supported (one must reset all the Sync data).
+    successfully_set = false;
+  } else if (sync_user_settings->IsPassphraseRequired() ||
+             sync_user_settings->IsTrustedVaultKeyRequired()) {
+    // Can't re-encrypt the data with |passphrase| if some of it hasn't even
+    // been decrypted yet due to a pending passphrase / trusted vault key.
+    successfully_set = false;
   } else {
-    // Don't allow "set new passphrase" if the SyncService doesn't allow it.
-    // The UI is hidden, but the user may have enabled it e.g. by fiddling with
-    // the web inspector.
-    configuration.set_new_passphrase = false;
+    sync_user_settings->SetEncryptionPassphrase(passphrase);
+    successfully_set = true;
+    ProfileMetrics::LogProfileSyncInfo(
+        ProfileMetrics::SYNC_CREATED_NEW_PASSPHRASE);
   }
+  ResolveJavascriptCallback(callback_id, base::Value(successfully_set));
+}
 
-  bool passphrase_failed = false;
-  if (!configuration.passphrase.empty()) {
-    // We call IsPassphraseRequired() here (instead of
-    // IsPassphraseRequiredForPreferredDataTypes()) because the user may try to
-    // enter a passphrase even though no encrypted data types are enabled.
-    if (service->GetUserSettings()->IsPassphraseRequired()) {
-      // If we have pending keys, try to decrypt them with the provided
-      // passphrase. We track if this succeeds or fails because a failed
-      // decryption should result in an error even if there aren't any encrypted
-      // data types.
-      passphrase_failed = !service->GetUserSettings()->SetDecryptionPassphrase(
-          configuration.passphrase);
-    } else if (service->GetUserSettings()->IsTrustedVaultKeyRequired()) {
-      // There are pending keys due to trusted vault keys being required, likely
-      // because something changed since the UI was displayed. A passphrase
-      // cannot be set in such circumstances.
-      passphrase_failed = true;
-    } else {
-      // OK, the user sent us a passphrase, but we don't have pending keys. So
-      // it either means that the pending keys were resolved somehow since the
-      // time the UI was displayed (re-encryption, pending passphrase change,
-      // etc) or the user wants to re-encrypt.
-      if (configuration.set_new_passphrase &&
-          !service->GetUserSettings()->IsUsingSecondaryPassphrase()) {
-        service->GetUserSettings()->SetEncryptionPassphrase(
-            configuration.passphrase);
-      }
+void PeopleHandler::HandleSetDecryptionPassphrase(const base::ListValue* args) {
+  const base::Value& callback_id = args->GetList()[0];
+
+  // Check the SyncService is up and running before retrieving SyncUserSettings,
+  // which contains the encryption-related APIs.
+  if (!GetSyncService() || !GetSyncService()->IsEngineInitialized()) {
+    // TODO(crbug.com/1139060): HandleSetDatatypes() also returns a success
+    // status in this case. Consider returning a failure in both methods. Maybe
+    // the CloseSyncSetup() call can also be removed.
+    CloseSyncSetup();
+    ResolveJavascriptCallback(callback_id, base::Value(true));
+    return;
+  }
+  syncer::SyncUserSettings* sync_user_settings =
+      GetSyncService()->GetUserSettings();
+
+  const std::string& passphrase = args->GetList()[1].GetString();
+  bool successfully_set = false;
+  if (!passphrase.empty() && sync_user_settings->IsPassphraseRequired()) {
+    successfully_set = sync_user_settings->SetDecryptionPassphrase(passphrase);
+    if (successfully_set) {
+      ProfileMetrics::LogProfileSyncInfo(
+          ProfileMetrics::SYNC_ENTERED_EXISTING_PASSPHRASE);
     }
   }
-
-  if (passphrase_failed ||
-      service->GetUserSettings()->IsPassphraseRequiredForPreferredDataTypes()) {
-    // If the user doesn't enter any passphrase, we won't call
-    // SetDecryptionPassphrase() (passphrase_failed == false), but we still
-    // want to display an error message to let the user know that their blank
-    // passphrase entry is not acceptable.
-
-    // TODO(tommycli): Switch this to RejectJavascriptCallback once the
-    // Sync page JavaScript has been further refactored.
-    ResolveJavascriptCallback(*callback_id,
-                              base::Value(kPassphraseFailedPageStatus));
-  } else {
-    ResolveJavascriptCallback(*callback_id, base::Value(kConfigurePageStatus));
-  }
-
-  if (!configuration.set_new_passphrase && !configuration.passphrase.empty())
-    ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_PASSPHRASE);
+  ResolveJavascriptCallback(callback_id, base::Value(successfully_set));
 }
 
 void PeopleHandler::HandleShowSyncSetupUI(const base::ListValue* args) {
diff --git a/chrome/browser/ui/webui/settings/people_handler.h b/chrome/browser/ui/webui/settings/people_handler.h
index b6013d9..c50237f 100644
--- a/chrome/browser/ui/webui/settings/people_handler.h
+++ b/chrome/browser/ui/webui/settings/people_handler.h
@@ -87,14 +87,16 @@
   FRIEND_TEST_ALL_PREFIXES(PeopleHandlerTest, TestSyncEverything);
   FRIEND_TEST_ALL_PREFIXES(PeopleHandlerTest, TestSyncAllManually);
   FRIEND_TEST_ALL_PREFIXES(PeopleHandlerTest, NonRegisteredType);
-  FRIEND_TEST_ALL_PREFIXES(PeopleHandlerTest, TestPassphraseStillRequired);
+  FRIEND_TEST_ALL_PREFIXES(PeopleHandlerTest, EnterCorrectExistingPassphrase);
   FRIEND_TEST_ALL_PREFIXES(PeopleHandlerTest, TestSyncIndividualTypes);
   FRIEND_TEST_ALL_PREFIXES(PeopleHandlerTest,
-                           EnterExistingFrozenImplicitPassword);
-  FRIEND_TEST_ALL_PREFIXES(PeopleHandlerTest, SetNewCustomPassphrase);
+                           SuccessfullyCreateCustomPassphrase);
   FRIEND_TEST_ALL_PREFIXES(PeopleHandlerTest, EnterWrongExistingPassphrase);
-  FRIEND_TEST_ALL_PREFIXES(PeopleHandlerTest, EnterBlankExistingPassphrase);
-  FRIEND_TEST_ALL_PREFIXES(PeopleHandlerTest, TurnOnEncryptAllDisallowed);
+  FRIEND_TEST_ALL_PREFIXES(PeopleHandlerTest, CannotCreateBlankPassphrase);
+  FRIEND_TEST_ALL_PREFIXES(PeopleHandlerTest,
+                           CannotCreatePassphraseIfEncryptEverythingDisallowed);
+  FRIEND_TEST_ALL_PREFIXES(PeopleHandlerTest,
+                           CannotOverwritePassphraseWithNewOne);
   FRIEND_TEST_ALL_PREFIXES(PeopleHandlerTest,
                            UnrecoverableErrorInitializingSync);
   FRIEND_TEST_ALL_PREFIXES(PeopleHandlerTest, GaiaErrorInitializingSync);
@@ -152,7 +154,8 @@
   void HandleGetProfileInfo(const base::ListValue* args);
   void OnDidClosePage(const base::ListValue* args);
   void HandleSetDatatypes(const base::ListValue* args);
-  void HandleSetEncryption(const base::ListValue* args);
+  void HandleSetEncryptionPassphrase(const base::ListValue* args);
+  void HandleSetDecryptionPassphrase(const base::ListValue* args);
   void HandleShowSyncSetupUI(const base::ListValue* args);
   void HandleSyncPrefsDispatch(const base::ListValue* args);
 #if BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/browser/ui/webui/settings/people_handler_unittest.cc b/chrome/browser/ui/webui/settings/people_handler_unittest.cc
index ccc0fca..1700d9e 100644
--- a/chrome/browser/ui/webui/settings/people_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/people_handler_unittest.cc
@@ -78,26 +78,12 @@
   CHOOSE_WHAT_TO_SYNC
 };
 
-enum EncryptAllConfig {
-  ENCRYPT_ALL_DATA,
-  ENCRYPT_PASSWORDS
-};
-
 // Create a json-format string with the key/value pairs appropriate for a call
-// to HandleSetEncryption(). If |extra_values| is non-null, then the values from
-// the passed dictionary are added to the json.
-std::string GetConfiguration(const base::DictionaryValue* extra_values,
-                             SyncAllDataConfig sync_all,
-                             syncer::UserSelectableTypeSet types,
-                             const std::string& passphrase,
-                             EncryptAllConfig encrypt_all) {
+// to HandleSetDatatypes().
+std::string GetConfiguration(SyncAllDataConfig sync_all,
+                             syncer::UserSelectableTypeSet types) {
   base::DictionaryValue result;
-  if (extra_values)
-    result.MergeDictionary(extra_values);
   result.SetBoolean("syncAllDataTypes", sync_all == SYNC_ALL_DATA);
-  result.SetBoolean("encryptAllData", encrypt_all == ENCRYPT_ALL_DATA);
-  if (!passphrase.empty())
-    result.SetString("passphrase", passphrase);
   // Add all of our data types.
   result.SetBoolean("appsSynced", types.Has(syncer::UserSelectableType::kApps));
   result.SetBoolean("autofillSynced",
@@ -314,6 +300,21 @@
     EXPECT_EQ(expected_status, status);
   }
 
+  // Expects a call to ResolveJavascriptCallback() with |should_succeed| as its
+  // argument.
+  void ExpectSetPassphraseSuccess(bool should_succeed) {
+    EXPECT_EQ(1u, web_ui_.call_data().size());
+    const auto& data = *web_ui_.call_data()[0];
+    EXPECT_EQ("cr.webUIResponse", data.function_name());
+    EXPECT_TRUE(data.arg2()->is_bool());
+    EXPECT_TRUE(data.arg2()->GetBool())
+        << "Callback should be resolved with a boolean indicating the success, "
+           "never rejected.";
+
+    EXPECT_TRUE(data.arg3()->is_bool());
+    EXPECT_EQ(should_succeed, data.arg3()->GetBool());
+  }
+
   const base::DictionaryValue* ExpectSyncPrefsChanged() {
     const content::TestWebUI::CallData& data1 = *web_ui_.call_data().back();
     EXPECT_EQ("cr.webUIListenerCallback", data1.function_name());
@@ -638,8 +639,7 @@
 TEST_F(PeopleHandlerTest, TestSyncEverything) {
   SigninUser();
   CreatePeopleHandler();
-  std::string args = GetConfiguration(nullptr, SYNC_ALL_DATA, GetAllTypes(),
-                                      std::string(), ENCRYPT_PASSWORDS);
+  std::string args = GetConfiguration(SYNC_ALL_DATA, GetAllTypes());
   base::ListValue list_args;
   list_args.AppendString(kTestCallbackId);
   list_args.AppendString(args);
@@ -656,147 +656,119 @@
   ExpectPageStatusResponse(PeopleHandler::kConfigurePageStatus);
 }
 
-TEST_F(PeopleHandlerTest, TestPassphraseStillRequired) {
+TEST_F(PeopleHandlerTest, EnterCorrectExistingPassphrase) {
   SigninUser();
   CreatePeopleHandler();
-  std::string args = GetConfiguration(nullptr, SYNC_ALL_DATA, GetAllTypes(),
-                                      std::string(), ENCRYPT_PASSWORDS);
-  base::ListValue list_args;
-  list_args.AppendString(kTestCallbackId);
-  list_args.AppendString(args);
-  ON_CALL(*mock_sync_service_->GetMockUserSettings(),
-          IsPassphraseRequiredForPreferredDataTypes())
-      .WillByDefault(Return(true));
+  SetupInitializedSyncService();
+
   ON_CALL(*mock_sync_service_->GetMockUserSettings(), IsPassphraseRequired())
       .WillByDefault(Return(true));
   ON_CALL(*mock_sync_service_->GetMockUserSettings(),
-          IsUsingSecondaryPassphrase())
-      .WillByDefault(Return(false));
-  SetupInitializedSyncService();
-  SetDefaultExpectationsForConfigPage();
-
-  handler_->HandleSetEncryption(&list_args);
-  // We should navigate back to the configure page since we need a passphrase.
-  ExpectPageStatusResponse(PeopleHandler::kPassphraseFailedPageStatus);
-}
-
-TEST_F(PeopleHandlerTest, EnterExistingFrozenImplicitPassword) {
-  SigninUser();
-  CreatePeopleHandler();
-  base::DictionaryValue dict;
-  dict.SetBoolean("setNewPassphrase", false);
-  std::string args = GetConfiguration(&dict, SYNC_ALL_DATA, GetAllTypes(),
-                                      "oldGaiaPassphrase", ENCRYPT_PASSWORDS);
-  base::ListValue list_args;
-  list_args.AppendString(kTestCallbackId);
-  list_args.AppendString(args);
-  // Act as if an encryption passphrase is required the first time, then never
-  // again after that.
-  EXPECT_CALL(*mock_sync_service_->GetMockUserSettings(),
-              IsPassphraseRequired())
-      .WillOnce(Return(true));
-  ON_CALL(*mock_sync_service_->GetMockUserSettings(),
-          IsPassphraseRequiredForPreferredDataTypes())
+          IsTrustedVaultKeyRequired())
       .WillByDefault(Return(false));
   ON_CALL(*mock_sync_service_->GetMockUserSettings(),
           IsUsingSecondaryPassphrase())
-      .WillByDefault(Return(false));
-  SetupInitializedSyncService();
-  EXPECT_CALL(*mock_sync_service_->GetMockUserSettings(),
-              SetDecryptionPassphrase("oldGaiaPassphrase"))
-      .WillOnce(Return(true));
-
-  handler_->HandleSetEncryption(&list_args);
-  ExpectPageStatusResponse(PeopleHandler::kConfigurePageStatus);
-}
-
-TEST_F(PeopleHandlerTest, SetNewCustomPassphrase) {
-  SigninUser();
-  CreatePeopleHandler();
-  base::DictionaryValue dict;
-  dict.SetBoolean("setNewPassphrase", true);
-  std::string args = GetConfiguration(&dict, SYNC_ALL_DATA, GetAllTypes(),
-                                      "custom_passphrase", ENCRYPT_ALL_DATA);
-  base::ListValue list_args;
-  list_args.AppendString(kTestCallbackId);
-  list_args.AppendString(args);
+      .WillByDefault(Return(true));
   ON_CALL(*mock_sync_service_->GetMockUserSettings(),
           IsEncryptEverythingAllowed())
       .WillByDefault(Return(true));
-  ON_CALL(*mock_sync_service_->GetMockUserSettings(),
-          IsPassphraseRequiredForPreferredDataTypes())
-      .WillByDefault(Return(false));
+
+  EXPECT_CALL(*mock_sync_service_->GetMockUserSettings(),
+              SetDecryptionPassphrase("correct_passphrase"))
+      .WillOnce(Return(true));
+
+  base::ListValue list_args;
+  list_args.AppendString(kTestCallbackId);
+  list_args.AppendString("correct_passphrase");
+  handler_->HandleSetDecryptionPassphrase(&list_args);
+
+  ExpectSetPassphraseSuccess(true);
+}
+
+TEST_F(PeopleHandlerTest, SuccessfullyCreateCustomPassphrase) {
+  SigninUser();
+  CreatePeopleHandler();
+  SetupInitializedSyncService();
+
   ON_CALL(*mock_sync_service_->GetMockUserSettings(), IsPassphraseRequired())
       .WillByDefault(Return(false));
   ON_CALL(*mock_sync_service_->GetMockUserSettings(),
+          IsTrustedVaultKeyRequired())
+      .WillByDefault(Return(false));
+  ON_CALL(*mock_sync_service_->GetMockUserSettings(),
           IsUsingSecondaryPassphrase())
       .WillByDefault(Return(false));
-  SetupInitializedSyncService();
+  ON_CALL(*mock_sync_service_->GetMockUserSettings(),
+          IsEncryptEverythingAllowed())
+      .WillByDefault(Return(true));
+
   EXPECT_CALL(*mock_sync_service_->GetMockUserSettings(),
               SetEncryptionPassphrase("custom_passphrase"));
 
-  handler_->HandleSetEncryption(&list_args);
-  ExpectPageStatusResponse(PeopleHandler::kConfigurePageStatus);
+  base::ListValue list_args;
+  list_args.AppendString(kTestCallbackId);
+  list_args.AppendString("custom_passphrase");
+  handler_->HandleSetEncryptionPassphrase(&list_args);
+
+  ExpectSetPassphraseSuccess(true);
 }
 
 TEST_F(PeopleHandlerTest, EnterWrongExistingPassphrase) {
   SigninUser();
   CreatePeopleHandler();
-  base::DictionaryValue dict;
-  dict.SetBoolean("setNewPassphrase", false);
-  std::string args = GetConfiguration(&dict, SYNC_ALL_DATA, GetAllTypes(),
-                                      "invalid_passphrase", ENCRYPT_ALL_DATA);
-  base::ListValue list_args;
-  list_args.AppendString(kTestCallbackId);
-  list_args.AppendString(args);
-  ON_CALL(*mock_sync_service_->GetMockUserSettings(),
-          IsPassphraseRequiredForPreferredDataTypes())
-      .WillByDefault(Return(true));
+  SetupInitializedSyncService();
+
   ON_CALL(*mock_sync_service_->GetMockUserSettings(), IsPassphraseRequired())
       .WillByDefault(Return(true));
   ON_CALL(*mock_sync_service_->GetMockUserSettings(),
-          IsUsingSecondaryPassphrase())
+          IsTrustedVaultKeyRequired())
       .WillByDefault(Return(false));
-  SetupInitializedSyncService();
+  ON_CALL(*mock_sync_service_->GetMockUserSettings(),
+          IsUsingSecondaryPassphrase())
+      .WillByDefault(Return(true));
+  ON_CALL(*mock_sync_service_->GetMockUserSettings(),
+          IsEncryptEverythingAllowed())
+      .WillByDefault(Return(true));
+
   EXPECT_CALL(*mock_sync_service_->GetMockUserSettings(),
               SetDecryptionPassphrase("invalid_passphrase"))
       .WillOnce(Return(false));
 
-  SetDefaultExpectationsForConfigPage();
-
-  handler_->HandleSetEncryption(&list_args);
-  // We should navigate back to the configure page since we need a passphrase.
-  ExpectPageStatusResponse(PeopleHandler::kPassphraseFailedPageStatus);
-}
-
-TEST_F(PeopleHandlerTest, EnterBlankExistingPassphrase) {
-  SigninUser();
-  CreatePeopleHandler();
-  base::DictionaryValue dict;
-  dict.SetBoolean("setNewPassphrase", false);
-  std::string args = GetConfiguration(&dict,
-                                      SYNC_ALL_DATA,
-                                      GetAllTypes(),
-                                      "",
-                                      ENCRYPT_PASSWORDS);
   base::ListValue list_args;
   list_args.AppendString(kTestCallbackId);
-  list_args.AppendString(args);
-  ON_CALL(*mock_sync_service_->GetMockUserSettings(),
-          IsPassphraseRequiredForPreferredDataTypes())
-      .WillByDefault(Return(true));
+  list_args.AppendString("invalid_passphrase");
+  handler_->HandleSetDecryptionPassphrase(&list_args);
+
+  ExpectSetPassphraseSuccess(false);
+}
+
+TEST_F(PeopleHandlerTest, CannotCreateBlankPassphrase) {
+  SigninUser();
+  CreatePeopleHandler();
+  SetupInitializedSyncService();
+
   ON_CALL(*mock_sync_service_->GetMockUserSettings(), IsPassphraseRequired())
-      .WillByDefault(Return(true));
+      .WillByDefault(Return(false));
+  ON_CALL(*mock_sync_service_->GetMockUserSettings(),
+          IsTrustedVaultKeyRequired())
+      .WillByDefault(Return(false));
   ON_CALL(*mock_sync_service_->GetMockUserSettings(),
           IsUsingSecondaryPassphrase())
       .WillByDefault(Return(false));
-  SetupInitializedSyncService();
+  ON_CALL(*mock_sync_service_->GetMockUserSettings(),
+          IsEncryptEverythingAllowed())
+      .WillByDefault(Return(true));
 
-  SetDefaultExpectationsForConfigPage();
+  EXPECT_CALL(*mock_sync_service_->GetMockUserSettings(),
+              SetEncryptionPassphrase)
+      .Times(0);
 
-  handler_->HandleSetEncryption(&list_args);
-  // We should navigate back to the configure page since we need a passphrase.
-  ExpectPageStatusResponse(PeopleHandler::kPassphraseFailedPageStatus);
+  base::ListValue list_args;
+  list_args.AppendString(kTestCallbackId);
+  list_args.AppendString("");
+  handler_->HandleSetEncryptionPassphrase(&list_args);
+
+  ExpectSetPassphraseSuccess(false);
 }
 
 // Walks through each user selectable type, and tries to sync just that single
@@ -808,9 +780,7 @@
   for (syncer::UserSelectableType type : GetAllTypes()) {
     syncer::UserSelectableTypeSet type_to_set;
     type_to_set.Put(type);
-    std::string args =
-        GetConfiguration(nullptr, CHOOSE_WHAT_TO_SYNC, type_to_set,
-                         std::string(), ENCRYPT_PASSWORDS);
+    std::string args = GetConfiguration(CHOOSE_WHAT_TO_SYNC, type_to_set);
     base::ListValue list_args;
     list_args.AppendString(kTestCallbackId);
     list_args.AppendString(args);
@@ -833,9 +803,7 @@
   SigninUser();
   CreatePeopleHandler();
   SetDefaultExpectationsForConfigPage();
-  std::string args =
-      GetConfiguration(nullptr, CHOOSE_WHAT_TO_SYNC, GetAllTypes(),
-                       std::string(), ENCRYPT_PASSWORDS);
+  std::string args = GetConfiguration(CHOOSE_WHAT_TO_SYNC, GetAllTypes());
   base::ListValue list_args;
   list_args.AppendString(kTestCallbackId);
   list_args.AppendString(args);
@@ -867,9 +835,7 @@
 
   // Simulate "Sync everything" being turned off, but all individual
   // toggles left on.
-  std::string config =
-      GetConfiguration(/*extra_values=*/nullptr, CHOOSE_WHAT_TO_SYNC,
-                       GetAllTypes(), std::string(), ENCRYPT_PASSWORDS);
+  std::string config = GetConfiguration(CHOOSE_WHAT_TO_SYNC, GetAllTypes());
   base::ListValue list_args;
   list_args.AppendString(kTestCallbackId);
   list_args.AppendString(config);
@@ -1078,34 +1044,62 @@
   CheckBool(dictionary, "encryptAllDataAllowed", false);
 }
 
-TEST_F(PeopleHandlerTest, TurnOnEncryptAllDisallowed) {
+TEST_F(PeopleHandlerTest, CannotCreatePassphraseIfEncryptEverythingDisallowed) {
   SigninUser();
   CreatePeopleHandler();
-  ON_CALL(*mock_sync_service_->GetMockUserSettings(),
-          IsPassphraseRequiredForPreferredDataTypes())
-      .WillByDefault(Return(false));
+  SetupInitializedSyncService();
+
   ON_CALL(*mock_sync_service_->GetMockUserSettings(), IsPassphraseRequired())
       .WillByDefault(Return(false));
-  SetupInitializedSyncService();
+  ON_CALL(*mock_sync_service_->GetMockUserSettings(),
+          IsTrustedVaultKeyRequired())
+      .WillByDefault(Return(false));
+  ON_CALL(*mock_sync_service_->GetMockUserSettings(),
+          IsUsingSecondaryPassphrase())
+      .WillByDefault(Return(false));
   ON_CALL(*mock_sync_service_->GetMockUserSettings(),
           IsEncryptEverythingAllowed())
       .WillByDefault(Return(false));
 
-  base::DictionaryValue dict;
-  dict.SetBoolean("setNewPassphrase", true);
-  std::string args = GetConfiguration(&dict, SYNC_ALL_DATA, GetAllTypes(),
-                                      "password", ENCRYPT_ALL_DATA);
-  base::ListValue list_args;
-  list_args.AppendString(kTestCallbackId);
-  list_args.AppendString(args);
-
   EXPECT_CALL(*mock_sync_service_->GetMockUserSettings(),
-              SetEncryptionPassphrase(_))
+              SetEncryptionPassphrase)
       .Times(0);
 
-  handler_->HandleSetEncryption(&list_args);
+  base::ListValue list_args;
+  list_args.AppendString(kTestCallbackId);
+  list_args.AppendString("passphrase123");
+  handler_->HandleSetEncryptionPassphrase(&list_args);
 
-  ExpectPageStatusResponse(PeopleHandler::kConfigurePageStatus);
+  ExpectSetPassphraseSuccess(false);
+}
+
+TEST_F(PeopleHandlerTest, CannotOverwritePassphraseWithNewOne) {
+  SigninUser();
+  CreatePeopleHandler();
+  SetupInitializedSyncService();
+
+  ON_CALL(*mock_sync_service_->GetMockUserSettings(), IsPassphraseRequired())
+      .WillByDefault(Return(false));
+  ON_CALL(*mock_sync_service_->GetMockUserSettings(),
+          IsTrustedVaultKeyRequired())
+      .WillByDefault(Return(false));
+  ON_CALL(*mock_sync_service_->GetMockUserSettings(),
+          IsUsingSecondaryPassphrase())
+      .WillByDefault(Return(true));
+  ON_CALL(*mock_sync_service_->GetMockUserSettings(),
+          IsEncryptEverythingAllowed())
+      .WillByDefault(Return(true));
+
+  EXPECT_CALL(*mock_sync_service_->GetMockUserSettings(),
+              SetEncryptionPassphrase)
+      .Times(0);
+
+  base::ListValue list_args;
+  list_args.AppendString(kTestCallbackId);
+  list_args.AppendString("passphrase123");
+  handler_->HandleSetEncryptionPassphrase(&list_args);
+
+  ExpectSetPassphraseSuccess(false);
 }
 
 TEST_F(PeopleHandlerTest, DashboardClearWhileSettingsOpen_ConfirmSoon) {
diff --git a/chrome/browser/web_applications/BUILD.gn b/chrome/browser/web_applications/BUILD.gn
index 36cd911..55f09a6f 100644
--- a/chrome/browser/web_applications/BUILD.gn
+++ b/chrome/browser/web_applications/BUILD.gn
@@ -87,6 +87,7 @@
     "//components/services/app_service/public/cpp:app_url_handling",
     "//components/services/app_service/public/cpp:protocol_handling",
     "//components/sync",
+    "//components/user_manager",
     "//content/public/browser",
     "//services/metrics/public/cpp:ukm_builders",
     "//skia",
diff --git a/chrome/browser/web_applications/system_web_app_manager.cc b/chrome/browser/web_applications/system_web_app_manager.cc
index b883366..38ba6ff 100644
--- a/chrome/browser/web_applications/system_web_app_manager.cc
+++ b/chrome/browser/web_applications/system_web_app_manager.cc
@@ -35,6 +35,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/pref_service.h"
+#include "components/user_manager/user_manager.h"
 #include "components/version_info/version_info.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/url_data_source.h"
@@ -62,6 +63,7 @@
 #include "chromeos/components/help_app_ui/url_constants.h"
 #include "chromeos/components/media_app_ui/url_constants.h"
 #include "chromeos/constants/chromeos_features.h"
+#include "chromeos/constants/chromeos_pref_names.h"
 #include "chromeos/strings/grit/chromeos_strings.h"
 #include "components/policy/core/common/policy_pref_names.h"
 #include "extensions/common/constants.h"
@@ -103,7 +105,8 @@
 }
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
-base::flat_map<SystemAppType, SystemAppInfo> CreateSystemWebApps() {
+base::flat_map<SystemAppType, SystemAppInfo> CreateSystemWebApps(
+    Profile* profile) {
   base::flat_map<SystemAppType, SystemAppInfo> infos;
 // TODO(calamity): Split this into per-platform functions.
 #if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -117,8 +120,11 @@
         SystemAppInfo(
             "Camera", GURL("chrome://camera-app/views/main.html"),
             base::BindRepeating(&CreateWebAppInfoForCameraSystemWebApp)));
-    infos.at(SystemAppType::CAMERA).uninstall_and_replace = {
-        extension_misc::kCameraAppId};
+    if (!profile->GetPrefs()->GetBoolean(
+            chromeos::prefs::kHasCameraAppMigratedToSWA)) {
+      infos.at(SystemAppType::CAMERA).uninstall_and_replace = {
+          extension_misc::kCameraAppId};
+    }
     // We need "FileHandling" to use File Handling API to set launch directory.
     // And we need "NativeFileSystem2" to use Native File System API.
     infos.at(SystemAppType::CAMERA).enabled_origin_trials =
@@ -339,7 +345,8 @@
       return true;
     case SystemAppType::CAMERA:
       return base::FeatureList::IsEnabled(
-          chromeos::features::kCameraSystemWebApp);
+                 chromeos::features::kCameraSystemWebApp) &&
+             !user_manager::UserManager::Get()->IsLoggedInAsGuest();
     case SystemAppType::TERMINAL:
       return true;
     case SystemAppType::MEDIA:
@@ -385,7 +392,7 @@
 
     // Populate with real system apps if the test asks for it.
     if (base::FeatureList::IsEnabled(features::kEnableAllSystemWebApps))
-      system_app_infos_ = CreateSystemWebApps();
+      system_app_infos_ = CreateSystemWebApps(profile_);
 
     return;
   }
@@ -398,7 +405,7 @@
   update_policy_ = UpdatePolicy::kAlwaysUpdate;
 #endif
 
-  system_app_infos_ = CreateSystemWebApps();
+  system_app_infos_ = CreateSystemWebApps(profile_);
 }
 
 SystemWebAppManager::~SystemWebAppManager() = default;
@@ -491,7 +498,7 @@
 
 void SystemWebAppManager::InstallSystemAppsForTesting() {
   on_apps_synchronized_.reset(new base::OneShotEvent());
-  system_app_infos_ = CreateSystemWebApps();
+  system_app_infos_ = CreateSystemWebApps(profile_);
   Start();
 
   // Wait for the System Web Apps to install.
@@ -804,6 +811,13 @@
     on_apps_synchronized_->Signal();
     OnAppsPolicyChanged();
   }
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  bool is_camera_app_installed =
+      system_app_infos_.find(SystemAppType::CAMERA) != system_app_infos_.end();
+  profile_->GetPrefs()->SetBoolean(chromeos::prefs::kHasCameraAppMigratedToSWA,
+                                   is_camera_app_installed);
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 }
 
 bool SystemWebAppManager::ShouldForceInstallApps() const {
diff --git a/chrome/browser/webshare/share_service_impl.cc b/chrome/browser/webshare/share_service_impl.cc
index d249c55..7bfc923 100644
--- a/chrome/browser/webshare/share_service_impl.cc
+++ b/chrome/browser/webshare/share_service_impl.cc
@@ -177,7 +177,8 @@
 #elif defined(OS_WIN)
   auto share_operation = std::make_unique<webshare::ShareOperation>(
       title, text, share_url, std::move(files), web_contents);
-  share_operation->Run(base::BindOnce(
+  auto* const share_operation_ptr = share_operation.get();
+  share_operation_ptr->Run(base::BindOnce(
       [](std::unique_ptr<webshare::ShareOperation> share_operation,
          ShareCallback callback,
          blink::mojom::ShareError result) { std::move(callback).Run(result); },
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 2730933..3e9e892 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-master-1606348564-f9ad4c8126c0686f48279a254d0d8ff9696d692f.profdata
+chrome-linux-master-1606391943-c49ce66f4f03ab73428a9c9ccbc70b089767b5c2.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index db33405..d5b80cf3 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-master-1606348564-46558a518073e70dc3e85439edab882c7abb26f5.profdata
+chrome-mac-master-1606391943-6f31c76b4f9e93d1de26d5f38d730905e1d3f7a7.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index db14604..1b89595 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-master-1606066622-6c4da226bf03284a24fb3e251f76a24f1e8c9c69.profdata
+chrome-win32-master-1606359294-a78100f1c02315e3a2df0ee09ef21dd127ea538b.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 9b9063e..f88a255 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-master-1606316157-945260897cb0815b47df3e1edecdf8b6cf6cb896.profdata
+chrome-win64-master-1606380888-a2312ebb2792a371a28e16249fe53f8f1a9dbcea.profdata
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index 28df0b0..bc11079 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -2633,8 +2633,6 @@
 // re-created.
 const char kAppShortcutsArch[] = "apps.shortcuts_arch";
 
-// A string pref for storing the salt used to compute the pepper device ID.
-const char kDRMSalt[] = "settings.privacy.drm_salt";
 // A boolean pref that enables the (private) pepper GetDeviceID() call and
 // enables the use of remote attestation for content protection.
 const char kEnableDRM[] = "settings.privacy.drm_enabled";
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index c3f3597..f8eb779 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -885,7 +885,6 @@
 extern const char kAppShortcutsVersion[];
 extern const char kAppShortcutsArch[];
 
-extern const char kDRMSalt[];
 extern const char kEnableDRM[];
 
 extern const char kWatchdogExtensionActive[];
diff --git a/chrome/renderer/BUILD.gn b/chrome/renderer/BUILD.gn
index 03d9d4c..01fc562 100644
--- a/chrome/renderer/BUILD.gn
+++ b/chrome/renderer/BUILD.gn
@@ -197,7 +197,6 @@
     "//mojo/public/cpp/bindings",
     "//net",
     "//ppapi/buildflags",
-    "//ppapi/shared_impl",
     "//printing/buildflags",
     "//services/metrics/public/cpp:metrics_cpp",
     "//services/metrics/public/cpp:ukm_builders",
diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc
index b0b945a..79159ef 100644
--- a/chrome/renderer/chrome_content_renderer_client.cc
+++ b/chrome/renderer/chrome_content_renderer_client.cc
@@ -128,7 +128,6 @@
 #include "mojo/public/cpp/bindings/remote.h"
 #include "net/base/net_errors.h"
 #include "ppapi/buildflags/buildflags.h"
-#include "ppapi/shared_impl/ppapi_switches.h"
 #include "printing/buildflags/buildflags.h"
 #include "services/network/public/cpp/is_potentially_trustworthy.h"
 #include "services/service_manager/public/cpp/interface_provider.h"
@@ -196,6 +195,7 @@
 #if BUILDFLAG(ENABLE_PLUGINS)
 #include "chrome/common/plugin_utils.h"
 #include "chrome/renderer/plugins/chrome_plugin_placeholder.h"
+#include "ppapi/shared_impl/ppapi_switches.h"  // nogncheck crbug.com/1125897
 #else
 #include "components/plugins/renderer/plugin_placeholder.h"
 #endif
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index b8c9b01..569cddb 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2679,6 +2679,7 @@
         "../browser/chromeos/policy/device_system_use_24hour_clock_browsertest.cc",
         "../browser/chromeos/policy/display_resolution_handler_browsertest.cc",
         "../browser/chromeos/policy/display_rotation_default_handler_browsertest.cc",
+        "../browser/chromeos/policy/dlp/data_transfer_dlp_controller_browsertest.cc",
         "../browser/chromeos/policy/dlp/dlp_content_manager_browsertest.cc",
         "../browser/chromeos/policy/dlp/dlp_rules_manager_browsertest.cc",
         "../browser/chromeos/policy/dlp/dlp_rules_manager_test_utils.cc",
@@ -3182,8 +3183,7 @@
           [ "../browser/site_isolation/spellcheck_per_process_browsertest.cc" ]
     }
 
-    if (is_win || is_mac ||
-        ((is_linux || is_chromeos_lacros) && !is_chromeos_lacros)) {
+    if (is_win || is_mac || (is_linux && !is_chromeos_lacros)) {
       sources +=
           [ "../browser/ui/views/profiles/profile_picker_view_browsertest.cc" ]
     }
@@ -6319,6 +6319,12 @@
         "../browser/password_manager/password_manager_signin_intercept_test_helper.h",
       ]
     }
+    if (is_win || is_mac || (is_linux && !is_chromeos_lacros)) {
+      sources += [
+        "../browser/ui/views/profiles/profile_picker_test_base.cc",
+        "../browser/ui/views/profiles/profile_picker_test_base.h",
+      ]
+    }
   }
 
   proto_library("test_proto") {
@@ -6474,37 +6480,6 @@
       "ppapi/ppapi_interactive_browsertest.cc",
     ]
 
-    if (is_mac) {
-      sources += [
-        "../browser/apps/platform_apps/app_shim_interactive_uitest_mac.mm",
-        "../browser/apps/platform_apps/app_shim_quit_interactive_uitest_mac.mm",
-        "../browser/global_keyboard_shortcuts_mac_browsertest.mm",
-        "../browser/spellchecker/spellcheck_mac_view_interactive_uitest.mm",
-        "../browser/ui/cocoa/apps/quit_with_apps_controller_mac_interactive_uitest.mm",
-        "../browser/ui/cocoa/status_bubble_mac_interactive_uitest.mm",
-        "../browser/ui/cocoa/tab_contents/web_contents_view_mac_interactive_uitest.mm",
-        "base/interactive_test_utils_mac.mm",
-      ]
-    }
-
-    if (is_win) {
-      sources += [
-        "../browser/ui/send_mouse_move_uitest_win.cc",
-        "../browser/ui/views/accessibility/navigation_accessibility_uitest_win.cc",
-        "base/always_on_top_window_killer_win.cc",
-        "base/always_on_top_window_killer_win.h",
-        "base/interactive_test_utils_win.cc",
-        "base/save_desktop_snapshot_win.cc",
-        "base/save_desktop_snapshot_win.h",
-        "base/window_contents_as_string_win.cc",
-        "base/window_contents_as_string_win.h",
-      ]
-
-      if (use_aura) {
-        sources += [ "../browser/ui/views/autofill/autofill_accessibility_win_browsertest.cc" ]
-      }
-    }
-
     configs += [ "//build/config:precompiled_headers" ]
     if ((is_linux || is_chromeos) && !is_component_build) {
       configs += [ "//build/config/gcc:rpath_for_built_shared_libraries" ]
@@ -6695,11 +6670,6 @@
       }
     }
 
-    if (is_mac) {
-      sources +=
-          [ "../browser/notifications/notification_interactive_uitest_mac.mm" ]
-    }
-
     if (is_chromeos_ash) {
       deps += [
         "//chrome/browser/media/router:test_support",
@@ -6726,10 +6696,24 @@
         "../browser/notifications/notification_platform_bridge_win_interactive_uitest.cc",
         "../browser/notifications/win/fake_itoastnotifier.cc",
         "../browser/notifications/win/fake_itoastnotifier.h",
+        "../browser/ui/send_mouse_move_uitest_win.cc",
+        "../browser/ui/views/accessibility/navigation_accessibility_uitest_win.cc",
         "../browser/ui/views/accessibility/uia_accessibility_event_waiter.cc",
         "../browser/ui/views/accessibility/uia_accessibility_event_waiter.h",
         "../browser/ui/views/touch_events_interactive_uitest_win.cc",
+        "base/always_on_top_window_killer_win.cc",
+        "base/always_on_top_window_killer_win.h",
+        "base/interactive_test_utils_win.cc",
+        "base/save_desktop_snapshot_win.cc",
+        "base/save_desktop_snapshot_win.h",
+        "base/window_contents_as_string_win.cc",
+        "base/window_contents_as_string_win.h",
       ]
+
+      if (use_aura) {
+        sources += [ "../browser/ui/views/autofill/autofill_accessibility_win_browsertest.cc" ]
+      }
+
       deps += [
         "//chrome:other_version",
         "//chrome/app:chrome_dll_resources",
@@ -6750,7 +6734,18 @@
     }
 
     if (is_mac) {
-      sources += [ "../browser/ui/find_bar/find_bar_platform_helper_mac_interactive_uitest.mm" ]
+      sources += [
+        "../browser/apps/platform_apps/app_shim_interactive_uitest_mac.mm",
+        "../browser/apps/platform_apps/app_shim_quit_interactive_uitest_mac.mm",
+        "../browser/global_keyboard_shortcuts_mac_browsertest.mm",
+        "../browser/notifications/notification_interactive_uitest_mac.mm",
+        "../browser/spellchecker/spellcheck_mac_view_interactive_uitest.mm",
+        "../browser/ui/cocoa/apps/quit_with_apps_controller_mac_interactive_uitest.mm",
+        "../browser/ui/cocoa/status_bubble_mac_interactive_uitest.mm",
+        "../browser/ui/cocoa/tab_contents/web_contents_view_mac_interactive_uitest.mm",
+        "../browser/ui/find_bar/find_bar_platform_helper_mac_interactive_uitest.mm",
+        "base/interactive_test_utils_mac.mm",
+      ]
 
       sources -= [
         # TODO(crbug.com/1026820): Re-enable this test when history backend failures are addressed.
@@ -6813,6 +6808,12 @@
       sources +=
           [ "../browser/ui/views/frame/webui_tab_strip_interactive_uitest.cc" ]
     }
+
+    if (is_win || is_mac || (is_linux && !is_chromeos_lacros)) {
+      sources += [
+        "../browser/ui/views/profiles/profile_picker_interactive_uitest.cc",
+      ]
+    }
   }
 }
 
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeActivityTestRule.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeActivityTestRule.java
index be13b4b..ec2abf63 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeActivityTestRule.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeActivityTestRule.java
@@ -5,30 +5,31 @@
 package org.chromium.chrome.test;
 
 import android.app.Activity;
-import android.app.Instrumentation;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.internal.runner.listener.InstrumentationResultPrinter;
-import android.support.test.rule.ActivityTestRule;
 import android.view.Menu;
 
+import androidx.annotation.NonNull;
+
 import org.hamcrest.Matchers;
 import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
-import org.chromium.base.ActivityState;
 import org.chromium.base.ApplicationStatus;
-import org.chromium.base.ApplicationStatus.ActivityStateListener;
 import org.chromium.base.CommandLine;
-import org.chromium.base.Log;
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.test.BaseActivityTestRule;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.base.test.util.ScalableTimeout;
+import org.chromium.chrome.browser.DeferredStartupHandler;
 import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.chrome.browser.document.ChromeLauncherActivity;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
@@ -41,7 +42,9 @@
 import org.chromium.chrome.browser.ui.appmenu.AppMenuTestSupport;
 import org.chromium.chrome.test.util.ChromeApplicationTestUtils;
 import org.chromium.chrome.test.util.ChromeTabUtils;
+import org.chromium.chrome.test.util.NewTabPageTestUtils;
 import org.chromium.chrome.test.util.browser.Features;
+import org.chromium.components.embedder_support.util.UrlUtilities;
 import org.chromium.components.infobars.InfoBar;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.browser.WebContents;
@@ -59,14 +62,13 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Custom  {@link ActivityTestRule} for test using  {@link ChromeActivity}.
  *
  * @param <T> The {@link Activity} class under test.
  */
-public class ChromeActivityTestRule<T extends ChromeActivity> extends ActivityTestRule<T> {
+public class ChromeActivityTestRule<T extends ChromeActivity> extends BaseActivityTestRule<T> {
     private static final String TAG = "ChromeATR";
 
     // The number of ms to wait for the rendering activity to be started.
@@ -75,20 +77,13 @@
     private static final long OMNIBOX_FIND_SUGGESTION_TIMEOUT_MS = 10 * 1000;
 
     private Thread.UncaughtExceptionHandler mDefaultUncaughtExceptionHandler;
-    private Class<T> mChromeActivityClass;
-    private T mSetActivity;
     private String mCurrentTestName;
 
     @Rule
     private EmbeddedTestServerRule mTestServerRule = new EmbeddedTestServerRule();
 
     protected ChromeActivityTestRule(Class<T> activityClass) {
-        this(activityClass, false);
-    }
-
-    protected ChromeActivityTestRule(Class<T> activityClass, boolean initialTouchMode) {
-        super(activityClass, initialTouchMode, false);
-        mChromeActivityClass = activityClass;
+        super(activityClass);
     }
 
     @Override
@@ -135,13 +130,11 @@
         return ACTIVITY_START_TIMEOUT_MS;
     }
 
-    // TODO(yolandyan): remove this once startActivityCompletely is refactored out of
-    // ChromeActivityTestRule
+    // This has to be here or getActivity will return a T that extends Activity, not a T that
+    // extends ChromeActivity.
     @Override
+    @SuppressWarnings("RedundantOverride")
     public T getActivity() {
-        if (mSetActivity != null) {
-            return mSetActivity;
-        }
         return super.getActivity();
     }
 
@@ -152,6 +145,14 @@
     }
 
     /**
+     * TODO(https://crbug.com/1146574): This only exists here because legacy ActivityTestRule
+     * inherited from UiThreadTestRule. This function should be removed.
+     */
+    public void runOnUiThread(Runnable r) {
+        ThreadUtils.runOnUiThreadBlocking(r);
+    }
+
+    /**
      * @return The {@link AppMenuCoordinator} for the activity.
      */
     public AppMenuCoordinator getAppMenuCoordinator() {
@@ -196,49 +197,37 @@
     }
 
     /**
-     * Invokes {@link Instrumentation#startActivitySync(Intent)} and sets the
-     * test case's activity to the result. See the documentation for
-     * {@link Instrumentation#startActivitySync(Intent)} on the timing of the
-     * return, but generally speaking the activity's "onCreate" has completed
-     * and the activity's main looper has become idle.
-     *
-     * TODO(yolandyan): very similar to ActivityTestRule#launchActivity(Intent),
-     * yet small differences remains (e.g. launchActivity() uses FLAG_ACTIVITY_NEW_TASK while
-     * startActivityCompletely doesn't), need to refactor and use only launchActivity
-     * after the JUnit4 migration
+     * Similar to #launchActivity(Intent), but waits for the Activity tab to be initialized.
      */
     public void startActivityCompletely(Intent intent) {
-        Features.ensureCommandLineIsUpToDate();
+        DeferredStartupHandler.setExpectingActivityStartupForTesting();
+        launchActivity(intent);
+        waitForActivityNativeInitializationComplete();
 
-        final CallbackHelper activityCallback = new CallbackHelper();
-        final AtomicReference<T> activityRef = new AtomicReference<>();
-        ActivityStateListener stateListener = new ActivityStateListener() {
-            @SuppressWarnings("unchecked")
-            @Override
-            public void onActivityStateChange(Activity activity, int newState) {
-                if (newState == ActivityState.RESUMED) {
-                    if (!mChromeActivityClass.isAssignableFrom(activity.getClass())) return;
+        CriteriaHelper.pollUiThread(
+                () -> getActivity().getActivityTab() != null, "Tab never selected/initialized.");
+        Tab tab = getActivity().getActivityTab();
 
-                    activityRef.set((T) activity);
-                    activityCallback.notifyCalled();
-                    ApplicationStatus.unregisterActivityStateListener(this);
-                }
-            }
-        };
-        ApplicationStatus.registerStateListenerForAllActivities(stateListener);
+        ChromeTabUtils.waitForTabPageLoaded(tab, (String) null);
 
-        try {
-            InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
-            activityCallback.waitForCallback("Activity did not start as expected", 0);
-            T activity = activityRef.get();
-            Assert.assertNotNull("Activity reference is null.", activity);
-            setActivity(activity);
-            Log.d(TAG, "startActivityCompletely <<");
-        } catch (TimeoutException e) {
-            throw new RuntimeException(e);
-        } finally {
-            ApplicationStatus.unregisterActivityStateListener(stateListener);
+        if (tab != null && UrlUtilities.isNTPUrl(ChromeTabUtils.getUrlStringOnUiThread(tab))
+                && !getActivity().isInOverviewMode()) {
+            NewTabPageTestUtils.waitForNtpLoaded(tab);
         }
+
+        Assert.assertTrue("Deferred startup never completed. Did you try to start an Activity "
+                        + "that was already started?",
+                DeferredStartupHandler.waitForDeferredStartupCompleteForTesting(
+                        ScalableTimeout.scaleTimeout(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL)));
+
+        Assert.assertNotNull(tab);
+        Assert.assertNotNull(tab.getView());
+    }
+
+    @Override
+    public void launchActivity(@NonNull Intent startIntent) {
+        Features.ensureCommandLineIsUpToDate();
+        super.launchActivity(startIntent);
     }
 
     /**
@@ -472,10 +461,6 @@
         return getActivity().getWindowAndroid().getKeyboardDelegate();
     }
 
-    public void setActivity(T chromeActivity) {
-        mSetActivity = chromeActivity;
-    }
-
     /**
      * Waits for an Activity of the given class to be started.
      * @return The Activity.
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeTabbedActivityTestRule.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeTabbedActivityTestRule.java
index cc708d7..65e683d 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeTabbedActivityTestRule.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeTabbedActivityTestRule.java
@@ -20,11 +20,8 @@
 import org.chromium.base.Log;
 import org.chromium.base.test.util.ApplicationTestUtils;
 import org.chromium.base.test.util.CallbackHelper;
-import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.base.test.util.ScalableTimeout;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
-import org.chromium.chrome.browser.DeferredStartupHandler;
 import org.chromium.chrome.browser.omnibox.UrlBar;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabCreationState;
@@ -36,7 +33,6 @@
 import org.chromium.chrome.test.util.MenuUtils;
 import org.chromium.chrome.test.util.NewTabPageTestUtils;
 import org.chromium.chrome.test.util.WaitForFocusHelper;
-import org.chromium.components.embedder_support.util.UrlUtilities;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.concurrent.TimeoutException;
@@ -113,31 +109,7 @@
      */
     public void startMainActivityFromIntent(Intent intent, String url) {
         prepareUrlIntent(intent, url);
-
-        DeferredStartupHandler.setExpectingActivityStartupForTesting();
         startActivityCompletely(intent);
-        waitForActivityNativeInitializationComplete();
-
-        CriteriaHelper.pollUiThread(
-                () -> getActivity().getActivityTab() != null, "Tab never selected/initialized.");
-        Tab tab = getActivity().getActivityTab();
-
-        ChromeTabUtils.waitForTabPageLoaded(tab, (String) null);
-
-        if (tab != null && UrlUtilities.isNTPUrl(ChromeTabUtils.getUrlStringOnUiThread(tab))
-                && !getActivity().isInOverviewMode()) {
-            NewTabPageTestUtils.waitForNtpLoaded(tab);
-        }
-
-        Assert.assertTrue("Deferred startup never completed. Did you try to start an Activity "
-                        + "that was already started?",
-                DeferredStartupHandler.waitForDeferredStartupCompleteForTesting(
-                        ScalableTimeout.scaleTimeout(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL)));
-
-        Assert.assertNotNull(tab);
-        Assert.assertNotNull(tab.getView());
-
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
 
     /**
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/batch/BlankCTATabInitialStateRule.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/batch/BlankCTATabInitialStateRule.java
index e401694..dba985d0 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/batch/BlankCTATabInitialStateRule.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/batch/BlankCTATabInitialStateRule.java
@@ -39,6 +39,7 @@
         super();
         mActivityTestRule = activityTestRule;
         mClearAllTabState = clearAllTabState;
+        mActivityTestRule.setFinishActivity(false);
     }
 
     @Override
diff --git a/chrome/test/data/webui/settings/password_check_test.js b/chrome/test/data/webui/settings/password_check_test.js
index 4e7cc9a3..a530a8b 100644
--- a/chrome/test/data/webui/settings/password_check_test.js
+++ b/chrome/test/data/webui/settings/password_check_test.js
@@ -982,6 +982,27 @@
     expectFalse(isElementVisible(section.$.subtitle));
   });
 
+  // When the user is signed out but has run a weak check a timestamp should be
+  // shown.
+  test('showWeakCheckTimestampWhenSignedOut', async function() {
+    loadTimeData.overrideValues({passwordsWeaknessCheck: true});
+    passwordManager.data.checkStatus = makePasswordCheckStatus(
+        /*state=*/ PasswordCheckState.SIGNED_OUT,
+        /*checked=*/ 0,
+        /*remaining=*/ 0,
+        /*lastCheck=*/ 'Just now');
+
+    const section = createCheckPasswordSection();
+    await passwordManager.whenCalled('getPasswordCheckStatus');
+    flush();
+    const titleRow = section.$.titleRow;
+    const subtitle = section.$.subtitle;
+    assertTrue(isElementVisible(titleRow));
+    assertTrue(isElementVisible(subtitle));
+    expectEquals(
+        section.i18n('checkedPasswords') + ' • Just now', titleRow.innerText);
+  });
+
   // If |passwordsWeaknessCheck| is true, user is signed out and has
   // compromised credentials that were found in the past, shows "Checked
   // passwords" and correct label in the top of comromised passwords section.
diff --git a/chrome/test/data/webui/settings/people_page_sync_page_test.js b/chrome/test/data/webui/settings/people_page_sync_page_test.js
index 624cd363..913638f 100644
--- a/chrome/test/data/webui/settings/people_page_sync_page_test.js
+++ b/chrome/test/data/webui/settings/people_page_sync_page_test.js
@@ -337,7 +337,7 @@
     assertTrue(passphraseConfirmationInput.invalid);
   });
 
-  test('CreatingPassphraseValidPassphrase', function() {
+  test('CreatingPassphraseValidPassphrase', async function() {
     encryptWithPassphrase.click();
     flush();
 
@@ -350,34 +350,31 @@
         encryptionElement.$$('#passphraseConfirmationInput');
     passphraseInput.value = 'foo';
     passphraseConfirmationInput.value = 'foo';
+    browserProxy.encryptionPassphraseSuccess = true;
     saveNewPassphrase.click();
 
-    function verifySetSyncEncryption(args) {
-      assertTrue(args.setNewPassphrase);
-      assertEquals('foo', args.passphrase);
+    const passphrase = await browserProxy.whenCalled('setEncryptionPassphrase');
 
-      // Fake backend response.
-      const newPrefs = getSyncAllPrefs();
-      newPrefs.fullEncryptionBody = 'Encrypted with custom passphrase';
-      newPrefs.encryptAllData = true;
-      webUIListenerCallback('sync-prefs-changed', newPrefs);
+    assertEquals('foo', passphrase);
 
-      flush();
+    // Fake backend response.
+    const newPrefs = getSyncAllPrefs();
+    newPrefs.fullEncryptionBody = 'Encrypted with custom passphrase';
+    newPrefs.encryptAllData = true;
+    webUIListenerCallback('sync-prefs-changed', newPrefs);
 
-      return waitBeforeNextRender(syncPage).then(() => {
-        // Need to re-retrieve this, as a different show passphrase radio
-        // button is shown once |syncPrefs.fullEncryptionBody| is non-empty.
-        encryptWithPassphrase = encryptionElement.$$(
-            'cr-radio-button[name="encrypt-with-passphrase"]');
+    flush();
 
-        // Assert that the radio boxes are disabled after encryption enabled.
-        assertTrue(encryptionRadioGroup.disabled);
-        assertEquals(-1, encryptWithGoogle.$.button.tabIndex);
-        assertEquals(-1, encryptWithPassphrase.$.button.tabIndex);
-      });
-    }
-    return browserProxy.whenCalled('setSyncEncryption')
-        .then(verifySetSyncEncryption);
+    await waitBeforeNextRender(syncPage);
+    // Need to re-retrieve this, as a different show passphrase radio
+    // button is shown once |syncPrefs.fullEncryptionBody| is non-empty.
+    encryptWithPassphrase =
+        encryptionElement.$$('cr-radio-button[name="encrypt-with-passphrase"]');
+
+    // Assert that the radio boxes are disabled after encryption enabled.
+    assertTrue(encryptionRadioGroup.disabled);
+    assertEquals(-1, encryptWithGoogle.$.button.tabIndex);
+    assertEquals(-1, encryptWithPassphrase.$.button.tabIndex);
   });
 
   test('RadioBoxesHiddenWhenPassphraseRequired', function() {
@@ -415,7 +412,7 @@
         assertFalse(submitExistingPassphrase.disabled);
       });
 
-  test('EnterExistingWrongPassphrase', function() {
+  test('EnterExistingWrongPassphrase', async function() {
     const prefs = getSyncAllPrefs();
     prefs.encryptAllData = true;
     prefs.passphraseRequired = true;
@@ -425,21 +422,19 @@
     const existingPassphraseInput = syncPage.$$('#existingPassphraseInput');
     assertTrue(!!existingPassphraseInput);
     existingPassphraseInput.value = 'wrong';
-    browserProxy.encryptionResponse = PageStatus.PASSPHRASE_FAILED;
+    browserProxy.decryptionPassphraseSuccess = false;
 
     const submitExistingPassphrase = syncPage.$$('#submitExistingPassphrase');
     assertTrue(!!submitExistingPassphrase);
     submitExistingPassphrase.click();
 
-    return browserProxy.whenCalled('setSyncEncryption').then(function(args) {
-      assertFalse(args.setNewPassphrase);
-      assertEquals('wrong', args.passphrase);
-      assertTrue(args.passphraseRequired);
-      assertTrue(existingPassphraseInput.invalid);
-    });
+    const passphrase = await browserProxy.whenCalled('setDecryptionPassphrase');
+
+    assertEquals('wrong', passphrase);
+    assertTrue(existingPassphraseInput.invalid);
   });
 
-  test('EnterExistingCorrectPassphrase', function() {
+  test('EnterExistingCorrectPassphrase', async function() {
     const prefs = getSyncAllPrefs();
     prefs.encryptAllData = true;
     prefs.passphraseRequired = true;
@@ -449,29 +444,27 @@
     const existingPassphraseInput = syncPage.$$('#existingPassphraseInput');
     assertTrue(!!existingPassphraseInput);
     existingPassphraseInput.value = 'right';
-    browserProxy.encryptionResponse = PageStatus.CONFIGURE;
+    browserProxy.decryptionPassphraseSuccess = true;
 
     const submitExistingPassphrase = syncPage.$$('#submitExistingPassphrase');
     assertTrue(!!submitExistingPassphrase);
     submitExistingPassphrase.click();
 
-    return browserProxy.whenCalled('setSyncEncryption').then(function(args) {
-      assertFalse(args.setNewPassphrase);
-      assertEquals('right', args.passphrase);
-      assertTrue(args.passphraseRequired);
+    const passphrase = await browserProxy.whenCalled('setDecryptionPassphrase');
 
-      // Fake backend response.
-      const newPrefs = getSyncAllPrefs();
-      newPrefs.encryptAllData = true;
-      webUIListenerCallback('sync-prefs-changed', newPrefs);
+    assertEquals('right', passphrase);
 
-      flush();
+    // Fake backend response.
+    const newPrefs = getSyncAllPrefs();
+    newPrefs.encryptAllData = true;
+    webUIListenerCallback('sync-prefs-changed', newPrefs);
 
-      // Verify that the encryption radio boxes are shown but disabled.
-      assertTrue(encryptionRadioGroup.disabled);
-      assertEquals(-1, encryptWithGoogle.$.button.tabIndex);
-      assertEquals(-1, encryptWithPassphrase.$.button.tabIndex);
-    });
+    flush();
+
+    // Verify that the encryption radio boxes are shown but disabled.
+    assertTrue(encryptionRadioGroup.disabled);
+    assertEquals(-1, encryptWithGoogle.$.button.tabIndex);
+    assertEquals(-1, encryptWithPassphrase.$.button.tabIndex);
   });
 
   test('SyncAdvancedRow', function() {
diff --git a/chrome/test/data/webui/settings/test_sync_browser_proxy.js b/chrome/test/data/webui/settings/test_sync_browser_proxy.js
index 5537f5f..567f747 100644
--- a/chrome/test/data/webui/settings/test_sync_browser_proxy.js
+++ b/chrome/test/data/webui/settings/test_sync_browser_proxy.js
@@ -19,7 +19,8 @@
       'getSyncStatus',
       'incrementPromoImpressionCount',
       'setSyncDatatypes',
-      'setSyncEncryption',
+      'setEncryptionPassphrase',
+      'setDecryptionPassphrase',
       'signOut',
       'pauseSync',
       'sendSyncPrefsChanged',
@@ -37,8 +38,10 @@
     this.impressionCount_ = 0;
 
     // Settable fake data.
-    /** @type {!settings.PageStatus} */
-    this.encryptionResponse = settings.PageStatus.CONFIGURE;
+    /** @type {boolean} */
+    this.encryptionPassphraseSuccess = false;
+    /** @type {boolean} */
+    this.decryptionPassphraseSuccess = false;
     /** @type {!Array<!settings.StoredAccount>} */
     this.storedAccounts = [];
     /** @type {!settings.SyncStatus} */
@@ -111,9 +114,15 @@
   }
 
   /** @override */
-  setSyncEncryption(syncPrefs) {
-    this.methodCalled('setSyncEncryption', syncPrefs);
-    return Promise.resolve(this.encryptionResponse);
+  setEncryptionPassphrase(passphrase) {
+    this.methodCalled('setEncryptionPassphrase', passphrase);
+    return Promise.resolve(this.encryptionPassphraseSuccess);
+  }
+
+  /** @override */
+  setDecryptionPassphrase(passphrase) {
+    this.methodCalled('setDecryptionPassphrase', passphrase);
+    return Promise.resolve(this.decryptionPassphraseSuccess);
   }
 
   /** @override */
diff --git a/chrome/updater/test/integration_tests.cc b/chrome/updater/test/integration_tests.cc
index 26f75e1..9fdc15b 100644
--- a/chrome/updater/test/integration_tests.cc
+++ b/chrome/updater/test/integration_tests.cc
@@ -183,7 +183,7 @@
   Uninstall();
 }
 
-TEST_F(IntegrationTest, UnregisterUninstalledApp) {
+TEST_F(IntegrationTest, DISABLED_UnregisterUninstalledApp) {
   RegisterTestApp();
   ExpectInstalled();
   ExpectActiveVersion(UPDATER_VERSION_STRING);
diff --git a/chromeos/components/camera_app_ui/resources/camera_app_resources.grd b/chromeos/components/camera_app_ui/resources/camera_app_resources.grd
index 830e9f1b..46a3a80c 100644
--- a/chromeos/components/camera_app_ui/resources/camera_app_resources.grd
+++ b/chromeos/components/camera_app_ui/resources/camera_app_resources.grd
@@ -68,7 +68,8 @@
       <structure name="IDR_CAMERA_SOUND_JS" file="js/sound.js" type="chrome_html" />
       <structure name="IDR_CAMERA_STATE_JS" file="js/state.js" type="chrome_html" />
       <structure name="IDR_CAMERA_TEST_BRIDGE_JS" file="js/test_bridge.js" type="chrome_html" />
-      <structure name="IDR_CAMERA_TEST_HTML" file="views/test.html" type="chrome_html" />
+      <structure name="IDR_CAMERA_TEST_HTML" file="test/test.html" type="chrome_html" />
+      <structure name="IDR_CAMERA_TEST_LEGACY_HTML" file="views/test.html" type="chrome_html" />
       <structure name="IDR_CAMERA_TIMERTICK_JS" file="js/views/camera/timertick.js" type="chrome_html" />
       <structure name="IDR_CAMERA_TOAST_JS" file="js/toast.js" type="chrome_html" />
       <structure name="IDR_CAMERA_TOOLTIP_JS" file="js/tooltip.js" type="chrome_html" />
diff --git a/chromeos/components/camera_app_ui/resources/js/dom.js b/chromeos/components/camera_app_ui/resources/js/dom.js
index 6caa43f..c6bb118 100644
--- a/chromeos/components/camera_app_ui/resources/js/dom.js
+++ b/chromeos/components/camera_app_ui/resources/js/dom.js
@@ -12,7 +12,7 @@
  * type.
  * @param {!Node} target
  * @param {string} selector
- * @param {function(new: T, ...)} type A user-defined constructor.
+ * @param {function(new: T, ...)} type The expected element type.
  * @return {T}
  * @template T
  */
@@ -25,7 +25,7 @@
  * their type to be specific type.
  * @param {!Node} target
  * @param {string} selector
- * @param {function(new: T, ...)} type A user-defined constructor.
+ * @param {function(new: T, ...)} type The expected element type.
  * @return {!NodeList<T>}
  * @template T
  */
@@ -40,7 +40,7 @@
 /**
  * Gets an element in document matching css selector and checks its type.
  * @param {string} selector
- * @param {function(new: T, ...)} type A user-defined constructor.
+ * @param {function(new: T, ...)} type The expected element type.
  * @return {T}
  * @template T
  */
@@ -52,7 +52,7 @@
  * Gets all elements in document matching css selector and asserts their type to
  * be specific type.
  * @param {string} selector
- * @param {function(new: T, ...)} type A user-defined constructor.
+ * @param {function(new: T, ...)} type The expected element type.
  * @return {!NodeList<T>}
  * @template T
  */
@@ -60,4 +60,16 @@
   return getAllFrom(document, selector, type);
 }
 
+/**
+ * Creates a typed element.
+ * @param {string} tag The HTML tag of the element to be created.
+ * @param {function(new: T, ...)} type The expected element type.
+ * @return {!T}
+ * @template T
+ */
+export function create(tag, type) {
+  const el = document.createElement(tag);
+  return assertInstanceof(el, type);
+}
+
 /* eslint-enable valid-jsdoc */
diff --git a/chromeos/components/camera_app_ui/resources/js/main.js b/chromeos/components/camera_app_ui/resources/js/main.js
index 1486db3..879daee 100644
--- a/chromeos/components/camera_app_ui/resources/js/main.js
+++ b/chromeos/components/camera_app_ui/resources/js/main.js
@@ -266,8 +266,7 @@
 
     const preloadImages = (async () => {
       const loadImage = (url) => new Promise((resolve, reject) => {
-        const link =
-            /** @type {!HTMLLinkElement} */ (document.createElement('link'));
+        const link = dom.create('link', HTMLLinkElement);
         link.rel = 'preload';
         link.as = 'image';
         link.href = url;
diff --git a/chromeos/components/camera_app_ui/resources/js/util.js b/chromeos/components/camera_app_ui/resources/js/util.js
index b4c2a89..92693ac 100644
--- a/chromeos/components/camera_app_ui/resources/js/util.js
+++ b/chromeos/components/camera_app_ui/resources/js/util.js
@@ -24,8 +24,7 @@
  *     Returns canvas element and the context for 2D drawing.
  */
 export function newDrawingCanvas({width, height}) {
-  const canvas =
-      assertInstanceof(document.createElement('canvas'), HTMLCanvasElement);
+  const canvas = dom.create('canvas', HTMLCanvasElement);
   canvas.width = width;
   canvas.height = height;
   const ctx =
@@ -169,8 +168,7 @@
     if (orientation.rotation === 0 && !orientation.flip) {
       onSuccess(blob);
     } else {
-      const original =
-          assertInstanceof(document.createElement('img'), HTMLImageElement);
+      const original = dom.create('img', HTMLImageElement);
       original.onload = function() {
         drawPhoto(original, orientation, onSuccess, onFailure);
       };
@@ -361,9 +359,8 @@
  * @return {!Promise<!Blob>} Promise for the result.
  */
 export async function scalePicture(url, isVideo, width, height = undefined) {
-  const element =
-      /** @type {(!HTMLImageElement|!HTMLVideoElement)} */ (
-          document.createElement(isVideo ? 'video' : 'img'));
+  const element = isVideo ? dom.create('video', HTMLVideoElement) :
+                            dom.create('img', HTMLImageElement);
   if (isVideo) {
     element.preload = 'auto';
   }
@@ -486,8 +483,7 @@
  */
 export async function createUntrustedJSModule(scriptUrl, origin) {
   const untrustedPageReady = new WaitableEvent();
-  const iFrame =
-      /** @type {!HTMLIFrameElement} */ (document.createElement('iframe'));
+  const iFrame = dom.create('iframe', HTMLIFrameElement);
   iFrame.addEventListener('load', () => untrustedPageReady.signal());
   iFrame.setAttribute('src', `${origin}/views/untrusted_script_loader.html`);
   iFrame.hidden = true;
diff --git a/chromeos/components/camera_app_ui/resources/test/test.html b/chromeos/components/camera_app_ui/resources/test/test.html
new file mode 100644
index 0000000..82c75d87
--- /dev/null
+++ b/chromeos/components/camera_app_ui/resources/test/test.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<!-- Copyright 2020 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+<html>
+  <head>
+  </head>
+</html>
diff --git a/chromeos/components/camera_app_ui/url_constants.cc b/chromeos/components/camera_app_ui/url_constants.cc
index 6e1a8b9..b131281 100644
--- a/chromeos/components/camera_app_ui/url_constants.cc
+++ b/chromeos/components/camera_app_ui/url_constants.cc
@@ -8,6 +8,7 @@
 
 const char kChromeUICameraAppHost[] = "camera-app";
 const char kChromeUICameraAppMainURL[] = "chrome://camera-app/views/main.html";
+const char kChromeUICameraAppScopeURL[] = "chrome://camera-app/views";
 const char kChromeUICameraAppURL[] = "chrome://camera-app/";
 const char kChromeUIUntrustedCameraAppURL[] = "chrome-untrusted://camera-app/";
 
diff --git a/chromeos/components/camera_app_ui/url_constants.h b/chromeos/components/camera_app_ui/url_constants.h
index 4a81ad5..95b78c7 100644
--- a/chromeos/components/camera_app_ui/url_constants.h
+++ b/chromeos/components/camera_app_ui/url_constants.h
@@ -9,6 +9,7 @@
 
 extern const char kChromeUICameraAppHost[];
 extern const char kChromeUICameraAppMainURL[];
+extern const char kChromeUICameraAppScopeURL[];
 extern const char kChromeUICameraAppURL[];
 extern const char kChromeUIUntrustedCameraAppURL[];
 
diff --git a/chromeos/constants/chromeos_features.cc b/chromeos/constants/chromeos_features.cc
index c0f56da..59fc41a 100644
--- a/chromeos/constants/chromeos_features.cc
+++ b/chromeos/constants/chromeos_features.cc
@@ -100,6 +100,10 @@
 const base::Feature kAssistAutoCorrect{"AssistAutoCorrect",
                                        base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Controls whether to enable assistive multi word suggestions.
+const base::Feature kAssistMultiWord{"AssistMultiWord",
+                                     base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Controls whether to enable assistive personal information.
 const base::Feature kAssistPersonalInfo{"AssistPersonalInfo",
                                         base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/chromeos/constants/chromeos_features.h b/chromeos/constants/chromeos_features.h
index 834d1ee4..b9cd168 100644
--- a/chromeos/constants/chromeos_features.h
+++ b/chromeos/constants/chromeos_features.h
@@ -62,6 +62,8 @@
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kAssistAutoCorrect;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
+extern const base::Feature kAssistMultiWord;
+COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kAssistPersonalInfo;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kAssistPersonalInfoAddress;
diff --git a/chromeos/constants/chromeos_pref_names.cc b/chromeos/constants/chromeos_pref_names.cc
index 1d44149..c92dd814 100644
--- a/chromeos/constants/chromeos_pref_names.cc
+++ b/chromeos/constants/chromeos_pref_names.cc
@@ -124,5 +124,9 @@
 // the Chrome OS launcher.
 const char kLauncherResultEverLaunched[] = "launcher.result_ever_launched";
 
+// Whether the status of the platform app version of camera app is migrated to
+// SWA.
+const char kHasCameraAppMigratedToSWA[] = "camera.has_migrated_to_swa";
+
 }  // namespace prefs
 }  // namespace chromeos
diff --git a/chromeos/constants/chromeos_pref_names.h b/chromeos/constants/chromeos_pref_names.h
index f5447d5c..09a586c 100644
--- a/chromeos/constants/chromeos_pref_names.h
+++ b/chromeos/constants/chromeos_pref_names.h
@@ -54,6 +54,8 @@
 extern const char kSuggestedContentEnabled[];
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const char kLauncherResultEverLaunched[];
+COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
+extern const char kHasCameraAppMigratedToSWA[];
 
 }  // namespace prefs
 }  // namespace chromeos
diff --git a/chromeos/profiles/atom.afdo.newest.txt b/chromeos/profiles/atom.afdo.newest.txt
index 88ac294c..ff53f0f 100644
--- a/chromeos/profiles/atom.afdo.newest.txt
+++ b/chromeos/profiles/atom.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-atom-89-4324.9-1606128713-benchmark-89.0.4335.0-r1-redacted.afdo.xz
+chromeos-chrome-amd64-atom-89-4324.9-1606128713-benchmark-89.0.4336.0-r1-redacted.afdo.xz
diff --git a/chromeos/profiles/bigcore.afdo.newest.txt b/chromeos/profiles/bigcore.afdo.newest.txt
index f333af2f..e3916aa 100644
--- a/chromeos/profiles/bigcore.afdo.newest.txt
+++ b/chromeos/profiles/bigcore.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-bigcore-89-4324.9-1606129869-benchmark-89.0.4335.0-r1-redacted.afdo.xz
+chromeos-chrome-amd64-bigcore-89-4324.9-1606129869-benchmark-89.0.4336.0-r1-redacted.afdo.xz
diff --git a/components/autofill/content/renderer/password_autofill_agent.cc b/components/autofill/content/renderer/password_autofill_agent.cc
index dd0a03a..83a970cc 100644
--- a/components/autofill/content/renderer/password_autofill_agent.cc
+++ b/components/autofill/content/renderer/password_autofill_agent.cc
@@ -1403,8 +1403,8 @@
   WebFormElement form = cleared_element.Form();
   if (form.IsNull()) {
     // Process password field clearing for fields outside the <form> tag.
-    GetPasswordManagerDriver()->PasswordFormCleared(
-        *GetFormDataFromUnownedInputElements());
+    if (auto unowned_form_data = GetFormDataFromUnownedInputElements())
+      GetPasswordManagerDriver()->PasswordFormCleared(*unowned_form_data);
     return;
   }
   // Process field clearing for a form under a <form> tag.
diff --git a/components/autofill/core/browser/autofill_download_manager_unittest.cc b/components/autofill/core/browser/autofill_download_manager_unittest.cc
index c78f7da..419e5cd 100644
--- a/components/autofill/core/browser/autofill_download_manager_unittest.cc
+++ b/components/autofill/core/browser/autofill_download_manager_unittest.cc
@@ -1946,7 +1946,7 @@
 
   AutofillDownloadManager download_manager(driver_.get(), this);
   FormStructure form_structure(form);
-  form_structure.set_page_language(LanguageCode("fr"));
+  form_structure.set_original_page_language(LanguageCode("fr"));
 
   pref_service_->SetBoolean(
       RandomizedEncoder::kUrlKeyedAnonymizedDataCollectionEnabled, true);
@@ -1983,7 +1983,8 @@
     ASSERT_TRUE(request.ParseFromString(payloads_.front()));
     ASSERT_TRUE(request.has_upload());
     const AutofillUploadContents& upload = request.upload();
-    EXPECT_EQ(upload.language(), form_structure.page_language().value());
+    EXPECT_EQ(upload.language(),
+              form_structure.original_page_language().value());
     ASSERT_TRUE(upload.has_randomized_form_metadata());
     EXPECT_TRUE(upload.randomized_form_metadata().has_id());
     EXPECT_TRUE(upload.randomized_form_metadata().has_name());
diff --git a/components/autofill/core/browser/autofill_handler.cc b/components/autofill/core/browser/autofill_handler.cc
index f408243..914fb25 100644
--- a/components/autofill/core/browser/autofill_handler.cc
+++ b/components/autofill/core/browser/autofill_handler.cc
@@ -40,9 +40,14 @@
   return nullptr;
 }
 
-// Returns true if |live_form| does not match |cached_form|.
+// Returns true if |live_form| does not match |cached_form|, assuming that
+// |live_form|'s language is |live_form_language|.
 bool CachedFormNeedsUpdate(const FormData& live_form,
+                           const LanguageCode& live_form_language,
                            const FormStructure& cached_form) {
+  if (cached_form.original_page_language() != live_form_language)
+    return true;
+
   if (live_form.fields.size() != cached_form.field_count())
     return true;
 
@@ -201,6 +206,7 @@
   driver_->SendFormDataToRenderer(query_id, action, data);
 }
 
+// Returns true if |live_form| does not match |cached_form|.
 bool AutofillHandler::GetCachedFormAndField(const FormData& form,
                                             const FormFieldData& field,
                                             FormStructure** form_structure,
@@ -210,7 +216,7 @@
       FindCachedFormByRendererId(form.unique_renderer_id);
   if (cached_form) {
     DCHECK(cached_form);
-    if (!CachedFormNeedsUpdate(form, *cached_form)) {
+    if (!CachedFormNeedsUpdate(form, GetPageLanguage(), *cached_form)) {
       // There is no data to return if there are no auto-fillable fields.
       if (!cached_form->autofill_count())
         return false;
@@ -288,7 +294,7 @@
       value_from_dynamic_change_form_ = true;
   }
 
-  form_structure->set_page_language(GetPageLanguage());
+  form_structure->set_original_page_language(GetPageLanguage());
 
   form_structure->DetermineHeuristicTypes(log_manager_);
 
diff --git a/components/autofill/core/browser/autofill_manager.cc b/components/autofill/core/browser/autofill_manager.cc
index 1015ea9..bb716b1 100644
--- a/components/autofill/core/browser/autofill_manager.cc
+++ b/components/autofill/core/browser/autofill_manager.cc
@@ -797,7 +797,7 @@
     copied_credit_cards.push_back(*card);
 
   // Annotate the form with the source language of the page.
-  form_structure->set_page_language(LanguageCode(GetPageLanguage()));
+  form_structure->set_original_page_language(GetPageLanguage());
 
   // Attach the Randomized Encoder.
   form_structure->set_randomized_encoder(
@@ -2796,9 +2796,9 @@
 LanguageCode AutofillManager::GetPageLanguage() const {
   DCHECK(client_);
   const translate::LanguageState* language_state = client_->GetLanguageState();
-  if (language_state)
-    return LanguageCode(language_state->original_language());
-  return LanguageCode();
+  if (!language_state)
+    return LanguageCode();
+  return LanguageCode(language_state->original_language());
 }
 
 }  // namespace autofill
diff --git a/components/autofill/core/browser/autofill_manager.h b/components/autofill/core/browser/autofill_manager.h
index 2f0eae83..82af7ed6 100644
--- a/components/autofill/core/browser/autofill_manager.h
+++ b/components/autofill/core/browser/autofill_manager.h
@@ -390,6 +390,8 @@
   FormData* pending_form_data() { return pending_form_data_.get(); }
 
  private:
+  FRIEND_TEST_ALL_PREFIXES(AutofillManagerTest, PageLanguageGetsCorrectlySet);
+
   // Keeps track of the filling context for a form, used to make refill attemps.
   struct FillingContext {
     // |optional_profile| or |optional_credit_card| must be non-null.
diff --git a/components/autofill/core/browser/autofill_manager_unittest.cc b/components/autofill/core/browser/autofill_manager_unittest.cc
index 2cd3b31..5f29a9f 100644
--- a/components/autofill/core/browser/autofill_manager_unittest.cc
+++ b/components/autofill/core/browser/autofill_manager_unittest.cc
@@ -8455,17 +8455,25 @@
 }
 
 TEST_F(AutofillManagerTest, PageLanguageGetsCorrectlySet) {
-  const char* kTestLanguage = "zh";
   FormData form;
   test::CreateTestAddressFormData(&form);
 
-  // Set up language state mock.
-  autofill_client_.GetLanguageState()->SetOriginalLanguage(kTestLanguage);
+  autofill_client_.GetLanguageState()->SetOriginalLanguage("und");
 
-  FormStructure* parsed_form = autofill_manager_->ParseFormForTest(form);
+  autofill_manager_->OnFormsSeen({form}, base::TimeTicks());
+  FormStructure* parsed_form =
+      autofill_manager_->FindCachedFormByRendererId(form.unique_renderer_id);
 
   ASSERT_TRUE(parsed_form);
-  ASSERT_EQ(LanguageCode(kTestLanguage), parsed_form->page_language());
+  ASSERT_EQ(LanguageCode("und"), parsed_form->original_page_language());
+
+  autofill_client_.GetLanguageState()->SetOriginalLanguage("zh");
+
+  autofill_manager_->OnFormsSeen({form}, base::TimeTicks());
+  parsed_form =
+      autofill_manager_->FindCachedFormByRendererId(form.unique_renderer_id);
+
+  ASSERT_EQ(LanguageCode("zh"), parsed_form->original_page_language());
 }
 
 // AutofillManagerTest with kAutofillDisabledMixedForms feature enabled.
diff --git a/components/autofill/core/browser/autofill_profile_sync_util.cc b/components/autofill/core/browser/autofill_profile_sync_util.cc
index 267ad01..f82bcdf 100644
--- a/components/autofill/core/browser/autofill_profile_sync_util.cc
+++ b/components/autofill/core/browser/autofill_profile_sync_util.cc
@@ -48,6 +48,8 @@
       return VerificationStatus::kObserved;
     case sync_pb::AutofillProfileSpecifics_VerificationStatus_USER_VERIFIED:
       return VerificationStatus::kUserVerified;
+    case sync_pb::AutofillProfileSpecifics_VerificationStatus_SERVER_PARSED:
+      return VerificationStatus::kServerParsed;
   }
 }
 
@@ -67,6 +69,8 @@
       return sync_pb::AutofillProfileSpecifics_VerificationStatus_OBSERVED;
     case (VerificationStatus::kUserVerified):
       return sync_pb::AutofillProfileSpecifics_VerificationStatus_USER_VERIFIED;
+    case (VerificationStatus::kServerParsed):
+      return sync_pb::AutofillProfileSpecifics_VerificationStatus_SERVER_PARSED;
   }
 }
 
diff --git a/components/autofill/core/browser/data_model/autofill_profile.cc b/components/autofill/core/browser/data_model/autofill_profile.cc
index d74c8d2..72de7229 100644
--- a/components/autofill/core/browser/data_model/autofill_profile.cc
+++ b/components/autofill/core/browser/data_model/autofill_profile.cc
@@ -451,10 +451,14 @@
   }
 
   for (ServerFieldType type : types) {
-    if (GetVerificationStatus(type) < profile.GetVerificationStatus(type))
+    if (structured_address::IsLessSignificantVerificationStatus(
+            GetVerificationStatus(type), profile.GetVerificationStatus(type))) {
       return -1;
-    if (GetVerificationStatus(type) > profile.GetVerificationStatus(type))
+    }
+    if (structured_address::IsLessSignificantVerificationStatus(
+            profile.GetVerificationStatus(type), GetVerificationStatus(type))) {
       return 1;
+    }
   }
 
   // TODO(crbug.com/1130194): Remove feature check once structured addresses are
@@ -475,11 +479,17 @@
         return comparison;
     }
 
-    for (ServerFieldType type : new_types) {
-      if (GetVerificationStatus(type) < profile.GetVerificationStatus(type))
+    for (ServerFieldType type : types) {
+      if (structured_address::IsLessSignificantVerificationStatus(
+              GetVerificationStatus(type),
+              profile.GetVerificationStatus(type))) {
         return -1;
-      if (GetVerificationStatus(type) > profile.GetVerificationStatus(type))
+      }
+      if (structured_address::IsLessSignificantVerificationStatus(
+              profile.GetVerificationStatus(type),
+              GetVerificationStatus(type))) {
         return 1;
+      }
     }
   }
 
diff --git a/components/autofill/core/browser/data_model/autofill_structured_address.cc b/components/autofill/core/browser/data_model/autofill_structured_address.cc
index 9bdc8a9..49232c5 100644
--- a/components/autofill/core/browser/data_model/autofill_structured_address.cc
+++ b/components/autofill/core/browser/data_model/autofill_structured_address.cc
@@ -135,7 +135,8 @@
 bool StreetAddress::HasNewerValuePrecendenceInMerging(
     const AddressComponent& newer_component) const {
   // If the newer component has a better verification status, use the newer one.
-  if (GetVerificationStatus() < newer_component.GetVerificationStatus())
+  if (IsLessSignificantVerificationStatus(
+          GetVerificationStatus(), newer_component.GetVerificationStatus()))
     return true;
 
   // If the verification statuses are the same, do not use the newer component
diff --git a/components/autofill/core/browser/data_model/autofill_structured_address_component.cc b/components/autofill/core/browser/data_model/autofill_structured_address_component.cc
index 3d13303..c32c74b 100644
--- a/components/autofill/core/browser/data_model/autofill_structured_address_component.cc
+++ b/components/autofill/core/browser/data_model/autofill_structured_address_component.cc
@@ -27,6 +27,28 @@
 
 namespace structured_address {
 
+bool IsLessSignificantVerificationStatus(VerificationStatus left,
+                                         VerificationStatus right) {
+  // Both the KUserVerified and kObserved are larger then kServerParsed although
+  // the underlying integer suggests differently.
+  if (left == VerificationStatus::kServerParsed &&
+      (right == VerificationStatus::kObserved ||
+       right == VerificationStatus::kUserVerified)) {
+    return true;
+  }
+
+  if (right == VerificationStatus::kServerParsed &&
+      (left == VerificationStatus::kObserved ||
+       left == VerificationStatus::kUserVerified)) {
+    return false;
+  }
+
+  // In all other cases, it is sufficient to compare the underlying integer
+  // values.
+  return static_cast<std::underlying_type_t<VerificationStatus>>(left) <
+         static_cast<std::underlying_type_t<VerificationStatus>>(right);
+}
+
 AddressComponent::AddressComponent(ServerFieldType storage_type,
                                    AddressComponent* parent,
                                    std::vector<AddressComponent*> subcomponents,
@@ -701,7 +723,7 @@
 void AddressComponent::MergeVerificationStatuses(
     const AddressComponent& newer_component) {
   if (IsValueAssigned() && (GetValue() == newer_component.GetValue()) &&
-      (GetVerificationStatus() < newer_component.GetVerificationStatus())) {
+      HasNewerValuePrecendenceInMerging(newer_component)) {
     value_verification_status_ = newer_component.GetVerificationStatus();
   }
 
@@ -811,7 +833,7 @@
 
   // If the normalized values are the same, optimize the verification status.
   if ((merge_mode_ & kUseBetterOrNewerForSameValue) && (value == value_newer)) {
-    if (newer_component.GetVerificationStatus() >= GetVerificationStatus()) {
+    if (HasNewerValuePrecendenceInMerging(newer_component)) {
       *this = newer_component;
     }
     return true;
@@ -904,7 +926,8 @@
 
 bool AddressComponent::HasNewerValuePrecendenceInMerging(
     const AddressComponent& newer_component) const {
-  return newer_component.GetVerificationStatus() >= GetVerificationStatus();
+  return !IsLessSignificantVerificationStatus(
+      newer_component.GetVerificationStatus(), GetVerificationStatus());
 }
 
 bool AddressComponent::MergeTokenEquivalentComponent(
@@ -1113,6 +1136,7 @@
     case VerificationStatus::kNoStatus:
     case VerificationStatus::kParsed:
     case VerificationStatus::kFormatted:
+    case VerificationStatus::kServerParsed:
       break;
     case VerificationStatus::kObserved:
       result += 1;
diff --git a/components/autofill/core/browser/data_model/autofill_structured_address_component.h b/components/autofill/core/browser/data_model/autofill_structured_address_component.h
index 9cfc4d1..913bebf2 100644
--- a/components/autofill/core/browser/data_model/autofill_structured_address_component.h
+++ b/components/autofill/core/browser/data_model/autofill_structured_address_component.h
@@ -37,8 +37,15 @@
   kObserved = 3,
   // The user used the autofill settings to verify and store this token.
   kUserVerified = 4,
+  // The token was parsed by the server.
+  kServerParsed = 5,
 };
 
+// Returns true if |left| has a less significant verification status compared to
+// |right|.
+bool IsLessSignificantVerificationStatus(VerificationStatus left,
+                                         VerificationStatus right);
+
 // The merge mode defines if and how two components are merged.
 enum MergeMode {
   // If one component has an empty value, use the non-empty one.
diff --git a/components/autofill/core/browser/data_model/autofill_structured_address_component_unittest.cc b/components/autofill/core/browser/data_model/autofill_structured_address_component_unittest.cc
index b0c79ee..41034c8d 100644
--- a/components/autofill/core/browser/data_model/autofill_structured_address_component_unittest.cc
+++ b/components/autofill/core/browser/data_model/autofill_structured_address_component_unittest.cc
@@ -1663,5 +1663,26 @@
   VerifyTestValues(&older, older_values);
 }
 
+// Test the comparison of different Verification statuses.
+TEST(AutofillStructuredAddressAddressComponent,
+     TestIsLessSignificantVerificationStatus) {
+  EXPECT_TRUE(IsLessSignificantVerificationStatus(
+      VerificationStatus::kParsed, VerificationStatus::kFormatted));
+  EXPECT_TRUE(IsLessSignificantVerificationStatus(
+      VerificationStatus::kParsed, VerificationStatus::kServerParsed));
+  EXPECT_TRUE(IsLessSignificantVerificationStatus(
+      VerificationStatus::kServerParsed, VerificationStatus::kObserved));
+  EXPECT_TRUE(IsLessSignificantVerificationStatus(
+      VerificationStatus::kServerParsed, VerificationStatus::kUserVerified));
+  EXPECT_FALSE(IsLessSignificantVerificationStatus(
+      VerificationStatus::kServerParsed, VerificationStatus::kFormatted));
+  EXPECT_FALSE(IsLessSignificantVerificationStatus(
+      VerificationStatus::kServerParsed, VerificationStatus::kParsed));
+  EXPECT_FALSE(IsLessSignificantVerificationStatus(
+      VerificationStatus::kObserved, VerificationStatus::kServerParsed));
+  EXPECT_FALSE(IsLessSignificantVerificationStatus(
+      VerificationStatus::kUserVerified, VerificationStatus::kServerParsed));
+}
+
 }  // namespace structured_address
 }  // namespace autofill
diff --git a/components/autofill/core/browser/form_structure.cc b/components/autofill/core/browser/form_structure.cc
index d2fa11d..ac7434b 100644
--- a/components/autofill/core/browser/form_structure.cc
+++ b/components/autofill/core/browser/form_structure.cc
@@ -658,7 +658,7 @@
   // prediction routines.
   if (ShouldRunHeuristics()) {
     const FieldCandidatesMap field_type_map = FormField::ParseFormFields(
-        fields_, page_language_, is_form_tag_, log_manager);
+        fields_, original_page_language_, is_form_tag_, log_manager);
     for (const auto& field : fields_) {
       const auto iter = field_type_map.find(field->unique_renderer_id);
       if (iter != field_type_map.end()) {
@@ -711,8 +711,8 @@
   upload->set_data_present(EncodeFieldTypes(available_field_types));
   upload->set_passwords_revealed(passwords_were_revealed_);
   upload->set_has_form_tag(is_form_tag_);
-  if (!page_language_->empty() && randomized_encoder_ != nullptr) {
-    upload->set_language(page_language_.value());
+  if (!original_page_language_->empty() && randomized_encoder_ != nullptr) {
+    upload->set_language(original_page_language_.value());
   }
 
   auto triggering_event = (submission_event_ != SubmissionIndicatorEvent::NONE)
diff --git a/components/autofill/core/browser/form_structure.h b/components/autofill/core/browser/form_structure.h
index f4e54f9..cee4187 100644
--- a/components/autofill/core/browser/form_structure.h
+++ b/components/autofill/core/browser/form_structure.h
@@ -366,10 +366,12 @@
 
   void set_is_rich_query_enabled(bool v) { is_rich_query_enabled_ = v; }
 
-  const LanguageCode& page_language() const { return page_language_; }
+  const LanguageCode& original_page_language() const {
+    return original_page_language_;
+  }
 
-  void set_page_language(LanguageCode language) {
-    page_language_ = std::move(language);
+  void set_original_page_language(LanguageCode language) {
+    original_page_language_ = std::move(language);
   }
 
   bool value_from_dynamic_change_form() const {
@@ -584,9 +586,9 @@
   static base::string16 FindLongestCommonPrefix(
       const std::vector<base::string16>& strings);
 
-  // The language detected for this form's page, prior to any translations
+  // The language detected for this form's page, before any translations
   // performed by Chrome.
-  LanguageCode page_language_;
+  LanguageCode original_page_language_;
 
   // The id attribute of the form.
   base::string16 id_attribute_;
diff --git a/components/autofill/core/browser/pattern_provider/pattern_configuration_parser.cc b/components/autofill/core/browser/pattern_provider/pattern_configuration_parser.cc
index 0dd1e6c..28fbe66 100644
--- a/components/autofill/core/browser/pattern_provider/pattern_configuration_parser.cc
+++ b/components/autofill/core/browser/pattern_provider/pattern_configuration_parser.cc
@@ -5,12 +5,14 @@
 #include "components/autofill/core/browser/pattern_provider/pattern_configuration_parser.h"
 
 #include "base/bind.h"
+#include "base/feature_list.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
 #include "base/values.h"
 #include "components/autofill/core/browser/autofill_type.h"
 #include "components/autofill/core/browser/field_types.h"
 #include "components/autofill/core/browser/pattern_provider/pattern_provider.h"
+#include "components/autofill/core/common/autofill_features.h"
 #include "components/autofill/core/common/language_code.h"
 #include "components/grit/components_resources.h"
 #include "ui/base/resource/resource_bundle.h"
@@ -76,18 +78,17 @@
   return true;
 }
 
-// Callback which is used once the JSON is parsed.
-// |overwrite_equal_version| should be true when loading a remote
-// configuration. If the configuration versions are equal or
-// both unspecified (i.e. set to 0) this prioritizes the remote
+// Callback which is used once the JSON is parsed. If the configuration versions
+// are equal or both unspecified (i.e. set to 0) this prioritizes the remote
 // configuration over the local one.
-void OnJsonParsed(bool overwrite_equal_version,
-                  base::OnceClosure done_callback,
-                  data_decoder::DataDecoder::ValueOrError result) {
-  // Skip any processing in case of an error.
+void OnJsonParsed(data_decoder::DataDecoder::ValueOrError result) {
+  if (!base::FeatureList::IsEnabled(features::kAutofillUseRemotePatterns)) {
+    DVLOG(1) << "Remote patterns are disabled.";
+    return;
+  }
+
   if (!result.value) {
     DVLOG(1) << "Failed to parse PatternProvider configuration JSON string.";
-    std::move(done_callback).Run();
     return;
   }
 
@@ -97,15 +98,12 @@
 
   if (patterns && version.IsValid()) {
     DVLOG(1) << "Successfully parsed PatternProvider configuration.";
-
     PatternProvider& pattern_provider = PatternProvider::GetInstance();
     pattern_provider.SetPatterns(std::move(patterns.value()),
-                                 std::move(version), overwrite_equal_version);
+                                 std::move(version));
   } else {
     DVLOG(1) << "Failed to parse PatternProvider configuration JSON object.";
   }
-
-  std::move(done_callback).Run();
 }
 
 }  // namespace
@@ -169,35 +167,8 @@
 }
 
 void PopulateFromJsonString(std::string json_string) {
-  data_decoder::DataDecoder::ParseJsonIsolated(
-      std::move(json_string),
-      base::BindOnce(&OnJsonParsed, true, base::DoNothing::Once()));
-}
-
-void PopulateFromResourceBundle(base::OnceClosure done_callback) {
-  if (!ui::ResourceBundle::HasSharedInstance()) {
-    VLOG(1) << "Resource Bundle unavailable to load Autofill Matching Pattern "
-               "definitions.";
-    std::move(done_callback).Run();
-    return;
-  }
-
-  ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
-
-  // Load the string from the Resource Bundle on a worker thread, then
-  // securely parse the JSON in a separate process and call |OnJsonParsed|
-  // with the result.
-  base::ThreadPool::PostTaskAndReplyWithResult(
-      FROM_HERE, {base::MayBlock()},
-      base::BindOnce(&ui::ResourceBundle::LoadDataResourceString,
-                     base::Unretained(&bundle), IDR_AUTOFILL_REGEX_JSON),
-      base::BindOnce(
-          [](base::OnceClosure done_callback, std::string resource_string) {
-            data_decoder::DataDecoder::ParseJsonIsolated(
-                std::move(resource_string),
-                base::BindOnce(&OnJsonParsed, false, std::move(done_callback)));
-          },
-          std::move(done_callback)));
+  data_decoder::DataDecoder::ParseJsonIsolated(std::move(json_string),
+                                               base::BindOnce(&OnJsonParsed));
 }
 
 base::Optional<PatternProvider::Map>
diff --git a/components/autofill/core/browser/pattern_provider/pattern_configuration_parser_unittest.cc b/components/autofill/core/browser/pattern_provider/pattern_configuration_parser_unittest.cc
index 1776543..de78bc2 100644
--- a/components/autofill/core/browser/pattern_provider/pattern_configuration_parser_unittest.cc
+++ b/components/autofill/core/browser/pattern_provider/pattern_configuration_parser_unittest.cc
@@ -23,7 +23,7 @@
 // parsed to the map structure used by |PatternProvider| as
 // expected, given the input is valid.
 TEST(PatternConfigurationParserTest, WellFormedParsedCorrectly) {
-  std::string JSON_message = R"(
+  std::string json_message = R"(
     {
       "version": "1.0",
       "FULL_NAME": {
@@ -61,14 +61,14 @@
         ]
       }
     })";
-  base::Optional<base::Value> JSON_object =
-      base::JSONReader::Read(JSON_message);
+  base::Optional<base::Value> json_object =
+      base::JSONReader::Read(json_message);
 
-  ASSERT_TRUE(JSON_object) << "Incorrectly formatted JSON string.";
+  ASSERT_TRUE(json_object) << "Incorrectly formatted JSON string.";
 
-  base::Version version = ExtractVersionFromJsonObject(JSON_object.value());
+  base::Version version = ExtractVersionFromJsonObject(json_object.value());
   base::Optional<PatternProvider::Map> optional_patterns =
-      GetConfigurationFromJsonObject(JSON_object.value());
+      GetConfigurationFromJsonObject(json_object.value());
 
   ASSERT_TRUE(version.IsValid());
   ASSERT_TRUE(optional_patterns);
@@ -101,7 +101,7 @@
 // Test that the parser does not return anything if some |MatchingPattern|
 // object is missing a property.
 TEST(PatternConfigurationParserTest, MalformedMissingProperty) {
-  std::string JSON_message = R"(
+  std::string json_message = R"(
     {
       "version": "1.0",
       "FULL_NAME": {
@@ -126,13 +126,13 @@
         ]
       }
     })";
-  base::Optional<base::Value> JSON_object =
-      base::JSONReader::Read(JSON_message);
+  base::Optional<base::Value> json_object =
+      base::JSONReader::Read(json_message);
 
-  ASSERT_TRUE(JSON_object) << "Incorrectly formatted JSON string.";
+  ASSERT_TRUE(json_object) << "Incorrectly formatted JSON string.";
 
   base::Optional<PatternProvider::Map> optional_patterns =
-      GetConfigurationFromJsonObject(JSON_object.value());
+      GetConfigurationFromJsonObject(json_object.value());
 
   ASSERT_FALSE(optional_patterns);
 }
@@ -140,7 +140,7 @@
 // Test that the parser correctly sets the default version if
 // it is not present in the configuration.
 TEST(PatternConfigurationParserTest, MalformedMissingVersion) {
-  std::string JSON_message = R"(
+  std::string json_message = R"(
     {
       "FULL_NAME": {
         "en": [
@@ -154,12 +154,12 @@
         ]
       }
     })";
-  base::Optional<base::Value> JSON_object =
-      base::JSONReader::Read(JSON_message);
+  base::Optional<base::Value> json_object =
+      base::JSONReader::Read(json_message);
 
-  ASSERT_TRUE(JSON_object) << "Incorrectly formatted JSON string.";
+  ASSERT_TRUE(json_object) << "Incorrectly formatted JSON string.";
 
-  base::Version version = ExtractVersionFromJsonObject(JSON_object.value());
+  base::Version version = ExtractVersionFromJsonObject(json_object.value());
 
   ASSERT_EQ(base::Version("0"), version);
 }
@@ -167,7 +167,7 @@
 // Test that the parser does not return anything if the inner key points
 // to a single object instead of a list.
 TEST(PatternConfigurationParserTest, MalformedNotList) {
-  std::string JSON_message = R"(
+  std::string json_message = R"(
     {
       "FULL_NAME": {
         "en": {
@@ -179,13 +179,13 @@
         }
       }
     })";
-  base::Optional<base::Value> JSON_object =
-      base::JSONReader::Read(JSON_message);
+  base::Optional<base::Value> json_object =
+      base::JSONReader::Read(json_message);
 
-  ASSERT_TRUE(JSON_object) << "Incorrectly formatted JSON string.";
+  ASSERT_TRUE(json_object) << "Incorrectly formatted JSON string.";
 
   base::Optional<PatternProvider::Map> optional_patterns =
-      GetConfigurationFromJsonObject(JSON_object.value());
+      GetConfigurationFromJsonObject(json_object.value());
 
   ASSERT_FALSE(optional_patterns);
 }
diff --git a/components/autofill/core/browser/pattern_provider/pattern_provider.cc b/components/autofill/core/browser/pattern_provider/pattern_provider.cc
index 73fbc08..f7d56f5 100644
--- a/components/autofill/core/browser/pattern_provider/pattern_provider.cc
+++ b/components/autofill/core/browser/pattern_provider/pattern_provider.cc
@@ -71,7 +71,7 @@
   static base::NoDestructor<PatternProvider> instance;
   static bool initialized = false;
   if (!initialized) {
-    instance->SetPatterns(CreateDefaultRegexPatterns(), base::Version(), true);
+    instance->SetPatterns(CreateDefaultRegexPatterns(), base::Version());
     initialized = true;
   }
   return *instance;
@@ -81,15 +81,12 @@
 PatternProvider::~PatternProvider() = default;
 
 void PatternProvider::SetPatterns(PatternProvider::Map patterns,
-                                  const base::Version version,
-                                  const bool overwrite_equal_version) {
+                                  const base::Version& version) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (!pattern_version_.IsValid() ||
-      (version.IsValid() && pattern_version_ < version) ||
-      (version.IsValid() && pattern_version_ == version &&
-       overwrite_equal_version)) {
-    patterns_ = patterns;
+      (version.IsValid() && pattern_version_ <= version)) {
+    patterns_ = std::move(patterns);
     pattern_version_ = version;
     EnrichPatternsWithEnVersion(&patterns_);
     SortPatternsByScore(&patterns_);
diff --git a/components/autofill/core/browser/pattern_provider/pattern_provider.h b/components/autofill/core/browser/pattern_provider/pattern_provider.h
index 3ae65738..767ad35 100644
--- a/components/autofill/core/browser/pattern_provider/pattern_provider.h
+++ b/components/autofill/core/browser/pattern_provider/pattern_provider.h
@@ -27,7 +27,6 @@
  public:
   // The outer keys are field types or other pattern names. The inner keys are
   // page languages in lower case.
-  // TODO(crbug/1142413): decide on uppercase or lowercase.
   using Map = std::map<std::string,
                        std::map<LanguageCode, std::vector<MatchingPattern>>>;
 
@@ -35,9 +34,7 @@
   static PatternProvider& GetInstance();
 
   // Setter for loading patterns from external storage.
-  void SetPatterns(const Map patterns,
-                   const base::Version version,
-                   const bool overwrite_equal_version);
+  void SetPatterns(const Map patterns, const base::Version& version);
 
   // Find the patterns for a given ServerFieldType and for a given
   // |page_language|.
diff --git a/components/autofill/core/browser/pattern_provider/pattern_provider_unittest.cc b/components/autofill/core/browser/pattern_provider/pattern_provider_unittest.cc
index 56b3990..9152d3c 100644
--- a/components/autofill/core/browser/pattern_provider/pattern_provider_unittest.cc
+++ b/components/autofill/core/browser/pattern_provider/pattern_provider_unittest.cc
@@ -18,9 +18,11 @@
 #include "components/autofill/core/browser/pattern_provider/pattern_provider.h"
 #include "components/autofill/core/common/autofill_features.h"
 #include "components/autofill/core/common/language_code.h"
+#include "components/grit/components_resources.h"
 #include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/resource/resource_bundle.h"
 
 namespace autofill {
 
@@ -65,10 +67,44 @@
         patterns[AutofillType::ServerFieldTypeToString(COMPANY_NAME)];
     company_patterns[kLanguageDe] = de_patterns;
     company_patterns[kLanguageEn] = en_patterns;
-    SetPatterns(patterns, base::Version(), true);
+    SetPatterns(std::move(patterns), base::Version());
   }
 };
 
+// Called when the JSON bundle has been parsed, and sets the PatternProvider's
+// patterns.
+void OnJsonParsed(base::OnceClosure done_callback,
+                  data_decoder::DataDecoder::ValueOrError result) {
+  base::Version version =
+      field_type_parsing::ExtractVersionFromJsonObject(result.value.value());
+  base::Optional<PatternProvider::Map> patterns =
+      field_type_parsing::GetConfigurationFromJsonObject(result.value.value());
+  ASSERT_TRUE(patterns);
+  ASSERT_TRUE(version.IsValid());
+  PatternProvider& pattern_provider = PatternProvider::GetInstance();
+  pattern_provider.SetPatterns(std::move(patterns.value()), std::move(version));
+  std::move(done_callback).Run();
+}
+
+// Loads the string from the Resource Bundle on a worker thread.
+void LoadPatternsFromResourceBundle() {
+  ASSERT_TRUE(ui::ResourceBundle::HasSharedInstance());
+  ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+  base::RunLoop run_loop;
+  base::ThreadPool::PostTaskAndReplyWithResult(
+      FROM_HERE, {base::MayBlock()},
+      base::BindOnce(&ui::ResourceBundle::LoadDataResourceString,
+                     base::Unretained(&bundle), IDR_AUTOFILL_REGEX_JSON),
+      base::BindOnce(
+          [](base::OnceClosure done_callback, std::string resource_string) {
+            data_decoder::DataDecoder::ParseJsonIsolated(
+                std::move(resource_string),
+                base::BindOnce(&OnJsonParsed, std::move(done_callback)));
+          },
+          run_loop.QuitClosure()));
+  run_loop.Run();
+}
+
 }  // namespace
 
 bool operator==(const MatchingPattern& mp1, const MatchingPattern& mp2) {
@@ -115,9 +151,7 @@
   ASSERT_NE(default_patterns, PatternProvider::GetInstance().patterns_);
 
   // Load the JSON explicitly from the file.
-  base::RunLoop run_loop;
-  field_type_parsing::PopulateFromResourceBundle(run_loop.QuitClosure());
-  run_loop.Run();
+  LoadPatternsFromResourceBundle();
 
   auto json_version = PatternProvider::GetInstance().pattern_version_;
   auto json_patterns = PatternProvider::GetInstance().patterns_;
diff --git a/components/autofill/core/browser/personal_data_manager_unittest.cc b/components/autofill/core/browser/personal_data_manager_unittest.cc
index e09998c..50fcc72 100644
--- a/components/autofill/core/browser/personal_data_manager_unittest.cc
+++ b/components/autofill/core/browser/personal_data_manager_unittest.cc
@@ -3924,18 +3924,21 @@
   TestAutofillClock test_clock;
   test_clock.SetNow(kArbitraryTime);
 
+  auto Check = [](const AutofillDataModel& data_model, size_t use_count,
+                  base::Time use_date, base::Time modification_date) {
+    EXPECT_EQ(use_count, data_model.use_count());
+    EXPECT_EQ(use_date, data_model.use_date());
+    EXPECT_EQ(modification_date, data_model.modification_date());
+  };
+
   AutofillProfile profile(test::GetFullProfile());
-  EXPECT_EQ(1U, profile.use_count());
-  EXPECT_EQ(kArbitraryTime, profile.use_date());
-  EXPECT_EQ(kArbitraryTime, profile.modification_date());
+  Check(profile, 1u, kArbitraryTime, kArbitraryTime);
   AddProfileToPersonalDataManager(profile);
 
   CreditCard credit_card(base::GenerateGUID(), test::kEmptyOrigin);
   test::SetCreditCardInfo(&credit_card, "John Dillinger",
                           "4234567890123456" /* Visa */, "01", "2999", "1");
-  EXPECT_EQ(1U, credit_card.use_count());
-  EXPECT_EQ(kArbitraryTime, credit_card.use_date());
-  EXPECT_EQ(kArbitraryTime, credit_card.modification_date());
+  Check(credit_card, 1u, kArbitraryTime, kArbitraryTime);
   personal_data_->AddCreditCard(credit_card);
 
   // Make sure everything is set up correctly.
@@ -3950,40 +3953,43 @@
       personal_data_->GetProfileByGUID(profile.guid());
   ASSERT_TRUE(added_profile);
   EXPECT_EQ(*added_profile, profile);
-  EXPECT_EQ(1U, added_profile->use_count());
-  EXPECT_EQ(kArbitraryTime, added_profile->use_date());
-  EXPECT_EQ(kArbitraryTime, added_profile->modification_date());
-
-  base::RunLoop run_loop;
-  EXPECT_CALL(personal_data_observer_, OnPersonalDataFinishedProfileTasks())
-      .WillOnce(QuitMessageLoop(&run_loop));
-  EXPECT_CALL(personal_data_observer_, OnPersonalDataChanged()).Times(1);
-
-  personal_data_->RecordUseOf(profile);
-
-  run_loop.Run();
+  Check(*added_profile, 1u, kArbitraryTime, kArbitraryTime);
 
   CreditCard* added_card =
       personal_data_->GetCreditCardByGUID(credit_card.guid());
   ASSERT_TRUE(added_card);
   EXPECT_EQ(*added_card, credit_card);
-  EXPECT_EQ(1U, added_card->use_count());
-  EXPECT_EQ(kArbitraryTime, added_card->use_date());
-  EXPECT_EQ(kArbitraryTime, added_card->modification_date());
-  personal_data_->RecordUseOf(credit_card);
+  Check(*added_card, 1u, kArbitraryTime, kArbitraryTime);
 
-  // Verify usage stats are updated.
+  // Use |profile|, then verify usage stats.
+  base::RunLoop profile_run_loop;
+  EXPECT_CALL(personal_data_observer_, OnPersonalDataFinishedProfileTasks())
+      .WillOnce(QuitMessageLoop(&profile_run_loop));
+  EXPECT_CALL(personal_data_observer_, OnPersonalDataChanged()).Times(1);
+  personal_data_->RecordUseOf(profile);
+  profile_run_loop.Run();
+
   added_profile = personal_data_->GetProfileByGUID(profile.guid());
-  ASSERT_TRUE(added_profile);
-  EXPECT_EQ(2U, added_profile->use_count());
-  EXPECT_EQ(kSomeLaterTime, added_profile->use_date());
-  EXPECT_EQ(kArbitraryTime, added_profile->modification_date());
-
   added_card = personal_data_->GetCreditCardByGUID(credit_card.guid());
+  ASSERT_TRUE(added_profile);
   ASSERT_TRUE(added_card);
-  EXPECT_EQ(2U, added_card->use_count());
-  EXPECT_EQ(kSomeLaterTime, added_card->use_date());
-  EXPECT_EQ(kArbitraryTime, added_card->modification_date());
+  Check(*added_profile, 2u, kSomeLaterTime, kArbitraryTime);
+  Check(*added_card, 1u, kArbitraryTime, kArbitraryTime);
+
+  // Use |credit_card|, then verify usage stats.
+  base::RunLoop credit_card_run_loop;
+  EXPECT_CALL(personal_data_observer_, OnPersonalDataFinishedProfileTasks())
+      .WillOnce(QuitMessageLoop(&credit_card_run_loop));
+  EXPECT_CALL(personal_data_observer_, OnPersonalDataChanged()).Times(1);
+  personal_data_->RecordUseOf(credit_card);
+  credit_card_run_loop.Run();
+
+  added_profile = personal_data_->GetProfileByGUID(profile.guid());
+  added_card = personal_data_->GetCreditCardByGUID(credit_card.guid());
+  ASSERT_TRUE(added_profile);
+  ASSERT_TRUE(added_card);
+  Check(*added_profile, 2u, kSomeLaterTime, kArbitraryTime);
+  Check(*added_card, 2u, kSomeLaterTime, kArbitraryTime);
 }
 
 TEST_F(PersonalDataManagerTest, ClearAllServerData) {
diff --git a/components/autofill/core/common/autofill_features.cc b/components/autofill/core/common/autofill_features.cc
index 5a5c6a3..1899168 100644
--- a/components/autofill/core/common/autofill_features.cc
+++ b/components/autofill/core/common/autofill_features.cc
@@ -161,12 +161,12 @@
 const base::Feature kAutofillNameSectionsWithRendererIds{
     "AutofillNameSectionsWithRendererIds", base::FEATURE_DISABLED_BY_DEFAULT};
 
-// When enabled, autofill suggestions are displayed in the keyboard accessory
+// When enabled, Autofill suggestions are displayed in the keyboard accessory
 // instead of the regular popup.
 const base::Feature kAutofillKeyboardAccessory{
     "AutofillKeyboardAccessory", base::FEATURE_DISABLED_BY_DEFAULT};
 
-// When enabled, autofill will use new logic to strip both prefixes
+// When enabled, Autofill will use new logic to strip both prefixes
 // and suffixes when setting FormStructure::parseable_name_
 extern const base::Feature kAutofillLabelAffixRemoval{
     "AutofillLabelAffixRemoval", base::FEATURE_DISABLED_BY_DEFAULT};
@@ -180,7 +180,7 @@
 const base::Feature kAutofillOffNoServerData{"AutofillOffNoServerData",
                                              base::FEATURE_DISABLED_BY_DEFAULT};
 
-// If feature is enabled, autofill will be disabled for mixed forms (forms on
+// If feature is enabled, Autofill will be disabled for mixed forms (forms on
 // HTTPS sites that submit over HTTP).
 const base::Feature kAutofillPreventMixedFormsFilling{
     "AutofillPreventMixedFormsFilling", base::FEATURE_DISABLED_BY_DEFAULT};
@@ -285,6 +285,11 @@
     "AutofillUsePageLanguageToSelectFieldParsingPatterns",
     base::FEATURE_DISABLED_BY_DEFAULT};
 
+// When enabled, Autofill will load remote patterns via the component updater.
+// TODO(crbug/1121990): Remove once launched.
+extern const base::Feature kAutofillUseRemotePatterns{
+    "AutofillUseRemotePatterns", base::FEATURE_DISABLED_BY_DEFAULT};
+
 #if defined(OS_ANDROID)
 // Controls whether the Autofill manual fallback for Addresses and Payments is
 // present on Android.
diff --git a/components/autofill/core/common/autofill_features.h b/components/autofill/core/common/autofill_features.h
index 304bfd0..010fbda 100644
--- a/components/autofill/core/common/autofill_features.h
+++ b/components/autofill/core/common/autofill_features.h
@@ -71,6 +71,7 @@
 extern const base::Feature kAutofillUseImprovedLabelDisambiguation;
 extern const base::Feature kAutofillUseNewSectioningMethod;
 extern const base::Feature kAutofillUsePageLanguageToSelectFieldParsingPatterns;
+extern const base::Feature kAutofillUseRemotePatterns;
 
 #if defined(OS_ANDROID)
 extern const base::Feature kAutofillManualFallbackAndroid;
diff --git a/components/autofill/core/common/language_code.h b/components/autofill/core/common/language_code.h
index eb7a678..ae6570f 100644
--- a/components/autofill/core/common/language_code.h
+++ b/components/autofill/core/common/language_code.h
@@ -9,15 +9,18 @@
 #include <string>
 #include <utility>
 
+#include "base/check.h"
 #include "base/ranges/algorithm.h"
 #include "base/types/strong_alias.h"
 
 namespace autofill {
 
 // Following the implicit conventions in //components/translate, a LanguageCode
-// in  is a lowercase alphabetic string of length up to 3, or "zh-CN", or
-// "zh-TW". A non-exhaustive list of common values is
+// is a lowercase alphabetic string of length up to 3, or "zh-CN", or "zh-TW". A
+// non-exhaustive list of common values is
 // translate::kDefaultSupportedLanguages.
+// C++ small string optimization keeps these objects lightweight so that copying
+// should not be a worry.
 class LanguageCode
     : public base::StrongAlias<class LanguageCodeTag, std::string> {
  private:
diff --git a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationDelegate.java b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationDelegate.java
index 62b3ff7..72a6336 100644
--- a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationDelegate.java
+++ b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationDelegate.java
@@ -11,7 +11,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResultType;
+import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResult;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.base.WindowAndroid;
@@ -100,10 +100,10 @@
      * @param intent The intent to be handled by the embedder.
      * @param referrerUrl The referrer for the current navigation.
      * @param fallbackUrl The fallback URL to load if the intent cannot be handled by the embedder.
-     * @return The OverrideUrlLoadingResultType for the action taken by the embedder.
+     * @return The OverrideUrlLoadingResult for the action taken by the embedder.
      */
-    @OverrideUrlLoadingResultType
-    int handleIncognitoIntentTargetingSelf(Intent intent, String referrerUrl, String fallbackUrl);
+    OverrideUrlLoadingResult handleIncognitoIntentTargetingSelf(
+            Intent intent, String referrerUrl, String fallbackUrl);
 
     /**
      * Loads a URL as specified by |loadUrlParams| if possible. May fail in exceptional conditions
diff --git a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
index a89afce..86feda6 100644
--- a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
+++ b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
@@ -204,6 +204,38 @@
     }
 
     /**
+     * Packages information about the result of a check of whether we should override URL loading.
+     */
+    public static class OverrideUrlLoadingResult {
+        @OverrideUrlLoadingResultType
+        int mResultType;
+
+        OverrideUrlLoadingResult(@OverrideUrlLoadingResultType int resultType) {
+            mResultType = resultType;
+        }
+
+        public @OverrideUrlLoadingResultType int getResultType() {
+            return mResultType;
+        }
+
+        public static OverrideUrlLoadingResult forAsyncAction() {
+            return new OverrideUrlLoadingResult(
+                    OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION);
+        }
+        public static OverrideUrlLoadingResult forNoOverride() {
+            return new OverrideUrlLoadingResult(OverrideUrlLoadingResultType.NO_OVERRIDE);
+        }
+        public static OverrideUrlLoadingResult forClobberingTab() {
+            return new OverrideUrlLoadingResult(
+                    OverrideUrlLoadingResultType.OVERRIDE_WITH_CLOBBERING_TAB);
+        }
+        public static OverrideUrlLoadingResult forExternalIntent() {
+            return new OverrideUrlLoadingResult(
+                    OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT);
+        }
+    }
+
+    /**
      * Constructs a new instance of {@link ExternalNavigationHandler}, using the injected
      * {@link ExternalNavigationDelegate}.
      */
@@ -217,8 +249,7 @@
      * @return Whether the URL generated an intent, caused a navigation in
      *         current tab, or wasn't handled at all.
      */
-    public @OverrideUrlLoadingResultType int shouldOverrideUrlLoading(
-            ExternalNavigationParams params) {
+    public OverrideUrlLoadingResult shouldOverrideUrlLoading(ExternalNavigationParams params) {
         if (DEBUG) Log.i(TAG, "shouldOverrideUrlLoading called on " + params.getUrl());
         Intent targetIntent;
         // Perform generic parsing of the URI to turn it into an Intent.
@@ -226,7 +257,7 @@
             targetIntent = Intent.parseUri(params.getUrl(), Intent.URI_INTENT_SCHEME);
         } catch (Exception ex) {
             Log.w(TAG, "Bad URI %s", params.getUrl(), ex);
-            return OverrideUrlLoadingResultType.NO_OVERRIDE;
+            return OverrideUrlLoadingResult.forNoOverride();
         }
 
         String browserFallbackUrl =
@@ -241,14 +272,13 @@
         MutableBoolean canLaunchExternalFallbackResult = new MutableBoolean();
 
         long time = SystemClock.elapsedRealtime();
-        @OverrideUrlLoadingResultType
-        int result = shouldOverrideUrlLoadingInternal(
+        OverrideUrlLoadingResult result = shouldOverrideUrlLoadingInternal(
                 params, targetIntent, browserFallbackUrl, canLaunchExternalFallbackResult);
         assert canLaunchExternalFallbackResult.get() != null;
         RecordHistogram.recordTimesHistogram(
                 "Android.StrictMode.OverrideUrlLoadingTime", SystemClock.elapsedRealtime() - time);
 
-        if (result != OverrideUrlLoadingResultType.NO_OVERRIDE) {
+        if (result.getResultType() != OverrideUrlLoadingResultType.NO_OVERRIDE) {
             int pageTransitionCore = params.getPageTransition() & PageTransition.CORE_MASK;
             boolean isFormSubmit = pageTransitionCore == PageTransition.FORM_SUBMIT;
             boolean isRedirectFromFormSubmit = isFormSubmit && params.isRedirect();
@@ -257,7 +287,8 @@
                         "Android.Intent.LaunchExternalAppFormSubmitHasUserGesture",
                         params.hasUserGesture());
             }
-        } else if (result == OverrideUrlLoadingResultType.NO_OVERRIDE && browserFallbackUrl != null
+        } else if (result.getResultType() == OverrideUrlLoadingResultType.NO_OVERRIDE
+                && browserFallbackUrl != null
                 && (params.getRedirectHandler() == null
                         // For instance, if this is a chained fallback URL, we ignore it.
                         || !params.getRedirectHandler().shouldNotOverrideUrlLoading())) {
@@ -268,7 +299,7 @@
         return result;
     }
 
-    private @OverrideUrlLoadingResultType int handleFallbackUrl(ExternalNavigationParams params,
+    private OverrideUrlLoadingResult handleFallbackUrl(ExternalNavigationParams params,
             Intent targetIntent, String browserFallbackUrl, boolean canLaunchExternalFallback) {
         if (mDelegate.isIntentToInstantApp(targetIntent)) {
             RecordHistogram.recordEnumeratedHistogram("Android.InstantApps.DirectInstantAppsIntent",
@@ -286,7 +317,7 @@
                 List<ResolveInfo> resolvingInfos = queryIntentActivities(intent);
                 if (!isAlreadyInTargetWebApk(resolvingInfos, params)
                         && launchWebApkIfSoleIntentHandler(resolvingInfos, intent)) {
-                    return OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT;
+                    return OverrideUrlLoadingResult.forExternalIntent();
                 }
             } catch (Exception e) {
                 if (DEBUG) Log.i(TAG, "Could not parse fallback url as intent");
@@ -307,7 +338,7 @@
         // http://crbug.com/364522.
         if (!params.isMainFrame()) {
             if (DEBUG) Log.i(TAG, "Don't support fallback url in subframes");
-            return OverrideUrlLoadingResultType.NO_OVERRIDE;
+            return OverrideUrlLoadingResult.forNoOverride();
         }
 
         // NOTE: any further redirection from fall-back URL should not override URL loading.
@@ -321,9 +352,9 @@
         return clobberCurrentTab(browserFallbackUrl, params.getReferrerUrl());
     }
 
-    private void printDebugShouldOverrideUrlLoadingResultType(int result) {
+    private void printDebugShouldOverrideUrlLoadingResultType(OverrideUrlLoadingResult result) {
         String resultString;
-        switch (result) {
+        switch (result.getResultType()) {
             case OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT:
                 resultString = "OVERRIDE_WITH_EXTERNAL_INTENT";
                 break;
@@ -471,7 +502,7 @@
      *         intent.)
      */
     @VisibleForTesting
-    protected @OverrideUrlLoadingResultType int clobberCurrentTab(String url, String referrerUrl) {
+    protected OverrideUrlLoadingResult clobberCurrentTab(String url, String referrerUrl) {
         int transitionType = PageTransition.LINK;
         final LoadUrlParams loadUrlParams = new LoadUrlParams(url, transitionType);
         if (!TextUtils.isEmpty(referrerUrl)) {
@@ -488,7 +519,7 @@
                     mDelegate.loadUrlIfPossible(loadUrlParams);
                 }
             });
-            return OverrideUrlLoadingResultType.OVERRIDE_WITH_CLOBBERING_TAB;
+            return OverrideUrlLoadingResult.forClobberingTab();
         } else {
             assert false : "clobberCurrentTab was called with an empty tab.";
             Uri uri = Uri.parse(url);
@@ -498,7 +529,7 @@
             intent.addCategory(Intent.CATEGORY_BROWSABLE);
             intent.setPackage(packageName);
             startActivity(intent, false, mDelegate);
-            return OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT;
+            return OverrideUrlLoadingResult.forExternalIntent();
         }
     }
 
@@ -779,17 +810,17 @@
      * If the intent can't be resolved, we should fall back to the browserFallbackUrl, or try to
      * find the app on the market if no fallback is provided.
      */
-    private int handleUnresolvableIntent(
+    private OverrideUrlLoadingResult handleUnresolvableIntent(
             ExternalNavigationParams params, Intent targetIntent, String browserFallbackUrl) {
         // Fallback URL will be handled by the caller of shouldOverrideUrlLoadingInternal.
-        if (browserFallbackUrl != null) return OverrideUrlLoadingResultType.NO_OVERRIDE;
+        if (browserFallbackUrl != null) return OverrideUrlLoadingResult.forNoOverride();
         if (targetIntent.getPackage() != null) return handleWithMarketIntent(params, targetIntent);
 
         if (DEBUG) Log.i(TAG, "Could not find an external activity to use");
-        return OverrideUrlLoadingResultType.NO_OVERRIDE;
+        return OverrideUrlLoadingResult.forNoOverride();
     }
 
-    private @OverrideUrlLoadingResultType int handleWithMarketIntent(
+    private OverrideUrlLoadingResult handleWithMarketIntent(
             ExternalNavigationParams params, Intent intent) {
         String marketReferrer = IntentUtils.safeGetStringExtra(intent, EXTRA_MARKET_REFERRER);
         if (TextUtils.isEmpty(marketReferrer)) {
@@ -851,9 +882,9 @@
      * This is the catch-all path for any intent that the app can handle that doesn't have a
      * specialized external app handling it.
      */
-    private @OverrideUrlLoadingResultType int fallBackToHandlingInApp() {
+    private OverrideUrlLoadingResult fallBackToHandlingInApp() {
         if (DEBUG) Log.i(TAG, "No specialized handler for URL");
-        return OverrideUrlLoadingResultType.NO_OVERRIDE;
+        return OverrideUrlLoadingResult.forNoOverride();
     }
 
     /**
@@ -950,7 +981,7 @@
                 params.isRendererInitiated(), params.getInitiatorOrigin());
     }
 
-    private @OverrideUrlLoadingResultType int handleExternalIncognitoIntent(Intent targetIntent,
+    private OverrideUrlLoadingResult handleExternalIncognitoIntent(Intent targetIntent,
             ExternalNavigationParams params, String browserFallbackUrl,
             boolean shouldProxyForInstantApps) {
         // This intent may leave this app. Warn the user that incognito does not carry over
@@ -959,10 +990,10 @@
                     params.shouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent(),
                     shouldProxyForInstantApps)) {
             if (DEBUG) Log.i(TAG, "Incognito navigation out");
-            return OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION;
+            return OverrideUrlLoadingResult.forAsyncAction();
         }
         if (DEBUG) Log.i(TAG, "Failed to show incognito alert dialog.");
-        return OverrideUrlLoadingResultType.NO_OVERRIDE;
+        return OverrideUrlLoadingResult.forNoOverride();
     }
 
     /**
@@ -1129,7 +1160,7 @@
                 || blockExternalNavFromBackgroundTab(params) || ignoreBackForwardNav(params);
     }
 
-    private @OverrideUrlLoadingResultType int shouldOverrideUrlLoadingInternal(
+    private OverrideUrlLoadingResult shouldOverrideUrlLoadingInternal(
             ExternalNavigationParams params, Intent targetIntent,
             @Nullable String browserFallbackUrl, MutableBoolean canLaunchExternalFallbackResult) {
         sanitizeQueryIntentActivitiesIntent(targetIntent);
@@ -1137,29 +1168,29 @@
         canLaunchExternalFallbackResult.set(false);
 
         if (shouldBlockAllExternalAppLaunches(params)) {
-            return OverrideUrlLoadingResultType.NO_OVERRIDE;
+            return OverrideUrlLoadingResult.forNoOverride();
         }
 
         if (handleWithAutofillAssistant(params, targetIntent, browserFallbackUrl)) {
-            return OverrideUrlLoadingResultType.NO_OVERRIDE;
+            return OverrideUrlLoadingResult.forNoOverride();
         }
 
         boolean isExternalProtocol = !UrlUtilities.isAcceptedScheme(params.getUrl());
 
         if (isInternalPdfDownload(isExternalProtocol, params)) {
-            return OverrideUrlLoadingResultType.NO_OVERRIDE;
+            return OverrideUrlLoadingResult.forNoOverride();
         }
 
         // This check should happen for reloads, navigations, etc..., which is why
         // it occurs before the subsequent blocks.
         if (startFileIntentIfNecessary(params, targetIntent)) {
-            return OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION;
+            return OverrideUrlLoadingResult.forAsyncAction();
         }
 
         // This should come after file intents, but before any returns of
         // OVERRIDE_WITH_EXTERNAL_INTENT.
         if (externalIntentRequestsDisabledForUrl(params)) {
-            return OverrideUrlLoadingResultType.NO_OVERRIDE;
+            return OverrideUrlLoadingResult.forNoOverride();
         }
 
         int pageTransitionCore = params.getPageTransition() & PageTransition.CORE_MASK;
@@ -1178,42 +1209,42 @@
                 (isLink && isFromIntent && params.isRedirect()) || isOnEffectiveIntentRedirect;
 
         if (handleCCTRedirectsToInstantApps(params, isExternalProtocol, incomingIntentRedirect)) {
-            return OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT;
+            return OverrideUrlLoadingResult.forExternalIntent();
         } else if (redirectShouldStayInApp(params, isExternalProtocol, targetIntent)) {
-            return OverrideUrlLoadingResultType.NO_OVERRIDE;
+            return OverrideUrlLoadingResult.forNoOverride();
         }
 
         if (!preferToShowIntentPicker(params, pageTransitionCore, isExternalProtocol, isFormSubmit,
                     linkNotFromIntent, incomingIntentRedirect)) {
-            return OverrideUrlLoadingResultType.NO_OVERRIDE;
+            return OverrideUrlLoadingResult.forNoOverride();
         }
 
-        if (isLinkFromChromeInternalPage(params)) return OverrideUrlLoadingResultType.NO_OVERRIDE;
+        if (isLinkFromChromeInternalPage(params)) return OverrideUrlLoadingResult.forNoOverride();
 
         if (handleWtaiMcProtocol(params)) {
-            return OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT;
+            return OverrideUrlLoadingResult.forExternalIntent();
         }
         // TODO: handle other WTAI schemes.
-        if (isUnhandledWtaiProtocol(params)) return OverrideUrlLoadingResultType.NO_OVERRIDE;
+        if (isUnhandledWtaiProtocol(params)) return OverrideUrlLoadingResult.forNoOverride();
 
         boolean hasIntentScheme = params.getUrl().startsWith(UrlConstants.INTENT_URL_SHORT_PREFIX)
                 || params.getUrl().startsWith(UrlConstants.APP_INTENT_URL_SHORT_PREFIX);
         if (hasInternalScheme(params, targetIntent, hasIntentScheme)) {
-            return OverrideUrlLoadingResultType.NO_OVERRIDE;
+            return OverrideUrlLoadingResult.forNoOverride();
         }
 
         if (hasContentScheme(params, targetIntent, hasIntentScheme)) {
-            return OverrideUrlLoadingResultType.NO_OVERRIDE;
+            return OverrideUrlLoadingResult.forNoOverride();
         }
 
         if (hasFileSchemeInIntentURI(targetIntent, hasIntentScheme)) {
-            return OverrideUrlLoadingResultType.NO_OVERRIDE;
+            return OverrideUrlLoadingResult.forNoOverride();
         }
 
-        if (isYoutubePairingCode(params)) return OverrideUrlLoadingResultType.NO_OVERRIDE;
+        if (isYoutubePairingCode(params)) return OverrideUrlLoadingResult.forNoOverride();
 
         if (shouldStayInIncognito(params, isExternalProtocol)) {
-            return OverrideUrlLoadingResultType.NO_OVERRIDE;
+            return OverrideUrlLoadingResult.forNoOverride();
         }
 
         if (!maybeSetSmsPackage(targetIntent)) maybeRecordPhoneIntentMetrics(targetIntent);
@@ -1236,7 +1267,7 @@
         if (!isExternalProtocol && !hasSpecializedHandler) {
             if (fallBackToHandlingWithInstantApp(
                         params, incomingIntentRedirect, linkNotFromIntent)) {
-                return OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT;
+                return OverrideUrlLoadingResult.forExternalIntent();
             }
             return fallBackToHandlingInApp();
         }
@@ -1246,14 +1277,14 @@
 
         if (shouldStayWithinHost(
                     params, isLink, isFormSubmit, resolvingInfos, isExternalProtocol)) {
-            return OverrideUrlLoadingResultType.NO_OVERRIDE;
+            return OverrideUrlLoadingResult.forNoOverride();
         }
 
         boolean isDirectInstantAppsIntent =
                 isExternalProtocol && mDelegate.isIntentToInstantApp(targetIntent);
         boolean shouldProxyForInstantApps = isDirectInstantAppsIntent && isSerpReferrer();
         if (preventDirectInstantAppsIntent(isDirectInstantAppsIntent, shouldProxyForInstantApps)) {
-            return OverrideUrlLoadingResultType.NO_OVERRIDE;
+            return OverrideUrlLoadingResult.forNoOverride();
         }
 
         prepareExternalIntent(targetIntent, params, resolvingInfos, shouldProxyForInstantApps);
@@ -1279,18 +1310,18 @@
 
         if (shouldKeepIntentRedirectInApp(
                     params, incomingIntentRedirect, resolvingInfos, isExternalProtocol)) {
-            return OverrideUrlLoadingResultType.NO_OVERRIDE;
+            return OverrideUrlLoadingResult.forNoOverride();
         }
 
         if (isAlreadyInTargetWebApk(resolvingInfos, params)) {
-            return OverrideUrlLoadingResultType.NO_OVERRIDE;
+            return OverrideUrlLoadingResult.forNoOverride();
         } else if (launchWebApkIfSoleIntentHandler(resolvingInfos, targetIntent)) {
-            return OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT;
+            return OverrideUrlLoadingResult.forExternalIntent();
         }
         if (launchExternalIntent(targetIntent, shouldProxyForInstantApps)) {
-            return OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT;
+            return OverrideUrlLoadingResult.forExternalIntent();
         }
-        return OverrideUrlLoadingResultType.NO_OVERRIDE;
+        return OverrideUrlLoadingResult.forNoOverride();
     }
 
     /**
@@ -1312,7 +1343,7 @@
      * @return OVERRIDE_WITH_EXTERNAL_INTENT when we successfully started market activity,
      *         NO_OVERRIDE otherwise.
      */
-    private @OverrideUrlLoadingResultType int sendIntentToMarket(
+    private OverrideUrlLoadingResult sendIntentToMarket(
             String packageName, String marketReferrer, ExternalNavigationParams params) {
         Uri marketUri =
                 new Uri.Builder()
@@ -1332,7 +1363,7 @@
         if (!deviceCanHandleIntent(intent)) {
             // Exit early if the Play Store isn't available. (https://crbug.com/820709)
             if (DEBUG) Log.i(TAG, "Play Store not installed.");
-            return OverrideUrlLoadingResultType.NO_OVERRIDE;
+            return OverrideUrlLoadingResult.forNoOverride();
         }
 
         if (params.isIncognito()) {
@@ -1340,14 +1371,14 @@
 
                         params.shouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent(), false)) {
                 if (DEBUG) Log.i(TAG, "Failed to show incognito alert dialog.");
-                return OverrideUrlLoadingResultType.NO_OVERRIDE;
+                return OverrideUrlLoadingResult.forNoOverride();
             }
             if (DEBUG) Log.i(TAG, "Incognito intent to Play Store.");
-            return OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION;
+            return OverrideUrlLoadingResult.forAsyncAction();
         } else {
             startActivity(intent, false, mDelegate);
             if (DEBUG) Log.i(TAG, "Intent to Play Store.");
-            return OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT;
+            return OverrideUrlLoadingResult.forExternalIntent();
         }
     }
 
diff --git a/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateImpl.java b/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateImpl.java
index 7b46d1a..817f638 100644
--- a/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateImpl.java
+++ b/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateImpl.java
@@ -81,7 +81,8 @@
 
         ExternalNavigationParams params =
                 new ExternalNavigationParams.Builder(url, incognito).setOpenInNewTab(true).build();
-        mLastOverrideUrlLoadingResultType = mExternalNavHandler.shouldOverrideUrlLoading(params);
+        mLastOverrideUrlLoadingResultType =
+                mExternalNavHandler.shouldOverrideUrlLoading(params).getResultType();
         return mLastOverrideUrlLoadingResultType
                 != ExternalNavigationHandler.OverrideUrlLoadingResultType.NO_OVERRIDE;
     }
@@ -131,7 +132,7 @@
                 buildExternalNavigationParams(navigationParams, redirectHandler, shouldCloseTab)
                         .build();
         @OverrideUrlLoadingResultType
-        int result = mExternalNavHandler.shouldOverrideUrlLoading(params);
+        int result = mExternalNavHandler.shouldOverrideUrlLoading(params).getResultType();
         mLastOverrideUrlLoadingResultType = result;
 
         switch (result) {
diff --git a/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java b/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java
index 5165306..fe07bf1 100644
--- a/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java
+++ b/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java
@@ -34,6 +34,7 @@
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.Batch;
+import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResult;
 import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResultType;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.browser.WebContents;
@@ -1235,9 +1236,9 @@
                 new ExternalNavigationParams.Builder(YOUTUBE_MOBILE_URL, false)
                         .setOpenInNewTab(true)
                         .build();
-        @OverrideUrlLoadingResultType
-        int result = mUrlHandler.shouldOverrideUrlLoading(params);
-        Assert.assertEquals(OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT, result);
+        OverrideUrlLoadingResult result = mUrlHandler.shouldOverrideUrlLoading(params);
+        Assert.assertEquals(
+                OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT, result.getResultType());
         Assert.assertTrue(mDelegate.startActivityIntent != null);
         Assert.assertTrue(
                 mDelegate.startActivityIntent.getBooleanExtra(Browser.EXTRA_CREATE_NEW_TAB, false));
@@ -2031,11 +2032,10 @@
         }
 
         @Override
-        protected @OverrideUrlLoadingResultType int clobberCurrentTab(
-                String url, String referrerUrl) {
+        protected OverrideUrlLoadingResult clobberCurrentTab(String url, String referrerUrl) {
             mNewUrlAfterClobbering = url;
             mReferrerUrlForClobbering = referrerUrl;
-            return OverrideUrlLoadingResultType.OVERRIDE_WITH_CLOBBERING_TAB;
+            return OverrideUrlLoadingResult.forClobberingTab();
         }
     };
 
@@ -2115,11 +2115,11 @@
         }
 
         @Override
-        public @OverrideUrlLoadingResultType int handleIncognitoIntentTargetingSelf(
+        public OverrideUrlLoadingResult handleIncognitoIntentTargetingSelf(
                 Intent intent, String referrerUrl, String fallbackUrl) {
             handleIncognitoIntentTargetingSelfCalled = true;
-            if (mCanLoadUrlInTab) return OverrideUrlLoadingResultType.OVERRIDE_WITH_CLOBBERING_TAB;
-            return OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT;
+            if (mCanLoadUrlInTab) return OverrideUrlLoadingResult.forClobberingTab();
+            return OverrideUrlLoadingResult.forExternalIntent();
         }
 
         @Override
@@ -2412,8 +2412,7 @@
                             .setHasUserGesture(mHasUserGesture)
                             .setIsRendererInitiated(mIsRendererInitiated)
                             .build();
-            @OverrideUrlLoadingResultType
-            int result = mUrlHandler.shouldOverrideUrlLoading(params);
+            OverrideUrlLoadingResult result = mUrlHandler.shouldOverrideUrlLoading(params);
             boolean startActivityCalled = false;
             boolean startWebApkCalled = false;
 
@@ -2431,7 +2430,7 @@
                 }
             }
 
-            Assert.assertEquals(expectedOverrideResult, result);
+            Assert.assertEquals(expectedOverrideResult, result.getResultType());
             Assert.assertEquals(expectStartIncognito, mUrlHandler.mStartIncognitoIntentCalled);
             Assert.assertEquals(expectStartActivity, startActivityCalled);
             Assert.assertEquals(expectStartWebApk, startWebApkCalled);
diff --git a/components/omnibox/browser/autocomplete_controller.cc b/components/omnibox/browser/autocomplete_controller.cc
index 70b9a2aa..fef8a67 100644
--- a/components/omnibox/browser/autocomplete_controller.cc
+++ b/components/omnibox/browser/autocomplete_controller.cc
@@ -317,7 +317,6 @@
         OnDeviceHeadProvider::Create(provider_client_.get(), this);
     if (on_device_head_provider_) {
       providers_.push_back(on_device_head_provider_);
-      on_device_head_provider_->AddModelUpdateCallback();
     }
   }
   if (provider_types & AutocompleteProvider::TYPE_CLIPBOARD) {
diff --git a/components/omnibox/browser/on_device_head_provider.cc b/components/omnibox/browser/on_device_head_provider.cc
index b142afb..3c93e13 100644
--- a/components/omnibox/browser/on_device_head_provider.cc
+++ b/components/omnibox/browser/on_device_head_provider.cc
@@ -22,6 +22,7 @@
 #include "components/omnibox/browser/base_search_provider.h"
 #include "components/omnibox/browser/omnibox_field_trial.h"
 #include "components/omnibox/browser/on_device_head_provider.h"
+#include "components/omnibox/browser/on_device_model_update_listener.h"
 #include "components/omnibox/common/omnibox_features.h"
 #include "components/search_engines/omnibox_focus_type.h"
 #include "components/search_engines/search_terms_data.h"
@@ -94,20 +95,6 @@
 
 OnDeviceHeadProvider::~OnDeviceHeadProvider() {}
 
-void OnDeviceHeadProvider::AddModelUpdateCallback() {
-  // Bail out if we have already subscribed.
-  if (model_update_subscription_) {
-    return;
-  }
-
-  auto* model_update_listener = OnDeviceModelUpdateListener::GetInstance();
-  if (model_update_listener) {
-    model_update_subscription_ = model_update_listener->AddModelUpdateCallback(
-        base::BindRepeating(&OnDeviceHeadProvider::OnModelUpdate,
-                            weak_ptr_factory_.GetWeakPtr()));
-  }
-}
-
 bool OnDeviceHeadProvider::IsOnDeviceHeadProviderAllowed(
     const AutocompleteInput& input) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
@@ -158,7 +145,7 @@
     return;
 
   matches_.clear();
-  if (input.text().empty() || model_filename_.empty())
+  if (input.text().empty() || GetOnDeviceHeadModelFilename().empty())
     return;
 
   // Note |on_device_search_request_id_| has already been changed in |Stop| so
@@ -198,13 +185,6 @@
   done_ = true;
 }
 
-void OnDeviceHeadProvider::OnModelUpdate(
-    const std::string& new_model_filename) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
-  if (!new_model_filename.empty())
-    model_filename_ = new_model_filename;
-}
-
 // TODO(crbug.com/925072): post OnDeviceHeadModel::GetSuggestionsForPrefix
 // directly and remove this function.
 // static
@@ -252,7 +232,8 @@
   base::PostTaskAndReplyWithResult(
       worker_task_runner_.get(), FROM_HERE,
       base::BindOnce(&OnDeviceHeadProvider::GetSuggestionsFromModel,
-                     model_filename_, provider_max_matches_, std::move(params)),
+                     GetOnDeviceHeadModelFilename(), provider_max_matches_,
+                     std::move(params)),
       base::BindOnce(&OnDeviceHeadProvider::SearchDone,
                      weak_ptr_factory_.GetWeakPtr()));
 }
@@ -302,3 +283,10 @@
   done_ = true;
   listener_->OnProviderUpdate(true);
 }
+
+std::string OnDeviceHeadProvider::GetOnDeviceHeadModelFilename() const {
+  auto* model_update_listener = OnDeviceModelUpdateListener::GetInstance();
+  return model_update_listener != nullptr
+             ? model_update_listener->model_filename()
+             : "";
+}
diff --git a/components/omnibox/browser/on_device_head_provider.h b/components/omnibox/browser/on_device_head_provider.h
index 26273b2..8035214f 100644
--- a/components/omnibox/browser/on_device_head_provider.h
+++ b/components/omnibox/browser/on_device_head_provider.h
@@ -14,7 +14,6 @@
 #include "components/omnibox/browser/autocomplete_provider.h"
 #include "components/omnibox/browser/autocomplete_provider_client.h"
 #include "components/omnibox/browser/on_device_head_model.h"
-#include "components/omnibox/browser/on_device_model_update_listener.h"
 
 class AutocompleteProviderListener;
 
@@ -32,10 +31,6 @@
   static OnDeviceHeadProvider* Create(AutocompleteProviderClient* client,
                                       AutocompleteProviderListener* listener);
 
-  // Adds a callback to on device head model updater listener which will update
-  // |model_filename_| once the model is ready on disk.
-  void AddModelUpdateCallback();
-
   void Start(const AutocompleteInput& input, bool minimal_changes) override;
   void Stop(bool clear_cached_results, bool due_to_user_inactivity) override;
   void AddProviderInfo(ProvidersInfo* provider_info) const override;
@@ -67,9 +62,9 @@
   // fetches by DoSearch and then calls OnProviderUpdate.
   void SearchDone(std::unique_ptr<OnDeviceHeadProviderParams> params);
 
-  // Used by OnDeviceModelUpdateListener to notify this provider when new model
-  // is available.
-  void OnModelUpdate(const std::string& new_model_filename);
+  // Helper functions to read model filename from the static
+  // OnDeviceModelUpdateListener instance.
+  std::string GetOnDeviceHeadModelFilename() const;
 
   // Fetches suggestions matching the params from the given on device head
   // model.
@@ -85,22 +80,15 @@
   // added to offload expensive operations out of the UI sequence.
   scoped_refptr<base::SequencedTaskRunner> worker_task_runner_;
 
-  // Sequence checker that ensure utocomplete request handling will only happen
-  // main thread.
+  // Sequence checker that ensure autocomplete request handling will only happen
+  // on main thread.
   SEQUENCE_CHECKER(main_sequence_checker_);
 
-  // The filename points to the on device head model on the disk.
-  std::string model_filename_;
-
   // The request id used to trace current request to the on device head model.
   // The id will be increased whenever a new request is received from the
   // AutocompleteController.
   size_t on_device_search_request_id_;
 
-  // Owns the callback added to the listener such that it can be removed
-  // automatically from the listener on provider's deconstruction.
-  base::CallbackListSubscription model_update_subscription_;
-
   base::WeakPtrFactory<OnDeviceHeadProvider> weak_ptr_factory_{this};
 };
 
diff --git a/components/omnibox/browser/on_device_head_provider_unittest.cc b/components/omnibox/browser/on_device_head_provider_unittest.cc
index 2bb3ce6..4f03c7e 100644
--- a/components/omnibox/browser/on_device_head_provider_unittest.cc
+++ b/components/omnibox/browser/on_device_head_provider_unittest.cc
@@ -15,6 +15,7 @@
 #include "components/omnibox/browser/autocomplete_provider_listener.h"
 #include "components/omnibox/browser/fake_autocomplete_provider_client.h"
 #include "components/omnibox/browser/on_device_head_model.h"
+#include "components/omnibox/browser/on_device_model_update_listener.h"
 #include "components/omnibox/browser/test_scheme_classifier.h"
 #include "components/omnibox/common/omnibox_features.h"
 #include "components/search_engines/omnibox_focus_type.h"
@@ -32,7 +33,6 @@
     client_.reset(new FakeAutocompleteProviderClient());
     SetTestOnDeviceHeadModel();
     provider_ = OnDeviceHeadProvider::Create(client_.get(), this);
-    provider_->AddModelUpdateCallback();
     task_environment_.RunUntilIdle();
   }
 
@@ -60,9 +60,9 @@
   }
 
   void ResetModelInstance() {
-    if (provider_) {
-      provider_->model_filename_.clear();
-    }
+    auto* update_listener = OnDeviceModelUpdateListener::GetInstance();
+    if (update_listener)
+      update_listener->ResetListenerForTest();
   }
 
   bool IsOnDeviceHeadProviderAllowed(const AutocompleteInput& input) {
diff --git a/components/omnibox/browser/on_device_model_update_listener.cc b/components/omnibox/browser/on_device_model_update_listener.cc
index 1cd0fa23..5a67609 100644
--- a/components/omnibox/browser/on_device_model_update_listener.cc
+++ b/components/omnibox/browser/on_device_model_update_listener.cc
@@ -8,7 +8,6 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/post_task.h"
 #include "base/task/thread_pool.h"
-#include "base/task_runner_util.h"
 #include "build/build_config.h"
 
 namespace {
@@ -41,35 +40,28 @@
   return listener.get();
 }
 
-OnDeviceModelUpdateListener::OnDeviceModelUpdateListener()
-    : task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
-          {base::TaskPriority::BEST_EFFORT,
-           base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN, base::MayBlock()})) {}
+std::string OnDeviceModelUpdateListener::model_filename() const {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  return model_filename_;
+}
+
+OnDeviceModelUpdateListener::OnDeviceModelUpdateListener() = default;
 
 OnDeviceModelUpdateListener::~OnDeviceModelUpdateListener() = default;
 
-base::CallbackListSubscription
-OnDeviceModelUpdateListener::AddModelUpdateCallback(
-    ModelUpdateCallback callback) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  if (!model_filename_.empty())
-    callback.Run(model_filename_);
-  return model_update_callbacks_.Add(callback);
-}
-
 void OnDeviceModelUpdateListener::OnModelUpdate(
     const base::FilePath& model_dir) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   if (!model_dir.empty() && model_dir != model_dir_) {
     model_dir_ = model_dir;
-    base::PostTaskAndReplyWithResult(
-        task_runner_.get(), FROM_HERE,
+    base::ThreadPool::PostTaskAndReplyWithResult(
+        FROM_HERE,
+        {base::TaskPriority::BEST_EFFORT,
+         base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN, base::MayBlock()},
         base::BindOnce(&GetModelFilenameFromDirectory, model_dir),
         base::BindOnce([](const std::string filename) {
-          if (!filename.empty()) {
+          if (!filename.empty())
             GetInstance()->model_filename_ = filename;
-            GetInstance()->model_update_callbacks_.Notify(filename);
-          }
         }));
   }
 }
diff --git a/components/omnibox/browser/on_device_model_update_listener.h b/components/omnibox/browser/on_device_model_update_listener.h
index e162176..1d8d32c 100644
--- a/components/omnibox/browser/on_device_model_update_listener.h
+++ b/components/omnibox/browser/on_device_model_update_listener.h
@@ -7,33 +7,22 @@
 
 #include <memory>
 
-#include "base/callback.h"
-#include "base/callback_list.h"
 #include "base/files/file_path.h"
 #include "base/no_destructor.h"
 #include "base/threading/thread_checker.h"
 
-// This class is used by OnDeviceHeadSuggestComponentInstaller to notify
-// OnDeviceHeadProvider when on device model update is finished.
+// This class is used by OnDeviceHeadSuggestComponentInstaller to hold the
+// directory & filename for the on device model downloaded by Component Updater.
 class OnDeviceModelUpdateListener {
  public:
-  using ModelUpdateCallback =
-      base::RepeatingCallback<void(const std::string& new_model_filename)>;
-  using UpdateCallbacks = base::CallbackList<void(const std::string&)>;
-  using UpdateSubscription = UpdateCallbacks::Subscription;
 
   static OnDeviceModelUpdateListener* GetInstance();
 
-  // Adds a callback which will be run on model update. This method will also
-  // notify the provider immediately if a model is available.
-  base::CallbackListSubscription AddModelUpdateCallback(
-      ModelUpdateCallback callback);
-
-  // Called by Component Updater when model update is completed to notify the
-  // on device head provider to reload the model.
+  // Called by Component Updater when model update is completed to update
+  // |model_dir_| and |model_filename_|.
   void OnModelUpdate(const base::FilePath& model_dir);
 
-  std::string model_filename() const { return model_filename_; }
+  std::string model_filename() const;
 
  private:
   friend class base::NoDestructor<OnDeviceModelUpdateListener>;
@@ -53,13 +42,6 @@
   // The filename of the model.
   std::string model_filename_;
 
-  // A list of callbacks which will be run on model update.
-  UpdateCallbacks model_update_callbacks_;
-
-  // The task runner which will be used to run file operations and
-  // |model_update_callbacks_|.
-  scoped_refptr<base::SequencedTaskRunner> task_runner_;
-
   THREAD_CHECKER(thread_checker_);
 };
 
diff --git a/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoController.java b/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoController.java
index 665c995..b0cb261 100644
--- a/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoController.java
+++ b/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoController.java
@@ -488,10 +488,6 @@
             mPendingRunAfterDismissTask.run();
             mPendingRunAfterDismissTask = null;
         }
-        if (mSubpageController != null) {
-            mSubpageController.onSubpageRemoved();
-            mSubpageController = null;
-        }
         mWebContentsObserver.destroy();
         mWebContentsObserver = null;
         PageInfoControllerJni.get().destroy(mNativePageInfoController, PageInfoController.this);
diff --git a/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoCookiesController.java b/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoCookiesController.java
index 9e7ff66..5b17029 100644
--- a/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoCookiesController.java
+++ b/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoCookiesController.java
@@ -122,8 +122,10 @@
     public void onSubpageRemoved() {
         assert mSubPage != null;
         AppCompatActivity host = (AppCompatActivity) mRowView.getContext();
-        host.getSupportFragmentManager().beginTransaction().remove(mSubPage).commitNow();
+        PageInfoCookiesPreference subBage = mSubPage;
         mSubPage = null;
+        if (host.isFinishing()) return;
+        host.getSupportFragmentManager().beginTransaction().remove(subBage).commitNow();
     }
 
     @Override
diff --git a/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoPermissionsController.java b/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoPermissionsController.java
index f99090b..fb30cfa 100644
--- a/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoPermissionsController.java
+++ b/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoPermissionsController.java
@@ -65,8 +65,10 @@
     public void onSubpageRemoved() {
         assert mSubpageFragment != null;
         AppCompatActivity host = (AppCompatActivity) mRowView.getContext();
-        host.getSupportFragmentManager().beginTransaction().remove(mSubpageFragment).commitNow();
+        SingleWebsiteSettings fragment = mSubpageFragment;
         mSubpageFragment = null;
+        if (host.isFinishing()) return;
+        host.getSupportFragmentManager().beginTransaction().remove(fragment).commitNow();
     }
 
     public void setPermissions(PageInfoView.PermissionParams params) {
diff --git a/components/password_manager/core/browser/login_database.cc b/components/password_manager/core/browser/login_database.cc
index 3108abe..f57a05f 100644
--- a/components/password_manager/core/browser/login_database.cc
+++ b/components/password_manager/core/browser/login_database.cc
@@ -594,16 +594,6 @@
 }
 #endif
 
-// This converts `i` to type `Enum`. Terminates in case `i` is outside the valid
-// ranges for `Enum`. Requires `Enum::kMinValue` and `Enum::kMaxValue` to exist
-// and have correct semantics.
-template <typename Enum>
-Enum ToEnumOrDie(int i) {
-  CHECK_LE(static_cast<int>(Enum::kMinValue), i);
-  CHECK_GE(static_cast<int>(Enum::kMaxValue), i);
-  return static_cast<Enum>(i);
-}
-
 }  // namespace
 
 struct LoginDatabase::PrimaryKeyAndPassword {
@@ -1437,9 +1427,11 @@
   form->date_created =
       base::Time::FromInternalValue(s.ColumnInt64(COLUMN_DATE_CREATED));
   form->blocked_by_user = (s.ColumnInt(COLUMN_BLACKLISTED_BY_USER) > 0);
-  form->scheme = ToEnumOrDie<PasswordForm::Scheme>(s.ColumnInt(COLUMN_SCHEME));
+  // TODO(crbug.com/1151214): Add metrics to capture how often these values fall
+  // out of the valid enum range.
+  form->scheme = static_cast<PasswordForm::Scheme>(s.ColumnInt(COLUMN_SCHEME));
   form->type =
-      ToEnumOrDie<PasswordForm::Type>(s.ColumnInt(COLUMN_PASSWORD_TYPE));
+      static_cast<PasswordForm::Type>(s.ColumnInt(COLUMN_PASSWORD_TYPE));
   if (s.ColumnByteLength(COLUMN_POSSIBLE_USERNAME_PAIRS)) {
     base::Pickle pickle(
         static_cast<const char*>(s.ColumnBlob(COLUMN_POSSIBLE_USERNAME_PAIRS)),
@@ -1467,7 +1459,7 @@
       url::Origin::Create(GURL(s.ColumnString(COLUMN_FEDERATION_URL)));
   form->skip_zero_click = (s.ColumnInt(COLUMN_SKIP_ZERO_CLICK) > 0);
   form->generation_upload_status =
-      ToEnumOrDie<PasswordForm::GenerationUploadStatus>(
+      static_cast<PasswordForm::GenerationUploadStatus>(
           s.ColumnInt(COLUMN_GENERATION_UPLOAD_STATUS));
   form->date_last_used = base::Time::FromDeltaSinceWindowsEpoch(
       base::TimeDelta::FromMicroseconds(s.ColumnInt64(COLUMN_DATE_LAST_USED)));
diff --git a/components/password_manager/core/browser/ui/insecure_credentials_manager.cc b/components/password_manager/core/browser/ui/insecure_credentials_manager.cc
index 56d3800..131f87b 100644
--- a/components/password_manager/core/browser/ui/insecure_credentials_manager.cc
+++ b/components/password_manager/core/browser/ui/insecure_credentials_manager.cc
@@ -9,6 +9,7 @@
 #include <set>
 
 #include "base/bind.h"
+#include "base/callback.h"
 #include "base/containers/flat_set.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/ranges/algorithm.h"
@@ -238,13 +239,15 @@
   compromised_credentials_reader_.Init();
 }
 
-void InsecureCredentialsManager::StartWeakCheck() {
+void InsecureCredentialsManager::StartWeakCheck(
+    base::OnceClosure on_check_done) {
   base::ThreadPool::PostTaskAndReplyWithResult(
       FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
       base::BindOnce(&BulkWeakCheck,
                      ExtractPasswords(presenter_->GetSavedPasswords())),
       base::BindOnce(&InsecureCredentialsManager::OnWeakCheckDone,
-                     weak_ptr_factory_.GetWeakPtr(), base::ElapsedTimer()));
+                     weak_ptr_factory_.GetWeakPtr(), base::ElapsedTimer())
+          .Then(std::move(on_check_done)));
 }
 
 void InsecureCredentialsManager::SaveCompromisedCredential(
diff --git a/components/password_manager/core/browser/ui/insecure_credentials_manager.h b/components/password_manager/core/browser/ui/insecure_credentials_manager.h
index 5527477..f1e21e5 100644
--- a/components/password_manager/core/browser/ui/insecure_credentials_manager.h
+++ b/components/password_manager/core/browser/ui/insecure_credentials_manager.h
@@ -8,6 +8,8 @@
 #include <map>
 #include <vector>
 
+#include "base/callback_forward.h"
+#include "base/callback_helpers.h"
 #include "base/containers/flat_set.h"
 #include "base/containers/span.h"
 #include "base/memory/scoped_refptr.h"
@@ -164,7 +166,7 @@
 
   // Computes weak credentials in a separate thread and then passes the result
   // to OnWeakCheckDone.
-  void StartWeakCheck();
+  void StartWeakCheck(base::OnceClosure on_check_done = base::DoNothing());
 
   // Marks all saved credentials which have same username & password as
   // compromised.
diff --git a/components/password_manager/core/browser/ui/insecure_credentials_manager_unittest.cc b/components/password_manager/core/browser/ui/insecure_credentials_manager_unittest.cc
index bcc2bde..9ef6d5b 100644
--- a/components/password_manager/core/browser/ui/insecure_credentials_manager_unittest.cc
+++ b/components/password_manager/core/browser/ui/insecure_credentials_manager_unittest.cc
@@ -8,6 +8,7 @@
 #include "base/strings/string_piece_forward.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/mock_callback.h"
 #include "base/test/task_environment.h"
 #include "base/timer/elapsed_timer.h"
 #include "build/build_config.h"
@@ -509,6 +510,13 @@
               ElementsAreArray(store().stored_passwords().at(kExampleOrg)));
 }
 
+TEST_F(InsecureCredentialsManagerTest, StartWeakCheckNotifiesOnCompletion) {
+  base::MockOnceClosure closure;
+  provider().StartWeakCheck(closure.Get());
+  EXPECT_CALL(closure, Run);
+  RunUntilIdle();
+}
+
 TEST_F(InsecureCredentialsManagerTest, StartWeakCheckOnEmptyPasswordsList) {
   EXPECT_THAT(
       histogram_tester().GetTotalCountsForPrefix("PasswordManager.WeakCheck"),
diff --git a/components/password_manager/core/browser/votes_uploader.cc b/components/password_manager/core/browser/votes_uploader.cc
index f6b904d..7eeae75 100644
--- a/components/password_manager/core/browser/votes_uploader.cc
+++ b/components/password_manager/core/browser/votes_uploader.cc
@@ -369,7 +369,7 @@
   }
 
   // Annotate the form with the source language of the page.
-  form_structure.set_page_language(client_->GetPageLanguage());
+  form_structure.set_original_page_language(client_->GetPageLanguage());
 
   // Attach the Randomized Encoder.
   form_structure.set_randomized_encoder(
@@ -423,7 +423,7 @@
   form_structure.set_upload_required(UPLOAD_REQUIRED);
 
   // Annotate the form with the source language of the page.
-  form_structure.set_page_language(client_->GetPageLanguage());
+  form_structure.set_original_page_language(client_->GetPageLanguage());
 
   // Attach the Randomized Encoder.
   form_structure.set_randomized_encoder(
diff --git a/components/permissions/notification_permission_ui_selector.cc b/components/permissions/notification_permission_ui_selector.cc
index b7e0a12..226b7e6 100644
--- a/components/permissions/notification_permission_ui_selector.cc
+++ b/components/permissions/notification_permission_ui_selector.cc
@@ -37,4 +37,9 @@
   return Decision(UseNormalUi(), ShowNoWarning());
 }
 
+base::Optional<PermissionUmaUtil::PredictionGrantLikelihood>
+NotificationPermissionUiSelector::PredictedGrantLikelihoodForUKM() {
+  return base::nullopt;
+}
+
 }  // namespace permissions
diff --git a/components/permissions/notification_permission_ui_selector.h b/components/permissions/notification_permission_ui_selector.h
index 9888bc5..c2dca79f 100644
--- a/components/permissions/notification_permission_ui_selector.h
+++ b/components/permissions/notification_permission_ui_selector.h
@@ -8,6 +8,7 @@
 #include "base/callback_forward.h"
 #include "base/optional.h"
 #include "components/permissions/permission_request.h"
+#include "components/permissions/permission_uma_util.h"
 
 namespace permissions {
 
@@ -79,6 +80,12 @@
   // can be issued. Can be called when there is no pending request which will
   // simply be a no-op.
   virtual void Cancel() {}
+
+  // Will return the selector's discretized prediction value, if any is
+  // applicable to be recorded in UKMs. This is specific only to a selector that
+  // makes use of the Web Permission Predictions Service to make decisions.
+  virtual base::Optional<PermissionUmaUtil::PredictionGrantLikelihood>
+  PredictedGrantLikelihoodForUKM();
 };
 
 }  // namespace permissions
diff --git a/components/permissions/permission_request_manager.cc b/components/permissions/permission_request_manager.cc
index 2a52cec..c11b3c2 100644
--- a/components/permissions/permission_request_manager.cc
+++ b/components/permissions/permission_request_manager.cc
@@ -23,7 +23,6 @@
 #include "components/permissions/permission_prompt.h"
 #include "components/permissions/permission_request.h"
 #include "components/permissions/permission_request_id.h"
-#include "components/permissions/permission_uma_util.h"
 #include "components/permissions/permissions_client.h"
 #include "components/permissions/switches.h"
 #include "content/public/browser/back_forward_cache.h"
@@ -598,6 +597,7 @@
     selector->Cancel();
 
   current_request_already_displayed_ = false;
+  prediction_grant_likelihood_.reset();
   current_request_ui_to_use_.reset();
   selector_decisions_.clear();
 
@@ -611,7 +611,9 @@
 
   PermissionUmaUtil::PermissionPromptResolved(
       requests_, web_contents(), permission_action,
-      DetermineCurrentRequestUIDispositionForUMA());
+      DetermineCurrentRequestUIDispositionForUMA(),
+      DetermineCurrentRequestUIDispositionReasonForUMA(),
+      prediction_grant_likelihood_);
 
   content::BrowserContext* browser_context =
       web_contents()->GetBrowserContext();
@@ -793,6 +795,12 @@
     }
   }
 
+  if (!prediction_grant_likelihood_.has_value()) {
+    prediction_grant_likelihood_ =
+        notification_permission_ui_selectors_[selector_index]
+            ->PredictedGrantLikelihoodForUKM();
+  }
+
   // We have already made a decision because of a higher priority selector
   // therefore this selector's decision can be discarded.
   if (current_request_ui_to_use_.has_value())
@@ -831,6 +839,24 @@
   return PermissionPromptDisposition::NONE_VISIBLE;
 }
 
+PermissionPromptDispositionReason
+PermissionRequestManager::DetermineCurrentRequestUIDispositionReasonForUMA() {
+  if (!ShouldCurrentRequestUseQuietUI()) {
+    return PermissionPromptDispositionReason::DEFAULT_FALLBACK;
+  }
+
+  switch (ReasonForUsingQuietUi()) {
+    case QuietUiReason::kEnabledInPrefs:
+      return PermissionPromptDispositionReason::USER_PREFERENCE_IN_SETTINGS;
+    case QuietUiReason::kTriggeredByCrowdDeny:
+    case QuietUiReason::kTriggeredDueToAbusiveRequests:
+    case QuietUiReason::kTriggeredDueToAbusiveContent:
+      return PermissionPromptDispositionReason::SAFE_BROWSING_VERDICT;
+    case QuietUiReason::kPredictedVeryUnlikelyGrant:
+      return PermissionPromptDispositionReason::PREDICTION_SERVICE;
+  }
+}
+
 void PermissionRequestManager::LogWarningToConsole(const char* message) {
   web_contents()->GetMainFrame()->AddMessageToConsole(
       blink::mojom::ConsoleMessageLevel::kWarning, message);
diff --git a/components/permissions/permission_request_manager.h b/components/permissions/permission_request_manager.h
index 3ccdbff..f12a19f 100644
--- a/components/permissions/permission_request_manager.h
+++ b/components/permissions/permission_request_manager.h
@@ -16,6 +16,7 @@
 #include "base/observer_list.h"
 #include "components/permissions/notification_permission_ui_selector.h"
 #include "components/permissions/permission_prompt.h"
+#include "components/permissions/permission_uma_util.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/browser/web_contents_user_data.h"
 
@@ -33,6 +34,7 @@
 class PermissionRequest;
 enum class PermissionAction;
 enum class PermissionPromptDisposition;
+enum class PermissionPromptDispositionReason;
 
 // The message to be printed in the Developer Tools console when the quiet
 // notification permission prompt UI is shown on sites with abusive permission
@@ -235,6 +237,8 @@
                                               const UiDecision& decision);
 
   PermissionPromptDisposition DetermineCurrentRequestUIDispositionForUMA();
+  PermissionPromptDispositionReason
+  DetermineCurrentRequestUIDispositionReasonForUMA();
 
   void LogWarningToConsole(const char* message);
 
@@ -307,6 +311,11 @@
   // still waiting on the result from |notification_permission_ui_selectors_|.
   base::Optional<UiDecision> current_request_ui_to_use_;
 
+  // The likelihood value returned by the Web Permission Predictions Service,
+  // to be recoreded in UKM.
+  base::Optional<PermissionUmaUtil::PredictionGrantLikelihood>
+      prediction_grant_likelihood_;
+
   // Whether the bubble is being destroyed by this class, rather than in
   // response to a UI event. In this case, callbacks from the bubble itself
   // should be ignored.
diff --git a/components/permissions/permission_uma_util.cc b/components/permissions/permission_uma_util.cc
index aef1b9e..393dca1 100644
--- a/components/permissions/permission_uma_util.cc
+++ b/components/permissions/permission_uma_util.cc
@@ -127,8 +127,11 @@
     int ignore_count,
     PermissionSourceUI source_ui,
     PermissionPromptDisposition ui_disposition,
+    base::Optional<PermissionPromptDispositionReason> ui_reason,
     base::Optional<bool> has_three_consecutive_denies,
     base::Optional<bool> has_previously_revoked_permission,
+    base::Optional<PermissionUmaUtil::PredictionGrantLikelihood>
+        predicted_grant_likelihood,
     base::Optional<ukm::SourceId> source_id) {
   // Only record the permission change if the origin is in the history.
   if (!source_id.has_value())
@@ -146,6 +149,14 @@
       .SetSource(static_cast<int64_t>(source_ui))
       .SetPromptDisposition(static_cast<int64_t>(ui_disposition));
 
+  if (ui_reason.has_value())
+    builder.SetPromptDispositionReason(static_cast<int64_t>(ui_reason.value()));
+
+  if (predicted_grant_likelihood.has_value()) {
+    builder.SetPredictionsApiResponse_GrantLikelihood(
+        static_cast<int64_t>(predicted_grant_likelihood.value()));
+  }
+
   if (has_three_consecutive_denies.has_value()) {
     int64_t satisfied_adaptive_triggers = 0;
     if (has_three_consecutive_denies.value())
@@ -284,8 +295,9 @@
     RecordPermissionAction(permission, PermissionAction::REVOKED, source_ui,
                            PermissionRequestGestureType::UNKNOWN,
                            PermissionPromptDisposition::NOT_APPLICABLE,
-                           revoked_origin,
-                           /*web_contents=*/nullptr, browser_context);
+                           base::nullopt /* ui_reason */, revoked_origin,
+                           nullptr /* web_contents */, browser_context,
+                           base::nullopt /* predicted_grant_likelihood */);
   }
 }
 
@@ -349,7 +361,9 @@
     const std::vector<PermissionRequest*>& requests,
     content::WebContents* web_contents,
     PermissionAction permission_action,
-    PermissionPromptDisposition ui_disposition) {
+    PermissionPromptDisposition ui_disposition,
+    base::Optional<PermissionPromptDispositionReason> ui_reason,
+    base::Optional<PredictionGrantLikelihood> predicted_grant_likelihood) {
   std::string action_string;
 
   switch (permission_action) {
@@ -388,10 +402,10 @@
     PermissionRequestGestureType gesture_type = request->GetGestureType();
     const GURL& requesting_origin = request->GetOrigin();
 
-    RecordPermissionAction(permission, permission_action,
-                           PermissionSourceUI::PROMPT, gesture_type,
-                           ui_disposition, requesting_origin, web_contents,
-                           web_contents->GetBrowserContext());
+    RecordPermissionAction(
+        permission, permission_action, PermissionSourceUI::PROMPT, gesture_type,
+        ui_disposition, ui_reason, requesting_origin, web_contents,
+        web_contents->GetBrowserContext(), predicted_grant_likelihood);
 
     std::string priorDismissPrefix =
         "Permissions.Prompt." + action_string + ".PriorDismissCount2.";
@@ -552,9 +566,11 @@
     PermissionSourceUI source_ui,
     PermissionRequestGestureType gesture_type,
     PermissionPromptDisposition ui_disposition,
+    base::Optional<PermissionPromptDispositionReason> ui_reason,
     const GURL& requesting_origin,
     const content::WebContents* web_contents,
-    content::BrowserContext* browser_context) {
+    content::BrowserContext* browser_context,
+    base::Optional<PredictionGrantLikelihood> predicted_grant_likelihood) {
   PermissionDecisionAutoBlocker* autoblocker =
       PermissionsClient::Get()->GetPermissionDecisionAutoBlocker(
           browser_context);
@@ -566,14 +582,15 @@
       browser_context, web_contents, requesting_origin,
       base::BindOnce(
           &RecordPermissionActionUkm, action, gesture_type, permission,
-          dismiss_count, ignore_count, source_ui, ui_disposition,
+          dismiss_count, ignore_count, source_ui, ui_disposition, ui_reason,
           permission == ContentSettingsType::NOTIFICATIONS
               ? PermissionsClient::Get()
                     ->HadThreeConsecutiveNotificationPermissionDenies(
                         browser_context)
               : base::nullopt,
           PermissionsClient::Get()->HasPreviouslyAutoRevokedPermission(
-              browser_context, requesting_origin, permission)));
+              browser_context, requesting_origin, permission),
+          predicted_grant_likelihood));
 
   switch (permission) {
     case ContentSettingsType::GEOLOCATION:
diff --git a/components/permissions/permission_uma_util.h b/components/permissions/permission_uma_util.h
index 4bf397f..2183307 100644
--- a/components/permissions/permission_uma_util.h
+++ b/components/permissions/permission_uma_util.h
@@ -13,6 +13,7 @@
 #include "components/permissions/permission_request.h"
 #include "components/permissions/permission_result.h"
 #include "components/permissions/permission_util.h"
+#include "components/permissions/prediction_service/prediction_service_messages.pb.h"
 
 namespace content {
 class BrowserContext;
@@ -106,6 +107,25 @@
   NONE_VISIBLE = 7,
 };
 
+// The reason why the permission prompt disposition was used. Enum used in UKMs,
+// do not re-order or change values. Deprecated items should only be commented
+// out.
+enum class PermissionPromptDispositionReason {
+  // Disposition was selected in prefs.
+  USER_PREFERENCE_IN_SETTINGS = 0,
+
+  // Disposition was chosen because Safe Browsing classifies the origin
+  // as being spammy or abusive with permission requests.
+  SAFE_BROWSING_VERDICT = 1,
+
+  // Disposition was chosen based on grant likelihood predicted by the
+  // Web Permission Prediction Service.
+  PREDICTION_SERVICE = 2,
+
+  // Disposition was used as a fallback, if no selector made a decision.
+  DEFAULT_FALLBACK = 3,
+};
+
 enum class AdaptiveTriggers {
   // None of the adaptive triggers were met. Currently this means two or less
   // consecutive denies in a row.
@@ -126,6 +146,9 @@
 // Provides a convenient way of logging UMA for permission related operations.
 class PermissionUmaUtil {
  public:
+  using PredictionGrantLikelihood =
+      PermissionSuggestion_Likelihood_DiscretizedLikelihood;
+
   static const char kPermissionsPromptShown[];
   static const char kPermissionsPromptShownGesture[];
   static const char kPermissionsPromptShownNoGesture[];
@@ -167,7 +190,9 @@
       const std::vector<PermissionRequest*>& requests,
       content::WebContents* web_contents,
       PermissionAction permission_action,
-      PermissionPromptDisposition ui_disposition);
+      PermissionPromptDisposition ui_disposition,
+      base::Optional<PermissionPromptDispositionReason> ui_reason,
+      base::Optional<PredictionGrantLikelihood> predicted_grant_likelihood);
 
   static void RecordWithBatteryBucket(const std::string& histogram);
 
@@ -225,14 +250,17 @@
   friend class PermissionUmaUtilTest;
 
   // web_contents may be null when for recording non-prompt actions.
-  static void RecordPermissionAction(ContentSettingsType permission,
-                                     PermissionAction action,
-                                     PermissionSourceUI source_ui,
-                                     PermissionRequestGestureType gesture_type,
-                                     PermissionPromptDisposition ui_disposition,
-                                     const GURL& requesting_origin,
-                                     const content::WebContents* web_contents,
-                                     content::BrowserContext* browser_context);
+  static void RecordPermissionAction(
+      ContentSettingsType permission,
+      PermissionAction action,
+      PermissionSourceUI source_ui,
+      PermissionRequestGestureType gesture_type,
+      PermissionPromptDisposition ui_disposition,
+      base::Optional<PermissionPromptDispositionReason> ui_reason,
+      const GURL& requesting_origin,
+      const content::WebContents* web_contents,
+      content::BrowserContext* browser_context,
+      base::Optional<PredictionGrantLikelihood> predicted_grant_likelihood);
 
   // Records |count| total prior actions for a prompt of type |permission|
   // for a single origin using |prefix| for the metric.
diff --git a/components/permissions/prediction_service/BUILD.gn b/components/permissions/prediction_service/BUILD.gn
index 18d731d..96b42b9 100644
--- a/components/permissions/prediction_service/BUILD.gn
+++ b/components/permissions/prediction_service/BUILD.gn
@@ -18,12 +18,12 @@
     "prediction_service_common.h",
   ]
   deps = [
-    ":prediction_service_messages_proto",
     "//components/keyed_service/content",
     "//components/permissions:permissions_common",
     "//services/network/public/cpp:cpp",
     "//third_party/protobuf:protobuf_lite",
   ]
+  public_deps = [ ":prediction_service_messages_proto" ]
 }
 
 source_set("unit_tests") {
@@ -31,7 +31,6 @@
   sources = [ "prediction_service_unittest.cc" ]
   deps = [
     ":prediction_service",
-    ":prediction_service_messages_proto",
     "//base/test:test_support",
     "//components/permissions:permissions_common",
     "//services/network:test_support",
diff --git a/components/search_engines/template_url_service.cc b/components/search_engines/template_url_service.cc
index 2e488fc..206536a 100644
--- a/components/search_engines/template_url_service.cc
+++ b/components/search_engines/template_url_service.cc
@@ -1007,11 +1007,6 @@
     const std::string error_msg =
         "ProcessSyncChanges failed on ChangeType " +
         syncer::SyncChange::ChangeTypeToString(iter->change_type());
-    if (iter->change_type() == syncer::SyncChange::ACTION_INVALID) {
-      error = sync_error_factory_->CreateAndUploadError(FROM_HERE, error_msg);
-      continue;
-    }
-
     if (iter->change_type() == syncer::SyncChange::ACTION_DELETE) {
       if (!existing_turl) {
         // Can't DELETE a non-existent engine, although we log it.
@@ -1231,7 +1226,6 @@
     const base::Location& from_here,
     const TemplateURL* turl,
     syncer::SyncChange::SyncChangeType type) {
-  DCHECK_NE(type, syncer::SyncChange::ACTION_INVALID);
   DCHECK(turl);
 
   if (!models_associated_)
diff --git a/components/signin/core/browser/android/java/src/org/chromium/components/signin/ProfileDataSource.java b/components/signin/core/browser/android/java/src/org/chromium/components/signin/ProfileDataSource.java
index a8a8101..617e077 100644
--- a/components/signin/core/browser/android/java/src/org/chromium/components/signin/ProfileDataSource.java
+++ b/components/signin/core/browser/android/java/src/org/chromium/components/signin/ProfileDataSource.java
@@ -72,8 +72,21 @@
         /**
          * Notifies that an account's profile data has been updated.
          * @param accountEmail An account email.
+         *
+         * This method will be removed after migrating all the callers to the second method
          */
+        @Deprecated
         void onProfileDataUpdated(String accountEmail);
+
+        /**
+         * Notifies that an account's profile data has been updated.
+         */
+        void onProfileDataUpdated(ProfileData profileData);
+
+        /**
+         * Removes the profile data of a given accountEmail.
+         */
+        void removeProfileData(String accountEmail);
     }
 
     /**
diff --git a/components/sync/BUILD.gn b/components/sync/BUILD.gn
index 7bba5178..fb8dc36 100644
--- a/components/sync/BUILD.gn
+++ b/components/sync/BUILD.gn
@@ -476,6 +476,7 @@
     "nigori/nigori_unittest.cc",
     "protocol/proto_enum_conversions_unittest.cc",
     "protocol/proto_value_conversions_unittest.cc",
+    "trusted_vault/download_keys_response_handler_unittest.cc",
     "trusted_vault/securebox_unittest.cc",
     "trusted_vault/standalone_trusted_vault_backend_unittest.cc",
     "trusted_vault/trusted_vault_access_token_fetcher_frontend_unittest.cc",
diff --git a/components/sync/model/sync_change.cc b/components/sync/model/sync_change.cc
index 8b56fbe..c618a23 100644
--- a/components/sync/model/sync_change.cc
+++ b/components/sync/model/sync_change.cc
@@ -10,8 +10,6 @@
 
 namespace syncer {
 
-SyncChange::SyncChange() : change_type_(ACTION_INVALID) {}
-
 SyncChange::SyncChange(const base::Location& from_here,
                        SyncChangeType change_type,
                        const SyncData& sync_data)
@@ -22,7 +20,9 @@
 SyncChange::~SyncChange() {}
 
 bool SyncChange::IsValid() const {
-  if (change_type_ == ACTION_INVALID || !sync_data_.IsValid())
+  // TODO(crbug.com/1152824): This implementation could be simplified if the
+  // public API provides guarantees around when it returns false.
+  if (!sync_data_.IsValid())
     return false;
 
   // Data from the syncer must always have valid specifics.
@@ -57,8 +57,6 @@
 // static
 std::string SyncChange::ChangeTypeToString(SyncChangeType change_type) {
   switch (change_type) {
-    case ACTION_INVALID:
-      return "ACTION_INVALID";
     case ACTION_ADD:
       return "ACTION_ADD";
     case ACTION_UPDATE:
diff --git a/components/sync/model/sync_change.h b/components/sync/model/sync_change.h
index 93a4798..8b786c1 100644
--- a/components/sync/model/sync_change.h
+++ b/components/sync/model/sync_change.h
@@ -22,28 +22,28 @@
 class SyncChange {
  public:
   enum SyncChangeType {
-    ACTION_INVALID,
     ACTION_ADD,
     ACTION_UPDATE,
     ACTION_DELETE,
   };
 
-  // Default constructor creates an invalid change.
-  SyncChange();
   // Create a new change with the specified sync data.
   SyncChange(const base::Location& from_here,
              SyncChangeType change_type,
              const SyncData& sync_data);
+  // Copy constructor and assignment operator welcome.
+  SyncChange(const SyncChange&) = default;
+  SyncChange& operator=(const SyncChange&) = default;
+  // Move constructor and assignment operator allowed (although questionable).
+  // TODO(crbug.com/1152824): Avoid move semantics if that leads invalid state.
+  SyncChange(SyncChange&&) = default;
+  SyncChange& operator=(SyncChange&&) = default;
   ~SyncChange();
 
-  // Copy constructor and assignment operator welcome.
-
   // Whether this change is valid. This must be true before attempting to access
-  // the data.
-  // Deletes: Requires valid tag when going to the syncer. Requires valid
-  //          specifics when coming from the syncer.
-  // Adds, Updates: Require valid tag and specifics when going to the syncer.
-  //                Require only valid specifics when coming from the syncer.
+  // the data. It may only return false for moved-away instances (unspecified
+  // behavior). Otherwise it's guaranteed to return true.
+  // TODO(crbug.com/1152824): Remove this API once move semantics are removed.
   bool IsValid() const;
 
   // Getters.
diff --git a/components/sync/model_impl/syncable_service_based_bridge.cc b/components/sync/model_impl/syncable_service_based_bridge.cc
index 5852bbb..2895911 100644
--- a/components/sync/model_impl/syncable_service_based_bridge.cc
+++ b/components/sync/model_impl/syncable_service_based_bridge.cc
@@ -70,7 +70,7 @@
       return SyncChange::ACTION_UPDATE;
   }
   NOTREACHED();
-  return SyncChange::ACTION_INVALID;
+  return SyncChange::ACTION_UPDATE;
 }
 
 // Parses the content of |record_list| into |*in_memory_store|. The output
@@ -133,10 +133,6 @@
 
     for (const SyncChange& change : change_list) {
       switch (change.change_type()) {
-        case SyncChange::ACTION_INVALID:
-          NOTREACHED() << " from " << change.location().ToString();
-          break;
-
         case SyncChange::ACTION_ADD:
         case SyncChange::ACTION_UPDATE: {
           DCHECK_EQ(type_, change.sync_data().GetDataType());
diff --git a/components/sync/protocol/autofill_specifics.proto b/components/sync/protocol/autofill_specifics.proto
index 671413b..d8dd598 100644
--- a/components/sync/protocol/autofill_specifics.proto
+++ b/components/sync/protocol/autofill_specifics.proto
@@ -40,6 +40,8 @@
     // This is currently only applicable to the full name, since users cannot
     // edit individual components of their name.
     USER_VERIFIED = 4;
+    // The token was parsed remotely.
+    SERVER_PARSED = 5;
   }
 
   optional string guid = 15;
diff --git a/components/sync/protocol/proto_enum_conversions.cc b/components/sync/protocol/proto_enum_conversions.cc
index 853885b..a2f6ee7 100644
--- a/components/sync/protocol/proto_enum_conversions.cc
+++ b/components/sync/protocol/proto_enum_conversions.cc
@@ -49,7 +49,7 @@
 const char* ProtoEnumToString(
     sync_pb::AutofillProfileSpecifics::VerificationStatus status) {
   ASSERT_ENUM_BOUNDS(sync_pb::AutofillProfileSpecifics, VerificationStatus,
-                     VERIFICATION_STATUS_UNSPECIFIED, USER_VERIFIED);
+                     VERIFICATION_STATUS_UNSPECIFIED, SERVER_PARSED);
   switch (status) {
     ENUM_CASE(sync_pb::AutofillProfileSpecifics,
               VERIFICATION_STATUS_UNSPECIFIED);
@@ -57,6 +57,7 @@
     ENUM_CASE(sync_pb::AutofillProfileSpecifics, FORMATTED);
     ENUM_CASE(sync_pb::AutofillProfileSpecifics, OBSERVED);
     ENUM_CASE(sync_pb::AutofillProfileSpecifics, USER_VERIFIED);
+    ENUM_CASE(sync_pb::AutofillProfileSpecifics, SERVER_PARSED);
   }
   NOTREACHED();
   return "";
diff --git a/components/sync/protocol/vault.proto b/components/sync/protocol/vault.proto
index ebc51e1..55a3835 100644
--- a/components/sync/protocol/vault.proto
+++ b/components/sync/protocol/vault.proto
@@ -13,6 +13,7 @@
   optional int32 epoch = 1;
   optional bytes wrapped_key = 2;
   optional bytes member_proof = 3;
+  optional bytes key_proof = 4;
 }
 
 message SecurityDomain {
@@ -29,3 +30,7 @@
 message JoinSecurityDomainsRequest {
   repeated SecurityDomain security_domains = 1;
 }
+
+message ListSecurityDomainsResponse {
+  repeated SecurityDomain security_domains = 1;
+}
diff --git a/components/sync/trusted_vault/BUILD.gn b/components/sync/trusted_vault/BUILD.gn
index 8494610..fb4341b 100644
--- a/components/sync/trusted_vault/BUILD.gn
+++ b/components/sync/trusted_vault/BUILD.gn
@@ -4,6 +4,8 @@
 
 static_library("trusted_vault") {
   sources = [
+    "download_keys_response_handler.cc",
+    "download_keys_response_handler.h",
     "securebox.cc",
     "securebox.h",
     "standalone_trusted_vault_backend.cc",
diff --git a/components/sync/trusted_vault/download_keys_response_handler.cc b/components/sync/trusted_vault/download_keys_response_handler.cc
new file mode 100644
index 0000000..b733635
--- /dev/null
+++ b/components/sync/trusted_vault/download_keys_response_handler.cc
@@ -0,0 +1,220 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/sync/trusted_vault/download_keys_response_handler.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "components/sync/protocol/vault.pb.h"
+#include "components/sync/trusted_vault/securebox.h"
+#include "crypto/hmac.h"
+
+namespace syncer {
+
+namespace {
+
+// TODO(crbug.com/1113598): move these constants to a dedicated header, since
+// they are used in multiple places already.
+const size_t kKeyProofLength = 32;
+const uint8_t kWrappedKeyHeader[] = {'V', '1', ' ', 's', 'h', 'a', 'r',
+                                     'e', 'd', '_', 'k', 'e', 'y'};
+const char kSecurityDomainName[] = "chromesync";
+
+struct ExtractedSharedKey {
+  int version;
+  std::vector<uint8_t> trusted_vault_key;
+  std::vector<uint8_t> key_proof;
+};
+
+// Returns pointer to sync security domain in |response|. Returns nullptr if
+// there is no sync security domain.
+const sync_pb::SecurityDomain* FindSyncSecurityDomain(
+    const sync_pb::ListSecurityDomainsResponse& response) {
+  for (const sync_pb::SecurityDomain& security_domain :
+       response.security_domains()) {
+    if (security_domain.name() == kSecurityDomainName) {
+      return &security_domain;
+    }
+  }
+  return nullptr;
+}
+
+// Returns pointer to the member in |response| corresponding to
+// |member_public_key|. Returns nullptr if sync security domain doesn't exist
+// in |response| or there is no such member in sync security domain.
+const sync_pb::SecurityDomain::Member* FindMember(
+    const sync_pb::ListSecurityDomainsResponse& response,
+    const std::vector<uint8_t>& member_public_key_bytes) {
+  const sync_pb::SecurityDomain* sync_security_domain =
+      FindSyncSecurityDomain(response);
+  if (!sync_security_domain) {
+    return nullptr;
+  }
+
+  const std::string member_public_key_string(member_public_key_bytes.begin(),
+                                             member_public_key_bytes.end());
+  for (const sync_pb::SecurityDomain::Member& member :
+       sync_security_domain->members()) {
+    if (member.public_key() == member_public_key_string) {
+      return &member;
+    }
+  }
+  return nullptr;
+}
+
+// Extracts (decrypts |wrapped_key| and converts to ExtractedSharedKey) shared
+// keys from |member| and sorts them by version.
+std::vector<ExtractedSharedKey> ExtractAndSortSharedKeys(
+    const sync_pb::SecurityDomain::Member& member,
+    const SecureBoxPrivateKey& member_private_key) {
+  std::vector<ExtractedSharedKey> result;
+  for (const sync_pb::SharedKey& shared_key : member.keys()) {
+    std::vector<uint8_t> wrapped_key(shared_key.wrapped_key().begin(),
+                                     shared_key.wrapped_key().end());
+    base::Optional<std::vector<uint8_t>> decrypted_key =
+        member_private_key.Decrypt(base::span<const uint8_t>(),
+                                   kWrappedKeyHeader, wrapped_key);
+    if (!decrypted_key.has_value()) {
+      // Decryption failed.
+      return std::vector<ExtractedSharedKey>();
+    }
+    result.push_back(
+        ExtractedSharedKey{/*version=*/shared_key.epoch(), *decrypted_key,
+                           /*key_proof=*/
+                           std::vector<uint8_t>(shared_key.key_proof().begin(),
+                                                shared_key.key_proof().end())});
+  }
+
+  std::sort(result.begin(), result.end(),
+            [](const ExtractedSharedKey& a, const ExtractedSharedKey& b) {
+              return a.version < b.version;
+            });
+  return result;
+}
+
+// Validates |key_proof| starting from the key next to
+// |last_known_trusted_vault_key|, returns false if validation fails or |keys|
+// doesn't have a key next to |last_known_trusted_vault_key|.
+bool IsValidKeyChain(const std::vector<ExtractedSharedKey>& key_chain,
+                     const std::vector<uint8_t>& last_known_trusted_vault_key,
+                     const int last_known_trusted_vault_key_version) {
+  DCHECK(!key_chain.empty());
+  if (key_chain.back().version <= last_known_trusted_vault_key_version) {
+    // |keys| doesn't contain any new key. Note: this may mean that key rotation
+    // happened, but state corresponding to the current member wasn't updated.
+    return false;
+  }
+  int last_valid_key_version = last_known_trusted_vault_key_version;
+  std::vector<uint8_t> last_valid_key = last_known_trusted_vault_key;
+  for (const ExtractedSharedKey& next_key : key_chain) {
+    if (next_key.version <= last_valid_key_version) {
+      continue;
+    }
+    if (next_key.version != last_valid_key_version + 1) {
+      // Missing intermediate key.
+      return false;
+    }
+
+    crypto::HMAC hmac(crypto::HMAC::SHA256);
+    CHECK(hmac.Init(last_valid_key));
+
+    std::vector<uint8_t> digest_bytes(kKeyProofLength);
+    if (!hmac.Verify(next_key.trusted_vault_key, next_key.key_proof)) {
+      // |key_proof| isn't valid.
+      return false;
+    }
+    last_valid_key_version = next_key.version;
+    last_valid_key = next_key.trusted_vault_key;
+  }
+
+  return true;
+}
+
+}  // namespace
+
+DownloadKeysResponseHandler::ProcessedResponse::ProcessedResponse(
+    TrustedVaultRequestStatus status)
+    : status(status), last_key_version(0) {}
+
+DownloadKeysResponseHandler::ProcessedResponse::ProcessedResponse(
+    TrustedVaultRequestStatus status,
+    std::vector<std::vector<uint8_t>> keys,
+    int last_key_version)
+    : status(status), keys(keys), last_key_version(last_key_version) {}
+
+DownloadKeysResponseHandler::ProcessedResponse::ProcessedResponse(
+    const ProcessedResponse& other) = default;
+
+DownloadKeysResponseHandler::ProcessedResponse&
+DownloadKeysResponseHandler::ProcessedResponse::operator=(
+    const ProcessedResponse& other) = default;
+
+DownloadKeysResponseHandler::ProcessedResponse::~ProcessedResponse() = default;
+
+DownloadKeysResponseHandler::DownloadKeysResponseHandler(
+    const std::vector<uint8_t>& last_trusted_vault_key,
+    int last_trusted_vault_key_version,
+    std::unique_ptr<SecureBoxKeyPair> device_key_pair)
+    : last_trusted_vault_key_(last_trusted_vault_key),
+      last_trusted_vault_key_version_(last_trusted_vault_key_version),
+      device_key_pair_(std::move(device_key_pair)) {
+  DCHECK(device_key_pair_);
+}
+
+DownloadKeysResponseHandler::~DownloadKeysResponseHandler() = default;
+
+DownloadKeysResponseHandler::ProcessedResponse
+DownloadKeysResponseHandler::ProcessResponse(
+    TrustedVaultRequest::HttpStatus http_status,
+    const std::string& response_body) const {
+  switch (http_status) {
+    case TrustedVaultRequest::HttpStatus::kSuccess:
+      break;
+    case TrustedVaultRequest::HttpStatus::kOtherError:
+    case TrustedVaultRequest::HttpStatus::kBadRequest:
+      // Don't distinguish kBadRequest here, because request content doesn't
+      // depend on the local state.
+      return ProcessedResponse(
+          /*status=*/TrustedVaultRequestStatus::kOtherError);
+  }
+
+  sync_pb::ListSecurityDomainsResponse deserialized_response;
+  if (!deserialized_response.ParseFromString(response_body)) {
+    return ProcessedResponse(/*status=*/TrustedVaultRequestStatus::kOtherError);
+  }
+
+  const sync_pb::SecurityDomain::Member* current_member = FindMember(
+      deserialized_response, device_key_pair_->public_key().ExportToBytes());
+  if (!current_member) {
+    // |device_key_pair_| isn't registered server-side, while client assumes
+    // it's registered when downloading keys.
+    return ProcessedResponse(
+        /*status=*/TrustedVaultRequestStatus::kLocalDataObsolete);
+  }
+
+  std::vector<ExtractedSharedKey> extracted_keys = ExtractAndSortSharedKeys(
+      *current_member, device_key_pair_->private_key());
+  if (extracted_keys.empty() ||
+      !IsValidKeyChain(extracted_keys, last_trusted_vault_key_,
+                       last_trusted_vault_key_version_)) {
+    return ProcessedResponse(
+        /*status=*/TrustedVaultRequestStatus::kLocalDataObsolete);
+  }
+
+  std::vector<std::vector<uint8_t>> new_keys;
+  for (const ExtractedSharedKey& key : extracted_keys) {
+    if (key.version >= last_trusted_vault_key_version_) {
+      // Don't include previous keys into the result, because they weren't
+      // validated using |last_trusted_vault_key_| and client should be already
+      // aware of them.
+      new_keys.push_back(key.trusted_vault_key);
+    }
+  }
+  return ProcessedResponse(/*status=*/TrustedVaultRequestStatus::kSuccess,
+                           new_keys,
+                           /*last_key_version=*/extracted_keys.back().version);
+}
+
+}  // namespace syncer
diff --git a/components/sync/trusted_vault/download_keys_response_handler.h b/components/sync/trusted_vault/download_keys_response_handler.h
new file mode 100644
index 0000000..f8fe26b
--- /dev/null
+++ b/components/sync/trusted_vault/download_keys_response_handler.h
@@ -0,0 +1,73 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_SYNC_TRUSTED_VAULT_DOWNLOAD_KEYS_RESPONSE_HANDLER_H_
+#define COMPONENTS_SYNC_TRUSTED_VAULT_DOWNLOAD_KEYS_RESPONSE_HANDLER_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "components/sync/trusted_vault/trusted_vault_connection.h"
+#include "components/sync/trusted_vault/trusted_vault_request.h"
+
+namespace syncer {
+
+class SecureBoxKeyPair;
+
+// Helper class to extract and validate trusted vault keys from
+// ListSecurityDomainsResponse.
+class DownloadKeysResponseHandler {
+ public:
+  struct ProcessedResponse {
+    explicit ProcessedResponse(TrustedVaultRequestStatus status);
+    ProcessedResponse(TrustedVaultRequestStatus status,
+                      std::vector<std::vector<uint8_t>> keys,
+                      int last_key_version);
+    ProcessedResponse(const ProcessedResponse& other);
+    ProcessedResponse& operator=(const ProcessedResponse& other);
+    ~ProcessedResponse();
+
+    // kSuccess is reported if extraction was successful and there are new
+    // trusted vault keys.
+    // kLocalDataObsolete is reported if it's impossible to extract keys due to
+    // data corruption or absence of SecurityDomain/Member or if there is no new
+    // keys.
+    // kOtherError is reported in case of http/network errors or if the response
+    // isn't valid serialized ListSecurityDomainsResponse proto.
+    TrustedVaultRequestStatus status;
+
+    // Contains new keys and potentially |last_trusted_vault_key| if it wasn't
+    // removed server-side. Doesn't contain keys that predate
+    // |last_trusted_vault_key|, because it's impossible to validate them and
+    // the client should be aware of them already.
+    std::vector<std::vector<uint8_t>> keys;
+    int last_key_version;
+  };
+
+  // |device_key_pair| must not be null. This class doesn't make an assumption
+  // of |last_trusted_vault_key| being non-empty or
+  // |last_trusted_vault_key_version| being non-negative.
+  DownloadKeysResponseHandler(
+      const std::vector<uint8_t>& last_trusted_vault_key,
+      int last_trusted_vault_key_version,
+      std::unique_ptr<SecureBoxKeyPair> device_key_pair);
+  DownloadKeysResponseHandler(const DownloadKeysResponseHandler& other) =
+      delete;
+  DownloadKeysResponseHandler& operator=(
+      const DownloadKeysResponseHandler& other) = delete;
+  ~DownloadKeysResponseHandler();
+
+  ProcessedResponse ProcessResponse(TrustedVaultRequest::HttpStatus http_status,
+                                    const std::string& response_body) const;
+
+ private:
+  const std::vector<uint8_t> last_trusted_vault_key_;
+  const int last_trusted_vault_key_version_;
+  const std::unique_ptr<SecureBoxKeyPair> device_key_pair_;
+};
+
+}  // namespace syncer
+
+#endif  // COMPONENTS_SYNC_TRUSTED_VAULT_DOWNLOAD_KEYS_RESPONSE_HANDLER_H_
diff --git a/components/sync/trusted_vault/download_keys_response_handler_unittest.cc b/components/sync/trusted_vault/download_keys_response_handler_unittest.cc
new file mode 100644
index 0000000..608fc17
--- /dev/null
+++ b/components/sync/trusted_vault/download_keys_response_handler_unittest.cc
@@ -0,0 +1,492 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/sync/trusted_vault/download_keys_response_handler.h"
+
+#include <vector>
+
+#include "base/strings/string_number_conversions.h"
+#include "components/sync/protocol/vault.pb.h"
+#include "components/sync/trusted_vault/securebox.h"
+#include "crypto/hmac.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace syncer {
+
+namespace {
+
+using testing::ElementsAre;
+using testing::Eq;
+using testing::IsEmpty;
+
+const size_t kKeyProofLength = 32;
+const char kEncodedPrivateKey[] =
+    "49e052293c29b5a50b0013eec9d030ac2ad70a42fe093be084264647cb04e16f";
+const uint8_t kWrappedKeyHeader[] = {'V', '1', ' ', 's', 'h', 'a', 'r',
+                                     'e', 'd', '_', 'k', 'e', 'y'};
+const char kSecurityDomainName[] = "chromesync";
+
+void AssignBytesToProtoString(base::span<const uint8_t> bytes,
+                              std::string* bytes_proto_field) {
+  *bytes_proto_field = std::string(bytes.begin(), bytes.end());
+}
+
+std::unique_ptr<SecureBoxKeyPair> MakeTestKeyPair() {
+  std::vector<uint8_t> private_key_bytes;
+  bool success = base::HexStringToBytes(kEncodedPrivateKey, &private_key_bytes);
+  DCHECK(success);
+  return SecureBoxKeyPair::CreateByPrivateKeyImport(private_key_bytes);
+}
+
+void FillSecurityDomainMember(
+    const SecureBoxPublicKey& public_key,
+    const std::vector<std::vector<uint8_t>>& trusted_vault_keys,
+    const std::vector<int> trusted_vault_keys_versions,
+    const std::vector<std::vector<uint8_t>>& signing_keys,
+    sync_pb::SecurityDomain::Member* member) {
+  DCHECK(member);
+  DCHECK_EQ(trusted_vault_keys.size(), trusted_vault_keys_versions.size());
+  DCHECK_EQ(trusted_vault_keys.size(), signing_keys.size());
+
+  std::vector<uint8_t> public_key_bytes = public_key.ExportToBytes();
+  AssignBytesToProtoString(public_key.ExportToBytes(),
+                           member->mutable_public_key());
+
+  for (size_t i = 0; i < trusted_vault_keys.size(); ++i) {
+    sync_pb::SharedKey* shared_key = member->add_keys();
+    shared_key->set_epoch(trusted_vault_keys_versions[i]);
+    AssignBytesToProtoString(
+        public_key.Encrypt(
+            /*shared_secret=*/base::span<const uint8_t>(), kWrappedKeyHeader,
+            /*payload=*/trusted_vault_keys[i]),
+        shared_key->mutable_wrapped_key());
+
+    if (!signing_keys[i].empty()) {
+      crypto::HMAC hmac(crypto::HMAC::SHA256);
+      CHECK(hmac.Init(signing_keys[i]));
+
+      std::vector<uint8_t> key_proof_bytes(kKeyProofLength);
+      CHECK(hmac.Sign(trusted_vault_keys[i], key_proof_bytes));
+      AssignBytesToProtoString(key_proof_bytes,
+                               shared_key->mutable_key_proof());
+    }
+  }
+}
+
+std::string CreateListSecurityDomainsResponseWithSingleSyncMember(
+    const std::vector<std::vector<uint8_t>>& trusted_vault_keys,
+    const std::vector<int> trusted_vault_keys_versions,
+    const std::vector<std::vector<uint8_t>>& signing_keys) {
+  sync_pb::ListSecurityDomainsResponse response;
+  sync_pb::SecurityDomain* security_domain = response.add_security_domains();
+  security_domain->set_name(kSecurityDomainName);
+  FillSecurityDomainMember(MakeTestKeyPair()->public_key(), trusted_vault_keys,
+                           trusted_vault_keys_versions, signing_keys,
+                           security_domain->add_members());
+  return response.SerializeAsString();
+}
+
+class DownloadKeysResponseHandlerTest : public testing::Test {
+ public:
+  DownloadKeysResponseHandlerTest()
+      : handler_(kKnownTrustedVaultKey,
+                 kKnownTrustedVaultKeyVersion,
+                 MakeTestKeyPair()) {}
+
+  ~DownloadKeysResponseHandlerTest() override = default;
+
+  const DownloadKeysResponseHandler& handler() const { return handler_; }
+
+  const int kKnownTrustedVaultKeyVersion = 5;
+  const std::vector<uint8_t> kKnownTrustedVaultKey = {1, 2, 3, 4};
+  const std::vector<uint8_t> kTrustedVaultKey1 = {1, 2, 3, 5};
+  const std::vector<uint8_t> kTrustedVaultKey2 = {1, 2, 3, 6};
+  const std::vector<uint8_t> kTrustedVaultKey3 = {1, 2, 3, 7};
+
+ private:
+  const DownloadKeysResponseHandler handler_;
+};
+
+// All HttpStatuses except kSuccess should end up in kOtherError reporting,
+// because underlying request doesn't have any parameters inferred from local
+// state.
+TEST_F(DownloadKeysResponseHandlerTest, ShouldHandleHttpErrors) {
+  EXPECT_THAT(
+      handler()
+          .ProcessResponse(
+              /*http_status=*/TrustedVaultRequest::HttpStatus::kBadRequest,
+              /*response_body=*/std::string())
+          .status,
+      Eq(TrustedVaultRequestStatus::kOtherError));
+  EXPECT_THAT(
+      handler()
+          .ProcessResponse(
+              /*http_status=*/TrustedVaultRequest::HttpStatus::kOtherError,
+              /*response_body=*/std::string())
+          .status,
+      Eq(TrustedVaultRequestStatus::kOtherError));
+}
+
+// Simplest legitimate case of key rotation, server side state corresponds to
+// kKnownTrustedVaultKey -> kTrustedVaultKey1 key chain.
+TEST_F(DownloadKeysResponseHandlerTest, ShouldHandleSingleKeyRotation) {
+  const DownloadKeysResponseHandler::ProcessedResponse processed_response =
+      handler().ProcessResponse(
+          /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
+          /*response_body=*/
+          CreateListSecurityDomainsResponseWithSingleSyncMember(
+              /*trusted_vault_keys=*/{kKnownTrustedVaultKey, kTrustedVaultKey1},
+              /*trusted_vault_keys_versions=*/
+              {kKnownTrustedVaultKeyVersion, kKnownTrustedVaultKeyVersion + 1},
+              /*signing_keys=*/{{}, kKnownTrustedVaultKey}));
+
+  EXPECT_THAT(processed_response.status,
+              Eq(TrustedVaultRequestStatus::kSuccess));
+  EXPECT_THAT(processed_response.keys,
+              ElementsAre(kKnownTrustedVaultKey, kTrustedVaultKey1));
+  EXPECT_THAT(processed_response.last_key_version,
+              Eq(kKnownTrustedVaultKeyVersion + 1));
+}
+
+// Multiple key rotations may happen while client is offline, server-side key
+// chain is kKnownTrustedVaultKey -> kTrustedVaultKey1 -> kTrustedVaultKey2.
+TEST_F(DownloadKeysResponseHandlerTest, ShouldHandleMultipleKeyRotations) {
+  const DownloadKeysResponseHandler::ProcessedResponse processed_response =
+      handler().ProcessResponse(
+          /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
+          /*response_body=*/
+          CreateListSecurityDomainsResponseWithSingleSyncMember(
+              /*trusted_vault_keys=*/
+              {kKnownTrustedVaultKey, kTrustedVaultKey1, kTrustedVaultKey2},
+              /*trusted_vault_keys_versions=*/
+              {kKnownTrustedVaultKeyVersion, kKnownTrustedVaultKeyVersion + 1,
+               kKnownTrustedVaultKeyVersion + 2},
+              /*signing_keys=*/{{}, kKnownTrustedVaultKey, kTrustedVaultKey1}));
+
+  EXPECT_THAT(processed_response.status,
+              Eq(TrustedVaultRequestStatus::kSuccess));
+  EXPECT_THAT(
+      processed_response.keys,
+      ElementsAre(kKnownTrustedVaultKey, kTrustedVaultKey1, kTrustedVaultKey2));
+  EXPECT_THAT(processed_response.last_key_version,
+              Eq(kKnownTrustedVaultKeyVersion + 2));
+}
+
+// There might be keys, that predates latest client-side trusted vault key.
+// Server-side key chain is kTrustedVaultKey1 -> kKnownTrustedVaultKey ->
+// kTrustedVaultKey2 -> kTrustedVaultKey3.
+// Since kTrustedVaultKey1 can't be validated using kKnownTrustedVaultKey it
+// shouldn't be included in processed response.
+TEST_F(DownloadKeysResponseHandlerTest, ShouldHandlePriorKeys) {
+  const DownloadKeysResponseHandler::ProcessedResponse processed_response =
+      handler().ProcessResponse(
+          /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
+          /*response_body=*/
+          CreateListSecurityDomainsResponseWithSingleSyncMember(
+              /*trusted_vault_keys=*/
+              {kTrustedVaultKey1, kKnownTrustedVaultKey, kTrustedVaultKey2,
+               kTrustedVaultKey3},
+              /*trusted_vault_keys_versions=*/
+              {kKnownTrustedVaultKeyVersion - 1, kKnownTrustedVaultKeyVersion,
+               kKnownTrustedVaultKeyVersion + 1,
+               kKnownTrustedVaultKeyVersion + 2},
+              /*signing_keys=*/
+              {{},
+               kTrustedVaultKey1,
+               kKnownTrustedVaultKey,
+               kTrustedVaultKey2}));
+
+  EXPECT_THAT(processed_response.status,
+              Eq(TrustedVaultRequestStatus::kSuccess));
+  EXPECT_THAT(
+      processed_response.keys,
+      ElementsAre(kKnownTrustedVaultKey, kTrustedVaultKey2, kTrustedVaultKey3));
+  EXPECT_THAT(processed_response.last_key_version,
+              Eq(kKnownTrustedVaultKeyVersion + 2));
+}
+
+// Server can already clean-up kKnownTrustedVaultKey, but it might still be
+// possible to validate the key-chain.
+// Full key chain is: kKnownTrustedVaultKey -> kTrustedVaultKey1 ->
+// kTrustedVaultKey2.
+// Server-side key chain is: kTrustedVaultKey1 -> kTrustedVaultKey2.
+TEST_F(DownloadKeysResponseHandlerTest,
+       ShouldHandleAbsenseOfKnownKeyWhenKeyChainIsRecoverable) {
+  const DownloadKeysResponseHandler::ProcessedResponse processed_response =
+      handler().ProcessResponse(
+          /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
+          /*response_body=*/
+          CreateListSecurityDomainsResponseWithSingleSyncMember(
+              /*trusted_vault_keys=*/
+              {kTrustedVaultKey1, kTrustedVaultKey2},
+              /*trusted_vault_keys_versions=*/
+              {kKnownTrustedVaultKeyVersion + 1,
+               kKnownTrustedVaultKeyVersion + 2},
+              /*signing_keys=*/
+              {kKnownTrustedVaultKey, kTrustedVaultKey1}));
+
+  EXPECT_THAT(processed_response.status,
+              Eq(TrustedVaultRequestStatus::kSuccess));
+  EXPECT_THAT(processed_response.keys,
+              ElementsAre(kTrustedVaultKey1, kTrustedVaultKey2));
+  EXPECT_THAT(processed_response.last_key_version,
+              Eq(kKnownTrustedVaultKeyVersion + 2));
+}
+
+// Server can already clean-up kKnownTrustedVaultKey and the following key. In
+// this case client state is not sufficient to silently download keys and
+// kLocalDataObsolete should be reported.
+// Possible full key chain is: kKnownTrustedVaultKey -> kTrustedVaultKey1 ->
+// kTrustedVaultKey2 -> kTrustedVaultKey3.
+// Server side key chain is: kTrustedVaultKey2 -> kTrustedVaultKey3.
+TEST_F(DownloadKeysResponseHandlerTest,
+       ShouldHandleAbsenseOfKnownKeyWhenKeyChainIsNotRecoverable) {
+  const DownloadKeysResponseHandler::ProcessedResponse processed_response =
+      handler().ProcessResponse(
+          /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
+          /*response_body=*/
+          CreateListSecurityDomainsResponseWithSingleSyncMember(
+              /*trusted_vault_keys=*/
+              {kTrustedVaultKey2, kTrustedVaultKey3},
+              /*trusted_vault_keys_versions=*/
+              {kKnownTrustedVaultKeyVersion + 2,
+               kKnownTrustedVaultKeyVersion + 3},
+              /*signing_keys=*/
+              {kTrustedVaultKey1, kTrustedVaultKey2}));
+
+  EXPECT_THAT(processed_response.status,
+              Eq(TrustedVaultRequestStatus::kLocalDataObsolete));
+  EXPECT_THAT(processed_response.keys, IsEmpty());
+}
+
+// The test populates undecryptable/corrupted |wrapped_key| field, handler
+// should return kLocalDataObsolete to allow client to restore Member by
+// re-registration.
+TEST_F(DownloadKeysResponseHandlerTest, ShouldHandleUndecryptableKey) {
+  sync_pb::ListSecurityDomainsResponse response;
+  sync_pb::SecurityDomain* security_domain = response.add_security_domains();
+  security_domain->set_name(kSecurityDomainName);
+  sync_pb::SecurityDomain::Member* member = security_domain->add_members();
+  FillSecurityDomainMember(
+      MakeTestKeyPair()->public_key(),
+      /*trusted_vault_keys=*/{kKnownTrustedVaultKey, kTrustedVaultKey1},
+      /*trusted_vault_keys_versions=*/
+      {kKnownTrustedVaultKeyVersion, kKnownTrustedVaultKeyVersion + 1},
+      /*signing_keys=*/{{}, kKnownTrustedVaultKey}, member);
+
+  // Corrupt wrapped key corresponding to kTrustedVaultKey1.
+  member->mutable_keys(1)->set_wrapped_key("undecryptable_key");
+
+  EXPECT_THAT(handler()
+                  .ProcessResponse(
+                      /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
+                      /*response_body=*/response.SerializeAsString())
+                  .status,
+              Eq(TrustedVaultRequestStatus::kLocalDataObsolete));
+}
+
+// The test populates invalid |key_proof| field for the single key rotation.
+// kTrustedVaultKey1 is expected to be signed with kKnownTrustedVaultKey, but
+// instead it's signed with kTrustedVaultKey2.
+TEST_F(DownloadKeysResponseHandlerTest,
+       ShouldHandleInvalidKeyProofOnSingleKeyRotation) {
+  const DownloadKeysResponseHandler::ProcessedResponse processed_response =
+      handler().ProcessResponse(
+          /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
+          /*response_body=*/
+          CreateListSecurityDomainsResponseWithSingleSyncMember(
+              /*trusted_vault_keys=*/{kKnownTrustedVaultKey, kTrustedVaultKey1},
+              /*trusted_vault_keys_versions=*/
+              {kKnownTrustedVaultKeyVersion, kKnownTrustedVaultKeyVersion + 1},
+              /*signing_keys=*/{{}, kTrustedVaultKey2}));
+
+  EXPECT_THAT(processed_response.status,
+              Eq(TrustedVaultRequestStatus::kLocalDataObsolete));
+  EXPECT_THAT(processed_response.keys, IsEmpty());
+}
+
+// The test populates invalid |key_proof| field for intermediate key when
+// multiple key rotations have happened.
+// kTrustedVaultKey1 is expected to be signed with kKnownTrustedVaultKey, but
+// instead it's signed with kTrustedVaultKey2.
+TEST_F(DownloadKeysResponseHandlerTest,
+       ShouldHandleInvalidKeyProofOnMultipleKeyRotations) {
+  const DownloadKeysResponseHandler::ProcessedResponse processed_response =
+      handler().ProcessResponse(
+          /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
+          /*response_body=*/
+          CreateListSecurityDomainsResponseWithSingleSyncMember(
+              /*trusted_vault_keys=*/{kKnownTrustedVaultKey, kTrustedVaultKey1,
+                                      kTrustedVaultKey2},
+              /*trusted_vault_keys_versions=*/
+              {kKnownTrustedVaultKeyVersion, kKnownTrustedVaultKeyVersion + 1,
+               kKnownTrustedVaultKeyVersion + 2},
+              /*signing_keys=*/{{}, kTrustedVaultKey2, kTrustedVaultKey1}));
+
+  EXPECT_THAT(processed_response.status,
+              Eq(TrustedVaultRequestStatus::kLocalDataObsolete));
+  EXPECT_THAT(processed_response.keys, IsEmpty());
+}
+
+// In this scenario client already has most recent trusted vault key. It should
+// be reported as kLocalDataObsolete, because by issuing the request client
+// indicates that there should be new keys and it's possible that key rotation
+// has happened by Member state wasn't updated (requires re-registration).
+TEST_F(DownloadKeysResponseHandlerTest, ShouldHandleAbsenseOfNewKeys) {
+  EXPECT_THAT(handler()
+                  .ProcessResponse(
+                      /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
+                      /*response_body=*/
+                      CreateListSecurityDomainsResponseWithSingleSyncMember(
+                          /*trusted_vault_keys=*/{kKnownTrustedVaultKey},
+                          /*trusted_vault_keys_versions=*/
+                          {kKnownTrustedVaultKeyVersion},
+                          /*signing_keys=*/{{}}))
+                  .status,
+              Eq(TrustedVaultRequestStatus::kLocalDataObsolete));
+}
+
+// Tests handling the situation, when response isn't a valid serialized
+// ListSecurityDomains proto.
+TEST_F(DownloadKeysResponseHandlerTest, ShouldHandleCorruptedResponseProto) {
+  EXPECT_THAT(handler()
+                  .ProcessResponse(
+                      /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
+                      /*response_body=*/"corrupted_proto")
+                  .status,
+              Eq(TrustedVaultRequestStatus::kOtherError));
+}
+
+// Client expects that the security domain exists, but the response indicates
+// it doesn't by having no security domains.
+TEST_F(DownloadKeysResponseHandlerTest, ShouldHandleEmptyResponse) {
+  EXPECT_THAT(handler()
+                  .ProcessResponse(
+                      /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
+                      /*response_body=*/std::string())
+                  .status,
+              Eq(TrustedVaultRequestStatus::kLocalDataObsolete));
+}
+
+// Same as above, but there is a different security domain.
+TEST_F(DownloadKeysResponseHandlerTest, ShouldHandleAbsenseOfSecurityDomain) {
+  sync_pb::ListSecurityDomainsResponse response;
+  sync_pb::SecurityDomain* security_domain = response.add_security_domains();
+  security_domain->set_name("other_domain");
+
+  EXPECT_THAT(handler()
+                  .ProcessResponse(
+                      /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
+                      /*response_body=*/response.SerializeAsString())
+                  .status,
+              Eq(TrustedVaultRequestStatus::kLocalDataObsolete));
+}
+
+// Tests handling presence of other security domains.
+TEST_F(DownloadKeysResponseHandlerTest, ShouldHandleMultipleSecurityDomains) {
+  sync_pb::ListSecurityDomainsResponse response;
+  sync_pb::SecurityDomain* other_domain = response.add_security_domains();
+  other_domain->set_name("other_domain");
+
+  sync_pb::SecurityDomain* sync_domain = response.add_security_domains();
+  sync_domain->set_name(kSecurityDomainName);
+  FillSecurityDomainMember(
+      /*public_key=*/MakeTestKeyPair()->public_key(),
+      /*trusted_vault_keys=*/{kKnownTrustedVaultKey, kTrustedVaultKey1},
+      /*trusted_vault_keys_versions=*/
+      {kKnownTrustedVaultKeyVersion, kKnownTrustedVaultKeyVersion + 1},
+      /*signing_keys=*/{{}, kKnownTrustedVaultKey}, sync_domain->add_members());
+
+  const DownloadKeysResponseHandler::ProcessedResponse processed_response =
+      handler().ProcessResponse(
+          /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
+          /*response_body=*/response.SerializeAsString());
+
+  EXPECT_THAT(processed_response.status,
+              Eq(TrustedVaultRequestStatus::kSuccess));
+  EXPECT_THAT(processed_response.keys,
+              ElementsAre(kKnownTrustedVaultKey, kTrustedVaultKey1));
+  EXPECT_THAT(processed_response.last_key_version,
+              Eq(kKnownTrustedVaultKeyVersion + 1));
+}
+
+// Security domain exists, but doesn't contain member corresponding to the
+// current device.
+TEST_F(DownloadKeysResponseHandlerTest, ShouldHandleAbsenseOfMember) {
+  sync_pb::ListSecurityDomainsResponse response;
+  sync_pb::SecurityDomain* security_domain = response.add_security_domains();
+  security_domain->set_name(kSecurityDomainName);
+
+  FillSecurityDomainMember(
+      /*public_key=*/SecureBoxKeyPair::GenerateRandom()->public_key(),
+      /*trusted_vault_keys=*/{kKnownTrustedVaultKey, kTrustedVaultKey1},
+      /*trusted_vault_keys_versions=*/
+      {kKnownTrustedVaultKeyVersion, kKnownTrustedVaultKeyVersion + 1},
+      /*signing_keys=*/{{}, kKnownTrustedVaultKey},
+      security_domain->add_members());
+
+  EXPECT_THAT(handler()
+                  .ProcessResponse(
+                      /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
+                      /*response_body=*/response.SerializeAsString())
+                  .status,
+              Eq(TrustedVaultRequestStatus::kLocalDataObsolete));
+}
+
+// Tests handling presence of other members.
+TEST_F(DownloadKeysResponseHandlerTest, ShouldHandleMultipleMembers) {
+  sync_pb::ListSecurityDomainsResponse response;
+  sync_pb::SecurityDomain* security_domain = response.add_security_domains();
+  security_domain->set_name(kSecurityDomainName);
+
+  // Other member.
+  FillSecurityDomainMember(
+      /*public_key=*/SecureBoxKeyPair::GenerateRandom()->public_key(),
+      /*trusted_vault_keys=*/{kKnownTrustedVaultKey, kTrustedVaultKey1},
+      /*trusted_vault_keys_versions=*/
+      {kKnownTrustedVaultKeyVersion, kKnownTrustedVaultKeyVersion + 1},
+      /*signing_keys=*/{{}, kKnownTrustedVaultKey},
+      security_domain->add_members());
+
+  // Member corresponding to the current device.
+  FillSecurityDomainMember(
+      /*public_key=*/MakeTestKeyPair()->public_key(),
+      /*trusted_vault_keys=*/{kKnownTrustedVaultKey, kTrustedVaultKey1},
+      /*trusted_vault_keys_versions=*/
+      {kKnownTrustedVaultKeyVersion, kKnownTrustedVaultKeyVersion + 1},
+      /*signing_keys=*/{{}, kKnownTrustedVaultKey},
+      security_domain->add_members());
+
+  const DownloadKeysResponseHandler::ProcessedResponse processed_response =
+      handler().ProcessResponse(
+          /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
+          /*response_body=*/response.SerializeAsString());
+
+  EXPECT_THAT(processed_response.status,
+              Eq(TrustedVaultRequestStatus::kSuccess));
+  EXPECT_THAT(processed_response.keys,
+              ElementsAre(kKnownTrustedVaultKey, kTrustedVaultKey1));
+  EXPECT_THAT(processed_response.last_key_version,
+              Eq(kKnownTrustedVaultKeyVersion + 1));
+}
+
+// Corrupted data case: the member corresponding to the current device exists,
+// but has no keys.
+TEST_F(DownloadKeysResponseHandlerTest, ShouldHandleEmptyMember) {
+  EXPECT_THAT(handler()
+                  .ProcessResponse(
+                      /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
+                      /*response_body=*/
+                      CreateListSecurityDomainsResponseWithSingleSyncMember(
+                          /*trusted_vault_keys=*/{},
+                          /*trusted_vault_keys_versions=*/{},
+                          /*signing_keys=*/{}))
+                  .status,
+              Eq(TrustedVaultRequestStatus::kLocalDataObsolete));
+}
+
+}  // namespace
+
+}  // namespace syncer
diff --git a/components/sync/trusted_vault/standalone_trusted_vault_backend.cc b/components/sync/trusted_vault/standalone_trusted_vault_backend.cc
index 54751cf..fa8ea11 100644
--- a/components/sync/trusted_vault/standalone_trusted_vault_backend.cc
+++ b/components/sync/trusted_vault/standalone_trusted_vault_backend.cc
@@ -357,6 +357,7 @@
 
   switch (status) {
     case TrustedVaultRequestStatus::kSuccess:
+      // TODO(crbug.com/1102340): consider keeping old keys as well.
       StoreKeys(gaia_id, vault_keys, last_vault_key_version);
       break;
     case TrustedVaultRequestStatus::kLocalDataObsolete: {
diff --git a/components/sync/trusted_vault/trusted_vault_connection_impl.cc b/components/sync/trusted_vault/trusted_vault_connection_impl.cc
index 5293bce..15311fd 100644
--- a/components/sync/trusted_vault/trusted_vault_connection_impl.cc
+++ b/components/sync/trusted_vault/trusted_vault_connection_impl.cc
@@ -9,6 +9,7 @@
 #include "base/containers/span.h"
 #include "components/signin/public/identity_manager/account_info.h"
 #include "components/sync/protocol/vault.pb.h"
+#include "components/sync/trusted_vault/download_keys_response_handler.h"
 #include "components/sync/trusted_vault/securebox.h"
 #include "components/sync/trusted_vault/trusted_vault_access_token_fetcher.h"
 #include "components/sync/trusted_vault/trusted_vault_request.h"
@@ -23,11 +24,15 @@
 const uint8_t kWrappedKeyHeader[] = {'V', '1', ' ', 's', 'h', 'a', 'r',
                                      'e', 'd', '_', 'k', 'e', 'y'};
 const char kJoinSecurityDomainsURLPath[] = "/domain:join";
+const char kListSecurityDomainsURLPathAndQuery[] = "/domain:list?view=1";
 const char kSecurityDomainName[] = "chromesync";
 
 // Helper function for filling protobuf bytes field: protobuf represent them as
 // std::string, while in code std::vector<uint8_t> or base::span<uint8_t> is
 // more common.
+// TODO(crbug.com/1113598): this function and its counterpart (proto string to
+// bytes vector) is useful in many places under trusted_vault directory,
+// consider moving it to some utility file.
 void AssignBytesToProtoString(base::span<const uint8_t> bytes,
                               std::string* bytes_proto_field) {
   *bytes_proto_field = std::string(bytes.begin(), bytes.end());
@@ -106,6 +111,17 @@
   return result;
 }
 
+void ProcessDownloadKeysResponse(
+    std::unique_ptr<DownloadKeysResponseHandler> response_handler,
+    TrustedVaultConnection::DownloadKeysCallback callback,
+    TrustedVaultRequest::HttpStatus http_status,
+    const std::string& response_body) {
+  DownloadKeysResponseHandler::ProcessedResponse processed_response =
+      response_handler->ProcessResponse(http_status, response_body);
+  std::move(callback).Run(processed_response.status, processed_response.keys,
+                          processed_response.last_key_version);
+}
+
 }  // namespace
 
 TrustedVaultConnectionImpl::TrustedVaultConnectionImpl(
@@ -149,9 +165,23 @@
     int last_trusted_vault_key_version,
     std::unique_ptr<SecureBoxKeyPair> device_key_pair,
     DownloadKeysCallback callback) {
-  NOTIMPLEMENTED();
-  // TODO(crbug.com/1113598): implement logic.
-  return std::make_unique<Request>();
+  auto request = std::make_unique<TrustedVaultRequest>(
+      TrustedVaultRequest::HttpMethod::kGet,
+      GURL(trusted_vault_service_url_.spec() +
+           kListSecurityDomainsURLPathAndQuery),
+      /*serialized_request_proto=*/base::nullopt);
+
+  request->FetchAccessTokenAndSendRequest(
+      account_info.account_id, GetOrCreateURLLoaderFactory(),
+      access_token_fetcher_.get(),
+      base::BindOnce(ProcessDownloadKeysResponse,
+                     /*response_processor=*/
+                     std::make_unique<DownloadKeysResponseHandler>(
+                         last_trusted_vault_key, last_trusted_vault_key_version,
+                         std::move(device_key_pair)),
+                     std::move(callback)));
+
+  return request;
 }
 
 scoped_refptr<network::SharedURLLoaderFactory>
diff --git a/components/sync/trusted_vault/trusted_vault_connection_impl_unittest.cc b/components/sync/trusted_vault/trusted_vault_connection_impl_unittest.cc
index ec38e05..a8023b0 100644
--- a/components/sync/trusted_vault/trusted_vault_connection_impl_unittest.cc
+++ b/components/sync/trusted_vault/trusted_vault_connection_impl_unittest.cc
@@ -39,6 +39,8 @@
 const char kTestURL[] = "https://test.com/test";
 const char kTestJoinSecurityDomainsURL[] =
     "https://test.com/test/domain:join?alt=proto";
+const char kTestListSecurityDomainsURL[] =
+    "https://test.com/test/domain:list?view=1&alt=proto";
 const uint8_t kWrappedKeyHeader[] = {'V', '1', ' ', 's', 'h', 'a', 'r',
                                      'e', 'd', '_', 'k', 'e', 'y'};
 
@@ -115,6 +117,15 @@
         /*content=*/std::string(), response_http_code);
   }
 
+  bool RespondToListSecurityDomainsRequest(
+      net::HttpStatusCode response_http_code) {
+    // Allow request to reach |test_url_loader_factory_|.
+    base::RunLoop().RunUntilIdle();
+    return test_url_loader_factory_.SimulateResponseForPendingRequest(
+        kTestListSecurityDomainsURL, /*content=*/std::string(),
+        response_http_code);
+  }
+
   const std::vector<uint8_t> kTrustedVaultKey = {1, 2, 3, 4};
 
  private:
@@ -300,6 +311,86 @@
   RespondToJoinSecurityDomainsRequest(net::HTTP_OK);
 }
 
+TEST_F(TrustedVaultConnectionImplTest, ShouldSendListSecurityDomainsRequest) {
+  std::unique_ptr<TrustedVaultConnection::Request> request =
+      connection()->DownloadKeys(
+          /*account_info=*/CoreAccountInfo(),
+          /*last_trusted_vault_key=*/std::vector<uint8_t>(),
+          /*last_trusted_vault_key_version=*/0,
+          /*device_key_pair=*/MakeTestKeyPair(), base::DoNothing());
+  EXPECT_THAT(request, NotNull());
+
+  network::TestURLLoaderFactory::PendingRequest* pending_http_request =
+      GetPendingHTTPRequest();
+  ASSERT_THAT(pending_http_request, NotNull());
+
+  const network::ResourceRequest& resource_request =
+      pending_http_request->request;
+  EXPECT_THAT(resource_request.method, Eq("GET"));
+  EXPECT_THAT(resource_request.url, Eq(GURL(kTestListSecurityDomainsURL)));
+}
+
+// TODO(crbug.com/1113598): add coverage for at least one successful case
+// (need to share some helper functions with
+// download_keys_response_handler_unittest.cc).
+TEST_F(TrustedVaultConnectionImplTest,
+       ShouldHandleFailedListSecurityDomainsRequest) {
+  base::MockCallback<TrustedVaultConnection::DownloadKeysCallback> callback;
+
+  std::unique_ptr<TrustedVaultConnection::Request> request =
+      connection()->DownloadKeys(
+          /*account_info=*/CoreAccountInfo(),
+          /*last_trusted_vault_key=*/std::vector<uint8_t>(),
+          /*last_trusted_vault_key_version=*/0,
+          /*device_key_pair=*/MakeTestKeyPair(), callback.Get());
+  ASSERT_THAT(request, NotNull());
+
+  EXPECT_CALL(callback, Run(Eq(TrustedVaultRequestStatus::kOtherError), _, _));
+  EXPECT_TRUE(
+      RespondToListSecurityDomainsRequest(net::HTTP_INTERNAL_SERVER_ERROR));
+}
+
+TEST_F(TrustedVaultConnectionImplTest,
+       ShouldHandleAccessTokenFetchingFailureWhenDownloadingKeys) {
+  std::unique_ptr<TrustedVaultConnectionImpl> connection =
+      CreateConnectionWithAccessToken(
+          /*access_token=*/base::nullopt);
+
+  base::MockCallback<TrustedVaultConnection::DownloadKeysCallback> callback;
+
+  // |callback| is called immediately after DownloadKeys(), because there is no
+  // access token.
+  EXPECT_CALL(callback, Run(Eq(TrustedVaultRequestStatus::kOtherError), _, _));
+  std::unique_ptr<TrustedVaultConnection::Request> request =
+      connection->DownloadKeys(
+          /*account_info=*/CoreAccountInfo(),
+          /*last_trusted_vault_key=*/std::vector<uint8_t>(),
+          /*last_trusted_vault_key_version=*/0,
+          /*device_key_pair=*/MakeTestKeyPair(), callback.Get());
+  ASSERT_THAT(request, NotNull());
+
+  // No requests should be sent to the network.
+  EXPECT_THAT(GetPendingHTTPRequest(), IsNull());
+}
+
+TEST_F(TrustedVaultConnectionImplTest, ShouldCancelListSecurityDomainsRequest) {
+  base::MockCallback<TrustedVaultConnection::DownloadKeysCallback> callback;
+
+  std::unique_ptr<TrustedVaultConnection::Request> request =
+      connection()->DownloadKeys(
+          /*account_info=*/CoreAccountInfo(),
+          /*last_trusted_vault_key=*/std::vector<uint8_t>(),
+          /*last_trusted_vault_key_version=*/0,
+          /*device_key_pair=*/MakeTestKeyPair(), callback.Get());
+  ASSERT_THAT(request, NotNull());
+
+  EXPECT_CALL(callback, Run).Times(0);
+  request.reset();
+  // Returned value isn't checked here, because the request can be cancelled
+  // before reaching TestURLLoaderFactory.
+  RespondToListSecurityDomainsRequest(net::HTTP_OK);
+}
+
 }  // namespace
 
 }  // namespace syncer
diff --git a/components/sync_preferences/pref_service_syncable_unittest.cc b/components/sync_preferences/pref_service_syncable_unittest.cc
index e689cba..f99ea09 100644
--- a/components/sync_preferences/pref_service_syncable_unittest.cc
+++ b/components/sync_preferences/pref_service_syncable_unittest.cc
@@ -132,8 +132,8 @@
                                     syncer::ModelType model_type) {
   std::string serialized;
   JSONStringValueSerializer json(&serialized);
-  if (!json.Serialize(value))
-    return syncer::SyncChange();
+  bool success = json.Serialize(value);
+  DCHECK(success);
   sync_pb::EntitySpecifics entity;
   sync_pb::PreferenceSpecifics* pref =
       PrefModelAssociator::GetMutableSpecifics(model_type, &entity);
diff --git a/components/ui_devtools/BUILD.gn b/components/ui_devtools/BUILD.gn
index 4063225..7bccde2 100644
--- a/components/ui_devtools/BUILD.gn
+++ b/components/ui_devtools/BUILD.gn
@@ -153,6 +153,7 @@
     "//base/test:test_support",
     "//net:test_support",
     "//services/network:network_service",
+    "//services/network:test_support",
     "//testing/gtest",
   ]
 
diff --git a/components/ui_devtools/DEPS b/components/ui_devtools/DEPS
index 454d984..1251e4bb 100644
--- a/components/ui_devtools/DEPS
+++ b/components/ui_devtools/DEPS
@@ -14,5 +14,6 @@
   "devtools_server_unittest\.cc": [
     "+services/network/network_context.h",
     "+services/network/network_service.h",
+    "+services/network/test/fake_test_cert_verifier_params_factory.h",
   ]
 }
diff --git a/components/ui_devtools/devtools_server_unittest.cc b/components/ui_devtools/devtools_server_unittest.cc
index 07a30f7..f7439ed 100644
--- a/components/ui_devtools/devtools_server_unittest.cc
+++ b/components/ui_devtools/devtools_server_unittest.cc
@@ -15,6 +15,7 @@
 #include "net/socket/tcp_client_socket.h"
 #include "services/network/network_context.h"
 #include "services/network/network_service.h"
+#include "services/network/test/fake_test_cert_verifier_params_factory.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace ui_devtools {
@@ -39,10 +40,17 @@
   mojo::Remote<network::mojom::NetworkService> network_service_remote;
   auto network_service = network::NetworkService::Create(
       network_service_remote.BindNewPipeAndPassReceiver());
+
+  network::mojom::NetworkContextParamsPtr context_params =
+      network::mojom::NetworkContextParams::New();
+  // Use a dummy CertVerifier that always passes cert verification, since
+  // these unittests don't need to test CertVerifier behavior.
+  context_params->cert_verifier_params =
+      network::FakeTestCertVerifierParamsFactory::GetCertVerifierParams();
   mojo::Remote<network::mojom::NetworkContext> network_context_remote;
   network_service_remote->CreateNetworkContext(
       network_context_remote.BindNewPipeAndPassReceiver(),
-      network::mojom::NetworkContextParams::New());
+      std::move(context_params));
 
   std::unique_ptr<UiDevToolsServer> server =
       UiDevToolsServer::CreateForViews(network_context_remote.get(), fake_port);
diff --git a/components/update_client/background_downloader_win.cc b/components/update_client/background_downloader_win.cc
index 682c357..dc9c961 100644
--- a/components/update_client/background_downloader_win.cc
+++ b/components/update_client/background_downloader_win.cc
@@ -25,6 +25,7 @@
 #include "base/strings/string_piece.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/task/thread_pool.h"
+#include "base/threading/scoped_thread_priority.h"
 #include "base/win/atl.h"
 #include "base/win/scoped_co_mem.h"
 #include "components/update_client/task_traits.h"
@@ -139,6 +140,9 @@
 
 // Retrieves the singleton instance of GIT for this process.
 HRESULT GetGit(ComPtr<IGlobalInterfaceTable>* git) {
+  // Mitigate the issues caused by loading DLLs on a background thread
+  // (http://crbug/973868).
+  SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
   return ::CoCreateInstance(CLSID_StdGlobalInterfaceTable, nullptr,
                             CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&(*git)));
 }
diff --git a/components/viz/service/display_embedder/skia_output_device_buffer_queue.cc b/components/viz/service/display_embedder/skia_output_device_buffer_queue.cc
index e4c0e24a..c358b62 100644
--- a/components/viz/service/display_embedder/skia_output_device_buffer_queue.cc
+++ b/components/viz/service/display_embedder/skia_output_device_buffer_queue.cc
@@ -77,6 +77,8 @@
     }
   }
 
+  void OnContextLost() { representation_->OnContextLost(); }
+
   bool unique() const { return ref_ == 1; }
   const gpu::Mailbox& mailbox() const { return representation_->mailbox(); }
   gpu::SharedImageRepresentationOverlay::ScopedReadAccess* scoped_read_access()
@@ -389,6 +391,18 @@
     it->Unref();
   }
 
+  // Code below can destroy last representation of the overlay shared image. On
+  // MacOS it needs context to be current.
+#if defined(OS_APPLE)
+  // TODO(vasilyt): We shouldn't need this after we stop using
+  // SharedImageBackingGLImage as backing.
+  if (!dependency_->GetSharedContextState()->MakeCurrent(nullptr)) {
+    for (auto& overlay : overlays_) {
+      overlay.OnContextLost();
+    }
+  }
+#endif
+
   // Go through backings of all overlays, and release overlay backings which are
   // not used.
   std::vector<gpu::Mailbox> released_overlays;
@@ -409,10 +423,11 @@
   });
 
   DCHECK(!result.gpu_fence);
+  const auto& mailbox =
+      image ? image->skia_representation()->mailbox() : gpu::Mailbox();
   FinishSwapBuffers(std::move(result), size, latency_info,
-                    /*damage_area=*/base::nullopt,
-                    std::move(released_overlays),
-                    image ? image->skia_representation()->mailbox() : gpu::Mailbox());
+                    /*damage_area=*/base::nullopt, std::move(released_overlays),
+                    mailbox);
   PageFlipComplete(image.get());
 }
 
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl.cc b/components/viz/service/display_embedder/skia_output_surface_impl.cc
index 463dc99..a75f9bef5 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl.cc
+++ b/components/viz/service/display_embedder/skia_output_surface_impl.cc
@@ -467,8 +467,10 @@
   // callbacks.
   auto task = base::BindOnce(&SkiaOutputSurfaceImplOnGpu::SwapBuffersSkipped,
                              base::Unretained(impl_on_gpu_.get()));
+  // SwapBuffersSkipped currently does mostly the same as SwapBuffers and needs
+  // MakeCurrent.
   EnqueueGpuTask(std::move(task), std::move(resource_sync_tokens_),
-                 /*make_current=*/false, /*need_framebuffer=*/false);
+                 /*make_current=*/true, /*need_framebuffer=*/false);
 
   // TODO(vasilyt): reuse root recorder
   RecreateRootRecorder();
@@ -657,7 +659,8 @@
       base::BindOnce(&SkiaOutputSurfaceImplOnGpu::RemoveRenderPassResource,
                      base::Unretained(impl_on_gpu_.get()), std::move(ids),
                      std::move(image_contexts));
-  EnqueueGpuTask(std::move(callback), {}, /*make_current=*/false,
+  // RemoveRenderPassResources will delete gpu resources and needs MakeCurrent.
+  EnqueueGpuTask(std::move(callback), {}, /*make_current=*/true,
                  /*need_framebuffer=*/false);
 }
 
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
index 3d96b81..42ef422 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
+++ b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
@@ -1384,7 +1384,11 @@
 #if defined(OS_APPLE)
   // Release any backings which are not reused by the current frame, probably
   // because the properties of render passes are changed or render passes are
-  // removed.
+  // removed
+  if (context_is_lost_) {
+    for (auto& image : available_render_pass_overlay_backings_)
+      image->OnContextLost();
+  }
   available_render_pass_overlay_backings_.clear();
 #endif
 
diff --git a/content/browser/accessibility/accessibility_tree_formatter_android.cc b/content/browser/accessibility/accessibility_tree_formatter_android.cc
index e3e9ebe..1a497c9 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_android.cc
+++ b/content/browser/accessibility/accessibility_tree_formatter_android.cc
@@ -99,8 +99,7 @@
                      base::DictionaryValue* dict) const;
 
   std::string ProcessTreeForOutput(
-      const base::DictionaryValue& node,
-      base::DictionaryValue* filtered_dict_result = nullptr) const override;
+      const base::DictionaryValue& node) const override;
 };
 
 // static
@@ -244,8 +243,7 @@
 }
 
 std::string AccessibilityTreeFormatterAndroid::ProcessTreeForOutput(
-    const base::DictionaryValue& dict,
-    base::DictionaryValue* filtered_dict_result) const {
+    const base::DictionaryValue& dict) const {
   std::string error_value;
   if (dict.GetString("error", &error_value))
     return error_value;
diff --git a/content/browser/accessibility/accessibility_tree_formatter_auralinux.cc b/content/browser/accessibility/accessibility_tree_formatter_auralinux.cc
index 8a14d70..d150bf3 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_auralinux.cc
+++ b/content/browser/accessibility/accessibility_tree_formatter_auralinux.cc
@@ -39,8 +39,7 @@
 
  private:
   std::string ProcessTreeForOutput(
-      const base::DictionaryValue& node,
-      base::DictionaryValue* filtered_dict_result = nullptr) const override;
+      const base::DictionaryValue& node) const override;
 
   base::Value BuildTree(BrowserAccessibility* root) const override;
   base::Value BuildTreeForWindow(gfx::AcceleratedWidget hwnd) const override;
@@ -608,8 +607,7 @@
 };
 
 std::string AccessibilityTreeFormatterAuraLinux::ProcessTreeForOutput(
-    const base::DictionaryValue& node,
-    base::DictionaryValue* filtered_dict_result) const {
+    const base::DictionaryValue& node) const {
   std::string error_value;
   if (node.GetString("error", &error_value))
     return error_value;
diff --git a/content/browser/accessibility/accessibility_tree_formatter_base.cc b/content/browser/accessibility/accessibility_tree_formatter_base.cc
index 337da77..32d2fc8 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_base.cc
+++ b/content/browser/accessibility/accessibility_tree_formatter_base.cc
@@ -71,22 +71,6 @@
   return contents;
 }
 
-base::Value AccessibilityTreeFormatterBase::FilterTree(
-    const base::Value& dict) const {
-  base::DictionaryValue filtered_dict;
-  ProcessTreeForOutput(base::Value::AsDictionaryValue(dict), &filtered_dict);
-  const base::Value* children = dict.FindListPath(kChildrenDictAttr);
-  if (children && !children->GetList().empty()) {
-    base::Value filtered_children(base::Value::Type::LIST);
-    for (const auto& child_dict : children->GetList()) {
-      auto filtered_child = FilterTree(child_dict);
-      filtered_children.Append(std::move(filtered_child));
-    }
-    filtered_dict.SetPath(kChildrenDictAttr, std::move(filtered_children));
-  }
-  return std::move(filtered_dict);
-}
-
 void AccessibilityTreeFormatterBase::RecursiveFormatTree(
     const base::Value& dict,
     std::string* contents,
diff --git a/content/browser/accessibility/accessibility_tree_formatter_base.h b/content/browser/accessibility/accessibility_tree_formatter_base.h
index b211183..5af8d38 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_base.h
+++ b/content/browser/accessibility/accessibility_tree_formatter_base.h
@@ -74,7 +74,6 @@
   // AXTreeFormatter overrides.
   void AddDefaultFilters(
       std::vector<AXPropertyFilter>* property_filters) override;
-  base::Value FilterTree(const base::Value& dict) const override;
   std::string FormatTree(const base::Value& tree_node) const override;
   void SetPropertyFilters(
       const std::vector<AXPropertyFilter>& property_filters) override;
@@ -102,8 +101,7 @@
   // - Provides a filtered version of the dictionary in an out param,
   //   (only if the out param is provided).
   virtual std::string ProcessTreeForOutput(
-      const base::DictionaryValue& node,
-      base::DictionaryValue* filtered_dict_result = nullptr) const = 0;
+      const base::DictionaryValue& node) const = 0;
 
   //
   // Utility functions to be used by each platform.
diff --git a/content/browser/accessibility/accessibility_tree_formatter_blink.cc b/content/browser/accessibility/accessibility_tree_formatter_blink.cc
index 2179f6c..d3fe32da 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_blink.cc
+++ b/content/browser/accessibility/accessibility_tree_formatter_blink.cc
@@ -402,8 +402,7 @@
 }
 
 std::string AccessibilityTreeFormatterBlink::ProcessTreeForOutput(
-    const base::DictionaryValue& dict,
-    base::DictionaryValue* filtered_dict_result) const {
+    const base::DictionaryValue& dict) const {
   std::string error_value;
   if (dict.GetString("error", &error_value))
     return error_value;
diff --git a/content/browser/accessibility/accessibility_tree_formatter_blink.h b/content/browser/accessibility/accessibility_tree_formatter_blink.h
index 80312d3..0bd1c57 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_blink.h
+++ b/content/browser/accessibility/accessibility_tree_formatter_blink.h
@@ -41,8 +41,7 @@
                      base::DictionaryValue* dict) const;
 
   std::string ProcessTreeForOutput(
-      const base::DictionaryValue& node,
-      base::DictionaryValue* filtered_dict_result = nullptr) const override;
+      const base::DictionaryValue& node) const override;
 };
 
 }  // namespace content
diff --git a/content/browser/accessibility/accessibility_tree_formatter_mac.mm b/content/browser/accessibility/accessibility_tree_formatter_mac.mm
index ee70a95..d5742c5 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_mac.mm
+++ b/content/browser/accessibility/accessibility_tree_formatter_mac.mm
@@ -102,8 +102,7 @@
   std::string NodeToLineIndex(id, const LineIndexer*) const;
 
   std::string ProcessTreeForOutput(
-      const base::DictionaryValue& node,
-      base::DictionaryValue* filtered_dict_result = nullptr) const override;
+      const base::DictionaryValue& node) const override;
 
   std::string FormatAttributeValue(const base::Value& value) const;
 };
@@ -453,8 +452,7 @@
 }
 
 std::string AccessibilityTreeFormatterMac::ProcessTreeForOutput(
-    const base::DictionaryValue& dict,
-    base::DictionaryValue* filtered_dict_result) const {
+    const base::DictionaryValue& dict) const {
   std::string error_value;
   if (dict.GetString("error", &error_value))
     return error_value;
diff --git a/content/browser/accessibility/accessibility_tree_formatter_uia_win.cc b/content/browser/accessibility/accessibility_tree_formatter_uia_win.cc
index 2a76c2e..d033e0d 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_uia_win.cc
+++ b/content/browser/accessibility/accessibility_tree_formatter_uia_win.cc
@@ -996,8 +996,7 @@
 }
 
 std::string AccessibilityTreeFormatterUia::ProcessTreeForOutput(
-    const base::DictionaryValue& dict,
-    base::DictionaryValue* filtered_result) const {
+    const base::DictionaryValue& dict) const {
   std::unique_ptr<base::DictionaryValue> tree;
   std::string line;
 
@@ -1006,16 +1005,10 @@
   dict.GetString(UiaIdentifierToCondensedString(UIA_ControlTypePropertyId),
                  &control_type_value);
   WriteAttribute(true, control_type_value, &line);
-  if (filtered_result) {
-    filtered_result->SetString(
-        UiaIdentifierToStringUTF8(UIA_ControlTypePropertyId),
-        control_type_value);
-  }
 
   // properties
   for (long i : properties_) {
-    ProcessPropertyForOutput(UiaIdentifierToCondensedString(i), dict, line,
-                             filtered_result);
+    ProcessPropertyForOutput(UiaIdentifierToCondensedString(i), dict, line);
   }
 
   // patterns
@@ -1049,8 +1042,7 @@
       "Window.IsModal"};
 
   for (const std::string& pattern_property_name : pattern_property_names) {
-    ProcessPropertyForOutput(pattern_property_name, dict, line,
-                             filtered_result);
+    ProcessPropertyForOutput(pattern_property_name, dict, line);
   }
 
   return line;
@@ -1059,75 +1051,60 @@
 void AccessibilityTreeFormatterUia::ProcessPropertyForOutput(
     const std::string& property_name,
     const base::DictionaryValue& dict,
-    std::string& line,
-    base::DictionaryValue* filtered_result) const {
+    std::string& line) const {
   //
   const base::Value* value;
   if (dict.Get(property_name, &value))
-    ProcessValueForOutput(property_name, value, line, filtered_result);
+    ProcessValueForOutput(property_name, value, line);
 }
 
 void AccessibilityTreeFormatterUia::ProcessValueForOutput(
     const std::string& name,
     const base::Value* value,
-    std::string& line,
-    base::DictionaryValue* filtered_result) const {
+    std::string& line) const {
   switch (value->type()) {
     case base::Value::Type::STRING: {
       std::string string_value;
       value->GetAsString(&string_value);
-      bool did_pass_filters = WriteAttribute(
+      WriteAttribute(
           false,
           base::StringPrintf("%s='%s'", name.c_str(), string_value.c_str()),
           &line);
-      if (filtered_result && did_pass_filters)
-        filtered_result->SetString(name, string_value);
       break;
     }
     case base::Value::Type::BOOLEAN: {
       bool bool_value = 0;
       value->GetAsBoolean(&bool_value);
-      bool did_pass_filters =
           WriteAttribute(false,
                          base::StringPrintf("%s=%s", name.c_str(),
                                             (bool_value ? "true" : "false")),
                          &line);
-      if (filtered_result && did_pass_filters)
-        filtered_result->SetBoolean(name, bool_value);
       break;
     }
     case base::Value::Type::INTEGER: {
       int int_value = 0;
       value->GetAsInteger(&int_value);
-      bool did_pass_filters = WriteAttribute(
+      WriteAttribute(
           false, base::StringPrintf("%s=%d", name.c_str(), int_value), &line);
-      if (filtered_result && did_pass_filters)
-        filtered_result->SetInteger(name, int_value);
       break;
     }
     case base::Value::Type::DOUBLE: {
       double double_value = 0.0;
       value->GetAsDouble(&double_value);
-      bool did_pass_filters = WriteAttribute(
-          false, base::StringPrintf("%s=%.2f", name.c_str(), double_value),
-          &line);
-      if (filtered_result && did_pass_filters)
-        filtered_result->SetDouble(name, double_value);
+      WriteAttribute(false,
+                     base::StringPrintf("%s=%.2f", name.c_str(), double_value),
+                     &line);
       break;
     }
     case base::Value::Type::DICTIONARY: {
       const base::DictionaryValue* dict_value = nullptr;
       value->GetAsDictionary(&dict_value);
-      bool did_pass_filters = false;
       if (name == "BoundingRectangle") {
-        did_pass_filters =
-            WriteAttribute(false,
-                           FormatRectangle(*dict_value, "BoundingRectangle",
-                                           "left", "top", "width", "height"),
-                           &line);
+        WriteAttribute(false,
+                       FormatRectangle(*dict_value, "BoundingRectangle", "left",
+                                       "top", "width", "height"),
+                       &line);
       }
-      if (filtered_result && did_pass_filters)
-        filtered_result->SetKey(name, dict_value->Clone());
       break;
     }
     default:
diff --git a/content/browser/accessibility/accessibility_tree_formatter_uia_win.h b/content/browser/accessibility/accessibility_tree_formatter_uia_win.h
index d0c8eec..be0c6b0 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_uia_win.h
+++ b/content/browser/accessibility/accessibility_tree_formatter_uia_win.h
@@ -91,16 +91,13 @@
                          base::DictionaryValue* dict) const;
   base::string16 GetNodeName(IUIAutomationElement* node) const;
   std::string ProcessTreeForOutput(
-      const base::DictionaryValue& node,
-      base::DictionaryValue* filtered_result = nullptr) const override;
+      const base::DictionaryValue& node) const override;
   void ProcessPropertyForOutput(const std::string& property_name,
                                 const base::DictionaryValue& dict,
-                                std::string& line,
-                                base::DictionaryValue* filtered_result) const;
+                                std::string& line) const;
   void ProcessValueForOutput(const std::string& name,
                              const base::Value* value,
-                             std::string& line,
-                             base::DictionaryValue* filtered_result) const;
+                             std::string& line) const;
   Microsoft::WRL::ComPtr<IUIAutomation> uia_;
   Microsoft::WRL::ComPtr<IUIAutomationCacheRequest> element_cache_request_;
   Microsoft::WRL::ComPtr<IUIAutomationCacheRequest> children_cache_request_;
diff --git a/content/browser/accessibility/accessibility_tree_formatter_win.cc b/content/browser/accessibility/accessibility_tree_formatter_win.cc
index 9684afd..ba79ad75 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_win.cc
+++ b/content/browser/accessibility/accessibility_tree_formatter_win.cc
@@ -79,8 +79,7 @@
   void AddIA2ValueProperties(const Microsoft::WRL::ComPtr<IAccessible>,
                              base::DictionaryValue* dict) const;
   std::string ProcessTreeForOutput(
-      const base::DictionaryValue& node,
-      base::DictionaryValue* filtered_dict_result = nullptr) const override;
+      const base::DictionaryValue& node) const override;
 };
 
 // static
@@ -872,16 +871,13 @@
 }
 
 std::string AccessibilityTreeFormatterWin::ProcessTreeForOutput(
-    const base::DictionaryValue& dict,
-    base::DictionaryValue* filtered_dict_result) const {
+    const base::DictionaryValue& dict) const {
   std::string line;
 
   // Always show role, and show it first.
   std::string role_value;
   dict.GetString("role", &role_value);
   WriteAttribute(true, role_value, &line);
-  if (filtered_dict_result)
-    filtered_dict_result->SetString("role", role_value);
 
   for (const char* attribute_name : ALL_ATTRIBUTES) {
     const base::Value* value;
@@ -892,32 +888,26 @@
       case base::Value::Type::STRING: {
         std::string string_value;
         value->GetAsString(&string_value);
-        bool did_pass_filters = WriteAttribute(
+        WriteAttribute(
             false,
             base::StringPrintf("%s='%s'", attribute_name, string_value.c_str()),
             &line);
-        if (filtered_dict_result && did_pass_filters)
-          filtered_dict_result->SetString(attribute_name, string_value);
         break;
       }
       case base::Value::Type::INTEGER: {
         int int_value = 0;
         value->GetAsInteger(&int_value);
-        bool did_pass_filters = WriteAttribute(
-            false, base::StringPrintf("%s=%d", attribute_name, int_value),
-            &line);
-        if (filtered_dict_result && did_pass_filters)
-          filtered_dict_result->SetInteger(attribute_name, int_value);
+        WriteAttribute(false,
+                       base::StringPrintf("%s=%d", attribute_name, int_value),
+                       &line);
         break;
       }
       case base::Value::Type::DOUBLE: {
         double double_value = 0.0;
         value->GetAsDouble(&double_value);
-        bool did_pass_filters = WriteAttribute(
+        WriteAttribute(
             false, base::StringPrintf("%s=%.2f", attribute_name, double_value),
             &line);
-        if (filtered_dict_result && did_pass_filters)
-          filtered_dict_result->SetDouble(attribute_name, double_value);
         break;
       }
       case base::Value::Type::LIST: {
@@ -934,8 +924,6 @@
             if (WriteAttribute(false, string_value, &line))
               filtered_list->AppendString(string_value);
         }
-        if (filtered_dict_result && !filtered_list->empty())
-          filtered_dict_result->Set(attribute_name, std::move(filtered_list));
         break;
       }
       case base::Value::Type::DICTIONARY: {
@@ -943,18 +931,15 @@
         // Revisit this if that changes.
         const base::DictionaryValue* dict_value;
         value->GetAsDictionary(&dict_value);
-        bool did_pass_filters = false;
         if (strcmp(attribute_name, "size") == 0) {
-          did_pass_filters = WriteAttribute(
+          WriteAttribute(
               false, FormatCoordinates(*dict_value, "size", "width", "height"),
               &line);
         } else if (strcmp(attribute_name, "location") == 0) {
-          did_pass_filters = WriteAttribute(
-              false, FormatCoordinates(*dict_value, "location", "x", "y"),
-              &line);
+          WriteAttribute(false,
+                         FormatCoordinates(*dict_value, "location", "x", "y"),
+                         &line);
         }
-        if (filtered_dict_result && did_pass_filters)
-          filtered_dict_result->SetKey(attribute_name, dict_value->Clone());
         break;
       }
       default:
diff --git a/content/browser/devtools/devtools_instrumentation.cc b/content/browser/devtools/devtools_instrumentation.cc
index 4c1ef76..97e6f89 100644
--- a/content/browser/devtools/devtools_instrumentation.cc
+++ b/content/browser/devtools/devtools_instrumentation.cc
@@ -271,7 +271,8 @@
   DispatchToAgents(frame_tree_node, &protocol::NetworkHandler::RequestSent,
                    request_id.ToString(), loader_id.ToString(), request,
                    protocol::Network::Initiator::TypeEnum::SignedExchange,
-                   signed_exchange_url, timestamp);
+                   signed_exchange_url, /*initiator_devtools_request_id=*/"",
+                   timestamp);
 
   auto value = std::make_unique<base::trace_event::TracedValue>();
   value->SetString("requestId", request_id.ToString());
@@ -719,21 +720,17 @@
                             int32_t render_frame_id,
                             const base::UnguessableToken& devtools_request_id,
                             const network::ResourceRequest& request,
-                            const GURL& initiator_url) {
+                            const GURL& initiator_url,
+                            const std::string& initiator_devtools_request_id) {
   FrameTreeNode* ftn = GetFtnForNetworkRequest(process_id, render_frame_id);
   if (!ftn)
     return;
   auto timestamp = base::TimeTicks::Now();
   auto id = devtools_request_id.ToString();
-  // TODO(crbug.com/941297): Currently we are using an empty string for
-  // |loader_id|. But when we will introduce a better UI for preflight requests,
-  // consider using the navigation token which is same as the |loader_id| of the
-  // original request or the |devtools_request_id| of the original request, so
-  // that we can associate the requests in the DevTools front end.
   DispatchToAgents(ftn, &protocol::NetworkHandler::RequestSent, id,
                    /* loader_id=*/"", request,
-                   protocol::Network::Initiator::TypeEnum::Other, initiator_url,
-                   timestamp);
+                   protocol::Network::Initiator::TypeEnum::Preflight,
+                   initiator_url, initiator_devtools_request_id, timestamp);
 }
 
 void OnCorsPreflightResponse(int32_t process_id,
@@ -747,7 +744,7 @@
   auto id = devtools_request_id.ToString();
   DispatchToAgents(ftn, &protocol::NetworkHandler::ResponseReceived, id,
                    /* loader_id=*/"", url,
-                   protocol::Network::ResourceTypeEnum::Other, *head,
+                   protocol::Network::ResourceTypeEnum::Preflight, *head,
                    protocol::Maybe<std::string>());
 }
 
@@ -761,7 +758,7 @@
     return;
   auto id = devtools_request_id.ToString();
   DispatchToAgents(ftn, &protocol::NetworkHandler::LoadingComplete, id,
-                   protocol::Network::ResourceTypeEnum::Other, status);
+                   protocol::Network::ResourceTypeEnum::Preflight, status);
 }
 
 namespace {
diff --git a/content/browser/devtools/devtools_instrumentation.h b/content/browser/devtools/devtools_instrumentation.h
index 5636489..736e746 100644
--- a/content/browser/devtools/devtools_instrumentation.h
+++ b/content/browser/devtools/devtools_instrumentation.h
@@ -160,7 +160,8 @@
                             int32_t render_frame_id,
                             const base::UnguessableToken& devtools_request_id,
                             const network::ResourceRequest& request,
-                            const GURL& signed_exchange_url);
+                            const GURL& signed_exchange_url,
+                            const std::string& initiator_devtools_request_id);
 void OnCorsPreflightResponse(int32_t process_id,
                              int32_t render_frame_id,
                              const base::UnguessableToken& devtools_request_id,
diff --git a/content/browser/devtools/protocol/network_handler.cc b/content/browser/devtools/protocol/network_handler.cc
index 7085282..4b8442f2 100644
--- a/content/browser/devtools/protocol/network_handler.cc
+++ b/content/browser/devtools/protocol/network_handler.cc
@@ -1776,12 +1776,14 @@
       common_params.has_user_gesture);
 }
 
-void NetworkHandler::RequestSent(const std::string& request_id,
-                                 const std::string& loader_id,
-                                 const network::ResourceRequest& request,
-                                 const char* initiator_type,
-                                 const base::Optional<GURL>& initiator_url,
-                                 base::TimeTicks timestamp) {
+void NetworkHandler::RequestSent(
+    const std::string& request_id,
+    const std::string& loader_id,
+    const network::ResourceRequest& request,
+    const char* initiator_type,
+    const base::Optional<GURL>& initiator_url,
+    const std::string& initiator_devtools_request_id,
+    base::TimeTicks timestamp) {
   if (!enabled_)
     return;
   std::unique_ptr<DictionaryValue> headers_dict(DictionaryValue::create());
@@ -1791,6 +1793,8 @@
       Network::Initiator::Create().SetType(initiator_type).Build();
   if (initiator_url)
     initiator->SetUrl(initiator_url->spec());
+  if (initiator_devtools_request_id.size())
+    initiator->SetRequestId(initiator_devtools_request_id);
   std::string url_fragment;
   std::string url_without_fragment =
       ExtractFragment(request.url, &url_fragment);
diff --git a/content/browser/devtools/protocol/network_handler.h b/content/browser/devtools/protocol/network_handler.h
index d03370b..735b49e1 100644
--- a/content/browser/devtools/protocol/network_handler.h
+++ b/content/browser/devtools/protocol/network_handler.h
@@ -177,6 +177,7 @@
                    const network::ResourceRequest& request,
                    const char* initiator_type,
                    const base::Optional<GURL>& initiator_url,
+                   const std::string& initiator_devtools_request_id,
                    base::TimeTicks timestamp);
   void ResponseReceived(const std::string& request_id,
                         const std::string& loader_id,
diff --git a/content/browser/devtools/service_worker_devtools_manager.cc b/content/browser/devtools/service_worker_devtools_manager.cc
index f8003e1..fd1c524 100644
--- a/content/browser/devtools/service_worker_devtools_manager.cc
+++ b/content/browser/devtools/service_worker_devtools_manager.cc
@@ -216,7 +216,8 @@
        protocol::NetworkHandler::ForAgentHost(it->second.get())) {
     network->RequestSent(request_id, std::string(), request,
                          protocol::Network::Initiator::TypeEnum::Preload,
-                         base::nullopt /* initiator_url */, timestamp);
+                         /*initiator_url=*/base::nullopt,
+                         /*initiator_devtools_request_id=*/"", timestamp);
   }
 }
 
diff --git a/content/browser/net/cross_origin_opener_policy_reporter.cc b/content/browser/net/cross_origin_opener_policy_reporter.cc
index 422c43d..395d780 100644
--- a/content/browser/net/cross_origin_opener_policy_reporter.cc
+++ b/content/browser/net/cross_origin_opener_policy_reporter.cc
@@ -76,18 +76,15 @@
 std::vector<FrameTreeNode*> CollectOtherWindowForCoopAccess(
     FrameTreeNode* frame) {
   DCHECK(frame->IsMainFrame());
-  SiteInstance* site_instance = frame->current_frame_host()->GetSiteInstance();
   int virtual_browsing_context_group =
       frame->current_frame_host()->virtual_browsing_context_group();
 
   std::vector<FrameTreeNode*> out;
-  for (WebContentsImpl* wc : WebContentsImpl::GetAllWebContents()) {
-    RenderFrameHostImpl* rfh = wc->GetMainFrame();
-
-    // Filters out windows from a different browsing context group.
-    if (!rfh->GetSiteInstance()->IsRelatedSiteInstance(site_instance))
-      continue;
-
+  for (RenderFrameHostImpl* rfh :
+       frame->current_frame_host()
+           ->delegate()
+           ->GetActiveTopLevelDocumentsInBrowsingContextGroup(
+               frame->current_frame_host())) {
     // Filter out windows from the same virtual browsing context group.
     if (rfh->virtual_browsing_context_group() == virtual_browsing_context_group)
       continue;
diff --git a/content/browser/network_service_client.cc b/content/browser/network_service_client.cc
index 44e8b72..38ab568 100644
--- a/content/browser/network_service_client.cc
+++ b/content/browser/network_service_client.cc
@@ -236,9 +236,11 @@
     int32_t render_frame_id,
     const base::UnguessableToken& devtools_request_id,
     const network::ResourceRequest& request,
-    const GURL& initiator_url) {
+    const GURL& initiator_url,
+    const std::string& initiator_devtools_request_id) {
   devtools_instrumentation::OnCorsPreflightRequest(
-      process_id, render_frame_id, devtools_request_id, request, initiator_url);
+      process_id, render_frame_id, devtools_request_id, request, initiator_url,
+      initiator_devtools_request_id);
 }
 
 void NetworkServiceClient::OnCorsPreflightResponse(
diff --git a/content/browser/network_service_client.h b/content/browser/network_service_client.h
index 6849c44..2eb3415 100644
--- a/content/browser/network_service_client.h
+++ b/content/browser/network_service_client.h
@@ -64,11 +64,13 @@
       const net::CookieAndLineAccessResultList& cookies_with_access_result,
       std::vector<network::mojom::HttpRawHeaderPairPtr> headers,
       const base::Optional<std::string>& raw_response_headers) override;
-  void OnCorsPreflightRequest(int32_t process_id,
-                              int32_t render_frame_id,
-                              const base::UnguessableToken& devtool_request_id,
-                              const network::ResourceRequest& request,
-                              const GURL& initiator_url) override;
+  void OnCorsPreflightRequest(
+      int32_t process_id,
+      int32_t render_frame_id,
+      const base::UnguessableToken& devtool_request_id,
+      const network::ResourceRequest& request,
+      const GURL& initiator_url,
+      const std::string& initiator_devtools_request_id) override;
   void OnCorsPreflightResponse(
       int32_t process_id,
       int32_t render_frame_id,
diff --git a/content/browser/renderer_host/cross_origin_opener_policy_status.cc b/content/browser/renderer_host/cross_origin_opener_policy_status.cc
index 3296a4c..58d11400 100644
--- a/content/browser/renderer_host/cross_origin_opener_policy_status.cc
+++ b/content/browser/renderer_host/cross_origin_opener_policy_status.cc
@@ -9,6 +9,7 @@
 #include "base/feature_list.h"
 #include "base/time/time.h"
 #include "content/browser/renderer_host/frame_tree_node.h"
+#include "content/browser/renderer_host/render_frame_host_delegate.h"
 #include "services/network/public/cpp/features.h"
 #include "services/network/public/cpp/is_potentially_trustworthy.h"
 #include "third_party/blink/public/common/origin_trials/trial_token_validator.h"
@@ -84,7 +85,6 @@
     : frame_tree_node_(frame_tree_node),
       virtual_browsing_context_group_(frame_tree_node->current_frame_host()
                                           ->virtual_browsing_context_group()),
-      had_opener_(!!frame_tree_node->opener()),
       is_initial_navigation_(!frame_tree_node_->has_committed_real_load()),
       current_coop_(
           frame_tree_node->current_frame_host()->cross_origin_opener_policy()),
@@ -176,17 +176,20 @@
           current_coop_.report_only_value, current_origin_,
           is_initial_navigation_, response_coop.value, response_origin);
 
+  bool has_other_window_in_browsing_context_group =
+      frame_tree_node_->current_frame_host()
+          ->delegate()
+          ->GetActiveTopLevelDocumentsInBrowsingContextGroup(
+              frame_tree_node_->current_frame_host())
+          .size() > 1;
+
   if (cross_origin_policy_swap) {
     require_browsing_instance_swap_ = true;
 
-    // If this response's COOP causes a BrowsingInstance swap that severs an
-    // opener, report this to the previous COOP reporter and/or the COOP
-    // reporter of the response if they exist.