diff --git a/DEPS b/DEPS
index f728b4b4..d9e89c3 100644
--- a/DEPS
+++ b/DEPS
@@ -206,11 +206,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': '530933006d3cb9ba1fcbfd32d5495390dc0c23d6',
+  'skia_revision': '42a741a2145c825b909327a520ebbb9b712e8fff',
   # 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': '42c17639e4e245ae9de211d0f5e24296c7ffe6bb',
+  'v8_revision': 'c572b4256fc2842c3e466038b8691e4b54a7a385',
   # 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.
@@ -329,11 +329,11 @@
   # 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': '24e6369261071cb59f55de5f987f6fae5dd9ddde',
+  'dawn_revision': '8901df8ffe29b3bf2ef3269edfd3a0e25fdf4b82',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'quiche_revision': '37c5f8fa5ffc46c034a791c1c66feb5f9c143618',
+  'quiche_revision': 'd84ef00c96669bd4cd59e37b0965669ba4357c24',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ios_webkit
   # and whatever else without interference from each other.
@@ -368,7 +368,7 @@
   'ukey2_revision': '0275885d8e6038c39b8a8ca55e75d1d4d1727f47',
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'tint_revision': '1b1d2a59074463a1635f37eb07b65fa846682e21',
+  'tint_revision': '9ab6e8b9eb0ec4aa1d8622801ce98b7b76105be5',
 
   # TODO(crbug.com/941824): The values below need to be kept in sync
   # between //DEPS and //buildtools/DEPS, so if you're updating one,
@@ -892,7 +892,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' + '@' + '0ce7bcc8e2960d5fb9b3888dca1f7b1101229726',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '5f4e51f38e3e2d19218568895b741ad5fd68aaff',
       'condition': 'checkout_chromeos',
   },
 
@@ -912,7 +912,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '805ba2eff0e127ddde8f7ffda714930293e79d99',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'f28949d69964dc271eaaaf0a26caf8ed4481d5e2',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -938,7 +938,7 @@
     Var('chromium_git') + '/chromium/deps/flac.git' + '@' + 'af862024c8c8fa0ae07ced05e89013d881b00596',
 
   'src/third_party/flatbuffers/src':
-    Var('chromium_git') + '/external/github.com/google/flatbuffers.git' + '@' + '136d75fa6580ef87d1b7cbc243e617f21149852e',
+    Var('chromium_git') + '/external/github.com/google/flatbuffers.git' + '@' + '6df40a2471737b27271bdd9b900ab5f3aec746c7',
 
   # Used for embedded builds. CrOS & Linux use the system version.
   'src/third_party/fontconfig/src': {
@@ -1497,7 +1497,7 @@
   },
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '9d69cbeabf5d725af07d8e4e288b39548e6e6f6f',
+    Var('webrtc_git') + '/src.git' + '@' + '3d25935127400418ef6b5970f0edec5db01d2ba6',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1569,7 +1569,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@1bec20b5b70cf0986b678cec683294ed914f98be',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@3691164b7e2c02674cca51a0428a82093107b987',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/browser/gfx/aw_draw_fn_impl.cc b/android_webview/browser/gfx/aw_draw_fn_impl.cc
index 4d8eb96..ea0139c3 100644
--- a/android_webview/browser/gfx/aw_draw_fn_impl.cc
+++ b/android_webview/browser/gfx/aw_draw_fn_impl.cc
@@ -280,8 +280,9 @@
   // We should never have a |vulkan_context_provider_| if we are calling VkInit.
   // This means context destroyed was not correctly called.
   DCHECK(!vulkan_context_provider_);
-  vulkan_context_provider_ =
-      AwVulkanContextProvider::GetOrCreateInstance(params);
+  vulkan_context_provider_ = AwVulkanContextProvider::Create(params);
+  render_thread_manager_.SetVulkanContextProviderOnRT(
+      vulkan_context_provider_.get());
 
   // Make sure we have a GL context.
   DCHECK(!gl_context_);
@@ -316,8 +317,8 @@
     LOG(ERROR) << "Received invalid colorspace.";
     color_space = SkColorSpace::MakeSRGB();
   }
-  auto draw_context = CreateDrawContext(vulkan_context_provider_->gr_context(),
-                                        params, color_space);
+  auto draw_context = CreateDrawContext(
+      vulkan_context_provider_->GetGrContext(), params, color_space);
 
   // Set the draw contexct in |vulkan_context_provider_|, so the SkiaRenderer
   // and SkiaOutputSurface* will use it as frame render target.
@@ -437,16 +438,18 @@
   }
 
   pending_draw->draw_context = CreateDrawContext(
-      vulkan_context_provider_->gr_context(), params, color_space);
+      vulkan_context_provider_->GetGrContext(), params, color_space);
 
   // If we have a |gl_done_fd|, create a Skia GrBackendSemaphore from
   // |gl_done_fd| and wait.
   if (gl_done_fd.is_valid()) {
     VkSemaphore gl_done_semaphore =
-        vulkan_context_provider_->implementation()->ImportSemaphoreHandle(
-            vulkan_context_provider_->device(),
-            gpu::SemaphoreHandle(VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
-                                 std::move(gl_done_fd)));
+        vulkan_context_provider_->GetVulkanImplementation()
+            ->ImportSemaphoreHandle(
+                vulkan_context_provider_->device(),
+                gpu::SemaphoreHandle(
+                    VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+                    std::move(gl_done_fd)));
     if (gl_done_semaphore == VK_NULL_HANDLE) {
       LOG(ERROR) << "Could not create Vulkan semaphore for GL completion.";
       return;
@@ -485,7 +488,7 @@
   GrBackendTexture backend_texture(params->width, params->height,
                                    pending_draw->image_info);
   pending_draw->ahb_skimage = SkImage::MakeFromTexture(
-      vulkan_context_provider_->gr_context(), backend_texture,
+      vulkan_context_provider_->GetGrContext(), backend_texture,
       kBottomLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType, kPremul_SkAlphaType,
       color_space);
   if (!pending_draw->ahb_skimage) {
@@ -555,14 +558,14 @@
   flushInfo.fNumSemaphores = 1;
   flushInfo.fSignalSemaphores = &gr_post_draw_semaphore;
   GrSemaphoresSubmitted submitted =
-      vulkan_context_provider_->gr_context()->flush(flushInfo);
-  vulkan_context_provider_->gr_context()->submit();
+      vulkan_context_provider_->GetGrContext()->flush(flushInfo);
+  vulkan_context_provider_->GetGrContext()->submit();
   if (submitted != GrSemaphoresSubmitted::kYes) {
     LOG(ERROR) << "Skia could not submit GrSemaphore.";
     return;
   }
   gpu::SemaphoreHandle semaphore_handle =
-      vulkan_context_provider_->implementation()->GetSemaphoreHandle(
+      vulkan_context_provider_->GetVulkanImplementation()->GetSemaphoreHandle(
           vulkan_context_provider_->device(),
           pending_draw->post_draw_semaphore);
   if (!semaphore_handle.is_valid()) {
@@ -625,7 +628,7 @@
     // to flush before the vkQueueWaitIdle below.
     if (ahb_skimage) {
       ahb_skimage.reset();
-      vk_context_provider->gr_context()->flushAndSubmit();
+      vk_context_provider->GetGrContext()->flushAndSubmit();
     }
     // We encountered an error and are not sure when our Vk objects are safe to
     // delete. VkQueueWaitIdle to ensure safety.
diff --git a/android_webview/browser/gfx/aw_vulkan_context_provider.cc b/android_webview/browser/gfx/aw_vulkan_context_provider.cc
index 5e74808..aaad08ab 100644
--- a/android_webview/browser/gfx/aw_vulkan_context_provider.cc
+++ b/android_webview/browser/gfx/aw_vulkan_context_provider.cc
@@ -24,9 +24,9 @@
 
 namespace android_webview {
 
-namespace {
+AwVulkanContextProvider::Globals* AwVulkanContextProvider::g_globals = nullptr;
 
-AwVulkanContextProvider* g_vulkan_context_provider = nullptr;
+namespace {
 
 bool InitVulkanForWebView(VkInstance instance,
                           VkPhysicalDevice physical_device,
@@ -69,72 +69,39 @@
 }  // namespace
 
 // static
-scoped_refptr<AwVulkanContextProvider>
-AwVulkanContextProvider::GetOrCreateInstance(AwDrawFn_InitVkParams* params) {
-  if (g_vulkan_context_provider) {
-    DCHECK(!params || params->device == g_vulkan_context_provider->device());
-    DCHECK(!params || params->queue == g_vulkan_context_provider->queue());
-    return base::WrapRefCounted(g_vulkan_context_provider);
+scoped_refptr<AwVulkanContextProvider::Globals>
+AwVulkanContextProvider::Globals::GetOrCreateInstance(
+    AwDrawFn_InitVkParams* params) {
+  if (g_globals) {
+    DCHECK(params->device == g_globals->device_queue->GetVulkanDevice());
+    DCHECK(params->queue == g_globals->device_queue->GetVulkanQueue());
+    return base::WrapRefCounted(g_globals);
   }
-
-  auto provider = base::WrapRefCounted(new AwVulkanContextProvider);
-  if (!provider->Initialize(params))
+  auto globals = base::MakeRefCounted<AwVulkanContextProvider::Globals>();
+  if (!globals->Initialize(params))
     return nullptr;
-
-  return provider;
+  return globals;
 }
 
-AwVulkanContextProvider::AwVulkanContextProvider() {
-  DCHECK_EQ(nullptr, g_vulkan_context_provider);
-  g_vulkan_context_provider = this;
+AwVulkanContextProvider::Globals::Globals() {
+  DCHECK_EQ(nullptr, g_globals);
+  g_globals = this;
 }
 
-AwVulkanContextProvider::~AwVulkanContextProvider() {
-  DCHECK_EQ(g_vulkan_context_provider, this);
-  g_vulkan_context_provider = nullptr;
+AwVulkanContextProvider::Globals::~Globals() {
+  DCHECK_EQ(g_globals, this);
+  g_globals = nullptr;
 
-  draw_context_.reset();
-  gr_context_.reset();
-
-  device_queue_->Destroy();
-  device_queue_ = nullptr;
+  gr_context.reset();
+  device_queue->Destroy();
+  device_queue = nullptr;
 }
 
-gpu::VulkanImplementation* AwVulkanContextProvider::GetVulkanImplementation() {
-  return implementation_.get();
-}
-
-gpu::VulkanDeviceQueue* AwVulkanContextProvider::GetDeviceQueue() {
-  return device_queue_.get();
-}
-
-GrDirectContext* AwVulkanContextProvider::GetGrContext() {
-  return gr_context_.get();
-}
-
-GrVkSecondaryCBDrawContext*
-AwVulkanContextProvider::GetGrSecondaryCBDrawContext() {
-  return draw_context_.get();
-}
-
-void AwVulkanContextProvider::EnqueueSecondaryCBSemaphores(
-    std::vector<VkSemaphore> semaphores) {
-  post_submit_semaphores_.reserve(post_submit_semaphores_.size() +
-                                  semaphores.size());
-  std::copy(semaphores.begin(), semaphores.end(),
-            std::back_inserter(post_submit_semaphores_));
-}
-
-void AwVulkanContextProvider::EnqueueSecondaryCBPostSubmitTask(
-    base::OnceClosure closure) {
-  post_submit_tasks_.push_back(std::move(closure));
-}
-
-bool AwVulkanContextProvider::Initialize(AwDrawFn_InitVkParams* params) {
-  DCHECK(params);
+bool AwVulkanContextProvider::Globals::Initialize(
+    AwDrawFn_InitVkParams* params) {
   // Don't call init on implementation. Instead call InitVulkanForWebView,
   // which avoids creating a new instance.
-  implementation_ = gpu::CreateVulkanImplementation();
+  implementation = gpu::CreateVulkanImplementation();
 
   gfx::ExtensionSet instance_extensions;
   for (uint32_t i = 0; i < params->enabled_instance_extension_names_length; ++i)
@@ -151,8 +118,8 @@
     return false;
   }
 
-  device_queue_ = std::make_unique<gpu::VulkanDeviceQueue>(params->instance);
-  device_queue_->InitializeForWebView(
+  device_queue = std::make_unique<gpu::VulkanDeviceQueue>(params->instance);
+  device_queue->InitializeForWebView(
       params->physical_device, params->device, params->queue,
       params->graphics_queue_index, std::move(device_extensions));
 
@@ -178,18 +145,70 @@
       .fVkExtensions = &vk_extensions,
       .fDeviceFeatures = params->device_features,
       .fDeviceFeatures2 = params->device_features_2,
-      .fMemoryAllocator = gpu::CreateGrVkMemoryAllocator(device_queue_.get()),
+      .fMemoryAllocator = gpu::CreateGrVkMemoryAllocator(device_queue.get()),
       .fGetProc = get_proc,
       .fOwnsInstanceAndDevice = false,
   };
-  gr_context_ = GrDirectContext::MakeVulkan(backend_context);
-  if (!gr_context_) {
+  gr_context = GrDirectContext::MakeVulkan(backend_context);
+  if (!gr_context) {
     LOG(ERROR) << "Unable to initialize GrContext.";
     return false;
   }
   return true;
 }
 
+// static
+scoped_refptr<AwVulkanContextProvider> AwVulkanContextProvider::Create(
+    AwDrawFn_InitVkParams* params) {
+  auto provider = base::WrapRefCounted(new AwVulkanContextProvider);
+  if (!provider->Initialize(params))
+    return nullptr;
+
+  return provider;
+}
+
+AwVulkanContextProvider::AwVulkanContextProvider() = default;
+
+AwVulkanContextProvider::~AwVulkanContextProvider() {
+  draw_context_.reset();
+}
+
+gpu::VulkanImplementation* AwVulkanContextProvider::GetVulkanImplementation() {
+  return globals_->implementation.get();
+}
+
+gpu::VulkanDeviceQueue* AwVulkanContextProvider::GetDeviceQueue() {
+  return globals_->device_queue.get();
+}
+
+GrDirectContext* AwVulkanContextProvider::GetGrContext() {
+  return globals_->gr_context.get();
+}
+
+GrVkSecondaryCBDrawContext*
+AwVulkanContextProvider::GetGrSecondaryCBDrawContext() {
+  return draw_context_.get();
+}
+
+void AwVulkanContextProvider::EnqueueSecondaryCBSemaphores(
+    std::vector<VkSemaphore> semaphores) {
+  post_submit_semaphores_.reserve(post_submit_semaphores_.size() +
+                                  semaphores.size());
+  std::copy(semaphores.begin(), semaphores.end(),
+            std::back_inserter(post_submit_semaphores_));
+}
+
+void AwVulkanContextProvider::EnqueueSecondaryCBPostSubmitTask(
+    base::OnceClosure closure) {
+  post_submit_tasks_.push_back(std::move(closure));
+}
+
+bool AwVulkanContextProvider::Initialize(AwDrawFn_InitVkParams* params) {
+  DCHECK(params);
+  globals_ = Globals::GetOrCreateInstance(params);
+  return !!globals_;
+}
+
 void AwVulkanContextProvider::SecondaryCBDrawBegin(
     sk_sp<GrVkSecondaryCBDrawContext> draw_context) {
   DCHECK(draw_context);
@@ -202,7 +221,7 @@
   DCHECK(draw_context_);
   auto draw_context = std::move(draw_context_);
 
-  auto* fence_helper = device_queue_->GetFenceHelper();
+  auto* fence_helper = globals_->device_queue->GetFenceHelper();
   VkFence vk_fence = VK_NULL_HANDLE;
   auto result = fence_helper->GetFence(&vk_fence);
   DCHECK(result == VK_SUCCESS);
diff --git a/android_webview/browser/gfx/aw_vulkan_context_provider.h b/android_webview/browser/gfx/aw_vulkan_context_provider.h
index deb2914..3b0ddf8b 100644
--- a/android_webview/browser/gfx/aw_vulkan_context_provider.h
+++ b/android_webview/browser/gfx/aw_vulkan_context_provider.h
@@ -42,8 +42,8 @@
     DISALLOW_COPY_AND_ASSIGN(ScopedSecondaryCBDraw);
   };
 
-  static scoped_refptr<AwVulkanContextProvider> GetOrCreateInstance(
-      AwDrawFn_InitVkParams* params = nullptr);
+  static scoped_refptr<AwVulkanContextProvider> Create(
+      AwDrawFn_InitVkParams* params);
 
   // viz::VulkanContextProvider implementation:
   gpu::VulkanImplementation* GetVulkanImplementation() override;
@@ -54,13 +54,8 @@
       std::vector<VkSemaphore> semaphores) override;
   void EnqueueSecondaryCBPostSubmitTask(base::OnceClosure closure) override;
 
-  VkPhysicalDevice physical_device() {
-    return device_queue_->GetVulkanPhysicalDevice();
-  }
-  VkDevice device() { return device_queue_->GetVulkanDevice(); }
-  VkQueue queue() { return device_queue_->GetVulkanQueue(); }
-  gpu::VulkanImplementation* implementation() { return implementation_.get(); }
-  GrDirectContext* gr_context() { return gr_context_.get(); }
+  VkDevice device() { return globals_->device_queue->GetVulkanDevice(); }
+  VkQueue queue() { return globals_->device_queue->GetVulkanQueue(); }
 
  private:
   friend class base::RefCounted<AwVulkanContextProvider>;
@@ -72,9 +67,24 @@
   void SecondaryCBDrawBegin(sk_sp<GrVkSecondaryCBDrawContext> draw_context);
   void SecondaryCMBDrawSubmitted();
 
-  std::unique_ptr<gpu::VulkanImplementation> implementation_;
-  std::unique_ptr<gpu::VulkanDeviceQueue> device_queue_;
-  sk_sp<GrDirectContext> gr_context_;
+  struct Globals : base::RefCountedThreadSafe<Globals> {
+    static scoped_refptr<Globals> GetOrCreateInstance(
+        AwDrawFn_InitVkParams* params);
+
+    Globals();
+    bool Initialize(AwDrawFn_InitVkParams* params);
+
+    std::unique_ptr<gpu::VulkanImplementation> implementation;
+    std::unique_ptr<gpu::VulkanDeviceQueue> device_queue;
+    sk_sp<GrDirectContext> gr_context;
+
+   private:
+    friend base::RefCountedThreadSafe<Globals>;
+    ~Globals();
+  };
+  static Globals* g_globals;
+
+  scoped_refptr<Globals> globals_;
   sk_sp<GrVkSecondaryCBDrawContext> draw_context_;
   std::vector<base::OnceClosure> post_submit_tasks_;
   std::vector<VkSemaphore> post_submit_semaphores_;
diff --git a/android_webview/browser/gfx/hardware_renderer_single_thread.cc b/android_webview/browser/gfx/hardware_renderer_single_thread.cc
index 5c8914c2..585ec24 100644
--- a/android_webview/browser/gfx/hardware_renderer_single_thread.cc
+++ b/android_webview/browser/gfx/hardware_renderer_single_thread.cc
@@ -51,8 +51,7 @@
 
 void HardwareRendererSingleThread::DrawAndSwap(
     HardwareRendererDrawParams* params) {
-  TRACE_EVENT1("android_webview", "HardwareRendererSingleThread::DrawAndSwap",
-               "vulkan", surfaces_->is_using_vulkan());
+  TRACE_EVENT0("android_webview", "HardwareRendererSingleThread::DrawAndSwap");
 
   bool submitted_new_frame = false;
   uint32_t frame_token = 0u;
diff --git a/android_webview/browser/gfx/hardware_renderer_viz.cc b/android_webview/browser/gfx/hardware_renderer_viz.cc
index a862df4..6a771e9b 100644
--- a/android_webview/browser/gfx/hardware_renderer_viz.cc
+++ b/android_webview/browser/gfx/hardware_renderer_viz.cc
@@ -271,8 +271,9 @@
 
 HardwareRendererViz::HardwareRendererViz(
     RenderThreadManager* state,
-    RootFrameSinkGetter root_frame_sink_getter)
-    : HardwareRenderer(state) {
+    RootFrameSinkGetter root_frame_sink_getter,
+    AwVulkanContextProvider* context_provider)
+    : HardwareRenderer(state), output_surface_provider_(context_provider) {
   DCHECK_CALLED_ON_VALID_THREAD(render_thread_checker_);
   DCHECK(output_surface_provider_.renderer_settings().use_skia_renderer);
 
diff --git a/android_webview/browser/gfx/hardware_renderer_viz.h b/android_webview/browser/gfx/hardware_renderer_viz.h
index ed772501..539912b 100644
--- a/android_webview/browser/gfx/hardware_renderer_viz.h
+++ b/android_webview/browser/gfx/hardware_renderer_viz.h
@@ -16,10 +16,13 @@
 
 namespace android_webview {
 
+class AwVulkanContextProvider;
+
 class HardwareRendererViz : public HardwareRenderer {
  public:
   HardwareRendererViz(RenderThreadManager* state,
-                      RootFrameSinkGetter root_frame_sink_getter);
+                      RootFrameSinkGetter root_frame_sink_getter,
+                      AwVulkanContextProvider* context_provider);
   ~HardwareRendererViz() override;
 
   // HardwareRenderer overrides.
diff --git a/android_webview/browser/gfx/output_surface_provider_webview.cc b/android_webview/browser/gfx/output_surface_provider_webview.cc
index 9345092..245e81e 100644
--- a/android_webview/browser/gfx/output_surface_provider_webview.cc
+++ b/android_webview/browser/gfx/output_surface_provider_webview.cc
@@ -40,7 +40,9 @@
 
 }  // namespace
 
-OutputSurfaceProviderWebview::OutputSurfaceProviderWebview() {
+OutputSurfaceProviderWebview::OutputSurfaceProviderWebview(
+    AwVulkanContextProvider* vulkan_context_provider)
+    : vulkan_context_provider_(vulkan_context_provider) {
   // Should be kept in sync with compositor_impl_android.cc.
   renderer_settings_.allow_antialiasing = false;
   renderer_settings_.highp_threshold_min = 2048;
@@ -52,6 +54,8 @@
 
   auto* command_line = base::CommandLine::ForCurrentProcess();
   enable_vulkan_ = command_line->HasSwitch(switches::kWebViewEnableVulkan);
+  DCHECK(!enable_vulkan_ || vulkan_context_provider_);
+
   enable_shared_image_ =
       base::FeatureList::IsEnabled(features::kEnableSharedImageForWebview);
   LOG_IF(FATAL, enable_vulkan_ && !enable_shared_image_)
@@ -90,15 +94,11 @@
         share_group.get(), gl_surface_.get(), gl::GLContextAttribs());
     gl_context->MakeCurrent(gl_surface_.get());
 
-    auto vulkan_context_provider =
-        enable_vulkan_ ? AwVulkanContextProvider::GetOrCreateInstance()
-                       : nullptr;
-
     shared_context_state_ = base::MakeRefCounted<gpu::SharedContextState>(
         share_group, gl_surface_, std::move(gl_context),
         false /* use_virtualized_gl_contexts */, base::BindOnce(&OnContextLost),
         GpuServiceWebView::GetInstance()->gpu_preferences().gr_context_type,
-        vulkan_context_provider.get());
+        vulkan_context_provider_);
     if (!enable_vulkan_) {
       auto feature_info = base::MakeRefCounted<gpu::gles2::FeatureInfo>(
           workarounds, GpuServiceWebView::GetInstance()->gpu_feature_info());
diff --git a/android_webview/browser/gfx/output_surface_provider_webview.h b/android_webview/browser/gfx/output_surface_provider_webview.h
index 3d629f5..dabb686 100644
--- a/android_webview/browser/gfx/output_surface_provider_webview.h
+++ b/android_webview/browser/gfx/output_surface_provider_webview.h
@@ -23,9 +23,13 @@
 
 namespace android_webview {
 
+class AwVulkanContextProvider;
+
+// Effectively a data struct to pass pointers from render thread to viz thread.
 class OutputSurfaceProviderWebview {
  public:
-  OutputSurfaceProviderWebview();
+  explicit OutputSurfaceProviderWebview(
+      AwVulkanContextProvider* vulkan_context_provider);
   ~OutputSurfaceProviderWebview();
 
   std::unique_ptr<viz::OutputSurface> CreateOutputSurface();
@@ -45,8 +49,9 @@
  private:
   void InitializeContext();
 
+  AwVulkanContextProvider* const vulkan_context_provider_;
   // The member variables are effectively const after constructor, so it's safe
-  // to call accessors on different threads
+  // to call accessors on different threads.
   viz::RendererSettings renderer_settings_;
   viz::DebugRendererSettings debug_settings_;
   scoped_refptr<AwGLSurface> gl_surface_;
diff --git a/android_webview/browser/gfx/render_thread_manager.cc b/android_webview/browser/gfx/render_thread_manager.cc
index fa11fa61..69a86690 100644
--- a/android_webview/browser/gfx/render_thread_manager.cc
+++ b/android_webview/browser/gfx/render_thread_manager.cc
@@ -172,6 +172,12 @@
     hardware_renderer_->CommitFrame();
 }
 
+void RenderThreadManager::SetVulkanContextProviderOnRT(
+    AwVulkanContextProvider* context_provider) {
+  DCHECK(!hardware_renderer_);
+  vulkan_context_provider_ = context_provider;
+}
+
 void RenderThreadManager::UpdateViewTreeForceDarkStateOnRT(
     bool view_tree_force_dark_state) {
   if (view_tree_force_dark_state_ == view_tree_force_dark_state)
@@ -199,8 +205,8 @@
         getter = root_frame_sink_getter_;
       }
       DCHECK(getter);
-      hardware_renderer_.reset(
-          new HardwareRendererViz(this, std::move(getter)));
+      hardware_renderer_.reset(new HardwareRendererViz(
+          this, std::move(getter), vulkan_context_provider_));
     } else {
       hardware_renderer_.reset(new HardwareRendererSingleThread(this));
     }
diff --git a/android_webview/browser/gfx/render_thread_manager.h b/android_webview/browser/gfx/render_thread_manager.h
index 5df11f0a..9f9a4ba 100644
--- a/android_webview/browser/gfx/render_thread_manager.h
+++ b/android_webview/browser/gfx/render_thread_manager.h
@@ -21,6 +21,7 @@
 
 namespace android_webview {
 
+class AwVulkanContextProvider;
 class ChildFrame;
 class CompositorFrameProducer;
 
@@ -60,6 +61,7 @@
       uint32_t layer_tree_frame_sink_id);
 
   void CommitFrameOnRT();
+  void SetVulkanContextProviderOnRT(AwVulkanContextProvider* context_provider);
   void UpdateViewTreeForceDarkStateOnRT(bool view_tree_force_dark_state);
   void DrawOnRT(bool save_restore, HardwareRendererDrawParams* params);
   void DestroyHardwareRendererOnRT(bool save_restore);
@@ -108,6 +110,7 @@
   // Accessed by RT thread.
   std::unique_ptr<HardwareRenderer> hardware_renderer_;
   bool view_tree_force_dark_state_ = false;
+  AwVulkanContextProvider* vulkan_context_provider_ = nullptr;
 
   // Accessed by both UI and RT thread.
   mutable base::Lock lock_;
diff --git a/android_webview/browser/gfx/surfaces_instance.cc b/android_webview/browser/gfx/surfaces_instance.cc
index 5d495a0..f42e086 100644
--- a/android_webview/browser/gfx/surfaces_instance.cc
+++ b/android_webview/browser/gfx/surfaces_instance.cc
@@ -9,7 +9,6 @@
 #include <utility>
 
 #include "android_webview/browser/gfx/aw_render_thread_context_provider.h"
-#include "android_webview/browser/gfx/aw_vulkan_context_provider.h"
 #include "android_webview/browser/gfx/deferred_gpu_command_service.h"
 #include "android_webview/browser/gfx/gpu_service_web_view.h"
 #include "android_webview/browser/gfx/output_surface_provider_webview.h"
@@ -61,7 +60,8 @@
 
 SurfacesInstance::SurfacesInstance()
     : frame_sink_id_allocator_(kDefaultClientId),
-      frame_sink_id_(AllocateFrameSinkId()) {
+      frame_sink_id_(AllocateFrameSinkId()),
+      output_surface_provider_(nullptr) {
   // The SharedBitmapManager is null as we do not support or use software
   // compositing on Android.
   frame_sink_manager_ = std::make_unique<viz::FrameSinkManagerImpl>(
diff --git a/android_webview/browser/gfx/surfaces_instance.h b/android_webview/browser/gfx/surfaces_instance.h
index ea3433a8..7d8ef4f 100644
--- a/android_webview/browser/gfx/surfaces_instance.h
+++ b/android_webview/browser/gfx/surfaces_instance.h
@@ -55,10 +55,6 @@
 
   void AddChildId(const viz::SurfaceId& child_id);
   void RemoveChildId(const viz::SurfaceId& child_id);
-  bool is_using_vulkan() const {
-    return output_surface_provider_.shared_context_state() &&
-           output_surface_provider_.shared_context_state()->GrContextIsVulkan();
-  }
 
  private:
   friend class base::RefCounted<SurfacesInstance>;
diff --git a/ash/accessibility/accessibility_controller_impl.cc b/ash/accessibility/accessibility_controller_impl.cc
index 7dc7bc0..6acdc31e 100644
--- a/ash/accessibility/accessibility_controller_impl.cc
+++ b/ash/accessibility/accessibility_controller_impl.cc
@@ -1526,6 +1526,12 @@
     client_->RequestAutoclickScrollableBoundsForPoint(point_in_screen);
 }
 
+void AccessibilityControllerImpl::MagnifierBoundsChanged(
+    const gfx::Rect& bounds_in_screen) {
+  if (client_)
+    client_->MagnifierBoundsChanged(bounds_in_screen);
+}
+
 void AccessibilityControllerImpl::UpdateAutoclickMenuBoundsIfNeeded() {
   Shell::Get()->autoclick_controller()->UpdateAutoclickMenuBoundsIfNeeded();
 }
diff --git a/ash/accessibility/accessibility_controller_impl.h b/ash/accessibility/accessibility_controller_impl.h
index 214427b6..2974aed 100644
--- a/ash/accessibility/accessibility_controller_impl.h
+++ b/ash/accessibility/accessibility_controller_impl.h
@@ -197,6 +197,7 @@
   void SetAutoclickMenuPosition(FloatingMenuPosition position);
   FloatingMenuPosition GetAutoclickMenuPosition();
   void RequestAutoclickScrollableBoundsForPoint(gfx::Point& point_in_screen);
+  void MagnifierBoundsChanged(const gfx::Rect& bounds_in_screen);
 
   void SetFloatingMenuPosition(FloatingMenuPosition position);
   FloatingMenuPosition GetFloatingMenuPosition();
diff --git a/ash/accessibility/test_accessibility_controller_client.cc b/ash/accessibility/test_accessibility_controller_client.cc
index 6f4c6e2..818edfc 100644
--- a/ash/accessibility/test_accessibility_controller_client.cc
+++ b/ash/accessibility/test_accessibility_controller_client.cc
@@ -70,6 +70,9 @@
 void TestAccessibilityControllerClient::
     RequestAutoclickScrollableBoundsForPoint(gfx::Point& point_in_screen) {}
 
+void TestAccessibilityControllerClient::MagnifierBoundsChanged(
+    const gfx::Rect& bounds_in_screen) {}
+
 void TestAccessibilityControllerClient::OnSwitchAccessDisabled() {}
 
 int32_t TestAccessibilityControllerClient::GetPlayedEarconAndReset() {
diff --git a/ash/accessibility/test_accessibility_controller_client.h b/ash/accessibility/test_accessibility_controller_client.h
index 0601591..5066659 100644
--- a/ash/accessibility/test_accessibility_controller_client.h
+++ b/ash/accessibility/test_accessibility_controller_client.h
@@ -40,6 +40,7 @@
   void RequestSelectToSpeakStateChange() override;
   void RequestAutoclickScrollableBoundsForPoint(
       gfx::Point& point_in_screen) override;
+  void MagnifierBoundsChanged(const gfx::Rect& bounds_in_screen) override;
   void OnSwitchAccessDisabled() override;
 
   int32_t GetPlayedEarconAndReset();
diff --git a/ash/app_list/views/app_list_view.cc b/ash/app_list/views/app_list_view.cc
index 277dcc9..0b6f0ce9 100644
--- a/ash/app_list/views/app_list_view.cc
+++ b/ash/app_list/views/app_list_view.cc
@@ -1572,11 +1572,42 @@
     GetInitiallyFocusedView()->RequestFocus();
   }
 
+  UpdateWindowTitle();
+
   // Updates the visibility of app list items according to the change of
   // |app_list_state_|.
   GetAppsContainerView()->UpdateControlVisibility(app_list_state_, is_in_drag_);
 }
 
+void AppListView::UpdateWindowTitle() {
+  if (!GetWidget())
+    return;
+  gfx::NativeView window = GetWidget()->GetNativeView();
+  AppListState contents_view_state = app_list_main_view_->model()->state();
+  if (window) {
+    if (contents_view_state == AppListState::kStateSearchResults ||
+        contents_view_state == AppListState::kStateEmbeddedAssistant) {
+      window->SetTitle(l10n_util::GetStringUTF16(
+          IDS_APP_LIST_LAUNCHER_ACCESSIBILITY_ANNOUNCEMENT));
+      return;
+    }
+    switch (app_list_state_) {
+      case AppListViewState::kPeeking:
+        window->SetTitle(l10n_util::GetStringUTF16(
+            IDS_APP_LIST_SUGGESTED_APPS_ACCESSIBILITY_ANNOUNCEMENT));
+        break;
+      case AppListViewState::kFullscreenAllApps:
+        window->SetTitle(l10n_util::GetStringUTF16(
+            IDS_APP_LIST_ALL_APPS_ACCESSIBILITY_ANNOUNCEMENT));
+        break;
+      case AppListViewState::kClosed:
+      case AppListViewState::kHalf:
+      case AppListViewState::kFullscreenSearch:
+        break;
+    }
+  }
+}
+
 base::TimeDelta AppListView::GetStateTransitionAnimationDuration(
     AppListViewState target_state) {
   if (ShortAnimationsForTesting() || is_side_shelf_ ||
diff --git a/ash/app_list/views/app_list_view.h b/ash/app_list/views/app_list_view.h
index a42c284..aff56397 100644
--- a/ash/app_list/views/app_list_view.h
+++ b/ash/app_list/views/app_list_view.h
@@ -377,6 +377,9 @@
   // page. Called when the app list's visibility changes.
   void UpdatePageResetTimer(bool app_list_visibility);
 
+  // Updates the title of the window that contains the launcher.
+  void UpdateWindowTitle();
+
  private:
   FRIEND_TEST_ALL_PREFIXES(AppListControllerImplTest,
                            CheckAppListViewBoundsWhenVKeyboardEnabled);
diff --git a/ash/app_list/views/contents_view.cc b/ash/app_list/views/contents_view.cc
index f130144..723314e 100644
--- a/ash/app_list/views/contents_view.cc
+++ b/ash/app_list/views/contents_view.cc
@@ -27,11 +27,13 @@
 #include "base/check_op.h"
 #include "base/notreached.h"
 #include "base/numerics/ranges.h"
+#include "ui/base/l10n/l10n_util.h"
 #include "ui/compositor/layer_animation_observer.h"
 #include "ui/compositor/scoped_layer_animation_settings.h"
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
 #include "ui/events/event.h"
+#include "ui/strings/grit/ui_strings.h"
 #include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/view_constants_aura.h"
 #include "ui/views/view_model.h"
@@ -309,6 +311,7 @@
 
   GetAppListMainView()->model()->SetState(state);
   UpdateSearchBoxVisibility(state);
+  app_list_view_->UpdateWindowTitle();
 }
 
 void ContentsView::ShowSearchResults(bool show) {
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 169828ab..7f6ba099 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -2977,10 +2977,10 @@
         Delete
       </message>
       <message name="IDS_ASH_SCREEN_CAPTURE_LABEL_FULLSCREEN_IMAGE_CAPTURE_CLAMSHELL" desc="The capture label message which shows in the middle of the screen when in fullscreen image capture mode in clamshell mode.">
-        Click anywhere to capture fullscreen
+        Click anywhere to capture full screen
       </message>
       <message name="IDS_ASH_SCREEN_CAPTURE_LABEL_FULLSCREEN_IMAGE_CAPTURE_TABLET" desc="The capture label message which shows in the middle of the screen when in fullscreen image capture mode in tablet mode.">
-        Tap anywhere to catpure fullscreen
+        Tap anywhere to catpure full screen
       </message>
       <message name="IDS_ASH_SCREEN_CAPTURE_LABEL_REGION_IMAGE_CAPTURE" desc="The caputre label message which shows in the middle of the screen when in region image capture mode.">
         Drag to select an area to capture
@@ -3000,6 +3000,30 @@
       <message name="IDS_ASH_SCREEN_CAPTURE_LABEL_VIDEO_RECORD" desc="The capture label message which shows in the middel of the captured region in region video record mode or shows in the middle of the screen in fullscreen video record mode.">
         Record
       </message>
+      <message name="IDS_ASH_SCREEN_CAPTURE_TOOLTIP_FULLSCREEN_SCREENSHOT" desc="Tooltip of the fullscreen toggle button when in image capture mode.">
+        Take full screen screenshot
+      </message>
+      <message name="IDS_ASH_SCREEN_CAPTURE_TOOLTIP_FULLSCREEN_RECORD" desc="Tooltip of the fullscreen toggle button when in video record mode.">
+        Record full screen
+      </message>
+      <message name="IDS_ASH_SCREEN_CAPTURE_TOOLTIP_REGION_SCREENSHOT" desc="Tooltip of the region toggle button when in image capture mode.">
+        Take partial screenshot
+      </message>
+      <message name="IDS_ASH_SCREEN_CAPTURE_TOOLTIP_REGION_RECORD" desc="Tooltip of the region toggle button when in video record mode.">
+        Record partial screen
+      </message>
+      <message name="IDS_ASH_SCREEN_CAPTURE_TOOLTIP_WINDOW_SCREENSHOT" desc="Tooltip of the window toggle button when in image capture mode.">
+        Take window screenshot
+      </message>
+      <message name="IDS_ASH_SCREEN_CAPTURE_TOOLTIP_WINDOW_RECORD" desc="Tooltip of the window toggle button when in video record mode.">
+        Record window
+      </message>
+      <message name="IDS_ASH_SCREEN_CAPTURE_TOOLTIP_SCREENSHOT" desc="Tooltip of the toggle button indicates the image capture mode.">
+        Screenshot
+      </message>
+      <message name="IDS_ASH_SCREEN_CAPTURE_TOOLTIP_SCREENRECORD" desc="Tooltip of the toggle button indicates the video record mode.">
+        Screen record
+      </message>
 
       <!-- Switch Between TABLET/LAPTOP MODE-->
       <message name="IDS_ASH_SWITCH_TO_TABLET_MODE" desc="Alert of switching to tablet mode.">
diff --git a/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_LABEL_FULLSCREEN_IMAGE_CAPTURE_CLAMSHELL.png.sha1 b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_LABEL_FULLSCREEN_IMAGE_CAPTURE_CLAMSHELL.png.sha1
index b568b91..f2e5663e 100644
--- a/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_LABEL_FULLSCREEN_IMAGE_CAPTURE_CLAMSHELL.png.sha1
+++ b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_LABEL_FULLSCREEN_IMAGE_CAPTURE_CLAMSHELL.png.sha1
@@ -1 +1 @@
-51f6a0e3df171b9ee9339d7875753530d84dcc51
\ No newline at end of file
+728a94adb51624cafff28b0c839c2b389c634fdd
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_LABEL_FULLSCREEN_IMAGE_CAPTURE_TABLET.png.sha1 b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_LABEL_FULLSCREEN_IMAGE_CAPTURE_TABLET.png.sha1
index 090ed6c..252efe99 100644
--- a/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_LABEL_FULLSCREEN_IMAGE_CAPTURE_TABLET.png.sha1
+++ b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_LABEL_FULLSCREEN_IMAGE_CAPTURE_TABLET.png.sha1
@@ -1 +1 @@
-5b2ff157131b82499fc5f821d3087a6752abe965
\ No newline at end of file
+2aeb331699f03d895b3af8abe7b1a977c59ab5ca
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_TOOLTIP_FULLSCREEN_RECORD.png.sha1 b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_TOOLTIP_FULLSCREEN_RECORD.png.sha1
new file mode 100644
index 0000000..31ec609
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_TOOLTIP_FULLSCREEN_RECORD.png.sha1
@@ -0,0 +1 @@
+e4fd029a129aab8eaa1638c9982281bf1d76a45e
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_TOOLTIP_FULLSCREEN_SCREENSHOT.png.sha1 b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_TOOLTIP_FULLSCREEN_SCREENSHOT.png.sha1
new file mode 100644
index 0000000..c861c53b6
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_TOOLTIP_FULLSCREEN_SCREENSHOT.png.sha1
@@ -0,0 +1 @@
+3c582b11e9b52139086b3cb66ff4a627bfae4a86
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_TOOLTIP_REGION_RECORD.png.sha1 b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_TOOLTIP_REGION_RECORD.png.sha1
new file mode 100644
index 0000000..b93636f7
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_TOOLTIP_REGION_RECORD.png.sha1
@@ -0,0 +1 @@
+227585a730147ac403b0d368ca2cae5e0b22e07e
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_TOOLTIP_REGION_SCREENSHOT.png.sha1 b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_TOOLTIP_REGION_SCREENSHOT.png.sha1
new file mode 100644
index 0000000..0f96aa6b
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_TOOLTIP_REGION_SCREENSHOT.png.sha1
@@ -0,0 +1 @@
+9453e3233acdf36e3b67323930fd6635da1a0656
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_TOOLTIP_SCREENRECORD.png.sha1 b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_TOOLTIP_SCREENRECORD.png.sha1
new file mode 100644
index 0000000..2e66468
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_TOOLTIP_SCREENRECORD.png.sha1
@@ -0,0 +1 @@
+c777120c5b9175508a41310e4a3aea5cb13fd59a
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_TOOLTIP_SCREENSHOT.png.sha1 b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_TOOLTIP_SCREENSHOT.png.sha1
new file mode 100644
index 0000000..180c8f2
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_TOOLTIP_SCREENSHOT.png.sha1
@@ -0,0 +1 @@
+68423bce68701ce6027b7936b4ac36cee3df2b03
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_TOOLTIP_WINDOW_RECORD.png.sha1 b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_TOOLTIP_WINDOW_RECORD.png.sha1
new file mode 100644
index 0000000..9a345ac
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_TOOLTIP_WINDOW_RECORD.png.sha1
@@ -0,0 +1 @@
+553b2b269908e5eec8a0f566a2e68f76403099d2
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_TOOLTIP_WINDOW_SCREENSHOT.png.sha1 b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_TOOLTIP_WINDOW_SCREENSHOT.png.sha1
new file mode 100644
index 0000000..752fbcaa
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_TOOLTIP_WINDOW_SCREENSHOT.png.sha1
@@ -0,0 +1 @@
+9932d80b33379c55e9039b9d8fff287c3b2b115c
\ No newline at end of file
diff --git a/ash/assistant/ui/base/assistant_button.cc b/ash/assistant/ui/base/assistant_button.cc
index af8f256..3fd5a25 100644
--- a/ash/assistant/ui/base/assistant_button.cc
+++ b/ash/assistant/ui/base/assistant_button.cc
@@ -50,7 +50,7 @@
   SetFocusForPlatform();
 
   // Image.
-  EnableCanvasFlippingForRTLUI(false);
+  SetFlipCanvasOnPaintForRTLUI(false);
   SetImageHorizontalAlignment(views::ImageButton::ALIGN_CENTER);
   SetImageVerticalAlignment(views::ImageButton::ALIGN_MIDDLE);
 
diff --git a/ash/capture_mode/capture_mode_bar_view.cc b/ash/capture_mode/capture_mode_bar_view.cc
index 978b1ee..67ea22a 100644
--- a/ash/capture_mode/capture_mode_bar_view.cc
+++ b/ash/capture_mode/capture_mode_bar_view.cc
@@ -91,6 +91,7 @@
 
 void CaptureModeBarView::OnCaptureTypeChanged(CaptureModeType new_type) {
   capture_type_view_->OnCaptureTypeChanged(new_type);
+  capture_source_view_->OnCaptureTypeChanged(new_type);
 }
 
 void CaptureModeBarView::OnButtonPressed() {
diff --git a/ash/capture_mode/capture_mode_source_view.cc b/ash/capture_mode/capture_mode_source_view.cc
index d051914c..69af4f0 100644
--- a/ash/capture_mode/capture_mode_source_view.cc
+++ b/ash/capture_mode/capture_mode_source_view.cc
@@ -9,7 +9,9 @@
 #include "ash/capture_mode/capture_mode_controller.h"
 #include "ash/capture_mode/capture_mode_toggle_button.h"
 #include "ash/resources/vector_icons/vector_icons.h"
+#include "ash/strings/grit/ash_strings.h"
 #include "base/bind.h"
+#include "ui/base/l10n/l10n_util.h"
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/metadata/metadata_impl_macros.h"
 
@@ -36,7 +38,9 @@
       capture_mode::kBetweenChildSpacing));
   box_layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kCenter);
-  OnCaptureSourceChanged(CaptureModeController::Get()->source());
+  auto* controller = CaptureModeController::Get();
+  OnCaptureSourceChanged(controller->source());
+  OnCaptureTypeChanged(controller->type());
 }
 
 CaptureModeSourceView::~CaptureModeSourceView() = default;
@@ -49,6 +53,19 @@
   window_toggle_button_->SetToggled(new_source == CaptureModeSource::kWindow);
 }
 
+void CaptureModeSourceView::OnCaptureTypeChanged(CaptureModeType new_type) {
+  const bool is_capturing_image = new_type == CaptureModeType::kImage;
+  fullscreen_toggle_button_->SetTooltipText(l10n_util::GetStringUTF16(
+      is_capturing_image ? IDS_ASH_SCREEN_CAPTURE_TOOLTIP_FULLSCREEN_SCREENSHOT
+                         : IDS_ASH_SCREEN_CAPTURE_TOOLTIP_FULLSCREEN_RECORD));
+  region_toggle_button_->SetTooltipText(l10n_util::GetStringUTF16(
+      is_capturing_image ? IDS_ASH_SCREEN_CAPTURE_TOOLTIP_REGION_SCREENSHOT
+                         : IDS_ASH_SCREEN_CAPTURE_TOOLTIP_REGION_RECORD));
+  window_toggle_button_->SetTooltipText(l10n_util::GetStringUTF16(
+      is_capturing_image ? IDS_ASH_SCREEN_CAPTURE_TOOLTIP_WINDOW_SCREENSHOT
+                         : IDS_ASH_SCREEN_CAPTURE_TOOLTIP_WINDOW_RECORD));
+}
+
 void CaptureModeSourceView::OnFullscreenToggle() {
   CaptureModeController::Get()->SetSource(CaptureModeSource::kFullscreen);
 }
diff --git a/ash/capture_mode/capture_mode_source_view.h b/ash/capture_mode/capture_mode_source_view.h
index e17d1f83..1fe5d5b 100644
--- a/ash/capture_mode/capture_mode_source_view.h
+++ b/ash/capture_mode/capture_mode_source_view.h
@@ -39,6 +39,9 @@
   // Called when the capture source changes.
   void OnCaptureSourceChanged(CaptureModeSource new_source);
 
+  // Called when the capture type changes to update tooltip texts.
+  void OnCaptureTypeChanged(CaptureModeType type);
+
  private:
   void OnFullscreenToggle();
   void OnRegionToggle();
diff --git a/ash/capture_mode/capture_mode_toggle_button.cc b/ash/capture_mode/capture_mode_toggle_button.cc
index 59c071a..27fe7a24 100644
--- a/ash/capture_mode/capture_mode_toggle_button.cc
+++ b/ash/capture_mode/capture_mode_toggle_button.cc
@@ -24,9 +24,6 @@
   SetImageVerticalAlignment(ALIGN_MIDDLE);
   GetViewAccessibility().OverrideIsLeaf(true);
 
-  // TODO(afakhry): Fix this.
-  SetTooltipText(base::UTF8ToUTF16(GetClassName()));
-
   SetInstallFocusRingOnFocus(true);
   SetFocusForPlatform();
   const auto* color_provider = AshColorProvider::Get();
@@ -57,7 +54,9 @@
 
 void CaptureModeToggleButton::GetAccessibleNodeData(ui::AXNodeData* node_data) {
   ImageButton::GetAccessibleNodeData(node_data);
-  node_data->SetName(GetTooltipText(gfx::Point()));
+  const base::string16 tooltip = GetTooltipText(gfx::Point());
+  DCHECK(!tooltip.empty());
+  node_data->SetName(tooltip);
   node_data->role = ax::mojom::Role::kToggleButton;
   node_data->SetCheckedState(GetToggled() ? ax::mojom::CheckedState::kTrue
                                           : ax::mojom::CheckedState::kFalse);
diff --git a/ash/capture_mode/capture_mode_type_view.cc b/ash/capture_mode/capture_mode_type_view.cc
index c62ca8b..081929e9f 100644
--- a/ash/capture_mode/capture_mode_type_view.cc
+++ b/ash/capture_mode/capture_mode_type_view.cc
@@ -9,8 +9,10 @@
 #include "ash/capture_mode/capture_mode_controller.h"
 #include "ash/capture_mode/capture_mode_toggle_button.h"
 #include "ash/resources/vector_icons/vector_icons.h"
+#include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_provider.h"
 #include "base/bind.h"
+#include "ui/base/l10n/l10n_util.h"
 #include "ui/views/background.h"
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/metadata/metadata_impl_macros.h"
@@ -50,6 +52,11 @@
   box_layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kCenter);
   OnCaptureTypeChanged(CaptureModeController::Get()->type());
+
+  image_toggle_button_->SetTooltipText(
+      l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_TOOLTIP_SCREENSHOT));
+  video_toggle_button_->SetTooltipText(
+      l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_TOOLTIP_SCREENRECORD));
 }
 
 CaptureModeTypeView::~CaptureModeTypeView() = default;
diff --git a/ash/capture_mode/capture_mode_unittests.cc b/ash/capture_mode/capture_mode_unittests.cc
index 378e1150..8457a08a 100644
--- a/ash/capture_mode/capture_mode_unittests.cc
+++ b/ash/capture_mode/capture_mode_unittests.cc
@@ -287,7 +287,8 @@
   EXPECT_EQ(controller->source(), CaptureModeSource::kFullscreen);
 }
 
-TEST_F(CaptureModeTest, VideoRecordingUiBehavior) {
+// TODO(https://crbug.com/1141927): test is flakey.
+TEST_F(CaptureModeTest, DISABLED_VideoRecordingUiBehavior) {
   // We need a non-zero duration to avoid infinite loop on countdown.
   ui::ScopedAnimationDurationScaleMode animatin_scale(
       ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
diff --git a/ash/drag_drop/drag_drop_controller.cc b/ash/drag_drop/drag_drop_controller.cc
index 1380097..8ee44976 100644
--- a/ash/drag_drop/drag_drop_controller.cc
+++ b/ash/drag_drop/drag_drop_controller.cc
@@ -269,7 +269,7 @@
 }
 
 bool DragDropController::IsDragDropInProgress() {
-  return !!drag_drop_tracker_.get();
+  return !!drag_drop_tracker_ && !!drag_data_;
 }
 
 void DragDropController::AddObserver(
@@ -520,6 +520,11 @@
       gfx::Point location_in_screen = event.root_location();
       ::wm::ConvertPointToScreen(target->GetRootWindow(), &location_in_screen);
       tab_drag_drop_delegate_->Drop(location_in_screen, copied_data);
+      // Override the drag event's drop effect as a move to inform the front-end
+      // that the tab or group was moved. Otherwise, the WebUI tab strip does
+      // not know that a drop resulted in a tab being moved and will temporarily
+      // visually return the tab to its original position. (crbug.com/1081905)
+      drag_operation_ = ui::DragDropTypes::DragOperation::DRAG_MOVE;
       StartCanceledAnimation(kCancelAnimationDuration);
     } else if (drag_operation_ == 0) {
       StartCanceledAnimation(kCancelAnimationDuration);
diff --git a/ash/drag_drop/drag_drop_controller_unittest.cc b/ash/drag_drop/drag_drop_controller_unittest.cc
index 5966cc7..3d94b70 100644
--- a/ash/drag_drop/drag_drop_controller_unittest.cc
+++ b/ash/drag_drop/drag_drop_controller_unittest.cc
@@ -8,14 +8,18 @@
 
 #include "ash/drag_drop/drag_drop_tracker.h"
 #include "ash/drag_drop/drag_image_view.h"
+#include "ash/public/cpp/ash_features.h"
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
+#include "ash/test_shell_delegate.h"
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/location.h"
 #include "base/run_loop.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "ui/aura/client/capture_client.h"
 #include "ui/aura/client/drag_drop_client_observer.h"
 #include "ui/aura/client/drag_drop_delegate.h"
@@ -39,6 +43,10 @@
 #include "ui/views/view.h"
 #include "ui/views/widget/widget.h"
 
+using ::testing::_;
+using ::testing::NiceMock;
+using ::testing::Return;
+
 namespace ash {
 
 namespace {
@@ -293,13 +301,29 @@
 
 }  // namespace
 
+class MockShellDelegate : public TestShellDelegate {
+ public:
+  MockShellDelegate() = default;
+  ~MockShellDelegate() override = default;
+
+  MOCK_METHOD(bool, IsTabDrag, (const ui::OSExchangeData&), (override));
+  MOCK_METHOD(aura::Window*,
+              CreateBrowserForTabDrop,
+              (aura::Window * source_window,
+               const ui::OSExchangeData& drop_data),
+              (override));
+};
+
 class DragDropControllerTest : public AshTestBase {
  public:
   DragDropControllerTest() = default;
   ~DragDropControllerTest() override = default;
 
   void SetUp() override {
-    AshTestBase::SetUp();
+    auto mock_shell_delegate = std::make_unique<NiceMock<MockShellDelegate>>();
+    mock_shell_delegate_ = mock_shell_delegate.get();
+    AshTestBase::SetUp(std::move(mock_shell_delegate));
+
     drag_drop_controller_ = std::make_unique<TestDragDropController>();
     drag_drop_controller_->set_should_block_during_drag_drop(false);
     drag_drop_controller_->set_enabled(true);
@@ -346,6 +370,8 @@
     return drag_drop_controller_->drag_drop_tracker_.get();
   }
 
+  MockShellDelegate* mock_shell_delegate() { return mock_shell_delegate_; }
+
   void CompleteCancelAnimation() {
     CompletableLinearAnimation* animation =
         static_cast<CompletableLinearAnimation*>(
@@ -366,6 +392,7 @@
   }
 
   std::unique_ptr<TestDragDropController> drag_drop_controller_;
+  NiceMock<MockShellDelegate>* mock_shell_delegate_ = nullptr;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(DragDropControllerTest);
@@ -1267,4 +1294,41 @@
   base::RunLoop().RunUntilIdle();
 }
 
+// Verifies that a tab drag changes the drag operation to a move.
+TEST_F(DragDropControllerTest, DragTabChangesDragOperationToMove) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(features::kWebUITabStripTabDragIntegration);
+
+  EXPECT_CALL(*mock_shell_delegate(), IsTabDrag(_))
+      .Times(1)
+      .WillOnce(Return(true));
+  std::unique_ptr<aura::Window> new_window = CreateToplevelTestWindow();
+  EXPECT_CALL(*mock_shell_delegate(), CreateBrowserForTabDrop(_, _))
+      .Times(1)
+      .WillOnce(Return(new_window.get()));
+
+  std::unique_ptr<views::Widget> widget = CreateFramelessWidget();
+  aura::Window* window = widget->GetNativeWindow();
+
+  // Posted task will be run when the inner loop runs in StartDragAndDrop.
+  ui::test::EventGenerator generator(window->GetRootWindow(), window);
+  generator.PressLeftButton();
+  // For drag enter.
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&ui::test::EventGenerator::MoveMouseBy,
+                                base::Unretained(&generator), 0, 1));
+  // For perform drop.
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&ui::test::EventGenerator::ReleaseLeftButton,
+                                base::Unretained(&generator)));
+
+  drag_drop_controller_->set_should_block_during_drag_drop(true);
+  int operation = drag_drop_controller_->StartDragAndDrop(
+      std::make_unique<ui::OSExchangeData>(), window->GetRootWindow(), window,
+      gfx::Point(5, 5), ui::DragDropTypes::DRAG_NONE,
+      ui::mojom::DragEventSource::kMouse);
+
+  EXPECT_EQ(operation, ui::DragDropTypes::DRAG_MOVE);
+}
+
 }  // namespace ash
diff --git a/ash/hud_display/graph_page_view_base.cc b/ash/hud_display/graph_page_view_base.cc
index f78f6bad..1433122 100644
--- a/ash/hud_display/graph_page_view_base.cc
+++ b/ash/hud_display/graph_page_view_base.cc
@@ -39,6 +39,9 @@
     SetBackground(std::make_unique<SolidSourceBackground>(kHUDLegendBackground,
                                                           /*radius=*/0));
     SetProperty(kHUDClickHandler, HTCLIENT);
+
+    DCHECK_EQ(views::View::FocusBehavior::ACCESSIBLE_ONLY, GetFocusBehavior());
+    SetFocusBehavior(views::View::FocusBehavior::ACCESSIBLE_ONLY);
   }
 
   MinMaxButton(const MinMaxButton&) = delete;
diff --git a/ash/hud_display/hud_header_view.cc b/ash/hud_display/hud_header_view.cc
index d545dcc..4f12785f 100644
--- a/ash/hud_display/hud_header_view.cc
+++ b/ash/hud_display/hud_header_view.cc
@@ -76,6 +76,9 @@
                                    kHUDSettingsIconSize, kHUDDefaultColor));
     SetBorder(views::CreateEmptyBorder(gfx::Insets(kSettingsIconBorder)));
     SetProperty(kHUDClickHandler, HTCLIENT);
+
+    DCHECK_EQ(views::View::FocusBehavior::ACCESSIBLE_ONLY, GetFocusBehavior());
+    SetFocusBehavior(views::View::FocusBehavior::ACCESSIBLE_ONLY);
   }
 
   SettingsButton(const SettingsButton&) = delete;
diff --git a/ash/hud_display/tab_strip.cc b/ash/hud_display/tab_strip.cc
index 7f7f544..8d5da05 100644
--- a/ash/hud_display/tab_strip.cc
+++ b/ash/hud_display/tab_strip.cc
@@ -88,6 +88,9 @@
   SetBorder(views::CreateEmptyBorder(
       kSettingsIconBorder, kTabOverlayWidth + kTabTitleBorder,
       kSettingsIconBorder, kTabOverlayWidth + kTabTitleBorder));
+
+  DCHECK_EQ(views::View::FocusBehavior::ACCESSIBLE_ONLY, GetFocusBehavior());
+  SetFocusBehavior(views::View::FocusBehavior::ACCESSIBLE_ONLY);
 }
 
 void HUDTabButton::SetStyle(Style style) {
diff --git a/ash/login/ui/note_action_launch_button.cc b/ash/login/ui/note_action_launch_button.cc
index e70f44b5..731fbda 100644
--- a/ash/login/ui/note_action_launch_button.cc
+++ b/ash/login/ui/note_action_launch_button.cc
@@ -192,7 +192,7 @@
                               ShelfConfig::Get()->shelf_icon_color()));
     SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
     SetFocusPainter(nullptr);
-    EnableCanvasFlippingForRTLUI(true);
+    SetFlipCanvasOnPaintForRTLUI(true);
     SetPreferredSize(gfx::Size(kLargeBubbleRadiusDp, kLargeBubbleRadiusDp));
     SetEventTargeter(
         std::make_unique<views::ViewTargeter>(&event_targeter_delegate_));
diff --git a/ash/magnifier/magnification_controller.cc b/ash/magnifier/magnification_controller.cc
index 334a8c7..7ed7ffec 100644
--- a/ash/magnifier/magnification_controller.cc
+++ b/ash/magnifier/magnification_controller.cc
@@ -10,6 +10,7 @@
 #include <vector>
 
 #include "ash/accelerators/accelerator_controller_impl.h"
+#include "ash/accessibility/accessibility_controller_impl.h"
 #include "ash/accessibility/accessibility_delegate.h"
 #include "ash/display/root_window_transformers.h"
 #include "ash/host/ash_window_tree_host.h"
@@ -701,6 +702,9 @@
   if (duration_in_ms > 0)
     is_on_animation_ = true;
 
+  Shell::Get()->accessibility_controller()->MagnifierBoundsChanged(
+      GetViewportRect());
+
   return true;
 }
 
diff --git a/ash/public/cpp/accessibility_controller_client.h b/ash/public/cpp/accessibility_controller_client.h
index 0c3e76c..18a1711 100644
--- a/ash/public/cpp/accessibility_controller_client.h
+++ b/ash/public/cpp/accessibility_controller_client.h
@@ -17,6 +17,7 @@
 namespace gfx {
 class Point;
 class PointF;
+class Rect;
 }  // namespace gfx
 
 namespace ash {
@@ -82,6 +83,10 @@
   virtual void RequestAutoclickScrollableBoundsForPoint(
       gfx::Point& point_in_screen) = 0;
 
+  // Dispatches update to Accessibility Common extension when magnifier bounds
+  // have changed.
+  virtual void MagnifierBoundsChanged(const gfx::Rect& bounds_in_screen) = 0;
+
   // Called when Switch Access is fully disabled by the user accepting the
   // disable dialog. Switch Access must be left running when the pref changes
   // and before the disable dialog is accepted, so that users can use Switch
diff --git a/ash/quick_answers/quick_answers_controller_unittest.cc b/ash/quick_answers/quick_answers_controller_unittest.cc
index 2bc5c2a..14b103e 100644
--- a/ash/quick_answers/quick_answers_controller_unittest.cc
+++ b/ash/quick_answers/quick_answers_controller_unittest.cc
@@ -33,10 +33,8 @@
 class QuickAnswersControllerTest : public AshTestBase {
  protected:
   QuickAnswersControllerTest() {
-    scoped_feature_list_.InitWithFeatures(
-        {chromeos::features::kQuickAnswers,
-         chromeos::features::kQuickAnswersRichUi},
-        {});
+    scoped_feature_list_.InitAndEnableFeature(
+        chromeos::features::kQuickAnswers);
   }
   QuickAnswersControllerTest(const QuickAnswersControllerTest&) = delete;
   QuickAnswersControllerTest& operator=(const QuickAnswersControllerTest&) =
diff --git a/ash/quick_answers/quick_answers_ui_controller_unittest.cc b/ash/quick_answers/quick_answers_ui_controller_unittest.cc
index 5752a0d..18698b1 100644
--- a/ash/quick_answers/quick_answers_ui_controller_unittest.cc
+++ b/ash/quick_answers/quick_answers_ui_controller_unittest.cc
@@ -22,10 +22,8 @@
 class QuickAnswersUiControllerTest : public AshTestBase {
  protected:
   QuickAnswersUiControllerTest() {
-    scoped_feature_list_.InitWithFeatures(
-        {chromeos::features::kQuickAnswers,
-         chromeos::features::kQuickAnswersRichUi},
-        {});
+    scoped_feature_list_.InitAndEnableFeature(
+        chromeos::features::kQuickAnswers);
   }
   QuickAnswersUiControllerTest(const QuickAnswersUiControllerTest&) = delete;
   QuickAnswersUiControllerTest& operator=(const QuickAnswersUiControllerTest&) =
diff --git a/ash/quick_answers/ui/quick_answers_view_unittest.cc b/ash/quick_answers/ui/quick_answers_view_unittest.cc
index e30eee5..a7dc78fc 100644
--- a/ash/quick_answers/ui/quick_answers_view_unittest.cc
+++ b/ash/quick_answers/ui/quick_answers_view_unittest.cc
@@ -28,10 +28,8 @@
 class QuickAnswersViewsTest : public AshTestBase {
  protected:
   QuickAnswersViewsTest() {
-    scoped_feature_list_.InitWithFeatures(
-        {chromeos::features::kQuickAnswers,
-         chromeos::features::kQuickAnswersRichUi},
-        {});
+    scoped_feature_list_.InitAndEnableFeature(
+        chromeos::features::kQuickAnswers);
   }
   QuickAnswersViewsTest(const QuickAnswersViewsTest&) = delete;
   QuickAnswersViewsTest& operator=(const QuickAnswersViewsTest&) = delete;
diff --git a/ash/search_box/search_box_view_base.cc b/ash/search_box/search_box_view_base.cc
index cee24a9..837ee039 100644
--- a/ash/search_box/search_box_view_base.cc
+++ b/ash/search_box/search_box_view_base.cc
@@ -277,7 +277,7 @@
   content_container_->AddChildView(search_box_right_space_);
 
   assistant_button_ = new SearchBoxImageButton(this);
-  assistant_button_->EnableCanvasFlippingForRTLUI(false);
+  assistant_button_->SetFlipCanvasOnPaintForRTLUI(false);
   // Default hidden, child class should decide if it should shown.
   assistant_button_->SetVisible(false);
   content_container_->AddChildView(assistant_button_);
diff --git a/ash/shelf/back_button.cc b/ash/shelf/back_button.cc
index bba968b..cf0e6de 100644
--- a/ash/shelf/back_button.cc
+++ b/ash/shelf/back_button.cc
@@ -30,7 +30,7 @@
 
 BackButton::BackButton(Shelf* shelf) : ShelfControlButton(shelf, this) {
   SetAccessibleName(l10n_util::GetStringUTF16(IDS_ASH_SHELF_BACK_BUTTON_TITLE));
-  EnableCanvasFlippingForRTLUI(true);
+  SetFlipCanvasOnPaintForRTLUI(true);
 }
 
 BackButton::~BackButton() {}
diff --git a/ash/shell.cc b/ash/shell.cc
index 3187a833..8e604ba7 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -1120,8 +1120,7 @@
   magnification_controller_ = std::make_unique<MagnificationController>();
   mru_window_tracker_ = std::make_unique<MruWindowTracker>();
   assistant_controller_ = std::make_unique<AssistantControllerImpl>();
-  if (chromeos::features::IsQuickAnswersEnabled() &&
-      chromeos::features::IsQuickAnswersRichUiEnabled()) {
+  if (chromeos::features::IsQuickAnswersEnabled()) {
     quick_answers_controller_ = std::make_unique<QuickAnswersControllerImpl>();
   }
 
diff --git a/ash/system/accessibility/autoclick_scroll_view.cc b/ash/system/accessibility/autoclick_scroll_view.cc
index 173fccb..425f63d 100644
--- a/ash/system/accessibility/autoclick_scroll_view.cc
+++ b/ash/system/accessibility/autoclick_scroll_view.cc
@@ -120,7 +120,7 @@
     SetTooltipText(l10n_util::GetStringUTF16(accessible_name_id));
     // Disable canvas flipping, as scroll left should always be left no matter
     // the language orientation.
-    EnableCanvasFlippingForRTLUI(false);
+    SetFlipCanvasOnPaintForRTLUI(false);
     scroll_hover_timer_ = std::make_unique<base::RetainingOneShotTimer>(
         FROM_HERE,
         base::TimeDelta::FromMilliseconds(
diff --git a/ash/system/accessibility/floating_menu_button.cc b/ash/system/accessibility/floating_menu_button.cc
index 4d425ca..82537178 100644
--- a/ash/system/accessibility/floating_menu_button.cc
+++ b/ash/system/accessibility/floating_menu_button.cc
@@ -51,7 +51,7 @@
       is_a11y_togglable_(is_a11y_togglable) {
   SetImageHorizontalAlignment(ALIGN_CENTER);
   SetImageVerticalAlignment(ALIGN_MIDDLE);
-  EnableCanvasFlippingForRTLUI(flip_for_rtl);
+  SetFlipCanvasOnPaintForRTLUI(flip_for_rtl);
   SetPreferredSize(gfx::Size(size_, size_));
   TrayPopupUtils::ConfigureTrayPopupButton(this);
   views::InstallCircleHighlightPathGenerator(this);
diff --git a/ash/system/bluetooth/bluetooth_detailed_view.cc b/ash/system/bluetooth/bluetooth_detailed_view.cc
index 24e71b9c38..dbfe75a0 100644
--- a/ash/system/bluetooth/bluetooth_detailed_view.cc
+++ b/ash/system/bluetooth/bluetooth_detailed_view.cc
@@ -236,8 +236,16 @@
 
   // Show user Bluetooth state if there is no bluetooth devices in list.
   if (device_map_.empty()) {
-    scroll_content()->AddChildView(new TrayInfoLabel(
-        nullptr /* delegate */, IDS_ASH_STATUS_TRAY_BLUETOOTH_DISCOVERING));
+    if (!bluetooth_discovering_label_) {
+      bluetooth_discovering_label_ = new TrayInfoLabel(
+          nullptr /* delegate */, IDS_ASH_STATUS_TRAY_BLUETOOTH_DISCOVERING);
+      scroll_content()->AddChildViewAt(bluetooth_discovering_label_, index++);
+    } else {
+      scroll_content()->ReorderChildView(bluetooth_discovering_label_, index++);
+    }
+  } else {
+    scroll_content()->RemoveChildView(bluetooth_discovering_label_);
+    bluetooth_discovering_label_ = nullptr;
   }
 
   // Remove views for devices from old_device_map that are not in device_map_.
diff --git a/ash/system/bluetooth/bluetooth_detailed_view.h b/ash/system/bluetooth/bluetooth_detailed_view.h
index e880925b..d748fa6 100644
--- a/ash/system/bluetooth/bluetooth_detailed_view.h
+++ b/ash/system/bluetooth/bluetooth_detailed_view.h
@@ -19,6 +19,7 @@
 namespace ash {
 
 class TriView;
+class TrayInfoLabel;
 
 namespace tray {
 
@@ -107,6 +108,7 @@
 
   TriView* paired_devices_heading_ = nullptr;
   TriView* unpaired_devices_heading_ = nullptr;
+  TrayInfoLabel* bluetooth_discovering_label_ = nullptr;
 
   // The container of the message "Bluetooth is disabled" and an icon. It should
   // be shown instead of Bluetooth device list when Bluetooth is disabled.
diff --git a/ash/system/holding_space/recent_files_container.cc b/ash/system/holding_space/recent_files_container.cc
index ce9ec43..a42e499a 100644
--- a/ash/system/holding_space/recent_files_container.cc
+++ b/ash/system/holding_space/recent_files_container.cc
@@ -64,7 +64,7 @@
     SetupLabel(label);
 
     auto* chevron = AddChildView(std::make_unique<views::ImageView>());
-    chevron->EnableCanvasFlippingForRTLUI(true);
+    chevron->SetFlipCanvasOnPaintForRTLUI(true);
 
     const SkColor icon_color = AshColorProvider::Get()->GetContentLayerColor(
         AshColorProvider::ContentLayerType::kIconColorPrimary);
diff --git a/ash/system/media/media_tray.cc b/ash/system/media/media_tray.cc
index 4c36723c..706f050 100644
--- a/ash/system/media/media_tray.cc
+++ b/ash/system/media/media_tray.cc
@@ -24,6 +24,7 @@
 #include "components/prefs/pref_change_registrar.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
+#include "media/base/media_switches.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/display/manager/display_manager.h"
 #include "ui/display/manager/managed_display_info.h"
@@ -67,6 +68,21 @@
   return diagonal_len > kMinimumScreenSizeDiagonal;
 }
 
+// Used for getting default pin state for experiment.
+bool GetIsPinnedToShelfByFeatureParams() {
+  switch (media::kCrosGlobalMediaControlsPinParam.Get()) {
+    case media::kCrosGlobalMediaControlsPinOptions::kPin:
+      return true;
+    case media::kCrosGlobalMediaControlsPinOptions::kNotPin:
+      return false;
+    case media::kCrosGlobalMediaControlsPinOptions::kHeuristic:
+      return GetIsPinnedToShelfByDefault();
+  }
+
+  NOTREACHED();
+  return false;
+}
+
 // Enum that specifies the pin state of global media controls.
 enum PinState {
   kDefault = 0,
@@ -135,7 +151,7 @@
     case PinState::kUnpinned:
       return false;
     case PinState::kDefault:
-      return GetIsPinnedToShelfByDefault();
+      return GetIsPinnedToShelfByFeatureParams();
   }
 
   NOTREACHED();
diff --git a/ash/system/media/media_tray_unittest.cc b/ash/system/media/media_tray_unittest.cc
index 4a21610..996ff40 100644
--- a/ash/system/media/media_tray_unittest.cc
+++ b/ash/system/media/media_tray_unittest.cc
@@ -366,4 +366,51 @@
   EXPECT_EQ(initial_bounds, GetBubbleBounds());
 }
 
+class MediaTrayPinnedParamTest : public AshTestBase {
+ public:
+  MediaTrayPinnedParamTest() = default;
+  ~MediaTrayPinnedParamTest() override = default;
+
+  void SetUp() override {
+    auto& pin_param = media::kCrosGlobalMediaControlsPinParam;
+    feature_list_.InitAndEnableFeatureWithParameters(
+        media::kGlobalMediaControlsForChromeOS,
+        {{pin_param.name,
+          pin_param.GetName(media::kCrosGlobalMediaControlsPinOptions::kPin)}});
+    AshTestBase::SetUp();
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+TEST_F(MediaTrayPinnedParamTest, PinParamTest) {
+  UpdateDisplay("100x100");
+  EXPECT_TRUE(MediaTray::IsPinnedToShelf());
+}
+
+class MediaTrayNotPinnedParamTest : public AshTestBase {
+ public:
+  MediaTrayNotPinnedParamTest() = default;
+  ~MediaTrayNotPinnedParamTest() override = default;
+
+  void SetUp() override {
+    auto& pin_param = media::kCrosGlobalMediaControlsPinParam;
+    feature_list_.InitAndEnableFeatureWithParameters(
+        media::kGlobalMediaControlsForChromeOS,
+        {{pin_param.name,
+          pin_param.GetName(
+              media::kCrosGlobalMediaControlsPinOptions::kNotPin)}});
+    AshTestBase::SetUp();
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+TEST_F(MediaTrayNotPinnedParamTest, PinParamTest) {
+  UpdateDisplay("2560x1440");
+  EXPECT_FALSE(MediaTray::IsPinnedToShelf());
+}
+
 }  // namespace ash
diff --git a/ash/system/message_center/notifier_settings_view.cc b/ash/system/message_center/notifier_settings_view.cc
index 6163bc3..c88fcab 100644
--- a/ash/system/message_center/notifier_settings_view.cc
+++ b/ash/system/message_center/notifier_settings_view.cc
@@ -724,7 +724,7 @@
   auto* label_ptr = row_view->AddChildView(std::move(label));
   row_layout->SetFlexForView(label_ptr, 1);
 
-  toggle_button->EnableCanvasFlippingForRTLUI(true);
+  toggle_button->SetFlipCanvasOnPaintForRTLUI(true);
   row_view->AddChildView(std::move(toggle_button));
 
   return row_view;
diff --git a/ash/system/network/network_list_view.cc b/ash/system/network/network_list_view.cc
index 9c43a46..bdd3518 100644
--- a/ash/system/network/network_list_view.cc
+++ b/ash/system/network/network_list_view.cc
@@ -508,7 +508,7 @@
   views::ImageView* icon = new views::ImageView;
   const SkColor icon_color = GetIconColor();
   icon->SetPreferredSize(gfx::Size(kMenuIconSize, kMenuIconSize));
-  icon->EnableCanvasFlippingForRTLUI(true);
+  icon->SetFlipCanvasOnPaintForRTLUI(true);
   PowerStatus::BatteryImageInfo icon_info;
   icon_info.charge_percent = info.battery_percentage;
   icon->SetImage(PowerStatus::GetBatteryImage(
diff --git a/ash/system/toast/toast_overlay.cc b/ash/system/toast/toast_overlay.cc
index a730b02..4ec5315 100644
--- a/ash/system/toast/toast_overlay.cc
+++ b/ash/system/toast/toast_overlay.cc
@@ -132,6 +132,8 @@
 
     views::InstallRoundRectHighlightPathGenerator(this, gfx::Insets(),
                                                   kToastCornerRounding);
+    DCHECK_EQ(views::View::FocusBehavior::ACCESSIBLE_ONLY, GetFocusBehavior());
+    SetFocusBehavior(views::View::FocusBehavior::ACCESSIBLE_ONLY);
   }
 
   ToastOverlayButton(const ToastOverlayButton&) = delete;
diff --git a/ash/wm/desks/close_desk_button.cc b/ash/wm/desks/close_desk_button.cc
index 850e30a..fe3fe458 100644
--- a/ash/wm/desks/close_desk_button.cc
+++ b/ash/wm/desks/close_desk_button.cc
@@ -31,6 +31,8 @@
   SetInkDropMode(InkDropMode::ON);
   SetHasInkDropActionOnClick(true);
   SetFocusPainter(nullptr);
+  DCHECK_EQ(views::View::FocusBehavior::ACCESSIBLE_ONLY, GetFocusBehavior());
+  SetFocusBehavior(views::View::FocusBehavior::ACCESSIBLE_ONLY);
 
   SetEventTargeter(std::make_unique<views::ViewTargeter>(this));
 
diff --git a/ash/wm/desks/new_desk_button.cc b/ash/wm/desks/new_desk_button.cc
index 81c46ca..c3bcfe99 100644
--- a/ash/wm/desks/new_desk_button.cc
+++ b/ash/wm/desks/new_desk_button.cc
@@ -46,6 +46,8 @@
   SetInkDropMode(InkDropMode::ON);
   SetHasInkDropActionOnClick(true);
   SetFocusPainter(nullptr);
+  DCHECK_EQ(views::View::FocusBehavior::ACCESSIBLE_ONLY, GetFocusBehavior());
+  SetFocusBehavior(views::View::FocusBehavior::ACCESSIBLE_ONLY);
 
   auto border = std::make_unique<WmHighlightItemBorder>(kCornerRadius);
   border_ptr_ = border.get();
diff --git a/ash/wm/overview/overview_item_view.cc b/ash/wm/overview/overview_item_view.cc
index b1713ed..b8da9a2f 100644
--- a/ash/wm/overview/overview_item_view.cc
+++ b/ash/wm/overview/overview_item_view.cc
@@ -110,6 +110,8 @@
     SetMinimumImageSize(gfx::Size(kHeaderHeightDp, kHeaderHeightDp));
     SetAccessibleName(l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE));
     SetTooltipText(l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE));
+    DCHECK_EQ(views::View::FocusBehavior::ACCESSIBLE_ONLY, GetFocusBehavior());
+    SetFocusBehavior(views::View::FocusBehavior::ACCESSIBLE_ONLY);
 
     views::InstallFixedSizeCircleHighlightPathGenerator(
         this, kCloseButtonInkDropRadiusDp);
diff --git a/base/logging.h b/base/logging.h
index cfc40f2..0fb480f 100644
--- a/base/logging.h
+++ b/base/logging.h
@@ -374,7 +374,6 @@
 const LogSeverity LOG_WARNING = LOGGING_WARNING;
 const LogSeverity LOG_ERROR = LOGGING_ERROR;
 const LogSeverity LOG_FATAL = LOGGING_FATAL;
-const LogSeverity LOG_NUM_SEVERITIES = LOGGING_NUM_SEVERITIES;
 const LogSeverity LOG_DFATAL = LOGGING_DFATAL;
 
 // A few definitions of macros that don't generate much code. These are used
diff --git a/build/config/fuchsia/tests-with-exec.cmx b/build/config/fuchsia/tests-with-exec.cmx
index 44cc51d..7e6b3d20 100644
--- a/build/config/fuchsia/tests-with-exec.cmx
+++ b/build/config/fuchsia/tests-with-exec.cmx
@@ -36,6 +36,7 @@
       "fuchsia.ui.input.ImeService",
       "fuchsia.ui.input.ImeVisibilityService",
       "fuchsia.ui.scenic.Scenic",
+      "fuchsia.ui.policy.Presenter",
       "fuchsia.vulkan.loader.Loader",
       "fuchsia.web.ContextProvider"
     ]
diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni
index 2ba64f9..08b229a 100644
--- a/chrome/android/chrome_java_resources.gni
+++ b/chrome/android/chrome_java_resources.gni
@@ -71,7 +71,6 @@
   "java/res/drawable-hdpi/ic_download_pending.png",
   "java/res/drawable-hdpi/ic_drag_handle_grey600_24dp.png",
   "java/res/drawable-hdpi/ic_drive_site_white_24dp.png",
-  "java/res/drawable-hdpi/ic_edit_24dp.png",
   "java/res/drawable-hdpi/ic_email_googblue_36dp.png",
   "java/res/drawable-hdpi/ic_error_grey800_24dp_filled.png",
   "java/res/drawable-hdpi/ic_error_outline_googblue_24dp.png",
@@ -219,7 +218,6 @@
   "java/res/drawable-mdpi/ic_download_pending.png",
   "java/res/drawable-mdpi/ic_drag_handle_grey600_24dp.png",
   "java/res/drawable-mdpi/ic_drive_site_white_24dp.png",
-  "java/res/drawable-mdpi/ic_edit_24dp.png",
   "java/res/drawable-mdpi/ic_email_googblue_36dp.png",
   "java/res/drawable-mdpi/ic_error_grey800_24dp_filled.png",
   "java/res/drawable-mdpi/ic_error_outline_googblue_24dp.png",
@@ -351,7 +349,6 @@
   "java/res/drawable-xhdpi/ic_download_pending.png",
   "java/res/drawable-xhdpi/ic_drag_handle_grey600_24dp.png",
   "java/res/drawable-xhdpi/ic_drive_site_white_24dp.png",
-  "java/res/drawable-xhdpi/ic_edit_24dp.png",
   "java/res/drawable-xhdpi/ic_email_googblue_36dp.png",
   "java/res/drawable-xhdpi/ic_error_grey800_24dp_filled.png",
   "java/res/drawable-xhdpi/ic_error_outline_googblue_24dp.png",
@@ -461,7 +458,6 @@
   "java/res/drawable-xxhdpi/ic_download_pending.png",
   "java/res/drawable-xxhdpi/ic_drag_handle_grey600_24dp.png",
   "java/res/drawable-xxhdpi/ic_drive_site_white_24dp.png",
-  "java/res/drawable-xxhdpi/ic_edit_24dp.png",
   "java/res/drawable-xxhdpi/ic_email_googblue_36dp.png",
   "java/res/drawable-xxhdpi/ic_error_grey800_24dp_filled.png",
   "java/res/drawable-xxhdpi/ic_error_outline_googblue_24dp.png",
@@ -568,7 +564,6 @@
   "java/res/drawable-xxxhdpi/ic_dialer_not_found_red_40dp.png",
   "java/res/drawable-xxxhdpi/ic_drag_handle_grey600_24dp.png",
   "java/res/drawable-xxxhdpi/ic_drive_site_white_24dp.png",
-  "java/res/drawable-xxxhdpi/ic_edit_24dp.png",
   "java/res/drawable-xxxhdpi/ic_email_googblue_36dp.png",
   "java/res/drawable-xxxhdpi/ic_error_grey800_24dp_filled.png",
   "java/res/drawable-xxxhdpi/ic_error_outline_googblue_24dp.png",
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index eb66e74..be2e35a 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -1619,6 +1619,7 @@
   "java/src/org/chromium/chrome/browser/tracing/settings/DeveloperSettings.java",
   "java/src/org/chromium/chrome/browser/tracing/settings/TracingCategoriesSettings.java",
   "java/src/org/chromium/chrome/browser/tracing/settings/TracingSettings.java",
+  "java/src/org/chromium/chrome/browser/translate/TranslateAssistContent.java",
   "java/src/org/chromium/chrome/browser/translate/TranslateBridge.java",
   "java/src/org/chromium/chrome/browser/translate/TranslateIntentHandler.java",
   "java/src/org/chromium/chrome/browser/translate/TranslateUtils.java",
diff --git a/chrome/android/chrome_test_java_sources.gni b/chrome/android/chrome_test_java_sources.gni
index ef42581..6f34832 100644
--- a/chrome/android/chrome_test_java_sources.gni
+++ b/chrome/android/chrome_test_java_sources.gni
@@ -596,6 +596,7 @@
   "javatests/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherActionMenuTest.java",
   "javatests/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhoneTest.java",
   "javatests/src/org/chromium/chrome/browser/tracing/settings/TracingSettingsTest.java",
+  "javatests/src/org/chromium/chrome/browser/translate/TranslateAssistContentTest.java",
   "javatests/src/org/chromium/chrome/browser/translate/TranslateCompactInfoBarTest.java",
   "javatests/src/org/chromium/chrome/browser/translate/TranslateIntentTest.java",
   "javatests/src/org/chromium/chrome/browser/ui/system/StatusBarColorControllerTest.java",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
index c7732a7d..50c27e3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
@@ -152,6 +152,7 @@
 import org.chromium.chrome.browser.tabmodel.TabModelUtils;
 import org.chromium.chrome.browser.toolbar.ControlContainer;
 import org.chromium.chrome.browser.toolbar.ToolbarManager;
+import org.chromium.chrome.browser.translate.TranslateAssistContent;
 import org.chromium.chrome.browser.translate.TranslateBridge;
 import org.chromium.chrome.browser.ui.BottomContainer;
 import org.chromium.chrome.browser.ui.RootUiCoordinator;
@@ -1191,9 +1192,18 @@
     @TargetApi(Build.VERSION_CODES.M)
     public void onProvideAssistContent(AssistContent outContent) {
         Tab tab = getActivityTab();
+        boolean inOverviewMode = isInOverviewMode();
+
+        // Attempt to fetch translate data here so we can record UMA even if it won't be attached.
+        @Nullable
+        String structuredData = TranslateAssistContent.getTranslateDataForTab(tab, inOverviewMode);
+
         // No information is provided in incognito mode and overview mode.
-        if (tab != null && !tab.isIncognito() && !isInOverviewMode()) {
+        if (tab != null && !tab.isIncognito() && !inOverviewMode) {
             outContent.setWebUri(Uri.parse(tab.getUrlString()));
+            if (structuredData != null) {
+                outContent.setStructuredData(structuredData);
+            }
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuHeaderMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuHeaderMediator.java
index 74e34787..d92e8b0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuHeaderMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuHeaderMediator.java
@@ -72,7 +72,6 @@
      * @param thumbnail The bitmap received that will be displayed as the header image.
      */
     void onImageThumbnailRetrieved(Bitmap thumbnail) {
-        RecordHistogram.recordBooleanHistogram("ContextMenu.ThumbnailFetched", thumbnail != null);
         if (thumbnail != null) {
             setHeaderImage(getImageWithCheckerBackground(mContext.getResources(), thumbnail), true);
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadForegroundServiceImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadForegroundServiceImpl.java
index e28847a..d84601e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadForegroundServiceImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadForegroundServiceImpl.java
@@ -132,7 +132,7 @@
         DownloadNotificationUmaHelper.recordForegroundServiceLifecycleHistogram(
                 DownloadNotificationUmaHelper.ForegroundLifecycle.STOP);
         DownloadNotificationUmaHelper.recordServiceStoppedHistogram(
-                DownloadNotificationUmaHelper.ServiceStopped.STOPPED, true /* withForeground */);
+                DownloadNotificationUmaHelper.ServiceStopped.STOPPED);
 
         // Handle notifications and stop foreground.
         if (stopForegroundNotification == StopForegroundNotification.KILL) {
@@ -173,7 +173,7 @@
         // In the case the service was restarted when the intent is null.
         if (intent == null) {
             DownloadNotificationUmaHelper.recordServiceStoppedHistogram(
-                    DownloadNotificationUmaHelper.ServiceStopped.START_STICKY, true);
+                    DownloadNotificationUmaHelper.ServiceStopped.START_STICKY);
 
             // Allow observers to restart service on their own, if needed.
             getService().stopSelf();
@@ -186,7 +186,7 @@
     @Override
     public void onDestroy() {
         DownloadNotificationUmaHelper.recordServiceStoppedHistogram(
-                DownloadNotificationUmaHelper.ServiceStopped.DESTROYED, true /* withForeground */);
+                DownloadNotificationUmaHelper.ServiceStopped.DESTROYED);
         DownloadForegroundServiceObservers.alertObserversServiceDestroyed();
         super.onDestroy();
     }
@@ -194,7 +194,7 @@
     @Override
     public void onTaskRemoved(Intent rootIntent) {
         DownloadNotificationUmaHelper.recordServiceStoppedHistogram(
-                DownloadNotificationUmaHelper.ServiceStopped.TASK_REMOVED, true /*withForeground*/);
+                DownloadNotificationUmaHelper.ServiceStopped.TASK_REMOVED);
         DownloadForegroundServiceObservers.alertObserversTaskRemoved();
         super.onTaskRemoved(rootIntent);
     }
@@ -202,7 +202,7 @@
     @Override
     public void onLowMemory() {
         DownloadNotificationUmaHelper.recordServiceStoppedHistogram(
-                DownloadNotificationUmaHelper.ServiceStopped.LOW_MEMORY, true /* withForeground */);
+                DownloadNotificationUmaHelper.ServiceStopped.LOW_MEMORY);
         super.onLowMemory();
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationUmaHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationUmaHelper.java
index 3d2f433..690f3bac 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationUmaHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationUmaHelper.java
@@ -108,18 +108,11 @@
      * understand the frequency of unexpected stops (low memory, task removed, etc).
      * @param stopType Type of the foreground stop that is being recorded ({@link ServiceStopped}).
      */
-    static void recordServiceStoppedHistogram(
-            @ServiceStopped int stopType, boolean withForeground) {
+    static void recordServiceStoppedHistogram(@ServiceStopped int stopType) {
         if (!LibraryLoader.getInstance().isInitialized()) return;
-        if (withForeground) {
             RecordHistogram.recordEnumeratedHistogram(
                     "Android.DownloadManager.ServiceStopped.DownloadForeground", stopType,
                     ServiceStopped.NUM_ENTRIES);
-        } else {
-            RecordHistogram.recordEnumeratedHistogram(
-                    "Android.DownloadManager.ServiceStopped.DownloadNotification", stopType,
-                    ServiceStopped.NUM_ENTRIES);
-        }
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestBottomBar.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestBottomBar.java
index 33dcdbe3..b1f1b12 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestBottomBar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestBottomBar.java
@@ -8,13 +8,14 @@
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.ImageView;
 
 import org.chromium.chrome.R;
 
 /** This class represents a bar to display at the bottom of the payment request UI. */
 public class PaymentRequestBottomBar extends ViewGroup {
-    private View mLogoWithName;
-    private View mLogo;
+    private ImageView mLogoWithName;
+    private ImageView mLogo;
     private View mPrimaryButton;
     private View mSecondaryButton;
     private View mSpace;
@@ -29,7 +30,13 @@
         super.onFinishInflate();
 
         mLogoWithName = findViewById(R.id.logo_name);
+        // Not doing it in the template because the //components payment_request_bottom_bar.xml
+        // cannot depend on the //chrome logo.png.
+        mLogoWithName.setImageResource(R.drawable.product_logo_name);
         mLogo = findViewById(R.id.logo);
+        // Not doing it in the template because the //components template
+        // payment_request_bottom_bar.xml cannot depend on the //chrome fre_product_logo.png.
+        mLogo.setImageResource(R.drawable.fre_product_logo);
         mPrimaryButton = findViewById(R.id.button_primary);
         mSecondaryButton = findViewById(R.id.button_secondary);
         mSpace = findViewById(R.id.space);
@@ -132,4 +139,4 @@
             child.layout(childLeft, childTop, childRight, childTop + childHeight);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/translate/TranslateAssistContent.java b/chrome/android/java/src/org/chromium/chrome/browser/translate/TranslateAssistContent.java
new file mode 100644
index 0000000..cb1a910
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/translate/TranslateAssistContent.java
@@ -0,0 +1,141 @@
+// 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.chrome.browser.translate;
+
+import android.text.TextUtils;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import org.chromium.base.FeatureList;
+import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.tab.Tab;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Provides utilities related to providing translate information to Assistant.
+ */
+public class TranslateAssistContent {
+    public static final String TYPE_KEY = "@type";
+    public static final String TYPE_VALUE = "WebPage";
+    public static final String URL_KEY = "url";
+    public static final String IN_LANGUAGE_KEY = "inLanguage";
+    public static final String WORK_TRANSLATION_KEY = "workTranslation";
+    public static final String TRANSLATION_OF_WORK_KEY = "translationOfWork";
+
+    /**
+     * Represents the result of attempting to attach translate data to AssistContent.
+     * DO NOT reorder items in this interface, because it's mirrored to UMA (as
+     * TranslateAssistContentResult). Values should be enumerated from 0 and can't have gaps.
+     * When removing items, comment them out and keep existing numeric values stable.
+     */
+    @IntDef({TranslateAssistContentResult.FEATURE_DISABLED,
+            TranslateAssistContentResult.TAB_WAS_NULL, TranslateAssistContentResult.INCOGNITO_TAB,
+            TranslateAssistContentResult.OVERVIEW_MODE,
+            TranslateAssistContentResult.CANNOT_TRANSLATE_TAB,
+            TranslateAssistContentResult.MISSING_ORIGINAL_LANGUAGE,
+            TranslateAssistContentResult.MISSING_CURRENT_LANGUAGE,
+            TranslateAssistContentResult.JSON_EXCEPTION,
+            TranslateAssistContentResult.TRANSLATABLE_PAGE,
+            TranslateAssistContentResult.TRANSLATED_PAGE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TranslateAssistContentResult {
+        int FEATURE_DISABLED = 0;
+        int TAB_WAS_NULL = 1;
+        int INCOGNITO_TAB = 2;
+        int OVERVIEW_MODE = 3;
+        int CANNOT_TRANSLATE_TAB = 4;
+        int MISSING_ORIGINAL_LANGUAGE = 5;
+        int MISSING_CURRENT_LANGUAGE = 6;
+        int JSON_EXCEPTION = 7;
+        int TRANSLATABLE_PAGE = 8;
+        int TRANSLATED_PAGE = 9;
+        // Update TranslateAssistContentResult in enums.xml when adding new items.
+        int NUM_ENTRIES = 10;
+    }
+
+    private static void recordTranslateAssistContentResultUMA(
+            @TranslateAssistContentResult int result) {
+        RecordHistogram.recordEnumeratedHistogram("Translate.TranslateAssistContentResult", result,
+                TranslateAssistContentResult.NUM_ENTRIES);
+    }
+
+    /**
+     * Returns a StructuredData string populated with translate information. This is used to inform
+     * the Assistant of the current page's translate state.
+     * @param tab The tab to create translate data for.
+     * @param isInOverviewMode Whether the ChromeActivity is in overview mode.
+     * @return A JSON string of translate data to be surfaced to the Assistant via
+     *         AssistContent#setStructuredData. Returns null if there was an issue creating the
+     *         StructuredData or if the feature is disabled.
+     */
+    public static @Nullable String getTranslateDataForTab(
+            @Nullable Tab tab, boolean isInOverviewMode) {
+        if (!FeatureList.isInitialized()
+                || !ChromeFeatureList.isEnabled(ChromeFeatureList.TRANSLATE_ASSIST_CONTENT)) {
+            recordTranslateAssistContentResultUMA(TranslateAssistContentResult.FEATURE_DISABLED);
+            return null;
+        } else if (isInOverviewMode) {
+            recordTranslateAssistContentResultUMA(TranslateAssistContentResult.OVERVIEW_MODE);
+            return null;
+        } else if (tab == null) {
+            recordTranslateAssistContentResultUMA(TranslateAssistContentResult.TAB_WAS_NULL);
+            return null;
+        } else if (tab.isIncognito()) {
+            recordTranslateAssistContentResultUMA(TranslateAssistContentResult.INCOGNITO_TAB);
+            return null;
+        }
+
+        try {
+            JSONObject structuredData =
+                    new JSONObject().put(TYPE_KEY, TYPE_VALUE).put(URL_KEY, tab.getUrl().getSpec());
+            if (!TranslateBridge.canManuallyTranslate(tab, /*menuLogging=*/false)) {
+                recordTranslateAssistContentResultUMA(
+                        TranslateAssistContentResult.CANNOT_TRANSLATE_TAB);
+                return structuredData.toString();
+            }
+
+            String originalLanguageCode = TranslateBridge.getOriginalLanguage(tab);
+            if (TextUtils.isEmpty(originalLanguageCode)) {
+                recordTranslateAssistContentResultUMA(
+                        TranslateAssistContentResult.MISSING_ORIGINAL_LANGUAGE);
+                return structuredData.toString();
+            }
+            String currentLanguageCode = TranslateBridge.getCurrentLanguage(tab);
+            if (TextUtils.isEmpty(currentLanguageCode)) {
+                recordTranslateAssistContentResultUMA(
+                        TranslateAssistContentResult.MISSING_CURRENT_LANGUAGE);
+                return structuredData.toString();
+            }
+            // The target language is not necessary for Assistant to decide whether to show the
+            // translate UI.
+            String targetLanguageCode = TranslateBridge.getTargetLanguage();
+
+            structuredData.put(IN_LANGUAGE_KEY, currentLanguageCode);
+            if (currentLanguageCode.equals(originalLanguageCode)) {
+                if (!TextUtils.isEmpty(targetLanguageCode)) {
+                    structuredData.put(WORK_TRANSLATION_KEY,
+                            new JSONObject().put(IN_LANGUAGE_KEY, targetLanguageCode));
+                }
+                recordTranslateAssistContentResultUMA(
+                        TranslateAssistContentResult.TRANSLATABLE_PAGE);
+            } else {
+                structuredData.put(TRANSLATION_OF_WORK_KEY,
+                        new JSONObject().put(IN_LANGUAGE_KEY, originalLanguageCode));
+                recordTranslateAssistContentResultUMA(TranslateAssistContentResult.TRANSLATED_PAGE);
+            }
+            return structuredData.toString();
+        } catch (JSONException e) {
+            recordTranslateAssistContentResultUMA(TranslateAssistContentResult.JSON_EXCEPTION);
+            return null;
+        }
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/translate/TranslateBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/translate/TranslateBridge.java
index 897c630..356266f4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/translate/TranslateBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/translate/TranslateBridge.java
@@ -59,6 +59,22 @@
     }
 
     /**
+     * @return The original language code of the given tab. Empty string if no language was detected
+     *         yet.
+     */
+    public static String getOriginalLanguage(Tab tab) {
+        return TranslateBridgeJni.get().getOriginalLanguage(tab.getWebContents());
+    }
+
+    /**
+     * @return The current language code of the given tab. Empty string if no language was detected
+     *         yet.
+     */
+    public static String getCurrentLanguage(Tab tab) {
+        return TranslateBridgeJni.get().getCurrentLanguage(tab.getWebContents());
+    }
+
+    /**
      * @return The best target language based on what the Translate Service knows about the user.
      */
     public static String getTargetLanguage() {
@@ -205,6 +221,8 @@
         boolean canManuallyTranslate(WebContents webContents, boolean menuLogging);
         boolean shouldShowManualTranslateIPH(WebContents webContents);
         void setPredefinedTargetLanguage(WebContents webContents, String targetLanguage);
+        String getOriginalLanguage(WebContents webContents);
+        String getCurrentLanguage(WebContents webContents);
         String getTargetLanguage();
         boolean isBlockedLanguage(String language);
         void getModelLanguages(LinkedHashSet<String> set);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/translate/TranslateAssistContentTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/translate/TranslateAssistContentTest.java
new file mode 100644
index 0000000..7d347fc
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/translate/TranslateAssistContentTest.java
@@ -0,0 +1,194 @@
+// 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.chrome.browser.translate;
+
+import androidx.test.filters.MediumTest;
+
+import org.hamcrest.Matchers;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.Restriction;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.flags.ChromeSwitches;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.util.TranslateUtil;
+import org.chromium.chrome.test.util.browser.Features;
+import org.chromium.content_public.browser.test.util.Criteria;
+import org.chromium.content_public.browser.test.util.CriteriaHelper;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Tests for the translate info included in onProvideAssistContent.
+ */
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
+public class TranslateAssistContentTest {
+    @Rule
+    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+
+    private static final String TRANSLATE_PAGE = "/chrome/test/data/translate/fr_test.html";
+    private static final String NON_TRANSLATE_PAGE = "/chrome/test/data/android/test.html";
+
+    /**
+     * Returns true if a test that requires internet access should be skipped due to an
+     * out-of-process NetworkService. When the NetworkService is run out-of-process, a fake DNS
+     * resolver is used that will fail to resolve any non-local names. crbug.com/1134812 is tracking
+     * the changes to make the translate service mockable and remove the internet requirement.
+     */
+    private boolean shouldSkipDueToNetworkService() {
+        return !ChromeFeatureList.isEnabled("NetworkServiceInProcess");
+    }
+
+    @Before
+    public void setUp() {
+        mActivityTestRule.startMainActivityOnBlankPage();
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> TranslateBridge.setIgnoreMissingKeyForTesting(true));
+    }
+
+    @Test
+    @MediumTest
+    @Restriction(Restriction.RESTRICTION_TYPE_INTERNET)
+    @Features.DisableFeatures({ChromeFeatureList.TRANSLATE_ASSIST_CONTENT})
+    public void testAssistContentDisabled() throws TimeoutException, ExecutionException {
+        if (shouldSkipDueToNetworkService()) return;
+        // Load a page that triggers the translate recommendation.
+        final String url = mActivityTestRule.getTestServer().getURL(TRANSLATE_PAGE);
+        mActivityTestRule.loadUrl(url);
+        TranslateUtil.waitUntilTranslatable(mActivityTestRule.getActivity().getActivityTab());
+
+        String structuredData = TestThreadUtils.runOnUiThreadBlocking(
+                ()
+                        -> TranslateAssistContent.getTranslateDataForTab(
+                                mActivityTestRule.getActivity().getActivityTab(),
+                                /*isInOverviewMode=*/false));
+        Assert.assertNull(structuredData);
+    }
+
+    @Test
+    @MediumTest
+    @Restriction(Restriction.RESTRICTION_TYPE_INTERNET)
+    @Features.EnableFeatures({ChromeFeatureList.TRANSLATE_ASSIST_CONTENT})
+    public void testAssistContentTranslatablePage()
+            throws TimeoutException, ExecutionException, JSONException {
+        if (shouldSkipDueToNetworkService()) return;
+        // Load a page that triggers the translate recommendation.
+        final String url = mActivityTestRule.getTestServer().getURL(TRANSLATE_PAGE);
+        mActivityTestRule.loadUrl(url);
+        TranslateUtil.waitUntilTranslatable(mActivityTestRule.getActivity().getActivityTab());
+
+        String structuredData = TestThreadUtils.runOnUiThreadBlocking(
+                ()
+                        -> TranslateAssistContent.getTranslateDataForTab(
+                                mActivityTestRule.getActivity().getActivityTab(),
+                                /*isInOverviewMode=*/false));
+
+        JSONObject parsed = new JSONObject(structuredData);
+        Assert.assertEquals(TranslateAssistContent.TYPE_VALUE,
+                parsed.getString(TranslateAssistContent.TYPE_KEY));
+        Assert.assertEquals(url, parsed.getString(TranslateAssistContent.URL_KEY));
+        Assert.assertEquals("fr", parsed.getString(TranslateAssistContent.IN_LANGUAGE_KEY));
+        Assert.assertEquals("en",
+                parsed.getJSONObject(TranslateAssistContent.WORK_TRANSLATION_KEY)
+                        .getString(TranslateAssistContent.IN_LANGUAGE_KEY));
+    }
+
+    @Test
+    @MediumTest
+    @Restriction(Restriction.RESTRICTION_TYPE_INTERNET)
+    @Features.EnableFeatures({ChromeFeatureList.TRANSLATE_ASSIST_CONTENT})
+    public void testAssistContentTranslatedPage()
+            throws TimeoutException, ExecutionException, JSONException {
+        if (shouldSkipDueToNetworkService()) return;
+        // Load a page that triggers the translate recommendation.
+        final String url = mActivityTestRule.getTestServer().getURL(TRANSLATE_PAGE);
+        mActivityTestRule.loadUrl(url);
+        TranslateUtil.waitUntilTranslatable(mActivityTestRule.getActivity().getActivityTab());
+        TestThreadUtils.runOnUiThreadBlocking(
+                ()
+                        -> TranslateBridge.translateTabWhenReady(
+                                mActivityTestRule.getActivity().getActivityTab()));
+
+        // Can't wait on the Translate infobar state here because the target language tab is
+        // selected before the translation is complete. Wait for the language to change instead.
+        CriteriaHelper.pollUiThread(() -> {
+            Criteria.checkThat(TranslateBridge.getCurrentLanguage(
+                                       mActivityTestRule.getActivity().getActivityTab()),
+                    Matchers.is("en"));
+        });
+
+        String structuredData = TestThreadUtils.runOnUiThreadBlocking(
+                ()
+                        -> TranslateAssistContent.getTranslateDataForTab(
+                                mActivityTestRule.getActivity().getActivityTab(),
+                                /*isInOverviewMode=*/false));
+
+        JSONObject parsed = new JSONObject(structuredData);
+        Assert.assertEquals(TranslateAssistContent.TYPE_VALUE,
+                parsed.getString(TranslateAssistContent.TYPE_KEY));
+        Assert.assertEquals(url, parsed.getString(TranslateAssistContent.URL_KEY));
+        Assert.assertEquals("en", parsed.getString(TranslateAssistContent.IN_LANGUAGE_KEY));
+        Assert.assertEquals("fr",
+                parsed.getJSONObject(TranslateAssistContent.TRANSLATION_OF_WORK_KEY)
+                        .getString(TranslateAssistContent.IN_LANGUAGE_KEY));
+    }
+
+    @Test
+    @MediumTest
+    @Restriction(Restriction.RESTRICTION_TYPE_INTERNET)
+    @Features.EnableFeatures({ChromeFeatureList.TRANSLATE_ASSIST_CONTENT})
+    public void testAssistContentNonTranslatePage()
+            throws TimeoutException, ExecutionException, JSONException {
+        if (shouldSkipDueToNetworkService()) return;
+        // Load a page that can't be translated.
+        final String url = mActivityTestRule.getTestServer().getURL(NON_TRANSLATE_PAGE);
+        mActivityTestRule.loadUrl(url);
+
+        String structuredData = TestThreadUtils.runOnUiThreadBlocking(
+                ()
+                        -> TranslateAssistContent.getTranslateDataForTab(
+                                mActivityTestRule.getActivity().getActivityTab(),
+                                /*isInOverviewMode=*/false));
+
+        JSONObject parsed = new JSONObject(structuredData);
+        Assert.assertEquals(TranslateAssistContent.TYPE_VALUE,
+                parsed.getString(TranslateAssistContent.TYPE_KEY));
+        Assert.assertEquals(url, parsed.getString(TranslateAssistContent.URL_KEY));
+        Assert.assertFalse(parsed.has(TranslateAssistContent.IN_LANGUAGE_KEY));
+        Assert.assertFalse(parsed.has(TranslateAssistContent.TRANSLATION_OF_WORK_KEY));
+        Assert.assertFalse(parsed.has(TranslateAssistContent.WORK_TRANSLATION_KEY));
+    }
+
+    @Test
+    @MediumTest
+    @Restriction(Restriction.RESTRICTION_TYPE_INTERNET)
+    @Features.EnableFeatures({ChromeFeatureList.TRANSLATE_ASSIST_CONTENT})
+    public void testAssistContentOverviewMode() throws TimeoutException, ExecutionException {
+        if (shouldSkipDueToNetworkService()) return;
+        // Load a page that triggers the translate recommendation.
+        final String url = mActivityTestRule.getTestServer().getURL(TRANSLATE_PAGE);
+        mActivityTestRule.loadUrl(url);
+        TranslateUtil.waitUntilTranslatable(mActivityTestRule.getActivity().getActivityTab());
+
+        // Pretend we're in overview mode.
+        String structuredData = TestThreadUtils.runOnUiThreadBlocking(
+                ()
+                        -> TranslateAssistContent.getTranslateDataForTab(
+                                mActivityTestRule.getActivity().getActivityTab(),
+                                /*isInOverviewMode=*/true));
+        Assert.assertNull(structuredData);
+    }
+}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/translate/TranslateIntentTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/translate/TranslateIntentTest.java
index afc6b8d..77b29215 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/translate/TranslateIntentTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/translate/TranslateIntentTest.java
@@ -12,7 +12,6 @@
 import androidx.test.filters.MediumTest;
 import androidx.test.filters.SmallTest;
 
-import org.hamcrest.Matchers;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -28,13 +27,10 @@
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.infobar.InfoBarContainer;
-import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.TranslateUtil;
 import org.chromium.chrome.test.util.browser.Features;
-import org.chromium.content_public.browser.test.util.Criteria;
-import org.chromium.content_public.browser.test.util.CriteriaHelper;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.List;
@@ -57,18 +53,6 @@
     private InfoBarContainer mInfoBarContainer;
 
     /**
-     * Wait until the activity tab is translatable. This is useful in cases where we can't wait on
-     * the infobar.
-     */
-    private void waitUntilTranslatable() {
-        CriteriaHelper.pollUiThread(() -> {
-            Tab tab = mActivityTestRule.getActivity().getActivityTab();
-            Criteria.checkThat(tab, Matchers.notNullValue());
-            Criteria.checkThat(TranslateBridge.canManuallyTranslate(tab, false), Matchers.is(true));
-        });
-    }
-
-    /**
      * Returns true if a test that requires internet access should be skipped due to an
      * out-of-process NetworkService. When the NetworkService is run out-of-process, a fake DNS
      * resolver is used that will fail to resolve any non-local names. crbug.com/1134812 is tracking
@@ -151,7 +135,7 @@
         // Load a page that doesn't trigger the translate recommendation.
         mActivityTestRule.loadUrl(url);
 
-        waitUntilTranslatable();
+        TranslateUtil.waitUntilTranslatable(mActivityTestRule.getActivity().getActivityTab());
         Assert.assertTrue(mInfoBarContainer.getInfoBarsForTesting().isEmpty());
 
         sendTranslateIntent(url, null);
@@ -171,7 +155,7 @@
         // Load a page that doesn't trigger the translate recommendation.
         mActivityTestRule.loadUrl(url);
 
-        waitUntilTranslatable();
+        TranslateUtil.waitUntilTranslatable(mActivityTestRule.getActivity().getActivityTab());
         Assert.assertTrue(mInfoBarContainer.getInfoBarsForTesting().isEmpty());
 
         List<String> acceptCodes =
@@ -199,7 +183,7 @@
         // Load a page that doesn't trigger the translate recommendation.
         mActivityTestRule.loadUrl(url);
 
-        waitUntilTranslatable();
+        TranslateUtil.waitUntilTranslatable(mActivityTestRule.getActivity().getActivityTab());
         Assert.assertTrue(mInfoBarContainer.getInfoBarsForTesting().isEmpty());
 
         sendTranslateIntent(url, "unsupported");
@@ -322,6 +306,6 @@
         intent.setPackage(context.getPackageName());
         final ComponentName component = intent.resolveActivity(pm);
         Assert.assertEquals(
-                component.getClassName(), TranslateIntentHandler.COMPONENT_TRANSLATE_DISPATCHER);
+                TranslateIntentHandler.COMPONENT_TRANSLATE_DISPATCHER, component.getClassName());
     }
 }
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index ebb2df0..9cb42e7 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -954,17 +954,6 @@
     Please retry. If you see this error again please contact your support representative.
   </message>
 
-  <!-- Strings for parental handoff login screen -->
-  <message name="IDS_LOGIN_PARENTAL_HANDOFF_SCREEN_TITLE" desc="Title of screen which tells user that the parent can handoff the device to supervised user.">
-    Now it's <ph name="SUPERVISED_USER_NAME">$1<ex>Child 1</ex></ph>'s turn
-  </message>
-  <message name="IDS_LOGIN_PARENTAL_HANDOFF_SCREEN_SUBTITLE" desc="Subtitle of screen which tells user that the parent can handoff the device to supervised user.">
-    You can hand this Chromebook to <ph name="SUPERVISED_USER_NAME">$1<ex>Child 1</ex></ph>. Setup is almost done, then it's time to explore.
-  </message>
-  <message name="IDS_LOGIN_PARENTAL_HANDOFF_SCREEN_NEXT_BUTTON" desc="Next button of parental handoff screen">
-    Next
-  </message>
-
   <!-- Strings for family link notice screen -->
   <message name="IDS_LOGIN_FAMILY_LINK_NOTICE_SCREEN_TITLE" desc="Title of the screen which tells user they can add parental control later.">
     Add parental controls after setup
diff --git a/chrome/app/chromeos_strings_grdp/IDS_LOGIN_PARENTAL_HANDOFF_SCREEN_NEXT_BUTTON.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_LOGIN_PARENTAL_HANDOFF_SCREEN_NEXT_BUTTON.png.sha1
deleted file mode 100644
index 0425e08..0000000
--- a/chrome/app/chromeos_strings_grdp/IDS_LOGIN_PARENTAL_HANDOFF_SCREEN_NEXT_BUTTON.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-5110db314fcfbbc1194cd27d5336c88de1790aa2
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_LOGIN_PARENTAL_HANDOFF_SCREEN_SUBTITLE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_LOGIN_PARENTAL_HANDOFF_SCREEN_SUBTITLE.png.sha1
deleted file mode 100644
index 79b9304b..0000000
--- a/chrome/app/chromeos_strings_grdp/IDS_LOGIN_PARENTAL_HANDOFF_SCREEN_SUBTITLE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-72c5848be1b964cd6e77471c295e4ad19888692e
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_LOGIN_PARENTAL_HANDOFF_SCREEN_TITLE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_LOGIN_PARENTAL_HANDOFF_SCREEN_TITLE.png.sha1
deleted file mode 100644
index f1578c3d..0000000
--- a/chrome/app/chromeos_strings_grdp/IDS_LOGIN_PARENTAL_HANDOFF_SCREEN_TITLE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-f7c276d755adae2584036084356c2521de1602d9
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index 8704538..36a994d 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -286,11 +286,11 @@
   <message name="IDS_OS_SETTINGS_LANGUAGES_CHANGE_DEVICE_LANGUAGE_CONFIRM_BUTTON_LABEL" desc="Label for the button where users can confirm the selected language and restart the device.">
     Confirm and restart
   </message>
-  <message name="IDS_OS_SETTINGS_LANGUAGES_LANGUAGES_PREFERENCE_TITLE" desc="Title of section that lets users choose the language for apps and websites. The web content languages will be served in the order of the list.">
-    Languages for apps and websites
+  <message name="IDS_OS_SETTINGS_LANGUAGES_LANGUAGES_PREFERENCE_TITLE" desc="Title of section that lets users choose the language for web content. The web content languages will be served in the order of the list.">
+    Web content languages
   </message>
-  <message name="IDS_OS_SETTINGS_LANGUAGES_LANGUAGES_PREFERENCE_DESCRIPTION" desc="Description for section that lets users choose the language for apps and websites. The web content languages will be served in the order of the list.">
-    Apps and websites that are available in multiple languages will use the first supported language from this list. These preferences are synced with your browser settings. <ph name="BEGIN_LINK_LEARN_MORE">&lt;a target="_blank" href="$1"&gt;</ph>Learn more<ph name="END_LINK_LEARN_MORE">&lt;/a&gt;</ph>
+  <message name="IDS_OS_SETTINGS_LANGUAGES_LANGUAGES_PREFERENCE_DESCRIPTION" desc="Description for section that lets users choose the language for web content. The web content languages will be served in the order of the list.">
+    Web content available in multiple languages will use the first supported language from this list. These preferences are synced with your browser settings. <ph name="BEGIN_LINK_LEARN_MORE">&lt;a target="_blank" href="$1"&gt;</ph>Learn more<ph name="END_LINK_LEARN_MORE">&lt;/a&gt;</ph>
   </message>
   <message name="IDS_OS_SETTINGS_LANGUAGES_TRANSLATE_TARGET_LABEL" desc="The label of the toggle that enables the prompt to translate a page to users.">
     Language used when translating pages
diff --git a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_LANGUAGES_LANGUAGES_PREFERENCE_DESCRIPTION.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_LANGUAGES_LANGUAGES_PREFERENCE_DESCRIPTION.png.sha1
index 88add76..4c2e9ca3 100644
--- a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_LANGUAGES_LANGUAGES_PREFERENCE_DESCRIPTION.png.sha1
+++ b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_LANGUAGES_LANGUAGES_PREFERENCE_DESCRIPTION.png.sha1
@@ -1 +1 @@
-300b1de537007bb5a5e650b54417374d864bd258
\ No newline at end of file
+ece2fcaa558dc550264205b05352ea7e8d2dcd60
diff --git a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_LANGUAGES_LANGUAGES_PREFERENCE_TITLE.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_LANGUAGES_LANGUAGES_PREFERENCE_TITLE.png.sha1
index 053d45f..4c2e9ca3 100644
--- a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_LANGUAGES_LANGUAGES_PREFERENCE_TITLE.png.sha1
+++ b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_LANGUAGES_LANGUAGES_PREFERENCE_TITLE.png.sha1
@@ -1 +1 @@
-d76b317a83c1ffbcc7d96e6340c8d49ab5d375b0
\ No newline at end of file
+ece2fcaa558dc550264205b05352ea7e8d2dcd60
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 13b48dd..285e329e65 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -4993,11 +4993,6 @@
      flag_descriptions::kEnableQuickAnswersDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(chromeos::features::kQuickAnswers)},
 
-    {"enable-quick-answers-rich-ui",
-     flag_descriptions::kEnableQuickAnswersRichUiName,
-     flag_descriptions::kEnableQuickAnswersRichUiDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(chromeos::features::kQuickAnswersRichUi)},
-
     {"enable-quick-answers-text-annotator",
      flag_descriptions::kEnableQuickAnswersTextAnnotatorName,
      flag_descriptions::kEnableQuickAnswersTextAnnotatorDescription, kOsCrOS,
diff --git a/chrome/browser/apps/platform_apps/api/first_run_private/first_run_private_api.cc b/chrome/browser/apps/platform_apps/api/first_run_private/first_run_private_api.cc
index c2c16056..419c486b 100644
--- a/chrome/browser/apps/platform_apps/api/first_run_private/first_run_private_api.cc
+++ b/chrome/browser/apps/platform_apps/api/first_run_private/first_run_private_api.cc
@@ -60,7 +60,8 @@
   const std::string& app_locale = g_browser_process->GetApplicationLocale();
   webui::SetLoadTimeDataDefaults(app_locale, localized_strings.get());
 
-  return RespondNow(OneArgument(std::move(localized_strings)));
+  return RespondNow(OneArgument(
+      base::Value::FromUniquePtrValue(std::move(localized_strings))));
 }
 
 ExtensionFunction::ResponseAction FirstRunPrivateLaunchTutorialFunction::Run() {
diff --git a/chrome/browser/apps/platform_apps/api/media_galleries/media_galleries_api.cc b/chrome/browser/apps/platform_apps/api/media_galleries/media_galleries_api.cc
index 63596f2..fe056f5 100644
--- a/chrome/browser/apps/platform_apps/api/media_galleries/media_galleries_api.cc
+++ b/chrome/browser/apps/platform_apps/api/media_galleries/media_galleries_api.cc
@@ -476,7 +476,7 @@
   }
 
   // The custom JS binding will use this list to create DOMFileSystem objects.
-  Respond(OneArgument(std::move(list)));
+  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(list))));
 }
 
 void MediaGalleriesGetMediaFileSystemsFunction::ShowDialog() {
@@ -600,7 +600,7 @@
   std::unique_ptr<base::DictionaryValue> results(new base::DictionaryValue);
   results->SetWithoutPathExpansion("mediaFileSystems", std::move(list));
   results->SetKey("selectedFileSystemIndex", base::Value(index));
-  Respond(OneArgument(std::move(results)));
+  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(results))));
 }
 
 void MediaGalleriesAddUserSelectedFolderFunction::
@@ -681,7 +681,8 @@
     std::unique_ptr<base::DictionaryValue> result_dictionary(
         new base::DictionaryValue);
     result_dictionary->Set(kMetadataKey, metadata.ToValue());
-    Respond(OneArgument(std::move(result_dictionary)));
+    Respond(OneArgument(
+        base::Value::FromUniquePtrValue(std::move(result_dictionary))));
     return;
   }
 
@@ -724,7 +725,8 @@
                          SerializeMediaMetadata(std::move(metadata)));
 
   if (attached_images->empty()) {
-    Respond(OneArgument(std::move(result_dictionary)));
+    Respond(OneArgument(
+        base::Value::FromUniquePtrValue(std::move(result_dictionary))));
     return;
   }
 
@@ -797,7 +799,8 @@
 
   // All Blobs have been constructed. The renderer will take ownership.
   SetTransferredBlobUUIDs(*blob_uuids);
-  Respond(OneArgument(std::move(result_dictionary)));
+  Respond(OneArgument(
+      base::Value::FromUniquePtrValue(std::move(result_dictionary))));
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/apps/platform_apps/api/sync_file_system/sync_file_system_api.cc b/chrome/browser/apps/platform_apps/api/sync_file_system/sync_file_system_api.cc
index d0bd854..f62d585 100644
--- a/chrome/browser/apps/platform_apps/api/sync_file_system/sync_file_system_api.cc
+++ b/chrome/browser/apps/platform_apps/api/sync_file_system/sync_file_system_api.cc
@@ -191,7 +191,7 @@
   std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
   dict->SetString("name", file_system_name);
   dict->SetString("root", root_url.spec());
-  Respond(OneArgument(std::move(dict)));
+  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(dict))));
 }
 
 ExtensionFunction::ResponseAction SyncFileSystemGetFileStatusFunction::Run() {
@@ -312,7 +312,8 @@
 
     status_array->Append(std::move(dict));
   }
-  Respond(OneArgument(std::move(status_array)));
+  Respond(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(status_array))));
 }
 
 ExtensionFunction::ResponseAction
diff --git a/chrome/browser/autocomplete/chrome_autocomplete_provider_client.cc b/chrome/browser/autocomplete/chrome_autocomplete_provider_client.cc
index 3548ec3..aa4c7807 100644
--- a/chrome/browser/autocomplete/chrome_autocomplete_provider_client.cc
+++ b/chrome/browser/autocomplete/chrome_autocomplete_provider_client.cc
@@ -25,6 +25,8 @@
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/history/history_service_factory.h"
 #include "chrome/browser/history/top_sites_factory.h"
+#include "chrome/browser/prefetch/search_prefetch/search_prefetch_service.h"
+#include "chrome/browser/prefetch/search_prefetch/search_prefetch_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_key.h"
 #include "chrome/browser/query_tiles/tile_service_factory.h"
@@ -461,6 +463,16 @@
 #endif
 }
 
+void ChromeAutocompleteProviderClient::OnAutocompleteControllerResultReady(
+    AutocompleteController* controller) {
+  auto* search_prefetch_service =
+      SearchPrefetchServiceFactory::GetForProfile(profile_);
+
+  // Prefetches result pages that the search provider marked as prefetchable.
+  if (search_prefetch_service)
+    search_prefetch_service->OnResultChanged(controller);
+}
+
 bool ChromeAutocompleteProviderClient::StrippedURLsAreEqual(
     const GURL& url1,
     const GURL& url2,
diff --git a/chrome/browser/autocomplete/chrome_autocomplete_provider_client.h b/chrome/browser/autocomplete/chrome_autocomplete_provider_client.h
index ede81a6..85b4082 100644
--- a/chrome/browser/autocomplete/chrome_autocomplete_provider_client.h
+++ b/chrome/browser/autocomplete/chrome_autocomplete_provider_client.h
@@ -84,6 +84,8 @@
   bool IsTabOpenWithURL(const GURL& url,
                         const AutocompleteInput* input) override;
   bool IsBrowserUpdateAvailable() const override;
+  void OnAutocompleteControllerResultReady(
+      AutocompleteController* controller) override;
 
   // For testing.
   void set_storage_partition(content::StoragePartition* storage_partition) {
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 593f825..70c05bf 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -1745,8 +1745,6 @@
     "login/screens/network_screen.h",
     "login/screens/packaged_license_screen.cc",
     "login/screens/packaged_license_screen.h",
-    "login/screens/parental_handoff_screen.cc",
-    "login/screens/parental_handoff_screen.h",
     "login/screens/pin_setup_screen.cc",
     "login/screens/pin_setup_screen.h",
     "login/screens/recommend_apps/fake_recommend_apps_fetcher.cc",
diff --git a/chrome/browser/chromeos/accessibility/accessibility_manager.cc b/chrome/browser/chromeos/accessibility/accessibility_manager.cc
index 79adcbd6..9a49282 100644
--- a/chrome/browser/chromeos/accessibility/accessibility_manager.cc
+++ b/chrome/browser/chromeos/accessibility/accessibility_manager.cc
@@ -531,11 +531,11 @@
 }
 
 void AccessibilityManager::OnTwoFingerTouchStart() {
-  if (!profile())
+  if (!profile_)
     return;
 
   extensions::EventRouter* event_router =
-      extensions::EventRouter::Get(profile());
+      extensions::EventRouter::Get(profile_);
 
   auto event_args = std::make_unique<base::ListValue>();
   auto event = std::make_unique<extensions::Event>(
@@ -546,11 +546,11 @@
 }
 
 void AccessibilityManager::OnTwoFingerTouchStop() {
-  if (!profile())
+  if (!profile_)
     return;
 
   extensions::EventRouter* event_router =
-      extensions::EventRouter::Get(profile());
+      extensions::EventRouter::Get(profile_);
 
   auto event_args = std::make_unique<base::ListValue>();
   auto event = std::make_unique<extensions::Event>(
@@ -599,7 +599,7 @@
     ax::mojom::Gesture gesture,
     gfx::PointF location) {
   extensions::EventRouter* event_router =
-      extensions::EventRouter::Get(profile());
+      extensions::EventRouter::Get(profile_);
 
   std::unique_ptr<base::ListValue> event_args =
       std::make_unique<base::ListValue>();
@@ -688,6 +688,35 @@
       extension_misc::kAccessibilityCommonExtensionId, std::move(event));
 }
 
+void AccessibilityManager::MagnifierBoundsChanged(
+    const gfx::Rect& bounds_in_screen) {
+  if (!profile_)
+    return;
+
+  extensions::EventRouter* event_router =
+      extensions::EventRouter::Get(profile_);
+
+  auto magnifier_bounds =
+      std::make_unique<extensions::api::accessibility_private::ScreenRect>();
+  magnifier_bounds->left = bounds_in_screen.x();
+  magnifier_bounds->top = bounds_in_screen.y();
+  magnifier_bounds->width = bounds_in_screen.width();
+  magnifier_bounds->height = bounds_in_screen.height();
+
+  auto event_args =
+      extensions::api::accessibility_private::OnMagnifierBoundsChanged::Create(
+          *magnifier_bounds.get());
+
+  auto event = std::make_unique<extensions::Event>(
+      extensions::events::ACCESSIBILITY_PRIVATE_ON_MAGNIFIER_BOUNDS_CHANGED,
+      extensions::api::accessibility_private::OnMagnifierBoundsChanged::
+          kEventName,
+      std::move(event_args));
+
+  event_router->DispatchEventWithLazyListener(
+      extension_misc::kAccessibilityCommonExtensionId, std::move(event));
+}
+
 void AccessibilityManager::EnableVirtualKeyboard(bool enabled) {
   if (!profile_)
     return;
diff --git a/chrome/browser/chromeos/accessibility/accessibility_manager.h b/chrome/browser/chromeos/accessibility/accessibility_manager.h
index 45432f3..e297382 100644
--- a/chrome/browser/chromeos/accessibility/accessibility_manager.h
+++ b/chrome/browser/chromeos/accessibility/accessibility_manager.h
@@ -158,6 +158,10 @@
   // ancestor to the point in the screen, as given in screen coordinates.
   void RequestAutoclickScrollableBoundsForPoint(gfx::Point& point_in_screen);
 
+  // Dispatches magnifier bounds update to Magnifier (through Accessibility
+  // Common extension).
+  void MagnifierBoundsChanged(const gfx::Rect& bounds_in_screen);
+
   // Enables or disables the virtual keyboard.
   void EnableVirtualKeyboard(bool enabled);
   // Returns true if the virtual keyboard is enabled, otherwise false.
diff --git a/chrome/browser/chromeos/accessibility/spoken_feedback_app_list_browsertest.cc b/chrome/browser/chromeos/accessibility/spoken_feedback_app_list_browsertest.cc
index 74183aa..d944ee5 100644
--- a/chrome/browser/chromeos/accessibility/spoken_feedback_app_list_browsertest.cc
+++ b/chrome/browser/chromeos/accessibility/spoken_feedback_app_list_browsertest.cc
@@ -14,6 +14,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/app_list/app_list_client_impl.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_window.h"
@@ -23,6 +24,7 @@
 #include "chromeos/constants/chromeos_switches.h"
 #include "components/user_manager/user_names.h"
 #include "content/public/test/browser_test.h"
+#include "extensions/browser/browsertest_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/ui_base_features.h"
@@ -105,6 +107,12 @@
     }
   }
 
+  void ReadWindowTitle() {
+    extensions::browsertest_util::ExecuteScriptInBackgroundPageNoWait(
+        browser()->profile(), extension_misc::kChromeVoxExtensionId,
+        "CommandHandler.onCommand('readCurrentTitle');");
+  }
+
  private:
   ash::test::AppListTestModel* app_list_test_model_ = nullptr;
   ash::SearchModel* search_model = nullptr;
@@ -543,4 +551,40 @@
   sm_.Replay();
 }
 
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackAppListTest,
+                       LauncherWindowTitleAnnouncement) {
+  EnableChromeVox();
+
+  sm_.Call(
+      [this]() { EXPECT_TRUE(PerformAcceleratorAction(ash::FOCUS_SHELF)); });
+  sm_.ExpectSpeechPattern("Launcher");
+  sm_.ExpectSpeech("Button");
+  sm_.ExpectSpeech("Shelf");
+  sm_.ExpectSpeech("Tool bar");
+  // Press space on the launcher button in shelf, this opens peeking
+  // launcher.
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_SPACE); });
+  sm_.ExpectSpeechPattern("Search your device,*");
+  sm_.ExpectSpeech("Edit text");
+  sm_.ExpectSpeech("Launcher, partial view");
+  sm_.Call([this]() { ReadWindowTitle(); });
+  sm_.ExpectSpeech("Launcher, partial view");
+  // Move focus to expand all apps button.
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_UP); });
+  sm_.ExpectSpeech("Expand to all apps");
+  // Press space on expand arrow to go to fullscreen launcher.
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_SPACE); });
+  sm_.ExpectSpeechPattern("Search your device,*");
+  sm_.ExpectSpeech("Edit text");
+  sm_.ExpectSpeech("Launcher, all apps");
+  sm_.Call([this]() { ReadWindowTitle(); });
+  sm_.ExpectSpeech("Launcher, all apps");
+  // Activate the search widget.
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_A); });
+  sm_.ExpectSpeechPattern("Displaying *");
+  sm_.Call([this]() { ReadWindowTitle(); });
+  sm_.ExpectSpeech("Launcher");
+  sm_.Replay();
+}
+
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
index f15e8c9..eedc38b1 100644
--- a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
+++ b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
@@ -1158,7 +1158,8 @@
       result->SetString("userImage", user_image);
     }
   }
-  return RespondNow(OneArgument(std::move(result)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -1282,7 +1283,8 @@
   std::unique_ptr<base::DictionaryValue> return_value(
       new base::DictionaryValue);
   return_value->Set("extensions", std::move(extensions_values));
-  return RespondNow(OneArgument(std::move(return_value)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(return_value))));
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -1474,7 +1476,8 @@
   auto values = std::make_unique<base::ListValue>();
   for (auto* notification : notification_set)
     values->Append(MakeDictionaryFromNotification(*notification));
-  return RespondNow(OneArgument(std::move(values)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(values))));
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -1685,7 +1688,8 @@
     app_value->SetKey("launchable", base::Value(app_info->launchable));
   }
 
-  return RespondNow(OneArgument(std::move(app_value)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(app_value))));
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -1731,7 +1735,8 @@
     package_value->SetKey("vpnProvider",
                           base::Value(package_info->vpn_provider));
   }
-  return RespondNow(OneArgument(std::move(package_value)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(package_value))));
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -2328,7 +2333,7 @@
 
   timeout_timer_.AbandonAndStop();
   DestroyPrintersManager();
-  Respond(OneArgument(std::move(results_)));
+  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(results_))));
 }
 
 void AutotestPrivateGetPrinterListFunction::OnEnterprisePrintersInitialized() {
@@ -3395,7 +3400,7 @@
   result->SetDoubleKey("fps", fps);
   result->SetDoubleKey("commitDeviation", commit_deviation);
   result->SetDoubleKey("renderQuality", render_quality);
-  Respond(OneArgument(std::move(result)));
+  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/chromeos/extensions/echo_private_api.cc b/chrome/browser/chromeos/extensions/echo_private_api.cc
index 2519846..90a6aa2 100644
--- a/chrome/browser/chromeos/extensions/echo_private_api.cc
+++ b/chrome/browser/chromeos/extensions/echo_private_api.cc
@@ -171,7 +171,7 @@
 
 void EchoPrivateGetOobeTimestampFunction::RespondWithResult(
     std::unique_ptr<base::Value> result) {
-  Respond(OneArgument(std::move(result)));
+  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
 }
 
 EchoPrivateGetUserConsentFunction::EchoPrivateGetUserConsentFunction()
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_drive.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_drive.cc
index 12fca20..6ba1f2f5 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_drive.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_drive.cc
@@ -627,7 +627,7 @@
       true, !is_offline_,
       FileManagerPrivateSearchDriveMetadataFunction::SearchType::kText,
       operation_start_);
-  Respond(OneArgument(std::move(result)));
+  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
 }
 
 FileManagerPrivateSearchDriveMetadataFunction::
@@ -754,7 +754,8 @@
   }
 
   UmaEmitSearchOutcome(true, !is_offline_, search_type_, operation_start_);
-  Respond(OneArgument(std::move(results_list)));
+  Respond(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(results_list))));
 }
 
 ExtensionFunction::ResponseAction
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.cc
index b1cbbea..0c7196f8 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.cc
@@ -442,7 +442,8 @@
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   auto result_value = std::make_unique<base::Value>(success);
   if (success) {
-    Respond(OneArgument(std::move(result_value)));
+    Respond(
+        OneArgument(base::Value::FromUniquePtrValue(std::move(result_value))));
   } else {
     Respond(Error(""));
   }
@@ -639,7 +640,7 @@
   sizes->SetDouble("totalSize", static_cast<double>(*total_size));
   sizes->SetDouble("remainingSize", static_cast<double>(*remaining_size));
 
-  Respond(OneArgument(std::move(sizes)));
+  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(sizes))));
 }
 
 ExtensionFunction::ResponseAction
@@ -1248,7 +1249,7 @@
     result->GetListWithoutPathExpansion(hashAndPath.hash, &list);
     list->AppendString(hashAndPath.path.value());
   }
-  Respond(OneArgument(std::move(result)));
+  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
 }
 
 FileManagerPrivateSearchFilesFunction::FileManagerPrivateSearchFilesFunction()
@@ -1310,7 +1311,7 @@
 
   auto result = std::make_unique<base::DictionaryValue>();
   result->SetKey("entries", std::move(*entries));
-  Respond(OneArgument(std::move(result)));
+  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
 }
 
 ExtensionFunction::ResponseAction
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_media_parser.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_media_parser.cc
index 2f4d05c..ff42b02 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_media_parser.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_media_parser.cc
@@ -195,7 +195,7 @@
     attached_images_list->Append(std::move(media_thumbnail_image));
   }
 
-  Respond(OneArgument(std::move(dictionary)));
+  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(dictionary))));
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_strings.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_strings.cc
index 791b8778..0f469a3 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_strings.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_strings.cc
@@ -80,7 +80,8 @@
 
   dict->SetString("UI_LOCALE", extension_l10n_util::CurrentLocaleOrDefault());
 
-  return RespondNow(OneArgument(std::move(dict)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(dict))));
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/chromeos/extensions/info_private_api.cc b/chrome/browser/chromeos/extensions/info_private_api.cc
index cf4f034..c5beb44 100644
--- a/chrome/browser/chromeos/extensions/info_private_api.cc
+++ b/chrome/browser/chromeos/extensions/info_private_api.cc
@@ -277,7 +277,8 @@
     if (value)
       result->Set(property_name, std::move(value));
   }
-  return RespondNow(OneArgument(std::move(result)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
 }
 
 std::unique_ptr<base::Value> ChromeosInfoPrivateGetFunction::GetValue(
diff --git a/chrome/browser/chromeos/extensions/input_method_api.cc b/chrome/browser/chromeos/extensions/input_method_api.cc
index bbbe7d6..858dc29 100644
--- a/chrome/browser/chromeos/extensions/input_method_api.cc
+++ b/chrome/browser/chromeos/extensions/input_method_api.cc
@@ -129,7 +129,8 @@
                      Profile::FromBrowserContext(browser_context())
                          ->GetPrefs()
                          ->GetBoolean(prefs::kLanguageImeMenuActivated));
-  return RespondNow(OneArgument(std::move(output)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(output))));
 }
 
 ExtensionFunction::ResponseAction
@@ -182,7 +183,8 @@
     val->SetString("indicator", util->GetInputMethodShortName(input_method));
     output->Append(std::move(val));
   }
-  return RespondNow(OneArgument(std::move(output)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(output))));
 }
 
 ExtensionFunction::ResponseAction
@@ -204,7 +206,8 @@
   for (auto it = words.begin(); it != words.end(); ++it) {
     output->AppendString(*it);
   }
-  return RespondNow(OneArgument(std::move(output)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(output))));
 }
 
 ExtensionFunction::ResponseAction
@@ -247,7 +250,8 @@
         InformativeError(kErrorSyncServiceNotReady, static_function_name())));
   std::unique_ptr<base::Value> ret(new base::Value(
       sync_service->GetUserSettings()->IsEncryptEverythingEnabled()));
-  return RespondNow(OneArgument(std::move(ret)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(ret))));
 }
 
 ExtensionFunction::ResponseAction
@@ -364,7 +368,8 @@
       "after", info.surrounding_text.substr(text_after_start,
                                             text_after_end - text_after_start));
 
-  return RespondNow(OneArgument(std::move(ret)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(ret))));
 }
 
 ExtensionFunction::ResponseAction InputMethodPrivateGetSettingsFunction::Run() {
@@ -520,7 +525,8 @@
   auto ret = std::make_unique<base::DictionaryValue>();
   ret->SetInteger("start", range.is_empty() ? 0 : range.start());
   ret->SetInteger("end", range.is_empty() ? 0 : range.end());
-  return RespondNow(OneArgument(std::move(ret)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(ret))));
 }
 
 ExtensionFunction::ResponseAction
@@ -544,7 +550,8 @@
   ret->SetInteger("y", rect.y());
   ret->SetInteger("width", rect.width());
   ret->SetInteger("height", rect.height());
-  return RespondNow(OneArgument(std::move(ret)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(ret))));
 }
 
 ExtensionFunction::ResponseAction
diff --git a/chrome/browser/chromeos/extensions/users_private/users_private_api.cc b/chrome/browser/chromeos/extensions/users_private/users_private_api.cc
index b47d0bb..1111596 100644
--- a/chrome/browser/chromeos/extensions/users_private/users_private_api.cc
+++ b/chrome/browser/chromeos/extensions/users_private/users_private_api.cc
@@ -304,7 +304,8 @@
   auto result = std::make_unique<base::DictionaryValue>();
   result->SetKey("isLoggedIn", base::Value(is_logged_in));
   result->SetKey("isScreenLocked", base::Value(is_screen_locked));
-  return RespondNow(OneArgument(std::move(result)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/chromeos/extensions/wallpaper_private_api.cc b/chrome/browser/chromeos/extensions/wallpaper_private_api.cc
index a2325e7..4b48005 100644
--- a/chrome/browser/chromeos/extensions/wallpaper_private_api.cc
+++ b/chrome/browser/chromeos/extensions/wallpaper_private_api.cc
@@ -267,7 +267,8 @@
   dict->SetString("currentWallpaperLayout",
                   wallpaper_api_util::GetLayoutString(info.layout));
 
-  return RespondNow(OneArgument(std::move(dict)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(dict))));
 }
 
 ExtensionFunction::ResponseAction
@@ -288,7 +289,7 @@
     // enabled by default so unless the user disables it explicitly it remains
     // enabled).
     dict->SetBoolean(kSyncThemes, true);
-    Respond(OneArgument(std::move(dict)));
+    Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(dict))));
     return;
   }
 
@@ -298,7 +299,7 @@
   if (!sync_service) {
     // Sync flag is disabled (perhaps prohibited by policy).
     dict->SetBoolean(kSyncThemes, false);
-    Respond(OneArgument(std::move(dict)));
+    Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(dict))));
     return;
   }
 
@@ -310,14 +311,14 @@
         profile->GetPrefs()->GetBoolean(
             chromeos::settings::prefs::kSyncOsWallpaper);
     dict->SetBoolean(kSyncThemes, os_wallpaper_sync_enabled);
-    Respond(OneArgument(std::move(dict)));
+    Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(dict))));
     return;
   }
 
   if (!sync_service->CanSyncFeatureStart()) {
     // Sync-the-feature is disabled.
     dict->SetBoolean(kSyncThemes, false);
-    Respond(OneArgument(std::move(dict)));
+    Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(dict))));
     return;
   }
 
@@ -327,7 +328,7 @@
     dict->SetBoolean(kSyncThemes,
                      sync_service->GetUserSettings()->GetSelectedTypes().Has(
                          syncer::UserSelectableType::kThemes));
-    Respond(OneArgument(std::move(dict)));
+    Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(dict))));
     return;
   }
 
@@ -671,7 +672,7 @@
     OnOfflineWallpaperListReturned(const std::vector<std::string>& url_list) {
   auto results = std::make_unique<base::ListValue>();
   results->AppendStrings(url_list);
-  Respond(OneArgument(std::move(results)));
+  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(results))));
 }
 
 ExtensionFunction::ResponseAction
diff --git a/chrome/browser/chromeos/login/debug_overlay_browsertest.cc b/chrome/browser/chromeos/login/debug_overlay_browsertest.cc
index 94961621..daf9d2a 100644
--- a/chrome/browser/chromeos/login/debug_overlay_browsertest.cc
+++ b/chrome/browser/chromeos/login/debug_overlay_browsertest.cc
@@ -22,8 +22,8 @@
 constexpr char kDebugOverlay[] = "debuggerOverlay";
 constexpr char kScreensPanel[] = "DebuggerPanelScreens";
 
-constexpr int kOobeScreensCount = 38;
-constexpr int kLoginScreensCount = 33;
+constexpr int kOobeScreensCount = 37;
+constexpr int kLoginScreensCount = 32;
 
 std::string ElementsInPanel(const std::string& panel) {
   return base::StrCat({"$('", panel, "').children.length"});
diff --git a/chrome/browser/chromeos/login/screens/parental_handoff_screen.cc b/chrome/browser/chromeos/login/screens/parental_handoff_screen.cc
deleted file mode 100644
index caca1165..0000000
--- a/chrome/browser/chromeos/login/screens/parental_handoff_screen.cc
+++ /dev/null
@@ -1,109 +0,0 @@
-// 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/chromeos/login/screens/parental_handoff_screen.h"
-
-#include <string>
-
-#include "base/strings/string16.h"
-#include "chrome/browser/chromeos/login/oobe_screen.h"
-#include "chrome/browser/chromeos/login/screen_manager.h"
-#include "chrome/browser/chromeos/profiles/profile_helper.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_manager.h"
-#include "chrome/browser/supervised_user/supervised_user_features.h"
-#include "chrome/browser/ui/webui/chromeos/login/parental_handoff_screen_handler.h"
-#include "chrome/grit/chromium_strings.h"
-#include "chrome/grit/generated_resources.h"
-#include "components/user_manager/user.h"
-#include "components/user_manager/user_manager.h"
-#include "ui/base/l10n/l10n_util.h"
-
-namespace chromeos {
-
-namespace {
-
-constexpr char kUserActionNext[] = "next";
-
-base::string16 GetActiveUserName() {
-  const user_manager::User* user =
-      user_manager::UserManager::Get()->GetActiveUser();
-  if (!user || !user->IsChild())
-    return base::string16();
-  return user->GetDisplayName();
-}
-
-}  // namespace
-
-// static
-ParentalHandoffScreen* ParentalHandoffScreen::Get(
-    ScreenManager* screen_manager) {
-  return static_cast<ParentalHandoffScreen*>(
-      screen_manager->GetScreen(ParentalHandoffScreenView::kScreenId));
-}
-
-// static
-std::string ParentalHandoffScreen::GetResultString(
-    ParentalHandoffScreen::Result result) {
-  switch (result) {
-    case ParentalHandoffScreen::Result::DONE:
-      return "Done";
-    case ParentalHandoffScreen::Result::SKIPPED:
-      return BaseScreen::kNotApplicable;
-  }
-}
-
-ParentalHandoffScreen::ParentalHandoffScreen(
-    ParentalHandoffScreenView* view,
-    const ScreenExitCallback& exit_callback)
-    : BaseScreen(ParentalHandoffScreenView::kScreenId,
-                 OobeScreenPriority::DEFAULT),
-      view_(view),
-      exit_callback_(exit_callback) {
-  if (view_)
-    view_->Bind(this);
-}
-
-ParentalHandoffScreen::~ParentalHandoffScreen() {
-  if (view_)
-    view_->Unbind();
-}
-
-void ParentalHandoffScreen::OnViewDestroyed(ParentalHandoffScreenView* view) {
-  if (view_ == view)
-    view_ = nullptr;
-}
-
-bool ParentalHandoffScreen::MaybeSkip(WizardContext* context) {
-  Profile* profile = ProfileManager::GetActiveUserProfile();
-  if (profile->IsChild() && supervised_users::IsEduCoexistenceFlowV2Enabled()) {
-    return false;
-  }
-
-  exit_callback_.Run(Result::SKIPPED);
-  return true;
-}
-
-void ParentalHandoffScreen::ShowImpl() {
-  if (!view_)
-    return;
-
-  base::string16 user_name = GetActiveUserName();
-
-  view_->Show(l10n_util::GetStringFUTF16(
-                  IDS_LOGIN_PARENTAL_HANDOFF_SCREEN_TITLE, user_name),
-              l10n_util::GetStringFUTF16(
-                  IDS_LOGIN_PARENTAL_HANDOFF_SCREEN_SUBTITLE, user_name));
-}
-void ParentalHandoffScreen::HideImpl() {}
-
-void ParentalHandoffScreen::OnUserAction(const std::string& action_id) {
-  if (action_id == kUserActionNext) {
-    exit_callback_.Run(Result::DONE);
-  } else {
-    BaseScreen::OnUserAction(action_id);
-  }
-}
-
-}  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/screens/parental_handoff_screen.h b/chrome/browser/chromeos/login/screens/parental_handoff_screen.h
deleted file mode 100644
index 79b4dbd2..0000000
--- a/chrome/browser/chromeos/login/screens/parental_handoff_screen.h
+++ /dev/null
@@ -1,54 +0,0 @@
-// 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_CHROMEOS_LOGIN_SCREENS_PARENTAL_HANDOFF_SCREEN_H_
-#define CHROME_BROWSER_CHROMEOS_LOGIN_SCREENS_PARENTAL_HANDOFF_SCREEN_H_
-
-#include <string>
-
-#include "base/bind.h"
-#include "chrome/browser/chromeos/login/screens/base_screen.h"
-
-namespace chromeos {
-
-class ParentalHandoffScreenView;
-class WizardContext;
-class ScreenManager;
-
-class ParentalHandoffScreen : public BaseScreen {
- public:
-  enum class Result { DONE, SKIPPED };
-
-  static ParentalHandoffScreen* Get(ScreenManager* screen_manager);
-  static std::string GetResultString(Result result);
-
-  using ScreenExitCallback = base::RepeatingCallback<void(Result)>;
-  ParentalHandoffScreen(ParentalHandoffScreenView* view,
-                        const ScreenExitCallback& exit_callback);
-  ParentalHandoffScreen(const ParentalHandoffScreen&) = delete;
-  ParentalHandoffScreen& operator=(const ParentalHandoffScreen&) = delete;
-  ~ParentalHandoffScreen() override;
-
-  void OnViewDestroyed(ParentalHandoffScreenView* view);
-
-  ScreenExitCallback get_exit_callback_for_test() { return exit_callback_; }
-
-  void set_exit_callback_for_test(const ScreenExitCallback& exit_callback) {
-    exit_callback_ = exit_callback;
-  }
-
- private:
-  // BaseScreen:
-  bool MaybeSkip(WizardContext* context) override;
-  void ShowImpl() override;
-  void HideImpl() override;
-  void OnUserAction(const std::string& action_id) override;
-
-  ParentalHandoffScreenView* view_ = nullptr;
-  ScreenExitCallback exit_callback_;
-};
-
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_CHROMEOS_LOGIN_SCREENS_PARENTAL_HANDOFF_SCREEN_H_
diff --git a/chrome/browser/chromeos/login/screens/parental_handoff_screen_browsertest.cc b/chrome/browser/chromeos/login/screens/parental_handoff_screen_browsertest.cc
deleted file mode 100644
index 4620c31f..0000000
--- a/chrome/browser/chromeos/login/screens/parental_handoff_screen_browsertest.cc
+++ /dev/null
@@ -1,241 +0,0 @@
-// 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/chromeos/login/screens/parental_handoff_screen.h"
-
-#include <memory>
-
-#include "base/auto_reset.h"
-#include "base/callback.h"
-#include "base/optional.h"
-#include "base/run_loop.h"
-#include "base/test/metrics/histogram_tester.h"
-#include "base/test/scoped_feature_list.h"
-#include "chrome/browser/chromeos/login/screens/edu_coexistence_login_screen.h"
-#include "chrome/browser/chromeos/login/screens/sync_consent_screen.h"
-#include "chrome/browser/chromeos/login/test/fake_gaia_mixin.h"
-#include "chrome/browser/chromeos/login/test/js_checker.h"
-#include "chrome/browser/chromeos/login/test/local_policy_test_server_mixin.h"
-#include "chrome/browser/chromeos/login/test/login_manager_mixin.h"
-#include "chrome/browser/chromeos/login/test/oobe_base_test.h"
-#include "chrome/browser/chromeos/login/test/oobe_screen_exit_waiter.h"
-#include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h"
-#include "chrome/browser/chromeos/login/test/user_policy_mixin.h"
-#include "chrome/browser/chromeos/login/test/wizard_controller_screen_exit_waiter.h"
-#include "chrome/browser/chromeos/login/wizard_context.h"
-#include "chrome/browser/chromeos/login/wizard_controller.h"
-#include "chrome/browser/supervised_user/supervised_user_features.h"
-#include "chrome/browser/supervised_user/supervised_user_service.h"
-#include "chrome/browser/ui/webui/chromeos/login/parental_handoff_screen_handler.h"
-#include "chrome/browser/ui/webui/chromeos/login/sync_consent_screen_handler.h"
-#include "chrome/browser/ui/webui/chromeos/login/user_creation_screen_handler.h"
-#include "chrome/browser/ui/webui/chromeos/system_web_dialog_delegate.h"
-#include "chromeos/constants/chromeos_features.h"
-#include "components/account_id/account_id.h"
-#include "content/public/test/browser_test.h"
-
-namespace chromeos {
-
-namespace {
-
-const test::UIPath kParentalHandoffDialog = {"parental-handoff",
-                                             "parentalHandoffDialog"};
-const test::UIPath kNextButton = {"parental-handoff", "nextButton"};
-
-SystemWebDialogDelegate* GetEduCoexistenceLoginDialog() {
-  return chromeos::SystemWebDialogDelegate::FindInstance(
-      SupervisedUserService::GetEduCoexistenceLoginUrl());
-}
-
-}  // namespace
-
-class ParentalHandoffScreenBrowserTest : public OobeBaseTest {
- public:
-  ParentalHandoffScreenBrowserTest();
-  ParentalHandoffScreenBrowserTest(const ParentalHandoffScreenBrowserTest&) =
-      delete;
-  ParentalHandoffScreenBrowserTest& operator=(
-      const ParentalHandoffScreenBrowserTest&) = delete;
-  ~ParentalHandoffScreenBrowserTest() override = default;
-
-  void SetUpOnMainThread() override;
-
- protected:
-  void WaitForScreenExit();
-
-  ParentalHandoffScreen* GetParentalHandoffScreen();
-
-  void ExitSyncConsentScreen();
-
-  const base::Optional<ParentalHandoffScreen::Result>& result() const {
-    return result_;
-  }
-
-  LoginManagerMixin& login_manager_mixin() { return login_manager_mixin_; }
-
-  base::HistogramTester& histogram_tester() { return histogram_tester_; }
-
- private:
-  void HandleScreenExit(ParentalHandoffScreen::Result result);
-
-  base::OnceCallback<void()> quit_closure_;
-
-  base::Optional<ParentalHandoffScreen::Result> result_;
-
-  ParentalHandoffScreen::ScreenExitCallback original_callback_;
-
-  FakeGaiaMixin fake_gaia_{&mixin_host_, embedded_test_server()};
-
-  base::test::ScopedFeatureList feature_list_;
-
-  base::HistogramTester histogram_tester_;
-
-  std::unique_ptr<base::AutoReset<bool>> chrome_sync_is_google_branded_build_;
-
-  LoginManagerMixin login_manager_mixin_{&mixin_host_, /* initial_users */ {},
-                                         &fake_gaia_};
-};
-
-ParentalHandoffScreenBrowserTest::ParentalHandoffScreenBrowserTest() {
-  feature_list_.InitAndEnableFeature(supervised_users::kEduCoexistenceFlowV2);
-}
-
-void ParentalHandoffScreenBrowserTest::SetUpOnMainThread() {
-  chrome_sync_is_google_branded_build_ =
-      SyncConsentScreen::ForceBrandedBuildForTesting(true);
-  ParentalHandoffScreen* screen = GetParentalHandoffScreen();
-  original_callback_ = screen->get_exit_callback_for_test();
-  screen->set_exit_callback_for_test(
-      base::BindRepeating(&ParentalHandoffScreenBrowserTest::HandleScreenExit,
-                          base::Unretained(this)));
-  OobeBaseTest::SetUpOnMainThread();
-}
-
-void ParentalHandoffScreenBrowserTest::WaitForScreenExit() {
-  if (result_.has_value())
-    return;
-  base::RunLoop run_loop;
-  quit_closure_ = base::BindOnce(run_loop.QuitClosure());
-  run_loop.Run();
-}
-
-ParentalHandoffScreen*
-ParentalHandoffScreenBrowserTest::GetParentalHandoffScreen() {
-  return ParentalHandoffScreen::Get(
-      WizardController::default_controller()->screen_manager());
-}
-
-void ParentalHandoffScreenBrowserTest::ExitSyncConsentScreen() {
-  test::OobeJS().CreateVisibilityWaiter(true, {"sync-consent"})->Wait();
-
-  const std::string button_name =
-      chromeos::features::IsSplitSettingsSyncEnabled()
-          ? "acceptButton"
-          : "settingsSaveAndContinueButton";
-  test::OobeJS().ExpectEnabledPath({"sync-consent", button_name});
-  test::OobeJS().CreateFocusWaiter({"sync-consent", button_name})->Wait();
-  test::OobeJS().TapOnPath({"sync-consent", button_name});
-
-  OobeScreenExitWaiter waiter(SyncConsentScreenView ::kScreenId);
-  waiter.Wait();
-}
-
-void ParentalHandoffScreenBrowserTest::HandleScreenExit(
-    ParentalHandoffScreen::Result result) {
-  result_ = result;
-  original_callback_.Run(result);
-  if (quit_closure_)
-    std::move(quit_closure_).Run();
-}
-
-IN_PROC_BROWSER_TEST_F(ParentalHandoffScreenBrowserTest, RegularUserLogin) {
-  login_manager_mixin().LoginAsNewRegularUser();
-
-  OobeScreenWaiter waiter(SyncConsentScreenView ::kScreenId);
-  waiter.Wait();
-
-  WizardController* wizard = WizardController::default_controller();
-
-  EXPECT_EQ(wizard->current_screen()->screen_id(),
-            SyncConsentScreenView::kScreenId);
-
-  ExitSyncConsentScreen();
-
-  WaitForScreenExit();
-
-  // Regular user login shouldn't show the EduCoexistenceLoginScreen.
-  EXPECT_EQ(result().value(), ParentalHandoffScreen::Result::SKIPPED);
-
-  histogram_tester().ExpectTotalCount(
-      "OOBE.StepCompletionTimeByExitReason.Parental-handoff.Done", 0);
-}
-
-class ParentalHandoffScreenChildBrowserTest
-    : public ParentalHandoffScreenBrowserTest {
- public:
-  // Child users require a user policy, set up an empty one so the user can
-  // get through login.
-  void SetUpInProcessBrowserTestFixture() override {
-    ASSERT_TRUE(user_policy_mixin_.RequestPolicyUpdate());
-    ParentalHandoffScreenBrowserTest::SetUpInProcessBrowserTestFixture();
-  }
-
-  void LoginAsNewChildUser() {
-    WizardController::default_controller()
-        ->get_wizard_context_for_testing()
-        ->sign_in_as_child = true;
-    login_manager_mixin().LoginAsNewChildUser();
-
-    WizardControllerExitWaiter(UserCreationView::kScreenId).Wait();
-    base::RunLoop().RunUntilIdle();
-
-    ASSERT_EQ(
-        WizardController::default_controller()->current_screen()->screen_id(),
-        EduCoexistenceLoginScreen::kScreenId);
-
-    // Current screen is EduCoexistenceLoginScreen. Close it.
-    GetEduCoexistenceLoginDialog()->Close();
-
-    OobeScreenWaiter sync_screen_waiter(SyncConsentScreenView ::kScreenId);
-    sync_screen_waiter.Wait();
-
-    ExitSyncConsentScreen();
-
-    OobeScreenWaiter parental_handoff_waiter(
-        ParentalHandoffScreenView::kScreenId);
-    parental_handoff_waiter.Wait();
-  }
-
- protected:
-  void ClickNextButtonOnParentalHandoffScreen() {
-    test::OobeJS().ExpectVisiblePath(kParentalHandoffDialog);
-    test::OobeJS().ExpectVisiblePath(kNextButton);
-    test::OobeJS().TapOnPath(kNextButton);
-  }
-
- private:
-  LocalPolicyTestServerMixin policy_server_mixin_{&mixin_host_};
-  UserPolicyMixin user_policy_mixin_{
-      &mixin_host_,
-      AccountId::FromUserEmailGaiaId(test::kTestEmail, test::kTestGaiaId),
-      &policy_server_mixin_};
-};
-
-IN_PROC_BROWSER_TEST_F(ParentalHandoffScreenChildBrowserTest, ChildUserLogin) {
-  LoginAsNewChildUser();
-
-  WizardController* wizard = WizardController::default_controller();
-
-  EXPECT_EQ(wizard->current_screen()->screen_id(),
-            ParentalHandoffScreenView::kScreenId);
-
-  ClickNextButtonOnParentalHandoffScreen();
-
-  WaitForScreenExit();
-
-  histogram_tester().ExpectTotalCount(
-      "OOBE.StepCompletionTimeByExitReason.Parental-handoff.Done", 1);
-}
-
-}  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/wizard_controller.cc b/chrome/browser/chromeos/login/wizard_controller.cc
index a326565..86eff58c 100644
--- a/chrome/browser/chromeos/login/wizard_controller.cc
+++ b/chrome/browser/chromeos/login/wizard_controller.cc
@@ -136,7 +136,6 @@
 #include "chrome/browser/ui/webui/chromeos/login/network_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h"
 #include "chrome/browser/ui/webui/chromeos/login/packaged_license_screen_handler.h"
-#include "chrome/browser/ui/webui/chromeos/login/parental_handoff_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/pin_setup_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/recommend_apps_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/reset_screen_handler.h"
@@ -683,11 +682,6 @@
       base::BindRepeating(&WizardController::OnSignInFatalErrorScreenExit,
                           weak_factory_.GetWeakPtr())));
 
-  append(std::make_unique<ParentalHandoffScreen>(
-      oobe_ui->GetView<ParentalHandoffScreenHandler>(),
-      base::BindRepeating(&WizardController::OnParentalHandoffScreenExit,
-                          weak_factory_.GetWeakPtr())));
-
   return result;
 }
 
@@ -881,10 +875,6 @@
   SetCurrentScreen(GetScreen(EduCoexistenceLoginScreen::kScreenId));
 }
 
-void WizardController::ShowParentalHandoffScreen() {
-  SetCurrentScreen(GetScreen(ParentalHandoffScreenView::kScreenId));
-}
-
 void WizardController::ShowActiveDirectoryPasswordChangeScreen(
     const std::string& username) {
   ActiveDirectoryPasswordChangeScreen::Get(screen_manager())
@@ -955,13 +945,6 @@
   ShowSyncConsentScreen();
 }
 
-void WizardController::OnParentalHandoffScreenExit(
-    ParentalHandoffScreen::Result result) {
-  OnScreenExit(ParentalHandoffScreenView::kScreenId,
-               ParentalHandoffScreen::GetResultString(result));
-  ShowMultiDeviceSetupScreen();
-}
-
 void WizardController::SkipToLoginForTesting() {
   VLOG(1) << "WizardController::SkipToLoginForTesting()";
   if (current_screen_ && current_screen_->screen_id() == GaiaView::kScreenId)
@@ -1416,7 +1399,7 @@
     AssistantOptInFlowScreen::Result result) {
   OnScreenExit(AssistantOptInFlowScreenView::kScreenId,
                AssistantOptInFlowScreen::GetResultString(result));
-  ShowParentalHandoffScreen();
+  ShowMultiDeviceSetupScreen();
 }
 
 void WizardController::OnMultiDeviceSetupScreenExit(
@@ -1932,8 +1915,7 @@
       current_screen_id == FingerprintSetupScreenView::kScreenId ||
       current_screen_id == ArcTermsOfServiceScreenView::kScreenId ||
       current_screen_id == PinSetupScreenView::kScreenId ||
-      current_screen_id == MarketingOptInScreenView::kScreenId ||
-      current_screen_id == ParentalHandoffScreenView::kScreenId) {
+      current_screen_id == MarketingOptInScreenView::kScreenId) {
     default_controller()->OnOobeFlowFinished();
   } else {
     LOG(WARNING) << "SkipPostLoginScreensForTesting(): Ignore screen "
diff --git a/chrome/browser/chromeos/login/wizard_controller.h b/chrome/browser/chromeos/login/wizard_controller.h
index c1663a4e..068bc28 100644
--- a/chrome/browser/chromeos/login/wizard_controller.h
+++ b/chrome/browser/chromeos/login/wizard_controller.h
@@ -41,7 +41,6 @@
 #include "chrome/browser/chromeos/login/screens/multidevice_setup_screen.h"
 #include "chrome/browser/chromeos/login/screens/network_screen.h"
 #include "chrome/browser/chromeos/login/screens/packaged_license_screen.h"
-#include "chrome/browser/chromeos/login/screens/parental_handoff_screen.h"
 #include "chrome/browser/chromeos/login/screens/pin_setup_screen.h"
 #include "chrome/browser/chromeos/login/screens/recommend_apps_screen.h"
 #include "chrome/browser/chromeos/login/screens/signin_fatal_error_screen.h"
@@ -256,7 +255,6 @@
   void ShowMarketingOptInScreen();
   void ShowPackagedLicenseScreen();
   void ShowEduCoexistenceLoginScreen();
-  void ShowParentalHandoffScreen();
 
   // Shows images login screen.
   void ShowLoginScreen();
@@ -318,7 +316,6 @@
   void OnSignInFatalErrorScreenExit();
   void OnEduCoexistenceLoginScreenExit(
       EduCoexistenceLoginScreen::Result result);
-  void OnParentalHandoffScreenExit(ParentalHandoffScreen::Result result);
 
   // Callback invoked once it has been determined whether the device is disabled
   // or not.
diff --git a/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc b/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc
index d105975..5b3aaaf 100644
--- a/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc
+++ b/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc
@@ -389,7 +389,8 @@
   RemoveDuplicatePhoneNumberAtIndex(params->index_of_new_number,
                                     params->country_code, phone_numbers.get());
 
-  return RespondNow(OneArgument(std::move(phone_numbers)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(phone_numbers))));
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/extensions/api/browsing_data/browsing_data_api.cc b/chrome/browser/extensions/api/browsing_data/browsing_data_api.cc
index 0cc936b..7da928c7 100644
--- a/chrome/browser/extensions/api/browsing_data/browsing_data_api.cc
+++ b/chrome/browser/extensions/api/browsing_data/browsing_data_api.cc
@@ -251,7 +251,8 @@
               std::move(selected));
   result->Set(extension_browsing_data_api_constants::kDataRemovalPermittedKey,
               std::move(permitted));
-  return RespondNow(OneArgument(std::move(result)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
 }
 
 void BrowsingDataSettingsFunction::SetDetails(
diff --git a/chrome/browser/extensions/api/commands/commands.cc b/chrome/browser/extensions/api/commands/commands.cc
index 3f13237..5f5991f 100644
--- a/chrome/browser/extensions/api/commands/commands.cc
+++ b/chrome/browser/extensions/api/commands/commands.cc
@@ -67,5 +67,6 @@
     command_list->Append(CreateCommandValue(iter->second, active));
   }
 
-  return RespondNow(OneArgument(std::move(command_list)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(command_list))));
 }
diff --git a/chrome/browser/extensions/api/content_settings/content_settings_api.cc b/chrome/browser/extensions/api/content_settings/content_settings_api.cc
index 6075c9b3..689371c 100644
--- a/chrome/browser/extensions/api/content_settings/content_settings_api.cc
+++ b/chrome/browser/extensions/api/content_settings/content_settings_api.cc
@@ -179,7 +179,8 @@
   result->SetString(content_settings_api_constants::kContentSettingKey,
                     setting_string);
 
-  return RespondNow(OneArgument(std::move(result)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
 }
 
 ExtensionFunction::ResponseAction
@@ -368,7 +369,7 @@
                     plugin_metadata->name());
     list->Append(std::move(dict));
   }
-  Respond(OneArgument(std::move(list)));
+  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(list))));
 }
 #endif  // BUILDFLAG(ENABLE_PLUGINS)
 
diff --git a/chrome/browser/extensions/api/data_reduction_proxy/data_reduction_proxy_api.cc b/chrome/browser/extensions/api/data_reduction_proxy/data_reduction_proxy_api.cc
index 78af5ac..5b3df28 100644
--- a/chrome/browser/extensions/api/data_reduction_proxy/data_reduction_proxy_api.cc
+++ b/chrome/browser/extensions/api/data_reduction_proxy/data_reduction_proxy_api.cc
@@ -70,7 +70,7 @@
 
   std::unique_ptr<base::DictionaryValue> result(new base::DictionaryValue());
   result->Set("data_usage_buckets", std::move(data_usage_buckets));
-  Respond(OneArgument(std::move(result)));
+  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/debugger/debugger_api.cc b/chrome/browser/extensions/api/debugger/debugger_api.cc
index 8382f6b..0ddf518 100644
--- a/chrome/browser/extensions/api/debugger/debugger_api.cc
+++ b/chrome/browser/extensions/api/debugger/debugger_api.cc
@@ -708,7 +708,8 @@
   for (size_t i = 0; i < list.size(); ++i)
     result->Append(SerializeTarget(list[i]));
 
-  return RespondNow(OneArgument(std::move(result)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/downloads/downloads_api.cc b/chrome/browser/extensions/api/downloads/downloads_api.cc
index 970f2d2..a1c9311 100644
--- a/chrome/browser/extensions/api/downloads/downloads_api.cc
+++ b/chrome/browser/extensions/api/downloads/downloads_api.cc
@@ -1191,7 +1191,8 @@
     json_results->Append(std::move(json_item));
   }
   RecordApiFunctions(DOWNLOADS_FUNCTION_SEARCH);
-  return RespondNow(OneArgument(std::move(json_results)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(json_results))));
 }
 
 DownloadsPauseFunction::DownloadsPauseFunction() {}
@@ -1283,7 +1284,8 @@
     (*it)->Remove();
   }
   RecordApiFunctions(DOWNLOADS_FUNCTION_ERASE);
-  return RespondNow(OneArgument(std::move(json_results)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(json_results))));
 }
 
 DownloadsRemoveFileFunction::DownloadsRemoveFileFunction() {
diff --git a/chrome/browser/extensions/api/extension_action/extension_action_api.cc b/chrome/browser/extensions/api/extension_action/extension_action_api.cc
index 3bfd3119..bf6aac8 100644
--- a/chrome/browser/extensions/api/extension_action/extension_action_api.cc
+++ b/chrome/browser/extensions/api/extension_action/extension_action_api.cc
@@ -535,7 +535,8 @@
   list->AppendInteger(static_cast<int>(SkColorGetG(color)));
   list->AppendInteger(static_cast<int>(SkColorGetB(color)));
   list->AppendInteger(static_cast<int>(SkColorGetA(color)));
-  return RespondNow(OneArgument(std::move(list)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(list))));
 }
 
 BrowserActionOpenPopupFunction::BrowserActionOpenPopupFunction() = default;
diff --git a/chrome/browser/extensions/api/font_settings/font_settings_api.cc b/chrome/browser/extensions/api/font_settings/font_settings_api.cc
index ea8a2e24..6d47d67d 100644
--- a/chrome/browser/extensions/api/font_settings/font_settings_api.cc
+++ b/chrome/browser/extensions/api/font_settings/font_settings_api.cc
@@ -261,7 +261,8 @@
   std::unique_ptr<base::DictionaryValue> result(new base::DictionaryValue());
   result->SetString(kFontIdKey, font_name);
   result->SetString(kLevelOfControlKey, level_of_control);
-  return RespondNow(OneArgument(std::move(result)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
 }
 
 ExtensionFunction::ResponseAction FontSettingsSetFontFunction::Run() {
@@ -327,7 +328,7 @@
     result->Append(std::move(font_name));
   }
 
-  return OneArgument(std::move(result));
+  return OneArgument(base::Value::FromUniquePtrValue(std::move(result)));
 }
 
 ExtensionFunction::ResponseAction ClearFontPrefExtensionFunction::Run() {
@@ -357,7 +358,8 @@
   std::unique_ptr<base::DictionaryValue> result(new base::DictionaryValue());
   result->Set(GetKey(), pref->GetValue()->CreateDeepCopy());
   result->SetString(kLevelOfControlKey, level_of_control);
-  return RespondNow(OneArgument(std::move(result)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
 }
 
 ExtensionFunction::ResponseAction SetFontPrefExtensionFunction::Run() {
diff --git a/chrome/browser/extensions/api/identity/identity_get_accounts_function.cc b/chrome/browser/extensions/api/identity/identity_get_accounts_function.cc
index 87a72fb..20b115e 100644
--- a/chrome/browser/extensions/api/identity/identity_get_accounts_function.cc
+++ b/chrome/browser/extensions/api/identity/identity_get_accounts_function.cc
@@ -37,7 +37,8 @@
   std::unique_ptr<base::ListValue> infos(new base::ListValue());
 
   if (accounts.empty()) {
-    return RespondNow(OneArgument(std::move(infos)));
+    return RespondNow(
+        OneArgument(base::Value::FromUniquePtrValue(std::move(infos))));
   }
 
   Profile* profile = Profile::FromBrowserContext(browser_context());
@@ -67,7 +68,8 @@
     }
   }
 
-  return RespondNow(OneArgument(std::move(infos)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(infos))));
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/input_ime/input_ime_api_chromeos.cc b/chrome/browser/extensions/api/input_ime/input_ime_api_chromeos.cc
index 14e52be..fcb9c785 100644
--- a/chrome/browser/extensions/api/input_ime/input_ime_api_chromeos.cc
+++ b/chrome/browser/extensions/api/input_ime/input_ime_api_chromeos.cc
@@ -1016,7 +1016,8 @@
     bounds_list->Append(std::move(bounds_value));
   }
 
-  return RespondNow(OneArgument(std::move(bounds_list)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(bounds_list))));
 }
 
 void InputImeAPI::OnExtensionLoaded(content::BrowserContext* browser_context,
diff --git a/chrome/browser/extensions/api/language_settings_private/language_settings_private_api.cc b/chrome/browser/extensions/api/language_settings_private/language_settings_private_api.cc
index 83c346a4..d9ad9d6 100644
--- a/chrome/browser/extensions/api/language_settings_private/language_settings_private_api.cc
+++ b/chrome/browser/extensions/api/language_settings_private/language_settings_private_api.cc
@@ -298,14 +298,16 @@
   }
 #endif  // defined(OS_WIN)
 
-  return RespondNow(OneArgument(std::move(language_list_)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(language_list_))));
 }
 
 #if defined(OS_WIN)
 void LanguageSettingsPrivateGetLanguageListFunction::
     OnDictionariesInitialized() {
   UpdateSupportedPlatformDictionaries();
-  Respond(OneArgument(std::move(language_list_)));
+  Respond(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(language_list_))));
   // Matches the AddRef in Run().
   Release();
 }
diff --git a/chrome/browser/extensions/api/notifications/notifications_api.cc b/chrome/browser/extensions/api/notifications/notifications_api.cc
index 3eb30fe..53a9e63 100644
--- a/chrome/browser/extensions/api/notifications/notifications_api.cc
+++ b/chrome/browser/extensions/api/notifications/notifications_api.cc
@@ -683,7 +683,8 @@
                    base::Value(true));
   }
 
-  return RespondNow(OneArgument(std::move(result)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
 }
 
 NotificationsGetPermissionLevelFunction::
diff --git a/chrome/browser/extensions/api/page_capture/page_capture_api.cc b/chrome/browser/extensions/api/page_capture/page_capture_api.cc
index cd660b9..08ffb76 100644
--- a/chrome/browser/extensions/api/page_capture/page_capture_api.cc
+++ b/chrome/browser/extensions/api/page_capture/page_capture_api.cc
@@ -303,7 +303,7 @@
   std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
   dict->SetString("mhtmlFilePath", mhtml_path_.value());
   dict->SetInteger("mhtmlFileLength", file_size);
-  Respond(OneArgument(std::move(dict)));
+  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(dict))));
 
   // Note that we'll wait for a response ack message received in
   // OnMessageReceived before we call Release() (to prevent the blob file from
diff --git a/chrome/browser/extensions/api/preference/preference_api.cc b/chrome/browser/extensions/api/preference/preference_api.cc
index 6f608df9..fdb6064 100644
--- a/chrome/browser/extensions/api/preference/preference_api.cc
+++ b/chrome/browser/extensions/api/preference/preference_api.cc
@@ -716,7 +716,8 @@
                        ep->HasIncognitoPrefValue(browser_pref));
   }
 
-  return RespondNow(OneArgument(std::move(result)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
 }
 
 SetPreferenceFunction::~SetPreferenceFunction() = default;
diff --git a/chrome/browser/extensions/api/resources_private/resources_private_api.cc b/chrome/browser/extensions/api/resources_private/resources_private_api.cc
index a359724..2615de5 100644
--- a/chrome/browser/extensions/api/resources_private/resources_private_api.cc
+++ b/chrome/browser/extensions/api/resources_private/resources_private_api.cc
@@ -69,7 +69,8 @@
   const std::string& app_locale = g_browser_process->GetApplicationLocale();
   webui::SetLoadTimeDataDefaults(app_locale, dict.get());
 
-  return RespondNow(OneArgument(std::move(dict)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(dict))));
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/settings_private/settings_private_api.cc b/chrome/browser/extensions/api/settings_private/settings_private_api.cc
index f29c7bc..88e0b85 100644
--- a/chrome/browser/extensions/api/settings_private/settings_private_api.cc
+++ b/chrome/browser/extensions/api/settings_private/settings_private_api.cc
@@ -89,7 +89,8 @@
   if (value->is_none())
     return RespondNow(Error("Pref * does not exist", parameters->name));
   else
-    return RespondNow(OneArgument(std::move(value)));
+    return RespondNow(
+        OneArgument(base::Value::FromUniquePtrValue(std::move(value))));
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/extensions/api/signed_in_devices/signed_in_devices_api.cc b/chrome/browser/extensions/api/signed_in_devices/signed_in_devices_api.cc
index 222f50c0..5aba4b3 100644
--- a/chrome/browser/extensions/api/signed_in_devices/signed_in_devices_api.cc
+++ b/chrome/browser/extensions/api/signed_in_devices/signed_in_devices_api.cc
@@ -131,7 +131,8 @@
     if (device.get()) {
       result->Append(device->ToValue());
     }
-    return RespondNow(OneArgument(std::move(result)));
+    return RespondNow(
+        OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
   }
 
   std::vector<std::unique_ptr<DeviceInfo>> devices =
@@ -142,7 +143,8 @@
   for (const std::unique_ptr<DeviceInfo>& device : devices)
     result->Append(device->ToValue());
 
-  return RespondNow(OneArgument(std::move(result)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/system_private/system_private_api.cc b/chrome/browser/extensions/api/system_private/system_private_api.cc
index 9d0a874..ce66a793 100644
--- a/chrome/browser/extensions/api/system_private/system_private_api.cc
+++ b/chrome/browser/extensions/api/system_private/system_private_api.cc
@@ -127,7 +127,8 @@
   std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
   dict->SetString(kStateKey, state);
   dict->SetDouble(kDownloadProgressKey, download_progress);
-  return RespondNow(OneArgument(std::move(dict)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(dict))));
 }
 
 ExtensionFunction::ResponseAction SystemPrivateGetApiKeyFunction::Run() {
diff --git a/chrome/browser/extensions/api/tab_capture/tab_capture_api.cc b/chrome/browser/extensions/api/tab_capture/tab_capture_api.cc
index 19b6bf6d..07c0f68 100644
--- a/chrome/browser/extensions/api/tab_capture/tab_capture_api.cc
+++ b/chrome/browser/extensions/api/tab_capture/tab_capture_api.cc
@@ -285,7 +285,8 @@
   // chrome/renderer/resources/extensions/tab_capture_custom_bindings.js
   std::unique_ptr<base::DictionaryValue> result(new base::DictionaryValue());
   result->MergeDictionary(params->options.ToValue().get());
-  return RespondNow(OneArgument(std::move(result)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
 }
 
 ExtensionFunction::ResponseAction TabCaptureGetCapturedTabsFunction::Run() {
@@ -293,7 +294,8 @@
   std::unique_ptr<base::ListValue> list(new base::ListValue());
   if (registry)
     registry->GetCapturedTabs(extension()->id(), list.get());
-  return RespondNow(OneArgument(std::move(list)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(list))));
 }
 
 ExtensionFunction::ResponseAction TabCaptureCaptureOffscreenTabFunction::Run() {
diff --git a/chrome/browser/extensions/api/tabs/tabs_api.cc b/chrome/browser/extensions/api/tabs/tabs_api.cc
index 90fdb964..fecf9bd 100644
--- a/chrome/browser/extensions/api/tabs/tabs_api.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_api.cc
@@ -393,7 +393,8 @@
   std::unique_ptr<base::DictionaryValue> windows =
       ExtensionTabUtil::CreateWindowValueForExtension(
           *browser, extension(), populate_tab_behavior, source_context_type());
-  return RespondNow(OneArgument(std::move(windows)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(windows))));
 }
 
 ExtensionFunction::ResponseAction WindowsGetCurrentFunction::Run() {
@@ -416,7 +417,8 @@
   std::unique_ptr<base::DictionaryValue> windows =
       ExtensionTabUtil::CreateWindowValueForExtension(
           *browser, extension(), populate_tab_behavior, source_context_type());
-  return RespondNow(OneArgument(std::move(windows)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(windows))));
 }
 
 ExtensionFunction::ResponseAction WindowsGetLastFocusedFunction::Run() {
@@ -450,7 +452,8 @@
   std::unique_ptr<base::DictionaryValue> windows =
       ExtensionTabUtil::CreateWindowValueForExtension(
           *browser, extension(), populate_tab_behavior, source_context_type());
-  return RespondNow(OneArgument(std::move(windows)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(windows))));
 }
 
 ExtensionFunction::ResponseAction WindowsGetAllFunction::Run() {
@@ -474,7 +477,8 @@
         source_context_type()));
   }
 
-  return RespondNow(OneArgument(std::move(window_list)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(window_list))));
 }
 
 bool WindowsCreateFunction::ShouldOpenIncognitoWindow(
@@ -732,7 +736,8 @@
         source_context_type());
   }
 
-  return RespondNow(OneArgument(std::move(result)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
 }
 
 ExtensionFunction::ResponseAction WindowsUpdateFunction::Run() {
@@ -1137,7 +1142,8 @@
     }
   }
 
-  return RespondNow(OneArgument(std::move(result)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
 }
 
 ExtensionFunction::ResponseAction TabsCreateFunction::Run() {
@@ -1163,8 +1169,10 @@
     return RespondNow(Error(std::move(error)));
 
   // Return data about the newly created tab.
-  return RespondNow(has_callback() ? OneArgument(std::move(result))
-                                   : NoArguments());
+  return RespondNow(
+      has_callback()
+          ? OneArgument(base::Value::FromUniquePtrValue(std::move(result)))
+          : NoArguments());
 }
 
 ExtensionFunction::ResponseAction TabsDuplicateFunction::Run() {
@@ -1524,11 +1532,13 @@
   if (num_tabs == 1) {
     std::unique_ptr<base::Value> value;
     CHECK(tab_values->Remove(0, &value));
-    return RespondNow(OneArgument(std::move(value)));
+    return RespondNow(
+        OneArgument(base::Value::FromUniquePtrValue(std::move(value))));
   }
 
   // Return the results as an array if there are multiple tabs.
-  return RespondNow(OneArgument(std::move(tab_values)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(tab_values))));
 }
 
 bool TabsMoveFunction::MoveTab(int tab_id,
diff --git a/chrome/browser/extensions/api/top_sites/top_sites_api.cc b/chrome/browser/extensions/api/top_sites/top_sites_api.cc
index 65817e7d..ad12402 100644
--- a/chrome/browser/extensions/api/top_sites/top_sites_api.cc
+++ b/chrome/browser/extensions/api/top_sites/top_sites_api.cc
@@ -52,7 +52,7 @@
     }
   }
 
-  Respond(OneArgument(std::move(pages_value)));
+  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(pages_value))));
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/webrtc_logging_private/webrtc_logging_private_api.cc b/chrome/browser/extensions/api/webrtc_logging_private/webrtc_logging_private_api.cc
index 3087660..06e126c 100644
--- a/chrome/browser/extensions/api/webrtc_logging_private/webrtc_logging_private_api.cc
+++ b/chrome/browser/extensions/api/webrtc_logging_private/webrtc_logging_private_api.cc
@@ -606,7 +606,7 @@
   std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
   dict->SetString("fileSystemId", filesystem_id);
   dict->SetString("baseName", base_name);
-  Respond(OneArgument(std::move(dict)));
+  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(dict))));
 }
 
 void WebrtcLoggingPrivateGetLogsDirectoryFunction::FireErrorCallback(
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index ecd0d9b..3109a7a 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -2013,11 +2013,6 @@
     "expiry_milestone": 90
   },
   {
-    "name": "enable-quick-answers-rich-ui",
-    "owners": [ "croissant-eng" ],
-    "expiry_milestone": 90
-  },
-  {
     "name": "enable-quick-answers-text-annotator",
     "owners": [ "croissant-eng" ],
     "expiry_milestone": 90
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index 79055c5..b41b75c 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -227,6 +227,7 @@
     &paint_preview::kPaintPreviewShowOnStartup,
     &language::kDetailedLanguageSettings,
     &language::kExplicitLanguageAsk,
+    &language::kTranslateAssistContent,
     &language::kTranslateIntent,
     &messages::kMessagesForAndroidInfrastructure,
     &offline_pages::kOfflineIndicatorFeature,
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index 84ce7244..1a62193 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -425,6 +425,7 @@
     public static final String TEST_DEFAULT_DISABLED = "TestDefaultDisabled";
     public static final String TEST_DEFAULT_ENABLED = "TestDefaultEnabled";
     public static final String TOUCH_TO_FILL_ANDROID = "TouchToFillAndroid";
+    public static final String TRANSLATE_ASSIST_CONTENT = "TranslateAssistContent";
     public static final String TRANSLATE_INTENT = "TranslateIntent";
     public static final String TRUSTED_WEB_ACTIVITY_LOCATION_DELEGATION =
             "TrustedWebActivityLocationDelegation";
diff --git a/chrome/browser/lookalikes/lookalike_url_navigation_throttle.cc b/chrome/browser/lookalikes/lookalike_url_navigation_throttle.cc
index 6427b2f..8aced89 100644
--- a/chrome/browser/lookalikes/lookalike_url_navigation_throttle.cc
+++ b/chrome/browser/lookalikes/lookalike_url_navigation_throttle.cc
@@ -211,14 +211,13 @@
   const GURL& last_url = navigation_handle()->GetURL();
   LookalikeUrlMatchType last_match_type;
   GURL last_suggested_url;
-  // If first_url == last_url, then don't check a second time. This saves time,
-  // and avoids clouding metrics.
+  // If first_url and last_url share a hostname, then don't check a second time.
+  // This saves time, and avoids clouding metrics.
   bool last_is_lookalike =
-      first_url != last_url &&
+      first_url.host() != last_url.host() &&
       IsLookalikeUrl(last_url, engaged_sites, &last_match_type,
                      &last_suggested_url);
 
-
   // If the first URL is a lookalike, but we ended up on the suggested site
   // anyway, don't warn.
   if (first_is_lookalike &&
@@ -290,11 +289,11 @@
     return false;
   }
 
-  const DomainInfo navigated_domain = GetDomainInfo(url);
-  // Empty domain_and_registry happens on private domains.
-  if (navigated_domain.domain_and_registry.empty() ||
-      IsTopDomain(navigated_domain)) {
-    return content::NavigationThrottle::PROCEED;
+  // Don't warn on non-public domains.
+  if (net::HostStringIsLocalhost(url.host()) ||
+      net::IsHostnameNonUnique(url.host()) ||
+      GetETLDPlusOne(url.host()).empty()) {
+    return false;
   }
 
   // Fetch the component allowlist.
@@ -305,12 +304,6 @@
     return false;
   }
 
-  // If the URL is in the component allowlist, don't show any warning.
-  if (reputation::IsUrlAllowlistedBySafetyTipsComponent(
-          proto, url.GetWithEmptyPath())) {
-    return false;
-  }
-
   // If the URL is in the local temporary allowlist, don't show any warning.
   if (ReputationService::Get(profile_)->IsIgnored(url)) {
     return false;
@@ -321,6 +314,18 @@
     return false;
   }
 
+  // If the URL is in the component allowlist, don't show any warning.
+  if (reputation::IsUrlAllowlistedBySafetyTipsComponent(
+          proto, url.GetWithEmptyPath())) {
+    return false;
+  }
+
+  // GetDomainInfo() is expensive, so do possible early-abort checks first.
+  const DomainInfo navigated_domain = GetDomainInfo(url);
+  if (IsTopDomain(navigated_domain)) {
+    return false;
+  }
+
   // Ensure that this URL is not already engaged. We can't use the synchronous
   // SiteEngagementService::IsEngagementAtLeast as it has side effects. We check
   // in PerformChecks to ensure we have up-to-date engaged_sites.
diff --git a/chrome/browser/media/webrtc/native_desktop_media_list.cc b/chrome/browser/media/webrtc/native_desktop_media_list.cc
index e08e556..63ee95a 100644
--- a/chrome/browser/media/webrtc/native_desktop_media_list.cc
+++ b/chrome/browser/media/webrtc/native_desktop_media_list.cc
@@ -12,6 +12,7 @@
 #include "base/single_thread_task_runner.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/threading/thread_restrictions.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
 #include "chrome/browser/media/webrtc/desktop_media_list.h"
 #include "chrome/grit/generated_resources.h"
@@ -39,6 +40,15 @@
 // Update the list every second.
 const int kDefaultNativeDesktopMediaListUpdatePeriod = 1000;
 
+bool IsFrameValid(webrtc::DesktopFrame* frame) {
+  // These checks ensure invalid data isn't passed along, potentially leading to
+  // crashes, e.g. when we calculate the hash which assumes a positive height
+  // and stride.
+  // TODO(crbug.com/1085230): figure out why the height is sometimes negative.
+  return frame && frame->data() && frame->stride() >= 0 &&
+         frame->size().height() >= 0;
+}
+
 // Returns a hash of a DesktopFrame content to detect when image for a desktop
 // media source has changed.
 uint32_t GetFrameHash(webrtc::DesktopFrame* frame) {
@@ -91,13 +101,21 @@
   void Start();
   void Refresh(const DesktopMediaID::Id& view_dialog_id, bool update_thumnails);
 
-  void RefreshThumbnails(const std::vector<DesktopMediaID>& native_ids,
+  void RefreshThumbnails(std::vector<DesktopMediaID> native_ids,
                          const gfx::Size& thumbnail_size);
 
  private:
   typedef std::map<DesktopMediaID, uint32_t> ImageHashesMap;
 
-  bool IsCurrentFrameValid() const;
+  // Used to hold state associated with a call to RefreshThumbnails.
+  struct RefreshThumbnailsState {
+    std::vector<DesktopMediaID> source_ids;
+    gfx::Size thumbnail_size;
+    ImageHashesMap new_image_hashes;
+    size_t next_source_index = 0;
+  };
+
+  void RefreshNextThumbnail();
 
   // webrtc::DesktopCapturer::Callback interface.
   void OnCaptureResult(webrtc::DesktopCapturer::Result result,
@@ -111,10 +129,14 @@
   DesktopMediaID::Type type_;
   std::unique_ptr<webrtc::DesktopCapturer> capturer_;
 
-  std::unique_ptr<webrtc::DesktopFrame> current_frame_;
-
+  // Stores hashes of snapshots previously captured.
   ImageHashesMap image_hashes_;
 
+  // Non-null when RefreshThumbnails hasn't yet completed.
+  std::unique_ptr<RefreshThumbnailsState> refresh_thumbnails_state_;
+
+  base::WeakPtrFactory<Worker> weak_factory_{this};
+
   DISALLOW_COPY_AND_ASSIGN(Worker);
 };
 
@@ -186,38 +208,50 @@
 }
 
 void NativeDesktopMediaList::Worker::RefreshThumbnails(
-    const std::vector<DesktopMediaID>& native_ids,
+    std::vector<DesktopMediaID> native_ids,
     const gfx::Size& thumbnail_size) {
   DCHECK(task_runner_->BelongsToCurrentThread());
-  ImageHashesMap new_image_hashes;
 
-  // Get a thumbnail for each native source.
-  for (const auto& id : native_ids) {
-    if (!capturer_->SelectSource(id.id))
-      continue;
-    capturer_->CaptureFrame();
+  // Ignore if refresh is already in progress.
+  if (refresh_thumbnails_state_)
+    return;
 
-    // Expect that DesktopCapturer to always captures frames synchronously.
-    // |current_frame_| may be NULL if capture failed (e.g. because window has
-    // been closed).
-    if (IsCurrentFrameValid()) {
-      uint32_t frame_hash = GetFrameHash(current_frame_.get());
-      new_image_hashes[id] = frame_hash;
+  // To refresh thumbnails, a snapshot of each window is captured and scaled
+  // down to the specified size. Snapshotting can be asynchronous, and so
+  // the process looks like the following steps:
+  //
+  // 1) RefreshNextThumbnail
+  // 2) OnCaptureResult
+  // 3) UpdateSourceThumbnail (if the snapshot changed)
+  // [repeat 1, 2 and 3 until all thumbnails are refreshed]
+  // 4) RefreshNextThumbnail
+  // 5) UpdateNativeThumbnailsFinished
+  //
+  // |image_hashes_| is used to help avoid updating thumbnails that haven't
+  // changed since the last refresh.
 
-      // Scale the image only if it has changed.
-      auto it = image_hashes_.find(id);
-      if (it == image_hashes_.end() || it->second != frame_hash) {
-        gfx::ImageSkia thumbnail =
-            ScaleDesktopFrame(std::move(current_frame_), thumbnail_size);
-        content::GetUIThreadTaskRunner({})->PostTask(
-            FROM_HERE,
-            base::BindOnce(&NativeDesktopMediaList::UpdateSourceThumbnail,
-                           media_list_, id, thumbnail));
-      }
+  refresh_thumbnails_state_ = std::make_unique<RefreshThumbnailsState>();
+  refresh_thumbnails_state_->source_ids = std::move(native_ids);
+  refresh_thumbnails_state_->thumbnail_size = thumbnail_size;
+  RefreshNextThumbnail();
+}
+
+void NativeDesktopMediaList::Worker::RefreshNextThumbnail() {
+  DCHECK(refresh_thumbnails_state_);
+
+  for (size_t index = refresh_thumbnails_state_->next_source_index;
+       index < refresh_thumbnails_state_->source_ids.size(); ++index) {
+    refresh_thumbnails_state_->next_source_index = index + 1;
+    DesktopMediaID source_id = refresh_thumbnails_state_->source_ids[index];
+    if (capturer_->SelectSource(source_id.id)) {
+      capturer_->CaptureFrame();  // Completes with OnCaptureResult.
+      return;
     }
   }
 
-  image_hashes_.swap(new_image_hashes);
+  // Done capturing thumbnails.
+  image_hashes_.swap(refresh_thumbnails_state_->new_image_hashes);
+  refresh_thumbnails_state_.reset();
 
   content::GetUIThreadTaskRunner({})->PostTask(
       FROM_HERE,
@@ -225,19 +259,36 @@
                      media_list_));
 }
 
-bool NativeDesktopMediaList::Worker::IsCurrentFrameValid() const {
-  // These checks ensure invalid data isn't passed along, potentially leading to
-  // crashes, e.g. when we calculate the hash which assumes a positive height
-  // and stride.
-  // TODO(crbug.com/1085230): figure out why the height is sometimes negative.
-  return current_frame_ && current_frame_->data() &&
-         current_frame_->stride() >= 0 && current_frame_->size().height() >= 0;
-}
-
 void NativeDesktopMediaList::Worker::OnCaptureResult(
     webrtc::DesktopCapturer::Result result,
     std::unique_ptr<webrtc::DesktopFrame> frame) {
-  current_frame_ = std::move(frame);
+  auto index = refresh_thumbnails_state_->next_source_index - 1;
+  DCHECK(index < refresh_thumbnails_state_->source_ids.size());
+  DesktopMediaID id = refresh_thumbnails_state_->source_ids[index];
+
+  // |frame| may be null if capture failed (e.g. because window has been
+  // closed).
+  if (IsFrameValid(frame.get())) {
+    uint32_t frame_hash = GetFrameHash(frame.get());
+    refresh_thumbnails_state_->new_image_hashes[id] = frame_hash;
+
+    // Scale the image only if it has changed.
+    auto it = image_hashes_.find(id);
+    if (it == image_hashes_.end() || it->second != frame_hash) {
+      gfx::ImageSkia thumbnail = ScaleDesktopFrame(
+          std::move(frame), refresh_thumbnails_state_->thumbnail_size);
+      content::GetUIThreadTaskRunner({})->PostTask(
+          FROM_HERE,
+          base::BindOnce(&NativeDesktopMediaList::UpdateSourceThumbnail,
+                         media_list_, id, thumbnail));
+    }
+  }
+
+  // Protect against possible re-entrancy since OnCaptureResult can be invoked
+  // from within the call to CaptureFrame.
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&Worker::RefreshNextThumbnail,
+                                weak_factory_.GetWeakPtr()));
 }
 
 NativeDesktopMediaList::NativeDesktopMediaList(
@@ -351,8 +402,8 @@
 #endif
     thread_.task_runner()->PostTask(
         FROM_HERE, base::BindOnce(&Worker::RefreshThumbnails,
-                                  base::Unretained(worker_.get()), native_ids,
-                                  thumbnail_size_));
+                                  base::Unretained(worker_.get()),
+                                  std::move(native_ids), thumbnail_size_));
   }
 }
 
diff --git a/chrome/browser/net/load_timing_browsertest.cc b/chrome/browser/net/load_timing_browsertest.cc
index 0ef86ba0..7f0b823 100644
--- a/chrome/browser/net/load_timing_browsertest.cc
+++ b/chrome/browser/net/load_timing_browsertest.cc
@@ -131,13 +131,8 @@
   EXPECT_EQ(navigation_deltas.ssl_start, -1);
 }
 
-// TODO(crbug.com/1128033): Flaky on Windows and Linux.
-#if defined(OS_WIN) || defined(OS_LINUX)
-#define MAYBE_HTTPS DISABLED_HTTPS
-#else
-#define MAYBE_HTTPS HTTPS
-#endif
-IN_PROC_BROWSER_TEST_F(LoadTimingBrowserTest, MAYBE_HTTPS) {
+// TODO(crbug.com/1128033): flaky.
+IN_PROC_BROWSER_TEST_F(LoadTimingBrowserTest, DISABLED_HTTPS) {
   net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
   https_server.AddDefaultHandlers();
   ASSERT_TRUE(https_server.Start());
diff --git a/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer_unittest.cc
index 3d31eb1..6c6d689b 100644
--- a/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer_unittest.cc
+++ b/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer_unittest.cc
@@ -7,24 +7,19 @@
 #include "base/memory/ptr_util.h"
 #include "chrome/browser/page_load_metrics/observers/page_load_metrics_observer_test_harness.h"
 #include "components/page_load_metrics/browser/page_load_tracker.h"
+#include "components/translate/core/browser/mock_translate_metrics_logger.h"
 #include "components/translate/core/browser/translate_metrics_logger.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
-class MockTranslateMetricsLogger : public translate::TranslateMetricsLogger {
- public:
-  MOCK_METHOD1(OnPageLoadStart, void(bool));
-  MOCK_METHOD1(OnForegroundChange, void(bool));
-  MOCK_METHOD1(RecordMetrics, void(bool));
-};
-
-// Wraps the above MockTranslateMetricsLogger so that test can retain a pointer
-// to the MockTranslateMetricsLogger after the TranslatePageLoadMetricsObserver
-// is done with it.
+// Wraps a MockTranslateMetricsLogger so that test can retain a pointer to the
+// MockTranslateMetricsLogger after the TranslatePageLoadMetricsObserver is done
+// with it.
 class MockTranslateMetricsLoggerContainer
     : public translate::TranslateMetricsLogger {
  public:
   explicit MockTranslateMetricsLoggerContainer(
-      MockTranslateMetricsLogger* mock_translate_metrics_logger)
+      translate::testing::MockTranslateMetricsLogger*
+          mock_translate_metrics_logger)
       : mock_translate_metrics_logger_(mock_translate_metrics_logger) {}
 
   void OnPageLoadStart(bool is_foreground) override {
@@ -39,8 +34,15 @@
     mock_translate_metrics_logger_->RecordMetrics(is_final);
   }
 
+  void LogRankerMetrics(translate::RankerDecision ranker_decision,
+                        uint32_t ranker_version) override {
+    mock_translate_metrics_logger_->LogRankerMetrics(ranker_decision,
+                                                     ranker_version);
+  }
+
  private:
-  MockTranslateMetricsLogger* mock_translate_metrics_logger_;  // Weak.
+  translate::testing::MockTranslateMetricsLogger*
+      mock_translate_metrics_logger_;  // Weak.
 };
 
 class TranslatePageLoadMetricsObserverTest
@@ -51,12 +53,13 @@
 
     // Creates the MockTranslateMetricsLogger that will be used for this test.
     mock_translate_metrics_logger_ =
-        std::make_unique<MockTranslateMetricsLogger>();
+        std::make_unique<translate::testing::MockTranslateMetricsLogger>();
   }
 
   void RegisterObservers(page_load_metrics::PageLoadTracker* tracker) override {
-    MockTranslateMetricsLogger* raw_mock_translate_metrics_logger =
-        mock_translate_metrics_logger_.get();
+    translate::testing::MockTranslateMetricsLogger*
+        raw_mock_translate_metrics_logger =
+            mock_translate_metrics_logger_.get();
 
     // Wraps the raw pointer in a container.
     std::unique_ptr<MockTranslateMetricsLoggerContainer>
@@ -68,14 +71,16 @@
         std::move(mock_translate_metrics_logger_container)));
   }
 
-  MockTranslateMetricsLogger& mock_translate_metrics_logger() const {
+  translate::testing::MockTranslateMetricsLogger&
+  mock_translate_metrics_logger() const {
     return *mock_translate_metrics_logger_;
   }
 
  private:
   // This is the TranslateMetricsLoggers used in a test.It is owned by the
   // TranslatePageLoadMetricsObserverTest.
-  std::unique_ptr<MockTranslateMetricsLogger> mock_translate_metrics_logger_;
+  std::unique_ptr<translate::testing::MockTranslateMetricsLogger>
+      mock_translate_metrics_logger_;
 };
 
 TEST_F(TranslatePageLoadMetricsObserverTest, SinglePageLoad) {
diff --git a/chrome/browser/performance_manager/decorators/process_priority_aggregator.cc b/chrome/browser/performance_manager/decorators/process_priority_aggregator.cc
index 00db484..bdeda07 100644
--- a/chrome/browser/performance_manager/decorators/process_priority_aggregator.cc
+++ b/chrome/browser/performance_manager/decorators/process_priority_aggregator.cc
@@ -4,9 +4,9 @@
 
 #include "chrome/browser/performance_manager/decorators/process_priority_aggregator.h"
 
-#include "components/performance_manager/graph/frame_node_impl.h"
 #include "components/performance_manager/graph/node_attached_data_impl.h"
 #include "components/performance_manager/graph/process_node_impl.h"
+#include "components/performance_manager/public/execution_context/execution_context_registry.h"
 #include "components/performance_manager/public/graph/node_data_describer_registry.h"
 
 namespace performance_manager {
@@ -113,54 +113,50 @@
 }
 
 ProcessPriorityAggregator::ProcessPriorityAggregator() = default;
+
 ProcessPriorityAggregator::~ProcessPriorityAggregator() = default;
 
-void ProcessPriorityAggregator::OnFrameNodeAdded(const FrameNode* frame_node) {
-  auto* process_node = ProcessNodeImpl::FromNode(frame_node->GetProcessNode());
-  DataImpl* data = DataImpl::Get(process_node);
-  data->Increment(frame_node->GetPriorityAndReason().priority());
-  // This is a nop if the priority didn't actually change.
-  process_node->set_priority(data->GetPriority());
-}
-
-void ProcessPriorityAggregator::OnBeforeFrameNodeRemoved(
-    const FrameNode* frame_node) {
-  auto* process_node = ProcessNodeImpl::FromNode(frame_node->GetProcessNode());
-  DataImpl* data = DataImpl::Get(process_node);
-  data->Decrement(frame_node->GetPriorityAndReason().priority());
-  // This is a nop if the priority didn't actually change.
-  process_node->set_priority(data->GetPriority());
-}
-
-void ProcessPriorityAggregator::OnPriorityAndReasonChanged(
-    const FrameNode* frame_node,
-    const PriorityAndReason& previous_value) {
-  // If the priority itself didn't change then ignore this notification.
-  const PriorityAndReason& new_value = frame_node->GetPriorityAndReason();
-  if (new_value.priority() == previous_value.priority())
-    return;
-
-  // Update the distinct frame priority counts, and set the process priority
-  // accordingly.
-  auto* process_node = ProcessNodeImpl::FromNode(frame_node->GetProcessNode());
-  DataImpl* data = DataImpl::Get(process_node);
-  data->Decrement(previous_value.priority());
-  data->Increment(new_value.priority());
-  // This is a nop if the priority didn't actually change.
-  process_node->set_priority(data->GetPriority());
+void ProcessPriorityAggregator::OnBeforeGraphDestroyed(Graph* graph) {
+  auto* registry =
+      execution_context::ExecutionContextRegistry::GetFromGraph(graph);
+  if (registry && registry->HasObserver(this))
+    registry->RemoveObserver(this);
 }
 
 void ProcessPriorityAggregator::OnPassedToGraph(Graph* graph) {
-  graph->AddFrameNodeObserver(this);
-  graph->AddProcessNodeObserver(this);
+  graph->AddGraphObserver(this);
   graph->GetNodeDataDescriberRegistry()->RegisterDescriber(this,
                                                            kDescriberName);
+  graph->AddProcessNodeObserver(this);
+
+  auto* registry =
+      execution_context::ExecutionContextRegistry::GetFromGraph(graph);
+  // We expect the registry to exist before we are passed to the graph.
+  DCHECK(registry);
+  registry->AddObserver(this);
 }
 
 void ProcessPriorityAggregator::OnTakenFromGraph(Graph* graph) {
-  graph->GetNodeDataDescriberRegistry()->UnregisterDescriber(this);
+  // Call OnBeforeGraphDestroyed as well. This unregisters us from the
+  // ExecutionContextRegistry in case we're being removed from the graph prior
+  // to its destruction.
+  OnBeforeGraphDestroyed(graph);
+
   graph->RemoveProcessNodeObserver(this);
-  graph->RemoveFrameNodeObserver(this);
+  graph->GetNodeDataDescriberRegistry()->UnregisterDescriber(this);
+  graph->RemoveGraphObserver(this);
+}
+
+base::Value ProcessPriorityAggregator::DescribeProcessNodeData(
+    const ProcessNode* node) const {
+  DataImpl* data = DataImpl::Get(ProcessNodeImpl::FromNode(node));
+  if (data == nullptr)
+    return base::Value();
+
+  base::Value ret(base::Value::Type::DICTIONARY);
+  ret.SetIntKey("user_visible_count", data->user_visible_count_);
+  ret.SetIntKey("user_blocking_count", data->user_blocking_count_);
+  return ret;
 }
 
 void ProcessPriorityAggregator::OnProcessNodeAdded(
@@ -182,16 +178,40 @@
 #endif
 }
 
-base::Value ProcessPriorityAggregator::DescribeProcessNodeData(
-    const ProcessNode* node) const {
-  DataImpl* data = DataImpl::Get(ProcessNodeImpl::FromNode(node));
-  if (data == nullptr)
-    return base::Value();
+void ProcessPriorityAggregator::OnExecutionContextAdded(
+    const execution_context::ExecutionContext* ec) {
+  auto* process_node = ProcessNodeImpl::FromNode(ec->GetProcessNode());
+  DataImpl* data = DataImpl::Get(process_node);
+  data->Increment(ec->GetPriorityAndReason().priority());
+  // This is a nop if the priority didn't actually change.
+  process_node->set_priority(data->GetPriority());
+}
 
-  base::Value ret(base::Value::Type::DICTIONARY);
-  ret.SetIntKey("user_visible_count", data->user_visible_count_);
-  ret.SetIntKey("user_blocking_count", data->user_blocking_count_);
-  return ret;
+void ProcessPriorityAggregator::OnBeforeExecutionContextRemoved(
+    const execution_context::ExecutionContext* ec) {
+  auto* process_node = ProcessNodeImpl::FromNode(ec->GetProcessNode());
+  DataImpl* data = DataImpl::Get(process_node);
+  data->Decrement(ec->GetPriorityAndReason().priority());
+  // This is a nop if the priority didn't actually change.
+  process_node->set_priority(data->GetPriority());
+}
+
+void ProcessPriorityAggregator::OnPriorityAndReasonChanged(
+    const execution_context::ExecutionContext* ec,
+    const PriorityAndReason& previous_value) {
+  // If the priority itself didn't change then ignore this notification.
+  const PriorityAndReason& new_value = ec->GetPriorityAndReason();
+  if (new_value.priority() == previous_value.priority())
+    return;
+
+  // Update the distinct frame priority counts, and set the process priority
+  // accordingly.
+  auto* process_node = ProcessNodeImpl::FromNode(ec->GetProcessNode());
+  DataImpl* data = DataImpl::Get(process_node);
+  data->Decrement(previous_value.priority());
+  data->Increment(new_value.priority());
+  // This is a nop if the priority didn't actually change.
+  process_node->set_priority(data->GetPriority());
 }
 
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/decorators/process_priority_aggregator.h b/chrome/browser/performance_manager/decorators/process_priority_aggregator.h
index 3ddf9c2f..9b829d92 100644
--- a/chrome/browser/performance_manager/decorators/process_priority_aggregator.h
+++ b/chrome/browser/performance_manager/decorators/process_priority_aggregator.h
@@ -5,7 +5,7 @@
 #ifndef CHROME_BROWSER_PERFORMANCE_MANAGER_DECORATORS_PROCESS_PRIORITY_AGGREGATOR_H_
 #define CHROME_BROWSER_PERFORMANCE_MANAGER_DECORATORS_PROCESS_PRIORITY_AGGREGATOR_H_
 
-#include "components/performance_manager/public/graph/frame_node.h"
+#include "components/performance_manager/public/execution_context/execution_context.h"
 #include "components/performance_manager/public/graph/graph.h"
 #include "components/performance_manager/public/graph/node_data_describer.h"
 #include "components/performance_manager/public/graph/page_node.h"
@@ -19,32 +19,41 @@
 // priority as an aggregate of the priorities of all executions contexts (frames
 // and workers) it hosts. A process will inherit the priority of the highest
 // priority context that it hosts.
-class ProcessPriorityAggregator : public FrameNode::ObserverDefaultImpl,
-                                  public GraphOwnedDefaultImpl,
-                                  public NodeDataDescriberDefaultImpl,
-                                  public ProcessNode::ObserverDefaultImpl {
+class ProcessPriorityAggregator
+    : public GraphObserver,
+      public GraphOwnedDefaultImpl,
+      public NodeDataDescriberDefaultImpl,
+      public ProcessNode::ObserverDefaultImpl,
+      public execution_context::ExecutionContextObserverDefaultImpl {
  public:
   class Data;
 
   ProcessPriorityAggregator();
   ~ProcessPriorityAggregator() override;
 
-  // FrameNodeObserver implementation:
-  void OnFrameNodeAdded(const FrameNode* frame_node) override;
-  void OnBeforeFrameNodeRemoved(const FrameNode* frame_node) override;
-  void OnPriorityAndReasonChanged(
-      const FrameNode* frame_node,
-      const PriorityAndReason& previous_value) override;
+  // GraphObserver implementation:
+  void OnBeforeGraphDestroyed(Graph* graph) override;
 
   // GraphOwned implementation:
   void OnPassedToGraph(Graph* graph) override;
   void OnTakenFromGraph(Graph* graph) override;
 
+  // NodeDataDescriber implementation:
+  base::Value DescribeProcessNodeData(const ProcessNode* node) const override;
+
   // ProcessNodeObserver implementation:
   void OnProcessNodeAdded(const ProcessNode* process_node) override;
   void OnBeforeProcessNodeRemoved(const ProcessNode* process_node) override;
 
-  base::Value DescribeProcessNodeData(const ProcessNode* node) const override;
+  // ExecutionContextObserver implementation:
+  void OnExecutionContextAdded(
+      const execution_context::ExecutionContext* ec) override;
+  void OnBeforeExecutionContextRemoved(
+      const execution_context::ExecutionContext* ec) override;
+  void OnPriorityAndReasonChanged(
+      const execution_context::ExecutionContext* ec,
+      const execution_context_priority::PriorityAndReason& previous_value)
+      override;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ProcessPriorityAggregator);
diff --git a/chrome/browser/performance_manager/decorators/process_priority_aggregator_unittest.cc b/chrome/browser/performance_manager/decorators/process_priority_aggregator_unittest.cc
index 79ea6748..95a1a83 100644
--- a/chrome/browser/performance_manager/decorators/process_priority_aggregator_unittest.cc
+++ b/chrome/browser/performance_manager/decorators/process_priority_aggregator_unittest.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/performance_manager/decorators/process_priority_aggregator.h"
 
 #include "base/memory/ptr_util.h"
+#include "components/performance_manager/execution_context/execution_context_registry_impl.h"
 #include "components/performance_manager/graph/frame_node_impl.h"
 #include "components/performance_manager/graph/process_node_impl.h"
 #include "components/performance_manager/test_support/graph_test_harness.h"
@@ -22,6 +23,8 @@
 class ProcessPriorityAggregatorTest : public GraphTestHarness {
  public:
   void SetUp() override {
+    graph()->PassToGraph(
+        std::make_unique<execution_context::ExecutionContextRegistryImpl>());
     ppa_ = new ProcessPriorityAggregator();
     graph()->PassToGraph(base::WrapUnique(ppa_));
   }
@@ -40,19 +43,22 @@
 }  // namespace
 
 TEST_F(ProcessPriorityAggregatorTest, ProcessAggregation) {
-  MockMultiplePagesWithMultipleProcessesGraph mock_graph(graph());
+  MockMultiplePagesAndWorkersWithMultipleProcessesGraph mock_graph(graph());
 
   auto& proc1 = mock_graph.process;
   auto& proc2 = mock_graph.other_process;
   auto& frame1_1 = mock_graph.frame;
   auto& frame1_2 = mock_graph.other_frame;
   auto& frame2_1 = mock_graph.child_frame;
+  auto& worker1 = mock_graph.worker;
+  auto& worker2 = mock_graph.other_worker;
 
   EXPECT_EQ(base::TaskPriority::LOWEST, proc1->priority());
   EXPECT_EQ(base::TaskPriority::LOWEST, proc2->priority());
   ExpectPriorityCounts(proc1.get(), 0, 0);
   ExpectPriorityCounts(proc2.get(), 0, 0);
 
+  // Set the priority of a frame in process 1 to USER_VISIBLE.
   frame1_1->SetPriorityAndReason(
       PriorityAndReason(base::TaskPriority::USER_VISIBLE, kReason));
   EXPECT_EQ(base::TaskPriority::USER_VISIBLE, proc1->priority());
@@ -60,6 +66,7 @@
   ExpectPriorityCounts(proc1.get(), 1, 0);
   ExpectPriorityCounts(proc2.get(), 0, 0);
 
+  // Set the priority of a frame in process 2 to USER_VISIBLE.
   frame2_1->SetPriorityAndReason(
       PriorityAndReason(base::TaskPriority::USER_VISIBLE, kReason));
   EXPECT_EQ(base::TaskPriority::USER_VISIBLE, proc1->priority());
@@ -67,6 +74,8 @@
   ExpectPriorityCounts(proc1.get(), 1, 0);
   ExpectPriorityCounts(proc2.get(), 1, 0);
 
+  // Set the priority of another frame in process 1 to USER_BLOCKING. This
+  // overwrites the vote from the first frame.
   frame1_2->SetPriorityAndReason(
       PriorityAndReason(base::TaskPriority::USER_BLOCKING, kReason));
   EXPECT_EQ(base::TaskPriority::USER_BLOCKING, proc1->priority());
@@ -74,13 +83,35 @@
   ExpectPriorityCounts(proc1.get(), 1, 1);
   ExpectPriorityCounts(proc2.get(), 1, 0);
 
+  // Set the priority of a worker in process 2 to USER_BLOCKING. This overwrites
+  // the vote from the sole frame in this process.
+  worker2->SetPriorityAndReason(
+      PriorityAndReason(base::TaskPriority::USER_BLOCKING, kReason));
+  EXPECT_EQ(base::TaskPriority::USER_BLOCKING, proc1->priority());
+  EXPECT_EQ(base::TaskPriority::USER_BLOCKING, proc2->priority());
+  ExpectPriorityCounts(proc1.get(), 1, 1);
+  ExpectPriorityCounts(proc2.get(), 1, 1);
+
+  // Reduces the priority of the second frame in process 1 to USER_VISIBLE. Now
+  // both frames in this process are at USER_VISIBLE.
   frame1_2->SetPriorityAndReason(
       PriorityAndReason(base::TaskPriority::USER_VISIBLE, kReason));
   EXPECT_EQ(base::TaskPriority::USER_VISIBLE, proc1->priority());
+  EXPECT_EQ(base::TaskPriority::USER_BLOCKING, proc2->priority());
+  ExpectPriorityCounts(proc1.get(), 2, 0);
+  ExpectPriorityCounts(proc2.get(), 1, 1);
+
+  // Reduces the priority of the worker in process 2 to LOWEST. The highest
+  // execution context priority of that process is now due to the sole frame.
+  worker2->SetPriorityAndReason(
+      PriorityAndReason(base::TaskPriority::LOWEST, kReason));
+  EXPECT_EQ(base::TaskPriority::USER_VISIBLE, proc1->priority());
   EXPECT_EQ(base::TaskPriority::USER_VISIBLE, proc2->priority());
   ExpectPriorityCounts(proc1.get(), 2, 0);
   ExpectPriorityCounts(proc2.get(), 1, 0);
 
+  // Reduces the priority of the sole frame in process 2 to LOWEST. All
+  // execution contexts in this process are now at LOWEST.
   frame2_1->SetPriorityAndReason(
       PriorityAndReason(base::TaskPriority::LOWEST, kReason));
   EXPECT_EQ(base::TaskPriority::USER_VISIBLE, proc1->priority());
@@ -88,6 +119,8 @@
   ExpectPriorityCounts(proc1.get(), 2, 0);
   ExpectPriorityCounts(proc2.get(), 0, 0);
 
+  // Reduces the priority of the first frame in process 1 to LOWEST. The highest
+  // execution priority of that process is now due to the second frame.
   frame1_1->SetPriorityAndReason(
       PriorityAndReason(base::TaskPriority::LOWEST, kReason));
   EXPECT_EQ(base::TaskPriority::USER_VISIBLE, proc1->priority());
@@ -95,12 +128,32 @@
   ExpectPriorityCounts(proc1.get(), 1, 0);
   ExpectPriorityCounts(proc2.get(), 0, 0);
 
+  // Reduces the priority of the second frame in process 1 to LOWEST. All
+  // execution contexts in this process are now at LOWEST.
   frame1_2->SetPriorityAndReason(
       PriorityAndReason(base::TaskPriority::LOWEST, kReason));
   EXPECT_EQ(base::TaskPriority::LOWEST, proc1->priority());
   EXPECT_EQ(base::TaskPriority::LOWEST, proc2->priority());
   ExpectPriorityCounts(proc1.get(), 0, 0);
   ExpectPriorityCounts(proc2.get(), 0, 0);
+
+  // Set the priority of the worker in process 1. It is the execution context
+  // with the highest priority and thus dictates the priority of this process.
+  worker1->SetPriorityAndReason(
+      PriorityAndReason(base::TaskPriority::USER_VISIBLE, kReason));
+  EXPECT_EQ(base::TaskPriority::USER_VISIBLE, proc1->priority());
+  EXPECT_EQ(base::TaskPriority::LOWEST, proc2->priority());
+  ExpectPriorityCounts(proc1.get(), 1, 0);
+  ExpectPriorityCounts(proc2.get(), 0, 0);
+
+  // Reduces the priority of the worker in process 1 to LOWEST. All execution
+  // contexts in this process are now at LOWEST.
+  worker1->SetPriorityAndReason(
+      PriorityAndReason(base::TaskPriority::LOWEST, kReason));
+  EXPECT_EQ(base::TaskPriority::LOWEST, proc1->priority());
+  EXPECT_EQ(base::TaskPriority::LOWEST, proc2->priority());
+  ExpectPriorityCounts(proc1.get(), 0, 0);
+  ExpectPriorityCounts(proc2.get(), 0, 0);
 }
 
 }  // namespace performance_manager
diff --git a/chrome/browser/prefetch/search_prefetch/field_trial_settings.cc b/chrome/browser/prefetch/search_prefetch/field_trial_settings.cc
index 028d561..42124da 100644
--- a/chrome/browser/prefetch/search_prefetch/field_trial_settings.cc
+++ b/chrome/browser/prefetch/search_prefetch/field_trial_settings.cc
@@ -48,3 +48,8 @@
                                              "error_backoff_duration_ms",
                                              60000));
 }
+
+bool SearchPrefetchOnlyFetchDefaultMatch() {
+  return base::GetFieldTrialParamByFeatureAsBool(
+      kSearchPrefetchServicePrefetching, "only_prefetch_default_match", false);
+}
diff --git a/chrome/browser/prefetch/search_prefetch/field_trial_settings.h b/chrome/browser/prefetch/search_prefetch/field_trial_settings.h
index 6a00843..cf5c495 100644
--- a/chrome/browser/prefetch/search_prefetch/field_trial_settings.h
+++ b/chrome/browser/prefetch/search_prefetch/field_trial_settings.h
@@ -28,4 +28,8 @@
 // service to stop prefetching responses.
 base::TimeDelta SearchPrefetchErrorBackoffDuration();
 
+// Only prefetch results when they are the top match and the default match.
+// Nothing is prefetched if the default match is not prefetchable.
+bool SearchPrefetchOnlyFetchDefaultMatch();
+
 #endif  // CHROME_BROWSER_PREFETCH_SEARCH_PREFETCH_FIELD_TRIAL_SETTINGS_H_
diff --git a/chrome/browser/prefetch/search_prefetch/search_prefetch_service.cc b/chrome/browser/prefetch/search_prefetch/search_prefetch_service.cc
index 38ce5bf..56cb290 100644
--- a/chrome/browser/prefetch/search_prefetch/search_prefetch_service.cc
+++ b/chrome/browser/prefetch/search_prefetch/search_prefetch_service.cc
@@ -11,6 +11,8 @@
 #include "chrome/browser/prefetch/search_prefetch/prefetched_response_container.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
+#include "components/omnibox/browser/autocomplete_controller.h"
+#include "components/omnibox/browser/base_search_provider.h"
 #include "components/search_engines/template_url_service.h"
 #include "components/variations/net/variations_http_headers.h"
 #include "content/public/browser/browser_context.h"
@@ -247,3 +249,23 @@
 void SearchPrefetchService::ReportError() {
   last_error_time_ticks_ = base::TimeTicks::Now();
 }
+
+void SearchPrefetchService::OnResultChanged(
+    AutocompleteController* controller) {
+  const auto& result = controller->result();
+  const auto* default_match = result.default_match();
+
+  // One arm of the experiment only prefetches the top match when it is default.
+  if (SearchPrefetchOnlyFetchDefaultMatch()) {
+    if (default_match && BaseSearchProvider::ShouldPrefetch(*default_match)) {
+      MaybePrefetchURL(default_match->destination_url);
+    }
+    return;
+  }
+
+  for (const auto& match : result) {
+    if (BaseSearchProvider::ShouldPrefetch(match)) {
+      MaybePrefetchURL(match.destination_url);
+    }
+  }
+}
diff --git a/chrome/browser/prefetch/search_prefetch/search_prefetch_service.h b/chrome/browser/prefetch/search_prefetch/search_prefetch_service.h
index 6387d3a5..764dbaa 100644
--- a/chrome/browser/prefetch/search_prefetch/search_prefetch_service.h
+++ b/chrome/browser/prefetch/search_prefetch/search_prefetch_service.h
@@ -19,6 +19,8 @@
 class GURL;
 class PrefetchedResponseContainer;
 
+class AutocompleteController;
+
 enum class SearchPrefetchStatus {
   // The request is on the network and may move to any other state.
   kInFlight = 1,
@@ -36,6 +38,9 @@
   SearchPrefetchService(const SearchPrefetchService&) = delete;
   SearchPrefetchService& operator=(const SearchPrefetchService&) = delete;
 
+  // Called when |controller| has updated information.
+  void OnResultChanged(AutocompleteController* controller);
+
   // Returns whether the prefetch started or not.
   bool MaybePrefetchURL(const GURL& url);
 
diff --git a/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc b/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
index add77181..46862c4 100644
--- a/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
+++ b/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
@@ -6,15 +6,23 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
+#include "chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.h"
 #include "chrome/browser/prefetch/search_prefetch/field_trial_settings.h"
 #include "chrome/browser/prefetch/search_prefetch/search_prefetch_service.h"
 #include "chrome/browser/prefetch/search_prefetch/search_prefetch_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
 #include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/location_bar/location_bar.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/search_test_utils.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "components/omnibox/browser/autocomplete_input.h"
+#include "components/omnibox/browser/autocomplete_match.h"
+#include "components/omnibox/browser/autocomplete_provider.h"
+#include "components/omnibox/browser/omnibox_popup_model.h"
+#include "components/omnibox/browser/omnibox_view.h"
 #include "components/prefs/pref_service.h"
 #include "components/search_engines/template_url_service.h"
 #include "content/public/browser/browser_task_traits.h"
@@ -29,6 +37,14 @@
 #include "net/test/embedded_test_server/http_response.h"
 #include "url/gurl.h"
 
+namespace {
+constexpr char kSuggestDomain[] = "suggest.com";
+constexpr char kSearchDomain[] = "search.com";
+constexpr char kOmniboxSuggestPrefetchQuery[] = "porgs";
+constexpr char kOmniboxSuggestPrefetchSecondItemQuery[] = "porgsandwich";
+constexpr char kOmniboxSuggestNonPrefetchQuery[] = "puffins";
+}  // namespace
+
 class SearchPrefetchBaseBrowserTest : public InProcessBrowserTest {
  public:
   SearchPrefetchBaseBrowserTest() {
@@ -39,12 +55,21 @@
         base::BindRepeating(&SearchPrefetchBaseBrowserTest::HandleSearchRequest,
                             base::Unretained(this)));
     EXPECT_TRUE(search_server_->Start());
+
+    search_suggest_server_ = std::make_unique<net::EmbeddedTestServer>(
+        net::EmbeddedTestServer::TYPE_HTTPS);
+    search_suggest_server_->ServeFilesFromSourceDirectory("chrome/test/data");
+    search_suggest_server_->RegisterRequestHandler(base::BindRepeating(
+        &SearchPrefetchBaseBrowserTest::HandleSearchSuggestRequest,
+        base::Unretained(this)));
+    EXPECT_TRUE(search_suggest_server_->Start());
   }
 
   void SetUpOnMainThread() override {
     InProcessBrowserTest::SetUpOnMainThread();
 
-    host_resolver()->AddRule("search.test", "127.0.0.1");
+    host_resolver()->AddRule(kSearchDomain, "127.0.0.1");
+    host_resolver()->AddRule(kSuggestDomain, "127.0.0.1");
 
     TemplateURLService* model =
         TemplateURLServiceFactory::GetForProfile(browser()->profile());
@@ -53,9 +78,12 @@
     ASSERT_TRUE(model->loaded());
 
     TemplateURLData data;
-    data.SetShortName(base::ASCIIToUTF16("search.test"));
+    data.SetShortName(base::ASCIIToUTF16(kSearchDomain));
     data.SetKeyword(data.short_name());
     data.SetURL(GetSearchServerQueryURL("{searchTerms}").spec());
+    data.suggestions_url =
+        search_suggest_server_->GetURL(kSuggestDomain, "/?q={searchTerms}")
+            .spec();
 
     TemplateURL* template_url = model->Add(std::make_unique<TemplateURL>(data));
     ASSERT_TRUE(template_url);
@@ -83,11 +111,11 @@
   }
 
   GURL GetSearchServerQueryURL(const std::string& path) const {
-    return search_server_->GetURL("search.test", "/search_page.html?q=" + path);
+    return search_server_->GetURL(kSearchDomain, "/search_page.html?q=" + path);
   }
 
   GURL GetSearchServerQueryURLWithNoQuery(const std::string& path) const {
-    return search_server_->GetURL("search.test", path);
+    return search_server_->GetURL(kSearchDomain, path);
   }
 
   void WaitUntilStatusChanges(base::string16 search_terms) {
@@ -102,6 +130,21 @@
     }
   }
 
+  void WaitUntilStatusChangesTo(base::string16 search_terms,
+                                SearchPrefetchStatus status) {
+    auto* search_prefetch_service =
+        SearchPrefetchServiceFactory::GetForProfile(browser()->profile());
+    while (!search_prefetch_service
+                ->GetSearchPrefetchStatusForTesting(search_terms)
+                .has_value() ||
+           status != search_prefetch_service
+                         ->GetSearchPrefetchStatusForTesting(search_terms)
+                         .value()) {
+      base::RunLoop run_loop;
+      run_loop.RunUntilIdle();
+    }
+  }
+
   content::WebContents* GetWebContents() const {
     return browser()->tab_strip_model()->GetActiveWebContents();
   }
@@ -123,6 +166,8 @@
     run_loop.Run();
   }
 
+  void set_phi_is_one(bool phi_is_one) { phi_is_one_ = phi_is_one; }
+
  private:
   std::unique_ptr<net::test_server::HttpResponse> HandleSearchRequest(
       const net::test_server::HttpRequest& request) {
@@ -173,13 +218,74 @@
     }
   }
 
+  std::unique_ptr<net::test_server::HttpResponse> HandleSearchSuggestRequest(
+      const net::test_server::HttpRequest& request) {
+    // |content| is a json request that contains the search suggest response.
+    // The first item is the query (not used), the second is the results list,
+    // the third is descriptions, fifth is an extra data dictionary. The
+    // google:clientdata contains "phi" which is the prefetch index (i.e., which
+    // suggest can be prefetched).
+    std::string content = "";
+
+    if (request.GetURL().spec().find(kOmniboxSuggestPrefetchQuery) !=
+        std::string::npos) {
+      if (phi_is_one_) {
+        content = R"([
+      "porgs",
+      ["porgs","porgsandwich"],
+      ["", ""],
+      [],
+      {
+        "google:clientdata": {
+          "phi": 1
+        }
+      }])";
+      } else {
+        content = R"([
+      "porgs",
+      ["porgs","porgsandwich"],
+      ["", ""],
+      [],
+      {
+        "google:clientdata": {
+          "phi": 0
+        }
+      }])";
+      }
+    }
+
+    if (request.GetURL().spec().find(kOmniboxSuggestNonPrefetchQuery) !=
+        std::string::npos) {
+      content = R"([
+      "puffins",
+      ["puffins","puffinsalad"],
+      ["", ""],
+      [],
+      {}])";
+    }
+
+    std::unique_ptr<net::test_server::BasicHttpResponse> resp =
+        std::make_unique<net::test_server::BasicHttpResponse>();
+    resp->set_code(net::HTTP_OK);
+    resp->set_content_type("application/json");
+    resp->set_content(content);
+    return resp;
+  }
+
   std::vector<net::test_server::HttpRequest> search_server_requests_;
   std::unique_ptr<net::EmbeddedTestServer> search_server_;
 
+  std::unique_ptr<net::EmbeddedTestServer> search_suggest_server_;
+
   bool should_hang_requests_ = false;
 
   size_t search_server_request_count_ = 0;
   size_t search_server_prefetch_request_count_ = 0;
+
+  // Sets the prefetch index to be 1 instead of 0, making the second result
+  // prefetchable, but marking the first result as not prefetchable (must be
+  // used with |kkOmniboxSuggestPrefetchQuery|).
+  bool phi_is_one_ = false;
 };
 
 class SearchPrefetchServiceDisabledBrowserTest
@@ -494,6 +600,131 @@
       GetSearchServerQueryURL("other_query")));
 }
 
+IN_PROC_BROWSER_TEST_F(SearchPrefetchServiceEnabledBrowserTest,
+                       OmniboxEditTriggersPrefetch) {
+  auto* search_prefetch_service =
+      SearchPrefetchServiceFactory::GetForProfile(browser()->profile());
+  std::string search_terms = kOmniboxSuggestPrefetchQuery;
+
+  // Trigger an omnibox suggest fetch that has a prefetch hint.
+  AutocompleteInput input(
+      base::ASCIIToUTF16(search_terms), metrics::OmniboxEventProto::BLANK,
+      ChromeAutocompleteSchemeClassifier(browser()->profile()));
+  LocationBar* location_bar = browser()->window()->GetLocationBar();
+  OmniboxView* omnibox = location_bar->GetOmniboxView();
+  AutocompleteController* autocomplete_controller =
+      omnibox->model()->autocomplete_controller();
+
+  // Prevent the stop timer from killing the hints fetch early.
+  autocomplete_controller->SetStartStopTimerDurationForTesting(
+      base::TimeDelta::FromSeconds(10));
+  autocomplete_controller->Start(input);
+
+  ui_test_utils::WaitForAutocompleteDone(browser());
+  EXPECT_TRUE(autocomplete_controller->done());
+
+  WaitUntilStatusChangesTo(base::ASCIIToUTF16(search_terms),
+                           SearchPrefetchStatus::kSuccessfullyCompleted);
+  auto prefetch_status =
+      search_prefetch_service->GetSearchPrefetchStatusForTesting(
+          base::ASCIIToUTF16(search_terms));
+  ASSERT_TRUE(prefetch_status.has_value());
+  EXPECT_EQ(SearchPrefetchStatus::kSuccessfullyCompleted,
+            prefetch_status.value());
+
+  ui_test_utils::NavigateToURL(browser(),
+                               GetSearchServerQueryURL(search_terms));
+
+  auto inner_html = GetDocumentInnerHTML();
+
+  EXPECT_FALSE(base::Contains(inner_html, "regular"));
+  EXPECT_TRUE(base::Contains(inner_html, "prefetch"));
+}
+
+IN_PROC_BROWSER_TEST_F(SearchPrefetchServiceEnabledBrowserTest,
+                       OmniboxEditDoesNotTriggersPrefetch) {
+  auto* search_prefetch_service =
+      SearchPrefetchServiceFactory::GetForProfile(browser()->profile());
+  std::string search_terms = kOmniboxSuggestNonPrefetchQuery;
+
+  // Trigger an omnibox suggest fetch that does not have a prefetch hint.
+  AutocompleteInput input(
+      base::ASCIIToUTF16(search_terms), metrics::OmniboxEventProto::BLANK,
+      ChromeAutocompleteSchemeClassifier(browser()->profile()));
+  LocationBar* location_bar = browser()->window()->GetLocationBar();
+  OmniboxView* omnibox = location_bar->GetOmniboxView();
+  AutocompleteController* autocomplete_controller =
+      omnibox->model()->autocomplete_controller();
+
+  // Prevent the stop timer from killing the hints fetch early.
+  autocomplete_controller->SetStartStopTimerDurationForTesting(
+      base::TimeDelta::FromSeconds(10));
+  autocomplete_controller->Start(input);
+
+  ui_test_utils::WaitForAutocompleteDone(browser());
+  EXPECT_TRUE(autocomplete_controller->done());
+
+  WaitForDuration(base::TimeDelta::FromMilliseconds(100));
+
+  auto prefetch_status =
+      search_prefetch_service->GetSearchPrefetchStatusForTesting(
+          base::ASCIIToUTF16(search_terms));
+  EXPECT_FALSE(prefetch_status.has_value());
+  ui_test_utils::NavigateToURL(browser(),
+                               GetSearchServerQueryURL(search_terms));
+
+  auto inner_html = GetDocumentInnerHTML();
+
+  EXPECT_TRUE(base::Contains(inner_html, "regular"));
+  EXPECT_FALSE(base::Contains(inner_html, "prefetch"));
+}
+
+IN_PROC_BROWSER_TEST_F(SearchPrefetchServiceEnabledBrowserTest,
+                       OmniboxEditTriggersPrefetchForSecondMatch) {
+  // phi being set to one causes the order of prefetch suggest to be different.
+  // This should still prefetch a result for the |kOmniboxSuggestPrefetchQuery|.
+  set_phi_is_one(true);
+  auto* search_prefetch_service =
+      SearchPrefetchServiceFactory::GetForProfile(browser()->profile());
+  std::string search_terms = kOmniboxSuggestPrefetchQuery;
+
+  // Trigger an omnibox suggest fetch that has a prefetch hint.
+  AutocompleteInput input(
+      base::ASCIIToUTF16(search_terms), metrics::OmniboxEventProto::BLANK,
+      ChromeAutocompleteSchemeClassifier(browser()->profile()));
+  LocationBar* location_bar = browser()->window()->GetLocationBar();
+  OmniboxView* omnibox = location_bar->GetOmniboxView();
+  AutocompleteController* autocomplete_controller =
+      omnibox->model()->autocomplete_controller();
+
+  // Prevent the stop timer from killing the hints fetch early.
+  autocomplete_controller->SetStartStopTimerDurationForTesting(
+      base::TimeDelta::FromSeconds(10));
+  autocomplete_controller->Start(input);
+
+  ui_test_utils::WaitForAutocompleteDone(browser());
+  EXPECT_TRUE(autocomplete_controller->done());
+
+  WaitUntilStatusChangesTo(
+      base::ASCIIToUTF16(kOmniboxSuggestPrefetchSecondItemQuery),
+      SearchPrefetchStatus::kSuccessfullyCompleted);
+  auto prefetch_status =
+      search_prefetch_service->GetSearchPrefetchStatusForTesting(
+          base::ASCIIToUTF16(kOmniboxSuggestPrefetchSecondItemQuery));
+  ASSERT_TRUE(prefetch_status.has_value());
+  EXPECT_EQ(SearchPrefetchStatus::kSuccessfullyCompleted,
+            prefetch_status.value());
+
+  ui_test_utils::NavigateToURL(
+      browser(),
+      GetSearchServerQueryURL(kOmniboxSuggestPrefetchSecondItemQuery));
+
+  auto inner_html = GetDocumentInnerHTML();
+
+  EXPECT_FALSE(base::Contains(inner_html, "regular"));
+  EXPECT_TRUE(base::Contains(inner_html, "prefetch"));
+}
+
 class SearchPrefetchServiceZeroCacheTimeBrowserTest
     : public SearchPrefetchBaseBrowserTest {
  public:
@@ -602,3 +833,60 @@
   EXPECT_TRUE(search_prefetch_service->MaybePrefetchURL(
       GetSearchServerQueryURL("other_query")));
 }
+
+class SearchPrefetchServiceDefaultMatchOnlyBrowserTest
+    : public SearchPrefetchBaseBrowserTest {
+ public:
+  SearchPrefetchServiceDefaultMatchOnlyBrowserTest() {
+    feature_list_.InitWithFeaturesAndParameters(
+        {{kSearchPrefetchServicePrefetching,
+          {{"only_prefetch_default_match", "true"}}},
+         {{kSearchPrefetchService}, {}}},
+        {});
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(SearchPrefetchServiceDefaultMatchOnlyBrowserTest,
+                       OmniboxEditDoesNotTriggerPrefetchForSecondMatch) {
+  // phi being set to one causes the order of prefetch suggest to be different.
+  // This should still prefetch a result for the |kOmniboxSuggestPrefetchQuery|.
+  set_phi_is_one(true);
+  auto* search_prefetch_service =
+      SearchPrefetchServiceFactory::GetForProfile(browser()->profile());
+  std::string search_terms = kOmniboxSuggestPrefetchQuery;
+
+  // Trigger an omnibox suggest fetch that does not have a prefetch hint.
+  AutocompleteInput input(
+      base::ASCIIToUTF16(search_terms), metrics::OmniboxEventProto::BLANK,
+      ChromeAutocompleteSchemeClassifier(browser()->profile()));
+  LocationBar* location_bar = browser()->window()->GetLocationBar();
+  OmniboxView* omnibox = location_bar->GetOmniboxView();
+  AutocompleteController* autocomplete_controller =
+      omnibox->model()->autocomplete_controller();
+
+  // Prevent the stop timer from killing the hints fetch early.
+  autocomplete_controller->SetStartStopTimerDurationForTesting(
+      base::TimeDelta::FromSeconds(10));
+  autocomplete_controller->Start(input);
+
+  ui_test_utils::WaitForAutocompleteDone(browser());
+  EXPECT_TRUE(autocomplete_controller->done());
+
+  WaitForDuration(base::TimeDelta::FromMilliseconds(100));
+
+  auto prefetch_status =
+      search_prefetch_service->GetSearchPrefetchStatusForTesting(
+          base::ASCIIToUTF16(kOmniboxSuggestPrefetchSecondItemQuery));
+  EXPECT_FALSE(prefetch_status.has_value());
+  ui_test_utils::NavigateToURL(
+      browser(),
+      GetSearchServerQueryURL(kOmniboxSuggestPrefetchSecondItemQuery));
+
+  auto inner_html = GetDocumentInnerHTML();
+
+  EXPECT_TRUE(base::Contains(inner_html, "regular"));
+  EXPECT_FALSE(base::Contains(inner_html, "prefetch"));
+}
diff --git a/chrome/browser/prerender/isolated/isolated_prerender_proxying_url_loader_factory.cc b/chrome/browser/prerender/isolated/isolated_prerender_proxying_url_loader_factory.cc
index 5a38415..237682f 100644
--- a/chrome/browser/prerender/isolated/isolated_prerender_proxying_url_loader_factory.cc
+++ b/chrome/browser/prerender/isolated/isolated_prerender_proxying_url_loader_factory.cc
@@ -212,7 +212,7 @@
     const network::URLLoaderCompletionStatus& status) {
   if (on_complete_metrics_callback_) {
     std::move(on_complete_metrics_callback_)
-        .Run(redirect_chain_[0], head_->Clone(), status);
+        .Run(redirect_chain_[0], head_ ? head_->Clone() : nullptr, status);
   }
   MaybeReportResourceLoadSuccess(status);
   target_client_->OnComplete(status);
diff --git a/chrome/browser/renderer_context_menu/quick_answers_menu_observer.cc b/chrome/browser/renderer_context_menu/quick_answers_menu_observer.cc
index 03c6b11..358f97b 100644
--- a/chrome/browser/renderer_context_menu/quick_answers_menu_observer.cc
+++ b/chrome/browser/renderer_context_menu/quick_answers_menu_observer.cc
@@ -12,12 +12,10 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/time/time.h"
 #include "build/branding_buildflags.h"
-#include "chrome/app/chrome_command_ids.h"
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chromeos/components/quick_answers/quick_answers_model.h"
-#include "chromeos/components/quick_answers/utils/quick_answers_metrics.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "chromeos/services/assistant/public/cpp/assistant_service.h"
 #include "components/language/core/browser/pref_names.h"
@@ -28,45 +26,16 @@
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents.h"
 #include "ui/base/l10n/l10n_util.h"
-#include "ui/base/models/image_model.h"
-#include "ui/base/models/simple_menu_model.h"
 #include "ui/gfx/text_constants.h"
 #include "ui/gfx/text_elider.h"
 
 namespace {
 
 using chromeos::quick_answers::Context;
-using chromeos::quick_answers::QuickAnswer;
 using chromeos::quick_answers::QuickAnswersClient;
-using chromeos::quick_answers::QuickAnswersRequest;
-using chromeos::quick_answers::ResultType;
 
-// TODO(llin): Update the placeholder after finalizing on the design.
-constexpr char kLoadingPlaceholder[] = "Loading...";
-constexpr char kNoResult[] = "See result in Assistant";
-constexpr char kNetworkError[] = "Cannot connect to internet.";
-
-constexpr size_t kMaxDisplayTextLength = 70;
 constexpr int kMaxSurroundingTextLength = 300;
 
-base::string16 TruncateString(const std::string& text) {
-  return gfx::TruncateString(base::UTF8ToUTF16(text), kMaxDisplayTextLength,
-                             gfx::WORD_BREAK);
-}
-
-base::string16 SanitizeText(const base::string16& text) {
-  base::string16 updated_text;
-  // Escape Ampersands.
-  base::ReplaceChars(text, base::ASCIIToUTF16("&"), base::ASCIIToUTF16("&&"),
-                     &updated_text);
-
-  // Remove invalid chars.
-  base::ReplaceChars(updated_text, base::kWhitespaceUTF16,
-                     base::ASCIIToUTF16(" "), &updated_text);
-
-  return updated_text;
-}
-
 }  // namespace
 
 QuickAnswersMenuObserver::QuickAnswersMenuObserver(
@@ -96,52 +65,10 @@
 
 QuickAnswersMenuObserver::~QuickAnswersMenuObserver() = default;
 
-void QuickAnswersMenuObserver::InitMenu(
-    const content::ContextMenuParams& params) {
-  if (IsRichUiEnabled())
-    return;
-
-  if (!is_eligible_ || !proxy_ || !quick_answers_client_)
-    return;
-
-  if (params.is_editable)
-    return;
-
-  auto selected_text = base::UTF16ToUTF8(SanitizeText(params.selection_text));
-  if (selected_text.empty())
-    return;
-
-  // Add Quick Answer Menu item.
-  // TODO(llin): Update the menu item after finalizing on the design.
-  auto truncated_text = TruncateString(selected_text);
-#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-  proxy_->AddMenuItemWithIcon(
-      IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_QUERY, truncated_text,
-      ui::ImageModel::FromVectorIcon(kAssistantIcon, /*color_id=*/-1,
-                                     ui::SimpleMenuModel::kDefaultIconSize));
-#else
-  proxy_->AddMenuItem(IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_QUERY,
-                      truncated_text);
-#endif
-  proxy_->AddMenuItem(IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_ANSWER,
-                      base::UTF8ToUTF16(kLoadingPlaceholder));
-  proxy_->AddSeparator();
-
-  // Fetch Quick Answer.
-  QuickAnswersRequest request;
-  request.selected_text = selected_text;
-  request.context.device_properties.language = GetDeviceLanguage();
-  query_ = request.selected_text;
-  quick_answers_client_->SendRequest(request);
-}
-
 void QuickAnswersMenuObserver::OnContextMenuShown(
     const content::ContextMenuParams& params,
     const gfx::Rect& bounds_in_screen) {
-  if (!IsRichUiEnabled())
-    return;
-
-  if (!quick_answers_controller_)
+  if (!quick_answers_controller_ || !is_eligible_)
     return;
 
   if (params.is_editable)
@@ -174,96 +101,20 @@
 }
 
 void QuickAnswersMenuObserver::OnMenuClosed() {
-  if (!IsRichUiEnabled())
-    return;
-
   if (!quick_answers_controller_)
     return;
 
   quick_answers_controller_->DismissQuickAnswers(!is_other_command_executed_);
 }
 
-bool QuickAnswersMenuObserver::IsCommandIdSupported(int command_id) {
-  return (command_id == IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_QUERY ||
-          command_id == IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_ANSWER);
-}
-
-bool QuickAnswersMenuObserver::IsCommandIdChecked(int command_id) {
-  return false;
-}
-
-bool QuickAnswersMenuObserver::IsCommandIdEnabled(int command_id) {
-  return command_id == IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_QUERY;
-}
-
-bool QuickAnswersMenuObserver::IsRichUiEnabled() {
-  return chromeos::features::IsQuickAnswersRichUiEnabled();
-}
-
-void QuickAnswersMenuObserver::ExecuteCommand(int command_id) {
-  if (command_id == IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_QUERY) {
-    SendAssistantQuery(query_);
-
-    quick_answers_client_->OnQuickAnswerClick(
-        quick_answer_ ? quick_answer_->result_type : ResultType::kNoResult);
-  }
-}
-
 void QuickAnswersMenuObserver::CommandWillBeExecuted(int command_id) {
-  is_other_command_executed_ =
-      command_id != IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_QUERY;
-}
-
-void QuickAnswersMenuObserver::OnQuickAnswerReceived(
-    std::unique_ptr<QuickAnswer> quick_answer) {
-  if (quick_answer) {
-    proxy_->UpdateMenuItem(IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_ANSWER,
-                           /*enabled=*/false,
-                           /*hidden=*/false,
-                           /*title=*/
-                           TruncateString(quick_answer->primary_answer.empty()
-                                              ? kNoResult
-                                              : quick_answer->primary_answer));
-
-    if (!quick_answer->secondary_answer.empty()) {
-      proxy_->UpdateMenuItem(
-          IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_QUERY,
-          /*enabled=*/true,
-          /*hidden=*/false,
-          /*title=*/TruncateString(quick_answer->secondary_answer));
-    }
-  } else {
-    proxy_->UpdateMenuItem(IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_ANSWER,
-                           /*enabled=*/false,
-                           /*hidden=*/false,
-                           /*title=*/TruncateString(kNoResult));
-  }
-  quick_answer_ = std::move(quick_answer);
-}
-
-void QuickAnswersMenuObserver::OnNetworkError() {
-  proxy_->UpdateMenuItem(IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_ANSWER,
-                         /*enabled=*/false,
-                         /*hidden=*/false,
-                         /*title=*/TruncateString(kNetworkError));
+  is_other_command_executed_ = true;
 }
 
 void QuickAnswersMenuObserver::OnEligibilityChanged(bool eligible) {
   is_eligible_ = eligible;
 }
 
-void QuickAnswersMenuObserver::SetQuickAnswerClientForTesting(
-    std::unique_ptr<chromeos::quick_answers::QuickAnswersClient>
-        quick_answers_client) {
-  quick_answers_client_ = std::move(quick_answers_client);
-}
-
-void QuickAnswersMenuObserver::SendAssistantQuery(const std::string& query) {
-  ash::AssistantInteractionController::Get()->StartTextInteraction(
-      query, /*allow_tts=*/false,
-      chromeos::assistant::AssistantQuerySource::kQuickAnswers);
-}
-
 std::string QuickAnswersMenuObserver::GetDeviceLanguage() {
   return l10n_util::GetLanguage(g_browser_process->GetApplicationLocale());
 }
diff --git a/chrome/browser/renderer_context_menu/quick_answers_menu_observer.h b/chrome/browser/renderer_context_menu/quick_answers_menu_observer.h
index 1009fc2..1b00a3c 100644
--- a/chrome/browser/renderer_context_menu/quick_answers_menu_observer.h
+++ b/chrome/browser/renderer_context_menu/quick_answers_menu_observer.h
@@ -30,11 +30,6 @@
   ~QuickAnswersMenuObserver() override;
 
   // RenderViewContextMenuObserver implementation.
-  void InitMenu(const content::ContextMenuParams& params) override;
-  bool IsCommandIdSupported(int command_id) override;
-  bool IsCommandIdChecked(int command_id) override;
-  bool IsCommandIdEnabled(int command_id) override;
-  void ExecuteCommand(int command_id) override;
   void CommandWillBeExecuted(int command_id) override;
   void OnContextMenuShown(const content::ContextMenuParams& params,
                           const gfx::Rect& bounds_in_screen) override;
@@ -44,17 +39,11 @@
 
   // QuickAnswersDelegate implementation.
   void OnQuickAnswerReceived(
-      std::unique_ptr<chromeos::quick_answers::QuickAnswer> answer) override;
+      std::unique_ptr<chromeos::quick_answers::QuickAnswer> answer) override {}
   void OnEligibilityChanged(bool eligible) override;
-  void OnNetworkError() override;
-
-  void SetQuickAnswerClientForTesting(
-      std::unique_ptr<chromeos::quick_answers::QuickAnswersClient>
-          quick_answers_client);
+  void OnNetworkError() override {}
 
  private:
-  bool IsRichUiEnabled();
-  void SendAssistantQuery(const std::string& query);
   std::string GetDeviceLanguage();
   void OnTextSurroundingSelectionAvailable(
       const std::string& selected_text,
@@ -72,13 +61,8 @@
   // locale, consents, etc).
   bool is_eligible_ = false;
 
-  // Query used to retrieve quick answer.
-  std::string query_;
-
   gfx::Rect bounds_in_screen_;
 
-  std::unique_ptr<chromeos::quick_answers::QuickAnswer> quick_answer_;
-
   ash::QuickAnswersController* quick_answers_controller_ = nullptr;
 
   // Whether commands other than quick answers is executed.
diff --git a/chrome/browser/renderer_context_menu/quick_answers_menu_observer_browsertest.cc b/chrome/browser/renderer_context_menu/quick_answers_menu_observer_browsertest.cc
index 1a4fef6f..303a0d7 100644
--- a/chrome/browser/renderer_context_menu/quick_answers_menu_observer_browsertest.cc
+++ b/chrome/browser/renderer_context_menu/quick_answers_menu_observer_browsertest.cc
@@ -27,15 +27,6 @@
 
 using testing::_;
 
-constexpr char kLongText[] =
-    "123456789101112131415161718192021222324252627282930313233343536373839404"
-    "\r\n\t1424344454647484950";
-constexpr char kLongAnswer[] =
-    "123456789101112131415161718192021222324252627282930313233343536373839404"
-    "1424344454647484950";
-constexpr char kTruncatedLongText[] =
-    "123456789101112131415161718192021222324252627282930313233343536373839…";
-
 class MockQuickAnswersClient : public QuickAnswersClient {
  public:
   MockQuickAnswersClient(network::mojom::URLLoaderFactory* url_loader_factory,
@@ -51,12 +42,12 @@
 };
 
 // A test class for Quick Answers. This test should be a browser test because it
-//// accesses resources.
+// accesses resources.
+// TODO(b/171579052): Add test cases for quick answers Rich UI.
 class QuickAnswersMenuObserverTest : public InProcessBrowserTest {
  public:
   QuickAnswersMenuObserverTest() {
-    feature_list_.InitWithFeatures({chromeos::features::kQuickAnswers},
-                                   {chromeos::features::kQuickAnswersRichUi});
+    feature_list_.InitAndEnableFeature(chromeos::features::kQuickAnswers);
   }
 
   QuickAnswersMenuObserverTest(const QuickAnswersMenuObserverTest&) = delete;
@@ -95,18 +86,6 @@
   QuickAnswersMenuObserver* observer() { return observer_.get(); }
 
  protected:
-  void VerifyMenuItems(int index,
-                       int expected_command_id,
-                       const std::string& expected_title,
-                       bool enabled) {
-    MockRenderViewContextMenu::MockMenuItem item;
-    menu()->GetMenuItem(index, &item);
-    EXPECT_EQ(expected_command_id, item.command_id);
-    EXPECT_EQ(base::UTF8ToUTF16(expected_title), item.title);
-    EXPECT_EQ(enabled, item.enabled);
-    EXPECT_FALSE(item.hidden);
-  }
-
   void MockQuickAnswerClient(const std::string expected_query) {
     std::unique_ptr<QuickAnswersRequest> expected_quick_answers_request =
         std::make_unique<QuickAnswersRequest>();
@@ -115,8 +94,6 @@
         *mock_quick_answers_cient_,
         SendRequest(QuickAnswersRequestEqual(*expected_quick_answers_request)))
         .Times(1);
-    observer_->SetQuickAnswerClientForTesting(
-        std::move(mock_quick_answers_cient_));
   }
 
   base::test::ScopedFeatureList feature_list_;
@@ -129,221 +106,14 @@
 
 }  // namespace
 
-IN_PROC_BROWSER_TEST_F(QuickAnswersMenuObserverTest, PlaceHolderMenuItems) {
-  MockQuickAnswerClient("sel");
-  InitMenu();
-
-  // Shows quick answers loading state.
-  ASSERT_EQ(3u, menu()->GetMenuSize());
-
-  // Verify the query menu item.
-  VerifyMenuItems(
-      /*index=*/0,
-      /*command_id=*/IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_QUERY,
-      /*expected_title=*/"sel",
-      /*enabled=*/true);
-  // Verify the answer menu item.
-  VerifyMenuItems(
-      /*index=*/1,
-      /*command_id=*/IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_ANSWER,
-      /*expected_title=*/"Loading...",
-      /*enabled=*/false);
-}
-
-IN_PROC_BROWSER_TEST_F(QuickAnswersMenuObserverTest,
-                       SanitizeAndTruncateSelectedText) {
-  MockQuickAnswerClient(
-      "123456789101112131415161718192021222324252627282930313233343536373839404"
-      "   1424344454647484950");
-
-  // Init Menu.
-  content::ContextMenuParams params;
-  static const base::string16 selected_text = base::ASCIIToUTF16(kLongText);
-  params.selection_text = selected_text;
-  observer_->InitMenu(params);
-
-  // Shows quick answers loading state.
-  ASSERT_EQ(3u, menu()->GetMenuSize());
-
-  // Verify the query menu item.
-  VerifyMenuItems(
-      /*index=*/0,
-      /*command_id=*/IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_QUERY,
-      /*expected_title=*/kTruncatedLongText,
-      /*enabled=*/true);
-  // Verify the answer menu item.
-  VerifyMenuItems(
-      /*index=*/1,
-      /*command_id=*/IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_ANSWER,
-      /*expected_title=*/"Loading...",
-      /*enabled=*/false);
-}
-
-IN_PROC_BROWSER_TEST_F(QuickAnswersMenuObserverTest, PrimaryAnswerOnly) {
-  MockQuickAnswerClient("sel");
-  InitMenu();
-
-  std::unique_ptr<QuickAnswer> quick_answer = std::make_unique<QuickAnswer>();
-  quick_answer->primary_answer = "primary answer";
-  observer_->OnQuickAnswerReceived(std::move(quick_answer));
-
-  // Verify that quick answer menu items is showing.
-  ASSERT_EQ(3u, menu()->GetMenuSize());
-
-  // Verify the query menu item.
-  VerifyMenuItems(
-      /*index=*/0,
-      /*command_id=*/IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_QUERY,
-      /*expected_title=*/"sel",
-      /*enabled=*/true);
-
-  // Verify the answer menu item.
-  VerifyMenuItems(
-      /*index=*/1,
-      /*command_id=*/IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_ANSWER,
-      /*expected_title=*/"primary answer",
-      /*enabled=*/false);
-}
-
-IN_PROC_BROWSER_TEST_F(QuickAnswersMenuObserverTest, SecondaryAnswerOnly) {
-  MockQuickAnswerClient("sel");
-  InitMenu();
-
-  std::unique_ptr<QuickAnswer> quick_answer = std::make_unique<QuickAnswer>();
-  quick_answer->secondary_answer = "secondary answer";
-  observer_->OnQuickAnswerReceived(std::move(quick_answer));
-
-  // Verify that quick answer menu items is showing.
-  ASSERT_EQ(3u, menu()->GetMenuSize());
-
-  // Verify the query menu item.
-  VerifyMenuItems(
-      /*index=*/0,
-      /*command_id=*/IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_QUERY,
-      /*expected_title=*/"secondary answer",
-      /*enabled=*/true);
-
-  // Verify the answer menu item.
-  VerifyMenuItems(
-      /*index=*/1,
-      /*command_id=*/IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_ANSWER,
-      /*expected_title=*/"See result in Assistant",
-      /*enabled=*/false);
-}
-
-IN_PROC_BROWSER_TEST_F(QuickAnswersMenuObserverTest,
-                       PrimaryAndSecondaryAnswer) {
-  MockQuickAnswerClient("sel");
-  InitMenu();
-
-  std::unique_ptr<QuickAnswer> quick_answer = std::make_unique<QuickAnswer>();
-  quick_answer->primary_answer = "primary answer";
-  quick_answer->secondary_answer = "secondary answer";
-  observer_->OnQuickAnswerReceived(std::move(quick_answer));
-
-  // Verify that quick answer menu items is showing.
-  ASSERT_EQ(3u, menu()->GetMenuSize());
-
-  // Verify the query menu item.
-  VerifyMenuItems(
-      /*index=*/0,
-      /*command_id=*/IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_QUERY,
-      /*expected_title=*/"secondary answer",
-      /*enabled=*/true);
-
-  // Verify the answer menu item.
-  VerifyMenuItems(
-      /*index=*/1,
-      /*command_id=*/IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_ANSWER,
-      /*expected_title=*/"primary answer",
-      /*enabled=*/false);
-}
-
-IN_PROC_BROWSER_TEST_F(QuickAnswersMenuObserverTest, TruncateLongAnswer) {
-  MockQuickAnswerClient("sel");
-  InitMenu();
-
-  std::unique_ptr<QuickAnswer> quick_answer = std::make_unique<QuickAnswer>();
-  quick_answer->primary_answer = kLongAnswer;
-  quick_answer->secondary_answer = kLongAnswer;
-  observer_->OnQuickAnswerReceived(std::move(quick_answer));
-
-  // Verify that quick answer menu items is showing.
-  ASSERT_EQ(3u, menu()->GetMenuSize());
-
-  // Verify the query menu item.
-  VerifyMenuItems(
-      /*index=*/0,
-      /*command_id=*/IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_QUERY,
-      /*expected_title=*/kTruncatedLongText,
-      /*enabled=*/true);
-
-  // Verify the answer menu item.
-  VerifyMenuItems(
-      /*index=*/1,
-      /*command_id=*/IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_ANSWER,
-      /*expected_title=*/kTruncatedLongText,
-      /*enabled=*/false);
-}
-
-IN_PROC_BROWSER_TEST_F(QuickAnswersMenuObserverTest, NoAnswer) {
-  MockQuickAnswerClient("sel");
-  InitMenu();
-
-  observer_->OnQuickAnswerReceived(nullptr);
-
-  // Verify that quick answer menu items is showing.
-  ASSERT_EQ(3u, menu()->GetMenuSize());
-
-  // Verify the query menu item.
-  VerifyMenuItems(
-      /*index=*/0,
-      /*command_id=*/IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_QUERY,
-      /*expected_title=*/"sel",
-      /*enabled=*/true);
-
-  // Verify the answer menu item.
-  VerifyMenuItems(
-      /*index=*/1,
-      /*command_id=*/IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_ANSWER,
-      /*expected_title=*/"See result in Assistant",
-      /*enabled=*/false);
-}
-
 IN_PROC_BROWSER_TEST_F(QuickAnswersMenuObserverTest, FeatureIneligible) {
   observer_->OnEligibilityChanged(false);
 
   // Verify that quick answer client is not called to fetch result.
   EXPECT_CALL(*mock_quick_answers_cient_, SendRequest(testing::_)).Times(0);
-  observer_->SetQuickAnswerClientForTesting(
-      std::move(mock_quick_answers_cient_));
 
   InitMenu();
 
   // Verify that no Quick Answer menu items shown.
   ASSERT_EQ(0u, menu()->GetMenuSize());
 }
-
-IN_PROC_BROWSER_TEST_F(QuickAnswersMenuObserverTest, NetworkError) {
-  MockQuickAnswerClient("sel");
-  InitMenu();
-
-  observer_->OnNetworkError();
-
-  // Verify that quick answer menu items is showing.
-  ASSERT_EQ(3u, menu()->GetMenuSize());
-
-  // Verify the query menu item.
-  VerifyMenuItems(
-      /*index=*/0,
-      /*command_id=*/IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_QUERY,
-      /*expected_title=*/"sel",
-      /*enabled=*/true);
-
-  // Verify the answer menu item.
-  VerifyMenuItems(
-      /*index=*/1,
-      /*command_id=*/IDC_CONTENT_CONTEXT_QUICK_ANSWERS_INLINE_ANSWER,
-      /*expected_title=*/"Cannot connect to internet.",
-      /*enabled=*/false);
-}
diff --git a/chrome/browser/resources/BUILD.gn b/chrome/browser/resources/BUILD.gn
index ecf438d..f63e599 100644
--- a/chrome/browser/resources/BUILD.gn
+++ b/chrome/browser/resources/BUILD.gn
@@ -76,7 +76,7 @@
       deps += [ "about_nacl:closure_compile" ]
     }
     if (enable_tab_search) {
-      deps += [ "tab_search:closure_compile" ]
+      deps += [ "tab_search_merge:closure_compile" ]
     }
     if (enable_webui_tab_strip) {
       deps += [ "tab_strip:closure_compile" ]
@@ -634,9 +634,9 @@
       "-E",
       "root_gen_dir=" + rebase_path(root_gen_dir, root_build_dir),
     ]
-    source = "tab_search/tab_search_resources.grd"
+    source = "tab_search_merge/tab_search_resources.grd"
     deps = [
-      "//chrome/browser/resources/tab_search:web_components",
+      "//chrome/browser/resources/tab_search_merge:web_components",
       "//chrome/browser/ui/webui/tab_search:mojo_bindings_js",
     ]
     defines = chrome_grit_defines
diff --git a/chrome/browser/resources/chromeos/login/BUILD.gn b/chrome/browser/resources/chromeos/login/BUILD.gn
index 4f2e722b..7ddffe24 100644
--- a/chrome/browser/resources/chromeos/login/BUILD.gn
+++ b/chrome/browser/resources/chromeos/login/BUILD.gn
@@ -39,7 +39,6 @@
     ":oobe_supervision_transition",
     ":oobe_update",
     ":oobe_welcome",
-    ":parental_handoff",
     ":recommend_apps",
     ":saml_confirm_password",
     ":sync_consent",
@@ -276,14 +275,6 @@
   ]
 }
 
-js_library("parental_handoff") {
-  deps = [
-    "components:login_screen_behavior",
-    "components:oobe_dialog_host_behavior",
-    "components:oobe_i18n_behavior",
-  ]
-}
-
 js_library("recommend_apps") {
   deps = [
     "components:login_screen_behavior",
diff --git a/chrome/browser/resources/chromeos/login/parental_handoff.html b/chrome/browser/resources/chromeos/login/parental_handoff.html
deleted file mode 100644
index 9ba88fa5..0000000
--- a/chrome/browser/resources/chromeos/login/parental_handoff.html
+++ /dev/null
@@ -1,37 +0,0 @@
-<!-- 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. -->
-
-<link rel="import" href="chrome://oobe/custom_elements.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-
-<dom-module id="parental-handoff">
-  <template>
-    <style include="oobe-dialog-host"></style>
-    <oobe-dialog id="parentalHandoffDialog" role="dialog" has-buttons
-        aria-label$="[[title_]]">
-      <hd-iron-icon icon1x="oobe-32:googleg" icon2x="oobe-64:googleg"
-          slot="oobe-icon">
-      </hd-iron-icon>
-      <h1 slot="title">
-        [[title_]]
-      </h1>
-      <div slot="subtitle">
-        [[subtitle_]]
-      </div>
-      <div slot="footer" class="flex layout vertical center center-justified">
-        <!-- TODO(yilkal): Replace the following image when the appropriate
-             image is ready to be used -->
-        <img srcset="images/1x/parental_control.svg 1x,
-            images/2x/parental_control.svg 2x" class="oobe-illustration"
-            alt$="[[title_]]">
-      </div>
-      <div slot="bottom-buttons" class="layout horizontal end-justified">
-        <oobe-next-button id="nextButton"
-            text-key="parentalHandoffDialogNextButton" class="focus-on-show"
-            inverse on-click="onNextButtonPressed_"></oobe-next-button>
-      </div>
-    </oobe-dialog>
-  </template>
-</dom-module>
diff --git a/chrome/browser/resources/chromeos/login/parental_handoff.js b/chrome/browser/resources/chromeos/login/parental_handoff.js
deleted file mode 100644
index cbe7e2c5..0000000
--- a/chrome/browser/resources/chromeos/login/parental_handoff.js
+++ /dev/null
@@ -1,68 +0,0 @@
-// 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.
-
-/**
- * @fileoverview Polymer element for Parental Handoff screen.
- */
-
-Polymer({
-  is: 'parental-handoff',
-
-  behaviors: [OobeI18nBehavior, OobeDialogHostBehavior, LoginScreenBehavior],
-
-  properties: {
-    /**
-     * THe title to be displayed
-     */
-    title_: {
-      type: String,
-      value: '',
-    },
-
-    /**
-     * The subtitle to be displayed.
-     */
-    subtitle_: {
-      type: String,
-      value: '',
-    },
-  },
-
-  /**
-   * Event handler that is invoked just before the frame is shown.
-   * @param {Object} data Screen init payload
-   */
-  onBeforeShow(data) {
-    if ('title' in data) {
-      this.title_ = data.title;
-    }
-
-    if ('subtitle' in data) {
-      this.subtitle_ = data.subtitle;
-    }
-  },
-
-  ready() {
-    this.initializeLoginScreen('ParentalHandoffScreen', {
-      resetAllowed: true,
-    });
-  },
-
-  /*
-   * Executed on language change.
-   */
-  updateLocalizedContent() {
-    this.i18nUpdateLocale();
-  },
-
-  /**
-   * On-tap event handler for Next button.
-   *
-   * @private
-   */
-  onNextButtonPressed_() {
-    this.userActed('next');
-  },
-
-});
diff --git a/chrome/browser/resources/chromeos/login/structure/components_common.html b/chrome/browser/resources/chromeos/login/structure/components_common.html
index 3d5680d0..6523a5b 100644
--- a/chrome/browser/resources/chromeos/login/structure/components_common.html
+++ b/chrome/browser/resources/chromeos/login/structure/components_common.html
@@ -54,7 +54,6 @@
 <include src="../family_link_notice.html">
 <include src="../user_creation.html">
 <include src="../screen_signin_fatal_error.html">
-<include src="../parental_handoff.html">
 
 <include src="components_[OOBE].html">
 
diff --git a/chrome/browser/resources/chromeos/login/structure/components_common.js b/chrome/browser/resources/chromeos/login/structure/components_common.js
index a0e132d..427f2aca 100644
--- a/chrome/browser/resources/chromeos/login/structure/components_common.js
+++ b/chrome/browser/resources/chromeos/login/structure/components_common.js
@@ -53,6 +53,5 @@
 // <include src="../family_link_notice.js">
 // <include src="../user_creation.js">
 // <include src="../screen_signin_fatal_error.js">
-// <include src="../parental_handoff.js">
 
 // <include src="components_[OOBE].js">
diff --git a/chrome/browser/resources/chromeos/login/structure/screens_common.html b/chrome/browser/resources/chromeos/login/structure/screens_common.html
index e80800f..934c0466 100644
--- a/chrome/browser/resources/chromeos/login/structure/screens_common.html
+++ b/chrome/browser/resources/chromeos/login/structure/screens_common.html
@@ -45,5 +45,3 @@
 </family-link-notice>
 <user-creation id="user-creation" class="step hidden">
 </user-creation>
-<parental-handoff id="parental-handoff" class="step hidden">
-</parental-handoff>
diff --git a/chrome/browser/resources/chromeos/multidevice_internals/BUILD.gn b/chrome/browser/resources/chromeos/multidevice_internals/BUILD.gn
index 70aadd8..e2ba892a 100644
--- a/chrome/browser/resources/chromeos/multidevice_internals/BUILD.gn
+++ b/chrome/browser/resources/chromeos/multidevice_internals/BUILD.gn
@@ -127,6 +127,7 @@
     ":quick_action_controller_form",
     ":types",
     "//ui/webui/resources/js:load_time_data.m",
+    "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
 }
 
diff --git a/chrome/browser/resources/chromeos/multidevice_internals/multidevice_phonehub_browser_proxy.js b/chrome/browser/resources/chromeos/multidevice_internals/multidevice_phonehub_browser_proxy.js
index c010a60..ebe348e 100644
--- a/chrome/browser/resources/chromeos/multidevice_internals/multidevice_phonehub_browser_proxy.js
+++ b/chrome/browser/resources/chromeos/multidevice_internals/multidevice_phonehub_browser_proxy.js
@@ -99,6 +99,21 @@
   setTetherStatus(tetherStatus) {
     chrome.send('setTetherStatus', [tetherStatus]);
   }
+
+  /**
+   * Resets should show onboarding UI for the real PhoneHubManager.
+   */
+  resetShouldShowOnboardingUi() {
+    chrome.send('resetShouldShowOnboardingUi');
+  }
+
+  /**
+   * Resets notification setup UI to not having been dismissed for the real
+   * PhoneHubManager.
+   */
+  resetHasNotificationSetupUiBeenDismissed() {
+    chrome.send('resetHasNotificationSetupUiBeenDismissed');
+  }
 }
 
 addSingletonGetter(MultidevicePhoneHubBrowserProxy);
diff --git a/chrome/browser/resources/chromeos/multidevice_internals/phonehub_tab.html b/chrome/browser/resources/chromeos/multidevice_internals/phonehub_tab.html
index 8064a9a..2f501323 100644
--- a/chrome/browser/resources/chromeos/multidevice_internals/phonehub_tab.html
+++ b/chrome/browser/resources/chromeos/multidevice_internals/phonehub_tab.html
@@ -24,6 +24,30 @@
     <cr-toggle checked="{{shouldEnableFakePhoneHubManager_}}">
     </cr-toggle>
   </div>
+  <template is="dom-if" if="[[!shouldEnableFakePhoneHubManager_]]" restamp>
+    <div class="cr-row">
+      <div class="cr-padded-text">
+        Click to undo Notification Setup UI user dismissal. Note that
+        Notification access must be revoked by the phone (Toggle "Google Play
+        Services" to off in Apps & notifications > Special app access >
+        Notification access.
+      </div>
+      <cr-button class="internals-button"
+          on-click="onResetHasNotificationSetupUiBeenDismissedButtonClick_">
+        Reset
+      </cr-button>
+    </div>
+    <div class="cr-row">
+      <div class="cr-padded-text">
+        Click to undo Onboarding UI user dismissal. Note that the user must
+        have not started the opt-in flow yet.
+      </div>
+      <cr-button class="internals-button"
+          on-click="onResetShouldShowOnboardingUiButtonClick_">
+        Reset
+      </cr-button>
+    </div>
+  </template>
   <template is="dom-if" if="[[shouldEnableFakePhoneHubManager_]]" restamp>
     <div class="cr-row">
       <div class="cr-padded-text">
diff --git a/chrome/browser/resources/chromeos/multidevice_internals/phonehub_tab.js b/chrome/browser/resources/chromeos/multidevice_internals/phonehub_tab.js
index 5a61733..980fc88 100644
--- a/chrome/browser/resources/chromeos/multidevice_internals/phonehub_tab.js
+++ b/chrome/browser/resources/chromeos/multidevice_internals/phonehub_tab.js
@@ -15,6 +15,7 @@
 import './quick_action_controller_form.js';
 
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
+import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
 import {flush, html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {MultidevicePhoneHubBrowserProxy} from './multidevice_phonehub_browser_proxy.js';
 import {FeatureStatus} from './types.js';
@@ -45,6 +46,10 @@
 
   _template: html`{__html_template__}`,
 
+  behaviors: [
+    WebUIListenerBehavior,
+  ],
+
   properties: {
     /** @private */
     isPhoneHubEnabled_: {
@@ -121,6 +126,13 @@
     this.browserProxy_ = MultidevicePhoneHubBrowserProxy.getInstance();
   },
 
+  /** @override */
+  attached() {
+    this.addWebUIListener(
+        'should-show-onboarding-ui-changed',
+        this.onShouldShowOnboardingUiChanged_.bind(this));
+  },
+
   /**
    * @return {boolean}
    * @private
@@ -190,6 +202,26 @@
     window.open('chrome://flags/#enable-phone-hub');
   },
 
+  /**
+   * @param {boolean} shouldShowOnboardingUi
+   * @private
+   */
+  onShouldShowOnboardingUiChanged_(shouldShowOnboardingUi) {
+    if (this.shouldShowOnboardingFlow_ !== shouldShowOnboardingUi) {
+      this.shouldShowOnboardingFlow_ = shouldShowOnboardingUi;
+    }
+  },
+
+  /** @private */
+  onResetHasNotificationSetupUiBeenDismissedButtonClick_() {
+    this.browserProxy_.resetHasNotificationSetupUiBeenDismissed();
+  },
+
+  /** @private */
+  onResetShouldShowOnboardingUiButtonClick_() {
+    this.browserProxy_.resetShouldShowOnboardingUi();
+  },
+
   /** @private */
   onShouldShowOnboardingFlowChanged_() {
     if (!this.shouldEnableFakePhoneHubManager_) {
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/BUILD.gn
index 7de06d7..064c870 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/BUILD.gn
@@ -62,6 +62,7 @@
     ":main_view",
     ":store",
     ":store_client",
+    "//ui/webui/resources/js/cr/ui:focus_without_ink",
   ]
 }
 
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/main_view.html b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/main_view.html
index 19dc0c8..e09cba4 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/main_view.html
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/main_view.html
@@ -5,6 +5,7 @@
 <link rel="import" href="store_client.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
 <link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
+<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
 
 <dom-module id="app-management-main-view">
   <template>
@@ -23,6 +24,7 @@
       <template is="dom-repeat" items="[[appList_]]" as="app">
         <app-management-app-item app="[[app]]">
           <cr-icon-button slot="right-content"
+              id$="app-subpage-button-[[app.id]]"
               class="subpage-arrow app-management-item-arrow"
               aria-label$="[[app.title]]"
               role="link"
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/main_view.js b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/main_view.js
index 28c4dba..23b975a 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/main_view.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/main_view.js
@@ -7,6 +7,7 @@
 
   behaviors: [
     app_management.StoreClient,
+    settings.RouteObserverBehavior,
   ],
 
   properties: {
@@ -41,6 +42,25 @@
   },
 
   /**
+   * @param {!settings.Route} route
+   * @param {!settings.Route} oldRoute
+   */
+  currentRouteChanged(route, oldRoute) {
+    if (route === settings.routes.APP_MANAGEMENT) {
+      const appId = app_management.Store.getInstance().data.selectedAppId;
+
+      // Expect this to be false the first time the "Manage your apps" page
+      // is requested as no app has been selected yet.
+      if (appId) {
+        const button = this.$$(`#app-subpage-button-${appId}`);
+        if (button) {
+          cr.ui.focusWithoutInk(button);
+        }
+      }
+    }
+  },
+
+  /**
    * @private
    * @param {Array<App>} appList
    * @return {boolean}
diff --git a/chrome/browser/resources/tab_search_merge/tab_search_resources.grd b/chrome/browser/resources/tab_search_merge/tab_search_resources.grd
index aa4978ea..cab83ff 100644
--- a/chrome/browser/resources/tab_search_merge/tab_search_resources.grd
+++ b/chrome/browser/resources/tab_search_merge/tab_search_resources.grd
@@ -13,7 +13,7 @@
   <release seq="1">
     <includes>
       <include name="IDR_APP_JS"
-               file="${root_gen_dir}/chrome/browser/resources/tab_search/app.js"
+               file="${root_gen_dir}/chrome/browser/resources/tab_search_merge/app.js"
                type="BINDATA"
                use_base_dir="false" />
       <include name="IDR_FUSE_JS"
@@ -29,7 +29,7 @@
                file="tab_search_api_proxy.js"
                type="BINDATA" />
       <include name="IDR_TAB_SEARCH_ITEM_JS"
-               file="${root_gen_dir}/chrome/browser/resources/tab_search/tab_search_item.js"
+               file="${root_gen_dir}/chrome/browser/resources/tab_search_merge/tab_search_item.js"
                type="BINDATA"
                use_base_dir="false"/>
       <include name="IDR_TAB_SEARCH_MOJO_LITE_JS"
@@ -40,7 +40,7 @@
                file="tab_search_page.html"
                type="BINDATA" />
       <include name="IDR_TAB_SEARCH_SEARCH_FIELD_JS"
-               file="${root_gen_dir}/chrome/browser/resources/tab_search/tab_search_search_field.js"
+               file="${root_gen_dir}/chrome/browser/resources/tab_search_merge/tab_search_search_field.js"
                type="BINDATA"
                use_base_dir="false" />
     </includes>
diff --git a/chrome/browser/speech/extension_api/tts_extension_api.cc b/chrome/browser/speech/extension_api/tts_extension_api.cc
index 588e4ced..2383347 100644
--- a/chrome/browser/speech/extension_api/tts_extension_api.cc
+++ b/chrome/browser/speech/extension_api/tts_extension_api.cc
@@ -333,7 +333,8 @@
     result_voices->Append(std::move(result_voice));
   }
 
-  return RespondNow(OneArgument(std::move(result_voices)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(result_voices))));
 }
 
 TtsAPI::TtsAPI(content::BrowserContext* context) {
diff --git a/chrome/browser/supervised_user/supervised_user_features.cc b/chrome/browser/supervised_user/supervised_user_features.cc
index 130db69..1c0ee95 100644
--- a/chrome/browser/supervised_user/supervised_user_features.cc
+++ b/chrome/browser/supervised_user/supervised_user_features.cc
@@ -4,8 +4,6 @@
 
 #include "chrome/browser/supervised_user/supervised_user_features.h"
 
-#include "base/feature_list.h"
-
 namespace supervised_users {
 
 const base::Feature kSupervisedUserIframeFilter{
@@ -17,9 +15,4 @@
 
 const base::Feature kEduCoexistenceFlowV2{"EduCoexistenceV2",
                                           base::FEATURE_DISABLED_BY_DEFAULT};
-
-bool IsEduCoexistenceFlowV2Enabled() {
-  return base::FeatureList::IsEnabled(kEduCoexistenceFlowV2);
-}
-
 }  // namespace supervised_users
diff --git a/chrome/browser/supervised_user/supervised_user_features.h b/chrome/browser/supervised_user/supervised_user_features.h
index 419381a..cc1212a2 100644
--- a/chrome/browser/supervised_user/supervised_user_features.h
+++ b/chrome/browser/supervised_user/supervised_user_features.h
@@ -15,8 +15,6 @@
 
 extern const base::Feature kEduCoexistenceFlowV2;
 
-bool IsEduCoexistenceFlowV2Enabled();
-
 }  // namespace supervised_users
 
 #endif  // CHROME_BROWSER_SUPERVISED_USER_SUPERVISED_USER_FEATURES_H_
diff --git a/chrome/browser/translate/android/translate_bridge.cc b/chrome/browser/translate/android/translate_bridge.cc
index e8bb18be..376fabba 100644
--- a/chrome/browser/translate/android/translate_bridge.cc
+++ b/chrome/browser/translate/android/translate_bridge.cc
@@ -133,6 +133,40 @@
   client->SetPredefinedTargetLanguage(translate_language);
 }
 
+static base::android::ScopedJavaLocalRef<jstring>
+JNI_TranslateBridge_GetOriginalLanguage(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& j_web_contents) {
+  content::WebContents* web_contents =
+      content::WebContents::FromJavaWebContents(j_web_contents);
+  ChromeTranslateClient* client =
+      ChromeTranslateClient::FromWebContents(web_contents);
+  DCHECK(client);
+  const std::string& original_language_code =
+      client->GetLanguageState().original_language();
+  DCHECK(!original_language_code.empty());
+  base::android::ScopedJavaLocalRef<jstring> j_original_language =
+      base::android::ConvertUTF8ToJavaString(env, original_language_code);
+  return j_original_language;
+}
+
+static base::android::ScopedJavaLocalRef<jstring>
+JNI_TranslateBridge_GetCurrentLanguage(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& j_web_contents) {
+  content::WebContents* web_contents =
+      content::WebContents::FromJavaWebContents(j_web_contents);
+  ChromeTranslateClient* client =
+      ChromeTranslateClient::FromWebContents(web_contents);
+  DCHECK(client);
+  const std::string& current_language_code =
+      client->GetLanguageState().current_language();
+  DCHECK(!current_language_code.empty());
+  base::android::ScopedJavaLocalRef<jstring> j_current_language =
+      base::android::ConvertUTF8ToJavaString(env, current_language_code);
+  return j_current_language;
+}
+
 // Returns the preferred target language to translate into for this user.
 static base::android::ScopedJavaLocalRef<jstring>
 JNI_TranslateBridge_GetTargetLanguage(JNIEnv* env) {
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 67242b05..96a5b7b 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -2306,8 +2306,6 @@
       "webui/chromeos/login/oobe_ui.h",
       "webui/chromeos/login/packaged_license_screen_handler.cc",
       "webui/chromeos/login/packaged_license_screen_handler.h",
-      "webui/chromeos/login/parental_handoff_screen_handler.cc",
-      "webui/chromeos/login/parental_handoff_screen_handler.h",
       "webui/chromeos/login/pin_setup_screen_handler.cc",
       "webui/chromeos/login/pin_setup_screen_handler.h",
       "webui/chromeos/login/recommend_apps_screen_handler.cc",
@@ -3623,6 +3621,8 @@
       "views/in_product_help/feature_promo_controller_views.h",
       "views/in_product_help/feature_promo_registry.cc",
       "views/in_product_help/feature_promo_registry.h",
+      "views/in_product_help/new_badge_label.cc",
+      "views/in_product_help/new_badge_label.h",
       "views/infobars/alternate_nav_infobar_view.cc",
       "views/infobars/alternate_nav_infobar_view.h",
       "views/infobars/confirm_infobar.cc",
diff --git a/chrome/browser/ui/ash/accessibility/accessibility_controller_client.cc b/chrome/browser/ui/ash/accessibility/accessibility_controller_client.cc
index d2e07f1..37c8cf9c 100644
--- a/chrome/browser/ui/ash/accessibility/accessibility_controller_client.cc
+++ b/chrome/browser/ui/ash/accessibility/accessibility_controller_client.cc
@@ -150,6 +150,12 @@
       ->RequestAutoclickScrollableBoundsForPoint(point_in_screen);
 }
 
+void AccessibilityControllerClient::MagnifierBoundsChanged(
+    const gfx::Rect& bounds_in_screen) {
+  chromeos::AccessibilityManager::Get()->MagnifierBoundsChanged(
+      bounds_in_screen);
+}
+
 void AccessibilityControllerClient::OnSwitchAccessDisabled() {
   chromeos::AccessibilityManager::Get()->OnSwitchAccessDisabled();
 }
diff --git a/chrome/browser/ui/ash/accessibility/accessibility_controller_client.h b/chrome/browser/ui/ash/accessibility/accessibility_controller_client.h
index a274fba..5a167465 100644
--- a/chrome/browser/ui/ash/accessibility/accessibility_controller_client.h
+++ b/chrome/browser/ui/ash/accessibility/accessibility_controller_client.h
@@ -32,6 +32,7 @@
   void RequestSelectToSpeakStateChange() override;
   void RequestAutoclickScrollableBoundsForPoint(
       gfx::Point& point_in_screen) override;
+  void MagnifierBoundsChanged(const gfx::Rect& bounds_in_screen) override;
   void OnSwitchAccessDisabled() override;
 
  private:
diff --git a/chrome/browser/ui/views/autofill/payments/local_card_migration_bubble_views.cc b/chrome/browser/ui/views/autofill/payments/local_card_migration_bubble_views.cc
index c6f345b..4e8f69c 100644
--- a/chrome/browser/ui/views/autofill/payments/local_card_migration_bubble_views.cc
+++ b/chrome/browser/ui/views/autofill/payments/local_card_migration_bubble_views.cc
@@ -60,6 +60,10 @@
   SetAcceptCallback(
       base::BindOnce(&LocalCardMigrationBubbleViews::OnDialogAccepted,
                      base::Unretained(this)));
+
+  SetShowCloseButton(true);
+  set_fixed_width(views::LayoutProvider::Get()->GetDistanceMetric(
+      views::DISTANCE_BUBBLE_PREFERRED_WIDTH));
 }
 
 void LocalCardMigrationBubbleViews::Show(DisplayReason reason) {
@@ -91,13 +95,6 @@
     controller_->OnCancelButtonClicked();
 }
 
-gfx::Size LocalCardMigrationBubbleViews::CalculatePreferredSize() const {
-  const int width = ChromeLayoutProvider::Get()->GetDistanceMetric(
-                        views::DISTANCE_BUBBLE_PREFERRED_WIDTH) -
-                    margins().width();
-  return gfx::Size(width, GetHeightForWidth(width));
-}
-
 void LocalCardMigrationBubbleViews::AddedToWidget() {
   auto title_container = std::make_unique<views::View>();
   title_container->SetLayoutManager(std::make_unique<views::BoxLayout>(
@@ -139,10 +136,6 @@
   GetBubbleFrameView()->SetTitleView(std::move(title_container));
 }
 
-bool LocalCardMigrationBubbleViews::ShouldShowCloseButton() const {
-  return true;
-}
-
 base::string16 LocalCardMigrationBubbleViews::GetWindowTitle() const {
   return controller_ ? l10n_util::GetStringUTF16(
                            IDS_AUTOFILL_LOCAL_CARD_MIGRATION_BUBBLE_TITLE)
diff --git a/chrome/browser/ui/views/autofill/payments/local_card_migration_bubble_views.h b/chrome/browser/ui/views/autofill/payments/local_card_migration_bubble_views.h
index e3eb1c4..e77f383 100644
--- a/chrome/browser/ui/views/autofill/payments/local_card_migration_bubble_views.h
+++ b/chrome/browser/ui/views/autofill/payments/local_card_migration_bubble_views.h
@@ -35,9 +35,7 @@
   void Hide() override;
 
   // LocationBarBubbleDelegateView:
-  gfx::Size CalculatePreferredSize() const override;
   void AddedToWidget() override;
-  bool ShouldShowCloseButton() const override;
   base::string16 GetWindowTitle() const override;
   void WindowClosing() override;
   void OnWidgetClosing(views::Widget* widget) override;
diff --git a/chrome/browser/ui/views/autofill/payments/save_card_bubble_views.cc b/chrome/browser/ui/views/autofill/payments/save_card_bubble_views.cc
index ba2d258..e81ef78 100644
--- a/chrome/browser/ui/views/autofill/payments/save_card_bubble_views.cc
+++ b/chrome/browser/ui/views/autofill/payments/save_card_bubble_views.cc
@@ -52,13 +52,17 @@
                                          SaveCardBubbleController* controller)
     : LocationBarBubbleDelegateView(anchor_view, web_contents),
       controller_(controller) {
+  DCHECK(controller);
   SetButtonLabel(ui::DIALOG_BUTTON_OK, controller->GetAcceptButtonText());
   SetButtonLabel(ui::DIALOG_BUTTON_CANCEL, controller->GetDeclineButtonText());
   SetCancelCallback(base::BindOnce(&SaveCardBubbleViews::OnDialogCancelled,
                                    base::Unretained(this)));
   SetAcceptCallback(base::BindOnce(&SaveCardBubbleViews::OnDialogAccepted,
                                    base::Unretained(this)));
-  DCHECK(controller);
+
+  SetShowCloseButton(true);
+  set_fixed_width(views::LayoutProvider::Get()->GetDistanceMetric(
+      views::DISTANCE_BUBBLE_PREFERRED_WIDTH));
   chrome::RecordDialogCreation(chrome::DialogIdentifier::SAVE_CARD);
 }
 
@@ -92,13 +96,6 @@
     controller_->OnCancelButton();
 }
 
-gfx::Size SaveCardBubbleViews::CalculatePreferredSize() const {
-  const int width = ChromeLayoutProvider::Get()->GetDistanceMetric(
-                        views::DISTANCE_BUBBLE_PREFERRED_WIDTH) -
-                    margins().width();
-  return gfx::Size(width, GetHeightForWidth(width));
-}
-
 void SaveCardBubbleViews::AddedToWidget() {
   // Use a custom title container if offering to upload a server card.
   // Done when this view is added to the widget, so the bubble frame
@@ -110,10 +107,6 @@
       std::make_unique<TitleWithIconAndSeparatorView>(GetWindowTitle()));
 }
 
-bool SaveCardBubbleViews::ShouldShowCloseButton() const {
-  return true;
-}
-
 base::string16 SaveCardBubbleViews::GetWindowTitle() const {
   return controller_ ? controller_->GetWindowTitle() : base::string16();
 }
diff --git a/chrome/browser/ui/views/autofill/payments/save_card_bubble_views.h b/chrome/browser/ui/views/autofill/payments/save_card_bubble_views.h
index e8c349e..dfd89f3 100644
--- a/chrome/browser/ui/views/autofill/payments/save_card_bubble_views.h
+++ b/chrome/browser/ui/views/autofill/payments/save_card_bubble_views.h
@@ -39,12 +39,8 @@
   // SaveCardBubbleView:
   void Hide() override;
 
-  // views::View:
-  gfx::Size CalculatePreferredSize() const override;
+  // LocationBarBubbleDelegateView:
   void AddedToWidget() override;
-
-  // views::WidgetDelegate:
-  bool ShouldShowCloseButton() const override;
   base::string16 GetWindowTitle() const override;
   void WindowClosing() override;
   void OnWidgetClosing(views::Widget* widget) override;
@@ -86,7 +82,7 @@
   // Attributes IDs to the dialog's DialogDelegate-supplied buttons.
   void AssignIdsToDialogButtons();
 
-  // views::BubbleDialogDelegateView:
+  // LocationBarBubbleDelegateView:
   void Init() override;
 
   void OnDialogAccepted();
diff --git a/chrome/browser/ui/views/autofill/payments/save_upi_offer_bubble_views.cc b/chrome/browser/ui/views/autofill/payments/save_upi_offer_bubble_views.cc
index da7c92c..1711a05 100644
--- a/chrome/browser/ui/views/autofill/payments/save_upi_offer_bubble_views.cc
+++ b/chrome/browser/ui/views/autofill/payments/save_upi_offer_bubble_views.cc
@@ -20,12 +20,16 @@
       controller_(controller) {
   DCHECK(controller_);
 
+  SetTitle(l10n_util::GetStringUTF16(IDS_AUTOFILL_SAVE_UPI_PROMPT_TITLE));
   SetButtonLabel(
       ui::DIALOG_BUTTON_OK,
       l10n_util::GetStringUTF16(IDS_AUTOFILL_SAVE_UPI_PROMPT_ACCEPT));
   SetButtonLabel(
       ui::DIALOG_BUTTON_CANCEL,
       l10n_util::GetStringUTF16(IDS_AUTOFILL_SAVE_UPI_PROMPT_REJECT));
+
+  set_fixed_width(views::LayoutProvider::Get()->GetDistanceMetric(
+      views::DISTANCE_BUBBLE_PREFERRED_WIDTH));
 }
 
 SaveUPIOfferBubbleViews::~SaveUPIOfferBubbleViews() = default;
@@ -34,22 +38,11 @@
   ShowForReason(LocationBarBubbleDelegateView::DisplayReason::AUTOMATIC);
 }
 
-gfx::Size SaveUPIOfferBubbleViews::CalculatePreferredSize() const {
-  const int width = ChromeLayoutProvider::Get()->GetDistanceMetric(
-                        views::DISTANCE_BUBBLE_PREFERRED_WIDTH) -
-                    margins().width();
-  return gfx::Size(width, GetHeightForWidth(width));
-}
-
 bool SaveUPIOfferBubbleViews::Accept() {
   controller_->OnAccept();
   return true;
 }
 
-base::string16 SaveUPIOfferBubbleViews::GetWindowTitle() const {
-  return l10n_util::GetStringUTF16(IDS_AUTOFILL_SAVE_UPI_PROMPT_TITLE);
-}
-
 void SaveUPIOfferBubbleViews::Hide() {
   if (controller_)
     controller_->OnBubbleClosed();
diff --git a/chrome/browser/ui/views/autofill/payments/save_upi_offer_bubble_views.h b/chrome/browser/ui/views/autofill/payments/save_upi_offer_bubble_views.h
index f6a993c9..7df7fd0 100644
--- a/chrome/browser/ui/views/autofill/payments/save_upi_offer_bubble_views.h
+++ b/chrome/browser/ui/views/autofill/payments/save_upi_offer_bubble_views.h
@@ -31,20 +31,14 @@
   // Displays the bubble.
   void Show();
 
-  // LocationBarBubbleDelegateView:
-  gfx::Size CalculatePreferredSize() const override;
-
  private:
   // views::View:
   bool Accept() override;
 
-  // views::WidgetDelegate:
-  base::string16 GetWindowTitle() const override;
-
   // autofill::SaveUPIBubble:
   void Hide() override;
 
-  // views::BubbleDialogDelegateView:
+  // LocationBarBubbleDelegateView:
   void Init() override;
 
   ~SaveUPIOfferBubbleViews() override;
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc b/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc
index d1d99d2..b74b4c1 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc
@@ -1488,7 +1488,7 @@
   // By default, menu buttons are not flipped because they generally contain
   // text and flipping the gfx::Canvas object will break text rendering. Since
   // the overflow button does not contain text, we can safely flip it.
-  button->EnableCanvasFlippingForRTLUI(true);
+  button->SetFlipCanvasOnPaintForRTLUI(true);
 
   // Make visible as necessary.
   button->SetVisible(false);
diff --git a/chrome/browser/ui/views/crostini/crostini_ansible_software_config_view.cc b/chrome/browser/ui/views/crostini/crostini_ansible_software_config_view.cc
index 14ea1ed0..797eb5b 100644
--- a/chrome/browser/ui/views/crostini/crostini_ansible_software_config_view.cc
+++ b/chrome/browser/ui/views/crostini/crostini_ansible_software_config_view.cc
@@ -74,13 +74,6 @@
   }
 }
 
-gfx::Size CrostiniAnsibleSoftwareConfigView::CalculatePreferredSize() const {
-  const int dialog_width = ChromeLayoutProvider::Get()->GetDistanceMetric(
-                               DISTANCE_STANDALONE_BUBBLE_PREFERRED_WIDTH) -
-                           margins().width();
-  return gfx::Size(dialog_width, GetHeightForWidth(dialog_width));
-}
-
 void CrostiniAnsibleSoftwareConfigView::
     OnAnsibleSoftwareConfigurationStarted() {}
 
@@ -119,6 +112,9 @@
           crostini::AnsibleManagementService::GetForProfile(profile)) {
   ansible_management_service_->AddObserver(this);
 
+  set_fixed_width(ChromeLayoutProvider::Get()->GetDistanceMetric(
+      DISTANCE_STANDALONE_BUBBLE_PREFERRED_WIDTH));
+
   views::LayoutProvider* provider = views::LayoutProvider::Get();
   SetLayoutManager(std::make_unique<views::BoxLayout>(
       views::BoxLayout::Orientation::kVertical,
diff --git a/chrome/browser/ui/views/crostini/crostini_ansible_software_config_view.h b/chrome/browser/ui/views/crostini/crostini_ansible_software_config_view.h
index d4f53d5d..4c989d7c 100644
--- a/chrome/browser/ui/views/crostini/crostini_ansible_software_config_view.h
+++ b/chrome/browser/ui/views/crostini/crostini_ansible_software_config_view.h
@@ -22,7 +22,6 @@
  public:
   // views::DialogDelegateView:
   bool Accept() override;
-  gfx::Size CalculatePreferredSize() const override;
 
   // crostini::AnsibleManagementService::Observer:
   void OnAnsibleSoftwareConfigurationStarted() override;
diff --git a/chrome/browser/ui/views/crostini/crostini_recovery_view.cc b/chrome/browser/ui/views/crostini/crostini_recovery_view.cc
index 7eede3f..235f39f 100644
--- a/chrome/browser/ui/views/crostini/crostini_recovery_view.cc
+++ b/chrome/browser/ui/views/crostini/crostini_recovery_view.cc
@@ -62,13 +62,6 @@
   g_crostini_recovery_view->GetWidget()->Show();
 }
 
-gfx::Size CrostiniRecoveryView::CalculatePreferredSize() const {
-  const int dialog_width = ChromeLayoutProvider::Get()->GetDistanceMetric(
-                               DISTANCE_STANDALONE_BUBBLE_PREFERRED_WIDTH) -
-                           margins().width();
-  return gfx::Size(dialog_width, GetHeightForWidth(dialog_width));
-}
-
 bool CrostiniRecoveryView::Accept() {
   SetButtonEnabled(ui::DIALOG_BUTTON_OK, false);
   SetButtonEnabled(ui::DIALOG_BUTTON_CANCEL, false);
@@ -126,6 +119,8 @@
       l10n_util::GetStringUTF16(IDS_CROSTINI_RECOVERY_TERMINAL_BUTTON));
   SetShowCloseButton(false);
   SetTitle(IDS_CROSTINI_RECOVERY_TITLE);
+  set_fixed_width(ChromeLayoutProvider::Get()->GetDistanceMetric(
+      DISTANCE_STANDALONE_BUBBLE_PREFERRED_WIDTH));
 
   views::LayoutProvider* provider = views::LayoutProvider::Get();
   SetLayoutManager(std::make_unique<views::BoxLayout>(
diff --git a/chrome/browser/ui/views/crostini/crostini_recovery_view.h b/chrome/browser/ui/views/crostini/crostini_recovery_view.h
index 4b4daf4..6af666e 100644
--- a/chrome/browser/ui/views/crostini/crostini_recovery_view.h
+++ b/chrome/browser/ui/views/crostini/crostini_recovery_view.h
@@ -27,7 +27,6 @@
                    crostini::CrostiniSuccessCallback callback);
 
   // views::DialogDelegateView:
-  gfx::Size CalculatePreferredSize() const override;
   bool Accept() override;
   bool Cancel() override;
 
diff --git a/chrome/browser/ui/views/crostini/crostini_uninstaller_view.cc b/chrome/browser/ui/views/crostini/crostini_uninstaller_view.cc
index f71ceff..5493cafa 100644
--- a/chrome/browser/ui/views/crostini/crostini_uninstaller_view.cc
+++ b/chrome/browser/ui/views/crostini/crostini_uninstaller_view.cc
@@ -78,13 +78,6 @@
   return true;  // Should close the dialog
 }
 
-gfx::Size CrostiniUninstallerView::CalculatePreferredSize() const {
-  const int dialog_width = ChromeLayoutProvider::Get()->GetDistanceMetric(
-                               DISTANCE_STANDALONE_BUBBLE_PREFERRED_WIDTH) -
-                           margins().width();
-  return gfx::Size(dialog_width, GetHeightForWidth(dialog_width));
-}
-
 // static
 CrostiniUninstallerView* CrostiniUninstallerView::GetActiveViewForTesting() {
   return g_crostini_uninstaller_view;
@@ -97,7 +90,8 @@
   SetButtonLabel(
       ui::DIALOG_BUTTON_OK,
       l10n_util::GetStringUTF16(IDS_CROSTINI_UNINSTALLER_UNINSTALL_BUTTON));
-
+  set_fixed_width(ChromeLayoutProvider::Get()->GetDistanceMetric(
+      DISTANCE_STANDALONE_BUBBLE_PREFERRED_WIDTH));
   views::LayoutProvider* provider = views::LayoutProvider::Get();
   SetLayoutManager(std::make_unique<views::BoxLayout>(
       views::BoxLayout::Orientation::kVertical,
diff --git a/chrome/browser/ui/views/crostini/crostini_uninstaller_view.h b/chrome/browser/ui/views/crostini/crostini_uninstaller_view.h
index 2ea12df..942150b 100644
--- a/chrome/browser/ui/views/crostini/crostini_uninstaller_view.h
+++ b/chrome/browser/ui/views/crostini/crostini_uninstaller_view.h
@@ -36,7 +36,6 @@
   // views::DialogDelegateView:
   bool Accept() override;
   bool Cancel() override;
-  gfx::Size CalculatePreferredSize() const override;
 
   static CrostiniUninstallerView* GetActiveViewForTesting();
   void set_destructor_callback_for_testing(base::OnceClosure callback) {
diff --git a/chrome/browser/ui/views/crostini/crostini_update_component_view.cc b/chrome/browser/ui/views/crostini/crostini_update_component_view.cc
index 5bcc78d0..97539b37 100644
--- a/chrome/browser/ui/views/crostini/crostini_update_component_view.cc
+++ b/chrome/browser/ui/views/crostini/crostini_update_component_view.cc
@@ -46,13 +46,6 @@
   g_crostini_upgrade_view->GetWidget()->Show();
 }
 
-gfx::Size CrostiniUpdateComponentView::CalculatePreferredSize() const {
-  const int dialog_width = ChromeLayoutProvider::Get()->GetDistanceMetric(
-                               DISTANCE_STANDALONE_BUBBLE_PREFERRED_WIDTH) -
-                           margins().width();
-  return gfx::Size(dialog_width, GetHeightForWidth(dialog_width));
-}
-
 // static
 CrostiniUpdateComponentView*
 CrostiniUpdateComponentView::GetActiveViewForTesting() {
@@ -63,6 +56,8 @@
   SetButtons(ui::DIALOG_BUTTON_OK);
   SetShowCloseButton(false);
   SetTitle(IDS_CROSTINI_TERMINA_UPDATE_REQUIRED);
+  set_fixed_width(ChromeLayoutProvider::Get()->GetDistanceMetric(
+      DISTANCE_STANDALONE_BUBBLE_PREFERRED_WIDTH));
 
   views::LayoutProvider* provider = views::LayoutProvider::Get();
   SetLayoutManager(std::make_unique<views::BoxLayout>(
diff --git a/chrome/browser/ui/views/crostini/crostini_update_component_view.h b/chrome/browser/ui/views/crostini/crostini_update_component_view.h
index e78bf025..98c5535 100644
--- a/chrome/browser/ui/views/crostini/crostini_update_component_view.h
+++ b/chrome/browser/ui/views/crostini/crostini_update_component_view.h
@@ -19,9 +19,6 @@
  public:
   static void Show(Profile* profile);
 
-  // views::DialogDelegateView:
-  gfx::Size CalculatePreferredSize() const override;
-
   static CrostiniUpdateComponentView* GetActiveViewForTesting();
 
  private:
diff --git a/chrome/browser/ui/views/crostini/crostini_update_filesystem_view.cc b/chrome/browser/ui/views/crostini/crostini_update_filesystem_view.cc
index bf9834e..35fde148 100644
--- a/chrome/browser/ui/views/crostini/crostini_update_filesystem_view.cc
+++ b/chrome/browser/ui/views/crostini/crostini_update_filesystem_view.cc
@@ -90,13 +90,6 @@
   g_crostini_update_filesystem_view_dialog->GetWidget()->Show();
 }
 
-gfx::Size CrostiniUpdateFilesystemView::CalculatePreferredSize() const {
-  const int dialog_width = ChromeLayoutProvider::Get()->GetDistanceMetric(
-                               DISTANCE_STANDALONE_BUBBLE_PREFERRED_WIDTH) -
-                           margins().width();
-  return gfx::Size(dialog_width, GetHeightForWidth(dialog_width));
-}
-
 // static
 CrostiniUpdateFilesystemView*
 CrostiniUpdateFilesystemView::GetActiveViewForTesting() {
@@ -110,6 +103,9 @@
   SetTitle(IDS_CROSTINI_UPGRADING_LABEL);
   SetButtons(ui::DIALOG_BUTTON_OK);
 
+  set_fixed_width(ChromeLayoutProvider::Get()->GetDistanceMetric(
+      DISTANCE_STANDALONE_BUBBLE_PREFERRED_WIDTH));
+
   views::LayoutProvider* provider = views::LayoutProvider::Get();
   SetLayoutManager(std::make_unique<views::BoxLayout>(
       views::BoxLayout::Orientation::kVertical,
diff --git a/chrome/browser/ui/views/crostini/crostini_update_filesystem_view.h b/chrome/browser/ui/views/crostini/crostini_update_filesystem_view.h
index 1a3a930..bd1e011a 100644
--- a/chrome/browser/ui/views/crostini/crostini_update_filesystem_view.h
+++ b/chrome/browser/ui/views/crostini/crostini_update_filesystem_view.h
@@ -19,9 +19,6 @@
  public:
   static void Show(Profile* profile);
 
-  // views::DialogDelegateView:
-  gfx::Size CalculatePreferredSize() const override;
-
   static CrostiniUpdateFilesystemView* GetActiveViewForTesting();
 
  private:
diff --git a/chrome/browser/ui/views/download/download_danger_prompt_views.cc b/chrome/browser/ui/views/download/download_danger_prompt_views.cc
index 88fd35c5..7b721a3 100644
--- a/chrome/browser/ui/views/download/download_danger_prompt_views.cc
+++ b/chrome/browser/ui/views/download/download_danger_prompt_views.cc
@@ -52,7 +52,6 @@
   void InvokeActionForTesting(Action action) override;
 
   // views::DialogDelegateView:
-  gfx::Size CalculatePreferredSize() const override;
   base::string16 GetWindowTitle() const override;
   ui::ModalType GetModalType() const override;
 
@@ -89,6 +88,9 @@
                      ? l10n_util::GetStringUTF16(IDS_CONFIRM_DOWNLOAD)
                      : l10n_util::GetStringUTF16(IDS_CONFIRM_DOWNLOAD_AGAIN));
 
+  set_fixed_width(views::LayoutProvider::Get()->GetDistanceMetric(
+      views::DISTANCE_BUBBLE_PREFERRED_WIDTH));
+
   auto make_done_callback = [&](DownloadDangerPrompt::Action action) {
     return base::BindOnce(&DownloadDangerPromptViews::RunDone,
                           base::Unretained(this), action);
@@ -182,13 +184,6 @@
   }
 }
 
-gfx::Size DownloadDangerPromptViews::CalculatePreferredSize() const {
-  int preferred_width = ChromeLayoutProvider::Get()->GetDistanceMetric(
-                            views::DISTANCE_BUBBLE_PREFERRED_WIDTH) -
-                        margins().width();
-  return gfx::Size(preferred_width, GetHeightForWidth(preferred_width));
-}
-
 base::string16 DownloadDangerPromptViews::GetMessageBody() const {
   if (show_context_) {
     switch (download_->GetDangerType()) {
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_view.cc b/chrome/browser/ui/views/extensions/extensions_menu_view.cc
index deea68f2..b1ea06f5 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_view.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_view.cc
@@ -87,7 +87,7 @@
   SetShowCloseButton(true);
   SetTitle(IDS_EXTENSIONS_MENU_TITLE);
 
-  EnableUpDownKeyboardAccelerators();
+  GetFocusManager()->set_arrow_key_traversal_enabled(true);
 
   // Let anchor view's MenuButtonController handle the highlight.
   set_highlight_button_when_shown(false);
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
index 2290528..e5ea4f3 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
@@ -51,7 +51,7 @@
 
   model_observer_.Add(model_);
   // Do not flip the Extensions icon in RTL.
-  extensions_button_->EnableCanvasFlippingForRTLUI(false);
+  extensions_button_->SetFlipCanvasOnPaintForRTLUI(false);
 
   const views::FlexSpecification hide_icon_flex_specification =
       views::FlexSpecification(views::LayoutOrientation::kHorizontal,
diff --git a/chrome/browser/ui/views/find_bar_view.cc b/chrome/browser/ui/views/find_bar_view.cc
index 07dfc25e..271b751b 100644
--- a/chrome/browser/ui/views/find_bar_view.cc
+++ b/chrome/browser/ui/views/find_bar_view.cc
@@ -190,7 +190,7 @@
   close_button_ = AddChildView(std::move(close_button));
   SetCommonButtonAttributes(close_button_);
 
-  EnableCanvasFlippingForRTLUI(true);
+  SetFlipCanvasOnPaintForRTLUI(true);
 
   // Normally we could space objects horizontally by simply passing a constant
   // value to BoxLayout for between-child spacing.  But for the vector image
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index 7946cb65..4309608 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -739,9 +739,7 @@
 }
 
 TabSearchButton* BrowserView::GetTabSearchButton() {
-  return base::FeatureList::IsEnabled(features::kTabSearch)
-             ? tab_strip_region_view_->tab_search_button()
-             : nullptr;
+  return tab_strip_region_view_->tab_search_button();
 }
 
 bool BrowserView::IsTabStripVisible() const {
diff --git a/chrome/browser/ui/views/frame/tab_strip_region_view.cc b/chrome/browser/ui/views/frame/tab_strip_region_view.cc
index e169e6af4..f37afbf3 100644
--- a/chrome/browser/ui/views/frame/tab_strip_region_view.cc
+++ b/chrome/browser/ui/views/frame/tab_strip_region_view.cc
@@ -115,7 +115,8 @@
                                views::MaximumFlexSizeRule::kUnbounded));
 
   if (base::FeatureList::IsEnabled(features::kTabSearch) &&
-      !tab_strip_->controller()->GetProfile()->IsIncognitoProfile()) {
+      !tab_strip_->controller()->GetProfile()->IsIncognitoProfile() &&
+      tab_strip_->controller()->GetBrowser()->is_type_normal()) {
     auto tab_search_button = std::make_unique<TabSearchButton>(tab_strip_);
     tab_search_button->SetTooltipText(
         l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_SEARCH));
diff --git a/chrome/browser/ui/views/global_media_controls/media_toolbar_button_view.cc b/chrome/browser/ui/views/global_media_controls/media_toolbar_button_view.cc
index ae2756e..0e3fee7f 100644
--- a/chrome/browser/ui/views/global_media_controls/media_toolbar_button_view.cc
+++ b/chrome/browser/ui/views/global_media_controls/media_toolbar_button_view.cc
@@ -37,7 +37,7 @@
       feature_promo_controller_(browser_view->feature_promo_controller()) {
   button_controller()->set_notify_action(
       views::ButtonController::NotifyAction::kOnPress);
-  EnableCanvasFlippingForRTLUI(false);
+  SetFlipCanvasOnPaintForRTLUI(false);
   SetTooltipText(
       l10n_util::GetStringUTF16(IDS_GLOBAL_MEDIA_CONTROLS_ICON_TOOLTIP_TEXT));
   GetViewAccessibility().OverrideHasPopup(ax::mojom::HasPopup::kDialog);
diff --git a/chrome/browser/ui/views/in_product_help/feature_promo_controller_views.cc b/chrome/browser/ui/views/in_product_help/feature_promo_controller_views.cc
index 8412b88..b02191f2 100644
--- a/chrome/browser/ui/views/in_product_help/feature_promo_controller_views.cc
+++ b/chrome/browser/ui/views/in_product_help/feature_promo_controller_views.cc
@@ -10,6 +10,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/optional.h"
+#include "base/token.h"
 #include "chrome/browser/feature_engagement/tracker_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/in_product_help/feature_promo_snooze_service.h"
@@ -48,6 +49,11 @@
   if (promos_blocked_for_testing_)
     return false;
 
+  // A normal promo cannot show if a critical promo is displayed. These
+  // are not registered with |tracker_| so check here.
+  if (current_critical_promo_)
+    return false;
+
   // Temporarily turn off IPH in incognito as a concern was raised that
   // the IPH backend ignores incognito and writes to the parent profile.
   // See https://bugs.chromium.org/p/chromium/issues/detail?id=1128728#c30
@@ -63,18 +69,7 @@
   // If the tracker says we should trigger, but we have a promo
   // currently showing, there is a bug somewhere in here.
   DCHECK(!current_iph_feature_);
-
-  params.anchor_view->SetProperty(kHasInProductHelpPromoKey, true);
-  anchor_view_tracker_.SetView(params.anchor_view);
-
   current_iph_feature_ = &iph_feature;
-  promo_bubble_ = FeaturePromoBubbleView::Create(
-      std::move(params),
-      base::BindRepeating(&FeaturePromoControllerViews::OnUserSnooze,
-                          base::Unretained(this), iph_feature),
-      base::BindRepeating(&FeaturePromoControllerViews::OnUserDismiss,
-                          base::Unretained(this), iph_feature));
-  widget_observer_.Add(promo_bubble_->GetWidget());
 
   // Record count of previous snoozes when an IPH triggers.
   int snooze_count = snooze_service_->GetSnoozeCount(iph_feature);
@@ -83,9 +78,46 @@
                                 snooze_count,
                                 snooze_service_->kUmaMaxSnoozeCount);
 
+  ShowPromoBubbleImpl(params);
+
   return true;
 }
 
+base::Optional<base::Token> FeaturePromoControllerViews::ShowCriticalPromo(
+    const FeaturePromoBubbleParams& params) {
+  if (promos_blocked_for_testing_)
+    return base::nullopt;
+
+  // Don't preempt an existing critical promo.
+  if (current_critical_promo_)
+    return base::nullopt;
+
+  // If a normal bubble is showing, close it. If the promo is has
+  // continued after a CloseBubbleAndContinuePromo() call, we can't stop
+  // it. However we will show the critical promo anyway.
+  if (current_iph_feature_ && promo_bubble_)
+    CloseBubble(*current_iph_feature_);
+
+  // Snooze is not supported for critical promos.
+  DCHECK(!params.allow_snooze);
+
+  DCHECK(!promo_bubble_);
+
+  current_critical_promo_ = base::Token::CreateRandom();
+  ShowPromoBubbleImpl(params);
+
+  return current_critical_promo_;
+}
+
+void FeaturePromoControllerViews::CloseBubbleForCriticalPromo(
+    const base::Token& critical_promo_id) {
+  if (current_critical_promo_ != critical_promo_id)
+    return;
+
+  DCHECK(promo_bubble_);
+  promo_bubble_->GetWidget()->Close();
+}
+
 bool FeaturePromoControllerViews::MaybeShowPromo(
     const base::Feature& iph_feature) {
   base::Optional<FeaturePromoBubbleParams> params =
@@ -176,15 +208,46 @@
   current_iph_feature_ = nullptr;
 }
 
+void FeaturePromoControllerViews::ShowPromoBubbleImpl(
+    const FeaturePromoBubbleParams& params) {
+  params.anchor_view->SetProperty(kHasInProductHelpPromoKey, true);
+  anchor_view_tracker_.SetView(params.anchor_view);
+
+  if (current_iph_feature_) {
+    // For normal promos:
+    promo_bubble_ = FeaturePromoBubbleView::Create(
+        params,
+        base::BindRepeating(&FeaturePromoControllerViews::OnUserSnooze,
+                            base::Unretained(this), *current_iph_feature_),
+        base::BindRepeating(&FeaturePromoControllerViews::OnUserDismiss,
+                            base::Unretained(this), *current_iph_feature_));
+  } else {
+    // For critical promos, since they aren't snoozable:
+    promo_bubble_ = FeaturePromoBubbleView::Create(params);
+  }
+
+  widget_observer_.Add(promo_bubble_->GetWidget());
+}
+
 void FeaturePromoControllerViews::HandleBubbleClosed() {
-  DCHECK(current_iph_feature_);
+  // A bubble should be showing.
+  DCHECK(promo_bubble_);
 
-  tracker_->Dismissed(*current_iph_feature_);
+  // Exactly one of current_iph_feature_ or current_critical_promo_ should have
+  // a value.
+  DCHECK_NE(current_iph_feature_ != nullptr,
+            current_critical_promo_.has_value());
+
   widget_observer_.Remove(promo_bubble_->GetWidget());
-
-  current_iph_feature_ = nullptr;
   promo_bubble_ = nullptr;
 
   if (anchor_view_tracker_.view())
     anchor_view_tracker_.view()->SetProperty(kHasInProductHelpPromoKey, false);
+
+  if (current_iph_feature_) {
+    tracker_->Dismissed(*current_iph_feature_);
+    current_iph_feature_ = nullptr;
+  } else {
+    current_critical_promo_ = base::nullopt;
+  }
 }
diff --git a/chrome/browser/ui/views/in_product_help/feature_promo_controller_views.h b/chrome/browser/ui/views/in_product_help/feature_promo_controller_views.h
index c32f549..524ef8e 100644
--- a/chrome/browser/ui/views/in_product_help/feature_promo_controller_views.h
+++ b/chrome/browser/ui/views/in_product_help/feature_promo_controller_views.h
@@ -8,7 +8,9 @@
 #include <memory>
 
 #include "base/memory/weak_ptr.h"
+#include "base/optional.h"
 #include "base/scoped_observer.h"
+#include "base/token.h"
 #include "chrome/browser/ui/in_product_help/feature_promo_controller.h"
 #include "ui/views/view_tracker.h"
 #include "ui/views/widget/widget.h"
@@ -21,6 +23,7 @@
 
 namespace base {
 struct Feature;
+class Token;
 }
 
 namespace feature_engagement {
@@ -46,6 +49,18 @@
   bool MaybeShowPromoWithParams(const base::Feature& iph_feature,
                                 const FeaturePromoBubbleParams& params);
 
+  // Only for security or privacy critical promos. Immedialy shows a
+  // promo with |params|, cancelling any normal promo and blocking any
+  // further promos until it's done.
+  //
+  // Returns an ID that can be passed to CloseBubbleForCriticalPromo()
+  // if successful. This can fail if another critical promo is showing.
+  base::Optional<base::Token> ShowCriticalPromo(
+      const FeaturePromoBubbleParams& params);
+
+  // Ends a promo started by ShowCriticalPromo() if it's still showing.
+  void CloseBubbleForCriticalPromo(const base::Token& critical_promo_id);
+
   // FeaturePromoController:
   bool MaybeShowPromo(const base::Feature& iph_feature) override;
   bool BubbleIsShowing(const base::Feature& iph_feature) const override;
@@ -78,6 +93,8 @@
   // Called when PromoHandle is destroyed to finish the promo.
   void FinishContinuedPromo() override;
 
+  void ShowPromoBubbleImpl(const FeaturePromoBubbleParams& params);
+
   void HandleBubbleClosed();
 
   // Call these methods when the user actively snooze or dismiss the IPH.
@@ -99,6 +116,13 @@
   // feature registered with |tracker_|.
   const base::Feature* current_iph_feature_ = nullptr;
 
+  // Has a value if a critical promo is showing. If this has a value,
+  // |current_iph_feature_| will usually be null. There is one edge case
+  // where this may not be true: when a critical promo is requested
+  // between a normal promo's CloseBubbleAndContinuePromo() call and its
+  // end.
+  base::Optional<base::Token> current_critical_promo_;
+
   // The bubble currently showing, if any.
   FeaturePromoBubbleView* promo_bubble_ = nullptr;
 
diff --git a/chrome/browser/ui/views/in_product_help/feature_promo_controller_views_unittest.cc b/chrome/browser/ui/views/in_product_help/feature_promo_controller_views_unittest.cc
index c9d5ac3..7b0265e3 100644
--- a/chrome/browser/ui/views/in_product_help/feature_promo_controller_views_unittest.cc
+++ b/chrome/browser/ui/views/in_product_help/feature_promo_controller_views_unittest.cc
@@ -267,8 +267,7 @@
 
 TEST_F(FeaturePromoControllerViewsTest, TestCanBlockPromos) {
   EXPECT_CALL(*mock_tracker_, ShouldTriggerHelpUI(Ref(kTestIPHFeature)))
-      .Times(0)
-      .WillOnce(Return(true));
+      .Times(0);
 
   controller_->BlockPromosForTesting();
   EXPECT_FALSE(controller_->MaybeShowPromoWithParams(kTestIPHFeature,
@@ -289,3 +288,86 @@
   EXPECT_FALSE(controller_->BubbleIsShowing(kTestIPHFeature));
   EXPECT_FALSE(controller_->promo_bubble_for_testing());
 }
+
+TEST_F(FeaturePromoControllerViewsTest, CriticalPromoBlocksNormalPromo) {
+  EXPECT_TRUE(controller_->ShowCriticalPromo(DefaultBubbleParams()));
+  EXPECT_TRUE(controller_->promo_bubble_for_testing());
+
+  EXPECT_CALL(*mock_tracker_, ShouldTriggerHelpUI(Ref(kTestIPHFeature)))
+      .Times(0);
+  EXPECT_FALSE(controller_->MaybeShowPromoWithParams(kTestIPHFeature,
+                                                     DefaultBubbleParams()));
+
+  EXPECT_FALSE(controller_->BubbleIsShowing(kTestIPHFeature));
+  EXPECT_TRUE(controller_->promo_bubble_for_testing());
+}
+
+TEST_F(FeaturePromoControllerViewsTest, CriticalPromoPreemptsNormalPromo) {
+  EXPECT_CALL(*mock_tracker_, ShouldTriggerHelpUI(Ref(kTestIPHFeature)))
+      .Times(1)
+      .WillOnce(Return(true));
+  EXPECT_TRUE(controller_->MaybeShowPromoWithParams(kTestIPHFeature,
+                                                    DefaultBubbleParams()));
+  EXPECT_TRUE(controller_->BubbleIsShowing(kTestIPHFeature));
+  EXPECT_TRUE(controller_->promo_bubble_for_testing());
+
+  EXPECT_CALL(*mock_tracker_, Dismissed(Ref(kTestIPHFeature))).Times(1);
+  EXPECT_TRUE(controller_->ShowCriticalPromo(DefaultBubbleParams()));
+  EXPECT_FALSE(controller_->BubbleIsShowing(kTestIPHFeature));
+  EXPECT_TRUE(controller_->promo_bubble_for_testing());
+}
+
+TEST_F(FeaturePromoControllerViewsTest, FirstCriticalPromoHasPrecedence) {
+  EXPECT_TRUE(controller_->ShowCriticalPromo(DefaultBubbleParams()));
+
+  const auto* first_bubble = controller_->promo_bubble_for_testing();
+  EXPECT_TRUE(first_bubble);
+
+  EXPECT_FALSE(controller_->ShowCriticalPromo(DefaultBubbleParams()));
+  EXPECT_EQ(controller_->promo_bubble_for_testing(), first_bubble);
+}
+
+TEST_F(FeaturePromoControllerViewsTest, CloseBubbleForCriticalPromo) {
+  base::Optional<base::Token> maybe_id =
+      controller_->ShowCriticalPromo(DefaultBubbleParams());
+  ASSERT_TRUE(maybe_id);
+  base::Token id = maybe_id.value();
+
+  EXPECT_TRUE(controller_->promo_bubble_for_testing());
+  controller_->CloseBubbleForCriticalPromo(id);
+  EXPECT_FALSE(controller_->promo_bubble_for_testing());
+}
+
+TEST_F(FeaturePromoControllerViewsTest,
+       CloseBubbleForCriticalPromoDoesNothingAfterClose) {
+  base::Optional<base::Token> maybe_id =
+      controller_->ShowCriticalPromo(DefaultBubbleParams());
+  ASSERT_TRUE(maybe_id);
+  base::Token id = maybe_id.value();
+
+  auto* bubble = controller_->promo_bubble_for_testing();
+  ASSERT_TRUE(bubble);
+  bubble->GetWidget()->Close();
+  EXPECT_FALSE(controller_->promo_bubble_for_testing());
+
+  EXPECT_TRUE(controller_->ShowCriticalPromo(DefaultBubbleParams()));
+  EXPECT_TRUE(controller_->promo_bubble_for_testing());
+
+  // Since |id| has expired, this should do nothing.
+  controller_->CloseBubbleForCriticalPromo(id);
+  EXPECT_TRUE(controller_->promo_bubble_for_testing());
+}
+
+TEST_F(FeaturePromoControllerViewsTest, ShowNewCriticalPromoAfterClose) {
+  base::Optional<base::Token> maybe_id =
+      controller_->ShowCriticalPromo(DefaultBubbleParams());
+  ASSERT_TRUE(maybe_id);
+  base::Token id = maybe_id.value();
+
+  EXPECT_TRUE(controller_->promo_bubble_for_testing());
+  controller_->CloseBubbleForCriticalPromo(id);
+  EXPECT_FALSE(controller_->promo_bubble_for_testing());
+
+  EXPECT_TRUE(controller_->ShowCriticalPromo(DefaultBubbleParams()));
+  EXPECT_TRUE(controller_->promo_bubble_for_testing());
+}
diff --git a/chrome/browser/ui/views/in_product_help/feature_promo_dialog_browsertest.cc b/chrome/browser/ui/views/in_product_help/feature_promo_dialog_browsertest.cc
index ca73520b..b563724 100644
--- a/chrome/browser/ui/views/in_product_help/feature_promo_dialog_browsertest.cc
+++ b/chrome/browser/ui/views/in_product_help/feature_promo_dialog_browsertest.cc
@@ -2,36 +2,169 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <algorithm>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/feature_list.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/feature_engagement/tracker_factory.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/test/test_browser_dialog.h"
+#include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
-#include "chrome/browser/ui/views/in_product_help/feature_promo_bubble_params.h"
-#include "chrome/browser/ui/views/in_product_help/feature_promo_bubble_view.h"
-#include "chrome/browser/ui/views/toolbar/browser_app_menu_button.h"
+#include "chrome/browser/ui/views/global_media_controls/media_toolbar_button_view.h"
+#include "chrome/browser/ui/views/in_product_help/feature_promo_controller_views.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
-#include "chrome/grit/generated_resources.h"
+#include "chrome/common/buildflags.h"
+#include "components/feature_engagement/public/feature_list.h"
+#include "components/feature_engagement/test/mock_tracker.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "content/public/test/browser_test.h"
+#include "media/base/media_switches.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+#if BUILDFLAG(ENABLE_WEBUI_TAB_STRIP)
+#include "ui/base/pointer/touch_ui_controller.h"
+#endif  // BUILDFLAG(ENABLE_WEBUI_TAB_STRIP)
+
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::NiceMock;
+using ::testing::Ref;
+using ::testing::Return;
 
 class FeaturePromoDialogTest : public DialogBrowserTest {
  public:
-  FeaturePromoDialogTest() = default;
+  FeaturePromoDialogTest() {
+    // TODO(crbug.com/1141984): fix cause of bubbles overflowing the
+    // screen and remove this.
+    set_should_verify_dialog_bounds(false);
+  }
+
   ~FeaturePromoDialogTest() override = default;
 
   // DialogBrowserTest:
   void ShowUi(const std::string& name) override {
-    auto* app_menu_button = BrowserView::GetBrowserViewForBrowser(browser())
-                                ->toolbar_button_provider()
-                                ->GetAppMenuButton();
-    // We use an arbitrary string because there are no test-only
-    // strings.
-    int placeholder_string = IDS_REOPEN_TAB_PROMO;
-    FeaturePromoBubbleParams bubble_params;
-    bubble_params.body_string_specifier = placeholder_string;
-    bubble_params.anchor_view = app_menu_button;
-    bubble_params.arrow = views::BubbleBorder::TOP_RIGHT;
-    FeaturePromoBubbleView::Create(std::move(bubble_params));
+    auto* mock_tracker = static_cast<feature_engagement::test::MockTracker*>(
+        feature_engagement::TrackerFactory::GetForBrowserContext(
+            browser()->profile()));
+    ASSERT_TRUE(mock_tracker);
+
+    FeaturePromoControllerViews* promo_controller =
+        BrowserView::GetBrowserViewForBrowser(browser())
+            ->feature_promo_controller();
+    ASSERT_TRUE(promo_controller);
+
+    // Look up the IPH name and get the base::Feature.
+    std::vector<const base::Feature*> iph_features =
+        feature_engagement::GetAllFeatures();
+    auto feature_it =
+        std::find_if(iph_features.begin(), iph_features.end(),
+                     [&](const base::Feature* f) { return f->name == name; });
+    ASSERT_NE(feature_it, iph_features.end());
+    const base::Feature& feature = **feature_it;
+
+    // Set up mock tracker to allow the IPH, then attempt to show it.
+    EXPECT_CALL(*mock_tracker, ShouldTriggerHelpUI(Ref(feature)))
+        .Times(1)
+        .WillOnce(Return(true));
+    ASSERT_TRUE(promo_controller->MaybeShowPromo(feature));
   }
+
+ private:
+  static void RegisterMockTracker(content::BrowserContext* context) {
+    feature_engagement::TrackerFactory::GetInstance()->SetTestingFactory(
+        context, base::BindRepeating(CreateMockTracker));
+  }
+
+  static std::unique_ptr<KeyedService> CreateMockTracker(
+      content::BrowserContext* context) {
+    auto mock_tracker =
+        std::make_unique<NiceMock<feature_engagement::test::MockTracker>>();
+
+    // Allow calls for other IPH.
+    EXPECT_CALL(*mock_tracker, ShouldTriggerHelpUI(_))
+        .Times(AnyNumber())
+        .WillRepeatedly(Return(false));
+
+    return mock_tracker;
+  }
+
+  std::unique_ptr<
+      BrowserContextDependencyManager::CreateServicesCallbackList::Subscription>
+      subscription_{BrowserContextDependencyManager::GetInstance()
+                        ->RegisterCreateServicesCallbackForTesting(
+                            base::BindRepeating(RegisterMockTracker))};
 };
 
-IN_PROC_BROWSER_TEST_F(FeaturePromoDialogTest, InvokeUi_default) {
+// Adding new tests for your promo
+//
+// When you add a new IPH, add a test for your promo. In most cases you
+// can follow these steps:
+//
+// 1. Get the feature name for your IPH. This will be the of the form
+//    IPH_<name>. It should be the same as defined in
+//    //components/feature_engagement/public/feature_constants.cc.
+//
+// 2. Define a new test below with the name InvokeUi_IPH_<name>. Place
+//    it in alphabetical order.
+//
+// 3. Call set_baseline("<cl-number>"). This enables Skia Gold pixel
+//    tests for your IPH.
+//
+// 4. Call ShowAndVerifyUi().
+//
+// For running your test reference the docs in
+// //chrome/browser/ui/test/test_browser_dialog.h
+
+IN_PROC_BROWSER_TEST_F(FeaturePromoDialogTest,
+                       InvokeUi_IPH_DesktopTabGroupsNewGroup) {
+  set_baseline("2473537");
   ShowAndVerifyUi();
 }
+
+IN_PROC_BROWSER_TEST_F(FeaturePromoDialogTest, InvokeUi_IPH_LiveCaption) {
+  if (!base::FeatureList::IsEnabled(media::kLiveCaption))
+    return;
+
+  BrowserView::GetBrowserViewForBrowser(browser())
+      ->toolbar()
+      ->media_button()
+      ->Show();
+  RunScheduledLayouts();
+
+  set_baseline("2473537");
+  ShowAndVerifyUi();
+}
+
+IN_PROC_BROWSER_TEST_F(FeaturePromoDialogTest, InvokeUi_IPH_ReopenTab) {
+  set_baseline("2473537");
+  ShowAndVerifyUi();
+}
+
+#if BUILDFLAG(ENABLE_WEBUI_TAB_STRIP)
+
+// Need a separate fixture to override the feature flag.
+class FeaturePromoDialogWebUITabStripTest : public FeaturePromoDialogTest {
+ public:
+  FeaturePromoDialogWebUITabStripTest() {
+    feature_list_.InitAndEnableFeature(features::kWebUITabStrip);
+  }
+
+  ~FeaturePromoDialogWebUITabStripTest() override = default;
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(FeaturePromoDialogWebUITabStripTest,
+                       InvokeUi_IPH_WebUITabStrip) {
+  ui::TouchUiController::TouchUiScoperForTesting touch_override(true);
+  RunScheduledLayouts();
+
+  set_baseline("2473537");
+  ShowAndVerifyUi();
+}
+
+#endif  // BUILDFLAG(ENABLE_WEBUI_TAB_STRIP)
diff --git a/chrome/browser/ui/views/in_product_help/new_badge_label.cc b/chrome/browser/ui/views/in_product_help/new_badge_label.cc
new file mode 100644
index 0000000..972816a
--- /dev/null
+++ b/chrome/browser/ui/views/in_product_help/new_badge_label.cc
@@ -0,0 +1,141 @@
+// 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/in_product_help/new_badge_label.h"
+
+#include "ui/accessibility/ax_node_data.h"
+#include "ui/gfx/text_utils.h"
+#include "ui/views/border.h"
+#include "ui/views/controls/menu/new_badge.h"
+#include "ui/views/metadata/metadata_impl_macros.h"
+#include "ui/views/metadata/type_conversion.h"
+#include "ui/views/view.h"
+#include "ui/views/view_class_properties.h"
+
+NewBadgeLabel::NewBadgeLabel(const base::string16& text,
+                             int text_context,
+                             int text_style,
+                             gfx::DirectionalityMode directionality_mode)
+    : Label(text, text_context, text_style, directionality_mode) {
+  UpdatePaddingForNewBadge();
+}
+
+NewBadgeLabel::NewBadgeLabel(const base::string16& text, const CustomFont& font)
+    : Label(text, font) {
+  UpdatePaddingForNewBadge();
+}
+
+NewBadgeLabel::~NewBadgeLabel() = default;
+
+void NewBadgeLabel::SetPadAfterNewBadge(bool pad_after_new_badge) {
+  if (pad_after_new_badge_ == pad_after_new_badge)
+    return;
+
+  pad_after_new_badge_ = pad_after_new_badge;
+  UpdatePaddingForNewBadge();
+  OnPropertyChanged(&pad_after_new_badge_, views::kPropertyEffectsLayout);
+}
+
+void NewBadgeLabel::SetBadgePlacement(BadgePlacement badge_placement) {
+  if (badge_placement_ == badge_placement)
+    return;
+
+  badge_placement_ = badge_placement;
+  UpdatePaddingForNewBadge();
+  OnPropertyChanged(&badge_placement_, views::kPropertyEffectsPaint);
+}
+
+void NewBadgeLabel::GetAccessibleNodeData(ui::AXNodeData* node_data) {
+  Label::GetAccessibleNodeData(node_data);
+  base::string16 accessible_name = GetText();
+  accessible_name.push_back(' ');
+  accessible_name.append(views::NewBadge::GetNewBadgeAccessibleDescription());
+  node_data->SetName(accessible_name);
+}
+
+gfx::Size NewBadgeLabel::CalculatePreferredSize() const {
+  gfx::Size size = Label::CalculatePreferredSize();
+  size.SetToMax(views::NewBadge::GetNewBadgeSize(font_list()));
+  return size;
+}
+
+gfx::Size NewBadgeLabel::GetMinimumSize() const {
+  gfx::Size size = Label::GetMinimumSize();
+  size.SetToMax(views::NewBadge::GetNewBadgeSize(font_list()));
+  return size;
+}
+
+int NewBadgeLabel::GetHeightForWidth(int w) const {
+  return std::max(Label::GetHeightForWidth(w),
+                  views::NewBadge::GetNewBadgeSize(font_list()).height());
+}
+
+void NewBadgeLabel::OnDeviceScaleFactorChanged(float old_device_scale_factor,
+                                               float new_device_scale_factor) {
+  UpdatePaddingForNewBadge();
+}
+
+void NewBadgeLabel::OnPaint(gfx::Canvas* canvas) {
+  Label::OnPaint(canvas);
+  const gfx::Rect contents_bounds = GetContentsBounds();
+  int extra_width = 0;
+  if (badge_placement_ == BadgePlacement::kImmediatelyAfterText)
+    extra_width = std::max(0, width() - GetPreferredSize().width());
+  const int badge_x = views::NewBadge::kNewBadgeHorizontalMargin - extra_width +
+                      (base::i18n::IsRTL() ? width() - contents_bounds.x()
+                                           : contents_bounds.right());
+  int top = contents_bounds.y();
+  switch (GetVerticalAlignment()) {
+    case gfx::VerticalAlignment::ALIGN_TOP:
+      break;
+    case gfx::VerticalAlignment::ALIGN_MIDDLE:
+      top += (contents_bounds.height() - font_list().GetHeight()) / 2;
+      break;
+    case gfx::VerticalAlignment::ALIGN_BOTTOM:
+      top += contents_bounds.height() - font_list().GetHeight();
+      break;
+  }
+
+  views::NewBadge::DrawNewBadge(canvas, this, badge_x, top, font_list());
+}
+
+void NewBadgeLabel::UpdatePaddingForNewBadge() {
+  // Calculate the width required for the badge plus separation from the text.
+  int width = views::NewBadge::GetNewBadgeSize(font_list()).width();
+  int right_padding = 0;
+  if (pad_after_new_badge_) {
+    width += 2 * views::NewBadge::kNewBadgeHorizontalMargin;
+    right_padding = views::NewBadge::kNewBadgeHorizontalMargin;
+  } else {
+    width += views::NewBadge::kNewBadgeHorizontalMargin;
+  }
+
+  // Reserve adequate space above and below the label so that the badge will fit
+  // vertically, and to the right to actually hold the badge.
+  gfx::Insets border = gfx::AdjustVisualBorderForFont(
+      font_list(), gfx::Insets(views::NewBadge::kNewBadgeInternalPadding));
+  if (base::i18n::IsRTL()) {
+    border.set_left(width);
+    border.set_right(0);
+  } else {
+    border.set_left(0);
+    border.set_right(width);
+  }
+  SetBorder(views::CreateEmptyBorder(border));
+
+  // If there is right-padding, ensure that layouts understand it can be
+  // collapsed into a margin.
+  SetProperty(views::kInternalPaddingKey, gfx::Insets(0, 0, 0, right_padding));
+}
+
+DEFINE_ENUM_CONVERTERS(NewBadgeLabel::BadgePlacement,
+                       {NewBadgeLabel::BadgePlacement::kImmediatelyAfterText,
+                        base::ASCIIToUTF16("kImmediatelyAfterText")},
+                       {NewBadgeLabel::BadgePlacement::kTrailingEdge,
+                        base::ASCIIToUTF16("kTrailingEdge")})
+
+BEGIN_METADATA(NewBadgeLabel, views::Label)
+ADD_PROPERTY_METADATA(NewBadgeLabel::BadgePlacement, BadgePlacement)
+ADD_PROPERTY_METADATA(bool, PadAfterNewBadge)
+END_METADATA
diff --git a/chrome/browser/ui/views/in_product_help/new_badge_label.h b/chrome/browser/ui/views/in_product_help/new_badge_label.h
new file mode 100644
index 0000000..ae19ce0
--- /dev/null
+++ b/chrome/browser/ui/views/in_product_help/new_badge_label.h
@@ -0,0 +1,69 @@
+// 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_IN_PRODUCT_HELP_NEW_BADGE_LABEL_H_
+#define CHROME_BROWSER_UI_VIEWS_IN_PRODUCT_HELP_NEW_BADGE_LABEL_H_
+
+#include "ui/views/controls/label.h"
+#include "ui/views/controls/menu/new_badge.h"
+#include "ui/views/style/typography.h"
+
+// Extends views::Label to display a "New" badge next to the text drawing
+// attention to a new feature in Chrome. You should be able to substitute this
+// anywhere you have a label - e.g. in a dialog with a new option or feature.
+class NewBadgeLabel : public views::Label {
+ public:
+  // Determines how the badge is placed relative to the label text if the label
+  // is wider than its preferred size (has no effect otherwise).
+  enum class BadgePlacement {
+    // Places the "New" badge immediately after the label text (default).
+    kImmediatelyAfterText,
+    // Places the "New" badge all the way at the trailing edge of the control,
+    // which is the right edge for LTR and the left edge for RTL.
+    kTrailingEdge
+  };
+
+  METADATA_HEADER(NewBadgeLabel);
+
+  // Constructs a new badge label. Designed to be argument-compatible with the
+  // views::Label constructor so they can be substituted.
+  explicit NewBadgeLabel(const base::string16& text = base::string16(),
+                         int text_context = views::style::CONTEXT_LABEL,
+                         int text_style = views::style::STYLE_PRIMARY,
+                         gfx::DirectionalityMode directionality_mode =
+                             gfx::DirectionalityMode::DIRECTIONALITY_FROM_TEXT);
+  NewBadgeLabel(const base::string16& text, const CustomFont& font);
+  ~NewBadgeLabel() override;
+
+  bool GetPadAfterNewBadge() const { return pad_after_new_badge_; }
+  void SetPadAfterNewBadge(bool pad_after_new_badge);
+
+  BadgePlacement GetBadgePlacement() const { return badge_placement_; }
+  void SetBadgePlacement(BadgePlacement badge_placement);
+
+  // Label:
+  void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
+  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size GetMinimumSize() const override;
+  int GetHeightForWidth(int w) const override;
+  void OnDeviceScaleFactorChanged(float old_device_scale_factor,
+                                  float new_device_scale_factor) override;
+  void OnPaint(gfx::Canvas* canvas) override;
+
+ private:
+  // Add the required internal padding to the label so that there is room to
+  // display the new badge.
+  void UpdatePaddingForNewBadge();
+
+  // Specifies the placement of the "New" badge when the label is wider than its
+  // preferred size.
+  BadgePlacement badge_placement_ = BadgePlacement::kImmediatelyAfterText;
+
+  // Determines whether there is additional internal margin to the right of the
+  // "New" badge. When set to true, the space will be allocated, and
+  // kInternalPaddingKey will be set so that layouts know this space is empty.
+  bool pad_after_new_badge_ = true;
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_IN_PRODUCT_HELP_NEW_BADGE_LABEL_H_
diff --git a/chrome/browser/ui/views/location_bar/content_setting_image_view.cc b/chrome/browser/ui/views/location_bar/content_setting_image_view.cc
index a6b80bc..fc992a38 100644
--- a/chrome/browser/ui/views/location_bar/content_setting_image_view.cc
+++ b/chrome/browser/ui/views/location_bar/content_setting_image_view.cc
@@ -81,7 +81,7 @@
       bubble_view_(nullptr) {
   DCHECK(delegate_);
   SetUpForInOutAnimation();
-  image()->EnableCanvasFlippingForRTLUI(true);
+  image()->SetFlipCanvasOnPaintForRTLUI(true);
 
   base::Optional<ViewID> view_id =
       GetViewID(content_setting_image_model_->image_type());
diff --git a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc
index 1e53ce22..9fbab356 100644
--- a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc
+++ b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc
@@ -149,7 +149,7 @@
   SetNotifyEnterExitOnChild(true);
 
   // Flip the canvas in RTL so the separator is drawn on the correct side.
-  separator_view_->EnableCanvasFlippingForRTLUI(true);
+  separator_view_->SetFlipCanvasOnPaintForRTLUI(true);
 
   auto alert_view = std::make_unique<views::AXVirtualView>();
   alert_view->GetCustomData().role = ax::mojom::Role::kAlert;
diff --git a/chrome/browser/ui/views/media_router/cast_toolbar_button.cc b/chrome/browser/ui/views/media_router/cast_toolbar_button.cc
index 927968e..f9b8301 100644
--- a/chrome/browser/ui/views/media_router/cast_toolbar_button.cc
+++ b/chrome/browser/ui/views/media_router/cast_toolbar_button.cc
@@ -59,7 +59,7 @@
   button_controller()->set_notify_action(
       views::ButtonController::NotifyAction::kOnPress);
 
-  EnableCanvasFlippingForRTLUI(false);
+  SetFlipCanvasOnPaintForRTLUI(false);
   SetTooltipText(l10n_util::GetStringUTF16(IDS_MEDIA_ROUTER_ICON_TOOLTIP_TEXT));
 
   IssuesObserver::Init();
diff --git a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
index db8f48c..f5a191b 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
@@ -182,7 +182,7 @@
 
   keyword_view_ = AddChildView(std::make_unique<OmniboxMatchCellView>(this));
   keyword_view_->SetVisible(false);
-  keyword_view_->icon()->EnableCanvasFlippingForRTLUI(true);
+  keyword_view_->icon()->SetFlipCanvasOnPaintForRTLUI(true);
   keyword_view_->icon()->SizeToPreferredSize();
 }
 
diff --git a/chrome/browser/ui/views/page_action/page_action_icon_view.cc b/chrome/browser/ui/views/page_action/page_action_icon_view.cc
index 459cb2d..7b363dc6 100644
--- a/chrome/browser/ui/views/page_action/page_action_icon_view.cc
+++ b/chrome/browser/ui/views/page_action/page_action_icon_view.cc
@@ -61,7 +61,7 @@
       command_id_(command_id) {
   DCHECK(delegate_);
 
-  image()->EnableCanvasFlippingForRTLUI(true);
+  image()->SetFlipCanvasOnPaintForRTLUI(true);
   SetInkDropMode(InkDropMode::ON);
   SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
   // Only shows bubble after mouse is released.
diff --git a/chrome/browser/ui/views/passwords/manage_passwords_icon_views.cc b/chrome/browser/ui/views/passwords/manage_passwords_icon_views.cc
index 138d3c7..af4ac0de 100644
--- a/chrome/browser/ui/views/passwords/manage_passwords_icon_views.cc
+++ b/chrome/browser/ui/views/passwords/manage_passwords_icon_views.cc
@@ -25,7 +25,7 @@
                          icon_label_bubble_delegate,
                          page_action_icon_delegate) {
   // Password icon should not be mirrored in RTL.
-  image()->EnableCanvasFlippingForRTLUI(false);
+  image()->SetFlipCanvasOnPaintForRTLUI(false);
 }
 
 ManagePasswordsIconViews::~ManagePasswordsIconViews() = default;
diff --git a/chrome/browser/ui/views/passwords/move_to_account_store_bubble_view.cc b/chrome/browser/ui/views/passwords/move_to_account_store_bubble_view.cc
index 7268ea3..825ec72 100644
--- a/chrome/browser/ui/views/passwords/move_to_account_store_bubble_view.cc
+++ b/chrome/browser/ui/views/passwords/move_to_account_store_bubble_view.cc
@@ -241,7 +241,7 @@
 
   auto arrow_view = std::make_unique<views::ColorTrackingIconView>(
       kChevronRightIcon, gfx::kFaviconSize);
-  arrow_view->EnableCanvasFlippingForRTLUI(true);
+  arrow_view->SetFlipCanvasOnPaintForRTLUI(true);
   AddChildView(std::move(arrow_view));
 
   to_view = AddChildView(std::move(to_image));
diff --git a/chrome/browser/ui/views/passwords/password_bubble_view_base.cc b/chrome/browser/ui/views/passwords/password_bubble_view_base.cc
index edd7e7f6..5f45d72 100644
--- a/chrome/browser/ui/views/passwords/password_bubble_view_base.cc
+++ b/chrome/browser/ui/views/passwords/password_bubble_view_base.cc
@@ -47,7 +47,8 @@
       button_provider->GetAnchorView(PageActionIconType::kManagePasswords);
 
   PasswordBubbleViewBase* bubble =
-      CreateBubble(web_contents, anchor_view, reason);
+      CreateBubble(web_contents, anchor_view, reason,
+                   browser_view->feature_promo_controller());
   DCHECK(bubble);
   DCHECK_EQ(bubble, g_manage_passwords_bubble_);
 
@@ -64,7 +65,8 @@
 PasswordBubbleViewBase* PasswordBubbleViewBase::CreateBubble(
     content::WebContents* web_contents,
     views::View* anchor_view,
-    DisplayReason reason) {
+    DisplayReason reason,
+    FeaturePromoControllerViews* promo_controller) {
   PasswordBubbleViewBase* view = nullptr;
   password_manager::ui::State model_state =
       PasswordsModelDelegateFromWebContents(web_contents)->GetState();
@@ -80,8 +82,8 @@
              model_state == password_manager::ui::PENDING_PASSWORD_STATE) {
     if (base::FeatureList::IsEnabled(
             password_manager::features::kEnablePasswordsAccountStorage)) {
-      view = new PasswordSaveUpdateWithAccountStoreView(web_contents,
-                                                        anchor_view, reason);
+      view = new PasswordSaveUpdateWithAccountStoreView(
+          web_contents, anchor_view, reason, promo_controller);
     } else {
       view = new PasswordSaveUpdateView(web_contents, anchor_view, reason);
     }
diff --git a/chrome/browser/ui/views/passwords/password_bubble_view_base.h b/chrome/browser/ui/views/passwords/password_bubble_view_base.h
index 7ac9e5a..63f15990d 100644
--- a/chrome/browser/ui/views/passwords/password_bubble_view_base.h
+++ b/chrome/browser/ui/views/passwords/password_bubble_view_base.h
@@ -23,6 +23,7 @@
 class Label;
 }
 
+class FeaturePromoControllerViews;
 class PasswordBubbleControllerBase;
 
 // Base class for all manage-passwords bubbles. Provides static methods for
@@ -50,7 +51,8 @@
   static PasswordBubbleViewBase* CreateBubble(
       content::WebContents* web_contents,
       views::View* anchor_view,
-      DisplayReason reason);
+      DisplayReason reason,
+      FeaturePromoControllerViews* promo_controller);
 
   // Closes the existing bubble.
   static void CloseCurrentBubble();
diff --git a/chrome/browser/ui/views/passwords/password_save_update_with_account_store_view.cc b/chrome/browser/ui/views/passwords/password_save_update_with_account_store_view.cc
index 8f1b2043..e12ed85 100644
--- a/chrome/browser/ui/views/passwords/password_save_update_with_account_store_view.cc
+++ b/chrome/browser/ui/views/passwords/password_save_update_with_account_store_view.cc
@@ -22,7 +22,7 @@
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "chrome/browser/ui/views/chrome_typography.h"
 #include "chrome/browser/ui/views/in_product_help/feature_promo_bubble_params.h"
-#include "chrome/browser/ui/views/in_product_help/feature_promo_bubble_view.h"
+#include "chrome/browser/ui/views/in_product_help/feature_promo_controller_views.h"
 #include "chrome/browser/ui/views/passwords/credentials_item_view.h"
 #include "chrome/browser/ui/views/passwords/password_items_view.h"
 #include "chrome/grit/generated_resources.h"
@@ -350,7 +350,8 @@
 PasswordSaveUpdateWithAccountStoreView::PasswordSaveUpdateWithAccountStoreView(
     content::WebContents* web_contents,
     views::View* anchor_view,
-    DisplayReason reason)
+    DisplayReason reason,
+    FeaturePromoControllerViews* promo_controller)
     : PasswordBubbleViewBase(web_contents,
                              anchor_view,
                              /*auto_dismissable=*/false),
@@ -362,7 +363,8 @@
       is_update_bubble_(controller_.state() ==
                         password_manager::ui::PENDING_PASSWORD_UPDATE_STATE),
       are_passwords_revealed_(
-          controller_.are_passwords_revealed_when_bubble_is_opened()) {
+          controller_.are_passwords_revealed_when_bubble_is_opened()),
+      promo_controller_(promo_controller) {
   // If kEnablePasswordsAccountStorage is disabled, then PasswordSaveUpdateView
   // should be used instead of this class.
   DCHECK(base::FeatureList::IsEnabled(
@@ -527,39 +529,20 @@
   controller_.OnToggleAccountStore(is_account_store_selected);
   // Saving in account and local stores have different header images.
   UpdateHeaderImage();
-  // If the user explicitly switched to "save on this device only", record this
-  // with the IPH tracker (so it can decide not to show the IPH again).
-  if (!is_account_store_selected) {
-    if (!iph_tracker_) {
-      iph_tracker_ = feature_engagement::TrackerFactory::GetForBrowserContext(
-          controller_.GetProfile());
-    }
-    iph_tracker_->NotifyEvent("passwords_account_storage_unselected");
+  // If the user explicitly switched to "save on this device only",
+  // record this with the IPH tracker (so it can decide not to show the
+  // IPH again). It may be null in tests, so handle that case.
+  if (!is_account_store_selected && promo_controller_) {
+    promo_controller_->feature_engagement_tracker()->NotifyEvent(
+        "passwords_account_storage_unselected");
   }
   // The IPH shown upon failure in reauth is used to informs the user that the
   // password will be stored on device. This is why it's important to close it
   // if the user changes the destination to account.
-  if (currenly_shown_iph_type_ == IPHType::kFailedReauth)
+  if (failed_reauth_promo_id_)
     CloseIPHBubbleIfOpen();
 }
 
-void PasswordSaveUpdateWithAccountStoreView::OnWidgetDestroying(
-    views::Widget* widget) {
-  // IPH bubble is getting closed.
-  if (account_storage_promo_ && account_storage_promo_->GetWidget() == widget) {
-    observed_account_storage_promo_.Remove(widget);
-    // If the reauth failed, we have shown the IPH unconditionally. No need to
-    // inform the tracker. Only regular IPH's are tracked
-    if (currenly_shown_iph_type_ == IPHType::kRegular) {
-      DCHECK(iph_tracker_);
-      iph_tracker_->Dismissed(
-          feature_engagement::kIPHPasswordsAccountStorageFeature);
-    }
-    currenly_shown_iph_type_ = IPHType::kNone;
-    account_storage_promo_ = nullptr;
-  }
-}
-
 views::View* PasswordSaveUpdateWithAccountStoreView::GetInitiallyFocusedView() {
   if (username_dropdown_ && username_dropdown_->GetText().empty())
     return username_dropdown_;
@@ -588,9 +571,9 @@
       ->SetAllowCharacterBreak(true);
 
   if (ShouldShowFailedReauthIPH())
-    ShowIPH(IPHType::kFailedReauth);
-  else if (ShouldShowRegularIPH())
-    ShowIPH(IPHType::kRegular);
+    MaybeShowIPH(IPHType::kFailedReauth);
+  else
+    MaybeShowIPH(IPHType::kRegular);
 }
 
 void PasswordSaveUpdateWithAccountStoreView::OnThemeChanged() {
@@ -615,8 +598,8 @@
 void PasswordSaveUpdateWithAccountStoreView::OnLayoutIsAnimatingChanged(
     views::AnimatingLayoutManager* source,
     bool is_animating) {
-  if (!is_animating && ShouldShowRegularIPH())
-    ShowIPH(IPHType::kRegular);
+  if (!is_animating)
+    MaybeShowIPH(IPHType::kRegular);
 }
 
 void PasswordSaveUpdateWithAccountStoreView::TogglePasswordVisibility() {
@@ -703,27 +686,7 @@
   GetBubbleFrameView()->SetHeaderView(CreateHeaderImage(id));
 }
 
-bool PasswordSaveUpdateWithAccountStoreView::ShouldShowRegularIPH() {
-  // IPH is shown only where the destination dropdown is shown (i.e. only for
-  // Save bubble).
-  if (!destination_dropdown_ || controller_.IsCurrentStateUpdate())
-    return false;
-
-  if (!iph_tracker_) {
-    iph_tracker_ = feature_engagement::TrackerFactory::GetForBrowserContext(
-        controller_.GetProfile());
-  }
-
-  return iph_tracker_->ShouldTriggerHelpUI(
-      feature_engagement::kIPHPasswordsAccountStorageFeature);
-}
-
 bool PasswordSaveUpdateWithAccountStoreView::ShouldShowFailedReauthIPH() {
-  // IPH is shown only where the destination dropdown is shown (i.e. only for
-  // Save bubble).
-  if (!destination_dropdown_ || controller_.IsCurrentStateUpdate())
-    return false;
-
   // If the reauth failed, we should have automatically switched to local mdoe,
   // and we should show the reauth failed IPH unconditionally as long as the
   // user didn't change the save location.
@@ -731,21 +694,17 @@
          !controller_.IsUsingAccountStore();
 }
 
-void PasswordSaveUpdateWithAccountStoreView::ShowIPH(IPHType type) {
+void PasswordSaveUpdateWithAccountStoreView::MaybeShowIPH(IPHType type) {
   DCHECK_NE(IPHType::kNone, type);
-  DCHECK(destination_dropdown_);
-  DCHECK(destination_dropdown_->GetVisible());
 
-  base::Optional<int> title_string_specificer;
-  if (type == IPHType::kRegular) {
-    // IPH when reauth fails has no title.
-    title_string_specificer = IDS_PASSWORD_MANAGER_IPH_TITLE_SAVE_TO_ACCOUNT;
-  }
+  // IPH is shown only where the destination dropdown is shown (i.e. only for
+  // Save bubble).
+  if (!destination_dropdown_ || controller_.IsCurrentStateUpdate())
+    return;
 
-  int body_string_specificer =
-      type == IPHType::kRegular
-          ? IDS_PASSWORD_MANAGER_IPH_BODY_SAVE_TO_ACCOUNT
-          : IDS_PASSWORD_MANAGER_IPH_BODY_SAVE_REAUTH_FAIL;
+  // The promo controller may not exist in tests.
+  if (!promo_controller_)
+    return;
 
   // Make sure the Save/Update bubble doesn't get closed when the IPH bubble is
   // opened.
@@ -753,8 +712,6 @@
   set_close_on_deactivate(false);
 
   FeaturePromoBubbleParams bubble_params;
-  bubble_params.body_string_specifier = body_string_specificer;
-  bubble_params.title_string_specifier = title_string_specificer;
   bubble_params.anchor_view = destination_dropdown_;
   bubble_params.arrow = views::BubbleBorder::RIGHT_CENTER;
   bubble_params.preferred_width = kAccountStoragePromoWidth;
@@ -763,18 +720,48 @@
   bubble_params.timeout_default = GetRegularIPHTimeout();
   bubble_params.timeout_short = GetShortIPHTimeout();
 
-  account_storage_promo_ =
-      FeaturePromoBubbleView::Create(std::move(bubble_params));
-  set_close_on_deactivate(close_save_bubble_on_deactivate_original_value);
-  observed_account_storage_promo_.Add(account_storage_promo_->GetWidget());
+  if (type == IPHType::kRegular) {
+    bubble_params.body_string_specifier =
+        IDS_PASSWORD_MANAGER_IPH_BODY_SAVE_TO_ACCOUNT;
+    bubble_params.title_string_specifier =
+        IDS_PASSWORD_MANAGER_IPH_TITLE_SAVE_TO_ACCOUNT;
 
-  currenly_shown_iph_type_ = type;
+    if (promo_controller_->MaybeShowPromoWithParams(
+            feature_engagement::kIPHPasswordsAccountStorageFeature,
+            bubble_params)) {
+      // If the regular promo was shown, the failed reauth promo is
+      // definitely finished. If not, we can't be confident it hasn't
+      // finished.
+      failed_reauth_promo_id_ = base::nullopt;
+    }
+  } else {
+    bubble_params.body_string_specifier =
+        IDS_PASSWORD_MANAGER_IPH_BODY_SAVE_REAUTH_FAIL;
+
+    failed_reauth_promo_id_ =
+        promo_controller_->ShowCriticalPromo(bubble_params);
+  }
+
+  set_close_on_deactivate(close_save_bubble_on_deactivate_original_value);
 }
 
 void PasswordSaveUpdateWithAccountStoreView::CloseIPHBubbleIfOpen() {
-  if (!account_storage_promo_)
+  // The promo controller may not exist in tests.
+  if (!promo_controller_)
     return;
-  account_storage_promo_->CloseBubble();
+
+  if (!failed_reauth_promo_id_) {
+    promo_controller_->CloseBubble(
+        feature_engagement::kIPHPasswordsAccountStorageFeature);
+    return;
+  }
+
+  // |failed_reauth_promo_id_| may have a value if it closed on its
+  // own. This is fine; CloseBubbleForCriticalPromo() handles expired
+  // IDs, and we reset ours when showing a normal IPH bubble.
+  promo_controller_->CloseBubbleForCriticalPromo(
+      failed_reauth_promo_id_.value());
+  failed_reauth_promo_id_ = base::nullopt;
 }
 
 void PasswordSaveUpdateWithAccountStoreView::AnnounceSaveUpdateChange() {
diff --git a/chrome/browser/ui/views/passwords/password_save_update_with_account_store_view.h b/chrome/browser/ui/views/passwords/password_save_update_with_account_store_view.h
index 2227306..74437a7f 100644
--- a/chrome/browser/ui/views/passwords/password_save_update_with_account_store_view.h
+++ b/chrome/browser/ui/views/passwords/password_save_update_with_account_store_view.h
@@ -5,6 +5,8 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_PASSWORDS_PASSWORD_SAVE_UPDATE_WITH_ACCOUNT_STORE_VIEW_H_
 #define CHROME_BROWSER_UI_VIEWS_PASSWORDS_PASSWORD_SAVE_UPDATE_WITH_ACCOUNT_STORE_VIEW_H_
 
+#include "base/optional.h"
+#include "base/token.h"
 #include "chrome/browser/ui/passwords/bubble_controllers/save_update_with_account_store_bubble_controller.h"
 #include "chrome/browser/ui/views/passwords/password_bubble_view_base.h"
 #include "ui/views/layout/animating_layout_manager.h"
@@ -17,11 +19,7 @@
 class ToggleImageButton;
 }  // namespace views
 
-namespace feature_engagement {
-class Tracker;
-}
-
-class FeaturePromoBubbleView;
+class FeaturePromoControllerViews;
 
 // A view offering the user the ability to save or update credentials (depending
 // on |is_update_bubble|) either in the profile and/or account stores. Contains
@@ -33,9 +31,11 @@
       public views::WidgetObserver,
       public views::AnimatingLayoutManager::Observer {
  public:
-  PasswordSaveUpdateWithAccountStoreView(content::WebContents* web_contents,
-                                         views::View* anchor_view,
-                                         DisplayReason reason);
+  PasswordSaveUpdateWithAccountStoreView(
+      content::WebContents* web_contents,
+      views::View* anchor_view,
+      DisplayReason reason,
+      FeaturePromoControllerViews* promo_controller);
 
   views::Combobox* DestinationDropdownForTesting() {
     return destination_dropdown_;
@@ -58,9 +58,6 @@
   PasswordBubbleControllerBase* GetController() override;
   const PasswordBubbleControllerBase* GetController() const override;
 
-  // views::WidgetObserver:
-  void OnWidgetDestroying(views::Widget* widget) override;
-
   // PasswordBubbleViewBase:
   views::View* GetInitiallyFocusedView() override;
   bool IsDialogButtonEnabled(ui::DialogButton button) const override;
@@ -81,19 +78,13 @@
 
   void DestinationChanged();
 
-  // Whether we should show the IPH informing the user about the destination
-  // picker and that they can now select where to store the passwords. It
-  // creates (if needed) and queries the |iph_tracker_|
-  bool ShouldShowRegularIPH();
-
   // Whether we should shown an IPH upon account reauth failure that informs the
   // user that the destination has been automatically switched to device.
   bool ShouldShowFailedReauthIPH();
 
-  // Opens an IPH bubble of |type|. Callers should make sure the
-  // pre-conditions are satisfied by calling the corresponding ShouldShow*IPH()
-  // methods before invoking this method.
-  void ShowIPH(IPHType type);
+  // Tries to show an IPH bubble of |type|. For kFailedReauth,
+  // ShouldShowFailedReauthIPH() should be checked first.
+  void MaybeShowIPH(IPHType type);
 
   void CloseIPHBubbleIfOpen();
 
@@ -119,18 +110,12 @@
   views::EditableCombobox* password_dropdown_ = nullptr;
   bool are_passwords_revealed_;
 
-  feature_engagement::Tracker* iph_tracker_ = nullptr;
+  // Used to display IPH. May be null in tests.
+  FeaturePromoControllerViews* const promo_controller_;
 
-  // Promotional UI that appears next to the |destination_dropdown_|. Owned by
-  // its NativeWidget.
-  FeaturePromoBubbleView* account_storage_promo_ = nullptr;
-
-  IPHType currenly_shown_iph_type_ = IPHType::kNone;
-
-  // Observes the |account_storage_promo_|'s Widget.  Used to tell whether the
-  // promo is open and get called back when it closes.
-  ScopedObserver<views::Widget, views::WidgetObserver>
-      observed_account_storage_promo_{this};
+  // When showing kReauthFailure IPH, |promo_controller_| gives back an
+  // ID. This is used to close the bubble later.
+  base::Optional<base::Token> failed_reauth_promo_id_;
 
   // Hidden view that will contain status text for immediate output by
   // screen readers when the bubble changes state between Save and Update.
diff --git a/chrome/browser/ui/views/passwords/password_save_update_with_account_store_view_unittest.cc b/chrome/browser/ui/views/passwords/password_save_update_with_account_store_view_unittest.cc
index 63f4652..1ba2cbe 100644
--- a/chrome/browser/ui/views/passwords/password_save_update_with_account_store_view_unittest.cc
+++ b/chrome/browser/ui/views/passwords/password_save_update_with_account_store_view_unittest.cc
@@ -100,7 +100,8 @@
   CreateAnchorViewAndShow();
 
   view_ = new PasswordSaveUpdateWithAccountStoreView(
-      web_contents(), anchor_view(), LocationBarBubbleDelegateView::AUTOMATIC);
+      web_contents(), anchor_view(), LocationBarBubbleDelegateView::AUTOMATIC,
+      /*promo_controller=*/nullptr);
   views::BubbleDialogDelegateView::CreateBubble(view_)->Show();
 }
 
diff --git a/chrome/browser/ui/views/profiles/avatar_toolbar_button.cc b/chrome/browser/ui/views/profiles/avatar_toolbar_button.cc
index bc842a4..d16b5e3c 100644
--- a/chrome/browser/ui/views/profiles/avatar_toolbar_button.cc
+++ b/chrome/browser/ui/views/profiles/avatar_toolbar_button.cc
@@ -67,7 +67,7 @@
 
   // The avatar should not flip with RTL UI. This does not affect text rendering
   // and LabelButton image/label placement is still flipped like usual.
-  EnableCanvasFlippingForRTLUI(false);
+  SetFlipCanvasOnPaintForRTLUI(false);
 
   GetViewAccessibility().OverrideHasPopup(ax::mojom::HasPopup::kMenu);
 
diff --git a/chrome/browser/ui/views/profiles/profile_indicator_icon.cc b/chrome/browser/ui/views/profiles/profile_indicator_icon.cc
index 2e8a497..cd1f5cc 100644
--- a/chrome/browser/ui/views/profiles/profile_indicator_icon.cc
+++ b/chrome/browser/ui/views/profiles/profile_indicator_icon.cc
@@ -9,7 +9,7 @@
 
 ProfileIndicatorIcon::ProfileIndicatorIcon() {
   // In RTL mode, the incognito icon should be looking the opposite direction.
-  EnableCanvasFlippingForRTLUI(true);
+  SetFlipCanvasOnPaintForRTLUI(true);
 }
 
 ProfileIndicatorIcon::~ProfileIndicatorIcon() {}
diff --git a/chrome/browser/ui/views/profiles/profile_menu_view_base.cc b/chrome/browser/ui/views/profiles/profile_menu_view_base.cc
index f0490b44..e7598b1 100644
--- a/chrome/browser/ui/views/profiles/profile_menu_view_base.cc
+++ b/chrome/browser/ui/views/profiles/profile_menu_view_base.cc
@@ -523,7 +523,7 @@
   DCHECK(anchor_button);
   anchor_button->AnimateInkDrop(views::InkDropState::ACTIVATED, nullptr);
 
-  EnableUpDownKeyboardAccelerators();
+  GetFocusManager()->set_arrow_key_traversal_enabled(true);
   GetViewAccessibility().OverrideRole(ax::mojom::Role::kMenu);
 }
 
@@ -732,7 +732,7 @@
                               base::Unretained(this), std::move(action)),
           icon, text, SK_ColorTRANSPARENT,
           /*show_border=*/true));
-  button->EnableCanvasFlippingForRTLUI(false);
+  button->SetFlipCanvasOnPaintForRTLUI(false);
 }
 
 void ProfileMenuViewBase::AddFeatureButton(const base::string16& text,
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc
index 2df6fa0..72a7bfc 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -1707,7 +1707,7 @@
 }
 
 views::View* TabStrip::GetTabViewForPromoAnchor(int index_hint) {
-  return tab_at(base::ClampToRange(index_hint, 0, tab_count()));
+  return tab_at(base::ClampToRange(index_hint, 0, tab_count() - 1));
 }
 
 views::View* TabStrip::GetDefaultFocusableChild() {
diff --git a/chrome/browser/ui/views/toolbar/app_menu.cc b/chrome/browser/ui/views/toolbar/app_menu.cc
index 0e7b9c29..01bed2ae 100644
--- a/chrome/browser/ui/views/toolbar/app_menu.cc
+++ b/chrome/browser/ui/views/toolbar/app_menu.cc
@@ -166,7 +166,7 @@
       // We need to flip the canvas for RTL iff the button is not auto-flipping
       // already, so we end up flipping exactly once.
       gfx::ScopedCanvas scoped_canvas(canvas);
-      if (!view->flip_canvas_on_paint_for_rtl_ui())
+      if (!view->GetFlipCanvasOnPaintForRTLUI())
         scoped_canvas.FlipIfRTL(view->width());
       ui::NativeTheme::ExtraParams params;
       gfx::Rect separator_bounds =
diff --git a/chrome/browser/ui/views/toolbar/toolbar_button.cc b/chrome/browser/ui/views/toolbar/toolbar_button.cc
index 2efbf23..7cb1ee9 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_button.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_button.cc
@@ -87,7 +87,7 @@
 
   // Make sure icons are flipped by default so that back, forward, etc. follows
   // UI direction.
-  EnableCanvasFlippingForRTLUI(true);
+  SetFlipCanvasOnPaintForRTLUI(true);
 
   SetInkDropVisibleOpacity(kToolbarInkDropVisibleOpacity);
 
diff --git a/chrome/browser/ui/views/toolbar/toolbar_view.cc b/chrome/browser/ui/views/toolbar/toolbar_view.cc
index aaec20f0..058a014 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_view.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_view.cc
@@ -290,7 +290,7 @@
       base::BindRepeating(&ToolbarView::AppMenuButtonPressed,
                           base::Unretained(this)),
       this);
-  app_menu_button->EnableCanvasFlippingForRTLUI(true);
+  app_menu_button->SetFlipCanvasOnPaintForRTLUI(true);
   app_menu_button->SetAccessibleName(
       l10n_util::GetStringUTF16(IDS_ACCNAME_APP));
   app_menu_button->SetTooltipText(
diff --git a/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc b/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc
index 8a7a4b2..eda14f9 100644
--- a/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc
+++ b/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc
@@ -71,7 +71,6 @@
 #include "chrome/browser/ui/webui/chromeos/login/network_state_informer.h"
 #include "chrome/browser/ui/webui/chromeos/login/oobe_display_chooser.h"
 #include "chrome/browser/ui/webui/chromeos/login/packaged_license_screen_handler.h"
-#include "chrome/browser/ui/webui/chromeos/login/parental_handoff_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/pin_setup_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/recommend_apps_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/reset_screen_handler.h"
@@ -182,7 +181,7 @@
   source->AddResourcePath(kArcPlaystoreCSSPath, IDR_ARC_SUPPORT_PLAYSTORE_CSS);
   source->AddResourcePath(kArcPlaystoreJSPath, IDR_ARC_SUPPORT_PLAYSTORE_JS);
   source->AddResourcePath(kArcPlaystoreLogoPath,
-                          IDR_ARC_SUPPORT_PLAYSTORE_LOGO);
+      IDR_ARC_SUPPORT_PLAYSTORE_LOGO);
 
   source->AddResourcePath(kRecommendAppListViewJSPath,
                           IDR_ARC_SUPPORT_RECOMMEND_APP_LIST_VIEW_JS);
@@ -506,9 +505,6 @@
   AddScreenHandler(
       std::make_unique<TpmErrorScreenHandler>(js_calls_container_.get()));
 
-  AddScreenHandler(std::make_unique<ParentalHandoffScreenHandler>(
-      js_calls_container_.get()));
-
   Profile* profile = Profile::FromWebUI(web_ui());
   // Set up the chrome://theme/ source, for Chrome logo.
   content::URLDataSource::Add(profile, std::make_unique<ThemeSource>(profile));
diff --git a/chrome/browser/ui/webui/chromeos/login/parental_handoff_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/parental_handoff_screen_handler.cc
deleted file mode 100644
index e57488b8..0000000
--- a/chrome/browser/ui/webui/chromeos/login/parental_handoff_screen_handler.cc
+++ /dev/null
@@ -1,64 +0,0 @@
-// 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/webui/chromeos/login/parental_handoff_screen_handler.h"
-
-#include "chrome/browser/chromeos/login/screens/parental_handoff_screen.h"
-#include "chrome/browser/ui/webui/chromeos/login/js_calls_container.h"
-#include "chrome/grit/chromium_strings.h"
-#include "chrome/grit/generated_resources.h"
-#include "components/login/localized_values_builder.h"
-#include "ui/base/l10n/l10n_util.h"
-
-namespace chromeos {
-
-namespace {
-
-constexpr char kTitle[] = "title";
-constexpr char kSubTitle[] = "subtitle";
-
-}  // namespace
-
-// static
-constexpr StaticOobeScreenId ParentalHandoffScreenView::kScreenId;
-
-ParentalHandoffScreenHandler::ParentalHandoffScreenHandler(
-    JSCallsContainer* js_calls_container)
-    : BaseScreenHandler(kScreenId, js_calls_container) {
-  set_user_acted_method_path("login.ParentalHandoffScreen.userActed");
-}
-
-ParentalHandoffScreenHandler::~ParentalHandoffScreenHandler() {
-  if (screen_)
-    screen_->OnViewDestroyed(this);
-}
-
-void ParentalHandoffScreenHandler::DeclareLocalizedValues(
-    ::login::LocalizedValuesBuilder* builder) {
-  builder->Add("parentalHandoffDialogNextButton",
-               IDS_LOGIN_PARENTAL_HANDOFF_SCREEN_NEXT_BUTTON);
-}
-
-void ParentalHandoffScreenHandler::Initialize() {}
-
-void ParentalHandoffScreenHandler::Show(const base::string16& title,
-                                        const base::string16& subtitle) {
-  base::DictionaryValue data;
-  data.SetString(kTitle, title);
-  data.SetString(kSubTitle, subtitle);
-
-  ShowScreenWithData(kScreenId, &data);
-}
-
-void ParentalHandoffScreenHandler::Bind(ParentalHandoffScreen* screen) {
-  screen_ = screen;
-  BaseScreenHandler::SetBaseScreen(screen_);
-}
-
-void ParentalHandoffScreenHandler::Unbind() {
-  screen_ = nullptr;
-  BaseScreenHandler::SetBaseScreen(nullptr);
-}
-
-}  // namespace chromeos
diff --git a/chrome/browser/ui/webui/chromeos/login/parental_handoff_screen_handler.h b/chrome/browser/ui/webui/chromeos/login/parental_handoff_screen_handler.h
deleted file mode 100644
index 91fbdee..0000000
--- a/chrome/browser/ui/webui/chromeos/login/parental_handoff_screen_handler.h
+++ /dev/null
@@ -1,67 +0,0 @@
-// 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_WEBUI_CHROMEOS_LOGIN_PARENTAL_HANDOFF_SCREEN_HANDLER_H_
-#define CHROME_BROWSER_UI_WEBUI_CHROMEOS_LOGIN_PARENTAL_HANDOFF_SCREEN_HANDLER_H_
-
-#include "base/strings/string16.h"
-#include "chrome/browser/ui/webui/chromeos/login/base_screen_handler.h"
-
-namespace login {
-class LocalizedValuesBuilder;
-}  // namespace login
-
-namespace chromeos {
-
-class ParentalHandoffScreen;
-class JSCallsContainer;
-
-// Interface for dependency injection between ParentalHandoffScreen and its
-// WebUI representation.
-class ParentalHandoffScreenView {
- public:
-  constexpr static StaticOobeScreenId kScreenId{"parental-handoff"};
-
-  virtual ~ParentalHandoffScreenView() = default;
-
-  // Shows the contents of the screen.
-  virtual void Show(const base::string16& title,
-                    const base::string16& subtitle) = 0;
-
-  // Binds |screen| to the view.
-  virtual void Bind(ParentalHandoffScreen* screen) = 0;
-
-  // Unbinds the screen from the view.
-  virtual void Unbind() = 0;
-};
-
-class ParentalHandoffScreenHandler : public BaseScreenHandler,
-                                     public ParentalHandoffScreenView {
- public:
-  using TView = ParentalHandoffScreenView;
-
-  explicit ParentalHandoffScreenHandler(JSCallsContainer* js_calls_container);
-  ParentalHandoffScreenHandler(const ParentalHandoffScreenHandler&) = delete;
-  ParentalHandoffScreenHandler& operator=(const ParentalHandoffScreenHandler&) =
-      delete;
-  ~ParentalHandoffScreenHandler() override;
-
- private:
-  // BaseScreenHandler:
-  void DeclareLocalizedValues(
-      ::login::LocalizedValuesBuilder* builder) override;
-  void Initialize() override;
-
-  // Shows the contents of the screen.
-  void Show(const base::string16& title,
-            const base::string16& subtitle) override;
-  void Bind(ParentalHandoffScreen* screen) override;
-  void Unbind() override;
-
-  ParentalHandoffScreen* screen_ = nullptr;
-};
-
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_UI_WEBUI_CHROMEOS_LOGIN_PARENTAL_HANDOFF_SCREEN_HANDLER_H_
diff --git a/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.cc b/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.cc
index d775a43..4df2cf1 100644
--- a/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.cc
@@ -11,6 +11,8 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chromeos/components/multidevice/logging/logging.h"
 #include "chromeos/components/phonehub/fake_phone_hub_manager.h"
+#include "chromeos/components/phonehub/pref_names.h"
+#include "components/prefs/pref_service.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/gfx/image/image.h"
 
@@ -184,6 +186,18 @@
       "setTetherStatus",
       base::BindRepeating(&MultidevicePhoneHubHandler::HandleSetTetherStatus,
                           base::Unretained(this)));
+
+  web_ui()->RegisterMessageCallback(
+      "resetShouldShowOnboardingUi",
+      base::BindRepeating(
+          &MultidevicePhoneHubHandler::HandleResetShouldShowOnboardingUi,
+          base::Unretained(this)));
+
+  web_ui()->RegisterMessageCallback(
+      "resetHasNotificationSetupUiBeenDismissed",
+      base::BindRepeating(&MultidevicePhoneHubHandler::
+                              HandleResetHasNotificationSetupUiBeenDismissed,
+                          base::Unretained(this)));
 }
 
 void MultidevicePhoneHubHandler::OnJavascriptDisallowed() {
@@ -199,6 +213,8 @@
       fake_phone_hub_manager_->fake_find_my_device_controller());
   tether_controller_observer_.Add(
       fake_phone_hub_manager_->fake_tether_controller());
+  onboarding_ui_tracker_observer_.Add(
+      fake_phone_hub_manager_->fake_onboarding_ui_tracker());
 }
 
 void MultidevicePhoneHubHandler::RemoveObservers() {
@@ -228,6 +244,12 @@
   if (tether_controller_observer_.IsObserving(fake_tether_controller)) {
     tether_controller_observer_.Remove(fake_tether_controller);
   }
+
+  phonehub::OnboardingUiTracker* fake_onboarding_ui_tracker =
+      fake_phone_hub_manager_->fake_onboarding_ui_tracker();
+  if (onboarding_ui_tracker_observer_.IsObserving(fake_onboarding_ui_tracker)) {
+    onboarding_ui_tracker_observer_.Remove(fake_onboarding_ui_tracker);
+  }
 }
 
 void MultidevicePhoneHubHandler::OnNotificationsRemoved(
@@ -261,6 +283,14 @@
   FireWebUIListener("tether-status-changed", base::Value(status_as_int));
 }
 
+void MultidevicePhoneHubHandler::OnShouldShowOnboardingUiChanged() {
+  bool should_show_onboarding_ui =
+      fake_phone_hub_manager_->fake_onboarding_ui_tracker()
+          ->ShouldShowOnboardingUi();
+  FireWebUIListener("should-show-onboarding-ui-changed",
+                    base::Value(should_show_onboarding_ui));
+}
+
 void MultidevicePhoneHubHandler::HandleEnableDnd(const base::ListValue* args) {
   bool enabled = false;
   CHECK(args->GetBoolean(0, &enabled));
@@ -528,5 +558,22 @@
   PA_LOG(VERBOSE) << "Removed notification with id " << notification_id;
 }
 
+void MultidevicePhoneHubHandler::HandleResetShouldShowOnboardingUi(
+    const base::ListValue* args) {
+  PrefService* prefs = Profile::FromWebUI(web_ui())->GetPrefs();
+  prefs->SetBoolean(
+      chromeos::phonehub::prefs::kHasDismissedUiAfterCompletingOnboarding,
+      false);
+  PA_LOG(VERBOSE) << "Reset kHasDismissedUiAfterCompletingOnboarding pref";
+}
+
+void MultidevicePhoneHubHandler::HandleResetHasNotificationSetupUiBeenDismissed(
+    const base::ListValue* args) {
+  PrefService* prefs = Profile::FromWebUI(web_ui())->GetPrefs();
+  prefs->SetBoolean(chromeos::phonehub::prefs::kHasDismissedSetupRequiredUi,
+                    false);
+  PA_LOG(VERBOSE) << "Reset kHasDismissedSetupRequiredUi pref";
+}
+
 }  // namespace multidevice
 }  // namespace chromeos
diff --git a/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.h b/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.h
index e4f63bc..e0ec4f05 100644
--- a/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.h
+++ b/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.h
@@ -9,6 +9,7 @@
 #include "chromeos/components/phonehub/do_not_disturb_controller.h"
 #include "chromeos/components/phonehub/find_my_device_controller.h"
 #include "chromeos/components/phonehub/notification_manager.h"
+#include "chromeos/components/phonehub/onboarding_ui_tracker.h"
 #include "chromeos/components/phonehub/tether_controller.h"
 #include "content/public/browser/web_ui_message_handler.h"
 
@@ -26,7 +27,8 @@
       public phonehub::NotificationManager::Observer,
       public phonehub::DoNotDisturbController::Observer,
       public phonehub::FindMyDeviceController::Observer,
-      public phonehub::TetherController::Observer {
+      public phonehub::TetherController::Observer,
+      public phonehub::OnboardingUiTracker::Observer {
  public:
   MultidevicePhoneHubHandler();
   MultidevicePhoneHubHandler(const MultidevicePhoneHubHandler&) = delete;
@@ -53,6 +55,9 @@
   // TetherController::Observer
   void OnTetherStatusChanged() override;
 
+  // OnboardingUiTracker::Observer
+  void OnShouldShowOnboardingUiChanged() override;
+
   void EnableRealPhoneHubManager();
   void EnableFakePhoneHubManager();
   void HandleEnableFakePhoneHubManager(const base::ListValue* args);
@@ -66,6 +71,9 @@
   void HandleEnableDnd(const base::ListValue* args);
   void HandleSetFindMyDeviceStatus(const base::ListValue* args);
   void HandleSetTetherStatus(const base::ListValue* args);
+  void HandleResetShouldShowOnboardingUi(const base::ListValue* args);
+  void HandleResetHasNotificationSetupUiBeenDismissed(
+      const base::ListValue* args);
 
   void AddObservers();
   void RemoveObservers();
@@ -83,6 +91,9 @@
   ScopedObserver<phonehub::TetherController,
                  phonehub::TetherController::Observer>
       tether_controller_observer_{this};
+  ScopedObserver<phonehub::OnboardingUiTracker,
+                 phonehub::OnboardingUiTracker::Observer>
+      onboarding_ui_tracker_observer_{this};
 };
 
 }  // namespace multidevice
diff --git a/chrome/browser/ui/webui/tab_search/tab_search_ui.cc b/chrome/browser/ui/webui/tab_search/tab_search_ui.cc
index f4e2e5f8..6caa474 100644
--- a/chrome/browser/ui/webui/tab_search/tab_search_ui.cc
+++ b/chrome/browser/ui/webui/tab_search/tab_search_ui.cc
@@ -28,7 +28,7 @@
 #if BUILDFLAG(ENABLE_TAB_SEARCH)
 namespace {
 constexpr char kGeneratedPath[] =
-    "@out_folder@/gen/chrome/browser/resources/tab_search/";
+    "@out_folder@/gen/chrome/browser/resources/tab_search_merge/";
 }
 #endif  // BUILDFLAG(ENABLE_TAB_SEARCH)
 
diff --git a/chrome/browser/video_tutorials/internal/android/java/res/layout/video_tutorial_large_card.xml b/chrome/browser/video_tutorials/internal/android/java/res/layout/video_tutorial_large_card.xml
index 4bcf06e..aba5a81 100644
--- a/chrome/browser/video_tutorials/internal/android/java/res/layout/video_tutorial_large_card.xml
+++ b/chrome/browser/video_tutorials/internal/android/java/res/layout/video_tutorial_large_card.xml
@@ -53,10 +53,13 @@
         android:layout_height="wrap_content"
         android:layout_marginStart="11dp"
         android:layout_marginBottom="11dp"
+        android:background="@color/modern_grey_900"
+        android:paddingStart="4dp"
+        android:paddingEnd="4dp"
         app:layout_column="0"
         app:layout_row="0"
         app:layout_gravity="bottom"
-        android:textAppearance="@style/TextAppearance.TextMedium.Secondary" />
+        android:textAppearance="@style/TextAppearance.TextSmall.Primary.Light" />
 
     <TextView
         android:id="@+id/title"
diff --git a/chrome/browser/video_tutorials/internal/proto_conversions.cc b/chrome/browser/video_tutorials/internal/proto_conversions.cc
index eb850eb..f8cb439 100644
--- a/chrome/browser/video_tutorials/internal/proto_conversions.cc
+++ b/chrome/browser/video_tutorials/internal/proto_conversions.cc
@@ -26,9 +26,8 @@
     case proto::FeatureType::TEST:
       return FeatureType::kTest;
     default:
-      NOTREACHED();
+      return static_cast<FeatureType>(type);
   }
-  return FeatureType::kInvalid;
 }
 
 proto::FeatureType FromFeatureType(FeatureType type) {
@@ -48,9 +47,8 @@
     case FeatureType::kTest:
       return proto::FeatureType::TEST;
     default:
-      NOTREACHED();
+      return static_cast<proto::FeatureType>(type);
   }
-  return proto::FeatureType::INVALID;
 }
 
 }  // namespace
diff --git a/chrome/browser/video_tutorials/internal/proto_conversions_unittest.cc b/chrome/browser/video_tutorials/internal/proto_conversions_unittest.cc
index c3d4e47..4ade7625 100644
--- a/chrome/browser/video_tutorials/internal/proto_conversions_unittest.cc
+++ b/chrome/browser/video_tutorials/internal/proto_conversions_unittest.cc
@@ -23,6 +23,13 @@
     TutorialFromProto(&intermediate, &actual);
     EXPECT_EQ(expected, actual);
   }
+
+  // Test an unknown feature.
+  FeatureType unknown = static_cast<FeatureType>(80);
+  expected.feature = unknown;
+  TutorialToProto(&expected, &intermediate);
+  TutorialFromProto(&intermediate, &actual);
+  EXPECT_EQ(expected, actual);
 }
 
 // Verify round-way conversion of Tutorial struct.
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index faf18d7..cb2b5ec 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-master-1603454266-512aeaba1be85f32943004abfaab59393bece0f1.profdata
+chrome-linux-master-1603475912-957f608884ccda47cbd83f12321a7d7559e0effc.profdata
diff --git a/chrome/common/extensions/api/accessibility_private.json b/chrome/common/extensions/api/accessibility_private.json
index f1aba0f..dd249df 100644
--- a/chrome/common/extensions/api/accessibility_private.json
+++ b/chrome/common/extensions/api/accessibility_private.json
@@ -566,6 +566,19 @@
         "platforms": ["chromeos"]
       },
       {
+        "name": "onMagnifierBoundsChanged",
+        "type": "function",
+        "description": "Fired when Chrome OS magnifier bounds are updated.",
+        "parameters": [
+          {
+            "name": "magnifierBounds",
+            "$ref": "ScreenRect",
+            "description": "Updated bounds of magnifier viewport."
+          }
+        ],
+        "platforms": ["chromeos"]
+      },
+      {
         "name": "onCustomSpokenFeedbackToggled",
         "type": "function",
         "description": "Fired when a custom spoken feedback on the active window gets enabled or disabled. Called from ARC++ accessibility.",
diff --git a/chrome/common/logging_chrome.cc b/chrome/common/logging_chrome.cc
index 85c2273..f85a597 100644
--- a/chrome/common/logging_chrome.cc
+++ b/chrome/common/logging_chrome.cc
@@ -357,7 +357,7 @@
         command_line.GetSwitchValueASCII(switches::kLoggingLevel);
     int level = 0;
     if (base::StringToInt(log_level, &level) && level >= 0 &&
-        level < LOG_NUM_SEVERITIES) {
+        level < LOGGING_NUM_SEVERITIES) {
       SetMinLogLevel(level);
     } else {
       DLOG(WARNING) << "Bad log level: " << log_level;
diff --git a/chrome/credential_provider/extension/BUILD.gn b/chrome/credential_provider/extension/BUILD.gn
index 850bb47..dbed56bd 100644
--- a/chrome/credential_provider/extension/BUILD.gn
+++ b/chrome/credential_provider/extension/BUILD.gn
@@ -76,6 +76,7 @@
     ":version",
     "../eventlog:gcp_eventlog_messages",
     "../gaiacp:common",
+    "../gaiacp:policies",
     "../gaiacp:util",
     "//base",
     "//components/crash/core/app:crash_export_thunks",
diff --git a/chrome/credential_provider/extension/extension_main.cc b/chrome/credential_provider/extension/extension_main.cc
index aab1a18..1c48745e8 100644
--- a/chrome/credential_provider/extension/extension_main.cc
+++ b/chrome/credential_provider/extension/extension_main.cc
@@ -15,7 +15,6 @@
 #include "chrome/credential_provider/extension/service.h"
 #include "chrome/credential_provider/extension/task_manager.h"
 #include "chrome/credential_provider/gaiacp/logging.h"
-#include "chrome/credential_provider/gaiacp/mdm_utils.h"
 #include "chrome/credential_provider/gaiacp/reg_utils.h"
 #include "chrome/credential_provider/gaiacp/user_policies_manager.h"
 
diff --git a/chrome/credential_provider/extension/task_manager_unittests.cc b/chrome/credential_provider/extension/task_manager_unittests.cc
index 490168da..b2adb05 100644
--- a/chrome/credential_provider/extension/task_manager_unittests.cc
+++ b/chrome/credential_provider/extension/task_manager_unittests.cc
@@ -36,6 +36,8 @@
   }
 
   void SetUp() override {
+    ScopedLsaPolicy::SetCreatorForTesting(
+        fake_scoped_lsa_factory_.GetCreatorCallback());
     registry_override.OverrideRegistry(HKEY_LOCAL_MACHINE);
   }
 
@@ -51,6 +53,7 @@
  private:
   FakeTaskManager fake_task_manager_;
   FakeOSUserManager fake_os_user_manager_;
+  FakeScopedLsaPolicyFactory fake_scoped_lsa_factory_;
   registry_util::RegistryOverrideManager registry_override;
   base::test::SingleThreadTaskEnvironment task_environment_{
       base::test::SingleThreadTaskEnvironment::TimeSource::MOCK_TIME};
diff --git a/chrome/credential_provider/gaiacp/BUILD.gn b/chrome/credential_provider/gaiacp/BUILD.gn
index cc861f73..ab5d988 100644
--- a/chrome/credential_provider/gaiacp/BUILD.gn
+++ b/chrome/credential_provider/gaiacp/BUILD.gn
@@ -16,29 +16,14 @@
 # This static library is shared with the setup program.
 source_set("common") {
   sources = [
-    "device_policies.cc",
-    "device_policies.h",
-    "device_policies_manager.cc",
-    "device_policies_manager.h",
     "gcp_crash_reporter_client.cc",
     "gcp_crash_reporter_client.h",
     "gcp_crash_reporting_utils.cc",
     "gcp_crash_reporting_utils.h",
-    "gcpw_strings.cc",
-    "gcpw_strings.h",
-    "gcpw_version.cc",
-    "gcpw_version.h",
-    "mdm_utils.cc",
-    "mdm_utils.h",
+    "gem_device_details_manager.cc",
+    "gem_device_details_manager.h",
     "os_user_manager.cc",
     "os_user_manager.h",
-    "scoped_handle.h",
-    "user_policies.cc",
-    "user_policies.h",
-    "user_policies_manager.cc",
-    "user_policies_manager.h",
-    "win_http_url_fetcher.cc",
-    "win_http_url_fetcher.h",
   ]
   public_configs = [ ":common_config" ]
   public_deps = [ "//chrome/credential_provider/common:common_constants" ]
@@ -55,7 +40,6 @@
     "//components/crash/core/common",
     "//components/version_info",
     "//google_apis:google_apis",
-    "//third_party/re2",
     "//url",
   ]
 }
@@ -79,14 +63,19 @@
   sources = [
     "gcp_utils.cc",
     "gcp_utils.h",
+    "gcpw_strings.cc",
+    "gcpw_strings.h",
     "logging.cc",
     "logging.h",
     "reg_utils.cc",
     "reg_utils.h",
+    "scoped_handle.h",
     "scoped_lsa_policy.cc",
     "scoped_lsa_policy.h",
     "token_generator.cc",
     "token_generator.h",
+    "win_http_url_fetcher.cc",
+    "win_http_url_fetcher.h",
   ]
   public_configs = [ ":util_config" ]
   public_deps = [ "//chrome/credential_provider/common:common_constants" ]
@@ -98,6 +87,7 @@
     "//chrome/common:version_header",
     "//chrome/installer/launcher_support",
     "//google_apis:google_apis",
+    "//third_party/re2",
     "//url:url",
   ]
 }
@@ -109,6 +99,31 @@
   ]
 }
 
+# This component is used for the cloud policies feature.
+component("policies") {
+  output_name = "gcpw_policies"
+  sources = [
+    "device_policies.cc",
+    "device_policies.h",
+    "device_policies_manager.cc",
+    "device_policies_manager.h",
+    "gcpw_version.cc",
+    "gcpw_version.h",
+    "user_policies.cc",
+    "user_policies.h",
+    "user_policies_manager.cc",
+    "user_policies_manager.h",
+  ]
+  defines = [ "IS_GCPW_POLICIES_IMPL" ]
+  deps = [
+    ":common",
+    ":util",
+    "../extension:extension_lib",
+    "//base",
+    "//url",
+  ]
+}
+
 # This static library is shared with the test code.
 
 source_set("gaiacp_lib") {
@@ -139,11 +154,11 @@
     "gaia_credential_provider_module.h",
     "gcp_crash_reporting.cc",
     "gcp_crash_reporting.h",
-    "gem_device_details_manager.cc",
-    "gem_device_details_manager.h",
     "initguid.cc",
     "internet_availability_checker.cc",
     "internet_availability_checker.h",
+    "mdm_utils.cc",
+    "mdm_utils.h",
     "os_process_manager.cc",
     "os_process_manager.h",
     "password_recovery_manager.cc",
@@ -158,6 +173,7 @@
   public_deps = [ ":common" ]
   deps = [
     ":gaia_credential_provider_idl",
+    ":policies",
     ":static_resources",
     ":string_resources",
     ":util",
@@ -272,6 +288,7 @@
   deps = [
     ":common",
     ":gaiacp_lib",
+    ":policies",
     ":util",
     ":version",
     "//base",
diff --git a/chrome/credential_provider/gaiacp/device_policies.cc b/chrome/credential_provider/gaiacp/device_policies.cc
index 035833a..d07e6204 100644
--- a/chrome/credential_provider/gaiacp/device_policies.cc
+++ b/chrome/credential_provider/gaiacp/device_policies.cc
@@ -9,7 +9,6 @@
 #include "chrome/credential_provider/gaiacp/gcp_utils.h"
 #include "chrome/credential_provider/gaiacp/gcpw_strings.h"
 #include "chrome/credential_provider/gaiacp/logging.h"
-#include "chrome/credential_provider/gaiacp/mdm_utils.h"
 #include "chrome/credential_provider/gaiacp/reg_utils.h"
 
 namespace credential_provider {
@@ -48,7 +47,7 @@
       enable_multi_user_login(kDevicePolicyDefaultMultiUserLogin) {
   // Override with the policies set in the registry.
 
-  base::string16 mdm_url = GetMdmUrl();
+  base::string16 mdm_url = GetGlobalFlagOrDefault(kRegMdmUrl, kDefaultMdmUrl);
   DWORD reg_enable_dm_enrollment;
   HRESULT hr = GetGlobalFlag(kRegEnableDmEnrollment, &reg_enable_dm_enrollment);
   if (SUCCEEDED(hr)) {
diff --git a/chrome/credential_provider/gaiacp/device_policies.h b/chrome/credential_provider/gaiacp/device_policies.h
index ced5304..06bf31d 100644
--- a/chrome/credential_provider/gaiacp/device_policies.h
+++ b/chrome/credential_provider/gaiacp/device_policies.h
@@ -7,13 +7,14 @@
 
 #include <vector>
 
+#include "base/component_export.h"
 #include "base/strings/string16.h"
 #include "chrome/credential_provider/gaiacp/user_policies.h"
 
 namespace credential_provider {
 
 // Structure to hold the policies for the device.
-struct DevicePolicies {
+struct COMPONENT_EXPORT(GCPW_POLICIES) DevicePolicies {
   // Controls whether MDM enrollment is enabled/disabled.
   bool enable_dm_enrollment;
 
diff --git a/chrome/credential_provider/gaiacp/device_policies_manager.cc b/chrome/credential_provider/gaiacp/device_policies_manager.cc
index 43c63f50..4e9c85d 100644
--- a/chrome/credential_provider/gaiacp/device_policies_manager.cc
+++ b/chrome/credential_provider/gaiacp/device_policies_manager.cc
@@ -19,7 +19,6 @@
 #include "chrome/credential_provider/gaiacp/gcp_utils.h"
 #include "chrome/credential_provider/gaiacp/gcpw_strings.h"
 #include "chrome/credential_provider/gaiacp/logging.h"
-#include "chrome/credential_provider/gaiacp/mdm_utils.h"
 #include "chrome/credential_provider/gaiacp/os_user_manager.h"
 #include "chrome/credential_provider/gaiacp/reg_utils.h"
 #include "chrome/credential_provider/gaiacp/user_policies_manager.h"
diff --git a/chrome/credential_provider/gaiacp/device_policies_manager.h b/chrome/credential_provider/gaiacp/device_policies_manager.h
index 2efa55f4..4df42674 100644
--- a/chrome/credential_provider/gaiacp/device_policies_manager.h
+++ b/chrome/credential_provider/gaiacp/device_policies_manager.h
@@ -5,12 +5,13 @@
 #ifndef CHROME_CREDENTIAL_PROVIDER_GAIACP_DEVICE_POLICIES_MANAGER_H_
 #define CHROME_CREDENTIAL_PROVIDER_GAIACP_DEVICE_POLICIES_MANAGER_H_
 
+#include "base/component_export.h"
 #include "chrome/credential_provider/gaiacp/device_policies.h"
 
 namespace credential_provider {
 
 // Manager used to fetch user policies from GCPW backends.
-class DevicePoliciesManager {
+class COMPONENT_EXPORT(GCPW_POLICIES) DevicePoliciesManager {
  public:
   // Get the user policies manager instance.
   static DevicePoliciesManager* Get();
diff --git a/chrome/credential_provider/gaiacp/device_policies_manager_unittests.cc b/chrome/credential_provider/gaiacp/device_policies_manager_unittests.cc
index e246bae..2b708ee1 100644
--- a/chrome/credential_provider/gaiacp/device_policies_manager_unittests.cc
+++ b/chrome/credential_provider/gaiacp/device_policies_manager_unittests.cc
@@ -9,7 +9,6 @@
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/credential_provider/gaiacp/device_policies_manager.h"
 #include "chrome/credential_provider/gaiacp/gcpw_strings.h"
-#include "chrome/credential_provider/gaiacp/mdm_utils.h"
 #include "chrome/credential_provider/gaiacp/reg_utils.h"
 #include "chrome/credential_provider/test/gls_runner_test_base.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -32,6 +31,12 @@
   EXPECT_EQ(ERROR_SUCCESS,
             key.Open(HKEY_LOCAL_MACHINE, kGcpRootKeyName, KEY_WRITE));
   EXPECT_EQ(ERROR_SUCCESS, key.DeleteValue(kRegMdmUrl));
+
+  FakesForTesting fakes;
+  fakes.fake_win_http_url_fetcher_creator =
+      fake_http_url_fetcher_factory()->GetCreatorCallback();
+  fakes.os_user_manager_for_testing = fake_os_user_manager();
+  UserPoliciesManager::Get()->SetFakesForTesting(&fakes);  // IN-TEST
 }
 
 TEST_F(GcpDevicePoliciesBaseTest, NewUserAssociationWithNoUserPoliciesPresent) {
@@ -110,8 +115,6 @@
   int mdm_url_flag = std::get<1>(GetParam());
   int multi_user_login_flag = std::get<2>(GetParam());
 
-  FakeDevicePoliciesManager fake_device_policies_manager(true);
-
   if (dm_enrollment_flag < 2) {
     ASSERT_EQ(S_OK, SetGlobalFlagForTesting(kRegEnableDmEnrollment,
                                             dm_enrollment_flag ? 1 : 0));
diff --git a/chrome/credential_provider/gaiacp/dllmain.cc b/chrome/credential_provider/gaiacp/dllmain.cc
index f9532ec..78ebb7fd 100644
--- a/chrome/credential_provider/gaiacp/dllmain.cc
+++ b/chrome/credential_provider/gaiacp/dllmain.cc
@@ -33,7 +33,6 @@
 #include "chrome/credential_provider/gaiacp/gaia_credential_provider_module.h"
 #include "chrome/credential_provider/gaiacp/gcp_utils.h"
 #include "chrome/credential_provider/gaiacp/logging.h"
-#include "chrome/credential_provider/gaiacp/mdm_utils.h"
 #include "chrome/credential_provider/gaiacp/os_process_manager.h"
 #include "chrome/credential_provider/gaiacp/os_user_manager.h"
 #include "chrome/credential_provider/gaiacp/reauth_credential.h"
diff --git a/chrome/credential_provider/gaiacp/event_logs_upload_manager.cc b/chrome/credential_provider/gaiacp/event_logs_upload_manager.cc
index cc34a19c..8885b5df 100644
--- a/chrome/credential_provider/gaiacp/event_logs_upload_manager.cc
+++ b/chrome/credential_provider/gaiacp/event_logs_upload_manager.cc
@@ -15,7 +15,6 @@
 #include "chrome/credential_provider/gaiacp/gcp_utils.h"
 #include "chrome/credential_provider/gaiacp/gcpw_strings.h"
 #include "chrome/credential_provider/gaiacp/logging.h"
-#include "chrome/credential_provider/gaiacp/mdm_utils.h"
 #include "chrome/credential_provider/gaiacp/reg_utils.h"
 #include "chrome/credential_provider/gaiacp/win_http_url_fetcher.h"
 
diff --git a/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc b/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc
index 98f59a6..1a683ab9 100644
--- a/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc
+++ b/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc
@@ -3532,7 +3532,19 @@
 // 3. bool :       Whether cloud policies feature is enabled.
 class GcpGaiaCredentialBaseFetchCloudPoliciesTest
     : public GcpGaiaCredentialBaseTest,
-      public ::testing::WithParamInterface<std::tuple<bool, bool, bool>> {};
+      public ::testing::WithParamInterface<std::tuple<bool, bool, bool>> {
+ protected:
+  void SetUp() override;
+};
+
+void GcpGaiaCredentialBaseFetchCloudPoliciesTest::SetUp() {
+  GcpGaiaCredentialBaseTest::SetUp();
+
+  FakesForTesting fakes;
+  fakes.fake_win_http_url_fetcher_creator =
+      fake_http_url_fetcher_factory()->GetCreatorCallback();
+  UserPoliciesManager::Get()->SetFakesForTesting(&fakes);  // IN-TEST
+}
 
 TEST_P(GcpGaiaCredentialBaseFetchCloudPoliciesTest, FetchAndStore) {
   bool fail_fetch_policies = std::get<0>(GetParam());
diff --git a/chrome/credential_provider/gaiacp/gaia_credential_provider_unittests.cc b/chrome/credential_provider/gaiacp/gaia_credential_provider_unittests.cc
index a213fa0..88e54d7 100644
--- a/chrome/credential_provider/gaiacp/gaia_credential_provider_unittests.cc
+++ b/chrome/credential_provider/gaiacp/gaia_credential_provider_unittests.cc
@@ -837,7 +837,20 @@
 //           cloud polcies.
 class GcpGaiaCredentialBaseMultiUserCloudPolicyTest
     : public GcpCredentialProviderTest,
-      public ::testing::WithParamInterface<std::tuple<bool, bool, bool>> {};
+      public ::testing::WithParamInterface<std::tuple<bool, bool, bool>> {
+ protected:
+  void SetUp() override;
+};
+
+void GcpGaiaCredentialBaseMultiUserCloudPolicyTest::SetUp() {
+  GcpCredentialProviderTest::SetUp();
+
+  FakesForTesting fakes;
+  fakes.fake_win_http_url_fetcher_creator =
+      fake_http_url_fetcher_factory()->GetCreatorCallback();
+  fakes.os_user_manager_for_testing = fake_os_user_manager();
+  UserPoliciesManager::Get()->SetFakesForTesting(&fakes);  // IN-TEST
+}
 
 TEST_P(GcpGaiaCredentialBaseMultiUserCloudPolicyTest, CanCreateNewUsers) {
   USES_CONVERSION;
diff --git a/chrome/credential_provider/gaiacp/gcp_utils.cc b/chrome/credential_provider/gaiacp/gcp_utils.cc
index a69883c..b9c7c52 100644
--- a/chrome/credential_provider/gaiacp/gcp_utils.cc
+++ b/chrome/credential_provider/gaiacp/gcp_utils.cc
@@ -50,6 +50,7 @@
 #include "chrome/common/chrome_version.h"
 #include "chrome/credential_provider/common/gcp_strings.h"
 #include "chrome/credential_provider/gaiacp/gaia_resources.h"
+#include "chrome/credential_provider/gaiacp/gcpw_strings.h"
 #include "chrome/credential_provider/gaiacp/logging.h"
 #include "chrome/credential_provider/gaiacp/reg_utils.h"
 #include "chrome/credential_provider/gaiacp/token_generator.h"
@@ -57,6 +58,7 @@
 #include "google_apis/gaia/gaia_auth_util.h"
 #include "google_apis/gaia/gaia_switches.h"
 #include "google_apis/gaia/gaia_urls.h"
+#include "third_party/re2/src/re2/re2.h"
 
 namespace credential_provider {
 
@@ -84,6 +86,13 @@
 const wchar_t kKernelLibFile[] = L"kernel32.dll";
 const int kVersionStringSize = 128;
 
+constexpr wchar_t kDefaultMdmUrl[] =
+    L"https://deviceenrollmentforwindows.googleapis.com/v1/discovery";
+
+constexpr int kMaxNumConsecutiveUploadDeviceFailures = 3;
+const base::TimeDelta kMaxTimeDeltaSinceLastUserPolicyRefresh =
+    base::TimeDelta::FromDays(1);
+
 namespace {
 
 // Minimum supported version of Chrome for GCPW.
@@ -1223,4 +1232,26 @@
 
 FakesForTesting::~FakesForTesting() {}
 
+GURL GetGcpwServiceUrl() {
+  base::string16 dev = GetGlobalFlagOrDefault(kRegDeveloperMode, L"");
+  if (!dev.empty())
+    return GURL(GetDevelopmentUrl(kDefaultGcpwServiceUrl, dev));
+
+  return GURL(kDefaultGcpwServiceUrl);
+}
+
+base::string16 GetDevelopmentUrl(const base::string16& url,
+                                 const base::string16& dev) {
+  std::string project;
+  std::string final_part;
+  if (re2::RE2::FullMatch(base::UTF16ToUTF8(url),
+                          "https://(.*).(googleapis.com.*)", &project,
+                          &final_part)) {
+    std::string url_prefix = "https://" + base::UTF16ToUTF8(dev) + "-";
+    return base::UTF8ToUTF16(
+        base::JoinString({url_prefix + project, "sandbox", final_part}, "."));
+  }
+  return url;
+}
+
 }  // namespace credential_provider
diff --git a/chrome/credential_provider/gaiacp/gcp_utils.h b/chrome/credential_provider/gaiacp/gcp_utils.h
index c4d0ec3..b9cb3a4 100644
--- a/chrome/credential_provider/gaiacp/gcp_utils.h
+++ b/chrome/credential_provider/gaiacp/gcp_utils.h
@@ -17,6 +17,7 @@
 #include "base/win/scoped_handle.h"
 #include "base/win/windows_types.h"
 #include "chrome/credential_provider/gaiacp/scoped_lsa_policy.h"
+#include "chrome/credential_provider/gaiacp/win_http_url_fetcher.h"
 #include "url/gurl.h"
 
 // These define are documented in
@@ -63,6 +64,17 @@
 // Name of the sub-folder under which all files for GCPW are stored.
 extern const base::FilePath::CharType kCredentialProviderFolder[];
 
+// Default URL for the GEM MDM API.
+extern const wchar_t kDefaultMdmUrl[];
+
+// Maximum number of consecutive Upload device details failures for which we do
+// enforce auth.
+extern const int kMaxNumConsecutiveUploadDeviceFailures;
+
+// Maximum allowed time delta after which user policies should be refreshed
+// again.
+extern const base::TimeDelta kMaxTimeDeltaSinceLastUserPolicyRefresh;
+
 // Because of some strange dependency problems with windows header files,
 // define STATUS_SUCCESS here instead of including ntstatus.h or SubAuth.h
 #define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
@@ -317,6 +329,7 @@
   ScopedLsaPolicy::CreatorCallback scoped_lsa_policy_creator;
   OSUserManager* os_user_manager_for_testing = nullptr;
   OSProcessManager* os_process_manager_for_testing = nullptr;
+  WinHttpUrlFetcher::CreatorCallback fake_win_http_url_fetcher_creator;
 };
 
 // DLL entrypoint signature for settings testing fakes.  This is used by
@@ -381,6 +394,16 @@
 // returns a result other than S_OK.
 HRESULT GetGCPWDmToken(const base::string16& sid, base::string16* token);
 
+// Gets the gcpw service URL.
+GURL GetGcpwServiceUrl();
+
+// Converts the |url| in the form of http://xxxxx.googleapis.com/...
+// to a form that points to a development URL as specified with |dev|
+// environment. Final url will be in the form
+// https://{dev}-xxxxx.sandbox.googleapis.com/...
+base::string16 GetDevelopmentUrl(const base::string16& url,
+                                 const base::string16& dev);
+
 }  // namespace credential_provider
 
 #endif  // CHROME_CREDENTIAL_PROVIDER_GAIACP_GCP_UTILS_H_
diff --git a/chrome/credential_provider/gaiacp/gcpw_version.h b/chrome/credential_provider/gaiacp/gcpw_version.h
index 5e04c391..dbedfe3f 100644
--- a/chrome/credential_provider/gaiacp/gcpw_version.h
+++ b/chrome/credential_provider/gaiacp/gcpw_version.h
@@ -8,10 +8,12 @@
 #include <array>
 #include <string>
 
+#include "base/component_export.h"
+
 namespace credential_provider {
 
 // A structure to hold the version of GCPW.
-class GcpwVersion {
+class COMPONENT_EXPORT(GCPW_POLICIES) GcpwVersion {
  public:
   // Create a default version which is not valid.
   GcpwVersion();
diff --git a/chrome/credential_provider/gaiacp/gem_device_details_manager.cc b/chrome/credential_provider/gaiacp/gem_device_details_manager.cc
index f3e5c14..f78c6f7 100644
--- a/chrome/credential_provider/gaiacp/gem_device_details_manager.cc
+++ b/chrome/credential_provider/gaiacp/gem_device_details_manager.cc
@@ -20,7 +20,6 @@
 #include "chrome/credential_provider/gaiacp/gcp_utils.h"
 #include "chrome/credential_provider/gaiacp/gcpw_strings.h"
 #include "chrome/credential_provider/gaiacp/logging.h"
-#include "chrome/credential_provider/gaiacp/mdm_utils.h"
 #include "chrome/credential_provider/gaiacp/os_user_manager.h"
 #include "chrome/credential_provider/gaiacp/reg_utils.h"
 #include "chrome/credential_provider/gaiacp/win_http_url_fetcher.h"
diff --git a/chrome/credential_provider/gaiacp/mdm_utils.cc b/chrome/credential_provider/gaiacp/mdm_utils.cc
index c22e4655..0976bc0 100644
--- a/chrome/credential_provider/gaiacp/mdm_utils.cc
+++ b/chrome/credential_provider/gaiacp/mdm_utils.cc
@@ -31,38 +31,16 @@
 #include "chrome/credential_provider/gaiacp/logging.h"
 #include "chrome/credential_provider/gaiacp/reg_utils.h"
 #include "chrome/credential_provider/gaiacp/user_policies_manager.h"
-#include "third_party/re2/src/re2/re2.h"
 
 namespace credential_provider {
 
-constexpr wchar_t kRegEnableVerboseLogging[] = L"enable_verbose_logging";
-constexpr wchar_t kRegInitializeCrashReporting[] = L"enable_crash_reporting";
-constexpr wchar_t kRegMdmUrl[] = L"mdm";
-constexpr wchar_t kRegEnableDmEnrollment[] = L"enable_dm_enrollment";
-constexpr wchar_t kRegDeveloperMode[] = L"developer_mode";
 constexpr wchar_t kRegMdmEnforceOnlineLogin[] = L"enforce_online_login";
-constexpr wchar_t kRegMdmEnableForcePasswordReset[] =
-    L"enable_force_reset_password_option";
-constexpr wchar_t kRegDisablePasswordSync[] = L"disable_password_sync";
-constexpr wchar_t kRegMdmSupportsMultiUser[] = L"enable_multi_user_login";
-constexpr wchar_t kRegMdmAllowConsumerAccounts[] = L"enable_consumer_accounts ";
-constexpr wchar_t kRegDeviceDetailsUploadStatus[] =
-    L"device_details_upload_status";
-constexpr wchar_t kRegDeviceDetailsUploadFailures[] =
-    L"device_details_upload_failures";
-constexpr wchar_t kRegUpdateCredentialsOnChange[] =
-    L"update_credentials_on_change";
-constexpr wchar_t kRegUseShorterAccountName[] = L"use_shorter_account_name";
 constexpr wchar_t kUserPasswordLsaStoreKeyPrefix[] =
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
     L"Chrome-GCPW-";
 #else
     L"Chromium-GCPW-";
 #endif
-const char kErrorKeyInRequestResult[] = "error";
-constexpr int kMaxNumConsecutiveUploadDeviceFailures = 3;
-const base::TimeDelta kMaxTimeDeltaSinceLastUserPolicyRefresh =
-    base::TimeDelta::FromDays(1);
 
 // Overridden in tests to force the MDM enrollment to either succeed or fail.
 enum class EnrollmentStatus {
@@ -100,9 +78,6 @@
 
 namespace {
 
-constexpr wchar_t kDefaultMdmUrl[] =
-    L"https://deviceenrollmentforwindows.googleapis.com/v1/discovery";
-
 constexpr wchar_t kDefaultEscrowServiceServerUrl[] =
     L"https://devicepasswordescrowforwindows-pa.googleapis.com";
 
@@ -375,7 +350,8 @@
     DWORD device_upload_failures = 1;
     GetUserProperty(sid, kRegDeviceDetailsUploadFailures,
                     &device_upload_failures);
-    if (device_upload_failures > kMaxNumConsecutiveUploadDeviceFailures) {
+    if (device_upload_failures >
+        DWORD(kMaxNumConsecutiveUploadDeviceFailures)) {
       LOGFN(WARNING) << "Reauth not enforced due to upload device details "
                         "failures exceeding threshhold.";
       return false;
@@ -435,14 +411,6 @@
   return GURL(base::UTF16ToUTF8(kDefaultEscrowServiceServerUrl));
 }
 
-GURL GetGcpwServiceUrl() {
-  base::string16 dev = GetGlobalFlagOrDefault(kRegDeveloperMode, L"");
-  if (!dev.empty())
-    return GURL(GetDevelopmentUrl(kDefaultGcpwServiceUrl, dev));
-
-  return GURL(kDefaultGcpwServiceUrl);
-}
-
 bool PasswordRecoveryEnabled() {
   return !EscrowServiceUrl().is_empty();
 }
@@ -509,20 +477,6 @@
   return kUserPasswordLsaStoreKeyPrefix + sid;
 }
 
-base::string16 GetDevelopmentUrl(const base::string16& url,
-                                 const base::string16& dev) {
-  std::string project;
-  std::string final_part;
-  if (re2::RE2::FullMatch(base::UTF16ToUTF8(url),
-                          "https://(.*).(googleapis.com.*)", &project,
-                          &final_part)) {
-    std::string url_prefix = "https://" + base::UTF16ToUTF8(dev) + "-";
-    return base::UTF8ToUTF16(
-        base::JoinString({url_prefix + project, "sandbox", final_part}, "."));
-  }
-  return url;
-}
-
 // GoogleMdmEnrollmentStatusForTesting ////////////////////////////////////////
 
 GoogleMdmEnrollmentStatusForTesting::GoogleMdmEnrollmentStatusForTesting(
diff --git a/chrome/credential_provider/gaiacp/mdm_utils.h b/chrome/credential_provider/gaiacp/mdm_utils.h
index 4dc3201..d59ad8b 100644
--- a/chrome/credential_provider/gaiacp/mdm_utils.h
+++ b/chrome/credential_provider/gaiacp/mdm_utils.h
@@ -15,67 +15,9 @@
 
 namespace credential_provider {
 
-// Mdm registry value key name.
-
-// Enables verbose logging in GCPW.
-extern const wchar_t kRegEnableVerboseLogging[];
-
-// Determines if crash reporting is initialized for credential provider DLL.
-extern const wchar_t kRegInitializeCrashReporting[];
-
-// The url used to register the machine to MDM. If specified and non-empty
-// additional user access restrictions will be applied to users associated
-// to GCPW that have invalid token handles.
-extern const wchar_t kRegMdmUrl[];
-
-// The registry entry is used to control whether to enable enrollment
-// Google device management solution.
-extern const wchar_t kRegEnableDmEnrollment[];
-
-// Disables password escrowing feature in GCPW.
-extern const wchar_t kRegDisablePasswordSync[];
-
-// Determines if multiple users can be added to a system managed by MDM.
-extern const wchar_t kRegMdmSupportsMultiUser[];
-
-// Allow sign in using normal consumer accounts.
-extern const wchar_t kRegMdmAllowConsumerAccounts[];
-
-// Enables force password reset option in forgot password flow.
-extern const wchar_t kRegMdmEnableForcePasswordReset[];
-
 // Password lsa store key prefix.
 extern const wchar_t kUserPasswordLsaStoreKeyPrefix[];
 
-// Error key name that is likely to be present in HTTP responses.
-extern const char kErrorKeyInRequestResult[];
-
-// Upload status for device details.
-extern const wchar_t kRegDeviceDetailsUploadStatus[];
-
-// Number of consecutive failures encountered when uploading device details.
-extern const wchar_t kRegDeviceDetailsUploadFailures[];
-
-// Maximum number of consecutive Upload device details failures for which we do
-// enforce auth.
-extern const int kMaxNumConsecutiveUploadDeviceFailures;
-
-// The URL part that is used when constructing the developer complete URL. When
-// it is empty, developer mode isn't enabled.
-extern const wchar_t kRegDeveloperMode[];
-
-// Enables updating credentials on login UI when the enforcement of any GCPW
-// associated account changes.
-extern const wchar_t kRegUpdateCredentialsOnChange[];
-
-// Maximum allowed time delta after which user policies should be refreshed
-// again.
-extern const base::TimeDelta kMaxTimeDeltaSinceLastUserPolicyRefresh;
-
-// Registry key that indicates account name for an unassociated Windows account
-// should be in shorter form.
-extern const wchar_t kRegUseShorterAccountName[];
-
 // Class used in tests to force either a successful on unsuccessful enrollment
 // to google MDM.
 class GoogleMdmEnrollmentStatusForTesting {
@@ -129,22 +71,12 @@
 // empty url is returned.
 GURL EscrowServiceUrl();
 
-// Gets the gcpw service URL.
-GURL GetGcpwServiceUrl();
-
 // Enrolls the machine to with the Google MDM server if not already.
 HRESULT EnrollToGoogleMdmIfNeeded(const base::Value& properties);
 
 // Constructs the password lsa store key for the given |sid|.
 base::string16 GetUserPasswordLsaStoreKey(const base::string16& sid);
 
-// Converts the |url| in the form of http://xxxxx.googleapis.com/...
-// to a form that points to a development URL as specified with |dev|
-// environment. Final url will be in the form
-// https://{dev}-xxxxx.sandbox.googleapis.com/...
-base::string16 GetDevelopmentUrl(const base::string16& url,
-                                 const base::string16& dev);
-
 }  // namespace credential_provider
 
 #endif  // CHROME_CREDENTIAL_PROVIDER_GAIACP_MDM_UTILS_H_
diff --git a/chrome/credential_provider/gaiacp/reg_utils.cc b/chrome/credential_provider/gaiacp/reg_utils.cc
index 86e701a..e680a90 100644
--- a/chrome/credential_provider/gaiacp/reg_utils.cc
+++ b/chrome/credential_provider/gaiacp/reg_utils.cc
@@ -45,6 +45,23 @@
 
 constexpr wchar_t kRegUserDeviceResourceId[] = L"device_resource_id";
 constexpr wchar_t kRegGlsPath[] = L"gls_path";
+constexpr wchar_t kRegEnableVerboseLogging[] = L"enable_verbose_logging";
+constexpr wchar_t kRegInitializeCrashReporting[] = L"enable_crash_reporting";
+constexpr wchar_t kRegMdmUrl[] = L"mdm";
+constexpr wchar_t kRegEnableDmEnrollment[] = L"enable_dm_enrollment";
+constexpr wchar_t kRegDisablePasswordSync[] = L"disable_password_sync";
+constexpr wchar_t kRegMdmSupportsMultiUser[] = L"enable_multi_user_login";
+constexpr wchar_t kRegMdmAllowConsumerAccounts[] = L"enable_consumer_accounts ";
+constexpr wchar_t kRegMdmEnableForcePasswordReset[] =
+    L"enable_force_reset_password_option";
+constexpr wchar_t kRegDeviceDetailsUploadStatus[] =
+    L"device_details_upload_status";
+constexpr wchar_t kRegDeviceDetailsUploadFailures[] =
+    L"device_details_upload_failures";
+constexpr wchar_t kRegDeveloperMode[] = L"developer_mode";
+constexpr wchar_t kRegUpdateCredentialsOnChange[] =
+    L"update_credentials_on_change";
+constexpr wchar_t kRegUseShorterAccountName[] = L"use_shorter_account_name";
 
 namespace {
 
diff --git a/chrome/credential_provider/gaiacp/reg_utils.h b/chrome/credential_provider/gaiacp/reg_utils.h
index 8ee0f5f..cb722dbd 100644
--- a/chrome/credential_provider/gaiacp/reg_utils.h
+++ b/chrome/credential_provider/gaiacp/reg_utils.h
@@ -32,6 +32,53 @@
 // Specifies custom Chrome path to use for GLS.
 extern const wchar_t kRegGlsPath[];
 
+// Mdm registry value key name.
+
+// Enables verbose logging in GCPW.
+extern const wchar_t kRegEnableVerboseLogging[];
+
+// Determines if crash reporting is initialized for credential provider DLL.
+extern const wchar_t kRegInitializeCrashReporting[];
+
+// The url used to register the machine to MDM. If specified and non-empty
+// additional user access restrictions will be applied to users associated
+// to GCPW that have invalid token handles.
+extern const wchar_t kRegMdmUrl[];
+
+// The registry entry is used to control whether to enable enrollment
+// Google device management solution.
+extern const wchar_t kRegEnableDmEnrollment[];
+
+// Disables password escrowing feature in GCPW.
+extern const wchar_t kRegDisablePasswordSync[];
+
+// Determines if multiple users can be added to a system managed by MDM.
+extern const wchar_t kRegMdmSupportsMultiUser[];
+
+// Allow sign in using normal consumer accounts.
+extern const wchar_t kRegMdmAllowConsumerAccounts[];
+
+// Enables force password reset option in forgot password flow.
+extern const wchar_t kRegMdmEnableForcePasswordReset[];
+
+// Upload status for device details.
+extern const wchar_t kRegDeviceDetailsUploadStatus[];
+
+// Number of consecutive failures encountered when uploading device details.
+extern const wchar_t kRegDeviceDetailsUploadFailures[];
+
+// The URL part that is used when constructing the developer complete URL. When
+// it is empty, developer mode isn't enabled.
+extern const wchar_t kRegDeveloperMode[];
+
+// Enables updating credentials on login UI when the enforcement of any GCPW
+// associated account changes.
+extern const wchar_t kRegUpdateCredentialsOnChange[];
+
+// Registry key that indicates account name for an unassociated Windows account
+// should be in shorter form.
+extern const wchar_t kRegUseShorterAccountName[];
+
 // Gets any HKLM registry key on the system.
 HRESULT GetMachineRegDWORD(const base::string16& key_name,
                            const base::string16& name,
diff --git a/chrome/credential_provider/gaiacp/user_policies.cc b/chrome/credential_provider/gaiacp/user_policies.cc
index 97631a0..8366a17 100644
--- a/chrome/credential_provider/gaiacp/user_policies.cc
+++ b/chrome/credential_provider/gaiacp/user_policies.cc
@@ -11,7 +11,6 @@
 #include "chrome/credential_provider/gaiacp/device_policies.h"
 #include "chrome/credential_provider/gaiacp/gcp_utils.h"
 #include "chrome/credential_provider/gaiacp/gcpw_strings.h"
-#include "chrome/credential_provider/gaiacp/mdm_utils.h"
 #include "chrome/credential_provider/gaiacp/reg_utils.h"
 
 namespace credential_provider {
diff --git a/chrome/credential_provider/gaiacp/user_policies.h b/chrome/credential_provider/gaiacp/user_policies.h
index cada80b..c410ed0 100644
--- a/chrome/credential_provider/gaiacp/user_policies.h
+++ b/chrome/credential_provider/gaiacp/user_policies.h
@@ -5,13 +5,14 @@
 #ifndef CHROME_CREDENTIAL_PROVIDER_GAIACP_USER_POLICIES_H_
 #define CHROME_CREDENTIAL_PROVIDER_GAIACP_USER_POLICIES_H_
 
+#include "base/component_export.h"
 #include "base/values.h"
 #include "chrome/credential_provider/gaiacp/gcpw_version.h"
 
 namespace credential_provider {
 
 // Structure to hold the policies for each user.
-struct UserPolicies {
+struct COMPONENT_EXPORT(GCPW_POLICIES) UserPolicies {
   // Controls whether MDM enrollment is enabled/disabled.
   bool enable_dm_enrollment;
 
diff --git a/chrome/credential_provider/gaiacp/user_policies_manager.cc b/chrome/credential_provider/gaiacp/user_policies_manager.cc
index da78340..efc10a4 100644
--- a/chrome/credential_provider/gaiacp/user_policies_manager.cc
+++ b/chrome/credential_provider/gaiacp/user_policies_manager.cc
@@ -23,7 +23,7 @@
 #include "chrome/credential_provider/gaiacp/gcp_utils.h"
 #include "chrome/credential_provider/gaiacp/gcpw_strings.h"
 #include "chrome/credential_provider/gaiacp/logging.h"
-#include "chrome/credential_provider/gaiacp/mdm_utils.h"
+#include "chrome/credential_provider/gaiacp/os_user_manager.h"
 #include "chrome/credential_provider/gaiacp/reg_utils.h"
 #include "chrome/credential_provider/gaiacp/win_http_url_fetcher.h"
 
@@ -388,4 +388,14 @@
   return fetch_status_;
 }
 
+void UserPoliciesManager::SetFakesForTesting(FakesForTesting* fakes) {
+  DCHECK(fakes);
+
+  WinHttpUrlFetcher::SetCreatorForTesting(
+      fakes->fake_win_http_url_fetcher_creator);
+  if (fakes->os_user_manager_for_testing) {
+    OSUserManager::SetInstanceForTesting(fakes->os_user_manager_for_testing);
+  }
+}
+
 }  // namespace credential_provider
diff --git a/chrome/credential_provider/gaiacp/user_policies_manager.h b/chrome/credential_provider/gaiacp/user_policies_manager.h
index 6014566a..dd958c78 100644
--- a/chrome/credential_provider/gaiacp/user_policies_manager.h
+++ b/chrome/credential_provider/gaiacp/user_policies_manager.h
@@ -5,17 +5,19 @@
 #ifndef CHROME_CREDENTIAL_PROVIDER_GAIACP_USER_POLICIES_MANAGER_H_
 #define CHROME_CREDENTIAL_PROVIDER_GAIACP_USER_POLICIES_MANAGER_H_
 
+#include "base/component_export.h"
 #include "base/strings/string16.h"
 #include "base/time/time.h"
 #include "base/win/windows_types.h"
 #include "chrome/credential_provider/extension/task_manager.h"
+#include "chrome/credential_provider/gaiacp/gcp_utils.h"
 #include "chrome/credential_provider/gaiacp/user_policies.h"
 #include "url/gurl.h"
 
 namespace credential_provider {
 
 // Manager used to fetch user policies from GCPW backends.
-class UserPoliciesManager {
+class COMPONENT_EXPORT(GCPW_POLICIES) UserPoliciesManager {
  public:
   // Get the user policies manager instance.
   static UserPoliciesManager* Get();
@@ -66,6 +68,9 @@
   // For testing manually control if the cloud policies feature is enabled.
   void SetCloudPoliciesEnabledForTesting(bool value);
 
+  // Set fakes for cloud policies unit tests.
+  void SetFakesForTesting(FakesForTesting* fakes);
+
  protected:
   // Returns the storage used for the instance pointer.
   static UserPoliciesManager** GetInstanceStorage();
diff --git a/chrome/credential_provider/gaiacp/user_policies_manager_unittests.cc b/chrome/credential_provider/gaiacp/user_policies_manager_unittests.cc
index 9e55bd6..c45d24b 100644
--- a/chrome/credential_provider/gaiacp/user_policies_manager_unittests.cc
+++ b/chrome/credential_provider/gaiacp/user_policies_manager_unittests.cc
@@ -11,7 +11,6 @@
 #include "base/test/scoped_path_override.h"
 #include "chrome/credential_provider/extension/user_device_context.h"
 #include "chrome/credential_provider/gaiacp/gcpw_strings.h"
-#include "chrome/credential_provider/gaiacp/mdm_utils.h"
 #include "chrome/credential_provider/gaiacp/reg_utils.h"
 #include "chrome/credential_provider/gaiacp/user_policies_manager.h"
 #include "chrome/credential_provider/test/gls_runner_test_base.h"
@@ -21,7 +20,19 @@
 
 namespace testing {
 
-class GcpUserPoliciesBaseTest : public GlsRunnerTestBase {};
+class GcpUserPoliciesBaseTest : public GlsRunnerTestBase {
+ protected:
+  void SetUp() override;
+};
+
+void GcpUserPoliciesBaseTest::SetUp() {
+  GlsRunnerTestBase::SetUp();
+
+  FakesForTesting fakes;
+  fakes.fake_win_http_url_fetcher_creator =
+      fake_http_url_fetcher_factory()->GetCreatorCallback();
+  UserPoliciesManager::Get()->SetFakesForTesting(&fakes);  // IN-TEST
+}
 
 TEST_F(GcpUserPoliciesBaseTest, NonExistentUser) {
   ASSERT_TRUE(FAILED(UserPoliciesManager::Get()->FetchAndStoreCloudUserPolicies(
@@ -83,6 +94,14 @@
                 kDefaultUsername, L"password", L"Full Name", L"comment",
                 base::UTF8ToUTF16(kDefaultGaiaId), L"user@company.com", &sid));
   sid_ = OLE2W(sid);
+
+  // Remove the mdm_url value which exists by default as it's added in
+  // InitializeRegistryOverrideForTesting and set to an empty value disabling
+  // MDM enrollment.
+  base::win::RegKey key;
+  EXPECT_EQ(ERROR_SUCCESS,
+            key.Open(HKEY_LOCAL_MACHINE, kGcpRootKeyName, KEY_WRITE));
+  EXPECT_EQ(ERROR_SUCCESS, key.DeleteValue(kRegMdmUrl));
 }
 
 void GcpUserPoliciesFetchAndReadTest::SetRegistryValues(bool dm_enrollment,
diff --git a/chrome/credential_provider/gaiacp/win_http_url_fetcher.cc b/chrome/credential_provider/gaiacp/win_http_url_fetcher.cc
index fca0b5a..2d8a68f 100644
--- a/chrome/credential_provider/gaiacp/win_http_url_fetcher.cc
+++ b/chrome/credential_provider/gaiacp/win_http_url_fetcher.cc
@@ -22,13 +22,15 @@
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/credential_provider/gaiacp/logging.h"
-#include "chrome/credential_provider/gaiacp/mdm_utils.h"
 
 namespace {
 // Key name containing the HTTP error code within the dictionary returned by the
 // server in case of errors.
 constexpr char kHttpErrorCodeKeyNameInResponse[] = "code";
 
+// Error key name that is likely to be present in HTTP responses.
+const char kErrorKeyInRequestResult[] = "error";
+
 // The HTTP response codes for which the request is re-tried on failure.
 const std::set<int> kRetryableHttpErrorCodes = {
     503,  // Service Unavailable
@@ -236,6 +238,11 @@
              : std::unique_ptr<WinHttpUrlFetcher>(new WinHttpUrlFetcher(url));
 }
 
+// static
+void WinHttpUrlFetcher::SetCreatorForTesting(CreatorCallback creator) {
+  *GetCreatorFunctionStorage() = creator;
+}
+
 WinHttpUrlFetcher::WinHttpUrlFetcher(const GURL& url)
     : url_(url), session_(nullptr), request_(nullptr) {
   LOGFN(VERBOSE) << "url=" << url.spec() << " (scheme and port ignored)";
diff --git a/chrome/credential_provider/gaiacp/win_http_url_fetcher.h b/chrome/credential_provider/gaiacp/win_http_url_fetcher.h
index a22a519..ace67e9 100644
--- a/chrome/credential_provider/gaiacp/win_http_url_fetcher.h
+++ b/chrome/credential_provider/gaiacp/win_http_url_fetcher.h
@@ -53,6 +53,12 @@
   virtual HRESULT Fetch(std::vector<char>* response);
   virtual HRESULT Close();
 
+  using CreatorFunc = decltype(Create);
+  using CreatorCallback = base::RepeatingCallback<CreatorFunc>;
+
+  // Set the creator callback function to use in tests.
+  static void SetCreatorForTesting(CreatorCallback creator);
+
  protected:
   using Headers = std::map<std::string, std::string>;
 
@@ -75,8 +81,6 @@
 
   // Gets storage of the function pointer used to create instances of this
   // class for tests.
-  using CreatorFunc = decltype(Create);
-  using CreatorCallback = base::RepeatingCallback<CreatorFunc>;
   static CreatorCallback* GetCreatorFunctionStorage();
 };
 
diff --git a/chrome/credential_provider/setup/setup.cc b/chrome/credential_provider/setup/setup.cc
index 5a2846c..cde648cb 100644
--- a/chrome/credential_provider/setup/setup.cc
+++ b/chrome/credential_provider/setup/setup.cc
@@ -35,7 +35,6 @@
 #include "chrome/credential_provider/eventlog/gcp_eventlog_messages.h"
 #include "chrome/credential_provider/gaiacp/gcp_utils.h"
 #include "chrome/credential_provider/gaiacp/logging.h"
-#include "chrome/credential_provider/gaiacp/mdm_utils.h"
 #include "chrome/credential_provider/gaiacp/reg_utils.h"
 #include "chrome/credential_provider/setup/gcp_installer_crash_reporting.h"
 #include "chrome/credential_provider/setup/setup_lib.h"
@@ -111,7 +110,7 @@
         cmdline->GetSwitchValueASCII(switches::kLoggingLevel);
     int level = 0;
     if (base::StringToInt(log_level, &level) && level >= 0 &&
-        level < logging::LOG_NUM_SEVERITIES) {
+        level < logging::LOGGING_NUM_SEVERITIES) {
       logging::SetMinLogLevel(level);
     } else {
       LOGFN(WARNING) << "Bad log level: " << log_level;
diff --git a/chrome/credential_provider/test/BUILD.gn b/chrome/credential_provider/test/BUILD.gn
index 47a434d..4ea775e0 100644
--- a/chrome/credential_provider/test/BUILD.gn
+++ b/chrome/credential_provider/test/BUILD.gn
@@ -38,6 +38,7 @@
     "../gaiacp:common",
     "../gaiacp:gaia_credential_provider_idl",
     "../gaiacp:gaiacp_lib",
+    "../gaiacp:policies",
     "../gaiacp:string_resources",
     "../gaiacp:util",
     "../gaiacp:version",
diff --git a/chrome/credential_provider/test/gcp_fakes.cc b/chrome/credential_provider/test/gcp_fakes.cc
index df92fd2..14dee694 100644
--- a/chrome/credential_provider/test/gcp_fakes.cc
+++ b/chrome/credential_provider/test/gcp_fakes.cc
@@ -646,14 +646,20 @@
 
 FakeWinHttpUrlFetcherFactory::FakeWinHttpUrlFetcherFactory()
     : original_creator_(*WinHttpUrlFetcher::GetCreatorFunctionStorage()) {
-  *WinHttpUrlFetcher::GetCreatorFunctionStorage() = base::BindRepeating(
-      &FakeWinHttpUrlFetcherFactory::Create, base::Unretained(this));
+  fake_creator_ = base::BindRepeating(&FakeWinHttpUrlFetcherFactory::Create,
+                                      base::Unretained(this));
+  *WinHttpUrlFetcher::GetCreatorFunctionStorage() = fake_creator_;
 }
 
 FakeWinHttpUrlFetcherFactory::~FakeWinHttpUrlFetcherFactory() {
   *WinHttpUrlFetcher::GetCreatorFunctionStorage() = original_creator_;
 }
 
+WinHttpUrlFetcher::CreatorCallback
+FakeWinHttpUrlFetcherFactory::GetCreatorCallback() {
+  return fake_creator_;
+}
+
 void FakeWinHttpUrlFetcherFactory::SetFakeResponse(
     const GURL& url,
     const WinHttpUrlFetcher::Headers& headers,
diff --git a/chrome/credential_provider/test/gcp_fakes.h b/chrome/credential_provider/test/gcp_fakes.h
index 45d99aa6..a9bf8d4 100644
--- a/chrome/credential_provider/test/gcp_fakes.h
+++ b/chrome/credential_provider/test/gcp_fakes.h
@@ -313,6 +313,12 @@
   FakeWinHttpUrlFetcherFactory();
   ~FakeWinHttpUrlFetcherFactory();
 
+  // Returns the fetcher callback function being used. This can be used to
+  // install the same fake explicitly in all the components being used. Those
+  // components would otherwise have different fakes created automatically when
+  // they get initialized.
+  WinHttpUrlFetcher::CreatorCallback GetCreatorCallback();
+
   // Sets the given |response| for any number of HTTP requests made for |url|.
   void SetFakeResponse(
       const GURL& url,
@@ -358,6 +364,7 @@
   std::unique_ptr<WinHttpUrlFetcher> Create(const GURL& url);
 
   WinHttpUrlFetcher::CreatorCallback original_creator_;
+  WinHttpUrlFetcher::CreatorCallback fake_creator_;
 
   struct Response {
     Response();
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index f29daf47e..25482517 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2519,7 +2519,6 @@
         "../browser/chromeos/login/screens/multidevice_setup_screen_browsertest.cc",
         "../browser/chromeos/login/screens/network_screen_browsertest.cc",
         "../browser/chromeos/login/screens/packaged_license_screen_browsertest.cc",
-        "../browser/chromeos/login/screens/parental_handoff_screen_browsertest.cc",
         "../browser/chromeos/login/screens/pin_setup_screen_browsertest.cc",
         "../browser/chromeos/login/screens/recommend_apps/scoped_test_recommend_apps_fetcher_factory.cc",
         "../browser/chromeos/login/screens/recommend_apps/scoped_test_recommend_apps_fetcher_factory.h",
@@ -4149,6 +4148,7 @@
     "//components/sync_device_info:test_support",
     "//components/sync_sessions:test_support",
     "//components/sync_user_events:test_support",
+    "//components/translate/core/browser:test_support",
     "//components/ukm/content",
     "//components/version_info:generate_version_info",
     "//content/app/resources",
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/TranslateUtil.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/TranslateUtil.java
index 5776bd4..38c16e4 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/TranslateUtil.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/TranslateUtil.java
@@ -7,15 +7,19 @@
 import android.app.Instrumentation;
 import android.view.View;
 
+import org.hamcrest.Matchers;
 import org.junit.Assert;
 
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.infobar.InfoBarContainer;
 import org.chromium.chrome.browser.infobar.TranslateCompactInfoBar;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.translate.TranslateBridge;
 import org.chromium.components.infobars.InfoBar;
 import org.chromium.components.infobars.InfoBarCompactLayout;
 import org.chromium.components.translate.TranslateMenu;
 import org.chromium.components.translate.TranslateTabLayout;
+import org.chromium.content_public.browser.test.util.Criteria;
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
@@ -127,4 +131,17 @@
             }
         });
     }
+
+    /**
+     * Wait until the given tab is translatable. This is useful in cases where we can't wait on the
+     * infobar, such as when a page is in a accept-lang but is still translatable.
+     */
+    public static void waitUntilTranslatable(Tab tab) {
+        Assert.assertNotNull(tab);
+        CriteriaHelper.pollUiThread(() -> {
+            boolean canManuallyTranslate =
+                    TranslateBridge.canManuallyTranslate(tab, /*menuLogging=*/false);
+            Criteria.checkThat(canManuallyTranslate, Matchers.is(true));
+        });
+    }
 }
diff --git a/chrome/test/data/webui/BUILD.gn b/chrome/test/data/webui/BUILD.gn
index 4f80ba14..df0cc1b 100644
--- a/chrome/test/data/webui/BUILD.gn
+++ b/chrome/test/data/webui/BUILD.gn
@@ -29,7 +29,7 @@
     ]
 
     if (enable_tab_search) {
-      sources += [ "tab_search/test/tab_search_interactive_ui_tests.js" ]
+      sources += [ "tab_search_merge/tab_search_interactive_ui_tests.js" ]
     }
 
     gen_include_files = [
@@ -180,7 +180,7 @@
       sources += [ "print_preview/print_preview_ui_browsertest.js" ]
     }
     if (enable_tab_search) {
-      sources += [ "tab_search/test/tab_search_browsertest.js" ]
+      sources += [ "tab_search_merge/tab_search_browsertest.js" ]
     }
     if (enable_webui_tab_strip) {
       sources += [ "tab_strip/tab_strip_browsertest.js" ]
@@ -502,7 +502,7 @@
     ]
   }
   if (enable_tab_search) {
-    deps += [ "tab_search/test:closure_compile" ]
+    deps += [ "tab_search_merge:closure_compile" ]
   }
 }
 
diff --git a/chrome/test/data/webui/chromeos/diagnostics/cpu_card_test.js b/chrome/test/data/webui/chromeos/diagnostics/cpu_card_test.js
index c88101f..6f5a712 100644
--- a/chrome/test/data/webui/chromeos/diagnostics/cpu_card_test.js
+++ b/chrome/test/data/webui/chromeos/diagnostics/cpu_card_test.js
@@ -54,6 +54,34 @@
     return flushTasks();
   }
 
+  /**
+   * Returns the routine-section from the card.
+   * @return {!RoutineSection}
+   */
+  function getRoutineSection() {
+    const routineSection = cpuElement.$$('routine-section');
+    assertTrue(!!routineSection);
+    return routineSection;
+  }
+
+  /**
+   * Returns the Run Tests button from inside the routine-section.
+   * @return {!CrButton}
+   */
+  function getRunTestsButton() {
+    const button = dx_utils.getRunTestsButtonFromSection(getRoutineSection());
+    assertTrue(!!button);
+    return button;
+  }
+
+  /**
+   * Returns whether the run tests button is disabled.
+   * @return {bool}
+   */
+  function isRunTestsButtonDisabled() {
+    return getRunTestsButton().disabled;
+  }
+
   test('CpuCardPopulated', () => {
     return initializeCpuCard(fakeCpuUsage).then(() => {
       const dataPoints = dx_utils.getDataPointElements(cpuElement);
@@ -66,6 +94,11 @@
       const cpuChart = dx_utils.getRealtimeCpuChartElement(cpuElement);
       assertEquals(fakeCpuUsage[0].percent_usage_user, cpuChart.user);
       assertEquals(fakeCpuUsage[0].percent_usage_system, cpuChart.system);
+
+      // Verify the routine section is in the page.
+      assertTrue(!!getRoutineSection());
+      assertTrue(!!getRunTestsButton());
+      assertFalse(isRunTestsButtonDisabled());
     });
   });
-});
\ No newline at end of file
+});
diff --git a/chrome/test/data/webui/chromeos/diagnostics/diagnostics_test_utils.js b/chrome/test/data/webui/chromeos/diagnostics/diagnostics_test_utils.js
index 6cece3a..3ed1a1b 100644
--- a/chrome/test/data/webui/chromeos/diagnostics/diagnostics_test_utils.js
+++ b/chrome/test/data/webui/chromeos/diagnostics/diagnostics_test_utils.js
@@ -52,6 +52,27 @@
 }
 
 /**
+ * Helper function for getting an array of routine-result-entry
+ * element from a routine-section.
+ * @param {!HTMLElement} element
+ * @return {!Array<!HTMLElement>}
+ */
+export function getResultEntriesFromSection(element) {
+  return getResultEntries(getResultList(element));
+}
+
+/**
+ * Helper function for getting the Run Tests button from a routine-section.
+ * @param {!HTMLElement} element
+ * @return {!Array<!HTMLElement>}
+ */
+export function getRunTestsButtonFromSection(element) {
+  const button = element.$$('#runTestsButton');
+  assertTrue(!!button);
+  return button;
+}
+
+/**
  * Helper function to check if a substring exists in an element.
  * @param {!HTMLElement} element
  * @param {string} substring to check
diff --git a/chrome/test/data/webui/chromeos/diagnostics/routine_section_test.js b/chrome/test/data/webui/chromeos/diagnostics/routine_section_test.js
index e244393..3d83dea6 100644
--- a/chrome/test/data/webui/chromeos/diagnostics/routine_section_test.js
+++ b/chrome/test/data/webui/chromeos/diagnostics/routine_section_test.js
@@ -11,7 +11,7 @@
 import {ExecutionProgress} from 'chrome://diagnostics/routine_list_executor.js';
 import {flushTasks} from 'chrome://test/test_util.m.js';
 
-import * as diagnostics_test_utils from './diagnostics_test_utils.js';
+import * as dx_utils from './diagnostics_test_utils.js';
 
 suite('RoutineSectionTest', () => {
   /** @type {?HTMLElement} */
@@ -51,8 +51,7 @@
    * @return {!RoutineList}
    */
   function getResultList() {
-    const resultList =
-        diagnostics_test_utils.getResultList(routineSectionElement);
+    const resultList = dx_utils.getResultList(routineSectionElement);
     assertTrue(!!resultList);
     return resultList;
   }
@@ -62,7 +61,7 @@
    * @return {!CrButton}
    */
   function getRunTestsButton() {
-    const button = routineSectionElement.$$('#runTestsButton');
+    const button = dx_utils.getRunTestsButtonFromSection(routineSectionElement);
     assertTrue(!!button);
     return button;
   }
@@ -89,7 +88,7 @@
    * @return {!Array<!RoutineResultEntry>}
    */
   function getEntries() {
-    return diagnostics_test_utils.getResultEntries(getResultList());
+    return dx_utils.getResultEntries(getResultList());
   }
 
   test('ElementRenders', () => {
diff --git a/chrome/test/data/webui/chromeos/scanning/scanning_app_test.js b/chrome/test/data/webui/chromeos/scanning/scanning_app_test.js
index 456399b..3d7b7cf 100644
--- a/chrome/test/data/webui/chromeos/scanning/scanning_app_test.js
+++ b/chrome/test/data/webui/chromeos/scanning/scanning_app_test.js
@@ -9,7 +9,7 @@
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {setScanServiceForTesting} from 'chrome://scanning/mojo_interface_provider.js';
 import {ScannerArr} from 'chrome://scanning/scanning_app_types.js';
-import {getColorModeString, getSourceTypeString, tokenToString} from 'chrome://scanning/scanning_app_util.js';
+import {getColorModeString, getPageSizeString, getSourceTypeString, tokenToString} from 'chrome://scanning/scanning_app_util.js';
 
 const ColorMode = {
   BLACK_AND_WHITE: chromeos.scanning.mojom.ColorMode.kBlackAndWhite,
@@ -23,6 +23,12 @@
   PNG: chromeos.scanning.mojom.FileType.kPng,
 };
 
+const PageSize = {
+  A4: chromeos.scanning.mojom.PageSize.kIsoA4,
+  Letter: chromeos.scanning.mojom.PageSize.kNaLetter,
+  Max: chromeos.scanning.mojom.PageSize.kMax,
+};
+
 const SourceType = {
   FLATBED: chromeos.scanning.mojom.SourceType.kFlatbed,
   ADF_SIMPLEX: chromeos.scanning.mojom.SourceType.kAdfSimplex,
@@ -226,7 +232,11 @@
 
     const firstCapabilities = {
       sources: [
-        {type: SourceType.FLATBED, name: 'platen'},
+        {
+          type: SourceType.FLATBED,
+          name: 'platen',
+          pageSizes: [PageSize.A4, PageSize.Letter, PageSize.Max]
+        },
         {type: SourceType.ADF_DUPLEX, name: 'adf duplex'}
       ],
       colorModes: [ColorMode.BLACK_AND_WHITE, ColorMode.COLOR],
@@ -256,6 +266,9 @@
               firstCapabilities.colorModes[0].toString(),
               scanningApp.selectedColorMode);
           assertEquals(
+              firstCapabilities.sources[0].pageSizes[0].toString(),
+              scanningApp.selectedPageSize);
+          assertEquals(
               firstCapabilities.resolutions[0].toString(),
               scanningApp.selectedResolution);
 
@@ -270,6 +283,8 @@
           const colorModeSelect =
               scanningApp.$$('#colorModeSelect').$$('select');
           assertFalse(colorModeSelect.disabled);
+          const pageSizeSelect = scanningApp.$$('#pageSizeSelect').$$('select');
+          assertFalse(pageSizeSelect.disabled);
           const resolutionSelect =
               scanningApp.$$('#resolutionSelect').$$('select');
           assertFalse(resolutionSelect.disabled);
@@ -291,6 +306,7 @@
           assertTrue(sourceSelect.disabled);
           assertTrue(fileTypeSelect.disabled);
           assertTrue(colorModeSelect.disabled);
+          assertTrue(pageSizeSelect.disabled);
           assertTrue(resolutionSelect.disabled);
           assertTrue(scanButton.disabled);
           assertEquals('Scanning...', statusText.textContent.trim());
@@ -304,6 +320,7 @@
           assertFalse(scanningApp.$$('#sourceSelect').$$('select').disabled);
           assertFalse(scanningApp.$$('#fileTypeSelect').$$('select').disabled);
           assertFalse(scanningApp.$$('#colorModeSelect').$$('select').disabled);
+          assertFalse(scanningApp.$$('#pageSizeSelect').$$('select').disabled);
           assertFalse(
               scanningApp.$$('#resolutionSelect').$$('select').disabled);
           assertFalse(scanningApp.$$('#scanButton').disabled);
@@ -563,6 +580,74 @@
   });
 });
 
+suite('PageSizeSelectTest', () => {
+  /** @type {!PageSizeSelectElement} */
+  let pageSizeSelect;
+
+  setup(() => {
+    pageSizeSelect = document.createElement('page-size-select');
+    assertTrue(!!pageSizeSelect);
+    document.body.appendChild(pageSizeSelect);
+  });
+
+  teardown(() => {
+    pageSizeSelect.remove();
+    pageSizeSelect = null;
+  });
+
+  test('initializePageSizeSelect', () => {
+    // Before options are added, the dropdown should be disabled and empty.
+    const select = pageSizeSelect.$$('select');
+    assertTrue(!!select);
+    assertTrue(select.disabled);
+    assertEquals(0, select.length);
+
+    const firstPageSize = PageSize.A4;
+    const secondPageSize = PageSize.Max;
+    pageSizeSelect.pageSizes = [firstPageSize, secondPageSize];
+    flush();
+
+    // Verify that adding more than one page size results in the dropdown
+    // becoming enabled with the correct options.
+    assertFalse(select.disabled);
+    assertEquals(2, select.length);
+    assertEquals(
+        getPageSizeString(firstPageSize), select.options[0].textContent.trim());
+    assertEquals(
+        getPageSizeString(secondPageSize),
+        select.options[1].textContent.trim());
+    assertEquals(firstPageSize.toString(), select.value);
+
+    // Selecting a different option should update the selected value.
+    select.value = secondPageSize.toString();
+    select.dispatchEvent(new CustomEvent('change'));
+    flush();
+
+    assertEquals(secondPageSize.toString(), pageSizeSelect.selectedPageSize);
+  });
+
+  test('pageSizeSelectDisabled', () => {
+    const select = pageSizeSelect.$$('select');
+    assertTrue(!!select);
+
+    let pageSizeArr = [PageSize.Letter];
+    pageSizeSelect.pageSizes = pageSizeArr;
+    flush();
+
+    // Verify the dropdown is disabled when there's only one option.
+    assertEquals(1, select.length);
+    assertTrue(select.disabled);
+
+    pageSizeArr = pageSizeArr.concat([PageSize.A4]);
+    pageSizeSelect.pageSizes = pageSizeArr;
+    flush();
+
+    // Verify the dropdown is enabled when there's more than one option.
+    assertEquals(2, select.length);
+    assertFalse(select.disabled);
+  });
+});
+
 suite('ResolutionSelectTest', () => {
   /** @type {!ResolutionSelectElement} */
   let resolutionSelect;
diff --git a/chrome/test/data/webui/cr_components/chromeos/network/network_ip_config_test.js b/chrome/test/data/webui/cr_components/chromeos/network/network_ip_config_test.js
index cbf4268..f0d5d16 100644
--- a/chrome/test/data/webui/cr_components/chromeos/network/network_ip_config_test.js
+++ b/chrome/test/data/webui/cr_components/chromeos/network/network_ip_config_test.js
@@ -19,6 +19,33 @@
     Polymer.dom.flush();
   });
 
+  test('Enabled', function() {
+    const mojom = chromeos.networkConfig.mojom;
+    assertTrue(!!ipConfig.$.autoConfigIpToggle);
+
+    // WiFi non-policy networks should enable autoConfigIpToggle.
+    ipConfig.managedProperties = {
+      ipAddressConfigType: {
+        activeValue: 'Static',
+        policySource: mojom.PolicySource.kNone,
+      },
+      type: mojom.NetworkType.kWiFi,
+    };
+    Polymer.dom.flush();
+    assertFalse(ipConfig.$.autoConfigIpToggle.disabled);
+
+    // Cellular network should disable autoConfigIpToggle.
+    ipConfig.managedProperties = {
+      ipAddressConfigType: {
+        activeValue: 'Static',
+        policySource: mojom.PolicySource.kNone,
+      },
+      type: mojom.NetworkType.kCellular,
+    };
+    Polymer.dom.flush();
+    assertTrue(ipConfig.$.autoConfigIpToggle.disabled);
+  });
+
   test('Auto-config toggle policy enforcement', function() {
     const mojom = chromeos.networkConfig.mojom;
 
@@ -34,7 +61,7 @@
     // ipAddressConfigType policy is not enforced (kNone).
     ipConfig.managedProperties = {
       ipAddressConfigType: {
-        activeValue: "Static",
+        activeValue: 'Static',
         policySource: mojom.PolicySource.kNone,
       },
     };
@@ -44,7 +71,7 @@
     // ipAddressConfigType policy is enforced.
     ipConfig.managedProperties = {
       ipAddressConfigType: {
-        activeValue: "Static",
+        activeValue: 'Static',
         policySource: mojom.PolicySource.kUserPolicyEnforced,
       },
     };
diff --git a/chrome/browser/resources/tab_search_merge/test/BUILD.gn b/chrome/test/data/webui/tab_search_merge/BUILD.gn
similarity index 74%
rename from chrome/browser/resources/tab_search_merge/test/BUILD.gn
rename to chrome/test/data/webui/tab_search_merge/BUILD.gn
index f0f059c..917b099 100644
--- a/chrome/browser/resources/tab_search_merge/test/BUILD.gn
+++ b/chrome/test/data/webui/tab_search_merge/BUILD.gn
@@ -7,7 +7,7 @@
 js_type_check("closure_compile") {
   is_polymer3 = true
   closure_flags = default_closure_args + [
-                    "browser_resolver_prefix_replacements=\"chrome://tab-search/=../../chrome/browser/resources/tab-search/\"",
+                    "browser_resolver_prefix_replacements=\"chrome://tab-search/=../../chrome/browser/resources/tab-search-merge/\"",
                     "js_module_root=../../chrome/test/data/webui/",
                     "js_module_root=./gen/chrome/test/data/webui/",
                   ]
@@ -22,7 +22,7 @@
 js_library("fuzzy_search_test") {
   deps = [
     ":test_tab_search_api_proxy",
-    "../..:chai_assert",
+    "..:chai_assert",
   ]
   externs_list = [ "$externs_path/mocha-2.5.js" ]
 }
@@ -30,8 +30,8 @@
 js_library("tab_search_app_test") {
   deps = [
     ":test_tab_search_api_proxy",
-    "../..:chai_assert",
-    "//chrome/browser/resources/tab_search:app",
+    "..:chai_assert",
+    "//chrome/browser/resources/tab_search_merge:app",
   ]
   externs_list = [ "$externs_path/mocha-2.5.js" ]
 }
@@ -39,24 +39,24 @@
 js_library("tab_search_app_focus_test") {
   deps = [
     ":test_tab_search_api_proxy",
-    "../..:chai_assert",
-    "//chrome/browser/resources/tab_search:app",
+    "..:chai_assert",
+    "//chrome/browser/resources/tab_search_merge:app",
   ]
   externs_list = [ "$externs_path/mocha-2.5.js" ]
 }
 
 js_library("test_tab_search_api_proxy") {
   deps = [
-    "//chrome/browser/resources/tab_search:tab_search_api_proxy",
+    "..:test_browser_proxy.m",
+    "//chrome/browser/resources/tab_search_merge:tab_search_api_proxy",
     "//chrome/browser/ui/webui/tab_search:mojo_bindings_js_library_for_compile",
-    "//chrome/test/data/webui:test_browser_proxy.m",
   ]
 }
 
 js_library("tab_search_item_test") {
   deps = [
-    "../..:chai_assert",
-    "//chrome/browser/resources/tab_search:tab_search_item",
+    "..:chai_assert",
+    "//chrome/browser/resources/tab_search_merge:tab_search_item",
   ]
   externs_list = [ "$externs_path/mocha-2.5.js" ]
 }
diff --git a/chrome/browser/resources/tab_search_merge/test/fuzzy_search_test.js b/chrome/test/data/webui/tab_search_merge/fuzzy_search_test.js
similarity index 95%
rename from chrome/browser/resources/tab_search_merge/test/fuzzy_search_test.js
rename to chrome/test/data/webui/tab_search_merge/fuzzy_search_test.js
index c442681..9b7907d 100644
--- a/chrome/browser/resources/tab_search_merge/test/fuzzy_search_test.js
+++ b/chrome/test/data/webui/tab_search_merge/fuzzy_search_test.js
@@ -176,8 +176,8 @@
     assertDeepEquals(records, fuzzySearch('', records, options));
 
     assertDeepEquals(archMatchedRecords, fuzzySearch('arch', records, options));
-    assertDeepEquals(searchMatchedRecords,
-                     fuzzySearch('search', records, options));
+    assertDeepEquals(
+        searchMatchedRecords, fuzzySearch('search', records, options));
 
     // No matches should return an empty list.
     assertDeepEquals([], fuzzySearch('archh', records, options));
@@ -214,10 +214,10 @@
       },
     ];
 
-    assertDeepEquals(backslashMatchedRecords,
-                     fuzzySearch('\\test', records, options));
-    assertDeepEquals(quoteMatchedRecords,
-                     fuzzySearch('\"end', records, options));
+    assertDeepEquals(
+        backslashMatchedRecords, fuzzySearch('\\test', records, options));
+    assertDeepEquals(
+        quoteMatchedRecords, fuzzySearch('\"end', records, options));
   });
 
   test('Test exact match result scoring accounts for match position.', () => {
diff --git a/chrome/browser/resources/tab_search_merge/test/tab_search_app_focus_test.js b/chrome/test/data/webui/tab_search_merge/tab_search_app_focus_test.js
similarity index 97%
rename from chrome/browser/resources/tab_search_merge/test/tab_search_app_focus_test.js
rename to chrome/test/data/webui/tab_search_merge/tab_search_app_focus_test.js
index 4e3f4eed..0b4f16d0 100644
--- a/chrome/browser/resources/tab_search_merge/test/tab_search_app_focus_test.js
+++ b/chrome/test/data/webui/tab_search_merge/tab_search_app_focus_test.js
@@ -6,7 +6,7 @@
 import {getDeepActiveElement} from 'chrome://resources/js/util.m.js';
 import {keyDownOn} from 'chrome://resources/polymer/v3_0/iron-test-helpers/mock-interactions.js';
 import {TabSearchAppElement} from 'chrome://tab-search/app.js';
-import {TabSearchApiProxy, TabSearchApiProxyImpl} from 'chrome://tab-search/tab_search_api_proxy.js'
+import {TabSearchApiProxy, TabSearchApiProxyImpl} from 'chrome://tab-search/tab_search_api_proxy.js';
 import {TabSearchItem} from 'chrome://tab-search/tab_search_item.js';
 import {TabSearchSearchField} from 'chrome://tab-search/tab_search_search_field.js';
 
@@ -53,7 +53,7 @@
     const searchField = /** @type {!TabSearchSearchField} */
         (tabSearchApp.shadowRoot.querySelector('#searchField'));
     const searchInput = /** @type {!HTMLInputElement} */
-        (searchField.shadowRoot.querySelector('#searchInput'))
+        (searchField.shadowRoot.querySelector('#searchInput'));
     assertEquals(searchInput, getDeepActiveElement());
 
     const tabSearchItems = /** @type {!NodeList<!HTMLElement>} */
diff --git a/chrome/browser/resources/tab_search_merge/test/tab_search_app_test.js b/chrome/test/data/webui/tab_search_merge/tab_search_app_test.js
similarity index 99%
rename from chrome/browser/resources/tab_search_merge/test/tab_search_app_test.js
rename to chrome/test/data/webui/tab_search_merge/tab_search_app_test.js
index 8ad57cb..1596191b 100644
--- a/chrome/browser/resources/tab_search_merge/test/tab_search_app_test.js
+++ b/chrome/test/data/webui/tab_search_merge/tab_search_app_test.js
@@ -5,7 +5,7 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {keyDownOn} from 'chrome://resources/polymer/v3_0/iron-test-helpers/mock-interactions.js';
 import {TabSearchAppElement} from 'chrome://tab-search/app.js';
-import {TabSearchApiProxy, TabSearchApiProxyImpl} from 'chrome://tab-search/tab_search_api_proxy.js'
+import {TabSearchApiProxy, TabSearchApiProxyImpl} from 'chrome://tab-search/tab_search_api_proxy.js';
 import {TabSearchItem} from 'chrome://tab-search/tab_search_item.js';
 import {TabSearchSearchField} from 'chrome://tab-search/tab_search_search_field.js';
 
diff --git a/chrome/browser/resources/tab_search_merge/test/tab_search_browsertest.js b/chrome/test/data/webui/tab_search_merge/tab_search_browsertest.js
similarity index 93%
rename from chrome/browser/resources/tab_search_merge/test/tab_search_browsertest.js
rename to chrome/test/data/webui/tab_search_merge/tab_search_browsertest.js
index 44a560e..0c54d86 100644
--- a/chrome/browser/resources/tab_search_merge/test/tab_search_browsertest.js
+++ b/chrome/test/data/webui/tab_search_merge/tab_search_browsertest.js
@@ -36,7 +36,7 @@
 var TabSearchAppTest = class extends TabSearchBrowserTest {
   /** @override */
   get browsePreload() {
-    return 'chrome://tab-search/test_loader.html?module=tab_search/test/tab_search_app_test.js';
+    return 'chrome://tab-search/test_loader.html?module=tab_search_merge/tab_search_app_test.js';
   }
 };
 
@@ -48,7 +48,7 @@
 var FuzzySearchTest = class extends TabSearchBrowserTest {
   /** @override */
   get browsePreload() {
-    return 'chrome://tab-search/test_loader.html?module=tab_search/test/fuzzy_search_test.js';
+    return 'chrome://tab-search/test_loader.html?module=tab_search_merge/fuzzy_search_test.js';
   }
 };
 
@@ -60,10 +60,10 @@
 var TabSearchItemTest = class extends TabSearchBrowserTest {
   /** @override */
   get browsePreload() {
-    return 'chrome://tab-search/test_loader.html?module=tab_search/test/tab_search_item_test.js';
+    return 'chrome://tab-search/test_loader.html?module=tab_search_merge/tab_search_item_test.js';
   }
 };
 
 TEST_F('TabSearchItemTest', 'All', function() {
   mocha.run();
-});
\ No newline at end of file
+});
diff --git a/chrome/browser/resources/tab_search_merge/test/tab_search_interactive_ui_tests.js b/chrome/test/data/webui/tab_search_merge/tab_search_interactive_ui_tests.js
similarity index 95%
rename from chrome/browser/resources/tab_search_merge/test/tab_search_interactive_ui_tests.js
rename to chrome/test/data/webui/tab_search_merge/tab_search_interactive_ui_tests.js
index 8bcca59..a9c89e3 100644
--- a/chrome/browser/resources/tab_search_merge/test/tab_search_interactive_ui_tests.js
+++ b/chrome/test/data/webui/tab_search_merge/tab_search_interactive_ui_tests.js
@@ -14,7 +14,7 @@
 var TabSearchInteractiveUITest = class extends PolymerInteractiveUITest {
   /** @override */
   get browsePreload() {
-    return 'chrome://tab-search/test_loader.html?module=tab_search/test/tab_search_app_focus_test.js';
+    return 'chrome://tab-search/test_loader.html?module=tab_search_merge/tab_search_app_focus_test.js';
   }
 
   get extraLibraries() {
@@ -32,7 +32,7 @@
       ]
     };
   }
-}
+};
 
 TEST_F('TabSearchInteractiveUITest', 'All', function() {
   mocha.run();
diff --git a/chrome/browser/resources/tab_search_merge/test/tab_search_item_test.js b/chrome/test/data/webui/tab_search_merge/tab_search_item_test.js
similarity index 100%
rename from chrome/browser/resources/tab_search_merge/test/tab_search_item_test.js
rename to chrome/test/data/webui/tab_search_merge/tab_search_item_test.js
diff --git a/chrome/browser/resources/tab_search_merge/test/tab_search_test_data.js b/chrome/test/data/webui/tab_search_merge/tab_search_test_data.js
similarity index 100%
rename from chrome/browser/resources/tab_search_merge/test/tab_search_test_data.js
rename to chrome/test/data/webui/tab_search_merge/tab_search_test_data.js
diff --git a/chrome/browser/resources/tab_search_merge/test/tab_search_test_helper.js b/chrome/test/data/webui/tab_search_merge/tab_search_test_helper.js
similarity index 100%
rename from chrome/browser/resources/tab_search_merge/test/tab_search_test_helper.js
rename to chrome/test/data/webui/tab_search_merge/tab_search_test_helper.js
diff --git a/chrome/browser/resources/tab_search_merge/test/test_tab_search_api_proxy.js b/chrome/test/data/webui/tab_search_merge/test_tab_search_api_proxy.js
similarity index 96%
rename from chrome/browser/resources/tab_search_merge/test/test_tab_search_api_proxy.js
rename to chrome/test/data/webui/tab_search_merge/test_tab_search_api_proxy.js
index 5d30c2fa9..494bd9d 100644
--- a/chrome/browser/resources/tab_search_merge/test/test_tab_search_api_proxy.js
+++ b/chrome/test/data/webui/tab_search_merge/test_tab_search_api_proxy.js
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import {TabSearchApiProxy} from 'chrome://tab-search/tab_search_api_proxy.js';
-import {TestBrowserProxy} from '../../test_browser_proxy.m.js';
+import {TestBrowserProxy} from '../test_browser_proxy.m.js';
 
 /** @implements {TabSearchApiProxy} */
 export class TestTabSearchApiProxy extends TestBrowserProxy {
diff --git a/chrome/updater/BUILD.gn b/chrome/updater/BUILD.gn
index e46e05a4..edc0e84 100644
--- a/chrome/updater/BUILD.gn
+++ b/chrome/updater/BUILD.gn
@@ -96,6 +96,8 @@
       "configurator.h",
       "control_service_impl.cc",
       "control_service_impl.h",
+      "control_service_impl_inactive.cc",
+      "control_service_impl_inactive.h",
       "crx_downloader_factory.h",
       "external_constants.cc",
       "external_constants.h",
@@ -113,6 +115,8 @@
       "tag.h",
       "update_service_impl.cc",
       "update_service_impl.h",
+      "update_service_impl_inactive.cc",
+      "update_service_impl_inactive.h",
       "updater.cc",
       "updater.h",
     ]
@@ -340,15 +344,16 @@
     }
   }
 
-  group("updater_integration_tests") {
-    testonly = true
+  script_test("updater_integration_tests") {
+    script = "//testing/scripts/run_isolated_script_test.py"
+
+    args = [ "@WrappedPath(" +
+             rebase_path("//chrome/updater/run_updater_tests.py",
+                         root_build_dir) + ")" ]
 
     data = [
       "//chrome/updater/run_updater_tests.py",
       "//chrome/updater/test/",
-      "//testing/scripts/common.py",
-      "//testing/scripts/run_isolated_script_test.py",
-      "//testing/xvfb.py",
       "//third_party/catapult/third_party/typ/",
     ]
 
diff --git a/chrome/updater/app/app_server.cc b/chrome/updater/app/app_server.cc
index 03fc22d..57d63ee6 100644
--- a/chrome/updater/app/app_server.cc
+++ b/chrome/updater/app/app_server.cc
@@ -8,11 +8,18 @@
 
 #include "base/bind.h"
 #include "base/logging.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/version.h"
 #include "chrome/updater/configurator.h"
 #include "chrome/updater/constants.h"
+#include "chrome/updater/control_service.h"
+#include "chrome/updater/control_service_impl.h"
+#include "chrome/updater/control_service_impl_inactive.h"
 #include "chrome/updater/persisted_data.h"
 #include "chrome/updater/prefs.h"
+#include "chrome/updater/update_service.h"
+#include "chrome/updater/update_service_impl.h"
+#include "chrome/updater/update_service_impl_inactive.h"
 #include "chrome/updater/updater_version.h"
 #include "components/prefs/pref_service.h"
 
@@ -41,7 +48,10 @@
 
   if (this_version < active_version) {
     global_prefs = nullptr;
-    return base::BindOnce(&AppServer::UninstallSelf, this);
+    uninstall_ = true;
+    return base::BindOnce(&AppServer::ActiveDuty, this,
+                          MakeInactiveUpdateService(),
+                          MakeInactiveControlService());
   }
 
   if (active_version != base::Version("0") && active_version != this_version) {
@@ -58,12 +68,18 @@
   }
 
   config_ = base::MakeRefCounted<Configurator>(std::move(global_prefs));
-  return base::BindOnce(&AppServer::ActiveDuty, this);
+  return base::BindOnce(&AppServer::ActiveDuty, this,
+                        base::MakeRefCounted<UpdateServiceImpl>(config_),
+                        base::MakeRefCounted<ControlServiceImpl>(config_));
 }
 
 void AppServer::Uninitialize() {
   if (config_)
     PrefsCommitPendingWrites(config_->GetPrefService());
+  if (uninstall_) {
+    VLOG(1) << "Uninstalling version " << UPDATER_VERSION_STRING;
+    UninstallSelf();
+  }
 }
 
 void AppServer::FirstTaskRun() {
@@ -75,7 +91,10 @@
   DVLOG(2) << __func__;
   local_prefs->SetQualified(true);
   PrefsCommitPendingWrites(local_prefs->GetPrefService());
-  Shutdown(kErrorQualificationExit);
+
+  // Start ActiveDuty with inactive service implementations. To use active
+  // implementations, the server would have to ModeCheck again.
+  ActiveDuty(MakeInactiveUpdateService(), MakeInactiveControlService());
 }
 
 bool AppServer::SwapVersions(GlobalPrefs* global_prefs) {
diff --git a/chrome/updater/app/app_server.h b/chrome/updater/app/app_server.h
index d8d7e98..7b8d8bb1 100644
--- a/chrome/updater/app/app_server.h
+++ b/chrome/updater/app/app_server.h
@@ -14,8 +14,10 @@
 namespace updater {
 
 class Configurator;
+class ControlService;
 class GlobalPrefs;
 class LocalPrefs;
+class UpdateService;
 
 // AppServer runs as the updater server process. Multiple servers of different
 // application versions can be run side-by-side. Each such server is called a
@@ -32,15 +34,15 @@
   // Overrides of App.
   void Uninitialize() override;
 
-  scoped_refptr<Configurator> config_;
-
  private:
   // Overrides of App.
   void Initialize() final;
   void FirstTaskRun() final;
 
-  // Set up the server for normal active-candidate functions.
-  virtual void ActiveDuty() = 0;
+  // Set up the server for normal active version functions using the provided
+  // services.
+  virtual void ActiveDuty(scoped_refptr<UpdateService> update_service,
+                          scoped_refptr<ControlService> control_service) = 0;
 
   // Set up all non-side-by-side RPC interfaces to point to this candidate
   // server.
@@ -63,6 +65,11 @@
   bool SwapVersions(GlobalPrefs* global_prefs);
 
   base::OnceClosure first_task_;
+  scoped_refptr<Configurator> config_;
+
+  // If true, this version of the updater should uninstall itself during
+  // shutdown.
+  bool uninstall_ = false;
 };
 
 scoped_refptr<App> AppServerInstance();
diff --git a/chrome/updater/app/app_server_unittest.cc b/chrome/updater/app/app_server_unittest.cc
index c491e42..5bf13c3 100644
--- a/chrome/updater/app/app_server_unittest.cc
+++ b/chrome/updater/app/app_server_unittest.cc
@@ -8,11 +8,14 @@
 
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/message_loop/message_pump_type.h"
 #include "base/task/single_thread_task_executor.h"
 #include "base/task/thread_pool/thread_pool_instance.h"
 #include "chrome/updater/constants.h"
+#include "chrome/updater/control_service.h"
 #include "chrome/updater/prefs.h"
+#include "chrome/updater/update_service.h"
 #include "chrome/updater/updater_version.h"
 #include "chrome/updater/util.h"
 #include "components/prefs/pref_service.h"
@@ -31,11 +34,12 @@
   AppServerTest() {
     ON_CALL(*this, ActiveDuty)
         .WillByDefault(Invoke(this, &AppServerTest::Shutdown0));
-    ON_CALL(*this, UninstallSelf)
-        .WillByDefault(Invoke(this, &AppServerTest::Shutdown0));
   }
 
-  MOCK_METHOD(void, ActiveDuty, (), (override));
+  MOCK_METHOD(void,
+              ActiveDuty,
+              (scoped_refptr<UpdateService>, scoped_refptr<ControlService>),
+              (override));
   MOCK_METHOD(bool, SwapRPCInterfaces, (), (override));
   MOCK_METHOD(void, UninstallSelf, (), (override));
 
@@ -89,11 +93,11 @@
   }
   auto app = base::MakeRefCounted<AppServerTest>();
 
-  // Expect the app to qualify and then shutdown.
-  EXPECT_CALL(*app, ActiveDuty).Times(0);
+  // Expect the app to qualify and then ActiveDuty.
+  EXPECT_CALL(*app, ActiveDuty).Times(1);
   EXPECT_CALL(*app, SwapRPCInterfaces).Times(0);
   EXPECT_CALL(*app, UninstallSelf).Times(0);
-  EXPECT_EQ(app->Run(), kErrorQualificationExit);
+  EXPECT_EQ(app->Run(), 0);
   EXPECT_TRUE(CreateLocalPrefs()->GetQualified());
 }
 
@@ -108,8 +112,8 @@
   }
   auto app = base::MakeRefCounted<AppServerTest>();
 
-  // Expect the app to SelfUninstall and then Shutdown(0).
-  EXPECT_CALL(*app, ActiveDuty).Times(0);
+  // Expect the app to ActiveDuty then SelfUninstall.
+  EXPECT_CALL(*app, ActiveDuty).Times(1);
   EXPECT_CALL(*app, SwapRPCInterfaces).Times(0);
   EXPECT_CALL(*app, UninstallSelf).Times(1);
   EXPECT_EQ(app->Run(), 0);
diff --git a/chrome/updater/app/server/mac/app_server.h b/chrome/updater/app/server/mac/app_server.h
index 8055f3e..c37d1ea 100644
--- a/chrome/updater/app/server/mac/app_server.h
+++ b/chrome/updater/app/server/mac/app_server.h
@@ -12,6 +12,7 @@
 #include "base/atomic_ref_count.h"
 #include "base/mac/scoped_nsobject.h"
 #include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/sequence_checker.h"
 #include "chrome/updater/app/app_server.h"
 #include "chrome/updater/app/server/mac/service_delegate.h"
@@ -26,6 +27,9 @@
 
 namespace updater {
 
+class ControlService;
+class UpdateService;
+
 class AppServerMac : public AppServer {
  public:
   AppServerMac();
@@ -40,7 +44,8 @@
   ~AppServerMac() override;
 
   // Overrides of AppServer.
-  void ActiveDuty() override;
+  void ActiveDuty(scoped_refptr<UpdateService> update_service,
+                  scoped_refptr<ControlService> control_service) override;
   bool SwapRPCInterfaces() override;
   void UninstallSelf() override;
 
diff --git a/chrome/updater/app/server/mac/server.mm b/chrome/updater/app/server/mac/server.mm
index 9c4f0e0a..09e7371 100644
--- a/chrome/updater/app/server/mac/server.mm
+++ b/chrome/updater/app/server/mac/server.mm
@@ -21,11 +21,11 @@
 #include "chrome/updater/app/server/mac/service_delegate.h"
 #include "chrome/updater/configurator.h"
 #include "chrome/updater/constants.h"
-#include "chrome/updater/control_service_impl.h"
+#include "chrome/updater/control_service.h"
 #include "chrome/updater/mac/setup/setup.h"
 #import "chrome/updater/mac/xpc_service_names.h"
 #include "chrome/updater/prefs.h"
-#include "chrome/updater/update_service_impl.h"
+#include "chrome/updater/update_service.h"
 
 namespace updater {
 
@@ -43,7 +43,8 @@
   AppServer::Uninitialize();
 }
 
-void AppServerMac::ActiveDuty() {
+void AppServerMac::ActiveDuty(scoped_refptr<UpdateService> update_service,
+                              scoped_refptr<ControlService> control_service) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   const base::CommandLine& command_line =
       *base::CommandLine::ForCurrentProcess();
@@ -58,8 +59,7 @@
     @autoreleasepool {
       // Sets up a listener and delegate for the CRUControlling XPC connection.
       control_service_delegate_.reset([[CRUControlServiceXPCDelegate alloc]
-          initWithControlService:base::MakeRefCounted<ControlServiceImpl>(
-                                     config_)
+          initWithControlService:control_service
                        appServer:scoped_refptr<AppServerMac>(this)]);
 
       control_service_listener_.reset([[NSXPCListener alloc]
@@ -74,7 +74,7 @@
       // Sets up a listener and delegate for the CRUUpdateChecking XPC
       // connection.
       update_check_delegate_.reset([[CRUUpdateCheckServiceXPCDelegate alloc]
-          initWithUpdateService:base::MakeRefCounted<UpdateServiceImpl>(config_)
+          initWithUpdateService:update_service
                       appServer:scoped_refptr<AppServerMac>(this)]);
 
       update_check_listener_.reset([[NSXPCListener alloc]
@@ -91,9 +91,7 @@
 }
 
 void AppServerMac::UninstallSelf() {
-  base::ThreadPool::PostTaskAndReplyWithResult(
-      FROM_HERE, {base::MayBlock()}, base::BindOnce(&UninstallCandidate),
-      base::BindOnce(&AppServerMac::Shutdown, this));
+  UninstallCandidate();
 }
 
 bool AppServerMac::SwapRPCInterfaces() {
diff --git a/chrome/updater/app/server/win/server.cc b/chrome/updater/app/server/win/server.cc
index 238211d..0be5efb 100644
--- a/chrome/updater/app/server/win/server.cc
+++ b/chrome/updater/app/server/win/server.cc
@@ -20,9 +20,9 @@
 #include "chrome/updater/app/server/win/com_classes.h"
 #include "chrome/updater/app/server/win/com_classes_legacy.h"
 #include "chrome/updater/configurator.h"
-#include "chrome/updater/control_service_impl.h"
+#include "chrome/updater/control_service.h"
 #include "chrome/updater/prefs.h"
-#include "chrome/updater/update_service_impl.h"
+#include "chrome/updater/update_service.h"
 #include "chrome/updater/win/constants.h"
 #include "chrome/updater/win/wrl_module.h"
 #include "components/prefs/pref_service.h"
@@ -147,23 +147,22 @@
   main_task_runner_->PostTask(
       FROM_HERE, base::BindOnce([]() {
         scoped_refptr<ComServerApp> this_server = AppServerSingletonInstance();
-        this_server->config_->GetPrefService()->CommitPendingWrite();
         this_server->update_service_ = nullptr;
         this_server->control_service_ = nullptr;
-        this_server->config_ = nullptr;
         this_server->Shutdown(0);
       }));
 }
 
-void ComServerApp::ActiveDuty() {
+void ComServerApp::ActiveDuty(scoped_refptr<UpdateService> update_service,
+                              scoped_refptr<ControlService> control_service) {
   if (!com_initializer_.Succeeded()) {
     PLOG(ERROR) << "Failed to initialize COM";
     Shutdown(-1);
     return;
   }
   main_task_runner_ = base::SequencedTaskRunnerHandle::Get();
-  update_service_ = base::MakeRefCounted<UpdateServiceImpl>(config_);
-  control_service_ = base::MakeRefCounted<ControlServiceImpl>(config_);
+  update_service_ = update_service;
+  control_service_ = control_service;
   CreateWRLModule();
   HRESULT hr = RegisterClassObjects();
   if (FAILED(hr))
diff --git a/chrome/updater/app/server/win/server.h b/chrome/updater/app/server/win/server.h
index 244ccd9..fe32594c 100644
--- a/chrome/updater/app/server/win/server.h
+++ b/chrome/updater/app/server/win/server.h
@@ -55,7 +55,8 @@
   void InitializeThreadPool() override;
 
   // Overrides for AppServer
-  void ActiveDuty() override;
+  void ActiveDuty(scoped_refptr<UpdateService> update_service,
+                  scoped_refptr<ControlService> control_service) override;
   bool SwapRPCInterfaces() override;
   void UninstallSelf() override;
 
diff --git a/chrome/updater/constants.h b/chrome/updater/constants.h
index def9771..98e397b 100644
--- a/chrome/updater/constants.h
+++ b/chrome/updater/constants.h
@@ -185,9 +185,6 @@
 // The server candidate failed to promote itself to active.
 constexpr int kErrorFailedToSwap = 2;
 
-// The server has finished qualification and will not do further operations.
-constexpr int kErrorQualificationExit = 3;
-
 // Policy Management constants.
 extern const char kProxyModeDirect[];
 extern const char kProxyModeAutoDetect[];
diff --git a/chrome/updater/control_service_impl.cc b/chrome/updater/control_service_impl.cc
index b624dfd..d0fd68f 100644
--- a/chrome/updater/control_service_impl.cc
+++ b/chrome/updater/control_service_impl.cc
@@ -27,8 +27,7 @@
     scoped_refptr<updater::Configurator> config)
     : config_(config),
       persisted_data_(
-          base::MakeRefCounted<PersistedData>(config_->GetPrefService())),
-      main_task_runner_(base::SequencedTaskRunnerHandle::Get()) {}
+          base::MakeRefCounted<PersistedData>(config_->GetPrefService())) {}
 
 void ControlServiceImpl::Run(base::OnceClosure callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -44,6 +43,8 @@
 }
 
 void ControlServiceImpl::MaybeCheckForUpdates(base::OnceClosure callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   const base::Time lastUpdateTime =
       config_->GetPrefService()->GetTime(kPrefUpdateTime);
 
@@ -54,7 +55,8 @@
           base::TimeDelta::FromSeconds(config_->NextCheckDelay())) {
     VLOG(0) << "Skipping checking for updates:  "
             << timeSinceUpdate.InMinutes();
-    main_task_runner_->PostTask(FROM_HERE, std::move(callback));
+    base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+                                                     std::move(callback));
     return;
   }
 
@@ -79,6 +81,7 @@
 }
 
 void ControlServiceImpl::UnregisterMissingApps() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   for (const auto& app_id : persisted_data_->GetAppIds()) {
     // Skip if app_id is equal to updater app id.
     if (app_id == kUpdaterAppId)
diff --git a/chrome/updater/control_service_impl.h b/chrome/updater/control_service_impl.h
index aa5e9603..8dd3fb9 100644
--- a/chrome/updater/control_service_impl.h
+++ b/chrome/updater/control_service_impl.h
@@ -10,10 +10,6 @@
 #include "base/sequence_checker.h"
 #include "chrome/updater/control_service.h"
 
-namespace base {
-class SequencedTaskRunner;
-}
-
 namespace updater {
 
 class Configurator;
@@ -45,7 +41,6 @@
 
   scoped_refptr<updater::Configurator> config_;
   scoped_refptr<updater::PersistedData> persisted_data_;
-  scoped_refptr<base::SequencedTaskRunner> main_task_runner_;
 };
 
 }  // namespace updater
diff --git a/chrome/updater/control_service_impl_inactive.cc b/chrome/updater/control_service_impl_inactive.cc
new file mode 100644
index 0000000..9ab8ce85
--- /dev/null
+++ b/chrome/updater/control_service_impl_inactive.cc
@@ -0,0 +1,43 @@
+// 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/updater/control_service_impl_inactive.h"
+
+#include "base/callback.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "chrome/updater/control_service.h"
+
+namespace updater {
+
+namespace {
+
+class ControlServiceImplInactive : public ControlService {
+ public:
+  ControlServiceImplInactive() = default;
+
+  // Overrides for updater::ControlService.
+  void Run(base::OnceClosure callback) override {
+    base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+                                                     std::move(callback));
+  }
+
+  void InitializeUpdateService(base::OnceClosure callback) override {
+    base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+                                                     std::move(callback));
+  }
+
+  void Uninitialize() override {}
+
+ private:
+  ~ControlServiceImplInactive() override = default;
+};
+
+}  // namespace
+
+scoped_refptr<ControlService> MakeInactiveControlService() {
+  return base::MakeRefCounted<ControlServiceImplInactive>();
+}
+
+}  // namespace updater
diff --git a/chrome/updater/control_service_impl_inactive.h b/chrome/updater/control_service_impl_inactive.h
new file mode 100644
index 0000000..9bd067b
--- /dev/null
+++ b/chrome/updater/control_service_impl_inactive.h
@@ -0,0 +1,18 @@
+// 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_UPDATER_CONTROL_SERVICE_IMPL_INACTIVE_H_
+#define CHROME_UPDATER_CONTROL_SERVICE_IMPL_INACTIVE_H_
+
+#include "base/memory/scoped_refptr.h"
+
+namespace updater {
+
+class ControlService;
+
+scoped_refptr<ControlService> MakeInactiveControlService();
+
+}  // namespace updater
+
+#endif  // CHROME_UPDATER_CONTROL_SERVICE_IMPL_INACTIVE_H_
diff --git a/chrome/updater/test/integration_tests.cc b/chrome/updater/test/integration_tests.cc
index 07712c0f..fcb8657 100644
--- a/chrome/updater/test/integration_tests.cc
+++ b/chrome/updater/test/integration_tests.cc
@@ -9,6 +9,9 @@
 #include "base/command_line.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/process/launch.h"
+#include "base/process/process.h"
 #include "base/test/task_environment.h"
 #include "base/version.h"
 #include "build/build_config.h"
@@ -37,6 +40,18 @@
   EXPECT_EQ(CreateGlobalPrefs()->GetActiveVersion(), expected);
 }
 
+void PrintLog() {
+  std::string contents;
+  VLOG(0) << GetDataDirPath().AppendASCII("updater.log");
+  if (base::ReadFileToString(GetDataDirPath().AppendASCII("updater.log"),
+                             &contents)) {
+    VLOG(0) << "Contents of updater.log:";
+    VLOG(0) << contents;
+  } else {
+    VLOG(0) << "Failed to read updater.log file.";
+  }
+}
+
 }  // namespace
 
 #if defined(OS_MAC)
@@ -90,6 +105,28 @@
 }
 #endif  // OS_MAC
 
+bool Run(base::CommandLine command_line, int* exit_code) {
+  command_line.AppendSwitch("enable-logging");
+  command_line.AppendSwitchASCII("vmodule", "*/updater/*=2");
+  base::Process process = base::LaunchProcess(command_line, {});
+  if (!process.IsValid())
+    return false;
+  return process.WaitForExitWithTimeout(base::TimeDelta::FromSeconds(60),
+                                        exit_code);
+}
+
+void SleepFor(int seconds) {
+  VLOG(2) << "Sleeping " << seconds << " seconds...";
+  base::WaitableEvent sleep(base::WaitableEvent::ResetPolicy::MANUAL,
+                            base::WaitableEvent::InitialState::NOT_SIGNALED);
+  base::ThreadPool::PostDelayedTask(
+      FROM_HERE, {base::MayBlock()},
+      base::BindOnce(&base::WaitableEvent::Signal, base::Unretained(&sleep)),
+      base::TimeDelta::FromSeconds(seconds));
+  sleep.Wait();
+  VLOG(2) << "Sleep complete.";
+}
+
 class IntegrationTest : public ::testing::Test {
  protected:
   void SetUp() override {
@@ -99,7 +136,11 @@
   }
 
   void TearDown() override {
+    if (::testing::Test::HasFailure())
+      PrintLog();
     ExpectClean();
+    if (::testing::Test::HasFailure())
+      PrintLog();
     Clean();
   }
 
@@ -123,6 +164,12 @@
   EXPECT_NE(CreateGlobalPrefs()->GetActiveVersion(), UPDATER_VERSION_STRING);
 
   RunWake(0);
+
+  // The mac server will remain active for 10 seconds after it replies to the
+  // wake client, then shut down and uninstall itself. Sleep to wait for this
+  // to happen.
+  SleepFor(11);
+
   ExpectCandidateUninstalled();
   Uninstall();
 }
diff --git a/chrome/updater/test/integration_tests.h b/chrome/updater/test/integration_tests.h
index 6ee1103..93a430b 100644
--- a/chrome/updater/test/integration_tests.h
+++ b/chrome/updater/test/integration_tests.h
@@ -28,6 +28,14 @@
 // Places the updater into test mode (use local servers and disable CUP).
 void EnterTestMode();
 
+// Sleeps for the given number of seconds. This should be avoided, but in some
+// cases surrounding uninstall it is necessary since the processes can exit
+// prior to completing the actual uninstallation.
+void SleepFor(int seconds);
+
+// Returns the path to the updater data dir.
+base::FilePath GetDataDirPath();
+
 // Expects that the updater is installed on the system.
 void ExpectInstalled();
 
diff --git a/chrome/updater/test/integration_tests_mac.mm b/chrome/updater/test/integration_tests_mac.mm
index 4962669..a443ef1c 100644
--- a/chrome/updater/test/integration_tests_mac.mm
+++ b/chrome/updater/test/integration_tests_mac.mm
@@ -9,14 +9,13 @@
 #include "base/files/file_util.h"
 #include "base/mac/foundation_util.h"
 #include "base/path_service.h"
-#include "base/process/launch.h"
-#include "base/process/process.h"
 #include "base/version.h"
 #include "chrome/common/mac/launchd.h"
 #include "chrome/updater/constants.h"
 #import "chrome/updater/mac/util.h"
 #include "chrome/updater/mac/xpc_service_names.h"
 #include "chrome/updater/prefs.h"
+#include "chrome/updater/test/integration_tests.h"
 #include "chrome/updater/test/test_app/constants.h"
 #include "chrome/updater/test/test_app/test_app_version.h"
 #include "chrome/updater/updater_version.h"
@@ -27,6 +26,9 @@
 
 namespace test {
 
+// crbug.com/1112527: These tests are not compatible with component build.
+#if !defined(COMPONENT_BUILD)
+
 namespace {
 
 base::FilePath GetExecutablePath() {
@@ -57,6 +59,8 @@
       .AppendASCII(PRODUCT_FULLNAME_STRING);
 }
 
+}  // namespace
+
 base::FilePath GetDataDirPath() {
   return base::mac::GetUserLibraryPath()
       .AppendASCII("Application Support")
@@ -64,18 +68,6 @@
       .AppendASCII(PRODUCT_FULLNAME_STRING);
 }
 
-}  // namespace
-
-bool Run(base::CommandLine command_line, int* exit_code) {
-  auto process = base::LaunchProcess(command_line, {});
-  if (!process.IsValid())
-    return false;
-  if (!process.WaitForExitWithTimeout(base::TimeDelta::FromSeconds(60),
-                                      exit_code))
-    return false;
-  return true;
-}
-
 void Clean() {
   EXPECT_TRUE(base::DeletePathRecursively(GetProductPath()));
   EXPECT_TRUE(Launchd::GetInstance()->DeletePlist(
@@ -183,6 +175,8 @@
   return GetExecutableFolderPathForVersion(version);
 }
 
+#endif  // !defined(COMPONENT_BUILD)
+
 }  // namespace test
 
 }  // namespace updater
diff --git a/chrome/updater/test/integration_tests_win.cc b/chrome/updater/test/integration_tests_win.cc
index cab64f3..d4d5d6c 100644
--- a/chrome/updater/test/integration_tests_win.cc
+++ b/chrome/updater/test/integration_tests_win.cc
@@ -6,14 +6,13 @@
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/path_service.h"
-#include "base/process/launch.h"
-#include "base/process/process.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
 #include "base/win/registry.h"
 #include "chrome/updater/constants.h"
+#include "chrome/updater/test/integration_tests.h"
 #include "chrome/updater/updater_version.h"
 #include "chrome/updater/util.h"
 #include "chrome/updater/win/constants.h"
@@ -30,25 +29,6 @@
   return test_executable.DirName().AppendASCII("UpdaterSetup.exe");
 }
 
-bool Run(base::CommandLine command_line, int* exit_code) {
-  auto process = base::LaunchProcess(command_line, {});
-  if (!process.IsValid())
-    return false;
-  if (!process.WaitForExitWithTimeout(base::TimeDelta::FromSeconds(60),
-                                      exit_code))
-    return false;
-  base::WaitableEvent sleep(base::WaitableEvent::ResetPolicy::MANUAL,
-                            base::WaitableEvent::InitialState::NOT_SIGNALED);
-  // The process will exit before it is done uninstalling: sleep for five
-  // seconds to allow uninstall to complete.
-  base::ThreadPool::PostDelayedTask(
-      FROM_HERE, {base::MayBlock()},
-      base::BindOnce(&base::WaitableEvent::Signal, base::Unretained(&sleep)),
-      base::TimeDelta::FromSeconds(5));
-  sleep.Wait();
-  return true;
-}
-
 base::FilePath GetProductPath() {
   base::FilePath app_data_dir;
   if (!base::PathService::Get(base::DIR_LOCAL_APP_DATA, &app_data_dir))
@@ -62,6 +42,8 @@
   return GetProductPath().AppendASCII("updater.exe");
 }
 
+}  // namespace
+
 base::FilePath GetDataDirPath() {
   base::FilePath app_data_dir;
   if (!base::PathService::Get(base::DIR_LOCAL_APP_DATA, &app_data_dir))
@@ -70,8 +52,6 @@
       .AppendASCII(PRODUCT_FULLNAME_STRING);
 }
 
-}  // namespace
-
 void Clean() {
   // TODO(crbug.com/1062288): Delete the Client / ClientState registry keys.
   base::win::RegKey(HKEY_CURRENT_USER, L"", KEY_SET_VALUE)
@@ -158,6 +138,10 @@
   int exit_code = -1;
   ASSERT_TRUE(Run(command_line, &exit_code));
   EXPECT_EQ(0, exit_code);
+
+  // Uninstallation involves a race with the uninstall.cmd script and the
+  // process exit. Sleep to allow the script to complete its work.
+  SleepFor(5);
 }
 
 }  // namespace test
diff --git a/chrome/updater/update_service.h b/chrome/updater/update_service.h
index 60634ac..1c85844 100644
--- a/chrome/updater/update_service.h
+++ b/chrome/updater/update_service.h
@@ -56,7 +56,10 @@
     // A function argument was invalid.
     kInvalidArgument = 7,
 
-    // Change the traits class in this file when adding new values.
+    // This server is not the active server.
+    kInactive = 8,
+
+    // Change the EnumTraits class in this file when adding new values.
   };
 
   // Run time errors are organized in specific categories to indicate the
@@ -215,7 +218,7 @@
 struct EnumTraits<UpdateService::Result> {
   using Result = UpdateService::Result;
   static constexpr Result first_elem = Result::kSuccess;
-  static constexpr Result last_elem = Result::kInvalidArgument;
+  static constexpr Result last_elem = Result::kInactive;
 };
 
 template <>
diff --git a/chrome/updater/update_service_impl_inactive.cc b/chrome/updater/update_service_impl_inactive.cc
new file mode 100644
index 0000000..ba77767
--- /dev/null
+++ b/chrome/updater/update_service_impl_inactive.cc
@@ -0,0 +1,66 @@
+// 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/updater/update_service_impl_inactive.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/version.h"
+#include "chrome/updater/registration_data.h"
+#include "chrome/updater/update_service.h"
+
+namespace updater {
+
+namespace {
+
+class UpdateServiceImplInactive : public UpdateService {
+ public:
+  UpdateServiceImplInactive() = default;
+
+  // Overrides for updater::UpdateService.
+  void GetVersion(
+      base::OnceCallback<void(const base::Version&)> callback) const override {
+    base::SequencedTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), base::Version()));
+  }
+
+  void RegisterApp(
+      const RegistrationRequest& request,
+      base::OnceCallback<void(const RegistrationResponse&)> callback) override {
+    base::SequencedTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE,
+        base::BindOnce(std::move(callback), RegistrationResponse(-1)));
+  }
+
+  void UpdateAll(StateChangeCallback state_update, Callback callback) override {
+    base::SequencedTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE,
+        base::BindOnce(std::move(callback), UpdateService::Result::kInactive));
+  }
+
+  void Update(const std::string& app_id,
+              Priority priority,
+              StateChangeCallback state_update,
+              Callback callback) override {
+    base::SequencedTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE,
+        base::BindOnce(std::move(callback), UpdateService::Result::kInactive));
+  }
+
+  void Uninitialize() override {}
+
+ private:
+  ~UpdateServiceImplInactive() override = default;
+};
+
+}  // namespace
+
+scoped_refptr<UpdateService> MakeInactiveUpdateService() {
+  return base::MakeRefCounted<UpdateServiceImplInactive>();
+}
+
+}  // namespace updater
diff --git a/chrome/updater/update_service_impl_inactive.h b/chrome/updater/update_service_impl_inactive.h
new file mode 100644
index 0000000..d7e5ef4
--- /dev/null
+++ b/chrome/updater/update_service_impl_inactive.h
@@ -0,0 +1,18 @@
+// 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_UPDATER_UPDATE_SERVICE_IMPL_INACTIVE_H_
+#define CHROME_UPDATER_UPDATE_SERVICE_IMPL_INACTIVE_H_
+
+#include "base/memory/scoped_refptr.h"
+
+namespace updater {
+
+class UpdateService;
+
+scoped_refptr<UpdateService> MakeInactiveUpdateService();
+
+}  // namespace updater
+
+#endif  // CHROME_UPDATER_UPDATE_SERVICE_IMPL_INACTIVE_H_
diff --git a/chromecast/media/audio/capture_service/constants.h b/chromecast/media/audio/capture_service/constants.h
index e87050bb0..96b8728 100644
--- a/chromecast/media/audio/capture_service/constants.h
+++ b/chromecast/media/audio/capture_service/constants.h
@@ -70,10 +70,10 @@
 };
 
 struct StreamInfo {
-  StreamType stream_type;
-  AudioCodec audio_codec;
+  StreamType stream_type = StreamType::kMicRaw;
+  AudioCodec audio_codec = AudioCodec::kPcm;
   int num_channels = 0;
-  SampleFormat sample_format;
+  SampleFormat sample_format = SampleFormat::INTERLEAVED_INT16;
   int sample_rate = 0;
   int frames_per_buffer = 0;
 };
diff --git a/chromeos/BUILD.gn b/chromeos/BUILD.gn
index 59cad0d..3c40b2f2 100644
--- a/chromeos/BUILD.gn
+++ b/chromeos/BUILD.gn
@@ -326,6 +326,9 @@
       # crbug.com/1131304
       "printer.PinPrintLexmark.no_pin",
       "printer.PinPrintLexmark.pin",
+
+      # crbug.com/1141944
+      "crostini.LaunchBrowser.artifact",
     ]
   }
 
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index 5a0c1b2a..cbb23e7 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-13540.0.0
\ No newline at end of file
+13550.0.0
\ No newline at end of file
diff --git a/chromeos/chromeos_strings.grd b/chromeos/chromeos_strings.grd
index 83893c1..4048e79a 100644
--- a/chromeos/chromeos_strings.grd
+++ b/chromeos/chromeos_strings.grd
@@ -489,6 +489,9 @@
       <message name="IDS_SCANNING_APP_COLOR_MODE_DROPDOWN_LABEL" desc="The label for the dropdown that displays available color modes (e.g. color, grayscale, black and white, etc.).">
         Color mode
       </message>
+      <message name="IDS_SCANNING_APP_PAGE_SIZE_DROPDOWN_LABEL" desc="The label for the dropdown that displays available page sizes.">
+        Page size
+      </message>
       <message name="IDS_SCANNING_APP_RESOLUTION_DROPDOWN_LABEL" desc="The label for the dropdown that displays available resolutions (e.g. 150 dpi, 300 dpi, etc.).">
         Resolution
       </message>
diff --git a/chromeos/chromeos_strings_grd/IDS_SCANNING_APP_PAGE_SIZE_DROPDOWN_LABEL.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SCANNING_APP_PAGE_SIZE_DROPDOWN_LABEL.png.sha1
new file mode 100644
index 0000000..12479b7
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SCANNING_APP_PAGE_SIZE_DROPDOWN_LABEL.png.sha1
@@ -0,0 +1 @@
+9916a93033bb44a6e5056b84a827c7c971a8f198
\ No newline at end of file
diff --git a/chromeos/components/diagnostics_ui/resources/cpu_card.html b/chromeos/components/diagnostics_ui/resources/cpu_card.html
index 43b8231b4..6240bd6 100644
--- a/chromeos/components/diagnostics_ui/resources/cpu_card.html
+++ b/chromeos/components/diagnostics_ui/resources/cpu_card.html
@@ -17,4 +17,6 @@
   <data-point slot="body" id="cpuTemp" header="[[i18n('cpuTemp')]]"
     value="[[cpuUsage_.cpu_temp_degrees_celcius]]">
   </data-point>
+
+  <routine-section slot="routines" routines="[[routines_]]"></routine-section>
 </diagnostics-card>
diff --git a/chromeos/components/diagnostics_ui/resources/cpu_card.js b/chromeos/components/diagnostics_ui/resources/cpu_card.js
index 5b1ffe6..35869ba 100644
--- a/chromeos/components/diagnostics_ui/resources/cpu_card.js
+++ b/chromeos/components/diagnostics_ui/resources/cpu_card.js
@@ -6,15 +6,14 @@
 import './diagnostics_card.js';
 import './diagnostics_shared_css.js';
 import './realtime_cpu_chart.js';
+import './routine_section.js'
 import './strings.m.js';
 
 import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
 import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-
-import {CpuUsage, SystemDataProviderInterface} from './diagnostics_types.js'
+import {CpuUsage, RoutineName, SystemDataProviderInterface} from './diagnostics_types.js'
 import {getSystemDataProvider} from './mojo_interface_provider.js';
 
-
 /**
  * @fileoverview
  * 'cpu-card' shows information about the CPU.
@@ -32,6 +31,18 @@
   systemDataProvider_: null,
 
   properties: {
+    routines_: {
+      type: Array,
+      value: () => {
+        return [
+          RoutineName.kCpuStress,
+          RoutineName.kCpuCache,
+          RoutineName.kFloatingPoint,
+          RoutineName.kPrimeSearch,
+        ];
+      }
+    },
+
     /** @private {!CpuUsage} */
     cpuUsage_: {
       type: Object,
diff --git a/chromeos/components/phonehub/fake_tether_controller.h b/chromeos/components/phonehub/fake_tether_controller.h
index 2b085a2..d12ed05 100644
--- a/chromeos/components/phonehub/fake_tether_controller.h
+++ b/chromeos/components/phonehub/fake_tether_controller.h
@@ -15,6 +15,8 @@
   FakeTetherController();
   ~FakeTetherController() override;
 
+  using TetherController::NotifyAttemptConnectionScanFailed;
+
   void SetStatus(Status status);
 
   // TetherController:
diff --git a/chromeos/components/phonehub/tether_controller.cc b/chromeos/components/phonehub/tether_controller.cc
index 5c998d4..311d4ecc 100644
--- a/chromeos/components/phonehub/tether_controller.cc
+++ b/chromeos/components/phonehub/tether_controller.cc
@@ -24,6 +24,11 @@
     observer.OnTetherStatusChanged();
 }
 
+void TetherController::NotifyAttemptConnectionScanFailed() {
+  for (auto& observer : observer_list_)
+    observer.OnAttemptConnectionScanFailed();
+}
+
 std::ostream& operator<<(std::ostream& stream,
                          TetherController::Status status) {
   switch (status) {
diff --git a/chromeos/components/phonehub/tether_controller.h b/chromeos/components/phonehub/tether_controller.h
index 2e5a951..bac524a 100644
--- a/chromeos/components/phonehub/tether_controller.h
+++ b/chromeos/components/phonehub/tether_controller.h
@@ -49,6 +49,10 @@
 
     // Called the status has changed; use GetStatus() to get the new status.
     virtual void OnTetherStatusChanged() = 0;
+
+    // Called when AttemptConnection() is called, a scan for a tether network is
+    // requested, but no tether network was found.
+    virtual void OnAttemptConnectionScanFailed() {}
   };
 
   TetherController(const TetherController&) = delete;
@@ -79,6 +83,7 @@
   TetherController();
 
   void NotifyStatusChanged();
+  void NotifyAttemptConnectionScanFailed();
 
  private:
   base::ObserverList<Observer> observer_list_;
diff --git a/chromeos/components/phonehub/tether_controller_impl.cc b/chromeos/components/phonehub/tether_controller_impl.cc
index 13920fb..4f7ca950 100644
--- a/chromeos/components/phonehub/tether_controller_impl.cc
+++ b/chromeos/components/phonehub/tether_controller_impl.cc
@@ -273,8 +273,10 @@
     break;
   }
 
-  if (!is_tether_device_scanning)
+  if (!is_tether_device_scanning) {
+    NotifyAttemptConnectionScanFailed();
     SetConnectDisconnectStatus(ConnectDisconnectStatus::kIdle);
+  }
 }
 
 void TetherControllerImpl::FetchVisibleTetherNetwork() {
diff --git a/chromeos/components/phonehub/tether_controller_impl_unittest.cc b/chromeos/components/phonehub/tether_controller_impl_unittest.cc
index 9685038..c5ed0ef 100644
--- a/chromeos/components/phonehub/tether_controller_impl_unittest.cc
+++ b/chromeos/components/phonehub/tether_controller_impl_unittest.cc
@@ -37,13 +37,16 @@
   FakeObserver() = default;
   ~FakeObserver() override = default;
 
-  size_t num_calls() const { return num_calls_; }
+  size_t num_status_changes() const { return num_status_changes_; }
+  size_t num_scan_failed() const { return num_scan_failed_; }
 
   // TetherController::Observer:
-  void OnTetherStatusChanged() override { ++num_calls_; }
+  void OnTetherStatusChanged() override { ++num_status_changes_; }
+  void OnAttemptConnectionScanFailed() override { ++num_scan_failed_; }
 
  private:
-  size_t num_calls_ = 0;
+  size_t num_status_changes_ = 0;
+  size_t num_scan_failed_ = 0;
 };
 
 }  // namespace
@@ -202,7 +205,13 @@
 
   void Disconnect() { controller_->Disconnect(); }
 
-  size_t GetNumObserverCalls() const { return fake_observer_.num_calls(); }
+  size_t GetNumObserverStatusChanged() const {
+    return fake_observer_.num_status_changes();
+  }
+
+  size_t GetNumObserverScanFailed() const {
+    return fake_observer_.num_scan_failed();
+  }
 
  private:
   base::test::TaskEnvironment task_environment_;
@@ -216,7 +225,7 @@
 TEST_F(TetherControllerImplTest, ExternalTetherChangesReflectToStatus) {
   EXPECT_EQ(GetStatus(), TetherController::Status::kIneligibleForFeature);
   SetMultideviceSetupFeatureState(FeatureState::kEnabledByUser);
-  EXPECT_EQ(GetNumObserverCalls(), 1U);
+  EXPECT_EQ(GetNumObserverStatusChanged(), 1U);
   EXPECT_EQ(GetStatus(), TetherController::Status::kConnectionUnavailable);
 
   // Tether device and network must be enabled for status changes other than
@@ -224,27 +233,27 @@
   EnableTetherDevice();
   AddVisibleTetherNetwork();
   EXPECT_EQ(GetStatus(), TetherController::Status::kConnectionAvailable);
-  EXPECT_EQ(GetNumObserverCalls(), 2U);
+  EXPECT_EQ(GetNumObserverStatusChanged(), 2U);
 
   // Starts connecting to tether network.
   SetTetherNetworkStateConnecting();
   EXPECT_EQ(GetStatus(), TetherController::Status::kConnecting);
-  EXPECT_EQ(GetNumObserverCalls(), 3U);
+  EXPECT_EQ(GetNumObserverStatusChanged(), 3U);
 
   // Connected to tether network.
   SetTetherNetworkStateConnected();
   EXPECT_EQ(GetStatus(), TetherController::Status::kConnected);
-  EXPECT_EQ(GetNumObserverCalls(), 4U);
+  EXPECT_EQ(GetNumObserverStatusChanged(), 4U);
 
   // Tether network disconnects on it's own.
   SetTetherNetworkStateDisconnected();
   EXPECT_EQ(GetStatus(), TetherController::Status::kConnectionAvailable);
-  EXPECT_EQ(GetNumObserverCalls(), 5U);
+  EXPECT_EQ(GetNumObserverStatusChanged(), 5U);
 
   // Tether network becomes unavailable.
   RemoveVisibleTetherNetwork();
   EXPECT_EQ(GetStatus(), TetherController::Status::kConnectionUnavailable);
-  EXPECT_EQ(GetNumObserverCalls(), 6U);
+  EXPECT_EQ(GetNumObserverStatusChanged(), 6U);
 }
 
 TEST_F(TetherControllerImplTest, AttemptConnectDisconnect) {
@@ -322,7 +331,7 @@
   AddVisibleTetherNetwork();
   SetTetherNetworkStateConnected();
   EXPECT_EQ(GetStatus(), TetherController::Status::kConnected);
-  EXPECT_EQ(GetNumObserverCalls(), 3U);
+  EXPECT_EQ(GetNumObserverStatusChanged(), 3U);
 
   // Tether network is lost.
   RemoveVisibleTetherNetwork();
@@ -400,11 +409,13 @@
   // unavailable.
   SetTetherScanState(false);
   EnableTetherDevice();
+  EXPECT_EQ(GetNumObserverScanFailed(), 1U);
   EXPECT_EQ(GetStatus(), TetherController::Status::kConnectionUnavailable);
 
   // Tether starts scanning after connection attempt ended.
   SetTetherScanState(true);
   EnableTetherDevice();
+  EXPECT_EQ(GetNumObserverScanFailed(), 1U);
   EXPECT_EQ(GetStatus(), TetherController::Status::kConnectionUnavailable);
 }
 
diff --git a/chromeos/components/scanning/resources/BUILD.gn b/chromeos/components/scanning/resources/BUILD.gn
index 997b044..66d1c277 100644
--- a/chromeos/components/scanning/resources/BUILD.gn
+++ b/chromeos/components/scanning/resources/BUILD.gn
@@ -13,6 +13,7 @@
     ":color_mode_select",
     ":file_type_select",
     ":mojo_interface_provider",
+    ":page_size_select",
     ":resolution_select",
     ":scanner_select",
     ":scanning_app",
@@ -42,6 +43,16 @@
   ]
 }
 
+js_library("page_size_select") {
+  deps = [
+    ":scanning_app_util",
+    ":select_behavior",
+    "//chromeos/components/scanning/mojom:mojom_js_library_for_compile",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:i18n_behavior.m",
+  ]
+}
+
 js_library("resolution_select") {
   deps = [
     ":select_behavior",
@@ -68,6 +79,7 @@
     ":color_mode_select",
     ":file_type_select",
     ":mojo_interface_provider",
+    ":page_size_select",
     ":resolution_select",
     ":scanner_select",
     ":scanning_app_types",
@@ -108,6 +120,7 @@
   js_files = [
     "color_mode_select.js",
     "file_type_select.js",
+    "page_size_select.js",
     "resolution_select.js",
     "scan_settings_section.js",
     "scanner_select.js",
diff --git a/chromeos/components/scanning/resources/page_size_select.html b/chromeos/components/scanning/resources/page_size_select.html
new file mode 100644
index 0000000..4c6dc872
--- /dev/null
+++ b/chromeos/components/scanning/resources/page_size_select.html
@@ -0,0 +1,16 @@
+<scan-settings-section>
+  <span id="pageSizeLabel" slot="label">[[i18n('pageSizeDropdownLabel')]]</span>
+  <div slot="settings">
+    <!-- TODO(jschettler): Verify this meets a11y expecations (e.g. ChromeVox
+        should announce when a new option is focused). -->
+    <select class="md-select" value="{{selectedPageSize::change}}"
+        disabled="[[disabled_]]">
+      <!-- TODO(jschettler): Sort the page sizes. -->
+      <template is="dom-repeat" items="[[pageSizes]]" as="pageSize">
+        <option value="[[pageSize]]">
+          [[getPageSizeString_(pageSize)]]
+        </option>
+      </template>
+    </select>
+  </div>
+</scan-settings-section>
diff --git a/chromeos/components/scanning/resources/page_size_select.js b/chromeos/components/scanning/resources/page_size_select.js
new file mode 100644
index 0000000..7b0bf32c
--- /dev/null
+++ b/chromeos/components/scanning/resources/page_size_select.js
@@ -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.
+
+import './scanning.mojom-lite.js';
+import './scan_settings_section.js';
+import './strings.m.js';
+
+import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
+import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {getPageSizeString} from './scanning_app_util.js';
+import {SelectBehavior} from './select_behavior.js';
+
+/**
+ * @fileoverview
+ * 'page-size-select' displays the available page sizes in a dropdown.
+ */
+Polymer({
+  is: 'page-size-select',
+
+  _template: html`{__html_template__}`,
+
+  behaviors: [I18nBehavior, SelectBehavior],
+
+  properties: {
+    /** @type {!Array<chromeos.scanning.mojom.PageSize>} */
+    pageSizes: {
+      type: Array,
+      value: () => [],
+    },
+
+    /** @type {?string} */
+    selectedPageSize: {
+      type: String,
+      notify: true,
+    },
+  },
+
+  observers: ['onNumOptionsChange(pageSizes.length)'],
+
+  /**
+   * @param {chromeos.scanning.mojom.PageSize} pageSize
+   * @return {!string}
+   * @private
+   */
+  getPageSizeString_(pageSize) {
+    return getPageSizeString(pageSize);
+  },
+});
diff --git a/chromeos/components/scanning/resources/scanning_app.html b/chromeos/components/scanning/resources/scanning_app.html
index 4e7ce290..b5c5664 100644
--- a/chromeos/components/scanning/resources/scanning_app.html
+++ b/chromeos/components/scanning/resources/scanning_app.html
@@ -11,6 +11,9 @@
     color-modes="[[capabilities_.colorModes]]"
     settings-disabled="[[settingsDisabled_]]"
     selected-color-mode="{{selectedColorMode}}"></color-mode-select>
+<page-size-select id="pageSizeSelect" page-sizes="[[selectedSourcePageSizes_]]"
+    settings-disabled="[[settingsDisabled_]]"
+    selected-page-size="{{selectedPageSize}}"></page-size-select>
 <resolution-select id="resolutionSelect"
     resolutions="[[capabilities_.resolutions]]"
     settings-disabled="[[settingsDisabled_]]"
diff --git a/chromeos/components/scanning/resources/scanning_app.js b/chromeos/components/scanning/resources/scanning_app.js
index 7a210b8..3dbaae2 100644
--- a/chromeos/components/scanning/resources/scanning_app.js
+++ b/chromeos/components/scanning/resources/scanning_app.js
@@ -8,6 +8,7 @@
 import 'chrome://resources/mojo/mojo/public/mojom/base/unguessable_token.mojom-lite.js';
 import './color_mode_select.js';
 import './file_type_select.js';
+import './page_size_select.js';
 import './resolution_select.js';
 import './scanner_select.js';
 import './source_select.js';
@@ -17,7 +18,7 @@
 
 import {getScanService} from './mojo_interface_provider.js';
 import {ScannerArr} from './scanning_app_types.js';
-import {colorModeFromString, tokenToString} from './scanning_app_util.js';
+import {colorModeFromString, pageSizeFromString, tokenToString} from './scanning_app_util.js';
 
 /**
  * @fileoverview
@@ -65,9 +66,22 @@
     selectedColorMode: String,
 
     /** @type {?string} */
+    selectedPageSize: String,
+
+    /** @type {?string} */
     selectedResolution: String,
 
     /**
+     * @type {!Array<chromeos.scanning.mojom.PageSize>}
+     * @private
+     */
+    selectedSourcePageSizes_: {
+      type: Array,
+      value: () => [],
+      computed: 'computePageSizes_(selectedSource)',
+    },
+
+    /**
      * @type {?string}
      * @private
      */
@@ -108,6 +122,21 @@
   },
 
   /**
+   * @param {?string} selectedSource
+   * @return {!Array<chromeos.scanning.mojom.PageSize>}
+   * @private
+   */
+  computePageSizes_(selectedSource) {
+    for (const source of this.capabilities_.sources) {
+      if (source.name === selectedSource) {
+        return source.pageSizes;
+      }
+    }
+
+    return [];
+  },
+
+  /**
    * @param {!{capabilities: !chromeos.scanning.mojom.ScannerCapabilities}}
    *     response
    * @private
@@ -119,6 +148,8 @@
     // first options in the dropdowns.
     this.selectedSource = this.capabilities_.sources[0].name;
     this.selectedColorMode = this.capabilities_.colorModes[0].toString();
+    this.selectedPageSize =
+        this.capabilities_.sources[0].pageSizes[0].toString();
     this.selectedResolution = this.capabilities_.resolutions[0].toString();
 
     // PDF is the default file type.
@@ -173,7 +204,7 @@
   onScanClick_() {
     if (!this.selectedScannerId || !this.selectedSource ||
         !this.selectedFileType || !this.selectedColorMode ||
-        !this.selectedResolution) {
+        !this.selectedPageSize || !this.selectedResolution) {
       // TODO(jschettler): Replace status text with finalized i18n strings.
       this.statusText_ = 'Failed to start scan.';
       return;
@@ -196,7 +227,7 @@
       'sourceName': this.selectedSource,
       'fileType': chromeos.scanning.mojom.FileType.kPng,
       'colorMode': colorModeFromString(this.selectedColorMode),
-      'pageSize': chromeos.scanning.mojom.PageSize.kNaLetter,
+      'pageSize': pageSizeFromString(this.selectedPageSize),
       'resolutionDpi': Number(this.selectedResolution),
     };
     this.scanService_
diff --git a/chromeos/components/scanning/resources/scanning_app_resources.grd b/chromeos/components/scanning/resources/scanning_app_resources.grd
index 7b3c360..1e7b862 100644
--- a/chromeos/components/scanning/resources/scanning_app_resources.grd
+++ b/chromeos/components/scanning/resources/scanning_app_resources.grd
@@ -24,6 +24,8 @@
       <include name="IDR_SCANNING_APP_FILE_TYPE_SELECT_JS" file="${root_gen_dir}/chromeos/components/scanning/resources/file_type_select.js" use_base_dir="false" type="BINDATA"/>
       <include name="IDR_SCANNING_APP_COLOR_MODE_SELECT_HTML" file="color_mode_select.html" type="BINDATA"/>
       <include name="IDR_SCANNING_APP_COLOR_MODE_SELECT_JS" file="${root_gen_dir}/chromeos/components/scanning/resources/color_mode_select.js" use_base_dir="false" type="BINDATA"/>
+      <include name="IDR_SCANNING_APP_PAGE_SIZE_SELECT_HTML" file="page_size_select.html" type="BINDATA"/>
+      <include name="IDR_SCANNING_APP_PAGE_SIZE_SELECT_JS" file="${root_gen_dir}/chromeos/components/scanning/resources/page_size_select.js" use_base_dir="false" type="BINDATA"/>
       <include name="IDR_SCANNING_APP_RESOLUTION_SELECT_HTML" file="resolution_select.html" type="BINDATA"/>
       <include name="IDR_SCANNING_APP_RESOLUTION_SELECT_JS" file="${root_gen_dir}/chromeos/components/scanning/resources/resolution_select.js" use_base_dir="false" type="BINDATA"/>
       <include name="IDR_SCANNING_APP_SCAN_SETTINGS_SECTION_JS" file="${root_gen_dir}/chromeos/components/scanning/resources/scan_settings_section.js" use_base_dir="false" type="BINDATA"/>
diff --git a/chromeos/components/scanning/resources/scanning_app_util.js b/chromeos/components/scanning/resources/scanning_app_util.js
index 6db54066..e844a3b 100644
--- a/chromeos/components/scanning/resources/scanning_app_util.js
+++ b/chromeos/components/scanning/resources/scanning_app_util.js
@@ -46,6 +46,27 @@
 }
 
 /**
+ * Converts a chromeos.scanning.mojom.PageSize to a string that can be
+ * displayed in the page size dropdown.
+ * @param {chromeos.scanning.mojom.PageSize} pageSize
+ * @return {!string}
+ */
+export function getPageSizeString(pageSize) {
+  // TODO(jschettler): Replace with finalized i18n strings.
+  switch (pageSize) {
+    case chromeos.scanning.mojom.PageSize.kIsoA4:
+      return 'A4';
+    case chromeos.scanning.mojom.PageSize.kNaLetter:
+      return 'Letter';
+    case chromeos.scanning.mojom.PageSize.kMax:
+      return 'Fit to scan area';
+    default:
+      assertNotReached();
+      return 'Unknown';
+  }
+}
+
+/**
  * Converts a chromeos.scanning.mojom.SourceType to a string that can be
  * displayed in the source dropdown.
  * @param {number} mojoSourceType
@@ -70,6 +91,26 @@
 }
 
 /**
+ * Converts a chromeos.scanning.mojom.PageSize string to the corresponding enum
+ * value.
+ * @param {!string} pageSizeString
+ * @return {chromeos.scanning.mojom.PageSize}
+ */
+export function pageSizeFromString(pageSizeString) {
+  switch (pageSizeString) {
+    case chromeos.scanning.mojom.PageSize.kIsoA4.toString():
+      return chromeos.scanning.mojom.PageSize.kIsoA4;
+    case chromeos.scanning.mojom.PageSize.kNaLetter.toString():
+      return chromeos.scanning.mojom.PageSize.kNaLetter;
+    case chromeos.scanning.mojom.PageSize.kMax.toString():
+      return chromeos.scanning.mojom.PageSize.kMax;
+    default:
+      assertNotReached();
+      return chromeos.scanning.mojom.PageSize.kNaLetter;
+  }
+}
+
+/**
  * Converts an unguessable token to a string by combining the high and low
  * values as strings with a hashtag as the separator.
  * @param {!mojoBase.mojom.UnguessableToken} token
diff --git a/chromeos/components/scanning/scanning_ui.cc b/chromeos/components/scanning/scanning_ui.cc
index 449c5dd..9dbf1f17 100644
--- a/chromeos/components/scanning/scanning_ui.cc
+++ b/chromeos/components/scanning/scanning_ui.cc
@@ -55,6 +55,7 @@
       {"noScannersText", IDS_SCANNING_APP_NO_SCANNERS_TEXT},
       {"pdfOptionText", IDS_SCANNING_APP_PDF_OPTION_TEXT},
       {"pngOptionText", IDS_SCANNING_APP_PNG_OPTION_TEXT},
+      {"pageSizeDropdownLabel", IDS_SCANNING_APP_PAGE_SIZE_DROPDOWN_LABEL},
       {"resolutionDropdownLabel", IDS_SCANNING_APP_RESOLUTION_DROPDOWN_LABEL},
       {"resolutionOptionText", IDS_SCANNING_APP_RESOLUTION_OPTION_TEXT},
       {"scannerDropdownLabel", IDS_SCANNING_APP_SCANNER_DROPDOWN_LABEL},
diff --git a/chromeos/constants/chromeos_features.cc b/chromeos/constants/chromeos_features.cc
index 067ad94..4baeef7 100644
--- a/chromeos/constants/chromeos_features.cc
+++ b/chromeos/constants/chromeos_features.cc
@@ -484,10 +484,6 @@
 const base::Feature kQuickAnswers{"QuickAnswers",
                                   base::FEATURE_DISABLED_BY_DEFAULT};
 
-// Controls whether to enable quick answers rich ui.
-const base::Feature kQuickAnswersRichUi{"QuickAnswersRichUi",
-                                        base::FEATURE_DISABLED_BY_DEFAULT};
-
 // Controls whether dogfood version of quick answers.
 const base::Feature kQuickAnswersDogfood{"QuickAnswersDogfood",
                                          base::FEATURE_DISABLED_BY_DEFAULT};
@@ -761,12 +757,8 @@
   return base::FeatureList::IsEnabled(kQuickAnswers);
 }
 
-bool IsQuickAnswersRichUiEnabled() {
-  return base::FeatureList::IsEnabled(kQuickAnswersRichUi);
-}
-
 bool IsQuickAnswersSettingToggleEnabled() {
-  return IsQuickAnswersEnabled() && IsQuickAnswersRichUiEnabled() &&
+  return IsQuickAnswersEnabled() &&
          base::FeatureList::IsEnabled(kQuickAnswersSubToggle);
 }
 
diff --git a/chromeos/profiles/atom.afdo.newest.txt b/chromeos/profiles/atom.afdo.newest.txt
index 066dc14..39309a54 100644
--- a/chromeos/profiles/atom.afdo.newest.txt
+++ b/chromeos/profiles/atom.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-atom-88-4280.20-1603104419-benchmark-88.0.4296.6-r1-redacted.afdo.xz
+chromeos-chrome-amd64-atom-88-4280.20-1603104419-benchmark-88.0.4300.0-r1-redacted.afdo.xz
diff --git a/chromeos/ui/base/BUILD.gn b/chromeos/ui/base/BUILD.gn
index 34052e5..1c3c5001 100644
--- a/chromeos/ui/base/BUILD.gn
+++ b/chromeos/ui/base/BUILD.gn
@@ -28,7 +28,7 @@
   deps = [
     "//base",
     "//skia",
-    "//ui/aura",
     "//ui/base",
+    "//ui/gfx/geometry",
   ]
 }
diff --git a/chromeos/ui/base/DEPS b/chromeos/ui/base/DEPS
index 7de734c..8423e250 100644
--- a/chromeos/ui/base/DEPS
+++ b/chromeos/ui/base/DEPS
@@ -1,5 +1,5 @@
 include_rules = [
-  "+ui/aura",
   "+ui/base",
   "+third_party/skia",
+  "+ui/gfx",
 ]
diff --git a/chromeos/ui/base/window_properties.cc b/chromeos/ui/base/window_properties.cc
index d235c2c6..fbf02057 100644
--- a/chromeos/ui/base/window_properties.cc
+++ b/chromeos/ui/base/window_properties.cc
@@ -6,7 +6,8 @@
 
 #include "chromeos/ui/base/window_pin_type.h"
 #include "chromeos/ui/base/window_state_type.h"
-#include "ui/aura/window.h"
+#include "ui/base/class_property.h"
+#include "ui/gfx/geometry/rect.h"
 
 DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(COMPONENT_EXPORT(CHROMEOS_UI_BASE),
                                        chromeos::WindowPinType)
diff --git a/chromeos/ui/base/window_properties.h b/chromeos/ui/base/window_properties.h
index 34b77a1e..b50c8b96 100644
--- a/chromeos/ui/base/window_properties.h
+++ b/chromeos/ui/base/window_properties.h
@@ -8,11 +8,6 @@
 #include "base/component_export.h"
 #include "ui/base/class_property.h"
 
-namespace aura {
-template <typename T>
-using WindowProperty = ui::ClassProperty<T>;
-}  // namespace aura
-
 namespace gfx {
 class Rect;
 }
@@ -29,30 +24,30 @@
 // Whether the shelf should be hidden when this window is put into fullscreen.
 // Exposed because some windows want to explicitly opt-out of this.
 COMPONENT_EXPORT(CHROMEOS_UI_BASE)
-extern const aura::WindowProperty<bool>* const kHideShelfWhenFullscreenKey;
+extern const ui::ClassProperty<bool>* const kHideShelfWhenFullscreenKey;
 
 // Whether entering fullscreen means that a window should automatically enter
 // immersive mode. This is false for some client windows, such as Chrome Apps.
 COMPONENT_EXPORT(CHROMEOS_UI_BASE)
-extern const aura::WindowProperty<bool>* const kImmersiveImpliedByFullscreen;
+extern const ui::ClassProperty<bool>* const kImmersiveImpliedByFullscreen;
 
 // Whether immersive is currently active (in ImmersiveFullscreenController
 // parlance, "enabled").
 COMPONENT_EXPORT(CHROMEOS_UI_BASE)
-extern const aura::WindowProperty<bool>* const kImmersiveIsActive;
+extern const ui::ClassProperty<bool>* const kImmersiveIsActive;
 
 // The bounds of the top container in screen coordinates.
 COMPONENT_EXPORT(CHROMEOS_UI_BASE)
-extern const aura::WindowProperty<gfx::Rect*>* const
+extern const ui::ClassProperty<gfx::Rect*>* const
     kImmersiveTopContainerBoundsInScreen;
 
 // If true, the window is currently showing in overview mode.
 COMPONENT_EXPORT(CHROMEOS_UI_BASE)
-extern const aura::WindowProperty<bool>* const kIsShowingInOverviewKey;
+extern const ui::ClassProperty<bool>* const kIsShowingInOverviewKey;
 
 // A property key to indicate ash's extended window state.
 COMPONENT_EXPORT(CHROMEOS_UI_BASE)
-extern const aura::WindowProperty<WindowStateType>* const kWindowStateTypeKey;
+extern const ui::ClassProperty<WindowStateType>* const kWindowStateTypeKey;
 
 // A property key to store ash::WindowPinType for a window.
 // When setting this property to PINNED or TRUSTED_PINNED, the window manager
@@ -60,7 +55,7 @@
 // window manager failed to do it, the property will be restored to NONE. When
 // setting this property to NONE, the window manager will restore the window.
 COMPONENT_EXPORT(CHROMEOS_UI_BASE)
-extern const aura::WindowProperty<WindowPinType>* const kWindowPinTypeKey;
+extern const ui::ClassProperty<WindowPinType>* const kWindowPinTypeKey;
 
 // Alphabetical sort.
 
diff --git a/codelabs/DIR_METADATA b/codelabs/DIR_METADATA
new file mode 100644
index 0000000..2217bb55
--- /dev/null
+++ b/codelabs/DIR_METADATA
@@ -0,0 +1,12 @@
+# Metadata information for this directory.
+#
+# For more information on DIR_METADATA files, see:
+#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#
+# For the schema of this file, see Metadata message:
+#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+
+monorail {
+  component: "Infra>Documentation"
+}
+team_email: "infra-dev@chromium.org"
\ No newline at end of file
diff --git a/components/autofill/core/browser/autofill_form_test_utils.cc b/components/autofill/core/browser/autofill_form_test_utils.cc
index 177004f..1d5696eb 100644
--- a/components/autofill/core/browser/autofill_form_test_utils.cc
+++ b/components/autofill/core/browser/autofill_form_test_utils.cc
@@ -48,6 +48,10 @@
       field.label = ASCIIToUTF16("E-mail address");
       field.name = ASCIIToUTF16("email");
       break;
+    case ServerFieldType::ADDRESS_HOME_LINE1:
+      field.label = ASCIIToUTF16("Address");
+      field.name = ASCIIToUTF16("home_line_one");
+      break;
     case ServerFieldType::ADDRESS_HOME_CITY:
       field.label = ASCIIToUTF16("City");
       field.name = ASCIIToUTF16("city");
@@ -84,11 +88,18 @@
   return field;
 }
 
-FormData GetFormData(const FormAttributes& form_attributes) {
+FormData GetFormData(const TestFormAttributes& test_form_attributes) {
   FormData form_data;
 
-  form_data.url = GURL(form_attributes.form_url);
-  for (const FieldDataDescription& field_description : form_attributes.fields) {
+  form_data.url = GURL(test_form_attributes.url);
+  form_data.action = GURL(test_form_attributes.action);
+  form_data.name = ASCIIToUTF16(test_form_attributes.name);
+  if (test_form_attributes.unique_renderer_id)
+    form_data.unique_renderer_id = *test_form_attributes.unique_renderer_id;
+  if (test_form_attributes.main_frame_origin)
+    form_data.main_frame_origin = *test_form_attributes.main_frame_origin;
+  for (const FieldDataDescription& field_description :
+       test_form_attributes.fields) {
     FormFieldData field = CreateFieldByRole(field_description.role);
     field.form_control_type = field_description.form_control_type;
     field.is_focusable = field_description.is_focusable;
@@ -98,11 +109,15 @@
       field.label = ASCIIToUTF16(field_description.label);
     if (ASCIIToUTF16(field_description.name) != ASCIIToUTF16(kNameText))
       field.name = ASCIIToUTF16(field_description.name);
+    if (field_description.value)
+      field.value = ASCIIToUTF16(*field_description.value);
+    if (field_description.is_autofilled)
+      field.is_autofilled = *field_description.is_autofilled;
     field.should_autocomplete = field_description.should_autocomplete;
     form_data.fields.push_back(field);
   }
-  form_data.is_formless_checkout = form_attributes.is_formless_checkout;
-  form_data.is_form_tag = form_attributes.is_form_tag;
+  form_data.is_formless_checkout = test_form_attributes.is_formless_checkout;
+  form_data.is_form_tag = test_form_attributes.is_form_tag;
 
   return form_data;
 }
diff --git a/components/autofill/core/browser/autofill_form_test_utils.h b/components/autofill/core/browser/autofill_form_test_utils.h
index 6ec1180c..0c4c9b9 100644
--- a/components/autofill/core/browser/autofill_form_test_utils.h
+++ b/components/autofill/core/browser/autofill_form_test_utils.h
@@ -26,7 +26,10 @@
 constexpr char kNameText[] = "name";
 
 // Default form url.
-constexpr char kFormUrl[] = "http://www.foo.com/";
+constexpr char kFormUrl[] = "http://example.com/form.html";
+
+// Default form action url.
+constexpr char kFormActionUrl[] = "http://example.com/submit.html";
 
 }  // namespace
 
@@ -39,17 +42,23 @@
   bool is_focusable = true;
   const char* label = kLabelText;
   const char* name = kNameText;
+  base::Optional<const char*> value = base::nullopt;
   const char* autocomplete_attribute = nullptr;
   const char* form_control_type = "text";
   bool should_autocomplete = true;
+  base::Optional<bool> is_autofilled = base::nullopt;
 };
 
 // Attributes provided to the test form.
 template <typename = void>
-struct FormAttributes {
-  const char* description_for_logging = "";
-  std::vector<FieldDataDescription<>> fields = {};
-  const char* form_url = kFormUrl;
+struct TestFormAttributes {
+  const char* description_for_logging;
+  std::vector<FieldDataDescription<>> fields;
+  base::Optional<FormRendererId> unique_renderer_id = base::nullopt;
+  const char* name = "TestForm";
+  const char* url = kFormUrl;
+  const char* action = kFormActionUrl;
+  base::Optional<url::Origin> main_frame_origin = base::nullopt;
   bool is_formless_checkout = false;
   bool is_form_tag = true;
 };
@@ -57,7 +66,7 @@
 // Flags determining whether the corresponding check should be run on the test
 // form.
 template <typename = void>
-struct FormFlags {
+struct TestFormFlags {
   // false means the function is not to be called.
   bool determine_heuristic_type = false;
   bool parse_query_response = false;
@@ -90,15 +99,15 @@
 // Describes a test case for the parser.
 template <typename = void>
 struct FormStructureTestCase {
-  FormAttributes<> form_attributes;
-  FormFlags<> form_flags;
+  TestFormAttributes<> form_attributes;
+  TestFormFlags<> form_flags;
   ExpectedFieldTypeValues<> expected_field_types;
 };
 
 }  // namespace internal
 
 using FieldDataDescription = internal::FieldDataDescription<>;
-using FormAttributes = internal::FormAttributes<>;
+using TestFormAttributes = internal::TestFormAttributes<>;
 using FormStructureTestCase = internal::FormStructureTestCase<>;
 
 // Describes the |form_data|. Use this in SCOPED_TRACE if other logging
@@ -109,7 +118,7 @@
 FormFieldData CreateFieldByRole(ServerFieldType role);
 
 // Creates a FormData to be fed to the parser.
-FormData GetFormData(const FormAttributes& form_attributes);
+FormData GetFormData(const TestFormAttributes& test_form_attributes);
 
 class FormStructureTest : public testing::Test {
  protected:
diff --git a/components/autofill/core/browser/autofill_metrics_unittest.cc b/components/autofill/core/browser/autofill_metrics_unittest.cc
index 3e69886..61c1441 100644
--- a/components/autofill/core/browser/autofill_metrics_unittest.cc
+++ b/components/autofill/core/browser/autofill_metrics_unittest.cc
@@ -26,6 +26,7 @@
 #include "build/build_config.h"
 #include "components/autofill/core/browser/autofill_data_util.h"
 #include "components/autofill/core/browser/autofill_external_delegate.h"
+#include "components/autofill/core/browser/autofill_form_test_utils.h"
 #include "components/autofill/core/browser/autofill_test_utils.h"
 #include "components/autofill/core/browser/metrics/address_form_event_logger.h"
 #include "components/autofill/core/browser/metrics/credit_card_form_event_logger.h"
@@ -642,53 +643,43 @@
 // Test that we log quality metrics appropriately.
 TEST_F(AutofillMetricsTest, QualityMetrics) {
   // Set up our form data.
-  FormData form;
-  form.unique_renderer_id = MakeFormRendererId();
-  form.name = ASCIIToUTF16("TestForm");
-  form.url = GURL("http://example.com/form.html");
-  form.action = GURL("http://example.com/submit.html");
-  form.main_frame_origin = url::Origin::Create(autofill_client_.form_origin());
+  FormData form =
+      test::GetFormData({.description_for_logging = "QualityMetrics",
+                         .fields = {{.label = "Autofilled",
+                                     .name = "autofilled",
+                                     .value = "Elvis Aaron Presley",
+                                     .is_autofilled = true},
+                                    {.label = "Autofill Failed",
+                                     .name = "autofillfailed",
+                                     .value = "buddy@gmail.com",
+                                     .is_autofilled = false},
+                                    {.label = "Empty",
+                                     .name = "empty",
+                                     .value = "",
+                                     .is_autofilled = false},
+                                    {.label = "Unknown",
+                                     .name = "unknown",
+                                     .value = "garbage",
+                                     .is_autofilled = false},
+                                    {.label = "Select",
+                                     .name = "select",
+                                     .value = "USA",
+                                     .form_control_type = "select-one",
+                                     .is_autofilled = false},
+                                    {.role = ServerFieldType::PHONE_HOME_NUMBER,
+                                     .value = "2345678901",
+                                     .form_control_type = "tel",
+                                     .is_autofilled = true}},
+                         .unique_renderer_id = MakeFormRendererId(),
+                         .main_frame_origin = url::Origin::Create(
+                             autofill_client_.form_origin())});
 
-  std::vector<ServerFieldType> heuristic_types, server_types;
-  FormFieldData field;
-
-  test::CreateTestFormField("Autofilled", "autofilled", "Elvis Aaron Presley",
-                            "text", &field);
-  field.is_autofilled = true;
-  form.fields.push_back(field);
-  heuristic_types.push_back(NAME_FULL);
-  server_types.push_back(NAME_FIRST);
-
-  test::CreateTestFormField("Autofill Failed", "autofillfailed",
-                            "buddy@gmail.com", "text", &field);
-  field.is_autofilled = false;
-  form.fields.push_back(field);
-  heuristic_types.push_back(PHONE_HOME_NUMBER);
-  server_types.push_back(EMAIL_ADDRESS);
-
-  test::CreateTestFormField("Empty", "empty", "", "text", &field);
-  field.is_autofilled = false;
-  form.fields.push_back(field);
-  heuristic_types.push_back(NAME_FULL);
-  server_types.push_back(NAME_FIRST);
-
-  test::CreateTestFormField("Unknown", "unknown", "garbage", "text", &field);
-  field.is_autofilled = false;
-  form.fields.push_back(field);
-  heuristic_types.push_back(PHONE_HOME_NUMBER);
-  server_types.push_back(EMAIL_ADDRESS);
-
-  test::CreateTestFormField("Select", "select", "USA", "select-one", &field);
-  field.is_autofilled = false;
-  form.fields.push_back(field);
-  heuristic_types.push_back(UNKNOWN_TYPE);
-  server_types.push_back(NO_SERVER_DATA);
-
-  test::CreateTestFormField("Phone", "phone", "2345678901", "tel", &field);
-  field.is_autofilled = true;
-  form.fields.push_back(field);
-  heuristic_types.push_back(PHONE_HOME_CITY_AND_NUMBER);
-  server_types.push_back(PHONE_HOME_CITY_AND_NUMBER);
+  std::vector<ServerFieldType> heuristic_types = {
+      NAME_FULL,         PHONE_HOME_NUMBER, NAME_FULL,
+      PHONE_HOME_NUMBER, UNKNOWN_TYPE,      PHONE_HOME_CITY_AND_NUMBER};
+  std::vector<ServerFieldType> server_types = {
+      NAME_FIRST,    EMAIL_ADDRESS,  NAME_FIRST,
+      EMAIL_ADDRESS, NO_SERVER_DATA, PHONE_HOME_CITY_AND_NUMBER};
 
   // Simulate having seen this form on page load.
   autofill_manager_->AddSeenForm(form, heuristic_types, server_types);
@@ -831,50 +822,31 @@
 // Test that the ProfileImportStatus logs a no import.
 TEST_F(AutofillMetricsTest, ProfileImportStatus_NoImport) {
   // Set up our form data.
-  FormData form;
-  form.name = ASCIIToUTF16("TestForm");
-  form.url = GURL("http://example.com/form.html");
-  form.action = GURL("http://example.com/submit.html");
+  FormData form = test::GetFormData(
+      {.description_for_logging = "ProfileImportStatus_NoImport",
+       .fields = {
+           {.role = ServerFieldType::NAME_FULL, .value = "Elvis Aaron Presley"},
+           {.role = ServerFieldType::ADDRESS_HOME_LINE1,
+            .value = "3734 Elvis Presley Blvd."},
+           {.role = ServerFieldType::ADDRESS_HOME_CITY, .value = "New York"},
+           {.role = ServerFieldType::PHONE_HOME_NUMBER, .value = "2345678901"},
+           {.role = ServerFieldType::ADDRESS_HOME_STATE,
+            .value = "Invalid State"},
+           {.role = ServerFieldType::ADDRESS_HOME_ZIP,
+            .value = "00000000000000000"},
+           {.role = ServerFieldType::ADDRESS_HOME_COUNTRY,
+            .value = "NoACountry"}}});
 
-  std::vector<ServerFieldType> heuristic_types, server_types;
-  FormFieldData field;
-
-  test::CreateTestFormField("Name", "name", "Elvis Aaron Presley", "text",
-                            &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(NAME_FULL);
-  server_types.push_back(NAME_FULL);
-
-  test::CreateTestFormField("Address", "home_line_one",
-                            "3734 Elvis Presley Blvd.", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_LINE1);
-  server_types.push_back(ADDRESS_HOME_LINE1);
-
-  test::CreateTestFormField("City", "city", "New York", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_CITY);
-  server_types.push_back(ADDRESS_HOME_CITY);
-
-  test::CreateTestFormField("Phone", "phone", "2345678901", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(PHONE_HOME_CITY_AND_NUMBER);
-  server_types.push_back(PHONE_HOME_CITY_AND_NUMBER);
-
-  test::CreateTestFormField("State", "state", "InvalidState", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_STATE);
-  server_types.push_back(ADDRESS_HOME_STATE);
-
-  test::CreateTestFormField("ZIP", "zip", "00000000000000000", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_ZIP);
-  server_types.push_back(ADDRESS_HOME_ZIP);
-
-  test::CreateTestFormField("Country", "country", "NoACountry", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_COUNTRY);
-  server_types.push_back(ADDRESS_HOME_COUNTRY);
+  std::vector<ServerFieldType> heuristic_types = {
+      NAME_FULL,           ADDRESS_HOME_LINE1,
+      ADDRESS_HOME_CITY,   PHONE_HOME_CITY_AND_NUMBER,
+      ADDRESS_HOME_STATE,  ADDRESS_HOME_ZIP,
+      ADDRESS_HOME_COUNTRY};
+  std::vector<ServerFieldType> server_types = {
+      NAME_FULL,           ADDRESS_HOME_LINE1,
+      ADDRESS_HOME_CITY,   PHONE_HOME_CITY_AND_NUMBER,
+      ADDRESS_HOME_STATE,  ADDRESS_HOME_ZIP,
+      ADDRESS_HOME_COUNTRY};
 
   // Simulate having seen this form on page load.
   autofill_manager_->AddSeenForm(form, heuristic_types, server_types);
@@ -904,50 +876,28 @@
 // Test that the ProfileImportStatus logs a regular import.
 TEST_F(AutofillMetricsTest, ProfileImportStatus_RegularImport) {
   // Set up our form data.
-  FormData form;
-  form.name = ASCIIToUTF16("TestForm");
-  form.url = GURL("http://example.com/form.html");
-  form.action = GURL("http://example.com/submit.html");
+  FormData form = test::GetFormData(
+      {.description_for_logging = "ProfileImportStatus_RegularImport",
+       .fields = {
+           {.role = ServerFieldType::NAME_FULL, .value = "Elvis Aaron Presley"},
+           {.role = ServerFieldType::ADDRESS_HOME_LINE1,
+            .value = "3734 Elvis Presley Blvd."},
+           {.role = ServerFieldType::ADDRESS_HOME_CITY, .value = "New York"},
+           {.role = ServerFieldType::PHONE_HOME_NUMBER, .value = "2345678901"},
+           {.role = ServerFieldType::ADDRESS_HOME_STATE, .value = "CA"},
+           {.role = ServerFieldType::ADDRESS_HOME_ZIP, .value = "37373"},
+           {.role = ServerFieldType::ADDRESS_HOME_COUNTRY, .value = "USA"}}});
 
-  std::vector<ServerFieldType> heuristic_types, server_types;
-  FormFieldData field;
-
-  test::CreateTestFormField("Name", "name", "Elvis Aaron Presley", "text",
-                            &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(NAME_FULL);
-  server_types.push_back(NAME_FULL);
-
-  test::CreateTestFormField("Address", "home_line_one",
-                            "3734 Elvis Presley Blvd.", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_LINE1);
-  server_types.push_back(ADDRESS_HOME_LINE1);
-
-  test::CreateTestFormField("City", "city", "New York", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_CITY);
-  server_types.push_back(ADDRESS_HOME_CITY);
-
-  test::CreateTestFormField("Phone", "phone", "2345678901", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(PHONE_HOME_CITY_AND_NUMBER);
-  server_types.push_back(PHONE_HOME_CITY_AND_NUMBER);
-
-  test::CreateTestFormField("State", "state", "CA", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_STATE);
-  server_types.push_back(ADDRESS_HOME_STATE);
-
-  test::CreateTestFormField("ZIP", "zip", "37373", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_ZIP);
-  server_types.push_back(ADDRESS_HOME_ZIP);
-
-  test::CreateTestFormField("Country", "country", "USA", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_COUNTRY);
-  server_types.push_back(ADDRESS_HOME_COUNTRY);
+  std::vector<ServerFieldType> heuristic_types = {
+      NAME_FULL,           ADDRESS_HOME_LINE1,
+      ADDRESS_HOME_CITY,   PHONE_HOME_CITY_AND_NUMBER,
+      ADDRESS_HOME_STATE,  ADDRESS_HOME_ZIP,
+      ADDRESS_HOME_COUNTRY};
+  std::vector<ServerFieldType> server_types = {
+      NAME_FULL,           ADDRESS_HOME_LINE1,
+      ADDRESS_HOME_CITY,   PHONE_HOME_CITY_AND_NUMBER,
+      ADDRESS_HOME_STATE,  ADDRESS_HOME_ZIP,
+      ADDRESS_HOME_COUNTRY};
 
   // Simulate having seen this form on page load.
   autofill_manager_->AddSeenForm(form, heuristic_types, server_types);
@@ -977,54 +927,36 @@
 // Test that the ProfileImportStatus logs a section union mport.
 TEST_F(AutofillMetricsTest, ProfileImportStatus_UnionImport) {
   // Set up our form data.
-  FormData form;
-  form.name = ASCIIToUTF16("TestForm");
-  form.url = GURL("http://example.com/form.html");
-  form.action = GURL("http://example.com/submit.html");
+  FormData form = test::GetFormData(
+      {.description_for_logging = "ProfileImportStatus_UnionImport",
+       .fields = {
+           {.role = ServerFieldType::NAME_FULL, .value = "Elvis Aaron Presley"},
+           {.role = ServerFieldType::ADDRESS_HOME_LINE1,
+            .value = "3734 Elvis Presley Blvd."},
+           {.role = ServerFieldType::ADDRESS_HOME_ZIP, .value = "37373"},
+           {.role = ServerFieldType::ADDRESS_HOME_COUNTRY, .value = "USA"},
+           {.role = ServerFieldType::PHONE_HOME_NUMBER, .value = "2345678901"},
+           {.role = ServerFieldType::ADDRESS_HOME_CITY,
+            .value = "New York",
+            .autocomplete_attribute = "section-billing locality"},
+           {.role = ServerFieldType::ADDRESS_HOME_STATE,
+            .value = "CA",
+            .autocomplete_attribute = "section-shipping address-level1"}}});
 
-  std::vector<ServerFieldType> heuristic_types, server_types;
-  FormFieldData field;
-
-  test::CreateTestFormField("Name", "name", "Elvis Aaron Presley", "text",
-                            &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(NAME_FULL);
-  server_types.push_back(NAME_FULL);
-
-  test::CreateTestFormField("Address", "home_line_one",
-                            "3734 Elvis Presley Blvd.", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_LINE1);
-  server_types.push_back(ADDRESS_HOME_LINE1);
-
-  test::CreateTestFormField("ZIP", "zip", "37373", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_ZIP);
-  server_types.push_back(ADDRESS_HOME_ZIP);
-
-  test::CreateTestFormField("Country", "country", "USA", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_COUNTRY);
-  server_types.push_back(ADDRESS_HOME_COUNTRY);
-
-  test::CreateTestFormField("Phone", "phone", "2345678901", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(PHONE_HOME_CITY_AND_NUMBER);
-  server_types.push_back(PHONE_HOME_CITY_AND_NUMBER);
-
-  test::CreateTestFormField("City", "city", "New York", "text", &field);
-  // Assign a specific section.
-  field.autocomplete_attribute = "section-billing locality";
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_CITY);
-  server_types.push_back(ADDRESS_HOME_CITY);
-
-  test::CreateTestFormField("State", "state", "CA", "text", &field);
-  // Make the state a different section than the city.
-  field.autocomplete_attribute = "section-shipping address-level1";
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_STATE);
-  server_types.push_back(ADDRESS_HOME_STATE);
+  std::vector<ServerFieldType> heuristic_types = {NAME_FULL,
+                                                  ADDRESS_HOME_LINE1,
+                                                  ADDRESS_HOME_ZIP,
+                                                  ADDRESS_HOME_COUNTRY,
+                                                  PHONE_HOME_CITY_AND_NUMBER,
+                                                  ADDRESS_HOME_CITY,
+                                                  ADDRESS_HOME_STATE};
+  std::vector<ServerFieldType> server_types = {NAME_FULL,
+                                               ADDRESS_HOME_LINE1,
+                                               ADDRESS_HOME_ZIP,
+                                               ADDRESS_HOME_COUNTRY,
+                                               PHONE_HOME_CITY_AND_NUMBER,
+                                               ADDRESS_HOME_CITY,
+                                               ADDRESS_HOME_STATE};
 
   // Simulate having seen this form on page load.
   autofill_manager_->AddSeenForm(form, heuristic_types, server_types);
@@ -1079,50 +1011,28 @@
 // 'perfect' profile import.
 TEST_F(AutofillMetricsTest, ProfileImportRequirements_AllFulfilled) {
   // Set up our form data.
-  FormData form;
-  form.name = ASCIIToUTF16("TestForm");
-  form.url = GURL("http://example.com/form.html");
-  form.action = GURL("http://example.com/submit.html");
+  FormData form = test::GetFormData(
+      {.description_for_logging = "ProfileImportRequirements_AllFulfilled",
+       .fields = {
+           {.role = ServerFieldType::NAME_FULL, .value = "Elvis Aaron Presley"},
+           {.role = ServerFieldType::ADDRESS_HOME_LINE1,
+            .value = "3734 Elvis Presley Blvd."},
+           {.role = ServerFieldType::ADDRESS_HOME_CITY, .value = "New York"},
+           {.role = ServerFieldType::PHONE_HOME_NUMBER, .value = "2345678901"},
+           {.role = ServerFieldType::ADDRESS_HOME_STATE, .value = "CA"},
+           {.role = ServerFieldType::ADDRESS_HOME_ZIP, .value = "37373"},
+           {.role = ServerFieldType::ADDRESS_HOME_COUNTRY, .value = "USA"}}});
 
-  std::vector<ServerFieldType> heuristic_types, server_types;
-  FormFieldData field;
-
-  test::CreateTestFormField("Name", "name", "Elvis Aaron Presley", "text",
-                            &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(NAME_FULL);
-  server_types.push_back(NAME_FULL);
-
-  test::CreateTestFormField("Address", "home_line_one",
-                            "3734 Elvis Presley Blvd.", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_LINE1);
-  server_types.push_back(ADDRESS_HOME_LINE1);
-
-  test::CreateTestFormField("City", "city", "New York", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_CITY);
-  server_types.push_back(ADDRESS_HOME_CITY);
-
-  test::CreateTestFormField("Phone", "phone", "2345678901", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(PHONE_HOME_CITY_AND_NUMBER);
-  server_types.push_back(PHONE_HOME_CITY_AND_NUMBER);
-
-  test::CreateTestFormField("State", "state", "CA", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_STATE);
-  server_types.push_back(ADDRESS_HOME_STATE);
-
-  test::CreateTestFormField("ZIP", "zip", "37373", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_ZIP);
-  server_types.push_back(ADDRESS_HOME_ZIP);
-
-  test::CreateTestFormField("Country", "country", "USA", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_COUNTRY);
-  server_types.push_back(ADDRESS_HOME_COUNTRY);
+  std::vector<ServerFieldType> heuristic_types = {
+      NAME_FULL,           ADDRESS_HOME_LINE1,
+      ADDRESS_HOME_CITY,   PHONE_HOME_CITY_AND_NUMBER,
+      ADDRESS_HOME_STATE,  ADDRESS_HOME_ZIP,
+      ADDRESS_HOME_COUNTRY};
+  std::vector<ServerFieldType> server_types = {
+      NAME_FULL,           ADDRESS_HOME_LINE1,
+      ADDRESS_HOME_CITY,   PHONE_HOME_CITY_AND_NUMBER,
+      ADDRESS_HOME_STATE,  ADDRESS_HOME_ZIP,
+      ADDRESS_HOME_COUNTRY};
 
   // Simulate having seen this form on page load.
   autofill_manager_->AddSeenForm(form, heuristic_types, server_types);
@@ -1180,49 +1090,28 @@
 // ADDRESS_HOME_LINE1 is missing.
 TEST_F(AutofillMetricsTest, ProfileImportRequirements_MissingHomeLineOne) {
   // Set up our form data.
-  FormData form;
-  form.name = ASCIIToUTF16("TestForm");
-  form.url = GURL("http://example.com/form.html");
-  form.action = GURL("http://example.com/submit.html");
+  FormData form = test::GetFormData(
+      {.description_for_logging =
+           "ProfileImportRequirements_MissingHomeLineOne",
+       .fields = {
+           {.role = ServerFieldType::NAME_FULL, .value = "Elvis Aaron Presley"},
+           {.role = ServerFieldType::ADDRESS_HOME_LINE1, .value = ""},
+           {.role = ServerFieldType::ADDRESS_HOME_CITY, .value = "New York"},
+           {.role = ServerFieldType::PHONE_HOME_NUMBER, .value = "2345678901"},
+           {.role = ServerFieldType::ADDRESS_HOME_STATE, .value = "CA"},
+           {.role = ServerFieldType::ADDRESS_HOME_ZIP, .value = "37373"},
+           {.role = ServerFieldType::ADDRESS_HOME_COUNTRY, .value = "USA"}}});
 
-  std::vector<ServerFieldType> heuristic_types, server_types;
-  FormFieldData field;
-
-  test::CreateTestFormField("Name", "name", "Elvis Aaron Presley", "text",
-                            &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(NAME_FULL);
-  server_types.push_back(NAME_FULL);
-
-  test::CreateTestFormField("Address", "home_line_one", "", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_LINE1);
-  server_types.push_back(ADDRESS_HOME_LINE1);
-
-  test::CreateTestFormField("City", "city", "New York", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_CITY);
-  server_types.push_back(ADDRESS_HOME_CITY);
-
-  test::CreateTestFormField("Phone", "phone", "2345678901", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(PHONE_HOME_CITY_AND_NUMBER);
-  server_types.push_back(PHONE_HOME_CITY_AND_NUMBER);
-
-  test::CreateTestFormField("State", "state", "CA", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_STATE);
-  server_types.push_back(ADDRESS_HOME_STATE);
-
-  test::CreateTestFormField("ZIP", "zip", "37373", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_ZIP);
-  server_types.push_back(ADDRESS_HOME_ZIP);
-
-  test::CreateTestFormField("Country", "country", "USA", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_COUNTRY);
-  server_types.push_back(ADDRESS_HOME_COUNTRY);
+  std::vector<ServerFieldType> heuristic_types = {
+      NAME_FULL,           ADDRESS_HOME_LINE1,
+      ADDRESS_HOME_CITY,   PHONE_HOME_CITY_AND_NUMBER,
+      ADDRESS_HOME_STATE,  ADDRESS_HOME_ZIP,
+      ADDRESS_HOME_COUNTRY};
+  std::vector<ServerFieldType> server_types = {
+      NAME_FULL,           ADDRESS_HOME_LINE1,
+      ADDRESS_HOME_CITY,   PHONE_HOME_CITY_AND_NUMBER,
+      ADDRESS_HOME_STATE,  ADDRESS_HOME_ZIP,
+      ADDRESS_HOME_COUNTRY};
 
   // Simulate having seen this form on page load.
   autofill_manager_->AddSeenForm(form, heuristic_types, server_types);
@@ -1282,50 +1171,30 @@
 TEST_F(AutofillMetricsTest,
        ProfileImportRequirements_AllFulfilledForNonStateCountry) {
   // Set up our form data.
-  FormData form;
-  form.name = ASCIIToUTF16("TestForm");
-  form.url = GURL("http://example.com/form.html");
-  form.action = GURL("http://example.com/submit.html");
+  FormData form = test::GetFormData(
+      {.description_for_logging =
+           "ProfileImportRequirements_AllFulfilledForNonStateCountry",
+       .fields = {
+           {.role = ServerFieldType::NAME_FULL, .value = "Elvis Aaron Presley"},
+           {.role = ServerFieldType::ADDRESS_HOME_LINE1,
+            .value = "3734 Elvis Presley Blvd."},
+           {.role = ServerFieldType::ADDRESS_HOME_CITY, .value = "New York"},
+           {.role = ServerFieldType::PHONE_HOME_NUMBER, .value = "2345678901"},
+           {.role = ServerFieldType::ADDRESS_HOME_STATE, .value = ""},
+           {.role = ServerFieldType::ADDRESS_HOME_ZIP, .value = "37373"},
+           {.role = ServerFieldType::ADDRESS_HOME_COUNTRY,
+            .value = "Germany"}}});
 
-  std::vector<ServerFieldType> heuristic_types, server_types;
-  FormFieldData field;
-
-  test::CreateTestFormField("Name", "name", "Elvis Aaron Presley", "text",
-                            &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(NAME_FULL);
-  server_types.push_back(NAME_FULL);
-
-  test::CreateTestFormField("Address", "home_line_one",
-                            "3734 Elvis Presley Blvd.", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_LINE1);
-  server_types.push_back(ADDRESS_HOME_LINE1);
-
-  test::CreateTestFormField("City", "city", "New York", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_CITY);
-  server_types.push_back(ADDRESS_HOME_CITY);
-
-  test::CreateTestFormField("Phone", "phone", "2345678901", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(PHONE_HOME_CITY_AND_NUMBER);
-  server_types.push_back(PHONE_HOME_CITY_AND_NUMBER);
-
-  test::CreateTestFormField("State", "state", "", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_STATE);
-  server_types.push_back(ADDRESS_HOME_STATE);
-
-  test::CreateTestFormField("ZIP", "zip", "37373", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_ZIP);
-  server_types.push_back(ADDRESS_HOME_ZIP);
-
-  test::CreateTestFormField("Country", "country", "Germany", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_COUNTRY);
-  server_types.push_back(ADDRESS_HOME_COUNTRY);
+  std::vector<ServerFieldType> heuristic_types = {
+      NAME_FULL,           ADDRESS_HOME_LINE1,
+      ADDRESS_HOME_CITY,   PHONE_HOME_CITY_AND_NUMBER,
+      ADDRESS_HOME_STATE,  ADDRESS_HOME_ZIP,
+      ADDRESS_HOME_COUNTRY};
+  std::vector<ServerFieldType> server_types = {
+      NAME_FULL,           ADDRESS_HOME_LINE1,
+      ADDRESS_HOME_CITY,   PHONE_HOME_CITY_AND_NUMBER,
+      ADDRESS_HOME_STATE,  ADDRESS_HOME_ZIP,
+      ADDRESS_HOME_COUNTRY};
 
   // Simulate having seen this form on page load.
   autofill_manager_->AddSeenForm(form, heuristic_types, server_types);
@@ -1383,56 +1252,38 @@
 TEST_F(AutofillMetricsTest,
        ProfileImportRequirements_FilledButInvalidZipEmailAndState) {
   // Set up our form data.
-  FormData form;
-  form.name = ASCIIToUTF16("TestForm");
-  form.url = GURL("http://example.com/form.html");
-  form.action = GURL("http://example.com/submit.html");
+  FormData form = test::GetFormData(
+      {.description_for_logging =
+           "ProfileImportRequirements_FilledButInvalidZipEmailAndState",
+       .fields = {
+           {.role = ServerFieldType::NAME_FULL, .value = "Elvis Aaron Presley"},
+           {.role = ServerFieldType::ADDRESS_HOME_LINE1,
+            .value = "3734 Elvis Presley Blvd."},
+           {.role = ServerFieldType::ADDRESS_HOME_CITY, .value = "New York"},
+           {.role = ServerFieldType::PHONE_HOME_NUMBER, .value = "2345678901"},
+           {.role = ServerFieldType::ADDRESS_HOME_STATE,
+            .value = "DefNotAState"},
+           {.role = ServerFieldType::ADDRESS_HOME_ZIP, .value = "1234567890"},
+           {.role = ServerFieldType::ADDRESS_HOME_COUNTRY, .value = "USA"},
+           {.role = ServerFieldType::EMAIL_ADDRESS,
+            .value = "test_noat_test.io"}}});
 
-  std::vector<ServerFieldType> heuristic_types, server_types;
-  FormFieldData field;
-
-  test::CreateTestFormField("Name", "name", "Elvis Aaron Presley", "text",
-                            &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(NAME_FULL);
-  server_types.push_back(NAME_FULL);
-
-  test::CreateTestFormField("Address", "home_line_one",
-                            "3734 Elvis Presley Blvd.", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_LINE1);
-  server_types.push_back(ADDRESS_HOME_LINE1);
-
-  test::CreateTestFormField("City", "city", "New York", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_CITY);
-  server_types.push_back(ADDRESS_HOME_CITY);
-
-  test::CreateTestFormField("Phone", "phone", "2345678901", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(PHONE_HOME_CITY_AND_NUMBER);
-  server_types.push_back(PHONE_HOME_CITY_AND_NUMBER);
-
-  test::CreateTestFormField("State", "state", "DefNotAState", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_STATE);
-  server_types.push_back(ADDRESS_HOME_STATE);
-
-  test::CreateTestFormField("ZIP", "zip", "1234567890", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_ZIP);
-  server_types.push_back(ADDRESS_HOME_ZIP);
-
-  test::CreateTestFormField("Country", "country", "USA", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_COUNTRY);
-  server_types.push_back(ADDRESS_HOME_COUNTRY);
-
-  test::CreateTestFormField("Email1", "email1", "test_noat_test.io", "text",
-                            &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(EMAIL_ADDRESS);
-  server_types.push_back(EMAIL_ADDRESS);
+  std::vector<ServerFieldType> heuristic_types = {NAME_FULL,
+                                                  ADDRESS_HOME_LINE1,
+                                                  ADDRESS_HOME_CITY,
+                                                  PHONE_HOME_CITY_AND_NUMBER,
+                                                  ADDRESS_HOME_STATE,
+                                                  ADDRESS_HOME_ZIP,
+                                                  ADDRESS_HOME_COUNTRY,
+                                                  EMAIL_ADDRESS};
+  std::vector<ServerFieldType> server_types = {NAME_FULL,
+                                               ADDRESS_HOME_LINE1,
+                                               ADDRESS_HOME_CITY,
+                                               PHONE_HOME_CITY_AND_NUMBER,
+                                               ADDRESS_HOME_STATE,
+                                               ADDRESS_HOME_ZIP,
+                                               ADDRESS_HOME_COUNTRY,
+                                               EMAIL_ADDRESS};
 
   // Simulate having seen this form on page load.
   autofill_manager_->AddSeenForm(form, heuristic_types, server_types);
@@ -1490,61 +1341,41 @@
 // profile with multiple email addresses.
 TEST_F(AutofillMetricsTest, ProfileImportRequirements_NonUniqueEmail) {
   // Set up our form data.
-  FormData form;
-  form.name = ASCIIToUTF16("TestForm");
-  form.url = GURL("http://example.com/form.html");
-  form.action = GURL("http://example.com/submit.html");
+  FormData form = test::GetFormData(
+      {.description_for_logging = "ProfileImportRequirements_NonUniqueEmail",
+       .fields = {
+           {.role = ServerFieldType::NAME_FULL, .value = "Elvis Aaron Presley"},
+           {.role = ServerFieldType::ADDRESS_HOME_LINE1,
+            .value = "3734 Elvis Presley Blvd."},
+           {.role = ServerFieldType::ADDRESS_HOME_CITY, .value = "New York"},
+           {.role = ServerFieldType::PHONE_HOME_NUMBER, .value = "2345678901"},
+           {.role = ServerFieldType::ADDRESS_HOME_STATE, .value = "CA"},
+           {.role = ServerFieldType::ADDRESS_HOME_ZIP, .value = "37373"},
+           {.role = ServerFieldType::ADDRESS_HOME_COUNTRY, .value = "USA"},
+           {.role = ServerFieldType::EMAIL_ADDRESS,
+            .value = "test_noat_test.io"},
+           {.label = "Email1",
+            .name = ".email1",
+            .value = "not_test@test.io"}}});
 
-  std::vector<ServerFieldType> heuristic_types, server_types;
-  FormFieldData field;
-
-  test::CreateTestFormField("Name", "name", "Elvis Aaron Presley", "text",
-                            &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(NAME_FULL);
-  server_types.push_back(NAME_FULL);
-
-  test::CreateTestFormField("Address", "home_line_one",
-                            "3734 Elvis Presley Blvd.", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_LINE1);
-  server_types.push_back(ADDRESS_HOME_LINE1);
-
-  test::CreateTestFormField("City", "city", "New York", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_CITY);
-  server_types.push_back(ADDRESS_HOME_CITY);
-
-  test::CreateTestFormField("Phone", "phone", "2345678901", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(PHONE_HOME_CITY_AND_NUMBER);
-  server_types.push_back(PHONE_HOME_CITY_AND_NUMBER);
-
-  test::CreateTestFormField("State", "state", "CA", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_STATE);
-  server_types.push_back(ADDRESS_HOME_STATE);
-
-  test::CreateTestFormField("ZIP", "zip", "37373", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_ZIP);
-  server_types.push_back(ADDRESS_HOME_ZIP);
-
-  test::CreateTestFormField("Country", "country", "USA", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_COUNTRY);
-  server_types.push_back(ADDRESS_HOME_COUNTRY);
-
-  test::CreateTestFormField("Email1", "email1", "test@test.io", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(EMAIL_ADDRESS);
-  server_types.push_back(EMAIL_ADDRESS);
-
-  test::CreateTestFormField("Email2", "email2", "not_test@test.io", "text",
-                            &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(EMAIL_ADDRESS);
-  server_types.push_back(EMAIL_ADDRESS);
+  std::vector<ServerFieldType> heuristic_types = {NAME_FULL,
+                                                  ADDRESS_HOME_LINE1,
+                                                  ADDRESS_HOME_CITY,
+                                                  PHONE_HOME_CITY_AND_NUMBER,
+                                                  ADDRESS_HOME_STATE,
+                                                  ADDRESS_HOME_ZIP,
+                                                  ADDRESS_HOME_COUNTRY,
+                                                  EMAIL_ADDRESS,
+                                                  EMAIL_ADDRESS};
+  std::vector<ServerFieldType> server_types = {NAME_FULL,
+                                               ADDRESS_HOME_LINE1,
+                                               ADDRESS_HOME_CITY,
+                                               PHONE_HOME_CITY_AND_NUMBER,
+                                               ADDRESS_HOME_STATE,
+                                               ADDRESS_HOME_ZIP,
+                                               ADDRESS_HOME_COUNTRY,
+                                               EMAIL_ADDRESS,
+                                               EMAIL_ADDRESS};
 
   // Simulate having seen this form on page load.
   autofill_manager_->AddSeenForm(form, heuristic_types, server_types);
@@ -1602,50 +1433,29 @@
 // missing.
 TEST_F(AutofillMetricsTest, ProfileImportRequirements_OnlyAddressLineOne) {
   // Set up our form data.
-  FormData form;
-  form.name = ASCIIToUTF16("TestForm");
-  form.url = GURL("http://example.com/form.html");
-  form.action = GURL("http://example.com/submit.html");
+  FormData form = test::GetFormData(
+      {.description_for_logging =
+           "ProfileImportRequirements_OnlyAddressLineOne",
+       .fields = {
+           {.role = ServerFieldType::NAME_FULL, .value = "Elvis Aaron Presley"},
+           {.role = ServerFieldType::ADDRESS_HOME_LINE1,
+            .value = "3734 Elvis Presley Blvd."},
+           {.role = ServerFieldType::ADDRESS_HOME_CITY, .value = ""},
+           {.role = ServerFieldType::PHONE_HOME_NUMBER, .value = ""},
+           {.role = ServerFieldType::ADDRESS_HOME_STATE, .value = ""},
+           {.role = ServerFieldType::ADDRESS_HOME_ZIP, .value = ""},
+           {.role = ServerFieldType::ADDRESS_HOME_COUNTRY, .value = ""}}});
 
-  std::vector<ServerFieldType> heuristic_types, server_types;
-  FormFieldData field;
-
-  test::CreateTestFormField("Name", "name", "Elvis Aaron Presley", "text",
-                            &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(NAME_FULL);
-  server_types.push_back(NAME_FULL);
-
-  test::CreateTestFormField("Address", "home_line_one",
-                            "3734 Elvis Presley Blvd.", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_LINE1);
-  server_types.push_back(ADDRESS_HOME_LINE1);
-
-  test::CreateTestFormField("City", "city", "", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_CITY);
-  server_types.push_back(ADDRESS_HOME_CITY);
-
-  test::CreateTestFormField("Phone", "phone", "", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(PHONE_HOME_CITY_AND_NUMBER);
-  server_types.push_back(PHONE_HOME_CITY_AND_NUMBER);
-
-  test::CreateTestFormField("State", "state", "", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_STATE);
-  server_types.push_back(ADDRESS_HOME_STATE);
-
-  test::CreateTestFormField("ZIP", "zip", "", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_ZIP);
-  server_types.push_back(ADDRESS_HOME_ZIP);
-
-  test::CreateTestFormField("Country", "", "USA", "text", &field);
-  form.fields.push_back(field);
-  heuristic_types.push_back(ADDRESS_HOME_COUNTRY);
-  server_types.push_back(ADDRESS_HOME_COUNTRY);
+  std::vector<ServerFieldType> heuristic_types = {
+      NAME_FULL,           ADDRESS_HOME_LINE1,
+      ADDRESS_HOME_CITY,   PHONE_HOME_CITY_AND_NUMBER,
+      ADDRESS_HOME_STATE,  ADDRESS_HOME_ZIP,
+      ADDRESS_HOME_COUNTRY};
+  std::vector<ServerFieldType> server_types = {
+      NAME_FULL,           ADDRESS_HOME_LINE1,
+      ADDRESS_HOME_CITY,   PHONE_HOME_CITY_AND_NUMBER,
+      ADDRESS_HOME_STATE,  ADDRESS_HOME_ZIP,
+      ADDRESS_HOME_COUNTRY};
 
   // Simulate having seen this form on page load.
   autofill_manager_->AddSeenForm(form, heuristic_types, server_types);
diff --git a/components/autofill/core/browser/autofill_profile_sync_util.h b/components/autofill/core/browser/autofill_profile_sync_util.h
index c3078c3..a435952 100644
--- a/components/autofill/core/browser/autofill_profile_sync_util.h
+++ b/components/autofill/core/browser/autofill_profile_sync_util.h
@@ -7,8 +7,6 @@
 
 #include <memory>
 #include <string>
-// TODO(crbug.com/904390): Remove when the investigation is over.
-#include <vector>
 
 namespace syncer {
 struct EntityData;
diff --git a/components/browser_ui/styles/android/BUILD.gn b/components/browser_ui/styles/android/BUILD.gn
index acb93800..759a440 100644
--- a/components/browser_ui/styles/android/BUILD.gn
+++ b/components/browser_ui/styles/android/BUILD.gn
@@ -28,6 +28,7 @@
     "java/res/color/default_icon_color_tint_list.xml",
     "java/res/drawable-hdpi/btn_star_filled.png",
     "java/res/drawable-hdpi/ic_delete_white_24dp.png",
+    "java/res/drawable-hdpi/ic_edit_24dp.png",
     "java/res/drawable-hdpi/ic_folder_blue_24dp.png",
     "java/res/drawable-hdpi/ic_logo_googleg_24dp.png",
     "java/res/drawable-hdpi/ic_pause_white_24dp.png",
@@ -44,6 +45,7 @@
     "java/res/drawable-hdpi/top_round.9.png",
     "java/res/drawable-mdpi/btn_star_filled.png",
     "java/res/drawable-mdpi/ic_delete_white_24dp.png",
+    "java/res/drawable-mdpi/ic_edit_24dp.png",
     "java/res/drawable-mdpi/ic_folder_blue_24dp.png",
     "java/res/drawable-mdpi/ic_logo_googleg_24dp.png",
     "java/res/drawable-mdpi/ic_pause_white_24dp.png",
@@ -65,6 +67,7 @@
     "java/res/drawable-night-xxxhdpi/top_round.9.png",
     "java/res/drawable-xhdpi/btn_star_filled.png",
     "java/res/drawable-xhdpi/ic_delete_white_24dp.png",
+    "java/res/drawable-xhdpi/ic_edit_24dp.png",
     "java/res/drawable-xhdpi/ic_folder_blue_24dp.png",
     "java/res/drawable-xhdpi/ic_logo_googleg_24dp.png",
     "java/res/drawable-xhdpi/ic_pause_white_24dp.png",
@@ -81,6 +84,7 @@
     "java/res/drawable-xhdpi/top_round.9.png",
     "java/res/drawable-xxhdpi/btn_star_filled.png",
     "java/res/drawable-xxhdpi/ic_delete_white_24dp.png",
+    "java/res/drawable-xxhdpi/ic_edit_24dp.png",
     "java/res/drawable-xxhdpi/ic_folder_blue_24dp.png",
     "java/res/drawable-xxhdpi/ic_logo_googleg_24dp.png",
     "java/res/drawable-xxhdpi/ic_pause_white_24dp.png",
@@ -97,6 +101,7 @@
     "java/res/drawable-xxhdpi/top_round.9.png",
     "java/res/drawable-xxxhdpi/btn_star_filled.png",
     "java/res/drawable-xxxhdpi/ic_delete_white_24dp.png",
+    "java/res/drawable-xxxhdpi/ic_edit_24dp.png",
     "java/res/drawable-xxxhdpi/ic_folder_blue_24dp.png",
     "java/res/drawable-xxxhdpi/ic_logo_googleg_24dp.png",
     "java/res/drawable-xxxhdpi/ic_pause_white_24dp.png",
diff --git a/chrome/android/java/res/drawable-hdpi/ic_edit_24dp.png b/components/browser_ui/styles/android/java/res/drawable-hdpi/ic_edit_24dp.png
similarity index 100%
rename from chrome/android/java/res/drawable-hdpi/ic_edit_24dp.png
rename to components/browser_ui/styles/android/java/res/drawable-hdpi/ic_edit_24dp.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-mdpi/ic_edit_24dp.png b/components/browser_ui/styles/android/java/res/drawable-mdpi/ic_edit_24dp.png
similarity index 100%
rename from chrome/android/java/res/drawable-mdpi/ic_edit_24dp.png
rename to components/browser_ui/styles/android/java/res/drawable-mdpi/ic_edit_24dp.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xhdpi/ic_edit_24dp.png b/components/browser_ui/styles/android/java/res/drawable-xhdpi/ic_edit_24dp.png
similarity index 100%
rename from chrome/android/java/res/drawable-xhdpi/ic_edit_24dp.png
rename to components/browser_ui/styles/android/java/res/drawable-xhdpi/ic_edit_24dp.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xxhdpi/ic_edit_24dp.png b/components/browser_ui/styles/android/java/res/drawable-xxhdpi/ic_edit_24dp.png
similarity index 100%
rename from chrome/android/java/res/drawable-xxhdpi/ic_edit_24dp.png
rename to components/browser_ui/styles/android/java/res/drawable-xxhdpi/ic_edit_24dp.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xxxhdpi/ic_edit_24dp.png b/components/browser_ui/styles/android/java/res/drawable-xxxhdpi/ic_edit_24dp.png
similarity index 100%
rename from chrome/android/java/res/drawable-xxxhdpi/ic_edit_24dp.png
rename to components/browser_ui/styles/android/java/res/drawable-xxxhdpi/ic_edit_24dp.png
Binary files differ
diff --git a/components/exo/client_controlled_shell_surface.cc b/components/exo/client_controlled_shell_surface.cc
index 3399e59..494ce6b 100644
--- a/components/exo/client_controlled_shell_surface.cc
+++ b/components/exo/client_controlled_shell_surface.cc
@@ -845,7 +845,6 @@
 
   views::View::OnDeviceScaleFactorChanged(old_dsf, new_dsf);
 
-  UpdateWidgetBounds();
   UpdateFrameWidth();
 }
 
diff --git a/components/language/core/common/language_experiments.cc b/components/language/core/common/language_experiments.cc
index 263aa35..0a25484 100644
--- a/components/language/core/common/language_experiments.cc
+++ b/components/language/core/common/language_experiments.cc
@@ -32,6 +32,8 @@
     "NotifySyncOnLanguageDetermined", base::FEATURE_ENABLED_BY_DEFAULT};
 const base::Feature kDetailedLanguageSettings{
     "DetailedLanguageSettings", base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kTranslateAssistContent{"TranslateAssistContent",
+                                            base::FEATURE_DISABLED_BY_DEFAULT};
 const base::Feature kTranslateIntent{"TranslateIntent",
                                      base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/components/language/core/common/language_experiments.h b/components/language/core/common/language_experiments.h
index 74ed64a..f648153 100644
--- a/components/language/core/common/language_experiments.h
+++ b/components/language/core/common/language_experiments.h
@@ -39,6 +39,9 @@
 // This feature enables setting the application language on Android.
 extern const base::Feature kDetailedLanguageSettings;
 
+// This feature enables providing Translate data to Assistant.
+extern const base::Feature kTranslateAssistContent;
+
 // This feature enables an intent that starts translating the foreground tab.
 extern const base::Feature kTranslateIntent;
 
diff --git a/components/media_message_center/media_notification_view_impl.cc b/components/media_message_center/media_notification_view_impl.cc
index fec409eb..d926997 100644
--- a/components/media_message_center/media_notification_view_impl.cc
+++ b/components/media_message_center/media_notification_view_impl.cc
@@ -230,7 +230,7 @@
       IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_PLAY));
   play_pause_button->SetToggledTooltipText(l10n_util::GetStringUTF16(
       IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_PAUSE));
-  play_pause_button->EnableCanvasFlippingForRTLUI(false);
+  play_pause_button->SetFlipCanvasOnPaintForRTLUI(false);
   play_pause_button_ =
       playback_button_container_->AddChildView(std::move(play_pause_button));
 
@@ -277,7 +277,7 @@
       IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_ENTER_PIP));
   picture_in_picture_button->SetToggledTooltipText(l10n_util::GetStringUTF16(
       IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_EXIT_PIP));
-  picture_in_picture_button->EnableCanvasFlippingForRTLUI(false);
+  picture_in_picture_button->SetFlipCanvasOnPaintForRTLUI(false);
   picture_in_picture_button_ =
       button_row_->AddChildView(std::move(picture_in_picture_button));
 
@@ -588,7 +588,7 @@
   button->SetAccessibleName(accessible_name);
   button->SetTooltipText(accessible_name);
   button->SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
-  button->EnableCanvasFlippingForRTLUI(false);
+  button->SetFlipCanvasOnPaintForRTLUI(false);
   playback_button_container_->AddChildView(std::move(button));
 }
 
diff --git a/components/media_message_center/media_notification_view_modern_impl.cc b/components/media_message_center/media_notification_view_modern_impl.cc
index d507ff0..798b3e6 100644
--- a/components/media_message_center/media_notification_view_modern_impl.cc
+++ b/components/media_message_center/media_notification_view_modern_impl.cc
@@ -264,7 +264,7 @@
         picture_in_picture_button->SetToggledTooltipText(
             l10n_util::GetStringUTF16(
                 IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_EXIT_PIP));
-        picture_in_picture_button->EnableCanvasFlippingForRTLUI(false);
+        picture_in_picture_button->SetFlipCanvasOnPaintForRTLUI(false);
         views::SetImageFromVectorIconWithColor(
             picture_in_picture_button.get(),
             *GetVectorIconForMediaAction(
@@ -336,7 +336,7 @@
           IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_PLAY));
       play_pause_button->SetToggledTooltipText(l10n_util::GetStringUTF16(
           IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_PAUSE));
-      play_pause_button->EnableCanvasFlippingForRTLUI(false);
+      play_pause_button->SetFlipCanvasOnPaintForRTLUI(false);
       play_pause_button_ =
           media_controls_container->AddChildView(std::move(play_pause_button));
     }
@@ -533,7 +533,7 @@
   button->SetAccessibleName(accessible_name);
   button->SetTooltipText(accessible_name);
   button->SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
-  button->EnableCanvasFlippingForRTLUI(false);
+  button->SetFlipCanvasOnPaintForRTLUI(false);
   parent_view->AddChildView(std::move(button));
 }
 
diff --git a/components/omnibox/browser/BUILD.gn b/components/omnibox/browser/BUILD.gn
index 8136b5a..ff938efb 100644
--- a/components/omnibox/browser/BUILD.gn
+++ b/components/omnibox/browser/BUILD.gn
@@ -134,6 +134,8 @@
     "local_history_zero_suggest_provider.cc",
     "local_history_zero_suggest_provider.h",
     "match_compare.h",
+    "most_visited_sites_provider.cc",
+    "most_visited_sites_provider.h",
     "omnibox_client.cc",
     "omnibox_client.h",
     "omnibox_controller.cc",
@@ -493,6 +495,7 @@
     "keyword_provider_unittest.cc",
     "local_history_zero_suggest_provider_unittest.cc",
     "location_bar_model_impl_unittest.cc",
+    "most_visited_sites_provider_unittest.cc",
     "omnibox_controller_unittest.cc",
     "omnibox_edit_model_unittest.cc",
     "omnibox_field_trial_unittest.cc",
diff --git a/components/omnibox/browser/autocomplete_classifier.cc b/components/omnibox/browser/autocomplete_classifier.cc
index a7c54964..8bcba27 100644
--- a/components/omnibox/browser/autocomplete_classifier.cc
+++ b/components/omnibox/browser/autocomplete_classifier.cc
@@ -45,6 +45,8 @@
       AutocompleteProvider::TYPE_KEYWORD |
 #else
       AutocompleteProvider::TYPE_CLIPBOARD |
+      AutocompleteProvider::TYPE_MOST_VISITED_SITES |
+      AutocompleteProvider::TYPE_VERBATIM_MATCH |
 #endif
       AutocompleteProvider::TYPE_ZERO_SUGGEST |
       AutocompleteProvider::TYPE_ZERO_SUGGEST_LOCAL_HISTORY |
diff --git a/components/omnibox/browser/autocomplete_controller.cc b/components/omnibox/browser/autocomplete_controller.cc
index 6c2eeec..f9ec488 100644
--- a/components/omnibox/browser/autocomplete_controller.cc
+++ b/components/omnibox/browser/autocomplete_controller.cc
@@ -38,6 +38,7 @@
 #include "components/omnibox/browser/history_url_provider.h"
 #include "components/omnibox/browser/keyword_provider.h"
 #include "components/omnibox/browser/local_history_zero_suggest_provider.h"
+#include "components/omnibox/browser/most_visited_sites_provider.h"
 #include "components/omnibox/browser/omnibox_field_trial.h"
 #include "components/omnibox/browser/omnibox_pedal_provider.h"
 #include "components/omnibox/browser/on_device_head_provider.h"
@@ -290,22 +291,22 @@
         ZeroSuggestProvider::Create(provider_client_.get(), this);
     if (zero_suggest_provider_)
       providers_.push_back(zero_suggest_provider_);
-#if defined(OS_ANDROID)
+  }
+  if (provider_types & AutocompleteProvider::TYPE_ZERO_SUGGEST_LOCAL_HISTORY) {
+    providers_.push_back(
+        LocalHistoryZeroSuggestProvider::Create(provider_client_.get(), this));
+  }
+  if (provider_types & AutocompleteProvider::TYPE_MOST_VISITED_SITES) {
+    providers_.push_back(
+        new MostVisitedSitesProvider(provider_client_.get(), this));
     // Note: the need for the always-present verbatim match originates from the
     // OmniboxSearchReadyIncognito feature.
     // The feature aims at showing SRO in an Incognito mode, where the
     // ZeroSuggestProvider intentionally never gets invoked.
     // The gating flag here should be removed when the SRO Incognito is
     // launched.
-    if (base::FeatureList::IsEnabled(omnibox::kOmniboxSearchReadyIncognito)) {
-      providers_.push_back(
-          new ZeroSuggestVerbatimMatchProvider(provider_client_.get()));
-    }
-#endif
-  }
-  if (provider_types & AutocompleteProvider::TYPE_ZERO_SUGGEST_LOCAL_HISTORY) {
     providers_.push_back(
-        LocalHistoryZeroSuggestProvider::Create(provider_client_.get(), this));
+        new ZeroSuggestVerbatimMatchProvider(provider_client_.get()));
   }
   if (provider_types & AutocompleteProvider::TYPE_DOCUMENT) {
     document_provider_ = DocumentProvider::Create(provider_client_.get(), this);
@@ -1003,3 +1004,8 @@
                   base::trace_event::MemoryAllocatorDump::kUnitsBytes, res);
   return true;
 }
+
+void AutocompleteController::SetStartStopTimerDurationForTesting(
+    base::TimeDelta duration) {
+  stop_timer_duration_ = duration;
+}
diff --git a/components/omnibox/browser/autocomplete_controller.h b/components/omnibox/browser/autocomplete_controller.h
index 7c27231b..7459a65 100644
--- a/components/omnibox/browser/autocomplete_controller.h
+++ b/components/omnibox/browser/autocomplete_controller.h
@@ -172,6 +172,9 @@
     return last_time_default_match_changed_;
   }
 
+  // Sets the provider timeout duration for future calls to |Start()|.
+  void SetStartStopTimerDurationForTesting(base::TimeDelta duration);
+
  private:
   friend class AutocompleteProviderTest;
   friend class OmniboxSuggestionButtonRowBrowserTest;
@@ -320,12 +323,11 @@
   // Timer used to tell the providers to Stop() searching for matches.
   base::OneShotTimer stop_timer_;
 
-  // Amount of time (in ms) between when the user stops typing and
-  // when we send Stop() to every provider.  This is intended to avoid
-  // the disruptive effect of belated omnibox updates, updates that
-  // come after the user has had to time to read the whole dropdown
-  // and doesn't expect it to change.
-  const base::TimeDelta stop_timer_duration_;
+  // Amount of time between when the user stops typing and when we send Stop()
+  // to every provider.  This is intended to avoid the disruptive effect of
+  // belated omnibox updates, updates that come after the user has had to time
+  // to read the whole dropdown and doesn't expect it to change.
+  base::TimeDelta stop_timer_duration_;
 
   // True if a query is not currently running.
   bool done_;
diff --git a/components/omnibox/browser/autocomplete_provider.cc b/components/omnibox/browser/autocomplete_provider.cc
index 211e2f0..65afd40 100644
--- a/components/omnibox/browser/autocomplete_provider.cc
+++ b/components/omnibox/browser/autocomplete_provider.cc
@@ -62,6 +62,10 @@
       return "LocalHistoryZeroSuggest";
     case TYPE_QUERY_TILE:
       return "QueryTile";
+    case TYPE_MOST_VISITED_SITES:
+      return "MostVisitedSites";
+    case TYPE_VERBATIM_MATCH:
+      return "VerbatimMatch";
     default:
       NOTREACHED() << "Unhandled AutocompleteProvider::Type " << type;
       return "Unknown";
@@ -134,6 +138,10 @@
       return metrics::OmniboxEventProto::ZERO_SUGGEST_LOCAL_HISTORY;
     case TYPE_QUERY_TILE:
       return metrics::OmniboxEventProto::QUERY_TILE;
+    case TYPE_MOST_VISITED_SITES:
+      return metrics::OmniboxEventProto::ZERO_SUGGEST;
+    case TYPE_VERBATIM_MATCH:
+      return metrics::OmniboxEventProto::ZERO_SUGGEST;
     default:
       NOTREACHED() << "Unhandled AutocompleteProvider::Type " << type_;
       return metrics::OmniboxEventProto::UNKNOWN_PROVIDER;
diff --git a/components/omnibox/browser/autocomplete_provider.h b/components/omnibox/browser/autocomplete_provider.h
index 90d8b18e..08d6a07 100644
--- a/components/omnibox/browser/autocomplete_provider.h
+++ b/components/omnibox/browser/autocomplete_provider.h
@@ -151,6 +151,8 @@
     TYPE_ON_DEVICE_HEAD = 1 << 10,
     TYPE_ZERO_SUGGEST_LOCAL_HISTORY = 1 << 11,
     TYPE_QUERY_TILE = 1 << 12,
+    TYPE_MOST_VISITED_SITES = 1 << 13,
+    TYPE_VERBATIM_MATCH = 1 << 14,
   };
 
   explicit AutocompleteProvider(Type type);
diff --git a/components/omnibox/browser/autocomplete_provider_client.h b/components/omnibox/browser/autocomplete_provider_client.h
index ecd0c46c..3b1db1a 100644
--- a/components/omnibox/browser/autocomplete_provider_client.h
+++ b/components/omnibox/browser/autocomplete_provider_client.h
@@ -150,8 +150,8 @@
   virtual void StartServiceWorker(const GURL& destination_url) {}
 
   // Called by |controller| when its results have changed and all providers are
-  // done processing the autocomplete request. Chrome ignores this. It's only
-  // used in components unit tests. TODO(blundell): remove it.
+  // done processing the autocomplete request. Used by chrome to inform the
+  // prefetch service of updated results.
   virtual void OnAutocompleteControllerResultReady(
       AutocompleteController* controller) {}
 
diff --git a/components/omnibox/browser/most_visited_sites_provider.cc b/components/omnibox/browser/most_visited_sites_provider.cc
new file mode 100644
index 0000000..ec324d5
--- /dev/null
+++ b/components/omnibox/browser/most_visited_sites_provider.cc
@@ -0,0 +1,148 @@
+// Copyright (c) 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/omnibox/browser/most_visited_sites_provider.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/feature_list.h"
+#include "base/strings/string16.h"
+#include "components/history/core/browser/top_sites.h"
+#include "components/omnibox/browser/autocomplete_input.h"
+#include "components/omnibox/browser/autocomplete_match.h"
+#include "components/omnibox/browser/autocomplete_match_classification.h"
+#include "components/omnibox/common/omnibox_features.h"
+#include "components/url_formatter/url_formatter.h"
+#include "net/base/escape.h"
+#include "third_party/metrics_proto/omnibox_event.pb.h"
+#include "third_party/metrics_proto/omnibox_input_type.pb.h"
+#include "url/gurl.h"
+
+namespace {
+// The relevance score for navsuggest tiles.
+// Navsuggest tiles should be positioned below the Query Tiles object.
+constexpr const int kMostVisitedTilesRelevance = 1500;
+}  // namespace
+
+void MostVisitedSitesProvider::Start(const AutocompleteInput& input,
+                                     bool minimal_changes) {
+  Stop(true, false);
+  if (!AllowMostVisitedSitesSuggestions(input))
+    return;
+
+  scoped_refptr<history::TopSites> top_sites = client_->GetTopSites();
+  if (!top_sites)
+    return;
+
+  top_sites->GetMostVisitedURLs(
+      base::BindRepeating(&MostVisitedSitesProvider::OnMostVisitedUrlsAvailable,
+                          request_weak_ptr_factory_.GetWeakPtr()));
+}
+
+void MostVisitedSitesProvider::Stop(bool clear_cached_results,
+                                    bool due_to_user_inactivity) {
+  request_weak_ptr_factory_.InvalidateWeakPtrs();
+  matches_.clear();
+}
+
+MostVisitedSitesProvider::MostVisitedSitesProvider(
+    AutocompleteProviderClient* client,
+    AutocompleteProviderListener* listener)
+    : AutocompleteProvider(TYPE_MOST_VISITED_SITES),
+      client_{client},
+      listener_{listener} {}
+
+MostVisitedSitesProvider::~MostVisitedSitesProvider() = default;
+
+AutocompleteMatch MostVisitedSitesProvider::BuildMatch(
+    const base::string16& description,
+    const GURL& url,
+    int relevance,
+    AutocompleteMatchType::Type type) {
+  AutocompleteMatch match(this, relevance, false, type);
+  match.destination_url = url;
+
+  match.fill_into_edit +=
+      AutocompleteInput::FormattedStringWithEquivalentMeaning(
+          url, url_formatter::FormatUrl(url), client_->GetSchemeClassifier(),
+          nullptr);
+
+  // Zero suggest results should always omit protocols and never appear bold.
+  auto format_types = AutocompleteMatch::GetFormatTypes(false, false);
+  match.contents = url_formatter::FormatUrl(
+      url, format_types, net::UnescapeRule::SPACES, nullptr, nullptr, nullptr);
+  match.contents_class = ClassifyTermMatches({}, match.contents.length(), 0,
+                                             ACMatchClassification::URL);
+
+  match.description = AutocompleteMatch::SanitizeString(description);
+  match.description_class = ClassifyTermMatches({}, match.description.length(),
+                                                0, ACMatchClassification::NONE);
+
+  return match;
+}
+
+void MostVisitedSitesProvider::OnMostVisitedUrlsAvailable(
+    const history::MostVisitedURLList& urls) {
+  if (urls.empty())
+    return;
+
+  if (base::FeatureList::IsEnabled(omnibox::kMostVisitedTiles)) {
+    AutocompleteMatch match = BuildMatch(
+        base::string16(), GURL::EmptyGURL(), kMostVisitedTilesRelevance,
+        AutocompleteMatchType::TILE_NAVSUGGEST);
+    match.navsuggest_tiles.reserve(urls.size());
+
+    for (const auto& url : urls) {
+      match.navsuggest_tiles.push_back({url.url, url.title});
+    }
+    matches_.push_back(std::move(match));
+  } else {
+    int relevance = 600;
+    for (const auto& url : urls) {
+      matches_.emplace_back(BuildMatch(url.title, url.url, relevance,
+                                       AutocompleteMatchType::NAVSUGGEST));
+      --relevance;
+    }
+  }
+  listener_->OnProviderUpdate(true);
+}
+
+bool MostVisitedSitesProvider::AllowMostVisitedSitesSuggestions(
+    const AutocompleteInput& input) const {
+  const auto& page_url = input.current_url();
+  const auto page_class = input.current_page_classification();
+  const auto input_type = input.type();
+
+  if (input.focus_type() == OmniboxFocusType::DEFAULT)
+    return false;
+
+  if (client_->IsOffTheRecord())
+    return false;
+
+  // Only serve Most Visited suggestions when the current context is page visit.
+  if (page_class != metrics::OmniboxEventProto::OTHER)
+    return false;
+
+  // When omnibox contains pre-populated content, only show zero suggest for
+  // pages with URLs the user will recognize.
+  //
+  // This list intentionally does not include items such as ftp: and file:
+  // because (a) these do not work on Android and iOS, where most visited
+  // zero suggest is launched and (b) on desktop, where contextual zero suggest
+  // is running, these types of schemes aren't eligible to be sent to the
+  // server to ask for suggestions (and thus in practice we won't display zero
+  // suggest for them).
+  if (input_type != metrics::OmniboxInputType::EMPTY &&
+      !(page_url.is_valid() &&
+        ((page_url.scheme() == url::kHttpScheme) ||
+         (page_url.scheme() == url::kHttpsScheme) ||
+         (page_url.scheme() == url::kAboutScheme) ||
+         (page_url.scheme() ==
+          client_->GetEmbedderRepresentationOfAboutScheme())))) {
+    return false;
+  }
+
+  return true;
+}
diff --git a/components/omnibox/browser/most_visited_sites_provider.h b/components/omnibox/browser/most_visited_sites_provider.h
new file mode 100644
index 0000000..659049b
--- /dev/null
+++ b/components/omnibox/browser/most_visited_sites_provider.h
@@ -0,0 +1,58 @@
+// Copyright (c) 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_OMNIBOX_BROWSER_MOST_VISITED_SITES_PROVIDER_H_
+#define COMPONENTS_OMNIBOX_BROWSER_MOST_VISITED_SITES_PROVIDER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/memory/weak_ptr.h"
+#include "components/omnibox/browser/autocomplete_input.h"
+#include "components/omnibox/browser/autocomplete_provider.h"
+#include "components/omnibox/browser/autocomplete_provider_client.h"
+#include "components/omnibox/browser/autocomplete_provider_listener.h"
+#include "third_party/metrics_proto/omnibox_event.pb.h"
+
+// Autocomplete provider serving Most Visited Sites in zero-prefix context.
+// Serves most frequently visited URLs in a form of either individual- or
+// aggregate suggestions.
+class MostVisitedSitesProvider : public AutocompleteProvider {
+ public:
+  MostVisitedSitesProvider(AutocompleteProviderClient* client,
+                           AutocompleteProviderListener* listener);
+
+  void Start(const AutocompleteInput& input, bool minimal_changes) override;
+  void Stop(bool clear_cached_results, bool due_to_user_inactivity) override;
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(MostVisitedSitesProviderTest,
+                           AllowMostVisitedSitesSuggestions);
+
+  ~MostVisitedSitesProvider() override;
+
+  // Constructs an AutocompleteMatch from supplied details.
+  AutocompleteMatch BuildMatch(const base::string16& description,
+                               const GURL& url,
+                               int relevance,
+                               AutocompleteMatchType::Type type);
+
+  // When the TopSites service serves the most visited URLs, this function
+  // converts those urls to AutocompleteMatches and adds them to |matches_|.
+  void OnMostVisitedUrlsAvailable(const history::MostVisitedURLList& urls);
+
+  // Whether zero suggest suggestions are allowed in the given context.
+  // Invoked early, confirms all the external conditions for ZeroSuggest are
+  // met.
+  bool AllowMostVisitedSitesSuggestions(const AutocompleteInput& input) const;
+
+  AutocompleteProviderClient* const client_;
+  AutocompleteProviderListener* const listener_;
+  // Note: used to cancel requests - not a general purpose WeakPtr factory.
+  base::WeakPtrFactory<MostVisitedSitesProvider> request_weak_ptr_factory_{
+      this};
+};
+
+#endif  // COMPONENTS_OMNIBOX_BROWSER_MOST_VISITED_SITES_PROVIDER_H_
diff --git a/components/omnibox/browser/most_visited_sites_provider_unittest.cc b/components/omnibox/browser/most_visited_sites_provider_unittest.cc
new file mode 100644
index 0000000..1523ee2
--- /dev/null
+++ b/components/omnibox/browser/most_visited_sites_provider_unittest.cc
@@ -0,0 +1,265 @@
+// 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/omnibox/browser/most_visited_sites_provider.h"
+
+#include <list>
+#include <map>
+#include <memory>
+#include <string>
+
+#include "build/build_config.h"
+#include "components/history/core/browser/top_sites.h"
+#include "components/omnibox/browser/autocomplete_provider_listener.h"
+#include "components/omnibox/browser/mock_autocomplete_provider_client.h"
+#include "components/omnibox/browser/omnibox_field_trial.h"
+#include "components/omnibox/browser/test_scheme_classifier.h"
+#include "components/omnibox/common/omnibox_features.h"
+#include "components/search_engines/omnibox_focus_type.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/metrics_proto/omnibox_event.pb.h"
+
+namespace {
+class FakeEmptyTopSites : public history::TopSites {
+ public:
+  // history::TopSites:
+  void GetMostVisitedURLs(GetMostVisitedURLsCallback callback) override;
+  void SyncWithHistory() override {}
+  bool HasBlockedUrls() const override { return false; }
+  void AddBlockedUrl(const GURL& url) override {}
+  void RemoveBlockedUrl(const GURL& url) override {}
+  bool IsBlocked(const GURL& url) override { return false; }
+  void ClearBlockedUrls() override {}
+  bool IsFull() override { return false; }
+  bool loaded() const override { return false; }
+  history::PrepopulatedPageList GetPrepopulatedPages() override {
+    return history::PrepopulatedPageList();
+  }
+  void OnNavigationCommitted(const GURL& url) override {}
+
+  // RefcountedKeyedService:
+  void ShutdownOnUIThread() override {}
+
+  // Only runs a single callback, so that the test can specify a different
+  // set per call.
+  void RunACallback(const history::MostVisitedURLList& urls) {
+    DCHECK(!callbacks.empty());
+    std::move(callbacks.front()).Run(urls);
+    callbacks.pop_front();
+  }
+
+ protected:
+  // A test-specific field for controlling when most visited callback is run
+  // after top sites have been requested.
+  std::list<GetMostVisitedURLsCallback> callbacks;
+
+  ~FakeEmptyTopSites() override = default;
+};
+
+void FakeEmptyTopSites::GetMostVisitedURLs(
+    GetMostVisitedURLsCallback callback) {
+  callbacks.push_back(std::move(callback));
+}
+
+class FakeAutocompleteProviderClient : public MockAutocompleteProviderClient {
+ public:
+  FakeAutocompleteProviderClient()
+      : template_url_service_(new TemplateURLService(nullptr, 0)),
+        top_sites_(new FakeEmptyTopSites()) {}
+  FakeAutocompleteProviderClient(const FakeAutocompleteProviderClient&) =
+      delete;
+  FakeAutocompleteProviderClient& operator=(
+      const FakeAutocompleteProviderClient&) = delete;
+
+  bool SearchSuggestEnabled() const override { return true; }
+
+  scoped_refptr<history::TopSites> GetTopSites() override { return top_sites_; }
+
+  TemplateURLService* GetTemplateURLService() override {
+    return template_url_service_.get();
+  }
+
+  TemplateURLService* GetTemplateURLService() const override {
+    return template_url_service_.get();
+  }
+
+  bool IsPersonalizedUrlDataCollectionActive() const override { return true; }
+
+  void Classify(
+      const base::string16& text,
+      bool prefer_keyword,
+      bool allow_exact_keyword_match,
+      metrics::OmniboxEventProto::PageClassification page_classification,
+      AutocompleteMatch* match,
+      GURL* alternate_nav_url) override {
+    // Populate enough of |match| to keep the MostVisitedSitesProvider happy.
+    match->type = AutocompleteMatchType::URL_WHAT_YOU_TYPED;
+    match->destination_url = GURL(text);
+  }
+
+  const AutocompleteSchemeClassifier& GetSchemeClassifier() const override {
+    return scheme_classifier_;
+  }
+
+ private:
+  std::unique_ptr<TemplateURLService> template_url_service_;
+  scoped_refptr<history::TopSites> top_sites_;
+  TestSchemeClassifier scheme_classifier_;
+};
+
+}  // namespace
+
+class MostVisitedSitesProviderTest : public testing::Test,
+                                     public AutocompleteProviderListener {
+ public:
+  MostVisitedSitesProviderTest() = default;
+  MostVisitedSitesProviderTest(const MostVisitedSitesProviderTest&) = delete;
+  MostVisitedSitesProviderTest& operator=(const MostVisitedSitesProviderTest&) =
+      delete;
+
+  void SetUp() override;
+
+ protected:
+  // AutocompleteProviderListener:
+  void OnProviderUpdate(bool updated_matches) override;
+
+  std::unique_ptr<FakeAutocompleteProviderClient> client_;
+  scoped_refptr<MostVisitedSitesProvider> provider_;
+
+  network::TestURLLoaderFactory* test_loader_factory() {
+    return client_->test_url_loader_factory();
+  }
+
+  GURL GetSuggestURL(
+      metrics::OmniboxEventProto::PageClassification page_classification) {
+    TemplateURLRef::SearchTermsArgs search_terms_args;
+    search_terms_args.page_classification = page_classification;
+    search_terms_args.focus_type = OmniboxFocusType::ON_FOCUS;
+    return RemoteSuggestionsService::EndpointUrl(
+        search_terms_args, client_->GetTemplateURLService());
+  }
+
+  AutocompleteInput CreateNTPOnFocusInputForRemoteNoUrl() {
+    // Use NTP as the page classification, since REMOTE_NO_URL is enabled by
+    // default for the NTP.
+    AutocompleteInput input(
+        base::string16(),
+        metrics::OmniboxEventProto::INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS,
+        TestSchemeClassifier());
+    input.set_focus_type(OmniboxFocusType::ON_FOCUS);
+    return input;
+  }
+};
+
+void MostVisitedSitesProviderTest::SetUp() {
+  client_ = std::make_unique<FakeAutocompleteProviderClient>();
+  provider_ = new MostVisitedSitesProvider(client_.get(), this);
+}
+
+void MostVisitedSitesProviderTest::OnProviderUpdate(bool updated_matches) {}
+
+TEST_F(MostVisitedSitesProviderTest, AllowMostVisitedSitesSuggestions) {
+  std::string input_url = "https://example.com/";
+
+  AutocompleteInput prefix_input(base::ASCIIToUTF16(input_url),
+                                 metrics::OmniboxEventProto::OTHER,
+                                 TestSchemeClassifier());
+  prefix_input.set_focus_type(OmniboxFocusType::DEFAULT);
+
+  AutocompleteInput on_focus_input(base::ASCIIToUTF16(input_url),
+                                   metrics::OmniboxEventProto::OTHER,
+                                   TestSchemeClassifier());
+  on_focus_input.set_current_url(GURL(input_url));
+  on_focus_input.set_focus_type(OmniboxFocusType::ON_FOCUS);
+
+  AutocompleteInput on_clobber_input(base::string16(),
+                                     metrics::OmniboxEventProto::OTHER,
+                                     TestSchemeClassifier());
+  on_clobber_input.set_current_url(GURL(input_url));
+  on_clobber_input.set_focus_type(OmniboxFocusType::DELETED_PERMANENT_TEXT);
+
+  // MostVisited should never deal with prefix suggestions.
+  EXPECT_FALSE(provider_->AllowMostVisitedSitesSuggestions(prefix_input));
+
+  // This should always be true, as otherwise we will break MostVisited.
+  EXPECT_TRUE(provider_->AllowMostVisitedSitesSuggestions(on_focus_input));
+}
+
+TEST_F(MostVisitedSitesProviderTest, TestMostVisitedCallback) {
+  std::string current_url("http://www.foxnews.com/");
+  std::string input_url("http://www.cnn.com/");
+  AutocompleteInput input(base::ASCIIToUTF16(input_url),
+                          metrics::OmniboxEventProto::OTHER,
+                          TestSchemeClassifier());
+  input.set_current_url(GURL(current_url));
+  input.set_focus_type(OmniboxFocusType::ON_FOCUS);
+  history::MostVisitedURLList urls;
+  history::MostVisitedURL url(GURL("http://foo.com/"),
+                              base::ASCIIToUTF16("Foo"));
+  urls.push_back(url);
+
+  provider_->Start(input, false);
+  EXPECT_TRUE(provider_->matches().empty());
+  scoped_refptr<history::TopSites> top_sites = client_->GetTopSites();
+  static_cast<FakeEmptyTopSites*>(top_sites.get())->RunACallback(urls);
+  EXPECT_EQ(1U, provider_->matches().size());
+  provider_->Stop(false, false);
+
+  provider_->Start(input, false);
+  provider_->Stop(false, false);
+  EXPECT_TRUE(provider_->matches().empty());
+  // Most visited results arriving after Stop() has been called, ensure they
+  // are not displayed.
+  static_cast<FakeEmptyTopSites*>(top_sites.get())->RunACallback(urls);
+  EXPECT_TRUE(provider_->matches().empty());
+
+  history::MostVisitedURLList urls2;
+  urls2.push_back(history::MostVisitedURL(GURL("http://bar.com/"),
+                                          base::ASCIIToUTF16("Bar")));
+  urls2.push_back(history::MostVisitedURL(GURL("http://zinga.com/"),
+                                          base::ASCIIToUTF16("Zinga")));
+  provider_->Start(input, false);
+  provider_->Stop(false, false);
+  provider_->Start(input, false);
+  static_cast<FakeEmptyTopSites*>(top_sites.get())->RunACallback(urls);
+  // Stale results should get rejected.
+  EXPECT_TRUE(provider_->matches().empty());
+  static_cast<FakeEmptyTopSites*>(top_sites.get())->RunACallback(urls2);
+  EXPECT_FALSE(provider_->matches().empty());
+  provider_->Stop(false, false);
+}
+
+TEST_F(MostVisitedSitesProviderTest, TestMostVisitedNavigateToSearchPage) {
+  std::string current_url("http://www.foxnews.com/");
+  std::string input_url("http://www.cnn.com/");
+  AutocompleteInput input(base::ASCIIToUTF16(input_url),
+                          metrics::OmniboxEventProto::OTHER,
+                          TestSchemeClassifier());
+  input.set_current_url(GURL(current_url));
+  input.set_focus_type(OmniboxFocusType::ON_FOCUS);
+  history::MostVisitedURLList urls;
+  history::MostVisitedURL url(GURL("http://foo.com/"),
+                              base::ASCIIToUTF16("Foo"));
+  urls.push_back(url);
+
+  provider_->Start(input, false);
+  EXPECT_TRUE(provider_->matches().empty());
+  // Stop() doesn't always get called.
+
+  std::string search_url("https://www.google.com/?q=flowers");
+  AutocompleteInput srp_input(
+      base::ASCIIToUTF16(search_url),
+      metrics::OmniboxEventProto::SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT,
+      TestSchemeClassifier());
+  srp_input.set_current_url(GURL(search_url));
+  srp_input.set_focus_type(OmniboxFocusType::ON_FOCUS);
+
+  provider_->Start(srp_input, false);
+  EXPECT_TRUE(provider_->matches().empty());
+  // Most visited results arriving after a new request has been started.
+  scoped_refptr<history::TopSites> top_sites = client_->GetTopSites();
+  static_cast<FakeEmptyTopSites*>(top_sites.get())->RunACallback(urls);
+  EXPECT_TRUE(provider_->matches().empty());
+}
diff --git a/components/omnibox/browser/zero_suggest_provider.cc b/components/omnibox/browser/zero_suggest_provider.cc
index aff58eb..fa6e734a 100644
--- a/components/omnibox/browser/zero_suggest_provider.cc
+++ b/components/omnibox/browser/zero_suggest_provider.cc
@@ -91,9 +91,6 @@
 
 // Relevance value to use if it was not set explicitly by the server.
 const int kDefaultZeroSuggestRelevance = 100;
-// The relevance score for navsuggest tiles.
-// Navsuggest tiles should be positioned below the Query Tiles object.
-const int kMostVisitedTilesRelevance = 1500;
 
 // Used for testing whether zero suggest is ever available.
 constexpr char kArbitraryInsecureUrlString[] = "http://www.google.com/";
@@ -183,21 +180,6 @@
 
   MaybeUseCachedSuggestions();
 
-  if (result_type_running_ == MOST_VISITED) {
-    most_visited_urls_.clear();
-    scoped_refptr<history::TopSites> ts = client()->GetTopSites();
-    if (!ts) {
-      done_ = true;
-      result_type_running_ = NONE;
-      return;
-    }
-
-    ts->GetMostVisitedURLs(base::BindRepeating(
-        &ZeroSuggestProvider::OnMostVisitedUrlsAvailable,
-        weak_ptr_factory_.GetWeakPtr(), most_visited_request_num_));
-    return;
-  }
-
   search_terms_args.current_page_url =
       result_type_running_ == REMOTE_SEND_URL ? current_query_ : std::string();
   // Create a request for suggestions, routing completion to
@@ -224,7 +206,6 @@
   // the TopSites::GetMostVisitedURLs request.
   done_ = true;
   result_type_running_ = NONE;
-  ++most_visited_request_num_;
 
   if (clear_cached_results) {
     // We do not call Clear() on |results_| to retain |verbatim_relevance|
@@ -237,7 +218,6 @@
     results_.headers_map.clear();
     current_query_.clear();
     current_title_.clear();
-    most_visited_urls_.clear();
   }
 }
 
@@ -259,9 +239,7 @@
 
 void ZeroSuggestProvider::AddProviderInfo(ProvidersInfo* provider_info) const {
   BaseSearchProvider::AddProviderInfo(provider_info);
-  if (!results_.suggest_results.empty() ||
-      !results_.navigation_results.empty() ||
-      !most_visited_urls_.empty())
+  if (!results_.suggest_results.empty() || !results_.navigation_results.empty())
     provider_info->back().set_times_returned_results_in_session(1);
 }
 
@@ -352,7 +330,6 @@
   loader_.reset();
   done_ = true;
   result_type_running_ = NONE;
-  ++most_visited_request_num_;
   listener_->OnProviderUpdate(results_updated);
 }
 
@@ -415,21 +392,6 @@
   return match;
 }
 
-void ZeroSuggestProvider::OnMostVisitedUrlsAvailable(
-    size_t orig_request_num,
-    const history::MostVisitedURLList& urls) {
-  if (result_type_running_ != MOST_VISITED ||
-      orig_request_num != most_visited_request_num_) {
-    return;
-  }
-  most_visited_urls_ = urls;
-  done_ = true;
-  ConvertResultsToAutocompleteMatches();
-  result_type_running_ = NONE;
-  ++most_visited_request_num_;
-  listener_->OnProviderUpdate(true);
-}
-
 void ZeroSuggestProvider::OnRemoteSuggestionsLoaderAvailable(
     std::unique_ptr<network::SimpleURLLoader> loader) {
   // RemoteSuggestionsService has already started |loader|, so here it's
@@ -468,63 +430,9 @@
   UMA_HISTOGRAM_COUNTS_1M("ZeroSuggest.URLResults", num_nav_results);
   UMA_HISTOGRAM_COUNTS_1M("ZeroSuggest.AllResults", num_results);
 
-  // Show Most Visited results after ZeroSuggest response is received.
-  if (result_type_running_ == MOST_VISITED) {
-    // Ensure we don't show most visited URL suggestions on NTP.
-    // This allows us to prevent undesired side outcome of presenting
-    // URL suggestions to users who are not in the personalized field trial for
-    // zero query suggestions.
-    if (IsNTPPage(current_page_classification_) ||
-        !current_text_match_.destination_url.is_valid()) {
-      return;
-    }
-    matches_.push_back(current_text_match_);
-
-    // Short-circuit in case we have no MOST_VISITED urls to show.
-    if (most_visited_urls_.empty())
-      return;
-
-    if (base::FeatureList::IsEnabled(omnibox::kMostVisitedTiles)) {
-      AutocompleteMatch match =
-          NavigationToMatch(SearchSuggestionParser::NavigationResult(
-              client()->GetSchemeClassifier(), GURL::EmptyGURL(),
-              AutocompleteMatchType::TILE_NAVSUGGEST, {}, base::string16(),
-              std::string(), false, kMostVisitedTilesRelevance, true,
-              base::ASCIIToUTF16(current_query_)));
-      match.navsuggest_tiles.reserve(most_visited_urls_.size());
-
-      for (const auto& url : most_visited_urls_) {
-        match.navsuggest_tiles.push_back({url.url, url.title});
-      }
-      matches_.push_back(std::move(match));
-    } else {
-      int relevance = 600;
-      for (const auto& url : most_visited_urls_) {
-        SearchSuggestionParser::NavigationResult nav(
-            client()->GetSchemeClassifier(), url.url,
-            AutocompleteMatchType::NAVSUGGEST, {}, url.title, std::string(),
-            false, relevance, true, base::ASCIIToUTF16(current_query_));
-        matches_.push_back(NavigationToMatch(nav));
-        --relevance;
-      }
-    }
-    return;
-  }
-
   if (num_results == 0)
     return;
 
-#if defined(OS_ANDROID) || defined(OS_IOS)
-  // Android needs the verbatim match on non-NTP surfaces to properly present
-  // the Search Ready Omnibox URL edit widget. Desktop specifically does NOT
-  // want to show verbatim matches in remotely-fetched ZeroSuggest anymore.
-  // iOS we are keeping the same as Android for now. No strong reason to change.
-  if (!IsNTPPage(current_page_classification_) &&
-      current_text_match_.destination_url.is_valid()) {
-    matches_.push_back(current_text_match_);
-  }
-#endif
-
   for (MatchMap::const_iterator it(map.begin()); it != map.end(); ++it)
     matches_.push_back(it->second);
 
@@ -713,12 +621,5 @@
   if (IsNTPPage(current_page_classification) && remote_no_url_allowed)
     return REMOTE_NO_URL;
 
-#if defined(OS_ANDROID) || defined(OS_IOS)
-  // For Android and iOS, default to MOST_VISITED everywhere except on the SERP.
-  if (!IsSearchResultsPage(current_page_classification)) {
-    return MOST_VISITED;
-  }
-#endif
-
   return NONE;
 }
diff --git a/components/omnibox/browser/zero_suggest_provider.h b/components/omnibox/browser/zero_suggest_provider.h
index 5de609f1c..8ad9de6 100644
--- a/components/omnibox/browser/zero_suggest_provider.h
+++ b/components/omnibox/browser/zero_suggest_provider.h
@@ -55,9 +55,6 @@
     // suggestions. The endpoint is sent the user's authentication state and
     // the current URL.
     REMOTE_SEND_URL,
-
-    // Gets the most visited sites from local history.
-    MOST_VISITED,
   };
 
   // Creates and returns an instance of this provider.
@@ -152,12 +149,6 @@
   // page.
   AutocompleteMatch MatchForCurrentText();
 
-  // When the user is in the Most Visited field trial, we ask the TopSites
-  // service for the most visited URLs. It then calls back to this function to
-  // return those |urls|.
-  void OnMostVisitedUrlsAvailable(size_t request_num,
-                                  const history::MostVisitedURLList& urls);
-
   // When the user is in the remote omnibox suggestions field trial, we ask
   // the RemoteSuggestionsService for a loader to retrieve recommendations.
   // When the loader has started, the remote suggestion service then calls
@@ -191,9 +182,6 @@
   // When the provider is not running, the result type is set to NONE.
   ResultType result_type_running_;
 
-  // For reconciling asynchronous requests for most visited URLs.
-  size_t most_visited_request_num_ = 0;
-
   // The URL for which a suggestion fetch is pending.
   std::string current_query_;
 
@@ -218,8 +206,6 @@
   // the response for the most recent zero suggest input URL.
   SearchSuggestionParser::Results results_;
 
-  history::MostVisitedURLList most_visited_urls_;
-
   // For callbacks that may be run after destruction.
   base::WeakPtrFactory<ZeroSuggestProvider> weak_ptr_factory_{this};
 };
diff --git a/components/omnibox/browser/zero_suggest_provider_unittest.cc b/components/omnibox/browser/zero_suggest_provider_unittest.cc
index 11bf7fe..f698683 100644
--- a/components/omnibox/browser/zero_suggest_provider_unittest.cc
+++ b/components/omnibox/browser/zero_suggest_provider_unittest.cc
@@ -36,59 +36,10 @@
 #include "third_party/metrics_proto/omnibox_event.pb.h"
 
 namespace {
-class FakeEmptyTopSites : public history::TopSites {
- public:
-  FakeEmptyTopSites() {
-  }
-  FakeEmptyTopSites(const FakeEmptyTopSites&) = delete;
-  FakeEmptyTopSites& operator=(const FakeEmptyTopSites&) = delete;
-
-  // history::TopSites:
-  void GetMostVisitedURLs(GetMostVisitedURLsCallback callback) override;
-  void SyncWithHistory() override {}
-  bool HasBlockedUrls() const override { return false; }
-  void AddBlockedUrl(const GURL& url) override {}
-  void RemoveBlockedUrl(const GURL& url) override {}
-  bool IsBlocked(const GURL& url) override { return false; }
-  void ClearBlockedUrls() override {}
-  bool IsFull() override { return false; }
-  bool loaded() const override {
-    return false;
-  }
-  history::PrepopulatedPageList GetPrepopulatedPages() override {
-    return history::PrepopulatedPageList();
-  }
-  void OnNavigationCommitted(const GURL& url) override {}
-
-  // RefcountedKeyedService:
-  void ShutdownOnUIThread() override {}
-
-  // Only runs a single callback, so that the test can specify a different
-  // set per call.
-  void RunACallback(const history::MostVisitedURLList& urls) {
-    DCHECK(!callbacks.empty());
-    std::move(callbacks.front()).Run(urls);
-    callbacks.pop_front();
-  }
-
- protected:
-  // A test-specific field for controlling when most visited callback is run
-  // after top sites have been requested.
-  std::list<GetMostVisitedURLsCallback> callbacks;
-
-  ~FakeEmptyTopSites() override {}
-};
-
-void FakeEmptyTopSites::GetMostVisitedURLs(
-    GetMostVisitedURLsCallback callback) {
-  callbacks.push_back(std::move(callback));
-}
-
 class FakeAutocompleteProviderClient : public MockAutocompleteProviderClient {
  public:
   FakeAutocompleteProviderClient()
-      : template_url_service_(new TemplateURLService(nullptr, 0)),
-        top_sites_(new FakeEmptyTopSites()) {
+      : template_url_service_(new TemplateURLService(nullptr, 0)) {
     pref_service_.registry()->RegisterStringPref(
         omnibox::kZeroSuggestCachedResults, std::string());
   }
@@ -99,8 +50,6 @@
 
   bool SearchSuggestEnabled() const override { return true; }
 
-  scoped_refptr<history::TopSites> GetTopSites() override { return top_sites_; }
-
   TemplateURLService* GetTemplateURLService() override {
     return template_url_service_.get();
   }
@@ -131,11 +80,9 @@
 
  private:
   std::unique_ptr<TemplateURLService> template_url_service_;
-  scoped_refptr<history::TopSites> top_sites_;
   TestingPrefServiceSimple pref_service_;
   TestSchemeClassifier scheme_classifier_;
 };
-
 }  // namespace
 
 class ZeroSuggestProviderTest : public testing::Test,
@@ -222,9 +169,6 @@
   // ZeroSuggest should never deal with prefix suggestions.
   EXPECT_FALSE(provider_->AllowZeroSuggestSuggestions(prefix_input));
 
-  // This should always be true, as otherwise we will break MostVisited.
-  // TODO(tommycli): We should split this into its own provider to avoid
-  // breaking it again.
   EXPECT_TRUE(provider_->AllowZeroSuggestSuggestions(on_focus_input));
 
   EXPECT_FALSE(provider_->AllowZeroSuggestSuggestions(on_clobber_input));
@@ -263,22 +207,11 @@
         GURL suggest_url = GetSuggestURL(current_page_classification);
         const auto result_type = ZeroSuggestProvider::TypeOfResultToRun(
             client_.get(), input, suggest_url);
-#if !defined(OS_ANDROID) && !defined(OS_IOS)  // Desktop
         EXPECT_EQ(BaseSearchProvider::IsNTPPage(current_page_classification) &&
                           remote_no_url_allowed
                       ? ZeroSuggestProvider::ResultType::REMOTE_NO_URL
                       : ZeroSuggestProvider::ResultType::NONE,
                   result_type);
-#else                                         // Android and iOS
-        EXPECT_EQ(BaseSearchProvider::IsNTPPage(current_page_classification) &&
-                          remote_no_url_allowed
-                      ? ZeroSuggestProvider::ResultType::REMOTE_NO_URL
-                      : !BaseSearchProvider::IsSearchResultsPage(
-                            current_page_classification)
-                            ? ZeroSuggestProvider::ResultType::MOST_VISITED
-                            : ZeroSuggestProvider::ResultType::NONE,
-                  result_type);
-#endif
       };
 
   // Verify OTHER defaults (contextual web).
@@ -352,13 +285,8 @@
   on_clobber_input.set_current_url(GURL(input_url));
   on_clobber_input.set_focus_type(OmniboxFocusType::DELETED_PERMANENT_TEXT);
 
-#if defined(OS_ANDROID) || defined(OS_IOS)
-  const ZeroSuggestProvider::ResultType kDefaultContextualWebResultType =
-      ZeroSuggestProvider::ResultType::MOST_VISITED;
-#else
   const ZeroSuggestProvider::ResultType kDefaultContextualWebResultType =
       ZeroSuggestProvider::ResultType::NONE;
-#endif
 
   EXPECT_EQ(kDefaultContextualWebResultType,
             ZeroSuggestProvider::TypeOfResultToRun(
@@ -455,87 +383,6 @@
   EXPECT_TRUE(provider_->done_);
 }
 
-// MostVisited in only ever enabled on Mobile platforms.
-#if defined(OS_IOS) || defined(OS_ANDROID)
-TEST_F(ZeroSuggestProviderTest, TestMostVisitedCallback) {
-  std::string current_url("http://www.foxnews.com/");
-  std::string input_url("http://www.cnn.com/");
-  AutocompleteInput input(base::ASCIIToUTF16(input_url),
-                          metrics::OmniboxEventProto::OTHER,
-                          TestSchemeClassifier());
-  input.set_current_url(GURL(current_url));
-  input.set_focus_type(OmniboxFocusType::ON_FOCUS);
-  history::MostVisitedURLList urls;
-  history::MostVisitedURL url(GURL("http://foo.com/"),
-                              base::ASCIIToUTF16("Foo"));
-  urls.push_back(url);
-
-  provider_->Start(input, false);
-  EXPECT_TRUE(provider_->matches().empty());
-  scoped_refptr<history::TopSites> top_sites = client_->GetTopSites();
-  static_cast<FakeEmptyTopSites*>(top_sites.get())->RunACallback(urls);
-  // Should have verbatim match + most visited url match.
-  EXPECT_EQ(2U, provider_->matches().size());
-  provider_->Stop(false, false);
-
-  provider_->Start(input, false);
-  provider_->Stop(false, false);
-  EXPECT_TRUE(provider_->matches().empty());
-  // Most visited results arriving after Stop() has been called, ensure they
-  // are not displayed.
-  static_cast<FakeEmptyTopSites*>(top_sites.get())->RunACallback(urls);
-  EXPECT_TRUE(provider_->matches().empty());
-
-  history::MostVisitedURLList urls2;
-  urls2.push_back(history::MostVisitedURL(GURL("http://bar.com/"),
-                                          base::ASCIIToUTF16("Bar")));
-  urls2.push_back(history::MostVisitedURL(GURL("http://zinga.com/"),
-                                          base::ASCIIToUTF16("Zinga")));
-  provider_->Start(input, false);
-  provider_->Stop(false, false);
-  provider_->Start(input, false);
-  static_cast<FakeEmptyTopSites*>(top_sites.get())->RunACallback(urls);
-  // Stale results should get rejected.
-  EXPECT_TRUE(provider_->matches().empty());
-  static_cast<FakeEmptyTopSites*>(top_sites.get())->RunACallback(urls2);
-  EXPECT_FALSE(provider_->matches().empty());
-  provider_->Stop(false, false);
-}
-
-TEST_F(ZeroSuggestProviderTest, TestMostVisitedNavigateToSearchPage) {
-  std::string current_url("http://www.foxnews.com/");
-  std::string input_url("http://www.cnn.com/");
-  AutocompleteInput input(base::ASCIIToUTF16(input_url),
-                          metrics::OmniboxEventProto::OTHER,
-                          TestSchemeClassifier());
-  input.set_current_url(GURL(current_url));
-  input.set_focus_type(OmniboxFocusType::ON_FOCUS);
-  history::MostVisitedURLList urls;
-  history::MostVisitedURL url(GURL("http://foo.com/"),
-                              base::ASCIIToUTF16("Foo"));
-  urls.push_back(url);
-
-  provider_->Start(input, false);
-  EXPECT_TRUE(provider_->matches().empty());
-  // Stop() doesn't always get called.
-
-  std::string search_url("https://www.google.com/?q=flowers");
-  AutocompleteInput srp_input(
-      base::ASCIIToUTF16(search_url),
-      metrics::OmniboxEventProto::SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT,
-      TestSchemeClassifier());
-  srp_input.set_current_url(GURL(search_url));
-  srp_input.set_focus_type(OmniboxFocusType::ON_FOCUS);
-
-  provider_->Start(srp_input, false);
-  EXPECT_TRUE(provider_->matches().empty());
-  // Most visited results arriving after a new request has been started.
-  scoped_refptr<history::TopSites> top_sites = client_->GetTopSites();
-  static_cast<FakeEmptyTopSites*>(top_sites.get())->RunACallback(urls);
-  EXPECT_TRUE(provider_->matches().empty());
-}
-#endif  // defined(OS_IOS) || defined(OS_ANDROID)
-
 TEST_F(ZeroSuggestProviderTest, TestPsuggestZeroSuggestCachingFirstRun) {
   EXPECT_CALL(*client_, IsAuthenticated())
       .WillRepeatedly(testing::Return(true));
diff --git a/components/omnibox/browser/zero_suggest_verbatim_match_provider.cc b/components/omnibox/browser/zero_suggest_verbatim_match_provider.cc
index c1c151a..5ab5ce6 100644
--- a/components/omnibox/browser/zero_suggest_verbatim_match_provider.cc
+++ b/components/omnibox/browser/zero_suggest_verbatim_match_provider.cc
@@ -4,9 +4,11 @@
 
 #include "components/omnibox/browser/zero_suggest_verbatim_match_provider.h"
 
+#include "base/feature_list.h"
 #include "components/omnibox/browser/autocomplete_provider_client.h"
 #include "components/omnibox/browser/autocomplete_provider_listener.h"
 #include "components/omnibox/browser/verbatim_match.h"
+#include "components/omnibox/common/omnibox_features.h"
 
 namespace {
 // The relevance score for verbatim match.
@@ -23,6 +25,8 @@
         SEARCH_RESULT_PAGE_DOING_SEARCH_TERM_REPLACEMENT:
     case metrics::OmniboxEventProto::
         SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT:
+      return base::FeatureList::IsEnabled(
+          omnibox::kOmniboxSearchReadyIncognito);
     case metrics::OmniboxEventProto::OTHER:
       return true;
     default:
@@ -34,7 +38,7 @@
 
 ZeroSuggestVerbatimMatchProvider::ZeroSuggestVerbatimMatchProvider(
     AutocompleteProviderClient* client)
-    : AutocompleteProvider(TYPE_ZERO_SUGGEST), client_(client) {}
+    : AutocompleteProvider(TYPE_VERBATIM_MATCH), client_(client) {}
 
 ZeroSuggestVerbatimMatchProvider::~ZeroSuggestVerbatimMatchProvider() = default;
 
@@ -46,21 +50,28 @@
 
   // Only offer verbatim match after the user just focused the Omnibox,
   // or if the input field is empty.
-  if (!((input.focus_type() == OmniboxFocusType::ON_FOCUS) ||
-        (input.type() == metrics::OmniboxInputType::EMPTY))) {
+  if (input.focus_type() == OmniboxFocusType::DEFAULT)
+    return;
+
+  // For consistency with other zero-prefix providers.
+  const auto& page_url = input.current_url();
+  if (input.type() != metrics::OmniboxInputType::EMPTY &&
+      !(page_url.is_valid() &&
+        ((page_url.scheme() == url::kHttpScheme) ||
+         (page_url.scheme() == url::kHttpsScheme) ||
+         (page_url.scheme() == url::kAboutScheme) ||
+         (page_url.scheme() ==
+          client_->GetEmbedderRepresentationOfAboutScheme())))) {
     return;
   }
-  // Do not offer verbatim match, if the Omnibox does not contain a valid URL.
-  if (!input.current_url().is_valid())
-    return;
 
   AutocompleteInput verbatim_input = input;
   verbatim_input.set_prevent_inline_autocomplete(true);
   verbatim_input.set_allow_exact_keyword_match(false);
 
   AutocompleteMatch match = VerbatimMatchForURL(
-      client_, verbatim_input, input.current_url(), input.current_title(),
-      nullptr, kVerbatimMatchRelevanceScore);
+      client_, verbatim_input, page_url, input.current_title(), nullptr,
+      kVerbatimMatchRelevanceScore);
 
   // In the case of native pages, the classifier may replace the URL with an
   // empty content, resulting with a verbatim match that does not point
@@ -75,4 +86,4 @@
 void ZeroSuggestVerbatimMatchProvider::Stop(bool clear_cached_results,
                                             bool due_to_user_inactivity) {
   matches_.clear();
-}
\ No newline at end of file
+}
diff --git a/components/omnibox/browser/zero_suggest_verbatim_match_provider_unittest.cc b/components/omnibox/browser/zero_suggest_verbatim_match_provider_unittest.cc
index 121dfd3..b7c99573 100644
--- a/components/omnibox/browser/zero_suggest_verbatim_match_provider_unittest.cc
+++ b/components/omnibox/browser/zero_suggest_verbatim_match_provider_unittest.cc
@@ -9,9 +9,11 @@
 #include <memory>
 #include <string>
 
+#include "base/feature_list.h"
 #include "base/strings/utf_string_conversions.h"
 #include "components/omnibox/browser/mock_autocomplete_provider_client.h"
 #include "components/omnibox/browser/test_scheme_classifier.h"
+#include "components/omnibox/common/omnibox_features.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/metrics_proto/omnibox_event.pb.h"
@@ -32,11 +34,13 @@
 bool ZeroSuggestVerbatimMatchProviderTest::IsVerbatimMatchEligible() const {
   switch (GetParam()) {
     case metrics::OmniboxEventProto::OTHER:
+      return true;
     case metrics::OmniboxEventProto::
         SEARCH_RESULT_PAGE_DOING_SEARCH_TERM_REPLACEMENT:
     case metrics::OmniboxEventProto::
         SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT:
-      return true;
+      return base::FeatureList::IsEnabled(
+          omnibox::kOmniboxSearchReadyIncognito);
     default:
       return false;
   }
diff --git a/components/payments/content/android/java/res/layout/payment_request_bottom_bar.xml b/components/payments/content/android/java/res/layout/payment_request_bottom_bar.xml
index f91344db..a3522e43 100644
--- a/components/payments/content/android/java/res/layout/payment_request_bottom_bar.xml
+++ b/components/payments/content/android/java/res/layout/payment_request_bottom_bar.xml
@@ -21,14 +21,12 @@
         android:id="@+id/logo_name"
         android:layout_width="72dp"
         android:layout_height="20dp"
-        android:src="@drawable/product_logo_name"
         tools:ignore="ContentDescription" />
 
     <ImageView
         android:id="@+id/logo"
         android:layout_width="20dp"
         android:layout_height="20dp"
-        android:src="@drawable/fre_product_logo"
         tools:ignore="ContentDescription" />
 
     <Space
diff --git a/components/performance_manager/execution_context/execution_context_registry_impl.cc b/components/performance_manager/execution_context/execution_context_registry_impl.cc
index aba57b47..c3d7d85 100644
--- a/components/performance_manager/execution_context/execution_context_registry_impl.cc
+++ b/components/performance_manager/execution_context/execution_context_registry_impl.cc
@@ -80,7 +80,6 @@
 ExecutionContextRegistry::ExecutionContextRegistry() = default;
 
 ExecutionContextRegistry::~ExecutionContextRegistry() = default;
-
 // static
 ExecutionContextRegistry* ExecutionContextRegistry::GetFromGraph(Graph* graph) {
   return GraphRegisteredImpl<ExecutionContextRegistryImpl>::GetFromGraph(graph);
diff --git a/components/performance_manager/execution_context/execution_context_registry_impl.h b/components/performance_manager/execution_context/execution_context_registry_impl.h
index b7b81f1..129f29e 100644
--- a/components/performance_manager/execution_context/execution_context_registry_impl.h
+++ b/components/performance_manager/execution_context/execution_context_registry_impl.h
@@ -91,7 +91,9 @@
                      ExecutionContextKeyEqual>
       execution_contexts_;
 
-  base::ObserverList<ExecutionContextObserver, /* check_empty = */ true>
+  base::ObserverList<ExecutionContextObserver,
+                     /* check_empty = */ true,
+                     /* allow_reentrancy */ false>
       observers_;
 
   SEQUENCE_CHECKER(sequence_checker_);
diff --git a/components/performance_manager/test_support/mock_graphs.cc b/components/performance_manager/test_support/mock_graphs.cc
index f954b27..8affb17f 100644
--- a/components/performance_manager/test_support/mock_graphs.cc
+++ b/components/performance_manager/test_support/mock_graphs.cc
@@ -109,4 +109,25 @@
   worker.reset();
 }
 
+MockMultiplePagesAndWorkersWithMultipleProcessesGraph::
+    MockMultiplePagesAndWorkersWithMultipleProcessesGraph(TestGraphImpl* graph)
+    : MockMultiplePagesWithMultipleProcessesGraph(graph),
+      worker(TestNodeWrapper<WorkerNodeImpl>::Create(
+          graph,
+          WorkerNode::WorkerType::kDedicated,
+          process.get())),
+      other_worker(TestNodeWrapper<WorkerNodeImpl>::Create(
+          graph,
+          WorkerNode::WorkerType::kDedicated,
+          other_process.get())) {
+  worker->AddClientFrame(frame.get());
+  other_worker->AddClientFrame(child_frame.get());
+}
+
+MockMultiplePagesAndWorkersWithMultipleProcessesGraph::
+    ~MockMultiplePagesAndWorkersWithMultipleProcessesGraph() {
+  other_worker->RemoveClientFrame(child_frame.get());
+  worker->RemoveClientFrame(frame.get());
+}
+
 }  // namespace performance_manager
diff --git a/components/performance_manager/test_support/mock_graphs.h b/components/performance_manager/test_support/mock_graphs.h
index 05330ac..ac3ef83 100644
--- a/components/performance_manager/test_support/mock_graphs.h
+++ b/components/performance_manager/test_support/mock_graphs.h
@@ -140,6 +140,39 @@
   void DeleteWorker();
 };
 
+// The following graph topology is created to emulate a scenario where multiple
+// pages making use of workers are hosted in multiple processes (e.g.
+// out-of-process iFrames and multiple pages in a process):
+//
+//    Pg    OPg
+//    |     |
+//    F     OF
+//   /\    /  \
+//  W  \  /   CF
+//   \ | /    | \
+//     Pr     | OW
+//            | /
+//            OPr
+//
+// Where:
+// Pg: page
+// OPg: other_page
+// F: frame(frame_tree_id:0)
+// OF: other_frame(frame_tree_id:1)
+// CF: child_frame(frame_tree_id:3)
+// W: worker
+// OW: other_worker
+// Pr: process(pid:1)
+// OPr: other_process(pid:2)
+struct MockMultiplePagesAndWorkersWithMultipleProcessesGraph
+    : public MockMultiplePagesWithMultipleProcessesGraph {
+  explicit MockMultiplePagesAndWorkersWithMultipleProcessesGraph(
+      TestGraphImpl* graph);
+  ~MockMultiplePagesAndWorkersWithMultipleProcessesGraph();
+  TestNodeWrapper<WorkerNodeImpl> worker;
+  TestNodeWrapper<WorkerNodeImpl> other_worker;
+};
+
 }  // namespace performance_manager
 
 #endif  // COMPONENTS_PERFORMANCE_MANAGER_TEST_SUPPORT_MOCK_GRAPHS_H_
diff --git a/components/shared_highlighting/OWNERS b/components/shared_highlighting/OWNERS
index e8ad847..eab70ff 100644
--- a/components/shared_highlighting/OWNERS
+++ b/components/shared_highlighting/OWNERS
@@ -1,4 +1,5 @@
 sebsg@chromium.org
+seblalancette@chromium.org
 
 # COMPONENT: UI>Browser>SharedHighlighting
 # TEAM: chrome-shared-highlighting@google.com
\ No newline at end of file
diff --git a/components/translate/core/browser/BUILD.gn b/components/translate/core/browser/BUILD.gn
index 5198ffb..a9c4a78 100644
--- a/components/translate/core/browser/BUILD.gn
+++ b/components/translate/core/browser/BUILD.gn
@@ -102,6 +102,7 @@
     "translate_browser_metrics_unittest.cc",
     "translate_language_list_unittest.cc",
     "translate_manager_unittest.cc",
+    "translate_metrics_logger_impl_unittest.cc",
     "translate_prefs_unittest.cc",
     "translate_ranker_impl_unittest.cc",
     "translate_script_unittest.cc",
@@ -141,6 +142,8 @@
     "mock_translate_client.h",
     "mock_translate_driver.cc",
     "mock_translate_driver.h",
+    "mock_translate_metrics_logger.cc",
+    "mock_translate_metrics_logger.h",
     "mock_translate_ranker.cc",
     "mock_translate_ranker.h",
   ]
diff --git a/components/translate/core/browser/mock_translate_metrics_logger.cc b/components/translate/core/browser/mock_translate_metrics_logger.cc
new file mode 100644
index 0000000..e7a93eef
--- /dev/null
+++ b/components/translate/core/browser/mock_translate_metrics_logger.cc
@@ -0,0 +1,17 @@
+// 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/translate/core/browser/mock_translate_metrics_logger.h"
+
+namespace translate {
+
+namespace testing {
+
+MockTranslateMetricsLogger::MockTranslateMetricsLogger() = default;
+
+MockTranslateMetricsLogger::~MockTranslateMetricsLogger() = default;
+
+}  // namespace testing
+
+}  // namespace translate
diff --git a/components/translate/core/browser/mock_translate_metrics_logger.h b/components/translate/core/browser/mock_translate_metrics_logger.h
new file mode 100644
index 0000000..053ae9c
--- /dev/null
+++ b/components/translate/core/browser/mock_translate_metrics_logger.h
@@ -0,0 +1,35 @@
+// 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_TRANSLATE_CORE_BROWSER_MOCK_TRANSLATE_METRICS_LOGGER_H_
+#define COMPONENTS_TRANSLATE_CORE_BROWSER_MOCK_TRANSLATE_METRICS_LOGGER_H_
+
+#include "components/translate/core/browser/translate_metrics_logger.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace translate {
+
+namespace testing {
+
+class MockTranslateMetricsLogger : public TranslateMetricsLogger {
+ public:
+  MockTranslateMetricsLogger();
+  ~MockTranslateMetricsLogger() override;
+
+  MockTranslateMetricsLogger(const MockTranslateMetricsLogger&) = delete;
+  MockTranslateMetricsLogger& operator=(const MockTranslateMetricsLogger&) =
+      delete;
+
+  MOCK_METHOD1(OnPageLoadStart, void(bool));
+  MOCK_METHOD1(OnForegroundChange, void(bool));
+  MOCK_METHOD1(RecordMetrics, void(bool));
+  MOCK_METHOD2(LogRankerMetrics, void(RankerDecision, uint32_t));
+};
+
+}  // namespace testing
+
+}  // namespace translate
+
+#endif  // COMPONENTS_TRANSLATE_CORE_BROWSER_MOCK_TRANSLATE_METRICS_LOGGER_H_
diff --git a/components/translate/core/browser/mock_translate_ranker.cc b/components/translate/core/browser/mock_translate_ranker.cc
index 4e8b5107..1f14777 100644
--- a/components/translate/core/browser/mock_translate_ranker.cc
+++ b/components/translate/core/browser/mock_translate_ranker.cc
@@ -19,7 +19,8 @@
 }
 
 bool MockTranslateRanker::ShouldOfferTranslation(
-    metrics::TranslateEventProto* /*translate_event */) {
+    metrics::TranslateEventProto* /*translate_event */,
+    TranslateMetricsLogger* /*translate_metrics_logger*/) {
   return should_offer_translation_;
 }
 
diff --git a/components/translate/core/browser/mock_translate_ranker.h b/components/translate/core/browser/mock_translate_ranker.h
index f7ecb6c..6fd1e76 100644
--- a/components/translate/core/browser/mock_translate_ranker.h
+++ b/components/translate/core/browser/mock_translate_ranker.h
@@ -44,7 +44,8 @@
     is_logging_enabled_ = logging_enabled;
   }
   bool ShouldOfferTranslation(
-      metrics::TranslateEventProto* translate_events) override;
+      metrics::TranslateEventProto* translate_events,
+      TranslateMetricsLogger* translate_metrics_logger) override;
   void FlushTranslateEvents(
       std::vector<metrics::TranslateEventProto>* events) override;
   MOCK_METHOD3(RecordTranslateEvent,
diff --git a/components/translate/core/browser/translate_manager.cc b/components/translate/core/browser/translate_manager.cc
index 9d4b78b..8d512c8 100644
--- a/components/translate/core/browser/translate_manager.cc
+++ b/components/translate/core/browser/translate_manager.cc
@@ -769,7 +769,8 @@
   // trained appropriately under those scenarios.
   if (!language::ShouldPreventRankerEnforcementInIndia(
           translate_prefs->GetForceTriggerOnEnglishPagesCount()) &&
-      !translate_ranker_->ShouldOfferTranslation(translate_event_.get())) {
+      !translate_ranker_->ShouldOfferTranslation(
+          translate_event_.get(), GetActiveTranslateMetricsLogger())) {
     decision.SuppressFromRanker();
   }
 
diff --git a/components/translate/core/browser/translate_metrics_logger.h b/components/translate/core/browser/translate_metrics_logger.h
index 7698198..4f86d3d 100644
--- a/components/translate/core/browser/translate_metrics_logger.h
+++ b/components/translate/core/browser/translate_metrics_logger.h
@@ -5,9 +5,19 @@
 #ifndef COMPONENTS_TRANSLATE_CORE_BROWSER_TRANSLATE_METRICS_LOGGER_H_
 #define COMPONENTS_TRANSLATE_CORE_BROWSER_TRANSLATE_METRICS_LOGGER_H_
 
+#include <stdint.h>
+
 namespace translate {
 
-class TranslateManager;
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum class RankerDecision {
+  kUninitialized = 0,
+  kNotQueried = 1,
+  kShowUI = 2,
+  kDontShowUI = 3,
+  kMaxValue = kDontShowUI,
+};
 
 // TranslateMetricsLogger tracks and logs various UKM and UMA metrics for Chrome
 // Translate over the course of a page load.
@@ -27,10 +37,8 @@
   // won't be called again.
   virtual void RecordMetrics(bool is_final) = 0;
 
-  // TODO(curranmax): Split this into two interfaces. One for the interaction
-  // with the |TranslatePageLoadMetricsObserver|, and the other for the
-  // interaction with |TranslateManager| and the rest of the Translate code.
-  // https://crbug.com/1114868.
+  virtual void LogRankerMetrics(RankerDecision ranker_decision,
+                                uint32_t ranker_version) = 0;
 };
 
 }  // namespace translate
diff --git a/components/translate/core/browser/translate_metrics_logger_impl.cc b/components/translate/core/browser/translate_metrics_logger_impl.cc
index 30e2f9c..7351f6a 100644
--- a/components/translate/core/browser/translate_metrics_logger_impl.cc
+++ b/components/translate/core/browser/translate_metrics_logger_impl.cc
@@ -4,10 +4,16 @@
 
 #include "components/translate/core/browser/translate_metrics_logger_impl.h"
 
+#include "base/metrics/histogram_functions.h"
 #include "components/translate/core/browser/translate_manager.h"
 
 namespace translate {
 
+const char kTranslatePageLoadRankerDecision[] =
+    "Translate.PageLoad.Ranker.Decision";
+const char kTranslatePageLoadRankerVersion[] =
+    "Translate.PageLoad.Ranker.Version";
+
 TranslateMetricsLoggerImpl::TranslateMetricsLoggerImpl(
     base::WeakPtr<TranslateManager> translate_manager)
     : translate_manager_(translate_manager) {}
@@ -27,10 +33,29 @@
 }
 
 void TranslateMetricsLoggerImpl::RecordMetrics(bool is_final) {
-  // TODO(curranmax): Log UKM and UMA metrics now that the page load is.
+  // The first time |RecordMetrics| is called, record all page load frequency
+  // UMA metrcis.
+  if (sequence_no_ == 0)
+    RecordPageLoadUmaMetrics();
+
+  // TODO(curranmax): Log UKM metrics now that the page load is.
   // completed. https://crbug.com/1114868.
 
   sequence_no_++;
 }
 
+void TranslateMetricsLoggerImpl::RecordPageLoadUmaMetrics() {
+  base::UmaHistogramEnumeration(kTranslatePageLoadRankerDecision,
+                                ranker_decision_);
+  base::UmaHistogramSparse(kTranslatePageLoadRankerVersion,
+                           int(ranker_version_));
+}
+
+void TranslateMetricsLoggerImpl::LogRankerMetrics(
+    RankerDecision ranker_decision,
+    uint32_t ranker_version) {
+  ranker_decision_ = ranker_decision;
+  ranker_version_ = ranker_version;
+}
+
 }  // namespace translate
diff --git a/components/translate/core/browser/translate_metrics_logger_impl.h b/components/translate/core/browser/translate_metrics_logger_impl.h
index 5717b370..742b7d85 100644
--- a/components/translate/core/browser/translate_metrics_logger_impl.h
+++ b/components/translate/core/browser/translate_metrics_logger_impl.h
@@ -12,6 +12,9 @@
 
 namespace translate {
 
+extern const char kTranslatePageLoadRankerDecision[];
+extern const char kTranslatePageLoadRankerVersion[];
+
 class NullTranslateMetricsLogger : public TranslateMetricsLogger {
  public:
   NullTranslateMetricsLogger() = default;
@@ -20,6 +23,8 @@
   void OnPageLoadStart(bool is_foreground) override {}
   void OnForegroundChange(bool is_foreground) override {}
   void RecordMetrics(bool is_final) override {}
+  void LogRankerMetrics(RankerDecision ranker_decision,
+                        uint32_t ranker_version) override {}
 };
 
 class TranslateManager;
@@ -40,10 +45,15 @@
   void OnPageLoadStart(bool is_foreground) override;
   void OnForegroundChange(bool is_foreground) override;
   void RecordMetrics(bool is_final) override;
+  void LogRankerMetrics(RankerDecision ranker_decision,
+                        uint32_t ranker_version) override;
 
   // TODO(curranmax): Add appropriate functions for the Translate code to log
   // relevant events. https://crbug.com/1114868.
  private:
+  // Logs all page load frequency UMA metrics based on the stored state.
+  void RecordPageLoadUmaMetrics();
+
   base::WeakPtr<TranslateManager> translate_manager_;
 
   // Since |RecordMetrics()| can be called multiple times, such as when Chrome
@@ -55,6 +65,10 @@
   // background (|false|)
   bool is_foreground_{false};
 
+  // Stores state about TranslateRanker for this page load.
+  RankerDecision ranker_decision_{RankerDecision::kUninitialized};
+  uint32_t ranker_version_{0};
+
   base::WeakPtrFactory<TranslateMetricsLoggerImpl> weak_method_factory_{this};
 };
 
diff --git a/components/translate/core/browser/translate_metrics_logger_impl_unittest.cc b/components/translate/core/browser/translate_metrics_logger_impl_unittest.cc
new file mode 100644
index 0000000..01ebd46
--- /dev/null
+++ b/components/translate/core/browser/translate_metrics_logger_impl_unittest.cc
@@ -0,0 +1,75 @@
+// 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/translate/core/browser/translate_metrics_logger_impl.h"
+
+#include <memory>
+
+#include "base/logging.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class TranslateMetricsLoggerImplTest : public testing::Test {
+ public:
+  void SetUp() override {
+    translate_metrics_logger_ =
+        std::make_unique<translate::TranslateMetricsLoggerImpl>(
+            nullptr /*translate_manager*/);
+
+    histogram_tester_ = std::make_unique<base::HistogramTester>();
+  }
+
+  translate::TranslateMetricsLoggerImpl* translate_metrics_logger() {
+    return translate_metrics_logger_.get();
+  }
+
+  base::HistogramTester* histogram_tester() { return histogram_tester_.get(); }
+
+ private:
+  // Test target.
+  std::unique_ptr<translate::TranslateMetricsLoggerImpl>
+      translate_metrics_logger_;
+
+  // Records the UMA histograms for each test.
+  std::unique_ptr<base::HistogramTester> histogram_tester_;
+};
+
+TEST_F(TranslateMetricsLoggerImplTest, MultipleRecordMetrics) {
+  // Set test constants and log them with the test target.
+  translate::RankerDecision ranker_decision =
+      translate::RankerDecision::kShowUI;
+  uint32_t ranker_model_version = 1234;
+
+  translate_metrics_logger()->LogRankerMetrics(ranker_decision,
+                                               ranker_model_version);
+
+  // Simulate |RecordMetrics| being called multiple times.
+  translate_metrics_logger()->RecordMetrics(false);
+  translate_metrics_logger()->RecordMetrics(false);
+  translate_metrics_logger()->RecordMetrics(true);
+
+  // The page-load UMA metrics should only be logged when the first
+  // |RecordMetrics| is called. Subsequent calls shouldn't cause UMA metrics to
+  // be logged.
+  histogram_tester()->ExpectUniqueSample(
+      translate::kTranslatePageLoadRankerDecision, ranker_decision, 1);
+  histogram_tester()->ExpectUniqueSample(
+      translate::kTranslatePageLoadRankerVersion, ranker_model_version, 1);
+}
+
+TEST_F(TranslateMetricsLoggerImplTest, LogRankerMetrics) {
+  translate::RankerDecision ranker_decision =
+      translate::RankerDecision::kDontShowUI;
+  uint32_t ranker_model_version = 4321;
+
+  translate_metrics_logger()->LogRankerMetrics(ranker_decision,
+                                               ranker_model_version);
+
+  translate_metrics_logger()->RecordMetrics(true);
+
+  histogram_tester()->ExpectUniqueSample(
+      translate::kTranslatePageLoadRankerDecision, ranker_decision, 1);
+  histogram_tester()->ExpectUniqueSample(
+      translate::kTranslatePageLoadRankerVersion, ranker_model_version, 1);
+}
diff --git a/components/translate/core/browser/translate_ranker.h b/components/translate/core/browser/translate_ranker.h
index 4b62b1c0..aa7144c 100644
--- a/components/translate/core/browser/translate_ranker.h
+++ b/components/translate/core/browser/translate_ranker.h
@@ -19,6 +19,8 @@
 
 namespace translate {
 
+class TranslateMetricsLogger;
+
 // If enabled, downloads a translate ranker model and uses it to determine
 // whether the user should be given a translation prompt or not.
 class TranslateRanker : public KeyedService {
@@ -33,7 +35,8 @@
   // other global browser context attributes suggests that the user should be
   // prompted as to whether translation should be performed.
   virtual bool ShouldOfferTranslation(
-      metrics::TranslateEventProto* translate_event) = 0;
+      metrics::TranslateEventProto* translate_event,
+      TranslateMetricsLogger* translate_metrics_logger) = 0;
 
   // Transfers cached translate events to the given vector pointer and clears
   // the cache.
diff --git a/components/translate/core/browser/translate_ranker_impl.cc b/components/translate/core/browser/translate_ranker_impl.cc
index 8ff6468..ffc112b 100644
--- a/components/translate/core/browser/translate_ranker_impl.cc
+++ b/components/translate/core/browser/translate_ranker_impl.cc
@@ -25,6 +25,7 @@
 #include "components/assist_ranker/ranker_model.h"
 #include "components/assist_ranker/ranker_model_loader_impl.h"
 #include "components/translate/core/browser/translate_download_manager.h"
+#include "components/translate/core/browser/translate_metrics_logger.h"
 #include "components/translate/core/common/translate_switches.h"
 #include "components/variations/variations_associated_data.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
@@ -223,7 +224,8 @@
 }
 
 bool TranslateRankerImpl::ShouldOfferTranslation(
-    metrics::TranslateEventProto* translate_event) {
+    metrics::TranslateEventProto* translate_event,
+    TranslateMetricsLogger* translate_metrics_logger) {
   DCHECK(sequence_checker_.CalledOnValidSequence());
   // The ranker is a gate in the "show a translation prompt" flow. To retain
   // the pre-existing functionality, it defaults to returning true in the
@@ -239,6 +241,8 @@
   if (!is_query_enabled_ && !is_enforcement_enabled_) {
     translate_event->set_ranker_response(
         metrics::TranslateEventProto::NOT_QUERIED);
+    translate_metrics_logger->LogRankerMetrics(RankerDecision::kNotQueried,
+                                               GetModelVersion());
     return kDefaultResponse;
   }
 
@@ -248,6 +252,8 @@
   if (model_ == nullptr) {
     translate_event->set_ranker_response(
         metrics::TranslateEventProto::NOT_QUERIED);
+    translate_metrics_logger->LogRankerMetrics(RankerDecision::kNotQueried,
+                                               GetModelVersion());
     return kDefaultResponse;
   }
 
@@ -261,6 +267,10 @@
       result ? metrics::TranslateEventProto::SHOW
              : metrics::TranslateEventProto::DONT_SHOW);
 
+  translate_metrics_logger->LogRankerMetrics(
+      result ? RankerDecision::kShowUI : RankerDecision::kDontShowUI,
+      GetModelVersion());
+
   if (!is_enforcement_enabled_) {
     return kDefaultResponse;
   }
diff --git a/components/translate/core/browser/translate_ranker_impl.h b/components/translate/core/browser/translate_ranker_impl.h
index c07ab56..14a91cd8 100644
--- a/components/translate/core/browser/translate_ranker_impl.h
+++ b/components/translate/core/browser/translate_ranker_impl.h
@@ -36,6 +36,8 @@
 
 namespace translate {
 
+class TranslateMetricsLogger;
+
 extern const char kDefaultTranslateRankerModelURL[];
 
 // Features used to enable ranker query, enforcement and logging. Note that
@@ -98,7 +100,8 @@
   void EnableLogging(bool value) override;
   uint32_t GetModelVersion() const override;
   bool ShouldOfferTranslation(
-      metrics::TranslateEventProto* translate_event) override;
+      metrics::TranslateEventProto* translate_event,
+      TranslateMetricsLogger* translate_metrics_logger) override;
   void FlushTranslateEvents(
       std::vector<metrics::TranslateEventProto>* events) override;
   void RecordTranslateEvent(
diff --git a/components/translate/core/browser/translate_ranker_impl_unittest.cc b/components/translate/core/browser/translate_ranker_impl_unittest.cc
index 59611fd6..c4e71c5 100644
--- a/components/translate/core/browser/translate_ranker_impl_unittest.cc
+++ b/components/translate/core/browser/translate_ranker_impl_unittest.cc
@@ -18,6 +18,7 @@
 #include "components/assist_ranker/proto/ranker_model.pb.h"
 #include "components/assist_ranker/proto/translate_ranker_model.pb.h"
 #include "components/assist_ranker/ranker_model.h"
+#include "components/translate/core/browser/mock_translate_metrics_logger.h"
 #include "components/ukm/test_ukm_recorder.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
 #include "services/metrics/public/cpp/ukm_source.h"
@@ -216,9 +217,24 @@
                {});
   metrics::TranslateEventProto translate_event = CreateDefaultTranslateEvent();
 
+  // Set up expectations for the |mock_translate_metrics_logger| to the below
+  // calls to |ShouldOfferTranslation|.
+  translate::testing::MockTranslateMetricsLogger mock_translate_metrics_logger;
+  testing::Sequence mock_translate_metrics_logger_sequence;
+  EXPECT_CALL(
+      mock_translate_metrics_logger,
+      LogRankerMetrics(translate::RankerDecision::kDontShowUI, kModelVersion))
+      .Times(1)
+      .InSequence(mock_translate_metrics_logger_sequence);
+  EXPECT_CALL(
+      mock_translate_metrics_logger,
+      LogRankerMetrics(translate::RankerDecision::kShowUI, kModelVersion))
+      .Times(1)
+      .InSequence(mock_translate_metrics_logger_sequence);
+
   // With a threshold of 0.99, en->fr is not over the threshold.
-  EXPECT_FALSE(
-      GetRankerForTest(0.99f)->ShouldOfferTranslation(&translate_event));
+  EXPECT_FALSE(GetRankerForTest(0.99f)->ShouldOfferTranslation(
+      &translate_event, &mock_translate_metrics_logger));
   EXPECT_NE(0U, translate_event.ranker_request_timestamp_sec());
   EXPECT_EQ(kModelVersion, translate_event.ranker_version());
   EXPECT_EQ(metrics::TranslateEventProto::DONT_SHOW,
@@ -226,8 +242,8 @@
 
   // With a threshold of 0.01, en-fr is over the threshold.
   translate_event.Clear();
-  EXPECT_TRUE(
-      GetRankerForTest(0.01f)->ShouldOfferTranslation(&translate_event));
+  EXPECT_TRUE(GetRankerForTest(0.01f)->ShouldOfferTranslation(
+      &translate_event, &mock_translate_metrics_logger));
   EXPECT_EQ(metrics::TranslateEventProto::SHOW,
             translate_event.ranker_response());
 }
@@ -235,9 +251,17 @@
   InitFeatures({}, {kTranslateRankerQuery, kTranslateRankerEnforcement,
                     kTranslateRankerPreviousLanguageMatchesOverride});
   metrics::TranslateEventProto translate_event = CreateDefaultTranslateEvent();
+
+  translate::testing::MockTranslateMetricsLogger mock_translate_metrics_logger;
+  EXPECT_CALL(
+      mock_translate_metrics_logger,
+      LogRankerMetrics(translate::RankerDecision::kNotQueried, kModelVersion))
+      .Times(1);
+
   // If query and other flags are turned off, returns true and do not query the
   // ranker.
-  EXPECT_TRUE(GetRankerForTest(0.5f)->ShouldOfferTranslation(&translate_event));
+  EXPECT_TRUE(GetRankerForTest(0.5f)->ShouldOfferTranslation(
+      &translate_event, &mock_translate_metrics_logger));
   EXPECT_NE(0U, translate_event.ranker_request_timestamp_sec());
   EXPECT_EQ(kModelVersion, translate_event.ranker_version());
   EXPECT_EQ(metrics::TranslateEventProto::NOT_QUERIED,
@@ -249,10 +273,17 @@
                {kTranslateRankerEnforcement,
                 kTranslateRankerPreviousLanguageMatchesOverride});
   metrics::TranslateEventProto translate_event = CreateDefaultTranslateEvent();
+
+  translate::testing::MockTranslateMetricsLogger mock_translate_metrics_logger;
+  EXPECT_CALL(
+      mock_translate_metrics_logger,
+      LogRankerMetrics(translate::RankerDecision::kDontShowUI, kModelVersion))
+      .Times(1);
+
   // If enforcement is turned off, returns true even if the decision
   // is not to show.
-  EXPECT_TRUE(
-      GetRankerForTest(0.99f)->ShouldOfferTranslation(&translate_event));
+  EXPECT_TRUE(GetRankerForTest(0.99f)->ShouldOfferTranslation(
+      &translate_event, &mock_translate_metrics_logger));
   EXPECT_NE(0U, translate_event.ranker_request_timestamp_sec());
   EXPECT_EQ(kModelVersion, translate_event.ranker_version());
   EXPECT_EQ(metrics::TranslateEventProto::DONT_SHOW,
@@ -264,8 +295,15 @@
                {kTranslateRankerEnforcement,
                 kTranslateRankerPreviousLanguageMatchesOverride});
   metrics::TranslateEventProto translate_event = CreateDefaultTranslateEvent();
-  EXPECT_TRUE(
-      GetRankerForTest(0.01f)->ShouldOfferTranslation(&translate_event));
+
+  translate::testing::MockTranslateMetricsLogger mock_translate_metrics_logger;
+  EXPECT_CALL(
+      mock_translate_metrics_logger,
+      LogRankerMetrics(translate::RankerDecision::kShowUI, kModelVersion))
+      .Times(1);
+
+  EXPECT_TRUE(GetRankerForTest(0.01f)->ShouldOfferTranslation(
+      &translate_event, &mock_translate_metrics_logger));
   EXPECT_NE(0U, translate_event.ranker_request_timestamp_sec());
   EXPECT_EQ(kModelVersion, translate_event.ranker_version());
   EXPECT_EQ(metrics::TranslateEventProto::SHOW,
@@ -278,9 +316,16 @@
       {kTranslateRankerEnforcement},
       {kTranslateRankerQuery, kTranslateRankerPreviousLanguageMatchesOverride});
   metrics::TranslateEventProto translate_event = CreateDefaultTranslateEvent();
+
+  translate::testing::MockTranslateMetricsLogger mock_translate_metrics_logger;
+  EXPECT_CALL(
+      mock_translate_metrics_logger,
+      LogRankerMetrics(translate::RankerDecision::kDontShowUI, kModelVersion))
+      .Times(1);
+
   // If enforcement is turned on, returns the ranker decision.
-  EXPECT_FALSE(
-      GetRankerForTest(0.99f)->ShouldOfferTranslation(&translate_event));
+  EXPECT_FALSE(GetRankerForTest(0.99f)->ShouldOfferTranslation(
+      &translate_event, &mock_translate_metrics_logger));
   EXPECT_NE(0U, translate_event.ranker_request_timestamp_sec());
   EXPECT_EQ(kModelVersion, translate_event.ranker_version());
   EXPECT_EQ(metrics::TranslateEventProto::DONT_SHOW,
@@ -292,9 +337,16 @@
       {kTranslateRankerEnforcement},
       {kTranslateRankerQuery, kTranslateRankerPreviousLanguageMatchesOverride});
   metrics::TranslateEventProto translate_event = CreateDefaultTranslateEvent();
+
+  translate::testing::MockTranslateMetricsLogger mock_translate_metrics_logger;
+  EXPECT_CALL(
+      mock_translate_metrics_logger,
+      LogRankerMetrics(translate::RankerDecision::kShowUI, kModelVersion))
+      .Times(1);
+
   // If enforcement is turned on, returns the ranker decision.
-  EXPECT_TRUE(
-      GetRankerForTest(0.01f)->ShouldOfferTranslation(&translate_event));
+  EXPECT_TRUE(GetRankerForTest(0.01f)->ShouldOfferTranslation(
+      &translate_event, &mock_translate_metrics_logger));
   EXPECT_NE(0U, translate_event.ranker_request_timestamp_sec());
   EXPECT_EQ(kModelVersion, translate_event.ranker_version());
   EXPECT_EQ(metrics::TranslateEventProto::SHOW,
@@ -306,9 +358,16 @@
                 kTranslateRankerPreviousLanguageMatchesOverride},
                {kTranslateRankerQuery});
   metrics::TranslateEventProto translate_event = CreateDefaultTranslateEvent();
+
+  translate::testing::MockTranslateMetricsLogger mock_translate_metrics_logger;
+  EXPECT_CALL(
+      mock_translate_metrics_logger,
+      LogRankerMetrics(translate::RankerDecision::kDontShowUI, kModelVersion))
+      .Times(1);
+
   // DecisionOverride will not interact with Query or Enforcement.
-  EXPECT_FALSE(
-      GetRankerForTest(0.99f)->ShouldOfferTranslation(&translate_event));
+  EXPECT_FALSE(GetRankerForTest(0.99f)->ShouldOfferTranslation(
+      &translate_event, &mock_translate_metrics_logger));
   EXPECT_NE(0U, translate_event.ranker_request_timestamp_sec());
   EXPECT_EQ(kModelVersion, translate_event.ranker_version());
   EXPECT_EQ(metrics::TranslateEventProto::DONT_SHOW,
@@ -322,8 +381,15 @@
                 kTranslateRankerQuery, kTranslateRankerEnforcement},
                {});
   metrics::TranslateEventProto translate_event = CreateDefaultTranslateEvent();
+
+  translate::testing::MockTranslateMetricsLogger mock_translate_metrics_logger;
+  EXPECT_CALL(mock_translate_metrics_logger,
+              LogRankerMetrics(translate::RankerDecision::kNotQueried, 0U))
+      .Times(1);
+
   // If we don't have a model, returns true.
-  EXPECT_TRUE(ranker->ShouldOfferTranslation(&translate_event));
+  EXPECT_TRUE(ranker->ShouldOfferTranslation(&translate_event,
+                                             &mock_translate_metrics_logger));
   EXPECT_NE(0U, translate_event.ranker_request_timestamp_sec());
   EXPECT_EQ(0U, translate_event.ranker_version());
   EXPECT_EQ(metrics::TranslateEventProto::NOT_QUERIED,
@@ -488,11 +554,18 @@
       GetRankerForTest(0.0f);
   ranker->EnableLogging(true);
   metrics::TranslateEventProto translate_event = CreateDefaultTranslateEvent();
+
+  translate::testing::MockTranslateMetricsLogger mock_translate_metrics_logger;
+  EXPECT_CALL(
+      mock_translate_metrics_logger,
+      LogRankerMetrics(translate::RankerDecision::kNotQueried, kModelVersion))
+      .Times(1);
+
   // DecisionOverride is decoupled from querying and enforcement. Enabling
   // only override will not query the Ranker. Ranker returns its default
   // response.
-  EXPECT_TRUE(
-      GetRankerForTest(0.99f)->ShouldOfferTranslation(&translate_event));
+  EXPECT_TRUE(GetRankerForTest(0.99f)->ShouldOfferTranslation(
+      &translate_event, &mock_translate_metrics_logger));
   EXPECT_TRUE(ranker->ShouldOverrideDecision(
       metrics::TranslateEventProto::LANGUAGE_DISABLED_BY_AUTO_BLACKLIST,
       kUkmSourceId0, &translate_event));
@@ -522,11 +595,18 @@
       GetRankerForTest(0.0f);
   ranker->EnableLogging(true);
   metrics::TranslateEventProto translate_event = CreateDefaultTranslateEvent();
+
+  translate::testing::MockTranslateMetricsLogger mock_translate_metrics_logger;
+  EXPECT_CALL(
+      mock_translate_metrics_logger,
+      LogRankerMetrics(translate::RankerDecision::kNotQueried, kModelVersion))
+      .Times(1);
+
   // DecisionOverride is decoupled from querying and enforcement. Enabling
   // only override will not query the Ranker. Ranker returns its default
   // response.
-  EXPECT_TRUE(
-      GetRankerForTest(0.99f)->ShouldOfferTranslation(&translate_event));
+  EXPECT_TRUE(GetRankerForTest(0.99f)->ShouldOfferTranslation(
+      &translate_event, &mock_translate_metrics_logger));
   EXPECT_TRUE(ranker->ShouldOverrideDecision(
       metrics::TranslateEventProto::MATCHES_PREVIOUS_LANGUAGE, kUkmSourceId0,
       &translate_event));
@@ -559,10 +639,17 @@
       GetRankerForTest(0.0f);
   ranker->EnableLogging(true);
   metrics::TranslateEventProto translate_event = CreateDefaultTranslateEvent();
+
+  translate::testing::MockTranslateMetricsLogger mock_translate_metrics_logger;
+  EXPECT_CALL(
+      mock_translate_metrics_logger,
+      LogRankerMetrics(translate::RankerDecision::kDontShowUI, kModelVersion))
+      .Times(1);
+
   // Ranker's decision is DONT_SHOW, but we are in query mode only, so Ranker
   // does not suppress the UI.
-  EXPECT_TRUE(
-      GetRankerForTest(0.99f)->ShouldOfferTranslation(&translate_event));
+  EXPECT_TRUE(GetRankerForTest(0.99f)->ShouldOfferTranslation(
+      &translate_event, &mock_translate_metrics_logger));
 
   EXPECT_TRUE(ranker->ShouldOverrideDecision(
       metrics::TranslateEventProto::LANGUAGE_DISABLED_BY_AUTO_BLACKLIST,
diff --git a/content/browser/cache_storage/cache_storage_cache_unittest.cc b/content/browser/cache_storage/cache_storage_cache_unittest.cc
index ce65122..7b79da4f 100644
--- a/content/browser/cache_storage/cache_storage_cache_unittest.cc
+++ b/content/browser/cache_storage/cache_storage_cache_unittest.cc
@@ -887,8 +887,8 @@
     callback_error_ = error;
     callback_strings_.clear();
     if (requests) {
-      for (size_t i = 0u; i < requests->size(); ++i)
-        callback_strings_.push_back(requests->at(i)->url.spec());
+      for (const blink::mojom::FetchAPIRequestPtr& request : *requests)
+        callback_strings_.push_back(request->url.spec());
     }
     if (run_loop)
       run_loop->Quit();
@@ -1042,7 +1042,7 @@
       BackgroundFetchSettledFetch::CloneRequest(body_request_);
   operation1->request->url = GURL("http://example.com/1");
   operation1->response = CreateBlobBodyResponse();
-  operation1->response->url_list.push_back(GURL("http://example.com/1"));
+  operation1->response->url_list.emplace_back("http://example.com/1");
   blink::mojom::FetchAPIRequestPtr request1 =
       BackgroundFetchSettledFetch::CloneRequest(operation1->request);
 
@@ -1053,7 +1053,7 @@
       BackgroundFetchSettledFetch::CloneRequest(body_request_);
   operation2->request->url = GURL("http://example.com/2");
   operation2->response = CreateBlobBodyResponse();
-  operation2->response->url_list.push_back(GURL("http://example.com/2"));
+  operation2->response->url_list.emplace_back("http://example.com/2");
   blink::mojom::FetchAPIRequestPtr request2 =
       BackgroundFetchSettledFetch::CloneRequest(operation2->request);
 
@@ -1064,7 +1064,7 @@
       BackgroundFetchSettledFetch::CloneRequest(body_request_);
   operation3->request->url = GURL("http://example.com/3");
   operation3->response = CreateBlobBodyResponse();
-  operation3->response->url_list.push_back(GURL("http://example.com/3"));
+  operation3->response->url_list.emplace_back("http://example.com/3");
   blink::mojom::FetchAPIRequestPtr request3 =
       BackgroundFetchSettledFetch::CloneRequest(operation3->request);
 
@@ -1154,7 +1154,7 @@
 TEST_P(CacheStorageCacheTestP, ResponseURLDiffersFromRequestURL) {
   blink::mojom::FetchAPIResponsePtr no_body_response = CreateNoBodyResponse();
   no_body_response->url_list.clear();
-  no_body_response->url_list.push_back(GURL("http://example.com/foobar"));
+  no_body_response->url_list.emplace_back("http://example.com/foobar");
   EXPECT_STRNE("http://example.com/foobar",
                no_body_request_->url.spec().c_str());
   EXPECT_TRUE(Put(no_body_request_, std::move(no_body_response)));
@@ -2167,7 +2167,7 @@
       cache_->GetRequiredSafeSpaceForResponse(response);
   SetQuota(safe_expected_entry_size.ValueOrDie());
 
-  response->url_list.push_back(GURL("http://example.com/another-url"));
+  response->url_list.emplace_back("http://example.com/another-url");
   EXPECT_FALSE(Put(body_request_, std::move(response)));
   EXPECT_EQ(CacheStorageError::kErrorQuotaExceeded, callback_error_);
 }
@@ -2638,7 +2638,7 @@
   operation->request = BackgroundFetchSettledFetch::CloneRequest(body_request_);
   operation->request->url = GURL("http://example.com/1");
   operation->response = CreateBlobBodyResponse();
-  operation->response->url_list.push_back(GURL("http://example.com/1"));
+  operation->response->url_list.emplace_back("http://example.com/1");
 
   std::vector<blink::mojom::BatchOperationPtr> operations;
   operations.push_back(std::move(operation));
diff --git a/content/browser/indexed_db/indexed_db_backing_store_unittest.cc b/content/browser/indexed_db/indexed_db_backing_store_unittest.cc
index c55dbcf..13dc0ac 100644
--- a/content/browser/indexed_db/indexed_db_backing_store_unittest.cc
+++ b/content/browser/indexed_db/indexed_db_backing_store_unittest.cc
@@ -1184,9 +1184,10 @@
   EXPECT_EQ(value3_.bits, read_result_value.bits);
   EXPECT_TRUE(CheckBlobInfoMatches(read_result_value.external_objects));
   EXPECT_TRUE(CheckBlobReadsMatchWrites(read_result_value.external_objects));
-  for (size_t i = 0; i < read_result_value.external_objects.size(); ++i) {
-    if (read_result_value.external_objects[i].mark_used_callback())
-      read_result_value.external_objects[i].mark_used_callback().Run();
+  for (const IndexedDBExternalObject& external_object :
+       read_result_value.external_objects) {
+    if (external_object.mark_used_callback())
+      external_object.mark_used_callback().Run();
   }
 
   std::unique_ptr<IndexedDBBackingStore::Transaction> transaction3 =
@@ -1207,9 +1208,10 @@
   EXPECT_TRUE(succeeded);
   EXPECT_TRUE(transaction3->CommitPhaseTwo().ok());
   EXPECT_EQ(0U, backing_store()->removals().size());
-  for (size_t i = 0; i < read_result_value.external_objects.size(); ++i) {
-    if (read_result_value.external_objects[i].release_callback())
-      read_result_value.external_objects[i].release_callback().Run();
+  for (const IndexedDBExternalObject& external_object :
+       read_result_value.external_objects) {
+    if (external_object.release_callback())
+      external_object.release_callback().Run();
   }
   task_environment_.RunUntilIdle();
 
diff --git a/content/browser/media/capture/desktop_capturer_lacros.cc b/content/browser/media/capture/desktop_capturer_lacros.cc
index 3827513..e5158142 100644
--- a/content/browser/media/capture/desktop_capturer_lacros.cc
+++ b/content/browser/media/capture/desktop_capturer_lacros.cc
@@ -130,6 +130,11 @@
 void DesktopCapturerLacros::CaptureFrame() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+#if DCHECK_IS_ON()
+  DCHECK(!capturing_frame_);
+  capturing_frame_ = true;
+#endif
+
   if (snapshot_capturer_) {
     snapshot_capturer_->TakeSnapshot(
         selected_source_,
@@ -162,6 +167,10 @@
                                             const SkBitmap& snapshot) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+#if DCHECK_IS_ON()
+  capturing_frame_ = false;
+#endif
+
   if (!success) {
     callback_->OnCaptureResult(Result::ERROR_PERMANENT,
                                std::unique_ptr<webrtc::DesktopFrame>());
@@ -177,6 +186,10 @@
     crosapi::Bitmap snapshot) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+#if DCHECK_IS_ON()
+  capturing_frame_ = false;
+#endif
+
   if (!success) {
     callback_->OnCaptureResult(Result::ERROR_PERMANENT,
                                std::unique_ptr<webrtc::DesktopFrame>());
diff --git a/content/browser/media/capture/desktop_capturer_lacros.h b/content/browser/media/capture/desktop_capturer_lacros.h
index e81fe49..b578fd9b 100644
--- a/content/browser/media/capture/desktop_capturer_lacros.h
+++ b/content/browser/media/capture/desktop_capturer_lacros.h
@@ -80,6 +80,10 @@
   // the deprecated methods.
   mojo::Remote<crosapi::mojom::SnapshotCapturer> snapshot_capturer_;
 
+#if DCHECK_IS_ON()
+  bool capturing_frame_ = false;
+#endif
+
   base::WeakPtrFactory<DesktopCapturerLacros> weak_factory_{this};
 };
 
diff --git a/content/browser/renderer_host/render_frame_host_delegate.cc b/content/browser/renderer_host/render_frame_host_delegate.cc
index f28463e..9cd1caa9 100644
--- a/content/browser/renderer_host/render_frame_host_delegate.cc
+++ b/content/browser/renderer_host/render_frame_host_delegate.cc
@@ -174,6 +174,16 @@
   return false;
 }
 
+RenderWidgetHostImpl* RenderFrameHostDelegate::CreateNewPopupWidget(
+    AgentSchedulingGroupHost& agent_scheduling_group,
+    int32_t route_id,
+    mojo::PendingAssociatedReceiver<blink::mojom::PopupWidgetHost>
+        blink_popup_widget_host,
+    mojo::PendingAssociatedReceiver<blink::mojom::WidgetHost> blink_widget_host,
+    mojo::PendingAssociatedRemote<blink::mojom::Widget> blink_widget) {
+  return nullptr;
+}
+
 bool RenderFrameHostDelegate::ShowPopupMenu(
     RenderFrameHostImpl* render_frame_host,
     mojo::PendingRemote<blink::mojom::PopupMenuClient>* popup_client,
diff --git a/content/browser/renderer_host/render_frame_host_delegate.h b/content/browser/renderer_host/render_frame_host_delegate.h
index 537c779..709cb67 100644
--- a/content/browser/renderer_host/render_frame_host_delegate.h
+++ b/content/browser/renderer_host/render_frame_host_delegate.h
@@ -86,6 +86,7 @@
 class AgentSchedulingGroupHost;
 class FrameTreeNode;
 class RenderFrameHostImpl;
+class RenderWidgetHostImpl;
 class SessionStorageNamespace;
 class WebContents;
 struct AXEventNotificationDetails;
@@ -572,14 +573,14 @@
   // widget should be created associated with the given
   // |agent_scheduling_group|, but it should not be shown yet. That should
   // happen in response to ShowCreatedWidget.
-  virtual void CreateNewPopupWidget(
+  virtual RenderWidgetHostImpl* CreateNewPopupWidget(
       AgentSchedulingGroupHost& agent_scheduling_group,
       int32_t route_id,
       mojo::PendingAssociatedReceiver<blink::mojom::PopupWidgetHost>
           blink_popup_widget_host,
       mojo::PendingAssociatedReceiver<blink::mojom::WidgetHost>
           blink_widget_host,
-      mojo::PendingAssociatedRemote<blink::mojom::Widget> blink_widget) {}
+      mojo::PendingAssociatedRemote<blink::mojom::Widget> blink_widget);
 
   // Return true if the popup is shown through WebContentsObserver.
   // BrowserPluginGuest for the guest WebContents will show the popup on Mac,
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index d8e74c5..76eabad 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -4180,6 +4180,13 @@
   commit_callback_interceptor_ = interceptor;
 }
 
+void RenderFrameHostImpl::SetCreateNewPopupCallbackForTesting(
+    const CreateNewPopupWidgetCallbackForTesting& callback) {
+  // This DCHECK aims to avoid unexpected replacement of a callback.
+  DCHECK(!create_new_popup_widget_callback_ || !callback);
+  create_new_popup_widget_callback_ = callback;
+}
+
 void RenderFrameHostImpl::DidBlockNavigation(
     const GURL& blocked_url,
     const GURL& initiator_url,
@@ -5302,10 +5309,12 @@
     CreateNewPopupWidgetCallback callback) {
   int32_t widget_route_id = GetProcess()->GetNextRoutingID();
   std::move(callback).Run(widget_route_id);
-  delegate_->CreateNewPopupWidget(agent_scheduling_group_, widget_route_id,
-                                  std::move(blink_popup_widget_host),
-                                  std::move(blink_widget_host),
-                                  std::move(blink_widget));
+  RenderWidgetHostImpl* widget = delegate_->CreateNewPopupWidget(
+      agent_scheduling_group_, widget_route_id,
+      std::move(blink_popup_widget_host), std::move(blink_widget_host),
+      std::move(blink_widget));
+  if (create_new_popup_widget_callback_)
+    create_new_popup_widget_callback_.Run(widget);
 }
 
 void RenderFrameHostImpl::IssueKeepAliveHandle(
diff --git a/content/browser/renderer_host/render_frame_host_impl.h b/content/browser/renderer_host/render_frame_host_impl.h
index 286c589f9..3676401 100644
--- a/content/browser/renderer_host/render_frame_host_impl.h
+++ b/content/browser/renderer_host/render_frame_host_impl.h
@@ -1266,6 +1266,13 @@
   void SetCommitCallbackInterceptorForTesting(
       CommitCallbackInterceptor* interceptor);
 
+  using CreateNewPopupWidgetCallbackForTesting =
+      base::RepeatingCallback<void(RenderWidgetHostImpl*)>;
+
+  // Set a callback to listen to the |CreateNewPopupWidget| for testing.
+  void SetCreateNewPopupCallbackForTesting(
+      const CreateNewPopupWidgetCallbackForTesting& callback);
+
   // Posts a message from a frame in another process to the current renderer.
   void PostMessageEvent(
       const base::Optional<base::UnguessableToken>& source_token,
@@ -3104,6 +3111,9 @@
   // Used to intercept DidCommit* calls in tests.
   CommitCallbackInterceptor* commit_callback_interceptor_;
 
+  // Used to hear about CreateNewPopupWidget calls in tests.
+  CreateNewPopupWidgetCallbackForTesting create_new_popup_widget_callback_;
+
   // Mask of the active features tracked by the scheduler used by this frame.
   // This is used only for metrics.
   // See blink::SchedulingPolicy::Feature for the meaning.
diff --git a/content/browser/renderer_host/render_view_host_delegate.h b/content/browser/renderer_host/render_view_host_delegate.h
index 0733e77..85de4921 100644
--- a/content/browser/renderer_host/render_view_host_delegate.h
+++ b/content/browser/renderer_host/render_view_host_delegate.h
@@ -112,12 +112,6 @@
   // The contents' preferred size changed.
   virtual void UpdatePreferredSize(const gfx::Size& pref_size) {}
 
-  // Show the newly created widget with the specified bounds.
-  // The widget is identified by the route_id passed to CreateNewWidget.
-  virtual void ShowCreatedWidget(int process_id,
-                                 int widget_route_id,
-                                 const gfx::Rect& initial_rect) {}
-
   // Returns the SessionStorageNamespace the render view should use. Might
   // create the SessionStorageNamespace on the fly.
   virtual SessionStorageNamespace* GetSessionStorageNamespace(
diff --git a/content/browser/renderer_host/render_view_host_impl.cc b/content/browser/renderer_host/render_view_host_impl.cc
index f442c02a..9669200 100644
--- a/content/browser/renderer_host/render_view_host_impl.cc
+++ b/content/browser/renderer_host/render_view_host_impl.cc
@@ -750,29 +750,13 @@
   // with URL of the main frame.
   ScopedActiveURL scoped_active_url(this);
 
-  if (delegate_->OnMessageReceived(this, msg))
-    return true;
-
-  bool handled = true;
-  IPC_BEGIN_MESSAGE_MAP(RenderViewHostImpl, msg)
-    IPC_MESSAGE_HANDLER(ViewHostMsg_ShowWidget, OnShowWidget)
-    IPC_MESSAGE_UNHANDLED(handled = false)
-  IPC_END_MESSAGE_MAP()
-
-  return handled;
+  return delegate_->OnMessageReceived(this, msg);
 }
 
 void RenderViewHostImpl::RenderWidgetDidInit() {
   PostRenderViewReady();
 }
 
-void RenderViewHostImpl::OnShowWidget(int widget_route_id,
-                                      const gfx::Rect& initial_rect) {
-  delegate_->ShowCreatedWidget(GetProcess()->GetID(), widget_route_id,
-                               initial_rect);
-  Send(new WidgetMsg_SetBounds_ACK(widget_route_id));
-}
-
 void RenderViewHostImpl::OnDidContentsPreferredSizeChange(
     const gfx::Size& new_size) {
   delegate_->UpdatePreferredSize(new_size);
diff --git a/content/browser/renderer_host/render_widget_host_browsertest.cc b/content/browser/renderer_host/render_widget_host_browsertest.cc
index a41e459..32e0425e 100644
--- a/content/browser/renderer_host/render_widget_host_browsertest.cc
+++ b/content/browser/renderer_host/render_widget_host_browsertest.cc
@@ -562,46 +562,19 @@
       blink::WebInputEvent::GetStaticTimeStampForTests());
   event.text[0] = ' ';
 
-  // A class to wait for ViewHostMsg_ShowWidget.
-  class WaitForShowWidgetFilter : public ObserveMessageFilter {
-   public:
-    explicit WaitForShowWidgetFilter()
-        : ObserveMessageFilter(ViewMsgStart, ViewHostMsg_ShowWidget::ID) {}
-
-    bool OnMessageReceived(const IPC::Message& message) override {
-      IPC_BEGIN_MESSAGE_MAP(WaitForShowWidgetFilter, message)
-        IPC_MESSAGE_HANDLER(ViewHostMsg_ShowWidget, OnShowWidget)
-      IPC_END_MESSAGE_MAP()
-      return ObserveMessageFilter::OnMessageReceived(message);
-    }
-
-    int routing_id() const { return routing_id_; }
-
-   private:
-    ~WaitForShowWidgetFilter() override = default;
-
-    void OnShowWidget(int routing_id, const gfx::Rect& initial_rect) {
-      routing_id_ = routing_id;
-    }
-
-    int routing_id_ = 0;
-
-    DISALLOW_COPY_AND_ASSIGN(WaitForShowWidgetFilter);
-  };
-
   for (int i = 0; i < 2; ++i) {
     bool browser_closes = i == 0;
 
     // This focuses and opens the select box, creating a popup RenderWidget. We
     // wait for the RenderWidgetHost to be shown.
-    auto filter = base::MakeRefCounted<WaitForShowWidgetFilter>();
-    process->AddFilter(filter.get());
+    auto filter =
+        std::make_unique<ShowPopupWidgetWaiter>(contents, root_frame_host);
     EXPECT_TRUE(ExecuteScript(root_frame_host, "focusSelectMenu();"));
     root_frame_host->GetRenderWidgetHost()->ForwardKeyboardEvent(event);
     filter->Wait();
 
     // The popup RenderWidget will get its own routing id.
-    int popup_routing_id = filter->routing_id();
+    int popup_routing_id = filter->last_routing_id();
     EXPECT_TRUE(popup_routing_id);
     // Grab a pointer to the popup RenderWidget.
     RenderWidgetHost* popup_widget_host =
diff --git a/content/browser/renderer_host/render_widget_host_delegate.h b/content/browser/renderer_host/render_widget_host_delegate.h
index cbe1b66e..a8cc695c 100644
--- a/content/browser/renderer_host/render_widget_host_delegate.h
+++ b/content/browser/renderer_host/render_widget_host_delegate.h
@@ -30,6 +30,7 @@
 
 namespace gfx {
 class Point;
+class Rect;
 class Size;
 }
 
@@ -331,6 +332,12 @@
   // opened by a frame in the FrameTree.
   virtual FrameTree* GetFrameTree();
 
+  // Show the newly created widget with the specified bounds.
+  // The widget is identified by the route_id passed to CreateNewWidget.
+  virtual void ShowCreatedWidget(int process_id,
+                                 int widget_route_id,
+                                 const gfx::Rect& initial_rect_in_dips) {}
+
  protected:
   virtual ~RenderWidgetHostDelegate() {}
 };
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index f9764c6..e8fb87936 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -2254,6 +2254,13 @@
   ShutdownAndDestroyWidget(true);
 }
 
+void RenderWidgetHostImpl::ShowPopup(const gfx::Rect& initial_rect,
+                                     ShowPopupCallback callback) {
+  delegate_->ShowCreatedWidget(GetProcess()->GetID(), GetRoutingID(),
+                               initial_rect);
+  std::move(callback).Run();
+}
+
 void RenderWidgetHostImpl::SetToolTipText(
     const base::string16& tooltip_text,
     base::i18n::TextDirection text_direction_hint) {
diff --git a/content/browser/renderer_host/render_widget_host_impl.h b/content/browser/renderer_host/render_widget_host_impl.h
index d77d25f..111ffbd 100644
--- a/content/browser/renderer_host/render_widget_host_impl.h
+++ b/content/browser/renderer_host/render_widget_host_impl.h
@@ -289,6 +289,8 @@
 
   // blink::mojom::PopupWidgetHost implementation.
   void RequestClosePopup() override;
+  void ShowPopup(const gfx::Rect& initial_rect,
+                 ShowPopupCallback callback) override;
 
   // Notification that the screen info has changed.
   void NotifyScreenInfoChanged();
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index 568e64d7..058e4396 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -8056,59 +8056,6 @@
   EXPECT_EQ(orig_site_instance, child->current_frame_host()->GetSiteInstance());
 }
 
-// Helper filter class to wait for a ShowWidget message, record the routing ID
-// from the message, and then drop the message.
-const uint32_t kMessageClasses[] = {ViewMsgStart, FrameMsgStart};
-class PendingWidgetMessageFilter : public BrowserMessageFilter {
- public:
-  PendingWidgetMessageFilter()
-      : BrowserMessageFilter(kMessageClasses, base::size(kMessageClasses)),
-        routing_id_(MSG_ROUTING_NONE) {}
-
-  bool OnMessageReceived(const IPC::Message& message) override {
-    bool handled = true;
-    IPC_BEGIN_MESSAGE_MAP(PendingWidgetMessageFilter, message)
-      IPC_MESSAGE_HANDLER(ViewHostMsg_ShowWidget, OnShowWidget)
-      IPC_MESSAGE_UNHANDLED(handled = false)
-    IPC_END_MESSAGE_MAP()
-    return handled;
-  }
-
-  void Wait() { run_loop_.Run(); }
-
-  int routing_id() { return routing_id_; }
-
- private:
-  ~PendingWidgetMessageFilter() override {}
-
-  void OnShowCreatedWindow(int pending_widget_routing_id,
-                           WindowOpenDisposition disposition,
-                           const gfx::Rect& initial_rect,
-                           bool user_gesture) {
-    content::GetUIThreadTaskRunner({})->PostTask(
-        FROM_HERE,
-        base::BindOnce(&PendingWidgetMessageFilter::OnReceivedRoutingIDOnUI,
-                       this, pending_widget_routing_id));
-  }
-
-  void OnShowWidget(int routing_id, const gfx::Rect& initial_rect) {
-    content::GetUIThreadTaskRunner({})->PostTask(
-        FROM_HERE,
-        base::BindOnce(&PendingWidgetMessageFilter::OnReceivedRoutingIDOnUI,
-                       this, routing_id));
-  }
-
-  void OnReceivedRoutingIDOnUI(int widget_routing_id) {
-    routing_id_ = widget_routing_id;
-    run_loop_.Quit();
-  }
-
-  int routing_id_;
-  base::RunLoop run_loop_;
-
-  DISALLOW_COPY_AND_ASSIGN(PendingWidgetMessageFilter);
-};
-
 // Intercepts calls to RenderFramHost's ShowCreatedWindow mojo method, and
 // invokes the provided callback.
 class ShowCreatedWindowInterceptor
@@ -8124,7 +8071,7 @@
         this);
   }
 
-  ~ShowCreatedWindowInterceptor() override {}
+  ~ShowCreatedWindowInterceptor() override = default;
 
   FrameHost* GetForwardingInterface() override { return render_frame_host_; }
 
@@ -8239,6 +8186,80 @@
   RenderWidgetHostImpl* render_widget_host_;
 };
 
+// Intercepts calls to PopupWidgetHost's ShowPopup mojo method, and
+// invokes the provided callback.
+class ShowCreatedPopupWidgetInterceptor
+    : public blink::mojom::PopupWidgetHostInterceptorForTesting {
+ public:
+  ShowCreatedPopupWidgetInterceptor(
+      RenderWidgetHostImpl* render_widget_host,
+      base::OnceCallback<void(int32_t pending_widget_routing_id)> test_callback)
+      : render_widget_host_(render_widget_host),
+        test_callback_(std::move(test_callback)) {
+    render_widget_host_->popup_widget_host_receiver_for_testing()
+        .SwapImplForTesting(this);
+  }
+
+  ~ShowCreatedPopupWidgetInterceptor() override = default;
+
+  blink::mojom::PopupWidgetHost* GetForwardingInterface() override {
+    return render_widget_host_;
+  }
+
+  void ShowPopup(const gfx::Rect& initial_rect,
+                 ShowPopupCallback callback) override {
+    show_callback_ = std::move(callback);
+    initial_rect_ = initial_rect;
+    std::move(test_callback_).Run(render_widget_host_->GetRoutingID());
+  }
+
+  void ResumeShowPopupWidget() {
+    GetForwardingInterface()->ShowPopup(initial_rect_,
+                                        std::move(show_callback_));
+  }
+
+ private:
+  RenderWidgetHostImpl* render_widget_host_;
+  base::OnceCallback<void(int32_t pending_widget_routing_id)> test_callback_;
+  ShowPopupCallback show_callback_;
+  gfx::Rect initial_rect_;
+};
+
+// Listens for the source RenderFrameHost opening the new popup widget then
+// attaches a show listener to the widget.
+class NewPopupWidgetCreatedObserver {
+ public:
+  NewPopupWidgetCreatedObserver(
+      RenderFrameHostImpl* frame_host,
+      base::OnceCallback<void(int32_t pending_widget_routing_id)> test_callback)
+      : frame_host_(frame_host), test_callback_(std::move(test_callback)) {
+    frame_host_->SetCreateNewPopupCallbackForTesting(base::BindRepeating(
+        &NewPopupWidgetCreatedObserver::DidCreatePopupWidget,
+        base::Unretained(this)));
+  }
+
+  ~NewPopupWidgetCreatedObserver() {
+    if (frame_host_)
+      frame_host_->SetCreateNewPopupCallbackForTesting(base::NullCallback());
+  }
+
+  void ResumeShowPopupWidget() { show_interceptor_->ResumeShowPopupWidget(); }
+
+ private:
+  void DidCreatePopupWidget(RenderWidgetHostImpl* widget) {
+    show_interceptor_ = std::make_unique<ShowCreatedPopupWidgetInterceptor>(
+        widget, std::move(test_callback_));
+
+    // Stop observing now.
+    frame_host_->SetCreateNewPopupCallbackForTesting(base::NullCallback());
+    frame_host_ = nullptr;
+  }
+
+  RenderFrameHostImpl* frame_host_;
+  std::unique_ptr<ShowCreatedPopupWidgetInterceptor> show_interceptor_;
+  base::OnceCallback<void(int32_t pending_widget_routing_id)> test_callback_;
+};
+
 // Test for https://crbug.com/612276.  Similar to
 // TwoSubframesOpenWindowsSimultaneously, but use popup menu widgets instead of
 // windows.
@@ -8283,16 +8304,20 @@
       blink::WebInputEvent::GetStaticTimeStampForTests());
   event.text[0] = ' ';
 
-  scoped_refptr<PendingWidgetMessageFilter> filter1 =
-      new PendingWidgetMessageFilter();
-  process1->AddFilter(filter1.get());
+  base::RunLoop run_loop1;
+  int32_t routing_id1;
+  NewPopupWidgetCreatedObserver interceptor1(
+      child1->current_frame_host(),
+      base::BindLambdaForTesting([&](int32_t pending_widget_routing_id) {
+        routing_id1 = pending_widget_routing_id;
+        run_loop1.Quit();
+      }));
   EXPECT_TRUE(ExecuteScript(child1, "focusSelectMenu();"));
   child1->current_frame_host()->GetRenderWidgetHost()->ForwardKeyboardEvent(
       event);
-  filter1->Wait();
+  run_loop1.Run();
 
-  auto first_popup_global_id =
-      GlobalRoutingID(process1->GetID(), filter1->routing_id());
+  auto first_popup_global_id = GlobalRoutingID(process1->GetID(), routing_id1);
   // Add an incerceptor for first popup widget so it doesn't get closed
   // immediately while the other one is being opened.
   EXPECT_TRUE(base::Contains(web_contents()->pending_widget_views_,
@@ -8304,37 +8329,37 @@
               ->pending_widget_views_[first_popup_global_id]
               ->GetRenderWidgetHost()));
 
-  scoped_refptr<PendingWidgetMessageFilter> filter2 =
-      new PendingWidgetMessageFilter();
-  process2->AddFilter(filter2.get());
+  base::RunLoop run_loop2;
+  int32_t routing_id2;
+  NewPopupWidgetCreatedObserver interceptor2(
+      child2->current_frame_host(),
+      base::BindLambdaForTesting([&](int32_t pending_widget_routing_id) {
+        routing_id2 = pending_widget_routing_id;
+        run_loop2.Quit();
+      }));
   EXPECT_TRUE(ExecuteScript(child2, "focusSelectMenu();"));
   child2->current_frame_host()->GetRenderWidgetHost()->ForwardKeyboardEvent(
       event);
-  filter2->Wait();
+  run_loop2.Run();
 
   // At this point, we should have two pending widgets.
   EXPECT_TRUE(base::Contains(web_contents()->pending_widget_views_,
                              first_popup_global_id));
-  EXPECT_TRUE(base::Contains(
-      web_contents()->pending_widget_views_,
-      GlobalRoutingID(process2->GetID(), filter2->routing_id())));
+  EXPECT_TRUE(base::Contains(web_contents()->pending_widget_views_,
+                             GlobalRoutingID(process2->GetID(), routing_id2)));
 
   // Both subframes were set up in the same way, so the next routing ID for the
   // new popup widgets should match up (this led to the collision in the
   // pending widgets map in the original bug).
-  EXPECT_EQ(filter1->routing_id(), filter2->routing_id());
+  EXPECT_EQ(routing_id1, routing_id2);
 
   // Now simulate both widgets being shown.
-  web_contents()->ShowCreatedWidget(process1->GetID(), filter1->routing_id(),
-                                    gfx::Rect());
-  web_contents()->ShowCreatedWidget(process2->GetID(), filter2->routing_id(),
-                                    gfx::Rect());
-  EXPECT_FALSE(base::Contains(
-      web_contents()->pending_widget_views_,
-      GlobalRoutingID(process1->GetID(), filter1->routing_id())));
-  EXPECT_FALSE(base::Contains(
-      web_contents()->pending_widget_views_,
-      GlobalRoutingID(process2->GetID(), filter2->routing_id())));
+  interceptor1.ResumeShowPopupWidget();
+  interceptor2.ResumeShowPopupWidget();
+  EXPECT_FALSE(base::Contains(web_contents()->pending_widget_views_,
+                              GlobalRoutingID(process1->GetID(), routing_id1)));
+  EXPECT_FALSE(base::Contains(web_contents()->pending_widget_views_,
+                              GlobalRoutingID(process2->GetID(), routing_id2)));
 }
 #endif
 
@@ -12808,13 +12833,8 @@
     loop.Run();
   }
 
-  scoped_refptr<ShowWidgetMessageFilter> show_widget_filter =
-      base::MakeRefCounted<ShowWidgetMessageFilter>(web_contents());
-  base::ScopedClosureRunner shutdown_show_widget_filter(
-      base::BindOnce(&ShowWidgetMessageFilter::Shutdown, show_widget_filter));
-  child_node->current_frame_host()->GetProcess()->AddFilter(
-      show_widget_filter.get());
-
+  auto show_popup_waiter = std::make_unique<ShowPopupWidgetWaiter>(
+      web_contents(), child_node->current_frame_host());
   SimulateMouseClick(child_node->current_frame_host()->GetRenderWidgetHost(),
                      55, 2005);
 
@@ -12825,7 +12845,7 @@
   // The test passes if this wait returns, indicating that the popup was
   // scrolled into view and the OOPIF renderer displayed it. Other tests verify
   // the correctness of popup menu coordinates.
-  show_widget_filter->Wait();
+  show_popup_waiter->Wait();
 }
 
 #if defined(OS_ANDROID)
diff --git a/content/browser/site_per_process_hit_test_browsertest.cc b/content/browser/site_per_process_hit_test_browsertest.cc
index 61b8ee20..f1bca40 100644
--- a/content/browser/site_per_process_hit_test_browsertest.cc
+++ b/content/browser/site_per_process_hit_test_browsertest.cc
@@ -5824,11 +5824,8 @@
   EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
             child_node->current_frame_host()->GetSiteInstance());
 
-  scoped_refptr<ShowWidgetMessageFilter> filter =
-      base::MakeRefCounted<ShowWidgetMessageFilter>(web_contents());
-  base::ScopedClosureRunner shut_down_filter(
-      base::BindOnce(&ShowWidgetMessageFilter::Shutdown, filter));
-  child_node->current_frame_host()->GetProcess()->AddFilter(filter.get());
+  auto popup_waiter = std::make_unique<ShowPopupWidgetWaiter>(
+      web_contents(), child_node->current_frame_host());
 
   // Target left-click event to child frame.
   blink::WebMouseEvent click_event(
@@ -5848,8 +5845,8 @@
   shell()->web_contents()->GetRenderWidgetHostView()->GetScreenInfo(
       &screen_info);
 
-  filter->Wait();
-  gfx::Rect popup_rect = filter->last_initial_rect();
+  popup_waiter->Wait();
+  gfx::Rect popup_rect = popup_waiter->last_initial_rect();
   if (IsUseZoomForDSFEnabled()) {
     popup_rect = gfx::ScaleToRoundedRect(popup_rect,
                                          1 / screen_info.device_scale_factor);
@@ -5873,7 +5870,9 @@
   // convention (it requires separate clicks to open the menu and select an
   // option). See https://crbug.com/703191.
   int process_id = child_node->current_frame_host()->GetProcess()->GetID();
-  filter->Reset();
+  popup_waiter->Stop();
+  popup_waiter = std::make_unique<ShowPopupWidgetWaiter>(
+      web_contents(), child_node->current_frame_host());
   RenderWidgetHostInputEventRouter* router =
       static_cast<WebContentsImpl*>(shell()->web_contents())
           ->GetInputEventRouter();
@@ -5882,22 +5881,12 @@
   click_event.click_count = 1;
   router->RouteMouseEvent(rwhv_root, &click_event, ui::LatencyInfo());
 
-  filter->Wait();
+  popup_waiter->Wait();
 
   RenderWidgetHostViewAura* popup_view = static_cast<RenderWidgetHostViewAura*>(
-      RenderWidgetHost::FromID(process_id, filter->last_routing_id())
+      RenderWidgetHost::FromID(process_id, popup_waiter->last_routing_id())
           ->GetView());
-  // The IO thread posts to ViewMsg_ShowWidget handlers in both the message
-  // filter above and the WebContents, which initializes the popup's view.
-  // It is possible for this code to execute before the WebContents handler,
-  // in which case OnMouseEvent would be called on an uninitialized RWHVA.
-  // This loop ensures that the initialization completes before proceeding.
-  while (!popup_view->window()) {
-    base::RunLoop loop;
-    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                                  loop.QuitClosure());
-    loop.Run();
-  }
+  EXPECT_TRUE(popup_view);
 
   RenderWidgetHostMouseEventMonitor popup_monitor(
       popup_view->GetRenderWidgetHost());
@@ -5957,11 +5946,8 @@
   EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
             c_node->current_frame_host()->GetSiteInstance());
 
-  scoped_refptr<ShowWidgetMessageFilter> filter =
-      base::MakeRefCounted<ShowWidgetMessageFilter>(web_contents());
-  base::ScopedClosureRunner shut_down_filter(
-      base::BindOnce(&ShowWidgetMessageFilter::Shutdown, filter));
-  c_node->current_frame_host()->GetProcess()->AddFilter(filter.get());
+  auto popup_waiter = std::make_unique<ShowPopupWidgetWaiter>(
+      web_contents(), c_node->current_frame_host());
 
   WaitForHitTestData(c_node->current_frame_host());
 
@@ -5979,9 +5965,9 @@
   SetWebEventPositions(&click_event, gfx::Point(1, 1), rwhv_root);
   rwhv_c_node->ProcessMouseEvent(click_event, ui::LatencyInfo());
 
-  filter->Wait();
+  popup_waiter->Wait();
 
-  gfx::Rect popup_rect = filter->last_initial_rect();
+  gfx::Rect popup_rect = popup_waiter->last_initial_rect();
 
 #if defined(OS_MAC) || defined(OS_ANDROID)
   EXPECT_EQ(popup_rect.x(), 9);
@@ -6009,7 +5995,9 @@
       "iframe.style.top = 150;";
   EXPECT_TRUE(ExecuteScript(root, script));
 
-  filter->Reset();
+  popup_waiter->Stop();
+  popup_waiter = std::make_unique<ShowPopupWidgetWaiter>(
+      web_contents(), c_node->current_frame_host());
 
   // Busy loop to wait for b_node's screen rect to get updated. There
   // doesn't seem to be any better way to find out when this happens.
@@ -6031,9 +6019,9 @@
   SetWebEventPositions(&click_event, gfx::Point(1, 1), rwhv_root);
   rwhv_c_node->ProcessMouseEvent(click_event, ui::LatencyInfo());
 
-  filter->Wait();
+  popup_waiter->Wait();
 
-  popup_rect = filter->last_initial_rect();
+  popup_rect = popup_waiter->last_initial_rect();
 
 #if defined(OS_MAC) || defined(OS_ANDROID)
   EXPECT_EQ(popup_rect.x(), 9);
@@ -6099,11 +6087,8 @@
               ->GetRenderWidgetHost()
               ->GetView());
 
-  scoped_refptr<ShowWidgetMessageFilter> filter =
-      base::MakeRefCounted<ShowWidgetMessageFilter>(web_contents());
-  base::ScopedClosureRunner shut_down_filter(
-      base::BindOnce(&ShowWidgetMessageFilter::Shutdown, filter));
-  grandchild_node->current_frame_host()->GetProcess()->AddFilter(filter.get());
+  auto popup_waiter = std::make_unique<ShowPopupWidgetWaiter>(
+      web_contents(), grandchild_node->current_frame_host());
 
   // Target left-click event to the select element in the innermost frame.
   DispatchMouseDownEventAndWaitUntilDispatch(
@@ -6114,11 +6099,11 @@
   DispatchMouseDownEventAndWaitUntilDispatch(web_contents(), rwhv_grandchild,
                                              gfx::PointF(2, 2), rwhv_grandchild,
                                              gfx::PointF(2, 2));
-  filter->Wait();
+  popup_waiter->Wait();
 
   // This test isn't verifying correctness of these coordinates, this is just
   // to ensure that they change after scroll.
-  gfx::Rect unscrolled_popup_rect = filter->last_initial_rect();
+  gfx::Rect unscrolled_popup_rect = popup_waiter->last_initial_rect();
   gfx::Rect initial_grandchild_view_bounds = rwhv_grandchild->GetViewBounds();
 
   // Scroll the main frame.
@@ -6135,7 +6120,9 @@
       break;
   }
 
-  filter->Reset();
+  popup_waiter->Stop();
+  popup_waiter = std::make_unique<ShowPopupWidgetWaiter>(
+      web_contents(), grandchild_node->current_frame_host());
   // This sends the message directly to the rwhv_grandchild, avoiding using
   // the helper methods, to avert a race condition with the surfaces or
   // HitTestRegions needing to update post-scroll. The event won't hit test
@@ -6155,8 +6142,9 @@
   DispatchMouseDownEventAndWaitUntilDispatch(web_contents(), rwhv_root,
                                              gfx::PointF(1, 1), rwhv_root,
                                              gfx::PointF(1, 1));
-  filter->Wait();
-  EXPECT_EQ(unscrolled_popup_rect.y(), filter->last_initial_rect().y() + 20);
+  popup_waiter->Wait();
+  EXPECT_EQ(unscrolled_popup_rect.y(),
+            popup_waiter->last_initial_rect().y() + 20);
 }
 #endif  // !defined(OS_ANDROID)
 
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 4dcf732..2c7abb4 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -3684,7 +3684,7 @@
   return new_contents_impl;
 }
 
-void WebContentsImpl::CreateNewPopupWidget(
+RenderWidgetHostImpl* WebContentsImpl::CreateNewPopupWidget(
     AgentSchedulingGroupHost& agent_scheduling_group,
     int32_t route_id,
     mojo::PendingAssociatedReceiver<blink::mojom::PopupWidgetHost>
@@ -3692,7 +3692,7 @@
     mojo::PendingAssociatedReceiver<blink::mojom::WidgetHost> blink_widget_host,
     mojo::PendingAssociatedRemote<blink::mojom::Widget> blink_widget) {
   OPTIONAL_TRACE_EVENT1(
-      "content", "WebContentsImpl::CreateNewWidget", "params",
+      "content", "WebContentsImpl::CreateNewPopupWidget", "params",
       base::trace_event::TracedValue::Build({{"route_id", route_id}}));
   RenderProcessHost* process = agent_scheduling_group.GetProcess();
   // A message to create a new widget can only come from an active process for
@@ -3700,7 +3700,7 @@
   // it is invalid and the process must be terminated.
   if (!HasMatchingProcess(&frame_tree_, process->GetID())) {
     ReceivedBadMessage(process, bad_message::WCI_NEW_WIDGET_PROCESS_MISMATCH);
-    return;
+    return nullptr;
   }
 
   RenderWidgetHostImpl* widget_host = new RenderWidgetHostImpl(
@@ -3714,11 +3714,12 @@
       static_cast<RenderWidgetHostViewBase*>(
           view_->CreateViewForChildWidget(widget_host));
   if (!widget_view)
-    return;
+    return nullptr;
   widget_view->SetWidgetType(WidgetType::kPopup);
   // Save the created widget associated with the route so we can show it later.
   pending_widget_views_[GlobalRoutingID(process->GetID(), route_id)] =
       widget_view;
+  return widget_host;
 }
 
 void WebContentsImpl::ShowCreatedWindow(RenderFrameHost* opener,
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 7292fde..da32720 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -731,7 +731,7 @@
       blink::mojom::TextAutosizerPageInfoPtr page_info) override;
   bool HasSeenRecentScreenOrientationChange() override;
   bool IsTransientAllowFullscreenActive() const override;
-  void CreateNewPopupWidget(
+  RenderWidgetHostImpl* CreateNewPopupWidget(
       AgentSchedulingGroupHost& agent_scheduling_group,
       int32_t route_id,
       mojo::PendingAssociatedReceiver<blink::mojom::PopupWidgetHost>
diff --git a/content/common/view_messages.h b/content/common/view_messages.h
index fc5ebd0..ccc8f80 100644
--- a/content/common/view_messages.h
+++ b/content/common/view_messages.h
@@ -99,14 +99,6 @@
 // -----------------------------------------------------------------------------
 // Messages sent from the renderer to the browser.
 
-// This message is sent to the parent RenderViewHost to display a widget
-// that was created by CreateWidget. |route_id| refers
-// to the id that was returned from the corresponding Create message above.
-// |initial_rect| is in screen coordinates.
-IPC_MESSAGE_ROUTED2(ViewHostMsg_ShowWidget,
-                    int /* route_id */,
-                    gfx::Rect /* initial_rect */)
-
 // Adding a new message? Stick to the sort order above: first platform
 // independent ViewMsg, then ifdefs for platform specific ViewMsg, then platform
 // independent ViewHostMsg, then ifdefs for platform specific ViewHostMsg.
diff --git a/content/public/test/fake_render_widget_host.cc b/content/public/test/fake_render_widget_host.cc
index 6ebc306..bfd5818 100644
--- a/content/public/test/fake_render_widget_host.cc
+++ b/content/public/test/fake_render_widget_host.cc
@@ -74,6 +74,11 @@
     mojo::PendingRemote<cc::mojom::RenderFrameMetadataObserver>
         render_frame_metadata_observer) {}
 
+void FakeRenderWidgetHost::RequestClosePopup() {}
+
+void FakeRenderWidgetHost::ShowPopup(const gfx::Rect& initial_rect,
+                                     ShowPopupCallback callback) {}
+
 void FakeRenderWidgetHost::SetTouchActionFromMain(
     cc::TouchAction touch_action) {}
 
diff --git a/content/public/test/fake_render_widget_host.h b/content/public/test/fake_render_widget_host.h
index 44efbf0..479fd21 100644
--- a/content/public/test/fake_render_widget_host.h
+++ b/content/public/test/fake_render_widget_host.h
@@ -19,6 +19,7 @@
 namespace content {
 
 class FakeRenderWidgetHost : public blink::mojom::FrameWidgetHost,
+                             public blink::mojom::PopupWidgetHost,
                              public blink::mojom::WidgetHost,
                              public blink::mojom::WidgetInputHandlerHost {
  public:
@@ -72,6 +73,11 @@
       mojo::PendingRemote<cc::mojom::RenderFrameMetadataObserver>
           render_frame_metadata_observer) override;
 
+  // blink::mojom::PopupWidgetHost overrides.
+  void RequestClosePopup() override;
+  void ShowPopup(const gfx::Rect& initial_rect,
+                 ShowPopupCallback callback) override;
+
   // blink::mojom::WidgetInputHandlerHost overrides.
   void SetTouchActionFromMain(cc::TouchAction touch_action) override;
   void DidOverscroll(blink::mojom::DidOverscrollParamsPtr params) override;
diff --git a/content/renderer/agent_scheduling_group.cc b/content/renderer/agent_scheduling_group.cc
index 13eb5d82..9fe80bec 100644
--- a/content/renderer/agent_scheduling_group.cc
+++ b/content/renderer/agent_scheduling_group.cc
@@ -152,12 +152,9 @@
   // process, and is used to release ownership of the corresponding
   // RenderViewImpl instance. https://crbug.com/1000035.
   base::ThreadTaskRunnerHandle::Get()->PostNonNestableTask(
-      FROM_HERE, base::BindOnce(
-                     [](RenderViewImpl* view, DestroyViewCallback callback) {
-                       view->Destroy();
-                       std::move(callback).Run();
-                     },
-                     base::Unretained(view), std::move(callback)));
+      FROM_HERE,
+      base::BindOnce(&RenderViewImpl::Destroy, base::Unretained(view))
+          .Then(std::move(callback)));
 }
 
 void AgentSchedulingGroup::CreateFrame(mojom::CreateFrameParamsPtr params) {
diff --git a/content/renderer/render_view_impl.cc b/content/renderer/render_view_impl.cc
index ca42488b..cf80667 100644
--- a/content/renderer/render_view_impl.cc
+++ b/content/renderer/render_view_impl.cc
@@ -329,13 +329,6 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-void RenderViewImpl::ShowCreatedPopupWidget(RenderWidget* popup_widget,
-                                            WebNavigationPolicy policy,
-                                            const gfx::Rect& initial_rect) {
-  Send(new ViewHostMsg_ShowWidget(GetRoutingID(), popup_widget->routing_id(),
-                                  initial_rect));
-}
-
 void RenderViewImpl::SendFrameStateUpdates() {
   // Tell each frame with pending state to send its UpdateState message.
   for (int render_frame_routing_id : frames_with_pending_state_) {
@@ -551,9 +544,6 @@
     return nullptr;
   }
 
-  RenderWidget::ShowCallback opener_callback = base::BindOnce(
-      &RenderViewImpl::ShowCreatedPopupWidget, weak_ptr_factory_.GetWeakPtr());
-
   RenderWidget* opener_render_widget =
       RenderFrameImpl::FromWebFrame(creator)->GetLocalRootRenderWidget();
 
@@ -571,7 +561,7 @@
   // when leaving scope. The WebPagePopup takes responsibility for Close()ing
   // and thus destroying the RenderWidget.
   popup_widget->InitForPopup(
-      std::move(opener_callback), opener_render_widget, popup_web_widget,
+      opener_render_widget, popup_web_widget,
       opener_render_widget->GetWebWidget()->GetOriginalScreenInfo());
   return popup_web_widget;
 }
diff --git a/content/renderer/render_view_impl.h b/content/renderer/render_view_impl.h
index cbae6c0..fefcfd4 100644
--- a/content/renderer/render_view_impl.h
+++ b/content/renderer/render_view_impl.h
@@ -162,11 +162,6 @@
   // be coalesced into one update.
   void StartNavStateSyncTimerIfNecessary(RenderFrameImpl* frame);
 
-  // A popup widget opened by this view needs to be shown.
-  void ShowCreatedPopupWidget(RenderWidget* popup_widget,
-                              blink::WebNavigationPolicy policy,
-                              const gfx::Rect& initial_rect);
-
   // Returns the length of the session history of this RenderView. Note that
   // this only coincides with the actual length of the session history if this
   // RenderView is the currently active RenderView of a WebContents.
diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc
index 3b58e92a..c27d40a 100644
--- a/content/renderer/render_widget.cc
+++ b/content/renderer/render_widget.cc
@@ -143,12 +143,11 @@
       << " RenderWidget must be destroyed via RenderWidget::Close()";
 }
 
-void RenderWidget::InitForPopup(ShowCallback show_callback,
-                                RenderWidget* opener_widget,
+void RenderWidget::InitForPopup(RenderWidget* opener_widget,
                                 blink::WebPagePopup* web_page_popup,
                                 const blink::ScreenInfo& screen_info) {
   for_popup_ = true;
-  Initialize(std::move(show_callback), web_page_popup, screen_info);
+  Initialize(base::NullCallback(), web_page_popup, screen_info);
 }
 
 void RenderWidget::InitForMainFrame(ShowCallback show_callback,
@@ -235,15 +234,11 @@
 }
 
 void RenderWidget::OnRequestSetBoundsAck() {
-  DCHECK(pending_window_rect_count_);
-  pending_window_rect_count_--;
-  if (pending_window_rect_count_ == 0)
-    GetWebWidget()->SetPendingWindowRect(nullptr);
+  GetWebWidget()->AckPendingWindowRect();
 }
 
 void RenderWidget::SetPendingWindowRect(const gfx::Rect& rect) {
-  pending_window_rect_count_++;
-  GetWebWidget()->SetPendingWindowRect(&rect);
+  GetWebWidget()->SetPendingWindowRect(rect);
 }
 
 void RenderWidget::RequestPresentation(PresentationTimeCallback callback) {
diff --git a/content/renderer/render_widget.h b/content/renderer/render_widget.h
index e5b90a7..f069c94 100644
--- a/content/renderer/render_widget.h
+++ b/content/renderer/render_widget.h
@@ -148,8 +148,7 @@
   // Initialize a new RenderWidget for a popup. The |show_callback| is called
   // when RenderWidget::Show() happens. The |opener_widget| is the local root
   // of the frame that is opening the popup.
-  void InitForPopup(ShowCallback show_callback,
-                    RenderWidget* opener_widget,
+  void InitForPopup(RenderWidget* opener_widget,
                     blink::WebPagePopup* web_page_popup,
                     const blink::ScreenInfo& screen_info);
 
@@ -306,10 +305,6 @@
   // verify destruction always goes through Close().
   bool closing_ = false;
 
-  // While we are waiting for the browser to update window sizes, we track the
-  // pending size temporarily.
-  int pending_window_rect_count_ = 0;
-
   // The time spent in input handlers this frame. Used to throttle input acks.
   base::TimeDelta total_input_handling_time_this_frame_;
 
diff --git a/content/test/content_browser_test_utils_internal.cc b/content/test/content_browser_test_utils_internal.cc
index ec039dff..b9e86b4d 100644
--- a/content/test/content_browser_test_utils_internal.cc
+++ b/content/test/content_browser_test_utils_internal.cc
@@ -405,55 +405,53 @@
   return static_cast<bad_message::BadMessageReason>(internal_result.value());
 }
 
-ShowWidgetMessageFilter::ShowWidgetMessageFilter(WebContents* web_contents)
-#if defined(OS_MAC) || defined(OS_ANDROID)
-    : BrowserMessageFilter(FrameMsgStart),
-#else
-    : BrowserMessageFilter(ViewMsgStart),
-#endif
-      run_loop_(std::make_unique<base::RunLoop>()) {
-  WebContentsObserver::Observe(web_contents);
+ShowPopupWidgetWaiter::ShowPopupWidgetWaiter(WebContents* web_contents,
+                                             RenderFrameHostImpl* frame_host)
+    : WebContentsObserver(web_contents), frame_host_(frame_host) {
+  frame_host_->SetCreateNewPopupCallbackForTesting(base::BindRepeating(
+      &ShowPopupWidgetWaiter::DidCreatePopupWidget, base::Unretained(this)));
 }
 
-ShowWidgetMessageFilter::~ShowWidgetMessageFilter() {
-  DCHECK(is_shut_down_);
+ShowPopupWidgetWaiter::~ShowPopupWidgetWaiter() {
+  if (auto* rwhi = RenderWidgetHostImpl::FromID(process_id_, routing_id_)) {
+    rwhi->popup_widget_host_receiver_for_testing().SwapImplForTesting(rwhi);
+  }
+  if (frame_host_)
+    frame_host_->SetCreateNewPopupCallbackForTesting(base::NullCallback());
 }
 
-void ShowWidgetMessageFilter::Shutdown() {
-  WebContentsObserver::Observe(nullptr);
-  is_shut_down_ = true;
+void ShowPopupWidgetWaiter::Wait() {
+  run_loop_.Run();
 }
 
-bool ShowWidgetMessageFilter::OnMessageReceived(const IPC::Message& message) {
-  IPC_BEGIN_MESSAGE_MAP(ShowWidgetMessageFilter, message)
-#if !defined(OS_MAC) && !defined(OS_ANDROID)
-    IPC_MESSAGE_HANDLER(ViewHostMsg_ShowWidget, OnShowWidget)
-#endif
-  IPC_END_MESSAGE_MAP()
-  return false;
+void ShowPopupWidgetWaiter::Stop() {
+  Observe(nullptr);
+  frame_host_->SetCreateNewPopupCallbackForTesting(base::NullCallback());
+  frame_host_ = nullptr;
 }
 
-void ShowWidgetMessageFilter::Wait() {
-  DCHECK(!is_shut_down_);
-  run_loop_->Run();
+blink::mojom::PopupWidgetHost* ShowPopupWidgetWaiter::GetForwardingInterface() {
+  DCHECK_NE(MSG_ROUTING_NONE, routing_id_);
+  return RenderWidgetHostImpl::FromID(process_id_, routing_id_);
 }
 
-void ShowWidgetMessageFilter::Reset() {
-  DCHECK(!is_shut_down_);
-  initial_rect_ = gfx::Rect();
-  routing_id_ = MSG_ROUTING_NONE;
-  run_loop_ = std::make_unique<base::RunLoop>();
+void ShowPopupWidgetWaiter::ShowPopup(const gfx::Rect& initial_rect,
+                                      ShowPopupCallback callback) {
+  GetForwardingInterface()->ShowPopup(initial_rect, std::move(callback));
+  initial_rect_ = initial_rect;
+  run_loop_.Quit();
 }
 
-void ShowWidgetMessageFilter::OnShowWidget(int route_id,
-                                           const gfx::Rect& initial_rect) {
-  content::GetUIThreadTaskRunner({})->PostTask(
-      FROM_HERE, base::BindOnce(&ShowWidgetMessageFilter::OnShowWidgetOnUI,
-                                this, route_id, initial_rect));
+void ShowPopupWidgetWaiter::DidCreatePopupWidget(
+    RenderWidgetHostImpl* render_widget_host) {
+  process_id_ = render_widget_host->GetProcess()->GetID();
+  routing_id_ = render_widget_host->GetRoutingID();
+  render_widget_host->popup_widget_host_receiver_for_testing()
+      .SwapImplForTesting(this);
 }
 
 #if defined(OS_MAC) || defined(OS_ANDROID)
-bool ShowWidgetMessageFilter::ShowPopupMenu(
+bool ShowPopupWidgetWaiter::ShowPopupMenu(
     RenderFrameHost* render_frame_host,
     mojo::PendingRemote<blink::mojom::PopupMenuClient>* popup_client,
     const gfx::Rect& bounds,
@@ -463,20 +461,12 @@
     std::vector<blink::mojom::MenuItemPtr>* menu_items,
     bool right_aligned,
     bool allow_multiple_selection) {
-  content::GetUIThreadTaskRunner({})->PostTask(
-      FROM_HERE, base::BindOnce(&ShowWidgetMessageFilter::OnShowWidgetOnUI,
-                                this, MSG_ROUTING_NONE, bounds));
+  initial_rect_ = bounds;
+  run_loop_.Quit();
   return true;
 }
 #endif
 
-void ShowWidgetMessageFilter::OnShowWidgetOnUI(int route_id,
-                                               const gfx::Rect& initial_rect) {
-  initial_rect_ = initial_rect;
-  routing_id_ = route_id;
-  run_loop_->Quit();
-}
-
 DropMessageFilter::DropMessageFilter(uint32_t message_class,
                                      uint32_t drop_message_id)
     : BrowserMessageFilter(message_class), drop_message_id_(drop_message_id) {}
diff --git a/content/test/content_browser_test_utils_internal.h b/content/test/content_browser_test_utils_internal.h
index 84b6eea9..af970bc737 100644
--- a/content/test/content_browser_test_utils_internal.h
+++ b/content/test/content_browser_test_utils_internal.h
@@ -31,12 +31,15 @@
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "third_party/blink/public/mojom/choosers/file_chooser.mojom-forward.h"
 #include "third_party/blink/public/mojom/choosers/popup_menu.mojom.h"
+#include "third_party/blink/public/mojom/page/widget.mojom-test-utils.h"
 #include "url/gurl.h"
 
 namespace content {
 
 class FrameTreeNode;
 class RenderFrameHost;
+class RenderFrameHostImpl;
+class RenderWidgetHostImpl;
 class Shell;
 class SiteInstance;
 class ToRenderFrameHost;
@@ -212,37 +215,25 @@
   DISALLOW_COPY_AND_ASSIGN(RenderProcessHostBadIpcMessageWaiter);
 };
 
-class ShowWidgetMessageFilter : public BrowserMessageFilter,
-                                public WebContentsObserver {
+class ShowPopupWidgetWaiter
+    : public WebContentsObserver,
+      public blink::mojom::PopupWidgetHostInterceptorForTesting {
  public:
-  explicit ShowWidgetMessageFilter(WebContents* web_contents);
-
-  // This must be called on the UI thread when this filter is not required
-  // anymore.
-  // Background: The ShowWidgetMessageFilter observes the |web_contents| passed
-  // to the constructor. It will remove itself as observer in the destructor,
-  // but as BrowserMessageFilter subclasses (such as this one) are ref-counted
-  // and a reference is potentially held by the IPC channel, it is not obvious
-  // when this class will be destroyed, and this may be well after a
-  // browsertest's body (which has instantiated this and called AddFilter with
-  // it) exits.
-  void Shutdown();
-
-  // BrowserMessageFilter:
-  bool OnMessageReceived(const IPC::Message& message) override;
+  ShowPopupWidgetWaiter(WebContents* web_contents,
+                        RenderFrameHostImpl* frame_host);
+  ~ShowPopupWidgetWaiter() override;
 
   gfx::Rect last_initial_rect() const { return initial_rect_; }
 
   int last_routing_id() const { return routing_id_; }
 
+  // Waits until a popup request is received.
   void Wait();
 
-  void Reset();
+  // Stops observing new messages.
+  void Stop();
 
  private:
-  ~ShowWidgetMessageFilter() override;
-
-  void OnShowWidget(int route_id, const gfx::Rect& initial_rect);
 
   // WebContentsObserver:
 #if defined(OS_MAC) || defined(OS_ANDROID)
@@ -258,14 +249,21 @@
       bool allow_multiple_selection) override;
 #endif
 
-  void OnShowWidgetOnUI(int route_id, const gfx::Rect& initial_rect);
+  // Callback bound for creating a popup widget.
+  void DidCreatePopupWidget(RenderWidgetHostImpl* render_widget_host);
 
-  std::unique_ptr<base::RunLoop> run_loop_;
+  // blink::mojom::PopupWidgetHostInterceptorForTesting:
+  blink::mojom::PopupWidgetHost* GetForwardingInterface() override;
+  void ShowPopup(const gfx::Rect& initial_rect,
+                 ShowPopupCallback callback) override;
+
+  base::RunLoop run_loop_;
   gfx::Rect initial_rect_;
-  int routing_id_ = MSG_ROUTING_NONE;
-  bool is_shut_down_ = false;
+  int32_t routing_id_ = MSG_ROUTING_NONE;
+  int32_t process_id_ = 0;
+  RenderFrameHostImpl* frame_host_;
 
-  DISALLOW_COPY_AND_ASSIGN(ShowWidgetMessageFilter);
+  DISALLOW_COPY_AND_ASSIGN(ShowPopupWidgetWaiter);
 };
 
 // A BrowserMessageFilter that drops a blacklisted message.
diff --git a/content/test/test_web_contents.cc b/content/test/test_web_contents.cc
index fbaeabb..db716caf 100644
--- a/content/test/test_web_contents.cc
+++ b/content/test/test_web_contents.cc
@@ -386,13 +386,15 @@
   return nullptr;
 }
 
-void TestWebContents::CreateNewPopupWidget(
+RenderWidgetHostImpl* TestWebContents::CreateNewPopupWidget(
     AgentSchedulingGroupHost& agent_scheduling_group,
     int32_t route_id,
     mojo::PendingAssociatedReceiver<blink::mojom::PopupWidgetHost>
         blink_popup_widget_host,
     mojo::PendingAssociatedReceiver<blink::mojom::WidgetHost> blink_widget_host,
-    mojo::PendingAssociatedRemote<blink::mojom::Widget> blink_widget) {}
+    mojo::PendingAssociatedRemote<blink::mojom::Widget> blink_widget) {
+  return nullptr;
+}
 
 void TestWebContents::ShowCreatedWindow(RenderFrameHost* opener,
                                         int route_id,
diff --git a/content/test/test_web_contents.h b/content/test/test_web_contents.h
index 763e6bc..a3df6e05 100644
--- a/content/test/test_web_contents.h
+++ b/content/test/test_web_contents.h
@@ -175,7 +175,7 @@
       bool is_new_browsing_instance,
       bool has_user_gesture,
       SessionStorageNamespace* session_storage_namespace) override;
-  void CreateNewPopupWidget(
+  RenderWidgetHostImpl* CreateNewPopupWidget(
       AgentSchedulingGroupHost& agent_scheduling_group,
       int32_t route_id,
       mojo::PendingAssociatedReceiver<blink::mojom::PopupWidgetHost>
diff --git a/docs/testing/android_test_instructions.md b/docs/testing/android_test_instructions.md
index 6a51945..4f1792fd 100644
--- a/docs/testing/android_test_instructions.md
+++ b/docs/testing/android_test_instructions.md
@@ -130,6 +130,12 @@
 tune2fs -e continue android_emulator_sdk/sdk/system-images/android-25/x86/userdata.img
 ```
 
+### AdbCommandFailedError: failed to stat remote object
+
+There's a known issue (https://crbug.com/1094062) where the unit test binaries can fail on
+Android R and later: if you see this error, try rerunning on an Android version
+with API level <= 29 (Android <= Q).
+
 ## Symbolizing Crashes
 
 Crash stacks are logged and can be viewed using `adb logcat`. To symbolize the
diff --git a/extensions/browser/api/alarms/alarms_api.cc b/extensions/browser/api/alarms/alarms_api.cc
index d1cbd38..c4aaea93 100644
--- a/extensions/browser/api/alarms/alarms_api.cc
+++ b/extensions/browser/api/alarms/alarms_api.cc
@@ -163,7 +163,8 @@
     for (const std::unique_ptr<Alarm>& alarm : *alarms)
       alarms_value->Append(alarm->js_alarm->ToValue());
   }
-  Respond(OneArgument(std::move(alarms_value)));
+  Respond(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(alarms_value))));
 }
 
 ExtensionFunction::ResponseAction AlarmsClearFunction::Run() {
diff --git a/extensions/browser/api/app_window/app_window_api.cc b/extensions/browser/api/app_window/app_window_api.cc
index e267edc..3c5405a 100644
--- a/extensions/browser/api/app_window/app_window_api.cc
+++ b/extensions/browser/api/app_window/app_window_api.cc
@@ -210,12 +210,14 @@
           // initialized. Hence, adding a callback for window first navigation
           // completion.
           if (existing_window->DidFinishFirstNavigation())
-            return RespondNow(OneArgument(std::move(result)));
+            return RespondNow(OneArgument(
+                base::Value::FromUniquePtrValue(std::move(result))));
 
-          existing_window->AddOnDidFinishFirstNavigationCallback(
-            base::BindOnce(&AppWindowCreateFunction::
-                           OnAppWindowFinishedFirstNavigationOrClosed,
-                           this, OneArgument(std::move(result))));
+          existing_window->AddOnDidFinishFirstNavigationCallback(base::BindOnce(
+              &AppWindowCreateFunction::
+                  OnAppWindowFinishedFirstNavigationOrClosed,
+              this,
+              OneArgument(base::Value::FromUniquePtrValue(std::move(result)))));
           return RespondLater();
         }
       }
@@ -418,7 +420,8 @@
   result->SetInteger("frameId", frame_id);
   result->SetString("id", app_window->window_key());
   app_window->GetSerializedState(result.get());
-  ResponseValue result_arg = OneArgument(std::move(result));
+  ResponseValue result_arg =
+      OneArgument(base::Value::FromUniquePtrValue(std::move(result)));
 
   if (AppWindowRegistry::Get(browser_context())
           ->HadDevToolsAttached(app_window->web_contents())) {
diff --git a/extensions/browser/api/bluetooth/bluetooth_api.cc b/extensions/browser/api/bluetooth/bluetooth_api.cc
index ca254b92..1443565d 100644
--- a/extensions/browser/api/bluetooth/bluetooth_api.cc
+++ b/extensions/browser/api/bluetooth/bluetooth_api.cc
@@ -169,7 +169,7 @@
     device_list->Append(extension_device.ToValue());
   }
 
-  Respond(OneArgument(std::move(device_list)));
+  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(device_list))));
 }
 
 BluetoothGetDeviceFunction::BluetoothGetDeviceFunction() = default;
diff --git a/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_api.cc b/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_api.cc
index 728ac8e8..dc6c7d1d 100644
--- a/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_api.cc
+++ b/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_api.cc
@@ -630,7 +630,7 @@
   for (apibtle::Characteristic& characteristic : characteristic_list)
     result->Append(apibtle::CharacteristicToValue(&characteristic));
 
-  Respond(OneArgument(std::move(result)));
+  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
 }
 
 BluetoothLowEnergyGetIncludedServicesFunction::
@@ -740,7 +740,7 @@
   for (apibtle::Descriptor& descriptor : descriptor_list)
     result->Append(apibtle::DescriptorToValue(&descriptor));
 
-  Respond(OneArgument(std::move(result)));
+  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
 }
 
 BluetoothLowEnergyReadCharacteristicValueFunction::
diff --git a/extensions/browser/api/cast_channel/cast_message_util.cc b/extensions/browser/api/cast_channel/cast_message_util.cc
index 38577664..d216fdfc 100644
--- a/extensions/browser/api/cast_channel/cast_message_util.cc
+++ b/extensions/browser/api/cast_channel/cast_message_util.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "base/check.h"
+#include "base/containers/span.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/values.h"
 #include "extensions/common/api/cast_channel.h"
@@ -60,29 +61,28 @@
   message->destination_id = message_proto.destination_id();
   message->namespace_ = message_proto.namespace_();
   // Determine the type of the payload and fill base::Value appropriately.
-  std::unique_ptr<base::Value> value;
+  base::Value value;
   switch (message_proto.payload_type()) {
     case ::cast::channel::CastMessage_PayloadType_STRING:
       if (message_proto.has_payload_utf8())
-        value.reset(new base::Value(message_proto.payload_utf8()));
+        value = base::Value(message_proto.payload_utf8());
       break;
     case ::cast::channel::CastMessage_PayloadType_BINARY:
-      if (message_proto.has_payload_binary())
-        value = base::Value::CreateWithCopiedBuffer(
-            message_proto.payload_binary().data(),
-            message_proto.payload_binary().size());
+      if (message_proto.has_payload_binary()) {
+        value = base::Value(
+            base::as_bytes(base::make_span(message_proto.payload_binary())));
+      }
       break;
     default:
       // Unknown payload type. value will remain unset.
       break;
   }
-  if (value.get()) {
-    DCHECK(!message->data.get());
-    message->data = std::move(value);
-    return true;
-  } else {
+  if (value.is_none())
     return false;
-  }
+
+  DCHECK(!message->data.get());
+  message->data = base::Value::ToUniquePtrValue(std::move(value));
+  return true;
 }
 
 }  // namespace extensions
diff --git a/extensions/browser/api/declarative/declarative_api.cc b/extensions/browser/api/declarative/declarative_api.cc
index e5cf129..8920b5f 100644
--- a/extensions/browser/api/declarative/declarative_api.cc
+++ b/extensions/browser/api/declarative/declarative_api.cc
@@ -222,7 +222,7 @@
   auto rules_value = std::make_unique<base::ListValue>();
   for (const auto* rule : rules_out)
     rules_value->Append(rule->ToValue());
-  return OneArgument(std::move(rules_value));
+  return OneArgument(base::Value::FromUniquePtrValue(std::move(rules_value)));
 }
 
 void EventsEventAddRulesFunction::RecordUMA(
@@ -309,7 +309,7 @@
   auto rules_value = std::make_unique<base::ListValue>();
   for (const auto* rule : rules)
     rules_value->Append(rule->ToValue());
-  return OneArgument(std::move(rules_value));
+  return OneArgument(base::Value::FromUniquePtrValue(std::move(rules_value)));
 }
 
 void EventsEventGetRulesFunction::RecordUMA(
diff --git a/extensions/browser/api/feedback_private/feedback_private_api.cc b/extensions/browser/api/feedback_private/feedback_private_api.cc
index e829bbce..98cde51 100644
--- a/extensions/browser/api/feedback_private/feedback_private_api.cc
+++ b/extensions/browser/api/feedback_private/feedback_private_api.cc
@@ -206,7 +206,8 @@
   if (test_callback_ && !test_callback_->is_null())
     test_callback_->Run();
 
-  return RespondNow(OneArgument(std::move(dict)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(dict))));
 }
 
 ExtensionFunction::ResponseAction FeedbackPrivateGetUserEmailFunction::Run() {
diff --git a/extensions/browser/api/file_system/file_system_api.cc b/extensions/browser/api/file_system/file_system_api.cc
index 2adafe7..76f7d2e 100644
--- a/extensions/browser/api/file_system/file_system_api.cc
+++ b/extensions/browser/api/file_system/file_system_api.cc
@@ -279,7 +279,7 @@
   std::unique_ptr<base::DictionaryValue> result = CreateResult();
   for (const auto& path : paths)
     AddEntryToResult(path, std::string(), result.get());
-  Respond(OneArgument(std::move(result)));
+  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
 }
 
 std::unique_ptr<base::DictionaryValue> FileSystemEntryFunction::CreateResult() {
@@ -919,7 +919,8 @@
     is_directory_ = file->is_directory;
     std::unique_ptr<base::DictionaryValue> result = CreateResult();
     AddEntryToResult(file->path, file->id, result.get());
-    return RespondNow(OneArgument(std::move(result)));
+    return RespondNow(
+        OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
   }
   return RespondNow(NoArguments());
 }
@@ -976,7 +977,7 @@
   std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
   dict->SetString("file_system_id", id);
   dict->SetString("file_system_path", path);
-  Respond(OneArgument(std::move(dict)));
+  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(dict))));
 }
 
 void FileSystemRequestFileSystemFunction::OnError(const std::string& error) {
diff --git a/extensions/browser/api/guest_view/guest_view_internal_api.cc b/extensions/browser/api/guest_view/guest_view_internal_api.cc
index 346db7ff..9730c00a 100644
--- a/extensions/browser/api/guest_view/guest_view_internal_api.cc
+++ b/extensions/browser/api/guest_view/guest_view_internal_api.cc
@@ -71,7 +71,8 @@
   auto return_params = std::make_unique<base::DictionaryValue>();
   return_params->SetInteger(guest_view::kID, guest_instance_id);
 
-  Respond(OneArgument(std::move(return_params)));
+  Respond(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(return_params))));
 }
 
 GuestViewInternalDestroyGuestFunction::
diff --git a/extensions/browser/api/hid/hid_api.cc b/extensions/browser/api/hid/hid_api.cc
index 2219b19..c78d674 100644
--- a/extensions/browser/api/hid/hid_api.cc
+++ b/extensions/browser/api/hid/hid_api.cc
@@ -93,7 +93,7 @@
 
 void HidGetDevicesFunction::OnEnumerationComplete(
     std::unique_ptr<base::ListValue> devices) {
-  Respond(OneArgument(std::move(devices)));
+  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(devices))));
 }
 
 HidGetUserSelectedDevicesFunction::HidGetUserSelectedDevicesFunction() {
@@ -259,10 +259,8 @@
     const base::Optional<std::vector<uint8_t>>& buffer) {
   if (success) {
     DCHECK(buffer);
-    Respond(TwoArguments(
-        std::make_unique<base::Value>(report_id),
-        base::Value::CreateWithCopiedBuffer(
-            reinterpret_cast<const char*>(buffer->data()), buffer->size())));
+    Respond(TwoArguments(std::make_unique<base::Value>(report_id),
+                         base::Value::ToUniquePtrValue(base::Value(*buffer))));
   } else {
     Respond(Error(kErrorTransfer));
   }
@@ -324,8 +322,7 @@
     const base::Optional<std::vector<uint8_t>>& buffer) {
   if (success) {
     DCHECK(buffer);
-    Respond(OneArgument(base::Value::CreateWithCopiedBuffer(
-        reinterpret_cast<const char*>(buffer->data()), buffer->size())));
+    Respond(OneArgument(base::Value(*buffer)));
   } else {
     Respond(Error(kErrorTransfer));
   }
diff --git a/extensions/browser/api/metrics_private/metrics_private_api.cc b/extensions/browser/api/metrics_private/metrics_private_api.cc
index ecb6590..84fc7d2 100644
--- a/extensions/browser/api/metrics_private/metrics_private_api.cc
+++ b/extensions/browser/api/metrics_private/metrics_private_api.cc
@@ -82,7 +82,9 @@
                                      &result.additional_properties)) {
     dict = result.ToValue();
   }
-  return RespondNow(dict ? OneArgument(std::move(dict)) : NoArguments());
+  return RespondNow(
+      dict ? OneArgument(base::Value::FromUniquePtrValue(std::move(dict)))
+           : NoArguments());
 }
 
 ExtensionFunction::ResponseAction
diff --git a/extensions/browser/api/networking_private/networking_private_api.cc b/extensions/browser/api/networking_private/networking_private_api.cc
index 3d1fa1aa..269782af 100644
--- a/extensions/browser/api/networking_private/networking_private_api.cc
+++ b/extensions/browser/api/networking_private/networking_private_api.cc
@@ -239,7 +239,7 @@
     std::unique_ptr<base::DictionaryValue> result) {
   FilterProperties(result.get(), PropertiesType::GET, extension(),
                    source_context_type(), source_url());
-  Respond(OneArgument(std::move(result)));
+  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
 }
 
 void NetworkingPrivateGetStateFunction::Failure(const std::string& error) {
@@ -405,7 +405,8 @@
 
 void NetworkingPrivateGetNetworksFunction::Success(
     std::unique_ptr<base::ListValue> network_list) {
-  return Respond(OneArgument(std::move(network_list)));
+  return Respond(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(network_list))));
 }
 
 void NetworkingPrivateGetNetworksFunction::Failure(const std::string& error) {
@@ -452,7 +453,8 @@
 
 void NetworkingPrivateGetVisibleNetworksFunction::Success(
     std::unique_ptr<base::ListValue> network_properties_list) {
-  Respond(OneArgument(std::move(network_properties_list)));
+  Respond(OneArgument(
+      base::Value::FromUniquePtrValue(std::move(network_properties_list))));
 }
 
 void NetworkingPrivateGetVisibleNetworksFunction::Failure(
@@ -500,7 +502,8 @@
       LOG(ERROR) << "networkingPrivate: Unexpected type: " << type;
     }
   }
-  return RespondNow(OneArgument(std::move(enabled_networks_list)));
+  return RespondNow(OneArgument(
+      base::Value::FromUniquePtrValue(std::move(enabled_networks_list))));
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -520,7 +523,8 @@
   std::unique_ptr<base::ListValue> device_state_list(new base::ListValue);
   for (const auto& properties : *device_states)
     device_state_list->Append(properties->ToValue());
-  return RespondNow(OneArgument(std::move(device_state_list)));
+  return RespondNow(OneArgument(
+      base::Value::FromUniquePtrValue(std::move(device_state_list))));
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -1003,7 +1007,8 @@
   std::unique_ptr<base::DictionaryValue> certificate_lists(
       GetDelegate(browser_context())->GetCertificateLists());
   DCHECK(certificate_lists);
-  return RespondNow(OneArgument(std::move(certificate_lists)));
+  return RespondNow(OneArgument(
+      base::Value::FromUniquePtrValue(std::move(certificate_lists))));
 }
 
 }  // namespace extensions
diff --git a/extensions/browser/api/runtime/runtime_api.cc b/extensions/browser/api/runtime/runtime_api.cc
index bf4e782..05a7698 100644
--- a/extensions/browser/api/runtime/runtime_api.cc
+++ b/extensions/browser/api/runtime/runtime_api.cc
@@ -742,7 +742,8 @@
   std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
   dict->SetString("fileSystemId", filesystem.id());
   dict->SetString("baseName", relative_path);
-  return RespondNow(OneArgument(std::move(dict)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(dict))));
 }
 
 }  // namespace extensions
diff --git a/extensions/browser/api/socket/socket_api.cc b/extensions/browser/api/socket/socket_api.cc
index 6daca9a..67a6a36 100644
--- a/extensions/browser/api/socket/socket_api.cc
+++ b/extensions/browser/api/socket/socket_api.cc
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "base/bind.h"
+#include "base/containers/span.h"
 #include "base/values.h"
 #include "build/build_config.h"
 #include "content/public/browser/browser_context.h"
@@ -564,16 +565,14 @@
 void SocketReadFunction::OnCompleted(int bytes_read,
                                      scoped_refptr<net::IOBuffer> io_buffer,
                                      bool socket_destroying) {
-  std::unique_ptr<base::DictionaryValue> result(new base::DictionaryValue());
-  result->SetInteger(kResultCodeKey, bytes_read);
-  if (bytes_read > 0) {
-    result->Set(kDataKey, base::Value::CreateWithCopiedBuffer(io_buffer->data(),
-                                                              bytes_read));
-  } else {
-    result->Set(kDataKey,
-                std::make_unique<base::Value>(base::Value::Type::BINARY));
-  }
-  SetResult(std::move(result));
+  base::Value result(base::Value::Type::DICTIONARY);
+  result.SetIntKey(kResultCodeKey, bytes_read);
+  base::span<const uint8_t> data_span;
+  if (bytes_read > 0)
+    data_span = base::as_bytes(base::make_span(io_buffer->data(), bytes_read));
+  result.SetKey(kDataKey, base::Value(data_span));
+  SetResult(base::DictionaryValue::From(
+      base::Value::ToUniquePtrValue(std::move(result))));
 
   AsyncWorkCompleted();
 }
@@ -646,18 +645,16 @@
                                          bool socket_destroying,
                                          const std::string& address,
                                          uint16_t port) {
-  std::unique_ptr<base::DictionaryValue> result(new base::DictionaryValue());
-  result->SetInteger(kResultCodeKey, bytes_read);
-  if (bytes_read > 0) {
-    result->Set(kDataKey, base::Value::CreateWithCopiedBuffer(io_buffer->data(),
-                                                              bytes_read));
-  } else {
-    result->Set(kDataKey,
-                std::make_unique<base::Value>(base::Value::Type::BINARY));
-  }
-  result->SetString(kAddressKey, address);
-  result->SetInteger(kPortKey, port);
-  SetResult(std::move(result));
+  base::Value result(base::Value::Type::DICTIONARY);
+  result.SetIntKey(kResultCodeKey, bytes_read);
+  base::span<const uint8_t> data_span;
+  if (bytes_read > 0)
+    data_span = base::as_bytes(base::make_span(io_buffer->data(), bytes_read));
+  result.SetKey(kDataKey, base::Value(data_span));
+  result.SetStringKey(kAddressKey, address);
+  result.SetIntKey(kPortKey, port);
+  SetResult(base::DictionaryValue::From(
+      base::Value::ToUniquePtrValue(std::move(result))));
 
   AsyncWorkCompleted();
 }
diff --git a/extensions/browser/api/storage/storage_api.cc b/extensions/browser/api/storage/storage_api.cc
index 4665c084..d7ee25b6 100644
--- a/extensions/browser/api/storage/storage_api.cc
+++ b/extensions/browser/api/storage/storage_api.cc
@@ -86,7 +86,7 @@
 
   std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
   dict->Swap(&result.settings());
-  return OneArgument(std::move(dict));
+  return OneArgument(base::Value::FromUniquePtrValue(std::move(dict)));
 }
 
 ExtensionFunction::ResponseValue SettingsFunction::UseWriteResult(
diff --git a/extensions/browser/api/usb/usb_api.cc b/extensions/browser/api/usb/usb_api.cc
index 0c77ce3f..d3a5a3c 100644
--- a/extensions/browser/api/usb/usb_api.cc
+++ b/extensions/browser/api/usb/usb_api.cc
@@ -475,7 +475,8 @@
     UsbTransferStatus status,
     std::unique_ptr<base::DictionaryValue> transfer_info) {
   if (status == UsbTransferStatus::COMPLETED) {
-    Respond(OneArgument(std::move(transfer_info)));
+    Respond(
+        OneArgument(base::Value::FromUniquePtrValue(std::move(transfer_info))));
   } else {
     auto error_args = std::make_unique<base::ListValue>();
     error_args->Append(std::move(transfer_info));
@@ -489,13 +490,12 @@
 void UsbTransferFunction::OnTransferInCompleted(
     UsbTransferStatus status,
     const std::vector<uint8_t>& data) {
-  auto transfer_info = std::make_unique<base::DictionaryValue>();
-  transfer_info->SetInteger(kResultCodeKey, static_cast<int>(status));
-  transfer_info->Set(
-      kDataKey, base::Value::CreateWithCopiedBuffer(
-                    reinterpret_cast<const char*>(data.data()), data.size()));
+  base::Value transfer_info(base::Value::Type::DICTIONARY);
+  transfer_info.SetIntKey(kResultCodeKey, static_cast<int>(status));
+  transfer_info.SetKey(kDataKey, base::Value(data));
 
-  OnCompleted(status, std::move(transfer_info));
+  OnCompleted(status, base::DictionaryValue::From(base::Value::ToUniquePtrValue(
+                          std::move(transfer_info))));
 }
 
 void UsbTransferFunction::OnTransferOutCompleted(UsbTransferStatus status) {
@@ -665,7 +665,7 @@
 }
 
 void UsbFindDevicesFunction::OpenComplete() {
-  Respond(OneArgument(std::move(result_)));
+  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(result_))));
 }
 
 UsbGetDevicesFunction::UsbGetDevicesFunction() = default;
@@ -714,7 +714,7 @@
     }
   }
 
-  Respond(OneArgument(std::move(result)));
+  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
 }
 
 UsbGetUserSelectedDevicesFunction::UsbGetUserSelectedDevicesFunction() =
@@ -773,7 +773,7 @@
     result->Append(api_device.ToValue());
   }
 
-  Respond(OneArgument(std::move(result)));
+  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
 }
 
 UsbGetConfigurationsFunction::UsbGetConfigurationsFunction() = default;
@@ -816,7 +816,8 @@
     }
     configs->Append(api_config.ToValue());
   }
-  return RespondNow(OneArgument(std::move(configs)));
+  return RespondNow(
+      OneArgument(base::Value::FromUniquePtrValue(std::move(configs))));
 }
 
 UsbRequestAccessFunction::UsbRequestAccessFunction() = default;
@@ -992,7 +993,8 @@
       for (size_t i = 0; i < api_config.interfaces.size(); ++i) {
         result->Append(api_config.interfaces[i].ToValue());
       }
-      return RespondNow(OneArgument(std::move(result)));
+      return RespondNow(
+          OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
     }
   }
   // Respond with an error if the config object can't be found according to
diff --git a/extensions/browser/api/virtual_keyboard_private/virtual_keyboard_private_api.cc b/extensions/browser/api/virtual_keyboard_private/virtual_keyboard_private_api.cc
index e9ddc7948..c3309ff 100644
--- a/extensions/browser/api/virtual_keyboard_private/virtual_keyboard_private_api.cc
+++ b/extensions/browser/api/virtual_keyboard_private/virtual_keyboard_private_api.cc
@@ -123,7 +123,9 @@
 
 void VirtualKeyboardPrivateGetKeyboardConfigFunction::OnKeyboardConfig(
     std::unique_ptr<base::DictionaryValue> results) {
-  Respond(results ? OneArgument(std::move(results)) : Error(kUnknownError));
+  Respond(results
+              ? OneArgument(base::Value::FromUniquePtrValue(std::move(results)))
+              : Error(kUnknownError));
 }
 
 ExtensionFunction::ResponseAction
diff --git a/extensions/browser/api/web_request/upload_data_presenter.cc b/extensions/browser/api/web_request/upload_data_presenter.cc
index a4b28fb..af036568 100644
--- a/extensions/browser/api/web_request/upload_data_presenter.cc
+++ b/extensions/browser/api/web_request/upload_data_presenter.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "base/containers/span.h"
 #include "base/files/file_path.h"
 #include "base/memory/ptr_util.h"
 #include "base/strings/string_util.h"
@@ -83,7 +84,8 @@
 
 void RawDataPresenter::FeedNextBytes(const char* bytes, size_t size) {
   subtle::AppendKeyValuePair(keys::kRequestBodyRawBytesKey,
-                             Value::CreateWithCopiedBuffer(bytes, size),
+                             base::Value::ToUniquePtrValue(base::Value(
+                                 base::as_bytes(base::make_span(bytes, size)))),
                              list_.get());
 }
 
diff --git a/extensions/browser/api/web_request/upload_data_presenter_unittest.cc b/extensions/browser/api/web_request/upload_data_presenter_unittest.cc
index 92f7afc..ec6cb77 100644
--- a/extensions/browser/api/web_request/upload_data_presenter_unittest.cc
+++ b/extensions/browser/api/web_request/upload_data_presenter_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "base/containers/span.h"
 #include "base/values.h"
 #include "extensions/browser/api/web_request/upload_data_presenter.h"
 #include "extensions/browser/api/web_request/web_request_api_constants.h"
@@ -52,22 +53,20 @@
   const size_t block2_size = sizeof(block2) - 1;
 
   // Expected output.
-  std::unique_ptr<base::Value> expected_a(
-      base::Value::CreateWithCopiedBuffer(block1, block1_size));
-  ASSERT_TRUE(expected_a.get() != NULL);
-  std::unique_ptr<base::Value> expected_b(new base::Value(kFilename));
-  ASSERT_TRUE(expected_b.get() != NULL);
-  std::unique_ptr<base::Value> expected_c(
-      base::Value::CreateWithCopiedBuffer(block2, block2_size));
-  ASSERT_TRUE(expected_c.get() != NULL);
+  base::Value expected_a(base::as_bytes(base::make_span(block1, block1_size)));
+  base::Value expected_b(kFilename);
+  base::Value expected_c(base::as_bytes(base::make_span(block2, block2_size)));
 
   base::ListValue expected_list;
-  subtle::AppendKeyValuePair(keys::kRequestBodyRawBytesKey,
-                             std::move(expected_a), &expected_list);
-  subtle::AppendKeyValuePair(keys::kRequestBodyRawFileKey,
-                             std::move(expected_b), &expected_list);
-  subtle::AppendKeyValuePair(keys::kRequestBodyRawBytesKey,
-                             std::move(expected_c), &expected_list);
+  subtle::AppendKeyValuePair(
+      keys::kRequestBodyRawBytesKey,
+      base::Value::ToUniquePtrValue(std::move(expected_a)), &expected_list);
+  subtle::AppendKeyValuePair(
+      keys::kRequestBodyRawFileKey,
+      base::Value::ToUniquePtrValue(std::move(expected_b)), &expected_list);
+  subtle::AppendKeyValuePair(
+      keys::kRequestBodyRawBytesKey,
+      base::Value::ToUniquePtrValue(std::move(expected_c)), &expected_list);
 
   // Real output.
   RawDataPresenter raw_presenter;
@@ -76,9 +75,8 @@
   raw_presenter.FeedNextBytes(block2, block2_size);
   EXPECT_TRUE(raw_presenter.Succeeded());
   std::unique_ptr<base::Value> result = raw_presenter.Result();
-  ASSERT_TRUE(result.get() != NULL);
-
-  EXPECT_TRUE(result->Equals(&expected_list));
+  ASSERT_TRUE(result);
+  EXPECT_EQ(expected_list, *result);
 }
 
 }  // namespace extensions
diff --git a/extensions/browser/extension_event_histogram_value.h b/extensions/browser/extension_event_histogram_value.h
index 18480352..b1ce566 100644
--- a/extensions/browser/extension_event_histogram_value.h
+++ b/extensions/browser/extension_event_histogram_value.h
@@ -483,6 +483,7 @@
   WINDOWS_ON_BOUNDS_CHANGED = 461,
   WALLPAPER_PRIVATE_ON_CLOSE_PREVIEW_WALLPAPER = 462,
   PASSWORDS_PRIVATE_ON_WEAK_CREDENTIALS_CHANGED = 463,
+  ACCESSIBILITY_PRIVATE_ON_MAGNIFIER_BOUNDS_CHANGED = 464,
   // Last entry: Add new entries above, then run:
   // python tools/metrics/histograms/update_extension_histograms.py
   ENUM_BOUNDARY
diff --git a/extensions/renderer/bindings/argument_spec_unittest.cc b/extensions/renderer/bindings/argument_spec_unittest.cc
index da0df8b..da8f082 100644
--- a/extensions/renderer/bindings/argument_spec_unittest.cc
+++ b/extensions/renderer/bindings/argument_spec_unittest.cc
@@ -405,18 +405,12 @@
                   base::Value(base::Value::Type::BINARY));
     {
       // A non-empty (but zero-filled) ArrayBufferView.
-      const char kBuffer[] = {0, 0, 0, 0};
-      std::unique_ptr<base::Value> expected_value =
-          base::Value::CreateWithCopiedBuffer(kBuffer, base::size(kBuffer));
-      ASSERT_TRUE(expected_value);
       ExpectSuccessWithNoConversion(spec, "(new Int32Array(2))");
     }
     {
       // Actual data.
-      const char kBuffer[] = {'p', 'i', 'n', 'g'};
-      std::unique_ptr<base::Value> expected_value =
-          base::Value::CreateWithCopiedBuffer(kBuffer, base::size(kBuffer));
-      ASSERT_TRUE(expected_value);
+      const uint8_t kBuffer[] = {'p', 'i', 'n', 'g'};
+      base::Value expected_value(base::make_span(kBuffer));
       ExpectSuccess(spec,
                     "var b = new ArrayBuffer(4);\n"
                     "var v = new Uint8Array(b);\n"
@@ -424,7 +418,7 @@
                     "for (var i = 0; i < s.length; ++i)\n"
                     "  v[i] = s.charCodeAt(i);\n"
                     "b;",
-                    *expected_value);
+                    expected_value);
     }
     ExpectFailure(spec, "1",
                   InvalidType(api_errors::kTypeBinary, kTypeInteger));
diff --git a/fuchsia/engine/browser/accessibility_bridge.cc b/fuchsia/engine/browser/accessibility_bridge.cc
index 72a3b6f..83c8a62 100644
--- a/fuchsia/engine/browser/accessibility_bridge.cc
+++ b/fuchsia/engine/browser/accessibility_bridge.cc
@@ -87,13 +87,19 @@
 
 void AccessibilityBridge::AccessibilityEventReceived(
     const content::AXEventNotificationDetails& details) {
-  if (!enable_semantic_updates_) {
-    // No need to process events if Fuchsia is not receiving them.
+  // No need to process events if Fuchsia is not receiving them.
+  if (!enable_semantic_updates_)
     return;
-  }
 
   // Updates to AXTree must be applied first.
   for (const ui::AXTreeUpdate& update : details.updates) {
+    if (!update.has_tree_data &&
+        ax_tree_.GetAXTreeID() != ui::AXTreeIDUnknown() &&
+        ax_tree_.GetAXTreeID() != details.ax_tree_id) {
+      // TODO(https://crbug.com/1128954): Add support for combining AXTrees.
+      continue;
+    }
+
     if (!ax_tree_.Unserialize(update)) {
       // If this fails, it is a fatal error that will cause an early exit.
       std::move(on_error_callback_).Run(ZX_ERR_INTERNAL);
diff --git a/fuchsia/engine/browser/frame_impl.cc b/fuchsia/engine/browser/frame_impl.cc
index a0667fb0..32a8e39 100644
--- a/fuchsia/engine/browser/frame_impl.cc
+++ b/fuchsia/engine/browser/frame_impl.cc
@@ -799,7 +799,7 @@
   DCHECK(!window_tree_host_);
 
   window_tree_host_ = std::make_unique<FrameWindowTreeHost>(
-      std::move(view_token), std::move(view_ref_pair));
+      std::move(view_token), std::move(view_ref_pair), web_contents_.get());
   window_tree_host_->InitHost();
   root_window()->AddPreTargetHandler(&event_filter_);
 
diff --git a/fuchsia/engine/browser/frame_impl_browsertest.cc b/fuchsia/engine/browser/frame_impl_browsertest.cc
index 547725a..93907f6 100644
--- a/fuchsia/engine/browser/frame_impl_browsertest.cc
+++ b/fuchsia/engine/browser/frame_impl_browsertest.cc
@@ -2,15 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <fuchsia/ui/policy/cpp/fidl.h>
 #include <lib/fidl/cpp/binding.h>
+#include <lib/sys/cpp/component_context.h>
 #include <lib/ui/scenic/cpp/view_token_pair.h>
 
 #include "base/bind.h"
 #include "base/containers/span.h"
 #include "base/fuchsia/fuchsia_logging.h"
+#include "base/fuchsia/process_context.h"
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
 #include "base/strings/stringprintf.h"
+#include "build/build_config.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/web_contents.h"
@@ -64,6 +68,7 @@
 const char kPopupPath[] = "/popup_parent.html";
 const char kPopupRedirectPath[] = "/popup_child.html";
 const char kPopupMultiplePath[] = "/popup_multiple.html";
+const char kVisibilityPath[] = "/visibility.html";
 const char kPage1Title[] = "title 1";
 const char kPage2Title[] = "title 2";
 const char kPage3Title[] = "websql not available";
@@ -142,8 +147,23 @@
   return visibility->data;
 }
 
-// Verifies that Frames are initially "hidden".
-IN_PROC_BROWSER_TEST_F(FrameImplTest, VisibilityState) {
+// Verifies that Frames are initially "hidden", changes to "visible" once the
+// View is attached to a Presenter and back to "hidden" when the View is
+// detached from the Presenter.
+// TODO(crbug.com/1058247): Re-enable this test on Arm64 when femu is available
+// for that architecture. This test requires Vulkan and Scenic to properly
+// signal the Views visibility.
+#if defined(ARCH_CPU_ARM_FAMILY)
+#define MAYBE_VisibilityState DISABLED_VisibilityState
+#else
+#define MAYBE_VisibilityState VisibilityState
+#endif
+IN_PROC_BROWSER_TEST_F(FrameImplTest, MAYBE_VisibilityState) {
+  net::test_server::EmbeddedTestServerHandle test_server_handle;
+  ASSERT_TRUE(test_server_handle =
+                  embedded_test_server()->StartAndReturnHandle());
+  GURL page_url(embedded_test_server()->GetURL(kVisibilityPath));
+
   fuchsia::web::FramePtr frame = CreateFrame();
   FrameImpl* frame_impl = context_impl()->GetFrameImplForTest(&frame);
 
@@ -155,9 +175,8 @@
 
   // Navigate to a page and wait for it to finish loading.
   ASSERT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
-      controller.get(), fuchsia::web::LoadUrlParams(), url::kAboutBlankURL));
-  navigation_listener_.RunUntilUrlAndTitleEquals(GURL(url::kAboutBlankURL),
-                                                 url::kAboutBlankURL);
+      controller.get(), fuchsia::web::LoadUrlParams(), page_url.spec()));
+  navigation_listener_.RunUntilUrlEquals(page_url);
 
   // Query the document.visibilityState before creating a View.
   EXPECT_EQ(GetDocumentVisibilityState(frame.get()), "\"hidden\"");
@@ -167,8 +186,24 @@
   auto view_tokens = scenic::ViewTokenPair::New();
   frame->CreateView(std::move(view_tokens.view_token));
   base::RunLoop().RunUntilIdle();
-
   EXPECT_EQ(GetDocumentVisibilityState(frame.get()), "\"hidden\"");
+
+  // Attach the View to a Presenter, the page should be visible.
+  auto presenter = base::ComponentContextForProcess()
+                       ->svc()
+                       ->Connect<::fuchsia::ui::policy::Presenter>();
+  presenter.set_error_handler(
+      [](zx_status_t) { ADD_FAILURE() << "Presenter disconnected."; });
+  presenter->PresentOrReplaceView(std::move(view_tokens.view_holder_token),
+                                  nullptr);
+  navigation_listener_.RunUntilTitleEquals("visible");
+
+  // Attach a new View to the Presenter, the page should be hidden again.
+  // This part of the test is a regression test for crbug.com/1141093.
+  auto view_tokens2 = scenic::ViewTokenPair::New();
+  presenter->PresentOrReplaceView(std::move(view_tokens2.view_holder_token),
+                                  nullptr);
+  navigation_listener_.RunUntilTitleEquals("hidden");
 }
 
 void VerifyCanGoBackAndForward(fuchsia::web::NavigationController* controller,
diff --git a/fuchsia/engine/browser/frame_window_tree_host.cc b/fuchsia/engine/browser/frame_window_tree_host.cc
index 738d88c..edc008c 100644
--- a/fuchsia/engine/browser/frame_window_tree_host.cc
+++ b/fuchsia/engine/browser/frame_window_tree_host.cc
@@ -51,13 +51,15 @@
 
 FrameWindowTreeHost::FrameWindowTreeHost(
     fuchsia::ui::views::ViewToken view_token,
-    scenic::ViewRefPair view_ref_pair) {
+    scenic::ViewRefPair view_ref_pair,
+    content::WebContents* web_contents)
+    : view_ref_(DupViewRef(view_ref_pair.view_ref)),
+      web_contents_(web_contents) {
   CreateCompositor();
 
   ui::PlatformWindowInitProperties properties;
   properties.view_token = std::move(view_token);
   properties.view_ref_pair = std::move(view_ref_pair);
-  view_ref_ = DupViewRef(properties.view_ref_pair.view_ref);
   CreateAndSetPlatformWindow(std::move(properties));
 
   window_parenting_client_ =
@@ -87,7 +89,9 @@
   // Tell the root aura::Window whether it is shown or hidden.
   if (new_state == ui::PlatformWindowState::kMinimized) {
     Hide();
+    web_contents_->WasOccluded();
   } else {
     Show();
+    web_contents_->WasShown();
   }
 }
diff --git a/fuchsia/engine/browser/frame_window_tree_host.h b/fuchsia/engine/browser/frame_window_tree_host.h
index c13e195..9ca6ea2 100644
--- a/fuchsia/engine/browser/frame_window_tree_host.h
+++ b/fuchsia/engine/browser/frame_window_tree_host.h
@@ -9,12 +9,17 @@
 
 #include "ui/aura/window_tree_host_platform.h"
 
+namespace content {
+class WebContents;
+}  // namespace content
+
 // aura::WindowTreeHost implementation used to present web content inside
 // web.Frame.
 class FrameWindowTreeHost : public aura::WindowTreeHostPlatform {
  public:
   FrameWindowTreeHost(fuchsia::ui::views::ViewToken view_token,
-                      scenic::ViewRefPair view_ref_pair);
+                      scenic::ViewRefPair view_ref_pair,
+                      content::WebContents* web_contents);
   ~FrameWindowTreeHost() final;
 
   FrameWindowTreeHost(const FrameWindowTreeHost&) = delete;
@@ -30,8 +35,9 @@
   void OnActivationChanged(bool active) final;
   void OnWindowStateChanged(ui::PlatformWindowState new_state) final;
 
-  fuchsia::ui::views::ViewRef view_ref_;
+  const fuchsia::ui::views::ViewRef view_ref_;
   std::unique_ptr<WindowParentingClientImpl> window_parenting_client_;
+  content::WebContents* const web_contents_;
 };
 
 #endif  // FUCHSIA_ENGINE_BROWSER_FRAME_WINDOW_TREE_HOST_H_
diff --git a/fuchsia/engine/test/data/visibility.html b/fuchsia/engine/test/data/visibility.html
new file mode 100644
index 0000000..c97bf12
--- /dev/null
+++ b/fuchsia/engine/test/data/visibility.html
@@ -0,0 +1,17 @@
+<!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><title>unset</title></head>
+  <body>
+    <script>
+        document.title = document.visibilityState;
+        document.addEventListener('visibilitychange', function(e) {
+          document.title = document.visibilityState;
+        });
+    </script>
+  </body>
+</html>
diff --git a/gpu/ipc/service/shared_image_stub.cc b/gpu/ipc/service/shared_image_stub.cc
index a8ad4c8..5cfe313 100644
--- a/gpu/ipc/service/shared_image_stub.cc
+++ b/gpu/ipc/service/shared_image_stub.cc
@@ -171,7 +171,6 @@
   }
   if (!factory_->CreateSharedImageWithAHB(out_mailbox, in_mailbox, usage)) {
     LOG(ERROR) << "SharedImageStub: Unable to update shared image";
-    OnError();
     return false;
   }
   return true;
diff --git a/headless/lib/headless_content_main_delegate.cc b/headless/lib/headless_content_main_delegate.cc
index edb7da9..ccb71a6 100644
--- a/headless/lib/headless_content_main_delegate.cc
+++ b/headless/lib/headless_content_main_delegate.cc
@@ -274,7 +274,7 @@
         command_line.GetSwitchValueASCII(::switches::kLoggingLevel);
     int level = 0;
     if (base::StringToInt(log_level, &level) && level >= 0 &&
-        level < logging::LOG_NUM_SEVERITIES) {
+        level < logging::LOGGING_NUM_SEVERITIES) {
       logging::SetMinLogLevel(level);
     } else {
       DLOG(WARNING) << "Bad log level: " << log_level;
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/address_view_controller_egtest.mm b/ios/chrome/browser/ui/autofill/manual_fill/address_view_controller_egtest.mm
index f64db3f..45f2617 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/address_view_controller_egtest.mm
+++ b/ios/chrome/browser/ui/autofill/manual_fill/address_view_controller_egtest.mm
@@ -39,7 +39,7 @@
   GREYCondition* waitForKeyboard = [GREYCondition
       conditionWithName:@"Wait for keyboard"
                   block:^BOOL {
-                    return [ChromeEarlGrey isKeyboardShownWithError:nil];
+                    return [EarlGrey isKeyboardShownWithError:nil];
                   }];
   return [waitForKeyboard waitWithTimeout:kWaitForActionTimeout];
 }
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/card_view_controller_egtest.mm b/ios/chrome/browser/ui/autofill/manual_fill/card_view_controller_egtest.mm
index ac402dba..0ad99be 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/card_view_controller_egtest.mm
+++ b/ios/chrome/browser/ui/autofill/manual_fill/card_view_controller_egtest.mm
@@ -97,7 +97,7 @@
   GREYCondition* waitForKeyboard = [GREYCondition
       conditionWithName:@"Wait for keyboard"
                   block:^BOOL {
-                    return [ChromeEarlGrey isKeyboardShownWithError:nil];
+                    return [EarlGrey isKeyboardShownWithError:nil];
                   }];
   return [waitForKeyboard waitWithTimeout:kWaitForActionTimeout];
 }
@@ -127,8 +127,7 @@
 
 - (void)tearDown {
   [AutofillAppInterface clearCreditCardStore];
-  [ChromeEarlGrey rotateDeviceToOrientation:UIDeviceOrientationPortrait
-                                      error:nil];
+  [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationPortrait error:nil];
   [super tearDown];
 }
 
@@ -515,8 +514,8 @@
       selectElementWithMatcher:ManualFallbackCreditCardTableViewMatcher()]
       assertWithMatcher:grey_sufficientlyVisible()];
 
-  [ChromeEarlGrey rotateDeviceToOrientation:UIDeviceOrientationLandscapeLeft
-                                      error:nil];
+  [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationLandscapeLeft
+                                error:nil];
 
   // Verify the credit card controller table view is still visible.
   [[EarlGrey
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/fallback_coordinator_egtest.mm b/ios/chrome/browser/ui/autofill/manual_fill/fallback_coordinator_egtest.mm
index 886523b..8fdb269c 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/fallback_coordinator_egtest.mm
+++ b/ios/chrome/browser/ui/autofill/manual_fill/fallback_coordinator_egtest.mm
@@ -126,7 +126,7 @@
   GREYCondition* waitForKeyboard = [GREYCondition
       conditionWithName:@"Wait for keyboard"
                   block:^BOOL {
-                    return [ChromeEarlGrey isKeyboardShownWithError:nil];
+                    return [EarlGrey isKeyboardShownWithError:nil];
                   }];
   return [waitForKeyboard waitWithTimeout:kWaitForActionTimeout];
 }
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/password_view_controller_egtest.mm b/ios/chrome/browser/ui/autofill/manual_fill/password_view_controller_egtest.mm
index 7d5f2e7..eb42f0e 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/password_view_controller_egtest.mm
+++ b/ios/chrome/browser/ui/autofill/manual_fill/password_view_controller_egtest.mm
@@ -266,7 +266,7 @@
       performAction:grey_tap()];
 
   // Verify keyboard is shown without the password controller.
-  GREYAssertTrue([ChromeEarlGrey isKeyboardShownWithError:nil],
+  GREYAssertTrue([EarlGrey isKeyboardShownWithError:nil],
                  @"Keyboard Should be Shown");
   [[EarlGrey selectElementWithMatcher:ManualFallbackPasswordTableViewMatcher()]
       assertWithMatcher:grey_notVisible()];
@@ -294,7 +294,7 @@
   // Tap the password search.
   [[EarlGrey selectElementWithMatcher:ManualFallbackPasswordSearchBarMatcher()]
       performAction:grey_tap()];
-  GREYAssertTrue([ChromeEarlGrey isKeyboardShownWithError:nil],
+  GREYAssertTrue([EarlGrey isKeyboardShownWithError:nil],
                  @"Keyboard Should be Shown");
 
   // Select a username.
@@ -484,8 +484,8 @@
   [[EarlGrey selectElementWithMatcher:ManualFallbackPasswordTableViewMatcher()]
       assertWithMatcher:grey_sufficientlyVisible()];
 
-  [ChromeEarlGrey rotateDeviceToOrientation:UIDeviceOrientationLandscapeLeft
-                                      error:nil];
+  [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationLandscapeLeft
+                                error:nil];
 
   // Verify the password controller table view is still visible.
   [[EarlGrey selectElementWithMatcher:ManualFallbackPasswordTableViewMatcher()]
@@ -505,7 +505,7 @@
       performAction:TapWebElementWithIdInFrame(kFormElementUsername, 0)];
 
   // Wait for the accessory icon to appear.
-  GREYAssertTrue([ChromeEarlGrey isKeyboardShownWithError:nil],
+  GREYAssertTrue([EarlGrey isKeyboardShownWithError:nil],
                  @"Keyboard Should be Shown");
 
   // Tap on the passwords icon.
@@ -541,7 +541,7 @@
       performAction:TapWebElementWithId(kFormElementUsername)];
 
   // Wait for the accessory icon to appear.
-  GREYAssertTrue([ChromeEarlGrey isKeyboardShownWithError:nil],
+  GREYAssertTrue([EarlGrey isKeyboardShownWithError:nil],
                  @"Keyboard Should be Shown");
 
   // Tap on the passwords icon.
@@ -570,7 +570,7 @@
       performAction:TapWebElementWithId(kFormElementUsername)];
 
   // Wait for the keyboard to appear.
-  GREYAssertTrue([ChromeEarlGrey isKeyboardShownWithError:nil],
+  GREYAssertTrue([EarlGrey isKeyboardShownWithError:nil],
                  @"Keyboard Should be Shown");
 
   // Assert the password icon is not enabled and not visible.
diff --git a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_egtest.mm b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_egtest.mm
index aa24979..6828d68 100644
--- a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_egtest.mm
+++ b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_egtest.mm
@@ -411,7 +411,7 @@
 
   [[EarlGrey selectElementWithMatcher:row]
       assertWithMatcher:grey_sufficientlyVisible()];
-  GREYAssertTrue([ChromeEarlGrey isKeyboardShownWithError:nil],
+  GREYAssertTrue([EarlGrey isKeyboardShownWithError:nil],
                  @"Keyboard Should be Shown");
 
   // Scroll the popup.
@@ -426,10 +426,10 @@
   // The keyboard should only be dismissed on phones. Ipads, even in
   // multitasking, are considered tall enough to fit all suggestions.
   if ([ChromeEarlGrey isIPadIdiom]) {
-    GREYAssertTrue([ChromeEarlGrey isKeyboardShownWithError:nil],
+    GREYAssertTrue([EarlGrey isKeyboardShownWithError:nil],
                    @"Keyboard Should be Shown");
   } else {
-    GREYAssertFalse([ChromeEarlGrey isKeyboardShownWithError:nil],
+    GREYAssertFalse([EarlGrey isKeyboardShownWithError:nil],
                     @"Keyboard Should not be Shown");
   }
 }
diff --git a/ios/chrome/browser/ui/page_info/legacy_page_info_egtest.mm b/ios/chrome/browser/ui/page_info/legacy_page_info_egtest.mm
index 8978715..9334d41 100644
--- a/ios/chrome/browser/ui/page_info/legacy_page_info_egtest.mm
+++ b/ios/chrome/browser/ui/page_info/legacy_page_info_egtest.mm
@@ -37,8 +37,7 @@
   }
 
   if ([[UIDevice currentDevice] orientation] != UIDeviceOrientationPortrait) {
-    [ChromeEarlGrey rotateDeviceToOrientation:UIDeviceOrientationPortrait
-                                        error:nil];
+    [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationPortrait error:nil];
   }
 
   [ChromeEarlGrey loadURL:GURL("https://invalid")];
@@ -55,8 +54,8 @@
   [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
                                           kPageInfoViewAccessibilityIdentifier)]
       assertWithMatcher:grey_sufficientlyVisible()];
-  [ChromeEarlGrey rotateDeviceToOrientation:UIDeviceOrientationLandscapeRight
-                                      error:nil];
+  [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationLandscapeRight
+                                error:nil];
 
   // Expect that the page info view has disappeared.
   [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
diff --git a/ios/chrome/browser/ui/tab_grid/tab_grid_transition_egtest.mm b/ios/chrome/browser/ui/tab_grid/tab_grid_transition_egtest.mm
index 941d9c2..4fca0d7 100644
--- a/ios/chrome/browser/ui/tab_grid/tab_grid_transition_egtest.mm
+++ b/ios/chrome/browser/ui/tab_grid/tab_grid_transition_egtest.mm
@@ -73,8 +73,7 @@
 // Rotate the device back to portrait if needed, since some tests attempt to run
 // in landscape.
 - (void)tearDown {
-  [ChromeEarlGrey rotateDeviceToOrientation:UIDeviceOrientationPortrait
-                                      error:nil];
+  [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationPortrait error:nil];
   [super tearDown];
 }
 
@@ -332,24 +331,22 @@
   [ChromeEarlGrey loadURL:[self makeURLForTitle:tab_title]];
 
   // Show the tab switcher and return to the BVC, in portrait.
-  [ChromeEarlGrey rotateDeviceToOrientation:UIDeviceOrientationPortrait
-                                      error:nil];
+  [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationPortrait error:nil];
   [ChromeEarlGrey showTabSwitcher];
   SelectTab(tab_title);
   [ChromeEarlGrey
       waitForWebStateContainingText:base::SysNSStringToUTF8(tab_title)];
 
   // Show the tab switcher and return to the BVC, in landscape.
-  [ChromeEarlGrey rotateDeviceToOrientation:UIDeviceOrientationLandscapeLeft
-                                      error:nil];
+  [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationLandscapeLeft
+                                error:nil];
   [ChromeEarlGrey showTabSwitcher];
   SelectTab(tab_title);
   [ChromeEarlGrey
       waitForWebStateContainingText:base::SysNSStringToUTF8(tab_title)];
 
   // Show the tab switcher and return to the BVC, in portrait.
-  [ChromeEarlGrey rotateDeviceToOrientation:UIDeviceOrientationPortrait
-                                      error:nil];
+  [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationPortrait error:nil];
   [ChromeEarlGrey showTabSwitcher];
   SelectTab(tab_title);
   [ChromeEarlGrey
diff --git a/ios/chrome/browser/ui/toolbar/adaptive_toolbar_egtest.mm b/ios/chrome/browser/ui/toolbar/adaptive_toolbar_egtest.mm
index 727e384..9144adf 100644
--- a/ios/chrome/browser/ui/toolbar/adaptive_toolbar_egtest.mm
+++ b/ios/chrome/browser/ui/toolbar/adaptive_toolbar_egtest.mm
@@ -173,8 +173,8 @@
             forViewController:topViewController];
   } else {
     // On iPhone rotate to test the the landscape orientation.
-    [ChromeEarlGrey rotateDeviceToOrientation:UIDeviceOrientationLandscapeLeft
-                                        error:nil];
+    [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationLandscapeLeft
+                                  error:nil];
     return topViewController.traitCollection;
   }
 }
@@ -415,8 +415,7 @@
     }
   } else {
     // Cancel the rotation.
-    [ChromeEarlGrey rotateDeviceToOrientation:UIDeviceOrientationPortrait
-                                        error:nil];
+    [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationPortrait error:nil];
   }
 
   // Check the visiblity after a rotation.
@@ -454,8 +453,7 @@
     }
   } else {
     // Cancel the rotation.
-    [ChromeEarlGrey rotateDeviceToOrientation:UIDeviceOrientationPortrait
-                                        error:nil];
+    [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationPortrait error:nil];
   }
 
   // Check the visiblity after a size class change. This should let the trait
@@ -540,8 +538,8 @@
 - (void)testShareButton {
   if (![ChromeEarlGrey isIPadIdiom]) {
     // If this test is run on an iPhone, rotate it to have the unsplit toolbar.
-    [ChromeEarlGrey rotateDeviceToOrientation:UIDeviceOrientationLandscapeLeft
-                                        error:nil];
+    [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationLandscapeLeft
+                                  error:nil];
   }
 
   // Setup the server.
@@ -557,8 +555,7 @@
 
   if (![ChromeEarlGrey isIPadIdiom]) {
     // Cancel rotation.
-    [ChromeEarlGrey rotateDeviceToOrientation:UIDeviceOrientationPortrait
-                                        error:nil];
+    [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationPortrait error:nil];
   }
 }
 
@@ -619,8 +616,7 @@
     }
   } else {
     // Cancel the rotation.
-    [ChromeEarlGrey rotateDeviceToOrientation:UIDeviceOrientationPortrait
-                                        error:nil];
+    [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationPortrait error:nil];
   }
 }
 
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey.h b/ios/chrome/test/earl_grey/chrome_earl_grey.h
index 5bbb599..fd63cc3 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey.h
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey.h
@@ -46,16 +46,6 @@
 
 #pragma mark - Device Utilities
 
-// Simulate the user action to rotate the device to a certain orientation.
-// TODO(crbug.com/1017265): Remove along EG1 support.
-- (void)rotateDeviceToOrientation:(UIDeviceOrientation)deviceOrientation
-                            error:(NSError**)error;
-
-// Returns |YES| if the keyboard is on screen. |error| is only supported if the
-// test is running in EG2.
-// TODO(crbug.com/1017281): Remove along EG1 support.
-- (BOOL)isKeyboardShownWithError:(NSError**)error;
-
 // Returns YES if running on an iPad.
 - (BOOL)isIPadIdiom;
 
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey.mm b/ios/chrome/test/earl_grey/chrome_earl_grey.mm
index a7fe700f..cfe6845 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey.mm
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey.mm
@@ -79,15 +79,6 @@
 
 #pragma mark - Device Utilities
 
-- (void)rotateDeviceToOrientation:(UIDeviceOrientation)deviceOrientation
-                            error:(NSError**)error {
-  [EarlGrey rotateDeviceToOrientation:deviceOrientation error:error];
-}
-
-- (BOOL)isKeyboardShownWithError:(NSError**)error {
-  return [EarlGrey isKeyboardShownWithError:error];
-}
-
 - (BOOL)isIPadIdiom {
   UIUserInterfaceIdiom idiom =
       [[GREY_REMOTE_CLASS_IN_APP(UIDevice) currentDevice] userInterfaceIdiom];
diff --git a/ios/chrome/test/earl_grey/chrome_test_case.mm b/ios/chrome/test/earl_grey/chrome_test_case.mm
index 3581fc9b..2269569 100644
--- a/ios/chrome/test/earl_grey/chrome_test_case.mm
+++ b/ios/chrome/test/earl_grey/chrome_test_case.mm
@@ -227,7 +227,7 @@
       _originalOrientation) {
     // Rotate the device back to the original orientation, since some tests
     // attempt to run in other orientations.
-    [ChromeEarlGrey rotateDeviceToOrientation:_originalOrientation error:nil];
+    [EarlGrey rotateDeviceToOrientation:_originalOrientation error:nil];
   }
   [super tearDown];
   _executedTestMethodSetUp = NO;
@@ -349,8 +349,7 @@
   [ChromeEarlGrey setContentSettings:CONTENT_SETTING_DEFAULT];
 
   // Enforce the assumption that the tests are runing in portrait.
-  [ChromeEarlGrey rotateDeviceToOrientation:UIDeviceOrientationPortrait
-                                      error:nil];
+  [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationPortrait error:nil];
 }
 
 // Resets the application state.
diff --git a/media/audio/audio_encoders_unittest.cc b/media/audio/audio_encoders_unittest.cc
index 78b4d4ef..4cff1e26 100644
--- a/media/audio/audio_encoders_unittest.cc
+++ b/media/audio/audio_encoders_unittest.cc
@@ -82,7 +82,7 @@
   ~AudioEncodersTest() override = default;
 
   const AudioParameters& input_params() const { return input_params_; }
-  const AudioEncoder* encoder() const { return encoder_.get(); }
+  AudioEncoder* encoder() const { return encoder_.get(); }
   int encode_callback_count() const { return encode_callback_count_; }
 
   void SetEncoder(std::unique_ptr<AudioEncoder> encoder) {
@@ -200,6 +200,14 @@
 
   EXPECT_EQ(1, encode_callback_count());
 
+  // If there are remaining frames in the opus encoder FIFO, we need to flush
+  // them before we destroy the encoder. Flushing should trigger the encode
+  // callback and we should be able to decode the resulting encoded frames.
+  if (total_frames > frames_in_60_ms) {
+    encoder()->Flush();
+    EXPECT_EQ(2, encode_callback_count());
+  }
+
   opus_decoder_destroy(opus_decoder);
   opus_decoder = nullptr;
 }
diff --git a/media/audio/audio_opus_encoder.cc b/media/audio/audio_opus_encoder.cc
index 2861653..fbc2ed5 100644
--- a/media/audio/audio_opus_encoder.cc
+++ b/media/audio/audio_opus_encoder.cc
@@ -207,7 +207,10 @@
       converted_params_.frames_per_buffer()));
 }
 
-AudioOpusEncoder::~AudioOpusEncoder() = default;
+AudioOpusEncoder::~AudioOpusEncoder() {
+  DCHECK_EQ(fifo_.queued_frames(), 0)
+      << "Must flush the encoder before destroying to avoid dropping frames.";
+}
 
 void AudioOpusEncoder::EncodeAudioImpl(const AudioBus& audio_bus,
                                        base::TimeTicks capture_time) {
@@ -220,6 +223,20 @@
   fifo_.Push(audio_bus);
 }
 
+void AudioOpusEncoder::FlushImpl() {
+  // Initializing the opus encoder may have failed.
+  if (!opus_encoder_)
+    return;
+
+  // This is needed to correctly compute the timestamp, since the number of
+  // frames of |output_bus| provided to OnFifoOutput() will always be equal to
+  // the full frames_per_buffer(), as the fifo's Flush() will pad the remaining
+  // empty frames with zeros.
+  number_of_flushed_frames_ = fifo_.queued_frames();
+  fifo_.Flush();
+  number_of_flushed_frames_ = base::nullopt;
+}
+
 void AudioOpusEncoder::OnFifoOutput(const AudioBus& output_bus,
                                     int frame_delay) {
   // Provides input to the converter from |output_bus| within this scope only.
@@ -236,7 +253,9 @@
     DCHECK_GT(encoded_data_size, 1u);
     encode_callback().Run(EncodedAudioBuffer(
         converted_params_, std::move(encoded_data), encoded_data_size,
-        ComputeTimestamp(output_bus.frames(), last_capture_time())));
+        ComputeTimestamp(
+            number_of_flushed_frames_.value_or(output_bus.frames()),
+            last_capture_time())));
   }
 }
 
diff --git a/media/audio/audio_opus_encoder.h b/media/audio/audio_opus_encoder.h
index d4919ab..42983c98 100644
--- a/media/audio/audio_opus_encoder.h
+++ b/media/audio/audio_opus_encoder.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <vector>
 
+#include "base/optional.h"
 #include "media/base/audio_bus.h"
 #include "media/base/audio_converter.h"
 #include "media/base/audio_encoder.h"
@@ -36,6 +37,7 @@
   // AudioEncoder:
   void EncodeAudioImpl(const AudioBus& audio_bus,
                        base::TimeTicks capture_time) override;
+  void FlushImpl() override;
 
  private:
   // Called synchronously by |fifo_| once enough audio frames have been
@@ -70,6 +72,11 @@
   // The actual libopus encoder instance. This is nullptr if creating the
   // encoder fails.
   OwnedOpusEncoder opus_encoder_;
+
+  // If FlushImpl() was called while |fifo_| has some frames but not full yet,
+  // this will be the number of flushed frames, which is used to compute the
+  // timestamp provided in the output |EncodedAudioBuffer|.
+  base::Optional<int> number_of_flushed_frames_;
 };
 
 }  // namespace media
diff --git a/media/audio/audio_pcm_encoder.cc b/media/audio/audio_pcm_encoder.cc
index 103378a3..a3722ef 100644
--- a/media/audio/audio_pcm_encoder.cc
+++ b/media/audio/audio_pcm_encoder.cc
@@ -27,4 +27,8 @@
                          ComputeTimestamp(audio_bus.frames(), capture_time)));
 }
 
+void AudioPcmEncoder::FlushImpl() {
+  // No buffering is done here, so do nothing.
+}
+
 }  // namespace media
diff --git a/media/audio/audio_pcm_encoder.h b/media/audio/audio_pcm_encoder.h
index af3a519..7bd0da1d 100644
--- a/media/audio/audio_pcm_encoder.h
+++ b/media/audio/audio_pcm_encoder.h
@@ -24,6 +24,7 @@
   // AudioEncoder:
   void EncodeAudioImpl(const AudioBus& audio_bus,
                        base::TimeTicks capture_time) override;
+  void FlushImpl() override;
 };
 
 }  // namespace media
diff --git a/media/base/audio_encoder.cc b/media/base/audio_encoder.cc
index 0a9ca22e..64b5cab 100644
--- a/media/base/audio_encoder.cc
+++ b/media/base/audio_encoder.cc
@@ -38,14 +38,16 @@
   DCHECK(audio_input_params_.IsValid());
   DCHECK(!encode_callback_.is_null());
   DCHECK(!status_callback_.is_null());
-  DETACH_FROM_THREAD(thread_checker_);
+  DETACH_FROM_SEQUENCE(sequence_checker_);
 }
 
-AudioEncoder::~AudioEncoder() = default;
+AudioEncoder::~AudioEncoder() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
 
 void AudioEncoder::EncodeAudio(const AudioBus& audio_bus,
                                base::TimeTicks capture_time) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK_EQ(audio_bus.channels(), audio_input_params_.channels());
   DCHECK(!capture_time.is_null());
 
@@ -61,6 +63,12 @@
   EncodeAudioImpl(audio_bus, capture_time);
 }
 
+void AudioEncoder::Flush() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  FlushImpl();
+}
+
 base::TimeTicks AudioEncoder::ComputeTimestamp(
     int num_frames,
     base::TimeTicks capture_time) const {
diff --git a/media/base/audio_encoder.h b/media/base/audio_encoder.h
index e7ad866..c25218c 100644
--- a/media/base/audio_encoder.h
+++ b/media/base/audio_encoder.h
@@ -8,7 +8,7 @@
 #include <memory>
 
 #include "base/callback.h"
-#include "base/threading/thread_checker.h"
+#include "base/sequence_checker.h"
 #include "base/time/time.h"
 #include "media/base/audio_bus.h"
 #include "media/base/audio_parameters.h"
@@ -41,7 +41,8 @@
   // number of encoded bytes may not be known in advance.
   const size_t encoded_data_size;
 
-  // The capture time of the first sample of the current AudioBus.
+  // The capture time of the first sample of the current AudioBus, or a previous
+  // AudioBus If this output was generated because of a call to Flush().
   const base::TimeTicks timestamp;
 };
 
@@ -50,9 +51,9 @@
 class MEDIA_EXPORT AudioEncoder {
  public:
   // Signature of the callback invoked to provide the encoded audio data. It is
-  // invoked on the same thread on which EncodeAudio() is called. The utility
+  // invoked on the same sequence on which EncodeAudio() is called. The utility
   // media::BindToCurrentLoop() can be used to create a callback that will be
-  // invoked on the same thread it is constructed on.
+  // invoked on the same sequence it is constructed on.
   using EncodeCB = base::RepeatingCallback<void(EncodedAudioBuffer output)>;
 
   // Signature of the callback to report errors.
@@ -62,8 +63,8 @@
   // encoder, and a callback to trigger to provide the encoded audio data.
   // |input_params| must be valid, and |encode_callback| and |status_callback|
   // must not be null callbacks. All calls to EncodeAudio() must happen on the
-  // same thread (usually an encoder thread), but the encoder itself can be
-  // constructed on any thread.
+  // same sequence (usually an encoder blocking pool sequence), but the encoder
+  // itself can be constructed on any sequence.
   AudioEncoder(const AudioParameters& input_params,
                EncodeCB encode_callback,
                StatusCB status_callback);
@@ -79,6 +80,11 @@
   // actual encoding.
   void EncodeAudio(const AudioBus& audio_bus, base::TimeTicks capture_time);
 
+  // Some encoders may choose to buffer audio frames before they encode them.
+  // This function provides a mechanism to drain and encode any buffered frames
+  // (if any). Must be called on the encoder sequence.
+  void Flush();
+
  protected:
   const EncodeCB& encode_callback() const { return encode_callback_; }
   const StatusCB& status_callback() const { return status_callback_; }
@@ -87,6 +93,8 @@
   virtual void EncodeAudioImpl(const AudioBus& audio_bus,
                                base::TimeTicks capture_time) = 0;
 
+  virtual void FlushImpl() = 0;
+
   // Computes the timestamp of an AudioBus which has |num_frames| and was
   // captured at |capture_time|. This timestamp is the capture time of the first
   // sample in that AudioBus.
@@ -104,7 +112,7 @@
   // EncodeAudio().
   base::TimeTicks last_capture_time_;
 
-  THREAD_CHECKER(thread_checker_);
+  SEQUENCE_CHECKER(sequence_checker_);
 };
 
 }  // namespace media
diff --git a/media/base/audio_push_fifo.h b/media/base/audio_push_fifo.h
index 2eb6bfc..fb9c615c 100644
--- a/media/base/audio_push_fifo.h
+++ b/media/base/audio_push_fifo.h
@@ -42,6 +42,9 @@
   // OutputCallback.
   int frames_per_buffer() const { return frames_per_buffer_; }
 
+  // The number of frames currently queued in this FIFO.
+  int queued_frames() const { return queued_frames_; }
+
   // Must be called at least once before the first call to Push().  May be
   // called later (e.g., to support an audio format change).
   void Reset(int frames_per_buffer);
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
index 15e08b5..21fd58f 100644
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -381,6 +381,19 @@
 const base::Feature kGlobalMediaControlsForChromeOS{
     "GlobalMediaControlsForChromeOS", base::FEATURE_DISABLED_BY_DEFAULT};
 
+constexpr base::FeatureParam<kCrosGlobalMediaControlsPinOptions>::Option
+    kCrosGlobalMediaControlsParamOptions[] = {
+        {kCrosGlobalMediaControlsPinOptions::kPin, "default-pinned"},
+        {kCrosGlobalMediaControlsPinOptions::kNotPin, "default-unpinned"},
+        {kCrosGlobalMediaControlsPinOptions::kHeuristic, "heuristic"}};
+
+constexpr base::FeatureParam<kCrosGlobalMediaControlsPinOptions>
+    kCrosGlobalMediaControlsPinParam(
+        &kGlobalMediaControlsForChromeOS,
+        "CrosGlobalMediaControlsPinParam",
+        kCrosGlobalMediaControlsPinOptions::kHeuristic,
+        &kCrosGlobalMediaControlsParamOptions);
+
 // Allow global media controls notifications to be dragged out into overlay
 // notifications. It is no-op if kGlobalMediaControls is not enabled.
 const base::Feature kGlobalMediaControlsOverlayControls{
diff --git a/media/base/media_switches.h b/media/base/media_switches.h
index 905aedcc..782bfaa 100644
--- a/media/base/media_switches.h
+++ b/media/base/media_switches.h
@@ -239,6 +239,17 @@
 
 MEDIA_EXPORT bool IsVideoCaptureAcceleratedJpegDecodingEnabled();
 
+enum class kCrosGlobalMediaControlsPinOptions {
+  kPin,
+  kNotPin,
+  kHeuristic,
+};
+
+// Feature param used to force default pin/unpin for global media controls in
+// CrOS.
+MEDIA_EXPORT extern const base::FeatureParam<kCrosGlobalMediaControlsPinOptions>
+    kCrosGlobalMediaControlsPinParam;
+
 }  // namespace media
 
 #endif  // MEDIA_BASE_MEDIA_SWITCHES_H_
diff --git a/media/gpu/vaapi/vaapi_video_decoder.cc b/media/gpu/vaapi/vaapi_video_decoder.cc
index 138ea86..550353a 100644
--- a/media/gpu/vaapi/vaapi_video_decoder.cc
+++ b/media/gpu/vaapi/vaapi_video_decoder.cc
@@ -143,16 +143,11 @@
     return;
   }
 
-#if BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA)
-  // TODO(jkardatzke): Implement protected media handling.
-  NOTREACHED();
-#else
   if (cdm_context || config.is_encrypted()) {
     VLOGF(1) << "Vaapi decoder does not support encrypted stream";
     std::move(init_cb).Run(StatusCode::kEncryptedContentUnsupported);
     return;
   }
-#endif
 
   // We expect the decoder to have released all output buffers (by the client
   // triggering a flush or reset), even if the
@@ -506,11 +501,7 @@
     vaapi_wrapper_ = std::move(new_vaapi_wrapper);
   }
 
-  if (vaapi_wrapper_->CreateContext(pic_size)) {
-    VLOGF(1) << "Failed creating context";
-    SetState(State::kError);
-    return;
-  }
+  vaapi_wrapper_->CreateContext(pic_size);
 
   // If we reset during resolution change, then there is no decode tasks. In
   // this case we do nothing and wait for next input. Otherwise, continue
diff --git a/media/gpu/vaapi/vaapi_wrapper.h b/media/gpu/vaapi/vaapi_wrapper.h
index 6fced32..d67b497 100644
--- a/media/gpu/vaapi/vaapi_wrapper.h
+++ b/media/gpu/vaapi/vaapi_wrapper.h
@@ -18,7 +18,6 @@
 #include <set>
 #include <vector>
 
-#include "base/compiler_specific.h"
 #include "base/files/file.h"
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
@@ -237,8 +236,7 @@
                                         const gfx::Size& size,
                                         SurfaceUsageHint surface_usage_hint,
                                         size_t num_surfaces,
-                                        std::vector<VASurfaceID>* va_surfaces)
-      WARN_UNUSED_RESULT;
+                                        std::vector<VASurfaceID>* va_surfaces);
 
   // Creates a single ScopedVASurface of |va_format| and |size| and, if
   // successful, creates a |va_context_id_| of the same size. Returns nullptr if
@@ -257,7 +255,7 @@
   // VPP VaapiWrapper, |size| is ignored and 0x0 is used to create the context.
   // The client is responsible for releasing it via DestroyContext() or
   // DestroyContextAndSurfaces(), or it will be released on dtor.
-  virtual bool CreateContext(const gfx::Size& size) WARN_UNUSED_RESULT;
+  virtual bool CreateContext(const gfx::Size& size);
 
   // Destroys the context identified by |va_context_id_|.
   virtual void DestroyContext();
@@ -304,7 +302,7 @@
 
   // Synchronize the VASurface explicitly. This is useful when sharing a surface
   // between contexts.
-  bool SyncSurface(VASurfaceID va_surface_id) WARN_UNUSED_RESULT;
+  bool SyncSurface(VASurfaceID va_surface_id);
 
   // Calls SubmitBuffer_Locked() to request libva to allocate a new VABufferID
   // of |va_buffer_type| and |size|, and to map-and-copy the |data| into it. The
@@ -312,14 +310,11 @@
   // that this method does not submit the buffers for execution, they are simply
   // stored until ExecuteAndDestroyPendingBuffers()/Execute_Locked(). The
   // ownership of |data| stays with the caller.
-  bool SubmitBuffer(VABufferType va_buffer_type,
-                    size_t size,
-                    const void* data) WARN_UNUSED_RESULT;
+  bool SubmitBuffer(VABufferType va_buffer_type, size_t size, const void* data);
   // Convenient templatized version of SubmitBuffer() where |size| is deduced to
   // be the size of the type of |*data|.
   template <typename T>
-  bool SubmitBuffer(VABufferType va_buffer_type,
-                    const T* data) WARN_UNUSED_RESULT {
+  bool SubmitBuffer(VABufferType va_buffer_type, const T* data) {
     return SubmitBuffer(va_buffer_type, sizeof(T), data);
   }
   // Batch-version of SubmitBuffer(), where the lock for accessing libva is
@@ -329,8 +324,7 @@
     size_t size;
     const void* data;
   };
-  bool SubmitBuffers(const std::vector<VABufferDescriptor>& va_buffers)
-      WARN_UNUSED_RESULT;
+  bool SubmitBuffers(const std::vector<VABufferDescriptor>& va_buffers);
 
   // Destroys all |pending_va_buffers_| sent via SubmitBuffer*(). Useful when a
   // pending job is to be cancelled (on reset or error).
@@ -338,22 +332,20 @@
 
   // Executes job in hardware on target |va_surface_id| and destroys pending
   // buffers. Returns false if Execute() fails.
-  virtual bool ExecuteAndDestroyPendingBuffers(VASurfaceID va_surface_id)
-      WARN_UNUSED_RESULT;
+  virtual bool ExecuteAndDestroyPendingBuffers(VASurfaceID va_surface_id);
 
   // Maps each |va_buffers| ID and copies the data described by the associated
   // VABufferDescriptor into it; then calls Execute_Locked() on |va_surface_id|.
   bool MapAndCopyAndExecute(
       VASurfaceID va_surface_id,
-      const std::vector<std::pair<VABufferID, VABufferDescriptor>>& va_buffers)
-      WARN_UNUSED_RESULT;
+      const std::vector<std::pair<VABufferID, VABufferDescriptor>>& va_buffers);
 
 #if defined(USE_X11)
   // Put data from |va_surface_id| into |x_pixmap| of size
   // |dest_size|, converting/scaling to it.
   bool PutSurfaceIntoPixmap(VASurfaceID va_surface_id,
                             Pixmap x_pixmap,
-                            gfx::Size dest_size) WARN_UNUSED_RESULT;
+                            gfx::Size dest_size);
 #endif  // USE_X11
 
   // Creates a ScopedVAImage from a VASurface |va_surface_id| and map it into
@@ -368,8 +360,7 @@
   // Uploads contents of |frame| into |va_surface_id| for encode.
   virtual bool UploadVideoFrameToSurface(const VideoFrame& frame,
                                          VASurfaceID va_surface_id,
-                                         const gfx::Size& va_surface_size)
-      WARN_UNUSED_RESULT;
+                                         const gfx::Size& va_surface_size);
 
   // Creates a buffer of |size| bytes to be used as encode output.
   virtual std::unique_ptr<ScopedVABuffer> CreateVABuffer(VABufferType type,
@@ -381,8 +372,7 @@
   // source surface passed to the encode job. Returns 0 if it fails for any
   // reason.
   virtual uint64_t GetEncodedChunkSize(VABufferID buffer_id,
-                                       VASurfaceID sync_surface_id)
-      WARN_UNUSED_RESULT;
+                                       VASurfaceID sync_surface_id);
 
   // Downloads the contents of the buffer with given |buffer_id| into a buffer
   // of size |target_size|, pointed to by |target_ptr|. The number of bytes
@@ -395,7 +385,7 @@
                                     VASurfaceID sync_surface_id,
                                     uint8_t* target_ptr,
                                     size_t target_size,
-                                    size_t* coded_data_size) WARN_UNUSED_RESULT;
+                                    size_t* coded_data_size);
 
   // Get the max number of reference frames for encoding supported by the
   // driver.
@@ -403,8 +393,7 @@
   // frames for both the reference picture list 0 (bottom 16 bits) and the
   // reference picture list 1 (top 16 bits).
   virtual bool GetVAEncMaxNumOfRefFrames(VideoCodecProfile profile,
-                                         size_t* max_ref_frames)
-      WARN_UNUSED_RESULT;
+                                         size_t* max_ref_frames);
 
   // Checks if the driver supports frame rotation.
   bool IsRotationSupported();
@@ -417,8 +406,7 @@
                    const VASurface& va_surface_dest,
                    base::Optional<gfx::Rect> src_rect = base::nullopt,
                    base::Optional<gfx::Rect> dest_rect = base::nullopt,
-                   VideoRotation rotation = VIDEO_ROTATION_0)
-      WARN_UNUSED_RESULT;
+                   VideoRotation rotation = VIDEO_ROTATION_0);
 
   // Initialize static data before sandbox is enabled.
   static void PreSandboxInitialization();
@@ -438,10 +426,9 @@
   FRIEND_TEST_ALL_PREFIXES(VaapiUtilsTest, BadScopedVAImage);
   FRIEND_TEST_ALL_PREFIXES(VaapiUtilsTest, BadScopedVABufferMapping);
 
-  bool Initialize(CodecMode mode, VAProfile va_profile) WARN_UNUSED_RESULT;
+  bool Initialize(CodecMode mode, VAProfile va_profile);
   void Deinitialize();
-  bool VaInitialize(const ReportErrorToUMACB& report_error_to_uma_cb)
-      WARN_UNUSED_RESULT;
+  bool VaInitialize(const ReportErrorToUMACB& report_error_to_uma_cb);
 
   // Tries to allocate |num_surfaces| VASurfaceIDs of |size| and |va_format|.
   // Fills |va_surfaces| and returns true if successful, or returns false.
@@ -449,26 +436,26 @@
                       const gfx::Size& size,
                       SurfaceUsageHint usage_hint,
                       size_t num_surfaces,
-                      std::vector<VASurfaceID>* va_surfaces) WARN_UNUSED_RESULT;
+                      std::vector<VASurfaceID>* va_surfaces);
 
   // Carries out the vaBeginPicture()-vaRenderPicture()-vaEndPicture() on target
   // |va_surface_id|. Returns false if any of these calls fails.
   bool Execute_Locked(VASurfaceID va_surface_id,
                       const std::vector<VABufferID>& va_buffers)
-      EXCLUSIVE_LOCKS_REQUIRED(va_lock_) WARN_UNUSED_RESULT;
+      EXCLUSIVE_LOCKS_REQUIRED(va_lock_);
 
   void DestroyPendingBuffers_Locked() EXCLUSIVE_LOCKS_REQUIRED(va_lock_);
 
   // Requests libva to allocate a new VABufferID of type |va_buffer.type|, then
   // maps-and-copies |va_buffer.size| contents of |va_buffer.data| to it.
   bool SubmitBuffer_Locked(const VABufferDescriptor& va_buffer)
-      EXCLUSIVE_LOCKS_REQUIRED(va_lock_) WARN_UNUSED_RESULT;
+      EXCLUSIVE_LOCKS_REQUIRED(va_lock_);
 
   // Maps |va_buffer_id| and, if successful, copies the contents of |va_buffer|
   // into it.
   bool MapAndCopy_Locked(VABufferID va_buffer_id,
                          const VABufferDescriptor& va_buffer)
-      EXCLUSIVE_LOCKS_REQUIRED(va_lock_) WARN_UNUSED_RESULT;
+      EXCLUSIVE_LOCKS_REQUIRED(va_lock_);
 
   const CodecMode mode_;
 
diff --git a/media/muxers/webm_muxer.cc b/media/muxers/webm_muxer.cc
index e0a01a03f9..f4e9be0 100644
--- a/media/muxers/webm_muxer.cc
+++ b/media/muxers/webm_muxer.cc
@@ -9,6 +9,7 @@
 
 #include "base/bind.h"
 #include "base/logging.h"
+#include "base/sequence_checker.h"
 #include "media/base/audio_parameters.h"
 #include "media/base/limits.h"
 #include "media/base/video_frame.h"
@@ -182,16 +183,13 @@
   info->set_writing_app("Chrome");
   info->set_muxing_app("Chrome");
 
-  // Creation is done on a different thread than main activities.
-  thread_checker_.DetachFromThread();
+  // Creation can be done on a different sequence than main activities.
+  DETACH_FROM_SEQUENCE(sequence_checker_);
 }
 
 WebmMuxer::~WebmMuxer() {
-  // No need to segment_.Finalize() since is not Seekable(), i.e. a live
-  // stream, but is a good practice.
-  DCHECK(thread_checker_.CalledOnValidThread());
-  FlushQueues();
-  segment_.Finalize();
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  Flush();
 }
 
 bool WebmMuxer::OnEncodedVideo(const VideoParameters& params,
@@ -200,7 +198,7 @@
                                base::TimeTicks timestamp,
                                bool is_key_frame) {
   DVLOG(1) << __func__ << " - " << encoded_data.size() << "B";
-  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(params.codec == kCodecVP8 || params.codec == kCodecVP9 ||
          params.codec == kCodecH264)
       << " Unsupported video codec: " << GetCodecName(params.codec);
@@ -244,7 +242,7 @@
                                std::string encoded_data,
                                base::TimeTicks timestamp) {
   DVLOG(2) << __func__ << " - " << encoded_data.size() << "B";
-  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (!audio_track_index_) {
     AddAudioTrack(params);
@@ -266,25 +264,33 @@
 
 void WebmMuxer::Pause() {
   DVLOG(1) << __func__;
-  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!elapsed_time_in_pause_)
     elapsed_time_in_pause_.reset(new base::ElapsedTimer());
 }
 
 void WebmMuxer::Resume() {
   DVLOG(1) << __func__;
-  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (elapsed_time_in_pause_) {
     total_time_in_pause_ += elapsed_time_in_pause_->Elapsed();
     elapsed_time_in_pause_.reset();
   }
 }
 
+bool WebmMuxer::Flush() {
+  // No need to segment_.Finalize() since is not Seekable(), i.e. a live
+  // stream, but is a good practice.
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  FlushQueues();
+  return segment_.Finalize();
+}
+
 void WebmMuxer::AddVideoTrack(
     const gfx::Size& frame_size,
     double frame_rate,
     const base::Optional<gfx::ColorSpace>& color_space) {
-  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK_EQ(0u, video_track_index_)
       << "WebmMuxer can only be initialized once.";
 
@@ -329,7 +335,7 @@
 
 void WebmMuxer::AddAudioTrack(const media::AudioParameters& params) {
   DVLOG(1) << __func__ << " " << params.AsHumanReadableString();
-  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK_EQ(0u, audio_track_index_)
       << "WebmMuxer audio can only be initialised once.";
 
@@ -370,7 +376,7 @@
 }
 
 mkvmuxer::int32 WebmMuxer::Write(const void* buf, mkvmuxer::uint32 len) {
-  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(buf);
   write_data_callback_.Run(
       base::StringPiece(reinterpret_cast<const char*>(buf), len));
@@ -399,14 +405,14 @@
 }
 
 void WebmMuxer::FlushQueues() {
-  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   while ((!video_frames_.empty() || !audio_frames_.empty()) &&
          FlushNextFrame()) {
   }
 }
 
 bool WebmMuxer::PartiallyFlushQueues() {
-  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   bool result = true;
   while (!(has_video_ && video_frames_.empty()) &&
          !(has_audio_ && audio_frames_.empty()) && result) {
@@ -416,7 +422,7 @@
 }
 
 bool WebmMuxer::FlushNextFrame() {
-  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   base::TimeDelta min_timestamp = base::TimeDelta::Max();
   base::circular_deque<EncodedFrame>* queue = &video_frames_;
   uint8_t track_index = video_track_index_;
diff --git a/media/muxers/webm_muxer.h b/media/muxers/webm_muxer.h
index 59c628f..f22f3c1 100644
--- a/media/muxers/webm_muxer.h
+++ b/media/muxers/webm_muxer.h
@@ -13,8 +13,8 @@
 #include "base/containers/circular_deque.h"
 #include "base/macros.h"
 #include "base/numerics/safe_math.h"
+#include "base/sequence_checker.h"
 #include "base/strings/string_piece.h"
-#include "base/threading/thread_checker.h"
 #include "base/time/time.h"
 #include "base/timer/elapsed_timer.h"
 #include "media/base/audio_codecs.h"
@@ -89,6 +89,10 @@
   void Pause();
   void Resume();
 
+  // Drains and writes out all buffered frames and finalizes the segment.
+  // Returns true on success, false otherwise.
+  bool Flush();
+
   void ForceOneLibWebmErrorForTesting() { force_one_libwebm_error_ = true; }
 
  private:
@@ -137,9 +141,6 @@
       base::TimeTicks timestamp,
       base::TimeTicks* last_timestamp);
 
-  // Used to DCHECK that we are called on the correct thread.
-  base::ThreadChecker thread_checker_;
-
   // Audio codec configured on construction. Video codec is taken from first
   // received frame.
   const AudioCodec audio_codec_;
@@ -192,6 +193,8 @@
   // frame appears.
   base::circular_deque<EncodedFrame> video_frames_;
 
+  SEQUENCE_CHECKER(sequence_checker_);
+
   DISALLOW_COPY_AND_ASSIGN(WebmMuxer);
 };
 
diff --git a/media/test/data/README.md b/media/test/data/README.md
index ce6f621..7ee9c2d12 100644
--- a/media/test/data/README.md
+++ b/media/test/data/README.md
@@ -880,6 +880,15 @@
 Manually dumped from libvpx with bear-vp9.ivf and test-25fps.vp9. See
 vp9_parser_unittest.cc for description of their format.
 
+### HEVC parser test files:
+
+#### bear.hevc
+Used by h265_parser_unittest.cc.
+
+#### bbb.hevc
+Used by h265_parser_unittest.cc. Copied from bbb_hevc_176x144_176kbps_60fps.hevc
+in Android repo.
+
 ###  WebM files for testing multiple tracks.
 
 #### green-a300hz.webm
diff --git a/media/test/data/bbb.hevc b/media/test/data/bbb.hevc
new file mode 100644
index 0000000..f82236f
--- /dev/null
+++ b/media/test/data/bbb.hevc
Binary files differ
diff --git a/media/video/BUILD.gn b/media/video/BUILD.gn
index f7f80f3b..e9265d34 100644
--- a/media/video/BUILD.gn
+++ b/media/video/BUILD.gn
@@ -154,3 +154,15 @@
     "//ui/gfx/geometry",
   ]
 }
+
+# TODO(jkardatzke): Get the fuzzer build config updated so this target will
+# be included.
+if (proprietary_codecs && enable_platform_hevc) {
+  fuzzer_test("media_h265_parser_fuzzer") {
+    sources = [ "h265_parser_fuzzertest.cc" ]
+    deps = [
+      "//base",
+      "//media",
+    ]
+  }
+}
diff --git a/media/video/h265_parser.cc b/media/video/h265_parser.cc
index cd3a31c0..d8e15dcc 100644
--- a/media/video/h265_parser.cc
+++ b/media/video/h265_parser.cc
@@ -699,11 +699,11 @@
               dst = scaling_list_data->scaling_list_8x8[matrix_id];
               break;
             case 2:
-              dst = scaling_list_data->scaling_list_16x16[ref_matrix_id];
+              src = scaling_list_data->scaling_list_16x16[ref_matrix_id];
               dst = scaling_list_data->scaling_list_16x16[matrix_id];
               break;
             case 3:
-              dst = scaling_list_data->scaling_list_32x32[ref_matrix_id];
+              src = scaling_list_data->scaling_list_32x32[ref_matrix_id];
               dst = scaling_list_data->scaling_list_32x32[matrix_id];
               break;
           }
diff --git a/media/video/h265_parser.h b/media/video/h265_parser.h
index 4528cb71..3c36541 100644
--- a/media/video/h265_parser.h
+++ b/media/video/h265_parser.h
@@ -10,9 +10,9 @@
 #include <stdint.h>
 #include <sys/types.h>
 
-#include <map>
 #include <vector>
 
+#include "base/containers/flat_map.h"
 #include "base/macros.h"
 #include "media/base/media_export.h"
 #include "media/base/ranges.h"
@@ -181,7 +181,6 @@
 
   // Syntax elements.
   int sps_max_sub_layers_minus1;
-  bool sps_temporal_id_nesting_flag;
   H265ProfileTierLevel profile_tier_level;
   int sps_seq_parameter_set_id;
   int chroma_format_idc;
@@ -321,7 +320,7 @@
   H264BitReader br_;
 
   // SPSes stored for future reference.
-  std::map<int, std::unique_ptr<H265SPS>> active_sps_;
+  base::flat_map<int, std::unique_ptr<H265SPS>> active_sps_;
 
   // Ranges of encrypted bytes in the buffer passed to SetEncryptedStream().
   Ranges<const uint8_t*> encrypted_ranges_;
diff --git a/media/video/h265_parser_fuzzertest.cc b/media/video/h265_parser_fuzzertest.cc
new file mode 100644
index 0000000..77b88d7
--- /dev/null
+++ b/media/video/h265_parser_fuzzertest.cc
@@ -0,0 +1,43 @@
+// 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 <stddef.h>
+
+#include "base/numerics/safe_conversions.h"
+#include "base/optional.h"
+#include "media/video/h265_parser.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/size.h"
+
+// Entry point for LibFuzzer.
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  if (!size)
+    return 0;
+
+  media::H265Parser parser;
+  parser.SetStream(data, base::checked_cast<off_t>(size));
+
+  // Parse until the end of stream/unsupported stream/error in stream is
+  // found.
+  while (true) {
+    media::H265NALU nalu;
+    media::H265Parser::Result res = parser.AdvanceToNextNALU(&nalu);
+    if (res != media::H265Parser::kOk)
+      break;
+
+    switch (nalu.nal_unit_type) {
+      case media::H265NALU::SPS_NUT:
+        int sps_id;
+        res = parser.ParseSPS(&sps_id);
+        break;
+      default:
+        // Skip any other NALU.
+        break;
+    }
+    if (res != media::H265Parser::kOk)
+      break;
+  }
+
+  return 0;
+}
diff --git a/media/video/h265_parser_unittest.cc b/media/video/h265_parser_unittest.cc
index 32cd4dd..dc000ab4f 100644
--- a/media/video/h265_parser_unittest.cc
+++ b/media/video/h265_parser_unittest.cc
@@ -2,53 +2,147 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "media/video/h265_parser.h"
+#include <memory>
+#include <string>
+
+#include "base/files/file_path.h"
 #include "base/files/memory_mapped_file.h"
 #include "base/logging.h"
 #include "media/base/test_data_util.h"
+#include "media/video/h265_parser.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace media {
 
-TEST(H265ParserTest, RawHevcStreamFileParsing) {
-  base::FilePath file_path = GetTestDataFilePath("bear.hevc");
+namespace {
+struct HevcTestData {
+  std::string file_name;
   // Number of NALUs in the test stream to be parsed.
-  const int num_nalus = 35;
+  int num_nalus;
+};
 
-  base::MemoryMappedFile stream;
-  ASSERT_TRUE(stream.Initialize(file_path))
-      << "Couldn't open stream file: " << file_path.MaybeAsASCII();
+}  // namespace
 
-  H265Parser parser;
-  parser.SetStream(stream.data(), stream.length());
+class H265ParserTest : public ::testing::Test {
+ protected:
+  void LoadParserFile(std::string file_name) {
+    parser_.Reset();
+    base::FilePath file_path = GetTestDataFilePath(file_name);
 
-  // Parse until the end of stream/unsupported stream/error in stream is found.
-  int num_parsed_nalus = 0;
-  while (true) {
-    H265NALU nalu;
-    H265Parser::Result res = parser.AdvanceToNextNALU(&nalu);
-    if (res == H265Parser::kEOStream) {
-      DVLOG(1) << "Number of successfully parsed NALUs before EOS: "
-               << num_parsed_nalus;
-      ASSERT_EQ(num_nalus, num_parsed_nalus);
-      return;
-    }
-    ASSERT_EQ(res, H265Parser::kOk);
+    stream_ = std::make_unique<base::MemoryMappedFile>();
+    ASSERT_TRUE(stream_->Initialize(file_path))
+        << "Couldn't open stream file: " << file_path.MaybeAsASCII();
 
-    ++num_parsed_nalus;
-    DVLOG(4) << "Found NALU " << nalu.nal_unit_type;
-
-    switch (nalu.nal_unit_type) {
-      case H265NALU::SPS_NUT:
-        int sps_id;
-        res = parser.ParseSPS(&sps_id);
-        ASSERT_TRUE(!!parser.GetSPS(sps_id));
-        break;
-      default:
-        break;
-    }
-    ASSERT_EQ(res, H265Parser::kOk);
+    parser_.SetStream(stream_->data(), stream_->length());
   }
+
+  bool ParseNalusUntilNut(H265NALU::Type nalu_type) {
+    while (true) {
+      H265NALU nalu;
+      H265Parser::Result res = parser_.AdvanceToNextNALU(&nalu);
+      if (res == H265Parser::kEOStream) {
+        return false;
+      }
+      EXPECT_EQ(res, H265Parser::kOk);
+      if (nalu.nal_unit_type == nalu_type)
+        return true;
+    }
+  }
+
+  H265Parser parser_;
+  std::unique_ptr<base::MemoryMappedFile> stream_;
+};
+
+TEST_F(H265ParserTest, RawHevcStreamFileParsing) {
+  HevcTestData test_data[] = {
+      {"bear.hevc", 35},
+      {"bbb.hevc", 64},
+  };
+
+  for (auto& data : test_data) {
+    LoadParserFile(data.file_name);
+    // Parse until the end of stream/unsupported stream/error in stream is
+    // found.
+    int num_parsed_nalus = 0;
+    while (true) {
+      H265NALU nalu;
+      H265Parser::Result res = parser_.AdvanceToNextNALU(&nalu);
+      if (res == H265Parser::kEOStream) {
+        DVLOG(1) << "Number of successfully parsed NALUs before EOS: "
+                 << num_parsed_nalus;
+        EXPECT_EQ(data.num_nalus, num_parsed_nalus);
+        break;
+      }
+      EXPECT_EQ(res, H265Parser::kOk);
+
+      ++num_parsed_nalus;
+      DVLOG(4) << "Found NALU " << nalu.nal_unit_type;
+
+      switch (nalu.nal_unit_type) {
+        case H265NALU::SPS_NUT:
+          int sps_id;
+          res = parser_.ParseSPS(&sps_id);
+          EXPECT_TRUE(!!parser_.GetSPS(sps_id));
+          break;
+        default:
+          break;
+      }
+      EXPECT_EQ(res, H265Parser::kOk);
+    }
+  }
+}
+
+TEST_F(H265ParserTest, SpsParsing) {
+  LoadParserFile("bear.hevc");
+  EXPECT_TRUE(ParseNalusUntilNut(H265NALU::SPS_NUT));
+  int sps_id;
+  EXPECT_EQ(H265Parser::kOk, parser_.ParseSPS(&sps_id));
+  const H265SPS* sps = parser_.GetSPS(sps_id);
+  EXPECT_TRUE(!!sps);
+  EXPECT_EQ(sps->sps_max_sub_layers_minus1, 0);
+  EXPECT_EQ(sps->profile_tier_level.general_profile_idc, 1);
+  EXPECT_EQ(sps->profile_tier_level.general_level_idc, 60);
+  EXPECT_EQ(sps->sps_seq_parameter_set_id, 0);
+  EXPECT_EQ(sps->chroma_format_idc, 1);
+  EXPECT_FALSE(sps->separate_colour_plane_flag);
+  EXPECT_EQ(sps->pic_width_in_luma_samples, 320);
+  EXPECT_EQ(sps->pic_height_in_luma_samples, 184);
+  EXPECT_EQ(sps->conf_win_left_offset, 0);
+  EXPECT_EQ(sps->conf_win_right_offset, 0);
+  EXPECT_EQ(sps->conf_win_top_offset, 0);
+  EXPECT_EQ(sps->conf_win_bottom_offset, 2);
+  EXPECT_EQ(sps->bit_depth_luma_minus8, 0);
+  EXPECT_EQ(sps->bit_depth_chroma_minus8, 0);
+  EXPECT_EQ(sps->log2_max_pic_order_cnt_lsb_minus4, 4);
+  EXPECT_EQ(sps->sps_max_dec_pic_buffering_minus1[0], 4);
+  EXPECT_EQ(sps->sps_max_num_reorder_pics[0], 2);
+  EXPECT_EQ(sps->sps_max_latency_increase_plus1[0], 0);
+  for (int i = 1; i < kMaxSubLayers; ++i) {
+    EXPECT_EQ(sps->sps_max_dec_pic_buffering_minus1[i], 0);
+    EXPECT_EQ(sps->sps_max_num_reorder_pics[i], 0);
+    EXPECT_EQ(sps->sps_max_latency_increase_plus1[i], 0);
+  }
+  EXPECT_EQ(sps->log2_min_luma_coding_block_size_minus3, 0);
+  EXPECT_EQ(sps->log2_diff_max_min_luma_coding_block_size, 3);
+  EXPECT_EQ(sps->log2_min_luma_transform_block_size_minus2, 0);
+  EXPECT_EQ(sps->log2_diff_max_min_luma_transform_block_size, 3);
+  EXPECT_EQ(sps->max_transform_hierarchy_depth_inter, 0);
+  EXPECT_EQ(sps->max_transform_hierarchy_depth_intra, 0);
+  EXPECT_FALSE(sps->scaling_list_enabled_flag);
+  EXPECT_FALSE(sps->sps_scaling_list_data_present_flag);
+  EXPECT_FALSE(sps->amp_enabled_flag);
+  EXPECT_TRUE(sps->sample_adaptive_offset_enabled_flag);
+  EXPECT_FALSE(sps->pcm_enabled_flag);
+  EXPECT_EQ(sps->pcm_sample_bit_depth_luma_minus1, 0);
+  EXPECT_EQ(sps->pcm_sample_bit_depth_chroma_minus1, 0);
+  EXPECT_EQ(sps->log2_min_pcm_luma_coding_block_size_minus3, 0);
+  EXPECT_EQ(sps->log2_diff_max_min_pcm_luma_coding_block_size, 0);
+  EXPECT_FALSE(sps->pcm_loop_filter_disabled_flag);
+  EXPECT_EQ(sps->num_short_term_ref_pic_sets, 0);
+  EXPECT_FALSE(sps->long_term_ref_pics_present_flag);
+  EXPECT_EQ(sps->num_long_term_ref_pics_sps, 0);
+  EXPECT_TRUE(sps->sps_temporal_mvp_enabled_flag);
+  EXPECT_TRUE(sps->strong_intra_smoothing_enabled_flag);
 }
 
 }  // namespace media
diff --git a/net/dns/dns_query.cc b/net/dns/dns_query.cc
index 2e33f4f3..c80f9d4 100644
--- a/net/dns/dns_query.cc
+++ b/net/dns/dns_query.cc
@@ -100,7 +100,10 @@
                    const OptRecordRdata* opt_rdata,
                    PaddingStrategy padding_strategy)
     : qname_size_(qname.size()) {
-  DCHECK(!DNSDomainToString(qname).empty());
+#if DCHECK_IS_ON()
+  base::Optional<std::string> dotted_name = DnsDomainToString(qname);
+  DCHECK(dotted_name && !dotted_name.value().empty());
+#endif  // DCHECK_IS_ON()
 
   size_t buffer_size = kHeaderSize + QuestionSize(qname_size_);
   base::Optional<OptRecordRdata> merged_opt_rdata =
diff --git a/net/dns/dns_response.cc b/net/dns/dns_response.cc
index f4a01c3..0017f9c2 100644
--- a/net/dns/dns_response.cc
+++ b/net/dns/dns_response.cc
@@ -476,7 +476,7 @@
 }
 
 std::string DnsResponse::GetDottedName() const {
-  return DNSDomainToString(qname());
+  return DnsDomainToString(qname()).value_or("");
 }
 
 DnsRecordParser DnsResponse::Parser() const {
diff --git a/net/dns/dns_transaction.cc b/net/dns/dns_transaction.cc
index b835f63..3a7dfbb 100644
--- a/net/dns/dns_transaction.cc
+++ b/net/dns/dns_transaction.cc
@@ -1368,9 +1368,11 @@
 
   // Begins query for the current name. Makes the first attempt.
   AttemptResult StartQuery() {
-    std::string dotted_qname = DNSDomainToString(qnames_.front());
-    net_log_.BeginEventWithStringParams(NetLogEventType::DNS_TRANSACTION_QUERY,
-                                        "qname", dotted_qname);
+    base::Optional<std::string> dotted_qname =
+        DnsDomainToString(qnames_.front());
+    net_log_.BeginEventWithStringParams(
+        NetLogEventType::DNS_TRANSACTION_QUERY, "qname",
+        dotted_qname.value_or("???MALFORMED_NAME???"));
 
     attempts_.clear();
     had_tcp_retry_ = false;
diff --git a/net/dns/dns_util.cc b/net/dns/dns_util.cc
index ac89983..74d401b 100644
--- a/net/dns/dns_util.cc
+++ b/net/dns/dns_util.cc
@@ -8,12 +8,14 @@
 #include <limits.h>
 
 #include <cstring>
+#include <string>
 #include <unordered_map>
 #include <vector>
 
 #include "base/big_endian.h"
 #include "base/metrics/field_trial.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/optional.h"
 #include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_split.h"
@@ -160,22 +162,22 @@
          (c >= '0' && c <= '9') || (!is_first_char && c == '-') || c == '_';
 }
 
-std::string DNSDomainToString(const base::StringPiece& domain) {
+base::Optional<std::string> DnsDomainToString(base::StringPiece domain) {
   std::string ret;
 
   for (unsigned i = 0; i < domain.size() && domain[i]; i += domain[i] + 1) {
 #if CHAR_MIN < 0
     if (domain[i] < 0)
-      return std::string();
+      return base::nullopt;
 #endif
     if (domain[i] > kMaxLabelLength)
-      return std::string();
+      return base::nullopt;
 
     if (i)
       ret += ".";
 
     if (static_cast<unsigned>(domain[i]) + i + 1 > domain.size())
-      return std::string();
+      return base::nullopt;
 
     ret.append(domain.data() + i + 1, domain[i]);
   }
diff --git a/net/dns/dns_util.h b/net/dns/dns_util.h
index 00b5ff59..f5e132b 100644
--- a/net/dns/dns_util.h
+++ b/net/dns/dns_util.h
@@ -8,6 +8,7 @@
 #include <string>
 #include <vector>
 
+#include "base/optional.h"
 #include "base/strings/string_piece.h"
 #include "base/time/time.h"
 #include "net/base/address_family.h"
@@ -63,9 +64,11 @@
 // not be NET_EXPORT_PRIVATE.
 NET_EXPORT_PRIVATE bool IsValidHostLabelCharacter(char c, bool is_first_char);
 
-// DNSDomainToString converts a domain in DNS format to a dotted string.
-// Excludes the dot at the end.
-NET_EXPORT std::string DNSDomainToString(const base::StringPiece& domain);
+// Converts a domain in DNS format to a dotted string. Excludes the dot at the
+// end. Assumes the standard terminating zero-length label at the end if not
+// included in the input. Returns nullopt on malformed input.
+NET_EXPORT base::Optional<std::string> DnsDomainToString(
+    base::StringPiece dns_name);
 
 // Return the expanded template when no variables have corresponding values.
 NET_EXPORT_PRIVATE std::string GetURLFromTemplateWithoutParameters(
diff --git a/net/dns/dns_util_unittest.cc b/net/dns/dns_util_unittest.cc
index 39a8988..c2ce410a 100644
--- a/net/dns/dns_util_unittest.cc
+++ b/net/dns/dns_util_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "net/dns/dns_util.h"
 
+#include "base/optional.h"
 #include "base/stl_util.h"
 #include "net/dns/public/dns_protocol.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -11,6 +12,8 @@
 
 namespace net {
 
+using testing::Eq;
+
 class DNSUtilTest : public testing::Test {
 };
 
@@ -79,19 +82,22 @@
       &out));
 }
 
-TEST_F(DNSUtilTest, DNSDomainToString) {
-  EXPECT_EQ("", DNSDomainToString(IncludeNUL("")));
-  EXPECT_EQ("foo", DNSDomainToString(IncludeNUL("\003foo")));
-  EXPECT_EQ("foo.bar", DNSDomainToString(IncludeNUL("\003foo\003bar")));
-  EXPECT_EQ("foo.bar.uk",
-            DNSDomainToString(IncludeNUL("\003foo\003bar\002uk")));
+TEST_F(DNSUtilTest, DnsDomainToString) {
+  EXPECT_THAT(DnsDomainToString(IncludeNUL("")), testing::Optional(Eq("")));
+  EXPECT_THAT(DnsDomainToString(IncludeNUL("\003foo")),
+              testing::Optional(Eq("foo")));
+  EXPECT_THAT(DnsDomainToString(IncludeNUL("\003foo\003bar")),
+              testing::Optional(Eq("foo.bar")));
+  EXPECT_THAT(DnsDomainToString(IncludeNUL("\003foo\003bar\002uk")),
+              testing::Optional(Eq("foo.bar.uk")));
 
   // It should cope with a lack of root label.
-  EXPECT_EQ("foo.bar", DNSDomainToString("\003foo\003bar"));
+  EXPECT_THAT(DnsDomainToString("\003foo\003bar"),
+              testing::Optional(Eq("foo.bar")));
 
-  // Invalid inputs should return an empty string.
-  EXPECT_EQ("", DNSDomainToString(IncludeNUL("\x80")));
-  EXPECT_EQ("", DNSDomainToString("\x06"));
+  // Invalid inputs should return nullopt.
+  EXPECT_EQ(DnsDomainToString(IncludeNUL("\x80")), base::nullopt);
+  EXPECT_EQ(DnsDomainToString("\x06"), base::nullopt);
 }
 
 TEST_F(DNSUtilTest, IsValidDNSDomain) {
diff --git a/net/http/transport_security_state.cc b/net/http/transport_security_state.cc
index 48eda88..41b427c 100644
--- a/net/http/transport_security_state.cc
+++ b/net/http/transport_security_state.cc
@@ -21,6 +21,7 @@
 #include "base/metrics/field_trial_params.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/optional.h"
 #include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
@@ -1221,8 +1222,13 @@
     // An entry matches if it is either an exact match, or if it is a prefix
     // match and the includeSubDomains directive was included.
     if (i == 0 || j->second.include_subdomains) {
+      base::Optional<std::string> dotted_name =
+          DnsDomainToString(host_sub_chunk);
+      if (!dotted_name)
+        return false;
+
       *result = j->second;
-      result->domain = DNSDomainToString(host_sub_chunk);
+      result->domain = std::move(dotted_name).value();
       return true;
     }
   }
@@ -1262,8 +1268,13 @@
     // implement HPKP, so this logic is only used via AddHPKP(), reachable from
     // Cronet.
     if (i == 0 || j->second.include_subdomains) {
+      base::Optional<std::string> dotted_name =
+          DnsDomainToString(host_sub_chunk);
+      if (!dotted_name)
+        return false;
+
       *result = j->second;
-      result->domain = DNSDomainToString(host_sub_chunk);
+      result->domain = std::move(dotted_name).value();
       return true;
     }
 
diff --git a/net/quic/quic_flags_list.h b/net/quic/quic_flags_list.h
index 9f7083b..3400dad 100644
--- a/net/quic/quic_flags_list.h
+++ b/net/quic/quic_flags_list.h
@@ -524,3 +524,15 @@
 QUIC_FLAG(bool,
           FLAGS_quic_reloadable_flag_quic_granular_qpack_error_codes,
           false)
+
+// When true, the server delays its Initial ACK-only packets the full
+// max_ack_delay.
+QUIC_FLAG(bool, FLAGS_quic_reloadable_flag_quic_delay_initial_ack, false)
+
+// If true, session tickets will always be enabled in QUIC.
+QUIC_FLAG(bool,
+          FLAGS_quic_restart_flag_quic_session_tickets_always_enabled,
+          false)
+
+// If true, QUIC client with TLS will not try 0-RTT.
+QUIC_FLAG(bool, FLAGS_quic_disable_client_tls_zero_rtt, false)
diff --git a/net/quic/quic_stream_factory.cc b/net/quic/quic_stream_factory.cc
index a5b79f0..bb888cf 100644
--- a/net/quic/quic_stream_factory.cc
+++ b/net/quic/quic_stream_factory.cc
@@ -1117,7 +1117,7 @@
   DCHECK(transport_security_state_);
   DCHECK(http_server_properties_);
   if (params_.disable_tls_zero_rtt)
-    SetQuicRestartFlag(quic_enable_zero_rtt_for_tls_v2, false);
+    SetQuicFlag(FLAGS_quic_disable_client_tls_zero_rtt, true);
   InitializeMigrationOptions();
 }
 
diff --git a/sandbox/policy/mac/gpu_v2.sb b/sandbox/policy/mac/gpu_v2.sb
index 1bb5edc..8b969c8 100644
--- a/sandbox/policy/mac/gpu_v2.sb
+++ b/sandbox/policy/mac/gpu_v2.sb
@@ -16,7 +16,7 @@
 
 ; TODO(https://crbug.com/1126350): Remove this after debugging. These blocks
 ; enumerate known denials, while turning unknown denials into fatal crashes.
-(define crash-on-unknown-denials #f) ; Single-line kill switch.
+(define crash-on-unknown-denials #t) ; Single-line kill switch.
 (if crash-on-unknown-denials
   (begin
     (deny mach-lookup (with no-report)
@@ -49,6 +49,7 @@
       (subpath (param bundle-path))
     )
     (deny file-write* (with send-signal SIGSYS))
+    (deny sysctl-read (with send-signal SIGSYS))
   )
 )
 
diff --git a/services/network/mdns_responder.cc b/services/network/mdns_responder.cc
index a7a2b71..ce2a56a 100644
--- a/services/network/mdns_responder.cc
+++ b/services/network/mdns_responder.cc
@@ -6,6 +6,7 @@
 #include <cmath>
 #include <numeric>
 #include <queue>
+#include <string>
 #include <utility>
 
 #include "services/network/mdns_responder.h"
@@ -16,6 +17,7 @@
 #include "base/logging.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/numerics/safe_conversions.h"
+#include "base/optional.h"
 #include "base/rand_util.h"
 #include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
@@ -966,11 +968,12 @@
   // responder only provides APIs to create address records, and hence limited
   // to handle only such records. Once we have expanded the API surface to
   // include the service publishing, the handling logic should be unified.
-  const std::string qname = net::DNSDomainToString(query.qname());
+  const base::Optional<std::string> qname =
+      net::DnsDomainToString(query.qname());
   if (base::FeatureList::IsEnabled(
           features::kMdnsResponderGeneratedNameListing)) {
-    if (should_respond_to_generator_service_query_ &&
-        qname == kMdnsNameGeneratorServiceInstanceName) {
+    if (should_respond_to_generator_service_query_ && qname &&
+        qname.value() == kMdnsNameGeneratorServiceInstanceName) {
       HandleMdnsNameGeneratorServiceQuery(query, recv_socket_handler_id);
       return;
     }
@@ -1233,8 +1236,11 @@
 void MdnsResponder::OnMdnsQueryReceived(const net::DnsQuery& query,
                                         uint16_t recv_socket_handler_id) {
   // Currently we only support a single question in DnsQuery.
-  std::string dotted_name_to_resolve = net::DNSDomainToString(query.qname());
-  auto it = name_addr_map_.find(dotted_name_to_resolve);
+  base::Optional<std::string> dotted_name_to_resolve =
+      net::DnsDomainToString(query.qname());
+  if (!dotted_name_to_resolve)
+    return;
+  auto it = name_addr_map_.find(dotted_name_to_resolve.value());
   if (it == name_addr_map_.end())
     return;
 
diff --git a/services/network/network_context.cc b/services/network/network_context.cc
index da30adf..1418e56 100644
--- a/services/network/network_context.cc
+++ b/services/network/network_context.cc
@@ -399,9 +399,17 @@
           url_loader_factory_for_cert_net_fetcher
               .InitWithNewPipeAndPassReceiver();
 
+  scoped_refptr<SessionCleanupCookieStore> session_cleanup_cookie_store =
+      MakeSessionCleanupCookieStore();
+
   url_request_context_owner_ =
-      MakeURLRequestContext(std::move(url_loader_factory_for_cert_net_fetcher));
+      MakeURLRequestContext(std::move(url_loader_factory_for_cert_net_fetcher),
+                            session_cleanup_cookie_store);
   url_request_context_ = url_request_context_owner_.url_request_context.get();
+  cookie_manager_ = std::make_unique<CookieManager>(
+      url_request_context_owner_.url_request_context->cookie_store(),
+      std::move(session_cleanup_cookie_store),
+      std::move(params_->cookie_manager_params));
 
   network_service_->RegisterNetworkContext(this);
 
@@ -1808,7 +1816,8 @@
 
 URLRequestContextOwner NetworkContext::MakeURLRequestContext(
     mojo::PendingRemote<mojom::URLLoaderFactory>
-        url_loader_factory_for_cert_net_fetcher) {
+        url_loader_factory_for_cert_net_fetcher,
+    scoped_refptr<SessionCleanupCookieStore> session_cleanup_cookie_store) {
   URLRequestContextBuilderMojo builder;
   const base::CommandLine* command_line =
       base::CommandLine::ForCurrentProcess();
@@ -1907,33 +1916,7 @@
         network_service_->network_quality_estimator());
   }
 
-  scoped_refptr<SessionCleanupCookieStore> session_cleanup_cookie_store;
-  if (params_->cookie_path) {
-    scoped_refptr<base::SequencedTaskRunner> client_task_runner =
-        base::ThreadTaskRunnerHandle::Get();
-    scoped_refptr<base::SequencedTaskRunner> background_task_runner =
-        base::ThreadPool::CreateSequencedTaskRunner(
-            {base::MayBlock(), net::GetCookieStoreBackgroundSequencePriority(),
-             base::TaskShutdownBehavior::BLOCK_SHUTDOWN});
-
-    net::CookieCryptoDelegate* crypto_delegate = nullptr;
-    if (params_->enable_encrypted_cookies) {
-#if defined(OS_LINUX) && !defined(OS_CHROMEOS) && !BUILDFLAG(IS_CHROMECAST)
-      DCHECK(network_service_->os_crypt_config_set())
-          << "NetworkService::SetCryptConfig must be called before creating a "
-             "NetworkContext with encrypted cookies.";
-#endif
-      crypto_delegate = cookie_config::GetCookieCryptoDelegate();
-    }
-    scoped_refptr<net::SQLitePersistentCookieStore> sqlite_store(
-        new net::SQLitePersistentCookieStore(
-            params_->cookie_path.value(), client_task_runner,
-            background_task_runner, params_->restore_old_session_cookies,
-            crypto_delegate));
-
-    session_cleanup_cookie_store =
-        base::MakeRefCounted<SessionCleanupCookieStore>(sqlite_store);
-
+  if (session_cleanup_cookie_store) {
     std::unique_ptr<net::CookieMonster> cookie_store =
         std::make_unique<net::CookieMonster>(session_cleanup_cookie_store.get(),
                                              net_log);
@@ -1941,9 +1924,6 @@
       cookie_store->SetPersistSessionCookies(true);
 
     builder.SetCookieStore(std::move(cookie_store));
-  } else {
-    DCHECK(!params_->restore_old_session_cookies);
-    DCHECK(!params_->persist_session_cookies);
   }
 
   if (base::FeatureList::IsEnabled(features::kTrustTokens)) {
@@ -2268,17 +2248,44 @@
         result.url_request_context->proxy_resolution_service());
   }
 
-  cookie_manager_ = std::make_unique<CookieManager>(
-      result.url_request_context->cookie_store(),
-      std::move(session_cleanup_cookie_store),
-      std::move(params_->cookie_manager_params));
-
   if (cert_net_fetcher_)
     cert_net_fetcher_->SetURLRequestContext(result.url_request_context.get());
 
   return result;
 }
 
+scoped_refptr<SessionCleanupCookieStore>
+NetworkContext::MakeSessionCleanupCookieStore() const {
+  if (!params_->cookie_path) {
+    DCHECK(!params_->restore_old_session_cookies);
+    DCHECK(!params_->persist_session_cookies);
+    return nullptr;
+  }
+  scoped_refptr<base::SequencedTaskRunner> client_task_runner =
+      base::ThreadTaskRunnerHandle::Get();
+  scoped_refptr<base::SequencedTaskRunner> background_task_runner =
+      base::ThreadPool::CreateSequencedTaskRunner(
+          {base::MayBlock(), net::GetCookieStoreBackgroundSequencePriority(),
+           base::TaskShutdownBehavior::BLOCK_SHUTDOWN});
+
+  net::CookieCryptoDelegate* crypto_delegate = nullptr;
+  if (params_->enable_encrypted_cookies) {
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS) && !BUILDFLAG(IS_CHROMECAST)
+    DCHECK(network_service_->os_crypt_config_set())
+        << "NetworkService::SetCryptConfig must be called before creating a "
+           "NetworkContext with encrypted cookies.";
+#endif
+    crypto_delegate = cookie_config::GetCookieCryptoDelegate();
+  }
+  scoped_refptr<net::SQLitePersistentCookieStore> sqlite_store(
+      new net::SQLitePersistentCookieStore(
+          params_->cookie_path.value(), client_task_runner,
+          background_task_runner, params_->restore_old_session_cookies,
+          crypto_delegate));
+
+  return base::MakeRefCounted<SessionCleanupCookieStore>(sqlite_store);
+}
+
 void NetworkContext::OnHttpCacheCleared(ClearHttpCacheCallback callback,
                                         HttpCacheDataRemover* remover) {
   bool removed = false;
diff --git a/services/network/network_context.h b/services/network/network_context.h
index e1a8746..066d3cb7 100644
--- a/services/network/network_context.h
+++ b/services/network/network_context.h
@@ -89,17 +89,18 @@
 class CookieManager;
 class ExpectCTReporter;
 class HostResolver;
+class MdnsResponderManager;
 class NetworkService;
 class NetworkServiceNetworkDelegate;
 class NetworkServiceProxyDelegate;
-class MdnsResponderManager;
 class P2PSocketManager;
+class PendingTrustTokenStore;
 class ProxyLookupRequest;
 class QuicTransport;
 class ResourceScheduler;
 class ResourceSchedulerClient;
+class SessionCleanupCookieStore;
 class SQLiteTrustTokenPersister;
-class PendingTrustTokenStore;
 class WebSocketFactory;
 
 namespace cors {
@@ -515,7 +516,10 @@
  private:
   URLRequestContextOwner MakeURLRequestContext(
       mojo::PendingRemote<mojom::URLLoaderFactory>
-          url_loader_factory_for_cert_net_fetcher);
+          url_loader_factory_for_cert_net_fetcher,
+      scoped_refptr<SessionCleanupCookieStore>);
+  scoped_refptr<SessionCleanupCookieStore> MakeSessionCleanupCookieStore()
+      const;
 
   // Invoked when the HTTP cache was cleared. Invokes |callback|.
   void OnHttpCacheCleared(ClearHttpCacheCallback callback,
diff --git a/storage/browser/quota/usage_tracker_unittest.cc b/storage/browser/quota/usage_tracker_unittest.cc
index df85bd1a..c3016b8 100644
--- a/storage/browser/quota/usage_tracker_unittest.cc
+++ b/storage/browser/quota/usage_tracker_unittest.cc
@@ -10,6 +10,7 @@
 #include "base/bind.h"
 #include "base/location.h"
 #include "base/macros.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/run_loop.h"
 #include "base/single_thread_task_runner.h"
 #include "base/test/task_environment.h"
@@ -117,7 +118,7 @@
 class UsageTrackerTest : public testing::Test {
  public:
   UsageTrackerTest()
-      : storage_policy_(new MockSpecialStoragePolicy()),
+      : storage_policy_(base::MakeRefCounted<MockSpecialStoragePolicy>()),
         quota_client_(base::MakeRefCounted<UsageTrackerTestQuotaClient>()),
         usage_tracker_(GetQuotaClientMap(),
                        StorageType::kTemporary,
diff --git a/testing/buildbot/filters/chromeos.msan.browser_tests.oobe_negative.filter b/testing/buildbot/filters/chromeos.msan.browser_tests.oobe_negative.filter
index 1b0e9c0..01a5234 100644
--- a/testing/buildbot/filters/chromeos.msan.browser_tests.oobe_negative.filter
+++ b/testing/buildbot/filters/chromeos.msan.browser_tests.oobe_negative.filter
@@ -96,8 +96,6 @@
 -OobeTest.*
 -OobeZeroTouchInteractiveUITest.*
 -PackagedLicenseScreenTest.*
--ParentalHandoffScreenBrowserTest.*
--ParentalHandoffScreenChildBrowserTest.*
 -PolicyProvidedCertsOnUserSessionInitTest.*
 -PresetPolicyDeviceDisablingTest.*
 -PrimaryUserPoliciesProxiedTest.*
diff --git a/testing/buildbot/filters/chromeos.msan.browser_tests.oobe_positive.filter b/testing/buildbot/filters/chromeos.msan.browser_tests.oobe_positive.filter
index 2be9b4a..4fd3370 100644
--- a/testing/buildbot/filters/chromeos.msan.browser_tests.oobe_positive.filter
+++ b/testing/buildbot/filters/chromeos.msan.browser_tests.oobe_positive.filter
@@ -96,8 +96,6 @@
 OobeTest.*
 OobeZeroTouchInteractiveUITest.*
 PackagedLicenseScreenTest.*
-ParentalHandoffScreenBrowserTest.*
-ParentalHandoffScreenChildBrowserTest.*
 PolicyProvidedCertsOnUserSessionInitTest.*
 PresetPolicyDeviceDisablingTest.*
 PrimaryUserPoliciesProxiedTest.*
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl
index 0c04d2c..afbc6f4c 100644
--- a/testing/buildbot/gn_isolate_map.pyl
+++ b/testing/buildbot/gn_isolate_map.pyl
@@ -1703,12 +1703,8 @@
     "type": "windowed_test_launcher",
   },
   "updater_integration_tests": {
-    "args": [
-      "../../chrome/updater/run_updater_tests.py",
-    ],
+    "type": "generated_script",
     "label": "//chrome/updater:updater_integration_tests",
-    "script": "//testing/scripts/run_isolated_script_test.py",
-    "type": "script",
   },
   "updater_tests": {
     "label": "//chrome/updater:updater_tests",
diff --git a/testing/test_env.py b/testing/test_env.py
index 9d60926..e8cc1d1b 100755
--- a/testing/test_env.py
+++ b/testing/test_env.py
@@ -18,7 +18,10 @@
     'six')
 sys.path.insert(0, SIX_DIR)
 
-import six
+try:
+  import six
+except ImportError:
+  raise Exception('Failed to import six. Run under vpython or install six.')
 
 # This is hardcoded to be src/ relative to this script.
 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
diff --git a/testing/xvfb.py b/testing/xvfb.py
index d868072..e65c8fa 100755
--- a/testing/xvfb.py
+++ b/testing/xvfb.py
@@ -8,7 +8,6 @@
 
 import os
 import os.path
-import psutil
 import random
 import re
 import signal
@@ -18,6 +17,11 @@
 import time
 import test_env
 
+try:
+  import psutil
+except ImportError:
+  raise Exception(
+        'Failed to import psutil. Run under vpython or install psutil.')
 
 class _XvfbProcessError(Exception):
   """Exception raised when Xvfb cannot start."""
diff --git a/third_party/blink/public/mojom/page/widget.mojom b/third_party/blink/public/mojom/page/widget.mojom
index e04b756..c57b8f7 100644
--- a/third_party/blink/public/mojom/page/widget.mojom
+++ b/third_party/blink/public/mojom/page/widget.mojom
@@ -267,8 +267,9 @@
 // process, so the PopupWidgetHost channel disconnecting indicates that the
 // widget in the renderer should be destroyed.
 interface PopupWidgetHost {
-  // TODO(dtapuska): Add ShowPopup method that will replace ViewHostMsg_ShowWidget
-
   // Request that this popup be dismissed.
   RequestClosePopup();
+
+  // Request the popup be shown.
+  ShowPopup(gfx.mojom.Rect initial_rect) => ();
 };
\ No newline at end of file
diff --git a/third_party/blink/public/web/web_widget.h b/third_party/blink/public/web/web_widget.h
index c0ed2ae4..07df4e5a 100644
--- a/third_party/blink/public/web/web_widget.h
+++ b/third_party/blink/public/web/web_widget.h
@@ -241,7 +241,13 @@
   // the window rect is delivered asynchronously to the browser. Pass in nullptr
   // to clear the pending window rect once the browser has acknowledged the
   // request.
-  virtual void SetPendingWindowRect(const gfx::Rect* window_screen_rect) = 0;
+  virtual void SetPendingWindowRect(const gfx::Rect& window_screen_rect) = 0;
+
+  // Acknowledge a pending window rect, must correspond to a prior
+  // SetPendingWindowRect call. This method is temporary and will be removed
+  // when WidgetMsg_SetBounds_ACK/WidgetHostMsg_RequestSetBounds are moved to
+  // mojo.
+  virtual void AckPendingWindowRect() = 0;
 
   virtual bool IsHidden() const = 0;
 
diff --git a/third_party/blink/renderer/core/animation/compositor_animations.cc b/third_party/blink/renderer/core/animation/compositor_animations.cc
index 3598c2b..91e58fb 100644
--- a/third_party/blink/renderer/core/animation/compositor_animations.cc
+++ b/third_party/blink/renderer/core/animation/compositor_animations.cc
@@ -244,7 +244,12 @@
         case CSSPropertyID::kScale:
         case CSSPropertyID::kTranslate:
         case CSSPropertyID::kTransform:
-          if (!RuntimeEnabledFeatures::CompositeRelativeKeyframesEnabled()) {
+          // TODO(crbug.com/389359): Currently only CSS boxes support
+          // compositing box-size-dependent transform animations. Once such
+          // support is fully working for SVG, this section (and the flag)
+          // should be removed.
+          if (!RuntimeEnabledFeatures::CompositeRelativeKeyframesEnabled() ||
+              (layout_object && layout_object->IsSVGChild())) {
             if (keyframe->GetCompositorKeyframeValue() &&
                 To<CompositorKeyframeTransform>(
                     keyframe->GetCompositorKeyframeValue())
@@ -732,12 +737,9 @@
       case CSSPropertyID::kScale:
       case CSSPropertyID::kTranslate:
       case CSSPropertyID::kTransform: {
-        FloatSize box_size;
-        if (RuntimeEnabledFeatures::CompositeRelativeKeyframesEnabled()) {
-          box_size = ComputedStyleUtils::ReferenceBoxForTransform(
-                         *target_element.GetLayoutObject())
-                         .Size();
-        }
+        FloatSize box_size = ComputedStyleUtils::ReferenceBoxForTransform(
+                                 *target_element.GetLayoutObject())
+                                 .Size();
         target_property = compositor_target_property::TRANSFORM;
         auto transform_curve =
             std::make_unique<CompositorTransformAnimationCurve>();
diff --git a/third_party/blink/renderer/core/animation/compositor_animations_test.cc b/third_party/blink/renderer/core/animation/compositor_animations_test.cc
index f447928..486707b 100644
--- a/third_party/blink/renderer/core/animation/compositor_animations_test.cc
+++ b/third_party/blink/renderer/core/animation/compositor_animations_test.cc
@@ -562,6 +562,8 @@
 
 TEST_P(AnimationCompositorAnimationsTest,
        IsNotCandidateForCompositorAnimationTransformDependsOnBoxSize) {
+  ScopedCompositeRelativeKeyframesForTest no_relative_keyframes(false);
+
   // Absolute transforms can be animated on the compositor.
   String transform = "translateX(2px) translateY(2px)";
   StringKeyframe* good_keyframe =
diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h
index d54d6ad..06141f7c 100644
--- a/third_party/blink/renderer/core/dom/document.h
+++ b/third_party/blink/renderer/core/dom/document.h
@@ -1681,6 +1681,7 @@
   friend class ThrowOnDynamicMarkupInsertionCountIncrementer;
   friend class IgnoreOpensDuringUnloadCountIncrementer;
   friend class NthIndexCache;
+  friend class CanvasRenderingAPIUkmMetricsTest;
   FRIEND_TEST_ALL_PREFIXES(FrameFetchContextSubresourceFilterTest,
                            DuringOnFreeze);
   FRIEND_TEST_ALL_PREFIXES(DocumentTest, FindInPageUkm);
diff --git a/third_party/blink/renderer/core/exported/web_page_popup_impl.cc b/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
index 36ff4eb..24488053 100644
--- a/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
@@ -391,10 +391,19 @@
 
   popup_owner_client_rect_ =
       popup_client_->OwnerElement().getBoundingClientRect();
-  WidgetClient()->Show(WebNavigationPolicy());
+  popup_widget_host_->ShowPopup(
+      initial_rect_,
+      WTF::Bind(&WebPagePopupImpl::DidShowPopup, WTF::Unretained(this)));
+  should_defer_setting_window_rect_ = false;
+  widget_base_->SetPendingWindowRect(initial_rect_);
+
   SetFocus(true);
 }
 
+void WebPagePopupImpl::DidShowPopup() {
+  widget_base_->AckPendingWindowRect();
+}
+
 cc::LayerTreeHost* WebPagePopupImpl::InitializeCompositing(
     scheduler::WebThreadScheduler* main_thread_scheduler,
     cc::TaskGraphRunner* task_graph_runner,
@@ -489,10 +498,14 @@
 }
 
 void WebPagePopupImpl::SetPendingWindowRect(
-    const gfx::Rect* window_screen_rect) {
+    const gfx::Rect& window_screen_rect) {
   widget_base_->SetPendingWindowRect(window_screen_rect);
 }
 
+void WebPagePopupImpl::AckPendingWindowRect() {
+  widget_base_->AckPendingWindowRect();
+}
+
 bool WebPagePopupImpl::IsHidden() const {
   return widget_base_->is_hidden();
 }
@@ -565,7 +578,11 @@
   if (opener_emulator_scale_)
     EmulatedToScreenRect(window_rect);
 
-  WidgetClient()->SetWindowRect(window_rect);
+  if (!should_defer_setting_window_rect_) {
+    WidgetClient()->SetWindowRect(window_rect);
+  } else {
+    initial_rect_ = window_rect;
+  }
 }
 
 void WebPagePopupImpl::SetRootLayer(scoped_refptr<cc::Layer> layer) {
diff --git a/third_party/blink/renderer/core/exported/web_page_popup_impl.h b/third_party/blink/renderer/core/exported/web_page_popup_impl.h
index 00b97978..ad28c1d 100644
--- a/third_party/blink/renderer/core/exported/web_page_popup_impl.h
+++ b/third_party/blink/renderer/core/exported/web_page_popup_impl.h
@@ -192,7 +192,8 @@
   void SetScreenRects(const gfx::Rect& widget_screen_rect,
                       const gfx::Rect& window_screen_rect) override;
   gfx::Size VisibleViewportSizeInDIPs() override;
-  void SetPendingWindowRect(const gfx::Rect* window_screen_rect) override;
+  void SetPendingWindowRect(const gfx::Rect& window_screen_rect) override;
+  void AckPendingWindowRect() override;
   bool IsHidden() const override;
 
   // This may only be called if page_ is non-null.
@@ -230,6 +231,7 @@
                                 WebInputEvent::Type injected_type);
 
   void WidgetHostDisconnected();
+  void DidShowPopup();
 
   WebPagePopupClient* web_page_popup_client_;
   WebViewImpl* web_view_ = nullptr;
@@ -261,6 +263,12 @@
   // be destroyed.
   mojo::AssociatedRemote<mojom::blink::PopupWidgetHost> popup_widget_host_;
 
+  // The rect before the widget is shown.
+  gfx::Rect initial_rect_;
+
+  // Defer setting the window rect until the widget is shown.
+  bool should_defer_setting_window_rect_ = true;
+
   // Base functionality all widgets have. This is a member as to avoid
   // complicated inheritance structures.
   std::unique_ptr<WidgetBase> widget_base_;
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_base.cc b/third_party/blink/renderer/core/frame/web_frame_widget_base.cc
index 324f629..fb6c8f3 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_base.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_base.cc
@@ -1239,10 +1239,14 @@
 }
 
 void WebFrameWidgetBase::SetPendingWindowRect(
-    const gfx::Rect* window_screen_rect) {
+    const gfx::Rect& window_screen_rect) {
   widget_base_->SetPendingWindowRect(window_screen_rect);
 }
 
+void WebFrameWidgetBase::AckPendingWindowRect() {
+  widget_base_->AckPendingWindowRect();
+}
+
 bool WebFrameWidgetBase::IsHidden() const {
   return widget_base_->is_hidden();
 }
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_base.h b/third_party/blink/renderer/core/frame/web_frame_widget_base.h
index e41dd97..0e07a33 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_base.h
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_base.h
@@ -330,7 +330,8 @@
   void SetScreenRects(const gfx::Rect& widget_screen_rect,
                       const gfx::Rect& window_screen_rect) override;
   gfx::Size VisibleViewportSizeInDIPs() override;
-  void SetPendingWindowRect(const gfx::Rect* window_screen_rect) override;
+  void SetPendingWindowRect(const gfx::Rect& window_screen_rect) override;
+  void AckPendingWindowRect() override;
   bool IsHidden() const override;
 
   // WidgetBaseClient methods.
diff --git a/third_party/blink/renderer/core/html/DEPS b/third_party/blink/renderer/core/html/DEPS
index 9eb6542..3b16d4e 100644
--- a/third_party/blink/renderer/core/html/DEPS
+++ b/third_party/blink/renderer/core/html/DEPS
@@ -1,4 +1,7 @@
 specific_include_rules = {
+    "canvas_rendering_context.cc": [
+        "+services/metrics/public/cpp/ukm_builders.h",
+    ],
     "trust_token_attribute_parsing_test.cc": [
         "+services/network/trust_tokens/test",
     ],
diff --git a/third_party/blink/renderer/core/html/build.gni b/third_party/blink/renderer/core/html/build.gni
index 3c21f5c6..2c41552 100644
--- a/third_party/blink/renderer/core/html/build.gni
+++ b/third_party/blink/renderer/core/html/build.gni
@@ -674,6 +674,7 @@
   "anchor_element_metrics_test.cc",
   "canvas/canvas_async_blob_creator_test.cc",
   "canvas/canvas_font_cache_test.cc",
+  "canvas/canvas_rendering_api_ukm_metrics_test.cc",
   "canvas/html_canvas_element_test.cc",
   "canvas/image_data_test.cc",
   "custom/custom_element_definition_test.cc",
diff --git a/third_party/blink/renderer/core/html/canvas/canvas_rendering_api_ukm_metrics_test.cc b/third_party/blink/renderer/core/html/canvas/canvas_rendering_api_ukm_metrics_test.cc
new file mode 100644
index 0000000..a626fc6
--- /dev/null
+++ b/third_party/blink/renderer/core/html/canvas/canvas_rendering_api_ukm_metrics_test.cc
@@ -0,0 +1,67 @@
+// 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/ukm/test_ukm_recorder.h"
+#include "services/metrics/public/cpp/ukm_builders.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/frame/local_dom_window.h"
+#include "third_party/blink/renderer/core/frame/local_frame_view.h"
+#include "third_party/blink/renderer/core/html/canvas/canvas_context_creation_attributes_core.h"
+#include "third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h"
+#include "third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.h"
+#include "third_party/blink/renderer/core/testing/page_test_base.h"
+
+using testing::Mock;
+
+namespace blink {
+
+class CanvasRenderingAPIUkmMetricsTest : public PageTestBase {
+ public:
+  CanvasRenderingAPIUkmMetricsTest();
+
+  void SetUp() override {
+    PageTestBase::SetUp();
+    GetDocument().documentElement()->setInnerHTML(
+        "<body><canvas id='c'></canvas></body>");
+    canvas_element_ = To<HTMLCanvasElement>(GetDocument().getElementById("c"));
+    UpdateAllLifecyclePhasesForTest();
+  }
+
+  void CheckContext(String context_type,
+                    CanvasRenderingContext::CanvasRenderingAPI expected_value) {
+    std::unique_ptr<ukm::TestUkmRecorder> test_ukm_recorder =
+        std::make_unique<ukm::TestUkmRecorder>();
+    CanvasContextCreationAttributesCore attributes;
+    canvas_element_->SetUkmRecorderForTesting(test_ukm_recorder.get());
+    canvas_element_->GetCanvasRenderingContext(context_type, attributes);
+
+    auto entries = test_ukm_recorder->GetEntriesByName(
+        ukm::builders::ClientRenderingAPI::kEntryName);
+    EXPECT_EQ(1ul, entries.size());
+    auto* entry = entries[0];
+    ukm::TestUkmRecorder::ExpectEntryMetric(
+        entry, ukm::builders::ClientRenderingAPI::kCanvas_RenderingContextName,
+        static_cast<int>(expected_value));
+  }
+
+ private:
+  Persistent<HTMLCanvasElement> canvas_element_;
+};
+
+CanvasRenderingAPIUkmMetricsTest::CanvasRenderingAPIUkmMetricsTest() = default;
+
+TEST_F(CanvasRenderingAPIUkmMetricsTest, Canvas2D) {
+  CheckContext("2d", CanvasRenderingContext::CanvasRenderingAPI::k2D);
+}
+
+TEST_F(CanvasRenderingAPIUkmMetricsTest, CanvasBitmapRenderer) {
+  CheckContext("bitmaprenderer",
+               CanvasRenderingContext::CanvasRenderingAPI::kBitmaprenderer);
+}
+
+// Skip tests for WebGL context for now
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.cc b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.cc
index 50af5f3..0896164 100644
--- a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.cc
+++ b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.cc
@@ -25,6 +25,7 @@
 
 #include "third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h"
 
+#include "services/metrics/public/cpp/ukm_builders.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/core/animation_frame/worker_animation_frame_provider.h"
 #include "third_party/blink/renderer/core/html/canvas/canvas_context_creation_attributes_core.h"
@@ -124,6 +125,22 @@
     Host()->PostFinalizeFrame();
 }
 
+void CanvasRenderingContext::RecordUKMCanvasRenderingAPI(
+    CanvasRenderingAPI canvasRenderingAPI) {
+  DCHECK(Host());
+  const auto& ukm_params = Host()->GetUkmParameters();
+  if (Host()->IsOffscreenCanvas()) {
+    ukm::builders::ClientRenderingAPI(ukm_params.source_id)
+        .SetOffscreenCanvas_RenderingContext(
+            static_cast<int>(canvasRenderingAPI))
+        .Record(ukm_params.ukm_recorder);
+  } else {
+    ukm::builders::ClientRenderingAPI(ukm_params.source_id)
+        .SetCanvas_RenderingContext(static_cast<int>(canvasRenderingAPI))
+        .Record(ukm_params.ukm_recorder);
+  }
+}
+
 CanvasRenderingContext::ContextType CanvasRenderingContext::ContextTypeFromId(
     const String& id) {
   if (id == "2d")
diff --git a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h
index 5e1d8c7..ad8eaa2 100644
--- a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h
+++ b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h
@@ -78,6 +78,18 @@
     kMaxValue = kContextTypeUnknown,
   };
 
+  // Correspond to CanvasRenderingAPI defined in
+  // tools/metrics/histograms/enums.xml
+  enum CanvasRenderingAPI {
+    k2D = 0,
+    kWebgl = 1,
+    kWebgl2 = 2,
+    kBitmaprenderer = 3,
+    kWebgpu = 4,
+  };
+
+  void RecordUKMCanvasRenderingAPI(CanvasRenderingAPI canvasRenderingAPI);
+
   static ContextType ContextTypeFromId(const String& id);
   static ContextType ResolveContextTypeAliases(ContextType);
 
diff --git a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.h b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.h
index 3711e86a..378cc7b 100644
--- a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.h
+++ b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.h
@@ -107,6 +107,9 @@
   bool IsOffscreenCanvas() const override;
 
   const UkmParameters GetUkmParameters() { return ukm_params_; }
+  void SetUkmRecorderForTesting(ukm::UkmRecorder* test_ukm_recorder) {
+    ukm_params_.ukm_recorder = test_ukm_recorder;
+  }
 
  protected:
   ~CanvasRenderingContextHost() override {}
diff --git a/third_party/blink/renderer/core/input/mouse_event_manager.cc b/third_party/blink/renderer/core/input/mouse_event_manager.cc
index 687de5a..ef2b67a 100644
--- a/third_party/blink/renderer/core/input/mouse_event_manager.cc
+++ b/third_party/blink/renderer/core/input/mouse_event_manager.cc
@@ -46,7 +46,6 @@
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
 #include "third_party/blink/renderer/core/svg/svg_document_extensions.h"
 #include "third_party/blink/renderer/platform/geometry/float_quad.h"
-#include "third_party/blink/renderer/platform/instrumentation/histogram.h"
 
 namespace blink {
 
@@ -403,19 +402,15 @@
   UMA_HISTOGRAM_BOOLEAN("Event.ClickTargetChangedDueToInteractiveElement",
                         click_target_node != old_click_target_node);
 
-  DEFINE_STATIC_LOCAL(BooleanHistogram, histogram,
-                      ("Event.ClickNotFiredDueToDomManipulation"));
+  const bool click_element_still_in_flat_tree =
+      (click_element_ && click_element_->CanParticipateInFlatTree() &&
+       click_element_->isConnected());
+  UMA_HISTOGRAM_BOOLEAN("Event.ClickNotFiredDueToDomManipulation",
+                        !click_element_still_in_flat_tree);
+  DCHECK_EQ(click_element_ == mouse_down_element_,
+            click_element_still_in_flat_tree);
 
-  if (click_element_ && click_element_->CanParticipateInFlatTree() &&
-      click_element_->isConnected()) {
-    DCHECK(click_element_ == mouse_down_element_);
-    histogram.Count(false);
-  } else {
-    histogram.Count(true);
-  }
-
-  if ((click_element_ && click_element_->CanParticipateInFlatTree() &&
-       click_element_->isConnected()) ||
+  if (click_element_still_in_flat_tree ||
       RuntimeEnabledFeatures::ClickRetargettingEnabled()) {
     return DispatchMouseEvent(
         click_target_node,
diff --git a/third_party/blink/renderer/modules/BUILD.gn b/third_party/blink/renderer/modules/BUILD.gn
index 41e01d38..ecf9a74 100644
--- a/third_party/blink/renderer/modules/BUILD.gn
+++ b/third_party/blink/renderer/modules/BUILD.gn
@@ -256,6 +256,7 @@
     "//third_party/blink/renderer/bindings/modules:bindings_modules_generated",
     "//third_party/blink/renderer/bindings/modules/v8:generated",
     "//third_party/blink/renderer/core:core_event_interfaces",
+    "//third_party/blink/renderer/modules/mediarecorder:buildflags",
   ]
 }
 
@@ -285,6 +286,7 @@
     "canvas/canvas2d/canvas_rendering_context_2d_api_test.cc",
     "canvas/canvas2d/canvas_rendering_context_2d_test.cc",
     "canvas/htmlcanvas/html_canvas_element_module_test.cc",
+    "canvas/offscreencanvas/offscreen_canvas_rendering_api_ukm_metrics_test.cc",
     "canvas/offscreencanvas/offscreen_canvas_test.cc",
     "content_index/content_description_type_converter_test.cc",
     "credentialmanager/credentials_container_test.cc",
@@ -473,12 +475,14 @@
     "//base/test:test_support",
     "//build:chromecast_buildflags",
     "//components/schema_org/common:mojom_blink",
+    "//components/ukm:test_support",
     "//media:test_support",
     "//media/mojo/mojom",
     "//media/webrtc:webrtc",
     "//mojo/public/cpp/bindings",
     "//net:test_support",
     "//services/device/public/cpp:test_support",
+    "//services/metrics/public/cpp:ukm_builders",
     "//skia",
     "//testing/gmock",
     "//testing/gtest",
diff --git a/third_party/blink/renderer/modules/DEPS b/third_party/blink/renderer/modules/DEPS
index b14c24d3..f87df72 100644
--- a/third_party/blink/renderer/modules/DEPS
+++ b/third_party/blink/renderer/modules/DEPS
@@ -39,5 +39,5 @@
     ],
     "canvas_fuzzer.cc": [
         "+base/test/bind_test_util.h",
-    ]
+    ],
 }
diff --git a/third_party/blink/renderer/modules/canvas/DEPS b/third_party/blink/renderer/modules/canvas/DEPS
index 6c5a3f5..55ced6c 100644
--- a/third_party/blink/renderer/modules/canvas/DEPS
+++ b/third_party/blink/renderer/modules/canvas/DEPS
@@ -2,5 +2,9 @@
     "html_canvas_element_module_test.cc": [
         "+components/viz/test/test_context_provider.h",
         "+components/viz/test/test_gles2_interface.h",
+    ],
+    "offscreen_canvas_rendering_api_ukm_metrics_test.cc": [
+        "+components/ukm/test_ukm_recorder.h",
+        "+services/metrics/public/cpp/ukm_builders.h",
     ]
 }
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
index 82f0686..5591bc8 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
@@ -117,6 +117,19 @@
   int save_count_;
 };
 
+CanvasRenderingContext* CanvasRenderingContext2D::Factory::Create(
+    CanvasRenderingContextHost* host,
+    const CanvasContextCreationAttributesCore& attrs) {
+  DCHECK(!host->IsOffscreenCanvas());
+  CanvasRenderingContext* rendering_context =
+      MakeGarbageCollected<CanvasRenderingContext2D>(
+          static_cast<HTMLCanvasElement*>(host), attrs);
+  DCHECK(rendering_context);
+  rendering_context->RecordUKMCanvasRenderingAPI(
+      CanvasRenderingContext::CanvasRenderingAPI::k2D);
+  return rendering_context;
+}
+
 CanvasRenderingContext2D::CanvasRenderingContext2D(
     HTMLCanvasElement* canvas,
     const CanvasContextCreationAttributesCore& attrs)
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.h b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.h
index 5278f719..9efd4554 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.h
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.h
@@ -85,11 +85,8 @@
 
     CanvasRenderingContext* Create(
         CanvasRenderingContextHost* host,
-        const CanvasContextCreationAttributesCore& attrs) override {
-      DCHECK(!host->IsOffscreenCanvas());
-      return MakeGarbageCollected<CanvasRenderingContext2D>(
-          static_cast<HTMLCanvasElement*>(host), attrs);
-    }
+        const CanvasContextCreationAttributesCore& attrs) override;
+
     CanvasRenderingContext::ContextType GetContextType() const override {
       return CanvasRenderingContext::kContext2D;
     }
diff --git a/third_party/blink/renderer/modules/canvas/imagebitmap/image_bitmap_rendering_context.cc b/third_party/blink/renderer/modules/canvas/imagebitmap/image_bitmap_rendering_context.cc
index d6cd4077..50c64c42 100644
--- a/third_party/blink/renderer/modules/canvas/imagebitmap/image_bitmap_rendering_context.cc
+++ b/third_party/blink/renderer/modules/canvas/imagebitmap/image_bitmap_rendering_context.cc
@@ -58,7 +58,12 @@
 CanvasRenderingContext* ImageBitmapRenderingContext::Factory::Create(
     CanvasRenderingContextHost* host,
     const CanvasContextCreationAttributesCore& attrs) {
-  return MakeGarbageCollected<ImageBitmapRenderingContext>(host, attrs);
+  CanvasRenderingContext* rendering_context =
+      MakeGarbageCollected<ImageBitmapRenderingContext>(host, attrs);
+  DCHECK(rendering_context);
+  rendering_context->RecordUKMCanvasRenderingAPI(
+      CanvasRenderingContext::CanvasRenderingAPI::kBitmaprenderer);
+  return rendering_context;
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/canvas/offscreencanvas/offscreen_canvas_rendering_api_ukm_metrics_test.cc b/third_party/blink/renderer/modules/canvas/offscreencanvas/offscreen_canvas_rendering_api_ukm_metrics_test.cc
new file mode 100644
index 0000000..d010b977
--- /dev/null
+++ b/third_party/blink/renderer/modules/canvas/offscreencanvas/offscreen_canvas_rendering_api_ukm_metrics_test.cc
@@ -0,0 +1,81 @@
+// 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 "third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.h"
+
+#include "components/ukm/test_ukm_recorder.h"
+#include "services/metrics/public/cpp/ukm_builders.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/frame/local_dom_window.h"
+#include "third_party/blink/renderer/core/frame/local_frame_view.h"
+#include "third_party/blink/renderer/core/html/canvas/canvas_context_creation_attributes_core.h"
+#include "third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h"
+#include "third_party/blink/renderer/core/testing/page_test_base.h"
+#include "third_party/blink/renderer/modules/canvas/htmlcanvas/html_canvas_element_module.h"
+
+using testing::Mock;
+
+namespace blink {
+
+class OffscreenCanvasRenderingAPIUkmMetricsTest : public PageTestBase {
+ public:
+  OffscreenCanvasRenderingAPIUkmMetricsTest();
+
+  void SetUp() override {
+    PageTestBase::SetUp();
+    GetDocument().documentElement()->setInnerHTML(
+        "<body><canvas id='c'></canvas></body>");
+    auto* canvas_element =
+        To<HTMLCanvasElement>(GetDocument().getElementById("c"));
+
+    DummyExceptionStateForTesting exception_state;
+    offscreen_canvas_element_ =
+        HTMLCanvasElementModule::transferControlToOffscreen(
+            GetDocument().domWindow(), *canvas_element, exception_state);
+    UpdateAllLifecyclePhasesForTest();
+  }
+
+  void CheckContext(String context_type,
+                    CanvasRenderingContext::CanvasRenderingAPI expected_value) {
+    std::unique_ptr<ukm::TestUkmRecorder> test_ukm_recorder =
+        std::make_unique<ukm::TestUkmRecorder>();
+    CanvasContextCreationAttributesCore attributes;
+    offscreen_canvas_element_->SetUkmRecorderForTesting(
+        test_ukm_recorder.get());
+    offscreen_canvas_element_->GetCanvasRenderingContext(
+        GetDocument().domWindow(), context_type, attributes);
+
+    auto entries = test_ukm_recorder->GetEntriesByName(
+        ukm::builders::ClientRenderingAPI::kEntryName);
+    EXPECT_EQ(1ul, entries.size());
+    auto* entry = entries[0];
+    ukm::TestUkmRecorder::ExpectEntryMetric(
+        entry,
+        ukm::builders::ClientRenderingAPI::
+            kOffscreenCanvas_RenderingContextName,
+        static_cast<int>(expected_value));
+  }
+
+ private:
+  Persistent<OffscreenCanvas> offscreen_canvas_element_;
+};
+
+OffscreenCanvasRenderingAPIUkmMetricsTest::
+    OffscreenCanvasRenderingAPIUkmMetricsTest() = default;
+
+TEST_F(OffscreenCanvasRenderingAPIUkmMetricsTest, OffscreenCanvas2D) {
+  CheckContext("2d", CanvasRenderingContext::CanvasRenderingAPI::k2D);
+}
+
+TEST_F(OffscreenCanvasRenderingAPIUkmMetricsTest,
+       OffscreenCanvasBitmapRenderer) {
+  CheckContext("bitmaprenderer",
+               CanvasRenderingContext::CanvasRenderingAPI::kBitmaprenderer);
+}
+
+// Skip tests for WebGL context for now
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.cc
index 1e0f96e2..9a1fd7b0 100644
--- a/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.cc
@@ -75,6 +75,19 @@
 
 namespace blink {
 
+CanvasRenderingContext* OffscreenCanvasRenderingContext2D::Factory::Create(
+    CanvasRenderingContextHost* host,
+    const CanvasContextCreationAttributesCore& attrs) {
+  DCHECK(host->IsOffscreenCanvas());
+  CanvasRenderingContext* rendering_context =
+      MakeGarbageCollected<OffscreenCanvasRenderingContext2D>(
+          static_cast<OffscreenCanvas*>(host), attrs);
+  DCHECK(rendering_context);
+  rendering_context->RecordUKMCanvasRenderingAPI(
+      CanvasRenderingContext::CanvasRenderingAPI::k2D);
+  return rendering_context;
+}
+
 OffscreenCanvasRenderingContext2D::~OffscreenCanvasRenderingContext2D() =
     default;
 
diff --git a/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.h b/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.h
index 025e6af49..0951320 100644
--- a/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.h
+++ b/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.h
@@ -34,11 +34,7 @@
 
     CanvasRenderingContext* Create(
         CanvasRenderingContextHost* host,
-        const CanvasContextCreationAttributesCore& attrs) override {
-      DCHECK(host->IsOffscreenCanvas());
-      return MakeGarbageCollected<OffscreenCanvasRenderingContext2D>(
-          static_cast<OffscreenCanvas*>(host), attrs);
-    }
+        const CanvasContextCreationAttributesCore& attrs) override;
 
     CanvasRenderingContext::ContextType GetContextType() const override {
       return CanvasRenderingContext::kContext2D;
diff --git a/third_party/blink/renderer/modules/hid/BUILD.gn b/third_party/blink/renderer/modules/hid/BUILD.gn
index 805fea0..4b46a7c0 100644
--- a/third_party/blink/renderer/modules/hid/BUILD.gn
+++ b/third_party/blink/renderer/modules/hid/BUILD.gn
@@ -14,8 +14,6 @@
     "hid_device.h",
     "hid_input_report_event.cc",
     "hid_input_report_event.h",
-    "navigator_hid.cc",
-    "navigator_hid.h",
   ]
 }
 
diff --git a/third_party/blink/renderer/modules/hid/hid.cc b/third_party/blink/renderer/modules/hid/hid.cc
index 17f957d..f3a47cc 100644
--- a/third_party/blink/renderer/modules/hid/hid.cc
+++ b/third_party/blink/renderer/modules/hid/hid.cc
@@ -15,7 +15,9 @@
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/dom_exception.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
+#include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
+#include "third_party/blink/renderer/core/frame/navigator.h"
 #include "third_party/blink/renderer/modules/event_target_modules.h"
 #include "third_party/blink/renderer/modules/hid/hid_connection_event.h"
 #include "third_party/blink/renderer/modules/hid/hid_device.h"
@@ -82,12 +84,27 @@
 
 }  // namespace
 
-HID::HID(ExecutionContext& context)
-    : ExecutionContextClient(&context),
-      service_(&context),
-      feature_handle_for_scheduler_(context.GetScheduler()->RegisterFeature(
-          SchedulingPolicy::Feature::kWebHID,
-          {SchedulingPolicy::RecordMetricsForBackForwardCache()})) {}
+const char HID::kSupplementName[] = "HID";
+
+HID* HID::hid(Navigator& navigator) {
+  if (!navigator.DomWindow())
+    return nullptr;
+
+  HID* hid = Supplement<Navigator>::From<HID>(navigator);
+  if (!hid) {
+    hid = MakeGarbageCollected<HID>(navigator);
+    ProvideTo(navigator, hid);
+  }
+  return hid;
+}
+
+HID::HID(Navigator& navigator)
+    : Supplement<Navigator>(navigator),
+      service_(navigator.DomWindow()),
+      feature_handle_for_scheduler_(
+          navigator.DomWindow()->GetScheduler()->RegisterFeature(
+              SchedulingPolicy::Feature::kWebHID,
+              {SchedulingPolicy::RecordMetricsForBackForwardCache()})) {}
 
 HID::~HID() {
   DCHECK(get_devices_promises_.IsEmpty());
@@ -95,7 +112,7 @@
 }
 
 ExecutionContext* HID::GetExecutionContext() const {
-  return ExecutionContextClient::GetExecutionContext();
+  return GetSupplementable()->DomWindow();
 }
 
 const AtomicString& HID::InterfaceName() const {
@@ -164,8 +181,7 @@
 ScriptPromise HID::requestDevice(ScriptState* script_state,
                                  const HIDDeviceRequestOptions* options,
                                  ExceptionState& exception_state) {
-  auto* frame = GetFrame();
-  if (!frame || !frame->GetDocument()) {
+  if (!GetExecutionContext()) {
     exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
                                       kContextGone);
     return ScriptPromise();
@@ -178,7 +194,8 @@
     return ScriptPromise();
   }
 
-  if (!LocalFrame::HasTransientUserActivation(frame)) {
+  if (!LocalFrame::HasTransientUserActivation(
+          GetSupplementable()->DomWindow()->GetFrame())) {
     exception_state.ThrowSecurityError(
         "Must be handling a user gesture to show a permission request.");
     return ScriptPromise();
@@ -289,7 +306,7 @@
   visitor->Trace(request_device_promises_);
   visitor->Trace(device_cache_);
   EventTargetWithInlineData::Trace(visitor);
-  ExecutionContextClient::Trace(visitor);
+  Supplement<Navigator>::Trace(visitor);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/hid/hid.h b/third_party/blink/renderer/modules/hid/hid.h
index 5791198..7bfc66c83 100644
--- a/third_party/blink/renderer/modules/hid/hid.h
+++ b/third_party/blink/renderer/modules/hid/hid.h
@@ -10,28 +10,34 @@
 #include "third_party/blink/public/mojom/hid/hid.mojom-blink.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
 #include "third_party/blink/renderer/core/dom/events/event_target.h"
-#include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
 #include "third_party/blink/renderer/platform/mojo/heap_mojo_wrapper_mode.h"
 #include "third_party/blink/renderer/platform/scheduler/public/frame_or_worker_scheduler.h"
+#include "third_party/blink/renderer/platform/supplementable.h"
 
 namespace blink {
 
 class ExecutionContext;
 class HIDDevice;
 class HIDDeviceRequestOptions;
+class Navigator;
 class ScriptPromiseResolver;
 class ScriptState;
 
 class HID : public EventTargetWithInlineData,
-            public ExecutionContextClient,
+            public Supplement<Navigator>,
             public device::mojom::blink::HidManagerClient {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
-  explicit HID(ExecutionContext& context);
+  static const char kSupplementName[];
+
+  // Web-exposed getter for navigator.hid
+  static HID* hid(Navigator&);
+
+  explicit HID(Navigator&);
   ~HID() override;
 
   // EventTarget:
@@ -43,7 +49,7 @@
   void DeviceRemoved(
       device::mojom::blink::HidDeviceInfoPtr device_info) override;
 
-  // Web-exposed interfaces:
+  // Web-exposed interfaces on hid object:
   DEFINE_ATTRIBUTE_EVENT_LISTENER(connect, kConnect)
   DEFINE_ATTRIBUTE_EVENT_LISTENER(disconnect, kDisconnect)
   ScriptPromise getDevices(ScriptState*, ExceptionState&);
diff --git a/third_party/blink/renderer/modules/hid/navigator_hid.cc b/third_party/blink/renderer/modules/hid/navigator_hid.cc
deleted file mode 100644
index 3779088..0000000
--- a/third_party/blink/renderer/modules/hid/navigator_hid.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2019 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 "third_party/blink/renderer/modules/hid/navigator_hid.h"
-
-#include "third_party/blink/renderer/core/frame/local_dom_window.h"
-#include "third_party/blink/renderer/core/frame/local_frame.h"
-#include "third_party/blink/renderer/core/frame/navigator.h"
-#include "third_party/blink/renderer/modules/hid/hid.h"
-
-namespace blink {
-
-NavigatorHID& NavigatorHID::From(Navigator& navigator) {
-  NavigatorHID* supplement =
-      Supplement<Navigator>::From<NavigatorHID>(navigator);
-  if (!supplement) {
-    supplement = MakeGarbageCollected<NavigatorHID>(navigator);
-    ProvideTo(navigator, supplement);
-  }
-  return *supplement;
-}
-
-HID* NavigatorHID::hid(Navigator& navigator) {
-  return NavigatorHID::From(navigator).hid();
-}
-
-HID* NavigatorHID::hid() {
-  return hid_;
-}
-
-void NavigatorHID::Trace(Visitor* visitor) const {
-  visitor->Trace(hid_);
-  Supplement<Navigator>::Trace(visitor);
-}
-
-NavigatorHID::NavigatorHID(Navigator& navigator) {
-  if (navigator.GetFrame()) {
-    DCHECK(navigator.GetFrame()->DomWindow());
-    hid_ = MakeGarbageCollected<HID>(*navigator.GetFrame()->DomWindow());
-  }
-}
-
-const char NavigatorHID::kSupplementName[] = "NavigatorHID";
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/modules/hid/navigator_hid.h b/third_party/blink/renderer/modules/hid/navigator_hid.h
deleted file mode 100644
index ef1f027..0000000
--- a/third_party/blink/renderer/modules/hid/navigator_hid.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2019 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 THIRD_PARTY_BLINK_RENDERER_MODULES_HID_NAVIGATOR_HID_H_
-#define THIRD_PARTY_BLINK_RENDERER_MODULES_HID_NAVIGATOR_HID_H_
-
-#include "third_party/blink/renderer/core/frame/navigator.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
-#include "third_party/blink/renderer/platform/supplementable.h"
-
-namespace blink {
-
-class Navigator;
-class HID;
-
-class NavigatorHID final : public GarbageCollected<NavigatorHID>,
-                           public Supplement<Navigator> {
- public:
-  static const char kSupplementName[];
-
-  // Gets, or creates, NavigatorHID supplement on Navigator.
-  // See platform/Supplementable.h
-  static NavigatorHID& From(Navigator&);
-
-  static HID* hid(Navigator&);
-  HID* hid();
-
-  void Trace(Visitor*) const override;
-
-  explicit NavigatorHID(Navigator&);
-
- private:
-  Member<HID> hid_;
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_HID_NAVIGATOR_HID_H_
diff --git a/third_party/blink/renderer/modules/hid/navigator_hid.idl b/third_party/blink/renderer/modules/hid/navigator_hid.idl
index 846c897..8a176dc 100644
--- a/third_party/blink/renderer/modules/hid/navigator_hid.idl
+++ b/third_party/blink/renderer/modules/hid/navigator_hid.idl
@@ -7,7 +7,7 @@
 // https://wicg.github.io/webhid/index.html#enumeration
 
 [
-    ImplementedAs=NavigatorHID,
+    ImplementedAs=HID,
     RuntimeEnabled=WebHID,
     SecureContext
 ] partial interface Navigator {
diff --git a/third_party/blink/renderer/modules/webgl/webgl2_rendering_context.cc b/third_party/blink/renderer/modules/webgl/webgl2_rendering_context.cc
index 8aefd14..2afee68 100644
--- a/third_party/blink/renderer/modules/webgl/webgl2_rendering_context.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl2_rendering_context.cc
@@ -107,7 +107,8 @@
 
   rendering_context->InitializeNewContext();
   rendering_context->RegisterContextExtensions();
-
+  rendering_context->RecordUKMCanvasRenderingAPI(
+      CanvasRenderingContext::CanvasRenderingAPI::kWebgl2);
   return rendering_context;
 }
 
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context.cc b/third_party/blink/renderer/modules/webgl/webgl_rendering_context.cc
index 086960a..dea00f2b 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context.cc
@@ -132,7 +132,8 @@
   }
   rendering_context->InitializeNewContext();
   rendering_context->RegisterContextExtensions();
-
+  rendering_context->RecordUKMCanvasRenderingAPI(
+      CanvasRenderingContext::CanvasRenderingAPI::kWebgl);
   return rendering_context;
 }
 
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 1d2f319..29fea16 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -346,6 +346,7 @@
     },
     {
       name: "CompositeRelativeKeyframes",
+      status: "stable",
     },
     {
       name: "CompositeSVG",
diff --git a/third_party/blink/renderer/platform/widget/widget_base.cc b/third_party/blink/renderer/platform/widget/widget_base.cc
index b68607b..328087b 100644
--- a/third_party/blink/renderer/platform/widget/widget_base.cc
+++ b/third_party/blink/renderer/platform/widget/widget_base.cc
@@ -1384,19 +1384,23 @@
   window_screen_rect_ = window_screen_rect;
 }
 
-void WidgetBase::SetPendingWindowRect(const gfx::Rect* rect) {
-  if (rect) {
-    pending_window_rect_ = *rect;
-    // Popups don't get size updates back from the browser so just store the set
-    // values.
-    if (!client_->FrameWidget()) {
-      SetScreenRects(*rect, *rect);
-    }
-  } else {
-    pending_window_rect_.reset();
+void WidgetBase::SetPendingWindowRect(const gfx::Rect& rect) {
+  pending_window_rect_count_++;
+  pending_window_rect_ = rect;
+  // Popups don't get size updates back from the browser so just store the set
+  // values.
+  if (!client_->FrameWidget()) {
+    SetScreenRects(rect, rect);
   }
 }
 
+void WidgetBase::AckPendingWindowRect() {
+  DCHECK(pending_window_rect_count_);
+  pending_window_rect_count_--;
+  if (pending_window_rect_count_ == 0)
+    pending_window_rect_.reset();
+}
+
 gfx::Rect WidgetBase::WindowRect() {
   gfx::Rect rect;
   if (pending_window_rect_) {
diff --git a/third_party/blink/renderer/platform/widget/widget_base.h b/third_party/blink/renderer/platform/widget/widget_base.h
index 6e012222..7ca3d6b 100644
--- a/third_party/blink/renderer/platform/widget/widget_base.h
+++ b/third_party/blink/renderer/platform/widget/widget_base.h
@@ -238,7 +238,10 @@
   // the window rect is delivered asynchronously to the browser. Pass in nullptr
   // to clear the pending window rect once the browser has acknowledged the
   // request.
-  void SetPendingWindowRect(const gfx::Rect* rect);
+  void SetPendingWindowRect(const gfx::Rect& rect);
+
+  // Must correspond with a previous call to SetPendingWindowRect.
+  void AckPendingWindowRect();
 
   // Returns the location/bounds of the widget (in screen coordinates).
   const gfx::Rect& WidgetScreenRect() const { return widget_screen_rect_; }
@@ -413,6 +416,14 @@
   // physical device pixels.
   gfx::Rect widget_screen_rect_;
   gfx::Rect window_screen_rect_;
+
+  // While we are waiting for the browser to update window sizes, we track the
+  // pending size temporarily.
+  int pending_window_rect_count_ = 0;
+
+  // A pending window rect that is inflight and hasn't been acknowledged by the
+  // browser yet. This should only be set if |pending_window_rect_count_| is
+  // non-zero.
   base::Optional<gfx::Rect> pending_window_rect_;
 
   // The size of the visible viewport (in DIPs).
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 21cfef1..4e09378 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -325,6 +325,7 @@
 
 # Service workers new newtwork button added
 crbug.com/1137050 http/tests/devtools/service-workers/service-workers-view.js [ Skip ]
+crbug.com/1137050 http/tests/devtools/service-workers/service-workers-redundant.js [ Skip ]
 
 # ==== Regressions introduced by BlinkGenPropertyTrees =====
 # Reflection / mask ordering issue
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index 16018de3..245abc3 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -64538,7 +64538,7 @@
       ]
      ],
      "flexbox_justifycontent-center-overflow.html": [
-      "6f4d2baaeeaaa4af6b26efb1b6c0cfd2d8c5d14d",
+      "6733760ea5bbf030459aa482f06841ce8e3bd941",
       [
        null,
        [
@@ -90456,8 +90456,34 @@
        {}
       ]
      ],
+     "first-line-with-out-of-flow-and-nested-div.html": [
+      "e277e9ce1cb69bcc1c7a61c8a100585e077bd359",
+      [
+       null,
+       [
+        [
+         "/css/css-pseudo/first-line-with-out-of-flow-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "first-line-with-out-of-flow-and-nested-span.html": [
+      "c0fe55cbc5292fa24e745760ea0c70a4bd15547f",
+      [
+       null,
+       [
+        [
+         "/css/css-pseudo/first-line-with-out-of-flow-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "first-line-with-out-of-flow.html": [
-      "798ecf8264e2ddb38257c6cb1174bcc069c6e6d1",
+      "76d50254506086bfd3364b4ebcab3384e4d6e4a6",
       [
        null,
        [
@@ -181961,7 +181987,7 @@
       []
      ],
      "flexbox_justifycontent-center-overflow-ref.html": [
-      "f6443f6a78adc038a427d9eeaf1265fd77e09a26",
+      "61d6dd95866c430d91c809ae3b025c7bd9c1806e",
       []
      ],
      "flexbox_justifycontent-center-ref.html": [
@@ -243833,7 +243859,7 @@
       []
      ],
      "vcs.py": [
-      "3dfd7c980d70594ff5d8ef48d034ae696521fbdd",
+      "344575276b42cb33ebb1d81f5e1a21eac39db2e6",
       []
      ]
     },
@@ -421579,6 +421605,13 @@
        {}
       ]
      ],
+     "unknown-mediatypes.html": [
+      "f5176d1c87b6a2a14281c603f4a491efd86c826d",
+      [
+       null,
+       {}
+      ]
+     ],
      "video-codecs.https.html": [
       "4ce0618bca3007e4193b98a95b0a6d030cc24b5f",
       [
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/flexbox_justifycontent-center-overflow-ref.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flexbox_justifycontent-center-overflow-ref.html
index f6443f6..61d6dd9 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-flexbox/flexbox_justifycontent-center-overflow-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flexbox_justifycontent-center-overflow-ref.html
@@ -12,24 +12,26 @@
 	position: relative;
 }
 span {
-	background: yellow;
-	margin: 1em 0 0 -2.85em;
+    background: white;
+	margin: 1em;
+	width: 2em;
 	height: 6em;
-	display: inline-block;
+	display: block;
+	position: absolute;
+}
+span:nth-child(1) {
+	background: yellow;
+	left: -4em;
 }
 span:nth-child(2) {
 	background: pink;
-	margin-left: 2em;
 }
 span:nth-child(3) {
 	background: lightblue;
-	margin-left: 0;
-	position: relative;
-	top: -7em;
-	left: 4.95em
+	left: 4em;
 }
 </style>
 
 <div>
-	<span>dam</span><span>dam</span><span>dam</span>
+	<span>x</span><span>x</span><span>x</span>
 </div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/flexbox_justifycontent-center-overflow.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flexbox_justifycontent-center-overflow.html
index 6f4d2baae..6733760 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-flexbox/flexbox_justifycontent-center-overflow.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flexbox_justifycontent-center-overflow.html
@@ -8,7 +8,6 @@
 div {
 	font-family: monospace;
 	background: blue;
-	padding: 0;
 	margin: 1em 0 0 10em;
 	border: 1px solid black;
 	height: 8em;
@@ -20,8 +19,8 @@
 span {
 	background: white;
 	margin: 1em;
-	width: 5em;
-	max-width: 6em;
+	min-width: 2em;
+	max-width: 2em;
 	display: inline-block;
 
 	flex: 1 0 0%;
@@ -32,5 +31,5 @@
 </style>
 
 <div>
-	<span>dam</span><span>dam</span><span>dam</span>
+	<span>x</span><span>x</span><span>x</span>
 </div>
diff --git a/third_party/blink/web_tests/external/wpt/tools/manifest/vcs.py b/third_party/blink/web_tests/external/wpt/tools/manifest/vcs.py
index 3dfd7c9..34457527 100644
--- a/third_party/blink/web_tests/external/wpt/tools/manifest/vcs.py
+++ b/third_party/blink/web_tests/external/wpt/tools/manifest/vcs.py
@@ -9,11 +9,9 @@
 
 from .utils import git
 
-try:
-    from ..gitignore import gitignore
-except ValueError:
-    # relative import beyond toplevel throws *ValueError*!
-    from gitignore import gitignore  # type: ignore
+# Cannot do `from ..gitignore import gitignore` because
+# relative import beyond toplevel throws *ImportError*!
+from gitignore import gitignore  # type: ignore
 
 
 MYPY = False
diff --git a/third_party/blink/web_tests/external/wpt/webauthn/createcredential-extensions.https.html b/third_party/blink/web_tests/external/wpt/webauthn/createcredential-extensions.https.html
index 46cab30..64690e7 100644
--- a/third_party/blink/web_tests/external/wpt/webauthn/createcredential-extensions.https.html
+++ b/third_party/blink/web_tests/external/wpt/webauthn/createcredential-extensions.https.html
@@ -23,28 +23,14 @@
     new CreateCredentialsTest("options.publicKey.extensions", "hi mom").runTest("Bad extensions: extensions is string", TypeError);
 
     // phony extensions
-    // TODO: not sure if this should pass or fail
-    // should be clarified as part of https://github.com/w3c/webauthn/pull/765
     var randomExtId = {};
     randomExtId[createRandomString(64)] = dummyExtension;
     new CreateCredentialsTest("options.publicKey.extensions", {foo: JSON.stringify(randomExtId)}).runTest("extensions is a nonsensical JSON string");
 
-    // Defined extensions.
-
     // appid
     new CreateCredentialsTest("options.publicKey.extensions", {appid: ""}).runTest("empty appid in create request", "NotSupportedError");
     new CreateCredentialsTest("options.publicKey.extensions", {appid: null}).runTest("null appid in create request", "NotSupportedError");
     new CreateCredentialsTest("options.publicKey.extensions", {appid: "anything"}).runTest("appid in create request", "NotSupportedError");
-
-    // TODO
-    // defined extensions:
-    // * txAuthSimple
-    // * txAuthGeneric
-    // * authnSel
-    // * exts
-    // * uvi
-    // * loc
-    // * uvm
 });
 
 /* JSHINT */
diff --git a/third_party/blink/web_tests/external/wpt/webauthn/createcredential-large-blob-not-supported.https.html b/third_party/blink/web_tests/external/wpt/webauthn/createcredential-large-blob-not-supported.https.html
new file mode 100644
index 0000000..1a320d54
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webauthn/createcredential-large-blob-not-supported.https.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>navigator.credentials.create() largeBlob extension tests with no authenticator support</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+  "use strict";
+
+  new CreateCredentialsTest("options.publicKey.extensions", {
+    largeBlob: {
+      write: new ArrayBuffer(),
+    },
+  }).runTest("navigator.credentials.create() with largeBlob.write set", "NotSupportedError");
+
+  new CreateCredentialsTest("options.publicKey.extensions", {
+    largeBlob: {
+      read: true,
+    },
+  }).runTest("navigator.credentials.create() with largeBlob.read set", "NotSupportedError");
+
+  promise_test(async t => {
+    const credential = await createCredential({
+      options: {
+        publicKey: {
+          authenticatorSelection: {
+            requireResidentKey: true,
+          },
+          extensions: {
+            largeBlob: {
+              support: "preferred",
+            },
+          },
+        },
+      },
+    });
+    assert_own_property(credential.getClientExtensionResults(), "largeBlob");
+    assert_false(credential.getClientExtensionResults().largeBlob.supported);
+    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "blob");
+    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "written");
+  }, "navigator.credentials.create() with largeBlob.support set to preferred and not supported by authenticator");
+
+  promise_test(async t => {
+    const credential = await createCredential({
+      options: {
+        publicKey: {
+          authenticatorSelection: {
+            requireResidentKey: true,
+          },
+          extensions: {
+            largeBlob: {},
+          },
+        },
+      },
+    });
+    assert_own_property(credential.getClientExtensionResults(), "largeBlob");
+    assert_false(credential.getClientExtensionResults().largeBlob.supported);
+    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "blob");
+    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "written");
+  }, "navigator.credentials.create() with largeBlob.support not set and not supported by authenticator");
+
+  new CreateCredentialsTest("options.publicKey.extensions", {
+    largeBlob: {
+      support: "required"
+    },
+  }).runTest("navigator.credentials.create() with largeBlob.support set to required and not supported by authenticator", "NotAllowedError");
+}, {
+  protocol: "ctap2",
+  hasResidentKey: true,
+  hasUserVerification: true,
+  isUserVerified: true,
+});
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/webauthn/createcredential-large-blob-supported.https.html b/third_party/blink/web_tests/external/wpt/webauthn/createcredential-large-blob-supported.https.html
new file mode 100644
index 0000000..46623de
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webauthn/createcredential-large-blob-supported.https.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>navigator.credentials.create() largeBlob extension tests with authenticator support</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+  "use strict";
+
+  promise_test(async t => {
+    const credential = await createCredential({
+      options: {
+        publicKey: {
+          authenticatorSelection: {
+            requireResidentKey: true,
+          },
+          extensions: {
+            largeBlob: {
+              support: "preferred",
+            },
+          },
+        },
+      },
+    });
+    assert_own_property(credential.getClientExtensionResults(), "largeBlob");
+    assert_true(credential.getClientExtensionResults().largeBlob.supported);
+    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "blob");
+    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "written");
+  }, "navigator.credentials.create() with largeBlob.support set to preferred and supported by authenticator");
+
+  promise_test(async t => {
+    const credential = await createCredential({
+      options: {
+        publicKey: {
+          authenticatorSelection: {
+            requireResidentKey: true,
+          },
+          extensions: {
+            largeBlob: {},
+          },
+        },
+      },
+    });
+    assert_own_property(credential.getClientExtensionResults(), "largeBlob");
+    assert_true(credential.getClientExtensionResults().largeBlob.supported);
+    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "blob");
+    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "written");
+  }, "navigator.credentials.create() with largeBlob.support not set and supported by authenticator");
+
+  promise_test(async t => {
+    const credential = await createCredential({
+      options: {
+        publicKey: {
+          authenticatorSelection: {
+            requireResidentKey: true,
+          },
+          extensions: {
+            largeBlob: {
+              support: "required"
+            },
+          },
+        },
+      },
+    });
+    assert_own_property(credential.getClientExtensionResults(), "largeBlob");
+    assert_true(credential.getClientExtensionResults().largeBlob.supported);
+    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "blob");
+    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "written");
+  }, "navigator.credentials.create() with largeBlob.support set to required and supported by authenticator");
+}, {
+  protocol: "ctap2",
+  hasResidentKey: true,
+  hasUserVerification: true,
+  isUserVerified: true,
+  extensions: ["largeBlob"],
+});
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/webauthn/getcredential-large-blob-not-supported.https.html b/third_party/blink/web_tests/external/wpt/webauthn/getcredential-large-blob-not-supported.https.html
new file mode 100644
index 0000000..26e91d8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webauthn/getcredential-large-blob-not-supported.https.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>navigator.credentials.get() largeBlob extension tests with no authenticator support</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(async function() {
+  "use strict";
+
+  const credential = createCredential();
+
+  promise_test(async t => {
+    return promise_rejects_dom(t, "NotSupportedError",
+      navigator.credentials.get({publicKey: {
+        challenge: new Uint8Array(),
+        allowCredentials: [{
+          id: (await credential).rawId,
+          type: "public-key",
+        }],
+        extensions: {
+          largeBlob: {
+            support: "preferred",
+          },
+        },
+      }}));
+  }, "navigator.credentials.get() with largeBlob.support set");
+
+  promise_test(async t => {
+    return promise_rejects_dom(t, "NotSupportedError",
+      navigator.credentials.get({publicKey: {
+        challenge: new Uint8Array(),
+        allowCredentials: [{
+          id: (await credential).rawId,
+          type: "public-key",
+        }],
+        extensions: {
+          largeBlob: {
+            read: true,
+            write: new ArrayBuffer(),
+          },
+        },
+      }}));
+  }, "navigator.credentials.get() with largeBlob.read and largeBlob.write set");
+
+  promise_test(async t => {
+    const assertion = await navigator.credentials.get({publicKey: {
+      challenge: new Uint8Array(),
+      allowCredentials: [{
+        id: (await credential).rawId,
+        type: "public-key",
+      }],
+      extensions: {
+        largeBlob: {
+          read: true,
+        },
+      },
+    }});
+    assert_not_own_property(assertion.getClientExtensionResults().largeBlob, "supported");
+    assert_not_own_property(assertion.getClientExtensionResults().largeBlob, "blob");
+    assert_not_own_property(assertion.getClientExtensionResults().largeBlob, "written");
+  }, "navigator.credentials.get() with largeBlob.read set without authenticator support");
+
+  promise_test(async t => {
+    const assertion = await navigator.credentials.get({publicKey: {
+      challenge: new Uint8Array(),
+      allowCredentials: [{
+        id: (await credential).rawId,
+        type: "public-key",
+      }],
+      extensions: {
+        largeBlob: {
+          write: new TextEncoder().encode("Don't call me Shirley"),
+        },
+      },
+    }});
+    assert_not_own_property(assertion.getClientExtensionResults().largeBlob, "supported");
+    assert_not_own_property(assertion.getClientExtensionResults().largeBlob, "blob");
+    assert_false(assertion.getClientExtensionResults().largeBlob.written);
+  }, "navigator.credentials.get() with largeBlob.write set without authenticator support");
+}, {
+  protocol: "ctap2",
+  hasResidentKey: true,
+  hasUserVerification: true,
+  isUserVerified: true,
+});
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/webauthn/getcredential-large-blob-supported.https.html b/third_party/blink/web_tests/external/wpt/webauthn/getcredential-large-blob-supported.https.html
new file mode 100644
index 0000000..02aea56
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webauthn/getcredential-large-blob-supported.https.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>navigator.credentials.get() largeBlob extension tests with authenticator support</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(async function(authenticator) {
+  "use strict";
+
+  const credential = createCredential({
+    options: {
+      publicKey: {
+        authenticatorSelection: {
+          requireResidentKey: true,
+        },
+        extensions: {
+          largeBlob: {
+            support: "required",
+          },
+        },
+      },
+    },
+  });
+
+  promise_test(async t => {
+    const assertion = await navigator.credentials.get({publicKey: {
+      challenge: new Uint8Array(),
+      allowCredentials: [{
+        id: (await credential).rawId,
+        type: "public-key",
+      }],
+      extensions: {
+        largeBlob: {
+          read: true,
+        },
+      },
+    }});
+    assert_not_own_property(assertion.getClientExtensionResults().largeBlob, "supported");
+    assert_not_own_property(assertion.getClientExtensionResults().largeBlob, "blob");
+    assert_not_own_property(assertion.getClientExtensionResults().largeBlob, "written");
+  }, "navigator.credentials.get() with largeBlob.read set with no blob on authenticator");
+
+  promise_test(async t => {
+    const blob = new TextEncoder().encode("According to all known laws of aviation, "
+                                        + "there is no way a bee should be able to fly");
+    let assertion = await navigator.credentials.get({publicKey: {
+      challenge: new Uint8Array(),
+      allowCredentials: [{
+        id: (await credential).rawId,
+        type: "public-key",
+      }],
+      extensions: {
+        largeBlob: {
+          write: blob,
+        },
+      },
+    }});
+    assert_not_own_property(assertion.getClientExtensionResults().largeBlob, "blob");
+    assert_not_own_property(assertion.getClientExtensionResults().largeBlob, "supported");
+    assert_true(assertion.getClientExtensionResults().largeBlob.written);
+
+    assertion = await navigator.credentials.get({publicKey: {
+      challenge: new Uint8Array(),
+      allowCredentials: [{
+        id: (await credential).rawId,
+        type: "public-key",
+      }],
+      extensions: {
+        largeBlob: {
+          read: true,
+        },
+      },
+    }});
+    assert_array_equals(new Uint8Array(assertion.getClientExtensionResults().largeBlob.blob), blob);
+    assert_not_own_property(assertion.getClientExtensionResults().largeBlob, "supported");
+    assert_not_own_property(assertion.getClientExtensionResults().largeBlob, "written");
+  }, "navigator.credentials.get() read and write blob");
+}, {
+  protocol: "ctap2",
+  hasResidentKey: true,
+  hasUserVerification: true,
+  extensions: ["largeBlob"],
+  isUserVerified: true,
+});
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/webauthn/helpers.js b/third_party/blink/web_tests/external/wpt/webauthn/helpers.js
index c355d05..620feeb 100644
--- a/third_party/blink/web_tests/external/wpt/webauthn/helpers.js
+++ b/third_party/blink/web_tests/external/wpt/webauthn/helpers.js
@@ -69,7 +69,7 @@
     createArgs.options.publicKey.user.id = new Uint8Array(16);
 
     // change the defaults with any options that were passed in
-    extendObject (createArgs, opts);
+    extendObject(createArgs, opts);
 
     // create the credential, return the Promise
     return navigator.credentials.create(createArgs.options);
@@ -344,7 +344,8 @@
 function extendObject(dst, src) {
     Object.keys(src).forEach(function(key) {
         if (isSimpleObject(src[key])) {
-            extendObject (dst[key], src[key]);
+            dst[key] ||= {};
+            extendObject(dst[key], src[key]);
         } else {
             dst[key] = src[key];
         }
diff --git a/third_party/blink/web_tests/http/tests/credentialmanager/credentialscontainer-create-with-virtual-authenticator.html b/third_party/blink/web_tests/http/tests/credentialmanager/credentialscontainer-create-with-virtual-authenticator.html
index e4d8ebd..6cecfaf 100644
--- a/third_party/blink/web_tests/http/tests/credentialmanager/credentialscontainer-create-with-virtual-authenticator.html
+++ b/third_party/blink/web_tests/http/tests/credentialmanager/credentialscontainer-create-with-virtual-authenticator.html
@@ -91,125 +91,6 @@
     authAbortController.abort();
     return promise;
   }, "navigator.credentials.create() with abort signal flag is set after promise resolved");
-
-  // TODO(nsatragno): move largeBlob tests to WPTs once the largeBlob webdriver
-  // extension is implemented.
-  promise_test(async t => {
-    var customMakeCredOptions = deepCopy(MAKE_CREDENTIAL_OPTIONS);
-    customMakeCredOptions.authenticatorSelection.requireResidentKey = true;
-    customMakeCredOptions.extensions = {
-      largeBlob: {
-        read: true,
-      },
-    };
-    return promise_rejects_dom(t, "NotSupportedError",
-      navigator.credentials.create({ publicKey : customMakeCredOptions }));
-  }, "navigator.credentials.create() with largeBlob.read set");
-
-  promise_test(async t => {
-    var customMakeCredOptions = deepCopy(MAKE_CREDENTIAL_OPTIONS);
-    customMakeCredOptions.authenticatorSelection.requireResidentKey = true;
-    customMakeCredOptions.extensions = {
-      largeBlob: {
-        write: new ArrayBuffer(),
-      },
-    };
-    return promise_rejects_dom(t, "NotSupportedError",
-      navigator.credentials.create({ publicKey : customMakeCredOptions }));
-  }, "navigator.credentials.create() with largeBlob.write set");
-
-  promise_test(async t => {
-    var customMakeCredOptions = deepCopy(MAKE_CREDENTIAL_OPTIONS);
-    customMakeCredOptions.authenticatorSelection.requireResidentKey = true;
-    customMakeCredOptions.extensions = {
-      largeBlob: {
-        support: "preferred",
-      },
-    };
-    var credential =
-      await navigator.credentials.create({ publicKey : customMakeCredOptions });
-    assert_false(credential.getClientExtensionResults().largeBlob.supported);
-    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "blob");
-    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "written");
-
-  }, "navigator.credentials.create() with largeBlob.support set to preferred and not supported by authenticator");
-
-  promise_test(async t => {
-    var customMakeCredOptions = deepCopy(MAKE_CREDENTIAL_OPTIONS);
-    customMakeCredOptions.authenticatorSelection.requireResidentKey = true;
-    customMakeCredOptions.extensions = {
-      largeBlob: {},
-    };
-    // Should be equivalent to preferred.
-    var credential =
-      await navigator.credentials.create({ publicKey : customMakeCredOptions });
-    assert_false(credential.getClientExtensionResults().largeBlob.supported);
-    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "blob");
-    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "written");
-
-  }, "navigator.credentials.create() with largeBlob.support not set and not supported by authenticator");
-
-  promise_test(async t => {
-    var customMakeCredOptions = deepCopy(MAKE_CREDENTIAL_OPTIONS);
-    customMakeCredOptions.authenticatorSelection.requireResidentKey = true;
-    customMakeCredOptions.extensions = {
-      largeBlob: {
-        support: "required",
-      },
-    };
-    return promise_rejects_dom(t, "NotAllowedError",
-      navigator.credentials.create({ publicKey : customMakeCredOptions }));
-  }, "navigator.credentials.create() with largeBlob.support set to required and not supported by authenticator");
 }, {});
 
-authenticatorSetup("With large blob", () => {
-  promise_test(async t => {
-    var customMakeCredOptions = deepCopy(MAKE_CREDENTIAL_OPTIONS);
-    customMakeCredOptions.authenticatorSelection.requireResidentKey = true;
-    customMakeCredOptions.extensions = {
-      largeBlob: {
-        support: "preferred",
-      },
-    };
-    var credential =
-      await navigator.credentials.create({ publicKey : customMakeCredOptions });
-    assert_true(credential.getClientExtensionResults().largeBlob.supported);
-    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "blob");
-    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "written");
-  }, "navigator.credentials.create() with largeBlob.support set to preferred and supported by authenticator");
-
-  promise_test(async t => {
-    var customMakeCredOptions = deepCopy(MAKE_CREDENTIAL_OPTIONS);
-    customMakeCredOptions.authenticatorSelection.requireResidentKey = true;
-    customMakeCredOptions.extensions = {
-      largeBlob: {},
-    };
-    // Should be equivalent to preferred.
-    var credential =
-      await navigator.credentials.create({ publicKey : customMakeCredOptions });
-    assert_true(credential.getClientExtensionResults().largeBlob.supported);
-    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "blob");
-    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "written");
-  }, "navigator.credentials.create() with largeBlob.support not set and supported by authenticator");
-
-  promise_test(async t => {
-    var customMakeCredOptions = deepCopy(MAKE_CREDENTIAL_OPTIONS);
-    customMakeCredOptions.authenticatorSelection.requireResidentKey = true;
-    customMakeCredOptions.extensions = {
-      largeBlob: {
-        support: "required",
-      },
-    };
-    var credential =
-      await navigator.credentials.create({ publicKey : customMakeCredOptions });
-    assert_true(credential.getClientExtensionResults().largeBlob.supported);
-    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "blob");
-    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "written");
-  }, "navigator.credentials.create() with largeBlob.support set to required and supported by authenticator");
-}, {
-  protocol: blink.test.mojom.ClientToAuthenticatorProtocol.CTAP2,
-  ctap2Version: blink.test.mojom.Ctap2Version.CTAP2_1,
-  hasLargeBlob: true,
-});
-
 </script>
diff --git a/third_party/blink/web_tests/http/tests/credentialmanager/credentialscontainer-get-with-virtual-authenticator.html b/third_party/blink/web_tests/http/tests/credentialmanager/credentialscontainer-get-with-virtual-authenticator.html
index 328202d..13731b4b 100644
--- a/third_party/blink/web_tests/http/tests/credentialmanager/credentialscontainer-get-with-virtual-authenticator.html
+++ b/third_party/blink/web_tests/http/tests/credentialmanager/credentialscontainer-get-with-virtual-authenticator.html
@@ -113,115 +113,5 @@
     customGetAssertionOptions.allowCredentials = [bleCredential];
     return navigator.credentials.get({ publicKey : customGetAssertionOptions });
   }, "navigator.credentials.get() with authenticators supporting different transports");
-
-  promise_test(async t => {
-    var customGetCredentialOptions = deepCopy(GET_CREDENTIAL_OPTIONS);
-    customGetCredentialOptions.extensions = {
-      largeBlob: {
-        support: "preferred",
-      },
-    };
-    return promise_rejects_dom(t, "NotSupportedError",
-      navigator.credentials.get({publicKey: customGetCredentialOptions}));
-  }, "navigator.credentials.get() with largeBlob.support set");
-
-  promise_test(async t => {
-    var customGetCredentialOptions = deepCopy(GET_CREDENTIAL_OPTIONS);
-    customGetCredentialOptions.extensions = {
-      largeBlob: {
-        read: true,
-        write: new ArrayBuffer(),
-      },
-    };
-    return promise_rejects_dom(t, "NotSupportedError",
-      navigator.credentials.get({publicKey: customGetCredentialOptions}));
-  }, "navigator.credentials.get() with largeBlob.read and largeBlob.write set");
-
-  promise_test(async t => {
-    var customGetCredentialOptions = deepCopy(GET_CREDENTIAL_OPTIONS);
-    customGetCredentialOptions.extensions = {
-      largeBlob: {
-        read: true,
-      },
-    };
-    var credential = await navigator.credentials.get({publicKey: customGetCredentialOptions});
-    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "supported");
-    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "blob");
-    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "written");
-  }, "navigator.credentials.get() with largeBlob.read set without authenticator support");
-
-  promise_test(async t => {
-    var customGetCredentialOptions = deepCopy(GET_CREDENTIAL_OPTIONS);
-    customGetCredentialOptions.extensions = {
-      largeBlob: {
-        write: new TextEncoder().encode("Don't call me Shirley"),
-      },
-    };
-    var credential = await navigator.credentials.get({publicKey: customGetCredentialOptions});
-    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "supported");
-    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "blob");
-    assert_false(credential.getClientExtensionResults().largeBlob.written);
-  }, "navigator.credentials.get() with largeBlob.write set without authenticator support");
 }, {});
-
-authenticatorSetup("CTAP2_1", () => {
-  promise_test(async _ => {
-    let authenticators = await navigator.credentials.test.authenticators();
-    assert_equals(authenticators.length, 1);
-    let testAuthenticator = authenticators[0];
-    assert_true(await testAuthenticator.generateAndRegisterKey(ACCEPTABLE_CREDENTIAL_ID, "subdomain.example.test"));
-  }, "Set up the testing environment for ctap 2.1.");
-
-  promise_test(async t => {
-    var customGetCredentialOptions = deepCopy(GET_CREDENTIAL_OPTIONS);
-    customGetCredentialOptions.extensions = {
-      largeBlob: {
-        read: true,
-      },
-    };
-    var credential = await navigator.credentials.get({publicKey: customGetCredentialOptions});
-    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "supported");
-    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "blob");
-    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "written");
-  }, "navigator.credentials.get() with largeBlob.read set with no blob on authenticator");
-
-  promise_test(async t => {
-    const blob = new TextEncoder().encode("According to all known laws of aviation, "
-                                        + "there is no way a bee should be able to fly");
-    let authenticator = (await navigator.credentials.test.authenticators())[0];
-    assert_true(await authenticator.setLargeBlob(ACCEPTABLE_CREDENTIAL_ID, blob));
-
-    var customGetCredentialOptions = deepCopy(GET_CREDENTIAL_OPTIONS);
-    customGetCredentialOptions.extensions = {
-      largeBlob: {
-        read: true,
-      },
-    };
-    var credential = await navigator.credentials.get({publicKey: customGetCredentialOptions});
-    assert_array_equals(new Uint8Array(credential.getClientExtensionResults().largeBlob.blob), blob);
-    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "supported");
-    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "written");
-  }, "navigator.credentials.get() with largeBlob.read set with blob on authenticator");
-
-  promise_test(async t => {
-    const blob = new TextEncoder().encode("When the sunlight strikes raindrops in the air, "
-                                        + "they act as a prism and form a rainbow");
-    let authenticator = (await navigator.credentials.test.authenticators())[0];
-    var customGetCredentialOptions = deepCopy(GET_CREDENTIAL_OPTIONS);
-    customGetCredentialOptions.extensions = {
-      largeBlob: {
-        write: blob,
-      },
-    };
-    var credential = await navigator.credentials.get({publicKey: customGetCredentialOptions});
-    assert_array_equals(blob, await authenticator.getLargeBlob(ACCEPTABLE_CREDENTIAL_ID));
-    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "supported");
-    assert_not_own_property(credential.getClientExtensionResults().largeBlob, "blob");
-    assert_true(credential.getClientExtensionResults().largeBlob.written);
-  }, "navigator.credentials.get() with largeBlob.write set with blob on authenticator");
-}, {
-  protocol: blink.test.mojom.ClientToAuthenticatorProtocol.CTAP2,
-  ctap2Version: blink.test.mojom.Ctap2Version.CTAP2_1,
-  hasLargeBlob: true,
-});
 </script>
diff --git a/third_party/blink/web_tests/resources/testdriver-vendor.js b/third_party/blink/web_tests/resources/testdriver-vendor.js
index 2c38446..6a4d336 100644
--- a/third_party/blink/web_tests/resources/testdriver-vendor.js
+++ b/third_party/blink/web_tests/resources/testdriver-vendor.js
@@ -300,6 +300,7 @@
       hasUserVerification: false,
       isUserConsenting: true,
       isUserVerified: false,
+      extensions: [],
     }, options);
     let mojoOptions = {};
     switch (options.protocol) {
@@ -334,9 +335,14 @@
     }
     mojoOptions.hasResidentKey = options.hasResidentKey;
     mojoOptions.hasUserVerification = options.hasUserVerification;
+    mojoOptions.hasLargeBlob = options.extensions.indexOf("largeBlob") !== -1;
     mojoOptions.isUserPresent = options.isUserConsenting;
+    // Force CTAP 2.1 to support large blob. This does not have any impact on
+    // WPTs until credProps is tested on WPTs, at which point we'll expose this.
+    mojoOptions.ctap2Version = blink.test.mojom.Ctap2Version.CTAP2_1;
 
     let authenticator = (await manager.createAuthenticator(mojoOptions)).authenticator;
+    await authenticator.setUserVerified(options.isUserVerified);
     return (await authenticator.getUniqueId()).id;
   };
 
diff --git a/third_party/closure_compiler/externs/accessibility_private.js b/third_party/closure_compiler/externs/accessibility_private.js
index bba6f222..be013ed 100644
--- a/third_party/closure_compiler/externs/accessibility_private.js
+++ b/third_party/closure_compiler/externs/accessibility_private.js
@@ -274,7 +274,7 @@
 chrome.accessibilityPrivate.updateSwitchAccessBubble = function(bubble, show, anchor, actions) {};
 
 /**
- * Enable point scanning in Switch Access.
+ * Enables or disables point scanning in Switch Access.
  * @param {boolean} enabled True for start point scanning, false for end point
  *     scanning.
  */
@@ -418,6 +418,12 @@
 chrome.accessibilityPrivate.onScrollableBoundsForPointRequested;
 
 /**
+ * Fired when Chrome OS magnifier bounds are updated.
+ * @type {!ChromeEvent}
+ */
+chrome.accessibilityPrivate.onMagnifierBoundsChanged;
+
+/**
  * Fired when a custom spoken feedback on the active window gets enabled or
  * disabled. Called from ARC++ accessibility.
  * @type {!ChromeEvent}
diff --git a/third_party/flatbuffers/BUILD.gn b/third_party/flatbuffers/BUILD.gn
index b7fd1772..fcd6abf 100644
--- a/third_party/flatbuffers/BUILD.gn
+++ b/third_party/flatbuffers/BUILD.gn
@@ -11,10 +11,14 @@
 
 # The part of FlatBuffers that Chrome is interested in.
 source_set("flatbuffers") {
-  sources = [
+  public = [
     "src/include/flatbuffers/base.h",
     "src/include/flatbuffers/flatbuffers.h",
     "src/include/flatbuffers/stl_emulation.h",
+
+    # Required for tflite.
+    "src/include/flatbuffers/flexbuffers.h",
+    "src/include/flatbuffers/util.h",
   ]
 
   if (is_win) {
@@ -41,6 +45,8 @@
     "src/grpc/src/compiler/java_generator.h",
     "src/grpc/src/compiler/python_generator.cc",
     "src/grpc/src/compiler/schema_interface.h",
+    "src/grpc/src/compiler/swift_generator.cc",
+    "src/grpc/src/compiler/swift_generator.h",
     "src/include/flatbuffers/code_generators.h",
     "src/include/flatbuffers/flatbuffers.h",
     "src/include/flatbuffers/flatc.h",
@@ -70,6 +76,7 @@
     "src/src/idl_gen_php.cpp",
     "src/src/idl_gen_python.cpp",
     "src/src/idl_gen_rust.cpp",
+    "src/src/idl_gen_swift.cpp",
     "src/src/idl_gen_text.cpp",
     "src/src/idl_parser.cpp",
     "src/src/reflection.cpp",
@@ -103,6 +110,38 @@
   flatc_include_dirs = [ "src/tests/include_test" ]
 }
 
+# This combines sources, "flatbuffers" and "flatbuffers_test", specified in the
+# public github repo required to build the unittests. This must be separate
+# from ":compiler_files".
+source_set("flatbuffers_test_files") {
+  include_dirs = [ "src/grpc" ]
+  sources = [
+    "src/include/flatbuffers/base.h",
+    "src/include/flatbuffers/code_generators.h",
+    "src/include/flatbuffers/flatbuffers.h",
+    "src/include/flatbuffers/flexbuffers.h",
+    "src/include/flatbuffers/grpc.h",
+    "src/include/flatbuffers/hash.h",
+    "src/include/flatbuffers/idl.h",
+    "src/include/flatbuffers/minireflect.h",
+    "src/include/flatbuffers/reflection.h",
+    "src/include/flatbuffers/reflection_generated.h",
+    "src/include/flatbuffers/registry.h",
+    "src/include/flatbuffers/stl_emulation.h",
+    "src/include/flatbuffers/util.h",
+    "src/src/code_generators.cpp",
+    "src/src/idl_gen_fbs.cpp",
+    "src/src/idl_gen_text.cpp",
+    "src/src/idl_parser.cpp",
+    "src/src/reflection.cpp",
+    "src/src/util.cpp",
+  ]
+
+  configs -= [ "//build/config/compiler:chromium_code" ]
+  configs += [ "//build/config/compiler:no_chromium_code" ]
+  deps = [ ":flatbuffers" ]
+}
+
 test("flatbuffers_unittests") {
   sources = [
     "src/tests/native_type_test_impl.cpp",
@@ -111,9 +150,9 @@
     "src/tests/test_builder.cpp",
   ]
   deps = [
-    ":compiler_files",
     ":flatbuffers",
     ":flatbuffers_samplebuffer",
+    ":flatbuffers_test_files",
   ]
   data = [
     "src/tests/",
diff --git a/third_party/flatbuffers/README.chromium b/third_party/flatbuffers/README.chromium
index 1605967..2699351 100644
--- a/third_party/flatbuffers/README.chromium
+++ b/third_party/flatbuffers/README.chromium
@@ -1,8 +1,8 @@
 Name: FlatBuffers
 Short Name: flatbuffers
 URL: https://github.com/google/flatbuffers
-Version: 136d75fa6580ef87d1b7cbc243e617f21149852e
-Date: 2019-02-25
+Version: 6df40a2471737b27271bdd9b900ab5f3aec746c7
+Date: 2020-10-21
 License: Apache 2.0
 License File: LICENSE
 Security Critical: yes
diff --git a/third_party/metrics_proto/README.chromium b/third_party/metrics_proto/README.chromium
index 92e9def3..58cd020c 100644
--- a/third_party/metrics_proto/README.chromium
+++ b/third_party/metrics_proto/README.chromium
@@ -1,8 +1,8 @@
 Name: Metrics Protos
 Short Name: metrics_proto
 URL: This is the canonical public repository
-Version: 336717828
-Date: 2020/10/12 UTC
+Version: 338697903
+Date: 2020/10/23 UTC
 License: BSD
 Security Critical: Yes
 
diff --git a/third_party/metrics_proto/cast_assistant_logs.proto b/third_party/metrics_proto/cast_assistant_logs.proto
index d1cfc7e7..7c4e4e3 100644
--- a/third_party/metrics_proto/cast_assistant_logs.proto
+++ b/third_party/metrics_proto/cast_assistant_logs.proto
@@ -13,7 +13,7 @@
 
 // Deprecated. Use CastLogsProto instead.
 // CastAssistant specific device information.
-// Next id: 13
+// Next id: 14
 message CastAssistantLogsProto {
   // A unique id generated by the assistant client (approx. 11 bytes).
   // This is used to join the assistant client logs with speech logs.
@@ -86,4 +86,7 @@
   // should send this list for logging. Please see b/77491741 and
   // go/concurrent_event_ids for details.
   repeated string event_id_list = 12;
+
+  // Optional field to log the system bundle version.
+  optional string system_bundle_version = 13;
 }
diff --git a/third_party/metrics_proto/omnibox_event.proto b/third_party/metrics_proto/omnibox_event.proto
index 4790341..b757911f 100644
--- a/third_party/metrics_proto/omnibox_event.proto
+++ b/third_party/metrics_proto/omnibox_event.proto
@@ -14,7 +14,7 @@
 import "omnibox_input_type.proto";
 
 // Stores information about an omnibox interaction.
-// Next tag: 22
+// Next tag: 23
 message OmniboxEventProto {
   // The timestamp for the event, in seconds.
   // This value comes from Chromium's TimeTicks::Now(), which is an abstract
@@ -394,4 +394,11 @@
   // Whether the omnibox input is a search query that is started
   // by clicking on a image tile.
   optional bool is_query_started_from_tile = 21;
+
+  // The set of features that triggered within the current omnibox session.
+  // Each element is a value of |OmniboxTriggeredFeatureService::Features| enum
+  // in components/omnibox/browser/omnibox_triggered_feature_service.h .
+  // See AutocompleteController::ResetSession() for more details on the
+  // definition of a session.
+  repeated int32 feature_triggered_in_session = 22;
 }
diff --git a/tools/auto-nav.py b/tools/auto-nav.py
new file mode 100644
index 0000000..46674a7
--- /dev/null
+++ b/tools/auto-nav.py
@@ -0,0 +1,145 @@
+# 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.
+"""
+This script runs Chrome and automatically navigates through the given list of
+URLs the specified number of times.
+
+Usage: vpython auto-nav.py <chrome dir> <number of navigations> <url> <url> ...
+
+Optional flags:
+* --interval <seconds>, -i <seconds>: specify a number of seconds to wait
+                                      between navigations, e.g., -i=5
+* --wait, -w: start Chrome, then wait for the user to press any key before
+              starting auto-navigation
+* --idlewakeups_dir: Windows only; specify the directory containing
+                     idlewakeups.exe to print measurements taken by IdleWakeups,
+                     e.g., --idlewakeups_dir=tools/win/IdleWakeups/x64/Debug
+"""
+
+# [VPYTHON:BEGIN]
+# python_version: "2.7"
+# wheel: <
+#   name: "infra/python/wheels/selenium-py2_py3"
+#   version: "version:3.14.0"
+# >
+# wheel: <
+#   name: "infra/python/wheels/urllib3-py2_py3"
+#   version: "version:1.24.3"
+# >
+# wheel: <
+#   name: "infra/python/wheels/psutil/${vpython_platform}"
+#   version: "version:5.6.2"
+# >
+# [VPYTHON:END]
+
+import argparse
+import os
+import subprocess
+import sys
+import time
+
+try:
+  import psutil
+  from selenium import webdriver
+except ImportError:
+  print('Error importing required modules. Run with vpython instead of python.')
+  sys.exit(1)
+
+DEFAULT_INTERVAL = 1
+
+
+# Returns an object containing the arguments parsed from this script's command
+# line.
+def ParseArgs():
+  parser = argparse.ArgumentParser()
+  parser.add_argument(
+      'chrome_dir', help='Directory containing chrome.exe and chromedriver.exe')
+  parser.add_argument('num_navigations',
+                      type=int,
+                      help='Number of times to navigate through list of URLs')
+  parser.add_argument('--interval',
+                      '-i',
+                      type=int,
+                      help='Seconds to wait between navigations; default is 1')
+  parser.add_argument('--wait',
+                      '-w',
+                      action='store_true',
+                      help='Wait for confirmation before beginning navigation')
+  parser.add_argument(
+      '--idlewakeups_dir',
+      type=str,
+      help='Windows only; directory containing idlewakeups.exe, if using')
+  parser.add_argument(
+      'url',
+      nargs='+',
+      help='URL(s) to navigate, separated by spaces; must include scheme, '
+      'e.g., "https://"')
+  return parser.parse_args()
+
+
+# If |path| does not exist, prints a generic error plus optional |error_message|
+# and exits.
+def ExitIfNotFound(path, error_message=None):
+  if not os.path.exists(path):
+    print('File not found: {}.'.format(path))
+    if error_message:
+      print(error_message)
+    exit()
+
+
+def main():
+  # Parse arguments and check that file paths received are valid.
+  args = ParseArgs()
+  ExitIfNotFound(os.path.join(args.chrome_dir, 'chrome.exe'),
+                 'Build target "chrome" to generate it first.')
+  chromedriver_exe = os.path.join(args.chrome_dir, 'chromedriver.exe')
+  ExitIfNotFound(chromedriver_exe,
+                 'Build target "chromedriver" to generate it first.')
+  if args.idlewakeups_dir:
+    idlewakeups_exe = os.path.join(args.idlewakeups_dir, 'idlewakeups.exe')
+    ExitIfNotFound(idlewakeups_exe)
+
+  # Start chrome.exe. Disable chrome.exe's extensive logging to make reading
+  # this script's output easier.
+  chrome_options = webdriver.ChromeOptions()
+  chrome_options.add_experimental_option('excludeSwitches', ['enable-logging'])
+  driver = webdriver.Chrome(os.path.abspath(chromedriver_exe),
+                            options=chrome_options)
+
+  if args.wait:
+    driver.get(args.url[0])
+    raw_input('Press Enter to begin navigation...')
+
+  # Start IdleWakeups, if using, passing the browser process's ID as its target.
+  # IdleWakeups will monitor the browser process and its children. Other running
+  # chrome.exe processes (i.e., those not launched by this script) are excluded.
+  if args.idlewakeups_dir:
+    launched_processes = psutil.Process(
+        driver.service.process.pid).children(recursive=False)
+    if not launched_processes:
+      print('Error getting browser process ID for IdleWakeups.')
+      exit()
+    # Assume the first child process created by |driver| is the browser process.
+    idlewakeups = subprocess.Popen([
+        idlewakeups_exe,
+        str(launched_processes[0].pid), '--stop-on-exit', '--tabbed'
+    ],
+                                   stdout=subprocess.PIPE)
+
+  # Navigate through |args.url| list |args.num_navigations| times, then close
+  # chrome.exe.
+  interval = args.interval if args.interval else DEFAULT_INTERVAL
+  for _ in range(args.num_navigations):
+    for url in args.url:
+      driver.get(url)
+      time.sleep(interval)
+  driver.quit()
+
+  # Print IdleWakeups' output, if using.
+  if args.idlewakeups_dir:
+    print(idlewakeups.communicate()[0])
+
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/tools/clang/rewrite_raw_ptr_fields/RewriteRawPtrFields.cpp b/tools/clang/rewrite_raw_ptr_fields/RewriteRawPtrFields.cpp
index e9dad109..1ae82ca 100644
--- a/tools/clang/rewrite_raw_ptr_fields/RewriteRawPtrFields.cpp
+++ b/tools/clang/rewrite_raw_ptr_fields/RewriteRawPtrFields.cpp
@@ -971,6 +971,23 @@
                cxxConstructExpr(templated_function_arg_matcher)),
       &affected_expr_rewriter);
 
+  // Calls to constructors via an implicit cast =========
+  // Given
+  //   struct I { I(int*) {} };
+  //   void bar(I i) {}
+  //   struct S { int* y; };
+  //   void foo(const S& s) {
+  //     bar(s.y);  // implicit cast from |s.y| to I.
+  //   }
+  // binds the |s.y| expr if it matches the |affected_expr_matcher| above.
+  //
+  // See also testcases in tests/affected-expr-original.cc
+  auto implicit_ctor_expr_matcher = implicitCastExpr(has(cxxConstructExpr(allOf(
+      hasDeclaration(
+          cxxConstructorDecl(allOf(parameterCountIs(1), unless(isExplicit())))),
+      forEachArgumentWithParam(affected_expr_matcher, parmVarDecl())))));
+  match_finder.addMatcher(implicit_ctor_expr_matcher, &affected_expr_rewriter);
+
   // |auto| type declarations =========
   // Given
   //   struct S { int* y; };
diff --git a/tools/clang/rewrite_raw_ptr_fields/tests/affected-expr-expected.cc b/tools/clang/rewrite_raw_ptr_fields/tests/affected-expr-expected.cc
index c5351b20..46f25b5 100644
--- a/tools/clang/rewrite_raw_ptr_fields/tests/affected-expr-expected.cc
+++ b/tools/clang/rewrite_raw_ptr_fields/tests/affected-expr-expected.cc
@@ -17,6 +17,7 @@
   CheckedPtr<SomeClass> ptr2;
   CheckedPtr<const SomeClass> const_ptr;
   int (*func_ptr_field)();
+  CheckedPtr<const char> const_char_ptr;
 };
 
 namespace auto_tests {
@@ -245,6 +246,46 @@
 
 }  // namespace templated_functions
 
+namespace implicit_constructors {
+
+// Based on //base/strings/string_piece_forward.h:
+template <typename STRING_TYPE>
+class BasicStringPiece;
+typedef BasicStringPiece<std::string> StringPiece;
+// Based on //base/strings/string_piece.h:
+template <typename STRING_TYPE>
+class BasicStringPiece {
+ public:
+  constexpr BasicStringPiece(const char* str) {}
+};
+// Test case:
+void FunctionTakingBasicStringPiece(StringPiece arg) {}
+
+class ClassWithImplicitConstructor {
+ public:
+  ClassWithImplicitConstructor(SomeClass* blah) {}
+};
+void FunctionTakingArgWithImplicitConstructor(
+    ClassWithImplicitConstructor arg) {}
+
+void foo() {
+  MyStruct my_struct;
+
+  // Expected rewrite - appending: .get().  This avoids the following error:
+  // error: no matching function for call to 'FunctionTakingBasicStringPiece'
+  // note: candidate function not viable: no known conversion from
+  // 'base::CheckedPtr<const char>' to 'templated_functions::StringPiece' (aka
+  // 'BasicStringPiece<basic_string<char, char_traits<char>, allocator<char>>>')
+  // for 1st argument
+  FunctionTakingBasicStringPiece(my_struct.const_char_ptr.get());
+
+  // Expected rewrite - appending: .get().  This is the same scenario as with
+  // StringPiece above (except that no templates are present here).
+  FunctionTakingArgWithImplicitConstructor(my_struct.ptr.get());
+}
+
+}  // namespace implicit_constructors
+
 namespace affected_implicit_template_specialization {
 
 template <typename T, typename T2>
diff --git a/tools/clang/rewrite_raw_ptr_fields/tests/affected-expr-original.cc b/tools/clang/rewrite_raw_ptr_fields/tests/affected-expr-original.cc
index a042ae90..7a25fdf 100644
--- a/tools/clang/rewrite_raw_ptr_fields/tests/affected-expr-original.cc
+++ b/tools/clang/rewrite_raw_ptr_fields/tests/affected-expr-original.cc
@@ -15,6 +15,7 @@
   SomeClass* ptr2;
   const SomeClass* const_ptr;
   int (*func_ptr_field)();
+  const char* const_char_ptr;
 };
 
 namespace auto_tests {
@@ -243,6 +244,46 @@
 
 }  // namespace templated_functions
 
+namespace implicit_constructors {
+
+// Based on //base/strings/string_piece_forward.h:
+template <typename STRING_TYPE>
+class BasicStringPiece;
+typedef BasicStringPiece<std::string> StringPiece;
+// Based on //base/strings/string_piece.h:
+template <typename STRING_TYPE>
+class BasicStringPiece {
+ public:
+  constexpr BasicStringPiece(const char* str) {}
+};
+// Test case:
+void FunctionTakingBasicStringPiece(StringPiece arg) {}
+
+class ClassWithImplicitConstructor {
+ public:
+  ClassWithImplicitConstructor(SomeClass* blah) {}
+};
+void FunctionTakingArgWithImplicitConstructor(
+    ClassWithImplicitConstructor arg) {}
+
+void foo() {
+  MyStruct my_struct;
+
+  // Expected rewrite - appending: .get().  This avoids the following error:
+  // error: no matching function for call to 'FunctionTakingBasicStringPiece'
+  // note: candidate function not viable: no known conversion from
+  // 'base::CheckedPtr<const char>' to 'templated_functions::StringPiece' (aka
+  // 'BasicStringPiece<basic_string<char, char_traits<char>, allocator<char>>>')
+  // for 1st argument
+  FunctionTakingBasicStringPiece(my_struct.const_char_ptr);
+
+  // Expected rewrite - appending: .get().  This is the same scenario as with
+  // StringPiece above (except that no templates are present here).
+  FunctionTakingArgWithImplicitConstructor(my_struct.ptr);
+}
+
+}  // namespace implicit_constructors
+
 namespace affected_implicit_template_specialization {
 
 template <typename T, typename T2>
diff --git a/tools/gritsettings/resource_ids.spec b/tools/gritsettings/resource_ids.spec
index 9a1bce9..ed3ae2e 100644
--- a/tools/gritsettings/resource_ids.spec
+++ b/tools/gritsettings/resource_ids.spec
@@ -179,7 +179,7 @@
     "META": {"sizes": {"includes": [50],}},
     "includes": [1850],
   },
-  "chrome/browser/resources/tab_search/tab_search_resources.grd": {
+  "chrome/browser/resources/tab_search_merge/tab_search_resources.grd": {
     "includes": [1880],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/tab_strip/tab_strip_resources.grd": {
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 36754543..94e40ab6 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -9028,6 +9028,14 @@
   <int value="11" label="Aborted because there was no surface"/>
 </enum>
 
+<enum name="CanvasRenderingAPI">
+  <int value="0" label="2d"/>
+  <int value="1" label="webgl"/>
+  <int value="2" label="webgl2"/>
+  <int value="3" label="bitmaprenderer"/>
+  <int value="4" label="webgpu"/>
+</enum>
+
 <enum name="CanvasResourceProviderType">
   <int value="0" label="Texture"/>
   <int value="1" label="Bitmap"/>
@@ -23524,6 +23532,7 @@
   <int value="461" label="WINDOWS_ON_BOUNDS_CHANGED"/>
   <int value="462" label="WALLPAPER_PRIVATE_ON_CLOSE_PREVIEW_WALLPAPER"/>
   <int value="463" label="PASSWORDS_PRIVATE_ON_WEAK_CREDENTIALS_CHANGED"/>
+  <int value="464" label="ACCESSIBILITY_PRIVATE_ON_MAGNIFIER_BOUNDS_CHANGED"/>
 </enum>
 
 <enum name="ExtensionFileWriteResult">
@@ -72574,6 +72583,19 @@
   <int value="30" label="kModuleBlacklistCacheMD5Digest"/>
 </enum>
 
+<enum name="TranslateAssistContentResult">
+  <int value="0" label="The feature was disabled"/>
+  <int value="1" label="The tab was null"/>
+  <int value="2" label="The tab was an incognito tab"/>
+  <int value="3" label="The activity was in overview mode"/>
+  <int value="4" label="canTranslateTab() was false"/>
+  <int value="5" label="The original page language was missing"/>
+  <int value="6" label="The current page language was missing"/>
+  <int value="7" label="There was a JSONException"/>
+  <int value="8" label="The page was translatable"/>
+  <int value="9" label="The page has already been translated"/>
+</enum>
+
 <enum name="TranslateBubbleUiEvent">
   <int value="1" label="Switch to Options page"/>
   <int value="2" label="Leave Options page"/>
@@ -72717,6 +72739,13 @@
   <int value="6" label="CLD can complement a sub code"/>
 </enum>
 
+<enum name="TranslateRankerDecision">
+  <int value="0" label="Uninitialized"/>
+  <int value="1" label="Not queried"/>
+  <int value="2" label="Show UI"/>
+  <int value="3" label="Don't show UI"/>
+</enum>
+
 <enum name="TranslateScheme">
   <int value="0" label="http"/>
   <int value="1" label="https"/>
diff --git a/tools/metrics/histograms/histograms_xml/android/histograms.xml b/tools/metrics/histograms/histograms_xml/android/histograms.xml
index 820b2bb..acbc6e47 100644
--- a/tools/metrics/histograms/histograms_xml/android/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/android/histograms.xml
@@ -744,6 +744,9 @@
 
 <histogram name="Android.DownloadManager.ServiceStopped.DownloadNotification"
     enum="DownloadNotificationServiceStopped" expires_after="M85">
+  <obsolete>
+    Deprecated as of 10/2020
+  </obsolete>
   <owner>xingliu@chromium.org</owner>
   <owner>clank-downloads@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml b/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
index d4e1924..01d6f73 100644
--- a/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
+++ b/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
@@ -11917,7 +11917,6 @@
   <suffix name="Network" label=""/>
   <suffix name="Network-selection" label=""/>
   <suffix name="Packaged-license" label=""/>
-  <suffix name="Parental-handoff" label=""/>
   <suffix name="Recommend-apps" label=""/>
   <suffix name="Reset" label=""/>
   <suffix name="Supervision-transition" label=""/>
@@ -11972,7 +11971,6 @@
   <suffix name="Oauth-enrollment.Completed" label=""/>
   <suffix name="Packaged-license.DontEnroll" label=""/>
   <suffix name="Packaged-license.Enroll" label=""/>
-  <suffix name="Parental-Handoff.Done" label=""/>
   <suffix name="Recommend-apps.Selected" label=""/>
   <suffix name="Recommend-apps.Skipped" label=""/>
   <suffix name="Reset.Cancel" label=""/>
diff --git a/tools/metrics/histograms/histograms_xml/others/histograms.xml b/tools/metrics/histograms/histograms_xml/others/histograms.xml
index 282ebfd6..f23c0753 100644
--- a/tools/metrics/histograms/histograms_xml/others/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/others/histograms.xml
@@ -2805,6 +2805,9 @@
 
 <histogram name="ContextMenu.ThumbnailFetched" enum="BooleanSuccess"
     expires_after="2020-06-30">
+  <obsolete>
+    Removed in M88.
+  </obsolete>
   <owner>twellington@chromium.org</owner>
   <owner>clank-app-team@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/translate/histograms.xml b/tools/metrics/histograms/histograms_xml/translate/histograms.xml
index 4efdba1..e9e663c 100644
--- a/tools/metrics/histograms/histograms_xml/translate/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/translate/histograms.xml
@@ -458,6 +458,31 @@
   </summary>
 </histogram>
 
+<histogram name="Translate.PageLoad.Ranker.Decision"
+    enum="TranslateRankerDecision" expires_after="2021-04-04">
+  <owner>curranmax@google.com</owner>
+  <owner>megjablon@google.com</owner>
+  <owner>chrome-language@google.com</owner>
+  <summary>
+    Logs the decision (show UI, don't show UI, or not queried) of the Translate
+    Ranker for a page load. This value is logged once the page load is completed
+    or the first time that Chrome is backgrounded during the course of the page
+    load, whichever comes first.
+  </summary>
+</histogram>
+
+<histogram name="Translate.PageLoad.Ranker.Version" units="version"
+    expires_after="2021-04-04">
+  <owner>curranmax@google.com</owner>
+  <owner>megjablon@google.com</owner>
+  <owner>chrome-language@google.com</owner>
+  <summary>
+    Logs the version of the Translate Ranker used for a page load. This value is
+    logged once the page load is completed or the first time that Chrome is
+    backgrounded during the course of the page load, whichever comes first.
+  </summary>
+</histogram>
+
 <histogram name="Translate.PageScheme" enum="TranslateScheme"
     expires_after="M77">
   <owner>kenjibaheux@google.com</owner>
@@ -676,6 +701,17 @@
   </summary>
 </histogram>
 
+<histogram name="Translate.TranslateAssistContentResult"
+    enum="TranslateAssistContentResult" expires_after="M90">
+  <owner>jds@chromium.org</owner>
+  <owner>chrome-language@google.com</owner>
+  <summary>
+    Android: Records the result of assembling translate data to provide to
+    Assistant via AssistContent. See TranslateAssistContentResult in enums.xml
+    for possible values.
+  </summary>
+</histogram>
+
 <histogram name="Translate.TranslateFrameCount" units="frames"
     expires_after="M88">
   <owner>sclittle@google.com</owner>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index ed2f1468..7926883 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -3129,6 +3129,38 @@
   </metric>
 </event>
 
+<event name="ClientRenderingAPI">
+  <owner>aaronhk@chromium.org</owner>
+  <owner>cwallez@chromium.org</owner>
+  <owner>enga@chromium.org</owner>
+  <owner>fserb@chromium.org</owner>
+  <owner>jdarpinian@chromium.org</owner>
+  <owner>juanmihd@chromium.org</owner>
+  <owner>kainino@chromium.org</owner>
+  <owner>kbr@chromium.org</owner>
+  <owner>shrekshao@google.com</owner>
+  <summary>
+    Metrics collecting types of rendering context created with
+    canvas/offscreencanvas. It also collects requests of GPU device via WebGPU
+    API.
+  </summary>
+  <metric name="Canvas.RenderingContext" enum="CanvasRenderingAPI">
+    <summary>
+      Type of rendering context created for HTMLCanvasElement.
+    </summary>
+  </metric>
+  <metric name="GPUDevice">
+    <summary>
+      Event fired when a GPUDevice from the WebGPU API is created.
+    </summary>
+  </metric>
+  <metric name="OffscreenCanvas.RenderingContext" enum="CanvasRenderingAPI">
+    <summary>
+      Type of rendering context created for OffscreenCanvas.
+    </summary>
+  </metric>
+</event>
+
 <event name="Compositor.Rendering">
   <owner>khushalsagar@chromium.org</owner>
   <summary>
diff --git a/ui/strings/ui_strings.grd b/ui/strings/ui_strings.grd
index b41cb06..3b166701 100644
--- a/ui/strings/ui_strings.grd
+++ b/ui/strings/ui_strings.grd
@@ -381,11 +381,13 @@
       <message name="IDS_CLIPBOARD_MENU_DELETE_ALL" desc="Used to indicate to a user that all clipboard items will be deleted if selected">
         Delete All
       </message>
-      <message name="IDS_MENU_ITEM_NEW_BADGE" desc="Appears as a badge on menu items denoting new features">
+
+      <!-- New Badge (user education) -->
+      <message name="IDS_NEW_BADGE" desc="Appears as a badge on UI elements like menus, denoting new features">
         New
       </message>
-      <message name="IDS_MENU_ITEM_NEW_BADGE_SCREEN_READER_MESSAGE"
-               desc="Message appended to screen reader description of menu items which have the 'New' badge">
+      <message name="IDS_NEW_BADGE_SCREEN_READER_MESSAGE"
+               desc="Message appended to screen reader description of UI elements which have the 'New' badge">
         This is a new feature
       </message>
 
@@ -940,6 +942,9 @@
       <message name="IDS_APP_LIST_SUGGESTED_APPS_ACCESSIBILITY_ANNOUNCEMENT" desc="The accessibility announcement that notifies users that the UI is showing a small version of the app list">
         Launcher, partial view
       </message>
+      <message name="IDS_APP_LIST_LAUNCHER_ACCESSIBILITY_ANNOUNCEMENT" desc="The accessibility announcement that notifies users that the UI is showing the search results">
+        Launcher
+      </message>
       <message name="IDS_APP_LIST_CLEAR_SEARCHBOX" desc="Tooltip for the button that clears all text from the search box in the app list.">
         Clear searchbox text
       </message>
diff --git a/ui/strings/ui_strings_grd/IDS_APP_LIST_LAUNCHER_ACCESSIBILITY_ANNOUNCEMENT.png.sha1 b/ui/strings/ui_strings_grd/IDS_APP_LIST_LAUNCHER_ACCESSIBILITY_ANNOUNCEMENT.png.sha1
new file mode 100644
index 0000000..3c81656
--- /dev/null
+++ b/ui/strings/ui_strings_grd/IDS_APP_LIST_LAUNCHER_ACCESSIBILITY_ANNOUNCEMENT.png.sha1
@@ -0,0 +1 @@
+13c79cfac43e1052063608d5b032e434c82d53b2
\ No newline at end of file
diff --git a/ui/strings/ui_strings_grd/IDS_MENU_ITEM_NEW_BADGE.png.sha1 b/ui/strings/ui_strings_grd/IDS_NEW_BADGE.png.sha1
similarity index 100%
rename from ui/strings/ui_strings_grd/IDS_MENU_ITEM_NEW_BADGE.png.sha1
rename to ui/strings/ui_strings_grd/IDS_NEW_BADGE.png.sha1
diff --git a/ui/strings/ui_strings_grd/IDS_MENU_ITEM_NEW_BADGE_SCREEN_READER_MESSAGE.png.sha1 b/ui/strings/ui_strings_grd/IDS_NEW_BADGE_SCREEN_READER_MESSAGE.png.sha1
similarity index 100%
rename from ui/strings/ui_strings_grd/IDS_MENU_ITEM_NEW_BADGE_SCREEN_READER_MESSAGE.png.sha1
rename to ui/strings/ui_strings_grd/IDS_NEW_BADGE_SCREEN_READER_MESSAGE.png.sha1
diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn
index 0b2f985..d444ca9 100644
--- a/ui/views/BUILD.gn
+++ b/ui/views/BUILD.gn
@@ -142,6 +142,7 @@
     "controls/menu/menu_scroll_view_container.h",
     "controls/menu/menu_separator.h",
     "controls/menu/menu_types.h",
+    "controls/menu/new_badge.h",
     "controls/menu/submenu_view.h",
     "controls/message_box_view.h",
     "controls/native/native_view_host.h",
@@ -355,6 +356,7 @@
     "controls/menu/menu_runner_impl_adapter.cc",
     "controls/menu/menu_scroll_view_container.cc",
     "controls/menu/menu_separator.cc",
+    "controls/menu/new_badge.cc",
     "controls/menu/submenu_view.cc",
     "controls/message_box_view.cc",
     "controls/native/native_view_host.cc",
diff --git a/ui/views/bubble/bubble_dialog_delegate_view.cc b/ui/views/bubble/bubble_dialog_delegate_view.cc
index 89a41ffe..57d9ad2c 100644
--- a/ui/views/bubble/bubble_dialog_delegate_view.cc
+++ b/ui/views/bubble/bubble_dialog_delegate_view.cc
@@ -736,12 +736,6 @@
                     : nullptr);
 }
 
-void BubbleDialogDelegateView::EnableUpDownKeyboardAccelerators() {
-  // The arrow keys can be used to tab between items.
-  AddAccelerator(ui::Accelerator(ui::VKEY_DOWN, ui::EF_NONE));
-  AddAccelerator(ui::Accelerator(ui::VKEY_UP, ui::EF_NONE));
-}
-
 void BubbleDialogDelegate::OnBubbleWidgetVisibilityChanged(bool visible) {
   UpdateHighlightedButton(visible);
 
diff --git a/ui/views/bubble/bubble_dialog_delegate_view.h b/ui/views/bubble/bubble_dialog_delegate_view.h
index bd9cc7b0..3a7401eb 100644
--- a/ui/views/bubble/bubble_dialog_delegate_view.h
+++ b/ui/views/bubble/bubble_dialog_delegate_view.h
@@ -408,9 +408,6 @@
   // Perform view initialization on the contents for bubble sizing.
   void Init() override;
 
-  // Allows the up and down arrow keys to tab between items.
-  void EnableUpDownKeyboardAccelerators();
-
  private:
   FRIEND_TEST_ALL_PREFIXES(BubbleDelegateTest, CreateDelegate);
   FRIEND_TEST_ALL_PREFIXES(BubbleDelegateTest, NonClientHitTest);
diff --git a/ui/views/controls/button/image_button.cc b/ui/views/controls/button/image_button.cc
index 1b9f096..c560664 100644
--- a/ui/views/controls/button/image_button.cc
+++ b/ui/views/controls/button/image_button.cc
@@ -33,7 +33,7 @@
   // By default, we request that the gfx::Canvas passed to our View::OnPaint()
   // implementation is flipped horizontally so that the button's images are
   // mirrored when the UI directionality is right-to-left.
-  EnableCanvasFlippingForRTLUI(true);
+  SetFlipCanvasOnPaintForRTLUI(true);
   // Not focusable by default, only for accessibility.
   SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
 }
diff --git a/ui/views/controls/button/label_button.cc b/ui/views/controls/button/label_button.cc
index 7bdea56..af441d9 100644
--- a/ui/views/controls/button/label_button.cc
+++ b/ui/views/controls/button/label_button.cc
@@ -379,11 +379,6 @@
   Button::Layout();
 }
 
-void LabelButton::EnableCanvasFlippingForRTLUI(bool flip) {
-  Button::EnableCanvasFlippingForRTLUI(flip);
-  image_->EnableCanvasFlippingForRTLUI(flip);
-}
-
 void LabelButton::GetAccessibleNodeData(ui::AXNodeData* node_data) {
   if (GetIsDefault())
     node_data->AddState(ax::mojom::State::kDefault);
@@ -610,6 +605,10 @@
                                                          : for_state;
 }
 
+void LabelButton::FlipCanvasOnPaintForRTLUIChanged() {
+  image_->SetFlipCanvasOnPaintForRTLUI(GetFlipCanvasOnPaintForRTLUI());
+}
+
 BEGIN_METADATA(LabelButton, Button)
 ADD_PROPERTY_METADATA(base::string16, Text)
 ADD_PROPERTY_METADATA(gfx::HorizontalAlignment, HorizontalAlignment)
diff --git a/ui/views/controls/button/label_button.h b/ui/views/controls/button/label_button.h
index 4990f7e..c569241 100644
--- a/ui/views/controls/button/label_button.h
+++ b/ui/views/controls/button/label_button.h
@@ -8,6 +8,7 @@
 #include <array>
 #include <memory>
 
+#include "base/bind.h"
 #include "base/compiler_specific.h"
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
@@ -43,6 +44,8 @@
   explicit LabelButton(ButtonListener* listener,
                        const base::string16& text = base::string16(),
                        int button_context = style::CONTEXT_BUTTON);
+  LabelButton(const LabelButton&) = delete;
+  LabelButton& operator=(const LabelButton&) = delete;
   ~LabelButton() override;
 
   // Gets or sets the image shown for the specified button state.
@@ -118,7 +121,6 @@
   gfx::Size GetMinimumSize() const override;
   int GetHeightForWidth(int w) const override;
   void Layout() override;
-  void EnableCanvasFlippingForRTLUI(bool flip) override;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
   void AddLayerBeneathView(ui::Layer* new_layer) override;
   void RemoveLayerBeneathView(ui::Layer* old_layer) override;
@@ -209,6 +211,8 @@
   // STATE_NORMAL when |for_state|'s image is empty.
   ButtonState ImageStateForState(ButtonState for_state) const;
 
+  void FlipCanvasOnPaintForRTLUIChanged();
+
   // The image and label shown in the button.
   ImageView* image_;
   internal::LabelButtonLabel* label_;
@@ -265,7 +269,10 @@
   std::unique_ptr<Widget::PaintAsActiveCallbackList::Subscription>
       paint_as_active_subscription_;
 
-  DISALLOW_COPY_AND_ASSIGN(LabelButton);
+  PropertyChangedSubscription flip_canvas_on_paint_subscription_ =
+      AddFlipCanvasOnPaintForRTLUIChangedCallback(
+          base::BindRepeating(&LabelButton::FlipCanvasOnPaintForRTLUIChanged,
+                              base::Unretained(this)));
 };
 
 BEGIN_VIEW_BUILDER(VIEWS_EXPORT, LabelButton, Button)
diff --git a/ui/views/controls/focus_ring.cc b/ui/views/controls/focus_ring.cc
index 35251aa4..c02c4fe4d 100644
--- a/ui/views/controls/focus_ring.cc
+++ b/ui/views/controls/focus_ring.cc
@@ -95,7 +95,7 @@
 
   // Need to match canvas direction with the parent. This is required to ensure
   // asymmetric focus ring shapes match their respective buttons in RTL mode.
-  EnableCanvasFlippingForRTLUI(parent()->flip_canvas_on_paint_for_rtl_ui());
+  SetFlipCanvasOnPaintForRTLUI(parent()->GetFlipCanvasOnPaintForRTLUI());
 }
 
 void FocusRing::ViewHierarchyChanged(
@@ -142,8 +142,8 @@
     path = GetHighlightPathInternal(parent());
 
   DCHECK(IsPathUsable(path));
-  DCHECK_EQ(flip_canvas_on_paint_for_rtl_ui(),
-            parent()->flip_canvas_on_paint_for_rtl_ui());
+  DCHECK_EQ(GetFlipCanvasOnPaintForRTLUI(),
+            parent()->GetFlipCanvasOnPaintForRTLUI());
   SkRect bounds;
   SkRRect rbounds;
   if (path.isRect(&bounds)) {
@@ -222,7 +222,7 @@
 
 SkPath GetHighlightPath(const View* view) {
   SkPath path = GetHighlightPathInternal(view);
-  if (view->flip_canvas_on_paint_for_rtl_ui() && base::i18n::IsRTL()) {
+  if (view->GetFlipCanvasOnPaintForRTLUI() && base::i18n::IsRTL()) {
     gfx::Point center = view->GetLocalBounds().CenterPoint();
     SkMatrix flip;
     flip.setScale(-1, 1, center.x(), center.y());
diff --git a/ui/views/controls/label.cc b/ui/views/controls/label.cc
index ffa6208..6f16db5 100644
--- a/ui/views/controls/label.cc
+++ b/ui/views/controls/label.cc
@@ -652,20 +652,33 @@
   if (display_text_)
     display_text_->Draw(canvas);
 
-#if DCHECK_IS_ON()
-  // Attempt to ensure that if we're using subpixel rendering, we're painting
-  // to an opaque background. What we don't want to find is an ancestor in the
-  // hierarchy that paints to a non-opaque layer.
+#if DCHECK_IS_ON() && !defined(OS_CHROMEOS)
+  // TODO(crbug.com/1139395): Enable this DCHECK on ChromeOS by fixing either
+  // this check (to correctly idenfify more paints-on-opaque cases), refactoring
+  // parents to use background() or by fixing subpixel-rendering issues that the
+  // DCHECK detects.
   if (!display_text_ || display_text_->subpixel_rendering_suppressed())
     return;
 
+  // Ensure that, if we're using subpixel rendering, we're painted to an opaque
+  // region. Subpixel rendering will sample from the r,g,b color channels of the
+  // canvas. These values are incorrect when sampling from transparent pixels.
+  // Note that these checks may need to be amended for other methods of painting
+  // opaquely underneath the Label or we might need to allow individual cases to
+  // skip this DCHECK.
   for (View* view = this; view; view = view->parent()) {
+    // This is our approximation of being painted on an opaque region. If any
+    // parent has an opaque background we assume that that background covers the
+    // text bounds. This is not necessarily true as the background could be
+    // inset from the parent bounds, and get_color() does not imply that all of
+    // the background is painted with the same opaque color.
     if (view->background() && IsOpaque(view->background()->get_color()))
       break;
 
-    if (view->layer() && view->layer()->fills_bounds_opaquely()) {
-      DLOG(WARNING) << "Ancestor view has a non-opaque layer: "
-                    << view->GetClassName() << " with ID " << view->GetID();
+    if (view->layer()) {
+      // If we aren't painted to an opaque background, we must paint to an
+      // opaque layer.
+      DCHECK(view->layer()->fills_bounds_opaquely());
       break;
     }
   }
diff --git a/ui/views/controls/label.h b/ui/views/controls/label.h
index f5b59c8..044bf77 100644
--- a/ui/views/controls/label.h
+++ b/ui/views/controls/label.h
@@ -314,6 +314,7 @@
   FRIEND_TEST_ALL_PREFIXES(LabelTest, FocusBounds);
   FRIEND_TEST_ALL_PREFIXES(LabelTest, MultiLineSizingWithElide);
   FRIEND_TEST_ALL_PREFIXES(LabelTest, IsDisplayTextTruncated);
+  FRIEND_TEST_ALL_PREFIXES(LabelTest, ChecksSubpixelRenderingOntoOpaqueSurface);
   friend class LabelSelectionTest;
 
   // ContextMenuController overrides:
diff --git a/ui/views/controls/label_unittest.cc b/ui/views/controls/label_unittest.cc
index c801df6e..ba66347b 100644
--- a/ui/views/controls/label_unittest.cc
+++ b/ui/views/controls/label_unittest.cc
@@ -14,6 +14,7 @@
 #include "base/command_line.h"
 #include "base/i18n/rtl.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/gtest_util.h"
 #include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/accessibility/ax_enums.mojom.h"
@@ -29,6 +30,7 @@
 #include "ui/gfx/text_constants.h"
 #include "ui/gfx/text_elider.h"
 #include "ui/strings/grit/ui_strings.h"
+#include "ui/views/background.h"
 #include "ui/views/border.h"
 #include "ui/views/controls/base_control_test_widget.h"
 #include "ui/views/controls/link.h"
@@ -1060,6 +1062,36 @@
   EXPECT_TRUE(text_changed);
 }
 
+// TODO(crbug.com/1139395): Enable on ChromeOS along with the DCHECK in Label.
+#if !defined(OS_CHROMEOS)
+// Ensures DCHECK for subpixel rendering on transparent layer is working.
+TEST_F(LabelTest, ChecksSubpixelRenderingOntoOpaqueSurface) {
+  View view;
+  Label* label = view.AddChildView(std::make_unique<TestLabel>());
+  EXPECT_TRUE(label->GetSubpixelRenderingEnabled());
+
+  gfx::Canvas canvas;
+
+  // Painting on a view not painted to a layer should be fine.
+  label->OnPaint(&canvas);
+
+  // Painting to an opaque layer should also be fine.
+  view.SetPaintToLayer();
+  label->OnPaint(&canvas);
+
+  // Set up a transparent layer for the parent view.
+  view.layer()->SetFillsBoundsOpaquely(false);
+
+  // Painting on a transparent layer should DCHECK.
+  EXPECT_DCHECK_DEATH(label->OnPaint(&canvas));
+
+  // Painting onto a transparent layer should not DCHECK if there's an opaque
+  // background in a parent of the Label.
+  view.SetBackground(CreateSolidBackground(SK_ColorWHITE));
+  label->OnPaint(&canvas);
+}
+#endif  // !defined(OS_CHROMEOS)
+
 TEST_F(LabelSelectionTest, Selectable) {
   // By default, labels don't support text selection.
   EXPECT_FALSE(label()->GetSelectable());
diff --git a/ui/views/controls/menu/menu_config.h b/ui/views/controls/menu/menu_config.h
index e64b3934..c84d1377 100644
--- a/ui/views/controls/menu/menu_config.h
+++ b/ui/views/controls/menu/menu_config.h
@@ -208,30 +208,6 @@
   // Margins for footnotes (HIGHLIGHTED item at the end of a menu).
   int footnote_vertical_margin = 11;
 
-  // New Badge -----------------------------------------------------------------
-  // Note that there are a few differences between Views and Mac constants here
-  // that are due to the fact that the rendering is different and therefore
-  // tweaks to the spacing need to be made to achieve the same visual result.
-
-  // Difference in the font size (in pixels) between menu label font and "new"
-  // badge font size.
-  static constexpr int kNewBadgeFontSizeAdjustment = -1;
-
-  // Space between primary text and "new" badge.
-  static constexpr int kNewBadgeHorizontalMargin = 8;
-
-  // Highlight padding around "new" text.
-  static constexpr int kNewBadgeInternalPadding = 4;
-  static constexpr int kNewBadgeInternalPaddingTopMac = 1;
-
-  // The baseline offset of the "new" badge image to the menu text baseline.
-  static constexpr int kNewBadgeBaslineOffsetMac = -4;
-
-  // The corner radius of the rounded rect for the "new" badge.
-  static constexpr int kNewBadgeCornerRadius = 3;
-  static_assert(kNewBadgeCornerRadius <= kNewBadgeInternalPadding,
-                "New badge corner radius should not exceed padding.");
-
  private:
   // Configures a MenuConfig as appropriate for the current platform.
   void Init();
diff --git a/ui/views/controls/menu/menu_item_view.cc b/ui/views/controls/menu/menu_item_view.cc
index 46a8788..38bb7252 100644
--- a/ui/views/controls/menu/menu_item_view.cc
+++ b/ui/views/controls/menu/menu_item_view.cc
@@ -41,6 +41,7 @@
 #include "ui/views/controls/menu/menu_image_util.h"
 #include "ui/views/controls/menu/menu_scroll_view_container.h"
 #include "ui/views/controls/menu/menu_separator.h"
+#include "ui/views/controls/menu/new_badge.h"
 #include "ui/views/controls/menu/submenu_view.h"
 #include "ui/views/controls/separator.h"
 #include "ui/views/metadata/metadata_impl_macros.h"
@@ -54,38 +55,6 @@
 
 namespace {
 
-// Returns the appropriate font to use for the "new" badge based on the font
-// currently being used to render the title of the menu item.
-gfx::FontList DeriveNewBadgeFont(const gfx::FontList& primary_font) {
-  // Preferred font is slightly smaller and slightly more bold than the title
-  // font. The size change is required to make it look correct in the badge; we
-  // add a small degree of bold to prevent color smearing/blurring due to font
-  // smoothing. This ensures readability on all platforms and in both light and
-  // dark modes.
-  return primary_font.Derive(MenuConfig::kNewBadgeFontSizeAdjustment,
-                             gfx::Font::NORMAL, gfx::Font::Weight::MEDIUM);
-}
-
-// Returns the horizontal space required for the "new" badge.
-int GetNewBadgeRequiredWidth(const gfx::FontList& primary_font) {
-  const base::string16 new_text =
-      l10n_util::GetStringUTF16(IDS_MENU_ITEM_NEW_BADGE);
-  gfx::FontList badge_font = DeriveNewBadgeFont(primary_font);
-  return gfx::GetStringWidth(new_text, badge_font) +
-         2 * MenuConfig::kNewBadgeInternalPadding +
-         2 * MenuConfig::kNewBadgeHorizontalMargin;
-}
-
-// Returns the highlight rect for the "new" badge given the font and text rect
-// for the badge text.
-gfx::Rect GetNewBadgeRectOutsetAroundText(const gfx::FontList& badge_font,
-                                          const gfx::Rect& badge_text_rect) {
-  gfx::Rect badge_rect = badge_text_rect;
-  badge_rect.Inset(-gfx::AdjustVisualBorderForFont(
-      badge_font, gfx::Insets(MenuConfig::kNewBadgeInternalPadding)));
-  return badge_rect;
-}
-
 // EmptyMenuMenuItem ---------------------------------------------------------
 
 // EmptyMenuMenuItem is used when a menu has no menu items. EmptyMenuMenuItem
@@ -289,8 +258,7 @@
 
   if (is_new_feature) {
     accessible_name.push_back(' ');
-    accessible_name.append(l10n_util::GetStringUTF16(
-        IDS_MENU_ITEM_NEW_BADGE_SCREEN_READER_MESSAGE));
+    accessible_name.append(NewBadge::GetNewBadgeAccessibleDescription());
   }
 
   return accessible_name;
@@ -1027,12 +995,11 @@
   PaintMinorIconAndText(canvas, style);
 
   if (ShouldShowNewBadge()) {
-    DrawNewBadge(
-        canvas,
-        gfx::Point(label_start + gfx::GetStringWidth(title(), style.font_list) +
-                       MenuConfig::kNewBadgeHorizontalMargin,
-                   top_margin),
-        style.font_list, flags);
+    NewBadge::DrawNewBadge(canvas, this,
+                           label_start +
+                               gfx::GetStringWidth(title(), style.font_list) +
+                               NewBadge::kNewBadgeHorizontalMargin,
+                           top_margin, style.font_list);
   }
 
   // Set the submenu indicator (arrow) image and color.
@@ -1291,7 +1258,9 @@
                           : gfx::GetStringWidth(minor_text, style.font_list));
 
   if (ShouldShowNewBadge())
-    dimensions.minor_text_width += GetNewBadgeRequiredWidth(style.font_list);
+    dimensions.minor_text_width +=
+        NewBadge::GetNewBadgeSize(style.font_list).width() +
+        2 * NewBadge::kNewBadgeHorizontalMargin;
 
   // Determine the height to use.
   int label_text_height = secondary_title().empty()
@@ -1350,39 +1319,6 @@
   return label_start;
 }
 
-void MenuItemView::DrawNewBadge(gfx::Canvas* canvas,
-                                const gfx::Point& unmirrored_badge_start,
-                                const gfx::FontList& primary_font,
-                                int text_render_flags) {
-  gfx::FontList badge_font = DeriveNewBadgeFont(primary_font);
-  const base::string16 new_text =
-      l10n_util::GetStringUTF16(IDS_MENU_ITEM_NEW_BADGE);
-
-  // Calculate bounding box for badge text.
-  gfx::Rect badge_text_bounds(unmirrored_badge_start,
-                              gfx::GetStringSize(new_text, badge_font));
-  badge_text_bounds.Offset(
-      MenuConfig::kNewBadgeInternalPadding,
-      gfx::GetFontCapHeightCenterOffset(primary_font, badge_font));
-  if (base::i18n::IsRTL())
-    badge_text_bounds.set_x(GetMirroredXForRect(badge_text_bounds));
-
-  // Render the badge itself.
-  cc::PaintFlags new_flags;
-  const SkColor background_color = GetNativeTheme()->GetSystemColor(
-      ui::NativeTheme::kColorId_ProminentButtonColor);
-  new_flags.setColor(background_color);
-  canvas->DrawRoundRect(
-      GetNewBadgeRectOutsetAroundText(badge_font, badge_text_bounds),
-      MenuConfig::kNewBadgeCornerRadius, new_flags);
-
-  // Render the badge text.
-  const SkColor foreground_color = GetNativeTheme()->GetSystemColor(
-      ui::NativeTheme::kColorId_TextOnProminentButtonColor);
-  canvas->DrawStringRectWithFlags(new_text, badge_font, foreground_color,
-                                  badge_text_bounds, text_render_flags);
-}
-
 base::string16 MenuItemView::GetMinorText() const {
   if (GetID() == kEmptyMenuItemViewID) {
     // Don't query the delegate for menus that represent no children.
diff --git a/ui/views/controls/menu/menu_item_view.h b/ui/views/controls/menu/menu_item_view.h
index a9222d1..e0c4189 100644
--- a/ui/views/controls/menu/menu_item_view.h
+++ b/ui/views/controls/menu/menu_item_view.h
@@ -463,13 +463,6 @@
   // Get the horizontal position at which to draw the menu item's label.
   int GetLabelStartForThisItem() const;
 
-  // Draws the "new" badge on |canvas|. |unmirrored_badge_start| is the
-  // upper-left corner of the badge, not mirrored for RTL.
-  void DrawNewBadge(gfx::Canvas* canvas,
-                    const gfx::Point& unmirrored_badge_start,
-                    const gfx::FontList& primary_font,
-                    int text_render_flags);
-
   // Used by MenuController to cache the menu position in use by the
   // active menu.
   MenuPosition actual_menu_position() const { return actual_menu_position_; }
diff --git a/ui/views/controls/menu/menu_runner_impl_cocoa.mm b/ui/views/controls/menu/menu_runner_impl_cocoa.mm
index c51447a..214011f 100644
--- a/ui/views/controls/menu/menu_runner_impl_cocoa.mm
+++ b/ui/views/controls/menu/menu_runner_impl_cocoa.mm
@@ -20,6 +20,7 @@
 #include "ui/strings/grit/ui_strings.h"
 #include "ui/views/controls/menu/menu_config.h"
 #include "ui/views/controls/menu/menu_runner_impl_adapter.h"
+#include "ui/views/controls/menu/new_badge.h"
 #include "ui/views/views_features.h"
 #include "ui/views/widget/widget.h"
 
@@ -29,7 +30,7 @@
   static NSImage* new_tag = []() {
     // 1. Make the attributed string.
 
-    NSString* badge_text = l10n_util::GetNSString(IDS_MENU_ITEM_NEW_BADGE);
+    NSString* badge_text = l10n_util::GetNSString(IDS_NEW_BADGE);
 
     // The preferred font is slightly smaller and slightly more bold than the
     // menu font. The size change is required to make it look correct in the
@@ -39,7 +40,7 @@
     gfx::Font badge_font = gfx::Font(
         new gfx::PlatformFontMac(gfx::PlatformFontMac::SystemFontType::kMenu));
     badge_font =
-        badge_font.Derive(views::MenuConfig::kNewBadgeFontSizeAdjustment,
+        badge_font.Derive(views::NewBadge::kNewBadgeFontSizeAdjustment,
                           gfx::Font::NORMAL, gfx::Font::Weight::MEDIUM);
 
     NSColor* badge_text_color = skia::SkColorToSRGBNSColor(
@@ -72,9 +73,9 @@
     badge_size.width = trunc(badge_size.width);
     badge_size.height = trunc(badge_size.height);
 
-    badge_size.width += 2 * views::MenuConfig::kNewBadgeInternalPadding +
-                        2 * views::MenuConfig::kNewBadgeHorizontalMargin;
-    badge_size.height += views::MenuConfig::kNewBadgeInternalPaddingTopMac;
+    badge_size.width += 2 * views::NewBadge::kNewBadgeInternalPadding +
+                        2 * views::NewBadge::kNewBadgeHorizontalMargin;
+    badge_size.height += views::NewBadge::kNewBadgeInternalPaddingTopMac;
 
     // 3. Craft the image.
 
@@ -83,12 +84,11 @@
                flipped:NO
         drawingHandler:^(NSRect dest_rect) {
           NSRect badge_frame = NSInsetRect(
-              dest_rect, views::MenuConfig::kNewBadgeHorizontalMargin, 0);
+              dest_rect, views::NewBadge::kNewBadgeHorizontalMargin, 0);
           NSBezierPath* rounded_badge_rect = [NSBezierPath
               bezierPathWithRoundedRect:badge_frame
-                                xRadius:views::MenuConfig::kNewBadgeCornerRadius
-                                yRadius:views::MenuConfig::
-                                            kNewBadgeCornerRadius];
+                                xRadius:views::NewBadge::kNewBadgeCornerRadius
+                                yRadius:views::NewBadge::kNewBadgeCornerRadius];
           NSColor* badge_color = skia::SkColorToSRGBNSColor(
               ui::NativeTheme::GetInstanceForNativeUi()->GetSystemColor(
                   ui::NativeTheme::kColorId_ProminentButtonColor));
@@ -96,9 +96,9 @@
           [rounded_badge_rect fill];
 
           NSPoint badge_text_location = NSMakePoint(
-              NSMinX(badge_frame) + views::MenuConfig::kNewBadgeInternalPadding,
+              NSMinX(badge_frame) + views::NewBadge::kNewBadgeInternalPadding,
               NSMinY(badge_frame) +
-                  views::MenuConfig::kNewBadgeInternalPaddingTopMac);
+                  views::NewBadge::kNewBadgeInternalPaddingTopMac);
           [badge_attr_string drawAtPoint:badge_text_location];
 
           return YES;
@@ -123,7 +123,7 @@
 }
 
 - (NSPoint)cellBaselineOffset {
-  return NSMakePoint(0, views::MenuConfig::kNewBadgeBaslineOffsetMac);
+  return NSMakePoint(0, views::NewBadge::kNewBadgeBaslineOffsetMac);
 }
 
 - (NSSize)cellSize {
diff --git a/ui/views/controls/menu/new_badge.cc b/ui/views/controls/menu/new_badge.cc
new file mode 100644
index 0000000..f4c1210
--- /dev/null
+++ b/ui/views/controls/menu/new_badge.cc
@@ -0,0 +1,95 @@
+// 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 "ui/views/controls/menu/new_badge.h"
+
+#include <algorithm>
+
+#include "base/i18n/rtl.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/font_list.h"
+#include "ui/gfx/geometry/insets.h"
+#include "ui/gfx/text_constants.h"
+#include "ui/gfx/text_utils.h"
+#include "ui/native_theme/native_theme.h"
+#include "ui/strings/grit/ui_strings.h"
+#include "ui/views/view.h"
+
+namespace views {
+
+namespace {
+
+// Returns the appropriate font to use for the "new" badge based on the font
+// currently being used to render the title of the menu item.
+gfx::FontList DeriveNewBadgeFont(const gfx::FontList& primary_font) {
+  // Preferred font is slightly smaller and slightly more bold than the title
+  // font. The size change is required to make it look correct in the badge; we
+  // add a small degree of bold to prevent color smearing/blurring due to font
+  // smoothing. This ensures readability on all platforms and in both light and
+  // dark modes.
+  return primary_font.Derive(NewBadge::kNewBadgeFontSizeAdjustment,
+                             gfx::Font::NORMAL, gfx::Font::Weight::MEDIUM);
+}
+
+// Returns the highlight rect for the "new" badge given the font and text rect
+// for the badge text.
+gfx::Rect GetNewBadgeRectOutsetAroundText(const gfx::FontList& badge_font,
+                                          const gfx::Rect& badge_text_rect) {
+  gfx::Rect badge_rect = badge_text_rect;
+  badge_rect.Inset(-gfx::AdjustVisualBorderForFont(
+      badge_font, gfx::Insets(NewBadge::kNewBadgeInternalPadding)));
+  return badge_rect;
+}
+
+}  // namespace
+
+// static
+void NewBadge::DrawNewBadge(gfx::Canvas* canvas,
+                            const View* view,
+                            int unmirrored_badge_left_x,
+                            int text_top_y,
+                            const gfx::FontList& primary_font) {
+  gfx::FontList badge_font = DeriveNewBadgeFont(primary_font);
+  const base::string16 new_text = l10n_util::GetStringUTF16(IDS_NEW_BADGE);
+
+  // Calculate bounding box for badge text.
+  unmirrored_badge_left_x += kNewBadgeInternalPadding;
+  text_top_y += gfx::GetFontCapHeightCenterOffset(primary_font, badge_font);
+  gfx::Rect badge_text_bounds(gfx::Point(unmirrored_badge_left_x, text_top_y),
+                              gfx::GetStringSize(new_text, badge_font));
+  if (base::i18n::IsRTL())
+    badge_text_bounds.set_x(view->GetMirroredXForRect(badge_text_bounds));
+
+  // Render the badge itself.
+  cc::PaintFlags new_flags;
+  const SkColor background_color = view->GetNativeTheme()->GetSystemColor(
+      ui::NativeTheme::kColorId_ProminentButtonColor);
+  new_flags.setColor(background_color);
+  canvas->DrawRoundRect(
+      GetNewBadgeRectOutsetAroundText(badge_font, badge_text_bounds),
+      kNewBadgeCornerRadius, new_flags);
+
+  // Render the badge text.
+  const SkColor foreground_color = view->GetNativeTheme()->GetSystemColor(
+      ui::NativeTheme::kColorId_TextOnProminentButtonColor);
+  canvas->DrawStringRect(new_text, badge_font, foreground_color,
+                         badge_text_bounds);
+}
+
+// static
+gfx::Size NewBadge::GetNewBadgeSize(const gfx::FontList& primary_font) {
+  const base::string16 new_text = l10n_util::GetStringUTF16(IDS_NEW_BADGE);
+  gfx::FontList badge_font = DeriveNewBadgeFont(primary_font);
+  const gfx::Size text_size = gfx::GetStringSize(new_text, badge_font);
+  return GetNewBadgeRectOutsetAroundText(badge_font, gfx::Rect(text_size))
+      .size();
+}
+
+// static
+base::string16 NewBadge::GetNewBadgeAccessibleDescription() {
+  return l10n_util::GetStringUTF16(IDS_NEW_BADGE_SCREEN_READER_MESSAGE);
+}
+
+}  // namespace views
diff --git a/ui/views/controls/menu/new_badge.h b/ui/views/controls/menu/new_badge.h
new file mode 100644
index 0000000..89c0602
--- /dev/null
+++ b/ui/views/controls/menu/new_badge.h
@@ -0,0 +1,80 @@
+// 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 UI_VIEWS_CONTROLS_MENU_NEW_BADGE_H_
+#define UI_VIEWS_CONTROLS_MENU_NEW_BADGE_H_
+
+#include "base/strings/string16.h"
+#include "ui/views/views_export.h"
+
+namespace gfx {
+class Canvas;
+class FontList;
+class Size;
+}  // namespace gfx
+
+namespace views {
+
+class View;
+
+// Represents a "New" badge that can be inserted into any other view as part of
+// a new feature promotion. Provides static methods only;
+class VIEWS_EXPORT NewBadge {
+ public:
+  // This is a utility class and should not be instantiated.
+  NewBadge() = delete;
+
+  // Draws the "new" badge on |canvas|. |unmirrored_badge_left_x| is the
+  // leading edge of the badge, not mirrored for RTL. |text_top_y| is the
+  // top of the text the badge should align with, and |primary_font| is the font
+  // of that text.
+  //
+  // You can call this method from any View to draw the badge directly onto the
+  // view as part of OnPaint() or a similar method, so you don't necessarily
+  // have to instantiate a NewBadge view to get this functionality.
+  static void DrawNewBadge(gfx::Canvas* canvas,
+                           const View* view,
+                           int unmirrored_badge_left_x,
+                           int text_top_y,
+                           const gfx::FontList& primary_font);
+
+  // Returns the space required for the "new" badge itself, not counting leading
+  // or trailing margin. It is recommended to leave a margin of
+  // NewBadge::kNewBadgeHorizontalMargin between the badge and any other text
+  // or image elements.
+  static gfx::Size GetNewBadgeSize(const gfx::FontList& primary_font);
+
+  // Gets the accessible description of the new badge, which can be added to
+  // tooltip/screen reader text.
+  static base::string16 GetNewBadgeAccessibleDescription();
+
+  // Layout Constants
+  //
+  // Note that there are a few differences between Views and Mac constants here
+  // that are due to the fact that the rendering is different and therefore
+  // tweaks to the spacing need to be made to achieve the same visual result.
+
+  // Difference in the font size (in pixels) between menu label font and "new"
+  // badge font size.
+  static constexpr int kNewBadgeFontSizeAdjustment = -1;
+
+  // Space between primary text and "new" badge.
+  static constexpr int kNewBadgeHorizontalMargin = 8;
+
+  // Highlight padding around "new" text.
+  static constexpr int kNewBadgeInternalPadding = 4;
+  static constexpr int kNewBadgeInternalPaddingTopMac = 1;
+
+  // The baseline offset of the "new" badge image to the menu text baseline.
+  static constexpr int kNewBadgeBaslineOffsetMac = -4;
+
+  // The corner radius of the rounded rect for the "new" badge.
+  static constexpr int kNewBadgeCornerRadius = 3;
+  static_assert(kNewBadgeCornerRadius <= kNewBadgeInternalPadding,
+                "New badge corner radius should not exceed padding.");
+};
+
+}  // namespace views
+
+#endif  // UI_VIEWS_CONTROLS_MENU_NEW_BADGE_H_
diff --git a/ui/views/controls/progress_bar.cc b/ui/views/controls/progress_bar.cc
index 4192f5b67..09e80a9 100644
--- a/ui/views/controls/progress_bar.cc
+++ b/ui/views/controls/progress_bar.cc
@@ -53,7 +53,7 @@
 ProgressBar::ProgressBar(int preferred_height, bool allow_round_corner)
     : preferred_height_(preferred_height),
       allow_round_corner_(allow_round_corner) {
-  EnableCanvasFlippingForRTLUI(true);
+  SetFlipCanvasOnPaintForRTLUI(true);
 }
 
 ProgressBar::~ProgressBar() = default;
diff --git a/ui/views/controls/scrollbar/overlay_scroll_bar.cc b/ui/views/controls/scrollbar/overlay_scroll_bar.cc
index 1dd7283a..276a558b 100644
--- a/ui/views/controls/scrollbar/overlay_scroll_bar.cc
+++ b/ui/views/controls/scrollbar/overlay_scroll_bar.cc
@@ -43,7 +43,7 @@
 OverlayScrollBar::Thumb::~Thumb() = default;
 
 void OverlayScrollBar::Thumb::Init() {
-  EnableCanvasFlippingForRTLUI(true);
+  SetFlipCanvasOnPaintForRTLUI(true);
   SetPaintToLayer();
   layer()->SetFillsBoundsOpaquely(false);
   // Animate all changes to the layer except the first one.
diff --git a/ui/views/controls/scrollbar/scroll_bar_views.cc b/ui/views/controls/scrollbar/scroll_bar_views.cc
index 46301377..45683aa9 100644
--- a/ui/views/controls/scrollbar/scroll_bar_views.cc
+++ b/ui/views/controls/scrollbar/scroll_bar_views.cc
@@ -72,7 +72,7 @@
 
 ScrollBarButton::ScrollBarButton(PressedCallback callback, Type type)
     : BaseScrollBarButton(std::move(callback)), type_(type) {
-  EnableCanvasFlippingForRTLUI(true);
+  SetFlipCanvasOnPaintForRTLUI(true);
   DCHECK_EQ(FocusBehavior::NEVER, GetFocusBehavior());
 }
 
@@ -196,7 +196,7 @@
 }  // namespace
 
 ScrollBarViews::ScrollBarViews(bool horizontal) : ScrollBar(horizontal) {
-  EnableCanvasFlippingForRTLUI(true);
+  SetFlipCanvasOnPaintForRTLUI(true);
   state_ = ui::NativeTheme::kNormal;
 
   auto* layout = SetLayoutManager(std::make_unique<views::FlexLayout>());
diff --git a/ui/views/controls/slider.cc b/ui/views/controls/slider.cc
index aa75afa..4cd9bd4 100644
--- a/ui/views/controls/slider.cc
+++ b/ui/views/controls/slider.cc
@@ -73,7 +73,7 @@
 
 Slider::Slider(SliderListener* listener) : listener_(listener) {
   highlight_animation_.SetSlideDuration(base::TimeDelta::FromMilliseconds(150));
-  EnableCanvasFlippingForRTLUI(true);
+  SetFlipCanvasOnPaintForRTLUI(true);
 #if defined(OS_APPLE)
   SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
 #else
diff --git a/ui/views/touchui/touch_selection_menu_views.cc b/ui/views/touchui/touch_selection_menu_views.cc
index ad3c452..b75223a 100644
--- a/ui/views/touchui/touch_selection_menu_views.cc
+++ b/ui/views/touchui/touch_selection_menu_views.cc
@@ -58,7 +58,7 @@
   set_margins(kMenuMargins);
   SetCanActivate(false);
   set_adjust_if_offscreen(true);
-  EnableCanvasFlippingForRTLUI(true);
+  SetFlipCanvasOnPaintForRTLUI(true);
 
   SetLayoutManager(
       std::make_unique<BoxLayout>(BoxLayout::Orientation::kHorizontal,
diff --git a/ui/views/view.cc b/ui/views/view.cc
index 03f02eb2..ca24f27 100644
--- a/ui/views/view.cc
+++ b/ui/views/view.cc
@@ -1148,8 +1148,30 @@
 
 // RTL painting ----------------------------------------------------------------
 
-void View::EnableCanvasFlippingForRTLUI(bool enable) {
+bool View::GetFlipCanvasOnPaintForRTLUI() const {
+  return flip_canvas_on_paint_for_rtl_ui_;
+}
+
+void View::SetFlipCanvasOnPaintForRTLUI(bool enable) {
+  if (enable == flip_canvas_on_paint_for_rtl_ui_)
+    return;
   flip_canvas_on_paint_for_rtl_ui_ = enable;
+
+  OnPropertyChanged(&flip_canvas_on_paint_for_rtl_ui_, kPropertyEffectsPaint);
+}
+
+PropertyChangedSubscription View::AddFlipCanvasOnPaintForRTLUIChangedCallback(
+    PropertyChangedCallback callback) {
+  return AddPropertyChangedCallback(&flip_canvas_on_paint_for_rtl_ui_,
+                                    std::move(callback));
+}
+
+void View::SetMirrored(bool is_mirrored) {
+  if (is_mirrored_ && is_mirrored_.value() == is_mirrored)
+    return;
+  is_mirrored_ = is_mirrored;
+
+  OnPropertyChanged(&is_mirrored_, kPropertyEffectsPaint);
 }
 
 bool View::GetMirrored() const {
@@ -3053,6 +3075,7 @@
 ADD_READONLY_PROPERTY_METADATA(const char*, ClassName)
 ADD_PROPERTY_METADATA(bool, Enabled)
 ADD_PROPERTY_METADATA(View::FocusBehavior, FocusBehavior)
+ADD_PROPERTY_METADATA(bool, FlipCanvasOnPaintForRTLUI)
 ADD_PROPERTY_METADATA(int, Group)
 ADD_PROPERTY_METADATA(int, ID)
 ADD_READONLY_PROPERTY_METADATA(gfx::Size, MaximumSize)
diff --git a/ui/views/view.h b/ui/views/view.h
index 2003eeb..d30a5ef 100644
--- a/ui/views/view.h
+++ b/ui/views/view.h
@@ -896,13 +896,10 @@
 
   // RTL painting --------------------------------------------------------------
 
-  // This method determines whether the gfx::Canvas object passed to
-  // Paint() needs to be transformed such that anything drawn on the canvas
-  // object during Paint() is flipped horizontally.
-  bool flip_canvas_on_paint_for_rtl_ui() const {
-    return flip_canvas_on_paint_for_rtl_ui_;
-  }
-
+  // Returns whether the gfx::Canvas object passed to Paint() needs to be
+  // transformed such that anything drawn on the canvas object during Paint()
+  // is flipped horizontally.
+  bool GetFlipCanvasOnPaintForRTLUI() const;
   // Enables or disables flipping of the gfx::Canvas during Paint(). Note that
   // if canvas flipping is enabled, the canvas will be flipped only if the UI
   // layout is right-to-left; that is, the canvas will be flipped only if
@@ -913,19 +910,25 @@
   // (views::Button, for example). This method is helpful for such classes
   // because their drawing logic stays the same and they can become agnostic to
   // the UI directionality.
-  virtual void EnableCanvasFlippingForRTLUI(bool enable);
+  void SetFlipCanvasOnPaintForRTLUI(bool enable);
+
+  // Adds a callback subscription associated with the above
+  // FlipCanvasOnPaintForRTLUI property. The callback will be invoked whenever
+  // the FlipCanvasOnPaintForRTLUI property changes.
+  PropertyChangedSubscription AddFlipCanvasOnPaintForRTLUIChangedCallback(
+      PropertyChangedCallback callback) WARN_UNUSED_RESULT;
 
   // When set, this view will ignore base::l18n::IsRTL() and instead be drawn
   // according to |is_mirrored|.
   //
   // This is useful for views that should be displayed the same regardless of UI
-  // direction. Unlike EnableCanvasFlippingForRTLUI this setting has an effect
+  // direction. Unlike SetFlipCanvasOnPaintForRTLUI this setting has an effect
   // on the visual order of child views.
   //
   // This setting does not propagate to child views. So while the visual order
   // of this view's children may change, the visual order of this view's
   // grandchildren in relation to their parents are unchanged.
-  void SetMirrored(bool is_mirrored) { is_mirrored_ = is_mirrored; }
+  void SetMirrored(bool is_mirrored);
   bool GetMirrored() const;
 
   // Input ---------------------------------------------------------------------
@@ -2073,6 +2076,7 @@
 VIEW_BUILDER_PROPERTY(SkPath, ClipPath)
 VIEW_BUILDER_PROPERTY_DEFAULT(ui::LayerType, PaintToLayer, ui::LAYER_TEXTURED)
 VIEW_BUILDER_PROPERTY(bool, Enabled)
+VIEW_BUILDER_PROPERTY(bool, FlipCanvasOnPaintForRTLUI)
 VIEW_BUILDER_PROPERTY(views::View::FocusBehavior, FocusBehavior)
 VIEW_BUILDER_PROPERTY(int, Group)
 VIEW_BUILDER_PROPERTY(int, ID)
diff --git a/ui/views/window/dialog_client_view.cc b/ui/views/window/dialog_client_view.cc
index e479496..98a32c4 100644
--- a/ui/views/window/dialog_client_view.cc
+++ b/ui/views/window/dialog_client_view.cc
@@ -111,6 +111,13 @@
 }
 
 gfx::Size DialogClientView::GetMinimumSize() const {
+  // TODO(pbos): Try to find a way for ClientView::GetMinimumSize() to be
+  // fixed-width aware. For now this uses min-size = preferred size for
+  // fixed-width dialogs (even though min height might not be preferred height).
+  // Fixing this might require View::GetMinHeightForWidth().
+  if (GetDialogDelegate()->fixed_width())
+    return CalculatePreferredSize();
+
   return GetBoundingSizeForVerticalStack(
       ClientView::GetMinimumSize(), button_row_container_->GetMinimumSize());
 }
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_ip_config.js b/ui/webui/resources/cr_components/chromeos/network/network_ip_config.js
index a3717a2..def0da8 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_ip_config.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_ip_config.js
@@ -197,6 +197,11 @@
    * @private
    */
   canChangeIPConfigType_(managedProperties) {
+    if (managedProperties.type ===
+        chromeos.networkConfig.mojom.NetworkType.kCellular) {
+      // Cellular IP config properties can not be changed.
+      return false;
+    }
     const ipConfigType = managedProperties.ipAddressConfigType;
     return !ipConfigType || !this.isNetworkPolicyEnforced(ipConfigType);
   },
diff --git a/ui/webui/resources/tools/generate_grd.gni b/ui/webui/resources/tools/generate_grd.gni
index 75f3218..8b2eed76 100644
--- a/ui/webui/resources/tools/generate_grd.gni
+++ b/ui/webui/resources/tools/generate_grd.gni
@@ -10,18 +10,29 @@
       deps = invoker.deps
     }
 
-    inputs = invoker.manifest_files
+    inputs = []
     outputs = [ invoker.out_grd ]
 
     args = [
-             "--out-grd",
-             rebase_path(invoker.out_grd, root_build_dir),
-             "--grd-prefix",
-             invoker.grd_prefix,
-             "--root-gen-dir",
-             rebase_path(root_gen_dir, root_build_dir),
-             "--manifest-files",
-           ] + rebase_path(invoker.manifest_files, root_build_dir)
+      "--out-grd",
+      rebase_path(invoker.out_grd, root_build_dir),
+      "--grd-prefix",
+      invoker.grd_prefix,
+      "--root-gen-dir",
+      rebase_path(root_gen_dir, root_build_dir),
+    ]
+
+    if (defined(invoker.manifest_files)) {
+      inputs += invoker.manifest_files
+      args += [ "--manifest-files" ] +
+              rebase_path(invoker.manifest_files, root_build_dir)
+    }
+
+    if (defined(invoker.grdp_files)) {
+      inputs += invoker.grdp_files
+      args +=
+          [ "--grdp-files" ] + rebase_path(invoker.grdp_files, root_build_dir)
+    }
 
     if (defined(invoker.input_files)) {
       args += [
diff --git a/ui/webui/resources/tools/generate_grd.py b/ui/webui/resources/tools/generate_grd.py
index bfcbf92..676893eb 100644
--- a/ui/webui/resources/tools/generate_grd.py
+++ b/ui/webui/resources/tools/generate_grd.py
@@ -71,6 +71,9 @@
                    '  </release>\n'\
                    '</grit>\n'
 
+GRDP_BEGIN_TEMPLATE = '<?xml version="1.0" encoding="UTF-8"?>\n'\
+                     '<grit-part>\n'
+GRDP_END_TEMPLATE = '</grit-part>\n'
 
 # Generates an <include .... /> row for the given file.
 def _generate_include_row(grd_prefix, filename, pathname, \
@@ -90,19 +93,31 @@
       type=type)
 
 
+def _generate_part_row(filename):
+  return '      <part file="%s" />\n' % filename
+
+
 def main(argv):
   parser = argparse.ArgumentParser()
-  parser.add_argument('--manifest-files', required=True, nargs="*")
+  parser.add_argument('--manifest-files', nargs="*")
   parser.add_argument('--out-grd', required=True)
   parser.add_argument('--grd-prefix', required=True)
   parser.add_argument('--root-gen-dir', required=True)
   parser.add_argument('--input-files', nargs="*")
   parser.add_argument('--input-files-base-dir')
+  parser.add_argument('--grdp-files', nargs="*")
   parser.add_argument('--resource-path-rewrites', nargs="*")
   args = parser.parse_args(argv)
 
   grd_file = open(os.path.normpath(os.path.join(_CWD, args.out_grd)), 'w')
-  grd_file.write(GRD_BEGIN_TEMPLATE.format(prefix=args.grd_prefix))
+  begin_template = GRDP_BEGIN_TEMPLATE if args.out_grd.endswith('.grdp') else \
+      GRD_BEGIN_TEMPLATE
+  grd_file.write(begin_template.format(prefix=args.grd_prefix))
+
+  if args.grdp_files != None:
+    for grdp_file in args.grdp_files:
+      grdp_path = os.path.relpath(grdp_file, os.path.dirname(args.out_grd))
+      grd_file.write(_generate_part_row(grdp_path))
 
   resource_path_rewrites = {}
   if args.resource_path_rewrites != None:
@@ -119,19 +134,22 @@
           args.grd_prefix, filename, '${root_src_dir}/' + filepath,
           resource_path_rewrites))
 
-  for manifest_file in args.manifest_files:
-    manifest_path = os.path.normpath(os.path.join(_CWD, manifest_file))
-    with open(manifest_path, 'r') as f:
-      data = json.load(f)
-      base_dir= os.path.normpath(os.path.join(_CWD, data['base_dir']))
-      for filename in data['files']:
-        filepath = os.path.join(base_dir, filename).replace('\\', '/')
-        rebased_path = os.path.relpath(filepath, args.root_gen_dir)
-        grd_file.write(_generate_include_row(
-            args.grd_prefix, filename, '${root_gen_dir}/' + rebased_path,
-            resource_path_rewrites))
+  if args.manifest_files != None:
+    for manifest_file in args.manifest_files:
+      manifest_path = os.path.normpath(os.path.join(_CWD, manifest_file))
+      with open(manifest_path, 'r') as f:
+        data = json.load(f)
+        base_dir= os.path.normpath(os.path.join(_CWD, data['base_dir']))
+        for filename in data['files']:
+          filepath = os.path.join(base_dir, filename).replace('\\', '/')
+          rebased_path = os.path.relpath(filepath, args.root_gen_dir)
+          grd_file.write(_generate_include_row(
+              args.grd_prefix, filename, '${root_gen_dir}/' + rebased_path,
+              resource_path_rewrites))
 
-  grd_file.write(GRD_END_TEMPLATE)
+  end_template = GRDP_END_TEMPLATE if args.out_grd.endswith('.grdp') else \
+      GRD_END_TEMPLATE
+  grd_file.write(end_template)
   return
 
 
diff --git a/ui/webui/resources/tools/generate_grd_test.py b/ui/webui/resources/tools/generate_grd_test.py
index 5659b739..de1dca9 100755
--- a/ui/webui/resources/tools/generate_grd_test.py
+++ b/ui/webui/resources/tools/generate_grd_test.py
@@ -14,10 +14,33 @@
 _HERE_DIR = os.path.dirname(__file__)
 pathToHere = os.path.relpath(_HERE_DIR, _CWD)
 
+# This needs to be a constant, because if this expected file is checked into
+# the repo, translation.py will try to find the dummy grdp files, which don't
+# exist. We can't alter the translation script, so work around it by
+# hardcoding this string, instead of checking in even more dummy files.
+EXPECTED_GRD_WITH_GRDP_FILES = '''<?xml version="1.0" encoding="UTF-8"?>
+<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
+  <outputs>
+    <output filename="grit/test_resources.h" type="rc_header">
+      <emit emit_type='prepend'></emit>
+    </output>
+    <output filename="grit/test_resources_map.cc"
+            type="resource_file_map_source" />
+    <output filename="grit/test_resources_map.h"
+            type="resource_map_header" />
+    <output filename="test_resources.pak" type="data_package" />
+  </outputs>
+  <release seq="1">
+    <includes>
+      <part file="foo_resources.grdp" />
+      <part file="foo/bar_resources.grdp" />
+    </includes>
+  </release>
+</grit>\n'''
 
 class GenerateGrdTest(unittest.TestCase):
   def setUp(self):
-    self._out_folder = None
+    self._out_folder = tempfile.mkdtemp(dir=_HERE_DIR)
 
   def tearDown(self):
     shutil.rmtree(self._out_folder)
@@ -26,39 +49,51 @@
     assert self._out_folder
     return open(os.path.join(self._out_folder, file_name), 'rb').read()
 
-  def _run_test_(self, grd_expected, manifest_files, input_files=None,
-                 input_files_base_dir=None, resource_path_rewrites=None):
-    assert not self._out_folder
-    self._out_folder = tempfile.mkdtemp(dir=_HERE_DIR)
+  def _run_test_(self, grd_expected,
+                 out_grd='test_resources.grd',
+                 manifest_files=None, input_files=None,
+                 input_files_base_dir=None, grdp_files=None,
+                 resource_path_rewrites=None):
     args = [
-      '--out-grd', os.path.join(self._out_folder, 'test_resources.grd'),
+      '--out-grd', os.path.join(self._out_folder, out_grd),
       '--grd-prefix', 'test',
       '--root-gen-dir', os.path.join(_CWD, pathToHere, 'tests'),
-      '--manifest-files',
-    ] + manifest_files
+    ]
+
+    if manifest_files != None:
+      args += [
+        '--manifest-files',
+      ] + manifest_files
+
+    if grdp_files != None:
+      args += [
+        '--grdp-files',
+      ] + grdp_files
 
     if (input_files_base_dir):
       args += [
         '--input-files-base-dir',
         input_files_base_dir,
         '--input-files',
-      ]
-      args += input_files
+      ] + input_files
 
     if (resource_path_rewrites):
       args += [ '--resource-path-rewrites' ] + resource_path_rewrites
 
     generate_grd.main(args)
 
-    actual_grd = self._read_out_file('test_resources.grd')
-    expected_grd = open(
-        os.path.join(_HERE_DIR, 'tests', grd_expected), 'rb').read()
-    self.assertEquals(expected_grd, actual_grd)
+    actual_grd = self._read_out_file(out_grd)
+    if (grd_expected.endswith('.grd') or grd_expected.endswith('.grdp')):
+      expected_grd_content = open(
+          os.path.join(_HERE_DIR, 'tests', grd_expected), 'rb').read()
+      self.assertEquals(expected_grd_content, actual_grd)
+    else:
+      self.assertEquals(grd_expected, actual_grd)
 
   def testSuccess(self):
     self._run_test_(
       'expected_grd.grd',
-      [
+      manifest_files = [
         os.path.join(pathToHere, 'tests', 'test_manifest_1.json'),
         os.path.join(pathToHere, 'tests', 'test_manifest_2.json'),
       ])
@@ -66,21 +101,35 @@
   def testSuccessWithInputFiles(self):
     self._run_test_(
       'expected_grd_with_input_files.grd',
-      [
+      manifest_files = [
         os.path.join(pathToHere, 'tests', 'test_manifest_1.json'),
         os.path.join(pathToHere, 'tests', 'test_manifest_2.json'),
       ],
-      [ 'images/test_svg.svg', 'test_html_in_src.html' ],
-      'test_src_dir')
+      input_files = [ 'images/test_svg.svg', 'test_html_in_src.html' ],
+      input_files_base_dir = 'test_src_dir')
+
+  def testSuccessWithGrdpFiles(self):
+    self._run_test_(
+      EXPECTED_GRD_WITH_GRDP_FILES,
+      grdp_files = [
+        os.path.join(self._out_folder, 'foo_resources.grdp'),
+        os.path.join(self._out_folder, 'foo', 'bar_resources.grdp'),
+      ])
+
+  def testSuccessGrdpWithInputFiles(self):
+    self._run_test_(
+      'expected_grdp_with_input_files.grdp',
+      out_grd = 'test_resources.grdp',
+      input_files = [ 'images/test_svg.svg', 'test_html_in_src.html' ],
+      input_files_base_dir = 'test_src_dir')
 
   def testSuccessWithRewrites(self):
     self._run_test_(
       'expected_grd_with_rewrites.grd',
-      [
+      manifest_files = [
         os.path.join(pathToHere, 'tests', 'test_manifest_1.json'),
         os.path.join(pathToHere, 'tests', 'test_manifest_2.json'),
       ],
-      input_files=None, input_files_base_dir=None,
       resource_path_rewrites=[
         'test.rollup.js|test.js',
         'dir/another_element_in_dir.js|dir2/another_element_in_dir_renamed.js',
diff --git a/ui/webui/resources/tools/tests/expected_grdp_with_input_files.grdp b/ui/webui/resources/tools/tests/expected_grdp_with_input_files.grdp
new file mode 100644
index 0000000..ce6cf87
--- /dev/null
+++ b/ui/webui/resources/tools/tests/expected_grdp_with_input_files.grdp
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grit-part>
+      <include name="IDR_TEST_IMAGES_TEST_SVG_SVG" file="${root_src_dir}/test_src_dir/images/test_svg.svg" resource_path="images/test_svg.svg" use_base_dir="false" type="BINDATA" />
+      <include name="IDR_TEST_TEST_HTML_IN_SRC_HTML" file="${root_src_dir}/test_src_dir/test_html_in_src.html" resource_path="test_html_in_src.html" use_base_dir="false" type="chrome_html" />
+</grit-part>