diff --git a/DEPS b/DEPS
index ad676aea..e05bd65 100644
--- a/DEPS
+++ b/DEPS
@@ -129,11 +129,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': '2c4ceca62d27ebd3617d91b94a0231030927f864',
+  'skia_revision': 'c132b122b93988ce9fb7f0a03cec448a9369c3e8',
   # 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': 'da7a3d8e0374cb531d9ce0a3215650eccdd9a8f0',
+  'v8_revision': '1eaccad69d406ebb986c67009383c8d4ce8167bb',
   # 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.
@@ -141,11 +141,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '232bfeae6b292ac4286348a430c4cef78edcf7c0',
+  'angle_revision': '3a0e5bebc040f894ea92df43430692c9d612177b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': 'aff2dd067015060a81d43ea768160045a42648c2',
+  'swiftshader_revision': '7bc4f45a391f60a0ef4ad045432d6af6a31a9d1b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -192,7 +192,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '4f8e8775daf0c57a4921cb3e39d5bf2d1608dc9e',
+  'catapult_revision': 'ea373a05edc25dd1d7f742c4013d7f6efa0e3f06',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -248,7 +248,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'spv_tools_revision': 'e1a76269b649b7263266c1197622468dae9950b3',
+  'spv_tools_revision': '1f60f98964687ffe8656ac35dce035e79d78ebf0',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -264,11 +264,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': '54e4d47db4910ebd1ffce0247b60d4e6f984774f',
+  'dawn_revision': 'e105f962cf0020ee96a5a14a881c0de4ad46706c',
   # 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': 'd5d13c2a81b5b504c1bf3308b12c5479a1eb8d58',
+  'quiche_revision': 'f1c9d5683b6a6b79553710cb9604b6084373299f',
   # 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.
@@ -288,7 +288,7 @@
   # Also, if you change these, make sure you update the svn_revisions in
   # //buildtools/deps_revisions.gni.
   'clang_format_revision': '96636aa0e9f047f17447f2d45a094d0b59ed7917',
-  'libcxx_revision': '9009625c821e9580bfece732c25bac1cc9c5a7c2',
+  'libcxx_revision': '4daecde1d737da594173591db71e543a3e2d51d8',
   'libcxxabi_revision': '0d529660e32d77d9111912d73f2c74fc5fa2a858',
   'libunwind_revision': '69d9b84cca8354117b9fe9705a4430d789ee599b',
 }
@@ -1339,7 +1339,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'a0f51b2e123f39c9ff12e621b0b47dd28dd64424',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'f7f9845d9e5b24c73a70fceb278a20b43e9d9919',
+    Var('webrtc_git') + '/src.git' + '@' + '94b57c044e81c6d1938f60aeabe7115a373f626d',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
@@ -1380,7 +1380,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@c2f5ff535204aee167e30fa9122290d9d505891f',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@a8f7a1f5d626bea655d75cc2d86f593b3949784a',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/browser/gfx/aw_gl_surface.cc b/android_webview/browser/gfx/aw_gl_surface.cc
index 9536a8e..be75655 100644
--- a/android_webview/browser/gfx/aw_gl_surface.cc
+++ b/android_webview/browser/gfx/aw_gl_surface.cc
@@ -4,6 +4,8 @@
 
 #include "android_webview/browser/gfx/aw_gl_surface.h"
 
+#include <utility>
+
 #include "android_webview/browser/gfx/scoped_app_gl_state_restore.h"
 
 namespace android_webview {
@@ -24,10 +26,15 @@
 }
 
 gfx::SwapResult AwGLSurface::SwapBuffers(PresentationCallback callback) {
-  // TODO(penghuang): Provide presentation feedback. https://crbug.com/776877
+  DCHECK(pending_presentation_callback_.is_null());
+  pending_presentation_callback_ = std::move(callback);
   return gfx::SwapResult::SWAP_ACK;
 }
 
+bool AwGLSurface::SupportsPresentationCallback() {
+  return true;
+}
+
 gfx::Size AwGLSurface::GetSize() {
   return size_;
 }
@@ -52,4 +59,10 @@
   return true;
 }
 
+void AwGLSurface::MaybeDidPresent(gfx::PresentationFeedback feedback) {
+  if (pending_presentation_callback_.is_null())
+    return;
+  std::move(pending_presentation_callback_).Run(std::move(feedback));
+}
+
 }  // namespace android_webview
diff --git a/android_webview/browser/gfx/aw_gl_surface.h b/android_webview/browser/gfx/aw_gl_surface.h
index ccc2b21..bf19955 100644
--- a/android_webview/browser/gfx/aw_gl_surface.h
+++ b/android_webview/browser/gfx/aw_gl_surface.h
@@ -22,6 +22,7 @@
   bool IsOffscreen() override;
   unsigned int GetBackingFramebufferObject() override;
   gfx::SwapResult SwapBuffers(PresentationCallback callback) override;
+  bool SupportsPresentationCallback() override;
   gfx::Size GetSize() override;
   void* GetHandle() override;
   void* GetDisplay() override;
@@ -31,10 +32,13 @@
               ColorSpace color_space,
               bool has_alpha) override;
 
+  void MaybeDidPresent(gfx::PresentationFeedback feedback);
+
  protected:
   ~AwGLSurface() override;
 
  private:
+  PresentationCallback pending_presentation_callback_;
   gfx::Size size_;
   DISALLOW_COPY_AND_ASSIGN(AwGLSurface);
 };
diff --git a/android_webview/browser/gfx/parent_output_surface.cc b/android_webview/browser/gfx/parent_output_surface.cc
index 641b504..24b3e718 100644
--- a/android_webview/browser/gfx/parent_output_surface.cc
+++ b/android_webview/browser/gfx/parent_output_surface.cc
@@ -4,6 +4,8 @@
 
 #include "android_webview/browser/gfx/parent_output_surface.h"
 
+#include <utility>
+
 #include "android_webview/browser/gfx/aw_render_thread_context_provider.h"
 #include "android_webview/browser/gfx/scoped_app_gl_state_restore.h"
 #include "components/viz/service/display/output_surface_client.h"
@@ -13,13 +15,20 @@
 namespace android_webview {
 
 ParentOutputSurface::ParentOutputSurface(
+    scoped_refptr<AwGLSurface> gl_surface,
     scoped_refptr<AwRenderThreadContextProvider> context_provider)
-    : viz::OutputSurface(std::move(context_provider)) {}
+    : viz::OutputSurface(std::move(context_provider)),
+      gl_surface_(std::move(gl_surface)),
+      weak_ptr_factory_(this) {}
 
 ParentOutputSurface::~ParentOutputSurface() {
 }
 
-void ParentOutputSurface::BindToClient(viz::OutputSurfaceClient* client) {}
+void ParentOutputSurface::BindToClient(viz::OutputSurfaceClient* client) {
+  DCHECK(client);
+  DCHECK(!client_);
+  client_ = client;
+}
 
 void ParentOutputSurface::EnsureBackbuffer() {}
 
@@ -41,6 +50,14 @@
 
 void ParentOutputSurface::SwapBuffers(viz::OutputSurfaceFrame frame) {
   context_provider_->ContextGL()->ShallowFlushCHROMIUM();
+  gl_surface_->SwapBuffers(base::BindOnce(&ParentOutputSurface::OnPresentation,
+                                          weak_ptr_factory_.GetWeakPtr()));
+}
+
+void ParentOutputSurface::OnPresentation(
+    const gfx::PresentationFeedback& feedback) {
+  DCHECK(client_);
+  client_->DidReceivePresentationFeedback(feedback);
 }
 
 bool ParentOutputSurface::HasExternalStencilTest() const {
diff --git a/android_webview/browser/gfx/parent_output_surface.h b/android_webview/browser/gfx/parent_output_surface.h
index ba9fd8e2..95d60d9 100644
--- a/android_webview/browser/gfx/parent_output_surface.h
+++ b/android_webview/browser/gfx/parent_output_surface.h
@@ -5,15 +5,23 @@
 #ifndef ANDROID_WEBVIEW_BROWSER_GFX_PARENT_OUTPUT_SURFACE_H_
 #define ANDROID_WEBVIEW_BROWSER_GFX_PARENT_OUTPUT_SURFACE_H_
 
+#include "android_webview/browser/gfx/aw_gl_surface.h"
 #include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
 #include "components/viz/service/display/output_surface.h"
 
+namespace gfx {
+struct PresentationFeedback;
+}
+
 namespace android_webview {
 class AwRenderThreadContextProvider;
 
 class ParentOutputSurface : public viz::OutputSurface {
  public:
-  explicit ParentOutputSurface(
+  ParentOutputSurface(
+      scoped_refptr<AwGLSurface> gl_surface,
       scoped_refptr<AwRenderThreadContextProvider> context_provider);
   ~ParentOutputSurface() override;
 
@@ -39,6 +47,13 @@
   unsigned UpdateGpuFence() override;
 
  private:
+  void OnPresentation(const gfx::PresentationFeedback& feedback);
+
+  viz::OutputSurfaceClient* client_ = nullptr;
+  // This is really a layering violation but needed for hooking up presentation
+  // feedbacks properly.
+  scoped_refptr<AwGLSurface> gl_surface_;
+  base::WeakPtrFactory<ParentOutputSurface> weak_ptr_factory_;
   DISALLOW_COPY_AND_ASSIGN(ParentOutputSurface);
 };
 
diff --git a/android_webview/browser/gfx/surfaces_instance.cc b/android_webview/browser/gfx/surfaces_instance.cc
index de1fd24c..3f183be8 100644
--- a/android_webview/browser/gfx/surfaces_instance.cc
+++ b/android_webview/browser/gfx/surfaces_instance.cc
@@ -8,7 +8,6 @@
 #include <memory>
 #include <utility>
 
-#include "android_webview/browser/gfx/aw_gl_surface.h"
 #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"
@@ -103,17 +102,17 @@
       enable_vulkan ? AwVulkanContextProvider::GetOrCreateInstance() : nullptr;
   std::unique_ptr<viz::OutputSurface> output_surface;
   viz::SkiaOutputSurface* skia_output_surface = nullptr;
+  gl_surface_ = base::MakeRefCounted<AwGLSurface>();
   if (settings.use_skia_renderer || settings.use_skia_renderer_non_ddl) {
     auto* task_executor = DeferredGpuCommandService::GetInstance();
     if (!shared_context_state_) {
-      auto surface = base::MakeRefCounted<AwGLSurface>();
       auto gl_context =
           gl::init::CreateGLContext(task_executor->share_group().get(),
-                                    surface.get(), gl::GLContextAttribs());
-      gl_context->MakeCurrent(surface.get());
+                                    gl_surface_.get(), gl::GLContextAttribs());
+      gl_context->MakeCurrent(gl_surface_.get());
       shared_context_state_ = base::MakeRefCounted<gpu::SharedContextState>(
-          task_executor->share_group(), std::move(surface),
-          std::move(gl_context), false /* use_virtualized_gl_contexts */,
+          task_executor->share_group(), gl_surface_, std::move(gl_context),
+          false /* use_virtualized_gl_contexts */,
           base::BindOnce(&OnContextLost), vulkan_context_provider.get());
       shared_context_state_->InitializeGrContext(
           gpu::GpuDriverBugWorkarounds(task_executor->gpu_feature_info()
@@ -122,24 +121,21 @@
     }
     if (settings.use_skia_renderer_non_ddl) {
       output_surface = std::make_unique<viz::SkiaOutputSurfaceImplNonDDL>(
-          base::MakeRefCounted<AwGLSurface>(), shared_context_state_,
-          task_executor->mailbox_manager(),
+          gl_surface_, shared_context_state_, task_executor->mailbox_manager(),
           task_executor->shared_image_manager(),
           task_executor->sync_point_manager(),
           false /* need_swapbuffers_ack */);
     } else {
       output_surface = std::make_unique<viz::SkiaOutputSurfaceImpl>(
-          task_executor, base::MakeRefCounted<AwGLSurface>(),
-          shared_context_state_);
+          task_executor, gl_surface_, shared_context_state_);
     }
     skia_output_surface =
         static_cast<viz::SkiaOutputSurface*>(output_surface.get());
   } else {
     auto context_provider = AwRenderThreadContextProvider::Create(
-        base::MakeRefCounted<AwGLSurface>(),
-        DeferredGpuCommandService::GetInstance());
-    output_surface =
-        std::make_unique<ParentOutputSurface>(std::move(context_provider));
+        gl_surface_, DeferredGpuCommandService::GetInstance());
+    output_surface = std::make_unique<ParentOutputSurface>(
+        gl_surface_, std::move(context_provider));
   }
 
   begin_frame_source_ = std::make_unique<viz::StubBeginFrameSource>();
@@ -248,7 +244,7 @@
   display_->Resize(viewport);
   display_->DrawAndSwap();
   display_->DidReceiveSwapBuffersAck();
-  display_->DidReceivePresentationFeedback(gfx::PresentationFeedback(
+  gl_surface_->MaybeDidPresent(gfx::PresentationFeedback(
       base::TimeTicks::Now(), base::TimeDelta(), 0 /* flags */));
 }
 
diff --git a/android_webview/browser/gfx/surfaces_instance.h b/android_webview/browser/gfx/surfaces_instance.h
index 2e39f8e0..837114a 100644
--- a/android_webview/browser/gfx/surfaces_instance.h
+++ b/android_webview/browser/gfx/surfaces_instance.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <vector>
 
+#include "android_webview/browser/gfx/aw_gl_surface.h"
 #include "base/memory/ref_counted.h"
 #include "components/viz/common/presentation_feedback_map.h"
 #include "components/viz/common/surfaces/frame_sink_id.h"
@@ -90,6 +91,7 @@
 
   viz::FrameSinkId frame_sink_id_;
 
+  scoped_refptr<AwGLSurface> gl_surface_;
   std::unique_ptr<viz::FrameSinkManagerImpl> frame_sink_manager_;
   std::unique_ptr<viz::BeginFrameSource> begin_frame_source_;
   std::unique_ptr<viz::Display> display_;
diff --git a/android_webview/java/src/org/chromium/android_webview/AwContents.java b/android_webview/java/src/org/chromium/android_webview/AwContents.java
index 76f4df3..ce068430 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwContents.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwContents.java
@@ -458,8 +458,6 @@
     // when in this state.
     private boolean mTemporarilyDetached;
 
-    private Handler mHandler;
-
     // True when this AwContents has been destroyed.
     // Do not use directly, call isDestroyed() instead.
     private boolean mIsDestroyed;
@@ -893,7 +891,6 @@
             mContainerView = containerView;
             mContainerView.setWillNotDraw(false);
 
-            mHandler = new Handler();
             mContext = context;
             mAutofillProvider = dependencyFactory.createAutofillProvider(context, mContainerView);
             mAppTargetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
@@ -1430,7 +1427,7 @@
         }
         mIsNoOperation = true;
         mIsDestroyed = true;
-        mHandler.post(() -> destroyNatives());
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> destroyNatives());
     }
 
     /**
@@ -2596,7 +2593,7 @@
                 // application callback is executed without any native code on the stack. This
                 // so that any exception thrown by the application callback won't have to be
                 // propagated through a native call stack.
-                mHandler.post(() -> callback.onResult(jsonResult));
+                PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> callback.onResult(jsonResult));
             };
         }
 
@@ -2839,7 +2836,7 @@
         // To prevent this flip of CVC visibility, post the task to update CVC
         // visibility during attach, detach and window visibility change.
         mIsUpdateVisibilityTaskPending = true;
-        mHandler.post(mUpdateVisibilityRunnable);
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, mUpdateVisibilityRunnable);
     }
 
     private void updateWebContentsVisibility() {
@@ -3175,7 +3172,7 @@
         if (isDestroyedOrNoOperation(NO_WARN)) return;
         // Posting avoids invoking the callback inside invoking_composite_
         // (see synchronous_compositor_impl.cc and crbug/452530).
-        mHandler.post(() -> callback.onComplete(requestId));
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> callback.onComplete(requestId));
     }
 
     // Called as a result of nativeUpdateLastHitTestData.
diff --git a/android_webview/system_webview_apk_tmpl.gni b/android_webview/system_webview_apk_tmpl.gni
index fd6adf5..100688b 100644
--- a/android_webview/system_webview_apk_tmpl.gni
+++ b/android_webview/system_webview_apk_tmpl.gni
@@ -99,9 +99,7 @@
     # Used as an additional apk in test scripts.
     never_incremental = true
 
-    if (is_java_debug) {
-      enable_multidex = true
-    } else {
+    if (!is_java_debug) {
       proguard_enabled = true
       if (!defined(proguard_configs)) {
         proguard_configs = []
diff --git a/android_webview/test/BUILD.gn b/android_webview/test/BUILD.gn
index 73d1ab0a..f237185 100644
--- a/android_webview/test/BUILD.gn
+++ b/android_webview/test/BUILD.gn
@@ -81,8 +81,6 @@
       rebase_path("$root_gen_dir/CHROME_VERSION.json", root_build_dir)
   native_lib_version_arg = "@FileArg($_native_lib_file:full-quoted)"
 
-  enable_multidex = true
-
   command_line_flags_file = "android-webview-command-line"
 }
 
@@ -310,9 +308,6 @@
 }
 
 test("android_webview_unittests") {
-  # Dependencies (e.g. Play services) make the binary reach the dex limit.
-  enable_multidex = true
-
   deps = [
     ":android_webview_unittests_assets",
     ":android_webview_unittests_java",
diff --git a/android_webview/tools/system_webview_shell/BUILD.gn b/android_webview/tools/system_webview_shell/BUILD.gn
index 65f836b..4aa2487 100644
--- a/android_webview/tools/system_webview_shell/BUILD.gn
+++ b/android_webview/tools/system_webview_shell/BUILD.gn
@@ -58,7 +58,6 @@
     "//third_party/android_support_test_runner:runner_java",
     "//third_party/junit",
   ]
-  enable_multidex = true
 }
 
 instrumentation_test_apk("system_webview_shell_layout_test_apk") {
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 804ca77f..8e4fd0b 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -618,6 +618,7 @@
     "profiler/native_stack_sampler_win.cc",
     "profiler/stack_sampling_profiler.cc",
     "profiler/stack_sampling_profiler.h",
+    "profiler/unwind_result.h",
     "rand_util.cc",
     "rand_util.h",
     "rand_util_nacl.cc",
diff --git a/base/android/java/src/org/chromium/base/multidex/ChromiumMultiDexInstaller.java b/base/android/java/src/org/chromium/base/multidex/ChromiumMultiDexInstaller.java
index 5588ec5b..a421341 100644
--- a/base/android/java/src/org/chromium/base/multidex/ChromiumMultiDexInstaller.java
+++ b/base/android/java/src/org/chromium/base/multidex/ChromiumMultiDexInstaller.java
@@ -44,10 +44,13 @@
      */
     @VisibleForTesting
     public static void install(Context context) {
+        // No-op on platforms that support multidex natively.
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            return;
+        }
         // TODO(jbudorick): Back out this version check once support for K & below works.
         // http://crbug.com/512357
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP
-                && !shouldInstallMultiDex(context)) {
+        if (!shouldInstallMultiDex(context)) {
             Log.i(TAG, "Skipping multidex installation: not needed for process.");
         } else {
             MultiDex.install(context);
diff --git a/base/profiler/OWNERS b/base/profiler/OWNERS
index 81ff9fa..db15beea 100644
--- a/base/profiler/OWNERS
+++ b/base/profiler/OWNERS
@@ -1,5 +1,2 @@
-# Stack sampling profiler
-per-file native_stack_sampler*=wittman@chromium.org
-per-file stack_sampling_profiler*=wittman@chromium.org
-per-file test_support_library*=wittman@chromium.org
-per-file win32_stack_frame_unwinder*=wittman@chromium.org
+wittman@chromium.org
+charliea@chromium.org
diff --git a/base/profiler/native_stack_sampler_win.cc b/base/profiler/native_stack_sampler_win.cc
index 67d9cd9..4eda475 100644
--- a/base/profiler/native_stack_sampler_win.cc
+++ b/base/profiler/native_stack_sampler_win.cc
@@ -19,6 +19,7 @@
 #include "base/logging.h"
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
+#include "base/profiler/unwind_result.h"
 #include "base/profiler/win32_stack_frame_unwinder.h"
 #include "base/sampling_heap_profiler/module_cache.h"
 #include "base/stl_util.h"
@@ -79,54 +80,63 @@
   return basic_info.Teb;
 }
 
-#if defined(_WIN64)
 // If the value at |pointer| points to the original stack, rewrite it to point
 // to the corresponding location in the copied stack.
-void RewritePointerIfInOriginalStack(uintptr_t top,
-                                     uintptr_t bottom,
-                                     void* stack_copy,
-                                     const void** pointer) {
-  const auto value = reinterpret_cast<uintptr_t>(*pointer);
-  if (value >= bottom && value < top) {
-    *pointer = reinterpret_cast<const void*>(
-        static_cast<unsigned char*>(stack_copy) + (value - bottom));
-  }
-}
-#endif
+//
+// IMPORTANT NOTE: This function is invoked while the target thread is
+// suspended so it must not do any allocation from the default heap, including
+// indirectly via use of DCHECK/CHECK or other logging statements. Otherwise
+// this code can deadlock on heap locks in the default heap acquired by the
+// target thread before it was suspended.
+uintptr_t RewritePointerIfInOriginalStack(
+    const uintptr_t* original_stack_bottom,
+    const uintptr_t* original_stack_top,
+    const uintptr_t* stack_copy_bottom,
+    uintptr_t pointer) {
+  auto original_stack_bottom_uint =
+      reinterpret_cast<uintptr_t>(original_stack_bottom);
+  auto original_stack_top_uint =
+      reinterpret_cast<uintptr_t>(original_stack_top);
+  auto stack_copy_bottom_uint = reinterpret_cast<uintptr_t>(stack_copy_bottom);
 
-void CopyMemoryFromStack(void* to, const void* from, size_t length)
-    NO_SANITIZE("address") {
-#if defined(ADDRESS_SANITIZER)
-  // The following loop is an inlined version of memcpy. The code must be
-  // inlined to avoid instrumentation when using ASAN (memory sanitizer). The
-  // stack profiler is generating false positive when walking the stack.
-  for (size_t pos = 0; pos < length; ++pos)
-    reinterpret_cast<char*>(to)[pos] = reinterpret_cast<const char*>(from)[pos];
-#else
-  std::memcpy(to, from, length);
-#endif
+  if (pointer < original_stack_bottom_uint ||
+      pointer >= original_stack_top_uint)
+    return pointer;
+
+  return stack_copy_bottom_uint + (pointer - original_stack_bottom_uint);
 }
 
-// Rewrites possible pointers to locations within the stack to point to the
-// corresponding locations in the copy, and rewrites the non-volatile registers
-// in |context| likewise. This is necessary to handle stack frames with dynamic
-// stack allocation, where a pointer to the beginning of the dynamic allocation
-// area is stored on the stack and/or in a non-volatile register.
+// Copies the stack to a buffer while rewriting possible pointers to locations
+// within the stack to point to the corresponding locations in the copy. This is
+// necessary to handle stack frames with dynamic stack allocation, where a
+// pointer to the beginning of the dynamic allocation area is stored on the
+// stack and/or in a non-volatile register.
 //
 // Eager rewriting of anything that looks like a pointer to the stack, as done
 // in this function, does not adversely affect the stack unwinding. The only
 // other values on the stack the unwinding depends on are return addresses,
 // which should not point within the stack memory. The rewriting is guaranteed
 // to catch all pointers because the stacks are guaranteed by the ABI to be
-// sizeof(void*) aligned.
+// sizeof(uintptr_t*) aligned.
 //
-// Note: this function must not access memory in the original stack as it may
-// have been changed or deallocated by this point. This is why |top| and
-// |bottom| are passed as uintptr_t.
-void RewritePointersToStackMemory(uintptr_t top,
-                                  uintptr_t bottom,
-                                  CONTEXT* context,
-                                  void* stack_copy) {
+// IMPORTANT NOTE: This function is invoked while the target thread is
+// suspended so it must not do any allocation from the default heap, including
+// indirectly via use of DCHECK/CHECK or other logging statements. Otherwise
+// this code can deadlock on heap locks in the default heap acquired by the
+// target thread before it was suspended.
+void CopyStackContentsAndRewritePointers(const uintptr_t* original_stack_bottom,
+                                         const uintptr_t* original_stack_top,
+                                         uintptr_t* stack_copy_bottom,
+                                         CONTEXT* thread_context)
+    NO_SANITIZE("address") {
+  const uintptr_t* src = original_stack_bottom;
+  uintptr_t* dst = stack_copy_bottom;
+  for (; src < original_stack_top; ++src, ++dst) {
+    *dst = RewritePointerIfInOriginalStack(
+        original_stack_bottom, original_stack_top, stack_copy_bottom, *src);
+  }
+
+  // Rewrite pointers in the context.
 #if defined(ARCH_CPU_64_BITS)
   DWORD64 CONTEXT::*const nonvolatile_registers[] = {
 #if defined(ARCH_CPU_X86_64)
@@ -141,19 +151,11 @@
 #endif
   };
 
-  // Rewrite pointers in the context.
-  for (size_t i = 0; i < size(nonvolatile_registers); ++i) {
-    DWORD64* const reg = &(context->*nonvolatile_registers[i]);
-    RewritePointerIfInOriginalStack(top, bottom, stack_copy,
-                                    reinterpret_cast<const void**>(reg));
+  for (auto reg_field : nonvolatile_registers) {
+    DWORD64* const reg = &(thread_context->*reg_field);
+    *reg = RewritePointerIfInOriginalStack(
+        original_stack_bottom, original_stack_top, stack_copy_bottom, *reg);
   }
-
-  // Rewrite pointers on the stack.
-  const void** start = reinterpret_cast<const void**>(stack_copy);
-  const void** end = reinterpret_cast<const void**>(
-      reinterpret_cast<char*>(stack_copy) + (top - bottom));
-  for (const void** loc = start; loc < end; ++loc)
-    RewritePointerIfInOriginalStack(top, bottom, stack_copy, loc);
 #endif
 }
 
@@ -275,7 +277,11 @@
   // the frames.
   std::vector<Frame> WalkStack(CONTEXT* thread_context);
 
-  std::vector<Frame> RecordStack(CONTEXT* context);
+  // Attempts to walk native frames in the stack represented by
+  // |thread_context|, appending frames to |stack|. Returns a result indicating
+  // the disposition of the unwinding.
+  UnwindResult WalkNativeFrames(CONTEXT* thread_context,
+                                std::vector<Frame>* stack);
 
   win::ScopedHandle thread_handle_;
 
@@ -334,99 +340,96 @@
                                       StackBuffer* stack_buffer,
                                       ProfileBuilder* profile_builder,
                                       CONTEXT* thread_context) {
-  // The stack bounds are saved to uintptr_ts for use outside
-  // ScopedSuspendThread, as the thread's memory is not safe to dereference
-  // beyond that point.
-  const auto top = reinterpret_cast<uintptr_t>(base_address);
-  uintptr_t bottom = 0u;
-
+  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cpu_profiler.debug"),
+               "SuspendThread");
   {
-    TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cpu_profiler.debug"),
-                 "SuspendThread");
-    {
-      ScopedSuspendThread suspend_thread(thread_handle);
+    ScopedSuspendThread suspend_thread(thread_handle);
 
-      if (!suspend_thread.was_successful())
-        return false;
+    if (!suspend_thread.was_successful())
+      return false;
 
-      if (!::GetThreadContext(thread_handle, thread_context))
-        return false;
+    if (!::GetThreadContext(thread_handle, thread_context))
+      return false;
+
+    const uintptr_t top = reinterpret_cast<uintptr_t>(base_address);
 
 #if defined(ARCH_CPU_X86_64)
-      bottom = thread_context->Rsp;
+    const uintptr_t bottom = thread_context->Rsp;
 #elif defined(ARCH_CPU_ARM64)
-      bottom = thread_context->Sp;
+    const uintptr_t bottom = thread_context->Sp;
 #else
-      bottom = thread_context->Esp;
+    const uintptr_t bottom = thread_context->Esp;
 #endif
 
-      if ((top - bottom) > stack_buffer->size())
-        return false;
+    if ((top - bottom) > stack_buffer->size())
+      return false;
 
-      // Dereferencing a pointer in the guard page in a thread that doesn't own
-      // the stack results in a STATUS_GUARD_PAGE_VIOLATION exception and a
-      // crash. This occurs very rarely, but reliably over the population.
-      if (PointsToGuardPage(bottom))
-        return false;
+    // Dereferencing a pointer in the guard page in a thread that doesn't own
+    // the stack results in a STATUS_GUARD_PAGE_VIOLATION exception and a
+    // crash. This occurs very rarely, but reliably over the population.
+    if (PointsToGuardPage(bottom))
+      return false;
 
-      profile_builder->RecordMetadata();
+    profile_builder->RecordMetadata();
 
-      CopyMemoryFromStack(stack_buffer->buffer(),
-                          reinterpret_cast<const void*>(bottom), top - bottom);
-    }
+    CopyStackContentsAndRewritePointers(
+        reinterpret_cast<uintptr_t*>(bottom), reinterpret_cast<uintptr_t*>(top),
+        reinterpret_cast<uintptr_t*>(stack_buffer->buffer()), thread_context);
   }
 
-  RewritePointersToStackMemory(top, bottom, thread_context,
-                               stack_buffer->buffer());
-
   return true;
 }
 
 std::vector<Frame> NativeStackSamplerWin::WalkStack(CONTEXT* thread_context) {
   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cpu_profiler.debug"), "WalkStack");
-  return RecordStack(thread_context);
-}
-
-// Walks the stack represented by |context| from the current frame downwards,
-// recording the instruction pointer and associated module for each frame.
-std::vector<Frame> NativeStackSamplerWin::RecordStack(CONTEXT* context) {
   std::vector<Frame> stack;
-
   // Reserve enough memory for most stacks, to avoid repeated
   // allocations. Approximately 99.9% of recorded stacks are 128 frames or
   // fewer.
   stack.reserve(128);
 
+  WalkNativeFrames(thread_context, &stack);
+
+  return stack;
+}
+
+UnwindResult NativeStackSamplerWin::WalkNativeFrames(
+    CONTEXT* thread_context,
+    std::vector<Frame>* stack) {
   Win32StackFrameUnwinder frame_unwinder;
-  while (ContextPC(context)) {
+  while (ContextPC(thread_context)) {
     const ModuleCache::Module* const module =
-        module_cache_->GetModuleForAddress(ContextPC(context));
+        module_cache_->GetModuleForAddress(ContextPC(thread_context));
 
     if (!module) {
       // There's no loaded module containing the instruction pointer. This can
-      // be due to executing code that is not in a module. In particular,
-      // runtime-generated code associated with third-party injected DLLs
-      // typically is not in a module. It can also be due to the the module
-      // having been unloaded since we recorded the stack.  In the latter case
-      // the function unwind information was part of the unloaded module, so
-      // it's not possible to unwind further.
+      // be due to executing code that is not in a module (e.g. V8 generated
+      // code or runtime-generated code associated with third-party injected
+      // DLLs). It can also be due to the the module having been unloaded since
+      // we recorded the stack.  In the latter case the function unwind
+      // information was part of the unloaded module, so it's not possible to
+      // unwind further.
       //
       // If a module was found, it's still theoretically possible for the
       // detected module module to be different than the one that was loaded
       // when the stack was copied (i.e. if the module was unloaded and a
       // different module loaded in overlapping memory). This likely would cause
       // a crash, but has not been observed in practice.
-      break;
+      //
+      // We return UNRECOGNIZED_FRAME on the optimistic assumption that this may
+      // be a frame the AuxUnwinder knows how to handle (e.g. a frame in V8
+      // generated code).
+      return UnwindResult::UNRECOGNIZED_FRAME;
     }
 
     // Record the current frame.
-    stack.emplace_back(ContextPC(context), module);
+    stack->emplace_back(ContextPC(thread_context), module);
 
-    if (!frame_unwinder.TryUnwind(context, module))
-      break;
+    if (!frame_unwinder.TryUnwind(thread_context, module))
+      return UnwindResult::ABORTED;
   }
 
-  return stack;
+  return UnwindResult::COMPLETED;
 }
 
 // NativeStackSampler ---------------------------------------------------------
diff --git a/base/profiler/unwind_result.h b/base/profiler/unwind_result.h
new file mode 100644
index 0000000..9aeaec8
--- /dev/null
+++ b/base/profiler/unwind_result.h
@@ -0,0 +1,25 @@
+// 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 BASE_PROFILER_UNWIND_RESULT_H_
+#define BASE_PROFILER_UNWIND_RESULT_H_
+
+namespace base {
+
+// The result of attempting to unwind stack frames.
+enum class UnwindResult {
+  // The end of the stack was reached successfully.
+  COMPLETED,
+
+  // The walk reached a frame that it doesn't know how to unwind, but might be
+  // unwindable by the other native/aux unwinder.
+  UNRECOGNIZED_FRAME,
+
+  // The walk was aborted and is not resumable.
+  ABORTED,
+};
+
+}  // namespace base
+
+#endif  // BASE_PROFILER_UNWIND_RESULT_H_
diff --git a/base/test/test_suite.cc b/base/test/test_suite.cc
index 7b895ff..33bd735 100644
--- a/base/test/test_suite.cc
+++ b/base/test/test_suite.cc
@@ -248,6 +248,8 @@
   // have the locale set. In the absence of such a call the "C" locale is the
   // default. In the gtk code (below) gtk_init() implicitly sets a locale.
   setlocale(LC_ALL, "");
+  // We still need number to string conversions to be locale insensitive.
+  setlocale(LC_NUMERIC, "C");
 #endif  // defined(OS_LINUX) && defined(USE_AURA)
 
   // On Android, AtExitManager is created in
diff --git a/build/android/stacktrace/crashpad_stackwalker.py b/build/android/stacktrace/crashpad_stackwalker.py
index f33d19eb..dc3fd89 100755
--- a/build/android/stacktrace/crashpad_stackwalker.py
+++ b/build/android/stacktrace/crashpad_stackwalker.py
@@ -23,6 +23,7 @@
 sys.path.append(_BUILD_ANDROID_PATH)
 import devil_chromium
 from devil.android import device_utils
+from devil.utils import timeout_retry
 
 
 def _CreateSymbolsDir(build_path, dynamic_library_names):
@@ -139,7 +140,12 @@
 
   device_crashpad_path = posixpath.join(args.chrome_cache_path, 'Crashpad',
                                         'pending')
-  crashpad_file = _ChooseLatestCrashpadDump(device, device_crashpad_path)
+
+  def CrashpadDumpExists():
+    return _ChooseLatestCrashpadDump(device, device_crashpad_path)
+
+  crashpad_file = timeout_retry.WaitFor(
+      CrashpadDumpExists, wait_period=1, max_tries=9)
   if not crashpad_file:
     logging.error('Could not locate a crashpad dump')
     return 1
diff --git a/build/config/android/internal_rules.gni b/build/config/android/internal_rules.gni
index cd1e130..6e353a2dc 100644
--- a/build/config/android/internal_rules.gni
+++ b/build/config/android/internal_rules.gni
@@ -1115,7 +1115,7 @@
     _proguarding_with_r8 =
         _proguard_enabled && (!defined(invoker.proguard_jar_path) || use_r8)
     _enable_multidex =
-        defined(invoker.enable_multidex) && invoker.enable_multidex
+        !defined(invoker.enable_multidex) || invoker.enable_multidex
     _enable_main_dex_list =
         _enable_multidex &&
         (!defined(invoker.min_sdk_version) || invoker.min_sdk_version < 21)
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index cecb1c4..268e1a6 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -2074,7 +2074,7 @@
     _is_base_module = defined(invoker.is_base_module) && invoker.is_base_module
 
     _enable_multidex =
-        defined(invoker.enable_multidex) && invoker.enable_multidex
+        !defined(invoker.enable_multidex) || invoker.enable_multidex
     _final_dex_path = "$_gen_dir/classes.dex.zip"
 
     if (defined(invoker.final_apk_path)) {
@@ -2619,7 +2619,7 @@
         if (defined(invoker.proguard_configs)) {
           proguard_configs += invoker.proguard_configs
         }
-        if (_enable_multidex) {
+        if (_enable_main_dex_list) {
           proguard_configs += [ "//build/android/multidex.flags" ]
         }
         proguard_mapping_path = _proguard_mapping_path
@@ -4027,7 +4027,7 @@
     _proguard_enabled =
         defined(invoker.proguard_enabled) && invoker.proguard_enabled
     _enable_multidex =
-        defined(invoker.enable_multidex) && invoker.enable_multidex
+        !defined(invoker.enable_multidex) || invoker.enable_multidex
 
     if (!_proguard_enabled && defined(invoker.min_sdk_version)) {
       not_needed(invoker, [ "min_sdk_version" ])
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 30a9d14..83a07e1 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-e4d6115c51ae86eedf493f0746088711929eb53f
\ No newline at end of file
+eaac7b7f5bb54fa74873c9d37796702e49e98987
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index c14a9ab..7641efa 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-7774f66f87ee66307e0277bf2d6f99614cb67ada
\ No newline at end of file
+4186125ff0046e4fe61888e0ecb60eae34e42327
\ No newline at end of file
diff --git a/buildtools/DEPS b/buildtools/DEPS
index 5cda918..4decb894 100644
--- a/buildtools/DEPS
+++ b/buildtools/DEPS
@@ -18,7 +18,7 @@
 
   # When changing these, also update the svn revisions in deps_revisions.gni
   'clang_format_revision': '96636aa0e9f047f17447f2d45a094d0b59ed7917',
-  'libcxx_revision':       '9009625c821e9580bfece732c25bac1cc9c5a7c2',
+  'libcxx_revision':       '4daecde1d737da594173591db71e543a3e2d51d8',
   'libcxxabi_revision':    '0d529660e32d77d9111912d73f2c74fc5fa2a858',
   'libunwind_revision':    '69d9b84cca8354117b9fe9705a4430d789ee599b',
 }
diff --git a/buildtools/deps_revisions.gni b/buildtools/deps_revisions.gni
index 0222020f..9e705a3 100644
--- a/buildtools/deps_revisions.gni
+++ b/buildtools/deps_revisions.gni
@@ -5,5 +5,5 @@
 declare_args() {
   # The libc++ svn revision that belongs to the git hash in DEPS. Used to cause
   # full rebuilds on libc++ rolls.
-  libcxx_svn_revision = "356574"
+  libcxx_svn_revision = "356640"
 }
diff --git a/cc/paint/paint_worklet_layer_painter.h b/cc/paint/paint_worklet_layer_painter.h
index c1a8e09..6535432b 100644
--- a/cc/paint/paint_worklet_layer_painter.h
+++ b/cc/paint/paint_worklet_layer_painter.h
@@ -10,11 +10,13 @@
 
 namespace cc {
 
+class PaintWorkletInput;
+
 class CC_EXPORT PaintWorkletLayerPainter {
  public:
   virtual ~PaintWorkletLayerPainter() {}
 
-  virtual sk_sp<PaintRecord> Paint() = 0;
+  virtual sk_sp<PaintRecord> Paint(PaintWorkletInput*) = 0;
 };
 
 }  // namespace cc
diff --git a/cc/paint/skia_paint_canvas.cc b/cc/paint/skia_paint_canvas.cc
index b5c5743..f80f7a1b1 100644
--- a/cc/paint/skia_paint_canvas.cc
+++ b/cc/paint/skia_paint_canvas.cc
@@ -9,10 +9,7 @@
 #include "cc/paint/paint_recorder.h"
 #include "cc/paint/scoped_raster_flags.h"
 #include "third_party/skia/include/core/SkAnnotation.h"
-#include "third_party/skia/include/core/SkColorSpaceXformCanvas.h"
-#include "third_party/skia/include/core/SkRegion.h"
 #include "third_party/skia/include/gpu/GrContext.h"
-#include "third_party/skia/include/utils/SkNWayCanvas.h"
 
 namespace cc {
 
@@ -36,27 +33,8 @@
                                  const SkSurfaceProps& props)
     : canvas_(new SkCanvas(bitmap, props)), owned_(canvas_) {}
 
-SkiaPaintCanvas::SkiaPaintCanvas(SkCanvas* canvas,
-                                 sk_sp<SkColorSpace> target_color_space,
-                                 ImageProvider* image_provider,
-                                 ContextFlushes context_flushes)
-    : canvas_(canvas),
-      image_provider_(image_provider),
-      context_flushes_(context_flushes) {
-  WrapCanvasInColorSpaceXformCanvas(target_color_space);
-}
-
 SkiaPaintCanvas::~SkiaPaintCanvas() = default;
 
-void SkiaPaintCanvas::WrapCanvasInColorSpaceXformCanvas(
-    sk_sp<SkColorSpace> target_color_space) {
-  if (target_color_space) {
-    color_space_xform_canvas_ =
-        SkCreateColorSpaceXformCanvas(canvas_, target_color_space);
-    canvas_ = color_space_xform_canvas_.get();
-  }
-}
-
 SkImageInfo SkiaPaintCanvas::imageInfo() const {
   return canvas_->imageInfo();
 }
diff --git a/cc/paint/skia_paint_canvas.h b/cc/paint/skia_paint_canvas.h
index 3f32da0..ff62157c 100644
--- a/cc/paint/skia_paint_canvas.h
+++ b/cc/paint/skia_paint_canvas.h
@@ -143,13 +143,10 @@
       PlaybackParams::CustomDataRasterCallback custom_raster_callback);
 
  private:
-  void WrapCanvasInColorSpaceXformCanvas(
-      sk_sp<SkColorSpace> target_color_space);
   void FlushAfterDrawIfNeeded();
 
   SkCanvas* canvas_;
   std::unique_ptr<SkCanvas> owned_;
-  std::unique_ptr<SkCanvas> color_space_xform_canvas_;
   ImageProvider* image_provider_ = nullptr;
 
   const ContextFlushes context_flushes_;
diff --git a/cc/test/test_paint_worklet_layer_painter.cc b/cc/test/test_paint_worklet_layer_painter.cc
index 08bd271..35dd5a47 100644
--- a/cc/test/test_paint_worklet_layer_painter.cc
+++ b/cc/test/test_paint_worklet_layer_painter.cc
@@ -13,7 +13,7 @@
 
 TestPaintWorkletLayerPainter::~TestPaintWorkletLayerPainter() = default;
 
-sk_sp<PaintRecord> TestPaintWorkletLayerPainter::Paint() {
+sk_sp<PaintRecord> TestPaintWorkletLayerPainter::Paint(PaintWorkletInput*) {
   auto manual_record = sk_make_sp<PaintOpBuffer>();
   scoped_refptr<TestPaintWorkletInput> input =
       base::MakeRefCounted<TestPaintWorkletInput>(gfx::SizeF(100, 100));
diff --git a/cc/test/test_paint_worklet_layer_painter.h b/cc/test/test_paint_worklet_layer_painter.h
index 6662ac5f..74911ed 100644
--- a/cc/test/test_paint_worklet_layer_painter.h
+++ b/cc/test/test_paint_worklet_layer_painter.h
@@ -10,12 +10,14 @@
 
 namespace cc {
 
+class PaintWorkletInput;
+
 class TestPaintWorkletLayerPainter : public PaintWorkletLayerPainter {
  public:
   TestPaintWorkletLayerPainter();
   ~TestPaintWorkletLayerPainter() override;
 
-  sk_sp<PaintRecord> Paint() override;
+  sk_sp<PaintRecord> Paint(PaintWorkletInput*) override;
 };
 
 }  // namespace cc
diff --git a/cc/tiles/paint_worklet_image_cache.cc b/cc/tiles/paint_worklet_image_cache.cc
index 9eecf2b..b955b08 100644
--- a/cc/tiles/paint_worklet_image_cache.cc
+++ b/cc/tiles/paint_worklet_image_cache.cc
@@ -71,7 +71,8 @@
   // slow Paint function.
   // TODO(xidachen): ensure that the canvas operations in the PaintRecord
   // matches the PaintGeneratedImage::Draw.
-  sk_sp<PaintRecord> record = painter_->Paint();
+  sk_sp<PaintRecord> record =
+      painter_->Paint(paint_image.paint_worklet_input());
   {
     base::AutoLock hold(records_lock_);
     // It is possible for two or more threads to both pass through the first
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index b655242..25a548e3 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -1585,6 +1585,7 @@
                              "is_modern",
                              "module_name",
                              "target_type",
+                             "enable_multidex",
                            ])
 
     deps = _chrome_public_shared_deps
@@ -1636,6 +1637,7 @@
 chrome_public_apk_or_module_tmpl("chrome_public_apk") {
   target_type = "android_apk"
   apk_name = "ChromePublic"
+  enable_multidex = is_java_debug
 }
 
 chrome_public_apk_or_module_tmpl("chrome_modern_public_apk") {
@@ -1896,11 +1898,6 @@
       proguard_enabled = true
       proguard_configs = [ "//chrome/android/java/apk_for_test.flags" ]
     }
-
-    # The test APK contains code from both the APK under test and the
-    # test APK when proguard is enabled. That causes this APK to exceed
-    # the dex limit.
-    enable_multidex = true
   }
 }
 
@@ -1953,9 +1950,6 @@
       ":chrome_test_vr_java",
     ]
     proguard_enabled = false
-
-    # APK exceeds dex limit if not enabled
-    enable_multidex = true
   }
 }
 
@@ -1977,11 +1971,6 @@
     }
     alternative_android_sdk_dep = webview_framework_dep
 
-    # The test APK contains code from both the APK under test and the
-    # test APK when proguard is enabled. That causes this APK to exceed
-    # the dex limit.
-    enable_multidex = proguard_enabled
-
     never_incremental = true
   }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FreIntentCreator.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FreIntentCreator.java
index a799660..228ea6fd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FreIntentCreator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FreIntentCreator.java
@@ -52,20 +52,8 @@
                     associatedAppNameForLightweightFre, intentToLaunchAfterFreComplete,
                     requiresBroadcast);
         } else {
-            Intent freIntent = createGenericFirstRunIntent(
+            return createGenericFirstRunIntent(
                     caller, fromIntent, intentToLaunchAfterFreComplete, requiresBroadcast);
-
-            if (shouldSwitchToTabbedMode(caller)) {
-                freIntent.setClass(caller, TabbedModeFirstRunActivity.class);
-
-                // We switched to TabbedModeFRE. We need to disable animation on the original
-                // intent, to make transition seamless.
-                intentToLaunchAfterFreComplete = new Intent(intentToLaunchAfterFreComplete);
-                intentToLaunchAfterFreComplete.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
-                addPendingIntent(
-                        caller, freIntent, intentToLaunchAfterFreComplete, requiresBroadcast);
-            }
-            return freIntent;
         }
     }
 
@@ -100,14 +88,24 @@
      */
     private static Intent createGenericFirstRunIntent(Context context, Intent fromIntent,
             Intent intentToLaunchAfterFreComplete, boolean requiresBroadcast) {
+        final Class<?> activityClass;
+        if (shouldSwitchToTabbedMode(context)) {
+            activityClass = TabbedModeFirstRunActivity.class;
+
+            // Going to launch TabbedModeFRE. Have to disable animation on the intent launched after
+            // the FRE is completed to make the transition seamless.
+            intentToLaunchAfterFreComplete = new Intent(intentToLaunchAfterFreComplete);
+            intentToLaunchAfterFreComplete.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+        } else {
+            activityClass = FirstRunActivity.class;
+        }
+
         Intent intent = new Intent();
-        intent.setClassName(context, FirstRunActivity.class.getName());
+        intent.setClass(context, activityClass);
         intent.putExtra(FirstRunActivity.EXTRA_COMING_FROM_CHROME_ICON,
                 TextUtils.equals(fromIntent.getAction(), Intent.ACTION_MAIN));
         intent.putExtra(FirstRunActivity.EXTRA_CHROME_LAUNCH_INTENT_IS_CCT,
                 LaunchIntentDispatcher.isCustomTabIntent(fromIntent));
-        addPendingIntent(context, intent, intentToLaunchAfterFreComplete, requiresBroadcast);
-
         // Copy extras bundle from intent which was used to launch Chrome. Copying the extras
         // enables the FirstRunActivity to locate the associated CustomTabsSession (if there
         // is one) and to notify the connection of whether the FirstRunActivity was completed.
@@ -117,6 +115,8 @@
             intent.putExtra(FirstRunActivity.EXTRA_CHROME_LAUNCH_INTENT_EXTRAS, copiedFromExtras);
         }
 
+        addPendingIntent(context, intent, intentToLaunchAfterFreComplete, requiresBroadcast);
+
         return intent;
     }
 
diff --git a/chrome/android/java/strings/android_chrome_strings.grd b/chrome/android/java/strings/android_chrome_strings.grd
index 71777725..de7e7f9 100644
--- a/chrome/android/java/strings/android_chrome_strings.grd
+++ b/chrome/android/java/strings/android_chrome_strings.grd
@@ -3505,7 +3505,7 @@
         Print
       </message>
       <message name="IDS_SEND_TAB_TO_SELF_SHARE_ACTIVITY_TITLE" desc="Title of the Send Tab To Self Share activity that will appear in the Android share dialog.">
-        Send to my devices
+        Send to your devices
       </message>
 
       <!-- Omnibox Autocomplete -->
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ActivityTabProviderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ActivityTabProviderTest.java
index 8c66dea..6c17812 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ActivityTabProviderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ActivityTabProviderTest.java
@@ -16,7 +16,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
@@ -29,6 +28,7 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.ChromeTabUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.common.ContentUrlConstants;
 import org.chromium.ui.test.util.UiRestriction;
 
@@ -121,13 +121,15 @@
         assertEquals("The activity tab should be the model's selected tab.", getModelSelectedTab(),
                 mActivityTab);
 
-        ThreadUtils.runOnUiThreadBlocking(() -> mActivity.getLayoutManager().showOverview(false));
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mActivity.getLayoutManager().showOverview(false));
         mActivityTabChangedHelper.waitForCallback(1);
         assertEquals("Entering the tab switcher should have triggered the event once.", 2,
                 mActivityTabChangedHelper.getCallCount());
         assertEquals("The activity tab should be null.", null, mActivityTab);
 
-        ThreadUtils.runOnUiThreadBlocking(() -> mActivity.getLayoutManager().hideOverview(false));
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mActivity.getLayoutManager().hideOverview(false));
         mActivityTabChangedHelper.waitForCallback(2);
         assertEquals("Exiting the tab switcher should have triggered the event once.", 3,
                 mActivityTabChangedHelper.getCallCount());
@@ -175,7 +177,7 @@
                 mActivityTab);
 
         int callCount = mActivityTabChangedHelper.getCallCount();
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             // Select the original tab without switching layouts.
             mActivity.getTabModelSelector().getCurrentModel().setIndex(
                     0, TabSelectionType.FROM_USER);
@@ -192,7 +194,7 @@
     @Feature({"ActivityTabObserver"})
     public void testTriggerOnLastTabClosed() throws InterruptedException, TimeoutException {
         int callCount = mActivityTabChangedHelper.getCallCount();
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> { mActivity.getTabModelSelector().closeTab(getModelSelectedTab()); });
         mActivityTabChangedHelper.waitForCallback(callCount);
 
@@ -221,7 +223,7 @@
         Tab activityTabBefore = mActivityTab;
 
         int callCount = mActivityTabChangedHelper.getCallCount();
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> { mActivity.getTabModelSelector().closeTab(startingTab); });
 
         assertEquals("The activity tab should not have changed.", activityTabBefore, mActivityTab);
@@ -269,10 +271,10 @@
 
         int sceneChangeCount = sceneChangeHelper.getCallCount();
         if (inSwitcher) {
-            ThreadUtils.runOnUiThreadBlocking(
+            TestThreadUtils.runOnUiThreadBlocking(
                     () -> mActivity.getLayoutManager().showOverview(true));
         } else {
-            ThreadUtils.runOnUiThreadBlocking(
+            TestThreadUtils.runOnUiThreadBlocking(
                     () -> mActivity.getLayoutManager().hideOverview(true));
         }
         sceneChangeHelper.waitForCallback(sceneChangeCount);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/BluetoothChooserDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/BluetoothChooserDialogTest.java
index b60c7b0..ace91f6 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/BluetoothChooserDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/BluetoothChooserDialogTest.java
@@ -21,7 +21,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.RetryOnFailure;
 import org.chromium.chrome.R;
@@ -31,6 +30,7 @@
 import org.chromium.components.security_state.ConnectionSecurityLevel;
 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 org.chromium.content_public.browser.test.util.TouchCommon;
 import org.chromium.ui.base.ActivityWindowAndroid;
 import org.chromium.ui.base.AndroidPermissionDelegate;
@@ -119,7 +119,7 @@
     }
 
     private BluetoothChooserDialogWithFakeNatives createDialog() {
-        return ThreadUtils.runOnUiThreadBlockingNoException(
+        return TestThreadUtils.runOnUiThreadBlockingNoException(
                 () -> {
                     mWindowAndroid = new ActivityWindowAndroid(mActivityTestRule.getActivity());
                     BluetoothChooserDialogWithFakeNatives dialog =
@@ -165,12 +165,12 @@
             return position >= visibleStart && position <= visibleEnd;
         };
 
-        if (!ThreadUtils.runOnUiThreadBlockingNoException(isVisible)) {
-            ThreadUtils.runOnUiThreadBlocking(() -> listView.setSelection(position));
+        if (!TestThreadUtils.runOnUiThreadBlockingNoException(isVisible)) {
+            TestThreadUtils.runOnUiThreadBlocking(() -> listView.setSelection(position));
             CriteriaHelper.pollUiThread(isVisible);
         }
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             TouchCommon.singleClickView(
                     listView.getChildAt(position - listView.getFirstVisiblePosition()));
         });
@@ -229,7 +229,7 @@
         final Button button = (Button) dialog.findViewById(R.id.positive);
         final View progress = dialog.findViewById(R.id.progress);
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             // Add non-connected device with no signal strength.
             mChooserDialog.addOrUpdateDevice("id-1", "Name 1", false /* isGATTConnected */,
                     -1 /* signalStrengthLevel */);
@@ -295,9 +295,8 @@
                 new TestAndroidPermissionDelegate(dialog);
         mWindowAndroid.setAndroidPermissionDelegate(permissionDelegate);
 
-        ThreadUtils.runOnUiThreadBlocking(
-                ()
-                        -> mChooserDialog.notifyDiscoveryState(
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mChooserDialog.notifyDiscoveryState(
                                 BluetoothChooserDialog.DiscoveryMode.DISCOVERY_FAILED_TO_START));
 
         Assert.assertEquals(removeLinkTags(mActivityTestRule.getActivity().getString(
@@ -311,7 +310,7 @@
         Assert.assertEquals(View.GONE, items.getVisibility());
         Assert.assertEquals(View.GONE, progress.getVisibility());
 
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> errorView.getClickableSpans()[0].onClick(errorView));
 
         // Permission was requested.
@@ -320,10 +319,10 @@
         Assert.assertNotNull(permissionDelegate.mCallback);
         // Grant permission.
         mLocationUtils.mLocationGranted = true;
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> permissionDelegate.mCallback.onRequestPermissionsResult(
-                        new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
-                        new int[]{PackageManager.PERMISSION_GRANTED}));
+                                new String[] {Manifest.permission.ACCESS_COARSE_LOCATION},
+                                new int[] {PackageManager.PERMISSION_GRANTED}));
 
         Assert.assertEquals(1, mChooserDialog.mRestartSearchCount);
         Assert.assertEquals(removeLinkTags(mActivityTestRule.getActivity().getString(
@@ -355,9 +354,8 @@
         mLocationUtils.mLocationGranted = true;
         mLocationUtils.mSystemLocationSettingsEnabled = false;
 
-        ThreadUtils.runOnUiThreadBlocking(
-                ()
-                        -> mChooserDialog.notifyDiscoveryState(
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mChooserDialog.notifyDiscoveryState(
                                 BluetoothChooserDialog.DiscoveryMode.DISCOVERY_FAILED_TO_START));
 
         Assert.assertEquals(removeLinkTags(mActivityTestRule.getActivity().getString(
@@ -373,10 +371,10 @@
 
         // Turn on Location Services.
         mLocationUtils.mSystemLocationSettingsEnabled = true;
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> mChooserDialog.mLocationModeBroadcastReceiver.onReceive(
-                        mActivityTestRule.getActivity(),
-                        new Intent(LocationManager.MODE_CHANGED_ACTION)));
+                                mActivityTestRule.getActivity(),
+                                new Intent(LocationManager.MODE_CHANGED_ACTION)));
 
         Assert.assertEquals(1, mChooserDialog.mRestartSearchCount);
         Assert.assertEquals(removeLinkTags(mActivityTestRule.getActivity().getString(
@@ -404,7 +402,7 @@
         final View progress = dialog.findViewById(R.id.progress);
 
         // Turn off adapter.
-        ThreadUtils.runOnUiThreadBlocking(() -> mChooserDialog.notifyAdapterTurnedOff());
+        TestThreadUtils.runOnUiThreadBlocking(() -> mChooserDialog.notifyAdapterTurnedOff());
 
         Assert.assertEquals(removeLinkTags(mActivityTestRule.getActivity().getString(
                                     R.string.bluetooth_adapter_off)),
@@ -418,7 +416,7 @@
         Assert.assertEquals(View.GONE, progress.getVisibility());
 
         // Turn on adapter.
-        ThreadUtils.runOnUiThreadBlocking(() -> itemChooser.signalInitializingAdapter());
+        TestThreadUtils.runOnUiThreadBlocking(() -> itemChooser.signalInitializingAdapter());
 
         Assert.assertEquals(View.GONE, errorView.getVisibility());
         Assert.assertEquals(View.GONE, items.getVisibility());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ChromeActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ChromeActivityTest.java
index f9322aa..846b41e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ChromeActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ChromeActivityTest.java
@@ -15,7 +15,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.RetryOnFailure;
 import org.chromium.chrome.browser.device.DeviceClassManager;
@@ -25,6 +24,7 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.net.test.EmbeddedTestServer;
 
 /**
@@ -62,7 +62,7 @@
     public void testTabVisibility() {
         // Create two tabs - tab[0] in the foreground and tab[1] in the background.
         final Tab[] tabs = new Tab[2];
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             // Foreground tab.
             ChromeTabCreator tabCreator =
                     mActivityTestRule.getActivity().getCurrentTabCreator();
@@ -80,19 +80,19 @@
         Assert.assertTrue(tabs[1].isHidden());
 
         // Fake sending the activity to background.
-        ThreadUtils.runOnUiThreadBlocking(() -> mActivityTestRule.getActivity().onPause());
-        ThreadUtils.runOnUiThreadBlocking(() -> mActivityTestRule.getActivity().onStop());
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(() -> mActivityTestRule.getActivity().onPause());
+        TestThreadUtils.runOnUiThreadBlocking(() -> mActivityTestRule.getActivity().onStop());
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> mActivityTestRule.getActivity().onWindowFocusChanged(false));
         // Verify that both Tabs are hidden.
         Assert.assertTrue(tabs[0].isHidden());
         Assert.assertTrue(tabs[1].isHidden());
 
         // Fake bringing the activity back to foreground.
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> mActivityTestRule.getActivity().onWindowFocusChanged(true));
-        ThreadUtils.runOnUiThreadBlocking(() -> mActivityTestRule.getActivity().onStart());
-        ThreadUtils.runOnUiThreadBlocking(() -> mActivityTestRule.getActivity().onResume());
+        TestThreadUtils.runOnUiThreadBlocking(() -> mActivityTestRule.getActivity().onStart());
+        TestThreadUtils.runOnUiThreadBlocking(() -> mActivityTestRule.getActivity().onResume());
         // Verify that the front tab is in the 'visible' state.
         Assert.assertFalse(tabs[0].isHidden());
         Assert.assertTrue(tabs[1].isHidden());
@@ -101,10 +101,8 @@
     @Test
     @SmallTest
     public void testTabAnimationsCorrectlyEnabled() {
-        boolean animationsEnabled = ThreadUtils.runOnUiThreadBlockingNoException(
-                () -> mActivityTestRule.getActivity()
-                        .getLayoutManager()
-                        .animationsEnabled());
+        boolean animationsEnabled = TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> mActivityTestRule.getActivity().getLayoutManager().animationsEnabled());
         Assert.assertEquals(animationsEnabled, DeviceClassManager.enableAnimations());
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ChromeHttpAuthHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ChromeHttpAuthHandlerTest.java
index c97cf69..ff0586a1 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ChromeHttpAuthHandlerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ChromeHttpAuthHandlerTest.java
@@ -14,7 +14,6 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.Callback;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Restriction;
@@ -25,6 +24,7 @@
 import org.chromium.chrome.test.util.ChromeTabUtils;
 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 org.chromium.content_public.common.ContentUrlConstants;
 import org.chromium.net.test.EmbeddedTestServer;
 
@@ -97,7 +97,7 @@
         ChromeTabUtils.newTabFromMenu(
                 InstrumentationRegistry.getInstrumentation(), mActivityTestRule.getActivity());
         // If the first tab was closed due to OOM, then just exit the test.
-        if (ThreadUtils.runOnUiThreadBlocking(
+        if (TestThreadUtils.runOnUiThreadBlocking(
                     () -> firstTab.isClosing() || SadTab.isShowing(firstTab))) {
             return;
         }
@@ -116,13 +116,13 @@
             handlerRef.set(handler);
             handlerCallback.notifyCalled();
         };
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> { ChromeHttpAuthHandler.setTestCreationCallback(callback); });
 
         String url = mTestServer.getURL("/auth-basic");
         ChromeTabUtils.loadUrlOnUiThread(tab, url);
         handlerCallback.waitForCallback();
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> { ChromeHttpAuthHandler.setTestCreationCallback(null); });
         return handlerRef.get();
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/CopylessPasteTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/CopylessPasteTest.java
index 03c4c6b..1692200 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/CopylessPasteTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/CopylessPasteTest.java
@@ -16,7 +16,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
@@ -30,6 +29,7 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.ChromeTabUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.url.mojom.Url;
 
@@ -68,14 +68,14 @@
 
         AppIndexingUtil.setCallbackForTesting(webpage -> mCallbackHelper.notifyCalled(webpage));
 
-        ThreadUtils.runOnUiThreadBlocking(() -> FirstRunStatus.setFirstRunFlowComplete(true));
+        TestThreadUtils.runOnUiThreadBlocking(() -> FirstRunStatus.setFirstRunFlowComplete(true));
         mActivityTestRule.startMainActivityOnBlankPage();
     }
 
     @After
     public void tearDown() throws Exception {
         mTestServer.stopAndDestroyServer();
-        ThreadUtils.runOnUiThreadBlocking(() -> FirstRunStatus.setFirstRunFlowComplete(false));
+        TestThreadUtils.runOnUiThreadBlocking(() -> FirstRunStatus.setFirstRunFlowComplete(false));
         AppIndexingUtil.setCallbackForTesting(null);
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ItemChooserDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ItemChooserDialogTest.java
index ca981d0..3b091d6d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ItemChooserDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ItemChooserDialogTest.java
@@ -22,7 +22,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.RetryOnFailure;
 import org.chromium.chrome.R;
@@ -30,6 +29,7 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 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 org.chromium.content_public.browser.test.util.TouchCommon;
 import org.chromium.ui.widget.TextViewWithClickableSpans;
 
@@ -96,7 +96,7 @@
         final ItemChooserDialog.ItemChooserLabels labels =
                 new ItemChooserDialog.ItemChooserLabels(title, searching, noneFound, statusActive,
                         statusIdleNoneFound, statusIdleSomeFound, positiveButton);
-        ItemChooserDialog dialog = ThreadUtils.runOnUiThreadBlockingNoException(() -> {
+        ItemChooserDialog dialog = TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
             ItemChooserDialog dialog1 = new ItemChooserDialog(
                     mActivityTestRule.getActivity(), ItemChooserDialogTest.this, labels);
             return dialog1;
@@ -155,7 +155,7 @@
     @Test
     @LargeTest
     public void testAddItemsWithNoIcons() throws Throwable {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Dialog dialog = mChooserDialog.getDialogForTesting();
             Assert.assertTrue(dialog.isShowing());
 
@@ -183,7 +183,7 @@
     @Test
     @LargeTest
     public void testAddItemsWithIcons() throws Throwable {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Dialog dialog = mChooserDialog.getDialogForTesting();
             Assert.assertTrue(dialog.isShowing());
 
@@ -219,7 +219,7 @@
     @Test
     @LargeTest
     public void testAddItemWithIconAfterItemWithNoIcon() throws Throwable {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Dialog dialog = mChooserDialog.getDialogForTesting();
             Assert.assertTrue(dialog.isShowing());
 
@@ -250,7 +250,7 @@
     @Test
     @LargeTest
     public void testAddItemWithNoIconAfterItemWithIcon() throws Throwable {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Dialog dialog = mChooserDialog.getDialogForTesting();
             Assert.assertTrue(dialog.isShowing());
 
@@ -281,7 +281,7 @@
     @Test
     @LargeTest
     public void testRemoveItemWithIconNoItemsWithIconsLeft() throws Throwable {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Dialog dialog = mChooserDialog.getDialogForTesting();
             Assert.assertTrue(dialog.isShowing());
 
@@ -318,7 +318,7 @@
     @Test
     @LargeTest
     public void testRemoveItemWithIconOneItemWithIconLeft() throws Throwable {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Dialog dialog = mChooserDialog.getDialogForTesting();
             Assert.assertTrue(dialog.isShowing());
 
@@ -368,7 +368,7 @@
     @Test
     @LargeTest
     public void testUpdateItemWithIconToNoIcon() throws Throwable {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Dialog dialog = mChooserDialog.getDialogForTesting();
             Assert.assertTrue(dialog.isShowing());
             ItemChooserDialog.ItemAdapter itemAdapter = mChooserDialog.getItemAdapterForTesting();
@@ -402,7 +402,7 @@
     @Test
     @LargeTest
     public void testUpdateItemWithNoIconToIcon() throws Throwable {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Dialog dialog = mChooserDialog.getDialogForTesting();
             Assert.assertTrue(dialog.isShowing());
             ItemChooserDialog.ItemAdapter itemAdapter = mChooserDialog.getItemAdapterForTesting();
@@ -436,7 +436,7 @@
     @Test
     @LargeTest
     public void testUpdateItemIcon() throws Throwable {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Dialog dialog = mChooserDialog.getDialogForTesting();
             Assert.assertTrue(dialog.isShowing());
             ItemChooserDialog.ItemAdapter itemAdapter = mChooserDialog.getItemAdapterForTesting();
@@ -487,7 +487,7 @@
         Assert.assertFalse(button.isEnabled());
         Assert.assertEquals(View.GONE, items.getVisibility());
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             mChooserDialog.addOrUpdateItem("key1", "desc1");
             mChooserDialog.addOrUpdateItem("key2", "desc2");
         });
@@ -499,7 +499,7 @@
         Assert.assertEquals("statusActive", statusView.getText().toString());
         Assert.assertFalse(button.isEnabled());
 
-        ThreadUtils.runOnUiThreadBlocking(() -> { mChooserDialog.setIdleState(); });
+        TestThreadUtils.runOnUiThreadBlocking(() -> { mChooserDialog.setIdleState(); });
 
         // After discovery stops the list should be visible with two items,
         // it should not show the empty view and the button should not be enabled.
@@ -533,7 +533,7 @@
         Assert.assertFalse(button.isEnabled());
         Assert.assertEquals(View.GONE, items.getVisibility());
 
-        ThreadUtils.runOnUiThreadBlocking(() -> { mChooserDialog.setIdleState(); });
+        TestThreadUtils.runOnUiThreadBlocking(() -> { mChooserDialog.setIdleState(); });
 
         // Listview should now be showing empty, with an empty view visible to
         // drive home the point and a status message at the bottom.
@@ -551,7 +551,7 @@
         Dialog dialog = mChooserDialog.getDialogForTesting();
         Assert.assertTrue(dialog.isShowing());
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             mChooserDialog.addOrUpdateItem("key1", "desc1");
             mChooserDialog.addOrUpdateItem("key2", "desc2");
         });
@@ -568,7 +568,7 @@
     @Test
     @LargeTest
     public void testSelectOneItemThenDisableTheSelectedItem() throws Throwable {
-        final Dialog dialog = ThreadUtils.runOnUiThreadBlocking(() -> {
+        final Dialog dialog = TestThreadUtils.runOnUiThreadBlocking(() -> {
             Dialog dialog1 = mChooserDialog.getDialogForTesting();
             Assert.assertTrue(dialog1.isShowing());
 
@@ -578,7 +578,7 @@
         });
 
         selectItem(dialog, 1, "key1", true);
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             ItemChooserDialog.ItemAdapter itemAdapter = mChooserDialog.getItemAdapterForTesting();
             Assert.assertEquals("key1", itemAdapter.getSelectedItemKey());
             mChooserDialog.setEnabled("key1", false);
@@ -596,7 +596,7 @@
     @LargeTest
     public void testPairButtonDisabledOrEnabledAfterSelectedItemDisabledOrEnabled()
             throws Throwable {
-        final Dialog dialog = ThreadUtils.runOnUiThreadBlocking(() -> {
+        final Dialog dialog = TestThreadUtils.runOnUiThreadBlocking(() -> {
             Dialog dialog1 = mChooserDialog.getDialogForTesting();
             Assert.assertTrue(dialog1.isShowing());
 
@@ -606,7 +606,7 @@
         });
 
         selectItem(dialog, 1, "key1", true);
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             final Button button = (Button) dialog.findViewById(R.id.positive);
             Assert.assertTrue(button.isEnabled());
 
@@ -625,7 +625,7 @@
     @Test
     @LargeTest
     public void testPairButtonDisabledAfterSelectedItemRemoved() throws Throwable {
-        final Dialog dialog = ThreadUtils.runOnUiThreadBlocking(() -> {
+        final Dialog dialog = TestThreadUtils.runOnUiThreadBlocking(() -> {
             Dialog dialog1 = mChooserDialog.getDialogForTesting();
             Assert.assertTrue(dialog1.isShowing());
 
@@ -637,7 +637,7 @@
 
         selectItem(dialog, 1, "key1", true);
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             final Button button = (Button) dialog.findViewById(R.id.positive);
             Assert.assertTrue(button.isEnabled());
 
@@ -651,7 +651,7 @@
     @Test
     @LargeTest
     public void testSelectAnItemAndRemoveAnotherItem() throws Throwable {
-        final Dialog dialog = ThreadUtils.runOnUiThreadBlocking(() -> {
+        final Dialog dialog = TestThreadUtils.runOnUiThreadBlocking(() -> {
             Dialog dialog1 = mChooserDialog.getDialogForTesting();
             Assert.assertTrue(dialog1.isShowing());
 
@@ -661,7 +661,7 @@
             return dialog1;
         });
         selectItem(dialog, 2, "key2", true);
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             final Button button = (Button) dialog.findViewById(R.id.positive);
             ItemChooserDialog.ItemAdapter itemAdapter = mChooserDialog.getItemAdapterForTesting();
 
@@ -684,7 +684,7 @@
     @Test
     @LargeTest
     public void testSelectAnItemAndRemoveTheSelectedItem() throws Throwable {
-        final Dialog dialog = ThreadUtils.runOnUiThreadBlocking(() -> {
+        final Dialog dialog = TestThreadUtils.runOnUiThreadBlocking(() -> {
             Dialog dialog1 = mChooserDialog.getDialogForTesting();
             Assert.assertTrue(dialog1.isShowing());
 
@@ -695,7 +695,7 @@
         });
 
         selectItem(dialog, 2, "key2", true);
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Button button = (Button) dialog.findViewById(R.id.positive);
             ItemChooserDialog.ItemAdapter itemAdapter = mChooserDialog.getItemAdapterForTesting();
             Assert.assertTrue(button.isEnabled());
@@ -712,7 +712,7 @@
     @Test
     @LargeTest
     public void testUpdateItemAndRemoveItemFromList() throws Throwable {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Dialog dialog = mChooserDialog.getDialogForTesting();
             Assert.assertTrue(dialog.isShowing());
 
@@ -764,7 +764,7 @@
     @Test
     @LargeTest
     public void testAddItemAndRemoveItemFromList() throws Throwable {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Dialog dialog = mChooserDialog.getDialogForTesting();
             Assert.assertTrue(dialog.isShowing());
 
@@ -836,7 +836,7 @@
     @Test
     @LargeTest
     public void testAddItemWithSameNameToListAndRemoveItemFromList() throws Throwable {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Dialog dialog = mChooserDialog.getDialogForTesting();
             Assert.assertTrue(dialog.isShowing());
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/JavaScriptEvalChromeTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/JavaScriptEvalChromeTest.java
index a07e71b..1865425 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/JavaScriptEvalChromeTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/JavaScriptEvalChromeTest.java
@@ -13,7 +13,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.RetryOnFailure;
@@ -23,6 +22,7 @@
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.ChromeTabUtils;
 import org.chromium.content_public.browser.test.util.JavaScriptUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.concurrent.TimeoutException;
 
@@ -75,7 +75,7 @@
         for (int i = 1; i <= 30; ++i) {
             for (int j = 0; j < 5; ++j) {
                 // Start evaluation of a JavaScript script -- we don't need a result.
-                ThreadUtils.runOnUiThreadBlocking(() -> {
+                TestThreadUtils.runOnUiThreadBlocking(() -> {
                     tab1.getWebContents().evaluateJavaScriptForTests("foobar();", null);
                     tab2.getWebContents().evaluateJavaScriptForTests("foobar();", null);
                 });
@@ -85,7 +85,7 @@
                             tab1.getWebContents(), "add2()")));
             for (int j = 0; j < 5; ++j) {
                 // Start evaluation of a JavaScript script -- we don't need a result.
-                ThreadUtils.runOnUiThreadBlocking(() -> {
+                TestThreadUtils.runOnUiThreadBlocking(() -> {
                     tab1.getWebContents().evaluateJavaScriptForTests("foobar();", null);
                     tab2.getWebContents().evaluateJavaScriptForTests("foobar();", null);
                 });
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/NavigateTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/NavigateTest.java
index 56557339..317397ef 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/NavigateTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/NavigateTest.java
@@ -19,7 +19,6 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.ApiCompatibilityUtils;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.DisabledTest;
@@ -45,6 +44,7 @@
 import org.chromium.content_public.browser.test.util.DOMUtils;
 import org.chromium.content_public.browser.test.util.JavaScriptUtils;
 import org.chromium.content_public.browser.test.util.KeyUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.browser.test.util.TouchCommon;
 import org.chromium.content_public.browser.test.util.UiUtils;
 import org.chromium.net.test.EmbeddedTestServer;
@@ -123,7 +123,7 @@
             throws Exception {
         final UrlBar urlBar = (UrlBar) mActivityTestRule.getActivity().findViewById(R.id.url_bar);
         Assert.assertNotNull("urlBar is null", urlBar);
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             urlBar.requestFocus();
             urlBar.setText(url);
         });
@@ -259,9 +259,9 @@
         navigateAndObserve(url1, url1);
 
         final Tab tab = mActivityTestRule.getActivity().getActivityTab();
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> tab.getWebContents().getNavigationController().setUseDesktopUserAgent(
-                        true /* useDesktop */, true /* reloadOnChange */));
+                                true /* useDesktop */, true /* reloadOnChange */));
         ChromeTabUtils.waitForTabPageLoaded(tab, url1);
 
         DOMUtils.clickNode(tab.getWebContents(), "aboutLink");
@@ -509,7 +509,7 @@
 
     private String getTabUrlOnUIThread(final Tab tab) {
         try {
-            return ThreadUtils.runOnUiThreadBlocking(() -> tab.getUrl());
+            return TestThreadUtils.runOnUiThreadBlocking(() -> tab.getUrl());
         } catch (ExecutionException ex) {
             assert false : "Unexpected ExecutionException";
         }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/NavigationBarColorControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/NavigationBarColorControllerTest.java
index c18544c9..f119b7d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/NavigationBarColorControllerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/NavigationBarColorControllerTest.java
@@ -20,13 +20,13 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.ApiCompatibilityUtils;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.chrome.R;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.ChromeTabUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 /** Tests for the NavigationBarColorController.  */
 @RunWith(ChromeJUnit4ClassRunner.class)
@@ -56,13 +56,13 @@
         assertEquals("Navigation bar should be white before entering overview mode.",
                 mLightNavigationColor, mWindow.getNavigationBarColor());
 
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> mActivityTestRule.getActivity().getLayoutManager().showOverview(false));
 
         assertEquals("Navigation bar should be white in overview mode.", mLightNavigationColor,
                 mWindow.getNavigationBarColor());
 
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> mActivityTestRule.getActivity().getLayoutManager().hideOverview(false));
 
         assertEquals("Navigation bar should be white after exiting overview mode.",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/NavigationPopupTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/NavigationPopupTest.java
index 6e98962..b49ae78 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/NavigationPopupTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/NavigationPopupTest.java
@@ -19,7 +19,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Restriction;
@@ -35,6 +34,7 @@
 import org.chromium.content_public.browser.NavigationHistory;
 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 org.chromium.ui.test.util.UiRestriction;
 
 import java.util.concurrent.ExecutionException;
@@ -57,7 +57,8 @@
     @Before
     public void setUp() throws Exception {
         mActivityTestRule.startMainActivityOnBlankPage();
-        ThreadUtils.runOnUiThreadBlocking((Runnable) () -> mProfile = Profile.getLastUsedProfile());
+        TestThreadUtils.runOnUiThreadBlocking(
+                (Runnable) () -> mProfile = Profile.getLastUsedProfile());
     }
 
     // Exists solely to expose protected methods to this test.
@@ -239,7 +240,7 @@
             }
         });
 
-        ThreadUtils.runOnUiThreadBlocking(() -> popup.dismiss());
+        TestThreadUtils.runOnUiThreadBlocking(() -> popup.dismiss());
     }
 
     @Test
@@ -249,7 +250,7 @@
         final TestNavigationController controller = new TestNavigationController();
         final ListPopupWindow popup = showPopup(controller);
 
-        ThreadUtils.runOnUiThreadBlocking((Runnable) () -> popup.performItemClick(1));
+        TestThreadUtils.runOnUiThreadBlocking((Runnable) () -> popup.performItemClick(1));
 
         Assert.assertFalse("Popup did not hide as expected.", popup.isShowing());
         Assert.assertEquals(
@@ -263,7 +264,7 @@
         final TestNavigationController controller = new TestNavigationController();
         final ListPopupWindow popup = showPopup(controller);
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             ListView list = popup.getListView();
             View view = list.getAdapter().getView(list.getAdapter().getCount() - 1, null, list);
             TextView text = (TextView) view.findViewById(R.id.entry_title);
@@ -279,7 +280,7 @@
     @Feature({"Navigation"})
     public void testLongPressBackTriggering() throws ExecutionException {
         KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> { mActivityTestRule.getActivity().onKeyDown(KeyEvent.KEYCODE_BACK, event); });
         CriteriaHelper.pollUiThread(
                 () -> mActivityTestRule.getActivity().hasPendingNavigationPopupForTesting());
@@ -294,13 +295,13 @@
     @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
     @Feature({"Navigation"})
     public void testLongPressBackTriggering_Cancellation() throws ExecutionException {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
             mActivityTestRule.getActivity().onKeyDown(KeyEvent.KEYCODE_BACK, event);
         });
         CriteriaHelper.pollUiThread(
                 () -> mActivityTestRule.getActivity().hasPendingNavigationPopupForTesting());
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             KeyEvent event = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
             mActivityTestRule.getActivity().onKeyUp(KeyEvent.KEYCODE_BACK, event);
         });
@@ -308,12 +309,12 @@
                 () -> !mActivityTestRule.getActivity().hasPendingNavigationPopupForTesting());
 
         // Ensure no navigation popup is showing.
-        Assert.assertNull(ThreadUtils.runOnUiThreadBlocking(
+        Assert.assertNull(TestThreadUtils.runOnUiThreadBlocking(
                 () -> mActivityTestRule.getActivity().getNavigationPopupForTesting()));
     }
 
     private ListPopupWindow showPopup(NavigationController controller) throws ExecutionException {
-        return ThreadUtils.runOnUiThreadBlocking(() -> {
+        return TestThreadUtils.runOnUiThreadBlocking(() -> {
             NavigationPopup popup = new NavigationPopup(mProfile, mActivityTestRule.getActivity(),
                     controller, NavigationPopup.Type.TABLET_FORWARD);
             popup.show(mActivityTestRule.getActivity()
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/OSKOverscrollTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/OSKOverscrollTest.java
index 493a5bb..532bda6 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/OSKOverscrollTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/OSKOverscrollTest.java
@@ -12,7 +12,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.RetryOnFailure;
@@ -25,6 +24,7 @@
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
 import org.chromium.content_public.browser.test.util.DOMUtils;
 import org.chromium.content_public.browser.test.util.JavaScriptUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.Locale;
 import java.util.concurrent.ExecutionException;
@@ -120,7 +120,7 @@
         mActivityTestRule.startMainActivityWithURL(FIXED_FOOTER_PAGE);
 
         final AtomicReference<WebContents> webContentsRef = new AtomicReference<WebContents>();
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> { webContentsRef.set(mActivityTestRule.getWebContents()); });
 
         DOMUtils.waitForNonZeroNodeBounds(webContentsRef.get(), "fn");
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/PowerBroadcastReceiverTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/PowerBroadcastReceiverTest.java
index a58a297c..734a7ce 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/PowerBroadcastReceiverTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/PowerBroadcastReceiverTest.java
@@ -15,7 +15,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
@@ -24,6 +23,7 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.ApplicationTestUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 /**
  * Tests for the PowerBroadcastReceiver.
@@ -93,9 +93,9 @@
     @Before
     public void setUp() throws Exception {
         mActivityTestRule.startMainActivityFromLauncher();
-        mReceiver = ThreadUtils.runOnUiThreadBlocking(
+        mReceiver = TestThreadUtils.runOnUiThreadBlocking(
                 () -> ChromeActivitySessionTracker.getInstance()
-                        .getPowerBroadcastReceiverForTesting());
+                                   .getPowerBroadcastReceiverForTesting());
 
         // Set up our mock runnable.
         mRunnable = new MockServiceRunnable();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/PrerenderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/PrerenderTest.java
index 308fb5e..925dabc2 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/PrerenderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/PrerenderTest.java
@@ -16,7 +16,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
@@ -28,6 +27,7 @@
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.PrerenderTestHelper;
 import org.chromium.chrome.test.util.browser.TabTitleObserver;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.ui.base.PageTransition;
 
@@ -138,6 +138,6 @@
 
         // Cancel the prerender. This will discard the prerendered WebContents and close the
         // infobars.
-        ThreadUtils.runOnUiThreadBlocking(() -> handler.cancelCurrentPrerender());
+        TestThreadUtils.runOnUiThreadBlocking(() -> handler.cancelCurrentPrerender());
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/SafeBrowsingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/SafeBrowsingTest.java
index 10c5f21e..df0ca7a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/SafeBrowsingTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/SafeBrowsingTest.java
@@ -13,7 +13,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeActivityTestRule;
@@ -23,6 +22,7 @@
 import org.chromium.content_public.browser.WebContents;
 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 org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.ui.base.PageTransition;
 
@@ -64,7 +64,7 @@
      */
     private void loadUrlNonBlocking(String url) {
         Tab tab = mActivityTestRule.getActivity().getActivityTab();
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 (Runnable) () -> tab.loadUrl(new LoadUrlParams(url, PageTransition.TYPED)));
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/SelectFileDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/SelectFileDialogTest.java
index ed3646f0..9ac6126 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/SelectFileDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/SelectFileDialogTest.java
@@ -17,7 +17,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
@@ -29,6 +28,7 @@
 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.DOMUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.base.ActivityWindowAndroid;
 import org.chromium.ui.base.SelectFileDialog;
 
@@ -96,7 +96,7 @@
     public void setUp() throws Exception {
         mActivityTestRule.startMainActivityWithURL(DATA_URL);
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             mActivityWindowAndroidForTest =
                     new ActivityWindowAndroidForTest(mActivityTestRule.getActivity());
             SelectFileDialog.setWindowAndroidForTests(mActivityWindowAndroidForTest);
@@ -187,9 +187,9 @@
     }
 
     private void resetActivityWindowAndroidForTest() {
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> mActivityWindowAndroidForTest.lastCallback.onIntentCompleted(
-                        mActivityWindowAndroidForTest, Activity.RESULT_CANCELED, null));
+                                mActivityWindowAndroidForTest, Activity.RESULT_CANCELED, null));
         mActivityWindowAndroidForTest.lastCallback = null;
         mActivityWindowAndroidForTest.lastIntent = null;
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ServicificationBackgroundService.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ServicificationBackgroundService.java
index 90006f1f..890ba9d7 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ServicificationBackgroundService.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ServicificationBackgroundService.java
@@ -10,7 +10,6 @@
 
 import org.junit.Assert;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.library_loader.LibraryProcessType;
 import org.chromium.base.library_loader.ProcessInitException;
 import org.chromium.chrome.browser.init.BrowserParts;
@@ -19,6 +18,7 @@
 import org.chromium.content_public.browser.BrowserStartupController;
 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;
 
 /**
  * Class for launching the service manager only mode for tests.
@@ -67,7 +67,7 @@
     // to onRunTask, it will be enqueued after any possible call to launchBrowser, and we
     // can reliably check whether launchBrowser was called.
     protected void assertLaunchBrowserCalled() {
-        ThreadUtils.runOnUiThreadBlocking(() -> { Assert.assertTrue(mLaunchBrowserCalled); });
+        TestThreadUtils.runOnUiThreadBlocking(() -> { Assert.assertTrue(mLaunchBrowserCalled); });
     }
 
     public void waitForNativeLoaded() {
@@ -89,16 +89,13 @@
         // the BrowserStartupControllerImpl#browserStartupComplete() is called on the UI thread when
         // the full browser starts. So we can use it to checks whether the
         // {@link mFullBrowserStartupDone} has been set to true.
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                Assert.assertTrue("The native service manager has not been started.",
-                        BrowserStartupController.get(LibraryProcessType.PROCESS_BROWSER)
-                                .isServiceManagerSuccessfullyStarted());
-                Assert.assertFalse("The full browser is started instead of ServiceManager only.",
-                        BrowserStartupController.get(LibraryProcessType.PROCESS_BROWSER)
-                                .isStartupSuccessfullyCompleted());
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Assert.assertTrue("The native service manager has not been started.",
+                    BrowserStartupController.get(LibraryProcessType.PROCESS_BROWSER)
+                            .isServiceManagerSuccessfullyStarted());
+            Assert.assertFalse("The full browser is started instead of ServiceManager only.",
+                    BrowserStartupController.get(LibraryProcessType.PROCESS_BROWSER)
+                            .isStartupSuccessfullyCompleted());
         });
     }
 
@@ -107,13 +104,9 @@
         // the BrowserStartupControllerImpl#browserStartupComplete() is called on the UI thread when
         // the full browser starts. So we can use it to checks whether the
         // {@link mFullBrowserStartupDone} has been set to true.
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                Assert.assertTrue("The full browser has not been started",
-                        BrowserStartupController.get(LibraryProcessType.PROCESS_BROWSER)
-                                .isStartupSuccessfullyCompleted());
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> Assert.assertTrue("The full browser has not been started",
+                                BrowserStartupController.get(LibraryProcessType.PROCESS_BROWSER)
+                                        .isStartupSuccessfullyCompleted()));
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ShareIntentTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ShareIntentTest.java
index 1cadc05..95f8e6fb 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ShareIntentTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ShareIntentTest.java
@@ -17,7 +17,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.task.PostTask;
 import org.chromium.base.task.TaskTraits;
 import org.chromium.base.test.util.CommandLineFlags;
@@ -28,6 +27,7 @@
 import org.chromium.chrome.browser.util.ChromeFileProvider;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
@@ -119,7 +119,7 @@
     @LargeTest
     @RetryOnFailure
     public void testShareIntent() throws ExecutionException, InterruptedException {
-        MockChromeActivity mockActivity = ThreadUtils.runOnUiThreadBlocking(() -> {
+        MockChromeActivity mockActivity = TestThreadUtils.runOnUiThreadBlocking(() -> {
             // Sets a test component as last shared and "shareDirectly" option is set so that
             // the share selector menu is not opened. The start activity is overriden, so the
             // package and class names do not matter.
@@ -130,8 +130,9 @@
         // Skips the capture of screenshot and notifies with an empty file.
         ShareMenuActionHandler.setScreenshotCaptureSkippedForTesting(true);
 
-        ThreadUtils.runOnUiThreadBlocking(() -> mockActivity.onShareMenuItemSelected(
-                    true /* shareDirectly */, false /* isIncognito */));
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mockActivity.onShareMenuItemSelected(
+                                true /* shareDirectly */, false /* isIncognito */));
 
         mockActivity.waitForFileCheck();
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/SmartClipProviderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/SmartClipProviderTest.java
index 0001b52..6605f1f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/SmartClipProviderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/SmartClipProviderTest.java
@@ -24,7 +24,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
@@ -36,6 +35,7 @@
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.test.util.Coordinates;
 import org.chromium.content_public.browser.test.util.DOMUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.test.util.UiRestriction;
 
 import java.lang.reflect.Method;
@@ -118,7 +118,7 @@
     public void setUp() throws Exception {
         mActivityTestRule.startMainActivityWithURL(DATA_URL);
         mActivity = mActivityTestRule.getActivity();
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> { mWebContents = mActivityTestRule.getWebContents(); });
 
         DOMUtils.waitForNonZeroNodeBounds(mWebContents, "simple_text");
@@ -197,7 +197,7 @@
     public void testSmartClipDataCallback() throws InterruptedException, TimeoutException {
         final float dpi = Coordinates.createFor(mWebContents).getDeviceScaleFactor();
         final Rect bounds = DOMUtils.getNodeBounds(mWebContents, "simple_text");
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             // This emulates what OEM will be doing when they want to call
             // functions on SmartClipProvider through view hierarchy.
 
@@ -227,7 +227,7 @@
     @Feature({"SmartClip"})
     @RetryOnFailure
     public void testSmartClipNoHandlerDoesntCrash() throws InterruptedException, TimeoutException {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Object scp = findSmartClipProvider(
                     mActivityTestRule.getActivity().findViewById(android.R.id.content));
             Assert.assertNotNull(scp);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/SubresourceFilterTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/SubresourceFilterTest.java
index d44b49c..4ca609e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/SubresourceFilterTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/SubresourceFilterTest.java
@@ -14,7 +14,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisabledTest;
@@ -32,6 +31,7 @@
 import org.chromium.components.safe_browsing.SafeBrowsingApiBridge;
 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 org.chromium.net.test.EmbeddedTestServer;
 
 import java.util.List;
@@ -58,7 +58,7 @@
 
     private void createAndPublishRulesetDisallowingSuffix(String suffix) {
         TestSubresourceFilterPublisher publisher = new TestSubresourceFilterPublisher();
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 (Runnable) ()
                         -> publisher.createAndPublishRulesetDisallowingSuffixForTesting(suffix));
         CriteriaHelper.pollUiThread(new Criteria() {
@@ -121,13 +121,13 @@
         AdsBlockedInfoBar infobar = (AdsBlockedInfoBar) infoBars.get(0);
 
         // Click the link once to expand it.
-        ThreadUtils.runOnUiThreadBlocking(infobar::onLinkClicked);
+        TestThreadUtils.runOnUiThreadBlocking(infobar::onLinkClicked);
 
         // Check the checkbox and press the button to reload.
-        ThreadUtils.runOnUiThreadBlocking(() -> infobar.onCheckedChanged(null, true));
+        TestThreadUtils.runOnUiThreadBlocking(() -> infobar.onCheckedChanged(null, true));
 
         // Think better of it and just close the infobar.
-        ThreadUtils.runOnUiThreadBlocking(infobar::onCloseButtonClicked);
+        TestThreadUtils.runOnUiThreadBlocking(infobar::onCloseButtonClicked);
         Tab tab = mActivityTestRule.getActivity().getActivityTab();
         CriteriaHelper.pollUiThread(() -> !InfoBarContainer.get(tab).hasInfoBars());
     }
@@ -146,12 +146,13 @@
         Tab originalTab = mActivityTestRule.getActivity().getActivityTab();
         CallbackHelper tabCreatedCallback = new CallbackHelper();
         TabModel tabModel = mActivityTestRule.getActivity().getTabModelSelector().getCurrentModel();
-        ThreadUtils.runOnUiThreadBlocking(() -> tabModel.addObserver(new EmptyTabModelObserver() {
-            @Override
-            public void didAddTab(Tab tab, @TabLaunchType int type) {
-                if (tab.getUrl().equals(LEARN_MORE_PAGE)) tabCreatedCallback.notifyCalled();
-            }
-        }));
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> tabModel.addObserver(new EmptyTabModelObserver() {
+                    @Override
+                    public void didAddTab(Tab tab, @TabLaunchType int type) {
+                        if (tab.getUrl().equals(LEARN_MORE_PAGE)) tabCreatedCallback.notifyCalled();
+                    }
+                }));
 
         // Check that the infobar is showing.
         List<InfoBar> infoBars = mActivityTestRule.getInfoBars();
@@ -159,10 +160,10 @@
         AdsBlockedInfoBar infobar = (AdsBlockedInfoBar) infoBars.get(0);
 
         // Click the link once to expand it.
-        ThreadUtils.runOnUiThreadBlocking(infobar::onLinkClicked);
+        TestThreadUtils.runOnUiThreadBlocking(infobar::onLinkClicked);
 
         // Click again to navigate, which should spawn a new tab.
-        ThreadUtils.runOnUiThreadBlocking(infobar::onLinkClicked);
+        TestThreadUtils.runOnUiThreadBlocking(infobar::onLinkClicked);
 
         // Wait for the tab to be added with the correct URL. Note, do not wait for this URL to be
         // loaded since it is not controlled by the test instrumentation. Just waiting for the
@@ -190,11 +191,11 @@
         AdsBlockedInfoBar infobar = (AdsBlockedInfoBar) infoBars.get(0);
 
         // Click the link once to expand it.
-        ThreadUtils.runOnUiThreadBlocking(infobar::onLinkClicked);
+        TestThreadUtils.runOnUiThreadBlocking(infobar::onLinkClicked);
 
         // Check the checkbox and press the button to reload.
-        ThreadUtils.runOnUiThreadBlocking(() -> infobar.onCheckedChanged(null, true));
-        ThreadUtils.runOnUiThreadBlocking(() -> infobar.onButtonClicked(true));
+        TestThreadUtils.runOnUiThreadBlocking(() -> infobar.onCheckedChanged(null, true));
+        TestThreadUtils.runOnUiThreadBlocking(() -> infobar.onButtonClicked(true));
 
         Tab tab = mActivityTestRule.getActivity().getActivityTab();
         ChromeTabUtils.waitForTabPageLoaded(tab, url);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/TabObserverTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/TabObserverTest.java
index c2034b1..63f1109 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/TabObserverTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/TabObserverTest.java
@@ -15,7 +15,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Restriction;
@@ -25,6 +24,7 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.ChromeTabUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.test.util.UiRestriction;
 
 import java.util.concurrent.TimeoutException;
@@ -73,14 +73,14 @@
         int interactableCallCount = interactabilityHelper.getCallCount();
 
         // Enter tab switcher mode and make sure the event is triggered.
-        ThreadUtils.runOnUiThreadBlocking(() -> layoutManager.showOverview(false));
+        TestThreadUtils.runOnUiThreadBlocking(() -> layoutManager.showOverview(false));
 
         interactabilityHelper.waitForCallback(interactableCallCount);
         interactableCallCount = interactabilityHelper.getCallCount();
         assertFalse("Tab should not be interactable.", mTab.isUserInteractable());
 
         // Exit tab switcher and wait for event again.
-        ThreadUtils.runOnUiThreadBlocking(() -> layoutManager.hideOverview(false));
+        TestThreadUtils.runOnUiThreadBlocking(() -> layoutManager.hideOverview(false));
 
         interactabilityHelper.waitForCallback(interactableCallCount);
         assertTrue("Tab should be interactable.", mTab.isUserInteractable());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/TabTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/TabTest.java
index d70a655b..98e5493 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/TabTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/TabTest.java
@@ -14,7 +14,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
@@ -31,6 +30,7 @@
 import org.chromium.components.security_state.ConnectionSecurityLevel;
 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;
 
 /**
  * Tests for Tab class.
@@ -54,7 +54,7 @@
     };
 
     private boolean isShowingSadTab() throws Exception {
-        return ThreadUtils.runOnUiThreadBlocking(() -> SadTab.isShowing(mTab));
+        return TestThreadUtils.runOnUiThreadBlocking(() -> SadTab.isShowing(mTab));
     }
 
     @Before
@@ -103,7 +103,7 @@
     @Feature({"Tab"})
     public void testTabRestoredIfKilledWhileActivityStopped() throws Exception {
         // Ensure the tab is showing before stopping the activity.
-        ThreadUtils.runOnUiThreadBlocking(() -> mTab.show(TabSelectionType.FROM_NEW));
+        TestThreadUtils.runOnUiThreadBlocking(() -> mTab.show(TabSelectionType.FROM_NEW));
 
         Assert.assertFalse(mTab.needsReload());
         Assert.assertFalse(mTab.isHidden());
@@ -111,7 +111,7 @@
 
         // Stop the activity and simulate a killed renderer.
         ApplicationTestUtils.fireHomeScreenIntent(InstrumentationRegistry.getTargetContext());
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> ChromeTabUtils.simulateRendererKilledForTesting(mTab, false));
 
         CriteriaHelper.pollUiThread(new Criteria() {
@@ -140,8 +140,9 @@
     @SmallTest
     @Feature({"Tab"})
     public void testTabSecurityLevel() {
-        ThreadUtils.runOnUiThreadBlocking(
-                (Runnable) () -> Assert.assertEquals(ConnectionSecurityLevel.NONE,
-                        mTab.getSecurityLevel()));
+        TestThreadUtils.runOnUiThreadBlocking(
+                (Runnable) ()
+                        -> Assert.assertEquals(
+                                ConnectionSecurityLevel.NONE, mTab.getSecurityLevel()));
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/TabThemeTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/TabThemeTest.java
index 4cdb4158..222c0b28 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/TabThemeTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/TabThemeTest.java
@@ -13,7 +13,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
@@ -24,6 +23,7 @@
 import org.chromium.chrome.browser.tab.TabThemeColorHelper;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.ui.test.util.UiRestriction;
 
@@ -86,11 +86,12 @@
     }
 
     private static int getThemeColor(Tab tab) throws ExecutionException {
-        return ThreadUtils.runOnUiThreadBlocking(() -> TabThemeColorHelper.getColor(tab));
+        return TestThreadUtils.runOnUiThreadBlocking(() -> TabThemeColorHelper.getColor(tab));
     }
 
     private static int getDefaultThemeColor(Tab tab) throws ExecutionException {
-        return ThreadUtils.runOnUiThreadBlocking(() -> TabThemeColorHelper.getDefaultColor(tab));
+        return TestThreadUtils.runOnUiThreadBlocking(
+                () -> TabThemeColorHelper.getDefaultColor(tab));
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/TabsOpenedFromExternalAppTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/TabsOpenedFromExternalAppTest.java
index b1c60b04..ee4bf9d1 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/TabsOpenedFromExternalAppTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/TabsOpenedFromExternalAppTest.java
@@ -24,7 +24,6 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.BaseSwitches;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
@@ -45,6 +44,7 @@
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
 import org.chromium.content_public.browser.test.util.DOMUtils;
 import org.chromium.content_public.browser.test.util.JavaScriptUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.browser.test.util.TouchCommon;
 import org.chromium.content_public.common.Referrer;
 import org.chromium.net.test.EmbeddedTestServer;
@@ -214,7 +214,7 @@
         }
 
         final Tab originalTab = testRule.getActivity().getActivityTab();
-        ThreadUtils.runOnUiThreadBlocking(() -> testRule.getActivity().onNewIntent(intent));
+        TestThreadUtils.runOnUiThreadBlocking(() -> testRule.getActivity().onNewIntent(intent));
         if (createNewTab) {
             CriteriaHelper.pollUiThread(new Criteria("Failed to select different tab") {
                 @Override
@@ -447,7 +447,8 @@
         // And pressing back should close Clank.
         Assert.assertTrue("Window does not have focus before pressing back.",
                 mActivityTestRule.getActivity().hasWindowFocus());
-        ThreadUtils.runOnUiThreadBlocking(() -> mActivityTestRule.getActivity().onBackPressed());
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mActivityTestRule.getActivity().onBackPressed());
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
         Assert.assertFalse("Window still has focus after pressing back.",
                 mActivityTestRule.getActivity().hasWindowFocus());
@@ -495,7 +496,8 @@
         // And pressing back should close Clank.
         Assert.assertTrue("Window does not have focus before pressing back.",
                 mActivityTestRule.getActivity().hasWindowFocus());
-        ThreadUtils.runOnUiThreadBlocking(() -> mActivityTestRule.getActivity().onBackPressed());
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mActivityTestRule.getActivity().onBackPressed());
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
         Assert.assertFalse("Window still has focus after pressing back.",
                 mActivityTestRule.getActivity().hasWindowFocus());
@@ -537,7 +539,8 @@
         // And pressing back should close Clank.
         Assert.assertTrue("Window does not have focus before pressing back.",
                 mActivityTestRule.getActivity().hasWindowFocus());
-        ThreadUtils.runOnUiThreadBlocking(() -> mActivityTestRule.getActivity().onBackPressed());
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mActivityTestRule.getActivity().onBackPressed());
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
         Assert.assertFalse("Window still has focus after pressing back.",
                 mActivityTestRule.getActivity().hasWindowFocus());
@@ -571,7 +574,8 @@
         // And pressing back should close Clank.
         Assert.assertTrue("Window does not have focus before pressing back.",
                 mActivityTestRule.getActivity().hasWindowFocus());
-        ThreadUtils.runOnUiThreadBlocking(() -> mActivityTestRule.getActivity().onBackPressed());
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mActivityTestRule.getActivity().onBackPressed());
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
         Assert.assertFalse("Window still has focus after pressing back.",
                 mActivityTestRule.getActivity().hasWindowFocus());
@@ -712,8 +716,10 @@
     public void testBackgroundSvelteTabIsSelectedAfterClosingExternalTab() throws Exception {
         // Start up Chrome and immediately close its tab -- it gets in the way.
         mActivityTestRule.startMainActivityFromLauncher();
-        ThreadUtils.runOnUiThreadBlocking((Runnable) () -> TabModelUtils.closeTabByIndex(
-                mActivityTestRule.getActivity().getCurrentTabModel(), 0));
+        TestThreadUtils.runOnUiThreadBlocking(
+                (Runnable) ()
+                        -> TabModelUtils.closeTabByIndex(
+                                mActivityTestRule.getActivity().getCurrentTabModel(), 0));
         CriteriaHelper.pollUiThread(Criteria.equals(0,
                 () -> mActivityTestRule.getActivity().getTabModelSelector().getTotalTabCount()));
 
@@ -751,7 +757,7 @@
         final TestTabObserver observer = new TestTabObserver();
         mActivityTestRule.getActivity().getActivityTab().addObserver(observer);
         Assert.assertNull(observer.mContextMenu);
-        final View view = ThreadUtils.runOnUiThreadBlocking(
+        final View view = TestThreadUtils.runOnUiThreadBlocking(
                 (Callable<View>) ()
                         -> mActivityTestRule.getActivity().getActivityTab().getContentView());
         TouchCommon.longPressView(view);
@@ -764,9 +770,9 @@
         mActivityTestRule.getActivity().getActivityTab().removeObserver(observer);
 
         // Select the "open in new tab" option.
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> Assert.assertTrue(observer.mContextMenu.performIdentifierAction(
-                        R.id.contextmenu_open_in_new_tab, 0)));
+                                R.id.contextmenu_open_in_new_tab, 0)));
 
         // The second tab should open in the background.
         CriteriaHelper.pollUiThread(Criteria.equals(2,
@@ -774,7 +780,8 @@
 
         // Hitting "back" should close the tab, minimize Chrome, and select the background tab.
         // Confirm that the number of tabs is correct and that closing the tab didn't cause a crash.
-        ThreadUtils.runOnUiThreadBlocking(() -> mActivityTestRule.getActivity().onBackPressed());
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mActivityTestRule.getActivity().onBackPressed());
         CriteriaHelper.pollUiThread(Criteria.equals(1,
                 () -> mActivityTestRule.getActivity().getTabModelSelector().getTotalTabCount()));
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/TabsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/TabsTest.java
index d8f66e45..1ab46976 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/TabsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/TabsTest.java
@@ -83,6 +83,7 @@
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
 import org.chromium.content_public.browser.test.util.DOMUtils;
 import org.chromium.content_public.browser.test.util.JavaScriptUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.browser.test.util.TouchCommon;
 import org.chromium.content_public.browser.test.util.UiUtils;
 import org.chromium.content_public.common.ContentSwitches;
@@ -196,11 +197,11 @@
 
         mActivityTestRule.newIncognitoTabFromMenu();
 
-        ThreadUtils.runOnUiThreadBlocking(() -> tab.getWebContents().evaluateJavaScriptForTests(
-                "(function() {"
-                        + "  window.open('www.google.com');"
-                        + "})()",
-                null));
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> tab.getWebContents().evaluateJavaScriptForTests("(function() {"
+                                        + "  window.open('www.google.com');"
+                                        + "})()",
+                                null));
 
         CriteriaHelper.pollUiThread(Criteria.equals(2,
                 () -> mActivityTestRule.getActivity()
@@ -217,11 +218,11 @@
         mActivityTestRule.newIncognitoTabFromMenu();
         mActivityTestRule.loadUrl(mTestServer.getURL(TEST_FILE_PATH));
         final Tab tab = mActivityTestRule.getActivity().getActivityTab();
-        ThreadUtils.runOnUiThreadBlocking(() -> tab.getWebContents().evaluateJavaScriptForTests(
-                "(function() {"
-                        + "  alert('hi');"
-                        + "})()",
-                null));
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> tab.getWebContents().evaluateJavaScriptForTests("(function() {"
+                                        + "  alert('hi');"
+                                        + "})()",
+                                null));
 
         final AtomicReference<JavascriptTabModalDialog> dialog = new AtomicReference<>();
 
@@ -534,7 +535,7 @@
         int initialTabCount = mActivityTestRule.getActivity().getCurrentTabModel().getCount();
         ChromeTabUtils.fullyLoadUrlInNewTab(InstrumentationRegistry.getInstrumentation(),
                 mActivityTestRule.getActivity(), UrlConstants.CHROME_BLANK_URL, false);
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> { mActivityTestRule.getActivity().getLayoutManager().showOverview(false); });
 
         Assert.assertTrue("Expected: " + (initialTabCount + 1) + " tab Got: "
@@ -999,7 +1000,7 @@
     private int getLayoutTabInStackCount(final boolean isIncognito) {
         final LayoutManagerChrome layoutManager = updateTabsViewSize();
         final int[] count = new int[1];
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Stack stack = getStack(layoutManager, isIncognito);
             count[0] = stack.getTabs().length;
         });
@@ -1009,7 +1010,7 @@
     private boolean stackTabIsVisible(final boolean isIncognito, final int index) {
         final LayoutManagerChrome layoutManager = updateTabsViewSize();
         final boolean[] isVisible = new boolean[1];
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Stack stack = getStack(layoutManager, isIncognito);
             isVisible[0] = (stack.getTabs())[index].getLayoutTab().isVisible();
         });
@@ -1019,7 +1020,7 @@
     private float[] getLayoutTabInStackXY(final boolean isIncognito, final int index) {
         final LayoutManagerChrome layoutManager = updateTabsViewSize();
         final float[] xy = new float[2];
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Stack stack = getStack(layoutManager, isIncognito);
             xy[0] = (stack.getTabs())[index].getLayoutTab().getX();
             xy[1] = (stack.getTabs())[index].getLayoutTab().getY();
@@ -1031,7 +1032,7 @@
             final boolean isLandscape) {
         final LayoutManagerChrome layoutManager = updateTabsViewSize();
         final float[] target = new float[2];
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Stack stack = getStack(layoutManager, isIncognito);
             StackTab[] tabs = stack.getTabs();
             // The position of the click is expressed from the top left corner of the content.
@@ -1634,7 +1635,7 @@
         staticLayoutCallbackHelper.waitForCallback(callLayouChangeCount, 1);
 
         if (expectsSelection) selectCallback.waitForCallback(tabSelectedCallCount, 1);
-        ThreadUtils.runOnUiThreadBlocking(() -> observer.destroy());
+        TestThreadUtils.runOnUiThreadBlocking(() -> observer.destroy());
 
         Assert.assertEquals("Index after toolbar side swipe is incorrect", finalIndex,
                 activity.getCurrentTabModel().index());
@@ -1799,9 +1800,10 @@
 
         Assert.assertEquals("Too many tabs at startup", 1, model.getCount());
 
-        ThreadUtils.runOnUiThreadBlocking((Runnable) () -> model.closeTab(tab, false, false, true));
+        TestThreadUtils.runOnUiThreadBlocking(
+                (Runnable) () -> model.closeTab(tab, false, false, true));
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Assert.assertTrue("Tab close is not undoable", model.isClosurePending(tab.getId()));
             Assert.assertTrue("Tab was not hidden", tab.isHidden());
         });
@@ -1827,7 +1829,8 @@
 
         Assert.assertEquals("Too many tabs at startup", 1, model.getCount());
 
-        ThreadUtils.runOnUiThreadBlocking((Runnable) () -> model.closeTab(tab, false, false, true));
+        TestThreadUtils.runOnUiThreadBlocking(
+                (Runnable) () -> model.closeTab(tab, false, false, true));
 
         Assert.assertTrue("notifyChanged() was not called", mNotifyChangedCalled);
     }
@@ -1846,7 +1849,7 @@
 
         final AtomicBoolean webContentsDestroyCalled = new AtomicBoolean();
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+        TestThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
                 @SuppressWarnings("unused") // Avoid GC of observer
@@ -1965,7 +1968,7 @@
     }
 
     private JavascriptTabModalDialog getCurrentAlertDialog() {
-        return (JavascriptTabModalDialog) ThreadUtils.runOnUiThreadBlockingNoException(() -> {
+        return (JavascriptTabModalDialog) TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
             PropertyModel dialogModel = mActivityTestRule.getActivity()
                                                 .getModalDialogManager()
                                                 .getCurrentDialogForTest();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/UrlSchemeTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/UrlSchemeTest.java
index e62514dc..539dde0 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/UrlSchemeTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/UrlSchemeTest.java
@@ -17,7 +17,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.RetryOnFailure;
@@ -28,6 +27,7 @@
 import org.chromium.chrome.test.TestContentProvider;
 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 org.chromium.net.test.EmbeddedTestServer;
 
 import java.io.File;
@@ -216,7 +216,7 @@
     }
 
     private String getTitleOnUiThread() {
-        return ThreadUtils.runOnUiThreadBlockingNoException(
+        return TestThreadUtils.runOnUiThreadBlockingNoException(
                 () -> mActivityTestRule.getActivity().getActivityTab().getTitle());
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/UsbChooserDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/UsbChooserDialogTest.java
index f0869be..e505eb8 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/UsbChooserDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/UsbChooserDialogTest.java
@@ -16,7 +16,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.RetryOnFailure;
 import org.chromium.chrome.R;
@@ -25,6 +24,7 @@
 import org.chromium.components.security_state.ConnectionSecurityLevel;
 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 org.chromium.content_public.browser.test.util.TouchCommon;
 import org.chromium.ui.widget.TextViewWithClickableSpans;
 
@@ -72,7 +72,7 @@
     }
 
     private UsbChooserDialogWithFakeNatives createDialog() {
-        return ThreadUtils.runOnUiThreadBlockingNoException(
+        return TestThreadUtils.runOnUiThreadBlockingNoException(
                 () -> {
                     UsbChooserDialogWithFakeNatives dialog =
                             new UsbChooserDialogWithFakeNatives();
@@ -160,7 +160,7 @@
         final Button button = (Button) dialog.findViewById(R.id.positive);
         final int position = 1;
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             mChooserDialog.addDevice("device_id_0", "device_name_0");
             mChooserDialog.addDevice("device_id_1", "device_name_1");
             mChooserDialog.addDevice("device_id_2", "device_name_2");
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/WarmupManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/WarmupManagerTest.java
index de17c67..7d10086 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/WarmupManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/WarmupManagerTest.java
@@ -18,7 +18,6 @@
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.task.PostTask;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.MetricsUtils;
@@ -31,6 +30,7 @@
 import org.chromium.content_public.browser.WebContentsObserver;
 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 org.chromium.content_public.browser.test.util.WebContentsUtils;
 import org.chromium.net.test.EmbeddedTestServer;
 
@@ -55,7 +55,7 @@
         mContext = InstrumentationRegistry.getInstrumentation()
                            .getTargetContext()
                            .getApplicationContext();
-        ThreadUtils.runOnUiThreadBlocking(new Callable<Void>() {
+        TestThreadUtils.runOnUiThreadBlocking(new Callable<Void>() {
             @Override
             public Void call() throws Exception {
                 ChromeBrowserInitializer.getInstance(mContext).handleSynchronousStartup();
@@ -67,7 +67,7 @@
 
     @After
     public void tearDown() throws Exception {
-        ThreadUtils.runOnUiThreadBlocking(() -> mWarmupManager.destroySpareWebContents());
+        TestThreadUtils.runOnUiThreadBlocking(() -> mWarmupManager.destroySpareWebContents());
     }
 
     @Test
@@ -230,8 +230,7 @@
 
             final String url = server.getURL("/hello_world.html");
             PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT,
-                    ()
-                            -> mWarmupManager.maybePreconnectUrlAndSubResources(
+                    () -> mWarmupManager.maybePreconnectUrlAndSubResources(
                                     Profile.getLastUsedProfile(), url));
             if (!connectionsSemaphore.tryAcquire(5, TimeUnit.SECONDS)) {
                 // Starts at -1.
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/accessibility/FontSizePrefsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/accessibility/FontSizePrefsTest.java
index 3b145ff9..223f0507 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/accessibility/FontSizePrefsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/accessibility/FontSizePrefsTest.java
@@ -16,11 +16,11 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.ContextUtils;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.browser.accessibility.FontSizePrefs.FontSizePrefsObserver;
 import org.chromium.chrome.browser.test.ChromeBrowserTestRule;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 /**
  * Tests for {@link FontSizePrefs}.
@@ -173,7 +173,7 @@
         }
 
         private void assertConsistent() {
-            ThreadUtils.runOnUiThreadBlocking(() -> {
+            TestThreadUtils.runOnUiThreadBlocking(() -> {
                 Assert.assertEquals(getUserFontScaleFactor(), mUserFontScaleFactor, EPSILON);
                 Assert.assertEquals(getFontScaleFactor(), mFontScaleFactor, EPSILON);
                 Assert.assertEquals(getForceEnableZoom(), mForceEnableZoom);
@@ -182,12 +182,12 @@
     }
 
     private FontSizePrefs getFontSizePrefs(final Context context) {
-        return ThreadUtils.runOnUiThreadBlockingNoException(
+        return TestThreadUtils.runOnUiThreadBlockingNoException(
                 () -> FontSizePrefs.getInstance(context));
     }
 
     private TestingObserver createAndAddFontSizePrefsObserver() {
-        return ThreadUtils.runOnUiThreadBlockingNoException(() -> {
+        return TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
             TestingObserver observer = new TestingObserver();
             mFontSizePrefs.addObserver(observer);
             return observer;
@@ -195,30 +195,32 @@
     }
 
     private void setUserFontScaleFactor(final float fontsize) {
-        ThreadUtils.runOnUiThreadBlocking(() -> mFontSizePrefs.setUserFontScaleFactor(fontsize));
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mFontSizePrefs.setUserFontScaleFactor(fontsize));
     }
 
     private float getUserFontScaleFactor() {
-        return ThreadUtils.runOnUiThreadBlockingNoException(
+        return TestThreadUtils.runOnUiThreadBlockingNoException(
                 () -> mFontSizePrefs.getUserFontScaleFactor());
     }
 
     private float getFontScaleFactor() {
-        return ThreadUtils.runOnUiThreadBlockingNoException(
+        return TestThreadUtils.runOnUiThreadBlockingNoException(
                 () -> mFontSizePrefs.getFontScaleFactor());
     }
 
     private void setForceEnableZoomFromUser(final boolean enabled) {
-        ThreadUtils.runOnUiThreadBlocking(() -> mFontSizePrefs.setForceEnableZoomFromUser(enabled));
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mFontSizePrefs.setForceEnableZoomFromUser(enabled));
     }
 
     private boolean getForceEnableZoom() {
-        return ThreadUtils.runOnUiThreadBlockingNoException(
+        return TestThreadUtils.runOnUiThreadBlockingNoException(
                 () -> mFontSizePrefs.getForceEnableZoom());
     }
 
     private void setSystemFontScaleForTest(final float systemFontScale) {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             mFontSizePrefs.setSystemFontScaleForTest(systemFontScale);
             mFontSizePrefs.onSystemFontScaleChanged();
         });
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingIntegrationTest.java
index 95ff027..fe14fcf 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingIntegrationTest.java
@@ -443,8 +443,7 @@
         // Create a simple, persistent snackbar and verify it's displayed.
         SnackbarManager manager = mActivityTestRule.getActivity().getSnackbarManager();
         PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT,
-                ()
-                        -> manager.showSnackbar(Snackbar.make(kSnackbarText,
+                () -> manager.showSnackbar(Snackbar.make(kSnackbarText,
                                 new SnackbarManager.SnackbarController() {},
                                 Snackbar.TYPE_PERSISTENT, Snackbar.UMA_TEST_SNACKBAR)));
         CriteriaHelper.pollUiThread(manager::isShowing);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkBridgeTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkBridgeTest.java
index f6467e54..52df5ab 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkBridgeTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkBridgeTest.java
@@ -15,7 +15,6 @@
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.RetryOnFailure;
@@ -24,6 +23,7 @@
 import org.chromium.chrome.browser.test.ChromeBrowserTestRule;
 import org.chromium.chrome.test.util.BookmarkTestUtil;
 import org.chromium.components.bookmarks.BookmarkId;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -47,23 +47,17 @@
 
     @Before
     public void setUp() throws Exception {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                Profile profile = Profile.getLastUsedProfile();
-                mBookmarkBridge = new BookmarkBridge(profile);
-                mBookmarkBridge.loadEmptyPartnerBookmarkShimForTesting();
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Profile profile = Profile.getLastUsedProfile();
+            mBookmarkBridge = new BookmarkBridge(profile);
+            mBookmarkBridge.loadEmptyPartnerBookmarkShimForTesting();
         });
 
         BookmarkTestUtil.waitForBookmarkModelLoaded();
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mMobileNode = mBookmarkBridge.getMobileFolderId();
-                mDesktopNode = mBookmarkBridge.getDesktopFolderId();
-                mOtherNode = mBookmarkBridge.getOtherFolderId();
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mMobileNode = mBookmarkBridge.getMobileFolderId();
+            mDesktopNode = mBookmarkBridge.getDesktopFolderId();
+            mOtherNode = mBookmarkBridge.getOtherFolderId();
         });
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkModelTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkModelTest.java
index 8595e42f5..8334384 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkModelTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkModelTest.java
@@ -15,7 +15,6 @@
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.RetryOnFailure;
@@ -24,6 +23,7 @@
 import org.chromium.chrome.browser.test.ChromeBrowserTestRule;
 import org.chromium.chrome.test.util.BookmarkTestUtil;
 import org.chromium.components.bookmarks.BookmarkId;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -51,23 +51,17 @@
 
     @Before
     public void setUp() throws Exception {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                Profile profile = Profile.getLastUsedProfile();
-                mBookmarkModel = new BookmarkModel(profile);
-                mBookmarkModel.loadEmptyPartnerBookmarkShimForTesting();
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Profile profile = Profile.getLastUsedProfile();
+            mBookmarkModel = new BookmarkModel(profile);
+            mBookmarkModel.loadEmptyPartnerBookmarkShimForTesting();
         });
 
         BookmarkTestUtil.waitForBookmarkModelLoaded();
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mMobileNode = mBookmarkModel.getMobileFolderId();
-                mDesktopNode = mBookmarkModel.getDesktopFolderId();
-                mOtherNode = mBookmarkModel.getOtherFolderId();
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mMobileNode = mBookmarkModel.getMobileFolderId();
+            mDesktopNode = mBookmarkModel.getDesktopFolderId();
+            mOtherNode = mBookmarkModel.getOtherFolderId();
         });
     }
 
@@ -247,12 +241,9 @@
             final String url) {
         final AtomicReference<BookmarkId> result = new AtomicReference<BookmarkId>();
         final Semaphore semaphore = new Semaphore(0);
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                result.set(mBookmarkModel.addBookmark(parent, index, title, url));
-                semaphore.release();
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            result.set(mBookmarkModel.addBookmark(parent, index, title, url));
+            semaphore.release();
         });
         try {
             if (semaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkPersonalizedSigninPromoTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkPersonalizedSigninPromoTest.java
index 61802b1..e50b187 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkPersonalizedSigninPromoTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkPersonalizedSigninPromoTest.java
@@ -28,7 +28,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.DisabledTest;
@@ -45,6 +44,7 @@
 import org.chromium.components.signin.ProfileDataSource;
 import org.chromium.components.signin.test.util.AccountHolder;
 import org.chromium.components.signin.test.util.FakeAccountManagerDelegate;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.test.util.UiDisableIf;
 
 import java.io.Closeable;
@@ -165,7 +165,7 @@
     }
 
     private void openBookmarkManager() throws InterruptedException {
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> BookmarkUtils.showBookmarkManager(mActivityTestRule.getActivity()));
     }
 
@@ -175,7 +175,7 @@
         mAccountManagerDelegate.addAccountHolderBlocking(accountHolder.build());
         ProfileDataSource.ProfileData profileData =
                 new ProfileDataSource.ProfileData(TEST_ACCOUNT_NAME, null, TEST_FULL_NAME, null);
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> mAccountManagerDelegate.setProfileData(TEST_ACCOUNT_NAME, profileData));
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java
index 68e31b3..c1b22bd 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java
@@ -23,7 +23,6 @@
 
 import org.chromium.base.ActivityState;
 import org.chromium.base.ApplicationStatus;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisableIf;
@@ -47,6 +46,7 @@
 import org.chromium.chrome.test.util.RenderTestRule;
 import org.chromium.components.bookmarks.BookmarkId;
 import org.chromium.components.bookmarks.BookmarkType;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.browser.test.util.TouchCommon;
 import org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.ui.test.util.UiRestriction;
@@ -87,12 +87,9 @@
     @Before
     public void setUp() throws Exception {
         mActivityTestRule.startMainActivityFromLauncher();
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mBookmarkModel = new BookmarkModel(
-                        mActivityTestRule.getActivity().getActivityTab().getProfile());
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mBookmarkModel = new BookmarkModel(
+                    mActivityTestRule.getActivity().getActivityTab().getProfile());
         });
         mTestServer = EmbeddedTestServer.createAndStartServer(InstrumentationRegistry.getContext());
         mTestPage = mTestServer.getURL(TEST_PAGE_URL_GOOGLE);
@@ -101,7 +98,7 @@
 
     private void readPartnerBookmarks() throws InterruptedException {
         // Do not read partner bookmarks in setUp(), so that the lazy reading is covered.
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> PartnerBookmarksShim.kickOffReading(mActivityTestRule.getActivity()));
         BookmarkTestUtil.waitForBookmarkModelLoaded();
     }
@@ -132,7 +129,7 @@
     }
 
     private boolean isItemPresentInBookmarkList(final String expectedTitle) {
-        return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<Boolean>() {
+        return TestThreadUtils.runOnUiThreadBlockingNoException(new Callable<Boolean>() {
             @Override
             public Boolean call() throws Exception {
                 for (int i = 0; i < mItemsContainer.getAdapter().getItemCount(); i++) {
@@ -162,19 +159,16 @@
                 mActivityTestRule.getActivity(), R.id.bookmark_this_page_id);
         BookmarkTestUtil.waitForBookmarkModelLoaded();
         // All actions with BookmarkModel needs to run on UI thread.
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                long bookmarkIdLong =
-                        mActivityTestRule.getActivity().getActivityTab().getUserBookmarkId();
-                BookmarkId id = new BookmarkId(bookmarkIdLong, BookmarkType.NORMAL);
-                Assert.assertTrue("The test page is not added as bookmark: ",
-                        mBookmarkModel.doesBookmarkExist(id));
-                BookmarkItem item = mBookmarkModel.getBookmarkById(id);
-                Assert.assertEquals(mBookmarkModel.getDefaultFolder(), item.getParentId());
-                Assert.assertEquals(mTestPage, item.getUrl());
-                Assert.assertEquals(TEST_PAGE_TITLE_GOOGLE, item.getTitle());
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            long bookmarkIdLong =
+                    mActivityTestRule.getActivity().getActivityTab().getUserBookmarkId();
+            BookmarkId id = new BookmarkId(bookmarkIdLong, BookmarkType.NORMAL);
+            Assert.assertTrue("The test page is not added as bookmark: ",
+                    mBookmarkModel.doesBookmarkExist(id));
+            BookmarkItem item = mBookmarkModel.getBookmarkById(id);
+            Assert.assertEquals(mBookmarkModel.getDefaultFolder(), item.getParentId());
+            Assert.assertEquals(mTestPage, item.getUrl());
+            Assert.assertEquals(TEST_PAGE_TITLE_GOOGLE, item.getTitle());
         });
     }
 
@@ -231,7 +225,7 @@
         final BookmarkActionBar toolbar = ((BookmarkManager) delegate).getToolbarForTests();
 
         // Open the "Mobile bookmarks" folder.
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> delegate.openFolder(mBookmarkModel.getMobileFolderId()));
 
         // Check that we are in the mobile bookmarks folder.
@@ -241,7 +235,7 @@
         Assert.assertFalse(toolbar.getMenu().findItem(R.id.edit_menu_id).isVisible());
 
         // Open the new test folder.
-        ThreadUtils.runOnUiThreadBlocking(() -> delegate.openFolder(testFolder));
+        TestThreadUtils.runOnUiThreadBlocking(() -> delegate.openFolder(testFolder));
 
         // Check that we are in the editable test folder.
         Assert.assertEquals(TEST_FOLDER_TITLE, toolbar.getTitle());
@@ -250,7 +244,7 @@
         Assert.assertTrue(toolbar.getMenu().findItem(R.id.edit_menu_id).isVisible());
 
         // Call BookmarkActionBar#onClick() to activate the navigation button.
-        ThreadUtils.runOnUiThreadBlocking(() -> toolbar.onClick(toolbar));
+        TestThreadUtils.runOnUiThreadBlocking(() -> toolbar.onClick(toolbar));
 
         // Check that we are back in the mobile folder
         Assert.assertEquals("Mobile bookmarks", toolbar.getTitle());
@@ -259,7 +253,7 @@
         Assert.assertFalse(toolbar.getMenu().findItem(R.id.edit_menu_id).isVisible());
 
         // Call BookmarkActionBar#onClick() to activate the navigation button.
-        ThreadUtils.runOnUiThreadBlocking(() -> toolbar.onClick(toolbar));
+        TestThreadUtils.runOnUiThreadBlocking(() -> toolbar.onClick(toolbar));
 
         // Check that we are in the root folder.
         Assert.assertEquals("Bookmarks", toolbar.getTitle());
@@ -283,14 +277,14 @@
         final BookmarkDelegate delegate = adapter.getDelegateForTesting();
 
         // Open the default folder where these bookmarks were created.
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> delegate.openFolder(mBookmarkModel.getDefaultFolder()));
 
         Assert.assertEquals(BookmarkUIState.STATE_FOLDER, delegate.getCurrentState());
         Assert.assertEquals(
                 "Wrong number of items before starting search.", 3, adapter.getItemCount());
 
-        ThreadUtils.runOnUiThreadBlocking(delegate::openSearchUI);
+        TestThreadUtils.runOnUiThreadBlocking(delegate::openSearchUI);
 
         Assert.assertEquals(BookmarkUIState.STATE_SEARCHING, delegate.getCurrentState());
         Assert.assertEquals(
@@ -313,7 +307,7 @@
         Assert.assertEquals("Wrong number of items after searching for non-existent item.", 0,
                 mItemsContainer.getAdapter().getItemCount());
 
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> ((BookmarkManager) delegate).getToolbarForTests().hideSearchView());
         Assert.assertEquals("Wrong number of items after closing search UI.", 3,
                 mItemsContainer.getAdapter().getItemCount());
@@ -338,16 +332,15 @@
                 "Wrong number of items before starting search.", 3, adapter.getItemCount());
 
         // Start searching without entering a query.
-        ThreadUtils.runOnUiThreadBlocking(manager::openSearchUI);
+        TestThreadUtils.runOnUiThreadBlocking(manager::openSearchUI);
         Assert.assertEquals("Wrong state, should be searching", BookmarkUIState.STATE_SEARCHING,
                 manager.getCurrentState());
 
         // Select the folder and delete it.
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> manager.getSelectionDelegate().toggleSelectionForItem(adapter.getItem(0)));
-        ThreadUtils.runOnUiThreadBlocking(
-                ()
-                        -> manager.getToolbarForTests().onMenuItemClick(
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> manager.getToolbarForTests().onMenuItemClick(
                                 manager.getToolbarForTests().getMenu().findItem(
                                         R.id.selection_mode_delete_menu_id)));
 
@@ -358,7 +351,7 @@
                 "Wrong number of items before starting search.", 2, adapter.getItemCount());
 
         // Start searching, enter a query.
-        ThreadUtils.runOnUiThreadBlocking(manager::openSearchUI);
+        TestThreadUtils.runOnUiThreadBlocking(manager::openSearchUI);
         Assert.assertEquals("Wrong state, should be searching", BookmarkUIState.STATE_SEARCHING,
                 manager.getCurrentState());
         searchBookmarks("Google");
@@ -375,7 +368,8 @@
                 mItemsContainer.getAdapter().getItemCount());
 
         // Undo the deletion.
-        ThreadUtils.runOnUiThreadBlocking(() -> manager.getUndoControllerForTests().onAction(null));
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> manager.getUndoControllerForTests().onAction(null));
 
         // The user should still be searching, and the bookmark should reappear.
         Assert.assertEquals("Wrong state, should be searching", BookmarkUIState.STATE_SEARCHING,
@@ -397,7 +391,7 @@
 
         mRenderTestRule.render(manager.getView(), "bookmark_manager_one_folder");
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             BookmarkRow itemView = (BookmarkRow) manager.getRecyclerViewForTests()
                                            .findViewHolderForAdapterPosition(0)
                                            .itemView;
@@ -408,7 +402,7 @@
 
         mRenderTestRule.render(manager.getView(), "bookmark_manager_folder_selected");
 
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> manager.getSelectionDelegate().toggleSelectionForItem(adapter.getItem(0)));
 
         mRenderTestRule.render(manager.getView(), "bookmark_manager_one_folder");
@@ -429,7 +423,7 @@
 
         final BookmarkActionBar toolbar = mManager.getToolbarForTests();
 
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> toolbar.onMenuItemClick(toolbar.getMenu().findItem(R.id.close_menu_id)));
 
         activityDestroyedCallback.waitForCallback(0);
@@ -458,7 +452,7 @@
      * @return The unique view, if one exists. Throws an exception if one doesn't exist.
      */
     private static View getViewWithText(final ViewGroup viewGroup, final String expectedText) {
-        return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<View>() {
+        return TestThreadUtils.runOnUiThreadBlockingNoException(new Callable<View>() {
             @Override
             public View call() throws Exception {
                 ArrayList<View> outViews = new ArrayList<>();
@@ -480,23 +474,23 @@
     private BookmarkId addBookmark(final String title, final String url)
             throws InterruptedException, ExecutionException {
         readPartnerBookmarks();
-        return ThreadUtils.runOnUiThreadBlocking(
+        return TestThreadUtils.runOnUiThreadBlocking(
                 () -> mBookmarkModel.addBookmark(mBookmarkModel.getDefaultFolder(), 0, title, url));
     }
 
     private BookmarkId addFolder(final String title)
             throws InterruptedException, ExecutionException {
         readPartnerBookmarks();
-        return ThreadUtils.runOnUiThreadBlocking(
+        return TestThreadUtils.runOnUiThreadBlocking(
                 () -> mBookmarkModel.addFolder(mBookmarkModel.getDefaultFolder(), 0, title));
     }
 
     private void removeBookmark(final BookmarkId bookmarkId) {
-        ThreadUtils.runOnUiThreadBlocking(() -> mBookmarkModel.deleteBookmark(bookmarkId));
+        TestThreadUtils.runOnUiThreadBlocking(() -> mBookmarkModel.deleteBookmark(bookmarkId));
     }
 
     private void searchBookmarks(final String query) {
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> ((BookmarkItemsAdapter) mItemsContainer.getAdapter()).search(query));
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/browseractions/BrowserActionActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/browseractions/BrowserActionActivityTest.java
index 9cc5535..d59d08f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/browseractions/BrowserActionActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/browseractions/BrowserActionActivityTest.java
@@ -40,7 +40,6 @@
 import org.chromium.base.ApplicationStatus;
 import org.chromium.base.Callback;
 import org.chromium.base.ContextUtils;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.MinAndroidSdkLevel;
@@ -66,6 +65,7 @@
 import org.chromium.content_public.browser.LoadUrlParams;
 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 org.chromium.net.test.EmbeddedTestServer;
 
 import java.util.ArrayList;
@@ -147,12 +147,8 @@
 
     @Before
     public void setUp() throws Exception {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                FirstRunStatus.setFirstRunFlowComplete(true);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { FirstRunStatus.setFirstRunFlowComplete(true); });
         mTestDelegate = new TestDelegate();
         mTestObserver = new EmptyTabModelObserver() {
             @Override
@@ -171,12 +167,8 @@
 
     @After
     public void tearDown() throws Exception {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                FirstRunStatus.setFirstRunFlowComplete(false);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { FirstRunStatus.setFirstRunFlowComplete(false); });
         mTestServer.stopAndDestroyServer();
     }
 
@@ -249,12 +241,8 @@
     @Test
     @SmallTest
     public void testMenuShownCorrectlyWithFRENotComplete() throws Exception {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                FirstRunStatus.setFirstRunFlowComplete(false);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { FirstRunStatus.setFirstRunFlowComplete(false); });
         List<BrowserActionItem> items = createCustomItems();
         BrowserActionActivity activity = startBrowserActionActivity(mTestPage, items, 0);
 
@@ -336,12 +324,8 @@
         final BrowserActionActivity activity = startBrowserActionActivity(mTestPage);
         mOnBrowserActionsMenuShownCallback.waitForCallback(0);
         // Download an url before initialization finishes.
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                activity.getHelperForTesting().onItemSelected(itemid, false);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { activity.getHelperForTesting().onItemSelected(itemid, false); });
 
         // If native initialization is not finished, A ProgressDialog should be displayed and
         // chosen item should be pending until initialization is finished.
@@ -477,8 +461,8 @@
         // No notification should be shown.
         Assert.assertFalse(BrowserActionsService.hasBrowserActionsNotification());
 
-        BrowserActionsTabModelSelector selector =
-                ThreadUtils.runOnUiThreadBlocking(new Callable<BrowserActionsTabModelSelector>() {
+        BrowserActionsTabModelSelector selector = TestThreadUtils.runOnUiThreadBlocking(
+                new Callable<BrowserActionsTabModelSelector>() {
                     @Override
                     public BrowserActionsTabModelSelector call() {
                         return BrowserActionsTabModelSelector.getInstance();
@@ -518,8 +502,8 @@
         mOnBrowserActionsMenuShownCallback.waitForCallback(0);
         mOnFinishNativeInitializationCallback.waitForCallback(0);
 
-        BrowserActionsTabModelSelector selector =
-                ThreadUtils.runOnUiThreadBlocking(new Callable<BrowserActionsTabModelSelector>() {
+        BrowserActionsTabModelSelector selector = TestThreadUtils.runOnUiThreadBlocking(
+                new Callable<BrowserActionsTabModelSelector>() {
                     @Override
                     public BrowserActionsTabModelSelector call() {
                         return BrowserActionsTabModelSelector.getInstance();
@@ -581,16 +565,13 @@
     }
 
     private void openTabInBackground(BrowserActionActivity activity) {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
-                try {
-                    activity.getHelperForTesting().onItemSelected(
-                            R.id.browser_actions_open_in_background, false);
-                } finally {
-                    StrictMode.setThreadPolicy(oldPolicy);
-                }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
+            try {
+                activity.getHelperForTesting().onItemSelected(
+                        R.id.browser_actions_open_in_background, false);
+            } finally {
+                StrictMode.setThreadPolicy(oldPolicy);
             }
         });
         CriteriaHelper.pollUiThread(new Criteria() {
@@ -623,23 +604,17 @@
             }
         });
         String cta2ActivityTabUrl = activity2.getActivityTab().getUrl();
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                activity2.getTabCreator(false).createNewTab(
-                        new LoadUrlParams(mTestPage2), TabLaunchType.FROM_CHROME_UI, null);
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            activity2.getTabCreator(false).createNewTab(
+                    new LoadUrlParams(mTestPage2), TabLaunchType.FROM_CHROME_UI, null);
         });
 
         // Save state and destroy both activities.
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                activity1.saveState();
-                activity2.saveState();
-                activity1.finishAndRemoveTask();
-                activity2.finishAndRemoveTask();
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            activity1.saveState();
+            activity2.saveState();
+            activity1.finishAndRemoveTask();
+            activity2.finishAndRemoveTask();
         });
 
         CriteriaHelper.pollUiThread(new Criteria() {
@@ -658,14 +633,10 @@
         openTabInBackground(activity3);
 
         // Save the Browser Actions tab states and destroy the selector and activity.
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                BrowserActionsTabModelSelector selector =
-                        BrowserActionsTabModelSelector.getInstance();
-                selector.saveState();
-                selector.destroy();
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            BrowserActionsTabModelSelector selector = BrowserActionsTabModelSelector.getInstance();
+            selector.saveState();
+            selector.destroy();
         });
         activity3.finish();
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/OriginVerifierTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/OriginVerifierTest.java
index 7b05035..310017d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/OriginVerifierTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/OriginVerifierTest.java
@@ -15,7 +15,6 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.ContextUtils;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.task.PostTask;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
@@ -30,6 +29,7 @@
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.HashSet;
 import java.util.Set;
@@ -115,8 +115,7 @@
                 mVerificationResultSemaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
         Assert.assertFalse(mLastVerified);
         PostTask.postTask(UiThreadTaskTraits.DEFAULT,
-                ()
-                        -> mHandleAllUrlsVerifier.start(
+                () -> mHandleAllUrlsVerifier.start(
                                 new TestOriginVerificationListener(), mHttpOrigin));
         Assert.assertTrue(
                 mVerificationResultSemaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
@@ -127,22 +126,20 @@
     @SmallTest
     public void testMultipleRelationships() throws Exception {
         PostTask.postTask(UiThreadTaskTraits.DEFAULT,
-                ()
-                        -> OriginVerifier.addVerificationOverride(PACKAGE_NAME, mHttpsOrigin,
+                () -> OriginVerifier.addVerificationOverride(PACKAGE_NAME, mHttpsOrigin,
                                 CustomTabsService.RELATION_USE_AS_ORIGIN));
         PostTask.postTask(UiThreadTaskTraits.DEFAULT,
-                ()
-                        -> mUseAsOriginVerifier.start(
+                () -> mUseAsOriginVerifier.start(
                                 new TestOriginVerificationListener(), mHttpsOrigin));
         Assert.assertTrue(
                 mVerificationResultSemaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
         Assert.assertTrue(mLastVerified);
-        Assert.assertTrue(ThreadUtils.runOnUiThreadBlocking(
+        Assert.assertTrue(TestThreadUtils.runOnUiThreadBlocking(
                 () -> OriginVerifier.wasPreviouslyVerified(PACKAGE_NAME, mHttpsOrigin,
-                        CustomTabsService.RELATION_USE_AS_ORIGIN)));
-        Assert.assertFalse(ThreadUtils.runOnUiThreadBlocking(
+                                CustomTabsService.RELATION_USE_AS_ORIGIN)));
+        Assert.assertFalse(TestThreadUtils.runOnUiThreadBlocking(
                 () -> OriginVerifier.wasPreviouslyVerified(PACKAGE_NAME, mHttpsOrigin,
-                        CustomTabsService.RELATION_HANDLE_ALL_URLS)));
+                                CustomTabsService.RELATION_HANDLE_ALL_URLS)));
         Assert.assertEquals(mLastPackageName, PACKAGE_NAME);
         Assert.assertEquals(mLastOrigin, mHttpsOrigin);
     }
@@ -161,7 +158,7 @@
         preferences.setVerifiedDigitalAssetLinks(savedLinks);
         Assert.assertTrue(preferences.getVerifiedDigitalAssetLinks().contains(relationship));
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             BrowsingDataBridge.getInstance().clearBrowsingData(callbackHelper::notifyCalled,
                     new int[] {BrowsingDataType.HISTORY}, TimePeriod.ALL_TIME);
         });
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityTest.java
index dd0295d0..00c09df 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityTest.java
@@ -21,16 +21,17 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.ContextUtils;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.library_loader.LibraryProcessType;
 import org.chromium.base.library_loader.ProcessInitException;
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.FlakyTest;
 import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule;
 import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
 import org.chromium.chrome.browser.customtabs.CustomTabsTestUtils;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.net.test.ServerCertificate;
 
@@ -76,9 +77,9 @@
 
     /** Caches a successful verification for the given |packageName| and |url|. */
     private static void spoofVerification(String packageName, String url) {
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> OriginVerifier.addVerificationOverride(packageName, new Origin(url),
-                        CustomTabsService.RELATION_HANDLE_ALL_URLS));
+                                CustomTabsService.RELATION_HANDLE_ALL_URLS));
     }
 
     /** Creates a Custom Tabs Session from the Intent, specifying the |packageName|. */
@@ -103,6 +104,7 @@
 
     @Test
     @MediumTest
+    @FlakyTest(message = "https://crbug.com/943847")
     public void launchesTwa() throws TimeoutException, InterruptedException {
         Intent intent = createTrustedWebActivityIntent(mTestPage);
         spoofVerification(PACKAGE_NAME, mTestPage);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/browsing_data/BrowsingDataRemoverIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/browsing_data/BrowsingDataRemoverIntegrationTest.java
index cc780ac..903134f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/browsing_data/BrowsingDataRemoverIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/browsing_data/BrowsingDataRemoverIntegrationTest.java
@@ -13,7 +13,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.RetryOnFailure;
 import org.chromium.chrome.browser.ChromeActivity;
@@ -27,6 +26,7 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 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.Arrays;
 import java.util.HashMap;
@@ -99,23 +99,16 @@
         Assert.assertEquals(apps.keySet(), WebappRegistry.getRegisteredWebappIdsForTesting());
 
         // Clear cookies and site data excluding the registrable domain "google.com".
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                BrowsingDataBridge.getInstance().clearBrowsingDataExcludingDomains(
-                        new OnClearBrowsingDataListener() {
-                            @Override
-                            public void onBrowsingDataCleared() {
-                                mCallbackCalled = true;
-                            }
-                        },
-                        new int[]{ BrowsingDataType.COOKIES },
-                        TimePeriod.ALL_TIME,
-                        new String[]{ "google.com" },
-                        new int[] { 1 },
-                        new String[0],
-                        new int[0]);
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            BrowsingDataBridge.getInstance().clearBrowsingDataExcludingDomains(
+                    new OnClearBrowsingDataListener() {
+                        @Override
+                        public void onBrowsingDataCleared() {
+                            mCallbackCalled = true;
+                        }
+                    },
+                    new int[] {BrowsingDataType.COOKIES}, TimePeriod.ALL_TIME,
+                    new String[] {"google.com"}, new int[] {1}, new String[0], new int[0]);
         });
         CriteriaHelper.pollUiThread(new CallbackCriteria());
 
@@ -124,19 +117,13 @@
                 WebappRegistry.getRegisteredWebappIdsForTesting());
 
         // Clear cookies and site data with no url filter.
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                BrowsingDataBridge.getInstance().clearBrowsingData(
-                        new OnClearBrowsingDataListener() {
-                            @Override
-                            public void onBrowsingDataCleared() {
-                                mCallbackCalled = true;
-                            }
-                        },
-                        new int[]{ BrowsingDataType.COOKIES },
-                        TimePeriod.ALL_TIME);
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            BrowsingDataBridge.getInstance().clearBrowsingData(new OnClearBrowsingDataListener() {
+                @Override
+                public void onBrowsingDataCleared() {
+                    mCallbackCalled = true;
+                }
+            }, new int[] {BrowsingDataType.COOKIES}, TimePeriod.ALL_TIME);
         });
         CriteriaHelper.pollUiThread(new CallbackCriteria());
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerTest.java
index 09c96ca..9578b16 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerTest.java
@@ -24,7 +24,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.library_loader.ProcessInitException;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Restriction;
@@ -44,6 +43,7 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.util.browser.tabmodel.MockTabModel.MockTabModelDelegate;
 import org.chromium.chrome.test.util.browser.tabmodel.MockTabModelSelector;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.test.util.UiRestriction;
 
 /**
@@ -504,15 +504,12 @@
     @Before
     public void setUp() throws Exception {
         // Load the browser process.
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                try {
-                    ChromeBrowserInitializer.getInstance(InstrumentationRegistry.getTargetContext())
-                            .handleSynchronousStartup();
-                } catch (ProcessInitException e) {
-                    Assert.fail("Failed to load browser");
-                }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            try {
+                ChromeBrowserInitializer.getInstance(InstrumentationRegistry.getTargetContext())
+                        .handleSynchronousStartup();
+            } catch (ProcessInitException e) {
+                Assert.fail("Failed to load browser");
             }
         });
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/overlays/strip/TabStripTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/overlays/strip/TabStripTest.java
index fd43adb..676574b4 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/overlays/strip/TabStripTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/overlays/strip/TabStripTest.java
@@ -14,7 +14,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
@@ -34,6 +33,7 @@
 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.DOMUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.base.LocalizationUtils;
 import org.chromium.ui.test.util.UiRestriction;
 
@@ -335,12 +335,9 @@
                 mActivityTestRule.getActivity().getActivityTab().getId());
 
         // 3. Invoke "close all tabs" menu action; block until action is completed
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                TabStripUtils.getActiveStripLayoutHelper(mActivityTestRule.getActivity())
-                        .clickTabMenuItem(StripLayoutHelper.ID_CLOSE_ALL_TABS);
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            TabStripUtils.getActiveStripLayoutHelper(mActivityTestRule.getActivity())
+                    .clickTabMenuItem(StripLayoutHelper.ID_CLOSE_ALL_TABS);
         });
 
         // 4. Ensure all tabs were closed
@@ -474,12 +471,9 @@
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
         // 3. Invoke menu action; block until action is completed
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                TabStripUtils.getActiveStripLayoutHelper(mActivityTestRule.getActivity())
-                        .clickTabMenuItem(StripLayoutHelper.ID_CLOSE_ALL_TABS);
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            TabStripUtils.getActiveStripLayoutHelper(mActivityTestRule.getActivity())
+                    .clickTabMenuItem(StripLayoutHelper.ID_CLOSE_ALL_TABS);
         });
 
         // 4. Ensure all incognito tabs were closed and TabStrip is switched to normal
@@ -1052,14 +1046,11 @@
         TabModel model = mActivityTestRule.getActivity().getCurrentTabModel();
         int selectedTabIndex = model.index();
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                TabStripUtils.getStripLayoutHelper(mActivityTestRule.getActivity(), true)
-                        .setShouldCascadeTabs(shouldCascadeTabs);
-                TabStripUtils.getStripLayoutHelper(mActivityTestRule.getActivity(), false)
-                        .setShouldCascadeTabs(shouldCascadeTabs);
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            TabStripUtils.getStripLayoutHelper(mActivityTestRule.getActivity(), true)
+                    .setShouldCascadeTabs(shouldCascadeTabs);
+            TabStripUtils.getStripLayoutHelper(mActivityTestRule.getActivity(), false)
+                    .setShouldCascadeTabs(shouldCascadeTabs);
         });
 
         // Assert that the correct StripStacker is being used.
@@ -1092,12 +1083,8 @@
     private void assertSetTabStripScrollOffset(final int scrollOffset) throws ExecutionException {
         final StripLayoutHelper strip =
                 TabStripUtils.getActiveStripLayoutHelper(mActivityTestRule.getActivity());
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                strip.setScrollOffsetForTesting(scrollOffset);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { strip.setScrollOffsetForTesting(scrollOffset); });
 
         Assert.assertEquals("Tab strip scroll incorrect.", scrollOffset, strip.getScrollOffset());
         compareAllTabStripsWithModel();
@@ -1170,7 +1157,7 @@
                 tabView.getVisiblePercentage(), 1.0f, 0);
 
         // Only tabs that can currently be seen on the screen should be visible.
-        Boolean shouldBeVisible = ThreadUtils.runOnUiThreadBlocking(new Callable<Boolean>() {
+        Boolean shouldBeVisible = TestThreadUtils.runOnUiThreadBlocking(new Callable<Boolean>() {
             @Override
             public Boolean call() throws Exception {
                 return (tabView.getDrawX() + tabView.getWidth()) >= 0
@@ -1187,7 +1174,7 @@
      */
     private void assertTabVisibility(final Boolean shouldBeVisible, final StripLayoutTab tabView)
             throws ExecutionException {
-        Boolean isVisible = ThreadUtils.runOnUiThreadBlocking(new Callable<Boolean>() {
+        Boolean isVisible = TestThreadUtils.runOnUiThreadBlocking(new Callable<Boolean>() {
             @Override
             public Boolean call() throws Exception {
                 return tabView.isVisible();
@@ -1207,7 +1194,7 @@
      */
     private void assertTabDrawX(float expectedDrawX, final StripLayoutTab tabView, int index)
             throws ExecutionException {
-        Float tabDrawX = ThreadUtils.runOnUiThreadBlocking(new Callable<Float>() {
+        Float tabDrawX = TestThreadUtils.runOnUiThreadBlocking(new Callable<Float>() {
             @Override
             public Float call() throws Exception {
                 return tabView.getDrawX();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contacts_picker/ContactsPickerDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contacts_picker/ContactsPickerDialogTest.java
index 14bb5344..e29226ee 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contacts_picker/ContactsPickerDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contacts_picker/ContactsPickerDialogTest.java
@@ -22,7 +22,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.chrome.R;
@@ -33,6 +32,7 @@
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.util.browser.RecyclerViewTestUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.browser.test.util.TestTouchUtils;
 import org.chromium.ui.ContactsPickerListener;
 
@@ -121,7 +121,7 @@
     private ContactsPickerDialog createDialog(final boolean multiselect, final boolean includeNames,
             final boolean includeEmails, final boolean includeTel) throws Exception {
         final ContactsPickerDialog dialog =
-                ThreadUtils.runOnUiThreadBlocking(new Callable<ContactsPickerDialog>() {
+                TestThreadUtils.runOnUiThreadBlocking(new Callable<ContactsPickerDialog>() {
                     @Override
                     public ContactsPickerDialog call() {
                         final ContactsPickerDialog dialog =
@@ -215,12 +215,7 @@
     }
 
     private void dismissDialog() {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mDialog.dismiss();
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> mDialog.dismiss());
     }
 
     @Test
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/ContextMenuTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/ContextMenuTest.java
index 98f9e9b..186efdd 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/ContextMenuTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/ContextMenuTest.java
@@ -22,7 +22,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
@@ -42,6 +41,7 @@
 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.DOMUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.browser.test.util.TestTouchUtils;
 import org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.policy.test.annotations.Policies;
@@ -79,13 +79,13 @@
 
     @Before
     public void setUp() throws Exception {
-        ThreadUtils.runOnUiThreadBlocking(() -> FirstRunStatus.setFirstRunFlowComplete(true));
+        TestThreadUtils.runOnUiThreadBlocking(() -> FirstRunStatus.setFirstRunFlowComplete(true));
     }
 
     @After
     public void tearDown() throws Exception {
         mTestServer.stopAndDestroyServer();
-        ThreadUtils.runOnUiThreadBlocking(() -> FirstRunStatus.setFirstRunFlowComplete(false));
+        TestThreadUtils.runOnUiThreadBlocking(() -> FirstRunStatus.setFirstRunFlowComplete(false));
         deleteTestFiles();
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuUiTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuUiTest.java
index 3c276168..d7bf7d96 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuUiTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuUiTest.java
@@ -21,7 +21,6 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.CollectionUtil;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
@@ -30,6 +29,7 @@
 import org.chromium.chrome.browser.contextmenu.ChromeContextMenuItem.Item;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.common.Referrer;
 import org.chromium.ui.base.MenuSourceType;
 
@@ -97,7 +97,7 @@
                                           .inflate(R.layout.tabular_context_menu, null);
         final TabularContextMenuViewPager pager =
                 (TabularContextMenuViewPager) tabularContextMenu.findViewById(R.id.custom_pager);
-        View view = ThreadUtils.runOnUiThreadBlocking(new Callable<View>() {
+        View view = TestThreadUtils.runOnUiThreadBlocking(new Callable<View>() {
             @Override
             public View call() {
                 return dialog.initPagerView(mActivityTestRule.getActivity(),
@@ -129,7 +129,7 @@
                                           .inflate(R.layout.tabular_context_menu, null);
         final TabularContextMenuViewPager pager =
                 (TabularContextMenuViewPager) tabularContextMenu.findViewById(R.id.custom_pager);
-        View view = ThreadUtils.runOnUiThreadBlocking(new Callable<View>() {
+        View view = TestThreadUtils.runOnUiThreadBlocking(new Callable<View>() {
             @Override
             public View call() {
                 return dialog.initPagerView(mActivityTestRule.getActivity(),
@@ -152,7 +152,7 @@
                         new ChromeContextMenuItem(Item.COPY_LINK_ADDRESS));
         final String createdUrl = "http://google.com";
         final String expectedUrlWithFormatUrlForDisplayOmitHTTPScheme = "google.com";
-        View view = ThreadUtils.runOnUiThreadBlocking(new Callable<View>() {
+        View view = TestThreadUtils.runOnUiThreadBlocking(new Callable<View>() {
             @Override
             public View call() {
                 return dialog.createContextMenuPageUi(mActivityTestRule.getActivity(),
@@ -177,7 +177,7 @@
                 CollectionUtil.newArrayList(new ChromeContextMenuItem(Item.ADD_TO_CONTACTS),
                         new ChromeContextMenuItem(Item.CALL),
                         new ChromeContextMenuItem(Item.COPY_LINK_ADDRESS));
-        View view = ThreadUtils.runOnUiThreadBlocking(new Callable<View>() {
+        View view = TestThreadUtils.runOnUiThreadBlocking(new Callable<View>() {
             @Override
             public View call() {
                 return dialog.createContextMenuPageUi(mActivityTestRule.getActivity(),
@@ -198,7 +198,7 @@
                 CollectionUtil.newArrayList(new ChromeContextMenuItem(Item.ADD_TO_CONTACTS),
                         new ChromeContextMenuItem(Item.CALL),
                         new ChromeContextMenuItem(Item.COPY_LINK_ADDRESS));
-        View view = ThreadUtils.runOnUiThreadBlocking(new Callable<View>() {
+        View view = TestThreadUtils.runOnUiThreadBlocking(new Callable<View>() {
             @Override
             public View call() {
                 return dialog.createContextMenuPageUi(mActivityTestRule.getActivity(),
@@ -213,12 +213,7 @@
         Assert.assertEquals("Expected a different number of default maximum lines.",
                 expectedMaxLines, actualMaxLines);
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                headerTextView.callOnClick();
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> headerTextView.callOnClick());
 
         expectedMaxLines = Integer.MAX_VALUE;
         actualMaxLines = headerTextView.getMaxLines();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextual_suggestions/EnabledStateMonitorTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextual_suggestions/EnabledStateMonitorTest.java
index 9278b29a..518fb92 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextual_suggestions/EnabledStateMonitorTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextual_suggestions/EnabledStateMonitorTest.java
@@ -13,7 +13,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags.Add;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Restriction;
@@ -26,6 +25,7 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 import org.chromium.components.signin.ChromeSigninController;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.policy.test.annotations.Policies;
 import org.chromium.ui.test.util.UiRestriction;
 
@@ -55,7 +55,7 @@
         });
 
         mActivityTestRule.startMainActivityOnBlankPage();
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             mOriginalSignedInAccountName = ChromeSigninController.get().getSignedInAccountName();
             ChromeSigninController.get().setSignedInAccountName("test@gmail.com");
             UnifiedConsentServiceBridge.setUrlKeyedAnonymizedDataCollectionEnabled(true);
@@ -65,7 +65,7 @@
 
     @After
     public void tearDown() throws Exception {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             ChromeSigninController.get().setSignedInAccountName(mOriginalSignedInAccountName);
         });
     }
@@ -75,7 +75,7 @@
     @Feature({"ContextualSuggestions"})
     @Policies.Add({ @Policies.Item(key = "ContextualSuggestionsEnabled", string = "false") })
     public void testEnterprisePolicy_Disabled() {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Assert.assertFalse(mEnabledStateMonitor.getEnabledState());
             Assert.assertFalse(mEnabledStateMonitor.getSettingsEnabled());
         });
@@ -86,7 +86,7 @@
     @Feature({"ContextualSuggestions"})
     @Policies.Add({ @Policies.Item(key = "ContextualSuggestionsEnabled", string = "true") })
     public void testEnterprisePolicy_Enabled() {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Assert.assertTrue(mEnabledStateMonitor.getEnabledState());
             Assert.assertTrue(mEnabledStateMonitor.getSettingsEnabled());
         });
@@ -97,7 +97,7 @@
     @Feature({"ContextualSuggestions"})
     @Policies.Remove({ @Policies.Item(key = "ContextualSuggestionsEnabled") })
     public void testEnterprisePolicy_DefaultEnabled() {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Assert.assertTrue(mEnabledStateMonitor.getEnabledState());
             Assert.assertTrue(mEnabledStateMonitor.getSettingsEnabled());
         });
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java
index 93407f8..bcaf036d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java
@@ -35,7 +35,6 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.ContextUtils;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.task.PostTask;
 import org.chromium.base.test.util.CallbackHelper;
@@ -90,6 +89,7 @@
 import org.chromium.content_public.browser.test.util.DOMUtils;
 import org.chromium.content_public.browser.test.util.KeyUtils;
 import org.chromium.content_public.browser.test.util.TestSelectionPopupController;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.browser.test.util.TouchCommon;
 import org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.ui.base.PageTransition;
@@ -188,12 +188,7 @@
         // We have to set up the test server before starting the activity.
         mTestServer = EmbeddedTestServer.createAndStartServer(InstrumentationRegistry.getContext());
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                FirstRunStatus.setFirstRunFlowComplete(true);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> FirstRunStatus.setFirstRunFlowComplete(true));
         LocaleManager.setInstanceForTest(new LocaleManager() {
             @Override
             public boolean needToCheckForSearchEnginePromo() {
@@ -238,12 +233,7 @@
     public void tearDown() throws Exception {
         clearUnifiedConsentMetadata();
         mTestServer.stopAndDestroyServer();
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                FirstRunStatus.setFirstRunFlowComplete(false);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> FirstRunStatus.setFirstRunFlowComplete(false));
     }
 
     /**
@@ -264,12 +254,7 @@
         mFakeServer.setIsOnline(isOnline);
         final String testUrl = mTestServer.getURL(TEST_PAGE);
         final Tab tab = mActivityTestRule.getActivity().getActivityTab();
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                tab.reload();
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> tab.reload());
         // Make sure the page is fully loaded.
         ChromeTabUtils.waitForTabPageLoaded(tab, testUrl);
     }
@@ -1034,16 +1019,13 @@
      * Resets all the counters used, by resetting all shared preferences.
      */
     private void resetCounters() {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
-                boolean freStatus = prefs.getBoolean(FirstRunStatus.FIRST_RUN_FLOW_COMPLETE, false);
-                prefs.edit()
-                        .clear()
-                        .putBoolean(FirstRunStatus.FIRST_RUN_FLOW_COMPLETE, freStatus)
-                        .apply();
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
+            boolean freStatus = prefs.getBoolean(FirstRunStatus.FIRST_RUN_FLOW_COMPLETE, false);
+            prefs.edit()
+                    .clear()
+                    .putBoolean(FirstRunStatus.FIRST_RUN_FLOW_COMPLETE, freStatus)
+                    .apply();
         });
     }
 
@@ -2182,12 +2164,8 @@
 
         // Dismiss select action mode.
         assertWaitForSelectActionBarVisible(true);
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                getSelectionPopupController().destroySelectActionMode();
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> getSelectionPopupController().destroySelectActionMode());
         assertWaitForSelectActionBarVisible(false);
 
         waitForPanelToClose();
@@ -2213,15 +2191,12 @@
         Assert.assertEquals("Search", getSelectedText());
 
         // Simulate a selection change event and assert that the panel has not reappeared.
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                SelectionClient selectionClient = mManager.getContextualSearchSelectionClient();
-                selectionClient.onSelectionEvent(
-                        SelectionEventType.SELECTION_HANDLE_DRAG_STARTED, 333, 450);
-                selectionClient.onSelectionEvent(
-                        SelectionEventType.SELECTION_HANDLE_DRAG_STOPPED, 303, 450);
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            SelectionClient selectionClient = mManager.getContextualSearchSelectionClient();
+            selectionClient.onSelectionEvent(
+                    SelectionEventType.SELECTION_HANDLE_DRAG_STARTED, 333, 450);
+            selectionClient.onSelectionEvent(
+                    SelectionEventType.SELECTION_HANDLE_DRAG_STOPPED, 303, 450);
         });
         assertPanelClosedOrUndefined();
 
@@ -2846,23 +2821,15 @@
         Assert.assertFalse(imageControl.getThumbnailVisible());
         Assert.assertTrue(TextUtils.isEmpty(imageControl.getThumbnailUrl()));
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                imageControl.setThumbnailUrl("http://someimageurl.com/image.png");
-                imageControl.onThumbnailFetched(true);
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            imageControl.setThumbnailUrl("http://someimageurl.com/image.png");
+            imageControl.onThumbnailFetched(true);
         });
 
         Assert.assertTrue(imageControl.getThumbnailVisible());
         Assert.assertEquals(imageControl.getThumbnailUrl(), "http://someimageurl.com/image.png");
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                imageControl.hideCustomImage(false);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> imageControl.hideCustomImage(false));
 
         Assert.assertFalse(imageControl.getThumbnailVisible());
         Assert.assertTrue(TextUtils.isEmpty(imageControl.getThumbnailUrl()));
@@ -2912,13 +2879,9 @@
 
         // Simulate a tap to show the Bar, then set the quick action data.
         simulateTapSearch("search");
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mPanel.onSearchTermResolved("search", null, "tel:555-555-5555",
-                        QuickActionCategory.PHONE);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mPanel.onSearchTermResolved(
+                                "search", null, "tel:555-555-5555", QuickActionCategory.PHONE));
 
         ContextualSearchBarControl barControl = mPanel.getSearchBarControl();
         ContextualSearchQuickActionControl quickActionControl = barControl.getQuickActionControl();
@@ -2933,12 +2896,7 @@
         Assert.assertEquals(1.f, imageControl.getCustomImageVisibilityPercentage(), 0);
 
         // Expand the bar.
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mPanel.simulateTapOnEndButton();
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> mPanel.simulateTapOnEndButton());
         waitForPanelToExpand();
 
         // Check that the expanded bar is showing the correct image and caption.
@@ -2981,13 +2939,9 @@
 
         // Simulate a tap to show the Bar, then set the quick action data.
         simulateTapSearch("search");
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mPanel.onSearchTermResolved("search", null, "tel:555-555-5555",
-                        QuickActionCategory.PHONE);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mPanel.onSearchTermResolved(
+                                "search", null, "tel:555-555-5555", QuickActionCategory.PHONE));
 
         // Tap on the portion of the bar that should trigger the quick action intent to be fired.
         clickPanelBar();
@@ -3010,12 +2964,9 @@
 
         // Simulate a tap to show the Bar, then set the quick action data.
         simulateTapSearch("search");
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mPanel.onSearchTermResolved("search", null, testUrl, QuickActionCategory.WEBSITE);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mPanel.onSearchTermResolved(
+                                "search", null, testUrl, QuickActionCategory.WEBSITE));
 
         // Tap on the portion of the bar that should trigger the quick action.
         clickPanelBar();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTapEventTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTapEventTest.java
index e44d0c6c..fe42d1f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTapEventTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTapEventTest.java
@@ -14,7 +14,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Restriction;
@@ -32,6 +31,7 @@
 import org.chromium.content_public.browser.SelectionPopupController;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.test.util.TestSelectionPopupController;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.base.ActivityWindowAndroid;
 import org.chromium.ui.base.ViewAndroidDelegate;
 import org.chromium.ui.resources.dynamics.DynamicResourceLoader;
@@ -188,13 +188,9 @@
      */
     private void mockLongpressText(String text) {
         mContextualSearchManager.getBaseSelectionPopupController().setSelectedText(text);
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mContextualSearchClient.onSelectionEvent(SelectionEventType.SELECTION_HANDLES_SHOWN,
-                        0, 0);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mContextualSearchClient.onSelectionEvent(
+                                SelectionEventType.SELECTION_HANDLES_SHOWN, 0, 0));
     }
 
     /**
@@ -202,12 +198,9 @@
      */
     private void mockTapText(String text) {
         mContextualSearchManager.getBaseSelectionPopupController().setSelectedText(text);
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mContextualSearchManager.getGestureStateListener().onTouchDown();
-                mContextualSearchManager.onShowUnhandledTapUIIfNeeded(0, 0, 12, 100);
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mContextualSearchManager.getGestureStateListener().onTouchDown();
+            mContextualSearchManager.onShowUnhandledTapUIIfNeeded(0, 0, 12, 100);
         });
     }
 
@@ -215,13 +208,10 @@
      * Trigger empty space tap.
      */
     private void mockTapEmptySpace() {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mContextualSearchManager.onShowUnhandledTapUIIfNeeded(0, 0, 0, 0);
-                mContextualSearchClient.onSelectionEvent(
-                        SelectionEventType.SELECTION_HANDLES_CLEARED, 0, 0);
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mContextualSearchManager.onShowUnhandledTapUIIfNeeded(0, 0, 0, 0);
+            mContextualSearchClient.onSelectionEvent(
+                    SelectionEventType.SELECTION_HANDLES_CLEARED, 0, 0);
         });
     }
 
@@ -229,14 +219,10 @@
      * Generates a call indicating that surrounding text and selection range are available.
      */
     private void generateTextSurroundingSelectionAvailable() {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                // It only makes sense to send dummy data here because we can't easily control
-                // what's in the native context.
-                mContextualSearchManager.onTextSurroundingSelectionAvailable(
-                        "UTF-8", "unused", 0, 0);
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            // It only makes sense to send dummy data here because we can't easily control
+            // what's in the native context.
+            mContextualSearchManager.onTextSurroundingSelectionAvailable("UTF-8", "unused", 0, 0);
         });
     }
 
@@ -245,13 +231,10 @@
      * action has completed with the given result.
      */
     private void generateSelectWordAroundCaretAck() {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                // It only makes sense to send dummy data here because we can't easily control
-                // what's in the native context.
-                mContextualSearchClient.selectWordAroundCaretAck(true, 0, 0);
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            // It only makes sense to send dummy data here because we can't easily control
+            // what's in the native context.
+            mContextualSearchClient.selectWordAroundCaretAck(true, 0, 0);
         });
     }
 
@@ -262,22 +245,18 @@
         mActivityTestRule.startMainActivityOnBlankPage();
         final ChromeActivity activity = mActivityTestRule.getActivity();
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mPanelManager = new OverlayPanelManagerWrapper();
-                mPanelManager.setContainerView(new LinearLayout(activity));
-                mPanelManager.setDynamicResourceLoader(new DynamicResourceLoader(0, null));
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mPanelManager = new OverlayPanelManagerWrapper();
+            mPanelManager.setContainerView(new LinearLayout(activity));
+            mPanelManager.setDynamicResourceLoader(new DynamicResourceLoader(0, null));
 
-                mContextualSearchManager = new ContextualSearchManagerWrapper(activity);
-                mPanel = new ContextualSearchPanelWrapper(activity,
-                        activity.getCompositorViewHolder().getLayoutManager(), mPanelManager);
-                mPanel.setManagementDelegate(mContextualSearchManager);
-                mContextualSearchManager.setContextualSearchPanel(mPanel);
+            mContextualSearchManager = new ContextualSearchManagerWrapper(activity);
+            mPanel = new ContextualSearchPanelWrapper(
+                    activity, activity.getCompositorViewHolder().getLayoutManager(), mPanelManager);
+            mPanel.setManagementDelegate(mContextualSearchManager);
+            mContextualSearchManager.setContextualSearchPanel(mPanel);
 
-                mContextualSearchClient =
-                        mContextualSearchManager.getContextualSearchSelectionClient();
-            }
+            mContextualSearchClient = mContextualSearchManager.getContextualSearchSelectionClient();
         });
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/crash/PureJavaExceptionReporterTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/crash/PureJavaExceptionReporterTest.java
index a91f113..d1d2539c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/crash/PureJavaExceptionReporterTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/crash/PureJavaExceptionReporterTest.java
@@ -11,11 +11,11 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.components.crash.CrashKeyIndex;
 import org.chromium.components.crash.CrashKeys;
 import org.chromium.components.minidump_uploader.CrashTestRule;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -106,7 +106,7 @@
     @Test
     @SmallTest
     public void verifyCrashKeys() {
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> { CrashKeys.getInstance().set(CrashKeyIndex.LOADED_DYNAMIC_MODULE, "foo"); });
 
         TestPureJavaExceptionReporter reporter = new TestPureJavaExceptionReporter();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/crypto/CipherFactoryTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/crypto/CipherFactoryTest.java
index 87e20c6..6082506 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/crypto/CipherFactoryTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/crypto/CipherFactoryTest.java
@@ -12,9 +12,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.chrome.browser.crypto.CipherFactory.CipherDataObserver;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.io.IOException;
 import java.security.GeneralSecurityException;
@@ -263,13 +263,13 @@
         CipherFactory.getInstance().addCipherDataObserver(observer);
         Assert.assertEquals(0, observer.getTimesNotified());
         CipherFactory.getInstance().getCipher(Cipher.DECRYPT_MODE);
-        ThreadUtils.runOnUiThreadBlocking(mEmptyRunnable);
+        TestThreadUtils.runOnUiThreadBlocking(mEmptyRunnable);
         Assert.assertEquals(1, observer.getTimesNotified());
         CipherFactory.getInstance().getCipher(Cipher.DECRYPT_MODE);
-        ThreadUtils.runOnUiThreadBlocking(mEmptyRunnable);
+        TestThreadUtils.runOnUiThreadBlocking(mEmptyRunnable);
         Assert.assertEquals(1, observer.getTimesNotified());
         CipherFactory.getInstance().getCipher(Cipher.ENCRYPT_MODE);
-        ThreadUtils.runOnUiThreadBlocking(mEmptyRunnable);
+        TestThreadUtils.runOnUiThreadBlocking(mEmptyRunnable);
         Assert.assertEquals(1, observer.getTimesNotified());
         CipherFactory.getInstance().removeCipherDataObserver(observer);
     }
@@ -283,13 +283,13 @@
     public void testCipherFactoryObserverTooLate() throws Exception {
         CipherFactory.getInstance().getCipher(Cipher.DECRYPT_MODE);
         // Ensures that cipher finishes initializing before running the rest of the test.
-        ThreadUtils.runOnUiThreadBlocking(mEmptyRunnable);
+        TestThreadUtils.runOnUiThreadBlocking(mEmptyRunnable);
         TestCipherDataObserver observer = new TestCipherDataObserver();
         CipherFactory.getInstance().addCipherDataObserver(observer);
-        ThreadUtils.runOnUiThreadBlocking(mEmptyRunnable);
+        TestThreadUtils.runOnUiThreadBlocking(mEmptyRunnable);
         Assert.assertEquals(0, observer.getTimesNotified());
         CipherFactory.getInstance().getCipher(Cipher.DECRYPT_MODE);
-        ThreadUtils.runOnUiThreadBlocking(mEmptyRunnable);
+        TestThreadUtils.runOnUiThreadBlocking(mEmptyRunnable);
         Assert.assertEquals(0, observer.getTimesNotified());
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/display_cutout/DisplayCutoutTestRule.java b/chrome/android/javatests/src/org/chromium/chrome/browser/display_cutout/DisplayCutoutTestRule.java
index 897cc13..cfb94d7 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/display_cutout/DisplayCutoutTestRule.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/display_cutout/DisplayCutoutTestRule.java
@@ -13,7 +13,6 @@
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.annotations.UsedByReflection;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.fullscreen.FullscreenOptions;
@@ -25,6 +24,7 @@
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
 import org.chromium.content_public.browser.test.util.DOMUtils;
 import org.chromium.content_public.browser.test.util.JavaScriptUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.net.test.EmbeddedTestServer;
 
 import java.util.concurrent.TimeoutException;
@@ -174,8 +174,9 @@
     protected void setUp() throws Exception {
         mTab = getActivity().getActivityTab();
         mTestController = new TestDisplayCutoutController(mTab);
-        ThreadUtils.runOnUiThreadBlocking(() -> DisplayCutoutController.initForTesting(
-                mTab.getUserDataHost(), mTestController));
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> DisplayCutoutController.initForTesting(
+                                mTab.getUserDataHost(), mTestController));
 
         FullscreenTabObserver observer = new FullscreenTabObserver();
         mTab.addObserver(observer);
@@ -257,7 +258,7 @@
 
     /** Set the viewport-fit value using internal APIs. */
     public void setViewportFitInternal(@WebContentsObserver.ViewportFitType int value) {
-        ThreadUtils.runOnUiThreadBlocking(() -> mTestController.setViewportFit(value));
+        TestThreadUtils.runOnUiThreadBlocking(() -> mTestController.setViewportFit(value));
     }
 
     /** Get the safe area using JS and parse the JSON result to a Rect. */
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/dom_distiller/DistilledPagePrefsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/dom_distiller/DistilledPagePrefsTest.java
index 9672ec8..ef43248 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/dom_distiller/DistilledPagePrefsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/dom_distiller/DistilledPagePrefsTest.java
@@ -16,7 +16,6 @@
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
@@ -26,6 +25,7 @@
 import org.chromium.components.dom_distiller.core.DomDistillerService;
 import org.chromium.components.dom_distiller.core.FontFamily;
 import org.chromium.components.dom_distiller.core.Theme;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.browser.test.util.UiUtils;
 
 /**
@@ -47,13 +47,10 @@
     }
 
     private void getDistilledPagePrefs() {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                DomDistillerService domDistillerService = DomDistillerServiceFactory
-                        .getForProfile(Profile.getLastUsedProfile());
-                mDistilledPagePrefs = domDistillerService.getDistilledPagePrefs();
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            DomDistillerService domDistillerService =
+                    DomDistillerServiceFactory.getForProfile(Profile.getLastUsedProfile());
+            mDistilledPagePrefs = domDistillerService.getDistilledPagePrefs();
         });
     }
 
@@ -289,29 +286,14 @@
     }
 
     private void setFontFamily(final FontFamily font) {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mDistilledPagePrefs.setFontFamily(font);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> mDistilledPagePrefs.setFontFamily(font));
     }
 
     private void setTheme(final Theme theme) {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mDistilledPagePrefs.setTheme(theme);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> mDistilledPagePrefs.setTheme(theme));
     }
 
     private void setFontScaling(final float scaling) {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mDistilledPagePrefs.setFontScaling(scaling);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> mDistilledPagePrefs.setFontScaling(scaling));
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadActivityTest.java
index b8c5581..477c81e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadActivityTest.java
@@ -205,8 +205,7 @@
         spaceDisplayCallCount = mAdapterObserver.onSpaceDisplayUpdatedCallback.getCallCount();
         final OfflineItem deletedPage = StubbedProvider.createOfflineItem(3, "20151021 07:28");
         PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT,
-                ()
-                        -> mStubbedProvider.getOfflineContentProvider().observer.onItemRemoved(
+                () -> mStubbedProvider.getOfflineContentProvider().observer.onItemRemoved(
                                 deletedPage.id));
         mAdapterObserver.onChangedCallback.waitForCallback(callCount, 2);
         mAdapterObserver.onSpaceDisplayUpdatedCallback.waitForCallback(spaceDisplayCallCount);
@@ -278,8 +277,7 @@
                 0, mStubbedProvider.getOfflineContentProvider().deleteItemCallback.getCallCount());
         int callCount = mAdapterObserver.onSpaceDisplayUpdatedCallback.getCallCount();
         PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT,
-                ()
-                        -> Assert.assertTrue(
+                () -> Assert.assertTrue(
                                 mUi.getDownloadManagerToolbarForTests()
                                         .getMenu()
                                         .performIdentifierAction(
@@ -372,8 +370,7 @@
         // Click the delete button.
         callCount = mAdapterObserver.onSpaceDisplayUpdatedCallback.getCallCount();
         PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT,
-                ()
-                        -> Assert.assertTrue(
+                () -> Assert.assertTrue(
                                 mUi.getDownloadManagerToolbarForTests()
                                         .getMenu()
                                         .performIdentifierAction(
@@ -512,8 +509,7 @@
         // Click the delete button.
         callCount = mAdapterObserver.onSpaceDisplayUpdatedCallback.getCallCount();
         PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT,
-                ()
-                        -> Assert.assertTrue(
+                () -> Assert.assertTrue(
                                 mUi.getDownloadManagerToolbarForTests()
                                         .getMenu()
                                         .performIdentifierAction(
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/explore_sites/ExploreSitesBridgeExperimentalTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/explore_sites/ExploreSitesBridgeExperimentalTest.java
index 562d287..2b79aaf 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/explore_sites/ExploreSitesBridgeExperimentalTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/explore_sites/ExploreSitesBridgeExperimentalTest.java
@@ -17,11 +17,11 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.Callback;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.UrlUtils;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.test.ChromeBrowserTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.net.test.EmbeddedTestServer;
 
 import java.util.concurrent.Semaphore;
@@ -42,7 +42,7 @@
 
     @Before
     public void setUp() throws Exception {
-        ThreadUtils.runOnUiThreadBlocking(() -> { mProfile = Profile.getLastUsedProfile(); });
+        TestThreadUtils.runOnUiThreadBlocking(() -> { mProfile = Profile.getLastUsedProfile(); });
         mTestServer = EmbeddedTestServer.createAndStartServer(InstrumentationRegistry.getContext());
     }
 
@@ -80,19 +80,15 @@
         // Use an AtomicReference and assert on the Instrumentation thread so that failures show
         // up as proper failures instead of browser crashes.
         final AtomicReference<Bitmap> actualIcon = new AtomicReference<Bitmap>();
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                ExploreSitesBridgeExperimental.getIcon(
-                        mProfile, testImageUrl, new Callback<Bitmap>() {
-                            @Override
-                            public void onResult(Bitmap icon) {
-                                actualIcon.set(icon);
-                                semaphore.release();
-                            }
-                        });
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> ExploreSitesBridgeExperimental.getIcon(
+                                mProfile, testImageUrl, new Callback<Bitmap>() {
+                                    @Override
+                                    public void onResult(Bitmap icon) {
+                                        actualIcon.set(icon);
+                                        semaphore.release();
+                                    }
+                                }));
         Assert.assertTrue(semaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
         Assert.assertNotNull(actualIcon.get());
         Assert.assertTrue(bitmapsEqual(expectedIcon, actualIcon.get()));
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/explore_sites/ExploreSitesPageTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/explore_sites/ExploreSitesPageTest.java
index 749ede5..008c2cb 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/explore_sites/ExploreSitesPageTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/explore_sites/ExploreSitesPageTest.java
@@ -23,7 +23,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
@@ -37,6 +36,7 @@
 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.ArrayList;
 
@@ -111,7 +111,8 @@
         // scroll bar to disappear.
         SystemClock.sleep(3000);
         mActivityTestRule.loadUrl("about:blank");
-        ThreadUtils.runOnUiThreadBlocking(() -> mActivityTestRule.getActivity().onBackPressed());
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mActivityTestRule.getActivity().onBackPressed());
         mRenderTestRule.render(mRecyclerView, "recycler_layout_back");
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
index 2b355b9e..8b9109a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
@@ -25,7 +25,6 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.ApiCompatibilityUtils;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.RetryOnFailure;
@@ -43,6 +42,7 @@
 import org.chromium.content_public.browser.NavigationHandle;
 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 org.chromium.content_public.browser.test.util.TouchCommon;
 import org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.ui.base.PageTransition;
@@ -252,7 +252,7 @@
     }
 
     private static InterceptNavigationDelegateImpl getInterceptNavigationDelegate(Tab tab) {
-        return ThreadUtils.runOnUiThreadBlockingNoException(
+        return TestThreadUtils.runOnUiThreadBlockingNoException(
                 () -> InterceptNavigationDelegateImpl.get(tab));
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/feature_engagement/ScreenshotMonitorTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/feature_engagement/ScreenshotMonitorTest.java
index 757dc9d..16b8a9f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/feature_engagement/ScreenshotMonitorTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/feature_engagement/ScreenshotMonitorTest.java
@@ -19,6 +19,7 @@
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
@@ -51,13 +52,10 @@
     public void setUp() throws Exception {
         mTestScreenshotMonitorDelegate = new TestScreenshotMonitorDelegate();
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mTestScreenshotMonitor = new ScreenshotMonitor(mTestScreenshotMonitorDelegate);
-                mContentObserver = mTestScreenshotMonitor.getContentObserver();
-                mTestScreenshotMonitor.setSkipOsCallsForUnitTesting();
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mTestScreenshotMonitor = new ScreenshotMonitor(mTestScreenshotMonitorDelegate);
+            mContentObserver = mTestScreenshotMonitor.getContentObserver();
+            mTestScreenshotMonitor.setSkipOsCallsForUnitTesting();
         });
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/feature_engagement/ScreenshotTabObserverTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/feature_engagement/ScreenshotTabObserverTest.java
index 0639243c..75f9ffe 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/feature_engagement/ScreenshotTabObserverTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/feature_engagement/ScreenshotTabObserverTest.java
@@ -12,7 +12,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.MetricsUtils;
@@ -22,6 +21,7 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.List;
 
@@ -42,7 +42,7 @@
     public void setUp() throws Exception {
         mActivityTestRule.startMainActivityOnBlankPage();
         mTab = mActivityTestRule.getActivity().getActivityTab();
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 (Runnable) () -> mObserver = ScreenshotTabObserver.from(mTab));
     }
 
@@ -52,7 +52,7 @@
         UserActionTester userActionTester = new UserActionTester();
         mObserver.onScreenshotTaken();
         // Must wait for the user action to arrive on the UI thread before checking it.
-        ThreadUtils.runOnUiThreadBlocking((Runnable) () -> {
+        TestThreadUtils.runOnUiThreadBlocking((Runnable) () -> {
             List<String> actions = userActionTester.getActions();
             Assert.assertEquals("Tab.Screenshot", actions.get(0));
         });
@@ -70,7 +70,7 @@
         MetricsUtils.HistogramDelta histogramDeltaTwoScreenshots =
                 new MetricsUtils.HistogramDelta("Tab.Screenshot.ScreenshotsPerPage", 2);
         mObserver.onScreenshotTaken();
-        ThreadUtils.runOnUiThreadBlocking((Runnable) () -> mTab.destroy());
+        TestThreadUtils.runOnUiThreadBlocking((Runnable) () -> mTab.destroy());
         // Check the first 3 buckets of the NumberOfScrenshots metric.
         Assert.assertEquals("Should be no pages with zero snapshots reported", 0,
                 histogramDeltaZeroScreenshots.getDelta());
@@ -89,7 +89,7 @@
                 new MetricsUtils.HistogramDelta("Tab.Screenshot.ScreenshotsPerPage", 2);
         mObserver.onScreenshotTaken();
         mObserver.onScreenshotTaken();
-        ThreadUtils.runOnUiThreadBlocking((Runnable) () -> mTab.destroy());
+        TestThreadUtils.runOnUiThreadBlocking((Runnable) () -> mTab.destroy());
         Assert.assertEquals("Should be one page with two snapshots reported", 1,
                 histogramDeltaTwoScreenshots.getDelta());
     }
@@ -107,7 +107,7 @@
                 new MetricsUtils.HistogramDelta("Tab.Screenshot.Action", 2);
         mObserver.onScreenshotTaken();
         mObserver.onActionPerformedAfterScreenshot(ScreenshotTabObserver.SCREENSHOT_ACTION_SHARE);
-        ThreadUtils.runOnUiThreadBlocking((Runnable) () -> mTab.destroy());
+        TestThreadUtils.runOnUiThreadBlocking((Runnable) () -> mTab.destroy());
         Assert.assertEquals("Should be no none actions reported", 0,
                 histogramDeltaScreenshotNoAction.getDelta());
         Assert.assertEquals("Should be one share action reported", 1,
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/feedback/ConnectivityCheckerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/feedback/ConnectivityCheckerTest.java
index 083d5be..332bcd6 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/feedback/ConnectivityCheckerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/feedback/ConnectivityCheckerTest.java
@@ -13,10 +13,10 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
@@ -150,16 +150,12 @@
             final boolean useSystemStack) throws Exception {
         Semaphore semaphore = new Semaphore(0);
         final Callback callback = new Callback(semaphore);
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                if (useSystemStack) {
-                    ConnectivityChecker.checkConnectivitySystemNetworkStack(
-                            url, timeoutMs, callback);
-                } else {
-                    ConnectivityChecker.checkConnectivityChromeNetworkStack(
-                            Profile.getLastUsedProfile(), url, timeoutMs, callback);
-                }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            if (useSystemStack) {
+                ConnectivityChecker.checkConnectivitySystemNetworkStack(url, timeoutMs, callback);
+            } else {
+                ConnectivityChecker.checkConnectivityChromeNetworkStack(
+                        Profile.getLastUsedProfile(), url, timeoutMs, callback);
             }
         });
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/feedback/ConnectivityTaskTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/feedback/ConnectivityTaskTest.java
index 0cfffdb..4ff554d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/feedback/ConnectivityTaskTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/feedback/ConnectivityTaskTest.java
@@ -15,7 +15,6 @@
 import org.junit.rules.ExpectedException;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.browser.feedback.ConnectivityTask.FeedbackData;
@@ -23,6 +22,7 @@
 import org.chromium.chrome.browser.profiles.Profile;
 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 org.chromium.net.ConnectionType;
 
 import java.util.HashMap;
@@ -49,8 +49,8 @@
     @MediumTest
     @Feature({"Feedback"})
     public void testNormalCaseShouldWork() {
-        final ConnectivityTask task = ThreadUtils.runOnUiThreadBlockingNoException(
-                new Callable<ConnectivityTask>() {
+        final ConnectivityTask task =
+                TestThreadUtils.runOnUiThreadBlockingNoException(new Callable<ConnectivityTask>() {
                     @Override
                     public ConnectivityTask call() {
                         // Intentionally make HTTPS-connection fail which should result in
@@ -115,15 +115,12 @@
                 semaphore.release();
             }
         };
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                // Intentionally make HTTPS-connection fail which should result in NOT_CONNECTED.
-                ConnectivityChecker.overrideUrlsForTest(
-                        mConnectivityCheckerTestRule.getGenerated204Url(),
-                        mConnectivityCheckerTestRule.getGenerated404Url());
-                ConnectivityTask.create(Profile.getLastUsedProfile(), TIMEOUT_MS, callback);
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            // Intentionally make HTTPS-connection fail which should result in NOT_CONNECTED.
+            ConnectivityChecker.overrideUrlsForTest(
+                    mConnectivityCheckerTestRule.getGenerated204Url(),
+                    mConnectivityCheckerTestRule.getGenerated404Url());
+            ConnectivityTask.create(Profile.getLastUsedProfile(), TIMEOUT_MS, callback);
         });
         if (!semaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
             Assert.fail("Failed to acquire semaphore.");
@@ -148,15 +145,12 @@
                 semaphore.release();
             }
         };
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                // Intentionally make HTTPS connections slow which should result in TIMEOUT.
-                ConnectivityChecker.overrideUrlsForTest(
-                        mConnectivityCheckerTestRule.getGenerated204Url(),
-                        mConnectivityCheckerTestRule.getGeneratedSlowUrl());
-                ConnectivityTask.create(Profile.getLastUsedProfile(), checkTimeoutMs, callback);
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            // Intentionally make HTTPS connections slow which should result in TIMEOUT.
+            ConnectivityChecker.overrideUrlsForTest(
+                    mConnectivityCheckerTestRule.getGenerated204Url(),
+                    mConnectivityCheckerTestRule.getGeneratedSlowUrl());
+            ConnectivityTask.create(Profile.getLastUsedProfile(), checkTimeoutMs, callback);
         });
         if (!semaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
             Assert.fail("Failed to acquire semaphore.");
@@ -173,8 +167,8 @@
     @Feature({"Feedback"})
     @SuppressWarnings("TryFailThrowable") // TODO(tedchoc): Remove after fixing timeout.
     public void testTwoTimeoutsShouldFillInTheRest() {
-        final ConnectivityTask task = ThreadUtils.runOnUiThreadBlockingNoException(
-                new Callable<ConnectivityTask>() {
+        final ConnectivityTask task =
+                TestThreadUtils.runOnUiThreadBlockingNoException(new Callable<ConnectivityTask>() {
                     @Override
                     public ConnectivityTask call() {
                         // Intentionally make HTTPS connections slow which should result in
@@ -228,8 +222,8 @@
     }
 
     private static FeedbackData getResult(final ConnectivityTask task) {
-        final FeedbackData result = ThreadUtils.runOnUiThreadBlockingNoException(
-                new Callable<FeedbackData>() {
+        final FeedbackData result =
+                TestThreadUtils.runOnUiThreadBlockingNoException(new Callable<FeedbackData>() {
                     @Override
                     public FeedbackData call() {
                         return task.get();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTest.java
index f800826..5ce4715 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTest.java
@@ -24,7 +24,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.task.PostTask;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
@@ -53,6 +52,7 @@
 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.JavaScriptUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.browser.test.util.TestTouchUtils;
 import org.chromium.content_public.browser.test.util.TouchCommon;
 import org.chromium.content_public.browser.test.util.UiUtils;
@@ -137,12 +137,8 @@
 
     @Before
     public void setUp() throws Exception {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                TabStateBrowserControlsVisibilityDelegate.disablePageLoadDelayForTests();
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> TabStateBrowserControlsVisibilityDelegate.disablePageLoadDelayForTests());
     }
 
     @Test
@@ -383,21 +379,18 @@
                     }
                 });
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                // Check that when the browser controls are gone, the entire decorView is contained
-                // in the transparent region of the app.
-                Rect visibleDisplayFrame = new Rect();
-                Region transparentRegion = new Region();
-                ViewGroup decorView =
-                        (ViewGroup) mActivityTestRule.getActivity().getWindow().getDecorView();
-                decorView.getWindowVisibleDisplayFrame(visibleDisplayFrame);
-                decorView.gatherTransparentRegion(transparentRegion);
-                Assert.assertTrue("Transparent region " + transparentRegion.getBounds()
-                                + " should contain " + visibleDisplayFrame,
-                        transparentRegion.quickContains(visibleDisplayFrame));
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            // Check that when the browser controls are gone, the entire decorView is contained
+            // in the transparent region of the app.
+            Rect visibleDisplayFrame = new Rect();
+            Region transparentRegion = new Region();
+            ViewGroup decorView =
+                    (ViewGroup) mActivityTestRule.getActivity().getWindow().getDecorView();
+            decorView.getWindowVisibleDisplayFrame(visibleDisplayFrame);
+            decorView.gatherTransparentRegion(transparentRegion);
+            Assert.assertTrue("Transparent region " + transparentRegion.getBounds()
+                            + " should contain " + visibleDisplayFrame,
+                    transparentRegion.quickContains(visibleDisplayFrame));
         });
 
         // Additional manual test that this is working:
@@ -550,7 +543,7 @@
         FullscreenManagerTestUtils.waitForBrowserControlsPosition(
                 mActivityTestRule, -browserControlsHeight);
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Assert.assertTrue("Navigation bar not hidden.",
                     (view.getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)
                             == 0);
@@ -570,7 +563,7 @@
         FullscreenManagerTestUtils.waitForBrowserControlsPosition(
                 mActivityTestRule, -browserControlsHeight);
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Assert.assertTrue("Navigation bar hidden.",
                     (view.getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)
                             != 0);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTestUtils.java b/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTestUtils.java
index 15b2ad8..4786867b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTestUtils.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTestUtils.java
@@ -19,6 +19,7 @@
 import org.chromium.content_public.browser.WebContents;
 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 org.chromium.content_public.browser.test.util.TouchCommon;
 import org.chromium.content_public.browser.test.util.WebContentsUtils;
 
@@ -225,12 +226,8 @@
      * Disable any browser visibility overrides for testing.
      */
     public static void disableBrowserOverrides() {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                BrowserStateBrowserControlsVisibilityDelegate.disableForTesting();
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> BrowserStateBrowserControlsVisibilityDelegate.disableForTesting());
     }
 
     public static void fling(ChromeTabbedActivityTestRule testRule, final int vx, final int vy) {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/hardware_acceleration/ToastHWATest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/hardware_acceleration/ToastHWATest.java
index 928be153..2fc49d6 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/hardware_acceleration/ToastHWATest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/hardware_acceleration/ToastHWATest.java
@@ -20,7 +20,6 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.BaseSwitches;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.task.PostTask;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
@@ -37,6 +36,7 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.util.browser.contextmenu.ContextMenuUtils;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.ui.widget.Toast;
 
@@ -63,12 +63,7 @@
 
     @Before
     public void setUp() throws Exception {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                FirstRunStatus.setFirstRunFlowComplete(true);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> FirstRunStatus.setFirstRunFlowComplete(true));
 
         mDownloadTestRule.deleteFilesInDownloadDirectory(TEST_FILES);
         mTestServer = EmbeddedTestServer.createAndStartServer(InstrumentationRegistry.getContext());
@@ -76,12 +71,7 @@
 
     @After
     public void tearDown() throws Exception {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                FirstRunStatus.setFirstRunFlowComplete(false);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> FirstRunStatus.setFirstRunFlowComplete(false));
 
         mTestServer.stopAndDestroyServer();
         mDownloadTestRule.deleteFilesInDownloadDirectory(TEST_FILES);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/hardware_acceleration/Utils.java b/chrome/android/javatests/src/org/chromium/chrome/browser/hardware_acceleration/Utils.java
index 7daa33b..ce81739 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/hardware_acceleration/Utils.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/hardware_acceleration/Utils.java
@@ -11,11 +11,11 @@
 import org.junit.Assert;
 
 import org.chromium.base.SysUtils;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.task.PostTask;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.HashSet;
 import java.util.Set;
@@ -58,21 +58,18 @@
         final AtomicBoolean accelerated = new AtomicBoolean();
         final CallbackHelper listenerCalled = new CallbackHelper();
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                final View view = activity.getWindow().getDecorView();
-                view.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        view.getViewTreeObserver().removeOnPreDrawListener(this);
-                        accelerated.set(view.isHardwareAccelerated());
-                        listenerCalled.notifyCalled();
-                        return true;
-                    }
-                });
-                view.invalidate();
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            final View view = activity.getWindow().getDecorView();
+            view.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
+                @Override
+                public boolean onPreDraw() {
+                    view.getViewTreeObserver().removeOnPreDrawListener(this);
+                    accelerated.set(view.isHardwareAccelerated());
+                    listenerCalled.notifyCalled();
+                    return true;
+                }
+            });
+            view.invalidate();
         });
 
         listenerCalled.waitForCallback(0);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/history/HistoryActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/history/HistoryActivityTest.java
index 7f1c8f45..4da4dae 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/history/HistoryActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/history/HistoryActivityTest.java
@@ -35,7 +35,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.task.PostTask;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
@@ -63,6 +62,7 @@
 import org.chromium.content_public.browser.UiThreadTaskTraits;
 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 org.chromium.ui.base.PageTransition;
 import org.chromium.ui.test.util.UiRestriction;
 
@@ -153,12 +153,8 @@
         launchHistoryActivity();
         if (!mAdapter.isClearBrowsingDataButtonVisible()) {
             int changedCallCount = mTestObserver.onChangedCallback.getCallCount();
-            ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-                @Override
-                public void run() {
-                    mAdapter.setClearBrowsingDataButtonVisibilityForTest(true);
-                }
-            });
+            TestThreadUtils.runOnUiThreadBlocking(
+                    () -> mAdapter.setClearBrowsingDataButtonVisibilityForTest(true));
             mTestObserver.onChangedCallback.waitForCallback(changedCallCount);
         }
 
@@ -192,12 +188,8 @@
         int callCount = mTestObserver.onChangedCallback.getCallCount();
         final SelectableItemView<HistoryItem> itemView = getItemView(2);
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                itemView.findViewById(R.id.remove).performClick();
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> itemView.findViewById(R.id.remove).performClick());
 
         // Check that one item was removed.
         mTestObserver.onChangedCallback.waitForCallback(callCount, 1);
@@ -343,12 +335,7 @@
 
         mHistoryProvider.removeItem(mItem1);
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mAdapter.onHistoryDeleted();
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> mAdapter.onHistoryDeleted());
 
         // The selection should be cleared and the items in the adapter should be reloaded.
         Assert.assertFalse(mHistoryManager.getSelectionDelegateForTests().isSelectionEnabled());
@@ -427,12 +414,8 @@
         Assert.assertTrue(mHistoryManager.getSelectionDelegateForTests().isSelectionEnabled());
 
         int callCount = mTestObserver.onSelectionCallback.getCallCount();
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                toolbar.getMenu().performIdentifierAction(R.id.search_menu_id, 0);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> toolbar.getMenu().performIdentifierAction(R.id.search_menu_id, 0));
 
         // The selection should be cleared when a search is started.
         mTestObserver.onSelectionCallback.waitForCallback(callCount, 1);
@@ -453,12 +436,7 @@
         Assert.assertEquals(View.VISIBLE, toolbarSearchView.getVisibility());
 
         // Close the search view.
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                toolbar.onNavigationBack();
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> toolbar.onNavigationBack());
         Assert.assertEquals(View.GONE, toolbarShadow.getVisibility());
         Assert.assertEquals(View.GONE, toolbarSearchView.getVisibility());
     }
@@ -480,12 +458,7 @@
         // Signed in but not synced and history has items. The info button should be hidden.
         signinController.setSignedInAccountName("test@gmail.com");
         setHasOtherFormsOfBrowsingData(false);
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                toolbar.onSignInStateChange();
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> toolbar.onSignInStateChange());
         Assert.assertFalse(infoMenuItem.isVisible());
 
         // Signed in, synced, has other forms and has items
@@ -497,23 +470,13 @@
         Assert.assertEquals(2, headerGroup.size());
 
         // Toggle Info Menu Item to off
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mHistoryManager.onMenuItemClick(infoMenuItem);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> mHistoryManager.onMenuItemClick(infoMenuItem));
         headerGroup = mAdapter.getFirstGroupForTests();
         Assert.assertTrue(mAdapter.hasListHeader());
         Assert.assertEquals(1, headerGroup.size());
 
         // Toggle Info Menu Item to on
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mHistoryManager.onMenuItemClick(infoMenuItem);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> mHistoryManager.onMenuItemClick(infoMenuItem));
         headerGroup = mAdapter.getFirstGroupForTests();
         Assert.assertTrue(mAdapter.hasListHeader());
         Assert.assertEquals(2, headerGroup.size());
@@ -532,12 +495,9 @@
         ChromeSigninController signinController = ChromeSigninController.get();
         signinController.setSignedInAccountName("test@gmail.com");
         setHasOtherFormsOfBrowsingData(true);
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                toolbar.onSignInStateChange();
-                mAdapter.onSignInStateChange();
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            toolbar.onSignInStateChange();
+            mAdapter.onSignInStateChange();
         });
         mTestObserver.onChangedCallback.waitForCallback(callCount, 1);
         DateDividedAdapter.ItemGroup firstGroup = mAdapter.getFirstGroupForTests();
@@ -547,12 +507,8 @@
 
         // Enter search mode
         callCount = mTestObserver.onSelectionCallback.getCallCount();
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                toolbar.getMenu().performIdentifierAction(R.id.search_menu_id, 0);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> toolbar.getMenu().performIdentifierAction(R.id.search_menu_id, 0));
 
         mTestObserver.onSelectionCallback.waitForCallback(callCount, 1);
         firstGroup = mAdapter.getFirstGroupForTests();
@@ -572,12 +528,9 @@
         // Not sign in and set clear browsing data button to invisible
         ChromeSigninController signinController = ChromeSigninController.get();
         signinController.setSignedInAccountName(null);
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mAdapter.setClearBrowsingDataButtonVisibilityForTest(false);
-                mAdapter.setPrivacyDisclaimer();
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mAdapter.setClearBrowsingDataButtonVisibilityForTest(false);
+            mAdapter.setPrivacyDisclaimer();
         });
 
         DateDividedAdapter.ItemGroup firstGroup = mAdapter.getFirstGroupForTests();
@@ -588,12 +541,13 @@
     @Test
     @SmallTest
     public void testCopyLink() throws Exception {
-        final ClipboardManager clipboardManager = ThreadUtils.runOnUiThreadBlocking(() -> {
-                ClipboardManager manager = (ClipboardManager) mActivityTestRule.getActivity()
-                    .getSystemService(Context.CLIPBOARD_SERVICE);
-                Assert.assertNotNull(manager);
-                manager.setPrimaryClip(ClipData.newPlainText(null, ""));
-                return manager;
+        final ClipboardManager clipboardManager = TestThreadUtils.runOnUiThreadBlocking(() -> {
+            ClipboardManager manager =
+                    (ClipboardManager) mActivityTestRule.getActivity().getSystemService(
+                            Context.CLIPBOARD_SERVICE);
+            Assert.assertNotNull(manager);
+            manager.setPrimaryClip(ClipData.newPlainText(null, ""));
+            return manager;
         });
         // Clear the clipboard to make sure we start with a clean state.
 
@@ -604,14 +558,11 @@
         Assert.assertTrue(toolbar.getItemById(R.id.selection_mode_copy_link).isVisible());
 
         // Check that link is copied to the clipboard.
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                Assert.assertTrue(
-                        mHistoryManager.getToolbarForTests().getMenu().performIdentifierAction(
-                                R.id.selection_mode_copy_link, 0));
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> Assert.assertTrue(mHistoryManager.getToolbarForTests()
+                                                     .getMenu()
+                                                     .performIdentifierAction(
+                                                             R.id.selection_mode_copy_link, 0)));
         CriteriaHelper.pollUiThread(new Criteria() {
             @Override
             public boolean isSatisfied() {
@@ -635,12 +586,7 @@
 
     private void clickItem(int position) throws Exception {
         final SelectableItemView<HistoryItem> itemView = getItemView(position);
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                itemView.performClick();
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> itemView.performClick());
     }
 
     @SuppressWarnings("unchecked")
@@ -652,17 +598,13 @@
     }
 
     private void setHasOtherFormsOfBrowsingData(final boolean hasOtherForms) {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mAdapter.hasOtherFormsOfBrowsingData(hasOtherForms);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mAdapter.hasOtherFormsOfBrowsingData(hasOtherForms));
     }
 
     private void signInToSupervisedAccount() throws Exception {
         // Initialize PrefChangeRegistrar for test.
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             mPrefChangeRegistrar = new PrefChangeRegistrar();
             mPrefChangeRegistrar.addObserver(Pref.ALLOW_DELETING_BROWSER_HISTORY, mTestObserver);
             mPrefChangeRegistrar.addObserver(Pref.INCOGNITO_MODE_AVAILABILITY, mTestObserver);
@@ -671,7 +613,7 @@
         // Sign in to account. Note that if supervised user is set before sign in, the supervised
         // user setting will be reset.
         final Account account = SigninTestUtil.addTestAccount();
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             SigninManager.get().onFirstRunCheckDone();
             SigninManager.get().addSignInStateObserver(mTestObserver);
             SigninManager.get().signIn(account, null, null);
@@ -691,7 +633,7 @@
 
         // Set supervised user.
         int onPreferenceChangeCallCount = mTestObserver.onPreferenceChangeCallback.getCallCount();
-        Assert.assertTrue(ThreadUtils.runOnUiThreadBlocking(() -> {
+        Assert.assertTrue(TestThreadUtils.runOnUiThreadBlocking(() -> {
             PrefServiceBridge.getInstance().setSupervisedUserId("ChildAccountSUID");
             return Profile.getLastUsedProfile().isChild()
                     && !PrefServiceBridge.getInstance().getBoolean(
@@ -714,7 +656,7 @@
         });
 
         // Clean up PrefChangeRegistrar for test.
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             mPrefChangeRegistrar.destroy();
             mPrefChangeRegistrar = null;
         });
@@ -722,30 +664,18 @@
 
     private void signOut() throws Exception {
         // Clear supervised user id.
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                PrefServiceBridge.getInstance().setSupervisedUserId("");
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> PrefServiceBridge.getInstance().setSupervisedUserId(""));
 
         // Sign out of account.
         int currentCallCount = mTestObserver.onSigninStateChangedCallback.getCallCount();
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                SigninManager.get().signOut(SignoutReason.SIGNOUT_TEST);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> SigninManager.get().signOut(SignoutReason.SIGNOUT_TEST));
         mTestObserver.onSigninStateChangedCallback.waitForCallback(currentCallCount, 1);
         Assert.assertNull(SigninTestUtil.getCurrentAccount());
 
         // Remove observer
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                SigninManager.get().removeSignInStateObserver(mTestObserver);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> SigninManager.get().removeSignInStateObserver(mTestObserver));
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/history/HistoryAdapterTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/history/HistoryAdapterTest.java
index 131d5ed..7bef315 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/history/HistoryAdapterTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/history/HistoryAdapterTest.java
@@ -12,11 +12,11 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.ContextUtils;
-import org.chromium.base.ThreadUtils;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.widget.DateDividedAdapter.ItemViewType;
 import org.chromium.chrome.browser.widget.selection.SelectionDelegate;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.Date;
 import java.util.concurrent.TimeUnit;
@@ -37,12 +37,7 @@
     }
 
     private void initializeAdapter() {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable(){
-            @Override
-            public void run() {
-                mAdapter.initialize();
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> mAdapter.initialize());
     }
 
     @Test
@@ -218,12 +213,7 @@
 
         mHistoryProvider.removeItem(item1);
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mAdapter.onHistoryDeleted();
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> mAdapter.onHistoryDeleted());
 
         checkAdapterContents(false);
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoNotificationServiceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoNotificationServiceTest.java
index 6035185..4874c52 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoNotificationServiceTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoNotificationServiceTest.java
@@ -16,7 +16,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
@@ -33,6 +32,7 @@
 import org.chromium.content_public.browser.LoadUrlParams;
 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.io.File;
 import java.util.concurrent.Callable;
@@ -47,13 +47,10 @@
     public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
 
     private void createTabOnUiThread() {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mActivityTestRule.getActivity().getTabCreator(true).createNewTab(
-                        new LoadUrlParams("about:blank"), TabLaunchType.FROM_CHROME_UI, null);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                (Runnable) () -> mActivityTestRule.getActivity().getTabCreator(true).createNewTab(
+                                new LoadUrlParams("about:blank"), TabLaunchType.FROM_CHROME_UI,
+                                null));
     }
 
     private void sendClearIncognitoIntent() throws CanceledException {
@@ -84,8 +81,8 @@
             }
         }));
 
-        final Profile incognitoProfile = ThreadUtils.runOnUiThreadBlockingNoException(
-                new Callable<Profile>() {
+        final Profile incognitoProfile =
+                TestThreadUtils.runOnUiThreadBlockingNoException(new Callable<Profile>() {
                     @Override
                     public Profile call() throws Exception {
                         return mActivityTestRule.getActivity()
@@ -94,12 +91,9 @@
                                 .getProfile();
                     }
                 });
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                Assert.assertTrue(incognitoProfile.isOffTheRecord());
-                Assert.assertTrue(incognitoProfile.isNativeInitialized());
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Assert.assertTrue(incognitoProfile.isOffTheRecord());
+            Assert.assertTrue(incognitoProfile.isNativeInitialized());
         });
 
         sendClearIncognitoIntent();
@@ -197,11 +191,7 @@
             }
         }));
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                Assert.assertFalse(LibraryLoader.getInstance().isInitialized());
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> Assert.assertFalse(LibraryLoader.getInstance().isInitialized()));
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoTabLauncherTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoTabLauncherTest.java
index d1b0642..1bd878d0 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoTabLauncherTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoTabLauncherTest.java
@@ -14,7 +14,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.browser.ChromeFeatureList;
@@ -24,6 +23,7 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.util.browser.Features;
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 /**
  * Tests for {@link IncognitoTabLauncher}.
@@ -80,7 +80,7 @@
         // ApplicationStatus internally, which ignores Tasks and tracks all Chrome Activities.
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
-        ThreadUtils.runOnUiThreadBlocking(() -> context.startActivity(intent));
+        TestThreadUtils.runOnUiThreadBlocking(() -> context.startActivity(intent));
 
         return ChromeActivityTestRule.waitFor(ChromeTabbedActivity.class);
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/InfoBarAppearanceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/InfoBarAppearanceTest.java
index eefed199..3450e35 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/InfoBarAppearanceTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/InfoBarAppearanceTest.java
@@ -13,7 +13,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
@@ -27,6 +26,7 @@
 import org.chromium.chrome.test.util.InfoBarTestAnimationListener;
 import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.List;
 import java.util.concurrent.TimeoutException;
@@ -77,7 +77,7 @@
         List<InfoBar> infobars;
         FramebustBlockInfoBar infoBar;
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             mTab.getTabWebContentsDelegateAndroid().showFramebustBlockInfobarForTesting(url1);
         });
         infobars = mActivityTestRule.getInfoBarContainer().getInfoBarsForTesting();
@@ -85,7 +85,7 @@
         infoBar = (FramebustBlockInfoBar) infobars.get(0);
         assertEquals(url1, infoBar.getBlockedUrl());
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             mTab.getTabWebContentsDelegateAndroid().showFramebustBlockInfobarForTesting(url2);
         });
         infobars = mActivityTestRule.getInfoBarContainer().getInfoBarsForTesting();
@@ -109,7 +109,7 @@
         };
         mTab.addObserver(navigationWaiter);
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             mTab.getTabWebContentsDelegateAndroid().showFramebustBlockInfobarForTesting(url);
         });
         FramebustBlockInfoBar infoBar =
@@ -117,10 +117,10 @@
                         .getInfoBarsForTesting()
                         .get(0);
 
-        ThreadUtils.runOnUiThreadBlocking(infoBar::onLinkClicked); // Once to expand the infobar
+        TestThreadUtils.runOnUiThreadBlocking(infoBar::onLinkClicked); // Once to expand the infobar
         assertEquals(0, callbackHelper.getCallCount());
 
-        ThreadUtils.runOnUiThreadBlocking(infoBar::onLinkClicked); // Now to navigate
+        TestThreadUtils.runOnUiThreadBlocking(infoBar::onLinkClicked); // Now to navigate
         callbackHelper.waitForCallback(0);
 
         CriteriaHelper.pollUiThread(
@@ -134,7 +134,7 @@
             throws TimeoutException, InterruptedException {
         String url = "http://very.evil.biz";
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             mTab.getTabWebContentsDelegateAndroid().showFramebustBlockInfobarForTesting(url);
         });
         FramebustBlockInfoBar infoBar =
@@ -142,7 +142,7 @@
                         .getInfoBarsForTesting()
                         .get(0);
 
-        ThreadUtils.runOnUiThreadBlocking(() -> infoBar.onButtonClicked(true));
+        TestThreadUtils.runOnUiThreadBlocking(() -> infoBar.onButtonClicked(true));
         CriteriaHelper.pollUiThread(
                 () -> InfoBarContainer.get(mTab).getInfoBarsForTesting().isEmpty());
     }
@@ -160,7 +160,7 @@
     @MediumTest
     @Feature({"InfoBars", "UiCatalogue"})
     public void testOomInfoBar() throws TimeoutException, InterruptedException {
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> InfoBarContainer.get(mTab).addInfoBarForTesting(new NearOomInfoBar()));
         mListener.addInfoBarAnimationFinished("InfoBar was not added.");
         mScreenShooter.shoot("oom_infobar");
@@ -168,12 +168,12 @@
 
     private void captureMiniAndRegularInfobar(InfoBar infobar)
             throws TimeoutException, InterruptedException {
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> InfoBarContainer.get(mTab).addInfoBarForTesting(infobar));
         mListener.addInfoBarAnimationFinished("InfoBar was not added.");
         mScreenShooter.shoot("compact");
 
-        ThreadUtils.runOnUiThreadBlocking(infobar::onLinkClicked);
+        TestThreadUtils.runOnUiThreadBlocking(infobar::onLinkClicked);
         mListener.swapInfoBarAnimationFinished("InfoBar did not expand.");
         mScreenShooter.shoot("expanded");
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/InfoBarContainerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/InfoBarContainerTest.java
index 48592de..5949d4e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/InfoBarContainerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/InfoBarContainerTest.java
@@ -19,7 +19,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.task.PostTask;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
@@ -36,6 +35,7 @@
 import org.chromium.content_public.browser.UiThreadTaskTraits;
 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 org.chromium.net.test.EmbeddedTestServer;
 
 import java.util.List;
@@ -200,18 +200,18 @@
     public void testInfoBarExpirationNoPrerender() throws Exception {
         // Save prediction preference.
         boolean networkPredictionEnabled =
-                ThreadUtils.runOnUiThreadBlocking(new Callable<Boolean>() {
+                TestThreadUtils.runOnUiThreadBlocking(new Callable<Boolean>() {
                     @Override
                     public Boolean call() {
                         return PrefServiceBridge.getInstance().getNetworkPredictionEnabled();
                     }
                 });
         try {
-            ThreadUtils.runOnUiThreadBlocking(setNetworkPredictionOptions(false));
+            TestThreadUtils.runOnUiThreadBlocking(setNetworkPredictionOptions(false));
             testInfoBarExpiration();
         } finally {
             // Make sure we restore prediction preference.
-            ThreadUtils.runOnUiThreadBlocking(
+            TestThreadUtils.runOnUiThreadBlocking(
                     setNetworkPredictionOptions(networkPredictionEnabled));
         }
     }
@@ -245,14 +245,11 @@
         Assert.assertEquals(1, mActivityTestRule.getInfoBars().size());
         final InfoBar infoBar = mActivityTestRule.getInfoBars().get(0);
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                Assert.assertEquals(0, infobarListener.dismissedCallback.getCallCount());
-                infoBar.onCloseButtonClicked();
-                mActivityTestRule.getActivity().getTabModelSelector().closeTab(
-                        mActivityTestRule.getActivity().getActivityTab());
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Assert.assertEquals(0, infobarListener.dismissedCallback.getCallCount());
+            infoBar.onCloseButtonClicked();
+            mActivityTestRule.getActivity().getTabModelSelector().closeTab(
+                    mActivityTestRule.getActivity().getActivityTab());
         });
 
         infobarListener.dismissedCallback.waitForCallback(0, 1);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/InfoBarTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/InfoBarTest.java
index 544fb20..4cb3188c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/InfoBarTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/InfoBarTest.java
@@ -19,7 +19,6 @@
 
 import org.chromium.base.CommandLine;
 import org.chromium.base.ContextUtils;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.task.PostTask;
 import org.chromium.base.test.util.AdvancedMockContext;
 import org.chromium.base.test.util.CommandLineFlags;
@@ -46,6 +45,7 @@
 import org.chromium.content_public.browser.WebContents;
 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 org.chromium.net.test.EmbeddedTestServer;
 
 import java.net.HttpURLConnection;
@@ -319,22 +319,19 @@
     @Feature({"Browser", "Main"})
     @RetryOnFailure
     public void testDataReductionPromoInfoBar() {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                Assert.assertFalse("Data Reduction Proxy enabled",
-                        DataReductionProxySettings.getInstance().isDataReductionProxyEnabled());
-                // Fake the FRE or second run promo being shown in M51.
-                DataReductionPromoUtils.saveFreOrSecondRunPromoDisplayed();
-                ContextUtils.getAppSharedPreferences()
-                        .edit()
-                        .putString(SHARED_PREF_DISPLAYED_FRE_OR_SECOND_PROMO_VERSION, M51_VERSION)
-                        .apply();
-                // Add an infobar.
-                Assert.assertTrue(DataReductionPromoInfoBar.maybeLaunchPromoInfoBar(
-                        mActivityTestRule.getActivity(), mActivityTestRule.getWebContents(),
-                        "http://google.com", false, false, HttpURLConnection.HTTP_OK));
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Assert.assertFalse("Data Reduction Proxy enabled",
+                    DataReductionProxySettings.getInstance().isDataReductionProxyEnabled());
+            // Fake the FRE or second run promo being shown in M51.
+            DataReductionPromoUtils.saveFreOrSecondRunPromoDisplayed();
+            ContextUtils.getAppSharedPreferences()
+                    .edit()
+                    .putString(SHARED_PREF_DISPLAYED_FRE_OR_SECOND_PROMO_VERSION, M51_VERSION)
+                    .apply();
+            // Add an infobar.
+            Assert.assertTrue(DataReductionPromoInfoBar.maybeLaunchPromoInfoBar(
+                    mActivityTestRule.getActivity(), mActivityTestRule.getWebContents(),
+                    "http://google.com", false, false, HttpURLConnection.HTTP_OK));
         });
 
         waitUntilDataReductionPromoInfoBarAppears();
@@ -344,30 +341,23 @@
         Assert.assertTrue("InfoBar does not have secondary button",
                 InfoBarUtil.hasSecondaryButton(infoBars.get(0)));
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                InfoBarUtil.clickPrimaryButton(infoBars.get(0));
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                (Runnable) () -> InfoBarUtil.clickPrimaryButton(infoBars.get(0)));
 
         // The renderer should have been killed and the infobar removed.
         InfoBarUtil.waitUntilNoInfoBarsExist(mActivityTestRule.getInfoBars());
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                Assert.assertTrue("Data Reduction Proxy not enabled",
-                        DataReductionProxySettings.getInstance().isDataReductionProxyEnabled());
-                // Turn Data Saver off so the promo can be reshown.
-                DataReductionProxySettings.getInstance().setDataReductionProxyEnabled(
-                        mActivityTestRule.getActivity(), false);
-                // Try to add an infobar. Infobar should not be added since it has already been
-                // shown.
-                Assert.assertFalse(DataReductionPromoInfoBar.maybeLaunchPromoInfoBar(
-                        mActivityTestRule.getActivity(), mActivityTestRule.getWebContents(),
-                        "http://google.com", false, false, HttpURLConnection.HTTP_OK));
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Assert.assertTrue("Data Reduction Proxy not enabled",
+                    DataReductionProxySettings.getInstance().isDataReductionProxyEnabled());
+            // Turn Data Saver off so the promo can be reshown.
+            DataReductionProxySettings.getInstance().setDataReductionProxyEnabled(
+                    mActivityTestRule.getActivity(), false);
+            // Try to add an infobar. Infobar should not be added since it has already been
+            // shown.
+            Assert.assertFalse(DataReductionPromoInfoBar.maybeLaunchPromoInfoBar(
+                    mActivityTestRule.getActivity(), mActivityTestRule.getWebContents(),
+                    "http://google.com", false, false, HttpURLConnection.HTTP_OK));
         });
     }
 
@@ -381,22 +371,19 @@
     @Feature({"Browser", "Main"})
     @RetryOnFailure
     public void testDataReductionPromoInfoBarDismissed() {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                Assert.assertFalse("Data Reduction Proxy enabled",
-                        DataReductionProxySettings.getInstance().isDataReductionProxyEnabled());
-                // Fake the first run experience or second run promo being shown in M51.
-                DataReductionPromoUtils.saveFreOrSecondRunPromoDisplayed();
-                ContextUtils.getAppSharedPreferences()
-                        .edit()
-                        .putString(SHARED_PREF_DISPLAYED_FRE_OR_SECOND_PROMO_VERSION, M51_VERSION)
-                        .apply();
-                // Add an infobar.
-                Assert.assertTrue(DataReductionPromoInfoBar.maybeLaunchPromoInfoBar(
-                        mActivityTestRule.getActivity(), mActivityTestRule.getWebContents(),
-                        "http://google.com", false, false, HttpURLConnection.HTTP_OK));
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Assert.assertFalse("Data Reduction Proxy enabled",
+                    DataReductionProxySettings.getInstance().isDataReductionProxyEnabled());
+            // Fake the first run experience or second run promo being shown in M51.
+            DataReductionPromoUtils.saveFreOrSecondRunPromoDisplayed();
+            ContextUtils.getAppSharedPreferences()
+                    .edit()
+                    .putString(SHARED_PREF_DISPLAYED_FRE_OR_SECOND_PROMO_VERSION, M51_VERSION)
+                    .apply();
+            // Add an infobar.
+            Assert.assertTrue(DataReductionPromoInfoBar.maybeLaunchPromoInfoBar(
+                    mActivityTestRule.getActivity(), mActivityTestRule.getWebContents(),
+                    "http://google.com", false, false, HttpURLConnection.HTTP_OK));
         });
 
         waitUntilDataReductionPromoInfoBarAppears();
@@ -406,27 +393,20 @@
         Assert.assertTrue("InfoBar does not have secondary button",
                 InfoBarUtil.hasSecondaryButton(infoBars.get(0)));
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                InfoBarUtil.clickSecondaryButton(infoBars.get(0));
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                (Runnable) () -> InfoBarUtil.clickSecondaryButton(infoBars.get(0)));
 
         // The renderer should have been killed and the infobar removed.
         InfoBarUtil.waitUntilNoInfoBarsExist(mActivityTestRule.getInfoBars());
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                Assert.assertFalse("Data Reduction Proxy enabled",
-                        DataReductionProxySettings.getInstance().isDataReductionProxyEnabled());
-                // Try to add an infobar. Infobar should not be added since the user clicked
-                // dismiss.
-                Assert.assertFalse(DataReductionPromoInfoBar.maybeLaunchPromoInfoBar(
-                        mActivityTestRule.getActivity(), mActivityTestRule.getWebContents(),
-                        "http://google.com", false, false, HttpURLConnection.HTTP_OK));
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Assert.assertFalse("Data Reduction Proxy enabled",
+                    DataReductionProxySettings.getInstance().isDataReductionProxyEnabled());
+            // Try to add an infobar. Infobar should not be added since the user clicked
+            // dismiss.
+            Assert.assertFalse(DataReductionPromoInfoBar.maybeLaunchPromoInfoBar(
+                    mActivityTestRule.getActivity(), mActivityTestRule.getWebContents(),
+                    "http://google.com", false, false, HttpURLConnection.HTTP_OK));
         });
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/PermissionUpdateInfobarTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/PermissionUpdateInfobarTest.java
index 3f7512c..e9844c2d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/PermissionUpdateInfobarTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/PermissionUpdateInfobarTest.java
@@ -15,7 +15,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.preferences.website.ContentSettingValues;
@@ -28,6 +27,7 @@
 import org.chromium.content_public.browser.WebContents;
 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 org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.ui.base.AndroidPermissionDelegate;
 import org.chromium.ui.base.PermissionCallback;
@@ -86,7 +86,7 @@
 
         final String locationUrl = mTestServer.getURL(GEOLOCATION_PAGE);
         final PermissionInfo geolocationSettings =
-                ThreadUtils.runOnUiThreadBlockingNoException(new Callable<PermissionInfo>() {
+                TestThreadUtils.runOnUiThreadBlockingNoException(new Callable<PermissionInfo>() {
                     @Override
                     public PermissionInfo call() {
                         return new PermissionInfo(
@@ -100,19 +100,15 @@
         LocationSettingsTestUtil.setSystemLocationSettingEnabled(true);
 
         try {
-            ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-                @Override
-                public void run() {
-                    geolocationSettings.setContentSetting(ContentSettingValues.ALLOW);
-                }
-            });
+            TestThreadUtils.runOnUiThreadBlocking(
+                    () -> geolocationSettings.setContentSetting(ContentSettingValues.ALLOW));
 
             mActivityTestRule.loadUrl(mTestServer.getURL(GEOLOCATION_PAGE));
             mListener.addInfoBarAnimationFinished("InfoBar not added");
             Assert.assertEquals(1, mActivityTestRule.getInfoBars().size());
 
-            final WebContents webContents = ThreadUtils.runOnUiThreadBlockingNoException(
-                    new Callable<WebContents>() {
+            final WebContents webContents =
+                    TestThreadUtils.runOnUiThreadBlockingNoException(new Callable<WebContents>() {
                         @Override
                         public WebContents call() throws Exception {
                             return mActivityTestRule.getActivity()
@@ -141,12 +137,8 @@
                 }
             }));
         } finally {
-            ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-                @Override
-                public void run() {
-                    geolocationSettings.setContentSetting(ContentSettingValues.DEFAULT);
-                }
-            });
+            TestThreadUtils.runOnUiThreadBlocking(
+                    () -> geolocationSettings.setContentSetting(ContentSettingValues.DEFAULT));
         }
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/init/ChainedTasksTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/init/ChainedTasksTest.java
index a4e65642..1ab18b2 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/init/ChainedTasksTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/init/ChainedTasksTest.java
@@ -10,10 +10,10 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.task.PostTask;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -52,12 +52,9 @@
         final ChainedTasks tasks = new ChainedTasks();
         for (String message : expectedMessages) tasks.add(new TestRunnable(messages, message));
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                tasks.start(true);
-                Assert.assertEquals(expectedMessages, messages);
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            tasks.start(true);
+            Assert.assertEquals(expectedMessages, messages);
         });
     }
 
@@ -113,12 +110,9 @@
             }
         });
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                tasks.start(false);
-                Assert.assertTrue("No task should run synchronously", messages.isEmpty());
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            tasks.start(false);
+            Assert.assertTrue("No task should run synchronously", messages.isEmpty());
         });
         Assert.assertTrue(finished.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
         Assert.assertEquals(expectedMessages, messages);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/init/ChromeBrowserInitializerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/init/ChromeBrowserInitializerTest.java
index 9efb9650..a4e2cb0 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/init/ChromeBrowserInitializerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/init/ChromeBrowserInitializerTest.java
@@ -11,8 +11,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
@@ -31,7 +31,7 @@
     @Test
     @SmallTest
     public void testSynchronousInitialization() throws Exception {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Assert.assertFalse(mInstance.hasNativeInitializationCompleted());
             mInstance.handleSynchronousStartup();
             Assert.assertTrue(mInstance.hasNativeInitializationCompleted());
@@ -49,7 +49,7 @@
                 done.release();
             }
         };
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Assert.assertFalse(mInstance.hasNativeInitializationCompleted());
             mInstance.handlePreNativeStartup(parts);
             mInstance.handlePostNativeStartup(true, parts);
@@ -58,7 +58,7 @@
                     "Should not be synchronous", mInstance.hasNativeInitializationCompleted());
             return true;
         });
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Assert.assertFalse("Inititialization tasks should yield to new UI thread tasks",
                     mInstance.hasNativeInitializationCompleted());
         });
@@ -69,14 +69,14 @@
     @SmallTest
     public void testDelayedTasks() throws Exception {
         final Semaphore done = new Semaphore(0);
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             mInstance.runNowOrAfterNativeInitialization(done::release);
             Assert.assertFalse("Should not run synchronously", done.tryAcquire());
             mInstance.handleSynchronousStartup();
             Assert.assertTrue(done.tryAcquire());
             return true;
         });
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             mInstance.runNowOrAfterNativeInitialization(done::release);
             // Runs right away in the same task is initialization is done.
             Assert.assertTrue(done.tryAcquire());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/input/SelectPopupOtherContentViewTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/input/SelectPopupOtherContentViewTest.java
index 16eb59f..106fa43d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/input/SelectPopupOtherContentViewTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/input/SelectPopupOtherContentViewTest.java
@@ -12,7 +12,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.RetryOnFailure;
@@ -27,6 +26,7 @@
 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.DOMUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.browser.test.util.WebContentsUtils;
 import org.chromium.ui.base.ActivityWindowAndroid;
 import org.chromium.ui.base.ViewAndroidDelegate;
@@ -71,7 +71,7 @@
     private boolean isSelectPopupVisibleOnUiThread() {
         try {
             // clang-format off
-            return ThreadUtils.runOnUiThreadBlocking(() ->
+            return TestThreadUtils.runOnUiThreadBlocking(() ->
                     WebContentsUtils.isSelectPopupVisible(mActivityTestRule.getWebContents()));
             // clang-format on
         } catch (ExecutionException e) {
@@ -98,18 +98,15 @@
         CriteriaHelper.pollInstrumentationThread(new PopupShowingCriteria());
 
         // Now create and destroy a different WebContents.
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                WebContents webContents = WebContentsFactory.createWebContents(false, false);
-                ChromeActivity activity = mActivityTestRule.getActivity();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            WebContents webContents = WebContentsFactory.createWebContents(false, false);
+            ChromeActivity activity = mActivityTestRule.getActivity();
 
-                ContentView cv = ContentView.createContentView(activity, webContents);
-                webContents.initialize("", ViewAndroidDelegate.createBasicDelegate(cv), cv,
-                        new ActivityWindowAndroid(activity),
-                        WebContents.createDefaultInternalsHolder());
-                webContents.destroy();
-            }
+            ContentView cv = ContentView.createContentView(activity, webContents);
+            webContents.initialize("", ViewAndroidDelegate.createBasicDelegate(cv), cv,
+                    new ActivityWindowAndroid(activity),
+                    WebContents.createDefaultInternalsHolder());
+            webContents.destroy();
         });
 
         // Process some more events to give a chance to the dialog to hide if it were to.
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/instantapps/InstantAppsHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/instantapps/InstantAppsHandlerTest.java
index 2b81a10e..994b400 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/instantapps/InstantAppsHandlerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/instantapps/InstantAppsHandlerTest.java
@@ -23,7 +23,6 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.ContextUtils;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ChromeSwitches;
@@ -32,6 +31,7 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 /**
  * Unit tests for {@link InstantAppsHandler}.
@@ -168,14 +168,12 @@
     @Test
     @SmallTest
     public void testHandleNavigation_startAsyncCheck() {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                Assert.assertFalse(mHandler.handleNavigation(mContext, INSTANT_APP_URL,
-                        REFERRER_URI,
-                        mActivityTestRule.getActivity().getTabModelSelector().getCurrentTab()));
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> Assert.assertFalse(
+                                mHandler.handleNavigation(mContext, INSTANT_APP_URL, REFERRER_URI,
+                                        mActivityTestRule.getActivity()
+                                                .getTabModelSelector()
+                                                .getCurrentTab())));
         Assert.assertFalse(mHandler.mLaunchInstantApp);
         Assert.assertTrue(mHandler.mStartedAsyncCall);
     }
@@ -191,18 +189,14 @@
                 InstrumentationRegistry.getInstrumentation().addMonitor(
                         new IntentFilter(Intent.ACTION_MAIN), null, true);
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mHandler.launchFromBanner(new InstantAppsBannerData("App", null, INSTANT_APP_URL,
-                        REFERRER_URI, i, "Launch",
-                        mActivityTestRule.getActivity()
-                                .getTabModelSelector()
-                                .getCurrentTab()
-                                .getWebContents(),
-                        false));
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mHandler.launchFromBanner(new InstantAppsBannerData("App", null,
+                                INSTANT_APP_URL, REFERRER_URI, i, "Launch",
+                                mActivityTestRule.getActivity()
+                                        .getTabModelSelector()
+                                        .getCurrentTab()
+                                        .getWebContents(),
+                                false)));
 
         // Started instant apps intent
         Assert.assertEquals(1, monitor.getHits());
@@ -216,13 +210,12 @@
 
         // After a banner launch, test that the next launch happens automatically
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                Assert.assertTrue(mHandler.handleNavigation(mContext, INSTANT_APP_URL, REFERRER_URI,
-                        mActivityTestRule.getActivity().getTabModelSelector().getCurrentTab()));
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> Assert.assertTrue(
+                                mHandler.handleNavigation(mContext, INSTANT_APP_URL, REFERRER_URI,
+                                        mActivityTestRule.getActivity()
+                                                .getTabModelSelector()
+                                                .getCurrentTab())));
         Assert.assertFalse(mHandler.mStartedAsyncCall);
         Assert.assertTrue(mHandler.mLaunchInstantApp);
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/jsdialog/JavascriptAppModalDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/jsdialog/JavascriptAppModalDialogTest.java
index 5f79e174..0bcd38b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/jsdialog/JavascriptAppModalDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/jsdialog/JavascriptAppModalDialogTest.java
@@ -21,7 +21,6 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.Log;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags.Add;
 import org.chromium.base.test.util.Feature;
@@ -37,6 +36,7 @@
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
 import org.chromium.content_public.browser.test.util.TestCallbackHelperContainer;
 import org.chromium.content_public.browser.test.util.TestCallbackHelperContainer.OnEvaluateJavaScriptResultHelper;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.browser.test.util.TouchCommon;
 import org.chromium.content_public.browser.test.util.WebContentsUtils;
 
@@ -180,7 +180,7 @@
         tapViewAndWait();
         executeJavaScriptAndWaitForDialog("history.back();");
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             ChromeActivity activity = mActivityTestRule.getActivity();
             activity.getCurrentTabModel().closeTab(activity.getActivityTab());
         });
@@ -229,7 +229,7 @@
      * showing.
      */
     private JavascriptAppModalDialog getCurrentDialog() throws ExecutionException {
-        return ThreadUtils.runOnUiThreadBlocking(
+        return TestThreadUtils.runOnUiThreadBlocking(
                 () -> JavascriptAppModalDialog.getCurrentDialogForTest());
     }
 
@@ -261,7 +261,7 @@
         @Override
         public boolean isSatisfied() {
             try {
-                return ThreadUtils.runOnUiThreadBlocking(() -> {
+                return TestThreadUtils.runOnUiThreadBlocking(() -> {
                     final boolean isShown =
                             JavascriptAppModalDialog.getCurrentDialogForTest() != null;
                     return mShouldBeShown == isShown;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/jsdialog/JavascriptTabModalDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/jsdialog/JavascriptTabModalDialogTest.java
index 1f82ae4..1531697 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/jsdialog/JavascriptTabModalDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/jsdialog/JavascriptTabModalDialogTest.java
@@ -25,7 +25,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Restriction;
@@ -39,6 +38,7 @@
 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.TestCallbackHelperContainer.OnEvaluateJavaScriptResultHelper;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.base.PageTransition;
 import org.chromium.ui.modaldialog.ModalDialogProperties;
 import org.chromium.ui.modelutil.PropertyModel;
@@ -102,7 +102,7 @@
         JavascriptTabModalDialog jsDialog = getCurrentDialog();
         Assert.assertNotNull("No dialog showing.", jsDialog);
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             PropertyModel model = mActivity.getModalDialogManager().getCurrentDialogForTest();
             jsDialog.onClick(model, ModalDialogProperties.ButtonType.POSITIVE);
             jsDialog.onClick(model, ModalDialogProperties.ButtonType.POSITIVE);
@@ -225,7 +225,7 @@
     public void testDialogDismissedAfterClosingTab() {
         executeJavaScriptAndWaitForDialog("alert('Android')");
 
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> { mActivity.getCurrentTabModel().closeTab(mActivity.getActivityTab()); });
 
         // Closing the tab should have dismissed the dialog.
@@ -260,7 +260,7 @@
     public void testDialogDismissedAfterUrlUpdated() {
         executeJavaScriptAndWaitForDialog("alert('Android')");
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             mActivity.getActivityTab().loadUrl(new LoadUrlParams(OTHER_PAGE, PageTransition.LINK));
         });
 
@@ -309,7 +309,7 @@
      * showing.
      */
     private JavascriptTabModalDialog getCurrentDialog() throws ExecutionException {
-        return (JavascriptTabModalDialog) ThreadUtils.runOnUiThreadBlocking(() -> {
+        return (JavascriptTabModalDialog) TestThreadUtils.runOnUiThreadBlocking(() -> {
             PropertyModel model = mActivity.getModalDialogManager().getCurrentDialogForTest();
             return model != null ? model.get(ModalDialogProperties.CONTROLLER) : null;
         });
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/locale/DefaultSearchEngineDialogHelperUtils.java b/chrome/android/javatests/src/org/chromium/chrome/browser/locale/DefaultSearchEngineDialogHelperUtils.java
index 2a64d237..5f9583a4 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/locale/DefaultSearchEngineDialogHelperUtils.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/locale/DefaultSearchEngineDialogHelperUtils.java
@@ -9,13 +9,13 @@
 
 import org.junit.Assert;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.search_engines.TemplateUrlService;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
 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;
 
 /**
  * Utilities for interacting with a {@link DefaultSearchEngineDialogHelper}.
@@ -38,13 +38,10 @@
         });
 
         // Click on the first search engine option available.
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                ViewGroup options = (ViewGroup) rootView.findViewById(OPTION_LAYOUT_ID);
-                options.getChildAt(0).performClick();
-                sSelectedEngine = (String) (options.getChildAt(0).getTag());
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            ViewGroup options = (ViewGroup) rootView.findViewById(OPTION_LAYOUT_ID);
+            options.getChildAt(0).performClick();
+            sSelectedEngine = (String) (options.getChildAt(0).getTag());
         });
 
         // Wait for the OK button to be clicakble.
@@ -63,15 +60,11 @@
         });
 
         // Confirm the engine was set appropriately.
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                Assert.assertEquals("Search engine wasn't set",
-                        TemplateUrlService.getInstance()
-                                .getDefaultSearchEngineTemplateUrl()
-                                .getKeyword(),
-                        sSelectedEngine);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> Assert.assertEquals("Search engine wasn't set",
+                                TemplateUrlService.getInstance()
+                                        .getDefaultSearchEngineTemplateUrl()
+                                        .getKeyword(),
+                                sSelectedEngine));
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/locale/DefaultSearchEnginePromoDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/locale/DefaultSearchEnginePromoDialogTest.java
index 6e307ff..c1964c779 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/locale/DefaultSearchEnginePromoDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/locale/DefaultSearchEnginePromoDialogTest.java
@@ -13,7 +13,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.library_loader.ProcessInitException;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
@@ -27,6 +26,7 @@
 import org.chromium.chrome.test.util.ActivityUtils;
 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;
 import java.util.concurrent.Callable;
@@ -40,7 +40,7 @@
 public class DefaultSearchEnginePromoDialogTest {
     @Before
     public void setUp() throws ExecutionException, ProcessInitException {
-        ThreadUtils.runOnUiThreadBlocking(new Callable<Void>() {
+        TestThreadUtils.runOnUiThreadBlocking(new Callable<Void>() {
             @Override
             public Void call() throws ProcessInitException {
                 ChromeBrowserInitializer.getInstance(InstrumentationRegistry.getTargetContext())
@@ -62,19 +62,16 @@
     @LargeTest
     public void testOnlyOneLiveDialog() throws Exception {
         final CallbackHelper templateUrlServiceInit = new CallbackHelper();
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                TemplateUrlService.getInstance().registerLoadListener(
-                        new TemplateUrlService.LoadListener() {
-                            @Override
-                            public void onTemplateUrlServiceLoaded() {
-                                TemplateUrlService.getInstance().unregisterLoadListener(this);
-                                templateUrlServiceInit.notifyCalled();
-                            }
-                        });
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> TemplateUrlService.getInstance().registerLoadListener(
+                                new TemplateUrlService.LoadListener() {
+                                    @Override
+                                    public void onTemplateUrlServiceLoaded() {
+                                        TemplateUrlService.getInstance().unregisterLoadListener(
+                                                this);
+                                        templateUrlServiceInit.notifyCalled();
+                                    }
+                                }));
         templateUrlServiceInit.waitForCallback(0);
 
         final SearchActivity searchActivity = ActivityUtils.waitForActivity(
@@ -101,12 +98,7 @@
             }
         }));
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                tabbedDialog.dismiss();
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> tabbedDialog.dismiss());
         CriteriaHelper.pollUiThread(new Criteria() {
             @Override
             public boolean isSatisfied() {
@@ -117,14 +109,15 @@
 
     private DefaultSearchEnginePromoDialog showDialog(final Activity activity)
             throws ExecutionException {
-        return ThreadUtils.runOnUiThreadBlocking(new Callable<DefaultSearchEnginePromoDialog>() {
-            @Override
-            public DefaultSearchEnginePromoDialog call() throws Exception {
-                DefaultSearchEnginePromoDialog dialog = new DefaultSearchEnginePromoDialog(
-                        activity, LocaleManager.SearchEnginePromoType.SHOW_EXISTING, null);
-                dialog.show();
-                return dialog;
-            }
-        });
+        return TestThreadUtils.runOnUiThreadBlocking(
+                new Callable<DefaultSearchEnginePromoDialog>() {
+                    @Override
+                    public DefaultSearchEnginePromoDialog call() throws Exception {
+                        DefaultSearchEnginePromoDialog dialog = new DefaultSearchEnginePromoDialog(
+                                activity, LocaleManager.SearchEnginePromoType.SHOW_EXISTING, null);
+                        dialog.show();
+                        return dialog;
+                    }
+                });
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/locale/LocaleManagerReferralTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/locale/LocaleManagerReferralTest.java
index 90de6912..82d0afb 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/locale/LocaleManagerReferralTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/locale/LocaleManagerReferralTest.java
@@ -16,13 +16,13 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.library_loader.ProcessInitException;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
 import org.chromium.chrome.browser.search_engines.TemplateUrlService;
 import org.chromium.chrome.test.util.ApplicationData;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.Locale;
 import java.util.concurrent.Callable;
@@ -51,7 +51,7 @@
             }
         });
 
-        ThreadUtils.runOnUiThreadBlocking(new Callable<Void>() {
+        TestThreadUtils.runOnUiThreadBlocking(new Callable<Void>() {
             @Override
             public Void call() throws ProcessInitException {
                 ChromeBrowserInitializer.getInstance(InstrumentationRegistry.getTargetContext())
@@ -70,43 +70,37 @@
     @Test
     public void testYandexReferralId() throws InterruptedException, TimeoutException {
         final CallbackHelper templateUrlServiceLoaded = new CallbackHelper();
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                TemplateUrlService templateUrlService = TemplateUrlService.getInstance();
-                templateUrlService.registerLoadListener(new TemplateUrlService.LoadListener() {
-                    @Override
-                    public void onTemplateUrlServiceLoaded() {
-                        templateUrlServiceLoaded.notifyCalled();
-                    }
-                });
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            TemplateUrlService templateUrlService = TemplateUrlService.getInstance();
+            templateUrlService.registerLoadListener(new TemplateUrlService.LoadListener() {
+                @Override
+                public void onTemplateUrlServiceLoaded() {
+                    templateUrlServiceLoaded.notifyCalled();
+                }
+            });
 
-                templateUrlService.load();
-            }
+            templateUrlService.load();
         });
 
         templateUrlServiceLoaded.waitForCallback("Template URLs never loaded", 0);
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                TemplateUrlService.getInstance().setSearchEngine("yandex.ru");
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            TemplateUrlService.getInstance().setSearchEngine("yandex.ru");
 
-                // The initial param is empty, so ensure no clid param is passed.
-                String url = TemplateUrlService.getInstance().getUrlForSearchQuery("blah");
-                Assert.assertThat(url, not(containsString("&clid=")));
+            // The initial param is empty, so ensure no clid param is passed.
+            String url = TemplateUrlService.getInstance().getUrlForSearchQuery("blah");
+            Assert.assertThat(url, not(containsString("&clid=")));
 
-                // Initialize the value to something and verify it is included in the generated
-                // URL.
-                mYandexReferralId = "TESTING_IS_AWESOME";
-                url = TemplateUrlService.getInstance().getUrlForSearchQuery("blah");
-                Assert.assertThat(url, containsString("&clid=TESTING_IS_AWESOME"));
+            // Initialize the value to something and verify it is included in the generated
+            // URL.
+            mYandexReferralId = "TESTING_IS_AWESOME";
+            url = TemplateUrlService.getInstance().getUrlForSearchQuery("blah");
+            Assert.assertThat(url, containsString("&clid=TESTING_IS_AWESOME"));
 
-                // Switch to google and ensure the clid param is no longer included.
-                TemplateUrlService.getInstance().setSearchEngine("google.com");
-                url = TemplateUrlService.getInstance().getUrlForSearchQuery("blah");
-                Assert.assertThat(url, not(containsString("&clid=")));
-            }
+            // Switch to google and ensure the clid param is no longer included.
+            TemplateUrlService.getInstance().setSearchEngine("google.com");
+            url = TemplateUrlService.getInstance().getUrlForSearchQuery("blah");
+            Assert.assertThat(url, not(containsString("&clid=")));
         });
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/locale/LocaleManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/locale/LocaleManagerTest.java
index 182b377e..09b949d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/locale/LocaleManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/locale/LocaleManagerTest.java
@@ -13,7 +13,6 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.Callback;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.library_loader.ProcessInitException;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
@@ -22,6 +21,7 @@
 import org.chromium.chrome.browser.searchwidget.SearchActivity;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.util.ActivityUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.policy.test.annotations.Policies;
 
 import java.util.concurrent.Callable;
@@ -36,7 +36,7 @@
 public class LocaleManagerTest {
     @Before
     public void setUp() throws ExecutionException, ProcessInitException {
-        ThreadUtils.runOnUiThreadBlocking(new Callable<Void>() {
+        TestThreadUtils.runOnUiThreadBlocking(new Callable<Void>() {
             @Override
             public Void call() throws ProcessInitException {
                 ChromeBrowserInitializer.getInstance(InstrumentationRegistry.getTargetContext())
@@ -65,19 +65,15 @@
                 InstrumentationRegistry.getInstrumentation(), SearchActivity.class);
 
         final CallbackHelper searchEnginesFinalizedCallback = new CallbackHelper();
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                LocaleManager.getInstance().showSearchEnginePromoIfNeeded(
-                        searchActivity, new Callback<Boolean>() {
-                            @Override
-                            public void onResult(Boolean result) {
-                                Assert.assertTrue(result);
-                                searchEnginesFinalizedCallback.notifyCalled();
-                            }
-                        });
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> LocaleManager.getInstance().showSearchEnginePromoIfNeeded(
+                                searchActivity, new Callback<Boolean>() {
+                                    @Override
+                                    public void onResult(Boolean result) {
+                                        Assert.assertTrue(result);
+                                        searchEnginesFinalizedCallback.notifyCalled();
+                                    }
+                                }));
         searchEnginesFinalizedCallback.waitForCallback(0);
         Assert.assertEquals(0, getShowTypeCallback.getCallCount());
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/media/ui/PictureInPictureControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/media/ui/PictureInPictureControllerTest.java
index 3a47793a..4d5d01a6 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/media/ui/PictureInPictureControllerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/media/ui/PictureInPictureControllerTest.java
@@ -17,7 +17,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.chrome.browser.ChromeSwitches;
@@ -32,6 +31,7 @@
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
 import org.chromium.content_public.browser.test.util.DOMUtils;
 import org.chromium.content_public.browser.test.util.JavaScriptUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.browser.test.util.WebContentsUtils;
 import org.chromium.media.MediaSwitches;
 import org.chromium.net.test.EmbeddedTestServer;
@@ -173,7 +173,8 @@
 
         CriteriaHelper.pollUiThread(Criteria.equals(true, navigationObserver::didNavigationOccur));
 
-        Assert.assertTrue(ThreadUtils.runOnUiThreadBlocking(mActivity::isInPictureInPictureMode));
+        Assert.assertTrue(
+                TestThreadUtils.runOnUiThreadBlocking(mActivity::isInPictureInPictureMode));
     }
 
     /** Tests that we can resume PiP after it has been cancelled. */
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/MainIntentBehaviorMetricsIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/MainIntentBehaviorMetricsIntegrationTest.java
index 09fdc775..591c5456 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/MainIntentBehaviorMetricsIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/MainIntentBehaviorMetricsIntegrationTest.java
@@ -22,7 +22,6 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.ContextUtils;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisabledTest;
@@ -45,6 +44,7 @@
 import org.chromium.content_public.browser.LoadUrlParams;
 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 org.chromium.content_public.common.ContentUrlConstants;
 
 import java.util.concurrent.Callable;
@@ -74,12 +74,9 @@
     public void testFocusOmnibox() {
         startActivity(true);
         assertMainIntentBehavior(null);
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                UrlBar urlBar = (UrlBar) mActivityTestRule.getActivity().findViewById(R.id.url_bar);
-                OmniboxTestUtils.toggleUrlBarFocus(urlBar, true);
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            UrlBar urlBar = (UrlBar) mActivityTestRule.getActivity().findViewById(R.id.url_bar);
+            OmniboxTestUtils.toggleUrlBarFocus(urlBar, true);
         });
         assertMainIntentBehavior(MainIntentBehaviorMetrics.MainIntentActionType.FOCUS_OMNIBOX);
     }
@@ -89,14 +86,10 @@
     public void testSwitchTabs() {
         startActivity(true);
         assertMainIntentBehavior(null);
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mActivityTestRule.getActivity().getTabCreator(false).createNewTab(
-                        new LoadUrlParams(ContentUrlConstants.ABOUT_BLANK_URL),
-                        TabLaunchType.FROM_RESTORE, null);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+            (Runnable) () -> mActivityTestRule.getActivity().getTabCreator(false).createNewTab(
+                                new LoadUrlParams(ContentUrlConstants.ABOUT_BLANK_URL),
+                                TabLaunchType.FROM_RESTORE, null));
         CriteriaHelper.pollUiThread(Criteria.equals(2, new Callable<Integer>() {
             @Override
             public Integer call() throws Exception {
@@ -105,12 +98,9 @@
         }));
         assertMainIntentBehavior(null);
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                TabModelUtils.setIndex(mActivityTestRule.getActivity().getCurrentTabModel(), 1);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> TabModelUtils.setIndex(
+                                mActivityTestRule.getActivity().getCurrentTabModel(), 1));
         assertMainIntentBehavior(MainIntentBehaviorMetrics.MainIntentActionType.SWITCH_TABS);
     }
 
@@ -119,12 +109,7 @@
     public void testBackgrounded() {
         startActivity(true);
         assertMainIntentBehavior(null);
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mActivityTestRule.getActivity().finish();
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> mActivityTestRule.getActivity().finish());
         assertMainIntentBehavior(MainIntentBehaviorMetrics.MainIntentActionType.BACKGROUNDED);
     }
 
@@ -133,12 +118,8 @@
     public void testCreateNtp() {
         startActivity(true);
         assertMainIntentBehavior(null);
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mActivityTestRule.getActivity().getTabCreator(false).launchNTP();
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mActivityTestRule.getActivity().getTabCreator(false).launchNTP());
         assertMainIntentBehavior(MainIntentBehaviorMetrics.MainIntentActionType.NTP_CREATED);
     }
 
@@ -318,7 +299,7 @@
 
         Intent intent = new Intent(Intent.ACTION_MAIN);
         intent.addCategory(Intent.CATEGORY_LAUNCHER);
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> { mActivityTestRule.getActivity().onNewIntent(intent); });
 
         assertThat(mActionTester.toString(), mActionTester.getActions(),
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/PageLoadMetricsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/PageLoadMetricsTest.java
index af84adf..7d1177f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/PageLoadMetricsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/PageLoadMetricsTest.java
@@ -14,7 +14,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.RetryOnFailure;
 import org.chromium.chrome.browser.ChromeActivity;
@@ -22,6 +21,7 @@
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.content_public.browser.WebContents;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.net.test.EmbeddedTestServer;
 
 import java.util.concurrent.CountDownLatch;
@@ -130,13 +130,13 @@
         Assert.assertFalse("Tab shouldn't be loading anything before we add observer",
                 mActivityTestRule.getActivity().getActivityTab().isLoading());
         PageLoadMetricsTestObserver metricsObserver = new PageLoadMetricsTestObserver();
-        ThreadUtils.runOnUiThreadBlockingNoException(
+        TestThreadUtils.runOnUiThreadBlockingNoException(
                 () -> PageLoadMetrics.addObserver(metricsObserver));
 
         mActivityTestRule.loadUrl(mTestPage);
         assertMetricsEmitted(metricsObserver);
 
-        ThreadUtils.runOnUiThreadBlockingNoException(
+        TestThreadUtils.runOnUiThreadBlockingNoException(
                 () -> PageLoadMetrics.removeObserver(metricsObserver));
     }
 
@@ -144,13 +144,13 @@
     @SmallTest
     public void testPageLoadMetricNavigationIdSetCorrectly() throws InterruptedException {
         PageLoadMetricsTestObserver metricsObserver = new PageLoadMetricsTestObserver();
-        ThreadUtils.runOnUiThreadBlockingNoException(
+        TestThreadUtils.runOnUiThreadBlockingNoException(
                 () -> PageLoadMetrics.addObserver(metricsObserver));
         mActivityTestRule.loadUrl(mTestPage);
         assertMetricsEmitted(metricsObserver);
 
         PageLoadMetricsTestObserver metricsObserver2 = new PageLoadMetricsTestObserver();
-        ThreadUtils.runOnUiThreadBlockingNoException(
+        TestThreadUtils.runOnUiThreadBlockingNoException(
                 () -> PageLoadMetrics.addObserver(metricsObserver2));
         mActivityTestRule.loadUrl(mTestPage2);
         assertMetricsEmitted(metricsObserver2);
@@ -158,9 +158,9 @@
         Assert.assertNotEquals("Subsequent navigations should have different navigation ids",
                 metricsObserver.getNavigationId(), metricsObserver2.getNavigationId());
 
-        ThreadUtils.runOnUiThreadBlockingNoException(
+        TestThreadUtils.runOnUiThreadBlockingNoException(
                 () -> PageLoadMetrics.removeObserver(metricsObserver));
-        ThreadUtils.runOnUiThreadBlockingNoException(
+        TestThreadUtils.runOnUiThreadBlockingNoException(
                 () -> PageLoadMetrics.removeObserver(metricsObserver2));
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java
index cd1c766..1eb8265d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java
@@ -16,7 +16,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.RetryOnFailure;
@@ -32,6 +31,7 @@
 import org.chromium.chrome.test.util.ChromeTabUtils;
 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 org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.webapk.lib.common.WebApkConstants;
 
@@ -88,13 +88,13 @@
     private void runAndWaitForPageLoadMetricsRecorded(CheckedRunnable runnable) throws Exception {
         PageLoadMetricsTest.PageLoadMetricsTestObserver testObserver =
                 new PageLoadMetricsTest.PageLoadMetricsTestObserver();
-        ThreadUtils.runOnUiThreadBlockingNoException(
+        TestThreadUtils.runOnUiThreadBlockingNoException(
                 () -> PageLoadMetrics.addObserver(testObserver));
         runnable.run();
         // First Contentful Paint may be recorded asynchronously after a page load is finished, we
         // have to wait the event to occur.
         testObserver.waitForFirstContentfulPaintEvent();
-        ThreadUtils.runOnUiThreadBlockingNoException(
+        TestThreadUtils.runOnUiThreadBlockingNoException(
                 () -> PageLoadMetrics.removeObserver(testObserver));
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/UkmTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/UkmTest.java
index bb4ed6c..f818552 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/UkmTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/UkmTest.java
@@ -13,7 +13,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.chrome.browser.ChromeSwitches;
@@ -25,6 +24,7 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.ChromeTabUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 /**
  * Android UKM tests.
@@ -46,12 +46,8 @@
      * @param incognito Whether to close an incognito or non-incognito tab.
      */
     protected void closeCurrentTab(final boolean incognito) throws InterruptedException {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mActivityTestRule.getActivity().getTabModelSelector().selectModel(incognito);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mActivityTestRule.getActivity().getTabModelSelector().selectModel(incognito));
         ChromeTabUtils.closeCurrentTab(
                 InstrumentationRegistry.getInstrumentation(), mActivityTestRule.getActivity());
     }
@@ -73,41 +69,42 @@
         // chrome/browser/metrics/ukm_browsertest.cc.
         Tab normalTab = mActivityTestRule.getActivity().getActivityTab();
 
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> { Assert.assertTrue(UkmUtilsForTest.isEnabled()); });
 
         long originalClientId =
-                ThreadUtils.runOnUiThreadBlocking(() -> { return UkmUtilsForTest.getClientId(); })
+                TestThreadUtils
+                        .runOnUiThreadBlocking(() -> { return UkmUtilsForTest.getClientId(); })
                         .longValue();
         Assert.assertFalse("Non-zero client id: " + originalClientId, originalClientId == 0);
 
         mActivityTestRule.newIncognitoTabFromMenu();
 
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> { Assert.assertFalse(UkmUtilsForTest.isEnabled()); });
 
         // Opening another regular tab mustn't enable UKM.
         ChromeTabUtils.newTabFromMenu(
                 InstrumentationRegistry.getInstrumentation(), mActivityTestRule.getActivity());
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> { Assert.assertFalse(UkmUtilsForTest.isEnabled()); });
 
         // Opening and closing another Incognito tab mustn't enable UKM.
         mActivityTestRule.newIncognitoTabFromMenu();
         closeIncognitoTab();
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> { Assert.assertFalse(UkmUtilsForTest.isEnabled()); });
 
         closeRegularTab();
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> { Assert.assertFalse(UkmUtilsForTest.isEnabled()); });
 
         closeIncognitoTab();
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> { Assert.assertTrue(UkmUtilsForTest.isEnabled()); });
 
         // Client ID should not have been reset.
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             Assert.assertEquals("Client id:", originalClientId, UkmUtilsForTest.getClientId());
         });
     }
@@ -128,11 +125,11 @@
                 InstrumentationRegistry.getInstrumentation(), mActivityTestRule.getActivity());
         Tab normalTab = mActivityTestRule.getActivity().getActivityTab();
 
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> { Assert.assertFalse(UkmUtilsForTest.isEnabled()); });
 
         closeIncognitoTab();
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> { Assert.assertTrue(UkmUtilsForTest.isEnabled()); });
     }
 
@@ -146,18 +143,19 @@
         ChromeTabUtils.closeAllTabs(
                 InstrumentationRegistry.getInstrumentation(), mActivityTestRule.getActivity());
 
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> { Assert.assertTrue(UkmUtilsForTest.isEnabled()); });
 
         long originalClientId =
-                ThreadUtils.runOnUiThreadBlocking(() -> { return UkmUtilsForTest.getClientId(); })
+                TestThreadUtils
+                        .runOnUiThreadBlocking(() -> { return UkmUtilsForTest.getClientId(); })
                         .longValue();
         Assert.assertFalse("Non-zero client id: " + originalClientId, originalClientId == 0);
 
         // Record some dummy UKM data (adding a Source).
         final long sourceId = 0x54321;
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             // Write data under a dummy sourceId and verify it is there.
             UkmUtilsForTest.recordSourceWithId(sourceId);
             Assert.assertTrue(UkmUtilsForTest.hasSourceWithId(sourceId));
@@ -165,7 +163,7 @@
         CallbackHelper callbackHelper = new CallbackHelper();
 
         // Clear all browsing history.
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             BrowsingDataBridge.getInstance().clearBrowsingData(new OnClearBrowsingDataListener() {
                 @Override
                 public void onBrowsingDataCleared() {
@@ -175,7 +173,7 @@
         });
         callbackHelper.waitForCallback(0);
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             // Verify that UKM is still running.
             Assert.assertTrue(UkmUtilsForTest.isEnabled());
             // The source under sourceId should be removed.
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogTestUtils.java b/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogTestUtils.java
index 543ee7cf..7b78a2b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogTestUtils.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogTestUtils.java
@@ -9,9 +9,9 @@
 
 import org.junit.Assert;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.modaldialog.DialogDismissalCause;
 import org.chromium.ui.modaldialog.ModalDialogManager;
 import org.chromium.ui.modaldialog.ModalDialogManager.ModalDialogType;
@@ -41,7 +41,7 @@
      */
     public static PropertyModel createDialog(ChromeActivity activity, String title,
             @Nullable TestDialogDismissedObserver observer) throws ExecutionException {
-        return ThreadUtils.runOnUiThreadBlocking(() -> {
+        return TestThreadUtils.runOnUiThreadBlocking(() -> {
             ModalDialogProperties.Controller controller = new ModalDialogProperties.Controller() {
                 @Override
                 public void onDismiss(
@@ -77,7 +77,7 @@
      */
     public static void showDialog(
             ModalDialogManager manager, PropertyModel model, @ModalDialogType int dialogType) {
-        ThreadUtils.runOnUiThreadBlocking(() -> manager.showDialog(model, dialogType));
+        TestThreadUtils.runOnUiThreadBlocking(() -> manager.showDialog(model, dialogType));
     }
 
     /**
@@ -85,7 +85,7 @@
      */
     public static void checkPendingSize(
             ModalDialogManager manager, @ModalDialogType int dialogType, int expected) {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             List list = manager.getPendingDialogsForTest(dialogType);
             Assert.assertEquals(expected, list != null ? list.size() : 0);
         });
@@ -97,7 +97,7 @@
      */
     public static void checkCurrentPresenter(
             ModalDialogManager manager, @Nullable Integer dialogType) {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             if (dialogType == null) {
                 Assert.assertFalse(manager.isShowing());
                 Assert.assertNull(manager.getCurrentPresenterForTest());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogViewTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogViewTest.java
index 92a0e84c..0c24a03 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogViewTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogViewTest.java
@@ -36,7 +36,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
@@ -45,6 +44,7 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.RenderTestRule;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.modaldialog.ModalDialogProperties;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
@@ -77,7 +77,7 @@
     public void setUp() throws Exception {
         mActivityTestRule.startMainActivityOnBlankPage();
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             ChromeActivity activity = mActivityTestRule.getActivity();
             mResources = activity.getResources();
             mModelBuilder = new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS);
@@ -140,7 +140,7 @@
     @MediumTest
     @Feature({"ModalDialog", "RenderTest"})
     public void testRender_CustomView() throws IOException {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             mCustomTextView1.setText(
                     TextUtils.join("\n", Collections.nCopies(100, "Custom Message")));
             mCustomScrollView.addView(mCustomTextView1);
@@ -181,14 +181,14 @@
         onView(withId(R.id.modal_dialog_scroll_view)).check(matches(not(isDisplayed())));
 
         // Set an empty title and verify that title is not shown.
-        ThreadUtils.runOnUiThreadBlocking(() -> model.set(ModalDialogProperties.TITLE, ""));
+        TestThreadUtils.runOnUiThreadBlocking(() -> model.set(ModalDialogProperties.TITLE, ""));
         onView(allOf(withId(R.id.title), withParent(withId(R.id.title_container))))
                 .check(matches(not(isDisplayed())));
         onView(withId(R.id.title_container)).check(matches(not(isDisplayed())));
         onView(withId(R.id.modal_dialog_scroll_view)).check(matches(not(isDisplayed())));
 
         // Set a String title and verify that title is displayed.
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> model.set(ModalDialogProperties.TITLE, "My Test Title"));
         onView(allOf(withId(R.id.title), withParent(withId(R.id.title_container))))
                 .check(matches(allOf(isDisplayed(), withText("My Test Title"))));
@@ -212,7 +212,7 @@
         onView(withId(R.id.message)).check(matches(not(isDisplayed())));
 
         // Set title to not scrollable and verify that non-scrollable title is displayed.
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> model.set(ModalDialogProperties.TITLE_SCROLLABLE, false));
         onView(allOf(withId(R.id.title), withParent(withId(R.id.title_container))))
                 .check(matches(allOf(isDisplayed(), withText(R.string.title))));
@@ -237,7 +237,8 @@
         onView(withId(R.id.scrollable_title_container)).check(matches(not(isDisplayed())));
 
         // Set icon to null and verify that icon is not shown.
-        ThreadUtils.runOnUiThreadBlocking(() -> model.set(ModalDialogProperties.TITLE_ICON, null));
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> model.set(ModalDialogProperties.TITLE_ICON, null));
         onView(allOf(withId(R.id.title), withParent(withId(R.id.title_container))))
                 .check(matches(not(isDisplayed())));
         onView(allOf(withId(R.id.title_icon), withParent(withId(R.id.title_container))))
@@ -259,7 +260,7 @@
         onView(withId(R.id.message)).check(matches(allOf(isDisplayed(), withText(R.string.more))));
 
         // Set an empty message and verify that message is not shown.
-        ThreadUtils.runOnUiThreadBlocking(() -> model.set(ModalDialogProperties.MESSAGE, ""));
+        TestThreadUtils.runOnUiThreadBlocking(() -> model.set(ModalDialogProperties.MESSAGE, ""));
         onView(withId(R.id.title_container)).check(matches(not(isDisplayed())));
         onView(withId(R.id.scrollable_title_container)).check(matches(not(isDisplayed())));
         onView(withId(R.id.modal_dialog_scroll_view)).check(matches(not(isDisplayed())));
@@ -277,14 +278,15 @@
                 .check(matches(allOf(isDisplayed(), withChild(withId(R.id.button_one)))));
 
         // Change custom view.
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> model.set(ModalDialogProperties.CUSTOM_VIEW, mCustomTextView2));
         onView(withId(R.id.custom))
                 .check(matches(allOf(isDisplayed(), not(withChild(withId(R.id.button_one))),
                         withChild(withId(R.id.button_two)))));
 
         // Set custom view to null.
-        ThreadUtils.runOnUiThreadBlocking(() -> model.set(ModalDialogProperties.CUSTOM_VIEW, null));
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> model.set(ModalDialogProperties.CUSTOM_VIEW, null));
         onView(withId(R.id.custom))
                 .check(matches(allOf(not(isDisplayed()), not(withChild(withId(R.id.button_one))),
                         not(withChild(withId(R.id.button_two))))));
@@ -307,7 +309,7 @@
                 .check(matches(allOf(isDisplayed(), isEnabled(), withText(R.string.cancel))));
 
         // Set positive button to be disabled state.
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> model.set(ModalDialogProperties.POSITIVE_BUTTON_DISABLED, true));
         onView(withId(R.id.button_bar)).check(matches(isDisplayed()));
         onView(withId(R.id.positive_button))
@@ -316,7 +318,7 @@
                 .check(matches(allOf(isDisplayed(), isEnabled(), withText(R.string.cancel))));
 
         // Set positive button text to empty.
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> model.set(ModalDialogProperties.POSITIVE_BUTTON_TEXT, ""));
         onView(withId(R.id.button_bar)).check(matches(isDisplayed()));
         onView(withId(R.id.positive_button)).check(matches(not(isDisplayed())));
@@ -324,7 +326,7 @@
                 .check(matches(allOf(isDisplayed(), isEnabled(), withText(R.string.cancel))));
 
         // Set negative button to be disabled state.
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> model.set(ModalDialogProperties.NEGATIVE_BUTTON_DISABLED, true));
         onView(withId(R.id.button_bar)).check(matches(isDisplayed()));
         onView(withId(R.id.positive_button)).check(matches(not(isDisplayed())));
@@ -332,7 +334,7 @@
                 .check(matches(allOf(isDisplayed(), not(isEnabled()), withText(R.string.cancel))));
 
         // Set negative button text to empty.
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> model.set(ModalDialogProperties.NEGATIVE_BUTTON_TEXT, ""));
         onView(withId(R.id.button_bar)).check(matches(not(isDisplayed())));
         onView(withId(R.id.positive_button)).check(matches(not(isDisplayed())));
@@ -380,7 +382,7 @@
     }
 
     private PropertyModel createModel(PropertyModel.Builder modelBuilder) {
-        return ThreadUtils.runOnUiThreadBlockingNoException(() -> {
+        return TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
             PropertyModel model = modelBuilder.build();
             PropertyModelChangeProcessor.create(
                     model, mModalDialogView, new ModalDialogViewBinder());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowIntegrationTest.java
index fed80590..114c37f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowIntegrationTest.java
@@ -20,7 +20,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.MinAndroidSdkLevel;
@@ -36,6 +35,7 @@
 import org.chromium.chrome.test.util.MenuUtils;
 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 org.chromium.net.test.EmbeddedTestServer;
 
 import java.util.concurrent.Callable;
@@ -70,12 +70,8 @@
     @CommandLineFlags.Add(ChromeSwitches.DISABLE_TAB_MERGING_FOR_TESTING)
     public void testIncognitoNtpHandledCorrectly() throws InterruptedException {
         try {
-            ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-                @Override
-                public void run() {
-                    FirstRunStatus.setFirstRunFlowComplete(true);
-                }
-            });
+            TestThreadUtils.runOnUiThreadBlocking(
+                    () -> FirstRunStatus.setFirstRunFlowComplete(true));
 
             mActivityTestRule.newIncognitoTabFromMenu();
             Assert.assertTrue(mActivityTestRule.getActivity().getActivityTab().isIncognito());
@@ -94,22 +90,15 @@
                         }
                     }));
 
-            ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-                @Override
-                public void run() {
-                    Assert.assertEquals(1, TabWindowManager.getInstance().getIncognitoTabCount());
+            TestThreadUtils.runOnUiThreadBlocking(() -> {
+                Assert.assertEquals(1, TabWindowManager.getInstance().getIncognitoTabCount());
 
-                    // Ensure the same tab exists in the new activity.
-                    Assert.assertEquals(incognitoTabId, cta2.getActivityTab().getId());
-                }
+                // Ensure the same tab exists in the new activity.
+                Assert.assertEquals(incognitoTabId, cta2.getActivityTab().getId());
             });
         } finally {
-            ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-                @Override
-                public void run() {
-                    FirstRunStatus.setFirstRunFlowComplete(false);
-                }
-            });
+            TestThreadUtils.runOnUiThreadBlocking(
+                    () -> FirstRunStatus.setFirstRunFlowComplete(false));
         }
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowTestHelper.java b/chrome/android/javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowTestHelper.java
index 29d0fe7..13c46aa 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowTestHelper.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowTestHelper.java
@@ -18,13 +18,13 @@
 import org.chromium.base.ActivityState;
 import org.chromium.base.ApplicationStatus;
 import org.chromium.base.ContextUtils;
-import org.chromium.base.ThreadUtils;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.ChromeTabbedActivity2;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.content_public.browser.LoadUrlParams;
 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.Locale;
 import java.util.concurrent.Callable;
@@ -127,17 +127,14 @@
      */
     @TargetApi(Build.VERSION_CODES.N)
     public static void moveActivityToFront(final Activity activity) {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                Context context = ContextUtils.getApplicationContext();
-                ActivityManager activityManager =
-                        (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
-                for (ActivityManager.AppTask task : activityManager.getAppTasks()) {
-                    if (activity.getTaskId() == task.getTaskInfo().id) {
-                        task.moveToFront();
-                        break;
-                    }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Context context = ContextUtils.getApplicationContext();
+            ActivityManager activityManager =
+                    (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+            for (ActivityManager.AppTask task : activityManager.getAppTasks()) {
+                if (activity.getTaskId() == task.getTaskInfo().id) {
+                    task.moveToFront();
+                    break;
                 }
             }
         });
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridgeTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridgeTest.java
index af1a265..25c8181d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridgeTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridgeTest.java
@@ -30,7 +30,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
@@ -53,6 +52,7 @@
 import org.chromium.components.url_formatter.UrlFormatter;
 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.net.URL;
 import java.util.ArrayList;
@@ -112,7 +112,7 @@
 
     private double getEngagementScoreBlocking() {
         try {
-            return ThreadUtils.runOnUiThreadBlocking(new Callable<Double>() {
+            return TestThreadUtils.runOnUiThreadBlocking(new Callable<Double>() {
                 @Override
                 public Double call() throws Exception {
                     return SiteEngagementService.getForProfile(Profile.getLastUsedProfile())
@@ -464,12 +464,8 @@
                 ContentSettingValues.ALLOW, mPermissionTestRule.getOrigin());
 
         // Disable notification vibration in preferences.
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                PrefServiceBridge.getInstance().setNotificationsVibrateEnabled(false);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> PrefServiceBridge.getInstance().setNotificationsVibrateEnabled(false));
 
         Notification notification = showAndGetNotification("MyNotification", notificationOptions);
 
@@ -518,12 +514,9 @@
                 ContentSettingValues.ALLOW, mPermissionTestRule.getOrigin());
 
         // By default, vibration is enabled in notifications.
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                Assert.assertTrue(PrefServiceBridge.getInstance().isNotificationsVibrateEnabled());
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> Assert.assertTrue(
+                                PrefServiceBridge.getInstance().isNotificationsVibrateEnabled()));
 
         Notification notification = showAndGetNotification("MyNotification", "{ vibrate: 42 }");
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/notifications/NotificationTestRule.java b/chrome/android/javatests/src/org/chromium/chrome/browser/notifications/NotificationTestRule.java
index 48df6355..a6bd7873 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/notifications/NotificationTestRule.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/notifications/NotificationTestRule.java
@@ -10,7 +10,6 @@
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.preferences.website.ContentSettingValues;
 import org.chromium.chrome.browser.preferences.website.PermissionInfo;
@@ -19,6 +18,7 @@
 import org.chromium.chrome.test.util.browser.notifications.MockNotificationManagerProxy.NotificationEntry;
 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;
 import java.util.concurrent.TimeoutException;
@@ -57,14 +57,11 @@
      */
     public void setNotificationContentSettingForOrigin(final @ContentSettingValues int setting,
             String origin) throws InterruptedException, TimeoutException {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                // The notification content setting does not consider the embedder origin.
-                PermissionInfo notificationInfo =
-                        new PermissionInfo(PermissionInfo.Type.NOTIFICATION, origin, "", false);
-                notificationInfo.setContentSetting(setting);
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            // The notification content setting does not consider the embedder origin.
+            PermissionInfo notificationInfo =
+                    new PermissionInfo(PermissionInfo.Type.NOTIFICATION, origin, "", false);
+            notificationInfo.setContentSetting(setting);
         });
 
         String permission = runJavaScriptCodeInCurrentTab("Notification.permission");
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/notifications/channels/SiteChannelsManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/notifications/channels/SiteChannelsManagerTest.java
index a939f6cf..eab40b7 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/notifications/channels/SiteChannelsManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/notifications/channels/SiteChannelsManagerTest.java
@@ -24,7 +24,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.MinAndroidSdkLevel;
@@ -35,6 +34,7 @@
 import org.chromium.chrome.browser.preferences.website.ContentSettingValues;
 import org.chromium.chrome.browser.preferences.website.PermissionInfo;
 import org.chromium.chrome.browser.test.ChromeBrowserTestRule;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -194,7 +194,8 @@
     public void testBlockingPermissionInIncognitoCreatesNoChannels() throws Exception {
         PermissionInfo info = new PermissionInfo(
                 PermissionInfo.Type.NOTIFICATION, "https://example-incognito.com", null, true);
-        ThreadUtils.runOnUiThreadBlocking(() -> info.setContentSetting(ContentSettingValues.BLOCK));
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> info.setContentSetting(ContentSettingValues.BLOCK));
         assertThat(Arrays.asList(mSiteChannelsManager.getSiteChannels()), hasSize(0));
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelperTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelperTest.java
index ab31caf..2d792a2 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelperTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelperTest.java
@@ -13,7 +13,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.task.PostTask;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
@@ -28,6 +27,7 @@
 import org.chromium.content_public.browser.UiThreadTaskTraits;
 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 org.chromium.ui.test.util.UiRestriction;
 
 /**
@@ -215,7 +215,7 @@
         // Enter the tab switcher.
         OverviewModeBehaviorWatcher overviewModeWatcher = new OverviewModeBehaviorWatcher(
                 mActivityTestRule.getActivity().getLayoutManager(), true, false);
-        ThreadUtils.runOnUiThreadBlocking(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> mActivityTestRule.getActivity().getLayoutManager().showOverview(false));
         overviewModeWatcher.waitForBehavior();
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/ConnectionInfoPopupTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/ConnectionInfoPopupTest.java
index 27c6bc8..f782332 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/ConnectionInfoPopupTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/ConnectionInfoPopupTest.java
@@ -10,7 +10,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.RetryOnFailure;
@@ -18,6 +17,7 @@
 import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 /**
  * Tests for ConnectionInfoPopup.
@@ -38,12 +38,8 @@
     @RetryOnFailure
     public void testShow() throws InterruptedException {
         mActivityTestRule.startMainActivityOnBlankPage();
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                ConnectionInfoPopup.show(mActivityTestRule.getActivity(),
-                        mActivityTestRule.getActivity().getActivityTab());
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> ConnectionInfoPopup.show(mActivityTestRule.getActivity(),
+                                mActivityTestRule.getActivity().getActivityTab()));
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoControllerTest.java
index 2d9d6db..1a36daca 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoControllerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoControllerTest.java
@@ -14,7 +14,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.RetryOnFailure;
@@ -23,6 +22,7 @@
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.components.security_state.ConnectionSecurityLevel;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.common.ContentSwitches;
 import org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.ui.base.PageTransition;
@@ -59,7 +59,7 @@
     @Feature({"PageInfoController"})
     @RetryOnFailure
     public void testShow() throws InterruptedException {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             PageInfoController.show(mActivityTestRule.getActivity(),
                     mActivityTestRule.getActivity().getActivityTab(), null,
                     PageInfoController.OpenedFromSource.MENU);
@@ -77,7 +77,7 @@
         String testUrl = mTestServer.getURLWithHostName("xn--allestrungen-9ib.ch", "/");
         mActivityTestRule.loadUrlInTab(
                 testUrl, PageTransition.TYPED, mActivityTestRule.getActivity().getActivityTab());
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             PageInfoController pageInfo = new PageInfoController(mActivityTestRule.getActivity(),
                     mActivityTestRule.getActivity().getActivityTab(), ConnectionSecurityLevel.NONE,
                     null, null, PageInfoController.OfflinePageState.NOT_OFFLINE_PAGE,
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/PasswordGenerationDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/PasswordGenerationDialogTest.java
index c4e40f2..59ad8737 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/PasswordGenerationDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/PasswordGenerationDialogTest.java
@@ -24,13 +24,13 @@
 import org.mockito.quality.Strictness;
 
 import org.chromium.base.Callback;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
@@ -53,10 +53,9 @@
     public void setUp() throws InterruptedException {
         mActivityTestRule.startMainActivityOnBlankPage();
         mDialog = new PasswordGenerationDialogCoordinator(mActivityTestRule.getActivity());
-        ThreadUtils.runOnUiThreadBlocking(
-                () -> mDialog.showDialog(mGeneratedPassword,
-                        mExplanationString,
-                        mOnPasswordAcceptedOrRejectedCallback));
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mDialog.showDialog(mGeneratedPassword, mExplanationString,
+                                mOnPasswordAcceptedOrRejectedCallback));
     }
 
     @Test
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/PermissionTestRule.java b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/PermissionTestRule.java
index cea05dc..67714ad 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/PermissionTestRule.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/PermissionTestRule.java
@@ -11,7 +11,6 @@
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ChromeFeatureList;
@@ -23,6 +22,7 @@
 import org.chromium.chrome.test.util.InfoBarUtil;
 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 org.chromium.content_public.browser.test.util.TouchCommon;
 import org.chromium.net.test.EmbeddedTestServer;
 
@@ -116,7 +116,7 @@
         @Override
         public boolean isSatisfied() {
             try {
-                return ThreadUtils.runOnUiThreadBlocking(new Callable<Boolean>() {
+                return TestThreadUtils.runOnUiThreadBlocking(new Callable<Boolean>() {
                     @Override
                     public Boolean call() {
                         mDialog = PermissionDialogController.getInstance()
@@ -167,12 +167,8 @@
      * Simulates clicking a button on an PermissionDialogView.
      */
     private void clickButton(final PermissionDialogView dialog, final int button) {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                dialog.getButton(button).performClick();
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+            (Runnable) () -> dialog.getButton(button).performClick());
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/photo_picker/PhotoPickerDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/photo_picker/PhotoPickerDialogTest.java
index 10fe82d..610240ea 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/photo_picker/PhotoPickerDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/photo_picker/PhotoPickerDialogTest.java
@@ -20,7 +20,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisableIf;
@@ -32,6 +31,7 @@
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.util.browser.RecyclerViewTestUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.browser.test.util.TouchCommon;
 import org.chromium.ui.PhotoPickerListener;
 
@@ -131,7 +131,7 @@
     private PhotoPickerDialog createDialog(final boolean multiselect, final List<String> mimeTypes)
             throws Exception {
         final PhotoPickerDialog dialog =
-                ThreadUtils.runOnUiThreadBlocking(new Callable<PhotoPickerDialog>() {
+                TestThreadUtils.runOnUiThreadBlocking(new Callable<PhotoPickerDialog>() {
                     @Override
                     public PhotoPickerDialog call() {
                         final PhotoPickerDialog dialog =
@@ -193,12 +193,7 @@
     }
 
     private void dismissDialog() {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mDialog.dismiss();
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> mDialog.dismiss());
     }
 
     @Test
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/policy/CombinedPolicyProviderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/policy/CombinedPolicyProviderTest.java
index c1fcc65..f488209 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/policy/CombinedPolicyProviderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/policy/CombinedPolicyProviderTest.java
@@ -12,7 +12,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.RetryOnFailure;
@@ -21,6 +20,7 @@
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.policy.CombinedPolicyProvider;
 import org.chromium.policy.PolicyProvider;
 
@@ -57,17 +57,12 @@
         Assert.assertEquals(2, incognitoTabModel.getCount());
 
         final CombinedPolicyProvider provider = CombinedPolicyProvider.get();
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+        TestThreadUtils.runOnUiThreadBlocking(() -> provider.registerProvider(new PolicyProvider() {
             @Override
-            public void run() {
-                provider.registerProvider(new PolicyProvider() {
-                    @Override
-                    public void refresh() {
-                        terminateIncognitoSession();
-                    }
-                });
+            public void refresh() {
+                terminateIncognitoSession();
             }
-        });
+        }));
 
         Assert.assertEquals(0, incognitoTabModel.getCount());
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/prerender/ExternalPrerenderHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/prerender/ExternalPrerenderHandlerTest.java
index 4d1fb9fc..190500f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/prerender/ExternalPrerenderHandlerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/prerender/ExternalPrerenderHandlerTest.java
@@ -18,7 +18,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Restriction;
@@ -27,6 +26,7 @@
 import org.chromium.content_public.browser.WebContents;
 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 org.chromium.net.test.EmbeddedTestServer;
 
 import java.util.concurrent.Callable;
@@ -65,7 +65,7 @@
                 return Profile.getLastUsedProfile();
             }
         };
-        mProfile = ThreadUtils.runOnUiThreadBlocking(profileCallable);
+        mProfile = TestThreadUtils.runOnUiThreadBlocking(profileCallable);
 
         mTestServer = EmbeddedTestServer.createAndStartServer(InstrumentationRegistry.getContext());
         mTestPage = mTestServer.getURL(TEST_PAGE);
@@ -74,12 +74,8 @@
 
     @After
     public void tearDown() throws Exception {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mExternalPrerenderHandler.cancelCurrentPrerender();
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mExternalPrerenderHandler.cancelCurrentPrerender());
         mTestServer.stopAndDestroyServer();
     }
 
@@ -99,13 +95,10 @@
     public void testAddAndCancelPrerender() throws Exception {
         final WebContents webContents = ensureStartedPrerenderForUrl(mTestPage);
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mExternalPrerenderHandler.cancelCurrentPrerender();
-                Assert.assertFalse(ExternalPrerenderHandler.hasPrerenderedUrl(
-                        mProfile, mTestPage, webContents));
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mExternalPrerenderHandler.cancelCurrentPrerender();
+            Assert.assertFalse(
+                    ExternalPrerenderHandler.hasPrerenderedUrl(mProfile, mTestPage, webContents));
         });
     }
 
@@ -137,7 +130,7 @@
                 return webContents.first;
             }
         };
-        return ThreadUtils.runOnUiThreadBlocking(addPrerenderCallable);
+        return TestThreadUtils.runOnUiThreadBlocking(addPrerenderCallable);
     }
 
     private void ensureCompletedPrefetchForUrl(final String url) {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/printing/PrintingControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/printing/PrintingControllerTest.java
index 2123cd8..e2af5d5 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/printing/PrintingControllerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/printing/PrintingControllerTest.java
@@ -23,7 +23,6 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.ApiCompatibilityUtils;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
@@ -37,6 +36,7 @@
 import org.chromium.chrome.browser.tabmodel.TabModelUtils;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.printing.PrintDocumentAdapterWrapper;
 import org.chromium.printing.PrintDocumentAdapterWrapper.LayoutResultCallbackWrapper;
 import org.chromium.printing.PrintDocumentAdapterWrapper.WriteResultCallbackWrapper;
@@ -242,7 +242,7 @@
         final PrintManagerDelegate mockPrintManagerDelegate =
                 mockPrintManagerDelegate(() -> Assert.fail("Shouldn't start a printing job."));
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             printingController.setPendingPrint(
                     new TabPrinter(currentTab), mockPrintManagerDelegate, -1, -1);
             TabModelUtils.closeCurrentTab(mActivityTestRule.getActivity().getCurrentTabModel());
@@ -290,7 +290,7 @@
         final ParcelFileDescriptor fileDescriptor =
                 ParcelFileDescriptor.open(tempFile, ParcelFileDescriptor.MODE_READ_WRITE);
         try {
-            ThreadUtils.runOnUiThreadBlocking(() -> {
+            TestThreadUtils.runOnUiThreadBlocking(() -> {
                 // Close tab.
                 TabModelUtils.closeCurrentTab(mActivityTestRule.getActivity().getCurrentTabModel());
                 Assert.assertFalse(
@@ -336,7 +336,7 @@
         final WaitForOnWriteHelper onWriteHelper = new WaitForOnWriteHelper();
         final Tab currentTab = mActivityTestRule.getActivity().getActivityTab();
         final PrintingControllerImpl printingController =
-                ThreadUtils.runOnUiThreadBlockingNoException(() -> {
+                TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
                     return new PrintingControllerImplPdfWritingDone(
                             new PrintDocumentAdapterWrapper(), PRINT_JOB_NAME, onWriteHelper);
                 });
@@ -380,7 +380,7 @@
     }
 
     private PrintingControllerImpl createControllerOnUiThread() {
-        return ThreadUtils.runOnUiThreadBlockingNoException(() -> {
+        return TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
             return (PrintingControllerImpl) PrintingControllerImpl.create(
                     new PrintDocumentAdapterWrapper(), PRINT_JOB_NAME);
         });
@@ -406,26 +406,26 @@
     }
 
     private void startControllerOnUiThread(final PrintingControllerImpl controller, final Tab tab) {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             controller.startPrint(new TabPrinter(tab),
                     /* non-op PrintManagerDelegate */ mockPrintManagerDelegate(null));
         });
     }
 
     private void callStartOnUiThread(final PrintingControllerImpl controller) {
-        ThreadUtils.runOnUiThreadBlocking(() -> controller.onStart());
+        TestThreadUtils.runOnUiThreadBlocking(() -> controller.onStart());
     }
 
     private void callLayoutOnUiThread(final PrintingControllerImpl controller,
             final PrintAttributes oldAttributes, final PrintAttributes newAttributes,
             final LayoutResultCallbackWrapper layoutResultCallback) {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             controller.onLayout(oldAttributes, newAttributes, new CancellationSignal(),
                     layoutResultCallback, null);
         });
     }
 
     private void callFinishOnUiThread(final PrintingControllerImpl controller) {
-        ThreadUtils.runOnUiThreadBlocking(() -> controller.onFinish());
+        TestThreadUtils.runOnUiThreadBlocking(() -> controller.onFinish());
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/provider/ProviderTestRule.java b/chrome/android/javatests/src/org/chromium/chrome/browser/provider/ProviderTestRule.java
index b217cbc9..b674af2c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/provider/ProviderTestRule.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/provider/ProviderTestRule.java
@@ -14,9 +14,9 @@
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 /**
  * Base class for Chrome's ContentProvider tests.
@@ -47,13 +47,10 @@
         Assert.assertNotNull(activity);
 
         final ContentProvider provider = new ChromeBrowserProvider();
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                ProviderInfo providerInfo = new ProviderInfo();
-                providerInfo.authority = ChromeBrowserProvider.getApiAuthority(activity);
-                provider.attachInfo(activity, providerInfo);
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            ProviderInfo providerInfo = new ProviderInfo();
+            providerInfo.authority = ChromeBrowserProvider.getApiAuthority(activity);
+            provider.attachInfo(activity, providerInfo);
         });
 
         MockContentResolver resolver = new MockContentResolver();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/push_messaging/PushMessagingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/push_messaging/PushMessagingTest.java
index 0237033f..738ffc82 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/push_messaging/PushMessagingTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/push_messaging/PushMessagingTest.java
@@ -22,7 +22,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.library_loader.ProcessInitException;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
@@ -46,6 +45,7 @@
 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.JavaScriptUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.net.test.EmbeddedTestServerRule;
 
 import java.util.List;
@@ -81,12 +81,9 @@
     @Before
     public void setUp() throws Exception {
         final PushMessagingServiceObserver.Listener listener = this;
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                FakeInstanceIDWithSubtype.clearDataAndSetEnabled(true);
-                PushMessagingServiceObserver.setListenerForTesting(listener);
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            FakeInstanceIDWithSubtype.clearDataAndSetEnabled(true);
+            PushMessagingServiceObserver.setListenerForTesting(listener);
         });
         mPushTestPage = mEmbeddedTestServerRule.getServer().getURL(PUSH_TEST_PAGE);
         mNotificationTestRule.loadUrl(mPushTestPage);
@@ -94,12 +91,9 @@
 
     @After
     public void tearDown() throws Exception {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                PushMessagingServiceObserver.setListenerForTesting(null);
-                FakeInstanceIDWithSubtype.clearDataAndSetEnabled(false);
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            PushMessagingServiceObserver.setListenerForTesting(null);
+            FakeInstanceIDWithSubtype.clearDataAndSetEnabled(false);
         });
     }
 
@@ -320,23 +314,20 @@
             throws InterruptedException, TimeoutException {
         final String appId = appIdAndSenderId.first;
         final String senderId = appIdAndSenderId.second;
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                Context context = InstrumentationRegistry.getInstrumentation()
-                                          .getTargetContext()
-                                          .getApplicationContext();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Context context = InstrumentationRegistry.getInstrumentation()
+                                      .getTargetContext()
+                                      .getApplicationContext();
 
-                Bundle extras = new Bundle();
-                extras.putString("subtype", appId);
+            Bundle extras = new Bundle();
+            extras.putString("subtype", appId);
 
-                GCMMessage message = new GCMMessage(senderId, extras);
-                try {
-                    ChromeBrowserInitializer.getInstance(context).handleSynchronousStartup();
-                    GCMDriver.dispatchMessage(message);
-                } catch (ProcessInitException e) {
-                    Assert.fail("Chrome browser failed to initialize.");
-                }
+            GCMMessage message = new GCMMessage(senderId, extras);
+            try {
+                ChromeBrowserInitializer.getInstance(context).handleSynchronousStartup();
+                GCMDriver.dispatchMessage(message);
+            } catch (ProcessInitException e) {
+                Assert.fail("Chrome browser failed to initialize.");
             }
         });
         mMessageHandledHelper.waitForCallback(mMessageHandledHelper.getCallCount());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/search_engines/TemplateUrlServiceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/search_engines/TemplateUrlServiceTest.java
index 56169c6..3ddf0ef 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/search_engines/TemplateUrlServiceTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/search_engines/TemplateUrlServiceTest.java
@@ -13,7 +13,6 @@
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
@@ -25,6 +24,7 @@
 import org.chromium.chrome.browser.test.ClearAppDataTestRule;
 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 org.chromium.ui.test.util.UiRestriction;
 
 import java.util.List;
@@ -61,7 +61,7 @@
     public void testUrlForContextualSearchQueryValid() throws ExecutionException {
         waitForTemplateUrlServiceToLoad();
 
-        Assert.assertTrue(ThreadUtils.runOnUiThreadBlockingNoException(new Callable<Boolean>() {
+        Assert.assertTrue(TestThreadUtils.runOnUiThreadBlockingNoException(new Callable<Boolean>() {
             @Override
             public Boolean call() throws Exception {
                 return TemplateUrlService.getInstance().isLoaded();
@@ -78,7 +78,7 @@
     private void validateQuery(final String query, final String alternative, final boolean prefetch,
             final String protocolVersion)
             throws ExecutionException {
-        String result = ThreadUtils.runOnUiThreadBlocking(new Callable<String>() {
+        String result = TestThreadUtils.runOnUiThreadBlocking(new Callable<String>() {
             @Override
             public String call() throws Exception {
                 return TemplateUrlService.getInstance().getUrlForContextualSearchQuery(
@@ -105,7 +105,7 @@
     public void testLoadUrlService() {
         waitForTemplateUrlServiceToLoad();
 
-        Assert.assertTrue(ThreadUtils.runOnUiThreadBlockingNoException(new Callable<Boolean>() {
+        Assert.assertTrue(TestThreadUtils.runOnUiThreadBlockingNoException(new Callable<Boolean>() {
             @Override
             public Boolean call() throws Exception {
                 return TemplateUrlService.getInstance().isLoaded();
@@ -115,17 +115,14 @@
         // Add another load listener and ensure that is notified without needing to call load()
         // again.
         final AtomicBoolean observerNotified = new AtomicBoolean(false);
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                TemplateUrlService service = TemplateUrlService.getInstance();
-                service.registerLoadListener(new LoadListener() {
-                    @Override
-                    public void onTemplateUrlServiceLoaded() {
-                        observerNotified.set(true);
-                    }
-                });
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            TemplateUrlService service = TemplateUrlService.getInstance();
+            service.registerLoadListener(new LoadListener() {
+                @Override
+                public void onTemplateUrlServiceLoaded() {
+                    observerNotified.set(true);
+                }
+            });
         });
         CriteriaHelper.pollInstrumentationThread(
                 new Criteria("Observer wasn't notified of TemplateUrlService load.") {
@@ -143,7 +140,7 @@
         final TemplateUrlService templateUrlService = waitForTemplateUrlServiceToLoad();
 
         List<TemplateUrl> searchEngines =
-                ThreadUtils.runOnUiThreadBlockingNoException(new Callable<List<TemplateUrl>>() {
+                TestThreadUtils.runOnUiThreadBlockingNoException(new Callable<List<TemplateUrl>>() {
                     @Override
                     public List<TemplateUrl> call() throws Exception {
                         return templateUrlService.getTemplateUrls();
@@ -151,7 +148,7 @@
                 });
         // Ensure known state of default search index before running test.
         TemplateUrl defaultSearchEngine =
-                ThreadUtils.runOnUiThreadBlockingNoException(new Callable<TemplateUrl>() {
+                TestThreadUtils.runOnUiThreadBlockingNoException(new Callable<TemplateUrl>() {
                     @Override
                     public TemplateUrl call() throws Exception {
                         return templateUrlService.getDefaultSearchEngineTemplateUrl();
@@ -161,17 +158,13 @@
         Assert.assertEquals(searchEngines.get(0), defaultSearchEngine);
 
         // Set search engine index and verified it stuck.
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                Assert.assertTrue(
-                        "There must be more than one search engine to change searchEngines",
-                        searchEngines.size() > 1);
-                templateUrlService.setSearchEngine(searchEngines.get(1).getKeyword());
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Assert.assertTrue("There must be more than one search engine to change searchEngines",
+                    searchEngines.size() > 1);
+            templateUrlService.setSearchEngine(searchEngines.get(1).getKeyword());
         });
         defaultSearchEngine =
-                ThreadUtils.runOnUiThreadBlockingNoException(new Callable<TemplateUrl>() {
+                TestThreadUtils.runOnUiThreadBlockingNoException(new Callable<TemplateUrl>() {
                     @Override
                     public TemplateUrl call() throws Exception {
                         return templateUrlService.getDefaultSearchEngineTemplateUrl();
@@ -191,7 +184,7 @@
         final int prepopulatedEngineNum = getSearchEngineCount(templateUrlService);
 
         TemplateUrl defaultSearchEngine =
-                ThreadUtils.runOnUiThreadBlockingNoException(new Callable<TemplateUrl>() {
+                TestThreadUtils.runOnUiThreadBlockingNoException(new Callable<TemplateUrl>() {
                     @Override
                     public TemplateUrl call() throws Exception {
                         return templateUrlService.getDefaultSearchEngineTemplateUrl();
@@ -201,7 +194,7 @@
         // Add custom search engines and verified only engines visited within 2 days are added.
         // Also verified custom engines are sorted correctly.
         List<TemplateUrl> customSearchEngines =
-                ThreadUtils.runOnUiThreadBlockingNoException(new Callable<List<TemplateUrl>>() {
+                TestThreadUtils.runOnUiThreadBlockingNoException(new Callable<List<TemplateUrl>>() {
                     @Override
                     public List<TemplateUrl> call() throws Exception {
                         templateUrlService.addSearchEngineForTesting("keyword1", 0);
@@ -220,7 +213,7 @@
         // Add more custom search engines and verified at most 3 custom engines are returned.
         // Also verified custom engines are sorted correctly.
         customSearchEngines =
-                ThreadUtils.runOnUiThreadBlockingNoException(new Callable<List<TemplateUrl>>() {
+                TestThreadUtils.runOnUiThreadBlockingNoException(new Callable<List<TemplateUrl>>() {
                     @Override
                     public List<TemplateUrl> call() throws Exception {
                         templateUrlService.addSearchEngineForTesting("keyword4", 0);
@@ -238,7 +231,7 @@
 
         // Verified last_visited is updated correctly and sorting in descending order correctly.
         customSearchEngines =
-                ThreadUtils.runOnUiThreadBlockingNoException(new Callable<List<TemplateUrl>>() {
+                TestThreadUtils.runOnUiThreadBlockingNoException(new Callable<List<TemplateUrl>>() {
                     @Override
                     public List<TemplateUrl> call() throws Exception {
                         templateUrlService.updateLastVisitedForTesting("keyword3");
@@ -256,7 +249,7 @@
         // Set a custom engine as default provider and verified still 3 custom engines are returned.
         // Also verified custom engines are sorted correctly.
         customSearchEngines =
-                ThreadUtils.runOnUiThreadBlockingNoException(new Callable<List<TemplateUrl>>() {
+                TestThreadUtils.runOnUiThreadBlockingNoException(new Callable<List<TemplateUrl>>() {
                     @Override
                     public List<TemplateUrl> call() throws Exception {
                         templateUrlService.setSearchEngine("keyword4");
@@ -276,7 +269,7 @@
     }
 
     private int getSearchEngineCount(final TemplateUrlService templateUrlService) {
-        return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<Integer>() {
+        return TestThreadUtils.runOnUiThreadBlockingNoException(new Callable<Integer>() {
             @Override
             public Integer call() throws Exception {
                 return templateUrlService.getTemplateUrls().size();
@@ -292,16 +285,17 @@
                 observerNotified.set(true);
             }
         };
-        final TemplateUrlService templateUrlService = ThreadUtils.runOnUiThreadBlockingNoException(
-                new Callable<TemplateUrlService>() {
-                    @Override
-                    public TemplateUrlService call() {
-                        TemplateUrlService service = TemplateUrlService.getInstance();
-                        service.registerLoadListener(listener);
-                        service.load();
-                        return service;
-                    }
-                });
+        final TemplateUrlService templateUrlService =
+                TestThreadUtils.runOnUiThreadBlockingNoException(
+                        new Callable<TemplateUrlService>() {
+                            @Override
+                            public TemplateUrlService call() {
+                                TemplateUrlService service = TemplateUrlService.getInstance();
+                                service.registerLoadListener(listener);
+                                service.load();
+                                return service;
+                            }
+                        });
 
         CriteriaHelper.pollInstrumentationThread(new Criteria(
                 "Observer wasn't notified of TemplateUrlService load.") {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/search_engines/TemplateUrlServiceTestUtils.java b/chrome/android/javatests/src/org/chromium/chrome/browser/search_engines/TemplateUrlServiceTestUtils.java
index 77e2dad..fa8e1b9 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/search_engines/TemplateUrlServiceTestUtils.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/search_engines/TemplateUrlServiceTestUtils.java
@@ -4,8 +4,8 @@
 
 package org.chromium.chrome.browser.search_engines;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
@@ -30,7 +30,7 @@
                 return null;
             }
         };
-        ThreadUtils.runOnUiThreadBlocking(setSearchEngineCallable);
+        TestThreadUtils.runOnUiThreadBlocking(setSearchEngineCallable);
         callback.waitForCallback("Failed to set search engine", 0);
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
index 59c6364..2f4fc79 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
@@ -23,7 +23,6 @@
 
 import org.chromium.base.ApplicationStatus;
 import org.chromium.base.Callback;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.RetryOnFailure;
@@ -50,6 +49,7 @@
 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.KeyUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.common.ContentUrlConstants;
 
 import java.util.ArrayList;
@@ -219,12 +219,8 @@
         setUrlBarText(searchActivity, ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL);
 
         // Start loading native, then let the activity finish initialization.
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                searchActivity.startDelayedNativeInitialization();
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> searchActivity.startDelayedNativeInitialization());
 
         Assert.assertEquals(
                 1, mTestDelegate.shouldDelayNativeInitializationCallback.getCallCount());
@@ -273,7 +269,7 @@
             public Void call() throws InterruptedException, TimeoutException {
                 // Finish initialization.  It should notice the URL is queued up and start the
                 // browser.
-                ThreadUtils.runOnUiThreadBlocking(
+                TestThreadUtils.runOnUiThreadBlocking(
                         () -> { searchActivity.startDelayedNativeInitialization(); });
 
                 Assert.assertEquals(
@@ -336,12 +332,8 @@
 
         // Set some text in the search box, then continue startup.
         setUrlBarText(searchActivity, ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL);
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mTestDelegate.onSearchEngineFinalizedCallback.onResult(true);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mTestDelegate.onSearchEngineFinalizedCallback.onResult(true));
 
         // Let the initialization finish completely.
         Assert.assertEquals(
@@ -514,7 +506,7 @@
                 return false;
             }
         });
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             UrlBar urlBar = (UrlBar) activity.findViewById(R.id.url_bar);
             urlBar.setText(url);
         });
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProviderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProviderTest.java
index 314d1c0c..635103e6 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProviderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProviderTest.java
@@ -24,7 +24,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.AdvancedMockContext;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.InMemorySharedPreferences;
@@ -37,6 +36,7 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.util.ApplicationTestUtils;
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -133,47 +133,32 @@
 
         // The microphone icon should disappear if voice queries are unavailable.
         mDelegate.mViews.clear();
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                SearchWidgetProvider.updateCachedVoiceSearchAvailability(false);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> SearchWidgetProvider.updateCachedVoiceSearchAvailability(false));
         checkWidgetStates(TEXT_GENERIC, View.GONE);
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                SearchWidgetProvider.updateCachedEngineName(TEXT_SEARCH_ENGINE);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> SearchWidgetProvider.updateCachedEngineName(TEXT_SEARCH_ENGINE));
         checkWidgetStates(TEXT_GENERIC, View.GONE);
 
         // After recording that the default search engine is "X" and search engine promo check,
         // it should say "Search with X".
         mDelegate.mViews.clear();
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                LocaleManager.setInstanceForTest(new LocaleManager() {
-                    @Override
-                    public boolean needToCheckForSearchEnginePromo() {
-                        return false;
-                    }
-                });
-                SearchWidgetProvider.updateCachedEngineName(TEXT_SEARCH_ENGINE);
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            LocaleManager.setInstanceForTest(new LocaleManager() {
+                @Override
+                public boolean needToCheckForSearchEnginePromo() {
+                    return false;
+                }
+            });
+            SearchWidgetProvider.updateCachedEngineName(TEXT_SEARCH_ENGINE);
         });
         checkWidgetStates(TEXT_SEARCH_ENGINE_FULL, View.GONE);
 
         // The microphone icon should appear if voice queries are available.
         mDelegate.mViews.clear();
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                SearchWidgetProvider.updateCachedVoiceSearchAvailability(true);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> SearchWidgetProvider.updateCachedVoiceSearchAvailability(true));
         checkWidgetStates(TEXT_SEARCH_ENGINE_FULL, View.VISIBLE);
     }
 
@@ -181,7 +166,7 @@
     @SmallTest
     @CommandLineFlags.Remove(ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE)
     public void testUpdateCachedEngineNameBeforeFirstRun() throws ExecutionException {
-        Assert.assertFalse(ThreadUtils.runOnUiThreadBlocking(new Callable<Boolean>() {
+        Assert.assertFalse(TestThreadUtils.runOnUiThreadBlocking(new Callable<Boolean>() {
             @Override
             public Boolean call() throws Exception {
                 return SearchWidgetProvider.shouldShowFullString();
@@ -198,17 +183,14 @@
         // already displaying the generic string, and should continue doing so, so they don't get
         // updated.
         mDelegate.mViews.clear();
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                LocaleManager.setInstanceForTest(new LocaleManager() {
-                    @Override
-                    public boolean needToCheckForSearchEnginePromo() {
-                        return false;
-                    }
-                });
-                SearchWidgetProvider.updateCachedEngineName(TEXT_SEARCH_ENGINE);
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            LocaleManager.setInstanceForTest(new LocaleManager() {
+                @Override
+                public boolean needToCheckForSearchEnginePromo() {
+                    return false;
+                }
+            });
+            SearchWidgetProvider.updateCachedEngineName(TEXT_SEARCH_ENGINE);
         });
         Assert.assertEquals(0, mDelegate.mViews.size());
 
@@ -220,12 +202,8 @@
                 .edit()
                 .putString(SearchWidgetProvider.PREF_SEARCH_ENGINE_SHORTNAME, TEXT_SEARCH_ENGINE)
                 .apply();
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                SearchWidgetProvider.updateCachedEngineName(TEXT_SEARCH_ENGINE);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> SearchWidgetProvider.updateCachedEngineName(TEXT_SEARCH_ENGINE));
         checkWidgetStates(TEXT_GENERIC, View.VISIBLE);
     }
 
@@ -236,25 +214,22 @@
             Assert.assertEquals(TestDelegate.ALL_IDS[i], mDelegate.mViews.get(i).first.intValue());
         }
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                // Check the contents of the RemoteViews by inflating them.
-                for (int i = 0; i < mDelegate.mViews.size(); i++) {
-                    FrameLayout parentView = new FrameLayout(mContext);
-                    RemoteViews views = mDelegate.mViews.get(i).second;
-                    View view = views.apply(mContext, parentView);
-                    parentView.addView(view);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            // Check the contents of the RemoteViews by inflating them.
+            for (int i = 0; i < mDelegate.mViews.size(); i++) {
+                FrameLayout parentView = new FrameLayout(mContext);
+                RemoteViews views = mDelegate.mViews.get(i).second;
+                View view = views.apply(mContext, parentView);
+                parentView.addView(view);
 
-                    // Confirm that the string is correct.
-                    TextView titleView = (TextView) view.findViewById(R.id.title);
-                    Assert.assertEquals(View.VISIBLE, titleView.getVisibility());
-                    Assert.assertEquals(expectedString, titleView.getHint());
+                // Confirm that the string is correct.
+                TextView titleView = (TextView) view.findViewById(R.id.title);
+                Assert.assertEquals(View.VISIBLE, titleView.getVisibility());
+                Assert.assertEquals(expectedString, titleView.getHint());
 
-                    // Confirm the visibility of the microphone.
-                    View microphoneView = view.findViewById(R.id.microphone_icon);
-                    Assert.assertEquals(expectedMicrophoneState, microphoneView.getVisibility());
-                }
+                // Confirm the visibility of the microphone.
+                View microphoneView = view.findViewById(R.id.microphone_icon);
+                Assert.assertEquals(expectedMicrophoneState, microphoneView.getVisibility());
             }
         });
     }
@@ -303,14 +278,11 @@
         instrumentation.addMonitor(monitor);
 
         // Click on the widget.
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                FrameLayout parentView = new FrameLayout(mContext);
-                View view = views.apply(mContext, parentView);
-                parentView.addView(view);
-                view.findViewById(clickTarget).performClick();
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            FrameLayout parentView = new FrameLayout(mContext);
+            View view = views.apply(mContext, parentView);
+            parentView.addView(view);
+            view.findViewById(clickTarget).performClick();
         });
 
         Activity activity = instrumentation.waitForMonitorWithTimeout(
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/send_tab_to_self/SendTabToSelfShareActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/send_tab_to_self/SendTabToSelfShareActivityTest.java
index cc2f179..35e64c1 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/send_tab_to_self/SendTabToSelfShareActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/send_tab_to_self/SendTabToSelfShareActivityTest.java
@@ -14,7 +14,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.browser.ChromeFeatureList;
@@ -26,6 +25,7 @@
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.browser.TabLoadObserver;
 import org.chromium.components.sync.ModelType;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.net.test.EmbeddedTestServer;
 
 import java.util.HashSet;
@@ -53,12 +53,9 @@
         mActivityTestRule.startMainActivityOnBlankPage();
         mTab = mActivityTestRule.getActivity().getActivityTab();
         mTestServer = EmbeddedTestServer.createAndStartServer(InstrumentationRegistry.getContext());
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                ProfileSyncService.overrideForTests(new FakeProfileSyncService());
-                mProfileSyncService = (FakeProfileSyncService) ProfileSyncService.get();
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            ProfileSyncService.overrideForTests(new FakeProfileSyncService());
+            mProfileSyncService = (FakeProfileSyncService) ProfileSyncService.get();
         });
     }
 
@@ -77,13 +74,10 @@
         setChosenTypes(ModelType.SEND_TAB_TO_SELF);
         mProfileSyncService.setNumberOfSyncedDevices(2);
         loadUrlInTab(TEST_PAGE);
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                boolean result = SendTabToSelfShareActivity.featureIsAvailable(mTab);
-                Assert.assertTrue(
-                        "SendTabToSelfShareActivity disabled because of unsupported type", result);
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            boolean result = SendTabToSelfShareActivity.featureIsAvailable(mTab);
+            Assert.assertTrue(
+                    "SendTabToSelfShareActivity disabled because of unsupported type", result);
         });
     }
 
@@ -138,12 +132,9 @@
     }
 
     private void assertFeatureIsDisabled(final String errorMessage) {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                boolean result = SendTabToSelfShareActivity.featureIsAvailable(mTab);
-                Assert.assertFalse(errorMessage, result);
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            boolean result = SendTabToSelfShareActivity.featureIsAvailable(mTab);
+            Assert.assertFalse(errorMessage, result);
         });
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/shape_detection/ShapeDetectionTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/shape_detection/ShapeDetectionTest.java
index 3d3d565..e7b2fdc 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/shape_detection/ShapeDetectionTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/shape_detection/ShapeDetectionTest.java
@@ -15,7 +15,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Restriction;
@@ -26,6 +25,7 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.util.ChromeRestriction;
 import org.chromium.chrome.test.util.browser.TabTitleObserver;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.net.test.EmbeddedTestServer;
 
 import java.util.concurrent.TimeoutException;
@@ -104,21 +104,12 @@
     @Before
     public void setUp() throws Exception {
         mActivityTestRule.startMainActivityOnBlankPage();
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mOldPolicy = StrictMode.allowThreadDiskWrites();
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mOldPolicy = StrictMode.allowThreadDiskWrites(); });
     }
 
     @After
     public void tearDown() throws Exception {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                StrictMode.setThreadPolicy(mOldPolicy);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> StrictMode.setThreadPolicy(mOldPolicy));
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareMenuActionHandlerIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareMenuActionHandlerIntegrationTest.java
index 427393e..ce9a7e3 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareMenuActionHandlerIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareMenuActionHandlerIntegrationTest.java
@@ -13,7 +13,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.MetricsUtils.HistogramDelta;
@@ -23,6 +22,7 @@
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.components.ui_metrics.CanonicalURLResult;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.net.test.ServerCertificate;
 
@@ -108,7 +108,7 @@
     private ShareParams triggerShare() throws InterruptedException, TimeoutException {
         final CallbackHelper helper = new CallbackHelper();
         final AtomicReference<ShareParams> paramsRef = new AtomicReference<>();
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             ShareMenuActionDelegate delegate = new ShareMenuActionDelegate() {
                 @Override
                 void share(ShareParams params) {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareMenuActionHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareMenuActionHandlerTest.java
index e75ac74..7da8175 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareMenuActionHandlerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareMenuActionHandlerTest.java
@@ -11,7 +11,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.chrome.browser.tab.MockTab;
 import org.chromium.chrome.browser.test.ChromeBrowserTestRule;
@@ -19,6 +18,7 @@
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.test.mock.MockRenderFrameHost;
 import org.chromium.content_public.browser.test.mock.MockWebContents;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.concurrent.ExecutionException;
 
@@ -36,7 +36,8 @@
     @Test
     @SmallTest
     public void testShouldFetchCanonicalUrl() throws ExecutionException {
-        MockUrlTab mockTab = ThreadUtils.runOnUiThreadBlocking(() -> { return new MockUrlTab(); });
+        MockUrlTab mockTab =
+                TestThreadUtils.runOnUiThreadBlocking(() -> { return new MockUrlTab(); });
         MockWebContents mockWebContents = new MockWebContents();
         MockRenderFrameHost mockRenderFrameHost = new MockRenderFrameHost();
         mSadTabRule.setTab(mockTab);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/OAuth2TokenServiceIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/OAuth2TokenServiceIntegrationTest.java
index 65633c8..17ab803 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/OAuth2TokenServiceIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/OAuth2TokenServiceIntegrationTest.java
@@ -15,7 +15,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.RetryOnFailure;
 import org.chromium.chrome.test.util.ApplicationData;
@@ -27,6 +26,7 @@
 import org.chromium.components.signin.test.util.AccountHolder;
 import org.chromium.components.signin.test.util.FakeAccountManagerDelegate;
 import org.chromium.content_public.browser.test.NativeLibraryTestRule;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 /**
  * Integration test for the OAuth2TokenService.
@@ -69,7 +69,7 @@
         mChromeSigninController = ChromeSigninController.get();
         mChromeSigninController.setSignedInAccountName(null);
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             // Seed test accounts to AccountTrackerService.
             seedAccountTrackerService();
 
@@ -84,7 +84,7 @@
 
     @After
     public void tearDown() {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             mChromeSigninController.setSignedInAccountName(null);
             mOAuth2TokenService.updateAccountList();
         });
@@ -93,7 +93,7 @@
     }
 
     private void mapAccountNamesToIds() {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             AccountIdProvider.setInstanceForTest(new AccountIdProvider() {
                 @Override
                 public String getAccountId(String accountName) {
@@ -119,18 +119,18 @@
 
     private void addAccount(AccountHolder accountHolder) {
         mAccountManager.addAccountHolderBlocking(accountHolder);
-        ThreadUtils.runOnUiThreadBlocking(this::seedAccountTrackerService);
+        TestThreadUtils.runOnUiThreadBlocking(this::seedAccountTrackerService);
     }
 
     private void removeAccount(AccountHolder accountHolder) {
         mAccountManager.removeAccountHolderBlocking(accountHolder);
-        ThreadUtils.runOnUiThreadBlocking(this::seedAccountTrackerService);
+        TestThreadUtils.runOnUiThreadBlocking(this::seedAccountTrackerService);
     }
 
     @Test
     @MediumTest
     public void testUpdateAccountListNoAccountsRegisteredAndNoSignedInUser() {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             // Run test.
             mOAuth2TokenService.updateAccountList();
 
@@ -146,7 +146,7 @@
     public void testUpdateAccountListOneAccountsRegisteredAndNoSignedInUser() {
         addAccount(TEST_ACCOUNT_HOLDER_1);
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             // Run test.
             mOAuth2TokenService.updateAccountList();
 
@@ -162,7 +162,7 @@
     public void testUpdateAccountListOneAccountsRegisteredSignedIn() {
         addAccount(TEST_ACCOUNT_HOLDER_1);
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             // Mark user as signed in.
             mChromeSigninController.setSignedInAccountName(TEST_ACCOUNT1.name);
 
@@ -187,7 +187,7 @@
     public void testUpdateAccountListSingleAccountThenAddOne() {
         addAccount(TEST_ACCOUNT_HOLDER_1);
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             // Mark user as signed in.
             mChromeSigninController.setSignedInAccountName(TEST_ACCOUNT1.name);
 
@@ -201,7 +201,7 @@
         // Add another account.
         addAccount(TEST_ACCOUNT_HOLDER_2);
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             // Re-run validation.
             mOAuth2TokenService.updateAccountList();
             Assert.assertEquals(3, mObserver.getAvailableCallCount());
@@ -217,7 +217,7 @@
         addAccount(TEST_ACCOUNT_HOLDER_1);
         addAccount(TEST_ACCOUNT_HOLDER_2);
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             // Mark user as signed in.
             mChromeSigninController.setSignedInAccountName(TEST_ACCOUNT1.name);
 
@@ -230,7 +230,7 @@
 
         removeAccount(TEST_ACCOUNT_HOLDER_2);
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             mOAuth2TokenService.updateAccountList();
 
             Assert.assertEquals(3, mObserver.getAvailableCallCount());
@@ -246,7 +246,7 @@
         addAccount(TEST_ACCOUNT_HOLDER_1);
         addAccount(TEST_ACCOUNT_HOLDER_2);
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             // Mark user as signed in.
             mChromeSigninController.setSignedInAccountName(TEST_ACCOUNT1.name);
 
@@ -260,7 +260,7 @@
         removeAccount(TEST_ACCOUNT_HOLDER_1);
         removeAccount(TEST_ACCOUNT_HOLDER_2);
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             // Re-validate and run checks.
             mOAuth2TokenService.updateAccountList();
             Assert.assertEquals(2, mObserver.getAvailableCallCount());
@@ -277,7 +277,7 @@
         addAccount(TEST_ACCOUNT_HOLDER_1);
         addAccount(TEST_ACCOUNT_HOLDER_2);
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             // Mark user as signed in.
             mChromeSigninController.setSignedInAccountName(TEST_ACCOUNT1.name);
 
@@ -293,7 +293,7 @@
         removeAccount(TEST_ACCOUNT_HOLDER_1);
         removeAccount(TEST_ACCOUNT_HOLDER_2);
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             // Re-validate and run checks.
             mOAuth2TokenService.updateAccountList();
             Assert.assertEquals(2, mObserver.getAvailableCallCount());
@@ -309,7 +309,7 @@
         addAccount(TEST_ACCOUNT_HOLDER_1);
         addAccount(TEST_ACCOUNT_HOLDER_2);
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             // Mark user as signed in.
             mChromeSigninController.setSignedInAccountName(TEST_ACCOUNT1.name);
 
@@ -327,7 +327,7 @@
     @Test
     @MediumTest
     public void testUpdateAccountListNoAccountsRegisteredButSignedIn() {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             // Mark user as signed in without setting up the account.
             mChromeSigninController.setSignedInAccountName(TEST_ACCOUNT1.name);
 
@@ -344,7 +344,7 @@
     @Test
     @MediumTest
     public void testUpdateAccountListFiresEventAtTheEnd() {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             // Mark user as signed in without setting up the account.
             mChromeSigninController.setSignedInAccountName(TEST_ACCOUNT1.name);
             TestObserver ob = new TestObserver() {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninTest.java
index 6ee3c51..f52d932 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninTest.java
@@ -42,6 +42,7 @@
 import org.chromium.chrome.test.util.browser.signin.SigninTestUtil;
 import org.chromium.components.bookmarks.BookmarkId;
 import org.chromium.components.signin.ChromeSigninController;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.browser.test.util.TestTouchUtils;
 
 /**
@@ -221,7 +222,7 @@
         mContext = InstrumentationRegistry.getTargetContext();
         final TestSignInAllowedObserver signinAllowedObserver = new TestSignInAllowedObserver();
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             // This call initializes the ChromeSigninController to use our test context.
             ChromeSigninController.get();
 
@@ -258,7 +259,7 @@
 
     @After
     public void tearDown() throws Exception {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             mBookmarks.removeObserver(mTestBookmarkModelObserver);
 
             mSigninManager.removeSignInStateObserver(mTestSignInObserver);
@@ -279,7 +280,7 @@
         SigninTestUtil.addTestAccount();
         signInToSingleAccount();
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             // Verify that the account isn't managed.
             Assert.assertNull(mSigninManager.getManagementDomain());
 
@@ -302,7 +303,7 @@
         // Sign out now.
         signOut();
 
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             // Verify that the profile data hasn't been wiped when signing out of a normal
             // account. We check that by looking for the test bookmark from setUp().
             Assert.assertEquals(1, mBookmarks.getChildCount(mBookmarks.getMobileFolderId()));
@@ -322,7 +323,7 @@
                 AccountSigninActivity.class.getName(), null, false);
 
         // Click sign in.
-        ThreadUtils.runOnUiThreadBlocking(() -> clickSigninPreference(prefActivity));
+        TestThreadUtils.runOnUiThreadBlocking(() -> clickSigninPreference(prefActivity));
 
         // Pick the mock account.
         AccountSigninActivity signinActivity =
@@ -340,7 +341,8 @@
         // Sync doesn't actually start up until we finish the sync setup. This usually happens
         // in the resume of the Main activity, but we forcefully do this here.
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        ThreadUtils.runOnUiThreadBlocking(() -> ProfileSyncService.get().setFirstSetupComplete());
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> ProfileSyncService.get().setFirstSetupComplete());
         prefActivity.finish();
 
         // Verify that signin succeeded.
@@ -360,7 +362,7 @@
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
         // Click on the signout button.
-        ThreadUtils.runOnUiThreadBlocking(() -> clickSignOut(prefActivity));
+        TestThreadUtils.runOnUiThreadBlocking(() -> clickSignOut(prefActivity));
 
         // Accept the warning dialog.
         acceptAlertDialogWithTag(prefActivity, AccountManagementFragment.SIGN_OUT_DIALOG_TAG);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/snackbar/undo/UndoBarControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/snackbar/undo/UndoBarControllerTest.java
index c4b51c27..b6d9fb0 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/snackbar/undo/UndoBarControllerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/snackbar/undo/UndoBarControllerTest.java
@@ -14,7 +14,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.RetryOnFailure;
 import org.chromium.chrome.R;
@@ -25,6 +24,7 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.ChromeTabUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
@@ -136,23 +136,15 @@
     }
 
     private void clickSnackbar() {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mSnackbarManager.onClick(
-                        mActivityTestRule.getActivity().findViewById(R.id.snackbar_button));
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mSnackbarManager.onClick(mActivityTestRule.getActivity().findViewById(
+                                R.id.snackbar_button)));
     }
 
     private void dismissSnackbars() {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mSnackbarManager.dismissSnackbars(
-                        mSnackbarManager.getCurrentSnackbarForTesting().getController());
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mSnackbarManager.dismissSnackbars(
+                                mSnackbarManager.getCurrentSnackbarForTesting().getController()));
     }
 
     private String getSnackbarText() {
@@ -162,7 +154,7 @@
     }
 
     private Snackbar getCurrentSnackbar() throws ExecutionException {
-        return ThreadUtils.runOnUiThreadBlocking(new Callable<Snackbar>() {
+        return TestThreadUtils.runOnUiThreadBlocking(new Callable<Snackbar>() {
             @Override
             public Snackbar call() throws Exception {
                 return mSnackbarManager.getCurrentSnackbarForTesting();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/LocationBarModelTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/LocationBarModelTest.java
index 4ec8782..88f8cae6 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/LocationBarModelTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/LocationBarModelTest.java
@@ -15,7 +15,6 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.ContextUtils;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.RetryOnFailure;
@@ -30,6 +29,7 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.ChromeTabUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 /**
  * Tests for LocationBarModel.
@@ -68,7 +68,7 @@
     @Test
     @SmallTest
     public void testDisplayAndEditText() throws Exception {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             TestLocationBarModel model = new TestLocationBarModel();
             model.mUrl = UrlConstants.NTP_URL;
             assertDisplayAndEditText(model, "", null);
@@ -92,7 +92,7 @@
 
     private void assertDisplayAndEditText(
             ToolbarDataProvider dataProvider, String displayText, String editText) {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             UrlBarData urlBarData = dataProvider.getUrlBarData();
             Assert.assertEquals(
                     "Display text did not match", displayText, urlBarData.displayText.toString());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/ToolbarTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/ToolbarTest.java
index 231e69d..29e7a14 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/ToolbarTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/ToolbarTest.java
@@ -13,7 +13,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Restriction;
@@ -30,6 +29,7 @@
 import org.chromium.chrome.test.util.OmniboxTestUtils;
 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 org.chromium.net.NetworkChangeNotifier;
 import org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.ui.test.util.UiRestriction;
@@ -74,12 +74,8 @@
 
     private boolean isErrorPage(final Tab tab) {
         final boolean[] isShowingError = new boolean[1];
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                isShowingError[0] = tab.isShowingErrorPage();
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { isShowingError[0] = tab.isShowingErrorPage(); });
         return isShowingError[0];
     }
 
@@ -99,12 +95,8 @@
 
         // Stop the server and also disconnect the network.
         testServer.stopAndDestroyServer();
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                NetworkChangeNotifier.forceConnectivityState(false);
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> NetworkChangeNotifier.forceConnectivityState(false));
 
         mActivityTestRule.loadUrl(testUrl);
         Assert.assertEquals(testUrl, tab.getUrl());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/BrandColorTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/BrandColorTest.java
index b737d2e..163969b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/BrandColorTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/BrandColorTest.java
@@ -17,7 +17,6 @@
 
 import org.chromium.base.ObserverList.RewindableIterator;
 import org.chromium.base.SysUtils;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.task.PostTask;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
@@ -39,6 +38,7 @@
 import org.chromium.content_public.browser.test.InterstitialPageDelegateAndroid;
 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 org.chromium.ui.test.util.UiRestriction;
 
 import java.util.concurrent.Callable;
@@ -151,16 +151,13 @@
         startMainActivityWithURL(getUrlWithBrandColor(BRAND_COLOR_1));
         checkForBrandColor(Color.parseColor(BRAND_COLOR_1));
 
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                mActivityTestRule.getActivity().getToolbarManager().onThemeColorChanged(
-                        mDefaultColor, false);
-                // Since the color should change instantly, there is no need to use the criteria
-                // helper.
-                Assert.assertEquals(mToolbarDataProvider.getPrimaryColor(),
-                        mToolbar.getBackgroundDrawable().getColor());
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mActivityTestRule.getActivity().getToolbarManager().onThemeColorChanged(
+                    mDefaultColor, false);
+            // Since the color should change instantly, there is no need to use the criteria
+            // helper.
+            Assert.assertEquals(mToolbarDataProvider.getPrimaryColor(),
+                    mToolbar.getBackgroundDrawable().getColor());
         });
     }
 
@@ -173,14 +170,11 @@
     @Feature({"Omnibox"})
     public void testBrandColorWithLoadStarted() throws InterruptedException {
         startMainActivityWithURL(getUrlWithBrandColor(BRAND_COLOR_1));
-        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
-            @Override
-            public void run() {
-                Tab tab = mActivityTestRule.getActivity().getActivityTab();
-                RewindableIterator<TabObserver> observers = TabTestUtils.getTabObservers(tab);
-                while (observers.hasNext()) {
-                    observers.next().onLoadStarted(tab, true);
-                }
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> {
+            Tab tab = mActivityTestRule.getActivity().getActivityTab();
+            RewindableIterator<TabObserver> observers = TabTestUtils.getTabObservers(tab);
+            while (observers.hasNext()) {
+                observers.next().onLoadStarted(tab, true);
             }
         });
         checkForBrandColor(Color.parseColor(BRAND_COLOR_1));
@@ -239,12 +233,9 @@
         checkForBrandColor(Color.parseColor(BRAND_COLOR_1));
         final InterstitialPageDelegateAndroid delegate =
                 new InterstitialPageDelegateAndroid(INTERSTITIAL_HTML);
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                delegate.showInterstitialPage(brandColorUrl, mActivityTestRule.getWebContents());
-            }
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> delegate.showInterstitialPage(
+                                brandColorUrl, mActivityTestRule.getWebContents()));
         CriteriaHelper.pollUiThread(new Criteria() {
             @Override
             public boolean isSatisfied() {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/util/FeatureUtilitiesTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/util/FeatureUtilitiesTest.java
index c986d0d..85d205f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/util/FeatureUtilitiesTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/util/FeatureUtilitiesTest.java
@@ -20,13 +20,13 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.AdvancedMockContext;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.components.signin.AccountManagerFacade;
 import org.chromium.components.signin.test.util.AccountHolder;
 import org.chromium.components.signin.test.util.FakeAccountManagerDelegate;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -125,15 +125,12 @@
     private static boolean isRecognitionIntentPresent(
             final IntentTestMockContext context, final boolean useCachedResult) {
         // Context can only be queried on a UI Thread.
-        return ThreadUtils.runOnUiThreadBlockingNoException(
-            new Callable<Boolean>() {
-                @Override
-                public Boolean call() {
-                    return FeatureUtilities.isRecognitionIntentPresent(
-                            context,
-                            useCachedResult);
-                }
-            });
+        return TestThreadUtils.runOnUiThreadBlockingNoException(new Callable<Boolean>() {
+            @Override
+            public Boolean call() {
+                return FeatureUtilities.isRecognitionIntentPresent(context, useCachedResult);
+            }
+        });
     }
 
     private void setUpAccountManager(String accountType) {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappSplashScreenTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappSplashScreenTest.java
index e0d6dcd..420af92 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappSplashScreenTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappSplashScreenTest.java
@@ -100,8 +100,7 @@
         Assert.assertTrue(mActivityTestRule.isSplashScreenVisible());
 
         PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT,
-                ()
-                        -> TabTestUtils.simulateFirstVisuallyNonEmptyPaint(
+                () -> TabTestUtils.simulateFirstVisuallyNonEmptyPaint(
                                 mActivityTestRule.getActivity().getActivityTab()));
 
         mActivityTestRule.waitUntilSplashscreenHides();
@@ -115,8 +114,7 @@
         Assert.assertTrue(mActivityTestRule.isSplashScreenVisible());
 
         PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT,
-                ()
-                        -> TabTestUtils.simulateCrash(
+                () -> TabTestUtils.simulateCrash(
                                 mActivityTestRule.getActivity().getActivityTab(), true));
 
         mActivityTestRule.waitUntilSplashscreenHides();
@@ -130,8 +128,7 @@
         Assert.assertTrue(mActivityTestRule.isSplashScreenVisible());
 
         PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT,
-                ()
-                        -> TabTestUtils.simulatePageLoadFinished(
+                () -> TabTestUtils.simulatePageLoadFinished(
                                 mActivityTestRule.getActivity().getActivityTab()));
 
         mActivityTestRule.waitUntilSplashscreenHides();
@@ -145,8 +142,7 @@
         Assert.assertTrue(mActivityTestRule.isSplashScreenVisible());
 
         PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT,
-                ()
-                        -> TabTestUtils.simulatePageLoadFailed(
+                () -> TabTestUtils.simulatePageLoadFailed(
                                 mActivityTestRule.getActivity().getActivityTab(), 0));
 
         mActivityTestRule.waitUntilSplashscreenHides();
@@ -223,8 +219,7 @@
     public void testUmaWhenSplashHides() throws Exception {
         mActivityTestRule.startWebappActivityAndWaitForSplashScreen();
         PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT,
-                ()
-                        -> TabTestUtils.simulateFirstVisuallyNonEmptyPaint(
+                () -> TabTestUtils.simulateFirstVisuallyNonEmptyPaint(
                                 mActivityTestRule.getActivity().getActivityTab()));
 
         mActivityTestRule.waitUntilSplashscreenHides();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappSplashScreenThemeColorTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappSplashScreenThemeColorTest.java
index 755046b6..4e4e850 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappSplashScreenThemeColorTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappSplashScreenThemeColorTest.java
@@ -80,8 +80,7 @@
         }
 
         PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT,
-                ()
-                        -> TabTestUtils.simulateChangeThemeColor(
+                () -> TabTestUtils.simulateChangeThemeColor(
                                 mActivityTestRule.getActivity().getActivityTab(), baseColor));
 
         // Waits for theme-color to change so the test doesn't rely on system timing.
diff --git a/chrome/browser/android/chrome_feature_list.cc b/chrome/browser/android/chrome_feature_list.cc
index e81b9ed..8db1743 100644
--- a/chrome/browser/android/chrome_feature_list.cc
+++ b/chrome/browser/android/chrome_feature_list.cc
@@ -248,7 +248,7 @@
 const base::Feature kAndroidPaymentApps{"AndroidPaymentApps",
                                         base::FEATURE_ENABLED_BY_DEFAULT};
 const base::Feature kAndroidSiteSettingsUIRefresh{
-    "AndroidSiteSettingsUIRefresh", base::FEATURE_DISABLED_BY_DEFAULT};
+    "AndroidSiteSettingsUIRefresh", base::FEATURE_ENABLED_BY_DEFAULT};
 
 const base::Feature kAndroidWebContentsDarkMode{
     "AndroidWebContentsDarkMode", base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/chrome/browser/android/webapk/webapk_update_data_fetcher.cc b/chrome/browser/android/webapk/webapk_update_data_fetcher.cc
index d36cbc4..1be1250 100644
--- a/chrome/browser/android/webapk/webapk_update_data_fetcher.cc
+++ b/chrome/browser/android/webapk/webapk_update_data_fetcher.cc
@@ -137,7 +137,7 @@
   // observing too. It is based on our assumption that it is invalid for
   // web developers to change the Web Manifest location. When it does
   // change, we will treat the new Web Manifest as the one of another WebAPK.
-  if (data.error_code != NO_ERROR_DETECTED || data.manifest->IsEmpty() ||
+  if (!data.errors.empty() || data.manifest->IsEmpty() ||
       web_manifest_url_ != data.manifest_url ||
       !AreWebManifestUrlsWebApkCompatible(*data.manifest)) {
     return;
diff --git a/chrome/browser/android/webapps/add_to_homescreen_data_fetcher.cc b/chrome/browser/android/webapps/add_to_homescreen_data_fetcher.cc
index d018467..aa940ddb 100644
--- a/chrome/browser/android/webapps/add_to_homescreen_data_fetcher.cc
+++ b/chrome/browser/android/webapps/add_to_homescreen_data_fetcher.cc
@@ -273,8 +273,8 @@
   installable_manager_->RecordAddToHomescreenNoTimeout();
 
   bool webapk_compatible =
-      (data.error_code == NO_ERROR_DETECTED && data.valid_manifest &&
-       data.has_worker && AreWebManifestUrlsWebApkCompatible(*data.manifest) &&
+      (data.errors.empty() && data.valid_manifest && data.has_worker &&
+       AreWebManifestUrlsWebApkCompatible(*data.manifest) &&
        ChromeWebApkHost::CanInstallWebApk());
   observer_->OnUserTitleAvailable(
       webapk_compatible ? shortcut_info_.name : shortcut_info_.user_title,
diff --git a/chrome/browser/android/webapps/add_to_homescreen_data_fetcher_unittest.cc b/chrome/browser/android/webapps/add_to_homescreen_data_fetcher_unittest.cc
index 81c93b8..655589c 100644
--- a/chrome/browser/android/webapps/add_to_homescreen_data_fetcher_unittest.cc
+++ b/chrome/browser/android/webapps/add_to_homescreen_data_fetcher_unittest.cc
@@ -144,7 +144,7 @@
     } else if (params.valid_manifest && params.has_worker) {
       if (!IsManifestValidForWebApp(manifest_,
                                     true /* check_webapp_manifest_display */)) {
-        code = valid_manifest_->error;
+        code = valid_manifest_->errors.at(0);
         is_installable = false;
       } else if (!is_installable_) {
         code = NOT_OFFLINE_CAPABLE;
@@ -168,7 +168,10 @@
     if (params.valid_manifest && params.has_worker && is_installable)
       ResolveMetrics(params, is_installable);
 
-    callback.Run({code, GURL(kDefaultManifestUrl), &manifest_,
+    std::vector<InstallableStatusCode> errors;
+    if (code != NO_ERROR_DETECTED)
+      errors.push_back(code);
+    callback.Run({std::move(errors), GURL(kDefaultManifestUrl), &manifest_,
                   params.valid_primary_icon ? primary_icon_url_ : GURL(),
                   params.valid_primary_icon ? primary_icon_.get() : nullptr,
                   params.prefer_maskable_icon,
diff --git a/chrome/browser/banners/app_banner_manager.cc b/chrome/browser/banners/app_banner_manager.cc
index a1130fdeb..84c2bdf19 100644
--- a/chrome/browser/banners/app_banner_manager.cc
+++ b/chrome/browser/banners/app_banner_manager.cc
@@ -317,8 +317,8 @@
 
 void AppBannerManager::OnDidGetManifest(const InstallableData& data) {
   UpdateState(State::ACTIVE);
-  if (data.error_code != NO_ERROR_DETECTED) {
-    Stop(data.error_code);
+  if (!data.errors.empty()) {
+    Stop(data.errors[0]);
     return;
   }
 
@@ -359,15 +359,15 @@
   if (data.has_worker && data.valid_manifest)
     TrackDisplayEvent(DISPLAY_EVENT_WEB_APP_BANNER_REQUESTED);
 
-  SetInstallable(data.error_code == NO_ERROR_DETECTED
-                     ? Installable::INSTALLABLE_YES
-                     : Installable::INSTALLABLE_NO);
+  auto error = data.errors.empty() ? NO_ERROR_DETECTED : data.errors[0];
+  SetInstallable(error == NO_ERROR_DETECTED ? Installable::INSTALLABLE_YES
+                                            : Installable::INSTALLABLE_NO);
 
-  if (data.error_code != NO_ERROR_DETECTED) {
-    if (data.error_code == NO_MATCHING_SERVICE_WORKER)
+  if (error != NO_ERROR_DETECTED) {
+    if (error == NO_MATCHING_SERVICE_WORKER)
       TrackDisplayEvent(DISPLAY_EVENT_LACKS_SERVICE_WORKER);
 
-    Stop(data.error_code);
+    Stop(error);
     return;
   }
 
@@ -461,17 +461,36 @@
   return NO_ERROR_DETECTED;
 }
 
+bool AppBannerManager::IsInstallable() const {
+  return installable_ == Installable::INSTALLABLE_YES;
+}
+
 void AppBannerManager::SetInstallable(Installable installable) {
   if (installable_ == installable)
     return;
 
   installable_ = installable;
-  install_animation_pending_ = IsInstallable();
+
+  if (IsInstallable()) {
+    // TODO(https://crbug.com/943916): Move empty scope handling up into the
+    // ManifestParser.
+    SetLastInstallableScope(manifest_.scope.is_empty() ? manifest_.start_url
+                                                       : manifest_.scope);
+  } else if (installable_ == Installable::INSTALLABLE_NO) {
+    SetLastInstallableScope(GURL());
+  }
 
   for (Observer& observer : observer_list_)
     observer.OnInstallabilityUpdated();
 }
 
+void AppBannerManager::SetLastInstallableScope(const GURL& url) {
+  if (last_installable_scope_ == url)
+    return;
+  last_installable_scope_ = url;
+  install_animation_pending_ = last_installable_scope_.is_valid();
+}
+
 void AppBannerManager::MigrateObserverListForTesting(
     content::WebContents* web_contents) {
   AppBannerManager* existing_manager = FromWebContents(web_contents);
@@ -485,6 +504,8 @@
 void AppBannerManager::Stop(InstallableStatusCode code) {
   ReportStatus(code);
 
+  if (installable_ == Installable::UNKNOWN)
+    SetInstallable(Installable::INSTALLABLE_NO);
   weak_factory_.InvalidateWeakPtrs();
   ResetBindings();
   UpdateState(State::COMPLETE);
@@ -643,12 +664,18 @@
   return manager->GetAppName();
 }
 
-bool AppBannerManager::IsInstallable() const {
-  return installable_ == Installable::INSTALLABLE_YES;
+bool AppBannerManager::IsProbablyInstallable() const {
+  if (IsInstallable())
+    return true;
+  return installable_ == Installable::UNKNOWN &&
+         last_installable_scope_.is_valid() &&
+         base::StartsWith(web_contents()->GetLastCommittedURL().spec(),
+                          last_installable_scope_.spec(),
+                          base::CompareCase::SENSITIVE);
 }
 
 bool AppBannerManager::MaybeConsumeInstallAnimation() {
-  DCHECK(IsInstallable());
+  DCHECK(IsProbablyInstallable());
   if (!install_animation_pending_)
     return false;
   install_animation_pending_ = false;
diff --git a/chrome/browser/banners/app_banner_manager.h b/chrome/browser/banners/app_banner_manager.h
index ec0d45a..bc65b1b 100644
--- a/chrome/browser/banners/app_banner_manager.h
+++ b/chrome/browser/banners/app_banner_manager.h
@@ -145,8 +145,9 @@
       content::WebContents* web_contents);
 
   // Returns whether installability checks have passed (e.g. having a service
-  // worker fetch event).
-  bool IsInstallable() const;
+  // worker fetch event) or have passed previously within the current manifest
+  // scope.
+  bool IsProbablyInstallable() const;
 
   // Each successful installability check gets to show one animation prompt,
   // this returns and consumes the animation prompt if it is available.
@@ -368,8 +369,11 @@
   // Returns a status code based on the current state, to log when terminating.
   InstallableStatusCode TerminationCode() const;
 
+  bool IsInstallable() const;
   void SetInstallable(Installable installable);
 
+  void SetLastInstallableScope(const GURL& url);
+
   // Fetches the data required to display a banner for the current page.
   InstallableManager* manager_;
 
@@ -394,6 +398,10 @@
   bool install_animation_pending_;
   Installable installable_;
 
+  // The scope of the most recent installability check if successful otherwise
+  // invalid.
+  GURL last_installable_scope_;
+
   base::ObserverList<Observer, true> observer_list_;
 
   // The concrete subclasses of this class are expected to have their lifetimes
diff --git a/chrome/browser/banners/test_app_banner_manager_desktop.cc b/chrome/browser/banners/test_app_banner_manager_desktop.cc
index b005211..424f7ad7 100644
--- a/chrome/browser/banners/test_app_banner_manager_desktop.cc
+++ b/chrome/browser/banners/test_app_banner_manager_desktop.cc
@@ -50,13 +50,13 @@
   // AppBannerManagerDesktop does not call |OnDidPerformInstallableCheck| to
   // complete the installability check in this case, instead it early exits
   // with failure.
-  if (result.error_code != NO_ERROR_DETECTED)
+  if (!result.errors.empty())
     SetInstallable(false);
 }
 void TestAppBannerManagerDesktop::OnDidPerformInstallableCheck(
     const InstallableData& result) {
   AppBannerManagerDesktop::OnDidPerformInstallableCheck(result);
-  SetInstallable(result.error_code == NO_ERROR_DETECTED);
+  SetInstallable(result.errors.empty());
 }
 
 void TestAppBannerManagerDesktop::SetInstallable(bool installable) {
diff --git a/chrome/browser/browsing_data/site_data_size_collector_unittest.cc b/chrome/browser/browsing_data/site_data_size_collector_unittest.cc
index 70162d9..5c047e2 100644
--- a/chrome/browser/browsing_data/site_data_size_collector_unittest.cc
+++ b/chrome/browser/browsing_data/site_data_size_collector_unittest.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/browsing_data/site_data_size_collector.h"
 
 #include "base/bind.h"
+#include "base/bind_helpers.h"
 #include "base/files/file_util.h"
 #include "base/run_loop.h"
 #include "base/stl_util.h"
@@ -37,7 +38,6 @@
  public:
   ~SiteDataSizeCollectorTest() override {
     profile_.reset();
-    base::RunLoop().RunUntilIdle();
   }
 
   void SetUp() override {
@@ -84,11 +84,12 @@
     mock_browsing_data_local_storage_helper_ = nullptr;
     mock_browsing_data_database_helper_ = nullptr;
     mock_browsing_data_flash_lso_helper_ = nullptr;
-    base::RunLoop().RunUntilIdle();
   }
 
-  void FetchCallback(int64_t size) {
+  void FetchCallback(base::OnceClosure done, int64_t size) {
     fetched_size_ = size;
+    if (done)
+      std::move(done).Run();
   }
 
  protected:
@@ -122,15 +123,17 @@
       profile_->GetPath(), mock_browsing_data_cookie_helper_.get(), nullptr,
       nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr);
 
-  collector.Fetch(base::Bind(&SiteDataSizeCollectorTest::FetchCallback,
-                             base::Unretained(this)));
+  base::RunLoop run_loop;
+  collector.Fetch(base::BindOnce(&SiteDataSizeCollectorTest::FetchCallback,
+                                 base::Unretained(this),
+                                 run_loop.QuitClosure()));
   // AddCookieSample() actually doesn't write the cookie to the file, only
   // triggers the condition to take the file into account.
   mock_browsing_data_cookie_helper_->AddCookieSamples(
       GURL("http://foo1"), "A=1");
   mock_browsing_data_cookie_helper_->Notify();
   // Wait until reading files on blocking pool finishes.
-  base::RunLoop().RunUntilIdle();
+  run_loop.Run();
   EXPECT_EQ(static_cast<int64_t>(base::size(kCookieFileData)), fetched_size_);
 }
 
@@ -140,8 +143,8 @@
       nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr);
 
   // Fetched size should be 0 if there are no cookies.
-  collector.Fetch(base::Bind(&SiteDataSizeCollectorTest::FetchCallback,
-                             base::Unretained(this)));
+  collector.Fetch(base::BindOnce(&SiteDataSizeCollectorTest::FetchCallback,
+                                 base::Unretained(this), base::OnceClosure()));
   mock_browsing_data_cookie_helper_->Notify();
   EXPECT_EQ(0, fetched_size_);
 }
@@ -151,8 +154,8 @@
       profile_->GetPath(), nullptr, mock_browsing_data_database_helper_.get(),
       nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr);
 
-  collector.Fetch(base::Bind(&SiteDataSizeCollectorTest::FetchCallback,
-                             base::Unretained(this)));
+  collector.Fetch(base::BindOnce(&SiteDataSizeCollectorTest::FetchCallback,
+                                 base::Unretained(this), base::OnceClosure()));
   mock_browsing_data_database_helper_->AddDatabaseSamples();
   mock_browsing_data_database_helper_->Notify();
   EXPECT_EQ(3, fetched_size_);
@@ -164,8 +167,8 @@
       mock_browsing_data_local_storage_helper_.get(), nullptr, nullptr, nullptr,
       nullptr, nullptr, nullptr);
 
-  collector.Fetch(base::Bind(&SiteDataSizeCollectorTest::FetchCallback,
-                             base::Unretained(this)));
+  collector.Fetch(base::BindOnce(&SiteDataSizeCollectorTest::FetchCallback,
+                                 base::Unretained(this), base::OnceClosure()));
   mock_browsing_data_local_storage_helper_->AddLocalStorageSamples();
   mock_browsing_data_local_storage_helper_->Notify();
   EXPECT_EQ(3, fetched_size_);
@@ -177,8 +180,8 @@
                                   mock_browsing_data_appcache_helper_.get(),
                                   nullptr, nullptr, nullptr, nullptr, nullptr);
 
-  collector.Fetch(base::Bind(&SiteDataSizeCollectorTest::FetchCallback,
-                             base::Unretained(this)));
+  collector.Fetch(base::BindOnce(&SiteDataSizeCollectorTest::FetchCallback,
+                                 base::Unretained(this), base::OnceClosure()));
   mock_browsing_data_appcache_helper_->AddAppCacheSamples();
   mock_browsing_data_appcache_helper_->Notify();
   EXPECT_EQ(6, fetched_size_);
@@ -190,8 +193,8 @@
                                   mock_browsing_data_indexed_db_helper_.get(),
                                   nullptr, nullptr, nullptr, nullptr);
 
-  collector.Fetch(base::Bind(&SiteDataSizeCollectorTest::FetchCallback,
-                             base::Unretained(this)));
+  collector.Fetch(base::BindOnce(&SiteDataSizeCollectorTest::FetchCallback,
+                                 base::Unretained(this), base::OnceClosure()));
   mock_browsing_data_indexed_db_helper_->AddIndexedDBSamples();
   mock_browsing_data_indexed_db_helper_->Notify();
   EXPECT_EQ(3, fetched_size_);
@@ -202,8 +205,8 @@
       profile_->GetPath(), nullptr, nullptr, nullptr, nullptr, nullptr,
       mock_browsing_data_file_system_helper_.get(), nullptr, nullptr, nullptr);
 
-  collector.Fetch(base::Bind(&SiteDataSizeCollectorTest::FetchCallback,
-                             base::Unretained(this)));
+  collector.Fetch(base::BindOnce(&SiteDataSizeCollectorTest::FetchCallback,
+                                 base::Unretained(this), base::OnceClosure()));
   mock_browsing_data_file_system_helper_->AddFileSystemSamples();
   mock_browsing_data_file_system_helper_->Notify();
   EXPECT_EQ(14, fetched_size_);
@@ -215,8 +218,8 @@
       mock_browsing_data_service_worker_helper_.get(),
       nullptr, nullptr);
 
-  collector.Fetch(base::Bind(&SiteDataSizeCollectorTest::FetchCallback,
-                             base::Unretained(this)));
+  collector.Fetch(base::BindOnce(&SiteDataSizeCollectorTest::FetchCallback,
+                                 base::Unretained(this), base::OnceClosure()));
   mock_browsing_data_service_worker_helper_->AddServiceWorkerSamples();
   mock_browsing_data_service_worker_helper_->Notify();
   EXPECT_EQ(3, fetched_size_);
@@ -227,8 +230,8 @@
       profile_->GetPath(), nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
       nullptr, mock_browsing_data_cache_storage_helper_.get(), nullptr);
 
-  collector.Fetch(base::Bind(&SiteDataSizeCollectorTest::FetchCallback,
-                             base::Unretained(this)));
+  collector.Fetch(base::BindOnce(&SiteDataSizeCollectorTest::FetchCallback,
+                                 base::Unretained(this), base::OnceClosure()));
   mock_browsing_data_cache_storage_helper_->AddCacheStorageSamples();
   mock_browsing_data_cache_storage_helper_->Notify();
   EXPECT_EQ(3, fetched_size_);
@@ -239,14 +242,17 @@
       profile_->GetPath(), nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
       nullptr, nullptr, mock_browsing_data_flash_lso_helper_.get());
 
-  collector.Fetch(base::Bind(&SiteDataSizeCollectorTest::FetchCallback,
-                             base::Unretained(this)));
+  base::RunLoop run_loop;
+  collector.Fetch(base::BindOnce(&SiteDataSizeCollectorTest::FetchCallback,
+                                 base::Unretained(this),
+                                 run_loop.QuitClosure()));
+
   // AddFlashLSODomain() actually doesn't write flash data to the file, only
   // triggers the condition to take the file into account.
   mock_browsing_data_flash_lso_helper_->AddFlashLSODomain("example.com");
   mock_browsing_data_flash_lso_helper_->Notify();
   // Wait until reading files on blocking pool finishes.
-  base::RunLoop().RunUntilIdle();
+  run_loop.Run();
   EXPECT_EQ(
       static_cast<int64_t>(base::size(kFlashData0) + base::size(kFlashData1)),
       fetched_size_);
@@ -257,8 +263,8 @@
       profile_->GetPath(), nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
       nullptr, nullptr, mock_browsing_data_flash_lso_helper_.get());
 
-  collector.Fetch(base::Bind(&SiteDataSizeCollectorTest::FetchCallback,
-                             base::Unretained(this)));
+  collector.Fetch(base::BindOnce(&SiteDataSizeCollectorTest::FetchCallback,
+                                 base::Unretained(this), base::OnceClosure()));
   mock_browsing_data_flash_lso_helper_->Notify();
   EXPECT_EQ(0, fetched_size_);
 }
@@ -269,8 +275,8 @@
       mock_browsing_data_indexed_db_helper_.get(), nullptr,
       mock_browsing_data_service_worker_helper_.get(), nullptr, nullptr);
 
-  collector.Fetch(base::Bind(&SiteDataSizeCollectorTest::FetchCallback,
-                             base::Unretained(this)));
+  collector.Fetch(base::BindOnce(&SiteDataSizeCollectorTest::FetchCallback,
+                                 base::Unretained(this), base::OnceClosure()));
 
   mock_browsing_data_indexed_db_helper_->AddIndexedDBSamples();
   mock_browsing_data_indexed_db_helper_->Notify();
diff --git a/chrome/browser/chrome_browser_main_win.cc b/chrome/browser/chrome_browser_main_win.cc
index 0cbc23c..8774f6c2 100644
--- a/chrome/browser/chrome_browser_main_win.cc
+++ b/chrome/browser/chrome_browser_main_win.cc
@@ -236,7 +236,8 @@
 // load event to the ModuleDatabase.
 void HandleModuleLoadEventWithoutTimeDateStamp(
     const base::FilePath& module_path,
-    size_t module_size) {
+    size_t module_size,
+    scoped_refptr<base::SequencedTaskRunner> ui_task_runner) {
   uint32_t size_of_image = 0;
   uint32_t time_date_stamp = 0;
   bool got_time_date_stamp = GetModuleImageSizeAndTimeDateStamp(
@@ -251,8 +252,10 @@
   if (!got_time_date_stamp)
     return;
 
-  ModuleDatabase::GetInstance()->OnModuleLoad(
-      content::PROCESS_TYPE_BROWSER, module_path, module_size, time_date_stamp);
+  ui_task_runner->PostTask(
+      FROM_HERE, base::BindOnce(&ModuleDatabase::HandleModuleLoadEvent,
+                                content::PROCESS_TYPE_BROWSER, module_path,
+                                module_size, time_date_stamp));
 }
 
 // Helper function for getting the module size associated with a module in this
@@ -331,12 +334,14 @@
 
 // Used as the callback for ModuleWatcher events in this process. Dispatches
 // them to the ModuleDatabase.
-void OnModuleEvent(const ModuleWatcher::ModuleEvent& event) {
+// Note: This callback may be invoked on any thread, even those not owned by the
+//       task scheduler, under the loader lock, directly on the thread where the
+//       DLL is currently loading.
+void OnModuleEvent(scoped_refptr<base::SequencedTaskRunner> ui_task_runner,
+                   const ModuleWatcher::ModuleEvent& event) {
   TRACE_EVENT1("browser", "OnModuleEvent", "module_path",
                event.module_path.BaseName().AsUTF8Unsafe());
 
-  auto* module_database = ModuleDatabase::GetInstance();
-
   switch (event.event_type) {
     case ModuleWatcher::ModuleEventType::kModuleAlreadyLoaded: {
       // kModuleAlreadyLoaded comes from the enumeration of loaded modules
@@ -345,9 +350,11 @@
       if (TryGetModuleTimeDateStamp(event.module_load_address,
                                     event.module_path, event.module_size,
                                     &time_date_stamp)) {
-        module_database->OnModuleLoad(content::PROCESS_TYPE_BROWSER,
-                                      event.module_path, event.module_size,
-                                      time_date_stamp);
+        ui_task_runner->PostTask(
+            FROM_HERE,
+            base::BindOnce(&ModuleDatabase::HandleModuleLoadEvent,
+                           content::PROCESS_TYPE_BROWSER, event.module_path,
+                           event.module_size, time_date_stamp));
       } else {
         // Failed to get the TimeDateStamp directly from memory. The next step
         // to try is to read the file on disk. This must be done in a blocking
@@ -357,14 +364,18 @@
             {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
              base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
             base::BindOnce(&HandleModuleLoadEventWithoutTimeDateStamp,
-                           event.module_path, event.module_size));
+                           event.module_path, event.module_size,
+                           ui_task_runner));
       }
       return;
     }
     case ModuleWatcher::ModuleEventType::kModuleLoaded: {
-      module_database->OnModuleLoad(
-          content::PROCESS_TYPE_BROWSER, event.module_path, event.module_size,
-          GetModuleTimeDateStamp(event.module_load_address));
+      ui_task_runner->PostTask(
+          FROM_HERE,
+          base::BindOnce(&ModuleDatabase::HandleModuleLoadEvent,
+                         content::PROCESS_TYPE_BROWSER, event.module_path,
+                         event.module_size,
+                         GetModuleTimeDateStamp(event.module_load_address)));
       return;
     }
   }
@@ -376,12 +387,12 @@
 void SetupModuleDatabase(std::unique_ptr<ModuleWatcher>* module_watcher) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  ModuleDatabase::SetInstance(
-      std::make_unique<ModuleDatabase>(base::SequencedTaskRunnerHandle::Get()));
+  ModuleDatabase::SetInstance(std::make_unique<ModuleDatabase>());
   auto* module_database = ModuleDatabase::GetInstance();
   module_database->StartDrainingModuleLoadAttemptsLog();
 
-  *module_watcher = ModuleWatcher::Create(base::BindRepeating(&OnModuleEvent));
+  *module_watcher = ModuleWatcher::Create(base::BindRepeating(
+      &OnModuleEvent, base::SequencedTaskRunnerHandle::Get()));
 
   // Enumerate shell extensions and input method editors. It is safe to use
   // base::Unretained() here because the ModuleDatabase is never freed.
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 7dfd5e6..c799b79b 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -1756,6 +1756,12 @@
     "preferences.h",
     "prefs/pref_connector_service.cc",
     "prefs/pref_connector_service.h",
+    "printing/bulk_printers_calculator.cc",
+    "printing/bulk_printers_calculator.h",
+    "printing/bulk_printers_calculator_factory.cc",
+    "printing/bulk_printers_calculator_factory.h",
+    "printing/calculators_policies_binder.cc",
+    "printing/calculators_policies_binder.h",
     "printing/cups_print_job.cc",
     "printing/cups_print_job.h",
     "printing/cups_print_job_manager.cc",
@@ -1770,17 +1776,8 @@
     "printing/cups_printers_manager.h",
     "printing/cups_printers_manager_factory.cc",
     "printing/cups_printers_manager_factory.h",
-    "printing/device_external_printers_factory.cc",
-    "printing/device_external_printers_factory.h",
-    "printing/device_external_printers_settings_bridge.cc",
-    "printing/device_external_printers_settings_bridge.h",
-    "printing/external_printers.cc",
-    "printing/external_printers.h",
-    "printing/external_printers_factory.cc",
-    "printing/external_printers_factory.h",
-    "printing/external_printers_policies.h",
-    "printing/external_printers_pref_bridge.cc",
-    "printing/external_printers_pref_bridge.h",
+    "printing/enterprise_printers_provider.cc",
+    "printing/enterprise_printers_provider.h",
     "printing/ppd_provider_factory.cc",
     "printing/ppd_provider_factory.h",
     "printing/printer_configurer.cc",
@@ -2475,8 +2472,8 @@
     "power/process_data_collector_unittest.cc",
     "power/renderer_freezer_unittest.cc",
     "preferences_unittest.cc",
+    "printing/bulk_printers_calculator_unittest.cc",
     "printing/cups_printers_manager_unittest.cc",
-    "printing/external_printers_unittest.cc",
     "printing/printer_detector_test_util.h",
     "printing/printer_event_tracker_unittest.cc",
     "printing/printers_sync_bridge_unittest.cc",
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 8cf063c9..5eeef7b5 100644
--- a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
+++ b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
@@ -1154,32 +1154,68 @@
 // AutotestPrivateGetPrinterListFunction
 ///////////////////////////////////////////////////////////////////////////////
 
+AutotestPrivateGetPrinterListFunction::AutotestPrivateGetPrinterListFunction()
+    : results_(std::make_unique<base::Value>(base::Value::Type::LIST)) {}
+
 AutotestPrivateGetPrinterListFunction::
-    ~AutotestPrivateGetPrinterListFunction() = default;
+    ~AutotestPrivateGetPrinterListFunction() {
+  printers_manager_->RemoveObserver(this);
+}
 
 ExtensionFunction::ResponseAction AutotestPrivateGetPrinterListFunction::Run() {
   DVLOG(1) << "AutotestPrivateGetPrinterListFunction";
 
-  auto values = std::make_unique<base::ListValue>();
   Profile* profile = Profile::FromBrowserContext(browser_context());
-  std::unique_ptr<chromeos::CupsPrintersManager> printers_manager =
-      chromeos::CupsPrintersManager::Create(profile);
+  printers_manager_ = chromeos::CupsPrintersManager::Create(profile);
+  printers_manager_->AddObserver(this);
+
+  // Set up a timer to finish waiting after 10 seconds
+  timeout_timer_.Start(
+      FROM_HERE, base::TimeDelta::FromSeconds(10),
+      base::BindOnce(
+          &AutotestPrivateGetPrinterListFunction::RespondWithTimeoutError,
+          this));
+
+  return RespondLater();
+}
+
+void AutotestPrivateGetPrinterListFunction::RespondWithTimeoutError() {
+  if (did_respond())
+    return;
+  Respond(Error("Timeout occured before Enterprise printers were initialized"));
+}
+
+void AutotestPrivateGetPrinterListFunction::RespondWithSuccess() {
+  if (did_respond())
+    return;
+  Respond(OneArgument(std::move(results_)));
+  timeout_timer_.AbandonAndStop();
+}
+
+void AutotestPrivateGetPrinterListFunction::OnEnterprisePrintersInitialized() {
+  // We are ready to get the list of printers and finish.
   std::vector<chromeos::CupsPrintersManager::PrinterClass> printer_type = {
       chromeos::CupsPrintersManager::PrinterClass::kConfigured,
       chromeos::CupsPrintersManager::PrinterClass::kEnterprise,
       chromeos::CupsPrintersManager::PrinterClass::kAutomatic};
+  base::Value::ListStorage& vresults = results_->GetList();
   for (const auto& type : printer_type) {
     std::vector<chromeos::Printer> printer_list =
-        printers_manager->GetPrinters(type);
+        printers_manager_->GetPrinters(type);
     for (const auto& printer : printer_list) {
-      auto result = std::make_unique<base::DictionaryValue>();
-      result->SetString("printerName", printer.display_name());
-      result->SetString("printerId", printer.id());
-      result->SetString("printerType", GetPrinterType(type));
-      values->Append(std::move(result));
+      vresults.push_back(base::Value(base::Value::Type::DICTIONARY));
+      base::Value& result = vresults.back();
+      result.SetKey("printerName", base::Value(printer.display_name()));
+      result.SetKey("printerId", base::Value(printer.id()));
+      result.SetKey("printerType", base::Value(GetPrinterType(type)));
     }
   }
-  return RespondNow(OneArgument(std::move(values)));
+  // We have to respond in separate task, because it will cause a destruction of
+  // CupsPrintersManager
+  PostTask(
+      FROM_HERE,
+      base::BindOnce(&AutotestPrivateGetPrinterListFunction::RespondWithSuccess,
+                     this));
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.h b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.h
index 25adcbd..d3719aca 100644
--- a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.h
+++ b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.h
@@ -407,14 +407,24 @@
                        scoped_refptr<base::RefCountedMemory> png_data);
 };
 
-class AutotestPrivateGetPrinterListFunction : public UIThreadExtensionFunction {
+class AutotestPrivateGetPrinterListFunction
+    : public UIThreadExtensionFunction,
+      public chromeos::CupsPrintersManager::Observer {
  public:
   DECLARE_EXTENSION_FUNCTION("autotestPrivate.getPrinterList",
                              AUTOTESTPRIVATE_GETPRINTERLIST)
+  AutotestPrivateGetPrinterListFunction();
 
  private:
   ~AutotestPrivateGetPrinterListFunction() override;
   ResponseAction Run() override;
+  void RespondWithTimeoutError();
+  void RespondWithSuccess();
+  // chromeos::CupsPrintersManager::Observer
+  void OnEnterprisePrintersInitialized() override;
+  std::unique_ptr<base::Value> results_;
+  std::unique_ptr<chromeos::CupsPrintersManager> printers_manager_;
+  base::OneShotTimer timeout_timer_;
 };
 
 class AutotestPrivateUpdatePrinterFunction : public UIThreadExtensionFunction {
diff --git a/chrome/browser/chromeos/login/users/chrome_user_manager_impl.cc b/chrome/browser/chromeos/login/users/chrome_user_manager_impl.cc
index d976d792..b626c45 100644
--- a/chrome/browser/chromeos/login/users/chrome_user_manager_impl.cc
+++ b/chrome/browser/chromeos/login/users/chrome_user_manager_impl.cc
@@ -52,8 +52,8 @@
 #include "chrome/browser/chromeos/login/users/supervised_user_manager_impl.h"
 #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
 #include "chrome/browser/chromeos/policy/device_network_configuration_updater.h"
-#include "chrome/browser/chromeos/printing/external_printers.h"
-#include "chrome/browser/chromeos/printing/external_printers_factory.h"
+#include "chrome/browser/chromeos/printing/bulk_printers_calculator.h"
+#include "chrome/browser/chromeos/printing/bulk_printers_calculator_factory.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/chromeos/session_length_limiter.h"
 #include "chrome/browser/chromeos/settings/cros_settings.h"
@@ -194,9 +194,9 @@
       ->GetMinimumVersionPolicyHandler();
 }
 
-base::WeakPtr<ExternalPrinters> GetExternalPrinters(
+base::WeakPtr<BulkPrintersCalculator> GetBulkPrintersCalculator(
     const AccountId& account_id) {
-  return ExternalPrintersFactory::Get()->GetForAccountId(account_id);
+  return BulkPrintersCalculatorFactory::Get()->GetForAccountId(account_id);
 }
 
 // Starts bluetooth logging service for accounts ending with |kGoogleDotCom|
@@ -457,7 +457,7 @@
   wallpaper_policy_observer_.reset();
   // Remove the observer before shutting down the printer policy objects.
   printers_policy_observer_.reset();
-  ExternalPrintersFactory::Get()->Shutdown();
+  BulkPrintersCalculatorFactory::Get()->ShutdownProfiles();
   registrar_.RemoveAll();
 }
 
@@ -703,7 +703,7 @@
   if (policy == policy::key::kUserAvatarImage)
     GetUserImageManager(account_id)->OnExternalDataSet(policy);
   else if (policy == policy::key::kNativePrintersBulkConfiguration)
-    GetExternalPrinters(account_id)->ClearData();
+    GetBulkPrintersCalculator(account_id)->ClearData();
   else if (policy != policy::key::kWallpaperImage)
     NOTREACHED();
 }
@@ -715,7 +715,7 @@
   if (policy == policy::key::kUserAvatarImage)
     GetUserImageManager(account_id)->OnExternalDataCleared(policy);
   else if (policy == policy::key::kNativePrintersBulkConfiguration)
-    GetExternalPrinters(account_id)->ClearData();
+    GetBulkPrintersCalculator(account_id)->ClearData();
   else if (policy == policy::key::kWallpaperImage)
     WallpaperControllerClient::Get()->RemovePolicyWallpaper(account_id);
   else
@@ -733,7 +733,7 @@
     GetUserImageManager(account_id)
         ->OnExternalDataFetched(policy, std::move(data));
   } else if (policy == policy::key::kNativePrintersBulkConfiguration) {
-    GetExternalPrinters(account_id)->SetData(std::move(data));
+    GetBulkPrintersCalculator(account_id)->SetData(std::move(data));
   } else if (policy == policy::key::kWallpaperImage) {
     WallpaperControllerClient::Get()->SetPolicyWallpaper(account_id,
                                                          std::move(data));
@@ -1154,7 +1154,7 @@
   // |known_user::RemovePrefs|. See https://crbug.com/778077.
   WallpaperControllerClient::Get()->RemoveUserWallpaper(account_id);
   GetUserImageManager(account_id)->DeleteUserImage();
-  ExternalPrintersFactory::Get()->RemoveForUserId(account_id);
+  BulkPrintersCalculatorFactory::Get()->RemoveForUserId(account_id);
   // TODO(tbarzic): Forward data removal request to ash::HammerDeviceHandler,
   // instead of removing the prefs value here.
   if (GetLocalState()->FindPreference(ash::prefs::kDetachableBaseDevices)) {
diff --git a/chrome/browser/chromeos/login/users/chrome_user_manager_impl.h b/chrome/browser/chromeos/login/users/chrome_user_manager_impl.h
index e75c886..a7dae9b 100644
--- a/chrome/browser/chromeos/login/users/chrome_user_manager_impl.h
+++ b/chrome/browser/chromeos/login/users/chrome_user_manager_impl.h
@@ -26,7 +26,6 @@
 #include "chrome/browser/chromeos/policy/device_local_account.h"
 #include "chrome/browser/chromeos/policy/device_local_account_policy_service.h"
 #include "chrome/browser/chromeos/policy/minimum_version_policy_handler.h"
-#include "chrome/browser/chromeos/printing/external_printers.h"
 #include "chrome/browser/chromeos/settings/cros_settings.h"
 #include "chrome/browser/chromeos/settings/device_settings_service.h"
 #include "components/account_id/account_id.h"
diff --git a/chrome/browser/chromeos/policy/device_native_printers_handler.cc b/chrome/browser/chromeos/policy/device_native_printers_handler.cc
index 1f614bd7..423e1c80 100644
--- a/chrome/browser/chromeos/policy/device_native_printers_handler.cc
+++ b/chrome/browser/chromeos/policy/device_native_printers_handler.cc
@@ -7,16 +7,15 @@
 #include <utility>
 
 #include "base/memory/weak_ptr.h"
-#include "chrome/browser/chromeos/printing/device_external_printers_factory.h"
-#include "chrome/browser/chromeos/printing/external_printers.h"
+#include "chrome/browser/chromeos/printing/bulk_printers_calculator_factory.h"
 #include "components/policy/policy_constants.h"
 
 namespace policy {
 
 namespace {
 
-base::WeakPtr<chromeos::ExternalPrinters> GetExternalPrinters() {
-  return chromeos::DeviceExternalPrintersFactory::Get()->GetForDevice();
+base::WeakPtr<chromeos::BulkPrintersCalculator> GetBulkPrintersCalculator() {
+  return chromeos::BulkPrintersCalculatorFactory::Get()->GetForDevice();
 }
 
 }  // namespace
@@ -33,25 +32,25 @@
 
 void DeviceNativePrintersHandler::OnDeviceExternalDataSet(
     const std::string& policy) {
-  GetExternalPrinters()->ClearData();
+  GetBulkPrintersCalculator()->ClearData();
 }
 
 void DeviceNativePrintersHandler::OnDeviceExternalDataCleared(
     const std::string& policy) {
-  GetExternalPrinters()->ClearData();
+  GetBulkPrintersCalculator()->ClearData();
 }
 
 void DeviceNativePrintersHandler::OnDeviceExternalDataFetched(
     const std::string& policy,
     std::unique_ptr<std::string> data,
     const base::FilePath& file_path) {
-  GetExternalPrinters()->SetData(std::move(data));
+  GetBulkPrintersCalculator()->SetData(std::move(data));
 }
 
 void DeviceNativePrintersHandler::Shutdown() {
   if (device_native_printers_observer_)
     device_native_printers_observer_.reset();
-  chromeos::DeviceExternalPrintersFactory::Get()->Shutdown();
+  chromeos::BulkPrintersCalculatorFactory::Get()->Shutdown();
 }
 
 }  // namespace policy
diff --git a/chrome/browser/chromeos/policy/device_native_printers_handler_unittest.cc b/chrome/browser/chromeos/policy/device_native_printers_handler_unittest.cc
index 6fb0a262..e9d1a24 100644
--- a/chrome/browser/chromeos/policy/device_native_printers_handler_unittest.cc
+++ b/chrome/browser/chromeos/policy/device_native_printers_handler_unittest.cc
@@ -8,8 +8,8 @@
 #include <string>
 
 #include "base/test/scoped_task_environment.h"
-#include "chrome/browser/chromeos/printing/device_external_printers_factory.h"
-#include "chrome/browser/chromeos/printing/external_printers.h"
+#include "chrome/browser/chromeos/printing/bulk_printers_calculator.h"
+#include "chrome/browser/chromeos/printing/bulk_printers_calculator_factory.h"
 #include "chrome/browser/chromeos/settings/scoped_cros_settings_test_helper.h"
 #include "chromeos/printing/printer_configuration.h"
 #include "chromeos/settings/cros_settings_names.h"
@@ -73,8 +73,9 @@
     device_native_printers_handler_ =
         std::make_unique<DeviceNativePrintersHandler>(&policy_service_);
     external_printers_ =
-        chromeos::DeviceExternalPrintersFactory::Get()->GetForDevice();
-    external_printers_->SetAccessMode(chromeos::ExternalPrinters::ALL_ACCESS);
+        chromeos::BulkPrintersCalculatorFactory::Get()->GetForDevice();
+    external_printers_->SetAccessMode(
+        chromeos::BulkPrintersCalculator::ALL_ACCESS);
   }
 
   void TearDown() override { device_native_printers_handler_->Shutdown(); }
@@ -83,7 +84,7 @@
   base::test::ScopedTaskEnvironment scoped_task_environment_;
   MockPolicyService policy_service_;
   std::unique_ptr<DeviceNativePrintersHandler> device_native_printers_handler_;
-  base::WeakPtr<chromeos::ExternalPrinters> external_printers_;
+  base::WeakPtr<chromeos::BulkPrintersCalculator> external_printers_;
 };
 
 TEST_F(DeviceNativePrintersHandlerTest, OnDataFetched) {
diff --git a/chrome/browser/chromeos/printing/bulk_printers_calculator.cc b/chrome/browser/chromeos/printing/bulk_printers_calculator.cc
new file mode 100644
index 0000000..95b7b26
--- /dev/null
+++ b/chrome/browser/chromeos/printing/bulk_printers_calculator.cc
@@ -0,0 +1,382 @@
+// Copyright 2017 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/printing/bulk_printers_calculator.h"
+
+#include <set>
+
+#include "base/bind.h"
+#include "base/feature_list.h"
+#include "base/json/json_reader.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/sequence_checker.h"
+#include "base/sequenced_task_runner.h"
+#include "base/stl_util.h"
+#include "base/task/post_task.h"
+#include "base/task_runner_util.h"
+#include "base/threading/scoped_blocking_call.h"
+#include "base/values.h"
+#include "chrome/common/chrome_features.h"
+#include "chromeos/printing/printer_translator.h"
+
+namespace chromeos {
+
+namespace {
+
+constexpr int kMaxRecords = 20000;
+
+// Represents a task scheduled to process in the Restrictions class.
+struct TaskDataInternal {
+  const unsigned task_id;  // unique ID in increasing order
+  std::unordered_map<std::string, Printer> printers;  // resultant list (output)
+  explicit TaskDataInternal(unsigned id) : task_id(id) {}
+};
+
+using PrinterCache = std::vector<std::unique_ptr<Printer>>;
+using TaskData = std::unique_ptr<TaskDataInternal>;
+
+// Parses |data|, a JSON blob, into a vector of Printers.  If |data| cannot be
+// parsed, returns nullptr.  This is run off the UI thread as it could be very
+// slow.
+std::unique_ptr<PrinterCache> ParsePrinters(std::unique_ptr<std::string> data) {
+  if (!data) {
+    LOG(WARNING) << "Received null data";
+    return nullptr;
+  }
+  int error_code = 0;
+  int error_line = 0;
+
+  // This could be really slow.
+  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
+                                                base::BlockingType::MAY_BLOCK);
+  std::unique_ptr<base::Value> json_blob =
+      base::JSONReader::ReadAndReturnErrorDeprecated(
+          *data, base::JSONParserOptions::JSON_PARSE_RFC, &error_code,
+          nullptr /* error_msg_out */, &error_line);
+  // It's not valid JSON.  Give up.
+  if (!json_blob || !json_blob->is_list()) {
+    LOG(WARNING) << "Failed to parse printers policy (" << error_code
+                 << ") on line " << error_line;
+    return nullptr;
+  }
+
+  const base::Value::ListStorage& printer_list = json_blob->GetList();
+  if (printer_list.size() > kMaxRecords) {
+    LOG(WARNING) << "Too many records in printers policy: "
+                 << printer_list.size();
+    return nullptr;
+  }
+
+  auto parsed_printers = std::make_unique<PrinterCache>();
+  parsed_printers->reserve(printer_list.size());
+  for (const base::Value& val : printer_list) {
+    // TODO(skau): Convert to the new Value APIs.
+    const base::DictionaryValue* printer_dict;
+    if (!val.GetAsDictionary(&printer_dict)) {
+      LOG(WARNING) << "Entry in printers policy skipped.  Not a dictionary.";
+      continue;
+    }
+
+    auto printer = RecommendedPrinterToPrinter(*printer_dict);
+    if (!printer) {
+      LOG(WARNING) << "Failed to parse printer configuration.  Skipped.";
+      continue;
+    }
+    parsed_printers->push_back(std::move(printer));
+  }
+
+  return parsed_printers;
+}
+
+// Computes the effective printer list using the access mode and
+// blacklist/whitelist.  Methods are required to be sequenced.  This object is
+// the owner of all the policy data. Methods updating the list of available
+// printers take TaskData (see above) as |task_data| parameter and returned it.
+class Restrictions {
+ public:
+  Restrictions() : printers_cache_(nullptr) {
+    DETACH_FROM_SEQUENCE(sequence_checker_);
+  }
+  ~Restrictions() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); }
+
+  // Sets the printer cache using the policy blob |data|.
+  TaskData SetData(TaskData task_data, std::unique_ptr<std::string> data) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    base::ScopedBlockingCall scoped_blocking_call(
+        FROM_HERE, base::BlockingType::MAY_BLOCK);
+    printers_cache_ = ParsePrinters(std::move(data));
+    return ComputePrinters(std::move(task_data));
+  }
+
+  // Clear the printer cache.
+  void ClearData() {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    printers_cache_.reset();
+  }
+
+  // Sets the access mode to |mode|.
+  TaskData UpdateAccessMode(TaskData task_data,
+                            BulkPrintersCalculator::AccessMode mode) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    mode_ = mode;
+    return ComputePrinters(std::move(task_data));
+  }
+
+  // Sets the blacklist to |blacklist|.
+  TaskData UpdateBlacklist(TaskData task_data,
+                           const std::vector<std::string>& blacklist) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    has_blacklist_ = true;
+    blacklist_ = std::set<std::string>(blacklist.begin(), blacklist.end());
+    return ComputePrinters(std::move(task_data));
+  }
+
+  // Sets the whitelist to |whitelist|.
+  TaskData UpdateWhitelist(TaskData task_data,
+                           const std::vector<std::string>& whitelist) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    has_whitelist_ = true;
+    whitelist_ = std::set<std::string>(whitelist.begin(), whitelist.end());
+    return ComputePrinters(std::move(task_data));
+  }
+
+ private:
+  // Returns true if we have enough data to compute the effective printer list.
+  bool IsReady() const {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    if (!printers_cache_) {
+      return false;
+    }
+    switch (mode_) {
+      case BulkPrintersCalculator::AccessMode::ALL_ACCESS:
+        return true;
+      case BulkPrintersCalculator::AccessMode::BLACKLIST_ONLY:
+        return has_blacklist_;
+      case BulkPrintersCalculator::AccessMode::WHITELIST_ONLY:
+        return has_whitelist_;
+      case BulkPrintersCalculator::AccessMode::UNSET:
+        return false;
+    }
+    NOTREACHED();
+    return false;
+  }
+
+  // Calculates resultant list of available printers.
+  TaskData ComputePrinters(TaskData task_data) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+    if (!IsReady()) {
+      return task_data;
+    }
+
+    switch (mode_) {
+      case BulkPrintersCalculator::UNSET:
+        NOTREACHED();
+        break;
+      case BulkPrintersCalculator::WHITELIST_ONLY:
+        for (const auto& printer : *printers_cache_) {
+          if (base::ContainsKey(whitelist_, printer->id())) {
+            task_data->printers.insert({printer->id(), *printer});
+          }
+        }
+        break;
+      case BulkPrintersCalculator::BLACKLIST_ONLY:
+        for (const auto& printer : *printers_cache_) {
+          if (!base::ContainsKey(blacklist_, printer->id())) {
+            task_data->printers.insert({printer->id(), *printer});
+          }
+        }
+        break;
+      case BulkPrintersCalculator::ALL_ACCESS:
+        for (const auto& printer : *printers_cache_) {
+          task_data->printers.insert({printer->id(), *printer});
+        }
+        break;
+    }
+
+    return task_data;
+  }
+
+  // Cache of the parsed printer configuration file.
+  std::unique_ptr<PrinterCache> printers_cache_;
+  // The type of restriction which is enforced.
+  BulkPrintersCalculator::AccessMode mode_ = BulkPrintersCalculator::UNSET;
+  // Blacklist: the list of ids which should not appear in the final list.
+  bool has_blacklist_ = false;
+  std::set<std::string> blacklist_;
+  // Whitelist: the list of the only ids which should appear in the final list.
+  bool has_whitelist_ = false;
+  std::set<std::string> whitelist_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+  DISALLOW_COPY_AND_ASSIGN(Restrictions);
+};
+
+class BulkPrintersCalculatorImpl : public BulkPrintersCalculator {
+ public:
+  BulkPrintersCalculatorImpl()
+      : restrictions_(std::make_unique<Restrictions>()),
+        restrictions_runner_(base::CreateSequencedTaskRunnerWithTraits(
+            {base::TaskPriority::BEST_EFFORT, base::MayBlock(),
+             base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})),
+        weak_ptr_factory_(this) {}
+  ~BulkPrintersCalculatorImpl() override {
+    bool success =
+        restrictions_runner_->DeleteSoon(FROM_HERE, std::move(restrictions_));
+    if (!success) {
+      LOG(WARNING) << "Unable to schedule deletion of policy object.";
+    }
+  }
+
+  void AddObserver(Observer* observer) override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    observers_.AddObserver(observer);
+  }
+
+  void RemoveObserver(Observer* observer) override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    observers_.RemoveObserver(observer);
+  }
+
+  void ClearData() override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    if (!base::FeatureList::IsEnabled(features::kBulkPrinters)) {
+      return;
+    }
+    data_is_set_ = false;
+    last_processed_task_ = ++last_received_task_;
+    printers_.clear();
+    // Forward data to Restrictions to clear "Data".
+    restrictions_runner_->PostTask(
+        FROM_HERE, base::BindOnce(&Restrictions::ClearData,
+                                  base::Unretained(restrictions_.get())));
+    // Notify observers.
+    for (auto& observer : observers_) {
+      observer.OnPrintersChanged(this);
+    }
+  }
+
+  void SetData(std::unique_ptr<std::string> data) override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    if (!base::FeatureList::IsEnabled(features::kBulkPrinters)) {
+      return;
+    }
+    data_is_set_ = true;
+    TaskData task_data =
+        std::make_unique<TaskDataInternal>(++last_received_task_);
+    base::PostTaskAndReplyWithResult(
+        restrictions_runner_.get(), FROM_HERE,
+        base::BindOnce(&Restrictions::SetData,
+                       base::Unretained(restrictions_.get()),
+                       std::move(task_data), std::move(data)),
+        base::BindOnce(&BulkPrintersCalculatorImpl::OnComputationComplete,
+                       weak_ptr_factory_.GetWeakPtr()));
+  }
+
+  void SetAccessMode(AccessMode mode) override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    TaskData task_data =
+        std::make_unique<TaskDataInternal>(++last_received_task_);
+    base::PostTaskAndReplyWithResult(
+        restrictions_runner_.get(), FROM_HERE,
+        base::BindOnce(&Restrictions::UpdateAccessMode,
+                       base::Unretained(restrictions_.get()),
+                       std::move(task_data), mode),
+        base::BindOnce(&BulkPrintersCalculatorImpl::OnComputationComplete,
+                       weak_ptr_factory_.GetWeakPtr()));
+  }
+
+  void SetBlacklist(const std::vector<std::string>& blacklist) override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    TaskData task_data =
+        std::make_unique<TaskDataInternal>(++last_received_task_);
+    base::PostTaskAndReplyWithResult(
+        restrictions_runner_.get(), FROM_HERE,
+        base::BindOnce(&Restrictions::UpdateBlacklist,
+                       base::Unretained(restrictions_.get()),
+                       std::move(task_data), blacklist),
+        base::BindOnce(&BulkPrintersCalculatorImpl::OnComputationComplete,
+                       weak_ptr_factory_.GetWeakPtr()));
+  }
+
+  void SetWhitelist(const std::vector<std::string>& whitelist) override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    TaskData task_data =
+        std::make_unique<TaskDataInternal>(++last_received_task_);
+    base::PostTaskAndReplyWithResult(
+        restrictions_runner_.get(), FROM_HERE,
+        base::BindOnce(&Restrictions::UpdateWhitelist,
+                       base::Unretained(restrictions_.get()),
+                       std::move(task_data), whitelist),
+        base::BindOnce(&BulkPrintersCalculatorImpl::OnComputationComplete,
+                       weak_ptr_factory_.GetWeakPtr()));
+  }
+
+  bool IsDataPolicySet() const override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    return data_is_set_;
+  }
+
+  bool IsComplete() const override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    return (last_processed_task_ == last_received_task_);
+  }
+
+  const std::unordered_map<std::string, Printer>& GetPrinters() const override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    return printers_;
+  }
+
+ private:
+  // Called on computation completion. |task_data| corresponds to finalized
+  // task.
+  void OnComputationComplete(TaskData task_data) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    if (!task_data || task_data->task_id <= last_processed_task_) {
+      // The task is outdated (ClearData() was called in the meantime).
+      return;
+    }
+    last_processed_task_ = task_data->task_id;
+    if (last_processed_task_ < last_received_task_ && printers_.empty() &&
+        task_data->printers.empty()) {
+      // No changes in the object's state.
+      return;
+    }
+    printers_.swap(task_data->printers);
+    task_data.reset();
+    // Notifies observers about changes.
+    for (auto& observer : observers_) {
+      observer.OnPrintersChanged(this);
+    }
+  }
+
+  // Holds the blacklist and whitelist.  Computes the effective printer list.
+  std::unique_ptr<Restrictions> restrictions_;
+  // Off UI sequence for computing the printer view.
+  scoped_refptr<base::SequencedTaskRunner> restrictions_runner_;
+
+  // True if printers_ is based on a current policy.
+  bool data_is_set_ = false;
+  // Id of the last scheduled task.
+  unsigned last_received_task_ = 0;
+  // Id of the last completed task.
+  unsigned last_processed_task_ = 0;
+  // The computed set of printers.
+  std::unordered_map<std::string, Printer> printers_;
+
+  base::ObserverList<BulkPrintersCalculator::Observer>::Unchecked observers_;
+  SEQUENCE_CHECKER(sequence_checker_);
+  DISALLOW_COPY_AND_ASSIGN(BulkPrintersCalculatorImpl);
+  base::WeakPtrFactory<BulkPrintersCalculatorImpl> weak_ptr_factory_;
+};
+
+}  // namespace
+
+// static
+std::unique_ptr<BulkPrintersCalculator> BulkPrintersCalculator::Create() {
+  return std::make_unique<BulkPrintersCalculatorImpl>();
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/printing/bulk_printers_calculator.h b/chrome/browser/chromeos/printing/bulk_printers_calculator.h
new file mode 100644
index 0000000..3bf1bc9
--- /dev/null
+++ b/chrome/browser/chromeos/printing/bulk_printers_calculator.h
@@ -0,0 +1,88 @@
+// 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 CHROME_BROWSER_CHROMEOS_PRINTING_BULK_PRINTERS_CALCULATOR_H_
+#define CHROME_BROWSER_CHROMEOS_PRINTING_BULK_PRINTERS_CALCULATOR_H_
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "chromeos/printing/printer_configuration.h"
+
+namespace chromeos {
+
+// Calculates a list of available printers from four policies: Data (json with
+// all printers), AccessMode (see below), Whitelist and Blacklist (lists with
+// ids). All methods must be called from the same sequence and all observers'
+// notifications will be called from this sequence. Resultant list of available
+// printers are calculated asynchronously on a dedicated internal sequence.
+class BulkPrintersCalculator
+    : public base::SupportsWeakPtr<BulkPrintersCalculator> {
+ public:
+  // Algorithm used to calculate a list of available printers from the content
+  // of the "Data" policy.
+  enum AccessMode {
+    UNSET = -1,
+    // Printers in the blacklist are disallowed.  Others are allowed.
+    BLACKLIST_ONLY = 0,
+    // Only printers in the whitelist are allowed.
+    WHITELIST_ONLY = 1,
+    // All printers in the "Data" policy are allowed.
+    ALL_ACCESS = 2
+  };
+
+  class Observer {
+   public:
+    // Observer is notified by this call when the state of the object changes.
+    // See the section "Methods returning the state of the object" below to
+    // learn about parameters defining the state of the object. |sender| is
+    // a pointer to the object calling the notification.
+    virtual void OnPrintersChanged(const BulkPrintersCalculator* sender) = 0;
+  };
+
+  static std::unique_ptr<BulkPrintersCalculator> Create();
+  virtual ~BulkPrintersCalculator() = default;
+
+  virtual void AddObserver(Observer* observer) = 0;
+  virtual void RemoveObserver(Observer* observer) = 0;
+
+  // ========================= Methods setting values of the four policies
+
+  // Sets the "Data" policy. |data| is a list of all printers in JSON format.
+  virtual void SetData(std::unique_ptr<std::string> data) = 0;
+  // Clears the "Data" policy.
+  virtual void ClearData() = 0;
+
+  // Sets the "AccessMode" policy. See description of the AccessMode enum.
+  virtual void SetAccessMode(AccessMode mode) = 0;
+  // Sets the "Blacklist" policy. |blacklist| is a list of printers ids.
+  virtual void SetBlacklist(const std::vector<std::string>& blacklist) = 0;
+  // Sets the "Whitelist" policy. |whitelist| is a list of printers ids.
+  virtual void SetWhitelist(const std::vector<std::string>& whitelist) = 0;
+
+  // ========================= Methods returning the state of the object
+  // Methods returning the three parameters defining the state of the object.
+
+  // Returns true if the "Data" policy has been set with SetData(...) method
+  // (may be not processed yet). Returns false if the "Data" policy has been
+  // cleared with ClearData() method or SetData(...) has been never called.
+  virtual bool IsDataPolicySet() const = 0;
+  // Returns false if current policies were not processed yet. Returns true
+  // if there is no on-going calculations and the method below returns the
+  // list of available printers that is up-to-date with current policies.
+  virtual bool IsComplete() const = 0;
+  // Returns a reference to a resultant list of available printers. Keys are
+  // printers ids. If the list of available printers cannot be calculated
+  // (because of some error or missing policy), an empty map is returned.
+  virtual const std::unordered_map<std::string, Printer>& GetPrinters()
+      const = 0;
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_PRINTING_BULK_PRINTERS_CALCULATOR_H_
diff --git a/chrome/browser/chromeos/printing/bulk_printers_calculator_factory.cc b/chrome/browser/chromeos/printing/bulk_printers_calculator_factory.cc
new file mode 100644
index 0000000..110b4db
--- /dev/null
+++ b/chrome/browser/chromeos/printing/bulk_printers_calculator_factory.cc
@@ -0,0 +1,80 @@
+// Copyright 2017 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/printing/bulk_printers_calculator_factory.h"
+
+#include <memory>
+
+#include "base/lazy_instance.h"
+#include "chrome/browser/chromeos/printing/bulk_printers_calculator.h"
+#include "chrome/browser/chromeos/profiles/profile_helper.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/user_manager/user.h"
+
+namespace chromeos {
+
+namespace {
+
+base::LazyInstance<BulkPrintersCalculatorFactory>::DestructorAtExit
+    g_printers_factory = LAZY_INSTANCE_INITIALIZER;
+
+}  // namespace
+
+// static
+BulkPrintersCalculatorFactory* BulkPrintersCalculatorFactory::Get() {
+  return g_printers_factory.Pointer();
+}
+
+base::WeakPtr<BulkPrintersCalculator>
+BulkPrintersCalculatorFactory::GetForAccountId(const AccountId& account_id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  auto found = printers_by_user_.find(account_id);
+  if (found != printers_by_user_.end()) {
+    return found->second->AsWeakPtr();
+  }
+
+  printers_by_user_[account_id] = BulkPrintersCalculator::Create();
+  return printers_by_user_[account_id]->AsWeakPtr();
+}
+
+base::WeakPtr<BulkPrintersCalculator>
+BulkPrintersCalculatorFactory::GetForProfile(Profile* profile) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  const user_manager::User* user =
+      ProfileHelper::Get()->GetUserByProfile(profile);
+  if (!user)
+    return nullptr;
+
+  return GetForAccountId(user->GetAccountId());
+}
+
+void BulkPrintersCalculatorFactory::RemoveForUserId(
+    const AccountId& account_id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  printers_by_user_.erase(account_id);
+}
+
+base::WeakPtr<BulkPrintersCalculator>
+BulkPrintersCalculatorFactory::GetForDevice() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!device_printers_)
+    device_printers_ = BulkPrintersCalculator::Create();
+  return device_printers_->AsWeakPtr();
+}
+
+void BulkPrintersCalculatorFactory::ShutdownProfiles() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  printers_by_user_.clear();
+}
+
+void BulkPrintersCalculatorFactory::Shutdown() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  printers_by_user_.clear();
+  device_printers_.reset();
+}
+
+BulkPrintersCalculatorFactory::BulkPrintersCalculatorFactory() = default;
+BulkPrintersCalculatorFactory::~BulkPrintersCalculatorFactory() = default;
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/printing/bulk_printers_calculator_factory.h b/chrome/browser/chromeos/printing/bulk_printers_calculator_factory.h
new file mode 100644
index 0000000..2755ac7c
--- /dev/null
+++ b/chrome/browser/chromeos/printing/bulk_printers_calculator_factory.h
@@ -0,0 +1,68 @@
+// Copyright 2017 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_PRINTING_BULK_PRINTERS_CALCULATOR_FACTORY_H_
+#define CHROME_BROWSER_CHROMEOS_PRINTING_BULK_PRINTERS_CALCULATOR_FACTORY_H_
+
+#include <map>
+#include <memory>
+
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "chrome/browser/chromeos/printing/bulk_printers_calculator.h"
+#include "components/account_id/account_id.h"
+
+class Profile;
+
+namespace chromeos {
+
+// Dispenses BulkPrintersCalculator objects based on account id.  Access to this
+// object should be sequenced.
+class BulkPrintersCalculatorFactory {
+ public:
+  static BulkPrintersCalculatorFactory* Get();
+
+  // Returns a WeakPtr to the BulkPrintersCalculator registered for
+  // |account_id|. If an BulkPrintersCalculator does not exist, one will be
+  // created for |account_id|. The returned object remains valid until
+  // RemoveForUserId or Shutdown is called.
+  base::WeakPtr<BulkPrintersCalculator> GetForAccountId(
+      const AccountId& account_id);
+
+  // Returns a WeakPtr to the BulkPrintersCalculator registered for |profile|
+  // which could be null if |profile| does not map to a valid AccountId. The
+  // returned object remains valid until RemoveForUserId or Shutdown is called.
+  base::WeakPtr<BulkPrintersCalculator> GetForProfile(Profile* profile);
+
+  // Returns a WeakPtr to the BulkPrintersCalculator registered for the device.
+  base::WeakPtr<BulkPrintersCalculator> GetForDevice();
+
+  // Deletes the BulkPrintersCalculator registered for |account_id|.
+  void RemoveForUserId(const AccountId& account_id);
+
+  // Tear down all BulkPrintersCalculator created for users/profiles.
+  void ShutdownProfiles();
+
+  // Tear down all BulkPrintersCalculator.
+  void Shutdown();
+
+ private:
+  friend struct base::LazyInstanceTraitsBase<BulkPrintersCalculatorFactory>;
+
+  BulkPrintersCalculatorFactory();
+  ~BulkPrintersCalculatorFactory();
+
+  std::map<AccountId, std::unique_ptr<BulkPrintersCalculator>>
+      printers_by_user_;
+  std::unique_ptr<BulkPrintersCalculator> device_printers_;
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  DISALLOW_COPY_AND_ASSIGN(BulkPrintersCalculatorFactory);
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_PRINTING_BULK_PRINTERS_CALCULATOR_FACTORY_H_
diff --git a/chrome/browser/chromeos/printing/external_printers_unittest.cc b/chrome/browser/chromeos/printing/bulk_printers_calculator_unittest.cc
similarity index 67%
rename from chrome/browser/chromeos/printing/external_printers_unittest.cc
rename to chrome/browser/chromeos/printing/bulk_printers_calculator_unittest.cc
index 35bea3e..7b3e7904 100644
--- a/chrome/browser/chromeos/printing/external_printers_unittest.cc
+++ b/chrome/browser/chromeos/printing/bulk_printers_calculator_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/chromeos/printing/external_printers.h"
+#include "chrome/browser/chromeos/printing/bulk_printers_calculator.h"
 
 #include <string>
 #include <vector>
@@ -77,13 +77,10 @@
 ])json";
 
 // Observer that counts the number of times it has been called.
-class TestObserver : public ExternalPrinters::Observer {
+class TestObserver : public BulkPrintersCalculator::Observer {
  public:
-  void OnPrintersChanged(
-      bool valid,
-      const std::map<const std::string, const Printer>& /* printers */)
-      override {
-    last_valid = valid;
+  void OnPrintersChanged(const BulkPrintersCalculator* sender) override {
+    last_valid = sender->IsComplete();
     called++;
   }
 
@@ -93,20 +90,20 @@
   bool last_valid = false;
 };
 
-class ExternalPrintersTest : public testing::Test {
+class BulkPrintersCalculatorTest : public testing::Test {
  public:
-  ExternalPrintersTest() : scoped_task_environment_() {
+  BulkPrintersCalculatorTest() : scoped_task_environment_() {
     scoped_feature_list_.InitAndEnableFeature(
         base::Feature(features::kBulkPrinters));
-    external_printers_ = ExternalPrinters::Create();
+    external_printers_ = BulkPrintersCalculator::Create();
   }
-  ~ExternalPrintersTest() override {
+  ~BulkPrintersCalculatorTest() override {
     // Delete the printer before the task environment.
     external_printers_.reset();
   }
 
  protected:
-  std::unique_ptr<ExternalPrinters> external_printers_;
+  std::unique_ptr<BulkPrintersCalculator> external_printers_;
   base::test::ScopedTaskEnvironment scoped_task_environment_;
 
  private:
@@ -114,16 +111,17 @@
 };
 
 // Verify that we're initiall unset and empty.
-TEST_F(ExternalPrintersTest, InitialConditions) {
-  EXPECT_FALSE(external_printers_->IsPolicySet());
+TEST_F(BulkPrintersCalculatorTest, InitialConditions) {
+  EXPECT_FALSE(external_printers_->IsDataPolicySet());
   EXPECT_TRUE(external_printers_->GetPrinters().empty());
 }
 
 // Verify that the object can be destroyed while parsing is in progress.
-TEST_F(ExternalPrintersTest, DestructionIsSafe) {
+TEST_F(BulkPrintersCalculatorTest, DestructionIsSafe) {
   {
-    std::unique_ptr<ExternalPrinters> printers = ExternalPrinters::Create();
-    printers->SetAccessMode(ExternalPrinters::BLACKLIST_ONLY);
+    std::unique_ptr<BulkPrintersCalculator> printers =
+        BulkPrintersCalculator::Create();
+    printers->SetAccessMode(BulkPrintersCalculator::BLACKLIST_ONLY);
     printers->SetBlacklist({"Third"});
     printers->SetData(std::make_unique<std::string>(kBulkPolicyContentsJson));
     // Data is valid.  Computation is proceeding.
@@ -133,51 +131,28 @@
   scoped_task_environment_.RunUntilIdle();
 }
 
-// Verifies that all IsPolicySet returns false until all necessary data is set.
-TEST_F(ExternalPrintersTest, PolicyUnsetWithMissingData) {
+// Verifies that IsDataPolicySet returns false until data is set.
+TEST_F(BulkPrintersCalculatorTest, PolicyUnsetWithMissingData) {
   auto data = std::make_unique<std::string>(kBulkPolicyContentsJson);
   external_printers_->ClearData();
+  EXPECT_FALSE(external_printers_->IsDataPolicySet());
   external_printers_->SetData(std::move(data));
-
-  // Waiting for AccessMode.
+  EXPECT_TRUE(external_printers_->IsDataPolicySet());
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_FALSE(external_printers_->IsPolicySet());
-
-  external_printers_->SetAccessMode(ExternalPrinters::AccessMode::ALL_ACCESS);
-  scoped_task_environment_.RunUntilIdle();
-  EXPECT_TRUE(external_printers_->IsPolicySet());
-
-  external_printers_->SetAccessMode(
-      ExternalPrinters::AccessMode::WHITELIST_ONLY);
-  scoped_task_environment_.RunUntilIdle();
-  EXPECT_FALSE(external_printers_->IsPolicySet());  // Waiting for Whitelist.
-
-  std::vector<std::string> whitelist = {"First", "Third"};
-  external_printers_->SetWhitelist(whitelist);
-  scoped_task_environment_.RunUntilIdle();
-  EXPECT_TRUE(external_printers_->IsPolicySet());  // Everything is set.
-
-  external_printers_->SetAccessMode(
-      ExternalPrinters::AccessMode::BLACKLIST_ONLY);
-  scoped_task_environment_.RunUntilIdle();
-  EXPECT_FALSE(external_printers_->IsPolicySet());  // Blacklist needed now.
-
-  std::vector<std::string> blacklist = {"Second"};
-  external_printers_->SetBlacklist(blacklist);
-  scoped_task_environment_.RunUntilIdle();
-  EXPECT_TRUE(
-      external_printers_->IsPolicySet());  // Blacklist was set.  Ready again.
+  EXPECT_TRUE(external_printers_->IsComplete());
 }
 
 // Verify printer list after all attributes have been set.
-TEST_F(ExternalPrintersTest, AllPoliciesResultInPrinters) {
+TEST_F(BulkPrintersCalculatorTest, AllPoliciesResultInPrinters) {
   auto data = std::make_unique<std::string>(kBulkPolicyContentsJson);
-  external_printers_->SetAccessMode(ExternalPrinters::AccessMode::ALL_ACCESS);
+  external_printers_->SetAccessMode(
+      BulkPrintersCalculator::AccessMode::ALL_ACCESS);
   external_printers_->SetData(std::move(data));
+  EXPECT_TRUE(external_printers_->IsDataPolicySet());
 
   scoped_task_environment_.RunUntilIdle();
+  EXPECT_TRUE(external_printers_->IsComplete());
   const auto& printers = external_printers_->GetPrinters();
-  EXPECT_TRUE(external_printers_->IsPolicySet());
   EXPECT_EQ(kNumPrinters, printers.size());
   EXPECT_EQ("LexaPrint", printers.at("First").display_name());
   EXPECT_EQ("Color Laser", printers.at("Second").display_name());
@@ -185,34 +160,34 @@
 }
 
 // The external policy was cleared, results should be invalidated.
-TEST_F(ExternalPrintersTest, PolicyClearedNowUnset) {
+TEST_F(BulkPrintersCalculatorTest, PolicyClearedNowUnset) {
   auto data = std::make_unique<std::string>(kBulkPolicyContentsJson);
-  external_printers_->SetAccessMode(ExternalPrinters::AccessMode::ALL_ACCESS);
+  external_printers_->SetAccessMode(
+      BulkPrintersCalculator::AccessMode::ALL_ACCESS);
   external_printers_->ClearData();
   external_printers_->SetData(std::move(data));
 
   scoped_task_environment_.RunUntilIdle();
-  ASSERT_TRUE(external_printers_->IsPolicySet());
+  ASSERT_TRUE(external_printers_->IsDataPolicySet());
 
   external_printers_->ClearData();
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_FALSE(external_printers_->IsPolicySet());
+  EXPECT_FALSE(external_printers_->IsDataPolicySet());
   EXPECT_TRUE(external_printers_->GetPrinters().empty());
 }
 
 // Verify that the blacklist policy is applied correctly.  Printers in the
 // blacklist policy should not be available.  Printers not in the blackslist
 // should be available.
-TEST_F(ExternalPrintersTest, BlacklistPolicySet) {
+TEST_F(BulkPrintersCalculatorTest, BlacklistPolicySet) {
   auto data = std::make_unique<std::string>(kBulkPolicyContentsJson);
   external_printers_->ClearData();
   external_printers_->SetData(std::move(data));
-  external_printers_->SetAccessMode(ExternalPrinters::BLACKLIST_ONLY);
+  external_printers_->SetAccessMode(BulkPrintersCalculator::BLACKLIST_ONLY);
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_FALSE(external_printers_->IsPolicySet());
   external_printers_->SetBlacklist({"Second", "Third"});
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_TRUE(external_printers_->IsPolicySet());
+  EXPECT_TRUE(external_printers_->IsComplete());
 
   scoped_task_environment_.RunUntilIdle();
   const auto& printers = external_printers_->GetPrinters();
@@ -222,79 +197,76 @@
 
 // Verify that the whitelist policy is correctly applied.  Only printers
 // available in the whitelist are available.
-TEST_F(ExternalPrintersTest, WhitelistPolicySet) {
+TEST_F(BulkPrintersCalculatorTest, WhitelistPolicySet) {
   auto data = std::make_unique<std::string>(kBulkPolicyContentsJson);
   external_printers_->ClearData();
   external_printers_->SetData(std::move(data));
-  external_printers_->SetAccessMode(ExternalPrinters::WHITELIST_ONLY);
+  external_printers_->SetAccessMode(BulkPrintersCalculator::WHITELIST_ONLY);
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_FALSE(external_printers_->IsPolicySet());
   external_printers_->SetWhitelist({"First"});
 
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_TRUE(external_printers_->IsPolicySet());
+  EXPECT_TRUE(external_printers_->IsComplete());
   const auto& printers = external_printers_->GetPrinters();
   EXPECT_EQ(1U, printers.size());
   EXPECT_EQ("LexaPrint", printers.at("First").display_name());
 }
 
 // Verify that an empty blacklist results in no printer limits.
-TEST_F(ExternalPrintersTest, EmptyBlacklistAllPrinters) {
+TEST_F(BulkPrintersCalculatorTest, EmptyBlacklistAllPrinters) {
   auto data = std::make_unique<std::string>(kBulkPolicyContentsJson);
   external_printers_->ClearData();
   external_printers_->SetData(std::move(data));
-  external_printers_->SetAccessMode(ExternalPrinters::BLACKLIST_ONLY);
+  external_printers_->SetAccessMode(BulkPrintersCalculator::BLACKLIST_ONLY);
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_FALSE(external_printers_->IsPolicySet());
   external_printers_->SetBlacklist({});
 
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_TRUE(external_printers_->IsPolicySet());
+  EXPECT_TRUE(external_printers_->IsComplete());
   const auto& printers = external_printers_->GetPrinters();
   EXPECT_EQ(kNumPrinters, printers.size());
 }
 
 // Verify that an empty whitelist results in no printers.
-TEST_F(ExternalPrintersTest, EmptyWhitelistNoPrinters) {
+TEST_F(BulkPrintersCalculatorTest, EmptyWhitelistNoPrinters) {
   auto data = std::make_unique<std::string>(kBulkPolicyContentsJson);
   external_printers_->ClearData();
   external_printers_->SetData(std::move(data));
-  external_printers_->SetAccessMode(ExternalPrinters::WHITELIST_ONLY);
+  external_printers_->SetAccessMode(BulkPrintersCalculator::WHITELIST_ONLY);
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_FALSE(external_printers_->IsPolicySet());
   external_printers_->SetWhitelist({});
 
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_TRUE(external_printers_->IsPolicySet());
+  EXPECT_TRUE(external_printers_->IsComplete());
   const auto& printers = external_printers_->GetPrinters();
   EXPECT_EQ(0U, printers.size());
 }
 
 // Verify that switching from whitelist to blacklist behaves correctly.
-TEST_F(ExternalPrintersTest, BlacklistToWhitelistSwap) {
+TEST_F(BulkPrintersCalculatorTest, BlacklistToWhitelistSwap) {
   auto data = std::make_unique<std::string>(kBulkPolicyContentsJson);
   external_printers_->ClearData();
   external_printers_->SetData(std::move(data));
-  external_printers_->SetAccessMode(ExternalPrinters::BLACKLIST_ONLY);
+  external_printers_->SetAccessMode(BulkPrintersCalculator::BLACKLIST_ONLY);
   external_printers_->SetWhitelist({"First"});
   external_printers_->SetBlacklist({"First"});
 
   // This should result in 2 printers.  But we're switching the mode anyway.
 
-  external_printers_->SetAccessMode(ExternalPrinters::WHITELIST_ONLY);
+  external_printers_->SetAccessMode(BulkPrintersCalculator::WHITELIST_ONLY);
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_TRUE(external_printers_->IsPolicySet());
+  EXPECT_TRUE(external_printers_->IsComplete());
   const auto& printers = external_printers_->GetPrinters();
   EXPECT_EQ(1U, printers.size());
   EXPECT_EQ("LexaPrint", printers.at("First").display_name());
 }
 
 // Verify that updated configurations are handled properly.
-TEST_F(ExternalPrintersTest, MultipleUpdates) {
+TEST_F(BulkPrintersCalculatorTest, MultipleUpdates) {
   auto data = std::make_unique<std::string>(kBulkPolicyContentsJson);
   external_printers_->ClearData();
   external_printers_->SetData(std::move(data));
-  external_printers_->SetAccessMode(ExternalPrinters::ALL_ACCESS);
+  external_printers_->SetAccessMode(BulkPrintersCalculator::ALL_ACCESS);
   // There will be 3 printers here.  But we don't want to wait for compuation to
   // complete to verify the final value gets used.
 
@@ -307,41 +279,41 @@
 }
 
 // Verifies that the observer is called at the expected times.
-TEST_F(ExternalPrintersTest, ObserverTest) {
+TEST_F(BulkPrintersCalculatorTest, ObserverTest) {
   TestObserver obs;
   external_printers_->AddObserver(&obs);
 
-  external_printers_->SetAccessMode(ExternalPrinters::ALL_ACCESS);
+  external_printers_->SetAccessMode(BulkPrintersCalculator::ALL_ACCESS);
   external_printers_->SetWhitelist(std::vector<std::string>());
   external_printers_->SetBlacklist(std::vector<std::string>());
   external_printers_->ClearData();
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_EQ(0, obs.called);
+  EXPECT_EQ(1, obs.called);
 
   external_printers_->SetData(
       std::make_unique<std::string>(kBulkPolicyContentsJson));
 
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_TRUE(external_printers_->IsPolicySet());
-  EXPECT_EQ(1, obs.called);
+  EXPECT_TRUE(external_printers_->IsDataPolicySet());
+  EXPECT_EQ(2, obs.called);
   EXPECT_TRUE(obs.last_valid);  // ready now
   // Printer list is correct after notification.
   EXPECT_EQ(kNumPrinters, external_printers_->GetPrinters().size());
 
-  external_printers_->SetAccessMode(ExternalPrinters::WHITELIST_ONLY);
-  scoped_task_environment_.RunUntilIdle();
-  EXPECT_EQ(2, obs.called);  // effective list changed.  Notified.
-  EXPECT_TRUE(obs.last_valid);
-
-  external_printers_->SetAccessMode(ExternalPrinters::BLACKLIST_ONLY);
+  external_printers_->SetAccessMode(BulkPrintersCalculator::WHITELIST_ONLY);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(3, obs.called);  // effective list changed.  Notified.
   EXPECT_TRUE(obs.last_valid);
 
+  external_printers_->SetAccessMode(BulkPrintersCalculator::BLACKLIST_ONLY);
+  scoped_task_environment_.RunUntilIdle();
+  EXPECT_EQ(4, obs.called);  // effective list changed.  Notified.
+  EXPECT_TRUE(obs.last_valid);
+
   external_printers_->ClearData();
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_EQ(4, obs.called);  // Called for transition to invalid policy.
-  EXPECT_FALSE(obs.last_valid);
+  EXPECT_EQ(5, obs.called);  // Called for transition to invalid policy.
+  EXPECT_TRUE(obs.last_valid);
   EXPECT_TRUE(external_printers_->GetPrinters().empty());
 
   // cleanup
diff --git a/chrome/browser/chromeos/printing/calculators_policies_binder.cc b/chrome/browser/chromeos/printing/calculators_policies_binder.cc
new file mode 100644
index 0000000..1f422df
--- /dev/null
+++ b/chrome/browser/chromeos/printing/calculators_policies_binder.cc
@@ -0,0 +1,195 @@
+// 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 "chrome/browser/chromeos/printing/calculators_policies_binder.h"
+
+#include <list>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "chrome/browser/chromeos/printing/bulk_printers_calculator.h"
+#include "chrome/browser/chromeos/printing/bulk_printers_calculator_factory.h"
+#include "chrome/browser/policy/profile_policy_connector.h"
+#include "chrome/browser/policy/profile_policy_connector_factory.h"
+#include "chrome/common/pref_names.h"
+#include "chromeos/settings/cros_settings_names.h"
+#include "components/prefs/pref_change_registrar.h"
+#include "components/prefs/pref_service.h"
+
+namespace chromeos {
+
+namespace {
+
+// It stores the number of bindings (instances of this class) connected to each
+// BulkPrintersCalculator object. It allows us to make sure, that every
+// BulkPrintersCalculator object is not binded more that once.
+std::map<BulkPrintersCalculator*, unsigned>& BindingsCount() {
+  static base::NoDestructor<std::map<BulkPrintersCalculator*, unsigned>>
+      bindings_count;
+  return *bindings_count;
+}
+
+BulkPrintersCalculator::AccessMode ConvertToAccessMode(int mode_val) {
+  if (mode_val >= BulkPrintersCalculator::BLACKLIST_ONLY &&
+      mode_val <= BulkPrintersCalculator::ALL_ACCESS) {
+    return static_cast<BulkPrintersCalculator::AccessMode>(mode_val);
+  }
+  // Error occurred, let's return the default value.
+  LOG(ERROR) << "Unrecognized access mode";
+  return BulkPrintersCalculator::ALL_ACCESS;
+}
+
+std::vector<std::string> ConvertToVector(const base::ListValue* list) {
+  std::vector<std::string> string_list;
+  if (list) {
+    for (const base::Value& value : *list) {
+      if (value.is_string()) {
+        string_list.push_back(value.GetString());
+      }
+    }
+  }
+  return string_list;
+}
+
+class CalculatorsPoliciesBinderImpl : public CalculatorsPoliciesBinder {
+ public:
+  CalculatorsPoliciesBinderImpl(CrosSettings* settings, Profile* profile)
+      : settings_(settings), profile_(profile) {
+    pref_change_registrar_.Init(profile->GetPrefs());
+    // Bind device policies to corresponding instance of BulkPrintersCalculator.
+    device_printers_ = BulkPrintersCalculatorFactory::Get()->GetForDevice();
+    if (device_printers_ && ++(BindingsCount()[device_printers_.get()]) == 1) {
+      BindSettings(kDeviceNativePrintersAccessMode,
+                   &CalculatorsPoliciesBinderImpl::UpdateDeviceAccessMode);
+      BindSettings(kDeviceNativePrintersBlacklist,
+                   &CalculatorsPoliciesBinderImpl::UpdateDeviceBlacklist);
+      BindSettings(kDeviceNativePrintersWhitelist,
+                   &CalculatorsPoliciesBinderImpl::UpdateDeviceWhitelist);
+    }
+    // Bind user policies to corresponding instance of BulkPrintersCalculator.
+    user_printers_ =
+        BulkPrintersCalculatorFactory::Get()->GetForProfile(profile);
+    if (user_printers_ && ++(BindingsCount()[user_printers_.get()]) == 1) {
+      BindPref(prefs::kRecommendedNativePrintersAccessMode,
+               &CalculatorsPoliciesBinderImpl::UpdateUserAccessMode);
+      BindPref(prefs::kRecommendedNativePrintersBlacklist,
+               &CalculatorsPoliciesBinderImpl::UpdateUserBlacklist);
+      BindPref(prefs::kRecommendedNativePrintersWhitelist,
+               &CalculatorsPoliciesBinderImpl::UpdateUserWhitelist);
+    }
+  }
+
+  ~CalculatorsPoliciesBinderImpl() override {
+    // We have to decrease counters in bindings_count.
+    if (device_printers_ && --(BindingsCount()[device_printers_.get()]) == 0) {
+      BindingsCount().erase(device_printers_.get());
+    }
+    if (user_printers_ && --(BindingsCount()[user_printers_.get()]) == 0) {
+      BindingsCount().erase(user_printers_.get());
+    }
+  }
+
+ private:
+  // Methods propagating values from policies to BulkPrintersCalculator.
+  void UpdateDeviceAccessMode() {
+    int mode_val;
+    if (!settings_->GetInteger(kDeviceNativePrintersAccessMode, &mode_val)) {
+      mode_val = BulkPrintersCalculator::AccessMode::UNSET;
+    }
+    device_printers_->SetAccessMode(ConvertToAccessMode(mode_val));
+  }
+
+  void UpdateDeviceBlacklist() {
+    device_printers_->SetBlacklist(
+        FromSettings(kDeviceNativePrintersBlacklist));
+  }
+
+  void UpdateDeviceWhitelist() {
+    device_printers_->SetWhitelist(
+        FromSettings(kDeviceNativePrintersWhitelist));
+  }
+
+  void UpdateUserAccessMode() {
+    user_printers_->SetAccessMode(
+        ConvertToAccessMode(profile_->GetPrefs()->GetInteger(
+            prefs::kRecommendedNativePrintersAccessMode)));
+  }
+
+  void UpdateUserBlacklist() {
+    user_printers_->SetBlacklist(
+        FromPrefs(prefs::kRecommendedNativePrintersBlacklist));
+  }
+
+  void UpdateUserWhitelist() {
+    user_printers_->SetWhitelist(
+        FromPrefs(prefs::kRecommendedNativePrintersWhitelist));
+  }
+
+  typedef void (CalculatorsPoliciesBinderImpl::*SimpleMethod)();
+
+  // Binds given device policy to given method and calls this method once.
+  void BindPref(const char* policy_name, SimpleMethod method_to_call) {
+    pref_change_registrar_.Add(
+        policy_name,
+        base::BindRepeating(method_to_call, base::Unretained(this)));
+    (this->*method_to_call)();
+  }
+
+  // Binds given user policy to given method and calls this method once.
+  void BindSettings(const char* policy_name, SimpleMethod method_to_call) {
+    subscriptions_.push_back(settings_->AddSettingsObserver(
+        policy_name,
+        base::BindRepeating(method_to_call, base::Unretained(this))));
+    (this->*method_to_call)();
+  }
+
+  // Extracts the list of strings named |policy_name| from device policies.
+  std::vector<std::string> FromSettings(const std::string& policy_name) {
+    const base::ListValue* list;
+    if (!settings_->GetList(policy_name, &list)) {
+      list = nullptr;
+    }
+    return ConvertToVector(list);
+  }
+
+  // Extracts the list of strings named |policy_name| from user policies.
+  std::vector<std::string> FromPrefs(const std::string& policy_name) {
+    return ConvertToVector(profile_->GetPrefs()->GetList(policy_name));
+  }
+
+  // Device and user bulk printers. Unowned.
+  base::WeakPtr<BulkPrintersCalculator> device_printers_;
+  base::WeakPtr<BulkPrintersCalculator> user_printers_;
+
+  // Device and profile (user) settings.
+  CrosSettings* settings_;
+  std::list<std::unique_ptr<CrosSettings::ObserverSubscription>> subscriptions_;
+  Profile* profile_;
+  PrefChangeRegistrar pref_change_registrar_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+  DISALLOW_COPY_AND_ASSIGN(CalculatorsPoliciesBinderImpl);
+};
+
+}  // namespace
+
+// static
+void CalculatorsPoliciesBinder::RegisterProfilePrefs(
+    user_prefs::PrefRegistrySyncable* registry) {
+  // Default value for access mode is AllAccess.
+  registry->RegisterIntegerPref(prefs::kRecommendedNativePrintersAccessMode,
+                                BulkPrintersCalculator::ALL_ACCESS);
+  registry->RegisterListPref(prefs::kRecommendedNativePrintersBlacklist);
+  registry->RegisterListPref(prefs::kRecommendedNativePrintersWhitelist);
+}
+
+// static
+std::unique_ptr<CalculatorsPoliciesBinder> CalculatorsPoliciesBinder::Create(
+    CrosSettings* settings,
+    Profile* profile) {
+  return std::make_unique<CalculatorsPoliciesBinderImpl>(settings, profile);
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/printing/calculators_policies_binder.h b/chrome/browser/chromeos/printing/calculators_policies_binder.h
new file mode 100644
index 0000000..926e840a0
--- /dev/null
+++ b/chrome/browser/chromeos/printing/calculators_policies_binder.h
@@ -0,0 +1,39 @@
+// Copyright 2018 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_PRINTING_CALCULATORS_POLICIES_BINDER_H_
+#define CHROME_BROWSER_CHROMEOS_PRINTING_CALCULATORS_POLICIES_BINDER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "chrome/browser/chromeos/settings/cros_settings.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+
+namespace chromeos {
+
+// Observes device settings & user profile modifications and propagates them to
+// BulkPrintersCalculator objects associated with given device context and user
+// profile. All methods must be called from the same sequence (UI).
+class CalculatorsPoliciesBinder {
+ public:
+  static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
+  // |settings| is the source of device policies. |profile| is a user profile.
+  static std::unique_ptr<CalculatorsPoliciesBinder> Create(
+      CrosSettings* settings,
+      Profile* profile);
+  virtual ~CalculatorsPoliciesBinder() = default;
+
+ protected:
+  CalculatorsPoliciesBinder() = default;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(CalculatorsPoliciesBinder);
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_PRINTING_CALCULATORS_POLICIES_BINDER_H_
diff --git a/chrome/browser/chromeos/printing/cups_print_job_manager_impl.cc b/chrome/browser/chromeos/printing/cups_print_job_manager_impl.cc
index e6dc236fe..45e2a4c1 100644
--- a/chrome/browser/chromeos/printing/cups_print_job_manager_impl.cc
+++ b/chrome/browser/chromeos/printing/cups_print_job_manager_impl.cc
@@ -487,7 +487,7 @@
     jobs_.clear();
   }
 
-  // Notify observers that a state update has occured for |job|.
+  // Notify observers that a state update has occurred for |job|.
   void NotifyJobStateUpdate(base::WeakPtr<CupsPrintJob> job) {
     DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
diff --git a/chrome/browser/chromeos/printing/cups_printers_manager.cc b/chrome/browser/chromeos/printing/cups_printers_manager.cc
index 1faee11..62dac18f 100644
--- a/chrome/browser/chromeos/printing/cups_printers_manager.cc
+++ b/chrome/browser/chromeos/printing/cups_printers_manager.cc
@@ -111,8 +111,10 @@
     // Prime the printer cache with the configured and enterprise printers.
     printers_[kConfigured] = synced_printers_manager_->GetConfiguredPrinters();
     RebuildConfiguredPrintersIndex();
-    printers_[kEnterprise] = synced_printers_manager_->GetEnterprisePrinters();
     synced_printers_manager_observer_.Add(synced_printers_manager_);
+    enterprise_printers_are_ready_ =
+        synced_printers_manager_->GetEnterprisePrinters(
+            &(printers_[kEnterprise]));
 
     // Callbacks may ensue immediately when the observer proxies are set up, so
     // these instantiations must come after everything else is initialized.
@@ -197,6 +199,9 @@
   void AddObserver(CupsPrintersManager::Observer* observer) override {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
     observer_list_.AddObserver(observer);
+    if (enterprise_printers_are_ready_) {
+      observer->OnEnterprisePrintersInitialized();
+    }
   }
 
   // Public API function.
@@ -246,10 +251,9 @@
   }
 
   // SyncedPrintersManager::Observer implementation
-  void OnConfiguredPrintersChanged(
-      const std::vector<Printer>& printers) override {
+  void OnConfiguredPrintersChanged() override {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
-    printers_[kConfigured] = printers;
+    printers_[kConfigured] = synced_printers_manager_->GetConfiguredPrinters();
     RebuildConfiguredPrintersIndex();
     RebuildDetectedLists();
     UpdateConfiguredPrinterURIs();
@@ -257,10 +261,17 @@
   }
 
   // SyncedPrintersManager::Observer implementation
-  void OnEnterprisePrintersChanged(
-      const std::vector<Printer>& printers) override {
+  void OnEnterprisePrintersChanged() override {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
-    printers_[kEnterprise] = printers;
+    const bool enterprise_printers_are_ready =
+        synced_printers_manager_->GetEnterprisePrinters(
+            &(printers_[kEnterprise]));
+    if (enterprise_printers_are_ready && !enterprise_printers_are_ready_) {
+      enterprise_printers_are_ready_ = true;
+      for (auto& observer : observer_list_) {
+        observer.OnEnterprisePrintersInitialized();
+      }
+    }
     NotifyObservers({kEnterprise});
   }
 
@@ -548,6 +559,13 @@
   // Categorized printers.  This is indexed by PrinterClass.
   std::vector<std::vector<Printer>> printers_;
 
+  // Equals true if the list of enterprise printers and related policies
+  // is initialized and configured correctly.
+  bool enterprise_printers_are_ready_ = false;
+
+  // Printer ids that occur in one of our categories or printers.
+  std::unordered_set<std::string> known_printer_ids_;
+
   // This is a dual-purpose structure.  The keys in the map are printer ids.
   // If an entry exists in this map it means we have received a response from
   // PpdProvider about a PpdReference for the given printer.  A null value
diff --git a/chrome/browser/chromeos/printing/cups_printers_manager.h b/chrome/browser/chromeos/printing/cups_printers_manager.h
index b03ab169..441a71db 100644
--- a/chrome/browser/chromeos/printing/cups_printers_manager.h
+++ b/chrome/browser/chromeos/printing/cups_printers_manager.h
@@ -45,9 +45,13 @@
    public:
     // The list of printers in this class has changed to the given printers.
     virtual void OnPrintersChanged(PrinterClass printer_class,
-                                   const std::vector<Printer>& printers) = 0;
+                                   const std::vector<Printer>& printers) {}
+    // It is called exactly once for each observer. It means that the
+    // subsystem for enterprise printers is initialized. When an observer is
+    // being registered after the subsystem's initialization, this call is
+    // scheduled immediately in AddObserver method.
+    virtual void OnEnterprisePrintersInitialized() {}
 
-   protected:
     virtual ~Observer() = default;
   };
 
@@ -90,9 +94,9 @@
   // the printer_id is not that of a configured printer.
   virtual void RemoveConfiguredPrinter(const std::string& printer_id) = 0;
 
-  // Add or remove observers.  Observers do not need to be on the same
+  // Add or remove observers.  Observers must be on the same
   // sequence as the CupsPrintersManager.  Callbacks for a given observer
-  // will be on the same sequence as was used to call AddObserver().
+  // will be on the same sequence as the CupsPrintersManager.
   virtual void AddObserver(Observer* observer) = 0;
   virtual void RemoveObserver(Observer* observer) = 0;
 
diff --git a/chrome/browser/chromeos/printing/cups_printers_manager_unittest.cc b/chrome/browser/chromeos/printing/cups_printers_manager_unittest.cc
index 76f234c..7f4b1f5f 100644
--- a/chrome/browser/chromeos/printing/cups_printers_manager_unittest.cc
+++ b/chrome/browser/chromeos/printing/cups_printers_manager_unittest.cc
@@ -36,8 +36,10 @@
   }
 
   // Returns printers from enterprise policy.
-  std::vector<Printer> GetEnterprisePrinters() const override {
-    return enterprise_printers_;
+  bool GetEnterprisePrinters(std::vector<Printer>* printers) const override {
+    if (printers != nullptr)
+      *printers = enterprise_printers_;
+    return true;
   }
 
   // Attach |observer| for notification of events.  |observer| is expected to
@@ -67,7 +69,7 @@
       configured_printers_.push_back(printer);
     }
     for (Observer& observer : observers_) {
-      observer.OnConfiguredPrintersChanged(configured_printers_);
+      observer.OnConfiguredPrintersChanged();
     }
   }
 
@@ -77,7 +79,7 @@
       if (it->id() == printer_id) {
         configured_printers_.erase(it);
         for (Observer& observer : observers_) {
-          observer.OnConfiguredPrintersChanged(configured_printers_);
+          observer.OnConfiguredPrintersChanged();
         }
         return true;
       }
@@ -109,7 +111,7 @@
     configured_printers_.insert(configured_printers_.end(), printers.begin(),
                                 printers.end());
     for (Observer& observer : observers_) {
-      observer.OnConfiguredPrintersChanged(configured_printers_);
+      observer.OnConfiguredPrintersChanged();
     }
   }
 
@@ -118,7 +120,7 @@
   void RemoveConfiguredPrinters(const std::unordered_set<std::string>& ids) {
     RemovePrinters(ids, &configured_printers_);
     for (Observer& observer : observers_) {
-      observer.OnConfiguredPrintersChanged(configured_printers_);
+      observer.OnConfiguredPrintersChanged();
     }
   }
 
@@ -128,7 +130,7 @@
     enterprise_printers_.insert(enterprise_printers_.end(), printers.begin(),
                                 printers.end());
     for (Observer& observer : observers_) {
-      observer.OnEnterprisePrintersChanged(enterprise_printers_);
+      observer.OnEnterprisePrintersChanged();
     }
   }
 
@@ -137,7 +139,7 @@
   void RemoveEnterprisePrinters(const std::unordered_set<std::string>& ids) {
     RemovePrinters(ids, &enterprise_printers_);
     for (Observer& observer : observers_) {
-      observer.OnEnterprisePrintersChanged(enterprise_printers_);
+      observer.OnEnterprisePrintersChanged();
     }
   }
 
diff --git a/chrome/browser/chromeos/printing/device_external_printers_factory.cc b/chrome/browser/chromeos/printing/device_external_printers_factory.cc
deleted file mode 100644
index f85bb38..0000000
--- a/chrome/browser/chromeos/printing/device_external_printers_factory.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2018 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/printing/device_external_printers_factory.h"
-
-#include <memory>
-
-#include "base/lazy_instance.h"
-#include "chrome/browser/chromeos/printing/external_printers.h"
-
-namespace chromeos {
-
-namespace {
-
-base::LazyInstance<DeviceExternalPrintersFactory>::DestructorAtExit
-    g_printers_factory = LAZY_INSTANCE_INITIALIZER;
-
-}  // namespace
-
-// static
-DeviceExternalPrintersFactory* DeviceExternalPrintersFactory::Get() {
-  return g_printers_factory.Pointer();
-}
-
-base::WeakPtr<ExternalPrinters> DeviceExternalPrintersFactory::GetForDevice() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!device_printers_)
-    device_printers_ = ExternalPrinters::Create();
-  return device_printers_->AsWeakPtr();
-}
-
-void DeviceExternalPrintersFactory::Shutdown() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  device_printers_.reset();
-}
-
-DeviceExternalPrintersFactory::DeviceExternalPrintersFactory() = default;
-DeviceExternalPrintersFactory::~DeviceExternalPrintersFactory() = default;
-
-}  // namespace chromeos
diff --git a/chrome/browser/chromeos/printing/device_external_printers_factory.h b/chrome/browser/chromeos/printing/device_external_printers_factory.h
deleted file mode 100644
index e73b311d..0000000
--- a/chrome/browser/chromeos/printing/device_external_printers_factory.h
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2018 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_PRINTING_DEVICE_EXTERNAL_PRINTERS_FACTORY_H_
-#define CHROME_BROWSER_CHROMEOS_PRINTING_DEVICE_EXTERNAL_PRINTERS_FACTORY_H_
-
-#include <memory>
-
-#include "base/lazy_instance.h"
-#include "base/macros.h"
-#include "base/memory/weak_ptr.h"
-#include "base/sequence_checker.h"
-#include "chrome/browser/chromeos/printing/external_printers.h"
-
-namespace chromeos {
-
-// Dispenses ExternalPrinters object for device. Access to this object should be
-// sequenced.
-class DeviceExternalPrintersFactory {
- public:
-  static DeviceExternalPrintersFactory* Get();
-
-  // Returns a WeakPtr to the ExternalPrinters registered for the device.
-  base::WeakPtr<ExternalPrinters> GetForDevice();
-
-  // Tear down device ExternalPrinters object.
-  void Shutdown();
-
- private:
-  friend struct base::LazyInstanceTraitsBase<DeviceExternalPrintersFactory>;
-
-  DeviceExternalPrintersFactory();
-  ~DeviceExternalPrintersFactory();
-
-  std::unique_ptr<ExternalPrinters> device_printers_;
-
-  SEQUENCE_CHECKER(sequence_checker_);
-
-  DISALLOW_COPY_AND_ASSIGN(DeviceExternalPrintersFactory);
-};
-
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_CHROMEOS_PRINTING_DEVICE_EXTERNAL_PRINTERS_FACTORY_H_
diff --git a/chrome/browser/chromeos/printing/device_external_printers_settings_bridge.cc b/chrome/browser/chromeos/printing/device_external_printers_settings_bridge.cc
deleted file mode 100644
index dfdabd88..0000000
--- a/chrome/browser/chromeos/printing/device_external_printers_settings_bridge.cc
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright 2018 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/printing/device_external_printers_settings_bridge.h"
-
-#include <string>
-#include <vector>
-
-#include "base/bind.h"
-#include "base/values.h"
-#include "chrome/browser/chromeos/printing/device_external_printers_factory.h"
-#include "chrome/browser/chromeos/printing/external_printers.h"
-#include "chrome/browser/chromeos/settings/cros_settings.h"
-
-namespace chromeos {
-
-namespace {
-
-// Extracts the list of strings named |policy_name| from |settings| and returns
-// it.
-std::vector<std::string> FromSettings(const CrosSettings* settings,
-                                      const std::string& policy_name) {
-  std::vector<std::string> string_list;
-  const base::ListValue* list = nullptr;
-  if (!settings->GetList(policy_name, &list) || !list) {
-    return string_list;
-  }
-
-  for (const base::Value& value : *list) {
-    if (value.is_string()) {
-      string_list.push_back(value.GetString());
-    }
-  }
-
-  return string_list;
-}
-
-}  // namespace
-
-DeviceExternalPrintersSettingsBridge::DeviceExternalPrintersSettingsBridge(
-    const ExternalPrinterPolicies& policies,
-    CrosSettings* settings)
-    : settings_(settings), policies_(policies) {
-  access_mode_subscription_ = settings->AddSettingsObserver(
-      policies_.access_mode,
-      base::BindRepeating(
-          &DeviceExternalPrintersSettingsBridge::AccessModeUpdated,
-          base::Unretained(this)));
-  blacklist_subscription_ = settings->AddSettingsObserver(
-      policies_.blacklist,
-      base::BindRepeating(
-          &DeviceExternalPrintersSettingsBridge::BlacklistUpdated,
-          base::Unretained(this)));
-  whitelist_subscription_ = settings->AddSettingsObserver(
-      policies_.whitelist,
-      base::BindRepeating(
-          &DeviceExternalPrintersSettingsBridge::WhitelistUpdated,
-          base::Unretained(this)));
-  Initialize();
-}
-
-DeviceExternalPrintersSettingsBridge::~DeviceExternalPrintersSettingsBridge() =
-    default;
-
-void DeviceExternalPrintersSettingsBridge::Initialize() {
-  BlacklistUpdated();
-  WhitelistUpdated();
-  AccessModeUpdated();
-}
-
-void DeviceExternalPrintersSettingsBridge::AccessModeUpdated() {
-  int mode_val;
-  // Settings should contain value for access mode device setting.
-  // Even if it's not pushed with device policy, the default value should be
-  // set.
-  CHECK(settings_->GetInteger(policies_.access_mode, &mode_val));
-  ExternalPrinters::AccessMode mode =
-      static_cast<ExternalPrinters::AccessMode>(mode_val);
-
-  base::WeakPtr<ExternalPrinters> printers =
-      DeviceExternalPrintersFactory::Get()->GetForDevice();
-  if (printers)
-    printers->SetAccessMode(mode);
-}
-
-void DeviceExternalPrintersSettingsBridge::BlacklistUpdated() {
-  base::WeakPtr<ExternalPrinters> printers =
-      DeviceExternalPrintersFactory::Get()->GetForDevice();
-  if (printers)
-    printers->SetBlacklist(FromSettings(settings_, policies_.blacklist));
-}
-
-void DeviceExternalPrintersSettingsBridge::WhitelistUpdated() {
-  base::WeakPtr<ExternalPrinters> printers =
-      DeviceExternalPrintersFactory::Get()->GetForDevice();
-  if (printers)
-    printers->SetWhitelist(FromSettings(settings_, policies_.whitelist));
-}
-
-}  // namespace chromeos
diff --git a/chrome/browser/chromeos/printing/device_external_printers_settings_bridge.h b/chrome/browser/chromeos/printing/device_external_printers_settings_bridge.h
deleted file mode 100644
index 49e908d..0000000
--- a/chrome/browser/chromeos/printing/device_external_printers_settings_bridge.h
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2018 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_PRINTING_DEVICE_EXTERNAL_PRINTERS_SETTINGS_BRIDGE_H_
-#define CHROME_BROWSER_CHROMEOS_PRINTING_DEVICE_EXTERNAL_PRINTERS_SETTINGS_BRIDGE_H_
-
-#include "base/macros.h"
-#include "chrome/browser/chromeos/printing/external_printers_policies.h"
-#include "chrome/browser/chromeos/settings/cros_settings.h"
-
-namespace chromeos {
-
-class ExternalPrinters;
-
-// Observe device settings changes and propagate changes to ExternalPrinters.
-class DeviceExternalPrintersSettingsBridge {
- public:
-  DeviceExternalPrintersSettingsBridge(const ExternalPrinterPolicies& policies,
-                                       CrosSettings* settings);
-  ~DeviceExternalPrintersSettingsBridge();
-
- private:
-  // Retrieve initial values for device settings.
-  void Initialize();
-
-  // Handle update for the access mode policy.
-  void AccessModeUpdated();
-
-  // Handle updates for the blacklist policy.
-  void BlacklistUpdated();
-
-  // Handle updates for the whitelist policy.
-  void WhitelistUpdated();
-
-  CrosSettings* settings_;
-  const ExternalPrinterPolicies policies_;
-  std::unique_ptr<CrosSettings::ObserverSubscription> access_mode_subscription_;
-  std::unique_ptr<CrosSettings::ObserverSubscription> blacklist_subscription_;
-  std::unique_ptr<CrosSettings::ObserverSubscription> whitelist_subscription_;
-
-  DISALLOW_COPY_AND_ASSIGN(DeviceExternalPrintersSettingsBridge);
-};
-
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_CHROMEOS_PRINTING_DEVICE_EXTERNAL_PRINTERS_SETTINGS_BRIDGE_H_
diff --git a/chrome/browser/chromeos/printing/enterprise_printers_provider.cc b/chrome/browser/chromeos/printing/enterprise_printers_provider.cc
new file mode 100644
index 0000000..7f6015d
--- /dev/null
+++ b/chrome/browser/chromeos/printing/enterprise_printers_provider.cc
@@ -0,0 +1,255 @@
+// Copyright 2018 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/printing/enterprise_printers_provider.h"
+
+#include <list>
+#include <vector>
+
+#include "base/feature_list.h"
+#include "base/json/json_reader.h"
+#include "base/md5.h"
+#include "chrome/browser/chromeos/printing/bulk_printers_calculator.h"
+#include "chrome/browser/chromeos/printing/bulk_printers_calculator_factory.h"
+#include "chrome/browser/chromeos/printing/calculators_policies_binder.h"
+#include "chrome/browser/policy/profile_policy_connector.h"
+#include "chrome/browser/policy/profile_policy_connector_factory.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/pref_names.h"
+#include "chromeos/printing/printer_translator.h"
+#include "components/policy/core/common/policy_service.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_change_registrar.h"
+#include "components/prefs/pref_service.h"
+
+namespace chromeos {
+
+namespace {
+
+std::vector<std::string> ConvertToVector(const base::ListValue* list) {
+  std::vector<std::string> string_list;
+  if (list) {
+    for (const base::Value& value : *list) {
+      if (value.is_string()) {
+        string_list.push_back(value.GetString());
+      }
+    }
+  }
+  return string_list;
+}
+
+class EnterprisePrintersProviderImpl : public EnterprisePrintersProvider,
+                                       public BulkPrintersCalculator::Observer {
+ public:
+  EnterprisePrintersProviderImpl(CrosSettings* settings, Profile* profile)
+      : profile_(profile) {
+    // initialization of pref_change_registrar
+    pref_change_registrar_.Init(profile->GetPrefs());
+
+    if (base::FeatureList::IsEnabled(features::kBulkPrinters)) {
+      // Binds instances of BulkPrintersCalculator to policies.
+      policies_binder_ = CalculatorsPoliciesBinder::Create(settings, profile);
+      // Get instance of BulkPrintersCalculator for device policies.
+      device_printers_ = BulkPrintersCalculatorFactory::Get()->GetForDevice();
+      if (device_printers_) {
+        device_printers_->AddObserver(this);
+        RecalculateCompleteFlagForDevicePrinters();
+      }
+      // Get instance of BulkPrintersCalculator for user policies.
+      user_printers_ =
+          BulkPrintersCalculatorFactory::Get()->GetForProfile(profile);
+      if (user_printers_) {
+        user_printers_->AddObserver(this);
+        RecalculateCompleteFlagForUserPrinters();
+      }
+    } else {
+      // If a "Bulk Printers" feature is inactive, we do not bind anything.
+      // The list of printers is always empty and is reported as complete.
+      complete_ = true;
+    }
+    // Binds policy with recommended printers (deprecated). This method calls
+    // indirectly RecalculateCurrentPrintersList() that prepares the first
+    // version of final list of printers.
+    BindPref(prefs::kRecommendedNativePrinters,
+             &EnterprisePrintersProviderImpl::UpdateUserRecommendedPrinters);
+  }
+
+  ~EnterprisePrintersProviderImpl() override {
+    if (device_printers_)
+      device_printers_->RemoveObserver(this);
+    if (user_printers_)
+      user_printers_->RemoveObserver(this);
+  }
+
+  void AddObserver(EnterprisePrintersProvider::Observer* observer) override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    observers_.AddObserver(observer);
+    observer->OnPrintersChanged(complete_, printers_);
+  }
+
+  void RemoveObserver(EnterprisePrintersProvider::Observer* observer) override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    observers_.RemoveObserver(observer);
+  }
+
+  // BulkPrintersCalculator::Observer implementation
+  void OnPrintersChanged(const BulkPrintersCalculator* sender) override {
+    if (sender == device_printers_.get()) {
+      RecalculateCompleteFlagForDevicePrinters();
+    } else {
+      RecalculateCompleteFlagForUserPrinters();
+    }
+    RecalculateCurrentPrintersList();
+  }
+
+ private:
+  // This method process value from the deprecated policy with recommended
+  // printers. It is called when value of the policy changes.
+  void UpdateUserRecommendedPrinters() {
+    recommended_printers_.clear();
+    std::vector<std::string> data =
+        FromPrefs(prefs::kRecommendedNativePrinters);
+    for (const auto& printer_json : data) {
+      std::unique_ptr<base::DictionaryValue> printer_dictionary =
+          base::DictionaryValue::From(base::JSONReader::ReadDeprecated(
+              printer_json, base::JSON_ALLOW_TRAILING_COMMAS));
+      if (!printer_dictionary) {
+        LOG(WARNING) << "Ignoring invalid printer.  Invalid JSON object: "
+                     << printer_json;
+        continue;
+      }
+
+      // Policy printers don't have id's but the ids only need to be locally
+      // unique so we'll hash the record.  This will not collide with the
+      // UUIDs generated for user entries.
+      std::string id = base::MD5String(printer_json);
+      printer_dictionary->SetString(kPrinterId, id);
+
+      auto new_printer = RecommendedPrinterToPrinter(*printer_dictionary);
+      if (!new_printer) {
+        LOG(WARNING) << "Recommended printer is malformed.";
+        continue;
+      }
+
+      if (!recommended_printers_.insert({id, *new_printer}).second) {
+        // Printer is already in the list.
+        LOG(WARNING) << "Duplicate printer ignored: " << id;
+        continue;
+      }
+    }
+    RecalculateCurrentPrintersList();
+  }
+
+  // These three methods calculate resultant list of printers and complete flag.
+
+  void RecalculateCompleteFlagForUserPrinters() {
+    user_printers_is_complete_ =
+        user_printers_->IsComplete() &&
+        (user_printers_->IsDataPolicySet() ||
+         !PolicyWithDataIsSet(policy::key::kNativePrintersBulkConfiguration));
+  }
+
+  void RecalculateCompleteFlagForDevicePrinters() {
+    device_printers_is_complete_ =
+        device_printers_->IsComplete() &&
+        (device_printers_->IsDataPolicySet() ||
+         !PolicyWithDataIsSet(policy::key::kDeviceNativePrinters));
+  }
+
+  void RecalculateCurrentPrintersList() {
+    complete_ = true;
+    printers_ = recommended_printers_;
+    if (device_printers_) {
+      complete_ = complete_ && device_printers_is_complete_;
+      const auto& printers = device_printers_->GetPrinters();
+      printers_.insert(printers.begin(), printers.end());
+    }
+    if (user_printers_) {
+      complete_ = complete_ && user_printers_is_complete_;
+      const auto& printers = user_printers_->GetPrinters();
+      printers_.insert(printers.begin(), printers.end());
+    }
+    for (auto& observer : observers_) {
+      observer.OnPrintersChanged(complete_, printers_);
+    }
+  }
+
+  typedef void (EnterprisePrintersProviderImpl::*SimpleMethod)();
+
+  // Binds given user policy to given method and calls this method once.
+  void BindPref(const char* policy_name, SimpleMethod method_to_call) {
+    pref_change_registrar_.Add(
+        policy_name,
+        base::BindRepeating(method_to_call, base::Unretained(this)));
+    (this->*method_to_call)();
+  }
+
+  // Extracts the list of strings named |policy_name| from user policies.
+  std::vector<std::string> FromPrefs(const std::string& policy_name) {
+    return ConvertToVector(profile_->GetPrefs()->GetList(policy_name));
+  }
+
+  // Checks if given policy is set and if it is a dictionary
+  bool PolicyWithDataIsSet(const char* policy_name) {
+    policy::ProfilePolicyConnector* policy_connector =
+        policy::ProfilePolicyConnectorFactory::GetForBrowserContext(profile_);
+    if (!policy_connector) {
+      // something is wrong
+      return false;
+    }
+    const policy::PolicyNamespace policy_namespace =
+        policy::PolicyNamespace(policy::PolicyDomain::POLICY_DOMAIN_CHROME, "");
+    const policy::PolicyMap& policy_map =
+        policy_connector->policy_service()->GetPolicies(policy_namespace);
+    const base::Value* value = policy_map.GetValue(policy_name);
+    if (value && value->is_dict()) {
+      // policy is set and its value is a dictionary
+      return true;
+    }
+    return false;
+  }
+
+  // current partial results
+  std::unordered_map<std::string, Printer> recommended_printers_;
+  bool device_printers_is_complete_ = true;
+  bool user_printers_is_complete_ = true;
+
+  // current final results
+  bool complete_ = false;
+  std::unordered_map<std::string, Printer> printers_;
+
+  // Calculators for bulk printers from device and user policies. Unowned.
+  base::WeakPtr<BulkPrintersCalculator> device_printers_;
+  base::WeakPtr<BulkPrintersCalculator> user_printers_;
+
+  // Policies binder (bridge between policies and calculators). Owned.
+  std::unique_ptr<CalculatorsPoliciesBinder> policies_binder_;
+
+  // Profile (user) settings.
+  Profile* profile_;
+  PrefChangeRegistrar pref_change_registrar_;
+
+  base::ObserverList<EnterprisePrintersProvider::Observer>::Unchecked
+      observers_;
+  SEQUENCE_CHECKER(sequence_checker_);
+  DISALLOW_COPY_AND_ASSIGN(EnterprisePrintersProviderImpl);
+};
+
+}  // namespace
+
+// static
+void EnterprisePrintersProvider::RegisterProfilePrefs(
+    user_prefs::PrefRegistrySyncable* registry) {
+  registry->RegisterListPref(prefs::kRecommendedNativePrinters);
+  CalculatorsPoliciesBinder::RegisterProfilePrefs(registry);
+}
+
+// static
+std::unique_ptr<EnterprisePrintersProvider> EnterprisePrintersProvider::Create(
+    CrosSettings* settings,
+    Profile* profile) {
+  return std::make_unique<EnterprisePrintersProviderImpl>(settings, profile);
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/printing/enterprise_printers_provider.h b/chrome/browser/chromeos/printing/enterprise_printers_provider.h
new file mode 100644
index 0000000..cdd4210f
--- /dev/null
+++ b/chrome/browser/chromeos/printing/enterprise_printers_provider.h
@@ -0,0 +1,61 @@
+// Copyright 2018 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_PRINTING_ENTERPRISE_PRINTERS_PROVIDER_H_
+#define CHROME_BROWSER_CHROMEOS_PRINTING_ENTERPRISE_PRINTERS_PROVIDER_H_
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include "base/macros.h"
+#include "chrome/browser/chromeos/settings/cros_settings.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chromeos/printing/printer_configuration.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+
+namespace chromeos {
+
+// Uses classes BulkPrintersCalculator and CalculatorsPoliciesBinder to track
+// device settings & user profile modifications and to calculates resultant
+// list of available enterprise printers. The final list of available enterprise
+// printers is propagated to Observers.
+// All methods must be called from the same sequence (UI) and all observers'
+// notifications will be called from this sequence.
+class EnterprisePrintersProvider {
+ public:
+  class Observer {
+   public:
+    // |complete| is true if all policies have been parsed and applied (even
+    // when parsing errors occurred), false means that a new list of available
+    // printers is being calculated. |printers| contains the current list of
+    // available printers: the map is indexed by printers ids. This
+    // notification is called when value of any of these two parameters changes.
+    virtual void OnPrintersChanged(
+        bool complete,
+        const std::unordered_map<std::string, Printer>& printers) = 0;
+  };
+
+  static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
+  // |settings| is the source of device policies. |profile| is a user profile.
+  static std::unique_ptr<EnterprisePrintersProvider> Create(
+      CrosSettings* settings,
+      Profile* profile);
+  virtual ~EnterprisePrintersProvider() = default;
+
+  // This method also calls directly OnPrintersChanged(...) from |observer|.
+  virtual void AddObserver(Observer* observer) = 0;
+  virtual void RemoveObserver(Observer* observer) = 0;
+
+ protected:
+  EnterprisePrintersProvider() = default;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(EnterprisePrintersProvider);
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_PRINTING_ENTERPRISE_PRINTERS_PROVIDER_H_
diff --git a/chrome/browser/chromeos/printing/external_printers.cc b/chrome/browser/chromeos/printing/external_printers.cc
deleted file mode 100644
index eee26f31..0000000
--- a/chrome/browser/chromeos/printing/external_printers.cc
+++ /dev/null
@@ -1,374 +0,0 @@
-// Copyright 2017 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/printing/external_printers.h"
-
-#include <set>
-
-#include "base/bind.h"
-#include "base/feature_list.h"
-#include "base/json/json_reader.h"
-#include "base/memory/weak_ptr.h"
-#include "base/observer_list.h"
-#include "base/sequence_checker.h"
-#include "base/sequenced_task_runner.h"
-#include "base/stl_util.h"
-#include "base/task/post_task.h"
-#include "base/task_runner_util.h"
-#include "base/threading/scoped_blocking_call.h"
-#include "base/values.h"
-#include "chrome/common/chrome_features.h"
-#include "chromeos/printing/printer_translator.h"
-
-namespace chromeos {
-
-namespace {
-
-constexpr int kMaxRecords = 20000;
-
-using PrinterCache = std::vector<std::unique_ptr<Printer>>;
-using PrinterView = std::map<const std::string, const Printer>;
-
-// Parses |data|, a JSON blob, into a vector of Printers.  If |data| cannot be
-// parsed, returns nullptr.  This is run off the UI thread as it could be very
-// slow.
-std::unique_ptr<PrinterCache> ParsePrinters(std::unique_ptr<std::string> data) {
-  int error_code = 0;
-  int error_line = 0;
-
-  // This could be really slow.
-  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
-                                                base::BlockingType::MAY_BLOCK);
-  std::unique_ptr<base::Value> json_blob =
-      base::JSONReader::ReadAndReturnErrorDeprecated(
-          *data, base::JSONParserOptions::JSON_PARSE_RFC, &error_code,
-          nullptr /* error_msg_out */, &error_line);
-  // It's not valid JSON.  Give up.
-  if (!json_blob || !json_blob->is_list()) {
-    LOG(WARNING) << "Failed to parse printers policy (" << error_code
-                 << ") on line " << error_line;
-    return nullptr;
-  }
-
-  const base::Value::ListStorage& printer_list = json_blob->GetList();
-  if (printer_list.size() > kMaxRecords) {
-    LOG(WARNING) << "Too many records in printers policy: "
-                 << printer_list.size();
-    return nullptr;
-  }
-
-  auto parsed_printers = std::make_unique<PrinterCache>();
-  parsed_printers->reserve(printer_list.size());
-  for (const base::Value& val : printer_list) {
-    // TODO(skau): Convert to the new Value APIs.
-    const base::DictionaryValue* printer_dict;
-    if (!val.GetAsDictionary(&printer_dict)) {
-      LOG(WARNING) << "Entry in printers policy skipped.  Not a dictionary.";
-      continue;
-    }
-
-    auto printer = RecommendedPrinterToPrinter(*printer_dict);
-    if (!printer) {
-      LOG(WARNING) << "Failed to parse printer configuration.  Skipped.";
-      continue;
-    }
-    parsed_printers->push_back(std::move(printer));
-  }
-
-  return parsed_printers;
-}
-
-// Computes the effective printer list using the access mode and
-// blacklist/whitelist.  Methods are required to be sequenced.  This object is
-// the owner of all the policy data.
-class Restrictions {
- public:
-  Restrictions() : printers_cache_(nullptr) {
-    DETACH_FROM_SEQUENCE(sequence_checker_);
-  }
-  ~Restrictions() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); }
-
-  // Sets the printer cache using the policy blob |data|.  If the policy can be
-  // computed, returns the computed list.  Otherwise, nullptr.
-  std::unique_ptr<PrinterView> SetData(std::unique_ptr<std::string> data) {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    base::ScopedBlockingCall scoped_blocking_call(
-        FROM_HERE, base::BlockingType::MAY_BLOCK);
-    printers_cache_ = ParsePrinters(std::move(data));
-    return ComputePrinters();
-  }
-
-  // Clear the printer cache.  Computed lists will be invalid until we receive
-  // new data.
-  void ClearData() {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    printers_cache_.reset();
-  }
-
-  // Sets the access mode to |mode|.  If the policy can be computed, returns the
-  // computed list.  Otherwise, nullptr.
-  std::unique_ptr<PrinterView> UpdateAccessMode(
-      ExternalPrinters::AccessMode mode) {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    mode_ = mode;
-    return ComputePrinters();
-  }
-
-  // Sets the blacklist to |blacklist|.  If the policy can be computed, returns
-  // the computed list. Otherwise, nullptr.
-  std::unique_ptr<PrinterView> UpdateBlacklist(
-      const std::vector<std::string>& blacklist) {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    has_blacklist_ = true;
-    blacklist_ = std::set<std::string>(blacklist.begin(), blacklist.end());
-    return ComputePrinters();
-  }
-
-  // Sets the whitelist to |whitelist|.  If the policy can be computed, returns
-  // the computed list.  Otherwise, nullptr.
-  std::unique_ptr<PrinterView> UpdateWhitelist(
-      const std::vector<std::string>& whitelist) {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    has_whitelist_ = true;
-    whitelist_ = std::set<std::string>(whitelist.begin(), whitelist.end());
-    return ComputePrinters();
-  }
-
- private:
-  // Returns true if we have enough data to compute the effective printer list.
-  bool IsReady() const {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    if (!printers_cache_) {
-      return false;
-    }
-
-    switch (mode_) {
-      case ExternalPrinters::AccessMode::ALL_ACCESS:
-        return true;
-      case ExternalPrinters::AccessMode::BLACKLIST_ONLY:
-        return has_blacklist_;
-      case ExternalPrinters::AccessMode::WHITELIST_ONLY:
-        return has_whitelist_;
-      case ExternalPrinters::AccessMode::UNSET:
-        return false;
-    }
-    NOTREACHED();
-    return false;
-  }
-
-  // Returns the effective printer list based on |mode_| from the entries in
-  // |printers_cache_|.
-  std::unique_ptr<PrinterView> ComputePrinters() {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
-    if (!IsReady()) {
-      return nullptr;
-    }
-
-    auto view = std::make_unique<PrinterView>();
-    switch (mode_) {
-      case ExternalPrinters::UNSET:
-        NOTREACHED();
-        break;
-      case ExternalPrinters::WHITELIST_ONLY:
-        for (const auto& printer : *printers_cache_) {
-          if (base::ContainsKey(whitelist_, printer->id())) {
-            view->insert({printer->id(), *printer});
-          }
-        }
-        break;
-      case ExternalPrinters::BLACKLIST_ONLY:
-        for (const auto& printer : *printers_cache_) {
-          if (!base::ContainsKey(blacklist_, printer->id())) {
-            view->insert({printer->id(), *printer});
-          }
-        }
-        break;
-      case ExternalPrinters::ALL_ACCESS:
-        for (const auto& printer : *printers_cache_) {
-          view->insert({printer->id(), *printer});
-        }
-        break;
-    }
-
-    return view;
-  }
-
-  // Cache of the parsed printer configuration file.
-  std::unique_ptr<PrinterCache> printers_cache_;
-
-  // The type of restriction which is enforced.
-  ExternalPrinters::AccessMode mode_ = ExternalPrinters::UNSET;
-  // The list of ids which should not appear in the final list.
-  bool has_blacklist_ = false;
-  std::set<std::string> blacklist_;
-  // The list of the only ids which should appear in the final list.
-  bool has_whitelist_ = false;
-  std::set<std::string> whitelist_;
-
-  SEQUENCE_CHECKER(sequence_checker_);
-  DISALLOW_COPY_AND_ASSIGN(Restrictions);
-};
-
-class ExternalPrintersImpl : public ExternalPrinters {
- public:
-  ExternalPrintersImpl()
-      : restrictions_(std::make_unique<Restrictions>()),
-        restrictions_runner_(base::CreateSequencedTaskRunnerWithTraits(
-            {base::TaskPriority::BEST_EFFORT, base::MayBlock(),
-             base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})),
-        weak_ptr_factory_(this) {}
-  ~ExternalPrintersImpl() override {
-    bool success =
-        restrictions_runner_->DeleteSoon(FROM_HERE, std::move(restrictions_));
-    if (!success) {
-      LOG(WARNING) << "Unable to schedule deletion of policy object.";
-    }
-  }
-
-  void AddObserver(Observer* observer) override {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    observers_.AddObserver(observer);
-  }
-
-  void RemoveObserver(Observer* observer) override {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    observers_.RemoveObserver(observer);
-  }
-
-  // Resets the printer state fields.
-  void ClearData() override {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    if (!base::FeatureList::IsEnabled(features::kBulkPrinters)) {
-      return;
-    }
-
-    // Update restrictions then clear our local cache on return so we don't get
-    // out of sequence.
-    restrictions_runner_->PostTaskAndReply(
-        FROM_HERE,
-        base::BindOnce(&Restrictions::ClearData,
-                       base::Unretained(restrictions_.get())),
-        base::BindOnce(&ExternalPrintersImpl::OnComputationComplete,
-                       weak_ptr_factory_.GetWeakPtr(), nullptr));
-  }
-
-  void SetData(std::unique_ptr<std::string> data) override {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    if (!base::FeatureList::IsEnabled(features::kBulkPrinters)) {
-      return;
-    }
-
-    if (!data) {
-      LOG(WARNING) << "Received null data";
-      return;
-    }
-
-    // Forward data to Restrictions for computation.
-    base::PostTaskAndReplyWithResult(
-        restrictions_runner_.get(), FROM_HERE,
-        base::BindOnce(&Restrictions::SetData,
-                       base::Unretained(restrictions_.get()), std::move(data)),
-        base::BindOnce(&ExternalPrintersImpl::OnComputationComplete,
-                       weak_ptr_factory_.GetWeakPtr()));
-  }
-
-  void SetAccessMode(AccessMode mode) override {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    base::PostTaskAndReplyWithResult(
-        restrictions_runner_.get(), FROM_HERE,
-        base::BindOnce(&Restrictions::UpdateAccessMode,
-                       base::Unretained(restrictions_.get()), mode),
-        base::BindOnce(&ExternalPrintersImpl::OnComputationComplete,
-                       weak_ptr_factory_.GetWeakPtr()));
-  }
-
-  void SetBlacklist(const std::vector<std::string>& blacklist) override {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    base::PostTaskAndReplyWithResult(
-        restrictions_runner_.get(), FROM_HERE,
-        base::BindOnce(&Restrictions::UpdateBlacklist,
-                       base::Unretained(restrictions_.get()), blacklist),
-        base::BindOnce(&ExternalPrintersImpl::OnComputationComplete,
-                       weak_ptr_factory_.GetWeakPtr()));
-  }
-
-  void SetWhitelist(const std::vector<std::string>& whitelist) override {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    base::PostTaskAndReplyWithResult(
-        restrictions_runner_.get(), FROM_HERE,
-        base::BindOnce(&Restrictions::UpdateWhitelist,
-                       base::Unretained(restrictions_.get()), whitelist),
-        base::BindOnce(&ExternalPrintersImpl::OnComputationComplete,
-                       weak_ptr_factory_.GetWeakPtr()));
-  }
-
-  bool IsPolicySet() const override {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    return received_data_;
-  }
-
-  const std::map<const std::string, const Printer>& GetPrinters()
-      const override {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    return printers_;
-  }
-
- private:
-  // Called on computation completion.  |view| is the computed printers which a
-  // user should be able to see.  If |view| is nullptr, it's taken to mean that
-  // the list is now invalid and will be cleared.
-  void OnComputationComplete(std::unique_ptr<PrinterView> view) {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    bool valid;
-    if (!view) {
-      // Printers are dropped if parsing failed.  We can no longer determine
-      // what the domain owner wanted.
-      printers_.clear();
-      valid = false;
-    } else {
-      printers_.swap(*view);
-      valid = true;
-    }
-
-    // Maybe notify that the computed list has changed.
-    // Do not notify for invalid->invalid transitions
-    if (!valid && !received_data_) {
-      return;
-    }
-
-    received_data_ = valid;
-    for (auto& observer : observers_) {
-      // We rely on the assumption that this is sequenced with the rest of our
-      // code to guarantee that printers_ remains valid.
-      observer.OnPrintersChanged(received_data_, printers_);
-    }
-  }
-
-  // Holds the blacklist and whitelist.  Computes the effective printer list.
-  std::unique_ptr<Restrictions> restrictions_;
-  // Off UI sequence for computing the printer view.
-  scoped_refptr<base::SequencedTaskRunner> restrictions_runner_;
-
-  // True if printers_ is based on a current policy.
-  bool received_data_ = false;
-  // The computed set of printers.
-  PrinterView printers_;
-
-  base::ObserverList<ExternalPrinters::Observer>::Unchecked observers_;
-
-  SEQUENCE_CHECKER(sequence_checker_);
-  base::WeakPtrFactory<ExternalPrintersImpl> weak_ptr_factory_;
-
-  DISALLOW_COPY_AND_ASSIGN(ExternalPrintersImpl);
-};
-
-}  // namespace
-
-// static
-std::unique_ptr<ExternalPrinters> ExternalPrinters::Create() {
-  return std::make_unique<ExternalPrintersImpl>();
-}
-
-}  // namespace chromeos
diff --git a/chrome/browser/chromeos/printing/external_printers.h b/chrome/browser/chromeos/printing/external_printers.h
deleted file mode 100644
index 81f0bb5..0000000
--- a/chrome/browser/chromeos/printing/external_printers.h
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright 2017 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_PRINTING_EXTERNAL_PRINTERS_H_
-#define CHROME_BROWSER_CHROMEOS_PRINTING_EXTERNAL_PRINTERS_H_
-
-#include <map>
-#include <memory>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "base/memory/weak_ptr.h"
-#include "chromeos/printing/printer_configuration.h"
-
-namespace chromeos {
-
-// Manages download and parsing of the external policy printer configuration and
-// enforces restrictions.
-class ExternalPrinters : public base::SupportsWeakPtr<ExternalPrinters> {
- public:
-  // Choose the policy for printer access.
-  enum AccessMode {
-    UNSET = -1,
-    // Printers in the blacklist are disallowed.  Others are allowed.
-    BLACKLIST_ONLY = 0,
-    // Only printers in the whitelist are allowed.
-    WHITELIST_ONLY = 1,
-    // All printers in the policy are allowed.
-    ALL_ACCESS = 2
-  };
-
-  // Observer is notified when the computed set of printers change.  It is
-  // assumed that the observer is on the same sequence as the object it is
-  // observing.
-  class Observer {
-   public:
-    // Called when the printers have changed and should be queried.  |valid| is
-    // true if |printers| is based on a valid policy.  |printers| are the
-    // printers that should be available to the user.
-    virtual void OnPrintersChanged(
-        bool valid,
-        const std::map<const std::string, const Printer>& printers) = 0;
-  };
-
-  // Creates a handler for the external printer policies.
-  static std::unique_ptr<ExternalPrinters> Create();
-
-  virtual ~ExternalPrinters() = default;
-
-  virtual void AddObserver(Observer* observer) = 0;
-  virtual void RemoveObserver(Observer* observer) = 0;
-
-  // Parses |data| which is the contents of the bulk printes file and extracts
-  // printer information.  The file format is assumed to be JSON.
-  virtual void SetData(std::unique_ptr<std::string> data) = 0;
-  // Removes all printer data and invalidates the configuration.
-  virtual void ClearData() = 0;
-
-  // Set the access mode which chooses the type of filtering that is performed.
-  virtual void SetAccessMode(AccessMode mode) = 0;
-  // Set the |blacklist| which excludes printers with the given id if access
-  // mode is BLACKLIST_ONLY.
-  virtual void SetBlacklist(const std::vector<std::string>& blacklist) = 0;
-  // Set the |whitelist| which is the complete list of printers that are show to
-  // the user.  This is in effect if access mode is WHITELIST_ONLY.
-  virtual void SetWhitelist(const std::vector<std::string>& whitelist) = 0;
-
-  // Returns true if the printer map has been computed from a valid policy.
-  // Returns false otherwise.  This is computed asynchronously.  Observe
-  // OnPrintersChanged() to be notified when it is updated.  This may never
-  // become true if a user does not have the appropriate printer policies.
-  virtual bool IsPolicySet() const = 0;
-
-  // Returns a refernce to a map of the computed set of printers.  The map is
-  // empty if either the policy was empty or we are yet to receive the full
-  // policy. Use IsPolicySet() to differentiate betweeen the two.
-  virtual const std::map<const std::string, const Printer>& GetPrinters()
-      const = 0;
-};
-
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_CHROMEOS_PRINTING_EXTERNAL_PRINTERS_H_
diff --git a/chrome/browser/chromeos/printing/external_printers_factory.cc b/chrome/browser/chromeos/printing/external_printers_factory.cc
deleted file mode 100644
index b4f311d..0000000
--- a/chrome/browser/chromeos/printing/external_printers_factory.cc
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2017 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/printing/external_printers_factory.h"
-
-#include <memory>
-
-#include "base/lazy_instance.h"
-#include "chrome/browser/chromeos/printing/external_printers.h"
-#include "chrome/browser/chromeos/profiles/profile_helper.h"
-#include "chrome/browser/profiles/profile.h"
-#include "components/user_manager/user.h"
-
-namespace chromeos {
-
-namespace {
-
-base::LazyInstance<ExternalPrintersFactory>::DestructorAtExit
-    g_printers_factory = LAZY_INSTANCE_INITIALIZER;
-
-}  // namespace
-
-// static
-ExternalPrintersFactory* ExternalPrintersFactory::Get() {
-  return g_printers_factory.Pointer();
-}
-
-base::WeakPtr<ExternalPrinters> ExternalPrintersFactory::GetForAccountId(
-    const AccountId& account_id) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  auto found = printers_by_user_.find(account_id);
-  if (found != printers_by_user_.end()) {
-    return found->second->AsWeakPtr();
-  }
-
-  printers_by_user_[account_id] = ExternalPrinters::Create();
-  return printers_by_user_[account_id]->AsWeakPtr();
-}
-
-base::WeakPtr<ExternalPrinters> ExternalPrintersFactory::GetForProfile(
-    Profile* profile) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  const user_manager::User* user =
-      ProfileHelper::Get()->GetUserByProfile(profile);
-  if (!user)
-    return nullptr;
-
-  return GetForAccountId(user->GetAccountId());
-}
-
-void ExternalPrintersFactory::RemoveForUserId(const AccountId& account_id) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  printers_by_user_.erase(account_id);
-}
-
-void ExternalPrintersFactory::Shutdown() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  printers_by_user_.clear();
-}
-
-ExternalPrintersFactory::ExternalPrintersFactory() = default;
-ExternalPrintersFactory::~ExternalPrintersFactory() = default;
-
-}  // namespace chromeos
diff --git a/chrome/browser/chromeos/printing/external_printers_factory.h b/chrome/browser/chromeos/printing/external_printers_factory.h
deleted file mode 100644
index c86d990..0000000
--- a/chrome/browser/chromeos/printing/external_printers_factory.h
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2017 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_PRINTING_EXTERNAL_PRINTERS_FACTORY_H_
-#define CHROME_BROWSER_CHROMEOS_PRINTING_EXTERNAL_PRINTERS_FACTORY_H_
-
-#include <map>
-#include <memory>
-
-#include "base/lazy_instance.h"
-#include "base/macros.h"
-#include "base/memory/weak_ptr.h"
-#include "base/sequence_checker.h"
-#include "chrome/browser/chromeos/printing/external_printers.h"
-#include "components/account_id/account_id.h"
-
-class Profile;
-
-namespace chromeos {
-
-// Dispenses ExternalPrinters objects based on account id.  Access to this
-// object should be sequenced.
-class ExternalPrintersFactory {
- public:
-  static ExternalPrintersFactory* Get();
-
-  // Returns a WeakPtr to the ExternalPrinters registered for |account_id|. If
-  // an ExternalPrinters does not exist, one will be created for |account_id|.
-  // The returned object remains valid until RemoveForUserId or Shutdown is
-  // called.
-  base::WeakPtr<ExternalPrinters> GetForAccountId(const AccountId& account_id);
-
-  // Returns a WeakPtr to the ExternalPrinters registered for |profile| which
-  // could be null if |profile| does not map to a valid AccountId. The returned
-  // object remains valid until RemoveForUserId or Shutdown is called.
-  base::WeakPtr<ExternalPrinters> GetForProfile(Profile* profile);
-
-  // Deletes the ExternalPrinters registered for |account_id|.
-  void RemoveForUserId(const AccountId& account_id);
-
-  // Tear down all ExternalPrinters.
-  void Shutdown();
-
- private:
-  friend struct base::LazyInstanceTraitsBase<ExternalPrintersFactory>;
-
-  ExternalPrintersFactory();
-  ~ExternalPrintersFactory();
-
-  std::map<AccountId, std::unique_ptr<ExternalPrinters>> printers_by_user_;
-  SEQUENCE_CHECKER(sequence_checker_);
-
-  DISALLOW_COPY_AND_ASSIGN(ExternalPrintersFactory);
-};
-
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_CHROMEOS_PRINTING_EXTERNAL_PRINTERS_FACTORY_H_
diff --git a/chrome/browser/chromeos/printing/external_printers_policies.h b/chrome/browser/chromeos/printing/external_printers_policies.h
deleted file mode 100644
index 2b895ab4..0000000
--- a/chrome/browser/chromeos/printing/external_printers_policies.h
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2018 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_PRINTING_EXTERNAL_PRINTERS_POLICIES_H_
-#define CHROME_BROWSER_CHROMEOS_PRINTING_EXTERNAL_PRINTERS_POLICIES_H_
-
-#include <string>
-
-// A collection of preference names representing the external printer fields.
-struct ExternalPrinterPolicies {
-  std::string access_mode;
-  std::string blacklist;
-  std::string whitelist;
-};
-
-#endif  // CHROME_BROWSER_CHROMEOS_PRINTING_EXTERNAL_PRINTERS_POLICIES_H_
diff --git a/chrome/browser/chromeos/printing/external_printers_pref_bridge.cc b/chrome/browser/chromeos/printing/external_printers_pref_bridge.cc
deleted file mode 100644
index 237ac8d..0000000
--- a/chrome/browser/chromeos/printing/external_printers_pref_bridge.cc
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright 2016 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/printing/external_printers_pref_bridge.h"
-
-#include <string>
-#include <vector>
-
-#include "base/bind.h"
-#include "base/values.h"
-#include "chrome/browser/chromeos/printing/external_printers.h"
-#include "chrome/browser/chromeos/printing/external_printers_factory.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/common/pref_names.h"
-#include "components/policy/policy_constants.h"
-#include "components/pref_registry/pref_registry_syncable.h"
-#include "components/prefs/pref_service.h"
-
-namespace chromeos {
-
-namespace {
-
-// Extracts the list of strings named |policy_name| from |prefs| and returns it.
-std::vector<std::string> FromPrefs(const PrefService* prefs,
-                                   const std::string& policy_name) {
-  std::vector<std::string> string_list;
-  const base::ListValue* list = prefs->GetList(policy_name);
-  for (const base::Value& value : *list) {
-    if (value.is_string()) {
-      string_list.push_back(value.GetString());
-    }
-  }
-
-  return string_list;
-}
-
-}  // namespace
-
-// static
-void ExternalPrintersPrefBridge::RegisterProfilePrefs(
-    user_prefs::PrefRegistrySyncable* registry,
-    const ExternalPrinterPolicies& policies) {
-  // Default value for access mode is AllAccess.
-  registry->RegisterIntegerPref(policies.access_mode,
-                                ExternalPrinters::ALL_ACCESS);
-  registry->RegisterListPref(policies.blacklist);
-  registry->RegisterListPref(policies.whitelist);
-}
-
-ExternalPrintersPrefBridge::ExternalPrintersPrefBridge(
-    const ExternalPrinterPolicies& policies,
-    Profile* profile)
-    : profile_(profile), policies_(policies) {
-  pref_change_registrar_.Init(profile_->GetPrefs());
-
-  pref_change_registrar_.Add(
-      policies_.access_mode,
-      base::BindRepeating(&ExternalPrintersPrefBridge::AccessModeUpdated,
-                          base::Unretained(this)));
-  pref_change_registrar_.Add(
-      policies_.blacklist,
-      base::BindRepeating(&ExternalPrintersPrefBridge::BlacklistUpdated,
-                          base::Unretained(this)));
-  pref_change_registrar_.Add(
-      policies_.whitelist,
-      base::BindRepeating(&ExternalPrintersPrefBridge::WhitelistUpdated,
-                          base::Unretained(this)));
-  Initialize();
-}
-
-void ExternalPrintersPrefBridge::Initialize() {
-  BlacklistUpdated();
-  WhitelistUpdated();
-  AccessModeUpdated();
-}
-
-void ExternalPrintersPrefBridge::AccessModeUpdated() {
-  const PrefService* prefs = profile_->GetPrefs();
-  ExternalPrinters::AccessMode mode = ExternalPrinters::UNSET;
-  int mode_val = prefs->GetInteger(policies_.access_mode);
-  if (mode_val >= ExternalPrinters::BLACKLIST_ONLY &&
-      mode_val <= ExternalPrinters::ALL_ACCESS) {
-    mode = static_cast<ExternalPrinters::AccessMode>(mode_val);
-  } else {
-    LOG(ERROR) << "Unrecognized access mode";
-    return;
-  }
-
-  base::WeakPtr<ExternalPrinters> printers =
-      ExternalPrintersFactory::Get()->GetForProfile(profile_);
-  if (printers)
-    printers->SetAccessMode(mode);
-}
-
-void ExternalPrintersPrefBridge::BlacklistUpdated() {
-  base::WeakPtr<ExternalPrinters> printers =
-      ExternalPrintersFactory::Get()->GetForProfile(profile_);
-  if (printers)
-    printers->SetBlacklist(
-        FromPrefs(profile_->GetPrefs(), policies_.blacklist));
-}
-
-void ExternalPrintersPrefBridge::WhitelistUpdated() {
-  base::WeakPtr<ExternalPrinters> printers =
-      ExternalPrintersFactory::Get()->GetForProfile(profile_);
-  if (printers)
-    printers->SetWhitelist(
-        FromPrefs(profile_->GetPrefs(), policies_.whitelist));
-}
-
-}  // namespace chromeos
diff --git a/chrome/browser/chromeos/printing/external_printers_pref_bridge.h b/chrome/browser/chromeos/printing/external_printers_pref_bridge.h
deleted file mode 100644
index b7041109..0000000
--- a/chrome/browser/chromeos/printing/external_printers_pref_bridge.h
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2017 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_PRINTING_EXTERNAL_PRINTERS_PREF_BRIDGE_H_
-#define CHROME_BROWSER_CHROMEOS_PRINTING_EXTERNAL_PRINTERS_PREF_BRIDGE_H_
-
-#include <string>
-
-#include "base/macros.h"
-#include "chrome/browser/chromeos/printing/external_printers_policies.h"
-#include "components/prefs/pref_change_registrar.h"
-
-class Profile;
-
-namespace user_prefs {
-class PrefRegistrySyncable;
-}
-
-namespace chromeos {
-
-class ExternalPrinters;
-
-// Observe preference changes and propogate changes to ExternalPrinters.
-class ExternalPrintersPrefBridge {
- public:
-  static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry,
-                                   const ExternalPrinterPolicies& policies);
-
-  ExternalPrintersPrefBridge(const ExternalPrinterPolicies& policies,
-                             Profile* profile);
-
- private:
-  // Retrieve initial values for preferences.
-  void Initialize();
-
-  // Handle update for the access mode policy.
-  void AccessModeUpdated();
-
-  // Handle updates for the blacklist policy.
-  void BlacklistUpdated();
-
-  // Handle updates for the whitelist policy.
-  void WhitelistUpdated();
-
-  Profile* profile_;
-  const ExternalPrinterPolicies policies_;
-  PrefChangeRegistrar pref_change_registrar_;
-
-  DISALLOW_COPY_AND_ASSIGN(ExternalPrintersPrefBridge);
-};
-
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_CHROMEOS_PRINTING_EXTERNAL_PRINTERS_PREF_BRIDGE_H_
diff --git a/chrome/browser/chromeos/printing/synced_printers_manager.cc b/chrome/browser/chromeos/printing/synced_printers_manager.cc
index ee03322..a3c5055 100644
--- a/chrome/browser/chromeos/printing/synced_printers_manager.cc
+++ b/chrome/browser/chromeos/printing/synced_printers_manager.cc
@@ -10,22 +10,14 @@
 #include <utility>
 #include <vector>
 
-#include "base/bind.h"
-#include "base/feature_list.h"
 #include "base/guid.h"
-#include "base/json/json_reader.h"
-#include "base/md5.h"
 #include "base/observer_list_threadsafe.h"
 #include "base/optional.h"
 #include "base/synchronization/lock.h"
 #include "base/values.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
-#include "chrome/browser/chromeos/printing/device_external_printers_factory.h"
-#include "chrome/browser/chromeos/printing/device_external_printers_settings_bridge.h"
-#include "chrome/browser/chromeos/printing/external_printers.h"
-#include "chrome/browser/chromeos/printing/external_printers_factory.h"
-#include "chrome/browser/chromeos/printing/external_printers_pref_bridge.h"
+#include "chrome/browser/chromeos/printing/enterprise_printers_provider.h"
 #include "chrome/browser/chromeos/printing/printer_configurer.h"
 #include "chrome/browser/chromeos/printing/printers_sync_bridge.h"
 #include "chrome/browser/chromeos/printing/specifics_translation.h"
@@ -43,36 +35,9 @@
 
 namespace {
 
-// Returns the collection policies for user printers.
-ExternalPrinterPolicies UserPolicyNames() {
-  ExternalPrinterPolicies user_policy_names;
-  user_policy_names.access_mode = prefs::kRecommendedNativePrintersAccessMode;
-  user_policy_names.blacklist = prefs::kRecommendedNativePrintersBlacklist;
-  user_policy_names.whitelist = prefs::kRecommendedNativePrintersWhitelist;
-  return user_policy_names;
-}
-
-// Returns the collection policies for device printers.
-ExternalPrinterPolicies DevicePolicyNames() {
-  ExternalPrinterPolicies device_policy_names;
-  device_policy_names.access_mode = kDeviceNativePrintersAccessMode;
-  device_policy_names.blacklist = kDeviceNativePrintersBlacklist;
-  device_policy_names.whitelist = kDeviceNativePrintersWhitelist;
-  return device_policy_names;
-}
-
-// Inserts |printer| into |new_printers| if the id does not already exist.
-// Returns true if the insert was successful, false if there was a conflict.
-bool InsertIfNotPresent(std::unordered_map<std::string, Printer>* new_printers,
-                        const Printer& printer) {
-  std::pair<std::unordered_map<std::string, Printer>::iterator, bool> ret =
-      new_printers->insert({printer.id(), printer});
-  return ret.second;
-}
-
 class SyncedPrintersManagerImpl : public SyncedPrintersManager,
                                   public PrintersSyncBridge::Observer,
-                                  public ExternalPrinters::Observer {
+                                  public EnterprisePrintersProvider::Observer {
  public:
   SyncedPrintersManagerImpl(Profile* profile,
                             std::unique_ptr<PrintersSyncBridge> sync_bridge)
@@ -81,42 +46,14 @@
         observers_(new base::ObserverListThreadSafe<
                    SyncedPrintersManager::Observer>()),
         weak_factory_(this) {
-    pref_change_registrar_.Init(profile->GetPrefs());
-    pref_change_registrar_.Add(
-        prefs::kRecommendedNativePrinters,
-        base::Bind(&SyncedPrintersManagerImpl::UpdateRecommendedPrinters,
-                   base::Unretained(this)));
-    if (base::FeatureList::IsEnabled(features::kBulkPrinters)) {
-      user_external_printers_observer_ =
-          std::make_unique<ExternalPrintersPrefBridge>(UserPolicyNames(),
-                                                       profile_);
-      user_external_printers_ =
-          ExternalPrintersFactory::Get()->GetForProfile(profile_);
-      if (user_external_printers_) {
-        user_external_printers_->AddObserver(this);
-      }
-    }
-
-    device_external_printers_observer_ =
-        std::make_unique<DeviceExternalPrintersSettingsBridge>(
-            DevicePolicyNames(), CrosSettings::Get());
-    device_external_printers_ =
-        DeviceExternalPrintersFactory::Get()->GetForDevice();
-    if (device_external_printers_) {
-      device_external_printers_->AddObserver(this);
-    }
-
-    UpdateRecommendedPrinters();
+    printers_provider_ =
+        EnterprisePrintersProvider::Create(CrosSettings::Get(), profile_);
+    printers_provider_->AddObserver(this);
     sync_bridge_->AddObserver(this);
   }
 
   ~SyncedPrintersManagerImpl() override {
-    if (user_external_printers_) {
-      user_external_printers_->RemoveObserver(this);
-    }
-    if (device_external_printers_) {
-      device_external_printers_->RemoveObserver(this);
-    }
+    printers_provider_->RemoveObserver(this);
     sync_bridge_->RemoveObserver(this);
   }
 
@@ -132,9 +69,11 @@
     return printers;
   }
 
-  std::vector<Printer> GetEnterprisePrinters() const override {
+  bool GetEnterprisePrinters(std::vector<Printer>* printers) const override {
     base::AutoLock l(lock_);
-    return GetEnterprisePrintersLocked();
+    if (printers != nullptr)
+      *printers = GetEnterprisePrintersLocked();
+    return enterprise_printers_are_ready_;
   }
 
   std::unique_ptr<Printer> GetPrinter(
@@ -186,18 +125,20 @@
   void OnPrintersUpdated() override {
     observers_->Notify(
         FROM_HERE,
-        &SyncedPrintersManager::Observer::OnConfiguredPrintersChanged,
-        GetConfiguredPrinters());
+        &SyncedPrintersManager::Observer::OnConfiguredPrintersChanged);
   }
 
-  // ExternalPrinters::Observer override
+  // EnterprisePrintersProvider::Observer override
   void OnPrintersChanged(
-      bool valid,
-      const std::map<const ::std::string, const Printer>& printers) override {
-    // User or device policy printers changed.  Update the lists.
-    // |valid| is safe to ignore here since we're recomputing and the cached
-    // printers are always cleared.
-    UpdateRecommendedPrinters();
+      bool complete,
+      const std::unordered_map<std::string, Printer>& printers) override {
+    // Enterprise printers policy changed.  Update the lists.
+    base::AutoLock l(lock_);
+    enterprise_printers_ = printers;
+    enterprise_printers_are_ready_ = complete;
+    observers_->Notify(
+        FROM_HERE,
+        &SyncedPrintersManager::Observer::OnEnterprisePrintersChanged);
   }
 
  private:
@@ -237,128 +178,12 @@
     sync_bridge_->UpdatePrinter(PrinterToSpecifics(printer));
   }
 
-  // Reads printers provided by NativePrinters policy.  Appends ids to |new_ids|
-  // in the order they were received. Appends printers to |new_printers| indexed
-  // by id.  Discards printers with duplicate ids.
-  void PolicyNativePrinters(
-      std::vector<std::string>* new_ids,
-      std::unordered_map<std::string, Printer>* new_printers) {
-    const PrefService* prefs = profile_->GetPrefs();
-    const base::ListValue* values =
-        prefs->GetList(prefs::kRecommendedNativePrinters);
-    for (const auto& value : *values) {
-      std::string printer_json;
-      if (!value.GetAsString(&printer_json)) {
-        NOTREACHED();
-        continue;
-      }
-
-      std::unique_ptr<base::DictionaryValue> printer_dictionary =
-          base::DictionaryValue::From(base::JSONReader::ReadDeprecated(
-              printer_json, base::JSON_ALLOW_TRAILING_COMMAS));
-
-      if (!printer_dictionary) {
-        LOG(WARNING) << "Ignoring invalid printer.  Invalid JSON object: "
-                     << printer_json;
-        continue;
-      }
-
-      // Policy printers don't have id's but the ids only need to be locally
-      // unique so we'll hash the record.  This will not collide with the
-      // UUIDs generated for user entries.
-      std::string id = base::MD5String(printer_json);
-      printer_dictionary->SetString(kPrinterId, id);
-
-      auto new_printer = RecommendedPrinterToPrinter(*printer_dictionary);
-      if (!new_printer) {
-        LOG(WARNING) << "Recommended printer is malformed.";
-        continue;
-      }
-
-      if (!InsertIfNotPresent(new_printers, *new_printer)) {
-        // Printer is already in the list.
-        LOG(WARNING) << "Duplicate printer ignored: " << id;
-        continue;
-      }
-
-      new_ids->push_back(id);
-    }
-  }
-
-  // Reads printers provided by NativePrintersBulkConfigurations and
-  // DeviceNativePrinters policies. Appends ids to |new_ids| in the order they
-  // were received. Appends printers to |new_printers| indexed by id. Discards
-  // printers with duplicate ids.
-  void ReadPolicyPrinters(
-      base::WeakPtr<ExternalPrinters> external_printers,
-      std::vector<std::string>* new_ids,
-      std::unordered_map<std::string, Printer>* new_printers) {
-    DCHECK(new_ids);
-    DCHECK(new_printers);
-
-    if (!external_printers || !external_printers->IsPolicySet())
-      return;
-
-    const std::map<const std::string, const Printer>& printers =
-        external_printers->GetPrinters();
-    for (const auto& entry : printers) {
-      Printer printer(entry.second);
-      printer.set_source(Printer::SRC_POLICY);
-
-      if (!InsertIfNotPresent(new_printers, printer)) {
-        // Printer is already in the list.
-        LOG(WARNING) << "Duplicate printer ignored: " << printer.id();
-        continue;
-      }
-
-      new_ids->push_back(printer.id());
-    }
-  }
-
-  // Reads printers provided by NativePrintersBulkConfigurations policy.
-  void BulkPolicyPrinters(
-      std::vector<std::string>* new_ids,
-      std::unordered_map<std::string, Printer>* new_printers) {
-    ReadPolicyPrinters(ExternalPrintersFactory::Get()->GetForProfile(profile_),
-                       new_ids, new_printers);
-  }
-
-  // Reads printers provided by DeviceNativePrinters policy.
-  void DevicePolicyPrinters(
-      std::vector<std::string>* new_ids,
-      std::unordered_map<std::string, Printer>* new_printers) {
-    ReadPolicyPrinters(DeviceExternalPrintersFactory::Get()->GetForDevice(),
-                       new_ids, new_printers);
-  }
-
-  void UpdateRecommendedPrinters() {
-    // Parse the policy JSON into new structures outside the lock.
-    std::vector<std::string> new_ids;
-    std::unordered_map<std::string, Printer> new_printers;
-
-    PolicyNativePrinters(&new_ids, &new_printers);
-    if (base::FeatureList::IsEnabled(features::kBulkPrinters)) {
-      BulkPolicyPrinters(&new_ids, &new_printers);
-    }
-    DevicePolicyPrinters(&new_ids, &new_printers);
-
-    // Objects not in the most recent update get deallocated after method
-    // exit.
-    base::AutoLock l(lock_);
-    enterprise_printer_ids_.swap(new_ids);
-    enterprise_printers_.swap(new_printers);
-    observers_->Notify(
-        FROM_HERE,
-        &SyncedPrintersManager::Observer::OnEnterprisePrintersChanged,
-        GetEnterprisePrintersLocked());
-  }
-
   std::vector<Printer> GetEnterprisePrintersLocked() const {
     lock_.AssertAcquired();
     std::vector<Printer> ret;
     ret.reserve(enterprise_printers_.size());
-    for (const std::string& id : enterprise_printer_ids_) {
-      ret.push_back(enterprise_printers_.find(id)->second);
+    for (auto kv : enterprise_printers_) {
+      ret.push_back(kv.second);
     }
     return ret;
   }
@@ -366,29 +191,17 @@
   mutable base::Lock lock_;
 
   Profile* profile_;
-  PrefChangeRegistrar pref_change_registrar_;
-
-  // Bulk user printers. Unowned.
-  base::WeakPtr<ExternalPrinters> user_external_printers_;
-
-  // Device printers. Unowned.
-  base::WeakPtr<ExternalPrinters> device_external_printers_;
 
   // The backend for profile printers.
   std::unique_ptr<PrintersSyncBridge> sync_bridge_;
 
-  // Connects external printers preferences with the tracking object.
-  std::unique_ptr<ExternalPrintersPrefBridge> user_external_printers_observer_;
+  // The Object that provides updates about enterprise printers.
+  std::unique_ptr<EnterprisePrintersProvider> printers_provider_;
 
-  // Connects external printers device settings with the tracking object.
-  std::unique_ptr<DeviceExternalPrintersSettingsBridge>
-      device_external_printers_observer_;
-
-  // Enterprise printers as of the last time we got a policy update.  The ids
-  // vector is used to preserve the received ordering.
-  std::vector<std::string> enterprise_printer_ids_;
-  // Map is from id to printer.
+  // Enterprise printers as of the last time we got a policy update.
   std::unordered_map<std::string, Printer> enterprise_printers_;
+  // This flag is set to true if all enterprise policies were loaded.
+  bool enterprise_printers_are_ready_ = false;
 
   // Map of printer ids to PrinterConfigurer setup fingerprints at the time
   // the printers was last installed with CUPS.
@@ -404,9 +217,7 @@
 // static
 void SyncedPrintersManager::RegisterProfilePrefs(
     user_prefs::PrefRegistrySyncable* registry) {
-  registry->RegisterListPref(prefs::kRecommendedNativePrinters);
-
-  ExternalPrintersPrefBridge::RegisterProfilePrefs(registry, UserPolicyNames());
+  EnterprisePrintersProvider::RegisterProfilePrefs(registry);
 }
 
 // static
diff --git a/chrome/browser/chromeos/printing/synced_printers_manager.h b/chrome/browser/chromeos/printing/synced_printers_manager.h
index 2ec83c2..35bf0497 100644
--- a/chrome/browser/chromeos/printing/synced_printers_manager.h
+++ b/chrome/browser/chromeos/printing/synced_printers_manager.h
@@ -36,10 +36,8 @@
  public:
   class Observer {
    public:
-    virtual void OnConfiguredPrintersChanged(
-        const std::vector<Printer>& printers) = 0;
-    virtual void OnEnterprisePrintersChanged(
-        const std::vector<Printer>& printers) = 0;
+    virtual void OnConfiguredPrintersChanged() = 0;
+    virtual void OnEnterprisePrintersChanged() {}
   };
 
   static std::unique_ptr<SyncedPrintersManager> Create(
@@ -53,8 +51,9 @@
   // Returns the printers that are saved in preferences.
   virtual std::vector<Printer> GetConfiguredPrinters() const = 0;
 
-  // Returns printers from enterprise policy.
-  virtual std::vector<Printer> GetEnterprisePrinters() const = 0;
+  // Replaces given vector with vector of printers from enterprise policy.
+  // Returns true if the enterprise policy was loaded and is valid.
+  virtual bool GetEnterprisePrinters(std::vector<Printer>* printers) const = 0;
 
   // Returns the printer with id |printer_id|, or nullptr if no such printer
   // exists.  Searches both Configured and Enterprise printers.
diff --git a/chrome/browser/chromeos/printing/synced_printers_manager_unittest.cc b/chrome/browser/chromeos/printing/synced_printers_manager_unittest.cc
index ff90a8c..aa86cde 100644
--- a/chrome/browser/chromeos/printing/synced_printers_manager_unittest.cc
+++ b/chrome/browser/chromeos/printing/synced_printers_manager_unittest.cc
@@ -62,18 +62,17 @@
 // Helper class to record observed events.
 class LoggingObserver : public SyncedPrintersManager::Observer {
  public:
-  explicit LoggingObserver(SyncedPrintersManager* source) : observer_(this) {
+  explicit LoggingObserver(SyncedPrintersManager* source)
+      : observer_(this), manager_(source) {
     observer_.Add(source);
   }
 
-  void OnConfiguredPrintersChanged(
-      const std::vector<Printer>& printers) override {
-    configured_printers_ = printers;
+  void OnConfiguredPrintersChanged() override {
+    configured_printers_ = manager_->GetConfiguredPrinters();
   }
 
-  void OnEnterprisePrintersChanged(
-      const std::vector<Printer>& printer) override {
-    enterprise_printers_ = printer;
+  void OnEnterprisePrintersChanged() override {
+    manager_->GetEnterprisePrinters(&enterprise_printers_);
   }
 
   const std::vector<Printer>& configured_printers() const {
@@ -88,6 +87,7 @@
   std::vector<Printer> enterprise_printers_;
   ScopedObserver<SyncedPrintersManager, SyncedPrintersManager::Observer>
       observer_;
+  SyncedPrintersManager* manager_;
 };
 
 class SyncedPrintersManagerTest : public testing::Test {
@@ -200,10 +200,12 @@
   // TestingPrefSyncableService assumes ownership of |value|.
   prefs->SetManagedPref(prefs::kRecommendedNativePrinters, std::move(value));
 
-  auto printers = manager_->GetEnterprisePrinters();
+  std::vector<Printer> printers;
+  manager_->GetEnterprisePrinters(&printers);
   ASSERT_EQ(2U, printers.size());
-  EXPECT_EQ("Color Laser", printers[0].display_name());
-  EXPECT_EQ("ipp://192.168.1.5", printers[1].uri());
+  // order not specified
+  // EXPECT_EQ("Color Laser", printers[0].display_name());
+  // EXPECT_EQ("ipp://192.168.1.5", printers[1].uri());
   EXPECT_EQ(Printer::Source::SRC_POLICY, printers[1].source());
 }
 
@@ -217,7 +219,8 @@
   // TestingPrefSyncableService assumes ownership of |value|.
   prefs->SetManagedPref(prefs::kRecommendedNativePrinters, std::move(value));
 
-  auto printers = manager_->GetEnterprisePrinters();
+  std::vector<Printer> printers;
+  manager_->GetEnterprisePrinters(&printers);
 
   const Printer& from_list = printers.front();
   std::unique_ptr<Printer> retrieved = manager_->GetPrinter(from_list.id());
@@ -250,7 +253,9 @@
   prefs->SetManagedPref(prefs::kRecommendedNativePrinters, std::move(value));
 
   // Figure out the id of the enterprise printer that was just installed.
-  std::string enterprise_id = manager_->GetEnterprisePrinters().at(0).id();
+  std::vector<Printer> printers;
+  manager_->GetEnterprisePrinters(&printers);
+  std::string enterprise_id = printers.at(0).id();
 
   Printer configured(kTestPrinterId);
 
diff --git a/chrome/browser/conflicts/module_database_win.cc b/chrome/browser/conflicts/module_database_win.cc
index 4485eac..0e545122 100644
--- a/chrome/browser/conflicts/module_database_win.cc
+++ b/chrome/browser/conflicts/module_database_win.cc
@@ -62,10 +62,8 @@
 // static
 constexpr base::TimeDelta ModuleDatabase::kIdleTimeout;
 
-ModuleDatabase::ModuleDatabase(
-    scoped_refptr<base::SequencedTaskRunner> task_runner)
-    : task_runner_(task_runner),
-      idle_timer_(
+ModuleDatabase::ModuleDatabase()
+    : idle_timer_(
           FROM_HERE,
           kIdleTimeout,
           base::Bind(&ModuleDatabase::OnDelayExpired, base::Unretained(this))),
@@ -85,6 +83,8 @@
 }
 
 ModuleDatabase::~ModuleDatabase() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   if (this == g_module_database_win_instance)
     g_module_database_win_instance = nullptr;
 }
@@ -121,7 +121,7 @@
 void ModuleDatabase::OnShellExtensionEnumerated(const base::FilePath& path,
                                                 uint32_t size_of_image,
                                                 uint32_t time_date_stamp) {
-  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   idle_timer_.Reset();
 
@@ -132,7 +132,7 @@
 }
 
 void ModuleDatabase::OnShellExtensionEnumerationFinished() {
-  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!shell_extensions_enumerated_);
 
   shell_extensions_enumerated_ = true;
@@ -144,7 +144,7 @@
 void ModuleDatabase::OnImeEnumerated(const base::FilePath& path,
                                      uint32_t size_of_image,
                                      uint32_t time_date_stamp) {
-  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   idle_timer_.Reset();
 
@@ -154,7 +154,7 @@
 }
 
 void ModuleDatabase::OnImeEnumerationFinished() {
-  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!ime_enumerated_);
 
   ime_enumerated_ = true;
@@ -167,18 +167,7 @@
                                   const base::FilePath& module_path,
                                   uint32_t module_size,
                                   uint32_t module_time_date_stamp) {
-  // Messages can arrive from any thread (UI thread for calls over IPC, and
-  // anywhere at all for calls from ModuleWatcher), so bounce if necessary.
-  // It is safe to use base::Unretained() because this class is a singleton that
-  // is never freed.
-  if (!task_runner_->RunsTasksInCurrentSequence()) {
-    task_runner_->PostTask(
-        FROM_HERE,
-        base::BindOnce(&ModuleDatabase::OnModuleLoad, base::Unretained(this),
-                       process_type, module_path, module_size,
-                       module_time_date_stamp));
-    return;
-  }
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   ModuleInfo* module_info = nullptr;
   bool new_module = FindOrCreateModuleInfo(
@@ -219,6 +208,8 @@
 void ModuleDatabase::OnModuleBlocked(const base::FilePath& module_path,
                                      uint32_t module_size,
                                      uint32_t module_time_date_stamp) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   ModuleInfo* module_info = nullptr;
   FindOrCreateModuleInfo(module_path, module_size, module_time_date_stamp,
                          &module_info);
@@ -229,6 +220,8 @@
 void ModuleDatabase::OnModuleAddedToBlacklist(const base::FilePath& module_path,
                                               uint32_t module_size,
                                               uint32_t module_time_date_stamp) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   auto iter = modules_.find(
       ModuleInfoKey(module_path, module_size, module_time_date_stamp));
 
@@ -239,6 +232,8 @@
 }
 
 void ModuleDatabase::AddObserver(ModuleDatabaseObserver* observer) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   observer_list_.AddObserver(observer);
 
   // If the registered modules enumeration is not finished yet, the |observer|
@@ -253,6 +248,8 @@
 }
 
 void ModuleDatabase::RemoveObserver(ModuleDatabaseObserver* observer) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   observer_list_.RemoveObserver(observer);
 }
 
@@ -351,7 +348,7 @@
 void ModuleDatabase::OnModuleInspected(
     const ModuleInfoKey& module_key,
     ModuleInspectionResult inspection_result) {
-  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   auto it = modules_.find(module_key);
   if (it == modules_.end())
@@ -389,6 +386,8 @@
 
 #if defined(GOOGLE_CHROME_BUILD)
 void ModuleDatabase::MaybeInitializeThirdPartyConflictsManager() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   // Temporarily disable this class on domain-joined machines because enterprise
   // clients depend on IAttachmentExecute::Save() to be invoked for downloaded
   // files, but that API call has a known issue (https://crbug.com/870998) with
@@ -419,6 +418,8 @@
 }
 
 void ModuleDatabase::OnThirdPartyBlockingPolicyChanged() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   if (!IsThirdPartyBlockingPolicyEnabled()) {
     DCHECK(third_party_conflicts_manager_);
     ThirdPartyConflictsManager::ShutdownAndDestroy(
diff --git a/chrome/browser/conflicts/module_database_win.h b/chrome/browser/conflicts/module_database_win.h
index 5fa1580..1a091cc5 100644
--- a/chrome/browser/conflicts/module_database_win.h
+++ b/chrome/browser/conflicts/module_database_win.h
@@ -8,9 +8,8 @@
 #include <map>
 #include <memory>
 
-#include "base/memory/ref_counted.h"
 #include "base/observer_list.h"
-#include "base/sequenced_task_runner.h"
+#include "base/sequence_checker.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "chrome/browser/conflicts/module_info_win.h"
@@ -51,11 +50,8 @@
   static constexpr base::TimeDelta kIdleTimeout =
       base::TimeDelta::FromSeconds(10);
 
-  // A ModuleDatabase is by default bound to a provided sequenced task runner.
-  // All calls must be made in the context of this task runner, unless
-  // otherwise noted. For calls from other contexts this task runner is used to
-  // bounce the call when appropriate.
-  explicit ModuleDatabase(scoped_refptr<base::SequencedTaskRunner> task_runner);
+  // Creates the ModuleDatabase. Lives on the sequence where it is created.
+  ModuleDatabase();
   ~ModuleDatabase() override;
 
   // Retrieves the singleton global instance of the ModuleDatabase.
@@ -77,8 +73,7 @@
   // the last 10 seconds.
   bool IsIdle();
 
-  // Indicates that a new registered shell extension was found. Must be called
-  // in the same sequence as |task_runner_|.
+  // Indicates that a new registered shell extension was found.
   void OnShellExtensionEnumerated(const base::FilePath& path,
                                   uint32_t size_of_image,
                                   uint32_t time_date_stamp);
@@ -86,8 +81,7 @@
   // Indicates that all shell extensions have been enumerated.
   void OnShellExtensionEnumerationFinished();
 
-  // Indicates that a new registered input method editor was found. Must be
-  // called in the same sequence as |task_runner_|.
+  // Indicates that a new registered input method editor was found.
   void OnImeEnumerated(const base::FilePath& path,
                        uint32_t size_of_image,
                        uint32_t time_date_stamp);
@@ -129,8 +123,6 @@
   // called once for all modules that are already loaded before returning to the
   // caller. In addition, if the ModuleDatabase is currently idle,
   // OnModuleDatabaseIdle() will also be invoked.
-  // Must be called in the same sequence as |task_runner_|, and all
-  // notifications will be sent on that same task runner.
   //
   // ModuleDatabaseEventSource:
   void AddObserver(ModuleDatabaseObserver* observer) override;
@@ -229,9 +221,6 @@
   void OnThirdPartyBlockingPolicyChanged();
 #endif
 
-  // The task runner to which this object is bound.
-  scoped_refptr<base::SequencedTaskRunner> task_runner_;
-
   // A map of all known modules.
   ModuleMap modules_;
 
@@ -266,6 +255,8 @@
   // Records metrics on third-party modules.
   ThirdPartyMetricsRecorder third_party_metrics_;
 
+  SEQUENCE_CHECKER(sequence_checker_);
+
   DISALLOW_COPY_AND_ASSIGN(ModuleDatabase);
 };
 
diff --git a/chrome/browser/conflicts/module_database_win_unittest.cc b/chrome/browser/conflicts/module_database_win_unittest.cc
index fa059991..3749a75 100644
--- a/chrome/browser/conflicts/module_database_win_unittest.cc
+++ b/chrome/browser/conflicts/module_database_win_unittest.cc
@@ -8,7 +8,6 @@
 
 #include "base/bind.h"
 #include "base/task/post_task.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 #include "base/time/time.h"
 #include "chrome/browser/conflicts/module_database_observer_win.h"
 #include "chrome/test/base/scoped_testing_local_state.h"
@@ -40,8 +39,7 @@
         test_browser_thread_bundle_(
             base::test::ScopedTaskEnvironment::MainThreadType::UI_MOCK_TIME),
         scoped_testing_local_state_(TestingBrowserProcess::GetGlobal()),
-        module_database_(std::make_unique<ModuleDatabase>(
-            base::SequencedTaskRunnerHandle::Get())) {}
+        module_database_(std::make_unique<ModuleDatabase>()) {}
 
   ~ModuleDatabaseTest() override {
     module_database_ = nullptr;
@@ -82,22 +80,6 @@
   DISALLOW_COPY_AND_ASSIGN(ModuleDatabaseTest);
 };
 
-TEST_F(ModuleDatabaseTest, TasksAreBounced) {
-  // Run a task on the current thread. This should not be bounced, so their
-  // results should be immediately available.
-  module_database()->OnModuleLoad(kProcessType1, dll1_, kSize1, kTime1);
-  EXPECT_EQ(1u, modules().size());
-
-  // Run similar tasks on another thread with another module. These should be
-  // bounced.
-  base::PostTask(FROM_HERE, base::Bind(&ModuleDatabase::OnModuleLoad,
-                                       base::Unretained(module_database()),
-                                       kProcessType2, dll2_, kSize1, kTime1));
-  EXPECT_EQ(1u, modules().size());
-  RunSchedulerUntilIdle();
-  EXPECT_EQ(2u, modules().size());
-}
-
 TEST_F(ModuleDatabaseTest, DatabaseIsConsistent) {
   EXPECT_EQ(0u, modules().size());
 
diff --git a/chrome/browser/extensions/bookmark_app_helper.cc b/chrome/browser/extensions/bookmark_app_helper.cc
index 6db1194..c8e2913 100644
--- a/chrome/browser/extensions/bookmark_app_helper.cc
+++ b/chrome/browser/extensions/bookmark_app_helper.cc
@@ -319,10 +319,9 @@
     return;
   }
 
-  for_installable_site_ =
-      data.error_code == NO_ERROR_DETECTED && !shortcut_app_requested_
-          ? web_app::ForInstallableSite::kYes
-          : web_app::ForInstallableSite::kNo;
+  for_installable_site_ = data.errors.empty() && !shortcut_app_requested_
+                              ? web_app::ForInstallableSite::kYes
+                              : web_app::ForInstallableSite::kNo;
 
   web_app::UpdateWebAppInfoFromManifest(*data.manifest, &web_app_info_,
                                         for_installable_site_);
diff --git a/chrome/browser/extensions/bookmark_app_helper_unittest.cc b/chrome/browser/extensions/bookmark_app_helper_unittest.cc
index bab629de..6f1f4fa 100644
--- a/chrome/browser/extensions/bookmark_app_helper_unittest.cc
+++ b/chrome/browser/extensions/bookmark_app_helper_unittest.cc
@@ -141,7 +141,10 @@
                                 ForInstallableSite for_installable_site) {
     bool installable = for_installable_site == ForInstallableSite::kYes;
     InstallableData data = {
-        installable ? NO_ERROR_DETECTED : MANIFEST_DISPLAY_NOT_SUPPORTED,
+        installable
+            ? std::vector<InstallableStatusCode>()
+            : std::vector<
+                  InstallableStatusCode>{MANIFEST_DISPLAY_NOT_SUPPORTED},
         GURL(manifest_url),
         &manifest,
         GURL(kAppIconURL1),
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 93d80c5..b8a0e21 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -989,7 +989,7 @@
   },
   {
     "name": "enable-credit-card-assist",
-    // "owners": [ "your-team" ],
+    "owners": [ "ftirelo", "gogerald" ],
     "expiry_milestone": 76
   },
   {
diff --git a/chrome/browser/installable/fake_installable_manager.cc b/chrome/browser/installable/fake_installable_manager.cc
index 1cdbd23..c3dc027 100644
--- a/chrome/browser/installable/fake_installable_manager.cc
+++ b/chrome/browser/installable/fake_installable_manager.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/installable/fake_installable_manager.h"
 
 #include <utility>
+#include <vector>
 
 #include "base/bind.h"
 #include "base/callback.h"
@@ -20,7 +21,12 @@
 void FakeInstallableManager::GetData(const InstallableParams& params,
                                      const InstallableCallback& callback) {
   base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindRepeating(callback, *data_));
+      FROM_HERE, base::BindOnce(&FakeInstallableManager::RunCallback,
+                                base::Unretained(this), callback));
+}
+
+void FakeInstallableManager::RunCallback(const InstallableCallback& callback) {
+  callback.Run(*data_);
 }
 
 // static
@@ -46,13 +52,17 @@
 
   const bool valid_manifest = true;
   const bool has_worker = true;
+  std::vector<InstallableStatusCode> errors;
 
   // Not used:
   const GURL icon_url;
   const std::unique_ptr<SkBitmap> icon;
 
+  if (installable_code != NO_ERROR_DETECTED)
+    errors.push_back(installable_code);
+
   auto installable_data = std::make_unique<InstallableData>(
-      installable_code, manifest_url, installable_manager->manifest_.get(),
+      std::move(errors), manifest_url, installable_manager->manifest_.get(),
       icon_url, icon.get(), false, icon_url, icon.get(), valid_manifest,
       has_worker);
 
diff --git a/chrome/browser/installable/fake_installable_manager.h b/chrome/browser/installable/fake_installable_manager.h
index 52ba6de0..eb06b0f 100644
--- a/chrome/browser/installable/fake_installable_manager.h
+++ b/chrome/browser/installable/fake_installable_manager.h
@@ -29,6 +29,8 @@
   void GetData(const InstallableParams& params,
                const InstallableCallback& callback) override;
 
+  void RunCallback(const InstallableCallback& callback);
+
   // Create the manager and attach it to |web_contents|.
   static FakeInstallableManager* CreateForWebContents(
       content::WebContents* web_contents);
diff --git a/chrome/browser/installable/installable_data.cc b/chrome/browser/installable/installable_data.cc
index f3fd80a..13d093b1 100644
--- a/chrome/browser/installable/installable_data.cc
+++ b/chrome/browser/installable/installable_data.cc
@@ -4,17 +4,19 @@
 
 #include "chrome/browser/installable/installable_data.h"
 
-InstallableData::InstallableData(InstallableStatusCode error_code,
-                                 GURL manifest_url,
+#include <utility>
+
+InstallableData::InstallableData(std::vector<InstallableStatusCode> errors,
+                                 const GURL& manifest_url,
                                  const blink::Manifest* manifest,
-                                 GURL primary_icon_url,
+                                 const GURL& primary_icon_url,
                                  const SkBitmap* primary_icon,
                                  bool has_maskable_primary_icon,
-                                 GURL badge_icon_url,
+                                 const GURL& badge_icon_url,
                                  const SkBitmap* badge_icon,
                                  bool valid_manifest,
                                  bool has_worker)
-    : error_code(error_code),
+    : errors(std::move(errors)),
       manifest_url(manifest_url),
       manifest(manifest),
       primary_icon_url(primary_icon_url),
@@ -25,6 +27,4 @@
       valid_manifest(valid_manifest),
       has_worker(has_worker) {}
 
-InstallableData::InstallableData(const InstallableData&) = default;
-
 InstallableData::~InstallableData() = default;
diff --git a/chrome/browser/installable/installable_data.h b/chrome/browser/installable/installable_data.h
index 6af8e87..e56fec6 100644
--- a/chrome/browser/installable/installable_data.h
+++ b/chrome/browser/installable/installable_data.h
@@ -5,31 +5,35 @@
 #ifndef CHROME_BROWSER_INSTALLABLE_INSTALLABLE_DATA_H_
 #define CHROME_BROWSER_INSTALLABLE_INSTALLABLE_DATA_H_
 
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
 #include "chrome/browser/installable/installable_logging.h"
 #include "third_party/blink/public/common/manifest/manifest.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "url/gurl.h"
 
 // This struct is passed to an InstallableCallback when the InstallableManager
-// has finished working. Each reference is owned by InstallableManager, and
+// has finished working. Each pointer is owned by InstallableManager, and
 // callers should copy any objects which they wish to use later. Non-requested
 // fields will be set to null, empty, or false.
 struct InstallableData {
-  InstallableData(InstallableStatusCode error_code,
-                  GURL manifest_url,
+  InstallableData(std::vector<InstallableStatusCode> errors,
+                  const GURL& manifest_url,
                   const blink::Manifest* manifest,
-                  GURL primary_icon_url,
+                  const GURL& primary_icon_url,
                   const SkBitmap* primary_icon,
                   bool has_maskable_primary_icon,
-                  GURL badge_icon_url,
+                  const GURL& badge_icon_url,
                   const SkBitmap* badge_icon,
                   bool valid_manifest,
                   bool has_worker);
-  InstallableData(const InstallableData&);
   ~InstallableData();
 
-  // NO_ERROR_DETECTED if there were no issues.
-  const InstallableStatusCode error_code = NO_ERROR_DETECTED;
+  // Empty if there were no issues. Otherwise contains all errors encountered
+  // while InstallableManager was working.
+  std::vector<InstallableStatusCode> errors;
 
   // Empty if the site has no <link rel="manifest"> tag.
   const GURL manifest_url;
@@ -42,9 +46,7 @@
 
   // nullptr if the most appropriate primary icon couldn't be determined or
   // downloaded. The underlying primary icon is owned by the InstallableManager;
-  // clients must copy the bitmap if they want to to use it. If
-  // valid_primary_icon was true and a primary icon could not be retrieved, the
-  // reason will be in error_code.
+  // clients must copy the bitmap if they want to to use it.
   const SkBitmap* primary_icon;
 
   // Whether the primary icon had the 'maskable' purpose, meaningless if no
@@ -67,9 +69,11 @@
   // error_code.
   const bool valid_manifest = false;
 
-  // true if the site has a service worker with a fetch handler. If has_worker
-  // was true and the site isn't installable, the reason will be in error_code.
+  // true if the site has a service worker with a fetch handler.
   const bool has_worker = false;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(InstallableData);
 };
 
 using InstallableCallback = base::Callback<void(const InstallableData&)>;
diff --git a/chrome/browser/installable/installable_logging.cc b/chrome/browser/installable/installable_logging.cc
index 65dab8f..07a3ff92 100644
--- a/chrome/browser/installable/installable_logging.cc
+++ b/chrome/browser/installable/installable_logging.cc
@@ -35,7 +35,7 @@
 static const char kManifestEmptyMessage[] =
     "the manifest could not be fetched, is empty, or could not be parsed";
 static const char kStartUrlNotValidMessage[] =
-    "the start URL in manifest is not valid";
+    "the manifest start URL is not valid";
 static const char kManifestMissingNameOrShortNameMessage[] =
     "one of manifest name or short name must be specified";
 static const char kManifestDisplayNotSupportedMessage[] =
@@ -63,10 +63,12 @@
     "a Play Store app URL and Play Store ID were specified in the manifest, "
     "but they do not match";
 static const char kUrlNotSupportedForWebApkMessage[] =
-    "a URL in the web manifest contains a username, password, or port";
+    "a URL in the manifest contains a username, password, or port";
 static const char kInIncognitoMessage[] =
     "the page is loaded in an incognito window";
 static const char kNotOfflineCapable[] = "the page does not work offline";
+static const char kNoUrlForServiceWorker[] =
+    "could not check service worker for null start URL";
 }  // namespace
 
 void LogErrorToConsole(content::WebContents* web_contents,
@@ -165,6 +167,9 @@
     case NOT_OFFLINE_CAPABLE:
       message = kNotOfflineCapable;
       break;
+    case NO_URL_FOR_SERVICE_WORKER:
+      message = kNoUrlForServiceWorker;
+      break;
   }
 
   web_contents->GetMainFrame()->AddMessageToConsole(
diff --git a/chrome/browser/installable/installable_logging.h b/chrome/browser/installable/installable_logging.h
index 9f1fbaa..c8902ad 100644
--- a/chrome/browser/installable/installable_logging.h
+++ b/chrome/browser/installable/installable_logging.h
@@ -52,6 +52,7 @@
   NO_GESTURE = 32,
   WAITING_FOR_NATIVE_DATA = 33,
   SHOWING_APP_INSTALLATION_DIALOG = 34,
+  NO_URL_FOR_SERVICE_WORKER = 35,
   MAX_ERROR_CODE,
 };
 
diff --git a/chrome/browser/installable/installable_manager.cc b/chrome/browser/installable/installable_manager.cc
index 8f95112..3a05c64 100644
--- a/chrome/browser/installable/installable_manager.cc
+++ b/chrome/browser/installable/installable_manager.cc
@@ -132,6 +132,14 @@
 
 }  // namespace
 
+InstallableManager::EligiblityProperty::EligiblityProperty() = default;
+
+InstallableManager::EligiblityProperty::~EligiblityProperty() = default;
+
+InstallableManager::ValidManifestProperty::ValidManifestProperty() = default;
+
+InstallableManager::ValidManifestProperty::~ValidManifestProperty() = default;
+
 InstallableManager::IconProperty::IconProperty()
     : error(NO_ERROR_DETECTED), url(), icon(), fetched(false) {}
 
@@ -262,24 +270,30 @@
   return IconPurpose::ANY;
 }
 
-InstallableStatusCode InstallableManager::GetErrorCode(
+std::vector<InstallableStatusCode> InstallableManager::GetErrors(
     const InstallableParams& params) {
-  if (params.check_eligibility && eligibility_->error != NO_ERROR_DETECTED)
-    return eligibility_->error;
+  std::vector<InstallableStatusCode> errors;
+
+  if (params.check_eligibility && !eligibility_->errors.empty()) {
+    errors.insert(errors.end(), eligibility_->errors.begin(),
+                  eligibility_->errors.end());
+  }
 
   if (manifest_->error != NO_ERROR_DETECTED)
-    return manifest_->error;
+    errors.push_back(manifest_->error);
 
-  if (params.valid_manifest && valid_manifest_->error != NO_ERROR_DETECTED)
-    return valid_manifest_->error;
+  if (params.valid_manifest && !valid_manifest_->errors.empty()) {
+    errors.insert(errors.end(), valid_manifest_->errors.begin(),
+                  valid_manifest_->errors.end());
+  }
 
   if (params.has_worker && worker_->error != NO_ERROR_DETECTED)
-    return worker_->error;
+    errors.push_back(worker_->error);
 
   if (params.valid_primary_icon) {
     IconProperty& icon = icons_[GetPrimaryIconPurpose(params)];
     if (icon.error != NO_ERROR_DETECTED)
-      return icon.error;
+      errors.push_back(icon.error);
   }
 
   if (params.valid_badge_icon) {
@@ -289,14 +303,15 @@
     // in the manifest. Ignore this case since we only want to fail the check if
     // there was a suitable badge icon specified and we couldn't fetch it.
     if (icon.error != NO_ERROR_DETECTED && icon.error != NO_ACCEPTABLE_ICON)
-      return icon.error;
+      errors.push_back(icon.error);
   }
 
-  return NO_ERROR_DETECTED;
+  return errors;
 }
 
 InstallableStatusCode InstallableManager::eligibility_error() const {
-  return eligibility_->error;
+  return eligibility_->errors.empty() ? NO_ERROR_DETECTED
+                                      : eligibility_->errors[0];
 }
 
 InstallableStatusCode InstallableManager::manifest_error() const {
@@ -304,12 +319,15 @@
 }
 
 InstallableStatusCode InstallableManager::valid_manifest_error() const {
-  return valid_manifest_->error;
+  return valid_manifest_->errors.empty() ? NO_ERROR_DETECTED
+                                         : valid_manifest_->errors[0];
 }
 
 void InstallableManager::set_valid_manifest_error(
     InstallableStatusCode error_code) {
-  valid_manifest_->error = error_code;
+  valid_manifest_->errors.clear();
+  if (error_code != NO_ERROR_DETECTED)
+    valid_manifest_->errors.push_back(error_code);
 }
 
 InstallableStatusCode InstallableManager::worker_error() const {
@@ -387,8 +405,9 @@
   SetIconFetched(IconPurpose::MASKABLE);
 }
 
-void InstallableManager::RunCallback(const InstallableTask& task,
-                                     InstallableStatusCode code) {
+void InstallableManager::RunCallback(
+    const InstallableTask& task,
+    std::vector<InstallableStatusCode> errors) {
   const InstallableParams& params = task.params;
   IconProperty null_icon;
   IconProperty* primary_icon = &null_icon;
@@ -404,15 +423,9 @@
     badge_icon = &icons_[IconPurpose::BADGE];
 
   InstallableData data = {
-      code,
-      manifest_url(),
-      &manifest(),
-      primary_icon->url,
-      primary_icon->icon.get(),
-      has_maskable_primary_icon,
-      badge_icon->url,
-      badge_icon->icon.get(),
-      valid_manifest_->is_valid,
+      std::move(errors),   manifest_url(),           &manifest(),
+      primary_icon->url,   primary_icon->icon.get(), has_maskable_primary_icon,
+      badge_icon->url,     badge_icon->icon.get(),   valid_manifest_->is_valid,
       worker_->has_worker,
   };
 
@@ -423,11 +436,11 @@
   const InstallableTask& task = task_queue_.Current();
   const InstallableParams& params = task.params;
 
-  InstallableStatusCode code = GetErrorCode(params);
-  bool check_passed = (code == NO_ERROR_DETECTED);
-  if (!check_passed || IsComplete(params)) {
+  auto errors = GetErrors(params);
+  bool check_passed = errors.empty();
+  if ((!check_passed && !params.is_debug_mode) || IsComplete(params)) {
     ResolveMetrics(params, check_passed);
-    RunCallback(task, code);
+    RunCallback(task, std::move(errors));
 
     // Sites can always register a service worker after we finish checking, so
     // don't cache a missing service worker error to ensure we always check
@@ -472,11 +485,13 @@
   content::WebContents* web_contents = GetWebContents();
   if (Profile::FromBrowserContext(web_contents->GetBrowserContext())
           ->IsOffTheRecord()) {
-    eligibility_->error = IN_INCOGNITO;
-  } else if (web_contents->GetMainFrame()->GetParent()) {
-    eligibility_->error = NOT_IN_MAIN_FRAME;
-  } else if (!IsContentSecure(web_contents)) {
-    eligibility_->error = NOT_FROM_SECURE_ORIGIN;
+    eligibility_->errors.push_back(IN_INCOGNITO);
+  }
+  if (web_contents->GetMainFrame()->GetParent()) {
+    eligibility_->errors.push_back(NOT_IN_MAIN_FRAME);
+  }
+  if (!IsContentSecure(web_contents)) {
+    eligibility_->errors.push_back(NOT_FROM_SECURE_ORIGIN);
   }
 
   eligibility_->fetched = true;
@@ -526,42 +541,50 @@
 bool InstallableManager::IsManifestValidForWebApp(
     const blink::Manifest& manifest,
     bool check_webapp_manifest_display) {
+  bool is_valid = true;
   if (manifest.IsEmpty()) {
-    valid_manifest_->error = MANIFEST_EMPTY;
+    valid_manifest_->errors.push_back(MANIFEST_EMPTY);
     return false;
   }
 
   if (!manifest.start_url.is_valid()) {
-    valid_manifest_->error = START_URL_NOT_VALID;
-    return false;
+    valid_manifest_->errors.push_back(START_URL_NOT_VALID);
+    is_valid = false;
   }
 
   if ((manifest.name.is_null() || manifest.name.string().empty()) &&
       (manifest.short_name.is_null() || manifest.short_name.string().empty())) {
-    valid_manifest_->error = MANIFEST_MISSING_NAME_OR_SHORT_NAME;
-    return false;
+    valid_manifest_->errors.push_back(MANIFEST_MISSING_NAME_OR_SHORT_NAME);
+    is_valid = false;
   }
 
   if (check_webapp_manifest_display &&
       manifest.display != blink::kWebDisplayModeStandalone &&
       manifest.display != blink::kWebDisplayModeFullscreen &&
       manifest.display != blink::kWebDisplayModeMinimalUi) {
-    valid_manifest_->error = MANIFEST_DISPLAY_NOT_SUPPORTED;
-    return false;
+    valid_manifest_->errors.push_back(MANIFEST_DISPLAY_NOT_SUPPORTED);
+    is_valid = false;
   }
 
   if (!DoesManifestContainRequiredIcon(manifest)) {
-    valid_manifest_->error = MANIFEST_MISSING_SUITABLE_ICON;
-    return false;
+    valid_manifest_->errors.push_back(MANIFEST_MISSING_SUITABLE_ICON);
+    is_valid = false;
   }
 
-  return true;
+  return is_valid;
 }
 
 void InstallableManager::CheckServiceWorker() {
   DCHECK(!worker_->fetched);
   DCHECK(!manifest().IsEmpty());
-  DCHECK(manifest().start_url.is_valid());
+
+  if (!manifest().start_url.is_valid()) {
+    worker_->has_worker = false;
+    worker_->error = NO_URL_FOR_SERVICE_WORKER;
+    worker_->fetched = true;
+    WorkOnTask();
+    return;
+  }
 
   // Check to see if there is a service worker for the manifest's start url.
   service_worker_context_->CheckHasServiceWorker(
diff --git a/chrome/browser/installable/installable_manager.h b/chrome/browser/installable/installable_manager.h
index 6054019..5995a75 100644
--- a/chrome/browser/installable/installable_manager.h
+++ b/chrome/browser/installable/installable_manager.h
@@ -93,7 +93,10 @@
   using IconPurpose = blink::Manifest::ImageResource::Purpose;
 
   struct EligiblityProperty {
-    InstallableStatusCode error = NO_ERROR_DETECTED;
+    EligiblityProperty();
+    ~EligiblityProperty();
+
+    std::vector<InstallableStatusCode> errors;
     bool fetched = false;
   };
 
@@ -105,7 +108,10 @@
   };
 
   struct ValidManifestProperty {
-    InstallableStatusCode error = NO_ERROR_DETECTED;
+    ValidManifestProperty();
+    ~ValidManifestProperty();
+
+    std::vector<InstallableStatusCode> errors;
     bool is_valid = false;
     bool fetched = false;
   };
@@ -144,9 +150,9 @@
   // Gets the purpose of the icon to use as a primary icon.
   IconPurpose GetPrimaryIconPurpose(const InstallableParams& params) const;
 
-  // Returns the error code associated with the resources requested in |params|,
-  // or NO_ERROR_DETECTED if there is no error.
-  InstallableStatusCode GetErrorCode(const InstallableParams& params);
+  // Returns a vector with all errors encountered for the resources requested in
+  // |params|, or an empty vector if there is no error.
+  std::vector<InstallableStatusCode> GetErrors(const InstallableParams& params);
 
   // Gets/sets parts of particular properties. Exposed for testing.
   InstallableStatusCode eligibility_error() const;
@@ -177,7 +183,8 @@
   void SetManifestDependentTasksComplete();
 
   // Methods coordinating and dispatching work for the current task.
-  void RunCallback(const InstallableTask& task, InstallableStatusCode error);
+  void RunCallback(const InstallableTask& task,
+                   std::vector<InstallableStatusCode> errors);
   void WorkOnTask();
 
   // Data retrieval methods.
diff --git a/chrome/browser/installable/installable_manager_browsertest.cc b/chrome/browser/installable/installable_manager_browsertest.cc
index f4ea781..80c42fd5 100644
--- a/chrome/browser/installable/installable_manager_browsertest.cc
+++ b/chrome/browser/installable/installable_manager_browsertest.cc
@@ -114,7 +114,7 @@
       : quit_closure_(quit_closure) {}
 
   void OnDidFinishInstallableCheck(const InstallableData& data) {
-    error_code_ = data.error_code;
+    errors_ = data.errors;
     manifest_url_ = data.manifest_url;
     manifest_ = *data.manifest;
     primary_icon_url_ = data.primary_icon_url;
@@ -129,7 +129,7 @@
     quit_closure_.Run();
   }
 
-  InstallableStatusCode error_code() const { return error_code_; }
+  const std::vector<InstallableStatusCode> errors() const { return errors_; }
   const GURL& manifest_url() const { return manifest_url_; }
   const blink::Manifest& manifest() const { return manifest_; }
   const GURL& primary_icon_url() const { return primary_icon_url_; }
@@ -142,7 +142,7 @@
 
  private:
   base::Closure quit_closure_;
-  InstallableStatusCode error_code_;
+  std::vector<InstallableStatusCode> errors_;
   GURL manifest_url_;
   blink::Manifest manifest_;
   GURL primary_icon_url_;
@@ -168,7 +168,7 @@
   }
 
   void OnDidFinishFirstCheck(const InstallableData& data) {
-    error_code_ = data.error_code;
+    errors_ = data.errors;
     manifest_url_ = data.manifest_url;
     manifest_ = *data.manifest;
     primary_icon_url_ = data.primary_icon_url;
@@ -183,7 +183,7 @@
   }
 
   void OnDidFinishSecondCheck(const InstallableData& data) {
-    EXPECT_EQ(error_code_, data.error_code);
+    EXPECT_EQ(errors_, data.errors);
     EXPECT_EQ(manifest_url_, data.manifest_url);
     EXPECT_EQ(primary_icon_url_, data.primary_icon_url);
     EXPECT_EQ(primary_icon_.get(), data.primary_icon);
@@ -202,7 +202,7 @@
   InstallableManager* manager_;
   InstallableParams params_;
   base::Closure quit_closure_;
-  InstallableStatusCode error_code_;
+  std::vector<InstallableStatusCode> errors_;
   GURL manifest_url_;
   blink::Manifest manifest_;
   GURL primary_icon_url_;
@@ -340,7 +340,7 @@
   EXPECT_FALSE(tester->has_worker());
   EXPECT_TRUE(tester->badge_icon_url().is_empty());
   EXPECT_EQ(nullptr, tester->badge_icon());
-  EXPECT_EQ(NO_MANIFEST, tester->error_code());
+  EXPECT_EQ(std::vector<InstallableStatusCode>{NO_MANIFEST}, tester->errors());
 
   histograms.ExpectUniqueSample(
       "Webapp.InstallabilityCheckStatus.MenuOpen",
@@ -383,7 +383,8 @@
   EXPECT_FALSE(tester->has_worker());
   EXPECT_TRUE(tester->badge_icon_url().is_empty());
   EXPECT_EQ(nullptr, tester->badge_icon());
-  EXPECT_EQ(MANIFEST_EMPTY, tester->error_code());
+  EXPECT_EQ(std::vector<InstallableStatusCode>{MANIFEST_EMPTY},
+            tester->errors());
 }
 
 IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckManifestOnly) {
@@ -406,7 +407,7 @@
   EXPECT_FALSE(tester->has_worker());
   EXPECT_TRUE(tester->badge_icon_url().is_empty());
   EXPECT_EQ(nullptr, tester->badge_icon());
-  EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
+  EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
 }
 
 IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
@@ -432,7 +433,7 @@
   EXPECT_FALSE(tester->has_worker());
   EXPECT_TRUE(tester->badge_icon_url().is_empty());
   EXPECT_EQ(nullptr, tester->badge_icon());
-  EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
+  EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
 }
 
 IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
@@ -459,7 +460,8 @@
     EXPECT_FALSE(tester->has_worker());
     EXPECT_TRUE(tester->badge_icon_url().is_empty());
     EXPECT_EQ(nullptr, tester->badge_icon());
-    EXPECT_EQ(NO_ACCEPTABLE_ICON, tester->error_code());
+    EXPECT_EQ(std::vector<InstallableStatusCode>{NO_ACCEPTABLE_ICON},
+              tester->errors());
   }
 
   // Ask for everything except badge icon. This should fail with
@@ -482,7 +484,8 @@
     EXPECT_FALSE(tester->has_worker());
     EXPECT_TRUE(tester->badge_icon_url().is_empty());
     EXPECT_EQ(nullptr, tester->badge_icon());
-    EXPECT_EQ(NO_ACCEPTABLE_ICON, tester->error_code());
+    EXPECT_EQ(std::vector<InstallableStatusCode>{NO_ACCEPTABLE_ICON},
+              tester->errors());
   }
 
   // Ask for a badge icon. This should fail to get a badge icon but not record
@@ -506,7 +509,7 @@
     EXPECT_EQ(nullptr, tester->badge_icon());
     EXPECT_FALSE(tester->valid_manifest());
     EXPECT_FALSE(tester->has_worker());
-    EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
+    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
   }
 }
 
@@ -535,7 +538,7 @@
     EXPECT_FALSE(tester->has_worker());
     EXPECT_TRUE(tester->badge_icon_url().is_empty());
     EXPECT_EQ(nullptr, tester->badge_icon());
-    EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
+    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
   }
 
   // Ask for a primary icon (but don't navigate). This should fail with
@@ -558,7 +561,8 @@
     EXPECT_FALSE(tester->has_worker());
     EXPECT_TRUE(tester->badge_icon_url().is_empty());
     EXPECT_EQ(nullptr, tester->badge_icon());
-    EXPECT_EQ(NO_ACCEPTABLE_ICON, tester->error_code());
+    EXPECT_EQ(std::vector<InstallableStatusCode>{NO_ACCEPTABLE_ICON},
+              tester->errors());
   }
 
   // Ask for everything except badge icon. This should fail with
@@ -582,10 +586,12 @@
     EXPECT_FALSE(tester->has_worker());
     EXPECT_TRUE(tester->badge_icon_url().is_empty());
     EXPECT_EQ(nullptr, tester->badge_icon());
-    EXPECT_EQ(NO_ACCEPTABLE_ICON, tester->error_code());
+    EXPECT_EQ(std::vector<InstallableStatusCode>{NO_ACCEPTABLE_ICON},
+              tester->errors());
   }
 
-  // Do not ask for primary icon. This should fail with START_URL_NOT_VALID.
+  // Do not ask for primary icon. This should fail with several validity
+  // errors.
   {
     base::RunLoop run_loop;
     std::unique_ptr<CallbackTester> tester(
@@ -606,7 +612,11 @@
     EXPECT_EQ(nullptr, tester->badge_icon());
     EXPECT_FALSE(tester->valid_manifest());
     EXPECT_FALSE(tester->has_worker());
-    EXPECT_EQ(START_URL_NOT_VALID, tester->error_code());
+    EXPECT_EQ(
+        std::vector<InstallableStatusCode>(
+            {START_URL_NOT_VALID, MANIFEST_MISSING_NAME_OR_SHORT_NAME,
+             MANIFEST_DISPLAY_NOT_SUPPORTED, MANIFEST_MISSING_SUITABLE_ICON}),
+        tester->errors());
   }
 }
 
@@ -631,7 +641,7 @@
     EXPECT_FALSE(tester->has_worker());
     EXPECT_TRUE(tester->badge_icon_url().is_empty());
     EXPECT_EQ(nullptr, tester->badge_icon());
-    EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
+    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
   }
 
   // Add to homescreen checks for manifest + primary icon + badge icon.
@@ -653,7 +663,7 @@
     EXPECT_FALSE(tester->has_worker());
     EXPECT_FALSE(tester->badge_icon_url().is_empty());
     EXPECT_NE(nullptr, tester->badge_icon());
-    EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
+    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
   }
 
   // Navigate to a page with a bad badge icon. This should now fail with
@@ -678,7 +688,8 @@
     EXPECT_EQ(nullptr, tester->badge_icon());
     EXPECT_FALSE(tester->valid_manifest());
     EXPECT_FALSE(tester->has_worker());
-    EXPECT_EQ(NO_ICON_AVAILABLE, tester->error_code());
+    EXPECT_EQ(std::vector<InstallableStatusCode>{NO_ICON_AVAILABLE},
+              tester->errors());
   }
 }
 
@@ -706,7 +717,7 @@
     EXPECT_TRUE(tester->has_worker());
     EXPECT_TRUE(tester->badge_icon_url().is_empty());
     EXPECT_EQ(nullptr, tester->badge_icon());
-    EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
+    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
 
     // Verify that the returned state matches manager internal state.
     InstallableManager* manager = GetManager(browser());
@@ -761,7 +772,7 @@
     EXPECT_TRUE(tester->has_worker());
     EXPECT_TRUE(tester->badge_icon_url().is_empty());
     EXPECT_EQ(nullptr, tester->badge_icon());
-    EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
+    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
 
     // Verify that the returned state matches manager internal state.
     InstallableManager* manager = GetManager(browser());
@@ -825,7 +836,7 @@
     EXPECT_FALSE(tester->has_worker());
     EXPECT_TRUE(tester->badge_icon_url().is_empty());
     EXPECT_EQ(nullptr, tester->badge_icon());
-    EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
+    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
   }
 
   // Checks that we don't pick a MASKABLE icon if it was not requested.
@@ -852,7 +863,7 @@
     EXPECT_FALSE(tester->has_worker());
     EXPECT_TRUE(tester->badge_icon_url().is_empty());
     EXPECT_EQ(nullptr, tester->badge_icon());
-    EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
+    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
   }
 
   // Checks that we fall back to using an ANY icon if a MASKABLE icon is
@@ -879,7 +890,7 @@
     EXPECT_FALSE(tester->has_worker());
     EXPECT_TRUE(tester->badge_icon_url().is_empty());
     EXPECT_EQ(nullptr, tester->badge_icon());
-    EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
+    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
   }
 }
 
@@ -1003,7 +1014,7 @@
   EXPECT_FALSE(tester->has_worker());
   EXPECT_TRUE(tester->badge_icon_url().is_empty());
   EXPECT_EQ(nullptr, tester->badge_icon());
-  EXPECT_EQ(NO_MANIFEST, tester->error_code());
+  EXPECT_EQ(std::vector<InstallableStatusCode>{NO_MANIFEST}, tester->errors());
 }
 
 IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
@@ -1028,7 +1039,7 @@
     EXPECT_FALSE(tester->has_worker());
     EXPECT_TRUE(tester->badge_icon_url().is_empty());
     EXPECT_EQ(nullptr, tester->badge_icon());
-    EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
+    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
   }
 
   // Fetching the full criteria should fail if we don't wait for the worker.
@@ -1051,7 +1062,8 @@
     EXPECT_FALSE(tester->has_worker());
     EXPECT_TRUE(tester->badge_icon_url().is_empty());
     EXPECT_EQ(nullptr, tester->badge_icon());
-    EXPECT_EQ(NO_MATCHING_SERVICE_WORKER, tester->error_code());
+    EXPECT_EQ(std::vector<InstallableStatusCode>{NO_MATCHING_SERVICE_WORKER},
+              tester->errors());
   }
 }
 
@@ -1114,7 +1126,7 @@
     EXPECT_FALSE(nested_tester->has_worker());
     EXPECT_TRUE(nested_tester->badge_icon_url().is_empty());
     EXPECT_EQ(nullptr, nested_tester->badge_icon());
-    EXPECT_EQ(NO_ERROR_DETECTED, nested_tester->error_code());
+    EXPECT_EQ(std::vector<InstallableStatusCode>{}, nested_tester->errors());
   }
 
   // Load the service worker.
@@ -1131,7 +1143,7 @@
   EXPECT_TRUE(tester->has_worker());
   EXPECT_TRUE(tester->badge_icon_url().is_empty());
   EXPECT_EQ(nullptr, tester->badge_icon());
-  EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
+  EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
 
   // Verify internal state.
   EXPECT_FALSE(manager->manifest().IsEmpty());
@@ -1190,7 +1202,8 @@
   EXPECT_FALSE(tester->has_worker());
   EXPECT_TRUE(tester->badge_icon_url().is_empty());
   EXPECT_EQ(nullptr, tester->badge_icon());
-  EXPECT_EQ(NOT_OFFLINE_CAPABLE, tester->error_code());
+  EXPECT_EQ(std::vector<InstallableStatusCode>{NOT_OFFLINE_CAPABLE},
+            tester->errors());
 }
 
 IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
@@ -1280,7 +1293,8 @@
     EXPECT_FALSE(tester->manifest().IsEmpty());
     EXPECT_TRUE(tester->valid_manifest());
     EXPECT_FALSE(tester->has_worker());
-    EXPECT_EQ(NO_MATCHING_SERVICE_WORKER, tester->error_code());
+    EXPECT_EQ(std::vector<InstallableStatusCode>{NO_MATCHING_SERVICE_WORKER},
+              tester->errors());
   }
 
   {
@@ -1303,7 +1317,7 @@
     EXPECT_FALSE(tester->manifest().IsEmpty());
     EXPECT_TRUE(tester->valid_manifest());
     EXPECT_TRUE(tester->has_worker());
-    EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
+    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
   }
 }
 
@@ -1329,7 +1343,8 @@
   EXPECT_FALSE(tester->has_worker());
   EXPECT_TRUE(tester->badge_icon_url().is_empty());
   EXPECT_EQ(nullptr, tester->badge_icon());
-  EXPECT_EQ(NOT_OFFLINE_CAPABLE, tester->error_code());
+  EXPECT_EQ(std::vector<InstallableStatusCode>{NOT_OFFLINE_CAPABLE},
+            tester->errors());
 }
 
 IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
@@ -1353,7 +1368,7 @@
   EXPECT_TRUE(tester->has_worker());
   EXPECT_TRUE(tester->badge_icon_url().is_empty());
   EXPECT_EQ(nullptr, tester->badge_icon());
-  EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
+  EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
 }
 
 IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, CheckDataUrlIcon) {
@@ -1377,7 +1392,7 @@
   EXPECT_TRUE(tester->has_worker());
   EXPECT_TRUE(tester->badge_icon_url().is_empty());
   EXPECT_EQ(nullptr, tester->badge_icon());
-  EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
+  EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
 }
 
 IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
@@ -1402,7 +1417,8 @@
   EXPECT_FALSE(tester->has_worker());
   EXPECT_TRUE(tester->badge_icon_url().is_empty());
   EXPECT_EQ(nullptr, tester->badge_icon());
-  EXPECT_EQ(NO_ICON_AVAILABLE, tester->error_code());
+  EXPECT_EQ(std::vector<InstallableStatusCode>{NO_ICON_AVAILABLE},
+            tester->errors());
 }
 
 IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
@@ -1426,7 +1442,7 @@
     EXPECT_TRUE(tester->has_worker());
     EXPECT_TRUE(tester->badge_icon_url().is_empty());
     EXPECT_EQ(nullptr, tester->badge_icon());
-    EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
+    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
   }
 
   {
@@ -1446,7 +1462,7 @@
     EXPECT_TRUE(tester->has_worker());
     EXPECT_TRUE(tester->badge_icon_url().is_empty());
     EXPECT_EQ(nullptr, tester->badge_icon());
-    EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
+    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
   }
 }
 
@@ -1486,7 +1502,8 @@
 
     EXPECT_TRUE(tester->manifest().IsEmpty());
     EXPECT_EQ(NO_MANIFEST, manager->manifest_error());
-    EXPECT_EQ(NO_MANIFEST, tester->error_code());
+    EXPECT_EQ(std::vector<InstallableStatusCode>{NO_MANIFEST},
+              tester->errors());
   }
 
   {
@@ -1512,7 +1529,7 @@
     run_loop.Run();
 
     EXPECT_FALSE(tester->manifest().IsEmpty());
-    EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
+    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
     EXPECT_EQ(base::ASCIIToUTF16("Manifest test app"),
               tester->manifest().name.string());
     EXPECT_EQ(base::string16(), tester->manifest().short_name.string());
@@ -1547,10 +1564,48 @@
     EXPECT_EQ(base::string16(), tester->manifest().name.string());
     EXPECT_EQ(base::ASCIIToUTF16("Manifest"),
               tester->manifest().short_name.string());
-    EXPECT_EQ(NO_ERROR_DETECTED, tester->error_code());
+    EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
   }
 }
 
+IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest, DebugModeWithNoManifest) {
+  // Ensure that a page with no manifest stops with NO_MANIFEST in debug mode.
+  base::RunLoop run_loop;
+  std::unique_ptr<CallbackTester> tester(
+      new CallbackTester(run_loop.QuitClosure()));
+
+  InstallableParams params = GetWebAppParams();
+  params.is_debug_mode = true;
+  ui_test_utils::NavigateToURL(
+      browser(),
+      embedded_test_server()->GetURL("/banners/no_manifest_test_page.html"));
+  RunInstallableManager(browser(), tester.get(), params);
+  run_loop.Run();
+
+  EXPECT_EQ(std::vector<InstallableStatusCode>({NO_MANIFEST}),
+            tester->errors());
+}
+
+IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
+                       DebugModeAccumulatesErrorsWithManifest) {
+  base::RunLoop run_loop;
+  std::unique_ptr<CallbackTester> tester(
+      new CallbackTester(run_loop.QuitClosure()));
+
+  InstallableParams params = GetWebAppParams();
+  params.is_debug_mode = true;
+  NavigateAndRunInstallableManager(browser(), tester.get(), params,
+                                   GetURLOfPageWithServiceWorkerAndManifest(
+                                       "/banners/play_app_manifest.json"));
+  run_loop.Run();
+
+  EXPECT_EQ(std::vector<InstallableStatusCode>(
+                {START_URL_NOT_VALID, MANIFEST_MISSING_NAME_OR_SHORT_NAME,
+                 MANIFEST_DISPLAY_NOT_SUPPORTED, MANIFEST_MISSING_SUITABLE_ICON,
+                 NO_URL_FOR_SERVICE_WORKER, NO_ACCEPTABLE_ICON}),
+            tester->errors());
+}
+
 IN_PROC_BROWSER_TEST_F(InstallableManagerWhitelistOriginBrowserTest,
                        SecureOriginCheckRespectsUnsafeFlag) {
   // The whitelisted origin should be regarded as secure.
diff --git a/chrome/browser/installable/installable_params.h b/chrome/browser/installable/installable_params.h
index 9cd9740..58b4c357 100644
--- a/chrome/browser/installable/installable_params.h
+++ b/chrome/browser/installable/installable_params.h
@@ -7,8 +7,9 @@
 
 // This struct specifies the work to be done by the InstallableManager.
 // Data is cached and fetched in the order specified in this struct.
-// If the eligibility check fails, processing halts immediately. Otherwise, a
-// web app manifest is fetched before the remaining items.
+// Processing halts immediately upon the first error unless |is_debug_mode| is
+// true, otherwise, all tasks will be run and a complete list of errors will be
+// returned.
 struct InstallableParams {
   // Check whether the current WebContents is eligible to be installed, i.e it:
   //  - is served over HTTPS
@@ -44,6 +45,10 @@
   // that the callback will not be called for any site that does not install a
   // service worker.
   bool wait_for_worker = false;
+
+  // True if the check should not short-circuit exit on errors, but continue
+  // and accumulate all possible errors.
+  bool is_debug_mode = false;
 };
 
 #endif  // CHROME_BROWSER_INSTALLABLE_INSTALLABLE_PARAMS_H_
diff --git a/chrome/browser/media/webrtc/webrtc_event_log_manager.cc b/chrome/browser/media/webrtc/webrtc_event_log_manager.cc
index 44804ec..32b28ad 100644
--- a/chrome/browser/media/webrtc/webrtc_event_log_manager.cc
+++ b/chrome/browser/media/webrtc/webrtc_event_log_manager.cc
@@ -393,12 +393,15 @@
 
   if (!browser_context) {
     // RPH died before processing of this notification.
+    UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kDeadRph);
     error = kStartRemoteLoggingFailureGeneric;
   } else if (!IsRemoteLoggingAllowedForBrowserContext(browser_context)) {
+    UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kFeatureDisabled);
     error = kStartRemoteLoggingFailureFeatureDisabled;
   } else if (browser_context->IsOffTheRecord()) {
     // Feature disable in incognito. Since the feature can be disabled for
     // non-incognito sessions, this should not expose incognito mode.
+    UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kIncognito);
     error = kStartRemoteLoggingFailureFeatureDisabled;
   }
 
diff --git a/chrome/browser/media/webrtc/webrtc_event_log_manager_common.cc b/chrome/browser/media/webrtc/webrtc_event_log_manager_common.cc
index 1d524564..a886f05 100644
--- a/chrome/browser/media/webrtc/webrtc_event_log_manager_common.cc
+++ b/chrome/browser/media/webrtc/webrtc_event_log_manager_common.cc
@@ -10,6 +10,7 @@
 #include "base/files/file_util.h"
 #include "base/logging.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_piece.h"
@@ -60,6 +61,14 @@
 const BrowserContextId kNullBrowserContextId =
     reinterpret_cast<BrowserContextId>(nullptr);
 
+void UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma result) {
+  base::UmaHistogramEnumeration("WebRtcEventLogging.Api", result);
+}
+
+void UmaRecordWebRtcEventLoggingUpload(WebRtcEventLoggingUploadUma result) {
+  base::UmaHistogramEnumeration("WebRtcEventLogging.Upload", result);
+}
+
 namespace {
 
 constexpr int kDefaultMemLevel = 8;
diff --git a/chrome/browser/media/webrtc/webrtc_event_log_manager_common.h b/chrome/browser/media/webrtc/webrtc_event_log_manager_common.h
index 4740325..b5392ad 100644
--- a/chrome/browser/media/webrtc/webrtc_event_log_manager_common.h
+++ b/chrome/browser/media/webrtc/webrtc_event_log_manager_common.h
@@ -108,6 +108,53 @@
 extern const char kStartRemoteLoggingFailureAlreadyLogging[];
 extern const char kStartRemoteLoggingFailureGeneric[];
 
+// Values for the histogram for the result of the API call to collect
+// a WebRTC event log.
+// Must match the numbering of WebRtcEventLoggingApiEnum in enums.xml.
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum class WebRtcEventLoggingApiUma {
+  kSuccess = 0,                         // Log successfully collected.
+  kDeadRph = 1,                         // Log not collected.
+  kFeatureDisabled = 2,                 // Log not collected.
+  kIncognito = 3,                       // Log not collected.
+  kInvalidArguments = 4,                // Log not collected.
+  kIllegalSessionId = 5,                // Log not collected.
+  kDisabledBrowserContext = 6,          // Log not collected.
+  kUnknownOrInvalidPeerConnection = 7,  // Log not collected.
+  kAlreadyLogging = 8,                  // Log not collected.
+  kNoAdditionalLogsAllowed = 9,         // Log not collected.
+  kLogPathNotAvailable = 10,            // Log not collected.
+  kHistoryPathNotAvailable = 11,        // Log not collected.
+  kFileCreationError = 12,              // Log not collected.
+  kMaxValue = kFileCreationError
+};
+
+void UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma result);
+
+// Values for the histogram for the result of the upload of a WebRTC event log.
+// Must match the numbering of WebRtcEventLoggingUploadEnum in enums.xml.
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum class WebRtcEventLoggingUploadUma {
+  kSuccess = 0,                            // Uploaded successfully.
+  kLogFileWriteError = 1,                  // Will not be uploaded.
+  kActiveLogCancelledDueToCacheClear = 2,  // Will not be uploaded.
+  kPendingLogDeletedDueToCacheClear = 3,   // Will not be uploaded.
+  kHistoryFileCreationError = 4,           // Will not be uploaded.
+  kHistoryFileWriteError = 5,              // Will not be uploaded.
+  kLogFileReadError = 6,                   // Will not be uploaded.
+  kLogFileNameError = 7,                   // Will not be uploaded.
+  kUploadCancelled = 8,                    // Upload started then cancelled.
+  kUploadFailure = 9,                      // Upload attempted and failed.
+  kIncompletePastUpload = 10,              // Upload attempted and failed.
+  kExpiredLogFileAtChromeStart = 11,       // Expired before upload opportunity.
+  kExpiredLogFileDuringSession = 12,       // Expired before upload opportunity.
+  kMaxValue = kExpiredLogFileDuringSession
+};
+
+void UmaRecordWebRtcEventLoggingUpload(WebRtcEventLoggingUploadUma result);
+
 // For a given Chrome session, this is a unique key for PeerConnections.
 // It's not, however, unique between sessions (after Chrome is restarted).
 struct WebRtcEventLogPeerConnectionKey {
diff --git a/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.cc b/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.cc
index d693595..2326052 100644
--- a/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.cc
+++ b/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.cc
@@ -91,16 +91,14 @@
 
 // Do not attempt to upload when there is no active connection.
 // Do not attempt to upload if the connection is known to be a mobile one.
-// Err on the side of caution with unknown connection types (by not uploading).
 // Note #1: A device may have multiple connections, so this is not bullet-proof.
 // Note #2: Does not attempt to recognize mobile hotspots.
 bool UploadSupportedUsingConnectionType(
     network::mojom::ConnectionType connection) {
-  if (connection == network::mojom::ConnectionType::CONNECTION_ETHERNET ||
-      connection == network::mojom::ConnectionType::CONNECTION_WIFI) {
-    return true;
-  }
-  return false;
+  return connection != network::mojom::ConnectionType::CONNECTION_NONE &&
+         connection != network::mojom::ConnectionType::CONNECTION_2G &&
+         connection != network::mojom::ConnectionType::CONNECTION_3G &&
+         connection != network::mojom::ConnectionType::CONNECTION_4G;
 }
 
 // Produce a history file for a given file.
@@ -441,22 +439,28 @@
     // |error_message| will have been set by AreLogParametersValid().
     DCHECK(!error_message->empty()) << "AreLogParametersValid() reported an "
                                        "error without an error message.";
+    UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kInvalidArguments);
     return false;
   }
 
   if (session_id.empty()) {
     *error_message = kStartRemoteLoggingFailureUnknownOrInactivePeerConnection;
+    UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kIllegalSessionId);
     return false;
   }
 
   if (!BrowserContextEnabled(browser_context_id)) {
     *error_message = kStartRemoteLoggingFailureGeneric;
+    UmaRecordWebRtcEventLoggingApi(
+        WebRtcEventLoggingApiUma::kDisabledBrowserContext);
     return false;
   }
 
   PeerConnectionKey key;
   if (!FindPeerConnection(render_process_id, session_id, &key)) {
     *error_message = kStartRemoteLoggingFailureUnknownOrInactivePeerConnection;
+    UmaRecordWebRtcEventLoggingApi(
+        WebRtcEventLoggingApiUma::kUnknownOrInvalidPeerConnection);
     return false;
   }
 
@@ -465,6 +469,7 @@
   if (it != active_logs_.end()) {
     LOG(ERROR) << "Remote logging already underway for " << session_id << ".";
     *error_message = kStartRemoteLoggingFailureAlreadyLogging;
+    UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kAlreadyLogging);
     return false;
   }
 
@@ -477,6 +482,8 @@
     // as there being too many other peer connections on other tabs that might
     // also be logging.
     *error_message = kStartRemoteLoggingFailureGeneric;
+    UmaRecordWebRtcEventLoggingApi(
+        WebRtcEventLoggingApiUma::kNoAdditionalLogsAllowed);
     return false;
   }
 
@@ -518,7 +525,8 @@
   //    by ClearCacheForBrowserContext() could accidentally replace it.
   // 5. Explicitly consider uploading, now that things have changed.
   MaybeCancelActiveLogs(delete_begin, delete_end, browser_context_id);
-  MaybeRemovePendingLogs(delete_begin, delete_end, browser_context_id);
+  MaybeRemovePendingLogs(delete_begin, delete_end, browser_context_id,
+                         /*is_cache_clear=*/true);
   MaybeRemoveHistoryFiles(delete_begin, delete_end, browser_context_id);
   MaybeCancelUpload(delete_begin, delete_end, browser_context_id);
   ManageUploadSchedule();
@@ -724,7 +732,7 @@
 
   const PeerConnectionKey peer_connection = it->first;  // Copy, not reference.
 
-  bool valid_file = it->second->Close();  // !valid_file -> Close() deletes.
+  const bool valid_file = it->second->Close();
   if (valid_file) {
     if (make_pending) {
       // The current time is a good enough approximation of the file's last
@@ -742,6 +750,10 @@
         LOG(ERROR) << "Failed to delete " << log_file_path << ".";
       }
     }
+  } else {  // !valid_file
+    // Close() deleted the file.
+    UmaRecordWebRtcEventLoggingUpload(
+        WebRtcEventLoggingUploadUma::kLogFileWriteError);
   }
 
   it = active_logs_.erase(it);
@@ -849,11 +861,15 @@
   if (base::PathExists(history_path)) {
     // Log file has associated history file, indicating an upload was started
     // for it. We should delete the original log from disk.
+    UmaRecordWebRtcEventLoggingUpload(
+        WebRtcEventLoggingUploadUma::kIncompletePastUpload);
     return false;
   }
 
   const base::Time now = base::Time::Now();
   if (last_modified + kRemoteBoundWebRtcEventLogsMaxRetention < now) {
+    UmaRecordWebRtcEventLoggingUpload(
+        WebRtcEventLoggingUploadUma::kExpiredLogFileAtChromeStart);
     return false;
   }
 
@@ -991,6 +1007,8 @@
   if (base::PathExists(log_path)) {
     LOG(ERROR) << "Previously used ID selected.";
     *error_message_out = kStartRemoteLoggingFailureGeneric;
+    UmaRecordWebRtcEventLoggingApi(
+        WebRtcEventLoggingApiUma::kLogPathNotAvailable);
     return false;
   }
 
@@ -999,6 +1017,8 @@
   if (base::PathExists(history_file_path)) {
     LOG(ERROR) << "Previously used ID selected.";
     *error_message_out = kStartRemoteLoggingFailureGeneric;
+    UmaRecordWebRtcEventLoggingApi(
+        WebRtcEventLoggingApiUma::kHistoryPathNotAvailable);
     return false;
   }
 
@@ -1010,6 +1030,8 @@
     // TODO(crbug.com/775415): Add UMA for exact failure type.
     LOG(ERROR) << "Failed to initialize remote-bound WebRTC event log file.";
     *error_message_out = kStartRemoteLoggingFailureGeneric;
+    UmaRecordWebRtcEventLoggingApi(
+        WebRtcEventLoggingApiUma::kFileCreationError);
     return false;
   }
   const auto it = active_logs_.emplace(key, std::move(log_file));
@@ -1018,6 +1040,8 @@
   observer_->OnRemoteLogStarted(key, it.first->second->path(),
                                 output_period_ms);
 
+  UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kSuccess);
+
   *log_id_out = log_id;
   return true;
 }
@@ -1042,7 +1066,7 @@
   MaybeRemovePendingLogs(
       base::Time::Min(),
       base::Time::Now() - kRemoteBoundWebRtcEventLogsMaxRetention,
-      browser_context_id);
+      browser_context_id, /*is_cache_clear=*/false);
 }
 
 void WebRtcRemoteEventLogManager::RecurringlyPrunePendingLogs() {
@@ -1092,6 +1116,8 @@
     // Since the file is active, assume it's still being modified.
     if (MatchesFilter(it->first.browser_context_id, base::Time::Now(),
                       browser_context_id, delete_begin, delete_end)) {
+      UmaRecordWebRtcEventLoggingUpload(
+          WebRtcEventLoggingUploadUma::kActiveLogCancelledDueToCacheClear);
       it = CloseLogFile(it, /*make_pending=*/false);
     } else {
       ++it;
@@ -1102,18 +1128,26 @@
 void WebRtcRemoteEventLogManager::MaybeRemovePendingLogs(
     const base::Time& delete_begin,
     const base::Time& delete_end,
-    base::Optional<BrowserContextId> browser_context_id) {
+    base::Optional<BrowserContextId> browser_context_id,
+    bool is_cache_clear) {
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
 
   for (auto it = pending_logs_.begin(); it != pending_logs_.end();) {
     if (MatchesFilter(it->browser_context_id, it->last_modified,
                       browser_context_id, delete_begin, delete_end)) {
+      UmaRecordWebRtcEventLoggingUpload(
+          is_cache_clear
+              ? WebRtcEventLoggingUploadUma::kPendingLogDeletedDueToCacheClear
+              : WebRtcEventLoggingUploadUma::kExpiredLogFileDuringSession);
+
       if (!base::DeleteFile(it->path, /*recursive=*/false)) {
         LOG(ERROR) << "Failed to delete " << it->path << ".";
       }
 
       // Produce a history file (they have longer retention) to replace the log.
-      CreateHistoryFile(it->path, it->last_modified);
+      if (is_cache_clear) {  // Will be immediately deleted otherwise.
+        CreateHistoryFile(it->path, it->last_modified);
+      }
 
       it = pending_logs_.erase(it);
     } else {
diff --git a/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.h b/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.h
index f9194cb6..ff1f93a 100644
--- a/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.h
+++ b/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.h
@@ -308,7 +308,8 @@
   void MaybeRemovePendingLogs(
       const base::Time& delete_begin,
       const base::Time& delete_end,
-      base::Optional<BrowserContextId> browser_context_id = base::nullopt);
+      base::Optional<BrowserContextId> browser_context_id,
+      bool is_cache_clear);
 
   // Remove all history files associated with |browser_context_id| which were
   // either captured or uploaded between |delete_begin| and |delete_end|.
diff --git a/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest.cc b/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest.cc
index 306159804..48bf87e 100644
--- a/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest.cc
+++ b/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest.cc
@@ -4357,7 +4357,8 @@
         ::testing::Bool(),
         // The upload-supporting network type to be used.
         ::testing::Values(network::mojom::ConnectionType::CONNECTION_ETHERNET,
-                          network::mojom::ConnectionType::CONNECTION_WIFI),
+                          network::mojom::ConnectionType::CONNECTION_WIFI,
+                          network::mojom::ConnectionType::CONNECTION_UNKNOWN),
         // The upload-unsupporting network type to be used.
         ::testing::Values(network::mojom::ConnectionType::CONNECTION_NONE,
                           network::mojom::ConnectionType::CONNECTION_4G)));
diff --git a/chrome/browser/media/webrtc/webrtc_event_log_uploader.cc b/chrome/browser/media/webrtc/webrtc_event_log_uploader.cc
index 571da26..2a491a8 100644
--- a/chrome/browser/media/webrtc/webrtc_event_log_uploader.cc
+++ b/chrome/browser/media/webrtc/webrtc_event_log_uploader.cc
@@ -151,6 +151,8 @@
   if (!history_file_writer_) {
     // File either could not be created, or, if a different error occurred,
     // Create() will have tried to remove the file it has created.
+    UmaRecordWebRtcEventLoggingUpload(
+        WebRtcEventLoggingUploadUma::kHistoryFileCreationError);
     ReportResult(false);
     return;
   }
@@ -159,6 +161,8 @@
   if (!history_file_writer_->WriteCaptureTime(log_file.last_modified) ||
       !history_file_writer_->WriteUploadTime(now)) {
     LOG(ERROR) << "Writing to history file failed.";
+    UmaRecordWebRtcEventLoggingUpload(
+        WebRtcEventLoggingUploadUma::kHistoryFileWriteError);
     DeleteHistoryFile();  // Avoid partial, potentially-corrupt history files.
     ReportResult(false);
     return;
@@ -167,7 +171,7 @@
   std::string upload_data;
   if (!PrepareUploadData(&upload_data)) {
     // History file will reflect a failed upload attempt.
-    ReportResult(false);
+    ReportResult(false);  // UMA recorded by PrepareUploadData().
     return;
   }
 
@@ -203,8 +207,8 @@
 bool WebRtcEventLogUploaderImpl::Cancel() {
   DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
 
-  // The upload either already completed, or was never properly started (due
-  // to a file read failure, etc.).
+  // The upload could already have been completed, or maybe was never properly
+  // started (due to a file read failure, etc.).
   const bool upload_was_active = (url_loader_.get() != nullptr);
 
   // Note that in this case, it might still be that the last bytes hit the
@@ -215,6 +219,11 @@
   DeleteLogFile();
   DeleteHistoryFile();
 
+  if (upload_was_active) {
+    UmaRecordWebRtcEventLoggingUpload(
+        WebRtcEventLoggingUploadUma::kUploadCancelled);
+  }
+
   return upload_was_active;
 }
 
@@ -225,6 +234,8 @@
   if (!base::ReadFileToStringWithMaxSize(log_file_.path, &log_file_contents,
                                          max_log_file_size_bytes_)) {
     LOG(WARNING) << "Couldn't read event log file, or max file size exceeded.";
+    UmaRecordWebRtcEventLoggingUpload(
+        WebRtcEventLoggingUploadUma::kLogFileReadError);
     return false;
   }
 
@@ -234,6 +245,8 @@
   const std::string filename_str = log_file_.path.BaseName().MaybeAsASCII();
   if (filename_str.empty()) {
     LOG(WARNING) << "Log filename is not according to acceptable format.";
+    UmaRecordWebRtcEventLoggingUpload(
+        WebRtcEventLoggingUploadUma::kLogFileNameError);
     return false;
   }
 
@@ -314,6 +327,10 @@
     // the upload was initiated, but did not end successfully.
   }
 
+  UmaRecordWebRtcEventLoggingUpload(
+      upload_successful ? WebRtcEventLoggingUploadUma::kSuccess
+                        : WebRtcEventLoggingUploadUma::kUploadFailure);
+
   url_loader_.reset();  // Explicitly maintain determinant.
 
   ReportResult(upload_successful);
diff --git a/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.cc
index 5433289..8911c13 100644
--- a/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.cc
@@ -335,7 +335,7 @@
   if (it == amp_subframe_info_.end())
     return;
 
-  it->second.render_data = render_data.Clone();
+  it->second.render_data.layout_jank_score = render_data.layout_jank_score;
 }
 
 void AMPPageLoadMetricsObserver::OnComplete(
@@ -501,30 +501,28 @@
     }
   }
 
-  if (!subframe_info.render_data.is_null()) {
-    // Clamp the score to a max of 10, which is equivalent to a frame with 10
-    // full-frame janks.
-    float clamped_jank_score =
-        std::min(subframe_info.render_data->layout_jank_score, 10.0f);
+  // Clamp the score to a max of 10, which is equivalent to a frame with 10
+  // full-frame janks.
+  float clamped_jank_score =
+      std::min(subframe_info.render_data.layout_jank_score, 10.0f);
 
-    // For UKM, report (jank_score * 100) as an int in the range [0, 1000].
-    builder.SetSubFrame_LayoutStability_JankScore(
-        static_cast<int>(roundf(clamped_jank_score * 100.0f)));
+  // For UKM, report (jank_score * 100) as an int in the range [0, 1000].
+  builder.SetSubFrame_LayoutStability_JankScore(
+      static_cast<int>(roundf(clamped_jank_score * 100.0f)));
 
-    // For UMA, report (jank_score * 10) an an int in the range [0,100].
-    int32_t uma_value = static_cast<int>(roundf(clamped_jank_score * 10.0f));
-    if (current_main_frame_nav_info_->is_same_document_navigation) {
-      UMA_HISTOGRAM_COUNTS_100(
-          std::string(kHistogramPrefix)
-              .append(kHistogramAMPSubframeLayoutStabilityJankScore),
-          uma_value);
-    } else {
-      UMA_HISTOGRAM_COUNTS_100(
-          std::string(kHistogramPrefix)
-              .append(
-                  kHistogramAMPSubframeLayoutStabilityJankScoreFullNavigation),
-          uma_value);
-    }
+  // For UMA, report (jank_score * 10) an an int in the range [0,100].
+  int32_t uma_value = static_cast<int>(roundf(clamped_jank_score * 10.0f));
+  if (current_main_frame_nav_info_->is_same_document_navigation) {
+    UMA_HISTOGRAM_COUNTS_100(
+        std::string(kHistogramPrefix)
+            .append(kHistogramAMPSubframeLayoutStabilityJankScore),
+        uma_value);
+  } else {
+    UMA_HISTOGRAM_COUNTS_100(
+        std::string(kHistogramPrefix)
+            .append(
+                kHistogramAMPSubframeLayoutStabilityJankScoreFullNavigation),
+        uma_value);
   }
 
   builder.Record(ukm::UkmRecorder::Get());
diff --git a/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.h b/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.h
index 9a6c7d86..5743219 100644
--- a/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.h
+++ b/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.h
@@ -130,7 +130,7 @@
 
     // Performance metrics observed in the AMP iframe.
     page_load_metrics::mojom::PageLoadTimingPtr timing;
-    page_load_metrics::mojom::PageRenderDataPtr render_data;
+    page_load_metrics::PageRenderData render_data;
   };
 
   void ProcessMainFrameNavigation(content::NavigationHandle* navigation_handle,
diff --git a/chrome/browser/page_load_metrics/page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/page_load_metrics_observer.cc
index 3be19ff..b4f934d9 100644
--- a/chrome/browser/page_load_metrics/page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/page_load_metrics_observer.cc
@@ -22,7 +22,7 @@
     const base::Optional<base::TimeDelta>& page_end_time,
     const mojom::PageLoadMetadata& main_frame_metadata,
     const mojom::PageLoadMetadata& subframe_metadata,
-    const mojom::PageRenderData& main_frame_render_data,
+    const PageRenderData& main_frame_render_data,
     ukm::SourceId source_id)
     : navigation_start(navigation_start),
       first_background_time(first_background_time),
@@ -57,8 +57,8 @@
       page_load_metrics::END_NONE,
       page_load_metrics::UserInitiatedInfo::NotUserInitiated(),
       base::TimeDelta(), page_load_metrics::mojom::PageLoadMetadata(),
-      page_load_metrics::mojom::PageLoadMetadata(),
-      page_load_metrics::mojom::PageRenderData(), 0 /* source_id */);
+      page_load_metrics::mojom::PageLoadMetadata(), PageRenderData(),
+      0 /* source_id */);
 }
 
 ExtraRequestCompleteInfo::ExtraRequestCompleteInfo(
diff --git a/chrome/browser/page_load_metrics/page_load_metrics_observer.h b/chrome/browser/page_load_metrics/page_load_metrics_observer.h
index 10d43aa1..1a18245 100644
--- a/chrome/browser/page_load_metrics/page_load_metrics_observer.h
+++ b/chrome/browser/page_load_metrics/page_load_metrics_observer.h
@@ -126,6 +126,15 @@
         user_input_event(user_input_event) {}
 };
 
+// Information about how the page rendered during the browsing session.
+// Derived from the mojom::PageRenderData that is sent via UpdateTiming IPC.
+struct PageRenderData {
+  PageRenderData() : layout_jank_score(0) {}
+
+  // How much visible elements on the page shifted (bit.ly/lsm-explainer).
+  float layout_jank_score;
+};
+
 struct PageLoadExtraInfo {
   PageLoadExtraInfo(
       base::TimeTicks navigation_start,
@@ -141,7 +150,7 @@
       const base::Optional<base::TimeDelta>& page_end_time,
       const mojom::PageLoadMetadata& main_frame_metadata,
       const mojom::PageLoadMetadata& subframe_metadata,
-      const mojom::PageRenderData& main_frame_render_data,
+      const PageRenderData& main_frame_render_data,
       ukm::SourceId source_id);
 
   // Simplified version of the constructor, intended for use in tests.
@@ -216,7 +225,7 @@
   // PageLoadMetadata for subframes of the current page load.
   const mojom::PageLoadMetadata subframe_metadata;
 
-  const mojom::PageRenderData main_frame_render_data;
+  const PageRenderData main_frame_render_data;
 
   // UKM SourceId for the current page load.
   const ukm::SourceId source_id;
diff --git a/chrome/browser/page_load_metrics/page_load_metrics_update_dispatcher.cc b/chrome/browser/page_load_metrics/page_load_metrics_update_dispatcher.cc
index b9cfc4f2..20a5257 100644
--- a/chrome/browser/page_load_metrics/page_load_metrics_update_dispatcher.cc
+++ b/chrome/browser/page_load_metrics/page_load_metrics_update_dispatcher.cc
@@ -411,8 +411,7 @@
       current_merged_page_timing_(CreatePageLoadTiming()),
       pending_merged_page_timing_(CreatePageLoadTiming()),
       main_frame_metadata_(mojom::PageLoadMetadata::New()),
-      subframe_metadata_(mojom::PageLoadMetadata::New()),
-      main_frame_render_data_(mojom::PageRenderData::New()) {}
+      subframe_metadata_(mojom::PageLoadMetadata::New()) {}
 
 PageLoadMetricsUpdateDispatcher::~PageLoadMetricsUpdateDispatcher() {
   ShutDown();
@@ -608,7 +607,7 @@
 
 void PageLoadMetricsUpdateDispatcher::UpdateMainFrameRenderData(
     mojom::PageRenderDataPtr render_data) {
-  main_frame_render_data_ = std::move(render_data);
+  main_frame_render_data_.layout_jank_score = render_data->layout_jank_score;
 }
 
 void PageLoadMetricsUpdateDispatcher::UpdateSubFrameRenderData(
diff --git a/chrome/browser/page_load_metrics/page_load_metrics_update_dispatcher.h b/chrome/browser/page_load_metrics/page_load_metrics_update_dispatcher.h
index 2f4ad5dbe..78eb838c 100644
--- a/chrome/browser/page_load_metrics/page_load_metrics_update_dispatcher.h
+++ b/chrome/browser/page_load_metrics/page_load_metrics_update_dispatcher.h
@@ -11,6 +11,7 @@
 #include "base/macros.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
+#include "chrome/browser/page_load_metrics/page_load_metrics_observer.h"
 #include "chrome/common/page_load_metrics/page_load_metrics.mojom.h"
 
 namespace content {
@@ -157,8 +158,8 @@
   const mojom::PageLoadMetadata& subframe_metadata() const {
     return *(subframe_metadata_.get());
   }
-  const mojom::PageRenderData& main_frame_render_data() const {
-    return *(main_frame_render_data_.get());
+  const PageRenderData& main_frame_render_data() const {
+    return main_frame_render_data_;
   }
 
  private:
@@ -202,7 +203,7 @@
   mojom::PageLoadMetadataPtr main_frame_metadata_;
   mojom::PageLoadMetadataPtr subframe_metadata_;
 
-  mojom::PageRenderDataPtr main_frame_render_data_;
+  PageRenderData main_frame_render_data_;
 
   // Navigation start offsets for the most recently committed document in each
   // frame.
diff --git a/chrome/browser/performance_manager/graph/frame_node_impl.cc b/chrome/browser/performance_manager/graph/frame_node_impl.cc
index 90c7eec..9822d54 100644
--- a/chrome/browser/performance_manager/graph/frame_node_impl.cc
+++ b/chrome/browser/performance_manager/graph/frame_node_impl.cc
@@ -26,54 +26,38 @@
 
 FrameNodeImpl::~FrameNodeImpl() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (parent_frame_node_)
-    parent_frame_node_->RemoveChildFrame(this);
-  if (page_node_)
-    page_node_->RemoveFrameImpl(this);
-  if (process_node_)
-    process_node_->RemoveFrame(this);
-  for (auto* child_frame : child_frame_nodes_)
-    child_frame->RemoveParentFrame(this);
 }
 
-void FrameNodeImpl::SetProcess(
-    const resource_coordinator::CoordinationUnitID& cu_id) {
+void FrameNodeImpl::SetProcess(ProcessNodeImpl* process_node) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  ProcessNodeImpl* process_node = ProcessNodeImpl::GetNodeByID(graph_, cu_id);
-  if (!process_node)
-    return;
+  DCHECK(NodeInGraph(process_node));
   DCHECK(!process_node_);
   process_node_ = process_node;
   process_node->AddFrame(this);
 }
 
-void FrameNodeImpl::AddChildFrame(
-    const resource_coordinator::CoordinationUnitID& cu_id) {
+void FrameNodeImpl::AddChildFrame(FrameNodeImpl* child_frame_node) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(cu_id != id());
-  FrameNodeImpl* frame_node = FrameNodeImpl::GetNodeByID(graph_, cu_id);
-  if (!frame_node)
-    return;
-  if (HasFrameNodeInAncestors(frame_node) ||
-      frame_node->HasFrameNodeInDescendants(this)) {
-    DCHECK(false) << "Cyclic reference in frame coordination units detected!";
-    return;
-  }
-  if (AddChildFrameImpl(frame_node)) {
-    frame_node->AddParentFrame(this);
-  }
+  DCHECK(child_frame_node);
+  DCHECK_NE(this, child_frame_node);
+  DCHECK(NodeInGraph(child_frame_node));
+  DCHECK(!HasFrameNodeInAncestors(child_frame_node) &&
+         !child_frame_node->HasFrameNodeInDescendants(this));
+
+  bool inserted = child_frame_nodes_.insert(child_frame_node).second;
+  DCHECK(inserted);
+  child_frame_node->AddParentFrame(this);
 }
 
-void FrameNodeImpl::RemoveChildFrame(
-    const resource_coordinator::CoordinationUnitID& cu_id) {
+void FrameNodeImpl::RemoveChildFrame(FrameNodeImpl* child_frame_node) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(cu_id != id());
-  FrameNodeImpl* frame_node = FrameNodeImpl::GetNodeByID(graph_, cu_id);
-  if (!frame_node)
-    return;
-  if (RemoveChildFrame(frame_node)) {
-    frame_node->RemoveParentFrame(this);
-  }
+  DCHECK(child_frame_node);
+  DCHECK_NE(this, child_frame_node);
+  DCHECK(NodeInGraph(child_frame_node));
+
+  size_t removed = child_frame_nodes_.erase(child_frame_node);
+  DCHECK_EQ(1u, removed);
+  child_frame_node->RemoveParentFrame(this);
 }
 
 void FrameNodeImpl::SetNetworkAlmostIdle(bool network_almost_idle) {
@@ -193,6 +177,20 @@
   }
 }
 
+void FrameNodeImpl::BeforeDestroyed() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  NodeBase::BeforeDestroyed();
+
+  if (parent_frame_node_)
+    parent_frame_node_->RemoveChildFrame(this);
+  if (page_node_)
+    page_node_->RemoveFrame(this);
+  if (process_node_)
+    process_node_->RemoveFrame(this);
+  for (auto* child_frame : child_frame_nodes_)
+    child_frame->RemoveParentFrame(this);
+}
+
 void FrameNodeImpl::OnEventReceived(resource_coordinator::mojom::Event event) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   for (auto& observer : observers())
@@ -229,27 +227,16 @@
 
 void FrameNodeImpl::AddParentFrame(FrameNodeImpl* parent_frame_node) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK_EQ(nullptr, parent_frame_node_);
   parent_frame_node_ = parent_frame_node;
 }
 
-bool FrameNodeImpl::AddChildFrameImpl(FrameNodeImpl* child_frame_node) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  return child_frame_nodes_.count(child_frame_node)
-             ? false
-             : child_frame_nodes_.insert(child_frame_node).second;
-}
-
 void FrameNodeImpl::RemoveParentFrame(FrameNodeImpl* parent_frame_node) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(parent_frame_node_ == parent_frame_node);
   parent_frame_node_ = nullptr;
 }
 
-bool FrameNodeImpl::RemoveChildFrame(FrameNodeImpl* child_frame_node) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  return child_frame_nodes_.erase(child_frame_node) > 0;
-}
-
 void FrameNodeImpl::AddPageNode(PageNodeImpl* page_node) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!page_node_);
diff --git a/chrome/browser/performance_manager/graph/frame_node_impl.h b/chrome/browser/performance_manager/graph/frame_node_impl.h
index 9fd8b90..48182c77 100644
--- a/chrome/browser/performance_manager/graph/frame_node_impl.h
+++ b/chrome/browser/performance_manager/graph/frame_node_impl.h
@@ -43,9 +43,9 @@
       resource_coordinator::mojom::InterventionPolicy policy) override;
   void OnNonPersistentNotificationCreated() override;
 
-  void SetProcess(const resource_coordinator::CoordinationUnitID& cu_id);
-  void AddChildFrame(const resource_coordinator::CoordinationUnitID& cu_id);
-  void RemoveChildFrame(const resource_coordinator::CoordinationUnitID& cu_id);
+  void SetProcess(ProcessNodeImpl* process_node);
+  void AddChildFrame(FrameNodeImpl* frame_node);
+  void RemoveChildFrame(FrameNodeImpl* frame_node);
 
   FrameNodeImpl* GetParentFrameNode() const;
   PageNodeImpl* GetPageNode() const;
@@ -74,6 +74,8 @@
   friend class PageNodeImpl;
   friend class ProcessNodeImpl;
 
+  void BeforeDestroyed() override;
+
   // CoordinationUnitInterface implementation.
   void OnEventReceived(resource_coordinator::mojom::Event event) override;
   void OnPropertyChanged(
@@ -89,7 +91,6 @@
   void AddParentFrame(FrameNodeImpl* parent_frame_node);
   bool AddChildFrameImpl(FrameNodeImpl* child_frame_node);
   void RemoveParentFrame(FrameNodeImpl* parent_frame_node);
-  bool RemoveChildFrame(FrameNodeImpl* child_frame_node);
   void AddPageNode(PageNodeImpl* page_node);
   void AddProcessNode(ProcessNodeImpl* process_node);
   void RemovePageNode(PageNodeImpl* page_node);
diff --git a/chrome/browser/performance_manager/graph/frame_node_impl_unittest.cc b/chrome/browser/performance_manager/graph/frame_node_impl_unittest.cc
index 9eecff3..6ebab27 100644
--- a/chrome/browser/performance_manager/graph/frame_node_impl_unittest.cc
+++ b/chrome/browser/performance_manager/graph/frame_node_impl_unittest.cc
@@ -34,8 +34,6 @@
   base::SimpleTestTickClock clock_;
 };
 
-using FrameNodeImplDeathTest = FrameNodeImplTest;
-
 }  // namespace
 
 TEST_F(FrameNodeImplTest, AddChildFrameBasic) {
@@ -43,41 +41,14 @@
   auto frame2_node = CreateNode<FrameNodeImpl>();
   auto frame3_node = CreateNode<FrameNodeImpl>();
 
-  frame1_node->AddChildFrame(frame2_node->id());
-  frame1_node->AddChildFrame(frame3_node->id());
+  frame1_node->AddChildFrame(frame2_node.get());
+  frame1_node->AddChildFrame(frame3_node.get());
   EXPECT_EQ(nullptr, frame1_node->GetParentFrameNode());
   EXPECT_EQ(2u, frame1_node->child_frame_nodes_for_testing().size());
   EXPECT_EQ(frame1_node.get(), frame2_node->GetParentFrameNode());
   EXPECT_EQ(frame1_node.get(), frame3_node->GetParentFrameNode());
 }
 
-TEST_F(FrameNodeImplDeathTest, AddChildFrameOnCyclicReference) {
-  ::testing::FLAGS_gtest_death_test_style = "threadsafe";
-
-  auto frame1_node = CreateNode<FrameNodeImpl>();
-  auto frame2_node = CreateNode<FrameNodeImpl>();
-  auto frame3_node = CreateNode<FrameNodeImpl>();
-
-  frame1_node->AddChildFrame(frame2_node->id());
-  frame2_node->AddChildFrame(frame3_node->id());
-// |frame3_node| can't add |frame1_node| because |frame1_node| is an ancestor of
-// |frame3_node|, and this will hit a DCHECK because of cyclic reference.
-#if !defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)
-  EXPECT_DEATH_IF_SUPPORTED(frame3_node->AddChildFrame(frame1_node->id()), "");
-#else
-  frame3_node->AddChildFrame(frame1_node->id());
-#endif  // !defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)
-
-  EXPECT_EQ(1u, frame1_node->child_frame_nodes_for_testing().count(
-                    frame2_node.get()));
-  EXPECT_EQ(1u, frame2_node->child_frame_nodes_for_testing().count(
-                    frame3_node.get()));
-  // |frame1_node| was not added successfully because |frame1_node| is one of
-  // the ancestors of |frame3_node|.
-  EXPECT_EQ(0u, frame3_node->child_frame_nodes_for_testing().count(
-                    frame1_node.get()));
-}
-
 TEST_F(FrameNodeImplTest, RemoveChildFrame) {
   auto parent_frame_node = CreateNode<FrameNodeImpl>();
   auto child_frame_node = CreateNode<FrameNodeImpl>();
@@ -88,7 +59,7 @@
   EXPECT_EQ(0u, child_frame_node->child_frame_nodes_for_testing().size());
   EXPECT_TRUE(!child_frame_node->GetParentFrameNode());
 
-  parent_frame_node->AddChildFrame(child_frame_node->id());
+  parent_frame_node->AddChildFrame(child_frame_node.get());
 
   // Ensure correct Parent-child relationships have been established.
   EXPECT_EQ(1u, parent_frame_node->child_frame_nodes_for_testing().size());
@@ -96,7 +67,7 @@
   EXPECT_EQ(0u, child_frame_node->child_frame_nodes_for_testing().size());
   EXPECT_EQ(parent_frame_node.get(), child_frame_node->GetParentFrameNode());
 
-  parent_frame_node->RemoveChildFrame(child_frame_node->id());
+  parent_frame_node->RemoveChildFrame(child_frame_node.get());
 
   // Parent-child relationships should no longer exist.
   EXPECT_EQ(0u, parent_frame_node->child_frame_nodes_for_testing().size());
diff --git a/chrome/browser/performance_manager/graph/graph.cc b/chrome/browser/performance_manager/graph/graph.cc
index dc7efa5..7a9a8879 100644
--- a/chrome/browser/performance_manager/graph/graph.cc
+++ b/chrome/browser/performance_manager/graph/graph.cc
@@ -23,9 +23,13 @@
 
 namespace performance_manager {
 
-Graph::Graph() = default;
+Graph::Graph() {
+  DETACH_FROM_SEQUENCE(sequence_checker_);
+}
 
 Graph::~Graph() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   // Because the graph has ownership of the CUs, and because the process CUs
   // unregister on destruction, there is reentrancy to this class on
   // destruction. The order of operations here is optimized to minimize the work
@@ -42,11 +46,13 @@
 }
 
 void Graph::RegisterObserver(std::unique_ptr<GraphObserver> observer) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   observer->set_node_graph(this);
   observers_.push_back(std::move(observer));
 }
 
 void Graph::OnNodeAdded(NodeBase* node) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   for (auto& observer : observers_) {
     if (observer->ShouldObserve(node)) {
       node->AddObserver(observer.get());
@@ -56,10 +62,12 @@
 }
 
 void Graph::OnBeforeNodeRemoved(NodeBase* node) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   node->BeforeDestroyed();
 }
 
 SystemNodeImpl* Graph::FindOrCreateSystemNode() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!system_node_) {
     // Create the singleton SystemCU instance. Ownership is taken by the graph.
     resource_coordinator::CoordinationUnitID id(
@@ -74,6 +82,7 @@
 
 NodeBase* Graph::GetNodeByID(
     const resource_coordinator::CoordinationUnitID cu_id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   const auto& it = nodes_.find(cu_id);
   if (it == nodes_.end())
     return nullptr;
@@ -81,6 +90,7 @@
 }
 
 ProcessNodeImpl* Graph::GetProcessNodeByPid(base::ProcessId pid) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   auto it = processes_by_pid_.find(pid);
   if (it == processes_by_pid_.end())
     return nullptr;
@@ -102,6 +112,7 @@
 
 size_t Graph::GetNodeAttachedDataCountForTesting(NodeBase* node,
                                                  const void* key) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!node && !key)
     return node_attached_data_map_.size();
 
@@ -118,12 +129,14 @@
 }
 
 void Graph::AddNewNode(NodeBase* new_node) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   auto it = nodes_.emplace(new_node->id(), new_node);
   DCHECK(it.second);  // Inserted successfully
   OnNodeAdded(new_node);
 }
 
 void Graph::RemoveNode(NodeBase* node) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   OnBeforeNodeRemoved(node);
 
   // Remove any node attached data affiliated with this node.
@@ -140,6 +153,7 @@
 
 void Graph::BeforeProcessPidChange(ProcessNodeImpl* process,
                                    base::ProcessId new_pid) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   // On Windows, PIDs are aggressively reused, and because not all process
   // creation/death notifications are synchronized, it's possible for more than
   // one CU to have the same PID. To handle this, the second and subsequent
@@ -156,6 +170,7 @@
 
 template <typename CUType>
 std::vector<CUType*> Graph::GetAllNodesOfType() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   const auto type = CUType::Type();
   std::vector<CUType*> ret;
   for (const auto& el : nodes_) {
diff --git a/chrome/browser/performance_manager/graph/graph.h b/chrome/browser/performance_manager/graph/graph.h
index c960747..ac68786 100644
--- a/chrome/browser/performance_manager/graph/graph.h
+++ b/chrome/browser/performance_manager/graph/graph.h
@@ -15,6 +15,7 @@
 
 #include "base/macros.h"
 #include "base/process/process_handle.h"
+#include "base/sequence_checker.h"
 #include "chrome/browser/performance_manager/graph/node_attached_data.h"
 #include "services/metrics/public/cpp/mojo_ukm_recorder.h"
 #include "services/metrics/public/cpp/ukm_recorder.h"
@@ -97,8 +98,7 @@
       std::map<NodeAttachedDataKey, std::unique_ptr<NodeAttachedData>>;
   NodeAttachedDataMap node_attached_data_map_;
 
-  static void Create();
-
+  SEQUENCE_CHECKER(sequence_checker_);
   DISALLOW_COPY_AND_ASSIGN(Graph);
 };
 
diff --git a/chrome/browser/performance_manager/graph/mock_graphs.cc b/chrome/browser/performance_manager/graph/mock_graphs.cc
index 751a36a..e19ab94 100644
--- a/chrome/browser/performance_manager/graph/mock_graphs.cc
+++ b/chrome/browser/performance_manager/graph/mock_graphs.cc
@@ -19,32 +19,38 @@
 MockSinglePageInSingleProcessGraph::MockSinglePageInSingleProcessGraph(
     Graph* graph)
     : system(TestNodeWrapper<SystemNodeImpl>::Create(graph)),
-      frame(TestNodeWrapper<FrameNodeImpl>::Create(graph)),
       process(TestNodeWrapper<ProcessNodeImpl>::Create(graph)),
-      page(TestNodeWrapper<PageNodeImpl>::Create(graph)) {
+      page(TestNodeWrapper<PageNodeImpl>::Create(graph)),
+      frame(TestNodeWrapper<FrameNodeImpl>::Create(graph)) {
   frame->SetAllInterventionPoliciesForTesting(
       resource_coordinator::mojom::InterventionPolicy::kDefault);
-  page->AddFrame(frame->id());
-  frame->SetProcess(process->id());
+  page->AddFrame(frame.get());
+  frame->SetProcess(process.get());
   process->SetPID(1);
 }
 
-MockSinglePageInSingleProcessGraph::~MockSinglePageInSingleProcessGraph() =
-    default;
+MockSinglePageInSingleProcessGraph::~MockSinglePageInSingleProcessGraph() {
+  // Make sure frame nodes are torn down before pages.
+  frame.reset();
+  page.reset();
+}
 
 MockMultiplePagesInSingleProcessGraph::MockMultiplePagesInSingleProcessGraph(
     Graph* graph)
     : MockSinglePageInSingleProcessGraph(graph),
-      other_frame(TestNodeWrapper<FrameNodeImpl>::Create(graph)),
-      other_page(TestNodeWrapper<PageNodeImpl>::Create(graph)) {
+      other_page(TestNodeWrapper<PageNodeImpl>::Create(graph)),
+      other_frame(TestNodeWrapper<FrameNodeImpl>::Create(graph)) {
   other_frame->SetAllInterventionPoliciesForTesting(
       resource_coordinator::mojom::InterventionPolicy::kDefault);
-  other_page->AddFrame(other_frame->id());
-  other_frame->SetProcess(process->id());
+  other_page->AddFrame(other_frame.get());
+  other_frame->SetProcess(process.get());
 }
 
 MockMultiplePagesInSingleProcessGraph::
-    ~MockMultiplePagesInSingleProcessGraph() = default;
+    ~MockMultiplePagesInSingleProcessGraph() {
+  other_frame.reset();
+  other_page.reset();
+}
 
 MockSinglePageWithMultipleProcessesGraph::
     MockSinglePageWithMultipleProcessesGraph(Graph* graph)
@@ -53,9 +59,9 @@
       other_process(TestNodeWrapper<ProcessNodeImpl>::Create(graph)) {
   child_frame->SetAllInterventionPoliciesForTesting(
       resource_coordinator::mojom::InterventionPolicy::kDefault);
-  frame->AddChildFrame(child_frame->id());
-  page->AddFrame(child_frame->id());
-  child_frame->SetProcess(other_process->id());
+  frame->AddChildFrame(child_frame.get());
+  page->AddFrame(child_frame.get());
+  child_frame->SetProcess(other_process.get());
   other_process->SetPID(2);
 }
 
@@ -69,9 +75,9 @@
       other_process(TestNodeWrapper<ProcessNodeImpl>::Create(graph)) {
   child_frame->SetAllInterventionPoliciesForTesting(
       resource_coordinator::mojom::InterventionPolicy::kDefault);
-  other_frame->AddChildFrame(child_frame->id());
-  other_page->AddFrame(child_frame->id());
-  child_frame->SetProcess(other_process->id());
+  other_frame->AddChildFrame(child_frame.get());
+  other_page->AddFrame(child_frame.get());
+  child_frame->SetProcess(other_process.get());
   other_process->SetPID(2);
 }
 
diff --git a/chrome/browser/performance_manager/graph/mock_graphs.h b/chrome/browser/performance_manager/graph/mock_graphs.h
index 2a7e0fa..9937128 100644
--- a/chrome/browser/performance_manager/graph/mock_graphs.h
+++ b/chrome/browser/performance_manager/graph/mock_graphs.h
@@ -30,9 +30,9 @@
   explicit MockSinglePageInSingleProcessGraph(Graph* graph);
   ~MockSinglePageInSingleProcessGraph();
   TestNodeWrapper<SystemNodeImpl> system;
-  TestNodeWrapper<FrameNodeImpl> frame;
   TestNodeWrapper<ProcessNodeImpl> process;
   TestNodeWrapper<PageNodeImpl> page;
+  TestNodeWrapper<FrameNodeImpl> frame;
 };
 
 // The following coordination unit graph topology is created to emulate a
@@ -52,8 +52,8 @@
     : public MockSinglePageInSingleProcessGraph {
   explicit MockMultiplePagesInSingleProcessGraph(Graph* graph);
   ~MockMultiplePagesInSingleProcessGraph();
-  TestNodeWrapper<FrameNodeImpl> other_frame;
   TestNodeWrapper<PageNodeImpl> other_page;
+  TestNodeWrapper<FrameNodeImpl> other_frame;
 };
 
 // The following coordination unit graph topology is created to emulate a
diff --git a/chrome/browser/performance_manager/graph/node_attached_data_unittest.cc b/chrome/browser/performance_manager/graph/node_attached_data_unittest.cc
index 6c2925a..5b0b3de 100644
--- a/chrome/browser/performance_manager/graph/node_attached_data_unittest.cc
+++ b/chrome/browser/performance_manager/graph/node_attached_data_unittest.cc
@@ -234,6 +234,7 @@
 
   // Release the page node and expect the node attached data to have been
   // cleaned up.
+  mock_graph.frame.reset();
   mock_graph.page.reset();
   EXPECT_EQ(1u, graph()->GetNodeAttachedDataCountForTesting(nullptr, nullptr));
   EXPECT_EQ(0u,
diff --git a/chrome/browser/performance_manager/graph/node_base.cc b/chrome/browser/performance_manager/graph/node_base.cc
index 62d650dd..c952e038 100644
--- a/chrome/browser/performance_manager/graph/node_base.cc
+++ b/chrome/browser/performance_manager/graph/node_base.cc
@@ -59,6 +59,10 @@
   return default_value;
 }
 
+bool NodeBase::NodeInGraph(const NodeBase* other_node) const {
+  return graph_->GetNodeByID(other_node->id()) == other_node;
+}
+
 void NodeBase::OnEventReceived(resource_coordinator::mojom::Event event) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   for (auto& observer : observers())
diff --git a/chrome/browser/performance_manager/graph/node_base.h b/chrome/browser/performance_manager/graph/node_base.h
index bcd9a9d..df44b0b0 100644
--- a/chrome/browser/performance_manager/graph/node_base.h
+++ b/chrome/browser/performance_manager/graph/node_base.h
@@ -33,7 +33,7 @@
   NodeBase(const resource_coordinator::CoordinationUnitID& id, Graph* graph);
   virtual ~NodeBase();
 
-  void BeforeDestroyed();
+  virtual void BeforeDestroyed();
   void AddObserver(GraphObserver* observer);
   void RemoveObserver(GraphObserver* observer);
   bool GetProperty(
@@ -61,6 +61,9 @@
   }
 
  protected:
+  // Returns true if |other_node| is in the same graph.
+  bool NodeInGraph(const NodeBase* other_node) const;
+
   // Helper function for setting a property, and notifying observers if the
   // value has changed.
   template <typename NodeType,
diff --git a/chrome/browser/performance_manager/graph/page_node_impl.cc b/chrome/browser/performance_manager/graph/page_node_impl.cc
index 6a8f725..50230e1 100644
--- a/chrome/browser/performance_manager/graph/page_node_impl.cc
+++ b/chrome/browser/performance_manager/graph/page_node_impl.cc
@@ -41,30 +41,45 @@
 
 PageNodeImpl::~PageNodeImpl() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  for (auto* child_frame : frame_nodes_)
-    child_frame->RemovePageNode(this);
 }
 
-void PageNodeImpl::AddFrame(
-    const resource_coordinator::CoordinationUnitID& cu_id) {
+void PageNodeImpl::AddFrame(FrameNodeImpl* frame_node) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(cu_id.type == resource_coordinator::CoordinationUnitType::kFrame);
-  FrameNodeImpl* frame_node = FrameNodeImpl::GetNodeByID(graph_, cu_id);
-  if (!frame_node)
-    return;
-  if (AddFrameImpl(frame_node))
+  DCHECK(frame_node);
+  DCHECK(NodeInGraph(frame_node));
+
+  // TODO(https://crbug.com/944150): This method is called on navigation
+  //     complete, and as such can fire more than once for a given frame in
+  //     its lifetime. The |frame_nodes_| set is redundant to the frame tree and
+  //     should be removed.
+  const bool inserted = frame_nodes_.insert(frame_node).second;
+  if (inserted) {
     frame_node->AddPageNode(this);
+
+    OnNumFrozenFramesStateChange(
+        frame_node->lifecycle_state() ==
+                resource_coordinator::mojom::LifecycleState::kFrozen
+            ? 1
+            : 0);
+    MaybeInvalidateInterventionPolicies(frame_node, true /* adding_frame */);
+  }
 }
 
-void PageNodeImpl::RemoveFrame(
-    const resource_coordinator::CoordinationUnitID& cu_id) {
+void PageNodeImpl::RemoveFrame(FrameNodeImpl* frame_node) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(cu_id != id());
-  FrameNodeImpl* frame_node = FrameNodeImpl::GetNodeByID(graph_, cu_id);
-  if (!frame_node)
-    return;
-  if (RemoveFrameImpl(frame_node))
-    frame_node->RemovePageNode(this);
+  DCHECK(frame_node);
+  DCHECK(NodeInGraph(frame_node));
+
+  size_t removed = frame_nodes_.erase(frame_node);
+  DCHECK_EQ(1u, removed);
+  frame_node->RemovePageNode(this);
+
+  OnNumFrozenFramesStateChange(
+      frame_node->lifecycle_state() ==
+              resource_coordinator::mojom::LifecycleState::kFrozen
+          ? -1
+          : 0);
+  MaybeInvalidateInterventionPolicies(frame_node, false /* adding_frame */);
 }
 
 void PageNodeImpl::SetIsLoading(bool is_loading) {
@@ -240,6 +255,19 @@
   return intervention_policy_[kIndex];
 }
 
+void PageNodeImpl::BeforeDestroyed() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // TODO(siggi): This fails browser_tests for some reason. Would be nice to
+  //     assert this.
+  // DCHECK(frame_nodes_.empty());
+
+  NodeBase::BeforeDestroyed();
+
+  for (auto* child_frame : frame_nodes_)
+    child_frame->RemovePageNode(this);
+}
+
 void PageNodeImpl::set_page_almost_idle(bool page_almost_idle) {
   if (page_almost_idle_ == page_almost_idle)
     return;
@@ -262,35 +290,6 @@
     observer.OnPagePropertyChanged(this, property_type, value);
 }
 
-bool PageNodeImpl::AddFrameImpl(FrameNodeImpl* frame_node) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  const bool inserted = frame_nodes_.insert(frame_node).second;
-  if (inserted) {
-    OnNumFrozenFramesStateChange(
-        frame_node->lifecycle_state() ==
-                resource_coordinator::mojom::LifecycleState::kFrozen
-            ? 1
-            : 0);
-    MaybeInvalidateInterventionPolicies(frame_node, true /* adding_frame */);
-  }
-  return inserted;
-}
-
-bool PageNodeImpl::RemoveFrameImpl(FrameNodeImpl* frame_node) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  bool removed = frame_nodes_.erase(frame_node) > 0;
-  if (removed) {
-    OnNumFrozenFramesStateChange(
-        frame_node->lifecycle_state() ==
-                resource_coordinator::mojom::LifecycleState::kFrozen
-            ? -1
-            : 0);
-    MaybeInvalidateInterventionPolicies(frame_node, false /* adding_frame */);
-  }
-
-  return removed;
-}
-
 void PageNodeImpl::OnNumFrozenFramesStateChange(int num_frozen_frames_delta) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   num_frozen_frames_ += num_frozen_frames_delta;
diff --git a/chrome/browser/performance_manager/graph/page_node_impl.h b/chrome/browser/performance_manager/graph/page_node_impl.h
index 1713b31..89624e6 100644
--- a/chrome/browser/performance_manager/graph/page_node_impl.h
+++ b/chrome/browser/performance_manager/graph/page_node_impl.h
@@ -29,8 +29,8 @@
                Graph* graph);
   ~PageNodeImpl() override;
 
-  void AddFrame(const resource_coordinator::CoordinationUnitID& cu_id);
-  void RemoveFrame(const resource_coordinator::CoordinationUnitID& cu_id);
+  void AddFrame(FrameNodeImpl* frame_node);
+  void RemoveFrame(FrameNodeImpl* frame_node);
   void SetIsLoading(bool is_loading);
   void SetIsVisible(bool is_visible);
   void SetUKMSourceId(int64_t ukm_source_id);
@@ -125,6 +125,8 @@
  private:
   friend class FrameNodeImpl;
 
+  void BeforeDestroyed() override;
+
   void set_page_almost_idle(bool page_almost_idle);
 
   // CoordinationUnitInterface implementation.
@@ -133,9 +135,6 @@
       resource_coordinator::mojom::PropertyType property_type,
       int64_t value) override;
 
-  bool AddFrameImpl(FrameNodeImpl* frame_node);
-  bool RemoveFrameImpl(FrameNodeImpl* frame_node);
-
   // This is called whenever |num_frozen_frames_| changes, or whenever
   // |frame_nodes_.size()| changes. It is used to synthesize the
   // value of |has_nonempty_beforeunload| and to update the LifecycleState of
diff --git a/chrome/browser/performance_manager/graph/page_node_impl_unittest.cc b/chrome/browser/performance_manager/graph/page_node_impl_unittest.cc
index 43d74a0..bea8d36 100644
--- a/chrome/browser/performance_manager/graph/page_node_impl_unittest.cc
+++ b/chrome/browser/performance_manager/graph/page_node_impl_unittest.cc
@@ -43,9 +43,9 @@
   auto frame2_node = CreateNode<FrameNodeImpl>();
   auto frame3_node = CreateNode<FrameNodeImpl>();
 
-  page_node->AddFrame(frame1_node->id());
-  page_node->AddFrame(frame2_node->id());
-  page_node->AddFrame(frame3_node->id());
+  page_node->AddFrame(frame1_node.get());
+  page_node->AddFrame(frame2_node.get());
+  page_node->AddFrame(frame3_node.get());
   EXPECT_EQ(3u, page_node->GetFrameNodes().size());
 }
 
@@ -54,9 +54,9 @@
   auto frame1_node = CreateNode<FrameNodeImpl>();
   auto frame2_node = CreateNode<FrameNodeImpl>();
 
-  page_node->AddFrame(frame1_node->id());
-  page_node->AddFrame(frame2_node->id());
-  page_node->AddFrame(frame1_node->id());
+  page_node->AddFrame(frame1_node.get());
+  page_node->AddFrame(frame2_node.get());
+  page_node->AddFrame(frame1_node.get());
   EXPECT_EQ(2u, page_node->GetFrameNodes().size());
 }
 
@@ -68,14 +68,14 @@
   EXPECT_EQ(0u, page_node->GetFrameNodes().size());
   EXPECT_FALSE(frame_node->GetPageNode());
 
-  page_node->AddFrame(frame_node->id());
+  page_node->AddFrame(frame_node.get());
 
   // Ensure correct Parent-child relationships have been established.
   EXPECT_EQ(1u, page_node->GetFrameNodes().size());
   EXPECT_EQ(1u, page_node->GetFrameNodes().count(frame_node.get()));
   EXPECT_EQ(page_node.get(), frame_node->GetPageNode());
 
-  page_node->RemoveFrame(frame_node->id());
+  page_node->RemoveFrame(frame_node.get());
 
   // Parent-child relationships should no longer exist.
   EXPECT_EQ(0u, page_node->GetFrameNodes().size());
@@ -302,7 +302,7 @@
 
   // Add a frame and expect the values to be invalidated. Reaggregate and
   // ensure the appropriate value results.
-  page->AddFrame(f0->id());
+  page->AddFrame(f0.get());
   EXPECT_EQ(1u, page->GetInterventionPolicyFramesReportedForTesting());
   ExpectRawInterventionPolicy(
       resource_coordinator::mojom::InterventionPolicy::kUnknown, page.get());
@@ -310,7 +310,7 @@
 
   // Do it again. This time the raw values should be the same as the
   // aggregated values above.
-  page->AddFrame(f1->id());
+  page->AddFrame(f1.get());
   EXPECT_EQ(2u, page->GetInterventionPolicyFramesReportedForTesting());
   ExpectRawInterventionPolicy(
       resource_coordinator::mojom::InterventionPolicy::kUnknown, page.get());
@@ -322,6 +322,9 @@
   ExpectRawInterventionPolicy(
       resource_coordinator::mojom::InterventionPolicy::kUnknown, page.get());
   ExpectInterventionPolicy(f0_policy_aggregated, page.get());
+
+  f0.reset();
+  page.reset();
 }
 
 }  // namespace
@@ -433,9 +436,9 @@
   TestNodeWrapper<FrameNodeImpl> f1 =
       TestNodeWrapper<FrameNodeImpl>::Create(mock_graph);
   EXPECT_EQ(0u, page->GetInterventionPolicyFramesReportedForTesting());
-  page->AddFrame(f0->id());
+  page->AddFrame(f0.get());
   EXPECT_EQ(0u, page->GetInterventionPolicyFramesReportedForTesting());
-  page->AddFrame(f1->id());
+  page->AddFrame(f1.get());
   EXPECT_EQ(0u, page->GetInterventionPolicyFramesReportedForTesting());
 
   // Set the policies on the first frame. This should be observed by the page
diff --git a/chrome/browser/performance_manager/graph/process_node_impl.cc b/chrome/browser/performance_manager/graph/process_node_impl.cc
index 221d0875..5c60dc26 100644
--- a/chrome/browser/performance_manager/graph/process_node_impl.cc
+++ b/chrome/browser/performance_manager/graph/process_node_impl.cc
@@ -19,13 +19,6 @@
 
 ProcessNodeImpl::~ProcessNodeImpl() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  // Make as if we're transitioning to the null PID before we die to clear this
-  // instance from the PID map.
-  if (process_id_ != base::kNullProcessId)
-    graph()->BeforeProcessPidChange(this, base::kNullProcessId);
-
-  for (auto* child_frame : frame_nodes_)
-    child_frame->RemoveProcessNode(this);
 }
 
 void ProcessNodeImpl::AddFrame(FrameNodeImpl* frame_node) {
@@ -128,6 +121,19 @@
     IncrementNumFrozenFrames();
 }
 
+void ProcessNodeImpl::BeforeDestroyed() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  NodeBase::BeforeDestroyed();
+
+  // Make as if we're transitioning to the null PID before we die to clear this
+  // instance from the PID map.
+  if (process_id_ != base::kNullProcessId)
+    graph()->BeforeProcessPidChange(this, base::kNullProcessId);
+
+  for (auto* child_frame : frame_nodes_)
+    child_frame->RemoveProcessNode(this);
+}
+
 void ProcessNodeImpl::OnEventReceived(
     resource_coordinator::mojom::Event event) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
diff --git a/chrome/browser/performance_manager/graph/process_node_impl.h b/chrome/browser/performance_manager/graph/process_node_impl.h
index b05e9c9..eeb2bae 100644
--- a/chrome/browser/performance_manager/graph/process_node_impl.h
+++ b/chrome/browser/performance_manager/graph/process_node_impl.h
@@ -80,6 +80,8 @@
       resource_coordinator::mojom::LifecycleState old_state);
 
  private:
+  void BeforeDestroyed() override;
+
   // CoordinationUnitInterface implementation.
   void OnEventReceived(resource_coordinator::mojom::Event event) override;
   void OnPropertyChanged(
diff --git a/chrome/browser/performance_manager/observers/metrics_collector_unittest.cc b/chrome/browser/performance_manager/observers/metrics_collector_unittest.cc
index 71ba823a..10e694e 100644
--- a/chrome/browser/performance_manager/observers/metrics_collector_unittest.cc
+++ b/chrome/browser/performance_manager/observers/metrics_collector_unittest.cc
@@ -111,7 +111,7 @@
        FromBackgroundedToFirstNonPersistentNotificationCreatedUMA) {
   auto page_node = CreateNode<PageNodeImpl>();
   auto frame_node = CreateNode<FrameNodeImpl>();
-  page_node->AddFrame(frame_node->id());
+  page_node->AddFrame(frame_node.get());
 
   page_node->OnMainFrameNavigationCommitted(
       ResourceCoordinatorClock::NowTicks(), kDummyID, kDummyUrl);
@@ -147,7 +147,7 @@
     FromBackgroundedToFirstNonPersistentNotificationCreatedUMA5MinutesTimeout) {
   auto page_node = CreateNode<PageNodeImpl>();
   auto frame_node = CreateNode<FrameNodeImpl>();
-  page_node->AddFrame(frame_node->id());
+  page_node->AddFrame(frame_node.get());
 
   page_node->OnMainFrameNavigationCommitted(
       ResourceCoordinatorClock::NowTicks(), kDummyID, kDummyUrl);
@@ -219,8 +219,8 @@
   auto process_node = CreateNode<ProcessNodeImpl>();
 
   auto frame_node = CreateNode<FrameNodeImpl>();
-  page_node->AddFrame(frame_node->id());
-  frame_node->SetProcess(process_node->id());
+  page_node->AddFrame(frame_node.get());
+  frame_node->SetProcess(process_node.get());
 
   ukm::TestUkmRecorder ukm_recorder;
   graph()->set_ukm_recorder(&ukm_recorder);
diff --git a/chrome/browser/performance_manager/performance_manager_tab_helper.cc b/chrome/browser/performance_manager/performance_manager_tab_helper.cc
index bd0455e..1194f4ce 100644
--- a/chrome/browser/performance_manager/performance_manager_tab_helper.cc
+++ b/chrome/browser/performance_manager/performance_manager_tab_helper.cc
@@ -103,7 +103,7 @@
     performance_manager_->task_runner()->PostTask(
         FROM_HERE,
         base::BindOnce(&FrameNodeImpl::AddChildFrame,
-                       base::Unretained(parent_frame_node.get()), frame->id()));
+                       base::Unretained(parent_frame_node.get()), frame.get()));
   }
 
   RenderProcessUserData* user_data =
@@ -117,7 +117,7 @@
     performance_manager_->task_runner()->PostTask(
         FROM_HERE, base::BindOnce(&FrameNodeImpl::SetProcess,
                                   base::Unretained(frame.get()),
-                                  user_data->process_node()->id()));
+                                  user_data->process_node()));
   }
 
   frames_[render_frame_host] = std::move(frame);
@@ -163,7 +163,7 @@
   content::RenderFrameHost* render_frame_host =
       navigation_handle->GetRenderFrameHost();
   // Make sure the hierarchical structure is constructed before sending signal
-  // to Resource Coordinator.
+  // to the performance manager.
   // TODO(siggi): Ideally this would be a DCHECK, but it seems it's possible
   //     to get a DidFinishNavigation notification for a deleted frame with
   //     the network service.
@@ -173,7 +173,7 @@
     performance_manager_->task_runner()->PostTask(
         FROM_HERE,
         base::BindOnce(&PageNodeImpl::AddFrame,
-                       base::Unretained(page_node_.get()), it->second->id()));
+                       base::Unretained(page_node_.get()), it->second.get()));
 
     if (navigation_handle->IsInMainFrame()) {
       OnMainFrameNavigation(navigation_handle->GetNavigationId());
diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
index 7185b8cf9..5f6f9377 100644
--- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
+++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "build/build_config.h"
+#include "chrome/browser/autocomplete/autocomplete_classifier_factory.h"
 #include "chrome/browser/autocomplete/in_memory_url_index_factory.h"
 #include "chrome/browser/autocomplete/shortcuts_backend_factory.h"
 #include "chrome/browser/autofill/personal_data_manager_factory.h"
@@ -226,6 +227,7 @@
   AccountFetcherServiceFactory::GetInstance();
   AccountInvestigatorFactory::GetInstance();
   AccountReconcilorFactory::GetInstance();
+  AutocompleteClassifierFactory::GetInstance();
   autofill::PersonalDataManagerFactory::GetInstance();
 #if BUILDFLAG(ENABLE_BACKGROUND_CONTENTS)
   BackgroundContentsServiceFactory::GetInstance();
diff --git a/chrome/browser/profiling_host/memlog_browsertest.cc b/chrome/browser/profiling_host/memlog_browsertest.cc
index 00a2008e..67238e6 100644
--- a/chrome/browser/profiling_host/memlog_browsertest.cc
+++ b/chrome/browser/profiling_host/memlog_browsertest.cc
@@ -87,7 +87,13 @@
 
 // Ensure invocations via TracingController can generate a valid JSON file with
 // expected data.
-IN_PROC_BROWSER_TEST_P(MemlogBrowserTest, EndToEnd) {
+// https://crbug.com/944429
+#if defined(OS_MACOSX)
+#define MAYBE_EndToEnd DISABLED_EndToEnd
+#else
+#define MAYBE_EndToEnd EndToEnd
+#endif
+IN_PROC_BROWSER_TEST_P(MemlogBrowserTest, MAYBE_EndToEnd) {
   LOG(INFO) << "Memlog mode: " << static_cast<int>(GetParam().mode);
   LOG(INFO) << "Memlog stack mode: " << static_cast<int>(GetParam().stack_mode);
   LOG(INFO) << "Stream samples: " << GetParam().stream_samples;
diff --git a/chrome/browser/resource_coordinator/tab_manager.cc b/chrome/browser/resource_coordinator/tab_manager.cc
index bd1a204..702dbda9 100644
--- a/chrome/browser/resource_coordinator/tab_manager.cc
+++ b/chrome/browser/resource_coordinator/tab_manager.cc
@@ -94,12 +94,6 @@
 // load the next background tab when the loading slots free up.
 constexpr size_t kNumOfLoadingSlots = 1;
 
-#if defined(OS_CHROMEOS)
-// The default interval in seconds after which to adjust the oom_score_adj
-// value.
-constexpr int kAdjustmentIntervalSeconds = 10;
-#endif
-
 struct LifecycleUnitAndSortKey {
   explicit LifecycleUnitAndSortKey(LifecycleUnit* lifecycle_unit)
       : lifecycle_unit(lifecycle_unit),
@@ -223,13 +217,8 @@
     return;
 #endif
 
-// TODO(adityakeerthi): Move this logic into TabManagerDelegate.
 #if defined(OS_CHROMEOS)
-  if (!update_timer_.IsRunning()) {
-    update_timer_.Start(FROM_HERE,
-                        TimeDelta::FromSeconds(kAdjustmentIntervalSeconds),
-                        this, &TabManager::UpdateTimerCallback);
-  }
+  delegate_->StartPeriodicOOMScoreUpdate();
 #endif
 
 // MemoryPressureMonitor is not implemented on Linux so far and tabs are never
@@ -377,23 +366,6 @@
   return false;
 }
 
-// This function is called when |update_timer_| fires. It will adjust the clock
-// if needed (if it detects that the machine was asleep) and will fire the stats
-// updating on ChromeOS via the delegate.
-void TabManager::UpdateTimerCallback() {
-  // If Chrome is shutting down, do not do anything.
-  if (g_browser_process->IsShuttingDown())
-    return;
-
-  if (BrowserList::GetInstance()->empty())
-    return;
-
-#if defined(OS_CHROMEOS)
-  // This starts the CrOS specific OOM adjustments in /proc/<pid>/oom_score_adj.
-  delegate_->AdjustOomPriorities();
-#endif
-}
-
 void TabManager::PauseBackgroundTabOpeningIfNeeded() {
   TRACE_EVENT_INSTANT0("navigation",
                        "TabManager::PauseBackgroundTabOpeningIfNeeded",
diff --git a/chrome/browser/resource_coordinator/tab_manager.h b/chrome/browser/resource_coordinator/tab_manager.h
index 2628c31c..bf78f2d4 100644
--- a/chrome/browser/resource_coordinator/tab_manager.h
+++ b/chrome/browser/resource_coordinator/tab_manager.h
@@ -53,20 +53,7 @@
 #endif
 class TabManagerStatsCollector;
 
-// The TabManager periodically updates (see
-// |kAdjustmentIntervalSeconds| in the source) the status of renderers
-// which are then used by the algorithm embedded here for priority in being
-// killed upon OOM conditions.
-//
-// The algorithm used favors killing tabs that are not active, not in an active
-// window, not in a visible window, not pinned, and have been idle for longest,
-// in that order of priority.
-//
-// On Chrome OS (via the delegate), the kernel (via /proc/<pid>/oom_score_adj)
-// will be informed of each renderer's score, which is based on the status, so
-// in case Chrome is not able to relieve the pressure quickly enough and the
-// kernel is forced to kill processes, it will be able to do so using the same
-// algorithm as the one used here.
+// TabManager is responsible for triggering tab lifecycle state transitions.
 //
 // The TabManager also delays background tabs' navigation when needed in order
 // to improve users' experience with the foreground tab.
@@ -259,10 +246,6 @@
   // can be easily reloaded and hence makes a good choice to discard.
   static bool IsInternalPage(const GURL& url);
 
-  // Callback for when |update_timer_| fires. Takes care of executing the tasks
-  // that need to be run periodically (see comment in implementation).
-  void UpdateTimerCallback();
-
   // Makes a request to the WebContents at the specified index to freeze its
   // page.
   void FreezeWebContentsAt(int index, TabStripModel* model);
@@ -466,9 +449,6 @@
   std::unique_ptr<base::MemoryPressureListener> memory_pressure_listener_;
 
 #if defined(OS_CHROMEOS)
-  // Timer to periodically make OOM adjustments on ChromeOS.
-  base::RepeatingTimer update_timer_;
-
   std::unique_ptr<TabManagerDelegate> delegate_;
 #endif
 
diff --git a/chrome/browser/resource_coordinator/tab_manager_delegate_chromeos.cc b/chrome/browser/resource_coordinator/tab_manager_delegate_chromeos.cc
index c00d3a2..8fae804 100644
--- a/chrome/browser/resource_coordinator/tab_manager_delegate_chromeos.cc
+++ b/chrome/browser/resource_coordinator/tab_manager_delegate_chromeos.cc
@@ -26,6 +26,7 @@
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/time/time.h"
+#include "chrome/browser/browser_process.h"
 #include "chrome/browser/memory/memory_kills_monitor.h"
 #include "chrome/browser/resource_coordinator/tab_lifecycle_unit_external.h"
 #include "chrome/browser/resource_coordinator/tab_manager_stats_collector.h"
@@ -57,6 +58,10 @@
 namespace resource_coordinator {
 namespace {
 
+// The default interval after which to adjust OOM scores.
+constexpr base::TimeDelta kAdjustmentInterval =
+    base::TimeDelta::FromSeconds(10);
+
 // When switching to a new tab the tab's renderer's OOM score needs to be
 // updated to reflect its front-most status and protect it from discard.
 // However, doing this immediately might slow down tab switch time, so wait
@@ -358,6 +363,12 @@
   }
 }
 
+void TabManagerDelegate::StartPeriodicOOMScoreUpdate() {
+  DCHECK(!adjust_oom_priorities_timer_.IsRunning());
+  adjust_oom_priorities_timer_.Start(FROM_HERE, kAdjustmentInterval, this,
+                                     &TabManagerDelegate::AdjustOomPriorities);
+}
+
 void TabManagerDelegate::ScheduleEarlyOomPrioritiesAdjustment() {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   AdjustOomPriorities();
@@ -503,6 +514,10 @@
 // 2) last time a tab was selected
 // 3) is the tab currently selected
 void TabManagerDelegate::AdjustOomPriorities() {
+  // If Chrome is shutting down, do not do anything
+  if (g_browser_process->IsShuttingDown())
+    return;
+
   arc::ArcProcessService* arc_process_service = arc::ArcProcessService::Get();
   if (arc_process_service) {
     arc_process_service->RequestAppProcessList(
diff --git a/chrome/browser/resource_coordinator/tab_manager_delegate_chromeos.h b/chrome/browser/resource_coordinator/tab_manager_delegate_chromeos.h
index 4fdb93e9..7715a81c 100644
--- a/chrome/browser/resource_coordinator/tab_manager_delegate_chromeos.h
+++ b/chrome/browser/resource_coordinator/tab_manager_delegate_chromeos.h
@@ -87,6 +87,10 @@
                          aura::Window* gained_active,
                          aura::Window* lost_active) override;
 
+  // Called by TabManager::Start to start a timer that periodically updates
+  // OOM scores.
+  void StartPeriodicOOMScoreUpdate();
+
   // Kills a process on memory pressure.
   void LowMemoryKill(::mojom::LifecycleUnitDiscardReason reason,
                      TabManager::TabDiscardDoneCB tab_discard_done);
@@ -96,9 +100,6 @@
   // range of oom_score_adj is [-1000, 1000].
   int GetCachedOomScore(base::ProcessHandle process_handle);
 
-  // Called when the timer fires, sets oom_adjust_score for all renderers.
-  void AdjustOomPriorities();
-
   // Returns true if the process has recently been killed.
   // Virtual for unit testing.
   virtual bool IsRecentlyKilledArcProcess(const std::string& process_name,
@@ -171,6 +172,9 @@
   // Sets a newly focused tab the highest priority process if it wasn't.
   void AdjustFocusedTabScore(base::ProcessHandle pid);
 
+  // Called when the timer fires, sets oom_adjust_score for all renderers.
+  void AdjustOomPriorities();
+
   // Called by AdjustOomPriorities. Runs on the main thread.
   void AdjustOomPrioritiesImpl(OptionalArcProcessList arc_processes);
 
@@ -204,6 +208,9 @@
   // Registrar to receive renderer notifications.
   content::NotificationRegistrar registrar_;
 
+  // Timer to periodically make OOM score adjustments.
+  base::RepeatingTimer adjust_oom_priorities_timer_;
+
   // Timer to guarantee that the tab or app is focused for a certain amount of
   // time.
   base::OneShotTimer focus_process_score_adjust_timer_;
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/recovery_strategy_test.extjs b/chrome/browser/resources/chromeos/chromevox/cvox2/background/recovery_strategy_test.extjs
index 502f7677..72e6398 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/recovery_strategy_test.extjs
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/recovery_strategy_test.extjs
@@ -43,18 +43,12 @@
     var pTreePathRecovery = new TreePathRecoveryStrategy(p);
     var sTreePathRecovery = new TreePathRecoveryStrategy(s);
     this.listenOnce(b, 'clicked', function() {
-      assertFalse(bAncestryRecovery.requiresRecovery(),
-                  "bAncestryRecovery.requiresRecovery");
-      assertTrue(pAncestryRecovery.requiresRecovery(),
-                 "pAncestryRecovery.requiresRecovery()");
-      assertTrue(sAncestryRecovery.requiresRecovery(),
-                 "sAncestryRecovery.requiresRecovery()");
-      assertFalse(bTreePathRecovery.requiresRecovery(),
-                  "bTreePathRecovery.requiresRecovery()");
-      assertTrue(pTreePathRecovery.requiresRecovery(),
-                 "pTreePathRecovery.requiresRecovery()");
-      assertTrue(sTreePathRecovery.requiresRecovery(),
-                 "sTreePathRecovery.requiresRecovery()");
+      assertFalse(bAncestryRecovery.requiresRecovery());
+      assertTrue(pAncestryRecovery.requiresRecovery());
+      assertTrue(sAncestryRecovery.requiresRecovery());
+      assertFalse(bTreePathRecovery.requiresRecovery());
+      assertTrue(pTreePathRecovery.requiresRecovery());
+      assertTrue(sTreePathRecovery.requiresRecovery());
 
       assertEquals(RoleType.BUTTON, bAncestryRecovery.node.role);
       assertEquals(root, pAncestryRecovery.node);
@@ -64,18 +58,12 @@
       assertEquals(b, pTreePathRecovery.node);
       assertEquals(b, sTreePathRecovery.node);
 
-      assertFalse(bAncestryRecovery.requiresRecovery(),
-                  "bAncestryRecovery.requiresRecovery()");
-      assertFalse(pAncestryRecovery.requiresRecovery(),
-                  "pAncestryRecovery.requiresRecovery()");
-      assertFalse(sAncestryRecovery.requiresRecovery(),
-                  "sAncestryRecovery.requiresRecovery()");
-      assertFalse(bTreePathRecovery.requiresRecovery(),
-                  "bTreePathRecovery.requiresRecovery()");
-      assertFalse(pTreePathRecovery.requiresRecovery(),
-                  "pTreePathRecovery.requiresRecovery()");
-      assertFalse(sTreePathRecovery.requiresRecovery(),
-                  "sTreePathRecovery.requiresRecovery()");
+      assertFalse(bAncestryRecovery.requiresRecovery());
+      assertFalse(pAncestryRecovery.requiresRecovery());
+      assertFalse(sAncestryRecovery.requiresRecovery());
+      assertFalse(bTreePathRecovery.requiresRecovery());
+      assertFalse(pTreePathRecovery.requiresRecovery());
+      assertFalse(sTreePathRecovery.requiresRecovery());
     });
     // Trigger the change.
     b.doDefault();
diff --git a/chrome/browser/resources/settings/internet_page/internet_detail_page.js b/chrome/browser/resources/settings/internet_page/internet_detail_page.js
index 1b748b27..738f8af 100644
--- a/chrome/browser/resources/settings/internet_page/internet_detail_page.js
+++ b/chrome/browser/resources/settings/internet_page/internet_detail_page.js
@@ -217,7 +217,7 @@
     const guid = queryParams.get('guid') || '';
     if (!guid) {
       console.error('No guid specified for page:' + route);
-      this.close_();
+      this.close();
     }
 
     this.shouldShowConfigureWhenNetworkLoaded_ =
@@ -249,8 +249,7 @@
     this.getNetworkDetails_();
   },
 
-  /** @private */
-  close_: function() {
+  close: function() {
     this.guid = '';
     // Delay navigating to allow other subpages to load first.
     requestAnimationFrame(() => settings.navigateToPreviousRoute());
@@ -375,7 +374,7 @@
             'Unexpected networkingPrivate.getManagedProperties error: ' +
             message + ' For: ' + this.guid);
       }
-      this.close_();
+      this.close();
       return;
     }
 
@@ -386,14 +385,14 @@
 
     if (!properties) {
       console.error('No properties for: ' + this.guid);
-      this.close_();
+      this.close();
       return;
     }
 
     // Detail page should not be shown when Arc VPN is not connected.
     if (this.isArcVpn_(properties) && !this.isConnectedState_(properties)) {
       this.guid = '';
-      this.close_();
+      this.close();
     }
 
     this.networkProperties_ = properties;
@@ -411,7 +410,7 @@
       // If |state| is null, the network is no longer visible, close this.
       console.error('Network no longer exists: ' + this.guid);
       this.networkProperties_ = undefined;
-      this.close_();
+      this.close();
       return;
     }
     this.networkProperties_ = {
@@ -850,7 +849,7 @@
   onForgetTap_: function() {
     this.networkingPrivate.forgetNetwork(this.guid);
     // A forgotten network no longer has a valid GUID, close the subpage.
-    this.close_();
+    this.close();
   },
 
   /** @private */
diff --git a/chrome/browser/resources/settings/internet_page/internet_page.js b/chrome/browser/resources/settings/internet_page/internet_page.js
index 827a46e..ddf0927 100644
--- a/chrome/browser/resources/settings/internet_page/internet_page.js
+++ b/chrome/browser/resources/settings/internet_page/internet_page.js
@@ -269,9 +269,15 @@
         element = subPage.$$('#networkList');
       }
     } else if (this.detailType_) {
-      element = this.$$('network-summary')
-                    .$$(`#${this.detailType_}`)
-                    .$$('.subpage-arrow button');
+      const rowForDetailType =
+          this.$$('network-summary').$$(`#${this.detailType_}`);
+
+      // Note: It is possible that the row is no longer present in the DOM
+      // (e.g., when a Cellular dongle is unplugged or when Instant Tethering
+      // becomes unavailable due to the Bluetooth controller disconnecting).
+      if (rowForDetailType) {
+        element = rowForDetailType.$$('.subpage-arrow button');
+      }
     }
     if (element) {
       this.focusConfig_.set(oldRoute.path, element);
@@ -401,6 +407,17 @@
     if (this.managedNetworkAvailable != managedNetworkAvailable) {
       this.managedNetworkAvailable = managedNetworkAvailable;
     }
+
+    if (this.detailType_ && !this.deviceStates[this.detailType_]) {
+      // If the device type associated with the current network has been
+      // removed (e.g., due to unplugging a Cellular dongle), the details page,
+      // if visible, displays controls which are no longer functional. If this
+      // case occurs, close the details page.
+      const detailPage = this.$$('settings-internet-detail-page');
+      if (detailPage) {
+        detailPage.close();
+      }
+    }
   },
 
   /**
diff --git a/chrome/browser/resources/settings/printing_page/BUILD.gn b/chrome/browser/resources/settings/printing_page/BUILD.gn
index 5452ef3..e3766e3 100644
--- a/chrome/browser/resources/settings/printing_page/BUILD.gn
+++ b/chrome/browser/resources/settings/printing_page/BUILD.gn
@@ -9,8 +9,8 @@
     ":cloud_printers",
     ":cups_add_printer_dialog",
     ":cups_add_printer_dialog_elements",
-    ":cups_add_printer_dialog_util",
     ":cups_edit_printer_dialog",
+    ":cups_printer_dialog_util",
     ":cups_printers",
     ":cups_printers_browser_proxy",
     ":cups_printers_list",
@@ -41,7 +41,7 @@
   ]
 }
 
-js_library("cups_add_printer_dialog_util") {
+js_library("cups_printer_dialog_util") {
   deps = [
     "//ui/webui/resources/js:cr",
   ]
diff --git a/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog.html b/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog.html
index 37bd6416..437d19c 100644
--- a/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog.html
+++ b/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog.html
@@ -8,7 +8,7 @@
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html">
 <link rel="import" href="../i18n_setup.html">
 <link rel="import" href="cups_add_printer_dialog_elements.html">
-<link rel="import" href="cups_add_printer_dialog_util.html">
+<link rel="import" href="cups_printer_dialog_util.html">
 <link rel="import" href="cups_printer_shared_css.html">
 <link rel="import" href="cups_printers_browser_proxy.html">
 <link rel="import" href="cups_set_manufacturer_model_behavior.html">
@@ -42,7 +42,8 @@
         <add-printer-list printers="[[discoveredPrinters]]"
             selected-printer="{{selectedPrinter}}">
         </add-printer-list>
-        <div class="center" id="noPrinterMessage" hidden>
+        <div class="center" id="noPrinterMessage"
+            hidden="[[discoveredPrinters.length]]">
            $i18n{noPrinterNearbyMessage}
         </div>
         <div id="searchSpinner" hidden="[[!discovering_]]">
diff --git a/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog.js b/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog.js
index 8864868..138fed2 100644
--- a/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog.js
+++ b/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog.js
@@ -111,7 +111,6 @@
   onPrinterDiscoveryDone_: function() {
     this.discovering_ = false;
     this.$$('add-printer-list').style.maxHeight = kPrinterListFullHeight + 'px';
-    this.$.noPrinterMessage.hidden = !!this.discoveredPrinters.length;
 
     if (!this.discoveredPrinters.length) {
       this.selectedPrinter = getEmptyPrinter_();
diff --git a/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog_util.html b/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog_util.html
deleted file mode 100644
index c7d68b6b..0000000
--- a/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog_util.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="cups_add_printer_dialog_util.js"></script>
diff --git a/chrome/browser/resources/settings/printing_page/cups_edit_printer_dialog.html b/chrome/browser/resources/settings/printing_page/cups_edit_printer_dialog.html
index 4c86af0c..7086f2d50 100644
--- a/chrome/browser/resources/settings/printing_page/cups_edit_printer_dialog.html
+++ b/chrome/browser/resources/settings/printing_page/cups_edit_printer_dialog.html
@@ -5,7 +5,7 @@
 <link rel="import" href="chrome://resources/cr_elements/cr_searchable_drop_down/cr_searchable_drop_down.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html">
 <link rel="import" href="cups_add_printer_dialog_elements.html">
-<link rel="import" href="cups_add_printer_dialog_util.html">
+<link rel="import" href="cups_printer_dialog_util.html">
 <link rel="import" href="cups_printer_shared_css.html">
 <link rel="import" href="cups_printers_browser_proxy.html">
 <link rel="import" href="cups_set_manufacturer_model_behavior.html">
diff --git a/chrome/browser/resources/settings/printing_page/cups_printer_dialog_util.html b/chrome/browser/resources/settings/printing_page/cups_printer_dialog_util.html
new file mode 100644
index 0000000..061146b
--- /dev/null
+++ b/chrome/browser/resources/settings/printing_page/cups_printer_dialog_util.html
@@ -0,0 +1,2 @@
+<link rel="import" href="chrome://resources/html/cr.html">
+<script src="cups_printer_dialog_util.js"></script>
diff --git a/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog_util.js b/chrome/browser/resources/settings/printing_page/cups_printer_dialog_util.js
similarity index 100%
rename from chrome/browser/resources/settings/printing_page/cups_add_printer_dialog_util.js
rename to chrome/browser/resources/settings/printing_page/cups_printer_dialog_util.js
diff --git a/chrome/browser/resources/settings/settings_resources.grd b/chrome/browser/resources/settings/settings_resources.grd
index 029c2ee..30c729b 100644
--- a/chrome/browser/resources/settings/settings_resources.grd
+++ b/chrome/browser/resources/settings/settings_resources.grd
@@ -928,11 +928,11 @@
         <structure name="IDR_SETTINGS_CUPS_ADD_PRINTER_DIALOG_ELEMENTS_JS"
                    file="printing_page/cups_add_printer_dialog_elements.js"
                    type="chrome_html" />
-        <structure name="IDR_SETTINGS_CUPS_ADD_PRINTER_DIALOG_UTIL_HTML"
-                   file="printing_page/cups_add_printer_dialog_util.html"
+        <structure name="IDR_SETTINGS_CUPS_PRINTER_DIALOG_UTIL_HTML"
+                   file="printing_page/cups_printer_dialog_util.html"
                    type="chrome_html" />
-        <structure name="IDR_SETTINGS_CUPS_ADD_PRINTER_DIALOG_UTIL_JS"
-                   file="printing_page/cups_add_printer_dialog_util.js"
+        <structure name="IDR_SETTINGS_CUPS_PRINTER_DIALOG_UTIL_JS"
+                   file="printing_page/cups_printer_dialog_util.js"
                    type="chrome_html" />
       </if>
       <if expr="not chromeos">
diff --git a/chrome/browser/sync/chrome_sync_client.cc b/chrome/browser/sync/chrome_sync_client.cc
index ea72d5f4..72997ff7 100644
--- a/chrome/browser/sync/chrome_sync_client.cc
+++ b/chrome/browser/sync/chrome_sync_client.cc
@@ -189,8 +189,7 @@
 
   component_factory_ = std::make_unique<ProfileSyncComponentsFactoryImpl>(
       this, chrome::GetChannel(), prefs::kSavingBrowserHistoryDisabled,
-      base::CreateSingleThreadTaskRunnerWithTraits(
-          {content::BrowserThread::UI}),
+      base::CreateSequencedTaskRunnerWithTraits({content::BrowserThread::UI}),
       web_data_service_thread_, profile_web_data_service_,
       account_web_data_service_, password_store_,
       BookmarkSyncServiceFactory::GetForProfile(profile_));
@@ -421,7 +420,7 @@
   } else {
     controllers.push_back(std::make_unique<AsyncDirectoryTypeController>(
         syncer::APP_LIST, dump_stack, sync_service, this, syncer::GROUP_UI,
-        base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::UI})));
+        base::CreateSequencedTaskRunnerWithTraits({BrowserThread::UI})));
   }
 #endif  // BUILDFLAG(ENABLE_APP_LIST)
 
@@ -438,7 +437,7 @@
     } else {
       controllers.push_back(std::make_unique<AsyncDirectoryTypeController>(
           syncer::DICTIONARY, dump_stack, sync_service, this, syncer::GROUP_UI,
-          base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::UI})));
+          base::CreateSequencedTaskRunnerWithTraits({BrowserThread::UI})));
     }
   }
 #endif  // defined(OS_LINUX) || defined(OS_WIN)
diff --git a/chrome/browser/sync/chrome_sync_client.h b/chrome/browser/sync/chrome_sync_client.h
index c89058c..e4891d619 100644
--- a/chrome/browser/sync/chrome_sync_client.h
+++ b/chrome/browser/sync/chrome_sync_client.h
@@ -9,7 +9,7 @@
 #include <vector>
 
 #include "base/macros.h"
-#include "base/single_thread_task_runner.h"
+#include "base/sequenced_task_runner.h"
 #include "chrome/browser/sync/glue/extensions_activity_monitor.h"
 #include "components/browser_sync/browser_sync_client.h"
 #include "components/sync/model/model_type_store_service.h"
@@ -77,7 +77,7 @@
   scoped_refptr<password_manager::PasswordStore> password_store_;
 
   // The task runner for the |web_data_service_|, if any.
-  scoped_refptr<base::SingleThreadTaskRunner> web_data_service_thread_;
+  scoped_refptr<base::SequencedTaskRunner> web_data_service_thread_;
 
   // Generates and monitors the ExtensionsActivity object used by sync.
   ExtensionsActivityMonitor extensions_activity_monitor_;
diff --git a/chrome/browser/themes/browser_theme_pack.cc b/chrome/browser/themes/browser_theme_pack.cc
index cc07437..a97b211 100644
--- a/chrome/browser/themes/browser_theme_pack.cc
+++ b/chrome/browser/themes/browser_theme_pack.cc
@@ -241,19 +241,20 @@
     {"background_tab_incognito", TP::COLOR_BACKGROUND_TAB_INCOGNITO},
     {"background_tab_incognito_inactive",
      TP::COLOR_BACKGROUND_TAB_INCOGNITO_INACTIVE},
-    {"toolbar", TP::COLOR_TOOLBAR},
-    {"tab_text", TP::COLOR_TAB_TEXT},
+    {"bookmark_text", TP::COLOR_BOOKMARK_TEXT},
+    {"button_background", TP::COLOR_CONTROL_BUTTON_BACKGROUND},
     {"tab_background_text", TP::COLOR_BACKGROUND_TAB_TEXT},
     {"tab_background_text_inactive", TP::COLOR_BACKGROUND_TAB_TEXT_INACTIVE},
     {"tab_background_text_incognito", TP::COLOR_BACKGROUND_TAB_TEXT_INCOGNITO},
     {"tab_background_text_incognito_inactive",
      TP::COLOR_BACKGROUND_TAB_TEXT_INCOGNITO_INACTIVE},
-    {"bookmark_text", TP::COLOR_BOOKMARK_TEXT},
+    {"tab_text", TP::COLOR_TAB_TEXT},
+    {"toolbar", TP::COLOR_TOOLBAR},
+    {"toolbar_button_icon", TP::COLOR_TOOLBAR_BUTTON_ICON},
     {"ntp_background", TP::COLOR_NTP_BACKGROUND},
-    {"ntp_text", TP::COLOR_NTP_TEXT},
-    {"ntp_link", TP::COLOR_NTP_LINK},
     {"ntp_header", TP::COLOR_NTP_HEADER},
-    {"button_background", TP::COLOR_BUTTON_BACKGROUND},
+    {"ntp_link", TP::COLOR_NTP_LINK},
+    {"ntp_text", TP::COLOR_NTP_TEXT},
 };
 constexpr size_t kOverwritableColorTableLength =
     base::size(kOverwritableColorTable);
@@ -1525,7 +1526,7 @@
 
   SkColor button_bg_color;
   SkAlpha button_bg_alpha = SK_AlphaTRANSPARENT;
-  if (GetColor(TP::COLOR_BUTTON_BACKGROUND, &button_bg_color))
+  if (GetColor(TP::COLOR_CONTROL_BUTTON_BACKGROUND, &button_bg_color))
     button_bg_alpha = SkColorGetA(button_bg_color);
 
   button_bg_alpha =
diff --git a/chrome/browser/themes/browser_theme_pack_unittest.cc b/chrome/browser/themes/browser_theme_pack_unittest.cc
index 0c8a8b3f..b0595bc0 100644
--- a/chrome/browser/themes/browser_theme_pack_unittest.cc
+++ b/chrome/browser/themes/browser_theme_pack_unittest.cc
@@ -136,7 +136,7 @@
 
   // For the rest, use default colors.
   for (int i = TP::COLOR_FRAME_INCOGNITO_INACTIVE + 1;
-       i <= TP::COLOR_BUTTON_BACKGROUND; ++i) {
+       i <= TP::COLOR_CONTROL_BUTTON_BACKGROUND; ++i) {
     colors[i] = GetDefaultColor(i);
   }
 
@@ -939,7 +939,7 @@
 
   SkColor button_bg_color;
   const bool has_button_bg_color =
-      pack->GetColor(TP::COLOR_BUTTON_BACKGROUND, &button_bg_color);
+      pack->GetColor(TP::COLOR_CONTROL_BUTTON_BACKGROUND, &button_bg_color);
   ASSERT_TRUE(has_button_bg_color);
   SkAlpha button_bg_alpha = SkColorGetA(button_bg_color);
 
@@ -1130,3 +1130,16 @@
     EXPECT_TRUE(has_readable_contrast(toolbar_color));
   }
 }
+
+TEST_F(BrowserThemePackTest, TestToolbarButtonColor) {
+  scoped_refptr<BrowserThemePack> pack(
+      new BrowserThemePack(CustomThemeSupplier::ThemeType::EXTENSION));
+  BuildTestExtensionTheme("theme_test_toolbar_button_color", pack.get());
+
+  SkColor button_color;
+  EXPECT_TRUE(pack->GetColor(TP::COLOR_TOOLBAR_BUTTON_ICON, &button_color));
+  EXPECT_EQ(button_color, SkColorSetRGB(255, 0, 0));
+
+  color_utils::HSL hsl;
+  EXPECT_TRUE(pack->GetTint(TP::TINT_BUTTONS, &hsl));
+}
diff --git a/chrome/browser/themes/theme_properties.cc b/chrome/browser/themes/theme_properties.cc
index 77e8566..065a69d 100644
--- a/chrome/browser/themes/theme_properties.cc
+++ b/chrome/browser/themes/theme_properties.cc
@@ -230,13 +230,16 @@
       return kDefaultColorNTPLink;
     case COLOR_NTP_HEADER:
       return SkColorSetRGB(0x96, 0x96, 0x96);
-    case COLOR_BUTTON_BACKGROUND:
+    case COLOR_CONTROL_BUTTON_BACKGROUND:
       return SK_ColorTRANSPARENT;
+    case COLOR_TOOLBAR_BUTTON_ICON:
+      // If color is not explicitly specified, it should be calculated from
+      // TINT_BUTTONS.
+      NOTREACHED();
+      return gfx::kPlaceholderColor;
 
     // Properties not stored in theme pack.
     case COLOR_TAB_CLOSE_BUTTON_ACTIVE:
-    case COLOR_TOOLBAR_BUTTON_ICON:
-      return gfx::kChromeIconGrey;
     case COLOR_TAB_CLOSE_BUTTON_INACTIVE:
     case COLOR_TAB_ALERT_AUDIO:
       return gfx::kChromeIconGrey;
diff --git a/chrome/browser/themes/theme_properties.h b/chrome/browser/themes/theme_properties.h
index 35a8f3be..e10916a 100644
--- a/chrome/browser/themes/theme_properties.h
+++ b/chrome/browser/themes/theme_properties.h
@@ -49,7 +49,8 @@
     COLOR_NTP_TEXT,
     COLOR_NTP_LINK,
     COLOR_NTP_HEADER,
-    COLOR_BUTTON_BACKGROUND,
+    COLOR_CONTROL_BUTTON_BACKGROUND,
+    COLOR_TOOLBAR_BUTTON_ICON,
 
     TINT_BUTTONS,
     TINT_FRAME,
@@ -95,8 +96,6 @@
     // contents.
     COLOR_TOOLBAR_CONTENT_AREA_SEPARATOR,
 
-    // The color of a normal toolbar button's icon.
-    COLOR_TOOLBAR_BUTTON_ICON,
     // The color of a disabled toolbar button's icon.
     COLOR_TOOLBAR_BUTTON_ICON_INACTIVE,
 
diff --git a/chrome/browser/themes/theme_service.cc b/chrome/browser/themes/theme_service.cc
index 4dd33763..52fe12b 100644
--- a/chrome/browser/themes/theme_service.cc
+++ b/chrome/browser/themes/theme_service.cc
@@ -218,6 +218,18 @@
 bool ThemeService::BrowserThemeProvider::HasCustomColor(int id) const {
   DefaultScope scope(*this);
   bool has_custom_color = false;
+
+  // COLOR_TOOLBAR_BUTTON_ICON has custom value if it is explicitly specified or
+  // calclated from non {-1, -1, -1} tint (means "no change"). Note that, tint
+  // can have a value other than {-1, -1, -1} even if it is not explicitly
+  // specified (e.g incognito and dark mode).
+  if (id == ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON) {
+    theme_service_.GetColor(id, incognito_, &has_custom_color);
+    color_utils::HSL hsl =
+        theme_service_.GetTint(ThemeProperties::TINT_BUTTONS, incognito_);
+    return has_custom_color || (hsl.h != -1 || hsl.s != -1 || hsl.l != -1);
+  }
+
   theme_service_.GetColor(id, incognito_, &has_custom_color);
   return has_custom_color;
 }
diff --git a/chrome/browser/themes/theme_service_unittest.cc b/chrome/browser/themes/theme_service_unittest.cc
index 1c70249..0671b79 100644
--- a/chrome/browser/themes/theme_service_unittest.cc
+++ b/chrome/browser/themes/theme_service_unittest.cc
@@ -60,12 +60,18 @@
 
   // Moves a minimal theme to |temp_dir_path| and unpacks it from that
   // directory.
-  std::string LoadUnpackedThemeAt(const base::FilePath& temp_dir) {
+  std::string LoadUnpackedMinimalThemeAt(const base::FilePath& temp_dir) {
+    return LoadUnpackedTheme(temp_dir,
+                             "extensions/theme_minimal/manifest.json");
+  }
+
+  std::string LoadUnpackedTheme(const base::FilePath& temp_dir,
+                                const std::string source_file_path) {
     base::FilePath dst_manifest_path = temp_dir.AppendASCII("manifest.json");
     base::FilePath test_data_dir;
     EXPECT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir));
     base::FilePath src_manifest_path =
-        test_data_dir.AppendASCII("extensions/theme_minimal/manifest.json");
+        test_data_dir.AppendASCII(source_file_path);
     EXPECT_TRUE(base::CopyFile(src_manifest_path, dst_manifest_path));
 
     scoped_refptr<extensions::UnpackedInstaller> installer(
@@ -140,7 +146,8 @@
 
   base::ScopedTempDir temp_dir;
   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
-  const std::string& extension_id = LoadUnpackedThemeAt(temp_dir.GetPath());
+  const std::string& extension_id =
+      LoadUnpackedMinimalThemeAt(temp_dir.GetPath());
   EXPECT_FALSE(theme_service->UsingDefaultTheme());
   EXPECT_EQ(extension_id, theme_service->GetThemeID());
 
@@ -165,7 +172,8 @@
   ASSERT_TRUE(temp_dir2.CreateUniqueTempDir());
 
   // 1) Installing a theme should disable the previously active theme.
-  const std::string& extension1_id = LoadUnpackedThemeAt(temp_dir1.GetPath());
+  const std::string& extension1_id =
+      LoadUnpackedMinimalThemeAt(temp_dir1.GetPath());
   EXPECT_FALSE(theme_service->UsingDefaultTheme());
   EXPECT_EQ(extension1_id, theme_service->GetThemeID());
   EXPECT_TRUE(service_->IsExtensionEnabled(extension1_id));
@@ -173,7 +181,8 @@
   // Show an infobar to prevent the current theme from being uninstalled.
   theme_service->OnInfobarDisplayed();
 
-  const std::string& extension2_id = LoadUnpackedThemeAt(temp_dir2.GetPath());
+  const std::string& extension2_id =
+      LoadUnpackedMinimalThemeAt(temp_dir2.GetPath());
   EXPECT_EQ(extension2_id, theme_service->GetThemeID());
   EXPECT_TRUE(service_->IsExtensionEnabled(extension2_id));
   EXPECT_TRUE(registry_->GetExtensionById(extension1_id,
@@ -225,8 +234,10 @@
   base::ScopedTempDir temp_dir2;
   ASSERT_TRUE(temp_dir2.CreateUniqueTempDir());
 
-  const std::string& extension1_id = LoadUnpackedThemeAt(temp_dir1.GetPath());
-  const std::string& extension2_id = LoadUnpackedThemeAt(temp_dir2.GetPath());
+  const std::string& extension1_id =
+      LoadUnpackedMinimalThemeAt(temp_dir1.GetPath());
+  const std::string& extension2_id =
+      LoadUnpackedMinimalThemeAt(temp_dir2.GetPath());
 
   // Test the initial state.
   EXPECT_TRUE(registry_->GetExtensionById(extension1_id,
@@ -293,7 +304,7 @@
 
   base::ScopedTempDir temp_dir;
   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
-  LoadUnpackedThemeAt(temp_dir.GetPath());
+  LoadUnpackedMinimalThemeAt(temp_dir.GetPath());
 
   // Should get a new color after installing a theme.
   EXPECT_NE(ThemeService::GetThemeProviderForProfile(profile_.get())
@@ -306,6 +317,46 @@
             default_toolbar_color);
 }
 
+TEST_F(ThemeServiceTest, GetColorForToolbarButton) {
+  ThemeService* theme_service =
+      ThemeServiceFactory::GetForProfile(profile_.get());
+  theme_service->UseDefaultTheme();
+  // Let the ThemeService uninstall unused themes.
+  base::RunLoop().RunUntilIdle();
+
+  const ui::ThemeProvider& theme_provider =
+      ThemeService::GetThemeProviderForProfile(profile_.get());
+  SkColor default_toolbar_button_color =
+      theme_provider.GetColor(ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON);
+  EXPECT_FALSE(theme_provider.HasCustomColor(
+      ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON));
+
+  base::ScopedTempDir temp_dir1;
+  ASSERT_TRUE(temp_dir1.CreateUniqueTempDir());
+  LoadUnpackedTheme(temp_dir1.GetPath(),
+                    "extensions/theme_test_toolbar_button_color/manifest.json");
+
+  // Should get a new color after installing a theme.
+  SkColor toolbar_button_explicit_color =
+      theme_provider.GetColor(ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON);
+  EXPECT_NE(toolbar_button_explicit_color, default_toolbar_button_color);
+  EXPECT_TRUE(theme_provider.HasCustomColor(
+      ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON));
+
+  base::ScopedTempDir temp_dir2;
+  ASSERT_TRUE(temp_dir2.CreateUniqueTempDir());
+  LoadUnpackedTheme(temp_dir2.GetPath(),
+                    "extensions/theme_test_toolbar_button_tint/manifest.json");
+
+  // Should get the color based on a tint.
+  SkColor toolbar_button_tinted_color =
+      theme_provider.GetColor(ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON);
+  EXPECT_NE(toolbar_button_tinted_color, default_toolbar_button_color);
+  EXPECT_NE(toolbar_button_tinted_color, toolbar_button_explicit_color);
+  EXPECT_TRUE(theme_provider.HasCustomColor(
+      ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON));
+}
+
 namespace {
 
 // NotificationObserver which emulates an infobar getting destroyed when the
@@ -351,7 +402,8 @@
   base::ScopedTempDir temp_dir2;
   ASSERT_TRUE(temp_dir2.CreateUniqueTempDir());
 
-  const std::string& extension1_id = LoadUnpackedThemeAt(temp_dir1.GetPath());
+  const std::string& extension1_id =
+      LoadUnpackedMinimalThemeAt(temp_dir1.GetPath());
   ASSERT_EQ(extension1_id, theme_service->GetThemeID());
 
   // Show an infobar.
@@ -362,7 +414,8 @@
   // itself as a result of the NOTIFICATION_BROWSER_THEME_CHANGED notification.
   {
     InfobarDestroyerOnThemeChange destroyer(profile_.get());
-    const std::string& extension2_id = LoadUnpackedThemeAt(temp_dir2.GetPath());
+    const std::string& extension2_id =
+        LoadUnpackedMinimalThemeAt(temp_dir2.GetPath());
     EXPECT_EQ(extension2_id, theme_service->GetThemeID());
   }
 
@@ -385,7 +438,8 @@
   // Set theme from data pack and then override it with theme from color.
   base::ScopedTempDir temp_dir1;
   ASSERT_TRUE(temp_dir1.CreateUniqueTempDir());
-  const std::string& extension1_id = LoadUnpackedThemeAt(temp_dir1.GetPath());
+  const std::string& extension1_id =
+      LoadUnpackedMinimalThemeAt(temp_dir1.GetPath());
   EXPECT_EQ(extension1_id, theme_service->GetThemeID());
   EXPECT_FALSE(theme_service->UsingAutogenerated());
   base::FilePath path =
@@ -443,7 +497,8 @@
 
   base::ScopedTempDir temp_dir;
   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
-  const std::string& extension_id = LoadUnpackedThemeAt(temp_dir.GetPath());
+  const std::string& extension_id =
+      LoadUnpackedMinimalThemeAt(temp_dir.GetPath());
   ASSERT_EQ(extension_id, theme_service->GetThemeID());
 
   // Set preference |prefs::kUsesSystemTheme| to true which conflicts with
diff --git a/chrome/browser/translate/chrome_translate_client.cc b/chrome/browser/translate/chrome_translate_client.cc
index 3092036d..2cf16df 100644
--- a/chrome/browser/translate/chrome_translate_client.cc
+++ b/chrome/browser/translate/chrome_translate_client.cc
@@ -18,7 +18,6 @@
 #include "chrome/browser/language/language_model_manager_factory.h"
 #include "chrome/browser/language/url_language_histogram_factory.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/search_engines/template_url_service_factory.h"
 #include "chrome/browser/sync/user_event_service_factory.h"
 #include "chrome/browser/translate/translate_accept_languages_factory.h"
 #include "chrome/browser/translate/translate_ranker_factory.h"
@@ -120,8 +119,6 @@
     : content::WebContentsObserver(web_contents),
       translate_driver_(
           &web_contents->GetController(),
-          TemplateURLServiceFactory::GetForProfile(
-              Profile::FromBrowserContext(web_contents->GetBrowserContext())),
           UrlLanguageHistogramFactory::GetForBrowserContext(
               web_contents->GetBrowserContext())),
       translate_manager_(new translate::TranslateManager(
diff --git a/chrome/browser/translate/translate_manager_browsertest.cc b/chrome/browser/translate/translate_manager_browsertest.cc
index 7c870bd..b5aae4b 100644
--- a/chrome/browser/translate/translate_manager_browsertest.cc
+++ b/chrome/browser/translate/translate_manager_browsertest.cc
@@ -20,8 +20,6 @@
 #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/search_engines/template_url.h"
-#include "components/search_engines/template_url_service.h"
 #include "components/translate/core/browser/translate_accept_languages.h"
 #include "components/translate/core/browser/translate_error_details.h"
 #include "components/translate/core/common/language_detection_details.h"
@@ -29,6 +27,7 @@
 #include "content/public/browser/notification_service.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test_utils.h"
+#include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/http_request.h"
 #include "net/test/embedded_test_server/http_response.h"
 #include "url/gurl.h"
@@ -191,6 +190,8 @@
 
 }  // namespace
 
+namespace translate {
+
 class TranslateManagerBrowserTest : public InProcessBrowserTest {
  public:
   TranslateManagerBrowserTest() {
@@ -248,6 +249,7 @@
     ResetObserver();
     error_type_ = translate::TranslateErrors::NONE;
 
+    host_resolver()->AddRule("www.google.com", "127.0.0.1");
     embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
         &TranslateManagerBrowserTest::HandleRequest, base::Unretained(this)));
     embedded_test_server()->StartAcceptingConnections();
@@ -257,7 +259,7 @@
 
     // Enable Experimental web platform features for HrefTranslate tests
     command_line->AppendSwitch(
-        switches::kEnableExperimentalWebPlatformFeatures);
+        ::switches::kEnableExperimentalWebPlatformFeatures);
 
     command_line->AppendSwitchASCII(
         translate::switches::kTranslateScriptURL,
@@ -271,21 +273,6 @@
 
   void SetTranslateScript(const std::string& script) { script_ = script; }
 
-  void SetupDefaultSearchEngine(const std::string& base_url) {
-    base::ScopedAllowBlockingForTesting allow_blocking;
-    TemplateURLData data;
-    data.SetShortName(base::UTF8ToUTF16(base_url));
-    data.SetKeyword(base::UTF8ToUTF16(base_url));
-    data.SetURL(base_url + "url?bar={searchTerms}");
-
-    TemplateURLService* template_url_service =
-        TemplateURLServiceFactory::GetForProfile(browser()->profile());
-    search_test_utils::WaitForTemplateURLServiceToLoad(template_url_service);
-    TemplateURL* template_url =
-        template_url_service->Add(std::make_unique<TemplateURL>(data));
-    template_url_service->SetUserSelectedDefaultSearchProvider(template_url);
-  }
-
  private:
   translate::TranslateErrors::Type error_type_;
 
@@ -426,7 +413,6 @@
   chrome_translate_client->GetTranslateManager()->SetIgnoreMissingKeyForTesting(
       true);
   SetTranslateScript(kTestValidScript);
-  SetupDefaultSearchEngine(embedded_test_server()->base_url().spec());
 
   // There is a possible race condition, when the language is not yet detected,
   // so we check for that and wait if necessary.
@@ -438,9 +424,10 @@
 
   ResetObserver();
   // Load a German page and detect it's language
-  AddTabAtIndex(
-      0, GURL(embedded_test_server()->GetURL("/href_translate_test.html")),
-      ui::PAGE_TRANSITION_TYPED);
+  AddTabAtIndex(0,
+                GURL(embedded_test_server()->GetURL(
+                    "www.google.com", "/href_translate_test.html")),
+                ui::PAGE_TRANSITION_TYPED);
   chrome_translate_client = GetChromeTranslateClient();
   WaitUntilLanguageDetected();
   EXPECT_EQ("de",
@@ -472,13 +459,13 @@
 }
 
 // Test that hrefTranslate doesn't auto-translate if the originator of the
-// navigation isn't the default search engine.
-IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest, HrefTranslateWrongDSE) {
+// navigation isn't a Google origin.
+IN_PROC_BROWSER_TEST_F(TranslateManagerBrowserTest,
+                       HrefTranslateNotFromGoogle) {
   ChromeTranslateClient* chrome_translate_client = GetChromeTranslateClient();
   chrome_translate_client->GetTranslateManager()->SetIgnoreMissingKeyForTesting(
       true);
   SetTranslateScript(kTestValidScript);
-  SetupDefaultSearchEngine("https://www.google.com/");
 
   // There is a possible race condition, when the language is not yet detected,
   // so we check for that and wait if necessary.
@@ -521,7 +508,6 @@
   chrome_translate_client->GetTranslateManager()->SetIgnoreMissingKeyForTesting(
       true);
   SetTranslateScript(kTestValidScript);
-  SetupDefaultSearchEngine(embedded_test_server()->base_url().spec());
 
   // There is a possible race condition, when the language is not yet detected,
   // so we check for that and wait if necessary.
@@ -533,9 +519,10 @@
 
   ResetObserver();
   // Load a German page and detect it's language
-  AddTabAtIndex(
-      0, GURL(embedded_test_server()->GetURL("/href_translate_test.html")),
-      ui::PAGE_TRANSITION_TYPED);
+  AddTabAtIndex(0,
+                GURL(embedded_test_server()->GetURL(
+                    "www.google.com", "/href_translate_test.html")),
+                ui::PAGE_TRANSITION_TYPED);
   chrome_translate_client = GetChromeTranslateClient();
   WaitUntilLanguageDetected();
   EXPECT_EQ("de",
@@ -567,7 +554,6 @@
   chrome_translate_client->GetTranslateManager()->SetIgnoreMissingKeyForTesting(
       true);
   SetTranslateScript(kTestValidScript);
-  SetupDefaultSearchEngine(embedded_test_server()->base_url().spec());
 
   // There is a possible race condition, when the language is not yet detected,
   // so we check for that and wait if necessary.
@@ -579,9 +565,10 @@
 
   ResetObserver();
   // Load a German page and detect it's language
-  AddTabAtIndex(
-      0, GURL(embedded_test_server()->GetURL("/href_translate_test.html")),
-      ui::PAGE_TRANSITION_TYPED);
+  AddTabAtIndex(0,
+                GURL(embedded_test_server()->GetURL(
+                    "www.google.com", "/href_translate_test.html")),
+                ui::PAGE_TRANSITION_TYPED);
   chrome_translate_client = GetChromeTranslateClient();
   WaitUntilLanguageDetected();
   EXPECT_EQ("de",
@@ -614,7 +601,6 @@
   chrome_translate_client->GetTranslateManager()->SetIgnoreMissingKeyForTesting(
       true);
   SetTranslateScript(kTestValidScript);
-  SetupDefaultSearchEngine(embedded_test_server()->base_url().spec());
 
   // There is a possible race condition, when the language is not yet detected,
   // so we check for that and wait if necessary.
@@ -626,9 +612,10 @@
 
   ResetObserver();
   // Load a German page and detect it's language
-  AddTabAtIndex(
-      0, GURL(embedded_test_server()->GetURL("/href_translate_test.html")),
-      ui::PAGE_TRANSITION_TYPED);
+  AddTabAtIndex(0,
+                GURL(embedded_test_server()->GetURL(
+                    "www.google.com", "/href_translate_test.html")),
+                ui::PAGE_TRANSITION_TYPED);
   chrome_translate_client = GetChromeTranslateClient();
   WaitUntilLanguageDetected();
   EXPECT_EQ("de",
@@ -998,7 +985,6 @@
   EXPECT_EQ("ru", manager->GetLanguageState().GetPredefinedTargetLanguage());
 
   SetTranslateScript(kTestValidScript);
-  SetupDefaultSearchEngine(embedded_test_server()->base_url().spec());
 
   // There is a possible race condition, when the language is not yet detected,
   // so we check for that and wait if necessary.
@@ -1010,9 +996,10 @@
 
   ResetObserver();
   // Load a German page and detect it's language
-  AddTabAtIndex(
-      0, GURL(embedded_test_server()->GetURL("/href_translate_test.html")),
-      ui::PAGE_TRANSITION_TYPED);
+  AddTabAtIndex(0,
+                GURL(embedded_test_server()->GetURL(
+                    "www.google.com", "/href_translate_test.html")),
+                ui::PAGE_TRANSITION_TYPED);
   chrome_translate_client = GetChromeTranslateClient();
   WaitUntilLanguageDetected();
   EXPECT_EQ("de",
@@ -1037,3 +1024,5 @@
   EXPECT_EQ("ja",
             chrome_translate_client->GetLanguageState().current_language());
 }
+
+}  // namespace translate
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index f29cbc5..063b3f3 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1062,6 +1062,8 @@
       "thumbnails/thumbnail_tab_helper.h",
       "thumbnails/thumbnail_utils.cc",
       "thumbnails/thumbnail_utils.h",
+      "thumbnails/thumbnail_web_contents_observer.cc",
+      "thumbnails/thumbnail_web_contents_observer.h",
       "toolbar/app_menu_icon_controller.cc",
       "toolbar/app_menu_icon_controller.h",
       "toolbar/app_menu_model.cc",
@@ -2726,6 +2728,8 @@
       "views/payments/error_message_view_controller.h",
       "views/payments/order_summary_view_controller.cc",
       "views/payments/order_summary_view_controller.h",
+      "views/payments/payment_handler_modal_dialog_manager_delegate.cc",
+      "views/payments/payment_handler_modal_dialog_manager_delegate.h",
       "views/payments/payment_handler_web_flow_view_controller.cc",
       "views/payments/payment_handler_web_flow_view_controller.h",
       "views/payments/payment_method_view_controller.cc",
diff --git a/chrome/browser/ui/thumbnails/thumbnail_image.cc b/chrome/browser/ui/thumbnails/thumbnail_image.cc
index d139e210..b71fd63d 100644
--- a/chrome/browser/ui/thumbnails/thumbnail_image.cc
+++ b/chrome/browser/ui/thumbnails/thumbnail_image.cc
@@ -66,11 +66,10 @@
 
   base::PostTaskWithTraitsAndReplyWithResult(
       FROM_HERE,
-      {base::TaskPriority::BEST_EFFORT,
+      {base::TaskPriority::USER_VISIBLE,
        base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
       base::BindOnce(&ThumbnailData::ToImageSkia, image_representation_),
       std::move(callback));
-
   return true;
 }
 
@@ -98,7 +97,7 @@
                                        CreateThumbnailCallback callback) {
   base::PostTaskWithTraitsAndReplyWithResult(
       FROM_HERE,
-      {base::TaskPriority::BEST_EFFORT,
+      {base::TaskPriority::USER_VISIBLE,
        base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
       base::BindOnce(&ThumbnailData::FromSkBitmap, bitmap),
       base::BindOnce(
diff --git a/chrome/browser/ui/thumbnails/thumbnail_tab_helper.cc b/chrome/browser/ui/thumbnails/thumbnail_tab_helper.cc
index ac77bea..06ff85b1 100644
--- a/chrome/browser/ui/thumbnails/thumbnail_tab_helper.cc
+++ b/chrome/browser/ui/thumbnails/thumbnail_tab_helper.cc
@@ -5,277 +5,223 @@
 #include "chrome/browser/ui/thumbnails/thumbnail_tab_helper.h"
 
 #include "base/bind.h"
-#include "base/feature_list.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/task/post_task.h"
 #include "base/task/task_traits.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/tabs/tab_style.h"
 #include "chrome/browser/ui/thumbnails/thumbnail_utils.h"
-#include "chrome/common/chrome_features.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
-#include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/render_view_host.h"
 #include "content/public/browser/render_widget_host.h"
 #include "content/public/browser/render_widget_host_view.h"
+#include "content/public/browser/web_contents.h"
 #include "third_party/skia/include/core/SkBitmap.h"
-#include "ui/gfx/color_utils.h"
 #include "ui/gfx/scrollbar_size.h"
 
 ThumbnailTabHelper::ThumbnailTabHelper(content::WebContents* contents)
-    : content::WebContentsObserver(contents) {}
+    : ThumbnailWebContentsObserver(contents),
+      view_is_visible_(contents->GetVisibility() ==
+                       content::Visibility::VISIBLE) {}
 
 ThumbnailTabHelper::~ThumbnailTabHelper() = default;
 
-void ThumbnailTabHelper::RenderWidgetHostVisibilityChanged(
-    content::RenderWidgetHost* widget_host,
-    bool became_visible) {
-  if (!became_visible)
-    TabHidden();
+void ThumbnailTabHelper::TopLevelNavigationStarted(const GURL& url) {
+  UpdateCurrentUrl(url);
 }
 
-void ThumbnailTabHelper::RenderWidgetHostDestroyed(
-    content::RenderWidgetHost* widget_host) {
-  observer_.Remove(widget_host);
+void ThumbnailTabHelper::TopLevelNavigationEnded(const GURL& url) {
+  UpdateCurrentUrl(url);
 }
 
-void ThumbnailTabHelper::RenderViewCreated(
-    content::RenderViewHost* render_view_host) {
-  StartWatchingRenderViewHost(render_view_host);
-}
-
-void ThumbnailTabHelper::RenderViewHostChanged(
-    content::RenderViewHost* old_host,
-    content::RenderViewHost* new_host) {
-  StopWatchingRenderViewHost(old_host);
-  StartWatchingRenderViewHost(new_host);
-}
-
-void ThumbnailTabHelper::RenderViewDeleted(
-    content::RenderViewHost* render_view_host) {
-  StopWatchingRenderViewHost(render_view_host);
-}
-
-void ThumbnailTabHelper::DidStartNavigation(
-    content::NavigationHandle* navigation_handle) {
-  if (!navigation_handle->IsInMainFrame() ||
-      navigation_handle->IsSameDocument()) {
-    return;
-  }
-
-  // At this point, the new navigation has just been started, but the
-  // WebContents still shows the previous page. Grab a thumbnail before it
-  // goes away.
-  StartThumbnailCaptureIfNecessary(TriggerReason::NAVIGATING_AWAY);
-
-  // Now reset navigation-related state. It's important that this happens after
-  // calling StartThumbnailCaptureIfNecessary.
-  did_navigation_finish_ = false;
-  has_received_document_since_navigation_finished_ = false;
-  has_painted_since_document_received_ = false;
-  // Reset the page transition to some uninteresting type, since the actual
-  // type isn't available at this point. We'll get it in DidFinishNavigation
-  // (if that happens, which isn't guaranteed).
-  page_transition_ = ui::PAGE_TRANSITION_LINK;
-}
-
-void ThumbnailTabHelper::DidFinishNavigation(
-    content::NavigationHandle* navigation_handle) {
-  if (!navigation_handle->HasCommitted() ||
-      !navigation_handle->IsInMainFrame() ||
-      navigation_handle->IsSameDocument()) {
-    return;
-  }
-  did_navigation_finish_ = true;
-  page_transition_ = navigation_handle->GetPageTransition();
-}
-
-void ThumbnailTabHelper::DocumentAvailableInMainFrame() {
-  // If there's currently a screen capture going on, ignore its result.
-  // Otherwise there's a risk that we'll get a picture of the wrong page.
-  // Note: It *looks* like WebContentsObserver::DidFirstVisuallyNonEmptyPaint
-  // would be a better signal for this, but it uses a weird heuristic to detect
-  // "visually non empty" paints, so it might not be entirely safe.
-  waiting_for_capture_ = false;
-
-  // Mark that we got the document, unless we're in the middle of a navigation.
-  // In that case, this refers to the previous document, but we're tracking the
-  // state of the new one.
-  if (did_navigation_finish_) {
-    // From now on, we'll start watching for paint events.
-    has_received_document_since_navigation_finished_ = true;
+void ThumbnailTabHelper::UpdateCurrentUrl(const GURL& url) {
+  current_url_ = url;
+  if (current_url_ != thumbnail_url_ &&
+      thumbnail_state_ != ThumbnailState::kNoThumbnail) {
+    thumbnail_state_ = ThumbnailState::kNoThumbnail;
+    thumbnail_ = ThumbnailImage();
+    NotifyTabPreviewChanged();
   }
 }
 
-void ThumbnailTabHelper::DocumentOnLoadCompletedInMainFrame() {
-  // Usually, DocumentAvailableInMainFrame always gets called first, so this one
-  // shouldn't be necessary. However, DocumentAvailableInMainFrame is not fired
-  // for empty documents (i.e. about:blank), which are thus handled here.
-  DocumentAvailableInMainFrame();
+void ThumbnailTabHelper::PageLoadStarted(FrameContext frame_context) {
+  if (frame_context == FrameContext::kMainFrame)
+    is_loading_ = true;
+  ScheduleThumbnailCapture(CaptureSchedule::kDelayed);
 }
 
-void ThumbnailTabHelper::DidFirstVisuallyNonEmptyPaint() {
-  // If we haven't gotten the current document since navigating, then this paint
-  // refers to the *previous* document, so ignore it.
-  if (has_received_document_since_navigation_finished_) {
-    has_painted_since_document_received_ = true;
+void ThumbnailTabHelper::PageLoadFinished(FrameContext frame_context) {
+  if (frame_context == FrameContext::kMainFrame)
+    is_loading_ = false;
+  ScheduleThumbnailCapture(CaptureSchedule::kAttemptImmediate);
+}
+
+void ThumbnailTabHelper::PageUpdated(FrameContext frame_context) {
+  ScheduleThumbnailCapture(CaptureSchedule::kAttemptImmediate);
+}
+
+void ThumbnailTabHelper::VisibilityChanged(bool visible) {
+  // When the visibility of the current tab changes (most importantly, when the
+  // user is switching away from the current tab) we want to capture a snapshot
+  // of the tab to capture e.g. its scroll position, so that the preview will
+  // look like the tab did when the user last switched to/from it.
+  const bool was_visible = view_is_visible_;
+  view_is_visible_ = visible;
+  if (was_visible != visible) {
+    // Because it can take a moment for tabs to re-render, use a delay when
+    // returning to a tab, but capture immediately when switching away.
+    const CaptureSchedule schedule =
+        visible ? CaptureSchedule::kDelayed : CaptureSchedule::kImmediate;
+    ScheduleThumbnailCapture(schedule);
   }
 }
 
-void ThumbnailTabHelper::DidStartLoading() {
-  load_interrupted_ = false;
-}
-
-void ThumbnailTabHelper::NavigationStopped() {
-  // This function gets called when the page loading is interrupted by the
-  // stop button.
-  load_interrupted_ = true;
-}
-
-void ThumbnailTabHelper::StartWatchingRenderViewHost(
-    content::RenderViewHost* render_view_host) {
-  // We get notified whenever a new RenderView is created, which does not
-  // necessarily come with a new RenderViewHost, and there is no good way to get
-  // notifications of new RenderViewHosts only. So just be tolerant of
-  // re-registrations.
-  content::RenderWidgetHost* render_widget_host = render_view_host->GetWidget();
-  if (!observer_.IsObserving(render_widget_host))
-    observer_.Add(render_widget_host);
-}
-
-void ThumbnailTabHelper::StopWatchingRenderViewHost(
-    content::RenderViewHost* render_view_host) {
-  if (!render_view_host) {
-    return;
-  }
-
-  content::RenderWidgetHost* render_widget_host = render_view_host->GetWidget();
-  if (observer_.IsObserving(render_widget_host))
-    observer_.Remove(render_widget_host);
-}
-
-void ThumbnailTabHelper::StartThumbnailCaptureIfNecessary(
-    TriggerReason trigger) {
+void ThumbnailTabHelper::ScheduleThumbnailCapture(CaptureSchedule schedule) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  // Don't take a screenshot if we haven't painted anything since the last
-  // navigation. This can happen when navigating away again very quickly.
-  if (!has_painted_since_document_received_) {
-    LogThumbnailingOutcome(trigger, Outcome::NOT_ATTEMPTED_NO_PAINT_YET);
+  if (schedule != CaptureSchedule::kImmediate && !view_is_visible_)
+    return;
+
+  constexpr base::TimeDelta kDelayTime = base::TimeDelta::FromMilliseconds(250);
+  constexpr base::TimeDelta kMinTimeBetweenCaptures =
+      base::TimeDelta::FromMilliseconds(500);
+
+  // We will do the capture either now or at some point in the future.
+  base::TimeDelta delay;
+  if (schedule == CaptureSchedule::kDelayed)
+    delay += kDelayTime;
+
+  // The time until the next scheduled capture, or the time since the most
+  // recent (durations in the past are negative).
+  const base::TimeDelta until_scheduled =
+      last_scheduled_capture_time_ - base::TimeTicks::Now();
+
+  // If we would schedule a non-immediate capture too close to an existing
+  // capture, push it out or discard it altogether.
+  if (schedule != CaptureSchedule::kImmediate &&
+      delay - until_scheduled < kMinTimeBetweenCaptures) {
+    if (until_scheduled > delay)
+      return;
+    delay = until_scheduled + kMinTimeBetweenCaptures;
+  }
+
+  last_scheduled_capture_time_ = base::TimeTicks::Now() + delay;
+
+  if (delay.is_zero()) {
+    StartThumbnailCapture(schedule);
     return;
   }
 
-  // Ignore thumbnail update requests if one is already in progress.
-  if (thumbnailing_in_progress_) {
-    LogThumbnailingOutcome(trigger, Outcome::NOT_ATTEMPTED_IN_PROGRESS);
+  base::PostDelayedTaskWithTraits(
+      FROM_HERE, {content::BrowserThread::UI},
+      base::BindOnce(&ThumbnailTabHelper::StartThumbnailCapture,
+                     weak_factory_.GetWeakPtr(), schedule),
+      delay);
+}
+
+void ThumbnailTabHelper::StartThumbnailCapture(CaptureSchedule schedule) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  CaptureInfo capture_info{web_contents()->GetVisibleURL(),
+                           is_loading_ ? ThumbnailState::kLoadInProgress
+                                       : ThumbnailState::kFinishedLoading};
+  DCHECK(!capture_info.url.is_empty());
+
+  if (!view_is_visible_ && schedule != CaptureSchedule::kImmediate)
     return;
-  }
 
   // Destroying a WebContents may trigger it to be hidden, prompting a snapshot
   // which would be unwise to attempt <http://crbug.com/130097>. If the
   // WebContents is in the middle of destruction, do not risk it.
-  if (!web_contents() || web_contents()->IsBeingDestroyed()) {
-    LogThumbnailingOutcome(trigger, Outcome::NOT_ATTEMPTED_NO_WEBCONTENTS);
+  if (!web_contents() || web_contents()->IsBeingDestroyed())
     return;
-  }
 
-  // Note: Do *not* use GetLastVisibleURL - it might already have been updated
-  // for a new pending navigation. The committed URL is the one corresponding
-  // to the currently visible content.
-  const GURL& url = web_contents()->GetLastCommittedURL();
-  if (!url.is_valid()) {
-    LogThumbnailingOutcome(trigger, Outcome::NOT_ATTEMPTED_NO_URL);
-    return;
-  }
+  base::TimeTicks start_time = base::TimeTicks::Now();
 
-  // Check if the thumbnail needs to be updated. If not, log and return.
+  content::RenderWidgetHostView* const source_view =
+      web_contents()->GetRenderViewHost()->GetWidget()->GetView();
 
-  content::RenderWidgetHost* render_widget_host =
-      web_contents()->GetRenderViewHost()->GetWidget();
-  content::RenderWidgetHostView* view = render_widget_host->GetView();
-  if (!view || !view->IsSurfaceAvailableForCopy()) {
-    LogThumbnailingOutcome(trigger, Outcome::NOT_ATTEMPTED_VIEW_NOT_AVAILABLE);
+  // If there's no view or the view isn't available right now, put off
+  // capturing.
+  if (!source_view || !source_view->IsSurfaceAvailableForCopy()) {
+    ScheduleThumbnailCapture(CaptureSchedule::kDelayed);
     return;
   }
 
   // Note: this is the size in pixels on-screen, not the size in DIPs.
-  gfx::Size source_size = view->GetViewBounds().size();
+  gfx::Size source_size = source_view->GetViewBounds().size();
   // Clip the pixels that will commonly hold a scrollbar, which looks bad in
   // thumbnails.
-  const float scale_factor = view->GetDeviceScaleFactor();
+  const float scale_factor = source_view->GetDeviceScaleFactor();
   const int scrollbar_size = gfx::scrollbar_size() * scale_factor;
   source_size.Enlarge(-scrollbar_size, -scrollbar_size);
 
-  if (source_size.IsEmpty()) {
-    LogThumbnailingOutcome(trigger, Outcome::NOT_ATTEMPTED_EMPTY_RECT);
+  if (source_size.IsEmpty())
     return;
-  }
-
-  thumbnailing_in_progress_ = true;
 
   const gfx::Size desired_size = TabStyle::GetPreviewImageSize();
   thumbnails::CanvasCopyInfo copy_info =
       thumbnails::GetCanvasCopyInfo(source_size, scale_factor, desired_size);
-  copy_from_surface_start_time_ = base::TimeTicks::Now();
-  waiting_for_capture_ = true;
-  view->CopyFromSurface(
+
+  source_view->CopyFromSurface(
       copy_info.copy_rect, copy_info.target_size,
-      base::BindOnce(&ThumbnailTabHelper::ProcessCapturedBitmap,
-                     weak_factory_.GetWeakPtr(), trigger));
+      base::BindOnce(&ThumbnailTabHelper::ProcessCapturedThumbnail,
+                     weak_factory_.GetWeakPtr(), capture_info, start_time));
 }
 
-void ThumbnailTabHelper::ProcessCapturedBitmap(TriggerReason trigger,
-                                               const SkBitmap& bitmap) {
-  // If |waiting_for_capture_| is false, that means something happened in the
-  // meantime which makes the captured image unsafe to use.
-  bool was_canceled = !waiting_for_capture_;
-  waiting_for_capture_ = false;
+void ThumbnailTabHelper::ProcessCapturedThumbnail(
+    const CaptureInfo& capture_info,
+    base::TimeTicks start_time,
+    const SkBitmap& bitmap) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  base::TimeDelta copy_from_surface_time =
-      base::TimeTicks::Now() - copy_from_surface_start_time_;
+  DCHECK(!capture_info.url.is_empty());
+  DCHECK(capture_info.target_state != ThumbnailState::kNoThumbnail);
+
+  const base::TimeTicks finish_time = base::TimeTicks::Now();
+  const base::TimeDelta copy_from_surface_time = finish_time - start_time;
   UMA_HISTOGRAM_TIMES("Thumbnails.CopyFromSurfaceTime", copy_from_surface_time);
 
-  if (!bitmap.drawsNothing() && !was_canceled) {
-    // On success, we must be on the UI thread.
-    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-    // From here on, nothing can fail, so log success.
-    LogThumbnailingOutcome(trigger, Outcome::SUCCESS);
-    thumbnail_ = ThumbnailImage::FromSkBitmap(bitmap);
-    web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB);
-  } else {
-    LogThumbnailingOutcome(
-        trigger, was_canceled ? Outcome::CANCELED : Outcome::READBACK_FAILED);
-  }
-  thumbnailing_in_progress_ = false;
-}
-
-void ThumbnailTabHelper::TabHidden() {
-  // Skip if a pending entry exists. TabHidden can be called while navigating
-  // pages and this is not a time when thumbnails should be generated.
-  if (!web_contents() || web_contents()->GetController().GetPendingEntry()) {
-    LogThumbnailingOutcome(TriggerReason::TAB_HIDDEN,
-                           Outcome::NOT_ATTEMPTED_PENDING_NAVIGATION);
+  if (bitmap.drawsNothing()) {
+    // TODO(dfried): Log capture failed.
+    MaybeScheduleAnotherCapture(capture_info, finish_time);
     return;
   }
-  StartThumbnailCaptureIfNecessary(TriggerReason::TAB_HIDDEN);
+
+  // TODO(dfried): Log capture succeeded.
+  ThumbnailImage::FromSkBitmapAsync(
+      bitmap,
+      base::BindOnce(&ThumbnailTabHelper::StoreThumbnail,
+                     weak_factory_.GetWeakPtr(), capture_info, finish_time));
 }
 
-// static
-void ThumbnailTabHelper::LogThumbnailingOutcome(TriggerReason trigger,
-                                                Outcome outcome) {
-  UMA_HISTOGRAM_ENUMERATION("Thumbnails.CaptureOutcome", outcome,
-                            Outcome::COUNT);
+void ThumbnailTabHelper::StoreThumbnail(const CaptureInfo& capture_info,
+                                        base::TimeTicks start_time,
+                                        ThumbnailImage thumbnail) {
+  DCHECK(thumbnail.HasData());
+  const base::TimeTicks finish_time = base::TimeTicks::Now();
+  const base::TimeDelta process_time = finish_time - start_time;
+  UMA_HISTOGRAM_TIMES("Thumbnails.ProcessBitmapTime", process_time);
+  thumbnail_state_ = capture_info.target_state;
+  thumbnail_url_ = capture_info.url;
+  thumbnail_ = thumbnail;
 
-  switch (trigger) {
-    case TriggerReason::TAB_HIDDEN:
-      UMA_HISTOGRAM_ENUMERATION("Thumbnails.CaptureOutcome.TabHidden", outcome,
-                                Outcome::COUNT);
-      break;
-    case TriggerReason::NAVIGATING_AWAY:
-      UMA_HISTOGRAM_ENUMERATION("Thumbnails.CaptureOutcome.NavigatingAway",
-                                outcome, Outcome::COUNT);
-      break;
+  NotifyTabPreviewChanged();
+  MaybeScheduleAnotherCapture(capture_info, finish_time);
+}
+
+void ThumbnailTabHelper::NotifyTabPreviewChanged() {
+  web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB);
+}
+
+void ThumbnailTabHelper::MaybeScheduleAnotherCapture(
+    const CaptureInfo& capture_info,
+    base::TimeTicks finish_time) {
+  // If the page is still loading, schedule another capture a short time later.
+  if (capture_info.target_state != ThumbnailState::kFinishedLoading &&
+      finish_time > last_scheduled_capture_time_) {
+    ScheduleThumbnailCapture(CaptureSchedule::kDelayed);
   }
 }
 
diff --git a/chrome/browser/ui/thumbnails/thumbnail_tab_helper.h b/chrome/browser/ui/thumbnails/thumbnail_tab_helper.h
index a4fe69ec..687d8abd 100644
--- a/chrome/browser/ui/thumbnails/thumbnail_tab_helper.h
+++ b/chrome/browser/ui/thumbnails/thumbnail_tab_helper.h
@@ -7,107 +7,76 @@
 
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
-#include "base/scoped_observer.h"
 #include "base/time/time.h"
 #include "chrome/browser/ui/thumbnails/thumbnail_image.h"
-#include "content/public/browser/render_widget_host_observer.h"
-#include "content/public/browser/web_contents_observer.h"
+#include "chrome/browser/ui/thumbnails/thumbnail_web_contents_observer.h"
 #include "content/public/browser/web_contents_user_data.h"
-#include "ui/base/page_transition_types.h"
-
-namespace content {
-class NavigationHandle;
-class RenderViewHost;
-}  // namespace content
 
 class ThumbnailTabHelper
-    : public content::RenderWidgetHostObserver,
-      public content::WebContentsObserver,
+    : public ThumbnailWebContentsObserver,
       public content::WebContentsUserData<ThumbnailTabHelper> {
  public:
+  enum class ThumbnailState {
+    kNoThumbnail,     // no thumbnail is available
+    kLoadInProgress,  // thumbnail available but is of a page that is loading
+    kFinishedLoading  // thumbnail should represent the finished page
+  };
+
   ~ThumbnailTabHelper() override;
 
+  ThumbnailState thumbnail_state() const { return thumbnail_state_; }
   ThumbnailImage thumbnail() const { return thumbnail_; }
 
+ protected:
+  // ThumbnailWebContentsObserver:
+  void TopLevelNavigationStarted(const GURL& url) override;
+  void TopLevelNavigationEnded(const GURL& url) override;
+  void PageLoadStarted(FrameContext frame_context) override;
+  void PageLoadFinished(FrameContext frame_context) override;
+  void PageUpdated(FrameContext frame_context) override;
+  void VisibilityChanged(bool visible) override;
+
  private:
+  enum class CaptureSchedule { kImmediate, kAttemptImmediate, kDelayed };
+
+  struct CaptureInfo {
+    GURL url;
+    ThumbnailState target_state;
+  };
+
   explicit ThumbnailTabHelper(content::WebContents* contents);
   friend class content::WebContentsUserData<ThumbnailTabHelper>;
 
-  enum class TriggerReason {
-    TAB_HIDDEN,
-    NAVIGATING_AWAY,
-  };
+  void UpdateCurrentUrl(const GURL& url);
+  void ScheduleThumbnailCapture(CaptureSchedule schedule);
+  void StartThumbnailCapture(CaptureSchedule schedule);
+  void ProcessCapturedThumbnail(const CaptureInfo& capture_info,
+                                base::TimeTicks start_time,
+                                const SkBitmap& bitmap);
+  void StoreThumbnail(const CaptureInfo& capture_info,
+                      base::TimeTicks start_time,
+                      ThumbnailImage thumbnail);
+  void NotifyTabPreviewChanged();
 
-  // Used for UMA histograms. Don't change or delete entries, and only add new
-  // ones at the end.
-  enum class Outcome {
-    SUCCESS = 0,
-    NOT_ATTEMPTED_PENDING_NAVIGATION,
-    NOT_ATTEMPTED_NO_PAINT_YET,
-    NOT_ATTEMPTED_IN_PROGRESS,
-    NOT_ATTEMPTED_NO_WEBCONTENTS,
-    NOT_ATTEMPTED_NO_URL,
-    NOT_ATTEMPTED_SHOULD_NOT_ACQUIRE,
-    NOT_ATTEMPTED_VIEW_NOT_AVAILABLE,
-    NOT_ATTEMPTED_EMPTY_RECT,
-    CANCELED,
-    READBACK_FAILED,
-    // Add new entries here!
-    COUNT
-  };
-
-  // content::RenderWidgetHostObserver overrides.
-  void RenderWidgetHostVisibilityChanged(content::RenderWidgetHost* widget_host,
-                                         bool became_visible) override;
-  void RenderWidgetHostDestroyed(
-      content::RenderWidgetHost* widget_host) override;
-
-  // content::WebContentsObserver overrides.
-  void RenderViewCreated(content::RenderViewHost* render_view_host) override;
-  void RenderViewHostChanged(content::RenderViewHost* old_host,
-                             content::RenderViewHost* new_host) override;
-  void RenderViewDeleted(content::RenderViewHost* render_view_host) override;
-  void DidStartNavigation(
-      content::NavigationHandle* navigation_handle) override;
-  void DidFinishNavigation(
-      content::NavigationHandle* navigation_handle) override;
-  void DocumentAvailableInMainFrame() override;
-  void DocumentOnLoadCompletedInMainFrame() override;
-  void DidFirstVisuallyNonEmptyPaint() override;
-  void DidStartLoading() override;
-  void NavigationStopped() override;
-
-  void StartWatchingRenderViewHost(content::RenderViewHost* render_view_host);
-  void StopWatchingRenderViewHost(content::RenderViewHost* render_view_host);
-
-  // Starts the process of capturing a thumbnail of the current tab contents if
-  // necessary and possible.
-  void StartThumbnailCaptureIfNecessary(TriggerReason trigger);
-
-  // Creates a thumbnail from the web contents bitmap.
-  void ProcessCapturedBitmap(TriggerReason trigger, const SkBitmap& bitmap);
-
-  // Called when the current tab gets hidden.
-  void TabHidden();
-
-  static void LogThumbnailingOutcome(TriggerReason trigger, Outcome outcome);
-
-  bool did_navigation_finish_ = false;
-  bool has_received_document_since_navigation_finished_ = false;
-  bool has_painted_since_document_received_ = false;
-
-  ui::PageTransition page_transition_ = ui::PAGE_TRANSITION_LINK;
-  bool load_interrupted_ = false;
-
-  bool thumbnailing_in_progress_ = false;
-  bool waiting_for_capture_ = false;
-
-  base::TimeTicks copy_from_surface_start_time_;
+  // For tabs in the process of loading, schedules another capture if none is
+  // currently queued.
+  void MaybeScheduleAnotherCapture(const CaptureInfo& capture_info,
+                                   base::TimeTicks finish_time);
 
   ThumbnailImage thumbnail_;
+  ThumbnailState thumbnail_state_ = ThumbnailState::kNoThumbnail;
+  GURL thumbnail_url_;
 
-  ScopedObserver<content::RenderWidgetHost, content::RenderWidgetHostObserver>
-      observer_{this};
+  // Caches whether or not the web contents view is visible. See notes in
+  // VisibilityChanged() for more information.
+  bool view_is_visible_;  // set in constructor
+  bool is_loading_ = false;
+  GURL current_url_;
+
+  // The time that the most recently-scheduled capture is/was scheduled for.
+  // Can be in the past. Used to prevent captures from bunching up or being
+  // scheduled in the wrong order.
+  base::TimeTicks last_scheduled_capture_time_;
 
   WEB_CONTENTS_USER_DATA_KEY_DECL();
 
diff --git a/chrome/browser/ui/thumbnails/thumbnail_web_contents_observer.cc b/chrome/browser/ui/thumbnails/thumbnail_web_contents_observer.cc
new file mode 100644
index 0000000..35a75f4
--- /dev/null
+++ b/chrome/browser/ui/thumbnails/thumbnail_web_contents_observer.cc
@@ -0,0 +1,93 @@
+// 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 "chrome/browser/ui/thumbnails/thumbnail_web_contents_observer.h"
+
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/render_widget_host.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/browser/web_contents.h"
+
+ThumbnailWebContentsObserver::ThumbnailWebContentsObserver(
+    content::WebContents* contents)
+    : content::WebContentsObserver(contents) {}
+
+ThumbnailWebContentsObserver::~ThumbnailWebContentsObserver() = default;
+
+void ThumbnailWebContentsObserver::OnVisibilityChanged(
+    content::Visibility visibility) {
+  VisibilityChanged(visibility == content::Visibility::VISIBLE);
+}
+
+void ThumbnailWebContentsObserver::DidStartNavigation(
+    content::NavigationHandle* navigation_handle) {
+  if (navigation_handle->IsInMainFrame() &&
+      !navigation_handle->IsSameDocument()) {
+    TopLevelNavigationStarted(
+        navigation_handle->GetWebContents()->GetVisibleURL());
+  }
+}
+
+void ThumbnailWebContentsObserver::DidRedirectNavigation(
+    content::NavigationHandle* navigation_handle) {
+  if (navigation_handle->IsInMainFrame() &&
+      !navigation_handle->IsSameDocument()) {
+    TopLevelNavigationStarted(
+        navigation_handle->GetWebContents()->GetVisibleURL());
+  }
+}
+
+void ThumbnailWebContentsObserver::DidFinishNavigation(
+    content::NavigationHandle* navigation_handle) {
+  if (navigation_handle->IsInMainFrame()) {
+    TopLevelNavigationEnded(
+        navigation_handle->GetWebContents()->GetVisibleURL());
+  }
+}
+
+void ThumbnailWebContentsObserver::DidStartLoading() {
+  PageLoadStarted(FrameContext::kMainFrame);
+}
+
+void ThumbnailWebContentsObserver::DidStopLoading() {
+  PageLoadFinished(FrameContext::kMainFrame);
+}
+
+void ThumbnailWebContentsObserver::DidFinishLoad(
+    content::RenderFrameHost* render_frame_host,
+    const GURL& validated_url) {
+  PageLoadFinished(ContextFromRenderFrameHost(render_frame_host));
+}
+
+void ThumbnailWebContentsObserver::DidFailLoad(
+    content::RenderFrameHost* render_frame_host,
+    const GURL& validated_url,
+    int error_code,
+    const base::string16& error_description) {
+  PageLoadFinished(ContextFromRenderFrameHost(render_frame_host));
+}
+
+void ThumbnailWebContentsObserver::MainFrameWasResized(bool width_changed) {
+  PageUpdated(FrameContext::kMainFrame);
+}
+
+void ThumbnailWebContentsObserver::FrameSizeChanged(
+    content::RenderFrameHost* render_frame_host,
+    const gfx::Size& frame_size) {
+  PageUpdated(ContextFromRenderFrameHost(render_frame_host));
+}
+
+void ThumbnailWebContentsObserver::NavigationStopped() {
+  TopLevelNavigationEnded(web_contents()->GetVisibleURL());
+  PageLoadFinished(FrameContext::kMainFrame);
+}
+
+// static
+ThumbnailWebContentsObserver::FrameContext
+ThumbnailWebContentsObserver::ContextFromRenderFrameHost(
+    content::RenderFrameHost* render_frame_host) {
+  return render_frame_host->GetParent() ? FrameContext::kChildFrame
+                                        : FrameContext::kMainFrame;
+}
diff --git a/chrome/browser/ui/thumbnails/thumbnail_web_contents_observer.h b/chrome/browser/ui/thumbnails/thumbnail_web_contents_observer.h
new file mode 100644
index 0000000..285fe1e
--- /dev/null
+++ b/chrome/browser/ui/thumbnails/thumbnail_web_contents_observer.h
@@ -0,0 +1,81 @@
+// 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 CHROME_BROWSER_UI_THUMBNAILS_THUMBNAIL_WEB_CONTENTS_OBSERVER_H_
+#define CHROME_BROWSER_UI_THUMBNAILS_THUMBNAIL_WEB_CONTENTS_OBSERVER_H_
+
+#include "base/macros.h"
+#include "base/scoped_observer.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "url/gurl.h"
+
+namespace content {
+class NavigationHandle;
+class RenderFrameHost;
+}  // namespace content
+
+// Base class for thumbnail tab helper; processes specific web contents events
+// into a filtered-down set of navigation and loading events for ease of
+// processing.
+class ThumbnailWebContentsObserver : public content::WebContentsObserver {
+ public:
+  ~ThumbnailWebContentsObserver() override;
+
+ protected:
+  enum class FrameContext { kMainFrame, kChildFrame };
+
+  explicit ThumbnailWebContentsObserver(content::WebContents* contents);
+
+  // Called when navigation in the top-level browser window starts.
+  virtual void TopLevelNavigationStarted(const GURL& url) = 0;
+  // Called when navigation in the top-level browser window completes.
+  virtual void TopLevelNavigationEnded(const GURL& url) = 0;
+  // Called when the page/tab's visibility changes.
+  // If |view_is_valid| is false, no attempt should be made to read from the
+  // contents pane.
+  virtual void VisibilityChanged(bool visible) = 0;
+  // Called when a page begins to load.
+  virtual void PageLoadStarted(FrameContext frame_context) = 0;
+  // Called when a page finishes loading.
+  virtual void PageLoadFinished(FrameContext frame_context) = 0;
+  // Called when the page is resized or otherwise updated.
+  virtual void PageUpdated(FrameContext frame_context) = 0;
+
+  void OnVisibilityChanged(content::Visibility visibility) override;
+
+  // Track when navigation happens so that we know when a thumbnail is no longer
+  // valid (thumbnail will still be valid for the old URL until the new
+  // navigation has committed and the new page render occurs).
+  void DidStartNavigation(
+      content::NavigationHandle* navigation_handle) override;
+  void DidRedirectNavigation(
+      content::NavigationHandle* navigation_handle) override;
+  void DidFinishNavigation(
+      content::NavigationHandle* navigation_handle) override;
+
+  // Track the progress of loading. Thumbnails should be captured during the
+  // loading process, since some pages take a long time to load, but there is no
+  // point to capturing a thumbnail of a page that has not rendered anything
+  // yet.
+  void DidStartLoading() override;
+  void DidStopLoading() override;
+  void DidFinishLoad(content::RenderFrameHost* render_frame_host,
+                     const GURL& validated_url) override;
+  void DidFailLoad(content::RenderFrameHost* render_frame_host,
+                   const GURL& validated_url,
+                   int error_code,
+                   const base::string16& error_description) override;
+  void NavigationStopped() override;
+  void MainFrameWasResized(bool width_changed) override;
+  void FrameSizeChanged(content::RenderFrameHost* render_frame_host,
+                        const gfx::Size& frame_size) override;
+
+ private:
+  static FrameContext ContextFromRenderFrameHost(
+      content::RenderFrameHost* render_frame_host);
+
+  DISALLOW_COPY_AND_ASSIGN(ThumbnailWebContentsObserver);
+};
+
+#endif  // CHROME_BROWSER_UI_THUMBNAILS_THUMBNAIL_WEB_CONTENTS_OBSERVER_H_
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc b/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc
index 8a53ce5..bf09da8f 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc
@@ -1619,9 +1619,12 @@
       themify_icon = true;
     }
 
-    if (themify_icon && GetThemeProvider()) {
-      favicon = gfx::ImageSkiaOperations::CreateHSLShiftedImage(
-          favicon, GetThemeProvider()->GetTint(ThemeProperties::TINT_BUTTONS));
+    if (themify_icon && GetThemeProvider() &&
+        GetThemeProvider()->HasCustomColor(
+            ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON)) {
+      favicon = gfx::ImageSkiaOperations::CreateColorMask(
+          favicon, GetThemeProvider()->GetColor(
+                       ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON));
     }
 
     button->SetImage(views::Button::STATE_NORMAL, favicon);
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_editor_view.cc b/chrome/browser/ui/views/bookmarks/bookmark_editor_view.cc
index 92c88d92..935ba24 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_editor_view.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_editor_view.cc
@@ -32,6 +32,7 @@
 #include "ui/views/controls/button/md_text_button.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/controls/menu/menu_runner.h"
+#include "ui/views/controls/scroll_view.h"
 #include "ui/views/controls/textfield/textfield.h"
 #include "ui/views/controls/tree/tree_view.h"
 #include "ui/views/focus/focus_manager.h"
@@ -319,10 +320,11 @@
   title_tf_->SetText(title);
   title_tf_->set_controller(this);
 
+  std::unique_ptr<views::TreeView> tree_view;
   if (show_tree_) {
-    tree_view_ = new views::TreeView;
-    tree_view_->SetRootShown(false);
-    tree_view_->set_context_menu_controller(this);
+    tree_view = std::make_unique<views::TreeView>();
+    tree_view->SetRootShown(false);
+    tree_view->set_context_menu_controller(this);
 
     new_folder_button_.reset(views::MdTextButton::CreateSecondaryUiButton(
         this,
@@ -400,7 +402,10 @@
         views::GridLayout::kFixedSize,
         provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_VERTICAL));
     layout->StartRow(1.0, single_column_view_set_id);
-    layout->AddView(tree_view_->CreateParentIfNecessary());
+    tree_view_ = tree_view.get();
+    layout->AddView(
+        views::TreeView::CreateScrollViewWithTree(std::move(tree_view))
+            .release());
   }
 
   if (!show_tree_ || bb_model_->loaded())
diff --git a/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc b/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc
index 95407d8..4cf12e1 100644
--- a/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc
+++ b/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc
@@ -579,7 +579,7 @@
     // (&processed_bg_image) to create a local copy, so it's safe for this
     // to be locally scoped.
     button->SetBackgroundImage(
-        tp->GetColor(ThemeProperties::COLOR_BUTTON_BACKGROUND),
+        tp->GetColor(ThemeProperties::COLOR_CONTROL_BUTTON_BACKGROUND),
         (processed_bg_image.isNull() ? nullptr : &processed_bg_image),
         tp->GetImageSkiaNamed(mask_image_id));
   }
diff --git a/chrome/browser/ui/views/frame/windows_10_caption_button.cc b/chrome/browser/ui/views/frame/windows_10_caption_button.cc
index 6dff965..4cd7fdba 100644
--- a/chrome/browser/ui/views/frame/windows_10_caption_button.cc
+++ b/chrome/browser/ui/views/frame/windows_10_caption_button.cc
@@ -65,8 +65,8 @@
   // Paint the background of the button (the semi-transparent rectangle that
   // appears when you hover or press the button).
   const ui::ThemeProvider* theme_provider = GetThemeProvider();
-  const SkColor bg_color =
-      theme_provider->GetColor(ThemeProperties::COLOR_BUTTON_BACKGROUND);
+  const SkColor bg_color = theme_provider->GetColor(
+      ThemeProperties::COLOR_CONTROL_BUTTON_BACKGROUND);
   const SkAlpha theme_alpha = SkColorGetA(bg_color);
   gfx::Rect bounds = GetContentsBounds();
   bounds.Inset(GetBetweenButtonSpacing(), 0, 0, 0);
diff --git a/chrome/browser/ui/views/page_action/pwa_install_view.cc b/chrome/browser/ui/views/page_action/pwa_install_view.cc
index eb430378..1bedff7 100644
--- a/chrome/browser/ui/views/page_action/pwa_install_view.cc
+++ b/chrome/browser/ui/views/page_action/pwa_install_view.cc
@@ -31,7 +31,7 @@
       banners::AppBannerManager::FromWebContents(web_contents);
   DCHECK(manager);
 
-  bool is_installable = manager->IsInstallable();
+  bool is_installable = manager->IsProbablyInstallable();
   bool is_installed =
       web_app::WebAppTabHelperBase::FromWebContents(web_contents)
           ->HasAssociatedApp();
diff --git a/chrome/browser/ui/views/page_action/pwa_install_view_browsertest.cc b/chrome/browser/ui/views/page_action/pwa_install_view_browsertest.cc
index 58bef1e6..884f7aa3 100644
--- a/chrome/browser/ui/views/page_action/pwa_install_view_browsertest.cc
+++ b/chrome/browser/ui/views/page_action/pwa_install_view_browsertest.cc
@@ -51,6 +51,20 @@
         GetInstallableAppURL().GetOrigin().spec());
   }
 
+  void SetUpOnMainThread() override {
+    pwa_install_view_ =
+        BrowserView::GetBrowserViewForBrowser(browser())
+            ->toolbar_button_provider()
+            ->GetPageActionIconContainerView()
+            ->GetPageActionIconView(PageActionIconType::kPwaInstall);
+    EXPECT_FALSE(pwa_install_view_->visible());
+
+    web_contents_ = GetCurrentTab();
+    app_banner_manager_ =
+        banners::TestAppBannerManagerDesktop::CreateForWebContents(
+            web_contents_);
+  }
+
   content::WebContents* GetCurrentTab() {
     return browser()->tab_strip_model()->GetActiveWebContents();
   }
@@ -79,17 +93,14 @@
     return https_server_.GetURL("app.com", "/simple.html");
   }
 
-  PageActionIconView* GetPwaInstallView() {
-    return BrowserView::GetBrowserViewForBrowser(browser())
-        ->toolbar_button_provider()
-        ->GetPageActionIconContainerView()
-        ->GetPageActionIconView(PageActionIconType::kPwaInstall);
-  }
-
- private:
+ protected:
   base::test::ScopedFeatureList scoped_feature_list_;
   net::EmbeddedTestServer https_server_;
 
+  PageActionIconView* pwa_install_view_ = nullptr;
+  content::WebContents* web_contents_ = nullptr;
+  banners::TestAppBannerManagerDesktop* app_banner_manager_ = nullptr;
+
   DISALLOW_COPY_AND_ASSIGN(PwaInstallViewBrowserTest);
 };
 
@@ -97,9 +108,6 @@
 // installable/non-installable tabs.
 IN_PROC_BROWSER_TEST_F(PwaInstallViewBrowserTest,
                        IconVisibilityAfterTabSwitching) {
-  PageActionIconView* pwa_install_view = GetPwaInstallView();
-  EXPECT_FALSE(pwa_install_view->visible());
-
   content::WebContents* installable_web_contents =
       OpenNewTab(GetInstallableAppURL(), true);
   content::WebContents* non_installable_web_contents =
@@ -107,55 +115,114 @@
 
   chrome::SelectPreviousTab(browser());
   ASSERT_EQ(installable_web_contents, GetCurrentTab());
-  EXPECT_TRUE(pwa_install_view->visible());
+  EXPECT_TRUE(pwa_install_view_->visible());
 
   chrome::SelectNextTab(browser());
   ASSERT_EQ(non_installable_web_contents, GetCurrentTab());
-  EXPECT_FALSE(pwa_install_view->visible());
+  EXPECT_FALSE(pwa_install_view_->visible());
 }
 
 // Tests that the plus icon updates its visibiliy once the installability check
 // completes.
 IN_PROC_BROWSER_TEST_F(PwaInstallViewBrowserTest,
                        IconVisibilityAfterInstallabilityCheck) {
-  PageActionIconView* pwa_install_view = GetPwaInstallView();
-  EXPECT_FALSE(pwa_install_view->visible());
-
-  content::WebContents* web_contents = GetCurrentTab();
-  auto* app_banner_manager =
-      banners::TestAppBannerManagerDesktop::CreateForWebContents(web_contents);
-
   ui_test_utils::NavigateToURL(browser(), GetInstallableAppURL());
-  EXPECT_FALSE(pwa_install_view->visible());
-  ASSERT_TRUE(app_banner_manager->WaitForInstallableCheck());
-  EXPECT_TRUE(pwa_install_view->visible());
+  EXPECT_FALSE(pwa_install_view_->visible());
+  ASSERT_TRUE(app_banner_manager_->WaitForInstallableCheck());
+  EXPECT_TRUE(pwa_install_view_->visible());
 
   ui_test_utils::NavigateToURL(browser(), GetNonInstallableAppURL());
-  EXPECT_FALSE(pwa_install_view->visible());
-  ASSERT_FALSE(app_banner_manager->WaitForInstallableCheck());
-  EXPECT_FALSE(pwa_install_view->visible());
+  EXPECT_FALSE(pwa_install_view_->visible());
+  ASSERT_FALSE(app_banner_manager_->WaitForInstallableCheck());
+  EXPECT_FALSE(pwa_install_view_->visible());
 }
 
 // Tests that the plus icon animates its label when the installability check
 // passes but doesn't animate more than once for the same installability check.
 IN_PROC_BROWSER_TEST_F(PwaInstallViewBrowserTest, LabelAnimation) {
-  PageActionIconView* pwa_install_view = GetPwaInstallView();
-  EXPECT_FALSE(pwa_install_view->visible());
-
-  content::WebContents* web_contents = GetCurrentTab();
-  auto* app_banner_manager =
-      banners::TestAppBannerManagerDesktop::CreateForWebContents(web_contents);
-
   ui_test_utils::NavigateToURL(browser(), GetInstallableAppURL());
-  EXPECT_FALSE(pwa_install_view->visible());
-  ASSERT_TRUE(app_banner_manager->WaitForInstallableCheck());
-  EXPECT_TRUE(pwa_install_view->visible());
-  EXPECT_TRUE(pwa_install_view->is_animating_label());
+  EXPECT_FALSE(pwa_install_view_->visible());
+  ASSERT_TRUE(app_banner_manager_->WaitForInstallableCheck());
+  EXPECT_TRUE(pwa_install_view_->visible());
+  EXPECT_TRUE(pwa_install_view_->is_animating_label());
 
   chrome::NewTab(browser());
-  EXPECT_FALSE(pwa_install_view->visible());
+  EXPECT_FALSE(pwa_install_view_->visible());
 
   chrome::SelectPreviousTab(browser());
-  EXPECT_TRUE(pwa_install_view->visible());
-  EXPECT_FALSE(pwa_install_view->is_animating_label());
+  EXPECT_TRUE(pwa_install_view_->visible());
+  EXPECT_FALSE(pwa_install_view_->is_animating_label());
+}
+
+// Tests that the icon persists while loading the same scope and omits running
+// the label animation again.
+IN_PROC_BROWSER_TEST_F(PwaInstallViewBrowserTest, NavigateToSameScope) {
+  ui_test_utils::NavigateToURL(
+      browser(), https_server_.GetURL("/banners/scope_a/page_1.html"));
+  EXPECT_FALSE(pwa_install_view_->visible());
+  ASSERT_TRUE(app_banner_manager_->WaitForInstallableCheck());
+  EXPECT_TRUE(pwa_install_view_->visible());
+  EXPECT_TRUE(pwa_install_view_->is_animating_label());
+
+  ui_test_utils::NavigateToURL(
+      browser(), https_server_.GetURL("/banners/scope_a/page_2.html"));
+  EXPECT_TRUE(pwa_install_view_->visible());
+  ASSERT_TRUE(app_banner_manager_->WaitForInstallableCheck());
+  EXPECT_TRUE(pwa_install_view_->visible());
+  EXPECT_FALSE(pwa_install_view_->is_animating_label());
+}
+
+// Tests that the icon persists while loading the same scope but goes away when
+// the installability check fails.
+IN_PROC_BROWSER_TEST_F(PwaInstallViewBrowserTest,
+                       NavigateToSameScopeNonInstallable) {
+  ui_test_utils::NavigateToURL(
+      browser(), https_server_.GetURL("/banners/scope_a/page_1.html"));
+  EXPECT_FALSE(pwa_install_view_->visible());
+  ASSERT_TRUE(app_banner_manager_->WaitForInstallableCheck());
+  EXPECT_TRUE(pwa_install_view_->visible());
+  EXPECT_TRUE(pwa_install_view_->is_animating_label());
+
+  ui_test_utils::NavigateToURL(
+      browser(), https_server_.GetURL("/banners/scope_a/bad_manifest.html"));
+  EXPECT_TRUE(pwa_install_view_->visible());
+  ASSERT_FALSE(app_banner_manager_->WaitForInstallableCheck());
+  EXPECT_FALSE(pwa_install_view_->visible());
+  EXPECT_FALSE(pwa_install_view_->is_animating_label());
+}
+
+// Tests that the icon and animation resets while loading a different scope.
+IN_PROC_BROWSER_TEST_F(PwaInstallViewBrowserTest, NavigateToDifferentScope) {
+  ui_test_utils::NavigateToURL(
+      browser(), https_server_.GetURL("/banners/scope_a/page_1.html"));
+  EXPECT_FALSE(pwa_install_view_->visible());
+  ASSERT_TRUE(app_banner_manager_->WaitForInstallableCheck());
+  EXPECT_TRUE(pwa_install_view_->visible());
+  EXPECT_TRUE(pwa_install_view_->is_animating_label());
+
+  ui_test_utils::NavigateToURL(
+      browser(), https_server_.GetURL("/banners/scope_b/scope_b.html"));
+  EXPECT_FALSE(pwa_install_view_->visible());
+  ASSERT_TRUE(app_banner_manager_->WaitForInstallableCheck());
+  EXPECT_TRUE(pwa_install_view_->visible());
+  EXPECT_TRUE(pwa_install_view_->is_animating_label());
+}
+
+// Tests that the icon and animation resets while loading a different empty
+// scope.
+IN_PROC_BROWSER_TEST_F(PwaInstallViewBrowserTest,
+                       NavigateToDifferentEmptyScope) {
+  ui_test_utils::NavigateToURL(
+      browser(), https_server_.GetURL("/banners/scope_a/page_1.html"));
+  EXPECT_FALSE(pwa_install_view_->visible());
+  ASSERT_TRUE(app_banner_manager_->WaitForInstallableCheck());
+  EXPECT_TRUE(pwa_install_view_->visible());
+  EXPECT_TRUE(pwa_install_view_->is_animating_label());
+
+  ui_test_utils::NavigateToURL(
+      browser(), https_server_.GetURL("/banners/manifest_test_page.html"));
+  EXPECT_FALSE(pwa_install_view_->visible());
+  ASSERT_TRUE(app_banner_manager_->WaitForInstallableCheck());
+  EXPECT_TRUE(pwa_install_view_->visible());
+  EXPECT_TRUE(pwa_install_view_->is_animating_label());
 }
diff --git a/chrome/browser/ui/views/payments/payment_handler_modal_dialog_manager_delegate.cc b/chrome/browser/ui/views/payments/payment_handler_modal_dialog_manager_delegate.cc
new file mode 100644
index 0000000..9e9a0d8
--- /dev/null
+++ b/chrome/browser/ui/views/payments/payment_handler_modal_dialog_manager_delegate.cc
@@ -0,0 +1,47 @@
+// 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 "chrome/browser/ui/views/payments/payment_handler_modal_dialog_manager_delegate.h"
+
+#include "chrome/browser/platform_util.h"
+#include "components/web_modal/web_contents_modal_dialog_host.h"
+#include "content/public/browser/web_contents.h"
+
+namespace payments {
+
+PaymentHandlerModalDialogManagerDelegate::
+    PaymentHandlerModalDialogManagerDelegate(
+        web_modal::WebContentsModalDialogHost* host)
+    : host_(host), web_contents_(nullptr) {
+  DCHECK(host);
+}
+
+void PaymentHandlerModalDialogManagerDelegate::SetWebContentsBlocked(
+    content::WebContents* web_contents,
+    bool blocked) {
+  DCHECK(web_contents);
+  DCHECK_EQ(web_contents_, web_contents);
+  if (!blocked) {
+    web_contents->Focus();
+  }
+}
+
+web_modal::WebContentsModalDialogHost*
+PaymentHandlerModalDialogManagerDelegate::GetWebContentsModalDialogHost() {
+  return host_;
+}
+
+bool PaymentHandlerModalDialogManagerDelegate::IsWebContentsVisible(
+    content::WebContents* web_contents) {
+  DCHECK_EQ(web_contents_, web_contents);
+  return platform_util::IsVisible(web_contents->GetNativeView());
+}
+
+void PaymentHandlerModalDialogManagerDelegate::SetWebContents(
+    content::WebContents* web_contents) {
+  DCHECK(web_contents);
+  web_contents_ = web_contents;
+}
+
+}  // namespace payments
diff --git a/chrome/browser/ui/views/payments/payment_handler_modal_dialog_manager_delegate.h b/chrome/browser/ui/views/payments/payment_handler_modal_dialog_manager_delegate.h
new file mode 100644
index 0000000..0ac1cf71
--- /dev/null
+++ b/chrome/browser/ui/views/payments/payment_handler_modal_dialog_manager_delegate.h
@@ -0,0 +1,59 @@
+// 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 CHROME_BROWSER_UI_VIEWS_PAYMENTS_PAYMENT_HANDLER_MODAL_DIALOG_MANAGER_DELEGATE_H_
+#define CHROME_BROWSER_UI_VIEWS_PAYMENTS_PAYMENT_HANDLER_MODAL_DIALOG_MANAGER_DELEGATE_H_
+
+#include "base/macros.h"
+#include "components/web_modal/web_contents_modal_dialog_manager_delegate.h"
+
+namespace content {
+class WebContents;
+}
+
+namespace web_modal {
+class WebContentsModalDialogHost;
+}
+
+namespace payments {
+
+// A delegate for presenting modal dialogs that are triggered from a web-based
+// payment handler. Since the payment sheet is itself a modal dialog, the
+// WebContentsModalDialogHost is expected to be borrowed from the browser that
+// spawned the payment sheet.
+class PaymentHandlerModalDialogManagerDelegate
+    : public web_modal::WebContentsModalDialogManagerDelegate {
+ public:
+  // |host| must not be null.
+  explicit PaymentHandlerModalDialogManagerDelegate(
+      web_modal::WebContentsModalDialogHost* host);
+  ~PaymentHandlerModalDialogManagerDelegate() override {}
+
+  // Sets the |web_contents| that is behind the modal dialogs managed by this
+  // modal dialog manager. |web_contents| must not be null.
+  void SetWebContents(content::WebContents* web_contents);
+
+  // WebContentsModalDialogManagerDelegate:
+  // |web_contents| must not be null and is expected to be the same as the one
+  // provided to SetWebContents().
+  void SetWebContentsBlocked(content::WebContents* web_contents,
+                             bool blocked) override;
+  web_modal::WebContentsModalDialogHost* GetWebContentsModalDialogHost()
+      override;
+  bool IsWebContentsVisible(content::WebContents* web_contents) override;
+
+ private:
+  // A not-owned pointer to the WebContentsModalDialogHost associated with the
+  // browser that spawned the payment handler.
+  web_modal::WebContentsModalDialogHost* host_;
+
+  // A not-owned pointer to the WebContents behind the modal dialogs.
+  content::WebContents* web_contents_;
+
+  DISALLOW_COPY_AND_ASSIGN(PaymentHandlerModalDialogManagerDelegate);
+};
+
+}  // namespace payments
+
+#endif  // CHROME_BROWSER_UI_VIEWS_PAYMENTS_PAYMENT_HANDLER_MODAL_DIALOG_MANAGER_DELEGATE_H_
diff --git a/chrome/browser/ui/views/payments/payment_handler_web_flow_view_controller.cc b/chrome/browser/ui/views/payments/payment_handler_web_flow_view_controller.cc
index 7f5455c9..5cf42d5e 100644
--- a/chrome/browser/ui/views/payments/payment_handler_web_flow_view_controller.cc
+++ b/chrome/browser/ui/views/payments/payment_handler_web_flow_view_controller.cc
@@ -17,6 +17,8 @@
 #include "chrome/browser/ui/views/payments/payment_request_views_util.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/payments/content/origin_security_checker.h"
+#include "components/web_modal/web_contents_modal_dialog_manager.h"
+#include "components/web_modal/web_contents_modal_dialog_manager_delegate.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
@@ -145,12 +147,12 @@
     PaymentRequestSpec* spec,
     PaymentRequestState* state,
     PaymentRequestDialogView* dialog,
-    content::WebContents* log_destination,
+    content::WebContents* payment_request_web_contents,
     Profile* profile,
     GURL target,
     PaymentHandlerOpenWindowCallback first_navigation_complete_callback)
     : PaymentRequestSheetController(spec, state, dialog),
-      log_(log_destination),
+      log_(payment_request_web_contents),
       profile_(profile),
       target_(target),
       show_progress_bar_(false),
@@ -160,7 +162,16 @@
       first_navigation_complete_callback_(
           std::move(first_navigation_complete_callback)),
       https_prefix_(base::UTF8ToUTF16(url::kHttpsScheme) +
-                    base::UTF8ToUTF16(url::kStandardSchemeSeparator)) {
+                    base::UTF8ToUTF16(url::kStandardSchemeSeparator)),
+      // Borrow the browser's WebContentModalDialogHost to display modal dialogs
+      // triggered by the payment handler's web view (e.g. WebAuthn dialogs).
+      // The browser's WebContentModalDialogHost is valid throughout the
+      // lifetime of this controller because the payment sheet itself is a modal
+      // dialog.
+      dialog_manager_delegate_(
+          static_cast<web_modal::WebContentsModalDialogManagerDelegate*>(
+              chrome::FindBrowserWithWebContents(payment_request_web_contents))
+              ->GetWebContentsModalDialogHost()) {
   progress_bar_->set_owned_by_client();
   progress_bar_->set_foreground_color(gfx::kGoogleBlue500);
   progress_bar_->set_background_color(SK_ColorTRANSPARENT);
@@ -184,6 +195,13 @@
   web_contents()->SetDelegate(this);
   web_view->LoadInitialURL(target_);
 
+  // Enable modal dialogs for web-based payment handlers.
+  dialog_manager_delegate_.SetWebContents(web_contents());
+  web_modal::WebContentsModalDialogManager::CreateForWebContents(
+      web_contents());
+  web_modal::WebContentsModalDialogManager::FromWebContents(web_contents())
+      ->SetDelegate(&dialog_manager_delegate_);
+
   // The webview must get an explicitly set height otherwise the layout doesn't
   // make it fill its container. This is likely because it has no content at the
   // time of first layout (nothing has loaded yet). Because of this, set it to.
diff --git a/chrome/browser/ui/views/payments/payment_handler_web_flow_view_controller.h b/chrome/browser/ui/views/payments/payment_handler_web_flow_view_controller.h
index f2577ef..f84aa4b 100644
--- a/chrome/browser/ui/views/payments/payment_handler_web_flow_view_controller.h
+++ b/chrome/browser/ui/views/payments/payment_handler_web_flow_view_controller.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_PAYMENTS_PAYMENT_HANDLER_WEB_FLOW_VIEW_CONTROLLER_H_
 #define CHROME_BROWSER_UI_VIEWS_PAYMENTS_PAYMENT_HANDLER_WEB_FLOW_VIEW_CONTROLLER_H_
 
+#include "chrome/browser/ui/views/payments/payment_handler_modal_dialog_manager_delegate.h"
 #include "chrome/browser/ui/views/payments/payment_request_sheet_controller.h"
 #include "components/payments/content/developer_console_logger.h"
 #include "components/payments/content/payment_request_display_manager.h"
@@ -32,17 +33,22 @@
       public content::WebContentsObserver {
  public:
   // This ctor forwards its first 3 args to PaymentRequestSheetController's
-  // ctor. |log_destination| is the page whose web developer console will print
-  // error messages. That should be the page that instantiated PaymentRequest
-  // for developer convinience. |profile| is the browser context used to create
-  // the new WebContents object that will navigate to |target|.
-  // |first_navigation_complete_callback| is invoked once the WebContents
-  // finishes the initial navigation to |target|.
+  // ctor.
+  // |payment_request_web_contents| is the page that initiated the
+  // PaymentRequest. It is used in two ways:
+  // - Its web developer console is used to print error messages.
+  // - Its WebContentModalDialogHost is lent to the payment handler for the
+  //   display of modal dialogs initiated from the payment handler's web
+  //   content.
+  // |profile| is the browser context used to create the new payment handler
+  // WebContents object that will navigate to |target|.
+  // |first_navigation_complete_callback| is invoked once the payment handler
+  // WebContents finishes the initial navigation to |target|.
   PaymentHandlerWebFlowViewController(
       PaymentRequestSpec* spec,
       PaymentRequestState* state,
       PaymentRequestDialogView* dialog,
-      content::WebContents* log_destination,
+      content::WebContents* payment_request_web_contents,
       Profile* profile,
       GURL target,
       PaymentHandlerOpenWindowCallback first_navigation_complete_callback);
@@ -88,6 +94,9 @@
   std::unique_ptr<views::Separator> separator_;
   PaymentHandlerOpenWindowCallback first_navigation_complete_callback_;
   base::string16 https_prefix_;
+  // Used to present modal dialog triggered from the payment handler web view,
+  // e.g. an authenticator dialog.
+  PaymentHandlerModalDialogManagerDelegate dialog_manager_delegate_;
 };
 
 }  // namespace payments
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller.cc b/chrome/browser/ui/views/tabs/tab_drag_controller.cc
index 933f53d7..223c078 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller.cc
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller.cc
@@ -1921,6 +1921,7 @@
   gfx::Rect new_bounds(CalculateDraggedBrowserBounds(source,
                                                      point_in_screen,
                                                      drag_bounds));
+  *drag_offset = point_in_screen - new_bounds.origin();
 
   Profile* profile =
       Profile::FromBrowserContext(drag_data_[0].contents->GetBrowserContext());
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc b/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
index ff1194dc..4a0991a 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
@@ -1611,6 +1611,80 @@
   }
 }
 
+namespace {
+
+TabStrip* GetAttachedTabstrip() {
+  for (Browser* browser : *BrowserList::GetInstance()) {
+    BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
+    if (TabDragController::IsAttachedTo(browser_view->tabstrip()))
+      return browser_view->tabstrip();
+  }
+  return nullptr;
+}
+
+void DragWindowAndVerifyOffset(DetachToBrowserTabDragControllerTest* test,
+                               TabStrip* tab_strip,
+                               int tab_index) {
+  // Move to the tab and drag it enough so that it detaches.
+  const gfx::Point tab_center =
+      GetCenterInScreenCoordinates(tab_strip->tab_at(tab_index));
+  // The expected offset; the horizontal position should be relative to the
+  // pressed tab. The vertical position should be relative to the window itself
+  // since the top margin can be different between the existing browser and
+  // the dragged one.
+  const gfx::Vector2d press_offset(
+      tab_center.x() - tab_strip->tab_at(tab_index)->GetBoundsInScreen().x(),
+      tab_center.y() - tab_strip->GetWidget()->GetWindowBoundsInScreen().y());
+  const gfx::Point initial_move =
+      tab_center + gfx::Vector2d(0, GetDetachY(tab_strip));
+  const gfx::Point second_move = initial_move + gfx::Vector2d(20, 20);
+  ASSERT_TRUE(test->PressInput(tab_center));
+  ASSERT_TRUE(test->DragInputToNotifyWhenDone(
+      initial_move, base::BindLambdaForTesting([&]() {
+        // Moves slightly to cause the actual dragging effect on the system and
+        // makes sure the window is positioned correctly.
+        ASSERT_TRUE(test->DragInputToNotifyWhenDone(
+            second_move, base::BindLambdaForTesting([&]() {
+              TabStrip* attached = GetAttachedTabstrip();
+              // Same computation for drag offset. This operation drags a single
+              // tab, so the target tab index should be always 0.
+              gfx::Vector2d drag_offset(
+                  second_move.x() -
+                      attached->tab_at(0)->GetBoundsInScreen().x(),
+                  second_move.y() -
+                      attached->GetWidget()->GetWindowBoundsInScreen().y());
+              EXPECT_EQ(press_offset, drag_offset);
+              ASSERT_TRUE(test->ReleaseInput());
+            })));
+      })));
+  test::QuitDraggingObserver().Wait();
+}
+
+}  // namespace
+
+#if defined(OS_WIN)
+// TODO(mukai): enable those tests on Windows.
+#define MAYBE_OffsetForDraggingTab DISABLED_OffsetForDraggingTab
+#define MAYBE_OffsetForDraggingDetachedTab DISABLED_OffsetForDraggingDetachedTab
+#else
+#define MAYBE_OffsetForDraggingTab OffsetForDraggingTab
+#define MAYBE_OffsetForDraggingDetachedTab OffsetForDraggingDetachedTab
+#endif
+
+IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
+                       MAYBE_OffsetForDraggingTab) {
+  DragWindowAndVerifyOffset(this, GetTabStripForBrowser(browser()), 0);
+  ASSERT_FALSE(TabDragController::IsActive());
+}
+
+IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
+                       MAYBE_OffsetForDraggingDetachedTab) {
+  AddTabAndResetBrowser(browser());
+
+  DragWindowAndVerifyOffset(this, GetTabStripForBrowser(browser()), 1);
+  ASSERT_FALSE(TabDragController::IsActive());
+}
+
 #if defined(OS_CHROMEOS)
 namespace {
 
@@ -1670,57 +1744,6 @@
   EXPECT_TRUE(new_browser->window()->IsMaximized());
 }
 
-namespace {
-
-TabStrip* GetAttachedTabstrip() {
-  for (Browser* browser : *BrowserList::GetInstance()) {
-    BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
-    if (TabDragController::IsAttachedTo(browser_view->tabstrip()))
-      return browser_view->tabstrip();
-  }
-  return nullptr;
-}
-
-void DragWindowAndVerifyOffset(DetachToBrowserTabDragControllerTest* test,
-                               TabStrip* tab_strip,
-                               int tab_index) {
-  // Move to the tab and drag it enough so that it detaches.
-  const gfx::Point tab_center =
-      GetCenterInScreenCoordinates(tab_strip->tab_at(tab_index));
-  // The expected offset; the horizontal position should be relative to the
-  // pressed tab. The vertical position should be relative to the window itself
-  // since the top margin can be different between the existing browser and
-  // the dragged one.
-  const gfx::Vector2d press_offset(
-      tab_center.x() - tab_strip->tab_at(tab_index)->GetBoundsInScreen().x(),
-      tab_center.y() - tab_strip->GetWidget()->GetWindowBoundsInScreen().y());
-  const gfx::Point initial_move =
-      tab_center + gfx::Vector2d(0, GetDetachY(tab_strip));
-  const gfx::Point second_move = initial_move + gfx::Vector2d(20, 20);
-  ASSERT_TRUE(test->PressInput(tab_center));
-  ASSERT_TRUE(test->DragInputToNotifyWhenDone(
-      initial_move, base::BindLambdaForTesting([&]() {
-        // Moves slightly to cause the actual dragging effect on the system and
-        // makes sure the window is positioned correctly.
-        ASSERT_TRUE(test->DragInputToNotifyWhenDone(
-            second_move, base::BindLambdaForTesting([&]() {
-              TabStrip* attached = GetAttachedTabstrip();
-              // Same computation for drag offset. This operation drags a single
-              // tab, so the target tab index should be always 0.
-              gfx::Vector2d drag_offset(
-                  second_move.x() -
-                      attached->tab_at(0)->GetBoundsInScreen().x(),
-                  second_move.y() -
-                      attached->GetWidget()->GetWindowBoundsInScreen().y());
-              EXPECT_EQ(press_offset, drag_offset);
-              ASSERT_TRUE(test->ReleaseInput());
-            })));
-      })));
-  test::QuitDraggingObserver().Wait();
-}
-
-}  // namespace
-
 IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
                        OffsetForDraggingInMaximizedWindow) {
   AddTabAndResetBrowser(browser());
diff --git a/chrome/browser/ui/views/tabs/tab_icon.cc b/chrome/browser/ui/views/tabs/tab_icon.cc
index 4ae483ac..ae257f7 100644
--- a/chrome/browser/ui/views/tabs/tab_icon.cc
+++ b/chrome/browser/ui/views/tabs/tab_icon.cc
@@ -419,6 +419,11 @@
 }
 
 gfx::ImageSkia TabIcon::ThemeImage(const gfx::ImageSkia& source) {
-  return gfx::ImageSkiaOperations::CreateHSLShiftedImage(
-      source, GetThemeProvider()->GetTint(ThemeProperties::TINT_BUTTONS));
+  if (!GetThemeProvider()->HasCustomColor(
+          ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON))
+    return source;
+
+  return gfx::ImageSkiaOperations::CreateColorMask(
+      source,
+      GetThemeProvider()->GetColor(ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON));
 }
diff --git a/chrome/browser/ui/webui/settings/people_handler.cc b/chrome/browser/ui/webui/settings/people_handler.cc
index 1a0d132..afab9aa9 100644
--- a/chrome/browser/ui/webui/settings/people_handler.cc
+++ b/chrome/browser/ui/webui/settings/people_handler.cc
@@ -1042,8 +1042,8 @@
                   service->GetUserSettings()->IsPassphraseRequired());
 
   // To distinguish between PassphraseType::FROZEN_IMPLICIT_PASSPHRASE and
-  // PassphraseType::CUSTOM_PASSPHRASE
-  // we only set passphraseTypeIsCustom for PassphraseType::CUSTOM_PASSPHRASE.
+  // PassphraseType::CUSTOM_PASSPHRASE we only set passphraseTypeIsCustom for
+  // PassphraseType::CUSTOM_PASSPHRASE.
   args.SetBoolean("passphraseTypeIsCustom",
                   service->GetUserSettings()->GetPassphraseType() ==
                       syncer::PassphraseType::CUSTOM_PASSPHRASE);
@@ -1082,7 +1082,11 @@
                        GetStringUTF16(IDS_SYNC_FULL_ENCRYPTION_BODY_CUSTOM));
         break;
     }
-  } else if (passphrase_type == syncer::PassphraseType::CUSTOM_PASSPHRASE) {
+  } else if (syncer::IsExplicitPassphrase(passphrase_type)) {
+    args.SetString("enterPassphraseBody",
+                   GetStringUTF16(IDS_SYNC_ENTER_PASSPHRASE_BODY));
+    args.SetString("enterGooglePassphraseBody",
+                   GetStringUTF16(IDS_SYNC_ENTER_GOOGLE_PASSPHRASE_BODY));
     args.SetString("fullEncryptionBody",
                    GetStringUTF16(IDS_SYNC_FULL_ENCRYPTION_BODY_CUSTOM));
   }
diff --git a/chrome/browser/ui/webui/settings/people_handler_unittest.cc b/chrome/browser/ui/webui/settings/people_handler_unittest.cc
index cad8eaaa..51d8260 100644
--- a/chrome/browser/ui/webui/settings/people_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/people_handler_unittest.cc
@@ -1047,6 +1047,7 @@
   const base::DictionaryValue* dictionary = ExpectSyncPrefsChanged();
   CheckBool(dictionary, "passphraseRequired", true);
   CheckBool(dictionary, "passphraseTypeIsCustom", false);
+  EXPECT_TRUE(dictionary->FindKey("enterPassphraseBody"));
 }
 
 TEST_F(PeopleHandlerTest, ShowSetupCustomPassphraseRequired) {
@@ -1063,6 +1064,7 @@
   const base::DictionaryValue* dictionary = ExpectSyncPrefsChanged();
   CheckBool(dictionary, "passphraseRequired", true);
   CheckBool(dictionary, "passphraseTypeIsCustom", true);
+  EXPECT_TRUE(dictionary->FindKey("enterPassphraseBody"));
 }
 
 TEST_F(PeopleHandlerTest, ShowSetupEncryptAll) {
diff --git a/chrome/browser/web_applications/components/web_app_data_retriever.cc b/chrome/browser/web_applications/components/web_app_data_retriever.cc
index 1bea07a..70cc13a 100644
--- a/chrome/browser/web_applications/components/web_app_data_retriever.cc
+++ b/chrome/browser/web_applications/components/web_app_data_retriever.cc
@@ -138,7 +138,7 @@
     const InstallableData& data) {
   DCHECK(data.manifest_url.is_valid() || data.manifest->IsEmpty());
 
-  const bool is_installable = data.error_code == NO_ERROR_DETECTED;
+  const bool is_installable = data.errors.empty();
 
   std::move(callback).Run(*data.manifest, is_installable);
 }
diff --git a/chrome/browser/web_applications/extensions/bookmark_app_installation_task_unittest.cc b/chrome/browser/web_applications/extensions/bookmark_app_installation_task_unittest.cc
index 62d7f205..f065967 100644
--- a/chrome/browser/web_applications/extensions/bookmark_app_installation_task_unittest.cc
+++ b/chrome/browser/web_applications/extensions/bookmark_app_installation_task_unittest.cc
@@ -66,8 +66,8 @@
   void CompleteInstallableCheck() {
     blink::Manifest manifest;
     InstallableData data = {
-        NO_MANIFEST, GURL(), &manifest, GURL(), nullptr,
-        false,       GURL(), nullptr,   false,  false,
+        {NO_MANIFEST}, GURL(), &manifest, GURL(), nullptr,
+        false,         GURL(), nullptr,   false,  false,
     };
     BookmarkAppHelper::OnDidPerformInstallableCheck(data);
   }
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 67324f2..fd0d4d8 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -571,8 +571,6 @@
       "//components/module_installer/android:module_installer_stub_java",
       "//v8:v8_external_startup_data_assets",
     ]
-
-    enable_multidex = true
   } else {  # Not Android.
     sources += [
       # The list of sources which is only used by chrome browser tests on
@@ -3251,8 +3249,6 @@
     } else {
       deps += [ "//v8:v8_external_startup_data_assets" ]
     }
-
-    enable_multidex = true
   } else {
     # !is_android
     sources += [
diff --git a/chrome/test/data/banners/scope_a/bad_manifest.html b/chrome/test/data/banners/scope_a/bad_manifest.html
new file mode 100644
index 0000000..14724acc
--- /dev/null
+++ b/chrome/test/data/banners/scope_a/bad_manifest.html
@@ -0,0 +1,5 @@
+<head>
+  <title>Web app scope_a bad manifest test page</title>
+  <link rel="manifest" href="bad_manifest.webmanifest">
+  <script>navigator.serviceWorker.register('/banners/service_worker.js');</script>
+</head>
diff --git a/chrome/test/data/banners/scope_a/bad_manifest.webmanifest b/chrome/test/data/banners/scope_a/bad_manifest.webmanifest
new file mode 100644
index 0000000..72c50e01
--- /dev/null
+++ b/chrome/test/data/banners/scope_a/bad_manifest.webmanifest
@@ -0,0 +1,3 @@
+{
+  "name": "Bad manifest test app in scope_a",
+}
diff --git a/chrome/test/data/banners/scope_a/manifest.webmanifest b/chrome/test/data/banners/scope_a/manifest.webmanifest
new file mode 100644
index 0000000..06d9f21
--- /dev/null
+++ b/chrome/test/data/banners/scope_a/manifest.webmanifest
@@ -0,0 +1,13 @@
+{
+  "name": "Manifest test app in scope_a",
+  "icons": [
+    {
+      "src": "/banners/image-512px.png",
+      "sizes": "512x512",
+      "type": "image/png"
+    }
+  ],
+  "scope": "/banners/scope_a",
+  "start_url": "page_1.html",
+  "display": "standalone"
+}
diff --git a/chrome/test/data/banners/scope_a/page_1.html b/chrome/test/data/banners/scope_a/page_1.html
new file mode 100644
index 0000000..5eae3e8
--- /dev/null
+++ b/chrome/test/data/banners/scope_a/page_1.html
@@ -0,0 +1,5 @@
+<head>
+  <title>Web app scope_a test page 1</title>
+  <link rel="manifest" href="manifest.webmanifest">
+  <script>navigator.serviceWorker.register('/banners/service_worker.js');</script>
+</head>
diff --git a/chrome/test/data/banners/scope_a/page_2.html b/chrome/test/data/banners/scope_a/page_2.html
new file mode 100644
index 0000000..4625765
--- /dev/null
+++ b/chrome/test/data/banners/scope_a/page_2.html
@@ -0,0 +1,5 @@
+<head>
+  <title>Web app scope_a test page 2</title>
+  <link rel="manifest" href="manifest.webmanifest">
+  <script>navigator.serviceWorker.register('/banners/service_worker.js');</script>
+</head>
diff --git a/chrome/test/data/banners/scope_b/manifest.webmanifest b/chrome/test/data/banners/scope_b/manifest.webmanifest
new file mode 100644
index 0000000..9230500
--- /dev/null
+++ b/chrome/test/data/banners/scope_b/manifest.webmanifest
@@ -0,0 +1,13 @@
+{
+  "name": "Manifest test app in scope_b",
+  "icons": [
+    {
+      "src": "/banners/image-512px.png",
+      "sizes": "512x512",
+      "type": "image/png"
+    }
+  ],
+  "scope": "/banners/scope_b",
+  "start_url": "scope_b.html",
+  "display": "standalone"
+}
diff --git a/chrome/test/data/banners/scope_b/scope_b.html b/chrome/test/data/banners/scope_b/scope_b.html
new file mode 100644
index 0000000..b007aec
--- /dev/null
+++ b/chrome/test/data/banners/scope_b/scope_b.html
@@ -0,0 +1,5 @@
+<head>
+  <title>Web app scope_b test page</title>
+  <link rel="manifest" href="manifest.webmanifest">
+  <script>navigator.serviceWorker.register('/banners/service_worker.js');</script>
+</head>
diff --git a/chrome/test/data/extensions/api_test/automation/tests/tabs/tab_id.js b/chrome/test/data/extensions/api_test/automation/tests/tabs/tab_id.js
index d416e39..49b9f922 100644
--- a/chrome/test/data/extensions/api_test/automation/tests/tabs/tab_id.js
+++ b/chrome/test/data/extensions/api_test/automation/tests/tabs/tab_id.js
@@ -3,12 +3,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-var originalActiveTab;
-
 function createBackgroundTab(url, callback) {
   chrome.tabs.query({ active: true }, function(tabs) {
     chrome.test.assertEq(1, tabs.length);
-    originalActiveTab = tabs[0];
+    var originalActiveTab = tabs[0];
     createTab(url, function(tab) {
       chrome.tabs.update(originalActiveTab.id, { active: true }, function() {
         callback(tab);
@@ -23,45 +21,22 @@
   chrome.test.succeed();
 }
 
-function getTreeForBackgroundTab(foregroundTabRootNode, backgroundTab) {
-  // We haven't cheated and loaded the test in the foreground tab.
-  chrome.test.assertTrue(foregroundTabRootNode.docLoaded);
-  var foregroundTabTitle = foregroundTabRootNode.docTitle;
-  chrome.test.assertFalse(foregroundTabTitle == 'Automation Tests');
-
-}
-
 var allTests = [
   function testGetTabById() {
     getUrlFromConfig('index.html', function(url) {
       // Keep the NTP as the active tab so that we know we're requesting the
       // tab by ID rather than just getting the active tab still.
-      createBackgroundTab(url, function(backgroundTab) {
-        // Fetch the current foreground tab to compare with the background tab.
-        chrome.automation.getTree(originalActiveTab.id,
-                                  function(ntpRootNode) {
-          chrome.test.assertEq(ntpRootNode, undefined,
-                               "Can't get automation tree for NTP");
+      createBackgroundTab(url, function(tab) {
+        chrome.automation.getTree(tab.id, function(rootNode) {
+          if (rootNode.docLoaded) {
+            assertCorrectTab(rootNode);
+            return;
+          }
 
-          chrome.automation.getTree(backgroundTab.id, function(rootNode) {
-            chrome.test.assertFalse(rootNode === undefined,
-                                    "Got automation tree for background tab");
-            chrome.test.assertFalse(
-                rootNode.docLoaded,
-                "Load complete never fires unless tab is foregrounded");
-
-            chrome.tabs.update(backgroundTab.id, { active: true }, function() {
-              if (rootNode.docLoaded) {
-                assertCorrectTab(rootNode);
-                return;
-              }
-
-              rootNode.addEventListener('loadComplete', function() {
-                assertCorrectTab(rootNode);
-              });
-            });
+          rootNode.addEventListener('loadComplete', function() {
+            assertCorrectTab(rootNode);
           });
-        });
+        })
       });
     });
   }
diff --git a/chrome/test/data/extensions/theme_test_toolbar_button_color/manifest.json b/chrome/test/data/extensions/theme_test_toolbar_button_color/manifest.json
new file mode 100644
index 0000000..cfca573
--- /dev/null
+++ b/chrome/test/data/extensions/theme_test_toolbar_button_color/manifest.json
@@ -0,0 +1,14 @@
+{
+  "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuGMyBRIRAOiUgubfmS+4RHiNi/u/okGcU17xoKTXnziTiwmo7lq4V7unr3Vn4wWSG5bIG+yhQhQAG1TvfsXimBIUrrVKX4b9IADZIHR5akWgb1K4EV111PW/D8RDOwYgTPHPx5eA49TxB0Yw36DNC9LE0tPZOGgDR9hCFgaN1bcszlmJBcBmx39iGCeKPIjKB4bp5hXe33T5IUdoyuW4JHf4JDqC6unwQ1Y9nwzCRUbFis7YevbGQCgWP3I+Hp+bOo30mC2/Gq2UZfIczR4vIOrarxBV8IO4Ue4gzaDl6dGkGKnkZ3JdZKbLeRke1DmaKfjVSsTUp++X2nfMaPZphQIDAQAB",
+  "manifest_version": 2,
+  "name": "Toolbar button color test",
+  "theme": {
+    "colors": {
+      "toolbar_button_icon": [255, 0, 0]
+    },
+    "tints": {
+      "buttons": [0.1, 0.1, 0.1]
+    }
+  },
+  "version": "0.0.1"
+}
\ No newline at end of file
diff --git a/chrome/test/data/extensions/theme_test_toolbar_button_tint/manifest.json b/chrome/test/data/extensions/theme_test_toolbar_button_tint/manifest.json
new file mode 100644
index 0000000..340fb7ad
--- /dev/null
+++ b/chrome/test/data/extensions/theme_test_toolbar_button_tint/manifest.json
@@ -0,0 +1,11 @@
+{
+  "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuGMyBRIRAOiUgubfmS+4RHiNi/u/okGcU17xoKTXnziTiwmo7lq4V7unr3Vn4wWSG5bIG+yhQhQAG1TvfsXimBIUrrVKX4b9IADZIHR5akWgb1K4EV111PW/D8RDOwYgTPHPx5eA49TxB0Yw36DNC9LE0tPZOGgDR9hCFgaN1bcszlmJBcBmx39iGCeKPIjKB4bp5hXe33T5IUdoyuW4JHf4JDqC6unwQ1Y9nwzCRUbFis7YevbGQCgWP3I+Hp+bOo30mC2/Gq2UZfIczR4vIOrarxBV8IO4Ue4gzaDl6dGkGKnkZ3JdZKbLeRke1DmaKfjVSsTUp++X2nfMaPZphQIDAQAB",
+  "manifest_version": 2,
+  "name": "Toolbar button color test",
+  "theme": {
+    "tints": {
+      "buttons": [0.1, 0.1, 0.1]
+    }
+  },
+  "version": "0.0.1"
+}
\ No newline at end of file
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index eb51d95..0a58f1d 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-11958.0.0
\ No newline at end of file
+11962.0.0
\ No newline at end of file
diff --git a/components/BUILD.gn b/components/BUILD.gn
index 948478e..486d546 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -56,8 +56,6 @@
   }
 
   if (is_android) {
-    enable_multidex = true
-
     # The tracing unittests require this for testing unwinding. See
     # stack_unwinder_android_unittest.cc.
     if (can_unwind_with_cfi_table && is_official_build) {
@@ -541,7 +539,6 @@
     }
 
     if (is_android) {
-      enable_multidex = true
       sources += [
         "autofill_assistant/browser/web_controller_browsertest.cc",
         "test/android/browsertests_apk/components_browser_tests_jni_onload.cc",
@@ -625,7 +622,6 @@
     ]
 
     if (is_android) {
-      enable_multidex = true
       deps += [ "//ui/android:ui_java" ]
       if (use_v8_context_snapshot) {
         deps += [ "//tools/v8_context_snapshot:v8_context_snapshot_assets" ]
diff --git a/components/autofill/core/browser/address_contact_form_label_formatter.cc b/components/autofill/core/browser/address_contact_form_label_formatter.cc
index 4d02fee..be4f2f0 100644
--- a/components/autofill/core/browser/address_contact_form_label_formatter.cc
+++ b/components/autofill/core/browser/address_contact_form_label_formatter.cc
@@ -8,9 +8,9 @@
 
 AddressContactFormLabelFormatter::AddressContactFormLabelFormatter(
     const std::string& app_locale,
-    FieldTypeGroup focused_group,
+    ServerFieldType focused_field_type,
     const std::vector<ServerFieldType>& field_types)
-    : LabelFormatter(app_locale, focused_group, field_types) {}
+    : LabelFormatter(app_locale, focused_field_type, field_types) {}
 
 AddressContactFormLabelFormatter::~AddressContactFormLabelFormatter() {}
 
diff --git a/components/autofill/core/browser/address_contact_form_label_formatter.h b/components/autofill/core/browser/address_contact_form_label_formatter.h
index 40b4eca..59df9ff 100644
--- a/components/autofill/core/browser/address_contact_form_label_formatter.h
+++ b/components/autofill/core/browser/address_contact_form_label_formatter.h
@@ -21,7 +21,7 @@
  public:
   AddressContactFormLabelFormatter(
       const std::string& app_locale,
-      FieldTypeGroup focused_group,
+      ServerFieldType focused_field_type,
       const std::vector<ServerFieldType>& field_types);
 
   ~AddressContactFormLabelFormatter() override;
diff --git a/components/autofill/core/browser/address_email_form_label_formatter.cc b/components/autofill/core/browser/address_email_form_label_formatter.cc
index 547365a3..e90ee9c5 100644
--- a/components/autofill/core/browser/address_email_form_label_formatter.cc
+++ b/components/autofill/core/browser/address_email_form_label_formatter.cc
@@ -10,9 +10,9 @@
 
 AddressEmailFormLabelFormatter::AddressEmailFormLabelFormatter(
     const std::string& app_locale,
-    FieldTypeGroup focused_group,
+    ServerFieldType focused_field_type,
     const std::vector<ServerFieldType>& field_types)
-    : LabelFormatter(app_locale, focused_group, field_types),
+    : LabelFormatter(app_locale, focused_field_type, field_types),
       form_has_street_address_(HasStreetAddress(field_types_for_labels())) {}
 
 AddressEmailFormLabelFormatter::~AddressEmailFormLabelFormatter() {}
@@ -22,7 +22,7 @@
   std::vector<base::string16> labels;
 
   for (const AutofillProfile* profile : profiles) {
-    switch (focused_group()) {
+    switch (GetFocusedGroup()) {
       case ADDRESS_HOME:
         labels.push_back(GetLabelForFocusedAddress(*profile));
         break;
diff --git a/components/autofill/core/browser/address_email_form_label_formatter.h b/components/autofill/core/browser/address_email_form_label_formatter.h
index 0d8d36a6..e481b83 100644
--- a/components/autofill/core/browser/address_email_form_label_formatter.h
+++ b/components/autofill/core/browser/address_email_form_label_formatter.h
@@ -21,7 +21,7 @@
  public:
   AddressEmailFormLabelFormatter(
       const std::string& app_locale,
-      FieldTypeGroup focused_group,
+      ServerFieldType focused_field_type,
       const std::vector<ServerFieldType>& field_types);
 
   ~AddressEmailFormLabelFormatter() override;
diff --git a/components/autofill/core/browser/address_form_label_formatter.cc b/components/autofill/core/browser/address_form_label_formatter.cc
index 9130868..c9ce2d2 100644
--- a/components/autofill/core/browser/address_form_label_formatter.cc
+++ b/components/autofill/core/browser/address_form_label_formatter.cc
@@ -10,9 +10,9 @@
 
 AddressFormLabelFormatter::AddressFormLabelFormatter(
     const std::string& app_locale,
-    FieldTypeGroup focused_group,
+    ServerFieldType focused_field_type,
     const std::vector<ServerFieldType>& field_types)
-    : LabelFormatter(app_locale, focused_group, field_types) {}
+    : LabelFormatter(app_locale, focused_field_type, field_types) {}
 
 AddressFormLabelFormatter::~AddressFormLabelFormatter() {}
 
@@ -20,7 +20,7 @@
     const std::vector<AutofillProfile*>& profiles) const {
   std::vector<base::string16> labels;
   for (const AutofillProfile* profile : profiles) {
-    if (focused_group() == ADDRESS_HOME) {
+    if (GetFocusedGroup() == ADDRESS_HOME) {
       labels.push_back(GetLabelName(*profile, app_locale()));
     } else {
       labels.push_back(GetLabelNationalAddress(*profile, app_locale(),
diff --git a/components/autofill/core/browser/address_form_label_formatter.h b/components/autofill/core/browser/address_form_label_formatter.h
index fbc8315..a5aac924 100644
--- a/components/autofill/core/browser/address_form_label_formatter.h
+++ b/components/autofill/core/browser/address_form_label_formatter.h
@@ -20,7 +20,7 @@
 class AddressFormLabelFormatter : public LabelFormatter {
  public:
   AddressFormLabelFormatter(const std::string& app_locale,
-                            FieldTypeGroup focused_group,
+                            ServerFieldType focused_field_type,
                             const std::vector<ServerFieldType>& field_types);
 
   ~AddressFormLabelFormatter() override;
diff --git a/components/autofill/core/browser/address_phone_form_label_formatter.cc b/components/autofill/core/browser/address_phone_form_label_formatter.cc
index 845a6c24..5d89ac3 100644
--- a/components/autofill/core/browser/address_phone_form_label_formatter.cc
+++ b/components/autofill/core/browser/address_phone_form_label_formatter.cc
@@ -8,9 +8,9 @@
 
 AddressPhoneFormLabelFormatter::AddressPhoneFormLabelFormatter(
     const std::string& app_locale,
-    FieldTypeGroup focused_group,
+    ServerFieldType focused_field_type,
     const std::vector<ServerFieldType>& field_types)
-    : LabelFormatter(app_locale, focused_group, field_types) {}
+    : LabelFormatter(app_locale, focused_field_type, field_types) {}
 
 AddressPhoneFormLabelFormatter::~AddressPhoneFormLabelFormatter() {}
 
diff --git a/components/autofill/core/browser/address_phone_form_label_formatter.h b/components/autofill/core/browser/address_phone_form_label_formatter.h
index 217fe2c..36fc307 100644
--- a/components/autofill/core/browser/address_phone_form_label_formatter.h
+++ b/components/autofill/core/browser/address_phone_form_label_formatter.h
@@ -21,7 +21,7 @@
  public:
   AddressPhoneFormLabelFormatter(
       const std::string& app_locale,
-      FieldTypeGroup focused_group,
+      ServerFieldType focused_field_type,
       const std::vector<ServerFieldType>& field_types);
 
   ~AddressPhoneFormLabelFormatter() override;
diff --git a/components/autofill/core/browser/contact_form_label_formatter.cc b/components/autofill/core/browser/contact_form_label_formatter.cc
index bf00bcd..c6ceff4d 100644
--- a/components/autofill/core/browser/contact_form_label_formatter.cc
+++ b/components/autofill/core/browser/contact_form_label_formatter.cc
@@ -10,10 +10,11 @@
 
 ContactFormLabelFormatter::ContactFormLabelFormatter(
     const std::string& app_locale,
-    FieldTypeGroup focused_group,
+    ServerFieldType focused_field_type,
     uint32_t groups,
     const std::vector<ServerFieldType>& field_types)
-    : LabelFormatter(app_locale, focused_group, field_types), groups_(groups) {}
+    : LabelFormatter(app_locale, focused_field_type, field_types),
+      groups_(groups) {}
 
 ContactFormLabelFormatter::~ContactFormLabelFormatter() {}
 
@@ -22,7 +23,7 @@
   std::vector<base::string16> labels;
 
   for (const AutofillProfile* profile : profiles) {
-    switch (focused_group()) {
+    switch (GetFocusedGroup()) {
       case EMAIL:
         labels.push_back(GetLabelForFocusedEmail(*profile));
         break;
diff --git a/components/autofill/core/browser/contact_form_label_formatter.h b/components/autofill/core/browser/contact_form_label_formatter.h
index 6ca2e17e..db421236 100644
--- a/components/autofill/core/browser/contact_form_label_formatter.h
+++ b/components/autofill/core/browser/contact_form_label_formatter.h
@@ -20,7 +20,7 @@
 class ContactFormLabelFormatter : public LabelFormatter {
  public:
   ContactFormLabelFormatter(const std::string& app_locale,
-                            FieldTypeGroup focused_group,
+                            ServerFieldType focused_field_type,
                             uint32_t groups,
                             const std::vector<ServerFieldType>& field_types);
 
diff --git a/components/autofill/core/browser/label_formatter.cc b/components/autofill/core/browser/label_formatter.cc
index 3eadaff..7605039 100644
--- a/components/autofill/core/browser/label_formatter.cc
+++ b/components/autofill/core/browser/label_formatter.cc
@@ -23,11 +23,11 @@
 using label_formatter_groups::kPhone;
 
 LabelFormatter::LabelFormatter(const std::string& app_locale,
-                               FieldTypeGroup focused_group,
+                               ServerFieldType focused_field_type,
                                const std::vector<ServerFieldType>& field_types)
-    : app_locale_(app_locale), focused_group_(focused_group) {
+    : app_locale_(app_locale), focused_field_type_(focused_field_type) {
   std::set<FieldTypeGroup> groups{NAME, ADDRESS_HOME, EMAIL, PHONE_HOME};
-  groups.erase(focused_group_);
+  groups.erase(GetFocusedGroup());
 
   auto can_be_shown_in_label = [&groups](ServerFieldType type) -> bool {
     return groups.find(
@@ -42,36 +42,36 @@
 
 LabelFormatter::~LabelFormatter() = default;
 
+FieldTypeGroup LabelFormatter::GetFocusedGroup() const {
+  return AutofillType(AutofillType(focused_field_type_).GetStorableType())
+      .group();
+}
+
 // static
 std::unique_ptr<LabelFormatter> LabelFormatter::Create(
     const std::string& app_locale,
     ServerFieldType focused_field_type,
     const std::vector<ServerFieldType>& field_types) {
   const uint32_t groups = DetermineGroups(field_types);
-  if (!ContainsName(groups)) {
-    return nullptr;
-  }
 
-  const FieldTypeGroup focused_group =
-      AutofillType(AutofillType(focused_field_type).GetStorableType()).group();
   switch (groups) {
     case kName | kAddress | kEmail | kPhone:
       return std::make_unique<AddressContactFormLabelFormatter>(
-          app_locale, focused_group, field_types);
+          app_locale, focused_field_type, field_types);
     case kName | kAddress | kPhone:
       return std::make_unique<AddressPhoneFormLabelFormatter>(
-          app_locale, focused_group, field_types);
+          app_locale, focused_field_type, field_types);
     case kName | kAddress | kEmail:
       return std::make_unique<AddressEmailFormLabelFormatter>(
-          app_locale, focused_group, field_types);
+          app_locale, focused_field_type, field_types);
     case kName | kAddress:
       return std::make_unique<AddressFormLabelFormatter>(
-          app_locale, focused_group, field_types);
+          app_locale, focused_field_type, field_types);
     case kName | kEmail | kPhone:
     case kName | kEmail:
     case kName | kPhone:
       return std::make_unique<ContactFormLabelFormatter>(
-          app_locale, focused_group, groups, field_types);
+          app_locale, focused_field_type, groups, field_types);
     default:
       return nullptr;
   }
diff --git a/components/autofill/core/browser/label_formatter.h b/components/autofill/core/browser/label_formatter.h
index 203eab4d..3a2f411 100644
--- a/components/autofill/core/browser/label_formatter.h
+++ b/components/autofill/core/browser/label_formatter.h
@@ -19,7 +19,7 @@
 class LabelFormatter {
  public:
   LabelFormatter(const std::string& app_locale,
-                 FieldTypeGroup focused_group,
+                 ServerFieldType focused_field_type,
                  const std::vector<ServerFieldType>& field_types);
   virtual ~LabelFormatter();
 
@@ -38,10 +38,15 @@
 
  protected:
   const std::string& app_locale() const { return app_locale_; }
-  FieldTypeGroup focused_group() const { return focused_group_; }
+  ServerFieldType focused_field_type() const { return focused_field_type_; }
   const std::vector<ServerFieldType>& field_types_for_labels() const {
     return field_types_for_labels_;
   }
+  // Returns the FieldTypeGroup with which |focused_field_type_| is associated.
+  // Billing field types are mapped to their corresponding home address field
+  // types. For example, if |focused_field_type_| is ADDRESS_BILLING_ZIP, then
+  // the resulting FieldTypeGroup is ADDRESS_HOME instead of ADDRESS_BILLING.
+  FieldTypeGroup GetFocusedGroup() const;
 
  private:
   // The locale for which to generate labels. This reflects the language and
@@ -49,9 +54,8 @@
   // English.
   std::string app_locale_;
 
-  // The group of the field on which the user is focused. For example, NAME
-  // is the group of the NAME_FIRST and NAME_MIDDLE fields.
-  FieldTypeGroup focused_group_;
+  // The type of field on which the user is focused, e.g. NAME_FIRST.
+  ServerFieldType focused_field_type_;
 
   // A collection of field types that can be used to make labels. It includes
   // types related to names, addresses, email addresses, and phone numbers.
diff --git a/components/autofill/core/browser/payments/autofill_wallet_data_type_controller.cc b/components/autofill/core/browser/payments/autofill_wallet_data_type_controller.cc
index caeb3e4..9b34a82 100644
--- a/components/autofill/core/browser/payments/autofill_wallet_data_type_controller.cc
+++ b/components/autofill/core/browser/payments/autofill_wallet_data_type_controller.cc
@@ -21,7 +21,7 @@
 
 AutofillWalletDataTypeController::AutofillWalletDataTypeController(
     syncer::ModelType type,
-    scoped_refptr<base::SingleThreadTaskRunner> db_thread,
+    scoped_refptr<base::SequencedTaskRunner> db_thread,
     const base::RepeatingClosure& dump_stack,
     syncer::SyncService* sync_service,
     syncer::SyncClient* sync_client,
diff --git a/components/autofill/core/browser/payments/autofill_wallet_data_type_controller.h b/components/autofill/core/browser/payments/autofill_wallet_data_type_controller.h
index af98486..283552a 100644
--- a/components/autofill/core/browser/payments/autofill_wallet_data_type_controller.h
+++ b/components/autofill/core/browser/payments/autofill_wallet_data_type_controller.h
@@ -7,7 +7,7 @@
 
 #include "base/callback.h"
 #include "base/macros.h"
-#include "base/single_thread_task_runner.h"
+#include "base/sequenced_task_runner.h"
 #include "components/prefs/pref_change_registrar.h"
 #include "components/sync/driver/async_directory_type_controller.h"
 
@@ -34,7 +34,7 @@
   // |dump_stack| is called when an unrecoverable error occurs.
   AutofillWalletDataTypeController(
       syncer::ModelType type,
-      scoped_refptr<base::SingleThreadTaskRunner> db_thread,
+      scoped_refptr<base::SequencedTaskRunner> db_thread,
       const base::RepeatingClosure& dump_stack,
       syncer::SyncService* sync_service,
       syncer::SyncClient* sync_client,
diff --git a/components/autofill/core/browser/webdata/autofill_profile_data_type_controller.cc b/components/autofill/core/browser/webdata/autofill_profile_data_type_controller.cc
index ae89e65..13fb57da 100644
--- a/components/autofill/core/browser/webdata/autofill_profile_data_type_controller.cc
+++ b/components/autofill/core/browser/webdata/autofill_profile_data_type_controller.cc
@@ -22,7 +22,7 @@
 namespace browser_sync {
 
 AutofillProfileDataTypeController::AutofillProfileDataTypeController(
-    scoped_refptr<base::SingleThreadTaskRunner> db_thread,
+    scoped_refptr<base::SequencedTaskRunner> db_thread,
     const base::Closure& dump_stack,
     syncer::SyncService* sync_service,
     syncer::SyncClient* sync_client,
diff --git a/components/autofill/core/browser/webdata/autofill_profile_data_type_controller.h b/components/autofill/core/browser/webdata/autofill_profile_data_type_controller.h
index 4641b0d2..c9079ba 100644
--- a/components/autofill/core/browser/webdata/autofill_profile_data_type_controller.h
+++ b/components/autofill/core/browser/webdata/autofill_profile_data_type_controller.h
@@ -10,7 +10,7 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/scoped_observer.h"
-#include "base/single_thread_task_runner.h"
+#include "base/sequenced_task_runner.h"
 #include "components/autofill/core/browser/personal_data_manager_observer.h"
 #include "components/prefs/pref_change_registrar.h"
 #include "components/sync/driver/async_directory_type_controller.h"
@@ -37,7 +37,7 @@
 
   // |dump_stack| is called when an unrecoverable error occurs.
   AutofillProfileDataTypeController(
-      scoped_refptr<base::SingleThreadTaskRunner> db_thread,
+      scoped_refptr<base::SequencedTaskRunner> db_thread,
       const base::Closure& dump_stack,
       syncer::SyncService* sync_service,
       syncer::SyncClient* sync_client,
diff --git a/components/browser_sync/profile_sync_components_factory_impl.cc b/components/browser_sync/profile_sync_components_factory_impl.cc
index c669f35..8ea99da 100644
--- a/components/browser_sync/profile_sync_components_factory_impl.cc
+++ b/components/browser_sync/profile_sync_components_factory_impl.cc
@@ -109,8 +109,8 @@
     browser_sync::BrowserSyncClient* sync_client,
     version_info::Channel channel,
     const char* history_disabled_pref,
-    const scoped_refptr<base::SingleThreadTaskRunner>& ui_thread,
-    const scoped_refptr<base::SingleThreadTaskRunner>& db_thread,
+    const scoped_refptr<base::SequencedTaskRunner>& ui_thread,
+    const scoped_refptr<base::SequencedTaskRunner>& db_thread,
     const scoped_refptr<autofill::AutofillWebDataService>&
         web_data_service_on_disk,
     const scoped_refptr<autofill::AutofillWebDataService>&
diff --git a/components/browser_sync/profile_sync_components_factory_impl.h b/components/browser_sync/profile_sync_components_factory_impl.h
index c68ccd1..c92badf 100644
--- a/components/browser_sync/profile_sync_components_factory_impl.h
+++ b/components/browser_sync/profile_sync_components_factory_impl.h
@@ -11,7 +11,7 @@
 #include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
-#include "base/single_thread_task_runner.h"
+#include "base/sequenced_task_runner.h"
 #include "components/sync/base/model_type.h"
 #include "components/sync/driver/sync_api_component_factory.h"
 #include "components/version_info/version_info.h"
@@ -45,8 +45,8 @@
       BrowserSyncClient* sync_client,
       version_info::Channel channel,
       const char* history_disabled_pref,
-      const scoped_refptr<base::SingleThreadTaskRunner>& ui_thread,
-      const scoped_refptr<base::SingleThreadTaskRunner>& db_thread,
+      const scoped_refptr<base::SequencedTaskRunner>& ui_thread,
+      const scoped_refptr<base::SequencedTaskRunner>& db_thread,
       const scoped_refptr<autofill::AutofillWebDataService>&
           web_data_service_on_disk,
       const scoped_refptr<autofill::AutofillWebDataService>&
@@ -112,8 +112,8 @@
   BrowserSyncClient* const sync_client_;
   const version_info::Channel channel_;
   const char* history_disabled_pref_;
-  const scoped_refptr<base::SingleThreadTaskRunner> ui_thread_;
-  const scoped_refptr<base::SingleThreadTaskRunner> db_thread_;
+  const scoped_refptr<base::SequencedTaskRunner> ui_thread_;
+  const scoped_refptr<base::SequencedTaskRunner> db_thread_;
   const scoped_refptr<autofill::AutofillWebDataService>
       web_data_service_on_disk_;
   const scoped_refptr<autofill::AutofillWebDataService>
diff --git a/components/browser_sync/profile_sync_service.cc b/components/browser_sync/profile_sync_service.cc
index e10c3fb..3b16a74 100644
--- a/components/browser_sync/profile_sync_service.cc
+++ b/components/browser_sync/profile_sync_service.cc
@@ -798,6 +798,15 @@
 
   // Shut all data types down.
   ShutdownImpl(syncer::DISABLE_SYNC);
+
+  // This is the equivalent for Directory::DeleteDirectoryFiles(), guaranteed
+  // to be called, either directly in ShutdownImpl(), or later in
+  // SyncBackendHostCore::DoShutdown().
+  // TODO(crbug.com/923285): This doesn't seem to belong here, or if it does,
+  // all preferences should be cleared via SyncPrefs::ClearPreferences(),
+  // which is done by some of the callers (but not all). Care must be taken
+  // however for scenarios like custom passphrase being set.
+  sync_prefs_.ClearDirectoryConsistencyPreferences();
 }
 
 void ProfileSyncService::ReenableDatatype(syncer::ModelType type) {
@@ -1022,9 +1031,27 @@
       // restart.
       sync_disabled_by_admin_ = true;
       ShutdownImpl(syncer::DISABLE_SYNC);
+      // This is the equivalent for Directory::DeleteDirectoryFiles(),
+      // guaranteed to be called, either directly in ShutdownImpl(), or later in
+      // SyncBackendHostCore::DoShutdown().
+      // TODO(crbug.com/923285): This doesn't seem to belong here, or if it
+      // does, all preferences should be cleared via
+      // SyncPrefs::ClearPreferences(), which is done by some of the callers
+      // (but not all). Care must be taken however for scenarios like custom
+      // passphrase being set.
+      sync_prefs_.ClearDirectoryConsistencyPreferences();
       break;
     case syncer::RESET_LOCAL_SYNC_DATA:
       ShutdownImpl(syncer::DISABLE_SYNC);
+      // This is the equivalent for Directory::DeleteDirectoryFiles(),
+      // guaranteed to be called, either directly in ShutdownImpl(), or later in
+      // SyncBackendHostCore::DoShutdown().
+      // TODO(crbug.com/923285): This doesn't seem to belong here, or if it
+      // does, all preferences should be cleared via
+      // SyncPrefs::ClearPreferences(), which is done by some of the callers
+      // (but not all). Care must be taken however for scenarios like custom
+      // passphrase being set.
+      sync_prefs_.ClearDirectoryConsistencyPreferences();
       startup_controller_->TryStart(IsSetupInProgress());
       break;
     case syncer::UNKNOWN_ACTION:
diff --git a/components/cronet/ios/BUILD.gn b/components/cronet/ios/BUILD.gn
index 5fce3410..3794f79e 100644
--- a/components/cronet/ios/BUILD.gn
+++ b/components/cronet/ios/BUILD.gn
@@ -304,6 +304,8 @@
       "//components/cronet/ios:cronet_framework",
       "--gn-out-dir",
       ".",
+      "--target-os",
+      "ios",
     ]
   }
 
diff --git a/components/password_manager/core/browser/BUILD.gn b/components/password_manager/core/browser/BUILD.gn
index 29cf80b..21eeda84 100644
--- a/components/password_manager/core/browser/BUILD.gn
+++ b/components/password_manager/core/browser/BUILD.gn
@@ -111,6 +111,8 @@
     "password_form_user_action.h",
     "password_generation_frame_helper.cc",
     "password_generation_frame_helper.h",
+    "password_generation_state.cc",
+    "password_generation_state.h",
     "password_list_sorter.cc",
     "password_list_sorter.h",
     "password_manager.cc",
@@ -428,6 +430,7 @@
     "password_form_manager_unittest.cc",
     "password_form_metrics_recorder_unittest.cc",
     "password_generation_frame_helper_unittest.cc",
+    "password_generation_state_unittest.cc",
     "password_hash_data_unittest.cc",
     "password_list_sorter_unittest.cc",
     "password_manager_metrics_recorder_unittest.cc",
diff --git a/components/password_manager/core/browser/form_saver.h b/components/password_manager/core/browser/form_saver.h
index 27037d45..0214149 100644
--- a/components/password_manager/core/browser/form_saver.h
+++ b/components/password_manager/core/browser/form_saver.h
@@ -57,6 +57,9 @@
   // password.
   virtual void RemovePresavedPassword() = 0;
 
+  // Removes |form| from the password store.
+  virtual void Remove(const autofill::PasswordForm& form) = 0;
+
   // Creates a new FormSaver with the same state as |*this|.
   virtual std::unique_ptr<FormSaver> Clone() = 0;
 
diff --git a/components/password_manager/core/browser/form_saver_impl.cc b/components/password_manager/core/browser/form_saver_impl.cc
index 5a3a378..106fb07 100644
--- a/components/password_manager/core/browser/form_saver_impl.cc
+++ b/components/password_manager/core/browser/form_saver_impl.cc
@@ -94,6 +94,10 @@
   presaved_ = nullptr;
 }
 
+void FormSaverImpl::Remove(const PasswordForm& form) {
+  store_->RemoveLogin(form);
+}
+
 std::unique_ptr<FormSaver> FormSaverImpl::Clone() {
   auto result = std::make_unique<FormSaverImpl>(store_);
   if (presaved_)
diff --git a/components/password_manager/core/browser/form_saver_impl.h b/components/password_manager/core/browser/form_saver_impl.h
index 4704b7ea..018d885 100644
--- a/components/password_manager/core/browser/form_saver_impl.h
+++ b/components/password_manager/core/browser/form_saver_impl.h
@@ -36,6 +36,7 @@
   void PresaveGeneratedPassword(
       const autofill::PasswordForm& generated) override;
   void RemovePresavedPassword() override;
+  void Remove(const autofill::PasswordForm& form) override;
   std::unique_ptr<FormSaver> Clone() override;
 
  private:
diff --git a/components/password_manager/core/browser/form_saver_impl_unittest.cc b/components/password_manager/core/browser/form_saver_impl_unittest.cc
index 125a8f8..e9c3dd1 100644
--- a/components/password_manager/core/browser/form_saver_impl_unittest.cc
+++ b/components/password_manager/core/browser/form_saver_impl_unittest.cc
@@ -532,6 +532,14 @@
   clone->PresaveGeneratedPassword(generated);
 }
 
+// Check that Remove() method is relayed properly.
+TEST_F(FormSaverImplTest, Remove) {
+  PasswordForm form = CreatePending("nameofuser", "wordToP4a55");
+
+  EXPECT_CALL(*mock_store_, RemoveLogin(form));
+  form_saver_.Remove(form);
+}
+
 // Check that on saving the pending form |form_data| is sanitized.
 TEST_F(FormSaverImplTest, FormDataSanitized) {
   PasswordForm pending = CreatePending("nameofuser", "wordToP4a55");
diff --git a/components/password_manager/core/browser/new_password_form_manager_unittest.cc b/components/password_manager/core/browser/new_password_form_manager_unittest.cc
index d6081f0..abf031c 100644
--- a/components/password_manager/core/browser/new_password_form_manager_unittest.cc
+++ b/components/password_manager/core/browser/new_password_form_manager_unittest.cc
@@ -202,6 +202,7 @@
   MOCK_METHOD1(PresaveGeneratedPassword,
                void(const autofill::PasswordForm& generated));
   MOCK_METHOD0(RemovePresavedPassword, void());
+  MOCK_METHOD1(Remove, void(const autofill::PasswordForm&));
 
   std::unique_ptr<FormSaver> Clone() override {
     return std::make_unique<MockFormSaver>();
diff --git a/components/password_manager/core/browser/password_form_manager_unittest.cc b/components/password_manager/core/browser/password_form_manager_unittest.cc
index 5dbddae1..538293fe 100644
--- a/components/password_manager/core/browser/password_form_manager_unittest.cc
+++ b/components/password_manager/core/browser/password_form_manager_unittest.cc
@@ -130,6 +130,7 @@
   MOCK_METHOD1(PresaveGeneratedPassword,
                void(const autofill::PasswordForm& generated));
   MOCK_METHOD0(RemovePresavedPassword, void());
+  MOCK_METHOD1(Remove, void(const autofill::PasswordForm&));
 
   std::unique_ptr<FormSaver> Clone() override {
     return std::make_unique<MockFormSaver>();
diff --git a/components/password_manager/core/browser/password_generation_state.cc b/components/password_manager/core/browser/password_generation_state.cc
new file mode 100644
index 0000000..7f3963c5
--- /dev/null
+++ b/components/password_manager/core/browser/password_generation_state.cc
@@ -0,0 +1,55 @@
+// 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 "components/password_manager/core/browser/password_generation_state.h"
+
+#include <utility>
+
+#include "components/password_manager/core/browser/form_saver.h"
+
+namespace password_manager {
+
+using autofill::PasswordForm;
+
+PasswordGenerationState::PasswordGenerationState(FormSaver* form_saver)
+    : form_saver_(form_saver) {}
+
+PasswordGenerationState::~PasswordGenerationState() = default;
+
+std::unique_ptr<PasswordGenerationState> PasswordGenerationState::Clone(
+    FormSaver* form_saver) const {
+  auto clone = std::make_unique<PasswordGenerationState>(form_saver);
+  clone->presaved_ = presaved_;
+  return clone;
+}
+
+void PasswordGenerationState::PresaveGeneratedPassword(PasswordForm generated) {
+  DCHECK(!generated.password_value.empty());
+  if (presaved_) {
+    form_saver_->Update(generated, {} /* best_matches */,
+                        nullptr /* credentials_to_update */,
+                        &presaved_.value() /* old_primary_key */);
+  } else {
+    form_saver_->Save(generated, {} /* best_matches */);
+  }
+  presaved_ = std::move(generated);
+}
+
+void PasswordGenerationState::PasswordNoLongerGenerated() {
+  DCHECK(presaved_);
+  form_saver_->Remove(*presaved_);
+  presaved_.reset();
+}
+
+void PasswordGenerationState::CommitGeneratedPassword(
+    const PasswordForm& generated,
+    const std::map<base::string16, const PasswordForm*>& best_matches,
+    const std::vector<PasswordForm>* credentials_to_update) {
+  DCHECK(presaved_);
+  form_saver_->Update(generated, best_matches, credentials_to_update,
+                      &presaved_.value() /* old_primary_key */);
+  presaved_.reset();
+}
+
+}  // namespace password_manager
diff --git a/components/password_manager/core/browser/password_generation_state.h b/components/password_manager/core/browser/password_generation_state.h
new file mode 100644
index 0000000..79866b4
--- /dev/null
+++ b/components/password_manager/core/browser/password_generation_state.h
@@ -0,0 +1,57 @@
+// 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 COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_PASSWORD_GENERATION_STATE_H_
+#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_PASSWORD_GENERATION_STATE_H_
+
+#include <map>
+#include <memory>
+
+#include "base/optional.h"
+#include "components/autofill/core/common/password_form.h"
+
+namespace password_manager {
+
+class FormSaver;
+
+class PasswordGenerationState {
+ public:
+  explicit PasswordGenerationState(FormSaver* form_saver);
+  ~PasswordGenerationState();
+  PasswordGenerationState(const PasswordGenerationState& rhs) = delete;
+  PasswordGenerationState& operator=(const PasswordGenerationState&) = delete;
+
+  std::unique_ptr<PasswordGenerationState> Clone(FormSaver* form_saver) const;
+
+  // Returns true iff the generated password was presaved.
+  bool HasGeneratedPassword() const { return presaved_.has_value(); }
+
+  // Called when generated password is accepted or changed by user.
+  void PresaveGeneratedPassword(autofill::PasswordForm generated);
+
+  // Signals that the user cancels password generation.
+  void PasswordNoLongerGenerated();
+
+  // Finish the generation flow by saving the final credential |generated| and
+  // leaving the generation state.
+  // |best_matches| constains possible passwords for the current site. They will
+  // be update according to the new preferred state.
+  // |credentials_to_update| are credentials for probably related domain that
+  // should be also updated.
+  void CommitGeneratedPassword(
+      const autofill::PasswordForm& generated,
+      const std::map<base::string16, const autofill::PasswordForm*>&
+          best_matches,
+      const std::vector<autofill::PasswordForm>* credentials_to_update);
+
+ private:
+  // Weak reference to the interface for saving credentials.
+  FormSaver* const form_saver_;
+  // Stores the pre-saved credential.
+  base::Optional<autofill::PasswordForm> presaved_;
+};
+
+}  // namespace password_manager
+
+#endif  // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_PASSWORD_GENERATION_STATE_H_
diff --git a/components/password_manager/core/browser/password_generation_state_unittest.cc b/components/password_manager/core/browser/password_generation_state_unittest.cc
new file mode 100644
index 0000000..46f0a7b8
--- /dev/null
+++ b/components/password_manager/core/browser/password_generation_state_unittest.cc
@@ -0,0 +1,254 @@
+// 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 "components/password_manager/core/browser/password_generation_state.h"
+
+#include "base/memory/scoped_refptr.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_task_environment.h"
+#include "components/password_manager/core/browser/form_saver_impl.h"
+#include "components/password_manager/core/browser/mock_password_store.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace password_manager {
+namespace {
+
+using autofill::PasswordForm;
+using base::ASCIIToUTF16;
+using testing::_;
+
+constexpr char kURL[] = "https://example.in/login";
+constexpr char kSubdomainURL[] = "https://m.example.in/login";
+
+// Creates a dummy saved credential.
+PasswordForm CreateSaved() {
+  PasswordForm form;
+  form.origin = GURL(kURL);
+  form.signon_realm = form.origin.spec();
+  form.action = GURL("https://login.example.org");
+  form.username_value = ASCIIToUTF16("old_username");
+  form.password_value = ASCIIToUTF16("12345");
+  return form;
+}
+
+// Creates a dummy saved PSL credential.
+PasswordForm CreateSavedPSL() {
+  PasswordForm form;
+  form.origin = GURL(kSubdomainURL);
+  form.signon_realm = form.origin.spec();
+  form.action = GURL("https://login.example.org");
+  form.username_value = ASCIIToUTF16("old_username2");
+  form.password_value = ASCIIToUTF16("passw0rd");
+  return form;
+}
+
+// Creates a dummy generated password.
+PasswordForm CreateGenerated() {
+  PasswordForm form;
+  form.origin = GURL(kURL);
+  form.signon_realm = form.origin.spec();
+  form.action = GURL("https://signup.example.org");
+  form.username_value = ASCIIToUTF16("MyName");
+  form.password_value = ASCIIToUTF16("Strong password");
+  form.preferred = true;
+  form.type = autofill::PasswordForm::TYPE_GENERATED;
+  return form;
+}
+
+MATCHER_P(FormHasUniqueKey, key, "") {
+  return ArePasswordFormUniqueKeyEqual(arg, key);
+}
+
+class PasswordGenerationStateTest : public testing::Test {
+ public:
+  PasswordGenerationStateTest();
+  ~PasswordGenerationStateTest() override;
+
+  MockPasswordStore& store() { return *mock_store_; }
+  PasswordGenerationState& state() { return generation_state_; }
+  FormSaverImpl& form_saver() { return form_saver_; }
+
+ private:
+  // For the MockPasswordStore.
+  base::test::ScopedTaskEnvironment task_environment_;
+  scoped_refptr<MockPasswordStore> mock_store_;
+  // Test with the real form saver for better robustness.
+  FormSaverImpl form_saver_;
+  PasswordGenerationState generation_state_;
+};
+
+PasswordGenerationStateTest::PasswordGenerationStateTest()
+    : mock_store_(new testing::StrictMock<MockPasswordStore>()),
+      form_saver_(mock_store_.get()),
+      generation_state_(&form_saver_) {}
+
+PasswordGenerationStateTest::~PasswordGenerationStateTest() {
+  mock_store_->ShutdownOnUIThread();
+}
+
+// Check that presaving a password for the first time results in adding it.
+TEST_F(PasswordGenerationStateTest, PresaveGeneratedPassword_New) {
+  const PasswordForm generated = CreateGenerated();
+
+  EXPECT_CALL(store(), AddLogin(generated));
+  state().PresaveGeneratedPassword(generated);
+  EXPECT_TRUE(state().HasGeneratedPassword());
+}
+
+// Check that presaving a password for the second time results in updating it.
+TEST_F(PasswordGenerationStateTest, PresaveGeneratedPassword_Replace) {
+  PasswordForm generated = CreateGenerated();
+
+  EXPECT_CALL(store(), AddLogin(generated));
+  state().PresaveGeneratedPassword(generated);
+
+  PasswordForm generated_updated = generated;
+  generated_updated.password_value = ASCIIToUTF16("newgenpwd");
+  EXPECT_CALL(store(), UpdateLoginWithPrimaryKey(generated_updated,
+                                                 FormHasUniqueKey(generated)));
+  state().PresaveGeneratedPassword(generated_updated);
+  EXPECT_TRUE(state().HasGeneratedPassword());
+}
+
+// Check that presaving a password for the third time results in updating it.
+TEST_F(PasswordGenerationStateTest, PresaveGeneratedPassword_ReplaceTwice) {
+  PasswordForm generated = CreateGenerated();
+
+  EXPECT_CALL(store(), AddLogin(generated));
+  state().PresaveGeneratedPassword(generated);
+
+  PasswordForm generated_updated = generated;
+  generated_updated.password_value = ASCIIToUTF16("newgenpwd");
+  EXPECT_CALL(store(), UpdateLoginWithPrimaryKey(generated_updated,
+                                                 FormHasUniqueKey(generated)));
+  state().PresaveGeneratedPassword(generated_updated);
+
+  generated = generated_updated;
+  generated_updated.password_value = ASCIIToUTF16("newgenpwd2");
+  generated_updated.username_value = ASCIIToUTF16("newusername");
+  EXPECT_CALL(store(), UpdateLoginWithPrimaryKey(generated_updated,
+                                                 FormHasUniqueKey(generated)));
+  state().PresaveGeneratedPassword(generated_updated);
+  EXPECT_TRUE(state().HasGeneratedPassword());
+}
+
+// Check that presaving a password followed by a call to save a pending
+// credential (as new) results in replacing the presaved password with the
+// pending one.
+TEST_F(PasswordGenerationStateTest, PresaveGeneratedPassword_ThenSaveAsNew) {
+  PasswordForm generated = CreateGenerated();
+
+  EXPECT_CALL(store(), AddLogin(_));
+  state().PresaveGeneratedPassword(generated);
+
+  // User edits after submission.
+  PasswordForm pending = generated;
+  pending.password_value = ASCIIToUTF16("edited_password");
+  pending.username_value = ASCIIToUTF16("edited_username");
+  EXPECT_CALL(store(),
+              UpdateLoginWithPrimaryKey(pending, FormHasUniqueKey(generated)));
+  state().CommitGeneratedPassword(pending, {} /* best_matches */,
+                                  nullptr /* credentials_to_update */);
+  EXPECT_FALSE(state().HasGeneratedPassword());
+}
+
+// Check that presaving a password followed by a call to save a pending
+// credential (as update) results in replacing the presaved password with the
+// pending one.
+TEST_F(PasswordGenerationStateTest, PresaveGeneratedPassword_ThenUpdate) {
+  PasswordForm generated = CreateGenerated();
+
+  EXPECT_CALL(store(), AddLogin(_));
+  state().PresaveGeneratedPassword(generated);
+
+  PasswordForm pending = generated;
+  pending.username_value = ASCIIToUTF16("edited_username");
+
+  PasswordForm old_saved = CreateSaved();
+  old_saved.preferred = false;
+  PasswordForm old_psl_saved = CreateSavedPSL();
+  old_psl_saved.password_value = pending.password_value;
+  std::vector<autofill::PasswordForm> credentials_to_update = {old_psl_saved};
+
+  EXPECT_CALL(store(),
+              UpdateLoginWithPrimaryKey(pending, FormHasUniqueKey(generated)));
+  EXPECT_CALL(store(), UpdateLogin(old_saved));
+  EXPECT_CALL(store(), UpdateLogin(old_psl_saved));
+
+  old_saved.preferred = true;
+  state().CommitGeneratedPassword(
+      pending, {{old_saved.username_value, &old_saved}} /* best_matches */,
+      &credentials_to_update);
+  EXPECT_FALSE(state().HasGeneratedPassword());
+}
+
+// Check that removing a presaved password removes the presaved password.
+TEST_F(PasswordGenerationStateTest, PasswordNoLongerGenerated) {
+  PasswordForm generated = CreateGenerated();
+
+  EXPECT_CALL(store(), AddLogin(_));
+  state().PresaveGeneratedPassword(generated);
+
+  EXPECT_CALL(store(), RemoveLogin(generated));
+  state().PasswordNoLongerGenerated();
+  EXPECT_FALSE(state().HasGeneratedPassword());
+}
+
+// Check that removing the presaved password and then presaving again results in
+// adding the second presaved password as new.
+TEST_F(PasswordGenerationStateTest, PasswordNoLongerGenerated_AndPresaveAgain) {
+  PasswordForm generated = CreateGenerated();
+
+  EXPECT_CALL(store(), AddLogin(generated));
+  state().PresaveGeneratedPassword(generated);
+
+  EXPECT_CALL(store(), RemoveLogin(generated));
+  state().PasswordNoLongerGenerated();
+
+  generated.username_value = ASCIIToUTF16("newgenusername");
+  generated.password_value = ASCIIToUTF16("newgenpwd");
+  EXPECT_CALL(store(), AddLogin(generated));
+  state().PresaveGeneratedPassword(generated);
+  EXPECT_TRUE(state().HasGeneratedPassword());
+}
+
+// Check that presaving a password once in original and then once in clone
+// results in the clone calling update, not a fresh save.
+TEST_F(PasswordGenerationStateTest, PresaveGeneratedPassword_CloneUpdates) {
+  PasswordForm generated = CreateGenerated();
+
+  EXPECT_CALL(store(), AddLogin(generated));
+  state().PresaveGeneratedPassword(generated);
+
+  std::unique_ptr<FormSaver> cloned_saver = form_saver().Clone();
+  std::unique_ptr<PasswordGenerationState> cloned_state =
+      state().Clone(cloned_saver.get());
+  EXPECT_TRUE(cloned_state->HasGeneratedPassword());
+  PasswordForm generated_updated = generated;
+  generated_updated.username_value = ASCIIToUTF16("newname");
+  EXPECT_CALL(store(), UpdateLoginWithPrimaryKey(generated_updated,
+                                                 FormHasUniqueKey(generated)));
+  cloned_state->PresaveGeneratedPassword(generated_updated);
+  EXPECT_TRUE(cloned_state->HasGeneratedPassword());
+}
+
+// Check that a clone can still work after the original is destroyed.
+TEST_F(PasswordGenerationStateTest, PresaveGeneratedPassword_CloneSurvives) {
+  auto original = std::make_unique<PasswordGenerationState>(&form_saver());
+  const PasswordForm generated = CreateGenerated();
+
+  EXPECT_CALL(store(), AddLogin(_));
+  original->PresaveGeneratedPassword(generated);
+
+  std::unique_ptr<FormSaver> cloned_saver = form_saver().Clone();
+  std::unique_ptr<PasswordGenerationState> cloned_state =
+      original->Clone(cloned_saver.get());
+  original.reset();
+  EXPECT_CALL(store(), UpdateLoginWithPrimaryKey(_, _));
+  cloned_state->PresaveGeneratedPassword(generated);
+}
+
+}  // namespace
+}  // namespace password_manager
diff --git a/components/password_manager/core/browser/stub_form_saver.h b/components/password_manager/core/browser/stub_form_saver.h
index 9e1a23d..d3cf6b2 100644
--- a/components/password_manager/core/browser/stub_form_saver.h
+++ b/components/password_manager/core/browser/stub_form_saver.h
@@ -30,6 +30,7 @@
   void PresaveGeneratedPassword(
       const autofill::PasswordForm& generated) override {}
   void RemovePresavedPassword() override {}
+  void Remove(const autofill::PasswordForm& form) override {}
   std::unique_ptr<FormSaver> Clone() override;
 
  private:
diff --git a/components/sync/base/sync_prefs.cc b/components/sync/base/sync_prefs.cc
index 26c1237..1af20ac 100644
--- a/components/sync/base/sync_prefs.cc
+++ b/components/sync/base/sync_prefs.cc
@@ -201,9 +201,8 @@
 void SyncPrefs::ClearPreferences() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  pref_service_->ClearPref(prefs::kSyncCacheGuid);
-  pref_service_->ClearPref(prefs::kSyncBirthday);
-  pref_service_->ClearPref(prefs::kSyncBagOfChips);
+  ClearDirectoryConsistencyPreferences();
+
   pref_service_->ClearPref(prefs::kSyncLastSyncedTime);
   pref_service_->ClearPref(prefs::kSyncLastPollTime);
   pref_service_->ClearPref(prefs::kSyncPollIntervalSeconds);
@@ -224,6 +223,13 @@
   pref_service_->ClearPref(prefs::kSyncFirstSetupComplete);
 }
 
+void SyncPrefs::ClearDirectoryConsistencyPreferences() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  pref_service_->ClearPref(prefs::kSyncCacheGuid);
+  pref_service_->ClearPref(prefs::kSyncBirthday);
+  pref_service_->ClearPref(prefs::kSyncBagOfChips);
+}
+
 bool SyncPrefs::IsFirstSetupComplete() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return pref_service_->GetBoolean(prefs::kSyncFirstSetupComplete);
diff --git a/components/sync/base/sync_prefs.h b/components/sync/base/sync_prefs.h
index 2659252..108af549 100644
--- a/components/sync/base/sync_prefs.h
+++ b/components/sync/base/sync_prefs.h
@@ -77,6 +77,13 @@
   // types.
   void ClearPreferences();
 
+  // Clears only the subset of preferences that are redundant with the sync
+  // directory and used only for verifying consistency with prefs.
+  // TODO(crbug.com/923285): Remove this function and instead rely solely on
+  // ClearPreferences() once investigations are finalized are we understand the
+  // source of discrepancies for UMA Sync.DirectoryVsPrefsConsistency.
+  void ClearDirectoryConsistencyPreferences();
+
   // Getters and setters for global sync prefs.
 
   bool IsFirstSetupComplete() const;
diff --git a/components/sync/device_info/device_info_sync_bridge.cc b/components/sync/device_info/device_info_sync_bridge.cc
index ce147ff..ce7788c 100644
--- a/components/sync/device_info/device_info_sync_bridge.cc
+++ b/components/sync/device_info/device_info_sync_bridge.cc
@@ -497,7 +497,12 @@
       base::Time begin = change_processor()->GetEntityCreationTime(pair.first);
       base::Time end =
           change_processor()->GetEntityModificationTime(pair.first);
-      DCHECK_LE(begin, end);
+      // Begin/end timestamps are received from other devices without local
+      // sanitizing, so potentially the timestamps could be malformed, and the
+      // modification time may predate the creation time.
+      if (begin > end) {
+        continue;
+      }
       relevant_events[pair.second->device_type()].emplace(begin, 1);
       relevant_events[pair.second->device_type()].emplace(end, -1);
     }
diff --git a/components/sync/device_info/device_info_sync_bridge_unittest.cc b/components/sync/device_info/device_info_sync_bridge_unittest.cc
index b856d1f3..e2c05e9 100644
--- a/components/sync/device_info/device_info_sync_bridge_unittest.cc
+++ b/components/sync/device_info/device_info_sync_bridge_unittest.cc
@@ -829,6 +829,36 @@
   EXPECT_EQ(4, bridge()->CountActiveDevices());
 }
 
+TEST_F(DeviceInfoSyncBridgeTest, CountActiveDevicesWithMalformedTimestamps) {
+  InitializeAndPump();
+  // Local device.
+  ASSERT_EQ(1, bridge()->CountActiveDevices());
+
+  const DeviceInfoSpecifics specifics1 = CreateSpecifics(1);
+  const DeviceInfoSpecifics specifics2 = CreateSpecifics(2);
+
+  // Time ranges are overlapping.
+  ON_CALL(*processor(), GetEntityCreationTime(specifics1.cache_guid()))
+      .WillByDefault(
+          Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(1)));
+  ON_CALL(*processor(), GetEntityModificationTime(specifics1.cache_guid()))
+      .WillByDefault(
+          Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(4)));
+  ON_CALL(*processor(), GetEntityCreationTime(specifics2.cache_guid()))
+      .WillByDefault(
+          Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(3)));
+  ON_CALL(*processor(), GetEntityModificationTime(specifics2.cache_guid()))
+      .WillByDefault(
+          Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(2)));
+
+  // With two devices, the local device gets ignored because it doesn't overlap.
+  bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
+                             EntityAddList({specifics1, specifics2}));
+
+  ASSERT_EQ(3u, bridge()->GetAllDeviceInfo().size());
+  EXPECT_EQ(1, bridge()->CountActiveDevices());
+}
+
 TEST_F(DeviceInfoSyncBridgeTest, MultipleOnProviderInitialized) {
   EXPECT_CALL(*processor(), ModelReadyToSync(_)).Times(0);
   set_provider(std::make_unique<LocalDeviceInfoProviderMock>());
diff --git a/components/sync_preferences/pref_model_associator.cc b/components/sync_preferences/pref_model_associator.cc
index 59de63d..960f62e 100644
--- a/components/sync_preferences/pref_model_associator.cc
+++ b/components/sync_preferences/pref_model_associator.cc
@@ -40,9 +40,9 @@
 
 // Enables deleting a pref from Sync if the the user clears it on a client. If
 // this feature is disabled, clearing a pref will cause setting it to the
-// default value instead of deleting it from sync. This has been introduced in
-// M75 as a safety mechanism, should be removed in M76 if no issues are
-// observed.
+// default value instead of deleting it from sync.
+// TODO(crbug.com/943579): This has been introduced in M75 as a safety
+// mechanism, should be removed in M78 if no issues are observed.
 const base::Feature kSyncDeleteClearedPref{"SyncDeleteClearedPrefs",
                                            base::FEATURE_ENABLED_BY_DEFAULT};
 
diff --git a/components/translate/content/browser/DEPS b/components/translate/content/browser/DEPS
index 697e520..f321c73 100644
--- a/components/translate/content/browser/DEPS
+++ b/components/translate/content/browser/DEPS
@@ -1,6 +1,6 @@
 include_rules = [
   "+content/public/browser",
-  "+components/search_engines",
+  "+components/google/core/common",
   "+components/ukm",
   "+services/metrics/public/cpp",
 ]
diff --git a/components/translate/content/browser/content_translate_driver.cc b/components/translate/content/browser/content_translate_driver.cc
index d0afc9d..0a406c9 100644
--- a/components/translate/content/browser/content_translate_driver.cc
+++ b/components/translate/content/browser/content_translate_driver.cc
@@ -12,8 +12,8 @@
 #include "base/logging.h"
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "components/google/core/common/google_util.h"
 #include "components/language/core/browser/url_language_histogram.h"
-#include "components/search_engines/template_url_service.h"
 #include "components/translate/core/browser/translate_download_manager.h"
 #include "components/translate/core/browser/translate_manager.h"
 #include "components/translate/core/common/translate_util.h"
@@ -45,13 +45,11 @@
 
 ContentTranslateDriver::ContentTranslateDriver(
     content::NavigationController* nav_controller,
-    const TemplateURLService* template_url_service,
     language::UrlLanguageHistogram* url_language_histogram)
     : content::WebContentsObserver(nav_controller->GetWebContents()),
       navigation_controller_(nav_controller),
       translate_manager_(nullptr),
       max_reload_check_attempts_(kMaxTranslateLoadCheckAttempts),
-      template_url_service_(template_url_service),
       next_page_seq_no_(0),
       language_histogram_(url_language_histogram),
       weak_pointer_factory_(this) {
@@ -258,39 +256,21 @@
   const base::Optional<url::Origin>& initiator_origin =
       navigation_handle->GetInitiatorOrigin();
 
-  bool navigation_from_dse =
+  bool navigation_from_google =
       initiator_origin.has_value() &&
-      IsDefaultSearchEngineOriginator(initiator_origin.value());
+      google_util::IsGoogleDomainUrl(initiator_origin->GetURL(),
+                                     google_util::DISALLOW_SUBDOMAIN,
+                                     google_util::ALLOW_NON_STANDARD_PORTS);
 
   translate_manager_->GetLanguageState().DidNavigate(
       navigation_handle->IsSameDocument(), navigation_handle->IsInMainFrame(),
-      reload, navigation_handle->GetHrefTranslate(), navigation_from_dse);
+      reload, navigation_handle->GetHrefTranslate(), navigation_from_google);
 }
 
 void ContentTranslateDriver::OnPageAway(int page_seq_no) {
   pages_.erase(page_seq_no);
 }
 
-bool ContentTranslateDriver::IsDefaultSearchEngineOriginator(
-    const url::Origin& originating_origin) const {
-  // This isn't always set in tests
-  if (!template_url_service_)
-    return false;
-
-  const TemplateURL* default_provider =
-      template_url_service_->GetDefaultSearchProvider();
-
-  if (default_provider) {
-    GURL search_url = default_provider->GenerateSearchURL(
-        template_url_service_->search_terms_data());
-
-    return search_url.is_valid() &&
-           url::Origin::Create(search_url) == originating_origin;
-  }
-
-  return false;
-}
-
 void ContentTranslateDriver::AddBinding(
     translate::mojom::ContentTranslateDriverRequest request) {
   bindings_.AddBinding(this, std::move(request));
diff --git a/components/translate/content/browser/content_translate_driver.h b/components/translate/content/browser/content_translate_driver.h
index 55e5170..9dc9ab175 100644
--- a/components/translate/content/browser/content_translate_driver.h
+++ b/components/translate/content/browser/content_translate_driver.h
@@ -28,8 +28,6 @@
 class UrlLanguageHistogram;
 }  // namespace language
 
-class TemplateURLService;
-
 namespace translate {
 
 struct LanguageDetectionDetails;
@@ -65,7 +63,6 @@
 
   ContentTranslateDriver(
       content::NavigationController* nav_controller,
-      const TemplateURLService* template_url_service,
       language::UrlLanguageHistogram* url_language_histogram);
   ~ContentTranslateDriver() override;
 
@@ -124,9 +121,6 @@
  private:
   void OnPageAway(int page_seq_no);
 
-  bool IsDefaultSearchEngineOriginator(
-      const url::Origin& originating_origin) const;
-
   // Creates a URLLoaderFactory that may be used by the translate scripts that
   // get injected into isolated worlds within the page to be translated.  Such
   // scripts (or rather, their isolated worlds) are associated with a
@@ -144,8 +138,6 @@
   // Max number of attempts before checking if a page has been reloaded.
   int max_reload_check_attempts_;
 
-  const TemplateURLService* template_url_service_;
-
   // Records mojo connections with all current alive pages.
   int next_page_seq_no_;
   // PagePtr is the connection between this driver and a TranslateHelper (which
diff --git a/components/translate/core/browser/language_state.cc b/components/translate/core/browser/language_state.cc
index 08bfcc8..f61ea8c 100644
--- a/components/translate/core/browser/language_state.cc
+++ b/components/translate/core/browser/language_state.cc
@@ -28,7 +28,7 @@
                                 bool is_main_frame,
                                 bool reload,
                                 const std::string& href_translate,
-                                bool navigation_from_dse) {
+                                bool navigation_from_google) {
   is_same_document_navigation_ = is_same_document_navigation;
   if (is_same_document_navigation_ || !is_main_frame)
     return;  // Don't reset our states, the page has not changed.
@@ -51,7 +51,7 @@
   translation_error_ = false;
   translation_declined_ = false;
   href_translate_ = href_translate;
-  navigation_from_dse_ = navigation_from_dse;
+  navigation_from_google_ = navigation_from_google;
 
   SetTranslateEnabled(false);
 }
diff --git a/components/translate/core/browser/language_state.h b/components/translate/core/browser/language_state.h
index 3352ef4..96260d1 100644
--- a/components/translate/core/browser/language_state.h
+++ b/components/translate/core/browser/language_state.h
@@ -32,7 +32,7 @@
                    bool is_main_frame,
                    bool reload,
                    const std::string& href_translate,
-                   bool navigation_from_dse);
+                   bool navigation_from_google);
 
   // Should be called when the language of the page has been determined.
   // |page_needs_translation| when false indicates that the browser should not
@@ -80,7 +80,7 @@
   bool HasLanguageChanged() const;
 
   std::string href_translate() const { return href_translate_; }
-  bool navigation_from_dse() const { return navigation_from_dse_; }
+  bool navigation_from_google() const { return navigation_from_google_; }
 
   std::string GetPredefinedTargetLanguage() const {
     return predefined_target_language_;
@@ -141,9 +141,9 @@
   // current navigation, if it was specified.
   std::string href_translate_;
 
-  // True when the current page was the result of a navigation originated in the
-  // origin of the user's default search engine.
-  bool navigation_from_dse_ = false;
+  // True when the current page was the result of a navigation originated in a
+  // Google origin.
+  bool navigation_from_google_ = false;
 
   // Target language set by client.
   std::string predefined_target_language_;
diff --git a/components/translate/core/browser/translate_infobar_delegate.cc b/components/translate/core/browser/translate_infobar_delegate.cc
index 3371696..1bcaac2 100644
--- a/components/translate/core/browser/translate_infobar_delegate.cc
+++ b/components/translate/core/browser/translate_infobar_delegate.cc
@@ -24,7 +24,6 @@
 #include "ui/base/l10n/l10n_util.h"
 
 namespace translate {
-namespace {
 // The number of times user should consecutively translate for "Always
 // Translate" to automatically trigger.
 const int kAutoAlwaysThreshold = 5;
@@ -35,7 +34,6 @@
 const int kMaxNumberOfAutoAlways = 2;
 // The maximum number of times "Never Translate" is automatically triggered.
 const int kMaxNumberOfAutoNever = 2;
-}  // namespace
 
 const base::Feature kTranslateCompactUI{"TranslateCompactUI",
                                         base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/components/translate/core/browser/translate_infobar_delegate.h b/components/translate/core/browser/translate_infobar_delegate.h
index 898c7c34..85127e2 100644
--- a/components/translate/core/browser/translate_infobar_delegate.h
+++ b/components/translate/core/browser/translate_infobar_delegate.h
@@ -29,6 +29,17 @@
 
 namespace translate {
 
+// The number of times user should consecutively translate for "Always
+// Translate" to automatically trigger.
+extern const int kAutoAlwaysThreshold;
+// The number of times user should consecutively dismiss the translate infobar
+// for "Never Translate" to automatically trigger.
+extern const int kAutoNeverThreshold;
+// The maximum number of times "Always Translate" is automatically triggered.
+extern const int kMaxNumberOfAutoAlways;
+// The maximum number of times "Never Translate" is automatically triggered.
+extern const int kMaxNumberOfAutoNever;
+
 // Feature flag for "Translate Compact Infobar UI" project.
 extern const base::Feature kTranslateCompactUI;
 
diff --git a/components/translate/core/browser/translate_manager.cc b/components/translate/core/browser/translate_manager.cc
index 2b5693b8..5575d24 100644
--- a/components/translate/core/browser/translate_manager.cc
+++ b/components/translate/core/browser/translate_manager.cc
@@ -831,7 +831,7 @@
     TranslateTriggerDecision* decision,
     TranslatePrefs* translate_prefs,
     const std::string& page_language_code) {
-  if (!language_state_.navigation_from_dse()) {
+  if (!language_state_.navigation_from_google()) {
     decision->PreventAutoHrefTranslate();
   }
 
diff --git a/content/browser/accessibility/accessibility_win_browsertest.cc b/content/browser/accessibility/accessibility_win_browsertest.cc
index 3f8627d..9729751 100644
--- a/content/browser/accessibility/accessibility_win_browsertest.cc
+++ b/content/browser/accessibility/accessibility_win_browsertest.cc
@@ -2794,6 +2794,7 @@
   // This test simulates a scenario where RenderWidgetHostViewAura::SetSize
   // is not called again after its window is added to the root window.
   // Ensure that we still get a legacy HWND for accessibility.
+
   ASSERT_TRUE(embedded_test_server()->Start());
   WebContentsImpl* web_contents =
       static_cast<WebContentsImpl*>(shell()->web_contents());
@@ -2805,13 +2806,13 @@
   // RenderWidgetHostViewAura with a null parent view.
   web_contents_view_aura->set_init_rwhv_with_null_parent_for_testing(true);
 
-  // Enable accessibility.
-  AccessibilityNotificationWaiter waiter(web_contents, ui::kAXModeComplete,
+  // Navigate to a new page and wait for the accessibility tree to load.
+  AccessibilityNotificationWaiter waiter(shell()->web_contents(),
+                                         ui::kAXModeComplete,
                                          ax::mojom::Event::kLoadComplete);
-
-  // Navigate to a new page.
   NavigateToURL(shell(), embedded_test_server()->GetURL(
                              "/accessibility/html/article.html"));
+  waiter.WaitForNotification();
 
   // At this point the root of the accessibility tree shouldn't have an HWND
   // because we never gave a parent window to the RWHVA.
@@ -2824,8 +2825,6 @@
   // an HWND for accessibility now.
   web_contents_view->GetNativeView()->AddChild(
       web_contents->GetRenderWidgetHostView()->GetNativeView());
-  // The load event will only fire after the page is attached.
-  waiter.WaitForNotification();
   ASSERT_NE(nullptr, manager->GetParentHWND());
 }
 
diff --git a/content/browser/accessibility/dump_accessibility_events_browsertest.cc b/content/browser/accessibility/dump_accessibility_events_browsertest.cc
index 72196c3..9e1db80 100644
--- a/content/browser/accessibility/dump_accessibility_events_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_events_browsertest.cc
@@ -543,18 +543,8 @@
   RunEventTest(FILE_PATH_LITERAL("tbody-focus.html"));
 }
 
-// Even with the deflaking in WaitForAccessibilityTreeToContainNodeWithName,
-// this test is still flaky on Windows.
-// TODO(aboxhall, dmazzoni, meredithl): re-enable with better fix for above.
-#if defined(OS_WIN)
-#define MAYBE_AccessibilityEventsAriaSelectedChanged \
-  DISABLED_AccessibilityEventsAriaSelectedChanged
-#else
-#define MAYBE_AccessibilityEventsAriaSelectedChanged \
-  AccessibilityEventsAriaSelectedChanged
-#endif
 IN_PROC_BROWSER_TEST_F(DumpAccessibilityEventsTest,
-                       MAYBE_AccessibilityEventsAriaSelectedChanged) {
+                       AccessibilityEventsAriaSelectedChanged) {
   RunEventTest(FILE_PATH_LITERAL("aria-selected-changed.html"));
 }
 
diff --git a/content/browser/accessibility/site_per_process_accessibility_browsertest.cc b/content/browser/accessibility/site_per_process_accessibility_browsertest.cc
index be09e2e..241e5fa6 100644
--- a/content/browser/accessibility/site_per_process_accessibility_browsertest.cc
+++ b/content/browser/accessibility/site_per_process_accessibility_browsertest.cc
@@ -136,9 +136,8 @@
   EXPECT_EQ(ax_child_frame_root->PlatformGetParent(), ax_iframe);
 }
 
-// TODO(aboxhall): Flaky test, discuss with dmazzoni
 IN_PROC_BROWSER_TEST_F(MAYBE_SitePerProcessAccessibilityBrowserTest,
-                       DISABLED_TwoCrossSiteNavigations) {
+                       TwoCrossSiteNavigations) {
   // Enable full accessibility for all current and future WebContents.
   BrowserAccessibilityState::GetInstance()->EnableAccessibility();
 
diff --git a/content/browser/frame_host/frame_tree_node.cc b/content/browser/frame_host/frame_tree_node.cc
index 6036fde..7e443f7c 100644
--- a/content/browser/frame_host/frame_tree_node.cc
+++ b/content/browser/frame_host/frame_tree_node.cc
@@ -563,10 +563,8 @@
   }
   replication_state_.has_received_user_gesture = true;
 
-  // TODO(mustaq): The following block relaxes UAv2 a bit to make it slightly
-  // closer to the old (v1) model, to address a Hangout regression.  We will
-  // remove this after implementing a mechanism to delegate activation to
-  // subframes (https://crbug.com/728334)
+  // See the "Same-origin Visibility" section in |UserActivationState| class
+  // doc.
   if (base::FeatureList::IsEnabled(features::kUserActivationV2) &&
       base::FeatureList::IsEnabled(
           features::kUserActivationSameOriginVisibility)) {
diff --git a/content/browser/frame_host/frame_tree_node.h b/content/browser/frame_host/frame_tree_node.h
index ca60b43b..c6700766 100644
--- a/content/browser/frame_host/frame_tree_node.h
+++ b/content/browser/frame_host/frame_tree_node.h
@@ -535,32 +535,8 @@
 
   bool was_discarded_;
 
-  // The user activation state of the current frame.
-  //
-  // Changes to this state update other FrameTreeNodes as follows: for
-  // notification updates (on user inputs) all ancestor nodes are updated; for
-  // activation consumption calls, the whole frame tree is updated (w/o such
-  // exhaustive consumption, a rouge iframe can cause multiple consumptions per
-  // user activation).
-  //
-  // The user activation state is replicated in the browser process (in
-  // FrameTreeNode) and in the renderer processes (in LocalFrame and
-  // RemoteFrames).  The replicated states across the browser and renderer
-  // processes are kept in sync as follows:
-  //
-  // [A] Consumption of activation state for popups starts in the frame tree of
-  // the browser process and propagate to the renderer trees through direct IPCs
-  // (one IPC sent to each renderer).
-  //
-  // [B] Consumption calls from JS/blink side (e.g. video picture-in-picture)
-  // update the originating renderer's local frame tree and send an IPC to the
-  // browser; the browser updates its frame tree and sends IPCs to all other
-  // renderers each of which then updates its local frame tree.
-  //
-  // [B'] Notification updates on user inputs still follow [B] but they should
-  // really follow [A].  TODO(mustaq): fix through https://crbug.com/848778.
-  //
-  // [C] Expiration of an active state is tracked independently in each process.
+  // The user activation state of the current frame.  See |UserActivationState|
+  // for details on how this state is maintained.
   blink::UserActivationState user_activation_state_;
 
   // A helper for tracing the snapshots of this FrameTreeNode and attributing
diff --git a/content/browser/frame_host/navigation_controller_impl_unittest.cc b/content/browser/frame_host/navigation_controller_impl_unittest.cc
index 704f321..bf29c1a 100644
--- a/content/browser/frame_host/navigation_controller_impl_unittest.cc
+++ b/content/browser/frame_host/navigation_controller_impl_unittest.cc
@@ -966,42 +966,42 @@
 
   // First make some history, starting with a privileged URL.
   const GURL kExistingURL1("http://privileged");
-  controller.LoadURL(
-      kExistingURL1, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
-  int entry_id = controller.GetPendingEntry()->GetUniqueID();
-  // Pretend it has bindings so we can tell if we incorrectly copy it.
+  auto navigation =
+      NavigationSimulator::CreateBrowserInitiated(kExistingURL1, contents());
+  navigation->Start();
+  navigation->ReadyToCommit();
+  // Pretend it has bindings so we can tell if we incorrectly copy it. This has
+  // to be done after ReadyToCommit, otherwise we won't use the current RFH to
+  // commit since its bindings don't match the URL.
   main_test_rfh()->AllowBindings(BINDINGS_POLICY_MOJO_WEB_UI);
-  main_test_rfh()->PrepareForCommit();
-  main_test_rfh()->SendNavigate(entry_id, true, kExistingURL1);
+  navigation->Commit();
   EXPECT_EQ(1U, navigation_entry_committed_counter_);
   navigation_entry_committed_counter_ = 0;
+  EXPECT_EQ(BINDINGS_POLICY_MOJO_WEB_UI,
+            controller.GetLastCommittedEntry()->bindings());
 
   // Navigate cross-process to a second URL.
   const GURL kExistingURL2("http://foo/eh");
-  controller.LoadURL(
-      kExistingURL2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
-  entry_id = controller.GetPendingEntry()->GetUniqueID();
-  main_test_rfh()->PrepareForCommit();
-  TestRenderFrameHost* foo_rfh = contents()->GetPendingMainFrame();
-  foo_rfh->SendNavigate(entry_id, true, kExistingURL2);
+  NavigationSimulator::NavigateAndCommitFromBrowser(contents(), kExistingURL2);
   EXPECT_EQ(1U, navigation_entry_committed_counter_);
   navigation_entry_committed_counter_ = 0;
+  EXPECT_EQ(0, controller.GetLastCommittedEntry()->bindings());
 
   // Now make a pending back/forward navigation to a privileged entry.
   // The zeroth entry should be pending.
-  controller.GoBack();
-  foo_rfh->SendBeforeUnloadACK(true);
+  auto back_navigation =
+      NavigationSimulator::CreateHistoryNavigation(-1, contents());
+  back_navigation->ReadyToCommit();
   EXPECT_EQ(0U, navigation_entry_changed_counter_);
   EXPECT_EQ(0U, navigation_list_pruned_counter_);
   EXPECT_EQ(0, controller.GetPendingEntryIndex());
   EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
-  EXPECT_EQ(2, controller.GetPendingEntry()->bindings());
+  EXPECT_EQ(BINDINGS_POLICY_MOJO_WEB_UI,
+            controller.GetPendingEntry()->bindings());
 
   // Before that commits, do a new navigation.
   const GURL kNewURL("http://foo/bee");
-  foo_rfh->SendRendererInitiatedNavigationRequest(kNewURL, true);
-  foo_rfh->PrepareForCommit();
-  foo_rfh->SendNavigate(0, true, kNewURL);
+  NavigationSimulator::NavigateAndCommitFromDocument(kNewURL, main_test_rfh());
 
   // There should no longer be any pending entry, and the new navigation we
   // just made should be committed.
@@ -1626,24 +1626,17 @@
   const GURL url2("http://foo/2");
   const GURL url3("http://foo/3");
 
-  controller.LoadURL(
-      url1, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
-  int entry1_id = controller.GetPendingEntry()->GetUniqueID();
-  main_test_rfh()->PrepareForCommit();
-  main_test_rfh()->SendNavigate(entry1_id, true, url1);
-  EXPECT_EQ(1U, navigation_entry_committed_counter_);
-  navigation_entry_committed_counter_ = 0;
-  entry1_id = controller.GetLastCommittedEntry()->GetUniqueID();
-
-  controller.LoadURL(
-      url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
-  int entry_id = controller.GetPendingEntry()->GetUniqueID();
-  main_test_rfh()->PrepareForCommit();
-  main_test_rfh()->SendNavigate(entry_id, true, url2);
+  NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url1);
   EXPECT_EQ(1U, navigation_entry_committed_counter_);
   navigation_entry_committed_counter_ = 0;
 
-  controller.GoBack();
+  NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url2);
+  EXPECT_EQ(1U, navigation_entry_committed_counter_);
+  navigation_entry_committed_counter_ = 0;
+
+  auto navigation =
+      NavigationSimulator::CreateHistoryNavigation(-1, contents());
+  navigation->Start();
   EXPECT_EQ(0U, navigation_entry_changed_counter_);
   EXPECT_EQ(0U, navigation_list_pruned_counter_);
 
@@ -1656,21 +1649,19 @@
   EXPECT_FALSE(controller.CanGoBack());
   EXPECT_TRUE(controller.CanGoForward());
 
-  main_test_rfh()->PrepareForCommitWithServerRedirect(url3);
-  main_test_rfh()->SendNavigateWithTransition(
-      entry1_id, true, url3, controller.GetPendingEntry()->GetTransitionType());
+  navigation->Redirect(url3);
+  navigation->Commit();
   EXPECT_EQ(1U, navigation_entry_committed_counter_);
   navigation_entry_committed_counter_ = 0;
 
-  // The back navigation resulted in a completely new navigation.
-  // TODO(darin): perhaps this behavior will be confusing to users?
-  EXPECT_EQ(controller.GetEntryCount(), 3);
-  EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 2);
+  // The first NavigationEntry should have been used.
+  EXPECT_EQ(controller.GetEntryCount(), 2);
+  EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
   EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
   EXPECT_TRUE(controller.GetLastCommittedEntry());
   EXPECT_FALSE(controller.GetPendingEntry());
-  EXPECT_TRUE(controller.CanGoBack());
-  EXPECT_FALSE(controller.CanGoForward());
+  EXPECT_FALSE(controller.CanGoBack());
+  EXPECT_TRUE(controller.CanGoForward());
 }
 
 // Receives a back message when there is a new pending navigation entry.
@@ -2639,19 +2630,8 @@
   EXPECT_EQ(timestamp, our_controller.GetEntryAtIndex(0)->GetTimestamp());
 
   // Say we navigated to that entry.
-  FrameHostMsg_DidCommitProvisionalLoad_Params params;
-  params.nav_entry_id = our_controller.GetPendingEntry()->GetUniqueID();
-  params.did_create_new_entry = false;
-  params.url = url;
-  params.transition = ui::PAGE_TRANSITION_LINK;
-  params.should_update_history = false;
-  params.gesture = NavigationGestureUser;
-  params.method = "GET";
-  params.page_state = PageState::CreateFromURL(url);
-  TestRenderFrameHost* main_rfh =
-      static_cast<TestRenderFrameHost*>(raw_our_contents->GetMainFrame());
-  main_rfh->PrepareForCommit();
-  main_rfh->SendNavigateWithParams(&params, false);
+  auto navigation = NavigationSimulator::CreateFromPending(raw_our_contents);
+  navigation->Commit();
 
   // There should be no longer any pending entry and one committed one. This
   // means that we were able to locate the entry, assign its site instance, and
diff --git a/content/browser/frame_host/render_frame_host_manager_unittest.cc b/content/browser/frame_host/render_frame_host_manager_unittest.cc
index 0df214a9..22ec973 100644
--- a/content/browser/frame_host/render_frame_host_manager_unittest.cc
+++ b/content/browser/frame_host/render_frame_host_manager_unittest.cc
@@ -502,19 +502,6 @@
                                                     nodes_with_back_links);
   }
 
-  void BaseSimultaneousNavigationWithOneWebUI(
-      const std::function<void(RenderFrameHostImpl*,
-                               RenderFrameHostImpl*,
-                               WebUIImpl*,
-                               RenderFrameHostManager*)>& commit_lambda);
-
-  void BaseSimultaneousNavigationWithTwoWebUIs(
-      const std::function<void(RenderFrameHostImpl*,
-                               RenderFrameHostImpl*,
-                               WebUIImpl*,
-                               WebUIImpl*,
-                               RenderFrameHostManager*)>& commit_lambda);
-
  private:
   RenderFrameHostManagerTestWebUIControllerFactory factory_;
 };
@@ -2573,15 +2560,12 @@
   EXPECT_FALSE(current_host->pending_web_ui());
 }
 
-// Shared code until before commit for the SimultaneousNavigationWithOneWebUI*
-// tests, accepting a lambda to execute the commit step.
-void RenderFrameHostManagerTest::BaseSimultaneousNavigationWithOneWebUI(
-    const std::function<void(RenderFrameHostImpl*,
-                             RenderFrameHostImpl*,
-                             WebUIImpl*,
-                             RenderFrameHostManager*)>& commit_lambda) {
+// Simulates two simultaneous navigations involving one WebUI where the current
+// RenderFrameHost commits.
+TEST_F(RenderFrameHostManagerTest, SimultaneousNavigationWithOneWebUI1) {
   set_should_create_webui(true);
-  NavigateActiveAndCommit(GURL("chrome://foo/"));
+  NavigationSimulator::NavigateAndCommitFromBrowser(contents(),
+                                                    GURL("chrome://foo/"));
 
   RenderFrameHostManager* manager = contents()->GetRenderManagerForTesting();
   RenderFrameHostImpl* host1 = manager->current_frame_host();
@@ -2591,7 +2575,8 @@
 
   // Starts a reload of the WebUI page.
   contents()->GetController().Reload(ReloadType::NORMAL, true);
-  main_test_rfh()->PrepareForCommit();
+  auto reload = NavigationSimulator::CreateFromPending(contents());
+  reload->ReadyToCommit();
 
   // It should be a same-site navigation reusing the same WebUI.
   EXPECT_EQ(web_ui, manager->GetNavigatingWebUI());
@@ -2601,11 +2586,10 @@
 
   // Navigation request to a non-WebUI page.
   const GURL kUrl("http://google.com");
-  NavigationEntryImpl entry(
-      nullptr /* instance */, kUrl, Referrer(), base::string16() /* title */,
-      ui::PAGE_TRANSITION_TYPED, false /* is_renderer_init */,
-      nullptr /* blob_url_loader_factory */);
-  RenderFrameHostImpl* host2 = NavigateToEntry(manager, &entry);
+  auto navigation =
+      NavigationSimulator::CreateBrowserInitiated(kUrl, contents());
+  navigation->ReadyToCommit();
+  RenderFrameHostImpl* host2 = GetPendingFrameHost(manager);
   ASSERT_TRUE(host2);
 
   // The previous navigation should still be ongoing along with the new,
@@ -2619,63 +2603,90 @@
   EXPECT_EQ(web_ui, host1->pending_web_ui());
 
   EXPECT_NE(host2, host1);
-  EXPECT_EQ(host2, GetPendingFrameHost(manager));
   EXPECT_FALSE(host2->web_ui());
   EXPECT_FALSE(host2->pending_web_ui());
   EXPECT_NE(web_ui, host2->web_ui());
 
-  commit_lambda(host1, host2, web_ui, manager);
-}
-
-// Simulates two simultaneous navigations involving one WebUI where the current
-// RenderFrameHost commits.
-TEST_F(RenderFrameHostManagerTest, SimultaneousNavigationWithOneWebUI1) {
-  auto commit_current_frame_host = [this](
-      RenderFrameHostImpl* host1, RenderFrameHostImpl* host2, WebUIImpl* web_ui,
-      RenderFrameHostManager* manager) {
     // The current RenderFrameHost commits; its WebUI should still be in place.
-    manager->DidNavigateFrame(host1, true /* was_caused_by_user_gesture */,
-                              false /* is_same_document_navigation */);
-    EXPECT_EQ(host1, manager->current_frame_host());
-    EXPECT_EQ(web_ui, host1->web_ui());
-    EXPECT_FALSE(host1->pending_web_ui());
-    EXPECT_FALSE(manager->GetNavigatingWebUI());
-    EXPECT_FALSE(GetPendingFrameHost(manager));
-  };
+  reload->Commit();
+  EXPECT_EQ(host1, manager->current_frame_host());
+  EXPECT_EQ(web_ui, host1->web_ui());
+  EXPECT_FALSE(host1->pending_web_ui());
+  EXPECT_FALSE(manager->GetNavigatingWebUI());
 
-  BaseSimultaneousNavigationWithOneWebUI(commit_current_frame_host);
+  // Because the Navigation that committed was browser-initiated, it will not
+  // have the user gesture bit set to true. This has the side-effect of not
+  // deleting the speculative RenderFrameHost at commit time.
+  // TODO(clamy): The speculative RenderFrameHost should be deleted at commit
+  // time if a browser-initiated navigation commits.
+  EXPECT_TRUE(GetPendingFrameHost(manager));
 }
 
 // Simulates two simultaneous navigations involving one WebUI where the new,
 // cross-site RenderFrameHost commits.
 TEST_F(RenderFrameHostManagerTest, SimultaneousNavigationWithOneWebUI2) {
-  auto commit_new_frame_host = [this](
-      RenderFrameHostImpl* host1, RenderFrameHostImpl* host2, WebUIImpl* web_ui,
-      RenderFrameHostManager* manager) {
-    // The new RenderFrameHost commits; there should be no active WebUI.
-    manager->DidNavigateFrame(host2, true /* was_caused_by_user_gesture */,
-                              false /* is_same_document_navigation */);
-    EXPECT_EQ(host2, manager->current_frame_host());
-    EXPECT_FALSE(host2->web_ui());
-    EXPECT_FALSE(host2->pending_web_ui());
-    EXPECT_FALSE(manager->GetNavigatingWebUI());
-    EXPECT_FALSE(GetPendingFrameHost(manager));
-  };
+  set_should_create_webui(true);
+  NavigationSimulator::NavigateAndCommitFromBrowser(contents(),
+                                                    GURL("chrome://foo/"));
 
-  BaseSimultaneousNavigationWithOneWebUI(commit_new_frame_host);
+  RenderFrameHostManager* manager = contents()->GetRenderManagerForTesting();
+  RenderFrameHostImpl* host1 = manager->current_frame_host();
+  EXPECT_TRUE(host1->IsRenderFrameLive());
+  WebUIImpl* web_ui = host1->web_ui();
+  EXPECT_TRUE(web_ui);
+
+  // Starts a reload of the WebUI page.
+  contents()->GetController().Reload(ReloadType::NORMAL, true);
+  auto reload = NavigationSimulator::CreateFromPending(contents());
+  reload->ReadyToCommit();
+
+  // It should be a same-site navigation reusing the same WebUI.
+  EXPECT_EQ(web_ui, manager->GetNavigatingWebUI());
+  EXPECT_EQ(web_ui, host1->web_ui());
+  EXPECT_EQ(web_ui, host1->pending_web_ui());
+  EXPECT_FALSE(GetPendingFrameHost(manager));
+
+  // Navigation request to a non-WebUI page.
+  const GURL kUrl("http://google.com");
+  auto navigation =
+      NavigationSimulator::CreateBrowserInitiated(kUrl, contents());
+  navigation->ReadyToCommit();
+  RenderFrameHostImpl* host2 = GetPendingFrameHost(manager);
+  ASSERT_TRUE(host2);
+
+  // The previous navigation should still be ongoing along with the new,
+  // cross-site one.
+  // Note: Simultaneous navigations are weird: there are two ongoing
+  // navigations, a same-site using a WebUI and a cross-site not using one. So
+  // it's unclear what GetNavigatingWebUI should return in this case. As it
+  // currently favors the cross-site navigation it returns null.
+  EXPECT_FALSE(manager->GetNavigatingWebUI());
+  EXPECT_EQ(web_ui, host1->web_ui());
+  EXPECT_EQ(web_ui, host1->pending_web_ui());
+
+  EXPECT_NE(host2, host1);
+  EXPECT_FALSE(host2->web_ui());
+  EXPECT_FALSE(host2->pending_web_ui());
+  EXPECT_NE(web_ui, host2->web_ui());
+
+  // The new RenderFrameHost commits; there should be no active WebUI.
+  navigation->Commit();
+  EXPECT_EQ(host2, manager->current_frame_host());
+  EXPECT_FALSE(host2->web_ui());
+  EXPECT_FALSE(host2->pending_web_ui());
+  EXPECT_FALSE(manager->GetNavigatingWebUI());
+  EXPECT_FALSE(GetPendingFrameHost(manager));
 }
 
 // Shared code until before commit for the SimultaneousNavigationWithTwoWebUIs*
 // tests, accepting a lambda to execute the commit step.
-void RenderFrameHostManagerTest::BaseSimultaneousNavigationWithTwoWebUIs(
-    const std::function<void(RenderFrameHostImpl*,
-                             RenderFrameHostImpl*,
-                             WebUIImpl*,
-                             WebUIImpl*,
-                             RenderFrameHostManager*)>& commit_lambda) {
+// Simulates two simultaneous navigations involving two WebUIs where the current
+// RenderFrameHost commits.
+TEST_F(RenderFrameHostManagerTest, SimultaneousNavigationWithTwoWebUIs1) {
   set_should_create_webui(true);
   set_webui_type(1);
-  NavigateActiveAndCommit(GURL("chrome://foo/"));
+  NavigationSimulator::NavigateAndCommitFromBrowser(contents(),
+                                                    GURL("chrome://foo/"));
 
   RenderFrameHostManager* manager = contents()->GetRenderManagerForTesting();
   RenderFrameHostImpl* host1 = manager->current_frame_host();
@@ -2685,6 +2696,8 @@
 
   // Starts a reload of the WebUI page.
   contents()->GetController().Reload(ReloadType::NORMAL, true);
+  auto reload = NavigationSimulator::CreateFromPending(contents());
+  reload->ReadyToCommit();
 
   // It should be a same-site navigation reusing the same WebUI.
   EXPECT_EQ(web_ui1, manager->GetNavigatingWebUI());
@@ -2695,11 +2708,10 @@
   // Navigation another WebUI page, with a different type.
   set_webui_type(2);
   const GURL kUrl("chrome://bar/");
-  NavigationEntryImpl entry(
-      nullptr /* instance */, kUrl, Referrer(), base::string16() /* title */,
-      ui::PAGE_TRANSITION_TYPED, false /* is_renderer_init */,
-      nullptr /* blob_url_loader_factory */);
-  RenderFrameHostImpl* host2 = NavigateToEntry(manager, &entry);
+  auto navigation =
+      NavigationSimulator::CreateBrowserInitiated(kUrl, contents());
+  navigation->ReadyToCommit();
+  RenderFrameHostImpl* host2 = GetPendingFrameHost(manager);
   ASSERT_TRUE(host2);
 
   // The previous navigation should still be ongoing along with the new,
@@ -2716,49 +2728,81 @@
   EXPECT_NE(web_ui2, web_ui1);
 
   EXPECT_NE(host2, host1);
-  EXPECT_EQ(host2, GetPendingFrameHost(manager));
   EXPECT_EQ(web_ui2, host2->web_ui());
   EXPECT_FALSE(host2->pending_web_ui());
 
-  commit_lambda(host1, host2, web_ui1, web_ui2, manager);
-}
-
-// Simulates two simultaneous navigations involving two WebUIs where the current
-// RenderFrameHost commits.
-TEST_F(RenderFrameHostManagerTest, SimultaneousNavigationWithTwoWebUIs1) {
-  auto commit_current_frame_host = [this](
-      RenderFrameHostImpl* host1, RenderFrameHostImpl* host2,
-      WebUIImpl* web_ui1, WebUIImpl* web_ui2, RenderFrameHostManager* manager) {
     // The current RenderFrameHost commits; its WebUI should still be active.
-    manager->DidNavigateFrame(host1, true /* was_caused_by_user_gesture */,
-                              false /* is_same_document_navigation */);
-    EXPECT_EQ(host1, manager->current_frame_host());
-    EXPECT_EQ(web_ui1, host1->web_ui());
-    EXPECT_FALSE(host1->pending_web_ui());
-    EXPECT_FALSE(manager->GetNavigatingWebUI());
-    EXPECT_FALSE(GetPendingFrameHost(manager));
-  };
+  reload->Commit();
+  EXPECT_EQ(host1, manager->current_frame_host());
+  EXPECT_EQ(web_ui1, host1->web_ui());
+  EXPECT_FALSE(host1->pending_web_ui());
 
-  BaseSimultaneousNavigationWithTwoWebUIs(commit_current_frame_host);
+  // Because the Navigation that committed was browser-initiated, it will not
+  // have the user gesture bit set to true. This has the side-effect of not
+  // deleting the speculative RenderFrameHost at commit time.
+  // TODO(clamy): The speculative RenderFrameHost should be deleted at commit
+  // time if a browser-initiated navigation commits.
+  EXPECT_TRUE(manager->GetNavigatingWebUI());
+  EXPECT_TRUE(GetPendingFrameHost(manager));
 }
 
 // Simulates two simultaneous navigations involving two WebUIs where the new,
 // cross-site RenderFrameHost commits.
 TEST_F(RenderFrameHostManagerTest, SimultaneousNavigationWithTwoWebUIs2) {
-  auto commit_new_frame_host = [this](
-      RenderFrameHostImpl* host1, RenderFrameHostImpl* host2,
-      WebUIImpl* web_ui1, WebUIImpl* web_ui2, RenderFrameHostManager* manager) {
-    // The new RenderFrameHost commits; its WebUI should now be active.
-    manager->DidNavigateFrame(host2, true /* was_caused_by_user_gesture */,
-                              false /* is_same_document_navigation */);
-    EXPECT_EQ(host2, manager->current_frame_host());
-    EXPECT_EQ(web_ui2, host2->web_ui());
-    EXPECT_FALSE(host2->pending_web_ui());
-    EXPECT_FALSE(manager->GetNavigatingWebUI());
-    EXPECT_FALSE(GetPendingFrameHost(manager));
-  };
+  set_should_create_webui(true);
+  set_webui_type(1);
+  NavigationSimulator::NavigateAndCommitFromBrowser(contents(),
+                                                    GURL("chrome://foo/"));
 
-  BaseSimultaneousNavigationWithTwoWebUIs(commit_new_frame_host);
+  RenderFrameHostManager* manager = contents()->GetRenderManagerForTesting();
+  RenderFrameHostImpl* host1 = manager->current_frame_host();
+  EXPECT_TRUE(host1->IsRenderFrameLive());
+  WebUIImpl* web_ui1 = host1->web_ui();
+  EXPECT_TRUE(web_ui1);
+
+  // Starts a reload of the WebUI page.
+  contents()->GetController().Reload(ReloadType::NORMAL, true);
+  auto reload = NavigationSimulator::CreateFromPending(contents());
+  reload->ReadyToCommit();
+
+  // It should be a same-site navigation reusing the same WebUI.
+  EXPECT_EQ(web_ui1, manager->GetNavigatingWebUI());
+  EXPECT_EQ(web_ui1, host1->web_ui());
+  EXPECT_EQ(web_ui1, host1->pending_web_ui());
+  EXPECT_FALSE(GetPendingFrameHost(manager));
+
+  // Navigation another WebUI page, with a different type.
+  set_webui_type(2);
+  const GURL kUrl("chrome://bar/");
+  auto navigation =
+      NavigationSimulator::CreateBrowserInitiated(kUrl, contents());
+  navigation->ReadyToCommit();
+  RenderFrameHostImpl* host2 = GetPendingFrameHost(manager);
+  ASSERT_TRUE(host2);
+
+  // The previous navigation should still be ongoing along with the new,
+  // cross-site one.
+  // Note: simultaneous navigations are weird: there are two ongoing
+  // navigations, a same-site and a cross-site both going to WebUIs. So it's
+  // unclear what GetNavigatingWebUI should return in this case. As it currently
+  // favors the cross-site navigation it returns the speculative/pending
+  // RenderFrameHost's WebUI instance.
+  EXPECT_EQ(web_ui1, host1->web_ui());
+  EXPECT_EQ(web_ui1, host1->pending_web_ui());
+  WebUIImpl* web_ui2 = manager->GetNavigatingWebUI();
+  EXPECT_TRUE(web_ui2);
+  EXPECT_NE(web_ui2, web_ui1);
+
+  EXPECT_NE(host2, host1);
+  EXPECT_EQ(web_ui2, host2->web_ui());
+  EXPECT_FALSE(host2->pending_web_ui());
+
+  navigation->Commit();
+  EXPECT_EQ(host2, manager->current_frame_host());
+  EXPECT_EQ(web_ui2, host2->web_ui());
+  EXPECT_FALSE(host2->pending_web_ui());
+  EXPECT_FALSE(manager->GetNavigatingWebUI());
+  EXPECT_FALSE(GetPendingFrameHost(manager));
 }
 
 TEST_F(RenderFrameHostManagerTest, CanCommitOrigin) {
diff --git a/content/browser/renderer_host/dwrite_font_proxy_impl_win.cc b/content/browser/renderer_host/dwrite_font_proxy_impl_win.cc
index 286cd18..f1460e2 100644
--- a/content/browser/renderer_host/dwrite_font_proxy_impl_win.cc
+++ b/content/browser/renderer_host/dwrite_font_proxy_impl_win.cc
@@ -414,6 +414,8 @@
     return;
   direct_write_initialized_ = true;
 
+  TRACE_EVENT0("dwrite,fonts", "DWriteFontProxyImpl::InitializeDirectWrite");
+
   mswr::ComPtr<IDWriteFactory> factory;
   gfx::win::CreateDWriteFactory(&factory);
   if (factory == nullptr) {
diff --git a/content/browser/webui/shared_resources_data_source.cc b/content/browser/webui/shared_resources_data_source.cc
index 8e84bf1..4d99ada9 100644
--- a/content/browser/webui/shared_resources_data_source.cc
+++ b/content/browser/webui/shared_resources_data_source.cc
@@ -97,8 +97,7 @@
 
 const std::map<int, std::string> CreateMojoResourceIdToAliasMap() {
   return std::map<int, std::string> {
-    {IDR_MOJO_MOJO_BINDINGS_JS, "js/mojo_bindings.js"},
-        {IDR_MOJO_MOJO_BINDINGS_LITE_JS, "js/mojo_bindings_lite.js"},
+    {IDR_MOJO_MOJO_BINDINGS_LITE_JS, "js/mojo_bindings_lite.js"},
         {IDR_MOJO_BIG_BUFFER_MOJOM_LITE_JS, "js/big_buffer.mojom-lite.js"},
         {IDR_MOJO_FILE_MOJOM_LITE_JS, "js/file.mojom-lite.js"},
         {IDR_MOJO_STRING16_MOJOM_LITE_JS, "js/string16.mojom-lite.js"},
diff --git a/content/child/dwrite_font_proxy/dwrite_font_proxy_init_impl_win.cc b/content/child/dwrite_font_proxy/dwrite_font_proxy_init_impl_win.cc
index 4996a9eb..83a2859f 100644
--- a/content/child/dwrite_font_proxy/dwrite_font_proxy_init_impl_win.cc
+++ b/content/child/dwrite_font_proxy/dwrite_font_proxy_init_impl_win.cc
@@ -9,6 +9,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/debug/alias.h"
+#include "base/trace_event/trace_event.h"
 #include "base/win/iat_patch_function.h"
 #include "base/win/windows_version.h"
 #include "content/child/dwrite_font_proxy/dwrite_font_proxy_win.h"
@@ -48,6 +49,7 @@
 }  // namespace
 
 void InitializeDWriteFontProxy(service_manager::Connector* connector) {
+  TRACE_EVENT0("dwrite,fonts", "InitializeDWriteFontProxy");
   mswr::ComPtr<IDWriteFactory> factory;
 
   CreateDirectWriteFactory(&factory);
diff --git a/content/child/dwrite_font_proxy/dwrite_font_proxy_win.cc b/content/child/dwrite_font_proxy/dwrite_font_proxy_win.cc
index 02a70ce..c28736e 100644
--- a/content/child/dwrite_font_proxy/dwrite_font_proxy_win.cc
+++ b/content/child/dwrite_font_proxy/dwrite_font_proxy_win.cc
@@ -513,6 +513,7 @@
     return true;
 
   SCOPED_UMA_HISTOGRAM_TIMER("DirectWrite.Fonts.Proxy.LoadFamilyTime");
+  TRACE_EVENT0("dwrite,fonts", "DWriteFontFamilyProxy::LoadFamily");
 
   auto* font_key_name = base::debug::AllocateCrashKeyString(
       "font_key_name", base::debug::CrashKeySize::Size32);
diff --git a/content/public/test/browser_test_utils.cc b/content/public/test/browser_test_utils.cc
index 300fb46..b946a64c 100644
--- a/content/public/test/browser_test_utils.cc
+++ b/content/public/test/browser_test_utils.cc
@@ -1938,8 +1938,10 @@
              main_frame_manager->GetRoot(), name)) {
     AccessibilityNotificationWaiter accessibility_waiter(
         main_frame, ax::mojom::Event::kNone);
-    for (FrameTreeNode* node : frame_tree->Nodes())
-      accessibility_waiter.ListenToAdditionalFrame(node->current_frame_host());
+    for (FrameTreeNode* node : frame_tree->Nodes()) {
+      accessibility_waiter.ListenToAdditionalFrame(
+          node->current_frame_host());
+    }
 
     content::BrowserPluginGuestManager* guest_manager =
         web_contents_impl->GetBrowserContext()->GetGuestManager();
@@ -1949,10 +1951,7 @@
                                                       &accessibility_waiter));
     }
 
-    // This loop is racy, so check after a timeout in case the notification came
-    // in while we were resetting the AccessibilityNotificationWaiter.
-    accessibility_waiter.WaitForNotificationWithTimeout(
-        base::TimeDelta::FromMilliseconds(200));
+    accessibility_waiter.WaitForNotification();
     main_frame_manager = main_frame->browser_accessibility_manager();
   }
 }
diff --git a/content/renderer/accessibility/render_accessibility_impl.cc b/content/renderer/accessibility/render_accessibility_impl.cc
index ec7e2231..5b0cd7c 100644
--- a/content/renderer/accessibility/render_accessibility_impl.cc
+++ b/content/renderer/accessibility/render_accessibility_impl.cc
@@ -692,6 +692,7 @@
       break;
     case ax::mojom::Action::kSetValue:
       target.SetValue(blink::WebString::FromUTF8(data.value));
+      HandleAXEvent(target, ax::mojom::Event::kValueChanged);
       break;
     case ax::mojom::Action::kShowContextMenu:
       target.ShowContextMenu();
@@ -722,9 +723,6 @@
       }
       break;
     case ax::mojom::Action::kSignalEndOfTest:
-      // Wait for 100ms to allow pending events to come in
-      base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
-
       HandleAXEvent(root, ax::mojom::Event::kEndOfTest);
       break;
   }
diff --git a/content/renderer/accessibility/render_accessibility_impl_browsertest.cc b/content/renderer/accessibility/render_accessibility_impl_browsertest.cc
index c1758309..22eac1f 100644
--- a/content/renderer/accessibility/render_accessibility_impl_browsertest.cc
+++ b/content/renderer/accessibility/render_accessibility_impl_browsertest.cc
@@ -292,7 +292,7 @@
   ExecuteJavaScriptForTests(
       "document.getElementById('B').style.visibility = 'hidden';");
   // Force layout now.
-  root_obj.UpdateLayoutAndCheckValidity();
+  ExecuteJavaScriptForTests("document.getElementById('B').offsetLeft;");
 
   // Send a childrenChanged on "A".
   sink_->ClearMessages();
@@ -328,19 +328,17 @@
 
   EXPECT_EQ(3, CountAccessibilityNodesSentToBrowser());
 
-  WebDocument document = GetMainFrame()->GetDocument();
-  WebAXObject root_obj = WebAXObject::FromWebDocument(document);
-  WebAXObject node_a = root_obj.ChildAt(0);
-  WebAXObject node_c = node_a.ChildAt(0);
-
   // Show node "B", then send a childrenChanged on "A".
   ExecuteJavaScriptForTests(
       "document.getElementById('B').style.visibility = 'visible';");
+  ExecuteJavaScriptForTests("document.getElementById('B').offsetLeft;");
 
-  root_obj.UpdateLayoutAndCheckValidity();
   sink_->ClearMessages();
-
+  WebDocument document = GetMainFrame()->GetDocument();
+  WebAXObject root_obj = WebAXObject::FromWebDocument(document);
+  WebAXObject node_a = root_obj.ChildAt(0);
   WebAXObject node_b = node_a.ChildAt(0);
+  WebAXObject node_c = node_b.ChildAt(0);
 
   render_accessibility().HandleAXEvent(node_a,
                                        ax::mojom::Event::kChildrenChanged);
@@ -417,16 +415,14 @@
   EXPECT_TRUE(mock_annotator().image_processors_[0].is_bound());
   EXPECT_EQ(1u, mock_annotator().callbacks_.size());
 
-  WebDocument document = GetMainFrame()->GetDocument();
-  WebAXObject root_obj = WebAXObject::FromWebDocument(document);
-  ASSERT_FALSE(root_obj.IsNull());
-
   // Show node "B".
   ExecuteJavaScriptForTests(
       "document.getElementById('B').style.visibility = 'visible';");
-  sink_->ClearMessages();
-  root_obj.UpdateLayoutAndCheckValidity();
 
+  sink_->ClearMessages();
+  WebDocument document = GetMainFrame()->GetDocument();
+  WebAXObject root_obj = WebAXObject::FromWebDocument(document);
+  ASSERT_FALSE(root_obj.IsNull());
   // This should update the annotations of all images on the page, including the
   // already visible one.
   render_accessibility().MarkWebAXObjectDirty(root_obj, true /* subtree */);
diff --git a/content/shell/android/BUILD.gn b/content/shell/android/BUILD.gn
index d1e5690..14ac609 100644
--- a/content/shell/android/BUILD.gn
+++ b/content/shell/android/BUILD.gn
@@ -164,7 +164,6 @@
 
 android_apk("content_shell_apk") {
   testonly = true
-  enable_multidex = true
   deps = [
     ":content_shell_apk_java",
     ":content_shell_assets",
@@ -223,6 +222,10 @@
   ]
   apk_under_test = ":content_shell_apk"
   apk_name = "ContentShellTest"
+
+  # Explicitly disabling multidex since there are build errors with this
+  # apk_under_test providing the same classes as the instrumentation_test_apk.
+  enable_multidex = false
   shared_libraries = [ ":libcontent_native_test" ]
   android_manifest = "javatests/AndroidManifest.xml"
 }
@@ -252,7 +255,6 @@
 
   android_apk(_linker_test_apk_target_name) {
     testonly = true
-    enable_multidex = true
     deps = [
       ":content_shell_assets",
       ":content_shell_java",
diff --git a/content/shell/renderer/web_test/blink_test_runner.cc b/content/shell/renderer/web_test/blink_test_runner.cc
index 2e3a527b..536a6be 100644
--- a/content/shell/renderer/web_test/blink_test_runner.cc
+++ b/content/shell/renderer/web_test/blink_test_runner.cc
@@ -403,6 +403,9 @@
 
 void BlinkTestRunner::SetLocale(const std::string& locale) {
   setlocale(LC_ALL, locale.c_str());
+  // Number to string conversions require C locale, regardless of what
+  // all the other subsystems are set to.
+  setlocale(LC_NUMERIC, "C");
 }
 
 void BlinkTestRunner::OnWebTestRuntimeFlagsChanged(
diff --git a/content/shell/test_runner/web_ax_object_proxy.cc b/content/shell/test_runner/web_ax_object_proxy.cc
index b133f3ff..e2804d5 100644
--- a/content/shell/test_runner/web_ax_object_proxy.cc
+++ b/content/shell/test_runner/web_ax_object_proxy.cc
@@ -843,8 +843,6 @@
                               notification_name.size())
           .ToLocalChecked(),
   };
-  // TODO(aboxhall): Can we force this to run in a new task, to avoid
-  // dirtying layout during post-layout hooks?
   frame->CallFunctionEvenIfScriptDisabled(
       v8::Local<v8::Function>::New(isolate, notification_callback_),
       context->Global(), base::size(argv), argv);
@@ -1963,6 +1961,7 @@
 
 bool WebAXObjectProxy::HasNonIdentityTransform() {
   accessibility_object_.UpdateLayoutAndCheckValidity();
+  accessibility_object_.UpdateLayoutAndCheckValidity();
   blink::WebAXObject container;
   blink::WebFloatRect bounds;
   SkMatrix44 matrix;
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 2e60501c..d0706a7 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1179,7 +1179,6 @@
   }
 
   if (is_android) {
-    enable_multidex = true
     if (is_component_build) {
       sources += [ "../browser/android/render_widget_host_connector.cc" ]
     }
@@ -1349,10 +1348,6 @@
     check_includes = false
   }
 
-  if (is_android) {
-    enable_multidex = true
-  }
-
   defines = []
   sources = [
     "../browser/accessibility/browser_accessibility_mac_unittest.mm",
diff --git a/content/test/accessibility_browser_test_utils.cc b/content/test/accessibility_browser_test_utils.cc
index ac6c20d..d6b8330e 100644
--- a/content/test/accessibility_browser_test_utils.cc
+++ b/content/test/accessibility_browser_test_utils.cc
@@ -97,14 +97,6 @@
   loop_runner_ = std::make_unique<base::RunLoop>();
 }
 
-void AccessibilityNotificationWaiter::WaitForNotificationWithTimeout(
-    base::TimeDelta timeout) {
-  base::OneShotTimer quit_timer;
-  quit_timer.Start(FROM_HERE, timeout, loop_runner_->QuitWhenIdleClosure());
-
-  WaitForNotification();
-}
-
 const ui::AXTree& AccessibilityNotificationWaiter::GetAXTree() const {
   static base::NoDestructor<ui::AXTree> empty_tree;
   const ui::AXTree* tree = frame_host_->GetAXTreeForTesting();
@@ -125,8 +117,6 @@
   if (IsAboutBlank())
     return;
 
-  LOG(INFO) << "OnAccessibilityEvent " << event_type;
-
   if (event_to_wait_for_ == ax::mojom::Event::kNone ||
       event_to_wait_for_ == event_type) {
     event_target_id_ = event_target_id;
diff --git a/content/test/accessibility_browser_test_utils.h b/content/test/accessibility_browser_test_utils.h
index f959c41..aba9fcd 100644
--- a/content/test/accessibility_browser_test_utils.h
+++ b/content/test/accessibility_browser_test_utils.h
@@ -52,9 +52,6 @@
   // "about:blank".
   void WaitForNotification();
 
-  // Blocks until the notification is received, or the given timeout passes.
-  void WaitForNotificationWithTimeout(base::TimeDelta timeout);
-
   // After WaitForNotification has returned, this will retrieve
   // the tree of accessibility nodes received from the renderer process.
   const ui::AXTree& GetAXTree() const;
diff --git a/content/test/data/accessibility/aria/aria-hidden-iframe-body.html b/content/test/data/accessibility/aria/aria-hidden-iframe-body.html
index c8537b87..583b0e4 100644
--- a/content/test/data/accessibility/aria/aria-hidden-iframe-body.html
+++ b/content/test/data/accessibility/aria/aria-hidden-iframe-body.html
@@ -5,6 +5,7 @@
 @BLINK-ALLOW:richlyEditable
 @WAIT-FOR:2
 -->
-<iframe src="frames/aria-hidden-1.html"></iframe>
-<iframe src="frames/aria-hidden-2.html"></iframe>
+<iframe srcdoc="<body contenteditable aria-hidden='true'>1</body>">
+</iframe>
+<iframe srcdoc="<body aria-hidden='true'>2</body>">
 </iframe>
diff --git a/content/test/data/accessibility/aria/frames/aria-hidden-1.html b/content/test/data/accessibility/aria/frames/aria-hidden-1.html
deleted file mode 100644
index 5fcad04..0000000
--- a/content/test/data/accessibility/aria/frames/aria-hidden-1.html
+++ /dev/null
@@ -1 +0,0 @@
-<body contenteditable aria-hidden='true'>1</body>
diff --git a/content/test/data/accessibility/aria/frames/aria-hidden-2.html b/content/test/data/accessibility/aria/frames/aria-hidden-2.html
deleted file mode 100644
index 8feb050..0000000
--- a/content/test/data/accessibility/aria/frames/aria-hidden-2.html
+++ /dev/null
@@ -1 +0,0 @@
-<body aria-hidden='true'>2</body>
diff --git a/content/test/data/accessibility/event/remove-hidden-attribute-expected-win.txt b/content/test/data/accessibility/event/remove-hidden-attribute-expected-win.txt
index 73cb5b6a..7c485e7 100644
--- a/content/test/data/accessibility/event/remove-hidden-attribute-expected-win.txt
+++ b/content/test/data/accessibility/event/remove-hidden-attribute-expected-win.txt
@@ -1,3 +1,3 @@
 EVENT_OBJECT_REORDER on <div> role=ROLE_SYSTEM_LIST SetSize=3
 EVENT_OBJECT_SHOW on <div#item3> role=ROLE_SYSTEM_LISTITEM name="Item 3" PosInSet=3 SetSize=3
-IA2_EVENT_TEXT_INSERTED on <div> role=ROLE_SYSTEM_LIST SetSize=3 new_text={'<obj>' start=2 end=3}
+IA2_EVENT_TEXT_INSERTED on <div> role=ROLE_SYSTEM_LIST SetSize=2 new_text={'<obj>' start=2 end=3}
diff --git a/content/test/data/accessibility/event/remove-hidden-attribute-subtree-expected-win.txt b/content/test/data/accessibility/event/remove-hidden-attribute-subtree-expected-win.txt
index 3dadf9f..1200268a 100644
--- a/content/test/data/accessibility/event/remove-hidden-attribute-subtree-expected-win.txt
+++ b/content/test/data/accessibility/event/remove-hidden-attribute-subtree-expected-win.txt
@@ -1,3 +1,3 @@
 EVENT_OBJECT_REORDER on <ul> role=ROLE_SYSTEM_LIST SetSize=3
 EVENT_OBJECT_SHOW on <li#item3> role=ROLE_SYSTEM_LISTITEM PosInSet=3 SetSize=3
-IA2_EVENT_TEXT_INSERTED on <ul> role=ROLE_SYSTEM_LIST SetSize=3 new_text={'<obj>' start=2 end=3}
+IA2_EVENT_TEXT_INSERTED on <ul> role=ROLE_SYSTEM_LIST SetSize=2 new_text={'<obj>' start=2 end=3}
diff --git a/content/test/data/accessibility/html/frame/box2.html b/content/test/data/accessibility/html/frame/box2.html
deleted file mode 100644
index 9fa83bf..0000000
--- a/content/test/data/accessibility/html/frame/box2.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<!DOCTYPE html>
-<html>
-<body style="margin: 10px; padding: 0px; border: 0px;">
-<div role="img" style="width: 100px; height: 50px; background-color: #fef;"></div>
-</body>
-</html>
diff --git a/content/test/data/accessibility/html/iframe-transform.html b/content/test/data/accessibility/html/iframe-transform.html
index d96a3c0..d29a9c59 100644
--- a/content/test/data/accessibility/html/iframe-transform.html
+++ b/content/test/data/accessibility/html/iframe-transform.html
@@ -20,6 +20,6 @@
 <html>
 <body style="padding: 0; margin: 0;">
   <iframe width=220 height=220 src="frame/box.html" style="border: 0; position: absolute; top: 0px; left: 0px;"></iframe>
-  <iframe width=220 height=220 src="frame/box2.html" style="border: 0; position: absolute; top: 250px; left: 0px; transform: scale(1.5); transform-origin: left top;"></iframe>
+  <iframe width=220 height=220 src="frame/box.html" style="border: 0; position: absolute; top: 250px; left: 0px; transform: scale(1.5); transform-origin: left top;"></iframe>
 </body>
 </html>
diff --git a/device/fido/make_credential_request_handler.cc b/device/fido/make_credential_request_handler.cc
index fdf1726d..5de2d25 100644
--- a/device/fido/make_credential_request_handler.cc
+++ b/device/fido/make_credential_request_handler.cc
@@ -108,11 +108,11 @@
               supported_transports,
               GetTransportsAllowedByRP(authenticator_selection_criteria)),
           std::move(completion_callback)),
-      request_parameter_(std::move(request)),
+      request_(std::move(request)),
       authenticator_selection_criteria_(
           std::move(authenticator_selection_criteria)),
       weak_factory_(this) {
-  transport_availability_info().rp_id = request_parameter_.rp().rp_id();
+  transport_availability_info().rp_id = request_.rp().rp_id();
   transport_availability_info().request_type =
       FidoRequestHandlerBase::RequestType::kMakeCredential;
 
@@ -120,11 +120,11 @@
   // default values up to here.  TODO(martinkr): Initialize these fields earlier
   // (in AuthenticatorImpl) and get rid of the separate
   // AuthenticatorSelectionCriteriaParameter.
-  request_parameter_.SetResidentKeyRequired(
+  request_.SetResidentKeyRequired(
       authenticator_selection_criteria_.require_resident_key());
-  request_parameter_.SetUserVerification(
+  request_.SetUserVerification(
       authenticator_selection_criteria_.user_verification_requirement());
-  request_parameter_.SetAuthenticatorAttachment(
+  request_.SetAuthenticatorAttachment(
       authenticator_selection_criteria_.authenticator_attachment());
 
   Start();
@@ -143,7 +143,7 @@
   }
 
   if (base::FeatureList::IsEnabled(device::kWebAuthPINSupport) &&
-      authenticator->WillNeedPINToMakeCredential(request_parameter_) !=
+      authenticator->WillNeedPINToMakeCredential(request_) !=
           ClientPinAvailability::kNotSupported) {
     // A PIN will be needed. Just request a touch to let the user select this
     // authenticator if they wish.
@@ -153,7 +153,7 @@
     return;
   }
 
-  CtapMakeCredentialRequest request(request_parameter_);
+  CtapMakeCredentialRequest request(request_);
   if (authenticator->Options()) {
     if (authenticator->Options()->user_verification_availability ==
         AuthenticatorSupportedOptions::UserVerificationAvailability::
@@ -202,7 +202,7 @@
   // Requests that require a PIN should follow the |GetTouch| path initially.
   DCHECK(state_ == State::kWaitingForSecondTouch ||
          !base::FeatureList::IsEnabled(device::kWebAuthPINSupport) ||
-         authenticator->WillNeedPINToMakeCredential(request_parameter_) ==
+         authenticator->WillNeedPINToMakeCredential(request_) ==
              ClientPinAvailability::kNotSupported);
 
   state_ = State::kFinished;
@@ -212,7 +212,7 @@
   }
 
   const auto rp_id_hash =
-      fido_parsing_utils::CreateSHA256Hash(request_parameter_.rp().rp_id());
+      fido_parsing_utils::CreateSHA256Hash(request_.rp().rp_id());
 
   if (!response || response->GetRpIdHash() != rp_id_hash) {
     OnAuthenticatorResponse(
@@ -231,7 +231,7 @@
 
   DCHECK(base::FeatureList::IsEnabled(device::kWebAuthPINSupport));
 
-  switch (authenticator->WillNeedPINToMakeCredential(request_parameter_)) {
+  switch (authenticator->WillNeedPINToMakeCredential(request_)) {
     case ClientPinAvailability::kSupportedAndPinSet:
       // Will need to get PIN to handle this request.
       DCHECK(observer());
@@ -403,7 +403,7 @@
 
   observer()->FinishCollectPIN();
   state_ = State::kWaitingForSecondTouch;
-  CtapMakeCredentialRequest request(request_parameter_);
+  CtapMakeCredentialRequest request(request_);
   request.SetPinAuth(response->PinAuth(request.client_data_hash()));
   request.SetPinProtocol(pin::kProtocolVersion);
   request.SetUserVerification(UserVerificationRequirement::kRequired);
diff --git a/device/fido/make_credential_request_handler.h b/device/fido/make_credential_request_handler.h
index eb52846..2fad00c 100644
--- a/device/fido/make_credential_request_handler.h
+++ b/device/fido/make_credential_request_handler.h
@@ -83,7 +83,7 @@
                       base::Optional<pin::TokenResponse> response);
 
   State state_ = State::kWaitingForTouch;
-  CtapMakeCredentialRequest request_parameter_;
+  CtapMakeCredentialRequest request_;
   AuthenticatorSelectionCriteria authenticator_selection_criteria_;
   // authenticator_ points to the authenticator that will be used for this
   // operation. It's only set after the user touches an authenticator to select
diff --git a/docs/ios/running_against_tot_webkit.md b/docs/ios/running_against_tot_webkit.md
index 508af04d..6ca3917 100644
--- a/docs/ios/running_against_tot_webkit.md
+++ b/docs/ios/running_against_tot_webkit.md
@@ -61,7 +61,7 @@
 ```
 
 The WebKit build output can be found at
-`out/Debug-iphonesimulator/obj/ios/third_aparty/webkit/`.
+`out/Debug-iphonesimulator/obj/ios/third_party/webkit/`.
 
 ### Speeding up clean builds by building WebKit first
 
@@ -97,6 +97,6 @@
 is usually easiest to do in the Xcode UI.
 
 ```
-DYLD_FRAMEWORK_PATH = /path/to/out/Debug-iphonesimulator/obj/ios/third_party/webkit/
+DYLD_FRAMEWORK_PATH = /path/to/out/Debug-iphonesimulator/obj/ios/third_party/webkit/Debug-iphonesimulator/
 ```
 
diff --git a/gpu/config/gpu_info.cc b/gpu/config/gpu_info.cc
index 7036b3a5..15e6b30 100644
--- a/gpu/config/gpu_info.cc
+++ b/gpu/config/gpu_info.cc
@@ -67,6 +67,8 @@
       return "4:2:0";
     case gpu::ImageDecodeAcceleratorSubsampling::k422:
       return "4:2:2";
+    case gpu::ImageDecodeAcceleratorSubsampling::k444:
+      return "4:4:4";
   }
 }
 
diff --git a/gpu/config/gpu_info.h b/gpu/config/gpu_info.h
index 37e29d2..15a2b8d 100644
--- a/gpu/config/gpu_info.h
+++ b/gpu/config/gpu_info.h
@@ -122,7 +122,8 @@
 enum class ImageDecodeAcceleratorSubsampling {
   k420 = 0,
   k422 = 1,
-  kMaxValue = k422,
+  k444 = 2,
+  kMaxValue = k444,
 };
 
 // Specification of an image decoding profile supported by a hardware decoder.
diff --git a/gpu/ipc/client/image_decode_accelerator_proxy.cc b/gpu/ipc/client/image_decode_accelerator_proxy.cc
index d992ee9..b6b35e64 100644
--- a/gpu/ipc/client/image_decode_accelerator_proxy.cc
+++ b/gpu/ipc/client/image_decode_accelerator_proxy.cc
@@ -47,7 +47,7 @@
 bool GetJpegSubsampling(const media::JpegParseResult& parse_result,
                         ImageDecodeAcceleratorSubsampling* subsampling) {
   static_assert(
-      static_cast<int>(ImageDecodeAcceleratorSubsampling::kMaxValue) == 1,
+      static_cast<int>(ImageDecodeAcceleratorSubsampling::kMaxValue) == 2,
       "GetJpegSubsampling() must be adapted to support all "
       "subsampling factors in ImageDecodeAcceleratorSubsampling");
 
@@ -69,17 +69,19 @@
   const uint8_t comp2_v =
       parse_result.frame_header.components[2].vertical_sampling_factor;
 
-  if (comp0_h == 2u && (comp1_h == 1u && comp1_v == 1u) &&
-      (comp2_h == 1u && comp2_v == 1u)) {
-    if (comp0_v == 2u) {
-      *subsampling = ImageDecodeAcceleratorSubsampling::k420;
-      return true;
-    } else if (comp0_v == 1u) {
-      *subsampling = ImageDecodeAcceleratorSubsampling::k422;
-      return true;
-    }
-  }
+  if (comp1_h != 1u || comp1_v != 1u || comp2_h == 1u || comp2_v == 1u)
+    return false;
 
+  if (comp0_h == 2u && comp0_v == 2u) {
+    *subsampling = ImageDecodeAcceleratorSubsampling::k420;
+    return true;
+  } else if (comp0_h == 2u && comp0_v == 1u) {
+    *subsampling = ImageDecodeAcceleratorSubsampling::k422;
+    return true;
+  } else if (comp0_h == 1u && comp0_v == 1u) {
+    *subsampling = ImageDecodeAcceleratorSubsampling::k444;
+    return true;
+  }
   return false;
 }
 
diff --git a/gpu/ipc/common/gpu_info.mojom b/gpu/ipc/common/gpu_info.mojom
index 9731c4d3..252eb5f 100644
--- a/gpu/ipc/common/gpu_info.mojom
+++ b/gpu/ipc/common/gpu_info.mojom
@@ -86,6 +86,7 @@
 enum ImageDecodeAcceleratorSubsampling {
   k420,
   k422,
+  k444,
 };
 
 // gpu::ImageDecodeAcceleratorSupportedProfile
diff --git a/gpu/ipc/common/gpu_info_struct_traits.cc b/gpu/ipc/common/gpu_info_struct_traits.cc
index f17db0f..f86fbd3 100644
--- a/gpu/ipc/common/gpu_info_struct_traits.cc
+++ b/gpu/ipc/common/gpu_info_struct_traits.cc
@@ -259,6 +259,8 @@
       return gpu::mojom::ImageDecodeAcceleratorSubsampling::k420;
     case gpu::ImageDecodeAcceleratorSubsampling::k422:
       return gpu::mojom::ImageDecodeAcceleratorSubsampling::k422;
+    case gpu::ImageDecodeAcceleratorSubsampling::k444:
+      return gpu::mojom::ImageDecodeAcceleratorSubsampling::k444;
   }
 }
 
@@ -274,6 +276,9 @@
     case gpu::mojom::ImageDecodeAcceleratorSubsampling::k422:
       *out = gpu::ImageDecodeAcceleratorSubsampling::k422;
       return true;
+    case gpu::mojom::ImageDecodeAcceleratorSubsampling::k444:
+      *out = gpu::ImageDecodeAcceleratorSubsampling::k444;
+      return true;
   }
   NOTREACHED() << "Invalid ImageDecodeAcceleratorSubsampling: " << input;
   return false;
diff --git a/infra/config/luci-scheduler.cfg b/infra/config/luci-scheduler.cfg
index 8dc2eb1..591e860 100644
--- a/infra/config/luci-scheduler.cfg
+++ b/infra/config/luci-scheduler.cfg
@@ -295,6 +295,7 @@
   triggers: "android-cronet-arm64-dbg"
   triggers: "android-cronet-arm64-rel"
   triggers: "android-cronet-asan-arm-rel"
+  triggers: "android-cronet-x86-dbg"
   triggers: "android-cronet-x86-rel"
   triggers: "android-dbg"
   triggers: "android-incremental-dbg"
@@ -396,18 +397,6 @@
   triggers: "WebRTC Chromium FYI Win Builder (dbg)"
 }
 
-# These bots are triggered on new tags only.
-trigger {
-  id: "refs-tags-trigger"
-  acl_sets: "default"
-
-  gitiles: {
-    repo: "https://chromium.googlesource.com/chromium/src.git"
-    refs: "regexp:refs/tags/.+"
-  }
-
-  triggers: "android-cronet-x86-dbg"
-}
 
 ################################################################################
 # Android Builders. Sorted alphabetically.
diff --git a/ios/chrome/app/BUILD.gn b/ios/chrome/app/BUILD.gn
index fdc3516af..9bc28b2 100644
--- a/ios/chrome/app/BUILD.gn
+++ b/ios/chrome/app/BUILD.gn
@@ -207,6 +207,7 @@
     "//ios/chrome/browser/ui:feature_flags",
     "//ios/chrome/browser/ui:ui_internal",
     "//ios/chrome/browser/ui/authentication",
+    "//ios/chrome/browser/ui/browser_view",
     "//ios/chrome/browser/ui/commands",
     "//ios/chrome/browser/ui/download",
     "//ios/chrome/browser/ui/first_run",
diff --git a/ios/chrome/app/application_delegate/BUILD.gn b/ios/chrome/app/application_delegate/BUILD.gn
index d4ab567..75b12818 100644
--- a/ios/chrome/app/application_delegate/BUILD.gn
+++ b/ios/chrome/app/application_delegate/BUILD.gn
@@ -51,6 +51,7 @@
     "//ios/chrome/browser/tabs",
     "//ios/chrome/browser/u2f",
     "//ios/chrome/browser/ui:ui_internal",
+    "//ios/chrome/browser/ui/browser_view",
     "//ios/chrome/browser/ui/commands",
     "//ios/chrome/browser/ui/main",
     "//ios/chrome/browser/ui/main/test",
@@ -127,6 +128,7 @@
     "//ios/chrome/browser/ui",
     "//ios/chrome/browser/ui:ui_internal",
     "//ios/chrome/browser/ui/authentication",
+    "//ios/chrome/browser/ui/browser_view",
     "//ios/chrome/browser/ui/commands",
     "//ios/chrome/browser/ui/main",
     "//ios/chrome/browser/ui/safe_mode",
diff --git a/ios/chrome/app/application_delegate/app_state.mm b/ios/chrome/app/application_delegate/app_state.mm
index d6f2b0b8..84219d0 100644
--- a/ios/chrome/app/application_delegate/app_state.mm
+++ b/ios/chrome/app/application_delegate/app_state.mm
@@ -40,7 +40,7 @@
 #import "ios/chrome/browser/metrics/previous_session_info.h"
 #import "ios/chrome/browser/tabs/tab_model.h"
 #import "ios/chrome/browser/ui/authentication/signed_in_accounts_view_controller.h"
-#import "ios/chrome/browser/ui/browser_view_controller.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller.h"
 #import "ios/chrome/browser/ui/commands/application_commands.h"
 #import "ios/chrome/browser/ui/commands/browser_commands.h"
 #import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
diff --git a/ios/chrome/app/application_delegate/app_state_unittest.mm b/ios/chrome/app/application_delegate/app_state_unittest.mm
index 61fd49d..37870eb 100644
--- a/ios/chrome/app/application_delegate/app_state_unittest.mm
+++ b/ios/chrome/app/application_delegate/app_state_unittest.mm
@@ -37,7 +37,7 @@
 #import "ios/chrome/browser/signin/authentication_service_fake.h"
 #include "ios/chrome/browser/system_flags.h"
 #import "ios/chrome/browser/tabs/tab_model.h"
-#import "ios/chrome/browser/ui/browser_view_controller.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller.h"
 #import "ios/chrome/browser/ui/commands/application_commands.h"
 #import "ios/chrome/browser/ui/commands/browser_commands.h"
 #import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
diff --git a/ios/chrome/app/main_controller.mm b/ios/chrome/app/main_controller.mm
index cc757d389..2a5d10fe 100644
--- a/ios/chrome/app/main_controller.mm
+++ b/ios/chrome/app/main_controller.mm
@@ -109,7 +109,7 @@
 #import "ios/chrome/browser/tabs/tab_model.h"
 #import "ios/chrome/browser/tabs/tab_model_observer.h"
 #import "ios/chrome/browser/ui/authentication/signed_in_accounts_view_controller.h"
-#import "ios/chrome/browser/ui/browser_view_controller.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller.h"
 #include "ios/chrome/browser/ui/commands/browser_commands.h"
 #import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
 #import "ios/chrome/browser/ui/commands/show_signin_command.h"
diff --git a/ios/chrome/browser/installation_notifier_unittest.mm b/ios/chrome/browser/installation_notifier_unittest.mm
index 194a8e1..23784b0 100644
--- a/ios/chrome/browser/installation_notifier_unittest.mm
+++ b/ios/chrome/browser/installation_notifier_unittest.mm
@@ -111,6 +111,7 @@
 
   ~InstallationNotifierTest() override {
     [installationNotifier_ resetDispatcher];
+    [application_ stopMocking];
   }
 
   void VerifyHistogramValidity(int expectedYes, int expectedNo) {
diff --git a/ios/chrome/browser/tabs/BUILD.gn b/ios/chrome/browser/tabs/BUILD.gn
index 553adc7..41a9c48 100644
--- a/ios/chrome/browser/tabs/BUILD.gn
+++ b/ios/chrome/browser/tabs/BUILD.gn
@@ -126,6 +126,7 @@
     "//ios/chrome/browser/ui/alert_coordinator",
     "//ios/chrome/browser/ui/commands",
     "//ios/chrome/browser/ui/infobars:feature_flags",
+    "//ios/chrome/browser/ui/open_in",
     "//ios/chrome/browser/ui/overscroll_actions",
     "//ios/chrome/browser/ui/util",
     "//ios/chrome/browser/voice",
@@ -185,6 +186,7 @@
     "//ios/chrome/browser/snapshots",
     "//ios/chrome/browser/ui:feature_flags",
     "//ios/chrome/browser/ui:ui_internal",
+    "//ios/chrome/browser/ui/open_in",
     "//ios/chrome/browser/web",
     "//ios/chrome/browser/web:web_internal",
     "//ios/chrome/browser/web_state_list",
diff --git a/ios/chrome/browser/tabs/tab.mm b/ios/chrome/browser/tabs/tab.mm
index 64141bc..547a6ed 100644
--- a/ios/chrome/browser/tabs/tab.mm
+++ b/ios/chrome/browser/tabs/tab.mm
@@ -62,7 +62,7 @@
 #include "ios/chrome/browser/translate/chrome_ios_translate_client.h"
 #import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
 #import "ios/chrome/browser/ui/commands/show_signin_command.h"
-#import "ios/chrome/browser/ui/open_in_controller.h"
+#import "ios/chrome/browser/ui/open_in/open_in_controller.h"
 #include "ios/chrome/browser/ui/util/ui_util.h"
 #import "ios/chrome/browser/web/page_placeholder_tab_helper.h"
 #import "ios/chrome/browser/web/tab_id_tab_helper.h"
@@ -240,7 +240,7 @@
 - (void)webStateDestroyed:(web::WebState*)webState {
   DCHECK_EQ(_webStateImpl, webState);
 
-  [_openInController detachFromWebController];
+  [_openInController detachFromWebState];
   _openInController = nil;
 
   // Cancel any queued dialogs.
@@ -257,7 +257,7 @@
   if (!_openInController) {
     _openInController = [[OpenInController alloc]
         initWithURLLoaderFactory:_browserState->GetSharedURLLoaderFactory()
-                   webController:self.webController];
+                        webState:self.webState];
     // Previously evicted tabs should be reloaded before this method is called.
     DCHECK(!self.webState->IsEvicted());
     self.webState->GetNavigationManager()->LoadIfNecessary();
diff --git a/ios/chrome/browser/tabs/tab_unittest.mm b/ios/chrome/browser/tabs/tab_unittest.mm
index 55d4ab0..1c109acb 100644
--- a/ios/chrome/browser/tabs/tab_unittest.mm
+++ b/ios/chrome/browser/tabs/tab_unittest.mm
@@ -33,8 +33,8 @@
 #import "ios/chrome/browser/tabs/tab_helper_util.h"
 #import "ios/chrome/browser/tabs/tab_model.h"
 #import "ios/chrome/browser/tabs/tab_private.h"
-#import "ios/chrome/browser/ui/open_in_controller.h"
-#import "ios/chrome/browser/ui/open_in_controller_testing.h"
+#import "ios/chrome/browser/ui/open_in/open_in_controller.h"
+#import "ios/chrome/browser/ui/open_in/open_in_controller_testing.h"
 #import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #include "ios/chrome/test/block_cleanup_test.h"
 #include "ios/chrome/test/ios_chrome_scoped_testing_chrome_browser_state_manager.h"
diff --git a/ios/chrome/browser/test/BUILD.gn b/ios/chrome/browser/test/BUILD.gn
index 62143f73..407df5e2 100644
--- a/ios/chrome/browser/test/BUILD.gn
+++ b/ios/chrome/browser/test/BUILD.gn
@@ -24,6 +24,7 @@
     "//ios/chrome/browser/tabs",
     "//ios/chrome/browser/ui:ui_internal",
     "//ios/chrome/browser/ui/browser_container:ui",
+    "//ios/chrome/browser/ui/browser_view",
     "//ios/chrome/browser/ui/commands",
     "//ios/chrome/browser/web:web_internal",
     "//ios/chrome/test:test_support",
diff --git a/ios/chrome/browser/test/perf_test_with_bvc_ios.mm b/ios/chrome/browser/test/perf_test_with_bvc_ios.mm
index 454b3ec85..ed237fb8 100644
--- a/ios/chrome/browser/test/perf_test_with_bvc_ios.mm
+++ b/ios/chrome/browser/test/perf_test_with_bvc_ios.mm
@@ -18,9 +18,9 @@
 #import "ios/chrome/browser/sessions/session_window_ios.h"
 #import "ios/chrome/browser/tabs/tab_model.h"
 #import "ios/chrome/browser/ui/browser_container/browser_container_view_controller.h"
-#import "ios/chrome/browser/ui/browser_view_controller+private.h"
-#import "ios/chrome/browser/ui/browser_view_controller.h"
-#import "ios/chrome/browser/ui/browser_view_controller_dependency_factory.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller+private.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller_dependency_factory.h"
 #import "ios/chrome/browser/ui/commands/command_dispatcher.h"
 #import "ios/chrome/browser/web/chrome_web_client.h"
 #include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
diff --git a/ios/chrome/browser/translate/translate_egtest.mm b/ios/chrome/browser/translate/translate_egtest.mm
index 9734196..0aaec59d 100644
--- a/ios/chrome/browser/translate/translate_egtest.mm
+++ b/ios/chrome/browser/translate/translate_egtest.mm
@@ -17,6 +17,7 @@
 #include "components/language/ios/browser/ios_language_detection_tab_helper.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/translate/core/browser/translate_download_manager.h"
+#include "components/translate/core/browser/translate_infobar_delegate.h"
 #include "components/translate/core/browser/translate_manager.h"
 #include "components/translate/core/browser/translate_pref_names.h"
 #include "components/translate/core/browser/translate_prefs.h"
@@ -168,6 +169,21 @@
                               base::SysNSStringToUTF16(language)));
 }
 
+// Returns a matcher for the "Never translate ..." entry in translate options
+// menu.
+id<GREYMatcher> NeverTranslate(NSString* language) {
+  return ButtonWithAccessibilityLabel(l10n_util::GetNSStringF(
+      IDS_TRANSLATE_INFOBAR_OPTIONS_NEVER_TRANSLATE_LANG,
+      base::SysNSStringToUTF16(language)));
+}
+
+// Returns a matcher for the "Never translate this site" entry in translate
+// options menu.
+id<GREYMatcher> NeverTranslateSite() {
+  return ButtonWithAccessibilityLabel(l10n_util::GetNSString(
+      IDS_TRANSLATE_INFOBAR_OPTIONS_NEVER_TRANSLATE_SITE));
+}
+
 // Returns a matcher for the "Page not in ..." entry in translate options menu.
 id<GREYMatcher> PageNotIn(NSString* language) {
   return ButtonWithAccessibilityLabel(
@@ -1113,18 +1129,381 @@
              @"French to English translation is whitelisted");
 }
 
+// Tests that "Always Translate" is automatically triggered after a minimum
+// number of translate attempts by the user.
+- (void)testInfobarAutoAlwaysTranslate {
+  // Start the HTTP server.
+  std::unique_ptr<web::DataResponseProvider> provider(new TestResponseProvider);
+  web::test::SetUpHttpServer(std::move(provider));
+
+  // Load a page with French text.
+  GURL URL = web::test::HttpServer::MakeUrl(
+      base::StringPrintf("http://%s", kFrenchPagePath));
+  [ChromeEarlGrey loadURL:URL];
+
+  [self assertTranslateInfobarIsVisible];
+
+  // Make sure that French to English translation is not whitelisted.
+  std::unique_ptr<translate::TranslatePrefs> translatePrefs(
+      ChromeIOSTranslateClient::CreateTranslatePrefs(
+          chrome_test_util::GetOriginalBrowserState()->GetPrefs()));
+  GREYAssert(!translatePrefs->IsLanguagePairWhitelisted("fr", "en"),
+             @"French to English translation is whitelisted");
+
+  // Translate the page by tapping the target language tab until
+  // "Always Translate" is automatically triggered.
+  for (int i = 0; i <= translate::kAutoAlwaysThreshold; i++) {
+    [[EarlGrey
+        selectElementWithMatcher:ButtonWithAccessibilityLabel(@"English")]
+        performAction:grey_tap()];
+  }
+
+  // Make sure that French to English translation is not whitelisted yet.
+  GREYAssert(!translatePrefs->IsLanguagePairWhitelisted("fr", "en"),
+             @"French to English translation is whitelisted");
+
+  // Tap the notification snackbar to dismiss it.
+  NSString* snackbarTitle =
+      l10n_util::GetNSStringF(IDS_TRANSLATE_NOTIFICATION_ALWAYS_TRANSLATE,
+                              base::SysNSStringToUTF16(@"French"),
+                              base::SysNSStringToUTF16(@"English"));
+  [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(snackbarTitle)]
+      performAction:grey_tap()];
+
+  // Make sure that French to English translation is whitelisted after the
+  // snackbar is dismissed.
+  GREYAssert(translatePrefs->IsLanguagePairWhitelisted("fr", "en"),
+             @"French to English translation is not whitelisted");
+}
+
+// Tests that "Always Translate" is automatically triggered only for a maximum
+// number of times if refused by the user.
+- (void)testInfobarAutoAlwaysTranslateMaxTries {
+  // Start the HTTP server.
+  std::unique_ptr<web::DataResponseProvider> provider(new TestResponseProvider);
+  web::test::SetUpHttpServer(std::move(provider));
+
+  // Load a page with French text.
+  GURL URL = web::test::HttpServer::MakeUrl(
+      base::StringPrintf("http://%s", kFrenchPagePath));
+  [ChromeEarlGrey loadURL:URL];
+
+  [self assertTranslateInfobarIsVisible];
+
+  // Make sure that French to English translation is not whitelisted.
+  std::unique_ptr<translate::TranslatePrefs> translatePrefs(
+      ChromeIOSTranslateClient::CreateTranslatePrefs(
+          chrome_test_util::GetOriginalBrowserState()->GetPrefs()));
+  GREYAssert(!translatePrefs->IsLanguagePairWhitelisted("fr", "en"),
+             @"French to English translation is whitelisted");
+
+  // Trigger and refuse the auto "Always Translate".
+  for (int i = 0; i < translate::kMaxNumberOfAutoAlways; i++) {
+    // Translate the page by tapping the target language tab until
+    // "Always Translate" is automatically triggered.
+    for (int j = 0; j <= translate::kAutoAlwaysThreshold; j++) {
+      [[EarlGrey
+          selectElementWithMatcher:ButtonWithAccessibilityLabel(@"English")]
+          performAction:grey_tap()];
+    }
+    // Tap the notification snackbar's "UNDO" button.
+    [[EarlGrey selectElementWithMatcher:UndoButton()] performAction:grey_tap()];
+  }
+
+  // Translate the page by tapping the target language tab in order to
+  // automatically trigger "Always Translate".
+  for (int i = 0; i <= translate::kAutoAlwaysThreshold; i++) {
+    [[EarlGrey
+        selectElementWithMatcher:ButtonWithAccessibilityLabel(@"English")]
+        performAction:grey_tap()];
+  }
+
+  // Make sure "Always Translate" is not triggered.
+  NSString* snackbarTitle =
+      l10n_util::GetNSStringF(IDS_TRANSLATE_NOTIFICATION_ALWAYS_TRANSLATE,
+                              base::SysNSStringToUTF16(@"French"),
+                              base::SysNSStringToUTF16(@"English"));
+  GREYAssertFalse([self waitForElementToAppearOrTimeout:grey_accessibilityLabel(
+                                                            snackbarTitle)],
+                  @"Always Translate was triggered.");
+}
+
+// Tests that the "Never Translate ..." options dismisses the infobar and
+// updates the prefs accordingly.
+- (void)testInfobarNeverTranslate {
+  // Start the HTTP server.
+  std::unique_ptr<web::DataResponseProvider> provider(new TestResponseProvider);
+  web::test::SetUpHttpServer(std::move(provider));
+
+  // Load a page with French text.
+  GURL URL = web::test::HttpServer::MakeUrl(
+      base::StringPrintf("http://%s", kFrenchPagePath));
+  [ChromeEarlGrey loadURL:URL];
+
+  [self assertTranslateInfobarIsVisible];
+
+  // Make sure that translation from French is not blocked.
+  std::unique_ptr<translate::TranslatePrefs> translatePrefs(
+      ChromeIOSTranslateClient::CreateTranslatePrefs(
+          chrome_test_util::GetOriginalBrowserState()->GetPrefs()));
+  GREYAssert(!translatePrefs->IsBlockedLanguage("fr"),
+             @"Translation from French is blocked");
+
+  // Open the translate options menu.
+  [[EarlGrey selectElementWithMatcher:OptionsButton()]
+      performAction:grey_tap()];
+
+  // Tap the "Never Translate French" entry.
+  [[EarlGrey selectElementWithMatcher:NeverTranslate(@"French")]
+      performAction:grey_tap()];
+
+  // Expect the translate options menu to have disappeared.
+  [[EarlGrey selectElementWithMatcher:OptionsMenu()]
+      assertWithMatcher:grey_nil()];
+
+  // Tap the notification snackbar's "UNDO" button.
+  [[EarlGrey selectElementWithMatcher:UndoButton()] performAction:grey_tap()];
+
+  // Make sure that translation from French is still not blocked.
+  GREYAssert(!translatePrefs->IsBlockedLanguage("fr"),
+             @"Translation from French is blocked");
+
+  // Open the translate options menu.
+  [[EarlGrey selectElementWithMatcher:OptionsButton()]
+      performAction:grey_tap()];
+
+  // Tap the "Never Translate French" entry.
+  [[EarlGrey selectElementWithMatcher:NeverTranslate(@"French")]
+      performAction:grey_tap()];
+
+  // Make sure that translation from French is not blocked yet.
+  GREYAssert(!translatePrefs->IsBlockedLanguage("fr"),
+             @"Translation from French is blocked");
+
+  // Tap the notification snackbar to dismiss it.
+  NSString* snackbarTitle =
+      l10n_util::GetNSStringF(IDS_TRANSLATE_NOTIFICATION_LANGUAGE_NEVER,
+                              base::SysNSStringToUTF16(@"French"));
+  [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(snackbarTitle)]
+      performAction:grey_tap()];
+
+  // Wait until the translate infobar disappears.
+  GREYAssert([self waitForElementToDisappearOrTimeout:TranslateInfobar()],
+             @"Translate infobar failed to disappear.");
+
+  // Make sure that translation from French is blocked after the snackbar is
+  // dismissed.
+  GREYAssert(translatePrefs->IsBlockedLanguage("fr"),
+             @"Translation from French is not blocked");
+
+  // Reload the page.
+  [ChromeEarlGrey reload];
+
+  // Make sure the translate infobar does not appear.
+  GREYAssertFalse([self waitForElementToAppearOrTimeout:TranslateInfobar()],
+                  @"Translate infobar appeared.");
+}
+
+// Tests that "Never Translate ..." is automatically triggered after a minimum
+// number of translate infobar dismissals by the user.
+- (void)testInfobarAutoNeverTranslate {
+  // Start the HTTP server.
+  std::unique_ptr<web::DataResponseProvider> provider(new TestResponseProvider);
+  web::test::SetUpHttpServer(std::move(provider));
+
+  // Load a page with French text.
+  GURL URL = web::test::HttpServer::MakeUrl(
+      base::StringPrintf("http://%s", kFrenchPagePath));
+  [ChromeEarlGrey loadURL:URL];
+
+  [self assertTranslateInfobarIsVisible];
+
+  // Make sure that translation from French is not blocked.
+  std::unique_ptr<translate::TranslatePrefs> translatePrefs(
+      ChromeIOSTranslateClient::CreateTranslatePrefs(
+          chrome_test_util::GetOriginalBrowserState()->GetPrefs()));
+  GREYAssert(!translatePrefs->IsBlockedLanguage("fr"),
+             @"Translation from French is blocked");
+
+  // Dismiss the translate infobar until "Never Translate ..." is automatically
+  // triggered.
+  for (int i = 0; i < translate::kAutoNeverThreshold; i++) {
+    // Reload the page.
+    [ChromeEarlGrey reload];
+
+    [self assertTranslateInfobarIsVisible];
+
+    // Dismiss the translate infobar.
+    [[EarlGrey selectElementWithMatcher:CloseButton()]
+        performAction:grey_tap()];
+  }
+
+  // Make sure that translation from French is not blocked yet.
+  GREYAssert(!translatePrefs->IsBlockedLanguage("fr"),
+             @"Translation from French is blocked");
+
+  // Tap the notification snackbar to dismiss it.
+  NSString* snackbarTitle =
+      l10n_util::GetNSStringF(IDS_TRANSLATE_NOTIFICATION_LANGUAGE_NEVER,
+                              base::SysNSStringToUTF16(@"French"));
+  [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(snackbarTitle)]
+      performAction:grey_tap()];
+
+  // Wait until the translate infobar disappears.
+  GREYAssert([self waitForElementToDisappearOrTimeout:TranslateInfobar()],
+             @"Translate infobar failed to disappear.");
+
+  // Make sure that translation from French is blocked after the snackbar is
+  // dismissed.
+  GREYAssert(translatePrefs->IsBlockedLanguage("fr"),
+             @"Translation from French is not blocked");
+}
+
+// Tests that "Never Translate ..." is automatically triggered only for a
+// maximum number of times if refused by the user.
+- (void)testInfobarAutoNeverTranslateMaxTries {
+  // Start the HTTP server.
+  std::unique_ptr<web::DataResponseProvider> provider(new TestResponseProvider);
+  web::test::SetUpHttpServer(std::move(provider));
+
+  // Load a page with French text.
+  GURL URL = web::test::HttpServer::MakeUrl(
+      base::StringPrintf("http://%s", kFrenchPagePath));
+  [ChromeEarlGrey loadURL:URL];
+
+  [self assertTranslateInfobarIsVisible];
+
+  // Make sure that translation from French is not blocked.
+  std::unique_ptr<translate::TranslatePrefs> translatePrefs(
+      ChromeIOSTranslateClient::CreateTranslatePrefs(
+          chrome_test_util::GetOriginalBrowserState()->GetPrefs()));
+  GREYAssert(!translatePrefs->IsBlockedLanguage("fr"),
+             @"Translation from French is blocked");
+
+  // Trigger and refuse the auto "Never Translate ...".
+  for (int i = 0; i < translate::kMaxNumberOfAutoNever; i++) {
+    // Dismiss the translate infobar until "Never Translate ..." is
+    // automatically triggered.
+    for (int j = 0; j < translate::kAutoNeverThreshold; j++) {
+      // Reload the page.
+      [ChromeEarlGrey reload];
+
+      [self assertTranslateInfobarIsVisible];
+
+      // Dismiss the translate infobar.
+      [[EarlGrey selectElementWithMatcher:CloseButton()]
+          performAction:grey_tap()];
+    }
+    // Tap the notification snackbar's "UNDO" button.
+    [[EarlGrey selectElementWithMatcher:UndoButton()] performAction:grey_tap()];
+
+    // Wait until the translate infobar disappears.
+    GREYAssert([self waitForElementToDisappearOrTimeout:TranslateInfobar()],
+               @"Translate infobar failed to disappear.");
+  }
+
+  // Dismiss the translate infobar in order to automatically trigger
+  // "Never Translate ...".
+  for (int i = 0; i < translate::kAutoNeverThreshold; i++) {
+    // Reload the page.
+    [ChromeEarlGrey reload];
+
+    [self assertTranslateInfobarIsVisible];
+
+    // Dismiss the translate infobar.
+    [[EarlGrey selectElementWithMatcher:CloseButton()]
+        performAction:grey_tap()];
+  }
+
+  // Make sure "Never Translate ..." is not triggered.
+  NSString* snackbarTitle =
+      l10n_util::GetNSStringF(IDS_TRANSLATE_NOTIFICATION_LANGUAGE_NEVER,
+                              base::SysNSStringToUTF16(@"French"));
+  GREYAssertFalse([self waitForElementToAppearOrTimeout:grey_accessibilityLabel(
+                                                            snackbarTitle)],
+                  @"Never Translate French was triggered.");
+}
+
+// Tests that the "Never Translate this site" option dismisses the infobar and
+// updates the prefs accordingly.
+- (void)testInfobarNeverTranslateSite {
+  // Start the HTTP server.
+  std::unique_ptr<web::DataResponseProvider> provider(new TestResponseProvider);
+  web::test::SetUpHttpServer(std::move(provider));
+
+  // Load a page with French text.
+  GURL URL = web::test::HttpServer::MakeUrl(
+      base::StringPrintf("http://%s", kFrenchPagePath));
+  [ChromeEarlGrey loadURL:URL];
+
+  [self assertTranslateInfobarIsVisible];
+
+  // Make sure that translation for the site is not blocked.
+  std::unique_ptr<translate::TranslatePrefs> translatePrefs(
+      ChromeIOSTranslateClient::CreateTranslatePrefs(
+          chrome_test_util::GetOriginalBrowserState()->GetPrefs()));
+  GREYAssert(!translatePrefs->IsSiteBlacklisted(URL.HostNoBrackets()),
+             @"Translation is blocked for the site");
+
+  // Open the translate options menu.
+  [[EarlGrey selectElementWithMatcher:OptionsButton()]
+      performAction:grey_tap()];
+
+  // Tap the "Never Translate this site" entry.
+  [[EarlGrey selectElementWithMatcher:NeverTranslateSite()]
+      performAction:grey_tap()];
+
+  // Expect the translate options menu to have disappeared.
+  [[EarlGrey selectElementWithMatcher:OptionsMenu()]
+      assertWithMatcher:grey_nil()];
+
+  // Tap the notification snackbar's "UNDO" button.
+  [[EarlGrey selectElementWithMatcher:UndoButton()] performAction:grey_tap()];
+
+  // Make sure that translation for the site is still not blocked.
+  GREYAssert(!translatePrefs->IsSiteBlacklisted(URL.HostNoBrackets()),
+             @"Translation is blocked for the site");
+
+  // Open the translate options menu.
+  [[EarlGrey selectElementWithMatcher:OptionsButton()]
+      performAction:grey_tap()];
+
+  // Tap the "Never Translate this site" entry.
+  [[EarlGrey selectElementWithMatcher:NeverTranslateSite()]
+      performAction:grey_tap()];
+
+  // Make sure that translation for the site is not blocked yet.
+  GREYAssert(!translatePrefs->IsSiteBlacklisted(URL.HostNoBrackets()),
+             @"Translation is blocked for the site");
+
+  // Tap the notification snackbar to dismiss it.
+  NSString* snackbarTitle =
+      l10n_util::GetNSString(IDS_TRANSLATE_NOTIFICATION_SITE_NEVER);
+  [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(snackbarTitle)]
+      performAction:grey_tap()];
+
+  // Make sure that translation for the site is blocked after the snackbar is
+  // dismissed.
+  GREYAssert(translatePrefs->IsSiteBlacklisted(URL.HostNoBrackets()),
+             @"Translation is not blocked for the site");
+
+  // Wait until the translate infobar disappears.
+  GREYAssert([self waitForElementToDisappearOrTimeout:TranslateInfobar()],
+             @"Translate infobar failed to disappear.");
+
+  // Reload the page.
+  [ChromeEarlGrey reload];
+
+  // Make sure the translate infobar does not appear.
+  GREYAssertFalse([self waitForElementToAppearOrTimeout:TranslateInfobar()],
+                  @"Translate infobar appeared.");
+}
+
 #pragma mark - Utility methods
 
 - (void)assertTranslateInfobarIsVisible {
   // Wait until the translate infobar becomes visible.
-  ConditionBlock condition = ^{
-    NSError* error = nil;
-    [[EarlGrey selectElementWithMatcher:TranslateInfobar()]
-        assertWithMatcher:grey_notNil()
-                    error:&error];
-    return error == nil;
-  };
-  GREYAssert(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, condition),
+  GREYAssert([self waitForElementToAppearOrTimeout:TranslateInfobar()],
              @"Translate infobar failed to show.");
 
   // Check that the translate infobar is fully visible.
@@ -1138,6 +1517,26 @@
       assertWithMatcher:grey_sufficientlyVisible()];
 }
 
+- (BOOL)waitForElementToAppearOrTimeout:(id<GREYMatcher>)matcher {
+  ConditionBlock condition = ^{
+    NSError* error = nil;
+    [[EarlGrey selectElementWithMatcher:matcher] assertWithMatcher:grey_notNil()
+                                                             error:&error];
+    return error == nil;
+  };
+  return WaitUntilConditionOrTimeout(kWaitForUIElementTimeout, condition);
+}
+
+- (BOOL)waitForElementToDisappearOrTimeout:(id<GREYMatcher>)matcher {
+  ConditionBlock condition = ^{
+    NSError* error = nil;
+    [[EarlGrey selectElementWithMatcher:matcher] assertWithMatcher:grey_nil()
+                                                             error:&error];
+    return error == nil;
+  };
+  return WaitUntilConditionOrTimeout(kWaitForUIElementTimeout, condition);
+}
+
 // Waits until a language has been detected and checks the language details.
 - (void)assertLanguageDetails:
     (const translate::LanguageDetectionDetails&)expectedDetails {
diff --git a/ios/chrome/browser/ui/BUILD.gn b/ios/chrome/browser/ui/BUILD.gn
index 6836ee9d..0b4a77b 100644
--- a/ios/chrome/browser/ui/BUILD.gn
+++ b/ios/chrome/browser/ui/BUILD.gn
@@ -30,7 +30,6 @@
     "//components/sessions",
     "//ios/chrome/browser",
     "//ios/chrome/browser/ui/commands",
-    "//ios/web",
     "//ui/base",
   ]
   allow_circular_includes_from = [ "//ios/chrome/browser/ui/commands" ]
@@ -63,14 +62,10 @@
   configs += [ "//build/config/compiler:enable_arc" ]
   testonly = true
   sources = [
-    "browser_view_controller_helper_unittest.mm",
-    "browser_view_controller_unittest.mm",
     "file_locations_unittest.mm",
     "key_commands_provider_unittest.mm",
     "native_content_controller_unittest.mm",
     "network_activity_indicator_manager_unittest.mm",
-    "open_in_controller_unittest.mm",
-    "open_in_toolbar_unittest.mm",
     "page_not_available_controller_unittest.mm",
   ]
   deps = [
@@ -116,7 +111,6 @@
     "//ios/chrome/test:test_support",
     "//ios/net",
     "//ios/testing:ocmock_support",
-    "//ios/web",
     "//ios/web/public/test",
     "//ios/web/public/test/fakes",
     "//net",
@@ -228,20 +222,8 @@
 source_set("ui_internal") {
   configs += [ "//build/config/compiler:enable_arc" ]
   sources = [
-    "browser_view_controller+private.h",
-    "browser_view_controller.h",
-    "browser_view_controller.mm",
-    "browser_view_controller_dependency_factory.h",
-    "browser_view_controller_dependency_factory.mm",
-    "browser_view_controller_helper.h",
-    "browser_view_controller_helper.mm",
     "key_commands_provider.h",
     "key_commands_provider.mm",
-    "open_in_controller.h",
-    "open_in_controller.mm",
-    "open_in_controller_testing.h",
-    "open_in_toolbar.h",
-    "open_in_toolbar.mm",
     "page_not_available_controller.h",
     "page_not_available_controller.mm",
   ]
@@ -376,7 +358,6 @@
     "//ios/public/provider/chrome/browser/ui",
     "//ios/public/provider/chrome/browser/voice",
     "//ios/third_party/material_components_ios",
-    "//ios/web",
     "//ios/web/public",
     "//third_party/google_toolbox_for_mac",
     "//ui/base",
@@ -429,7 +410,6 @@
   configs += [ "//build/config/compiler:enable_arc" ]
   testonly = true
   sources = [
-    "browser_view_controller_egtest.mm",
     "keyboard_commands_egtest.mm",
   ]
   deps = [
@@ -441,6 +421,7 @@
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/tabs",
     "//ios/chrome/browser/ui/bookmarks:bookmarks_ui",
+    "//ios/chrome/browser/ui/browser_view",
     "//ios/chrome/browser/ui/commands",
     "//ios/chrome/browser/ui/ntp:ntp_controller",
     "//ios/chrome/browser/ui/popup_menu:constants",
diff --git a/ios/chrome/browser/ui/OWNERS b/ios/chrome/browser/ui/OWNERS
index 1db61e0..1ccf6c3c 100644
--- a/ios/chrome/browser/ui/OWNERS
+++ b/ios/chrome/browser/ui/OWNERS
@@ -4,11 +4,5 @@
 marq@chromium.org
 rohitrao@chromium.org
 
-# BVC OWNERS for small changes. Please refer larger changes to one
-# of the above OWNERs.
-per-file browser_view_controller*=edchin@chromium.org
-per-file browser_view_controller*=kkhorimoto@chromium.org
-per-file browser_view_controller*=gambard@chromium.org
-
 # TEAM: ios-directory-owners@chromium.org
 # OS: iOS
diff --git a/ios/chrome/browser/ui/activity_services/activity_service_controller_egtest.mm b/ios/chrome/browser/ui/activity_services/activity_service_controller_egtest.mm
index a33c485..a0de3cb34 100644
--- a/ios/chrome/browser/ui/activity_services/activity_service_controller_egtest.mm
+++ b/ios/chrome/browser/ui/activity_services/activity_service_controller_egtest.mm
@@ -8,7 +8,6 @@
 
 #include "base/ios/ios_util.h"
 #include "components/strings/grit/components_strings.h"
-#import "ios/chrome/browser/ui/browser_view_controller_dependency_factory.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
 #include "ios/chrome/grit/ios_strings.h"
 #import "ios/chrome/test/app/chrome_test_util.h"
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/action_cell.mm b/ios/chrome/browser/ui/autofill/manual_fill/action_cell.mm
index 0dd7806..9227f2d 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/action_cell.mm
+++ b/ios/chrome/browser/ui/autofill/manual_fill/action_cell.mm
@@ -14,16 +14,6 @@
 #error "This file requires ARC support."
 #endif
 
-namespace {
-
-// The multiplier for the base system spacing at the top margin.
-static const CGFloat TopBaseSystemSpacingMultiplier = 0.8;
-
-// The multiplier for the base system spacing at the bottom margin.
-static const CGFloat BottomBaseSystemSpacingMultiplier = 1.8;
-
-}  // namespace
-
 @interface ManualFillActionItem ()
 
 // The action block to be called when the user taps the title.
@@ -114,21 +104,24 @@
                            forState:UIControlStateNormal];
   }
   self.action = action;
-
-  NSMutableArray<UIView*>* verticalLeadViews = [[NSMutableArray alloc] init];
-  [verticalLeadViews addObject:self.titleButton];
-
-  // When disabled, the label is in 'message' mode, and needs to be centered
-  // differently because it is in the data area rather than in the action area.
-  CGFloat topMultiplier =
-      enabled ? TopBaseSystemSpacingMultiplier : TopSystemSpacingMultiplier;
-  CGFloat bottomMultiplier = enabled ? BottomBaseSystemSpacingMultiplier
-                                     : MiddleSystemSpacingMultiplier;
-
-  self.dynamicConstraints = [[NSMutableArray alloc] init];
-  AppendVerticalConstraintsSpacingForViews(
-      self.dynamicConstraints, verticalLeadViews, self.contentView,
-      topMultiplier, MiddleSystemSpacingMultiplier, bottomMultiplier);
+  if (enabled) {
+    self.dynamicConstraints = [[NSMutableArray alloc] initWithArray:@[
+      [self.contentView.topAnchor
+          constraintEqualToAnchor:self.titleButton.topAnchor],
+      [self.contentView.bottomAnchor
+          constraintEqualToAnchor:self.titleButton.bottomAnchor],
+    ]];
+  } else {
+    self.dynamicConstraints = [[NSMutableArray alloc] initWithArray:@[
+      [self.titleButton.topAnchor
+          constraintEqualToSystemSpacingBelowAnchor:self.contentView.topAnchor
+                                         multiplier:1.0],
+      [self.contentView.bottomAnchor
+          constraintEqualToSystemSpacingBelowAnchor:self.titleButton
+                                                        .bottomAnchor
+                                         multiplier:1.0],
+    ]];
+  }
   [NSLayoutConstraint activateConstraints:self.dynamicConstraints];
 }
 
diff --git a/ios/chrome/browser/ui/browser_view/BUILD.gn b/ios/chrome/browser/ui/browser_view/BUILD.gn
new file mode 100644
index 0000000..c5fb286a
--- /dev/null
+++ b/ios/chrome/browser/ui/browser_view/BUILD.gn
@@ -0,0 +1,253 @@
+# 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.
+
+source_set("browser_view") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  sources = [
+    "browser_view_controller+private.h",
+    "browser_view_controller.h",
+    "browser_view_controller.mm",
+    "browser_view_controller_dependency_factory.h",
+    "browser_view_controller_dependency_factory.mm",
+    "browser_view_controller_helper.h",
+    "browser_view_controller_helper.mm",
+  ]
+  deps = [
+    "//base",
+    "//base:i18n",
+    "//components/bookmarks/browser",
+    "//components/image_fetcher/ios",
+    "//components/language/ios/browser",
+    "//components/omnibox/browser",
+    "//components/payments/core",
+    "//components/reading_list/core",
+    "//components/search_engines",
+    "//components/sessions",
+    "//components/signin/core/browser",
+    "//components/signin/ios/browser",
+    "//components/signin/ios/browser:active_state_manager",
+    "//components/strings",
+    "//components/url_formatter",
+    "//ios/chrome/app:tests_hook",
+    "//ios/chrome/app/strings",
+    "//ios/chrome/browser",
+    "//ios/chrome/browser/autofill:autofill_internal",
+    "//ios/chrome/browser/bookmarks",
+    "//ios/chrome/browser/browser_state",
+    "//ios/chrome/browser/download",
+    "//ios/chrome/browser/feature_engagement",
+    "//ios/chrome/browser/find_in_page",
+    "//ios/chrome/browser/first_run",
+    "//ios/chrome/browser/geolocation:geolocation_internal",
+    "//ios/chrome/browser/language",
+    "//ios/chrome/browser/metrics:metrics_internal",
+    "//ios/chrome/browser/net",
+    "//ios/chrome/browser/ntp",
+    "//ios/chrome/browser/overscroll_actions",
+    "//ios/chrome/browser/passwords",
+    "//ios/chrome/browser/prefs",
+    "//ios/chrome/browser/prerender",
+    "//ios/chrome/browser/reading_list",
+    "//ios/chrome/browser/search_engines",
+    "//ios/chrome/browser/sessions",
+    "//ios/chrome/browser/sessions:serialisation",
+    "//ios/chrome/browser/signin",
+    "//ios/chrome/browser/snapshots",
+    "//ios/chrome/browser/ssl",
+    "//ios/chrome/browser/tabs",
+    "//ios/chrome/browser/translate",
+    "//ios/chrome/browser/ui:feature_flags",
+    "//ios/chrome/browser/ui:notifications",
+    "//ios/chrome/browser/ui:ui_internal",
+    "//ios/chrome/browser/ui/activity_services:coordinator",
+    "//ios/chrome/browser/ui/activity_services/requirements",
+    "//ios/chrome/browser/ui/alert_coordinator",
+    "//ios/chrome/browser/ui/autofill:autofill",
+    "//ios/chrome/browser/ui/autofill/manual_fill",
+    "//ios/chrome/browser/ui/autofill/manual_fill:manual_fill_ui",
+    "//ios/chrome/browser/ui/bookmarks",
+    "//ios/chrome/browser/ui/browser_container",
+    "//ios/chrome/browser/ui/browser_container:ui",
+    "//ios/chrome/browser/ui/bubble",
+    "//ios/chrome/browser/ui/colors",
+    "//ios/chrome/browser/ui/commands",
+    "//ios/chrome/browser/ui/content_suggestions",
+    "//ios/chrome/browser/ui/content_suggestions:content_suggestions_constant",
+    "//ios/chrome/browser/ui/context_menu",
+    "//ios/chrome/browser/ui/dialogs:dialogs_internal",
+    "//ios/chrome/browser/ui/download",
+    "//ios/chrome/browser/ui/elements:elements_internal",
+    "//ios/chrome/browser/ui/find_bar",
+    "//ios/chrome/browser/ui/first_run",
+    "//ios/chrome/browser/ui/fullscreen",
+    "//ios/chrome/browser/ui/fullscreen:feature_flags",
+    "//ios/chrome/browser/ui/fullscreen:ui",
+    "//ios/chrome/browser/ui/history",
+    "//ios/chrome/browser/ui/image_util:web",
+    "//ios/chrome/browser/ui/infobars",
+    "//ios/chrome/browser/ui/infobars:public",
+    "//ios/chrome/browser/ui/keyboard",
+    "//ios/chrome/browser/ui/location_bar:location_bar_model_delegate",
+    "//ios/chrome/browser/ui/main:tab_switcher",
+    "//ios/chrome/browser/ui/main_content:main_content_ui",
+    "//ios/chrome/browser/ui/main_content:main_content_ui_broadcasting_util",
+    "//ios/chrome/browser/ui/ntp",
+    "//ios/chrome/browser/ui/ntp:coordinator",
+    "//ios/chrome/browser/ui/ntp:ntp_controller",
+    "//ios/chrome/browser/ui/ntp:util",
+    "//ios/chrome/browser/ui/omnibox:omnibox_internal",
+    "//ios/chrome/browser/ui/omnibox/popup",
+    "//ios/chrome/browser/ui/overscroll_actions",
+    "//ios/chrome/browser/ui/page_info/requirements",
+    "//ios/chrome/browser/ui/payments",
+    "//ios/chrome/browser/ui/popup_menu",
+    "//ios/chrome/browser/ui/presenters",
+    "//ios/chrome/browser/ui/qr_scanner:coordinator",
+    "//ios/chrome/browser/ui/reading_list",
+    "//ios/chrome/browser/ui/sad_tab",
+    "//ios/chrome/browser/ui/sad_tab:coordinator",
+    "//ios/chrome/browser/ui/settings/sync/utils",
+    "//ios/chrome/browser/ui/side_swipe",
+    "//ios/chrome/browser/ui/snackbar",
+    "//ios/chrome/browser/ui/static_content",
+    "//ios/chrome/browser/ui/tabs",
+    "//ios/chrome/browser/ui/tabs:coordinator",
+    "//ios/chrome/browser/ui/tabs/requirements",
+    "//ios/chrome/browser/ui/toolbar",
+    "//ios/chrome/browser/ui/toolbar:toolbar_ui",
+    "//ios/chrome/browser/ui/toolbar/buttons",
+    "//ios/chrome/browser/ui/toolbar/fullscreen",
+    "//ios/chrome/browser/ui/toolbar/fullscreen:fullscreen_broadcasting_util",
+    "//ios/chrome/browser/ui/toolbar/public",
+    "//ios/chrome/browser/ui/toolbar/public:feature_flags",
+    "//ios/chrome/browser/ui/toolbar_container",
+    "//ios/chrome/browser/ui/toolbar_container:feature_flags",
+    "//ios/chrome/browser/ui/translate",
+    "//ios/chrome/browser/ui/util",
+    "//ios/chrome/browser/ui/voice",
+    "//ios/chrome/browser/upgrade",
+    "//ios/chrome/browser/url_loading",
+    "//ios/chrome/browser/voice:voice",
+    "//ios/chrome/browser/web",
+    "//ios/chrome/browser/web:tab_helper_delegates",
+    "//ios/chrome/browser/web:web_internal",
+    "//ios/chrome/browser/web_state_list",
+    "//ios/chrome/browser/web_state_list/web_usage_enabler",
+    "//ios/chrome/browser/webui",
+    "//ios/chrome/common",
+    "//ios/chrome/common/ui_util",
+    "//ios/public/provider/chrome/browser",
+    "//ios/public/provider/chrome/browser/ui",
+    "//ios/public/provider/chrome/browser/voice",
+    "//ios/third_party/material_components_ios",
+    "//ios/web",
+    "//ios/web/public",
+    "//third_party/google_toolbox_for_mac",
+    "//ui/base",
+    "//ui/gfx",
+    "//url",
+
+    # Fake dependencies to break cycles
+    "//ios/chrome/browser/ui/settings",
+  ]
+  libs = [
+    "MessageUI.framework",
+    "Photos.framework",
+    "UIKit.framework",
+    "WebKit.framework",
+  ]
+}
+
+source_set("unit_tests") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  testonly = true
+  sources = [
+    "browser_view_controller_helper_unittest.mm",
+    "browser_view_controller_unittest.mm",
+  ]
+  deps = [
+    ":browser_view",
+    "//components/bookmarks/browser",
+    "//components/bookmarks/test",
+    "//components/omnibox/browser:test_support",
+    "//components/prefs:test_support",
+    "//components/search_engines",
+    "//components/sessions",
+    "//ios/chrome/app/strings",
+    "//ios/chrome/browser",
+    "//ios/chrome/browser/bookmarks",
+    "//ios/chrome/browser/browser_state:test_support",
+    "//ios/chrome/browser/favicon",
+    "//ios/chrome/browser/main:test_support",
+    "//ios/chrome/browser/search_engines",
+    "//ios/chrome/browser/sessions",
+    "//ios/chrome/browser/snapshots",
+    "//ios/chrome/browser/tabs",
+    "//ios/chrome/browser/ui:feature_flags",
+    "//ios/chrome/browser/ui:ui_internal",
+    "//ios/chrome/browser/ui/activity_services",
+    "//ios/chrome/browser/ui/alert_coordinator",
+    "//ios/chrome/browser/ui/browser_container:ui",
+    "//ios/chrome/browser/ui/commands",
+    "//ios/chrome/browser/ui/ntp:ntp_controller",
+    "//ios/chrome/browser/ui/toolbar/public",
+    "//ios/chrome/browser/ui/toolbar/test",
+    "//ios/chrome/browser/ui/util",
+    "//ios/chrome/browser/web",
+    "//ios/chrome/browser/web_state_list",
+    "//ios/chrome/browser/web_state_list:test_support",
+    "//ios/chrome/browser/web_state_list/web_usage_enabler",
+    "//ios/chrome/test:block_cleanup_test",
+    "//ios/chrome/test:test_support",
+    "//ios/net",
+    "//ios/testing:ocmock_support",
+    "//ios/web/public",
+    "//ios/web/public/test",
+    "//ios/web/public/test/fakes",
+    "//ios/web/web_state:web_state_impl_header",
+    "//ios/web/web_state/ui",
+    "//net",
+    "//net:test_support",
+    "//testing/gmock",
+    "//testing/gtest",
+    "//third_party/ocmock",
+    "//ui/base",
+    "//ui/base:test_support",
+  ]
+}
+
+source_set("eg_tests") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  testonly = true
+  sources = [
+    "browser_view_controller_egtest.mm",
+  ]
+  deps = [
+    ":browser_view",
+    "//base",
+    "//base/test:test_support",
+    "//components/strings",
+    "//ios/chrome/app/strings",
+    "//ios/chrome/browser/tabs",
+    "//ios/chrome/browser/ui/bookmarks:bookmarks_ui",
+    "//ios/chrome/browser/ui/commands",
+    "//ios/chrome/browser/ui/ntp:ntp_controller",
+    "//ios/chrome/browser/ui/popup_menu:constants",
+    "//ios/chrome/browser/ui/table_view",
+    "//ios/chrome/browser/ui/util",
+    "//ios/chrome/test/app:test_support",
+    "//ios/chrome/test/earl_grey:test_support",
+    "//ios/third_party/earl_grey:earl_grey+link",
+    "//ios/web:earl_grey_test_support",
+    "//ios/web/public/test",
+    "//ios/web/public/test/http_server",
+    "//ui/base",
+    "//url",
+  ]
+  libs = [
+    "UIKit.framework",
+    "WebKit.framework",
+    "XCTest.framework",
+  ]
+}
diff --git a/ios/chrome/browser/ui/browser_view/OWNERS b/ios/chrome/browser/ui/browser_view/OWNERS
new file mode 100644
index 0000000..c6b54d5
--- /dev/null
+++ b/ios/chrome/browser/ui/browser_view/OWNERS
@@ -0,0 +1,11 @@
+marq@chromium.org
+rohitrao@chromium.org
+
+# BVC OWNERS for small changes. Please refer larger changes to one
+# of the above OWNERs.
+per-file browser_view_controller*=edchin@chromium.org
+per-file browser_view_controller*=kkhorimoto@chromium.org
+per-file browser_view_controller*=gambard@chromium.org
+
+# TEAM: ios-directory-owners@chromium.org
+# OS: iOS
diff --git a/ios/chrome/browser/ui/browser_view_controller+private.h b/ios/chrome/browser/ui/browser_view/browser_view_controller+private.h
similarity index 79%
rename from ios/chrome/browser/ui/browser_view_controller+private.h
rename to ios/chrome/browser/ui/browser_view/browser_view_controller+private.h
index f556386..ca00cd3 100644
--- a/ios/chrome/browser/ui/browser_view_controller+private.h
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller+private.h
@@ -2,10 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef IOS_CHROME_BROWSER_UI_BROWSER_VIEW_CONTROLLER_PRIVATE_H_
-#define IOS_CHROME_BROWSER_UI_BROWSER_VIEW_CONTROLLER_PRIVATE_H_
+#ifndef IOS_CHROME_BROWSER_UI_BROWSER_VIEW_BROWSER_VIEW_CONTROLLER_PRIVATE_H_
+#define IOS_CHROME_BROWSER_UI_BROWSER_VIEW_BROWSER_VIEW_CONTROLLER_PRIVATE_H_
 
-#import "ios/chrome/browser/ui/browser_view_controller.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller.h"
 
 #import "base/ios/block_types.h"
 
@@ -34,4 +34,4 @@
 
 @end
 
-#endif  // IOS_CHROME_BROWSER_UI_BROWSER_VIEW_CONTROLLER_PRIVATE_H_
+#endif  // IOS_CHROME_BROWSER_UI_BROWSER_VIEW_BROWSER_VIEW_CONTROLLER_PRIVATE_H_
diff --git a/ios/chrome/browser/ui/browser_view_controller.h b/ios/chrome/browser/ui/browser_view/browser_view_controller.h
similarity index 95%
rename from ios/chrome/browser/ui/browser_view_controller.h
rename to ios/chrome/browser/ui/browser_view/browser_view_controller.h
index 77803d9..2834491c 100644
--- a/ios/chrome/browser/ui/browser_view_controller.h
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef IOS_CHROME_BROWSER_UI_BROWSER_VIEW_CONTROLLER_H_
-#define IOS_CHROME_BROWSER_UI_BROWSER_VIEW_CONTROLLER_H_
+#ifndef IOS_CHROME_BROWSER_UI_BROWSER_VIEW_BROWSER_VIEW_CONTROLLER_H_
+#define IOS_CHROME_BROWSER_UI_BROWSER_VIEW_BROWSER_VIEW_CONTROLLER_H_
 
 #import <UIKit/UIKit.h>
 
@@ -18,7 +18,6 @@
 @class BrowserContainerViewController;
 @class BrowserViewControllerDependencyFactory;
 @class CommandDispatcher;
-class GURL;
 @protocol OmniboxFocuser;
 @protocol PopupMenuCommands;
 @protocol FakeboxFocuser;
@@ -117,4 +116,4 @@
 
 @end
 
-#endif  // IOS_CHROME_BROWSER_UI_BROWSER_VIEW_CONTROLLER_H_
+#endif  // IOS_CHROME_BROWSER_UI_BROWSER_VIEW_BROWSER_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/ui/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
similarity index 98%
rename from ios/chrome/browser/ui/browser_view_controller.mm
rename to ios/chrome/browser/ui/browser_view/browser_view_controller.mm
index 6c16efd..fc2c479 100644
--- a/ios/chrome/browser/ui/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/chrome/browser/ui/browser_view_controller.h"
-#import "ios/chrome/browser/ui/browser_view_controller+private.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller+private.h"
 
 #import <MessageUI/MessageUI.h>
 
@@ -78,8 +78,8 @@
 #import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
 #import "ios/chrome/browser/ui/bookmarks/bookmark_interaction_controller.h"
 #import "ios/chrome/browser/ui/browser_container/browser_container_view_controller.h"
-#import "ios/chrome/browser/ui/browser_view_controller_dependency_factory.h"
-#import "ios/chrome/browser/ui/browser_view_controller_helper.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller_dependency_factory.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller_helper.h"
 #import "ios/chrome/browser/ui/bubble/bubble_presenter.h"
 #import "ios/chrome/browser/ui/bubble/bubble_presenter_delegate.h"
 #import "ios/chrome/browser/ui/commands/application_commands.h"
@@ -1749,13 +1749,14 @@
   [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
   [self dismissPopups];
 
-  [coordinator animateAlongsideTransition:^(
-                   id<UIViewControllerTransitionCoordinatorContext> context) {
-    // Force updates of the toolbar updater as the toolbar height might
-    // change on rotation.
-    [_toolbarUIUpdater updateState];
-  }
-                               completion:nil];
+  [coordinator
+      animateAlongsideTransition:^(
+          id<UIViewControllerTransitionCoordinatorContext> context) {
+        // Force updates of the toolbar updater as the toolbar height might
+        // change on rotation.
+        [_toolbarUIUpdater updateState];
+      }
+                      completion:nil];
 }
 
 - (void)dismissViewControllerAnimated:(BOOL)flag
@@ -1824,8 +1825,9 @@
 
       // Load view from Launch Screen and add it to window.
       NSBundle* mainBundle = base::mac::FrameworkBundle();
-      NSArray* topObjects =
-          [mainBundle loadNibNamed:@"LaunchScreen" owner:self options:nil];
+      NSArray* topObjects = [mainBundle loadNibNamed:@"LaunchScreen"
+                                               owner:self
+                                             options:nil];
       UIViewController* launchScreenController =
           base::mac::ObjCCastStrict<UIViewController>([topObjects lastObject]);
       // |launchScreenView| is loaded as an autoreleased object, and is retained
@@ -2266,7 +2268,6 @@
   fakeStatusBarFrame.size.height = topInset;
   _fakeStatusBarView.frame = fakeStatusBarFrame;
 
-
   // Position the toolbar next, either at the top of the browser view or
   // directly under the tabstrip.
   if (initialLayout) {
@@ -2329,8 +2330,8 @@
     AddNamedGuidesToView(guideNames, self.view);
 
     // Configure the content area guide.
-    NamedGuide* contentAreaGuide =
-        [NamedGuide guideWithName:kContentAreaGuide view:self.view];
+    NamedGuide* contentAreaGuide = [NamedGuide guideWithName:kContentAreaGuide
+                                                        view:self.view];
 
     // Constrain top to bottom of top toolbar.
     UIView* primaryToolbarView =
@@ -2677,8 +2678,8 @@
   if (CGPointEqualToPoint(originPoint, CGPointZero)) {
     _lastTapPoint = CGPointZero;
   } else {
-    _lastTapPoint =
-        [self.view.window convertPoint:originPoint toView:self.view];
+    _lastTapPoint = [self.view.window convertPoint:originPoint
+                                            toView:self.view];
   }
   _lastTapTime = CACurrentMediaTime();
 }
@@ -2693,8 +2694,8 @@
 - (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer {
   UIView* view = gestureRecognizer.view;
   CGPoint viewCoordinate = [gestureRecognizer locationInView:view];
-  _lastTapPoint =
-      [[view superview] convertPoint:viewCoordinate toView:self.view];
+  _lastTapPoint = [[view superview] convertPoint:viewCoordinate
+                                          toView:self.view];
   _lastTapTime = CACurrentMediaTime();
 }
 
@@ -3243,22 +3244,21 @@
     title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_SAVEIMAGE);
     action = ^{
       Record(ACTION_SAVE_IMAGE, isImage, isLink);
-        [weakSelf.imageSaver saveImageAtURL:imageUrl
-                                   referrer:referrer
-                                   webState:weakSelf.currentWebState];
+      [weakSelf.imageSaver saveImageAtURL:imageUrl
+                                 referrer:referrer
+                                 webState:weakSelf.currentWebState];
     };
     [_contextMenuCoordinator addItemWithTitle:title action:action];
     // Copy Image.
-      title =
-          l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_COPYIMAGE);
-      action = ^{
-        Record(ACTION_COPY_IMAGE, isImage, isLink);
-        DCHECK(imageUrl.is_valid());
-        [weakSelf.imageCopier copyImageAtURL:imageUrl
-                                    referrer:referrer
-                                    webState:weakSelf.currentWebState];
-      };
-      [_contextMenuCoordinator addItemWithTitle:title action:action];
+    title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_COPYIMAGE);
+    action = ^{
+      Record(ACTION_COPY_IMAGE, isImage, isLink);
+      DCHECK(imageUrl.is_valid());
+      [weakSelf.imageCopier copyImageAtURL:imageUrl
+                                  referrer:referrer
+                                  webState:weakSelf.currentWebState];
+    };
+    [_contextMenuCoordinator addItemWithTitle:title action:action];
     // Open Image.
     title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPENIMAGE);
     action = ^{
@@ -3298,12 +3298,12 @@
                                       defaultURL->short_name());
       action = ^{
         Record(ACTION_SEARCH_BY_IMAGE, isImage, isLink);
-          ImageFetchTabHelper* image_fetcher =
-              ImageFetchTabHelper::FromWebState(self.currentWebState);
-          DCHECK(image_fetcher);
-          image_fetcher->GetImageData(imageUrl, referrer, ^(NSData* data) {
-            [weakSelf searchByImageData:data atURL:imageUrl];
-          });
+        ImageFetchTabHelper* image_fetcher =
+            ImageFetchTabHelper::FromWebState(self.currentWebState);
+        DCHECK(image_fetcher);
+        image_fetcher->GetImageData(imageUrl, referrer, ^(NSData* data) {
+          [weakSelf searchByImageData:data atURL:imageUrl];
+        });
       };
       [_contextMenuCoordinator addItemWithTitle:title action:action];
     }
@@ -4526,8 +4526,8 @@
         self.primaryToolbarCoordinator.viewController;
     toolbarSnapshot =
         [toolbarViewController.view snapshotViewAfterScreenUpdates:NO];
-    toolbarSnapshot.frame =
-        [self.contentArea convertRect:toolbarSnapshot.frame fromView:self.view];
+    toolbarSnapshot.frame = [self.contentArea convertRect:toolbarSnapshot.frame
+                                                 fromView:self.view];
     [self.contentArea addSubview:toolbarSnapshot];
     newPage = [self viewForTab:tab];
     newPage.userInteractionEnabled = NO;
diff --git a/ios/chrome/browser/ui/browser_view_controller_dependency_factory.h b/ios/chrome/browser/ui/browser_view/browser_view_controller_dependency_factory.h
similarity index 81%
rename from ios/chrome/browser/ui/browser_view_controller_dependency_factory.h
rename to ios/chrome/browser/ui/browser_view/browser_view_controller_dependency_factory.h
index ad270e4..ac163668 100644
--- a/ios/chrome/browser/ui/browser_view_controller_dependency_factory.h
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller_dependency_factory.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef IOS_CHROME_BROWSER_UI_BROWSER_VIEW_CONTROLLER_DEPENDENCY_FACTORY_H_
-#define IOS_CHROME_BROWSER_UI_BROWSER_VIEW_CONTROLLER_DEPENDENCY_FACTORY_H_
+#ifndef IOS_CHROME_BROWSER_UI_BROWSER_VIEW_BROWSER_VIEW_CONTROLLER_DEPENDENCY_FACTORY_H_
+#define IOS_CHROME_BROWSER_UI_BROWSER_VIEW_BROWSER_VIEW_CONTROLLER_DEPENDENCY_FACTORY_H_
 
 #import <UIKit/UIKit.h>
 
@@ -37,4 +37,4 @@
 
 @end
 
-#endif  // IOS_CHROME_BROWSER_UI_BROWSER_VIEW_CONTROLLER_DEPENDENCY_FACTORY_H_
+#endif  // IOS_CHROME_BROWSER_UI_BROWSER_VIEW_BROWSER_VIEW_CONTROLLER_DEPENDENCY_FACTORY_H_
diff --git a/ios/chrome/browser/ui/browser_view_controller_dependency_factory.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller_dependency_factory.mm
similarity index 91%
rename from ios/chrome/browser/ui/browser_view_controller_dependency_factory.mm
rename to ios/chrome/browser/ui/browser_view/browser_view_controller_dependency_factory.mm
index 75a7bcb..48f61198 100644
--- a/ios/chrome/browser/ui/browser_view_controller_dependency_factory.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller_dependency_factory.mm
@@ -2,12 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/chrome/browser/ui/browser_view_controller_dependency_factory.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller_dependency_factory.h"
 
 #include "components/strings/grit/components_strings.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
-#import "ios/chrome/browser/ui/browser_view_controller_helper.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller_helper.h"
 #import "ios/chrome/browser/ui/key_commands_provider.h"
 #import "ios/chrome/browser/ui/toolbar/public/features.h"
 #include "ui/base/l10n/l10n_util_mac.h"
diff --git a/ios/chrome/browser/ui/browser_view_controller_egtest.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller_egtest.mm
similarity index 100%
rename from ios/chrome/browser/ui/browser_view_controller_egtest.mm
rename to ios/chrome/browser/ui/browser_view/browser_view_controller_egtest.mm
diff --git a/ios/chrome/browser/ui/browser_view_controller_helper.h b/ios/chrome/browser/ui/browser_view/browser_view_controller_helper.h
similarity index 78%
rename from ios/chrome/browser/ui/browser_view_controller_helper.h
rename to ios/chrome/browser/ui/browser_view/browser_view_controller_helper.h
index 6a3f809a..fecf4326 100644
--- a/ios/chrome/browser/ui/browser_view_controller_helper.h
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller_helper.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef IOS_CHROME_BROWSER_UI_BROWSER_VIEW_CONTROLLER_HELPER_H_
-#define IOS_CHROME_BROWSER_UI_BROWSER_VIEW_CONTROLLER_HELPER_H_
+#ifndef IOS_CHROME_BROWSER_UI_BROWSER_VIEW_BROWSER_VIEW_CONTROLLER_HELPER_H_
+#define IOS_CHROME_BROWSER_UI_BROWSER_VIEW_BROWSER_VIEW_CONTROLLER_HELPER_H_
 
 #import <UIKit/UIKit.h>
 
@@ -27,4 +27,4 @@
 
 @end
 
-#endif  // IOS_CHROME_BROWSER_UI_BROWSER_VIEW_CONTROLLER_HELPER_H_
+#endif  // IOS_CHROME_BROWSER_UI_BROWSER_VIEW_BROWSER_VIEW_CONTROLLER_HELPER_H_
diff --git a/ios/chrome/browser/ui/browser_view_controller_helper.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller_helper.mm
similarity index 95%
rename from ios/chrome/browser/ui/browser_view_controller_helper.mm
rename to ios/chrome/browser/ui/browser_view/browser_view_controller_helper.mm
index 950c6cd..c16cfde4 100644
--- a/ios/chrome/browser/ui/browser_view_controller_helper.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller_helper.mm
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/chrome/browser/ui/browser_view_controller_helper.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller_helper.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
diff --git a/ios/chrome/browser/ui/browser_view_controller_helper_unittest.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller_helper_unittest.mm
similarity index 97%
rename from ios/chrome/browser/ui/browser_view_controller_helper_unittest.mm
rename to ios/chrome/browser/ui/browser_view/browser_view_controller_helper_unittest.mm
index 3bff3bdd..9b30233 100644
--- a/ios/chrome/browser/ui/browser_view_controller_helper_unittest.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller_helper_unittest.mm
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/chrome/browser/ui/browser_view_controller_helper.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller_helper.h"
 
 #import <Foundation/Foundation.h>
 
diff --git a/ios/chrome/browser/ui/browser_view_controller_unittest.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm
similarity index 95%
rename from ios/chrome/browser/ui/browser_view_controller_unittest.mm
rename to ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm
index e2a89686..78f288d 100644
--- a/ios/chrome/browser/ui/browser_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/chrome/browser/ui/browser_view_controller.h"
-#import "ios/chrome/browser/ui/browser_view_controller+private.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller+private.h"
 
 #import <Foundation/Foundation.h>
 #import <PassKit/PassKit.h>
@@ -36,8 +36,8 @@
 #import "ios/chrome/browser/ui/activity_services/share_to_data.h"
 #import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
 #import "ios/chrome/browser/ui/browser_container/browser_container_view_controller.h"
-#import "ios/chrome/browser/ui/browser_view_controller_dependency_factory.h"
-#import "ios/chrome/browser/ui/browser_view_controller_helper.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller_dependency_factory.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller_helper.h"
 #import "ios/chrome/browser/ui/commands/application_commands.h"
 #import "ios/chrome/browser/ui/commands/browser_commands.h"
 #import "ios/chrome/browser/ui/commands/command_dispatcher.h"
@@ -90,8 +90,8 @@
 @class ToolbarButtonUpdater;
 
 // Private methods in BrowserViewController to test.
-@interface BrowserViewController (
-    Testing)<CRWNativeContentProvider, TabModelObserver>
+@interface BrowserViewController (Testing) <CRWNativeContentProvider,
+                                            TabModelObserver>
 - (void)pageLoadStarted:(NSNotification*)notification;
 - (void)pageLoadComplete:(NSNotification*)notification;
 - (void)tabSelected:(Tab*)tab notifyToolbar:(BOOL)notifyToolbar;
@@ -359,10 +359,11 @@
 
 TEST_F(BrowserViewControllerTest, TestClearPresentedState) {
   EXPECT_CALL(*this, OnCompletionCalled());
-  [bvc_ clearPresentedStateWithCompletion:^{
-    this->OnCompletionCalled();
-  }
-                           dismissOmnibox:YES];
+  [bvc_
+      clearPresentedStateWithCompletion:^{
+        this->OnCompletionCalled();
+      }
+                         dismissOmnibox:YES];
 }
 
 }  // namespace
diff --git a/ios/chrome/browser/ui/download/BUILD.gn b/ios/chrome/browser/ui/download/BUILD.gn
index 41da94e..fbe9659 100644
--- a/ios/chrome/browser/ui/download/BUILD.gn
+++ b/ios/chrome/browser/ui/download/BUILD.gn
@@ -122,6 +122,7 @@
     "//ios/chrome/browser/download:features",
     "//ios/chrome/browser/download:test_support",
     "//ios/chrome/browser/ui:ui_internal",
+    "//ios/chrome/browser/ui/browser_view",
     "//ios/chrome/browser/ui/util",
     "//ios/chrome/test:test_support",
     "//ios/chrome/test/app:test_support",
diff --git a/ios/chrome/browser/ui/download/ar_quick_look_egtest.mm b/ios/chrome/browser/ui/download/ar_quick_look_egtest.mm
index eff9586..b5dda116 100644
--- a/ios/chrome/browser/ui/download/ar_quick_look_egtest.mm
+++ b/ios/chrome/browser/ui/download/ar_quick_look_egtest.mm
@@ -13,7 +13,7 @@
 #include "ios/chrome/browser/download/download_test_util.h"
 #include "ios/chrome/browser/download/features.h"
 #include "ios/chrome/browser/download/usdz_mime_type.h"
-#import "ios/chrome/browser/ui/browser_view_controller.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller.h"
 #include "ios/chrome/browser/ui/util/ui_util.h"
 #import "ios/chrome/test/app/chrome_test_util.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
diff --git a/ios/chrome/browser/ui/download/pass_kit_egtest.mm b/ios/chrome/browser/ui/download/pass_kit_egtest.mm
index 26cabb0..7f78a910 100644
--- a/ios/chrome/browser/ui/download/pass_kit_egtest.mm
+++ b/ios/chrome/browser/ui/download/pass_kit_egtest.mm
@@ -12,7 +12,7 @@
 #import "ios/chrome/app/main_controller.h"
 #include "ios/chrome/browser/download/download_test_util.h"
 #include "ios/chrome/browser/download/pass_kit_mime_type.h"
-#import "ios/chrome/browser/ui/browser_view_controller.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller.h"
 #include "ios/chrome/browser/ui/util/ui_util.h"
 #include "ios/chrome/grit/ios_strings.h"
 #import "ios/chrome/test/app/chrome_test_util.h"
diff --git a/ios/chrome/browser/ui/infobars/modals/infobar_password_table_view_controller.mm b/ios/chrome/browser/ui/infobars/modals/infobar_password_table_view_controller.mm
index 2065d7b..85c2e55 100644
--- a/ios/chrome/browser/ui/infobars/modals/infobar_password_table_view_controller.mm
+++ b/ios/chrome/browser/ui/infobars/modals/infobar_password_table_view_controller.mm
@@ -7,8 +7,8 @@
 #include "base/logging.h"
 #include "base/mac/foundation_util.h"
 #import "ios/chrome/browser/ui/infobars/modals/infobar_modal_delegate.h"
-#import "ios/chrome/browser/ui/table_view/cells/settings_detail_item.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h"
+#import "ios/chrome/browser/ui/table_view/cells/table_view_detail_icon_item.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_text_button_item.h"
 #import "ios/chrome/browser/ui/table_view/chrome_table_view_styler.h"
 #include "ios/chrome/grit/ios_strings.h"
@@ -69,23 +69,23 @@
   TableViewModel* model = self.tableViewModel;
   [model addSectionWithIdentifier:SectionIdentifierContent];
 
-  SettingsDetailItem* URLDetailItem =
-      [[SettingsDetailItem alloc] initWithType:ItemTypeURL];
+  TableViewDetailIconItem* URLDetailItem =
+      [[TableViewDetailIconItem alloc] initWithType:ItemTypeURL];
   URLDetailItem.text = l10n_util::GetNSString(IDS_IOS_SHOW_PASSWORD_VIEW_SITE);
   URLDetailItem.detailText = self.URL;
   [model addItem:URLDetailItem
       toSectionWithIdentifier:SectionIdentifierContent];
 
-  SettingsDetailItem* usernameDetailItem =
-      [[SettingsDetailItem alloc] initWithType:ItemTypeUsername];
+  TableViewDetailIconItem* usernameDetailItem =
+      [[TableViewDetailIconItem alloc] initWithType:ItemTypeUsername];
   usernameDetailItem.text =
       l10n_util::GetNSString(IDS_IOS_SHOW_PASSWORD_VIEW_USERNAME);
   usernameDetailItem.detailText = self.username;
   [model addItem:usernameDetailItem
       toSectionWithIdentifier:SectionIdentifierContent];
 
-  SettingsDetailItem* passwordDetailItem =
-      [[SettingsDetailItem alloc] initWithType:ItemTypePassword];
+  TableViewDetailIconItem* passwordDetailItem =
+      [[TableViewDetailIconItem alloc] initWithType:ItemTypePassword];
   passwordDetailItem.text =
       l10n_util::GetNSString(IDS_IOS_SHOW_PASSWORD_VIEW_PASSWORD);
   // TODO(crbug.com/927064): Set the number of dots depending on Password
diff --git a/ios/chrome/browser/ui/keyboard_commands_egtest.mm b/ios/chrome/browser/ui/keyboard_commands_egtest.mm
index 4452681..faea8e8 100644
--- a/ios/chrome/browser/ui/keyboard_commands_egtest.mm
+++ b/ios/chrome/browser/ui/keyboard_commands_egtest.mm
@@ -8,7 +8,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "components/strings/grit/components_strings.h"
 #import "ios/chrome/browser/ui/bookmarks/bookmark_ui_constants.h"
-#import "ios/chrome/browser/ui/browser_view_controller.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller.h"
 #import "ios/chrome/browser/ui/ntp/new_tab_page_controller.h"
 #import "ios/chrome/browser/ui/popup_menu/popup_menu_constants.h"
 #import "ios/chrome/browser/ui/table_view/table_view_navigation_controller_constants.h"
diff --git a/ios/chrome/browser/ui/main/BUILD.gn b/ios/chrome/browser/ui/main/BUILD.gn
index c1838cc..6a21e1d 100644
--- a/ios/chrome/browser/ui/main/BUILD.gn
+++ b/ios/chrome/browser/ui/main/BUILD.gn
@@ -39,6 +39,7 @@
     "//ios/chrome/browser/ui/app_launcher",
     "//ios/chrome/browser/ui/autofill",
     "//ios/chrome/browser/ui/browser_container",
+    "//ios/chrome/browser/ui/browser_view",
     "//ios/chrome/browser/ui/commands",
     "//ios/chrome/browser/ui/coordinators:chrome_coordinators",
     "//ios/chrome/browser/ui/download",
@@ -93,6 +94,7 @@
     "//ios/chrome/browser/browser_state:test_support",
     "//ios/chrome/browser/tabs",
     "//ios/chrome/browser/ui:ui_internal",
+    "//ios/chrome/browser/ui/browser_view",
     "//ios/chrome/test:block_cleanup_test",
     "//ios/web/public/test",
     "//testing/gtest",
diff --git a/ios/chrome/browser/ui/main/browser_coordinator.mm b/ios/chrome/browser/ui/main/browser_coordinator.mm
index f8f23fa..9c843eb 100644
--- a/ios/chrome/browser/ui/main/browser_coordinator.mm
+++ b/ios/chrome/browser/ui/main/browser_coordinator.mm
@@ -24,9 +24,9 @@
 #import "ios/chrome/browser/ui/app_launcher/app_launcher_coordinator.h"
 #import "ios/chrome/browser/ui/autofill/form_input_accessory_coordinator.h"
 #import "ios/chrome/browser/ui/browser_container/browser_container_coordinator.h"
-#import "ios/chrome/browser/ui/browser_view_controller+private.h"
-#import "ios/chrome/browser/ui/browser_view_controller.h"
-#import "ios/chrome/browser/ui/browser_view_controller_dependency_factory.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller+private.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller_dependency_factory.h"
 #import "ios/chrome/browser/ui/commands/application_commands.h"
 #import "ios/chrome/browser/ui/commands/browser_coordinator_commands.h"
 #import "ios/chrome/browser/ui/commands/command_dispatcher.h"
diff --git a/ios/chrome/browser/ui/main/browser_view_wrangler.mm b/ios/chrome/browser/ui/main/browser_view_wrangler.mm
index 342591c7..8d0f6a7 100644
--- a/ios/chrome/browser/ui/main/browser_view_wrangler.mm
+++ b/ios/chrome/browser/ui/main/browser_view_wrangler.mm
@@ -17,8 +17,8 @@
 #import "ios/chrome/browser/tabs/tab.h"
 #import "ios/chrome/browser/tabs/tab_model.h"
 #import "ios/chrome/browser/tabs/tab_model_observer.h"
-#import "ios/chrome/browser/ui/browser_view_controller.h"
-#import "ios/chrome/browser/ui/browser_view_controller_dependency_factory.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller_dependency_factory.h"
 #import "ios/chrome/browser/ui/main/browser_coordinator.h"
 #import "ios/chrome/browser/url_loading/app_url_loading_service.h"
 #include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
diff --git a/ios/chrome/browser/ui/main/browser_view_wrangler_unittest.mm b/ios/chrome/browser/ui/main/browser_view_wrangler_unittest.mm
index 341aa32c..23213487 100644
--- a/ios/chrome/browser/ui/main/browser_view_wrangler_unittest.mm
+++ b/ios/chrome/browser/ui/main/browser_view_wrangler_unittest.mm
@@ -8,7 +8,7 @@
 
 #include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
 #import "ios/chrome/browser/tabs/tab_model.h"
-#import "ios/chrome/browser/ui/browser_view_controller.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller.h"
 #include "ios/web/public/test/test_web_thread_bundle.h"
 #include "testing/platform_test.h"
 
diff --git a/ios/chrome/browser/ui/ntp/BUILD.gn b/ios/chrome/browser/ui/ntp/BUILD.gn
index a5479f1..4e076e1 100644
--- a/ios/chrome/browser/ui/ntp/BUILD.gn
+++ b/ios/chrome/browser/ui/ntp/BUILD.gn
@@ -258,6 +258,7 @@
     "//ios/chrome/browser/tabs",
     "//ios/chrome/browser/test:perf_test_support",
     "//ios/chrome/browser/ui:ui_internal",
+    "//ios/chrome/browser/ui/browser_view",
     "//ios/chrome/browser/ui/commands",
   ]
 }
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_perftest.mm b/ios/chrome/browser/ui/ntp/new_tab_page_perftest.mm
index 7f92e03..ae6c1819 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_perftest.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_perftest.mm
@@ -6,8 +6,8 @@
 #include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h"
 #import "ios/chrome/browser/tabs/tab_model.h"
 #include "ios/chrome/browser/test/perf_test_with_bvc_ios.h"
-#import "ios/chrome/browser/ui/browser_view_controller.h"
-#import "ios/chrome/browser/ui/browser_view_controller_dependency_factory.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller_dependency_factory.h"
 #import "ios/chrome/browser/ui/commands/application_commands.h"
 #import "ios/chrome/browser/ui/commands/browser_commands.h"
 #import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
diff --git a/ios/chrome/browser/ui/open_in/BUILD.gn b/ios/chrome/browser/ui/open_in/BUILD.gn
new file mode 100644
index 0000000..5267ee9
--- /dev/null
+++ b/ios/chrome/browser/ui/open_in/BUILD.gn
@@ -0,0 +1,51 @@
+# 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.
+
+source_set("open_in") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  sources = [
+    "open_in_controller.h",
+    "open_in_controller.mm",
+    "open_in_controller_testing.h",
+    "open_in_toolbar.h",
+    "open_in_toolbar.mm",
+  ]
+  deps = [
+    "//base",
+    "//components/strings",
+    "//ios/chrome/app/strings",
+    "//ios/chrome/browser/ui/alert_coordinator",
+    "//ios/chrome/browser/ui/colors",
+    "//ios/chrome/browser/ui/util",
+    "//ios/chrome/common/ui_util",
+    "//ios/chrome/common/ui_util",
+    "//ios/third_party/material_components_ios",
+    "//ios/web/public",
+    "//net",
+    "//services/network/public/cpp",
+    "//ui/base",
+    "//ui/gfx",
+    "//url",
+  ]
+}
+
+source_set("unit_tests") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  testonly = true
+  sources = [
+    "open_in_controller_unittest.mm",
+    "open_in_toolbar_unittest.mm",
+  ]
+  deps = [
+    ":open_in",
+    "//ios/web/public/test",
+    "//ios/web/public/test/fakes",
+    "//net",
+    "//net:test_support",
+    "//services/network:test_support",
+    "//services/network/public/cpp",
+    "//testing/gtest",
+    "//third_party/ocmock",
+  ]
+}
diff --git a/ios/chrome/browser/ui/open_in_controller.h b/ios/chrome/browser/ui/open_in/open_in_controller.h
similarity index 69%
rename from ios/chrome/browser/ui/open_in_controller.h
rename to ios/chrome/browser/ui/open_in/open_in_controller.h
index 5981a3dd..737a7e97 100644
--- a/ios/chrome/browser/ui/open_in_controller.h
+++ b/ios/chrome/browser/ui/open_in/open_in_controller.h
@@ -2,21 +2,25 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef IOS_CHROME_BROWSER_UI_OPEN_IN_CONTROLLER_H_
-#define IOS_CHROME_BROWSER_UI_OPEN_IN_CONTROLLER_H_
+#ifndef IOS_CHROME_BROWSER_UI_OPEN_IN_OPEN_IN_CONTROLLER_H_
+#define IOS_CHROME_BROWSER_UI_OPEN_IN_OPEN_IN_CONTROLLER_H_
 
 #import <UIKit/UIKit.h>
 
 #include <memory>
 
 #include "base/memory/ref_counted.h"
-#import "ios/chrome/browser/ui/open_in_toolbar.h"
+#import "ios/chrome/browser/ui/open_in/open_in_toolbar.h"
 #include "url/gurl.h"
 
 namespace network {
 class SharedURLLoaderFactory;
 }
 
+namespace web {
+class WebState;
+}
+
 // Enum for the IOS.OpenIn.DownloadResult UMA histogram to log the result of
 // the file download initiated when the user tap on "open in" button.
 // These values are persisted to logs. Entries should not be renumbered and
@@ -28,15 +32,13 @@
   kMaxValue = kFailed,
 };
 
-@class CRWWebController;
-
 // Class used to handle opening files in other applications.
-@interface OpenInController : NSObject<UIGestureRecognizerDelegate,
-                                       UIDocumentInteractionControllerDelegate>
+@interface OpenInController : NSObject <UIGestureRecognizerDelegate,
+                                        UIDocumentInteractionControllerDelegate>
 // Designated initializer.
 - (id)initWithURLLoaderFactory:
           (scoped_refptr<network::SharedURLLoaderFactory>)urlLoaderFactory
-                 webController:(CRWWebController*)webController;
+                      webState:(web::WebState*)webState;
 
 // Base view on which the Open In toolbar will be presented.
 @property(nonatomic, weak) UIView* baseView;
@@ -45,9 +47,9 @@
 // variables specific to the loaded document.
 - (void)disable;
 
-// Disconnects the controller from its CRWWebController. Should be called when
-// the CRWWebController is being torn down.
-- (void)detachFromWebController;
+// Disconnects the controller from its WebState. Should be called when the
+// WebState is being torn down.
+- (void)detachFromWebState;
 
 // Adds the |openInToolbar_| to the |webController_|'s view and sets the url and
 // the filename for the currently loaded document.
@@ -55,4 +57,4 @@
             suggestedFilename:(NSString*)suggestedFilename;
 @end
 
-#endif  // IOS_CHROME_BROWSER_UI_OPEN_IN_CONTROLLER_H_
+#endif  // IOS_CHROME_BROWSER_UI_OPEN_IN_OPEN_IN_CONTROLLER_H_
diff --git a/ios/chrome/browser/ui/open_in_controller.mm b/ios/chrome/browser/ui/open_in/open_in_controller.mm
similarity index 94%
rename from ios/chrome/browser/ui/open_in_controller.mm
rename to ios/chrome/browser/ui/open_in/open_in_controller.mm
index b189fd9..a6b38f1 100644
--- a/ios/chrome/browser/ui/open_in_controller.mm
+++ b/ios/chrome/browser/ui/open_in/open_in_controller.mm
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/chrome/browser/ui/open_in_controller.h"
+#import "ios/chrome/browser/ui/open_in/open_in_controller.h"
 
 #include "base/bind.h"
 #include "base/files/file_path.h"
@@ -16,15 +16,15 @@
 #include "base/threading/scoped_blocking_call.h"
 #include "components/strings/grit/components_strings.h"
 #import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
-#import "ios/chrome/browser/ui/open_in_controller_testing.h"
+#import "ios/chrome/browser/ui/open_in/open_in_controller_testing.h"
 #include "ios/chrome/browser/ui/util/ui_util.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
 #import "ios/chrome/common/ui_util/constraints_ui_util.h"
 #include "ios/chrome/grit/ios_strings.h"
 #import "ios/web/public/web_state/ui/crw_web_view_proxy.h"
 #import "ios/web/public/web_state/ui/crw_web_view_scroll_view_proxy.h"
+#import "ios/web/public/web_state/web_state.h"
 #include "ios/web/public/web_thread.h"
-#import "ios/web/web_state/ui/crw_web_controller.h"
 #include "net/base/load_flags.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/public/cpp/simple_url_loader.h"
@@ -79,7 +79,7 @@
 
 }  // anonymous namespace
 
-@interface OpenInController ()<CRWWebViewScrollViewProxyObserver> {
+@interface OpenInController () <CRWWebViewScrollViewProxyObserver> {
   // AlertCoordinator for showing an alert if no applications were found to open
   // the current document.
   AlertCoordinator* _alertCoordinator;
@@ -102,7 +102,7 @@
 - (void)showOpenInToolbarWithTimer:(BOOL)withTimer;
 // Hides the overlayed toolbar |openInToolbar_|.
 - (void)hideOpenInToolbar;
-// Called when there is a tap on the |webController_|'s view to display the
+// Called when there is a tap on the |webState_|'s view to display the
 // overlayed toolbar |openInToolbar_| if necessary and (re)schedule the
 // |openInTimer_|.
 - (void)handleTapFrom:(UIGestureRecognizer*)gestureRecognizer;
@@ -194,9 +194,9 @@
   // Loader used to redownload the document and save it in the sandbox.
   std::unique_ptr<network::SimpleURLLoader> urlLoader_;
 
-  // CRWWebController used to check if the tap is not on a link and the
+  // WebState used to check if the tap is not on a link and the
   // |openInToolbar_| should be displayed.
-  CRWWebController* webController_;
+  web::WebState* webState_;
 
   // URLLoaderFactory instance needed for URLLoader.
   scoped_refptr<network::SharedURLLoaderFactory> urlLoaderFactory_;
@@ -225,11 +225,11 @@
 
 - (id)initWithURLLoaderFactory:
           (scoped_refptr<network::SharedURLLoaderFactory>)urlLoaderFactory
-                 webController:(CRWWebController*)webController {
+                      webState:(web::WebState*)webState {
   self = [super init];
   if (self) {
     urlLoaderFactory_ = std::move(urlLoaderFactory);
-    webController_ = webController;
+    webState_ = webState;
     tapRecognizer_ = [[UITapGestureRecognizer alloc]
         initWithTarget:self
                 action:@selector(handleTapFrom:)];
@@ -246,10 +246,11 @@
             suggestedFilename:(NSString*)suggestedFilename {
   documentURL_ = GURL(documentURL);
   suggestedFilename_ = suggestedFilename;
-  [webController_ addGestureRecognizerToWebView:tapRecognizer_];
+  [self.baseView addGestureRecognizer:tapRecognizer_];
   [self openInToolbar].alpha = 0.0f;
   [self.baseView addSubview:[self openInToolbar]];
-  [webController_.webViewProxy.scrollViewProxy addObserver:self];
+  if (webState_)
+    [[webState_->GetWebViewProxy() scrollViewProxy] addObserver:self];
 
   [self showOpenInToolbarWithTimer:NO];
 }
@@ -260,8 +261,9 @@
   if (bridge_.get())
     bridge_->OnOwnerDisabled();
   bridge_ = nil;
-  [webController_ removeGestureRecognizerFromWebView:tapRecognizer_];
-  [webController_.webViewProxy.scrollViewProxy removeObserver:self];
+  [self.baseView removeGestureRecognizer:tapRecognizer_];
+  if (webState_)
+    [[webState_->GetWebViewProxy() scrollViewProxy] removeObserver:self];
   self.previousScrollViewOffset = 0;
   [[self openInToolbar] removeFromSuperview];
   [documentController_ dismissMenuAnimated:NO];
@@ -271,11 +273,11 @@
   urlLoader_.reset();
 }
 
-- (void)detachFromWebController {
+- (void)detachFromWebState {
   [self disable];
   // Animation blocks may be keeping this object alive; don't extend the
-  // lifetime of CRWWebController.
-  webController_ = nil;
+  // lifetime of WebState.
+  webState_ = nullptr;
 }
 
 - (void)dealloc {
@@ -335,11 +337,11 @@
 - (void)exportFileWithOpenInMenuAnchoredAt:(UIView*)view {
   DCHECK([view isKindOfClass:[UIView class]]);
   DCHECK_CURRENTLY_ON(web::WebThread::UI);
-  if (!webController_)
+  if (!webState_)
     return;
 
   anchorLocation_ = [[self openInToolbar] convertRect:view.frame
-                                               toView:[webController_ view]];
+                                               toView:self.baseView];
   [openInTimer_ invalidate];
   if (!bridge_.get())
     bridge_ = new OpenInControllerBridge(self);
@@ -432,7 +434,7 @@
 }
 
 - (void)presentOpenInMenuForFileAtURL:(NSURL*)fileURL {
-  if (!webController_)
+  if (!webState_)
     return;
 
   if (!documentController_) {
@@ -446,10 +448,9 @@
   // support for other file types as well.
   [documentController_ setUTI:@"com.adobe.pdf"];
   [documentController_ setDelegate:self];
-  BOOL success =
-      [documentController_ presentOpenInMenuFromRect:anchorLocation_
-                                              inView:[webController_ view]
-                                            animated:YES];
+  BOOL success = [documentController_ presentOpenInMenuFromRect:anchorLocation_
+                                                         inView:self.baseView
+                                                       animated:YES];
   [self removeOverlayedView];
   if (!success) {
     if (IsIPadIdiom())
@@ -551,8 +552,8 @@
                                                 base::BlockingType::WILL_BLOCK);
   NSFileManager* fileManager = [NSFileManager defaultManager];
   NSError* error = nil;
-  NSArray* documentFiles =
-      [fileManager contentsOfDirectoryAtPath:tempDirPath error:&error];
+  NSArray* documentFiles = [fileManager contentsOfDirectoryAtPath:tempDirPath
+                                                            error:&error];
   if (!documentFiles) {
     DLOG(ERROR) << "Failed to get content of directory at path: "
                 << base::SysNSStringToUTF8([error description]);
diff --git a/ios/chrome/browser/ui/open_in_controller_testing.h b/ios/chrome/browser/ui/open_in/open_in_controller_testing.h
similarity index 63%
rename from ios/chrome/browser/ui/open_in_controller_testing.h
rename to ios/chrome/browser/ui/open_in/open_in_controller_testing.h
index c20a36d..588bac58 100644
--- a/ios/chrome/browser/ui/open_in_controller_testing.h
+++ b/ios/chrome/browser/ui/open_in/open_in_controller_testing.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef IOS_CHROME_BROWSER_UI_OPEN_IN_CONTROLLER_TESTING_H_
-#define IOS_CHROME_BROWSER_UI_OPEN_IN_CONTROLLER_TESTING_H_
+#ifndef IOS_CHROME_BROWSER_UI_OPEN_IN_OPEN_IN_CONTROLLER_TESTING_H_
+#define IOS_CHROME_BROWSER_UI_OPEN_IN_OPEN_IN_CONTROLLER_TESTING_H_
 
 @interface OpenInController (TestingAditions)
 - (NSString*)suggestedFilename;
@@ -12,4 +12,4 @@
     (UIDocumentInteractionController*)controller;
 @end
 
-#endif  // IOS_CHROME_BROWSER_UI_OPEN_IN_CONTROLLER_TESTING_H_
+#endif  // IOS_CHROME_BROWSER_UI_OPEN_IN_OPEN_IN_CONTROLLER_TESTING_H_
diff --git a/ios/chrome/browser/ui/open_in_controller_unittest.mm b/ios/chrome/browser/ui/open_in/open_in_controller_unittest.mm
similarity index 93%
rename from ios/chrome/browser/ui/open_in_controller_unittest.mm
rename to ios/chrome/browser/ui/open_in/open_in_controller_unittest.mm
index 703b6c5..09161c0 100644
--- a/ios/chrome/browser/ui/open_in_controller_unittest.mm
+++ b/ios/chrome/browser/ui/open_in/open_in_controller_unittest.mm
@@ -10,11 +10,10 @@
 #include "base/strings/sys_string_conversions.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_task_environment.h"
-#import "ios/chrome/browser/tabs/tab.h"
-#import "ios/chrome/browser/ui/open_in_controller.h"
-#import "ios/chrome/browser/ui/open_in_controller_testing.h"
+#import "ios/chrome/browser/ui/open_in/open_in_controller.h"
+#import "ios/chrome/browser/ui/open_in/open_in_controller_testing.h"
+#import "ios/web/public/test/fakes/test_web_state.h"
 #include "ios/web/public/test/test_web_thread.h"
-#import "ios/web/web_state/ui/crw_web_controller.h"
 #include "net/url_request/test_url_fetcher_factory.h"
 #include "net/url_request/url_fetcher_delegate.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
@@ -38,6 +37,8 @@
             base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
                 &test_url_loader_factory_)) {}
 
+  ~OpenInControllerTest() override { [open_in_controller_ detachFromWebState]; }
+
   void SetUp() override {
     PlatformTest::SetUp();
 
@@ -54,10 +55,9 @@
 
     GURL documentURL = GURL("http://www.test.com/doc.pdf");
     parent_view_ = [[UIView alloc] init];
-    id webController = [OCMockObject niceMockForClass:[CRWWebController class]];
     open_in_controller_ = [[OpenInController alloc]
         initWithURLLoaderFactory:test_shared_url_loader_factory_
-                   webController:webController];
+                        webState:&web_state_];
     [open_in_controller_ enableWithDocumentURL:documentURL
                              suggestedFilename:@"doc.pdf"];
   }
@@ -82,6 +82,7 @@
   OpenInController* open_in_controller_;
   UIView* parent_view_;
   base::HistogramTester histogram_tester_;
+  web::TestWebState web_state_;
 };
 
 TEST_F(OpenInControllerTest, TestDisplayOpenInMenu) {
diff --git a/ios/chrome/browser/ui/open_in_toolbar.h b/ios/chrome/browser/ui/open_in/open_in_toolbar.h
similarity index 70%
rename from ios/chrome/browser/ui/open_in_toolbar.h
rename to ios/chrome/browser/ui/open_in/open_in_toolbar.h
index 4e017b44..2402b071 100644
--- a/ios/chrome/browser/ui/open_in_toolbar.h
+++ b/ios/chrome/browser/ui/open_in/open_in_toolbar.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef IOS_CHROME_BROWSER_UI_OPEN_IN_TOOLBAR_H_
-#define IOS_CHROME_BROWSER_UI_OPEN_IN_TOOLBAR_H_
+#ifndef IOS_CHROME_BROWSER_UI_OPEN_IN_OPEN_IN_TOOLBAR_H_
+#define IOS_CHROME_BROWSER_UI_OPEN_IN_OPEN_IN_TOOLBAR_H_
 
 #import <UIKit/UIKit.h>
 
@@ -16,4 +16,4 @@
 
 @end
 
-#endif  // IOS_CHROME_BROWSER_UI_OPEN_IN_TOOLBAR_H_
+#endif  // IOS_CHROME_BROWSER_UI_OPEN_IN_OPEN_IN_TOOLBAR_H_
diff --git a/ios/chrome/browser/ui/open_in_toolbar.mm b/ios/chrome/browser/ui/open_in/open_in_toolbar.mm
similarity index 98%
rename from ios/chrome/browser/ui/open_in_toolbar.mm
rename to ios/chrome/browser/ui/open_in/open_in_toolbar.mm
index fd3213a3..e16e9a23 100644
--- a/ios/chrome/browser/ui/open_in_toolbar.mm
+++ b/ios/chrome/browser/ui/open_in/open_in_toolbar.mm
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/chrome/browser/ui/open_in_toolbar.h"
+#import "ios/chrome/browser/ui/open_in/open_in_toolbar.h"
 
 #include <cmath>
 
diff --git a/ios/chrome/browser/ui/open_in_toolbar_unittest.mm b/ios/chrome/browser/ui/open_in/open_in_toolbar_unittest.mm
similarity index 96%
rename from ios/chrome/browser/ui/open_in_toolbar_unittest.mm
rename to ios/chrome/browser/ui/open_in/open_in_toolbar_unittest.mm
index 9eed7f5f..bf56db2 100644
--- a/ios/chrome/browser/ui/open_in_toolbar_unittest.mm
+++ b/ios/chrome/browser/ui/open_in/open_in_toolbar_unittest.mm
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#import "ios/chrome/browser/ui/open_in/open_in_toolbar.h"
 #include "base/logging.h"
-#import "ios/chrome/browser/ui/open_in_toolbar.h"
 #include "testing/platform_test.h"
 #import "third_party/ocmock/OCMock/OCMock.h"
 
diff --git a/ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.mm b/ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.mm
index 02267bd..219fa83 100644
--- a/ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.mm
+++ b/ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.mm
@@ -11,7 +11,6 @@
 
 #include "base/logging.h"
 #include "base/metrics/histogram_macros.h"
-#import "ios/chrome/browser/ui/browser_view_controller.h"
 #import "ios/chrome/browser/ui/fullscreen/fullscreen_controller.h"
 #import "ios/chrome/browser/ui/fullscreen/fullscreen_controller_factory.h"
 #import "ios/chrome/browser/ui/fullscreen/fullscreen_features.h"
diff --git a/ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller_egtest.mm b/ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller_egtest.mm
index 036edb3f..3236b90 100644
--- a/ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller_egtest.mm
+++ b/ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller_egtest.mm
@@ -12,7 +12,6 @@
 #include "components/strings/grit/components_strings.h"
 #include "components/version_info/version_info.h"
 #import "ios/chrome/app/main_controller.h"
-#import "ios/chrome/browser/ui/browser_view_controller.h"
 #include "ios/chrome/browser/ui/icons/chrome_icon.h"
 #import "ios/chrome/browser/ui/location_bar/location_bar_coordinator.h"
 #import "ios/chrome/browser/ui/location_bar/location_bar_url_loader.h"
diff --git a/ios/chrome/browser/ui/settings/bandwidth_management_table_view_controller.mm b/ios/chrome/browser/ui/settings/bandwidth_management_table_view_controller.mm
index 81e194d..d955cfd3 100644
--- a/ios/chrome/browser/ui/settings/bandwidth_management_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/bandwidth_management_table_view_controller.mm
@@ -12,7 +12,7 @@
 #include "ios/chrome/browser/pref_names.h"
 #import "ios/chrome/browser/ui/settings/dataplan_usage_table_view_controller.h"
 #import "ios/chrome/browser/ui/settings/utils/settings_utils.h"
-#import "ios/chrome/browser/ui/table_view/cells/settings_detail_item.h"
+#import "ios/chrome/browser/ui/table_view/cells/table_view_detail_icon_item.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_link_header_footer_item.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_text_header_footer_item.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_text_item.h"
@@ -51,7 +51,7 @@
   PrefChangeRegistrar _prefChangeRegistrarApplicationContext;
 
   // Updatable Items
-  SettingsDetailItem* _preloadWebpagesDetailItem;
+  TableViewDetailIconItem* _preloadWebpagesDetailItem;
 }
 
 @end
@@ -156,14 +156,15 @@
 
 #pragma mark - Private
 
-// Returns a newly created SettingsDetailItem for the preload webpages menu.
-- (SettingsDetailItem*)preloadWebpagesItem {
+// Returns a newly created TableViewDetailIconItem for the preload webpages
+// menu.
+- (TableViewDetailIconItem*)preloadWebpagesItem {
   NSString* detailText = [DataplanUsageTableViewController
       currentLabelForPreference:_browserState->GetPrefs()
                        basePref:prefs::kNetworkPredictionEnabled
                        wifiPref:prefs::kNetworkPredictionWifiOnly];
   _preloadWebpagesDetailItem =
-      [[SettingsDetailItem alloc] initWithType:ItemTypePreload];
+      [[TableViewDetailIconItem alloc] initWithType:ItemTypePreload];
 
   _preloadWebpagesDetailItem.text =
       l10n_util::GetNSString(IDS_IOS_OPTIONS_PRELOAD_WEBPAGES);
diff --git a/ios/chrome/browser/ui/settings/content_settings_table_view_controller.mm b/ios/chrome/browser/ui/settings/content_settings_table_view_controller.mm
index 5619d88..808e73c 100644
--- a/ios/chrome/browser/ui/settings/content_settings_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/content_settings_table_view_controller.mm
@@ -20,7 +20,7 @@
 #import "ios/chrome/browser/ui/settings/settings_navigation_controller.h"
 #import "ios/chrome/browser/ui/settings/translate_table_view_controller.h"
 #import "ios/chrome/browser/ui/settings/utils/content_setting_backed_boolean.h"
-#import "ios/chrome/browser/ui/table_view/cells/settings_detail_item.h"
+#import "ios/chrome/browser/ui/table_view/cells/table_view_detail_icon_item.h"
 #include "ios/chrome/browser/ui/ui_feature_flags.h"
 #include "ios/chrome/grit/ios_strings.h"
 #include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
@@ -57,9 +57,9 @@
   ContentSettingBackedBoolean* _disablePopupsSetting;
 
   // Updatable Items
-  SettingsDetailItem* _blockPopupsDetailItem;
-  SettingsDetailItem* _translateDetailItem;
-  SettingsDetailItem* _composeEmailDetailItem;
+  TableViewDetailIconItem* _blockPopupsDetailItem;
+  TableViewDetailIconItem* _translateDetailItem;
+  TableViewDetailIconItem* _composeEmailDetailItem;
 }
 
 // Returns the value for the default setting with ID |settingID|.
@@ -137,8 +137,8 @@
 #pragma mark - ContentSettingsTableViewController
 
 - (TableViewItem*)blockPopupsItem {
-  _blockPopupsDetailItem =
-      [[SettingsDetailItem alloc] initWithType:ItemTypeSettingsBlockPopups];
+  _blockPopupsDetailItem = [[TableViewDetailIconItem alloc]
+      initWithType:ItemTypeSettingsBlockPopups];
   NSString* subtitle = [_disablePopupsSetting value]
                            ? l10n_util::GetNSString(IDS_IOS_SETTING_ON)
                            : l10n_util::GetNSString(IDS_IOS_SETTING_OFF);
@@ -152,7 +152,7 @@
 
 - (TableViewItem*)translateItem {
   _translateDetailItem =
-      [[SettingsDetailItem alloc] initWithType:ItemTypeSettingsTranslate];
+      [[TableViewDetailIconItem alloc] initWithType:ItemTypeSettingsTranslate];
   BOOL enabled =
       browserState_->GetPrefs()->GetBoolean(prefs::kOfferTranslateEnabled);
   NSString* subtitle = enabled ? l10n_util::GetNSString(IDS_IOS_SETTING_ON)
@@ -166,8 +166,8 @@
 }
 
 - (TableViewItem*)composeEmailItem {
-  _composeEmailDetailItem =
-      [[SettingsDetailItem alloc] initWithType:ItemTypeSettingsComposeEmail];
+  _composeEmailDetailItem = [[TableViewDetailIconItem alloc]
+      initWithType:ItemTypeSettingsComposeEmail];
   // Use the handler's preferred title string for the compose email item.
   MailtoHandlerProvider* provider =
       ios::GetChromeBrowserProvider()->GetMailtoHandlerProvider();
diff --git a/ios/chrome/browser/ui/settings/privacy_table_view_controller.mm b/ios/chrome/browser/ui/settings/privacy_table_view_controller.mm
index 1b512c6..9bb1833 100644
--- a/ios/chrome/browser/ui/settings/privacy_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/privacy_table_view_controller.mm
@@ -36,7 +36,7 @@
 #import "ios/chrome/browser/ui/settings/settings_navigation_controller.h"
 #import "ios/chrome/browser/ui/settings/utils/pref_backed_boolean.h"
 #import "ios/chrome/browser/ui/settings/utils/settings_utils.h"
-#import "ios/chrome/browser/ui/table_view/cells/settings_detail_item.h"
+#import "ios/chrome/browser/ui/table_view/cells/table_view_detail_icon_item.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_detail_text_item.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_link_header_footer_item.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_text_header_footer_item.h"
@@ -91,8 +91,8 @@
   PrefChangeRegistrar _prefChangeRegistrarApplicationContext;
 
   // Updatable Items
-  SettingsDetailItem* _handoffDetailItem;
-  SettingsDetailItem* _sendUsageDetailItem;
+  TableViewDetailIconItem* _handoffDetailItem;
+  TableViewDetailIconItem* _sendUsageDetailItem;
   SettingsSwitchItem* _sendUsageToggleSwitchItem;
 }
 
@@ -299,11 +299,11 @@
   return _sendUsageToggleSwitchItem;
 }
 
-- (SettingsDetailItem*)detailItemWithType:(NSInteger)type
-                                  titleId:(NSInteger)titleId
-                               detailText:(NSString*)detailText {
-  SettingsDetailItem* detailItem =
-      [[SettingsDetailItem alloc] initWithType:type];
+- (TableViewDetailIconItem*)detailItemWithType:(NSInteger)type
+                                       titleId:(NSInteger)titleId
+                                    detailText:(NSString*)detailText {
+  TableViewDetailIconItem* detailItem =
+      [[TableViewDetailIconItem alloc] initWithType:type];
   detailItem.text = l10n_util::GetNSString(titleId);
   detailItem.detailText = detailText;
   detailItem.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
diff --git a/ios/chrome/browser/ui/settings/settings_table_view_controller.mm b/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
index 66a711a..99e69743 100644
--- a/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
@@ -59,8 +59,8 @@
 #import "ios/chrome/browser/ui/settings/voice_search_table_view_controller.h"
 #import "ios/chrome/browser/ui/signin_interaction/public/signin_presenter.h"
 #import "ios/chrome/browser/ui/signin_interaction/signin_interaction_coordinator.h"
-#import "ios/chrome/browser/ui/table_view/cells/settings_detail_item.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h"
+#import "ios/chrome/browser/ui/table_view/cells/table_view_detail_icon_item.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_image_item.h"
 #import "ios/chrome/browser/ui/table_view/table_view_model.h"
 #include "ios/chrome/browser/ui/ui_feature_flags.h"
@@ -245,11 +245,11 @@
   PrefChangeRegistrar _prefChangeRegistrar;
 
   // Updatable Items.
-  SettingsDetailItem* _voiceSearchDetailItem;
-  SettingsDetailItem* _defaultSearchEngineItem;
-  SettingsDetailItem* _passwordsDetailItem;
-  SettingsDetailItem* _autoFillProfileDetailItem;
-  SettingsDetailItem* _autoFillCreditCardDetailItem;
+  TableViewDetailIconItem* _voiceSearchDetailItem;
+  TableViewDetailIconItem* _defaultSearchEngineItem;
+  TableViewDetailIconItem* _passwordsDetailItem;
+  TableViewDetailIconItem* _autoFillProfileDetailItem;
+  TableViewDetailIconItem* _autoFillCreditCardDetailItem;
 
   // YES if the user used at least once the sign-in promo view buttons.
   BOOL _signinStarted;
@@ -675,14 +675,14 @@
                   withDefaultsKey:kDevViewSourceKey];
 }
 
-- (SettingsDetailItem*)collectionViewCatalogDetailItem {
+- (TableViewDetailIconItem*)collectionViewCatalogDetailItem {
   return [self detailItemWithType:ItemTypeCollectionCellCatalog
                              text:@"Collection Cell Catalog"
                        detailText:nil
                     iconImageName:kSettingsDebugImageName];
 }
 
-- (SettingsDetailItem*)tableViewCatalogDetailItem {
+- (TableViewDetailIconItem*)tableViewCatalogDetailItem {
   return [self detailItemWithType:ItemTypeTableCellCatalog
                              text:@"TableView Cell Catalog"
                        detailText:nil
@@ -703,12 +703,12 @@
 
 #pragma mark Item Constructors
 
-- (SettingsDetailItem*)detailItemWithType:(NSInteger)type
-                                     text:(NSString*)text
-                               detailText:(NSString*)detailText
-                            iconImageName:(NSString*)iconImageName {
-  SettingsDetailItem* detailItem =
-      [[SettingsDetailItem alloc] initWithType:type];
+- (TableViewDetailIconItem*)detailItemWithType:(NSInteger)type
+                                          text:(NSString*)text
+                                    detailText:(NSString*)detailText
+                                 iconImageName:(NSString*)iconImageName {
+  TableViewDetailIconItem* detailItem =
+      [[TableViewDetailIconItem alloc] initWithType:type];
   detailItem.text = text;
   detailItem.detailText = detailText;
   detailItem.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
@@ -742,9 +742,9 @@
                      cellForRowAtIndexPath:indexPath];
   NSInteger itemType = [self.tableViewModel itemTypeForIndexPath:indexPath];
 
-  if ([cell isKindOfClass:[SettingsDetailCell class]]) {
-    SettingsDetailCell* detailCell =
-        base::mac::ObjCCastStrict<SettingsDetailCell>(cell);
+  if ([cell isKindOfClass:[TableViewDetailIconCell class]]) {
+    TableViewDetailIconCell* detailCell =
+        base::mac::ObjCCastStrict<TableViewDetailIconCell>(cell);
     if (itemType == ItemTypePasswords) {
       scoped_refptr<password_manager::PasswordStore> passwordStore =
           IOSChromePasswordStoreFactory::GetForBrowserState(
diff --git a/ios/chrome/browser/ui/settings/sync/sync_settings_table_view_controller.mm b/ios/chrome/browser/ui/settings/sync/sync_settings_table_view_controller.mm
index c86d7b37..dff8c0d 100644
--- a/ios/chrome/browser/ui/settings/sync/sync_settings_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/sync/sync_settings_table_view_controller.mm
@@ -38,7 +38,6 @@
 #import "ios/chrome/browser/ui/settings/sync/sync_encryption_passphrase_table_view_controller.h"
 #import "ios/chrome/browser/ui/settings/sync/sync_encryption_table_view_controller.h"
 #import "ios/chrome/browser/ui/settings/sync/utils/sync_util.h"
-#import "ios/chrome/browser/ui/table_view/cells/settings_detail_item.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_image_item.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_text_header_footer_item.h"
diff --git a/ios/chrome/browser/ui/settings/table_cell_catalog_view_controller.mm b/ios/chrome/browser/ui/settings/table_cell_catalog_view_controller.mm
index 96be5bc7..7830dbe 100644
--- a/ios/chrome/browser/ui/settings/table_cell_catalog_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/table_cell_catalog_view_controller.mm
@@ -16,7 +16,7 @@
 #import "ios/chrome/browser/ui/settings/cells/settings_image_detail_text_item.h"
 #import "ios/chrome/browser/ui/settings/cells/settings_switch_item.h"
 #import "ios/chrome/browser/ui/settings/cells/sync_switch_item.h"
-#import "ios/chrome/browser/ui/table_view/cells/settings_detail_item.h"
+#import "ios/chrome/browser/ui/table_view/cells/table_view_detail_icon_item.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_detail_text_item.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_image_item.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_link_header_footer_item.h"
@@ -174,18 +174,18 @@
   [model addItem:noDetailTextItem
       toSectionWithIdentifier:SectionIdentifierText];
 
-  SettingsDetailItem* settingsDetailItem =
-      [[SettingsDetailItem alloc] initWithType:ItemTypeTextSettingsDetail];
-  settingsDetailItem.text = @"Settings cells";
-  settingsDetailItem.detailText = @"Short";
-  [model addItem:settingsDetailItem
-      toSectionWithIdentifier:SectionIdentifierText];
+  TableViewDetailIconItem* detailIconItem =
+      [[TableViewDetailIconItem alloc] initWithType:ItemTypeTextSettingsDetail];
+  detailIconItem.text = @"Detail Icon Item Cell";
+  detailIconItem.iconImageName = @"settings_article_suggestions";
+  detailIconItem.detailText = @"Short";
+  [model addItem:detailIconItem toSectionWithIdentifier:SectionIdentifierText];
 
-  SettingsDetailItem* settingsDetailItemLong =
-      [[SettingsDetailItem alloc] initWithType:ItemTypeTextSettingsDetail];
-  settingsDetailItemLong.text = @"Very long text eating the other detail label";
-  settingsDetailItemLong.detailText = @"A bit less short";
-  [model addItem:settingsDetailItemLong
+  TableViewDetailIconItem* detailIconItemLong =
+      [[TableViewDetailIconItem alloc] initWithType:ItemTypeTextSettingsDetail];
+  detailIconItemLong.text = @"Very long text eating the other detail label";
+  detailIconItemLong.detailText = @"A bit less short";
+  [model addItem:detailIconItemLong
       toSectionWithIdentifier:SectionIdentifierText];
 
   // SectionIdentifierSettings.
diff --git a/ios/chrome/browser/ui/table_view/cells/BUILD.gn b/ios/chrome/browser/ui/table_view/cells/BUILD.gn
index 6e16fed..a7f463a 100644
--- a/ios/chrome/browser/ui/table_view/cells/BUILD.gn
+++ b/ios/chrome/browser/ui/table_view/cells/BUILD.gn
@@ -4,14 +4,14 @@
 
 source_set("cells") {
   sources = [
-    "settings_detail_item.h",
-    "settings_detail_item.mm",
     "table_view_activity_indicator_header_footer_item.h",
     "table_view_activity_indicator_header_footer_item.mm",
     "table_view_cell.h",
     "table_view_cell.mm",
     "table_view_cells_constants.h",
     "table_view_cells_constants.mm",
+    "table_view_detail_icon_item.h",
+    "table_view_detail_icon_item.mm",
     "table_view_detail_text_item.h",
     "table_view_detail_text_item.mm",
     "table_view_disclosure_header_footer_item.h",
diff --git a/ios/chrome/browser/ui/table_view/cells/settings_detail_item.h b/ios/chrome/browser/ui/table_view/cells/table_view_detail_icon_item.h
similarity index 83%
rename from ios/chrome/browser/ui/table_view/cells/settings_detail_item.h
rename to ios/chrome/browser/ui/table_view/cells/table_view_detail_icon_item.h
index 233957e..0ec99f5 100644
--- a/ios/chrome/browser/ui/table_view/cells/settings_detail_item.h
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_detail_icon_item.h
@@ -2,15 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef IOS_CHROME_BROWSER_UI_TABLE_VIEW_CELLS_SETTINGS_DETAIL_ITEM_H_
-#define IOS_CHROME_BROWSER_UI_TABLE_VIEW_CELLS_SETTINGS_DETAIL_ITEM_H_
+#ifndef IOS_CHROME_BROWSER_UI_TABLE_VIEW_CELLS_TABLE_VIEW_DETAIL_ICON_ITEM_H_
+#define IOS_CHROME_BROWSER_UI_TABLE_VIEW_CELLS_TABLE_VIEW_DETAIL_ICON_ITEM_H_
 
 #import <UIKit/UIKit.h>
 
 #import "ios/chrome/browser/ui/table_view/cells/table_view_item.h"
 
-// SettingsDetailItem is a model class that uses SettingsDetailCell.
-@interface SettingsDetailItem : TableViewItem
+// TableViewDetailIconItem is a model class that uses TableViewDetailIconCell.
+@interface TableViewDetailIconItem : TableViewItem
 
 // The filename for the leading icon.  If empty, no icon will be shown.
 @property(nonatomic, copy) NSString* iconImageName;
@@ -26,11 +26,11 @@
 
 @end
 
-// SettingsDetailCell implements an TableViewCell subclass containing an
+// TableViewDetailIconCell implements an TableViewCell subclass containing an
 // optional leading icon and two text labels: a "main" label and a "detail"
 // label. The two labels are laid out side-by-side and fill the full width of
 // the cell. Labels are truncated as needed to fit in the cell.
-@interface SettingsDetailCell : TableViewCell
+@interface TableViewDetailIconCell : TableViewCell
 
 // UILabels corresponding to |text| and |detailText| from the item.
 @property(nonatomic, readonly, strong) UILabel* textLabel;
@@ -64,4 +64,4 @@
 
 @end
 
-#endif  // IOS_CHROME_BROWSER_UI_TABLE_VIEW_CELLS_SETTINGS_DETAIL_ITEM_H_
+#endif  // IOS_CHROME_BROWSER_UI_TABLE_VIEW_CELLS_TABLE_VIEW_DETAIL_ICON_ITEM_H_
diff --git a/ios/chrome/browser/ui/table_view/cells/settings_detail_item.mm b/ios/chrome/browser/ui/table_view/cells/table_view_detail_icon_item.mm
similarity index 96%
rename from ios/chrome/browser/ui/table_view/cells/settings_detail_item.mm
rename to ios/chrome/browser/ui/table_view/cells/table_view_detail_icon_item.mm
index e0ee57bc..7879f74 100644
--- a/ios/chrome/browser/ui/table_view/cells/settings_detail_item.mm
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_detail_icon_item.mm
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/chrome/browser/ui/table_view/cells/settings_detail_item.h"
+#import "ios/chrome/browser/ui/table_view/cells/table_view_detail_icon_item.h"
 
 #include <algorithm>
 
@@ -30,7 +30,7 @@
 const CGFloat kMinDetailTextWidthRatio = 0.25f;
 }  // namespace
 
-@implementation SettingsDetailItem
+@implementation TableViewDetailIconItem
 
 @synthesize iconImageName = _iconImageName;
 @synthesize text = _text;
@@ -39,7 +39,7 @@
 - (instancetype)initWithType:(NSInteger)type {
   self = [super initWithType:type];
   if (self) {
-    self.cellClass = [SettingsDetailCell class];
+    self.cellClass = [TableViewDetailIconCell class];
     _cellBackgroundColor = [UIColor whiteColor];
   }
   return self;
@@ -47,7 +47,7 @@
 
 #pragma mark TableViewItem
 
-- (void)configureCell:(SettingsDetailCell*)cell
+- (void)configureCell:(TableViewDetailIconCell*)cell
            withStyler:(ChromeTableViewStyler*)styler {
   [super configureCell:cell withStyler:styler];
   cell.textLabel.text = self.text;
@@ -64,9 +64,9 @@
 
 @end
 
-#pragma mark - SettingsDetailCell
+#pragma mark - TableViewDetailIconCell
 
-@interface SettingsDetailCell ()
+@interface TableViewDetailIconCell ()
 
 // When they are activated, the labels are on one line.
 // They conflict with the accessibilityConstraints.
@@ -78,7 +78,7 @@
 
 @end
 
-@implementation SettingsDetailCell {
+@implementation TableViewDetailIconCell {
   UIImageView* _iconImageView;
   UILayoutGuide* _labelContainerGuide;
   NSLayoutConstraint* _iconHiddenConstraint;
diff --git a/ios/chrome/browser/ui/tabs/BUILD.gn b/ios/chrome/browser/ui/tabs/BUILD.gn
index e87784d..9e695206 100644
--- a/ios/chrome/browser/ui/tabs/BUILD.gn
+++ b/ios/chrome/browser/ui/tabs/BUILD.gn
@@ -54,6 +54,7 @@
     "//ios/chrome/browser/ui/fullscreen",
     "//ios/chrome/browser/ui/image_util",
     "//ios/chrome/browser/ui/ntp:util",
+    "//ios/chrome/browser/ui/open_in",
     "//ios/chrome/browser/ui/popup_menu/public",
     "//ios/chrome/browser/ui/tab_grid/grid/resources:grid_cell_close_button",
     "//ios/chrome/browser/ui/tabs/requirements",
diff --git a/ios/chrome/test/BUILD.gn b/ios/chrome/test/BUILD.gn
index b2b6352b..1076a7ee 100644
--- a/ios/chrome/test/BUILD.gn
+++ b/ios/chrome/test/BUILD.gn
@@ -175,6 +175,7 @@
     "//ios/chrome/browser/ui/bookmarks/cells:unit_tests",
     "//ios/chrome/browser/ui/broadcaster:unit_tests",
     "//ios/chrome/browser/ui/browser_container:unit_tests",
+    "//ios/chrome/browser/ui/browser_view:unit_tests",
     "//ios/chrome/browser/ui/bubble:unit_tests",
     "//ios/chrome/browser/ui/collection_view:unit_tests",
     "//ios/chrome/browser/ui/collection_view/cells:unit_tests",
@@ -203,6 +204,7 @@
     "//ios/chrome/browser/ui/ntp_tile_views:unit_tests",
     "//ios/chrome/browser/ui/omnibox:unit_tests",
     "//ios/chrome/browser/ui/omnibox/popup:unit_tests",
+    "//ios/chrome/browser/ui/open_in:unit_tests",
     "//ios/chrome/browser/ui/payments:unit_tests",
     "//ios/chrome/browser/ui/payments/cells:unit_tests",
     "//ios/chrome/browser/ui/popup_menu:unit_tests",
diff --git a/ios/chrome/test/app/BUILD.gn b/ios/chrome/test/app/BUILD.gn
index 9ffc32b..4bce2b4 100644
--- a/ios/chrome/test/app/BUILD.gn
+++ b/ios/chrome/test/app/BUILD.gn
@@ -66,6 +66,7 @@
     "//ios/chrome/browser/tabs",
     "//ios/chrome/browser/ui:ui_internal",
     "//ios/chrome/browser/ui/authentication/cells",
+    "//ios/chrome/browser/ui/browser_view",
     "//ios/chrome/browser/ui/commands",
     "//ios/chrome/browser/ui/main",
     "//ios/chrome/browser/ui/main:tab_switcher",
diff --git a/ios/chrome/test/app/chrome_test_util.mm b/ios/chrome/test/app/chrome_test_util.mm
index 5fcbf53..48b64d6 100644
--- a/ios/chrome/test/app/chrome_test_util.mm
+++ b/ios/chrome/test/app/chrome_test_util.mm
@@ -22,7 +22,7 @@
 #import "ios/chrome/browser/metrics/previous_session_info.h"
 #import "ios/chrome/browser/metrics/previous_session_info_private.h"
 #import "ios/chrome/browser/tabs/tab.h"
-#import "ios/chrome/browser/ui/browser_view_controller.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller.h"
 #import "ios/chrome/browser/ui/main/bvc_container_view_controller.h"
 #import "ios/chrome/browser/ui/main/tab_switcher.h"
 #import "ios/chrome/browser/ui/main/view_controller_swapping.h"
diff --git a/ios/chrome/test/app/tab_test_util.mm b/ios/chrome/test/app/tab_test_util.mm
index 2b85888..f48e872 100644
--- a/ios/chrome/test/app/tab_test_util.mm
+++ b/ios/chrome/test/app/tab_test_util.mm
@@ -14,7 +14,6 @@
 #include "ios/chrome/browser/system_flags.h"
 #import "ios/chrome/browser/tabs/tab.h"
 #import "ios/chrome/browser/tabs/tab_model.h"
-#import "ios/chrome/browser/ui/browser_view_controller.h"
 #import "ios/chrome/browser/ui/commands/browser_commands.h"
 #import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
 #import "ios/chrome/browser/ui/main/tab_switcher.h"
diff --git a/ios/chrome/test/earl_grey/BUILD.gn b/ios/chrome/test/earl_grey/BUILD.gn
index cb80093..03ac8a17 100644
--- a/ios/chrome/test/earl_grey/BUILD.gn
+++ b/ios/chrome/test/earl_grey/BUILD.gn
@@ -92,6 +92,7 @@
     "//ios/chrome/browser/ui/activity_services:eg_tests",
     "//ios/chrome/browser/ui/alert_coordinator:eg_tests",
     "//ios/chrome/browser/ui/autofill:eg_tests",
+    "//ios/chrome/browser/ui/browser_view:eg_tests",
     "//ios/chrome/browser/ui/content_suggestions:eg_tests",
     "//ios/chrome/browser/ui/dialogs:eg_tests",
     "//ios/chrome/browser/ui/download:eg_tests",
diff --git a/ios/web/public/webui/web_ui_ios_controller_factory.h b/ios/web/public/webui/web_ui_ios_controller_factory.h
index c224dee..adea92d 100644
--- a/ios/web/public/webui/web_ui_ios_controller_factory.h
+++ b/ios/web/public/webui/web_ui_ios_controller_factory.h
@@ -24,6 +24,9 @@
   // Call to register a factory.
   static void RegisterFactory(WebUIIOSControllerFactory* factory);
 
+  // Call to deregister a factory.
+  static void DeregisterFactory(WebUIIOSControllerFactory* factory);
+
   // Returns whether |url| has an associated WebUI URL controller.
   virtual bool HasWebUIIOSControllerForURL(const GURL& url) const = 0;
 
diff --git a/ios/web/web_state/ui/crw_web_controller.h b/ios/web/web_state/ui/crw_web_controller.h
index ef45cc9..ed76c272 100644
--- a/ios/web/web_state/ui/crw_web_controller.h
+++ b/ios/web/web_state/ui/crw_web_controller.h
@@ -200,11 +200,6 @@
 // Notifies the CRWWebController that it has been hidden.
 - (void)wasHidden;
 
-// Adds |recognizer| as a gesture recognizer to the web view.
-- (void)addGestureRecognizerToWebView:(UIGestureRecognizer*)recognizer;
-// Removes |recognizer| from the web view.
-- (void)removeGestureRecognizerFromWebView:(UIGestureRecognizer*)recognizer;
-
 // Returns the native controller (if any) current mananging the content.
 - (id<CRWNativeContent>)nativeController;
 
diff --git a/ios/web/web_state/ui/crw_web_controller.mm b/ios/web/web_state/ui/crw_web_controller.mm
index e1f80ef..b948574 100644
--- a/ios/web/web_state/ui/crw_web_controller.mm
+++ b/ios/web/web_state/ui/crw_web_controller.mm
@@ -269,8 +269,6 @@
   BOOL _applyingPageState;
   // Actions to execute once the page load is complete.
   NSMutableArray* _pendingLoadCompleteActions;
-  // UIGestureRecognizers to add to the web view.
-  NSMutableArray* _gestureRecognizers;
   // Flag to say if browsing is enabled.
   BOOL _webUsageEnabled;
   // The controller that tracks long press and check context menu trigger.
@@ -656,7 +654,6 @@
         [[CRWJSInjectionReceiver alloc] initWithEvaluator:self];
     _webViewProxy = [[CRWWebViewProxyImpl alloc] initWithWebController:self];
     [[_webViewProxy scrollViewProxy] addObserver:self];
-    _gestureRecognizers = [[NSMutableArray alloc] init];
     _pendingLoadCompleteActions = [[NSMutableArray alloc] init];
     web::BrowserState* browserState = _webStateImpl->GetBrowserState();
     _certVerificationController = [[CRWCertVerificationController alloc]
@@ -1455,22 +1452,6 @@
   }
 }
 
-- (void)addGestureRecognizerToWebView:(UIGestureRecognizer*)recognizer {
-  if ([_gestureRecognizers containsObject:recognizer])
-    return;
-
-  [self.webView addGestureRecognizer:recognizer];
-  [_gestureRecognizers addObject:recognizer];
-}
-
-- (void)removeGestureRecognizerFromWebView:(UIGestureRecognizer*)recognizer {
-  if (![_gestureRecognizers containsObject:recognizer])
-    return;
-
-  [self.webView removeGestureRecognizer:recognizer];
-  [_gestureRecognizers removeObject:recognizer];
-}
-
 - (void)didFinishGoToIndexSameDocumentNavigationWithType:
             (web::NavigationInitiationType)type
                                           hasUserGesture:(BOOL)hasUserGesture {
@@ -3863,11 +3844,6 @@
                                        injectionEvaluator:self
                                                  delegate:self];
 
-    // Add all additional gesture recognizers to the web view.
-    for (UIGestureRecognizer* recognizer in _gestureRecognizers) {
-      [self.webView addGestureRecognizer:recognizer];
-    }
-
     // WKWebViews with invalid or empty frames have exhibited rendering bugs, so
     // resize the view to match the container view upon creation.
     [self.webView setFrame:[_containerView bounds]];
diff --git a/ios/web/webui/crw_web_ui_scheme_handler_unittest.mm b/ios/web/webui/crw_web_ui_scheme_handler_unittest.mm
index fee1185..4c1bbf89 100644
--- a/ios/web/webui/crw_web_ui_scheme_handler_unittest.mm
+++ b/ios/web/webui/crw_web_ui_scheme_handler_unittest.mm
@@ -75,6 +75,10 @@
     WebUIIOSControllerFactory::RegisterFactory(&factory_);
   }
 
+  ~CRWWebUISchemeManagerTest() override {
+    WebUIIOSControllerFactory::DeregisterFactory(&factory_);
+  }
+
  protected:
   CRWWebUISchemeHandler* CreateSchemeHandler() API_AVAILABLE(ios(11.0)) {
     return [[CRWWebUISchemeHandler alloc]
diff --git a/ios/web/webui/web_ui_ios_controller_factory_registry.cc b/ios/web/webui/web_ui_ios_controller_factory_registry.cc
index fbbc1199..e614175 100644
--- a/ios/web/webui/web_ui_ios_controller_factory_registry.cc
+++ b/ios/web/webui/web_ui_ios_controller_factory_registry.cc
@@ -26,6 +26,14 @@
   GetGlobalFactories().push_back(factory);
 }
 
+void WebUIIOSControllerFactory::DeregisterFactory(
+    WebUIIOSControllerFactory* factory) {
+  std::vector<WebUIIOSControllerFactory*>& factories = GetGlobalFactories();
+  auto position = std::find(factories.begin(), factories.end(), factory);
+  if (position != factories.end())
+    factories.erase(position);
+}
+
 WebUIIOSControllerFactoryRegistry*
 WebUIIOSControllerFactoryRegistry::GetInstance() {
   static base::NoDestructor<WebUIIOSControllerFactoryRegistry> instance;
diff --git a/ios/web/webui/web_ui_mojo_inttest.mm b/ios/web/webui/web_ui_mojo_inttest.mm
index cca60245..8751b6c 100644
--- a/ios/web/webui/web_ui_mojo_inttest.mm
+++ b/ios/web/webui/web_ui_mojo_inttest.mm
@@ -150,8 +150,9 @@
       ui_handler_ = std::make_unique<TestUIHandler>();
       WebState::CreateParams params(GetBrowserState());
       web_state_ = WebState::Create(params);
-      WebUIIOSControllerFactory::RegisterFactory(
-          new TestWebUIControllerFactory(ui_handler_.get()));
+      factory_ =
+          std::make_unique<TestWebUIControllerFactory>(ui_handler_.get());
+      WebUIIOSControllerFactory::RegisterFactory(factory_.get());
     }
   }
 
@@ -167,6 +168,7 @@
       // WebThread::UI once WebThreadBundle is destroyed.
       web_state_.reset();
       ui_handler_.reset();
+      WebUIIOSControllerFactory::DeregisterFactory(factory_.get());
     }
 
     WebIntTest::TearDown();
@@ -180,6 +182,7 @@
  private:
   std::unique_ptr<WebState> web_state_;
   std::unique_ptr<TestUIHandler> ui_handler_;
+  std::unique_ptr<TestWebUIControllerFactory> factory_;
 };
 
 // Tests that JS can send messages to the native code and vice versa.
diff --git a/media/gpu/v4l2/v4l2_image_processor.cc b/media/gpu/v4l2/v4l2_image_processor.cc
index 2947565..9cd3e2b8 100644
--- a/media/gpu/v4l2/v4l2_image_processor.cc
+++ b/media/gpu/v4l2/v4l2_image_processor.cc
@@ -10,6 +10,7 @@
 #include <sys/ioctl.h>
 #include <sys/mman.h>
 #include <tuple>
+#include <utility>
 
 #include "base/bind.h"
 #include "base/bind_helpers.h"
@@ -44,15 +45,9 @@
 
 namespace media {
 
-V4L2ImageProcessor::OutputRecord::OutputRecord() : at_device(false) {}
+V4L2ImageProcessor::JobRecord::JobRecord() = default;
 
-V4L2ImageProcessor::OutputRecord::OutputRecord(OutputRecord&&) = default;
-
-V4L2ImageProcessor::OutputRecord::~OutputRecord() {}
-
-V4L2ImageProcessor::JobRecord::JobRecord() : output_buffer_index(-1) {}
-
-V4L2ImageProcessor::JobRecord::~JobRecord() {}
+V4L2ImageProcessor::JobRecord::~JobRecord() = default;
 
 V4L2ImageProcessor::V4L2ImageProcessor(
     scoped_refptr<V4L2Device> device,
@@ -79,9 +74,9 @@
       device_(device),
       device_thread_("V4L2ImageProcessorThread"),
       device_poll_thread_("V4L2ImageProcessorDevicePollThread"),
-      output_buffer_queued_count_(0),
       num_buffers_(num_buffers),
-      error_cb_(error_cb) {
+      error_cb_(error_cb),
+      weak_this_factory_(this) {
   DETACH_FROM_THREAD(device_thread_checker_);
 }
 
@@ -381,7 +376,6 @@
 
   auto job_record = std::make_unique<JobRecord>();
   job_record->input_frame = frame;
-  job_record->output_buffer_index = output_buffer_index;
   job_record->legacy_ready_cb = std::move(cb);
 
   switch (output_memory_type_) {
@@ -393,7 +387,10 @@
       break;
 
     case V4L2_MEMORY_DMABUF:
-      job_record->output_dmabuf_fds = std::move(output_dmabuf_fds);
+      job_record->output_frame = VideoFrame::WrapExternalDmabufs(
+          output_layout_, gfx::Rect(output_visible_size_), output_visible_size_,
+          std::move(output_dmabuf_fds), job_record->input_frame->timestamp());
+      job_record->output_buffer_index = output_buffer_index;
       break;
 
     default:
@@ -418,8 +415,6 @@
 }
 
 void V4L2ImageProcessor::ProcessTask(std::unique_ptr<JobRecord> job_record) {
-  DVLOGF(4) << "Reusing output buffer, index="
-            << job_record->output_buffer_index;
   DCHECK_CALLED_ON_VALID_THREAD(device_thread_checker_);
 
   input_job_queue_.emplace(std::move(job_record));
@@ -430,9 +425,9 @@
   DCHECK_CALLED_ON_VALID_THREAD(device_thread_checker_);
 
   while (!input_job_queue_.empty()) {
-    // The output buffer is already decided, but we need one free input buffer
-    // to schedule the job
-    if (input_queue_->FreeBuffersCount() == 0)
+    // We need one input and one output buffer to schedule the job
+    if (input_queue_->FreeBuffersCount() == 0 ||
+        output_queue_->FreeBuffersCount() == 0)
       break;
 
     auto job_record = std::move(input_job_queue_.front());
@@ -577,47 +572,9 @@
     return false;
   }
 
-  DCHECK(output_buffer_map_.empty());
-  output_buffer_map_.resize(output_queue_->AllocatedBuffersCount());
-
-  // Get the DMA-BUF FDs for MMAP buffers
-  if (output_memory_type_ == V4L2_MEMORY_MMAP) {
-    for (unsigned int i = 0; i < output_buffer_map_.size(); i++) {
-      OutputRecord& output_record = output_buffer_map_[i];
-      output_record.dmabuf_fds = device_->GetDmabufsForV4L2Buffer(
-          i, output_layout_.num_buffers(), V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
-      if (output_record.dmabuf_fds.empty()) {
-        VLOGF(1) << "failed to get fds of output buffer";
-        return false;
-      }
-    }
-  }
-
   return true;
 }
 
-void V4L2ImageProcessor::DestroyInputBuffers() {
-  VLOGF(2);
-  DCHECK_CALLED_ON_VALID_THREAD(device_thread_checker_);
-
-  // We may be destroyed before we allocate any buffer.
-  if (input_queue_)
-    input_queue_->DeallocateBuffers();
-  input_queue_ = nullptr;
-}
-
-void V4L2ImageProcessor::DestroyOutputBuffers() {
-  VLOGF(2);
-  DCHECK_CALLED_ON_VALID_THREAD(device_thread_checker_);
-
-  output_buffer_map_.clear();
-
-  // We may be destroyed before we allocate any buffer.
-  if (output_queue_)
-    output_queue_->DeallocateBuffers();
-  output_queue_ = nullptr;
-}
-
 void V4L2ImageProcessor::DevicePollTask(bool poll_device) {
   DVLOGF(4);
   DCHECK(device_poll_thread_.task_runner()->BelongsToCurrentThread());
@@ -657,7 +614,7 @@
   }
 
   bool poll_device = (input_queue_->QueuedBuffersCount() > 0 ||
-                      output_buffer_queued_count_ > 0);
+                      output_queue_->QueuedBuffersCount() > 0);
 
   device_poll_thread_.task_runner()->PostTask(
       FROM_HERE, base::BindOnce(&V4L2ImageProcessor::DevicePollTask,
@@ -667,9 +624,10 @@
             << "] => DEVICE[" << input_queue_->FreeBuffersCount() << "+"
             << input_queue_->QueuedBuffersCount() << "/"
             << input_queue_->AllocatedBuffersCount() << "->"
-            << output_buffer_map_.size() - output_buffer_queued_count_ << "+"
-            << output_buffer_queued_count_ << "/" << output_buffer_map_.size()
-            << "]";
+            << output_queue_->AllocatedBuffersCount() -
+                   output_queue_->QueuedBuffersCount()
+            << "+" << output_queue_->QueuedBuffersCount() << "/"
+            << output_queue_->AllocatedBuffersCount() << "]";
 }
 
 void V4L2ImageProcessor::EnqueueInput(const JobRecord* job_record) {
@@ -699,11 +657,11 @@
   DCHECK_CALLED_ON_VALID_THREAD(device_thread_checker_);
   DCHECK(output_queue_);
 
-  const int old_outputs_queued = output_buffer_queued_count_;
+  const int old_outputs_queued = output_queue_->QueuedBuffersCount();
   if (!EnqueueOutputRecord(job_record))
     return;
 
-  if (old_outputs_queued == 0 && output_buffer_queued_count_ != 0) {
+  if (old_outputs_queued == 0 && output_queue_->QueuedBuffersCount() != 0) {
     // We just started up a previously empty queue.
     // Queue state changed; signal interrupt.
     if (!device_->SetDevicePollInterrupt()) {
@@ -716,6 +674,24 @@
   }
 }
 
+void V4L2ImageProcessor::V4L2VFDestructionObserver(V4L2ReadableBufferRef buf) {
+  DCHECK(device_thread_.task_runner()->BelongsToCurrentThread());
+
+  // Release the buffer reference so we can directly call ProcessJobsTask()
+  // knowing that we have an extra output buffer.
+#if DCHECK_IS_ON()
+  size_t original_free_buffers_count = output_queue_->FreeBuffersCount();
+#endif
+  buf = nullptr;
+#if DCHECK_IS_ON()
+  DCHECK_EQ(output_queue_->FreeBuffersCount(), original_free_buffers_count + 1);
+#endif
+
+  // A CAPTURE buffer has just been returned to the free list, let's see if
+  // we can make progress on some jobs.
+  ProcessJobsTask();
+}
+
 void V4L2ImageProcessor::Dequeue() {
   DVLOGF(4);
   DCHECK_CALLED_ON_VALID_THREAD(device_thread_checker_);
@@ -725,8 +701,6 @@
 
   // Dequeue completed input (VIDEO_OUTPUT) buffers,
   // and recycle to the free list.
-  struct v4l2_buffer dqbuf;
-  struct v4l2_plane planes[VIDEO_MAX_PLANES];
   while (input_queue_->QueuedBuffersCount() > 0) {
     bool res;
     V4L2ReadableBufferRef buffer;
@@ -740,44 +714,47 @@
       break;
     }
   }
-  // Dequeue completed output (VIDEO_CAPTURE) buffers, recycle to the free list.
+
+  // Dequeue completed output (VIDEO_CAPTURE) buffers.
   // Return the finished buffer to the client via the job ready callback.
-  while (output_buffer_queued_count_ > 0) {
+  while (output_queue_->QueuedBuffersCount() > 0) {
     DCHECK(output_queue_->IsStreaming());
-    memset(&dqbuf, 0, sizeof(dqbuf));
-    memset(&planes, 0, sizeof(planes));
-    dqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
-    dqbuf.memory = output_memory_type_;
-    dqbuf.m.planes = planes;
-    dqbuf.length = output_layout_.num_buffers();
-    if (device_->Ioctl(VIDIOC_DQBUF, &dqbuf) != 0) {
-      if (errno == EAGAIN) {
-        // EAGAIN if we're just out of buffers to dequeue.
-        break;
-      }
-      VPLOGF(1) << "ioctl() failed: VIDIOC_DQBUF";
+
+    bool res;
+    V4L2ReadableBufferRef buffer;
+    std::tie(res, buffer) = output_queue_->DequeueBuffer();
+    if (!res) {
       NotifyError();
       return;
+    } else if (!buffer) {
+      break;
     }
-    OutputRecord& output_record = output_buffer_map_[dqbuf.index];
-    DCHECK(output_record.at_device);
-    output_record.at_device = false;
-    output_buffer_queued_count_--;
 
     // Jobs are always processed in FIFO order.
     DCHECK(!running_jobs_.empty());
     std::unique_ptr<JobRecord> job_record = std::move(running_jobs_.front());
     running_jobs_.pop();
 
-    std::vector<base::ScopedFD> output_dmabuf_fds;
+    int output_index;
+
+    scoped_refptr<VideoFrame> output_frame;
     switch (output_memory_type_) {
       case V4L2_MEMORY_MMAP:
-        output_dmabuf_fds =
-            DuplicateFDs(output_buffer_map_[dqbuf.index].dmabuf_fds);
+        // Wrap the V4L2 VideoFrame into another one with a destruction observer
+        // so we can reuse the MMAP buffer once the client is done with it.
+        output_frame = buffer->GetVideoFrame();
+        output_frame = VideoFrame::WrapVideoFrame(
+            output_frame, output_frame->format(), output_frame->visible_rect(),
+            output_frame->natural_size());
+        output_index = buffer->BufferId();
+        output_frame->AddDestructionObserver(BindToCurrentLoop(
+            base::BindOnce(&V4L2ImageProcessor::V4L2VFDestructionObserver,
+                           weak_this_factory_.GetWeakPtr(), buffer)));
         break;
 
       case V4L2_MEMORY_DMABUF:
-        output_dmabuf_fds = std::move(job_record->output_dmabuf_fds);
+        output_frame = std::move(job_record->output_frame);
+        output_index = job_record->output_buffer_index;
         break;
 
       default:
@@ -785,29 +762,11 @@
         return;
     }
 
-    if (output_dmabuf_fds.size() != output_layout_.num_buffers()) {
-      VLOGF(1) << "wrong number of output fds. Expected "
-               << output_layout_.num_buffers() << ", actual "
-               << output_dmabuf_fds.size();
-      return;
-    }
-
-    // Create the output frame
-    auto output_frame = VideoFrame::WrapExternalDmabufs(
-        output_layout_, gfx::Rect(output_visible_size_), output_visible_size_,
-        std::move(output_dmabuf_fds), job_record->input_frame->timestamp());
-
-    if (!output_frame) {
-      DVLOGF(1) << "Error creating output frame!";
-      NotifyError();
-      return;
-    }
-
-    DVLOGF(4) << "Processing finished, returning frame, index=" << dqbuf.index;
+    output_frame->set_timestamp(job_record->input_frame->timestamp());
 
     if (!job_record->legacy_ready_cb.is_null()) {
       std::move(job_record->legacy_ready_cb)
-          .Run(dqbuf.index, std::move(output_frame));
+          .Run(output_index, std::move(output_frame));
     } else {
       std::move(job_record->ready_cb).Run(std::move(output_frame));
     }
@@ -819,7 +778,6 @@
   DCHECK(input_queue_);
   DCHECK_GT(input_queue_->FreeBuffersCount(), 0u);
 
-  // Enqueue an input (VIDEO_OUTPUT) buffer for an input video frame.
   V4L2WritableBufferRef buffer(input_queue_->GetFreeBuffer());
   DCHECK(buffer.IsValid());
 
@@ -853,36 +811,21 @@
 
 bool V4L2ImageProcessor::EnqueueOutputRecord(const JobRecord* job_record) {
   DVLOGF(4);
-  int index = job_record->output_buffer_index;
-  DCHECK_GE(index, 0);
-  DCHECK_LT(static_cast<size_t>(index), output_buffer_map_.size());
-  // Enqueue an output (VIDEO_CAPTURE) buffer.
-  OutputRecord& output_record = output_buffer_map_[index];
-  DCHECK(!output_record.at_device);
-  struct v4l2_buffer qbuf;
-  struct v4l2_plane qbuf_planes[VIDEO_MAX_PLANES];
-  memset(&qbuf, 0, sizeof(qbuf));
-  memset(qbuf_planes, 0, sizeof(qbuf_planes));
-  qbuf.index = index;
-  qbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
-  qbuf.memory = output_memory_type_;
-  if (output_memory_type_ == V4L2_MEMORY_DMABUF) {
-    auto& fds = job_record->output_dmabuf_fds;
-    if (fds.size() != output_layout_.num_buffers()) {
-      VLOGF(1) << "Invalid number of FDs in output record";
-      return false;
-    }
-    for (size_t i = 0; i < fds.size(); ++i)
-      qbuf_planes[i].m.fd = fds[i].get();
-  }
-  qbuf.m.planes = qbuf_planes;
-  qbuf.length = output_layout_.num_buffers();
+  DCHECK_GT(output_queue_->FreeBuffersCount(), 0u);
 
-  DVLOGF(4) << "Calling VIDIOC_QBUF: " << V4L2Device::V4L2BufferToString(qbuf);
-  IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_QBUF, &qbuf);
-  output_record.at_device = true;
-  output_buffer_queued_count_++;
-  return true;
+  V4L2WritableBufferRef buffer(output_queue_->GetFreeBuffer());
+  DCHECK(buffer.IsValid());
+
+  switch (buffer.Memory()) {
+    case V4L2_MEMORY_MMAP:
+      return std::move(buffer).QueueMMap();
+    case V4L2_MEMORY_DMABUF:
+      return std::move(buffer).QueueDMABuf(
+          job_record->output_frame->DmabufFds());
+    default:
+      NOTREACHED();
+      return false;
+  }
 }
 
 void V4L2ImageProcessor::AllocateBuffersTask(bool* result,
@@ -898,8 +841,15 @@
   VLOGF(2);
   DCHECK_CALLED_ON_VALID_THREAD(device_thread_checker_);
 
-  DestroyInputBuffers();
-  DestroyOutputBuffers();
+  weak_this_factory_.InvalidateWeakPtrs();
+
+  // We may be destroyed before we allocate any buffer.
+  if (input_queue_)
+    input_queue_->DeallocateBuffers();
+  if (output_queue_)
+    output_queue_->DeallocateBuffers();
+  input_queue_ = nullptr;
+  output_queue_ = nullptr;
 }
 
 void V4L2ImageProcessor::StartDevicePoll() {
@@ -950,10 +900,6 @@
 
   while (!running_jobs_.empty())
     running_jobs_.pop();
-
-  for (auto& output_buffer : output_buffer_map_)
-    output_buffer.at_device = false;
-  output_buffer_queued_count_ = 0;
 }
 
 }  // namespace media
diff --git a/media/gpu/v4l2/v4l2_image_processor.h b/media/gpu/v4l2/v4l2_image_processor.h
index bc46c1c..c2f0753 100644
--- a/media/gpu/v4l2/v4l2_image_processor.h
+++ b/media/gpu/v4l2/v4l2_image_processor.h
@@ -76,31 +76,18 @@
       ErrorCB error_cb);
 
  private:
-  // Record for output buffers.
-  struct OutputRecord {
-    OutputRecord();
-    OutputRecord(OutputRecord&&);
-    ~OutputRecord();
-    bool at_device;
-    // The exported FDs of the frame will be stored here if
-    // |output_memory_type_| is V4L2_MEMORY_MMAP
-    std::vector<base::ScopedFD> dmabuf_fds;
-  };
-
-  // Job record. Jobs are processed in a FIFO order. This is separate from
-  // InputRecord, because an InputRecord may be returned before we dequeue
-  // the corresponding output buffer. The processed frame will be stored in
-  // |output_buffer_index| output buffer. If |output_memory_type_| is
-  // V4L2_MEMORY_DMABUF, the processed frame will be stored in
-  // |output_dmabuf_fds|.
+  // Job record. Jobs are processed in a FIFO order. |input_frame| will be
+  // processed and the result written into |output_frame|. Once processing is
+  // complete, |ready_cb| or |legacy_ready_cb| will be called depending on which
+  // Process() method has been used to create that JobRecord.
   struct JobRecord {
     JobRecord();
     ~JobRecord();
     scoped_refptr<VideoFrame> input_frame;
-    int output_buffer_index;
-    std::vector<base::ScopedFD> output_dmabuf_fds;
+    int output_buffer_index = -1;
     FrameReadyCB ready_cb;
     LegacyFrameReadyCB legacy_ready_cb;
+    scoped_refptr<VideoFrame> output_frame;
   };
 
   V4L2ImageProcessor(scoped_refptr<V4L2Device> device,
@@ -124,8 +111,8 @@
   bool EnqueueOutputRecord(const JobRecord* job_record);
   bool CreateInputBuffers();
   bool CreateOutputBuffers();
-  void DestroyInputBuffers();
-  void DestroyOutputBuffers();
+
+  void V4L2VFDestructionObserver(V4L2ReadableBufferRef buf);
 
   void NotifyError();
 
@@ -188,10 +175,6 @@
   scoped_refptr<V4L2Queue> input_queue_;
   scoped_refptr<V4L2Queue> output_queue_;
 
-  // Number of output buffers enqueued to the device.
-  int output_buffer_queued_count_;
-  // Mapping of int index to an output buffer record.
-  std::vector<OutputRecord> output_buffer_map_;
   // The number of input or output buffers.
   const size_t num_buffers_;
 
@@ -203,6 +186,9 @@
   // Checker for the device thread owned by this V4L2ImageProcessor.
   THREAD_CHECKER(device_thread_checker_);
 
+  // Keep at the end so it is destroyed first.
+  base::WeakPtrFactory<V4L2ImageProcessor> weak_this_factory_;
+
   DISALLOW_COPY_AND_ASSIGN(V4L2ImageProcessor);
 };
 
diff --git a/mojo/public/js/mojo_bindings_resources.grd b/mojo/public/js/mojo_bindings_resources.grd
index deddde08..eb61537 100644
--- a/mojo/public/js/mojo_bindings_resources.grd
+++ b/mojo/public/js/mojo_bindings_resources.grd
@@ -13,11 +13,14 @@
   <translations />
   <release seq="1">
     <includes>
-      <include name="IDR_MOJO_MOJO_BINDINGS_JS"
-          file="${root_gen_dir}/mojo/public/js/mojo_bindings.js"
-          use_base_dir="false"
-          type="BINDATA"
-          compress="gzip" />
+      <!-- All resources included on Android should use the lite bindings. -->
+      <if expr="not is_android">
+        <include name="IDR_MOJO_MOJO_BINDINGS_JS"
+            file="${root_gen_dir}/mojo/public/js/mojo_bindings.js"
+            use_base_dir="false"
+            type="BINDATA"
+            compress="gzip" />
+      </if>
       <include name="IDR_MOJO_MOJO_BINDINGS_LITE_JS"
           file="${root_gen_dir}/mojo/public/js/mojo_bindings_lite.js"
           use_base_dir="false"
diff --git a/net/BUILD.gn b/net/BUILD.gn
index 17841dfa..71eb22d 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -1348,6 +1348,10 @@
       "third_party/quiche/src/quic/core/crypto/aes_256_gcm_decrypter.h",
       "third_party/quiche/src/quic/core/crypto/aes_256_gcm_encrypter.cc",
       "third_party/quiche/src/quic/core/crypto/aes_256_gcm_encrypter.h",
+      "third_party/quiche/src/quic/core/crypto/aes_base_decrypter.cc",
+      "third_party/quiche/src/quic/core/crypto/aes_base_decrypter.h",
+      "third_party/quiche/src/quic/core/crypto/aes_base_encrypter.cc",
+      "third_party/quiche/src/quic/core/crypto/aes_base_encrypter.h",
       "third_party/quiche/src/quic/core/crypto/cert_compressor.cc",
       "third_party/quiche/src/quic/core/crypto/cert_compressor.h",
       "third_party/quiche/src/quic/core/crypto/chacha20_poly1305_decrypter.cc",
@@ -1358,6 +1362,10 @@
       "third_party/quiche/src/quic/core/crypto/chacha20_poly1305_tls_decrypter.h",
       "third_party/quiche/src/quic/core/crypto/chacha20_poly1305_tls_encrypter.cc",
       "third_party/quiche/src/quic/core/crypto/chacha20_poly1305_tls_encrypter.h",
+      "third_party/quiche/src/quic/core/crypto/chacha_base_decrypter.cc",
+      "third_party/quiche/src/quic/core/crypto/chacha_base_decrypter.h",
+      "third_party/quiche/src/quic/core/crypto/chacha_base_encrypter.cc",
+      "third_party/quiche/src/quic/core/crypto/chacha_base_encrypter.h",
       "third_party/quiche/src/quic/core/crypto/channel_id.cc",
       "third_party/quiche/src/quic/core/crypto/channel_id.h",
       "third_party/quiche/src/quic/core/crypto/common_cert_set.cc",
@@ -5754,7 +5762,6 @@
       "http/http_auth_unittest.cc",
       "http/http_content_disposition_unittest.cc",
       "http/http_network_transaction_unittest.cc",
-      "http/http_proxy_client_socket_pool_unittest.cc",
       "spdy/spdy_network_transaction_unittest.cc",
       "spdy/spdy_proxy_client_socket_unittest.cc",
       "url_request/url_request_job_unittest.cc",
diff --git a/net/quic/mock_decrypter.cc b/net/quic/mock_decrypter.cc
index 0b06df0e..689c58d 100644
--- a/net/quic/mock_decrypter.cc
+++ b/net/quic/mock_decrypter.cc
@@ -21,6 +21,15 @@
   return key.empty();
 }
 
+bool MockDecrypter::SetHeaderProtectionKey(QuicStringPiece key) {
+  return key.empty();
+}
+
+std::string MockDecrypter::GenerateHeaderProtectionMask(
+    quic::QuicDataReader* sample_reader) {
+  return std::string(5, 0);
+}
+
 bool MockDecrypter::SetNoncePrefix(QuicStringPiece nonce_prefix) {
   return nonce_prefix.empty();
 }
diff --git a/net/quic/mock_decrypter.h b/net/quic/mock_decrypter.h
index b375064..7b9256f 100644
--- a/net/quic/mock_decrypter.h
+++ b/net/quic/mock_decrypter.h
@@ -29,6 +29,7 @@
   // QuicDecrypter implementation
   bool SetKey(quic::QuicStringPiece key) override;
   bool SetNoncePrefix(quic::QuicStringPiece nonce_prefix) override;
+  bool SetHeaderProtectionKey(quic::QuicStringPiece key) override;
   bool SetIV(quic::QuicStringPiece iv) override;
   bool SetPreliminaryKey(quic::QuicStringPiece key) override;
   bool SetDiversificationNonce(
@@ -43,6 +44,8 @@
   size_t GetIVSize() const override;
   quic::QuicStringPiece GetKey() const override;
   quic::QuicStringPiece GetNoncePrefix() const override;
+  std::string GenerateHeaderProtectionMask(
+      quic::QuicDataReader* sample_reader) override;
 
   uint32_t cipher_id() const override;
 
diff --git a/net/quic/mock_encrypter.cc b/net/quic/mock_encrypter.cc
index 21bfe87..147cdda 100644
--- a/net/quic/mock_encrypter.cc
+++ b/net/quic/mock_encrypter.cc
@@ -43,6 +43,15 @@
   return true;
 }
 
+bool MockEncrypter::SetHeaderProtectionKey(QuicStringPiece key) {
+  return key.empty();
+}
+
+std::string MockEncrypter::GenerateHeaderProtectionMask(
+    QuicStringPiece sample) {
+  return std::string(5, 0);
+}
+
 size_t MockEncrypter::GetKeySize() const {
   return 0;
 }
diff --git a/net/quic/mock_encrypter.h b/net/quic/mock_encrypter.h
index d184ece..9200cf1a 100644
--- a/net/quic/mock_encrypter.h
+++ b/net/quic/mock_encrypter.h
@@ -27,6 +27,7 @@
   // QuicEncrypter implementation
   bool SetKey(quic::QuicStringPiece key) override;
   bool SetNoncePrefix(quic::QuicStringPiece nonce_prefix) override;
+  bool SetHeaderProtectionKey(quic::QuicStringPiece key) override;
   bool SetIV(quic::QuicStringPiece iv) override;
   bool EncryptPacket(uint64_t packet_number,
                      quic::QuicStringPiece associated_data,
@@ -34,6 +35,8 @@
                      char* output,
                      size_t* output_length,
                      size_t max_output_length) override;
+  std::string GenerateHeaderProtectionMask(
+      quic::QuicStringPiece sample) override;
   size_t GetKeySize() const override;
   size_t GetNoncePrefixSize() const override;
   size_t GetIVSize() const override;
diff --git a/services/service_manager/embedder/main.cc b/services/service_manager/embedder/main.cc
index 46ae054..0fbc214 100644
--- a/services/service_manager/embedder/main.cc
+++ b/services/service_manager/embedder/main.cc
@@ -136,17 +136,6 @@
   MSG msg;
   PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
 #endif
-#if defined(OS_POSIX) && !defined(OS_ANDROID)
-  // Various things break when you're using a locale where the decimal
-  // separator isn't a period.  See e.g. bugs 22782 and 39964.  For
-  // all processes except the browser process (where we call system
-  // APIs that may rely on the correct locale for formatting numbers
-  // when presenting them to the user), reset the locale for numeric
-  // formatting.
-  // Note that this is not correct for plugin processes -- they can
-  // surface UI -- but it's likely they get this wrong too so why not.
-  setlocale(LC_NUMERIC, "C");
-#endif
 
 #if !defined(OFFICIAL_BUILD) && defined(OS_WIN)
   base::RouteStdioToConsole(false);
@@ -319,9 +308,19 @@
 // On Android setlocale() is not supported, and we don't override the signal
 // handlers so we can get a stack trace when crashing.
 #if defined(OS_POSIX) && !defined(OS_ANDROID)
-    // Set C library locale to make sure CommandLine can parse argument values
-    // in the correct encoding.
+    // Set C library locale to make sure CommandLine can parse
+    // argument values in the correct encoding and to make sure
+    // generated file names (think downloads) are in the file system's
+    // encoding.
     setlocale(LC_ALL, "");
+    // For numbers we never want the C library's locale sensitive
+    // conversion from number to string because the only thing it
+    // changes is the decimal separator which is not good enough for
+    // the UI and can be harmful elsewhere. User interface number
+    // conversions need to go through ICU. Other conversions need to
+    // be locale insensitive so we force the number locale back to the
+    // default, "C", locale.
+    setlocale(LC_NUMERIC, "C");
 
     SetupSignalHandlers();
 #endif
diff --git a/testing/buildbot/filters/mojo.fyi.network_chrome_public_test_apk.filter b/testing/buildbot/filters/mojo.fyi.network_chrome_public_test_apk.filter
index 376edb1..7f1387da 100644
--- a/testing/buildbot/filters/mojo.fyi.network_chrome_public_test_apk.filter
+++ b/testing/buildbot/filters/mojo.fyi.network_chrome_public_test_apk.filter
@@ -18,9 +18,6 @@
 # http://crbug.com/941856
 -org.chromium.chrome.browser.webapps.WebApkIntegrationTest.testActivateWebApk
 
-# http://crbug.com/941858
--org.chromium.chrome.browser.payments.PaymentRequestUpdateWithTest.testUpdateWithShippingOptions
-
 # http://crbug.com/943829
 -org.chromium.chrome.browser.autofill_assistant.AutofillAssistantUiTest.testStartAndAccept
 -org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetViewTest.testRemovingTabDeletesItsView
diff --git a/third_party/blink/common/feature_policy/feature_policy.cc b/third_party/blink/common/feature_policy/feature_policy.cc
index 5a9a3af..2d8d3826f 100644
--- a/third_party/blink/common/feature_policy/feature_policy.cc
+++ b/third_party/blink/common/feature_policy/feature_policy.cc
@@ -362,6 +362,10 @@
        {mojom::FeaturePolicyFeature::kFormSubmission,
         FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForAll,
                             mojom::PolicyValueType::kBool)},
+       // kFrobulate is a test only feature.
+       {mojom::FeaturePolicyFeature::kFrobulate,
+        FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForSelf,
+                            mojom::PolicyValueType::kBool)},
        {mojom::FeaturePolicyFeature::kFullscreen,
         FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForSelf,
                             mojom::PolicyValueType::kBool)},
@@ -454,10 +458,6 @@
                             mojom::PolicyValueType::kBool)},
        {mojom::FeaturePolicyFeature::kWebVr,
         FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForSelf,
-                            mojom::PolicyValueType::kBool)},
-       // kFrobulate is a test only feature.
-       {mojom::FeaturePolicyFeature::kFrobulate,
-        FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForSelf,
                             mojom::PolicyValueType::kBool)}});
   return *default_feature_list;
 }
diff --git a/third_party/blink/public/common/frame/user_activation_state.h b/third_party/blink/public/common/frame/user_activation_state.h
index 1568a01b..a73a235 100644
--- a/third_party/blink/public/common/frame/user_activation_state.h
+++ b/third_party/blink/public/common/frame/user_activation_state.h
@@ -12,11 +12,78 @@
 
 // This class represents the user activation state of a frame.  It maintains two
 // bits of information: whether this frame has ever seen an activation in its
-// lifetime, and whether this frame has a current activation that was neither
-// expired nor consumed.
+// lifetime (the sticky bit), and whether this frame has a current activation
+// that is neither expired nor consumed (the transient bit).  See User
+// Activation v2 (UAv2) links below for more info.
 //
-// This provides a simple alternative to current user gesture tracking code
-// based on UserGestureIndicator and UserGestureToken.
+//
+// Tracking User Activation across the Frame Tree
+// ==============================================
+//
+// This state changes in three ways: activation (of both bits) through user
+// input notifications, and deactivation (of transient bit only) through expiry
+// and consumption.
+//
+// - A notification update at a frame activates all ancestor frames (sets both
+// bits to true).  Also see "Same-origin Visibility" below.
+//
+// - A consumption call deactivates the transient bits in the whole frame tree.
+// This exhaustive consumption guarantees that a malicious subframe can't embed
+// sub-subframes in a way that could allow multiple consumptions per user
+// activation.
+//
+// - After a certain time limit (few seconds), the transient bit is deactivated.
+// Internally this is updated lazily on state polling.
+//
+//
+// Same-origin Visibility of User Activation
+// =========================================
+//
+// We launched UAv2 with a relaxed visibility model that a user activation is
+// visible to all same-origin frames (w.r.t. the originally-activated frame), in
+// addition to the ancestor frames as per UAv2.  We will remove this relaxation
+// after implementing a mechanism for activation transfer
+// (https://crbug.com/928838).
+//
+// Details: Before UAv2, user activation was visible to all same-process frames
+// and no cross-process frames (undocumented).  The ancestor-only restriction
+// with UAv2 caused a few breakages because of reliance on the old assumption,
+// see the Type=Bug-Regression bugs blocking the above bug.  Once we have a
+// workaround for those broken cases (most likely through a postMessage
+// transfer), we will go back to the ancestor-only visibility model.
+//
+//
+// State Replication in Browser and Renderers
+// ==========================================
+//
+// The user activation state is replicated in the browser process (in
+// |FrameTreeNode|) and in the renderer processes (in |LocalFrame| and
+// |RemoteFrame|).  The replicated states across the browser and renderer
+// processes are kept in sync as follows:
+//
+// [A] Consumption of activation state for popups starts in the frame tree of
+// the browser process and propagate to the renderer trees through direct IPCs
+// (one IPC sent to each renderer).
+//
+// [B] Consumption calls from JS/blink side (e.g. video picture-in-picture)
+// update the originating renderer's local frame tree and send an IPC to the
+// browser; the browser updates its frame tree and sends IPCs to all other
+// renderers each of which then updates its local frame tree.
+//
+// [B'] Notification updates on user inputs is like [B] (renderer first).  These
+// should really be like [A] (browser first), see https://crbug.com/848778.
+//
+// [C] Expiration of an active state is tracked independently in each process.
+//
+//
+// More Info
+// =========
+//
+// - UAv2 explainer: https://mustaqahmed.github.io/user-activation-v2
+// - Main design:
+//   https://docs.google.com/a/chromium.org/document/d/1erpl1yqJlc1pH0QvVVmi1s3WzqQLsEXTLLh6VuYp228
+// - Browser-side replication for OOPIFs:
+//   https://docs.google.com/document/d/1XL3vCedkqL65ueaGVD-kfB5RnnrnTaxLc7kmU91oerg
 class BLINK_COMMON_EXPORT UserActivationState {
  public:
   void Activate();
diff --git a/third_party/blink/public/web/web_ax_object.h b/third_party/blink/public/web/web_ax_object.h
index a8b14c915..6aafedc 100644
--- a/third_party/blink/public/web/web_ax_object.h
+++ b/third_party/blink/public/web/web_ax_object.h
@@ -99,10 +99,10 @@
   // tree.
   BLINK_EXPORT int GenerateAXID() const;
 
-  // Update layout if necessary on the underlying tree, and return true if this
-  // object is still valid (not detached). Note that calling this method can
-  // cause other WebAXObjects to become invalid, too, so always call isDetached
-  // if any other blink/renderer/core code has run.
+  // Update layout on the underlying tree, and return true if this object is
+  // still valid (not detached). Note that calling this method
+  // can cause other WebAXObjects to become invalid, too,
+  // so always call isDetached if any other WebCore code has run.
   BLINK_EXPORT bool UpdateLayoutAndCheckValidity();
 
   BLINK_EXPORT unsigned ChildCount() const;
diff --git a/third_party/blink/renderer/bindings/core/v8/script_promise_property_test.cc b/third_party/blink/renderer/bindings/core/v8/script_promise_property_test.cc
index 78cce798..d2ad145 100644
--- a/third_party/blink/renderer/bindings/core/v8/script_promise_property_test.cc
+++ b/third_party/blink/renderer/bindings/core/v8/script_promise_property_test.cc
@@ -102,7 +102,7 @@
 class ScriptPromisePropertyTestBase {
  public:
   ScriptPromisePropertyTestBase()
-      : page_(DummyPageHolder::Create(IntSize(1, 1))) {
+      : page_(std::make_unique<DummyPageHolder>(IntSize(1, 1))) {
     v8::HandleScope handle_scope(GetIsolate());
     other_script_state_ = ScriptState::Create(
         v8::Context::New(GetIsolate()),
diff --git a/third_party/blink/renderer/bindings/core/v8/script_promise_resolver_test.cc b/third_party/blink/renderer/bindings/core/v8/script_promise_resolver_test.cc
index f2065e30d084..2565a5b0 100644
--- a/third_party/blink/renderer/bindings/core/v8/script_promise_resolver_test.cc
+++ b/third_party/blink/renderer/bindings/core/v8/script_promise_resolver_test.cc
@@ -47,7 +47,8 @@
 
 class ScriptPromiseResolverTest : public testing::Test {
  public:
-  ScriptPromiseResolverTest() : page_holder_(DummyPageHolder::Create()) {}
+  ScriptPromiseResolverTest()
+      : page_holder_(std::make_unique<DummyPageHolder>()) {}
 
   ~ScriptPromiseResolverTest() override {
     // Execute all pending microtasks
diff --git a/third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value_fuzzer.cc b/third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value_fuzzer.cc
index adf921cd..ade1761 100644
--- a/third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value_fuzzer.cc
+++ b/third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value_fuzzer.cc
@@ -42,7 +42,7 @@
   v8::V8::SetFlagsFromString(kExposeGC, sizeof(kExposeGC));
   static BlinkFuzzerTestSupport fuzzer_support =
       BlinkFuzzerTestSupport(*argc, *argv);
-  g_page_holder = DummyPageHolder::Create().release();
+  g_page_holder = std::make_unique<DummyPageHolder>().release();
   g_page_holder->GetFrame().GetSettings()->SetScriptEnabled(true);
   g_blob_info_array = new WebBlobInfoArray();
   g_blob_info_array->emplace_back(WebBlobInfo::BlobForTesting(
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.cc b/third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.cc
index 2b45567b0..ea967fa 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.cc
+++ b/third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.cc
@@ -12,7 +12,7 @@
 namespace blink {
 
 V8TestingScope::V8TestingScope()
-    : holder_(DummyPageHolder::Create()),
+    : holder_(std::make_unique<DummyPageHolder>()),
       handle_scope_(GetIsolate()),
       context_(GetScriptState()->GetContext()),
       context_scope_(GetContext()),
diff --git a/third_party/blink/renderer/controller/BUILD.gn b/third_party/blink/renderer/controller/BUILD.gn
index 65c03e2a..a52bb9d 100644
--- a/third_party/blink/renderer/controller/BUILD.gn
+++ b/third_party/blink/renderer/controller/BUILD.gn
@@ -100,7 +100,6 @@
   ]
 
   if (is_android) {
-    enable_multidex = true
     deps += [
       "//base:base_java",
       "//content/public/android:content_java",
diff --git a/third_party/blink/renderer/core/accessibility/ax_object_cache.h b/third_party/blink/renderer/core/accessibility/ax_object_cache.h
index 721bb00..02f4f86 100644
--- a/third_party/blink/renderer/core/accessibility/ax_object_cache.h
+++ b/third_party/blink/renderer/core/accessibility/ax_object_cache.h
@@ -58,10 +58,6 @@
 
   virtual void Dispose() = 0;
 
-  // Register/remove popups
-  virtual void InitializePopup(Document* document) = 0;
-  virtual void DisposePopup(Document* document) = 0;
-
   virtual void SelectionChanged(Node*) = 0;
   virtual void ChildrenChanged(Node*) = 0;
   virtual void ChildrenChanged(LayoutObject*) = 0;
@@ -90,8 +86,7 @@
   virtual void UpdateCacheAfterNodeIsAttached(Node*) = 0;
   virtual void DidInsertChildrenOfNode(Node*) = 0;
 
-  // Returns true if the AXObjectCache cares about this attribute
-  virtual bool HandleAttributeChanged(const QualifiedName& attr_name,
+  virtual void HandleAttributeChanged(const QualifiedName& attr_name,
                                       Element*) = 0;
   virtual void HandleFocusedUIElementChanged(Node* old_focused_node,
                                              Node* new_focused_node) = 0;
diff --git a/third_party/blink/renderer/core/animation/animation_test.cc b/third_party/blink/renderer/core/animation/animation_test.cc
index 09bd4ae..21566523 100644
--- a/third_party/blink/renderer/core/animation/animation_test.cc
+++ b/third_party/blink/renderer/core/animation/animation_test.cc
@@ -63,7 +63,7 @@
   }
 
   void SetUpWithoutStartingTimeline() {
-    page_holder = DummyPageHolder::Create();
+    page_holder = std::make_unique<DummyPageHolder>();
     document = &page_holder->GetDocument();
     document->GetAnimationClock().ResetTimeForTesting();
     timeline = DocumentTimeline::Create(document.Get());
diff --git a/third_party/blink/renderer/core/animation/css_basic_shape_interpolation_type.cc b/third_party/blink/renderer/core/animation/css_basic_shape_interpolation_type.cc
index f6f7eb0..5db24b1 100644
--- a/third_party/blink/renderer/core/animation/css_basic_shape_interpolation_type.cc
+++ b/third_party/blink/renderer/core/animation/css_basic_shape_interpolation_type.cc
@@ -47,18 +47,12 @@
 class UnderlyingCompatibilityChecker
     : public CSSInterpolationType::CSSConversionChecker {
  public:
-  static std::unique_ptr<UnderlyingCompatibilityChecker> Create(
-      scoped_refptr<NonInterpolableValue> underlying_non_interpolable_value) {
-    return base::WrapUnique(new UnderlyingCompatibilityChecker(
-        std::move(underlying_non_interpolable_value)));
-  }
-
- private:
   UnderlyingCompatibilityChecker(
       scoped_refptr<NonInterpolableValue> underlying_non_interpolable_value)
       : underlying_non_interpolable_value_(
             std::move(underlying_non_interpolable_value)) {}
 
+ private:
   bool IsValid(const StyleResolverState&,
                const InterpolationValue& underlying) const final {
     return basic_shape_interpolation_functions::ShapesAreCompatible(
@@ -97,7 +91,7 @@
       const_cast<NonInterpolableValue*>(
           underlying.non_interpolable_value.get());
   conversion_checkers.push_back(
-      UnderlyingCompatibilityChecker::Create(non_interpolable_value));
+      std::make_unique<UnderlyingCompatibilityChecker>(non_interpolable_value));
   return InterpolationValue(
       basic_shape_interpolation_functions::CreateNeutralValue(
           *underlying.non_interpolable_value),
diff --git a/third_party/blink/renderer/core/animation/timing_input_test.cc b/third_party/blink/renderer/core/animation/timing_input_test.cc
index 7953de5..40695b5 100644
--- a/third_party/blink/renderer/core/animation/timing_input_test.cc
+++ b/third_party/blink/renderer/core/animation/timing_input_test.cc
@@ -30,7 +30,7 @@
                                 bool is_keyframeeffectoptions = true);
 
  private:
-  void SetUp() override { page_holder_ = DummyPageHolder::Create(); }
+  void SetUp() override { page_holder_ = std::make_unique<DummyPageHolder>(); }
 
   Document* GetDocument() const { return &page_holder_->GetDocument(); }
 
diff --git a/third_party/blink/renderer/core/aom/accessible_node.cc b/third_party/blink/renderer/core/aom/accessible_node.cc
index b3c5b361..d940a04 100644
--- a/third_party/blink/renderer/core/aom/accessible_node.cc
+++ b/third_party/blink/renderer/core/aom/accessible_node.cc
@@ -8,9 +8,7 @@
 #include "third_party/blink/renderer/core/aom/accessible_node_list.h"
 #include "third_party/blink/renderer/core/dom/element.h"
 #include "third_party/blink/renderer/core/dom/qualified_name.h"
-#include "third_party/blink/renderer/core/frame/local_frame_view.h"
 #include "third_party/blink/renderer/core/frame/settings.h"
-#include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/platform//weborigin/security_origin.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 
@@ -1172,30 +1170,12 @@
     const blink::QualifiedName& attribute) {
   // TODO(dmazzoni): Make a cleaner API for this rather than pretending
   // the DOM attribute changed.
-  AXObjectCache* cache = GetAXObjectCache();
-  if (!cache)
-    return;
-
-  if (!element_) {
-    cache->HandleAttributeChanged(attribute, this);
-    return;
+  if (AXObjectCache* cache = GetAXObjectCache()) {
+    if (element_)
+      cache->HandleAttributeChanged(attribute, element_);
+    else
+      cache->HandleAttributeChanged(attribute, this);
   }
-
-  // By definition, any attribute on an AccessibleNode is interesting to
-  // AXObjectCache, so no need to check return value.
-  cache->HandleAttributeChanged(attribute, element_);
-
-  auto* page = GetDocument()->GetPage();
-  auto* view = GetDocument()->View();
-  if (!page || !view)
-    return;
-
-  // TODO(aboxhall): add a lifecycle phase for accessibility updates.
-  if (!view->CanThrottleRendering())
-    page->Animator().ScheduleVisualUpdate(GetDocument()->GetFrame());
-
-  GetDocument()->Lifecycle().EnsureStateAtMost(
-      DocumentLifecycle::kVisualUpdatePending);
 }
 
 AXObjectCache* AccessibleNode::GetAXObjectCache() {
diff --git a/third_party/blink/renderer/core/css/drag_update_test.cc b/third_party/blink/renderer/core/css/drag_update_test.cc
index 54682f3..5f884f8 100644
--- a/third_party/blink/renderer/core/css/drag_update_test.cc
+++ b/third_party/blink/renderer/core/css/drag_update_test.cc
@@ -16,8 +16,7 @@
   // Check that when dragging the div in the document below, you only get a
   // single element style recalc.
 
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Document& document = dummy_page_holder->GetDocument();
   document.documentElement()->SetInnerHTMLFromString(R"HTML(
     <style>div {width:100px;height:100px} div:-webkit-drag {
@@ -48,8 +47,7 @@
   // Check that when dragging the div in the document below, you get a
   // single element style recalc.
 
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Document& document = dummy_page_holder->GetDocument();
   document.documentElement()->SetInnerHTMLFromString(R"HTML(
     <style>div {width:100px;height:100px} div:-webkit-drag .drag {
@@ -78,8 +76,7 @@
   // Check that when dragging the div in the document below, you get a
   // single element style recalc.
 
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Document& document = dummy_page_holder->GetDocument();
   document.documentElement()->SetInnerHTMLFromString(R"HTML(
     <style>div {width:100px;height:100px} div:-webkit-drag + .drag {
diff --git a/third_party/blink/renderer/core/css/invalidation/invalidation_set_test.cc b/third_party/blink/renderer/core/css/invalidation/invalidation_set_test.cc
index e276ce9..3d71430 100644
--- a/third_party/blink/renderer/core/css/invalidation/invalidation_set_test.cc
+++ b/third_party/blink/renderer/core/css/invalidation/invalidation_set_test.cc
@@ -255,7 +255,7 @@
 }
 
 TEST(InvalidationSetTest, ClassInvalidatesElement) {
-  auto dummy_page_holder = DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   auto& document = dummy_page_holder->GetDocument();
   document.body()->SetInnerHTMLFromString("<div id=test class='a b'>");
   document.View()->UpdateAllLifecyclePhases(
@@ -281,7 +281,7 @@
 }
 
 TEST(InvalidationSetTest, AttributeInvalidatesElement) {
-  auto dummy_page_holder = DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   auto& document = dummy_page_holder->GetDocument();
   document.body()->SetInnerHTMLFromString("<div id=test a b>");
   document.View()->UpdateAllLifecyclePhases(
diff --git a/third_party/blink/renderer/core/css/invalidation/pending_invalidations_test.cc b/third_party/blink/renderer/core/css/invalidation/pending_invalidations_test.cc
index d67db7cc..747df2d3 100644
--- a/third_party/blink/renderer/core/css/invalidation/pending_invalidations_test.cc
+++ b/third_party/blink/renderer/core/css/invalidation/pending_invalidations_test.cc
@@ -27,7 +27,7 @@
 };
 
 void PendingInvalidationsTest::SetUp() {
-  dummy_page_holder_ = DummyPageHolder::Create(IntSize(800, 600));
+  dummy_page_holder_ = std::make_unique<DummyPageHolder>(IntSize(800, 600));
 }
 
 TEST_F(PendingInvalidationsTest, ScheduleOnDocumentNode) {
diff --git a/third_party/blink/renderer/core/css/media_query_evaluator_test.cc b/third_party/blink/renderer/core/css/media_query_evaluator_test.cc
index e2de522..f5ebbd5 100644
--- a/third_party/blink/renderer/core/css/media_query_evaluator_test.cc
+++ b/third_party/blink/renderer/core/css/media_query_evaluator_test.cc
@@ -259,8 +259,7 @@
 }
 
 TEST(MediaQueryEvaluatorTest, Dynamic) {
-  std::unique_ptr<DummyPageHolder> page_holder =
-      DummyPageHolder::Create(IntSize(500, 500));
+  auto page_holder = std::make_unique<DummyPageHolder>(IntSize(500, 500));
   page_holder->GetFrameView().SetMediaType(media_type_names::kScreen);
 
   MediaQueryEvaluator media_query_evaluator(&page_holder->GetFrame());
@@ -270,8 +269,7 @@
 }
 
 TEST(MediaQueryEvaluatorTest, DynamicNoView) {
-  std::unique_ptr<DummyPageHolder> page_holder =
-      DummyPageHolder::Create(IntSize(500, 500));
+  auto page_holder = std::make_unique<DummyPageHolder>(IntSize(500, 500));
   LocalFrame* frame = &page_holder->GetFrame();
   page_holder.reset();
   ASSERT_EQ(nullptr, frame->View());
@@ -302,8 +300,7 @@
 }
 
 TEST(MediaQueryEvaluatorTest, InitialViewport) {
-  std::unique_ptr<DummyPageHolder> page_holder =
-      DummyPageHolder::Create(IntSize(500, 500));
+  auto page_holder = std::make_unique<DummyPageHolder>(IntSize(500, 500));
   page_holder->GetFrameView().SetMediaType(media_type_names::kScreen);
   page_holder->GetFrameView().SetLayoutSizeFixedToFrameSize(false);
   page_holder->GetFrameView().SetInitialViewportSize(IntSize(500, 500));
@@ -316,8 +313,7 @@
 }
 
 TEST(MediaQueryEvaluatorTest, DynamicImmersive) {
-  std::unique_ptr<DummyPageHolder> page_holder =
-      DummyPageHolder::Create(IntSize(500, 500));
+  auto page_holder = std::make_unique<DummyPageHolder>(IntSize(500, 500));
   page_holder->GetFrameView().SetMediaType(media_type_names::kScreen);
 
   MediaQueryEvaluator media_query_evaluator(&page_holder->GetFrame());
diff --git a/third_party/blink/renderer/core/css/media_query_matcher_test.cc b/third_party/blink/renderer/core/css/media_query_matcher_test.cc
index 5c9b1d8..4d1a59e 100644
--- a/third_party/blink/renderer/core/css/media_query_matcher_test.cc
+++ b/third_party/blink/renderer/core/css/media_query_matcher_test.cc
@@ -13,8 +13,7 @@
 namespace blink {
 
 TEST(MediaQueryMatcherTest, LostFrame) {
-  std::unique_ptr<DummyPageHolder> page_holder =
-      DummyPageHolder::Create(IntSize(500, 500));
+  auto page_holder = std::make_unique<DummyPageHolder>(IntSize(500, 500));
   MediaQueryMatcher* matcher =
       MediaQueryMatcher::Create(page_holder->GetDocument());
   scoped_refptr<MediaQuerySet> query_set =
diff --git a/third_party/blink/renderer/core/css/parser/css_lazy_parsing_test.cc b/third_party/blink/renderer/core/css/parser/css_lazy_parsing_test.cc
index b8af849..0ad37e1 100644
--- a/third_party/blink/renderer/core/css/parser/css_lazy_parsing_test.cc
+++ b/third_party/blink/renderer/core/css/parser/css_lazy_parsing_test.cc
@@ -107,8 +107,7 @@
 // document from the StyleSheetContents without changing the UseCounter. This
 // test ensures that the new UseCounter is used when doing new parsing work.
 TEST_F(CSSLazyParsingTest, ChangeDocuments) {
-  std::unique_ptr<DummyPageHolder> dummy_holder =
-      DummyPageHolder::Create(IntSize(500, 500));
+  auto dummy_holder = std::make_unique<DummyPageHolder>(IntSize(500, 500));
   Page::InsertOrdinaryPageForTesting(&dummy_holder->GetPage());
 
   CSSParserContext* context = CSSParserContext::Create(
@@ -144,8 +143,7 @@
   // Ensure no stack references to oilpan objects.
   ThreadState::Current()->CollectAllGarbage();
 
-  std::unique_ptr<DummyPageHolder> dummy_holder2 =
-      DummyPageHolder::Create(IntSize(500, 500));
+  auto dummy_holder2 = std::make_unique<DummyPageHolder>(IntSize(500, 500));
   Page::InsertOrdinaryPageForTesting(&dummy_holder2->GetPage());
   CSSStyleSheet* sheet2 =
       CSSStyleSheet::Create(cached_contents_, dummy_holder2->GetDocument());
diff --git a/third_party/blink/renderer/core/css/parser/css_property_parser_test.cc b/third_party/blink/renderer/core/css/parser/css_property_parser_test.cc
index 87c6d401..8a8e72c 100644
--- a/third_party/blink/renderer/core/css/parser/css_property_parser_test.cc
+++ b/third_party/blink/renderer/core/css/parser/css_property_parser_test.cc
@@ -297,8 +297,7 @@
 }
 
 TEST(CSSPropertyParserTest, ClipPathEllipse) {
-  std::unique_ptr<DummyPageHolder> dummy_holder =
-      DummyPageHolder::Create(IntSize(500, 500));
+  auto dummy_holder = std::make_unique<DummyPageHolder>(IntSize(500, 500));
   Document* doc = &dummy_holder->GetDocument();
   Page::InsertOrdinaryPageForTesting(&dummy_holder->GetPage());
   CSSParserContext* context = CSSParserContext::Create(
@@ -372,8 +371,7 @@
 }
 
 TEST(CSSPropertyParserTest, GradientUseCount) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Document& document = dummy_page_holder->GetDocument();
   Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
   WebFeature feature = WebFeature::kCSSGradient;
@@ -384,8 +382,7 @@
 }
 
 TEST(CSSPropertyParserTest, PaintUseCount) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Document& document = dummy_page_holder->GetDocument();
   Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
   document.SetSecureContextStateForTesting(SecureContextState::kSecure);
@@ -397,8 +394,7 @@
 }
 
 TEST(CSSPropertyParserTest, CrossFadeUseCount) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Document& document = dummy_page_holder->GetDocument();
   Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
   WebFeature feature = WebFeature::kWebkitCrossFade;
@@ -428,7 +424,7 @@
 class CSSPropertyUseCounterTest : public ::testing::Test {
  public:
   void SetUp() override {
-    dummy_page_holder_ = DummyPageHolder::Create(IntSize(800, 600));
+    dummy_page_holder_ = std::make_unique<DummyPageHolder>(IntSize(800, 600));
     Page::InsertOrdinaryPageForTesting(&dummy_page_holder_->GetPage());
     // Use strict mode.
     GetDocument().SetCompatibilityMode(Document::kNoQuirksMode);
diff --git a/third_party/blink/renderer/core/css/parser/css_selector_parser_test.cc b/third_party/blink/renderer/core/css/parser/css_selector_parser_test.cc
index ddafad7..164f6dc5 100644
--- a/third_party/blink/renderer/core/css/parser/css_selector_parser_test.cc
+++ b/third_party/blink/renderer/core/css/parser/css_selector_parser_test.cc
@@ -585,8 +585,7 @@
 }
 
 TEST(CSSSelectorParserTest, UseCountShadowPseudo) {
-  std::unique_ptr<DummyPageHolder> dummy_holder =
-      DummyPageHolder::Create(IntSize(500, 500));
+  auto dummy_holder = std::make_unique<DummyPageHolder>(IntSize(500, 500));
   Document* doc = &dummy_holder->GetDocument();
   Page::InsertOrdinaryPageForTesting(&dummy_holder->GetPage());
   CSSParserContext* context = CSSParserContext::Create(
diff --git a/third_party/blink/renderer/core/css/properties/computed_style_utils.cc b/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
index bc4cff1..160c1da 100644
--- a/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
+++ b/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
@@ -1728,7 +1728,7 @@
        content_data = content_data->Next()) {
     if (content_data->IsCounter()) {
       const CounterContent* counter =
-          ToCounterContentData(content_data)->Counter();
+          To<CounterContentData>(content_data)->Counter();
       DCHECK(counter);
       CSSCustomIdentValue* identifier =
           CSSCustomIdentValue::Create(counter->Identifier());
@@ -1744,14 +1744,14 @@
           CSSIdentifierValue::Create(list_style_ident);
       list->Append(*CSSCounterValue::Create(identifier, list_style, separator));
     } else if (content_data->IsImage()) {
-      const StyleImage* image = ToImageContentData(content_data)->GetImage();
+      const StyleImage* image = To<ImageContentData>(content_data)->GetImage();
       DCHECK(image);
       list->Append(*image->ComputedCSSValue());
     } else if (content_data->IsText()) {
-      list->Append(
-          *CSSStringValue::Create(ToTextContentData(content_data)->GetText()));
+      list->Append(*CSSStringValue::Create(
+          To<TextContentData>(content_data)->GetText()));
     } else if (content_data->IsQuote()) {
-      const QuoteType quote_type = ToQuoteContentData(content_data)->Quote();
+      const QuoteType quote_type = To<QuoteContentData>(content_data)->Quote();
       list->Append(*CSSIdentifierValue::Create(ValueForQuoteType(quote_type)));
     } else {
       NOTREACHED();
diff --git a/third_party/blink/renderer/core/css/properties/css_parsing_utils_test.cc b/third_party/blink/renderer/core/css/properties/css_parsing_utils_test.cc
index b93ab5a..701c77e 100644
--- a/third_party/blink/renderer/core/css/properties/css_parsing_utils_test.cc
+++ b/third_party/blink/renderer/core/css/properties/css_parsing_utils_test.cc
@@ -11,8 +11,7 @@
 namespace blink {
 
 TEST(CSSParsingUtilsTest, BasicShapeUseCount) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
   Document& document = dummy_page_holder->GetDocument();
   WebFeature feature = WebFeature::kCSSBasicShape;
diff --git a/third_party/blink/renderer/core/css/properties/longhands/content_custom.cc b/third_party/blink/renderer/core/css/properties/longhands/content_custom.cc
index 0da8b1d4..20e1d57 100644
--- a/third_party/blink/renderer/core/css/properties/longhands/content_custom.cc
+++ b/third_party/blink/renderer/core/css/properties/longhands/content_custom.cc
@@ -195,7 +195,7 @@
         string = To<CSSStringValue>(*item).Value();
       }
       if (prev_content && prev_content->IsText()) {
-        TextContentData* text_content = ToTextContentData(prev_content);
+        TextContentData* text_content = To<TextContentData>(prev_content);
         text_content->SetText(text_content->GetText() + string);
         continue;
       }
diff --git a/third_party/blink/renderer/core/css/resolver/css_variable_resolver_test.cc b/third_party/blink/renderer/core/css/resolver/css_variable_resolver_test.cc
index f02fd20..c9ac015 100644
--- a/third_party/blink/renderer/core/css/resolver/css_variable_resolver_test.cc
+++ b/third_party/blink/renderer/core/css/resolver/css_variable_resolver_test.cc
@@ -148,8 +148,7 @@
 TEST_F(CSSVariableResolverTest, NoResolutionWithoutVar) {
   scoped_refptr<StyleInheritedVariables> inherited_variables =
       StyleInheritedVariables::Create();
-  std::unique_ptr<StyleNonInheritedVariables> non_inherited_variables =
-      StyleNonInheritedVariables::Create();
+  auto non_inherited_variables = std::make_unique<StyleNonInheritedVariables>();
 
   EXPECT_FALSE(inherited_variables->NeedsResolution());
   EXPECT_FALSE(non_inherited_variables->NeedsResolution());
@@ -166,8 +165,7 @@
 TEST_F(CSSVariableResolverTest, VarNeedsResolution) {
   scoped_refptr<StyleInheritedVariables> inherited_variables =
       StyleInheritedVariables::Create();
-  std::unique_ptr<StyleNonInheritedVariables> non_inherited_variables =
-      StyleNonInheritedVariables::Create();
+  auto non_inherited_variables = std::make_unique<StyleNonInheritedVariables>();
 
   EXPECT_FALSE(inherited_variables->NeedsResolution());
   EXPECT_FALSE(non_inherited_variables->NeedsResolution());
@@ -201,8 +199,7 @@
 TEST_F(CSSVariableResolverTest, UrlNeedsResolution) {
   scoped_refptr<StyleInheritedVariables> inherited_variables =
       StyleInheritedVariables::Create();
-  std::unique_ptr<StyleNonInheritedVariables> non_inherited_variables =
-      StyleNonInheritedVariables::Create();
+  auto non_inherited_variables = std::make_unique<StyleNonInheritedVariables>();
 
   EXPECT_FALSE(inherited_variables->NeedsResolution());
   EXPECT_FALSE(non_inherited_variables->NeedsResolution());
@@ -219,8 +216,7 @@
 TEST_F(CSSVariableResolverTest, CopiedVariablesRetainNeedsResolution) {
   scoped_refptr<StyleInheritedVariables> inherited_variables =
       StyleInheritedVariables::Create();
-  std::unique_ptr<StyleNonInheritedVariables> non_inherited_variables =
-      StyleNonInheritedVariables::Create();
+  auto non_inherited_variables = std::make_unique<StyleNonInheritedVariables>();
 
   const auto* prop = CreateCustomProperty("var(--x)");
 
@@ -348,8 +344,7 @@
 }
 
 TEST_F(CSSVariableResolverTest, RemoveNonInheritedVariable) {
-  std::unique_ptr<StyleNonInheritedVariables> non_inherited_variables =
-      StyleNonInheritedVariables::Create();
+  auto non_inherited_variables = std::make_unique<StyleNonInheritedVariables>();
 
   AtomicString name("--prop");
   const auto* prop = CreateCustomProperty("test");
@@ -375,8 +370,7 @@
 }
 
 TEST_F(CSSVariableResolverTest, DontCrashWhenSettingNonInheritedNullVariable) {
-  std::unique_ptr<StyleNonInheritedVariables> inherited_variables =
-      StyleNonInheritedVariables::Create();
+  auto inherited_variables = std::make_unique<StyleNonInheritedVariables>();
   AtomicString name("--test");
   inherited_variables->SetVariable(name, nullptr);
   inherited_variables->SetRegisteredVariable(name, nullptr);
diff --git a/third_party/blink/renderer/core/css/resolver/element_style_resources.cc b/third_party/blink/renderer/core/css/resolver/element_style_resources.cc
index 5b3cb0b..270e9c73 100644
--- a/third_party/blink/renderer/core/css/resolver/element_style_resources.cc
+++ b/third_party/blink/renderer/core/css/resolver/element_style_resources.cc
@@ -230,9 +230,9 @@
                  const_cast<ContentData*>(style->GetContentData());
              content_data; content_data = content_data->Next()) {
           if (content_data->IsImage()) {
-            StyleImage* image = ToImageContentData(content_data)->GetImage();
+            StyleImage* image = To<ImageContentData>(content_data)->GetImage();
             if (image->IsPendingImage()) {
-              ToImageContentData(content_data)
+              To<ImageContentData>(content_data)
                   ->SetImage(
                       LoadPendingImage(style, To<StylePendingImage>(image),
                                        FetchParameters::kAllowPlaceholder));
diff --git a/third_party/blink/renderer/core/css/resolver/font_builder_test.cc b/third_party/blink/renderer/core/css/resolver/font_builder_test.cc
index 9a35a8d3f..a5fc0b5 100644
--- a/third_party/blink/renderer/core/css/resolver/font_builder_test.cc
+++ b/third_party/blink/renderer/core/css/resolver/font_builder_test.cc
@@ -17,7 +17,8 @@
 
 class FontBuilderTest {
  public:
-  FontBuilderTest() : dummy_(DummyPageHolder::Create(IntSize(800, 600))) {
+  FontBuilderTest()
+      : dummy_(std::make_unique<DummyPageHolder>(IntSize(800, 600))) {
     GetSettings().SetDefaultFontSize(16.0f);
   }
 
diff --git a/third_party/blink/renderer/core/css/resolver/selector_filter_parent_scope_test.cc b/third_party/blink/renderer/core/css/resolver/selector_filter_parent_scope_test.cc
index efcf3df..3df1f62 100644
--- a/third_party/blink/renderer/core/css/resolver/selector_filter_parent_scope_test.cc
+++ b/third_party/blink/renderer/core/css/resolver/selector_filter_parent_scope_test.cc
@@ -14,7 +14,7 @@
 class SelectorFilterParentScopeTest : public testing::Test {
  protected:
   void SetUp() override {
-    dummy_page_holder_ = DummyPageHolder::Create(IntSize(800, 600));
+    dummy_page_holder_ = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   }
 
   Document& GetDocument() { return dummy_page_holder_->GetDocument(); }
diff --git a/third_party/blink/renderer/core/css/style_element_test.cc b/third_party/blink/renderer/core/css/style_element_test.cc
index 4420791..69096ab 100644
--- a/third_party/blink/renderer/core/css/style_element_test.cc
+++ b/third_party/blink/renderer/core/css/style_element_test.cc
@@ -14,8 +14,7 @@
 namespace blink {
 
 TEST(StyleElementTest, CreateSheetUsesCache) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Document& document = dummy_page_holder->GetDocument();
 
   document.documentElement()->SetInnerHTMLFromString(
diff --git a/third_party/blink/renderer/core/css/style_engine_test.cc b/third_party/blink/renderer/core/css/style_engine_test.cc
index 0e600004..c03029ea 100644
--- a/third_party/blink/renderer/core/css/style_engine_test.cc
+++ b/third_party/blink/renderer/core/css/style_engine_test.cc
@@ -70,7 +70,7 @@
 };
 
 void StyleEngineTest::SetUp() {
-  dummy_page_holder_ = DummyPageHolder::Create(IntSize(800, 600));
+  dummy_page_holder_ = std::make_unique<DummyPageHolder>(IntSize(800, 600));
 }
 
 void StyleEngineTest::TearDown() {
diff --git a/third_party/blink/renderer/core/css/style_environment_variables_test.cc b/third_party/blink/renderer/core/css/style_environment_variables_test.cc
index 187ef07..656c075 100644
--- a/third_party/blink/renderer/core/css/style_environment_variables_test.cc
+++ b/third_party/blink/renderer/core/css/style_environment_variables_test.cc
@@ -232,13 +232,11 @@
   InitializeTestPageWithVariableNamed(GetFrame(), kVariableName);
 
   // Create a second page that uses the variable.
-  std::unique_ptr<DummyPageHolder> new_page =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto new_page = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   InitializeTestPageWithVariableNamed(new_page->GetFrame(), kVariableName);
 
   // Create an empty page that does not use the variable.
-  std::unique_ptr<DummyPageHolder> empty_page =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto empty_page = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   empty_page->GetDocument().View()->UpdateAllLifecyclePhases(
       DocumentLifecycle::LifecycleUpdateReason::kTest);
 
@@ -255,8 +253,7 @@
   InitializeTestPageWithVariableNamed(GetFrame(), kVariableName);
 
   // Create a second page that uses the variable.
-  std::unique_ptr<DummyPageHolder> new_page =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto new_page = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   InitializeTestPageWithVariableNamed(new_page->GetFrame(), kVariableName);
 
   GetDocumentVariables().SetVariable(kVariableName, kVariableTestColor);
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_context.cc b/third_party/blink/renderer/core/display_lock/display_lock_context.cc
index 01d83f5..c879ab7 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_context.cc
+++ b/third_party/blink/renderer/core/display_lock/display_lock_context.cc
@@ -631,12 +631,12 @@
   }
 }
 
-void DisplayLockContext::WillStartLifecycleUpdate(const LocalFrameView& view) {
+void DisplayLockContext::WillStartLifecycleUpdate() {
   if (state_ == kUpdating)
     update_budget_->WillStartLifecycleUpdate();
 }
 
-void DisplayLockContext::DidFinishLifecycleUpdate(const LocalFrameView& view) {
+void DisplayLockContext::DidFinishLifecycleUpdate() {
   if (state_ == kPendingAcquire) {
     if (!ElementSupportsDisplayLocking()) {
       FinishAcquireResolver(kReject, rejection_names::kContainmentNotSatisfied);
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_context.h b/third_party/blink/renderer/core/display_lock/display_lock_context.h
index a5fcc96..aee69443 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_context.h
+++ b/third_party/blink/renderer/core/display_lock/display_lock_context.h
@@ -163,8 +163,8 @@
   void AddToWhitespaceReattachSet(Element& element);
 
   // LifecycleNotificationObserver overrides.
-  void WillStartLifecycleUpdate(const LocalFrameView&) override;
-  void DidFinishLifecycleUpdate(const LocalFrameView&) override;
+  void WillStartLifecycleUpdate() override;
+  void DidFinishLifecycleUpdate() override;
 
  private:
   friend class DisplayLockContextTest;
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index 4da7c1b..faecf20 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -2474,6 +2474,9 @@
   if (Lifecycle().GetState() < DocumentLifecycle::kLayoutClean)
     Lifecycle().AdvanceTo(DocumentLifecycle::kLayoutClean);
 
+  if (AXObjectCache* cache = ExistingAXObjectCache())
+    cache->ProcessUpdatesAfterLayout(*this);
+
   if (LocalFrameView* frame_view_anchored = View())
     frame_view_anchored->PerformScrollAnchoringAdjustments();
 
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index a416238..ae861d3 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -1690,22 +1690,8 @@
 
   if (isConnected()) {
     if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache()) {
-      if (params.old_value != params.new_value) {
-        auto* page = GetDocument().GetPage();
-        auto* view = GetDocument().View();
-        // If this attribute is interesting for accessibility (e.g. `role` or
-        // `alt`), but doesn't trigger a lifecycle update on its own
-        // (e.g. because it doesn't make layout dirty), make sure we run
-        // lifecycle phases to update the computed accessibility tree.
-        if (cache->HandleAttributeChanged(name, this) && page && view) {
-          if (!view->CanThrottleRendering())
-            page->Animator().ScheduleVisualUpdate(GetDocument().GetFrame());
-
-          // TODO(aboxhall): add a lifecycle phase for accessibility updates.
-          GetDocument().Lifecycle().EnsureStateAtMost(
-              DocumentLifecycle::kVisualUpdatePending);
-        }
-      }
+      if (params.old_value != params.new_value)
+        cache->HandleAttributeChanged(name, this);
     }
   }
 
diff --git a/third_party/blink/renderer/core/dom/scripted_animation_controller_test.cc b/third_party/blink/renderer/core/dom/scripted_animation_controller_test.cc
index 06de506..76af63a3 100644
--- a/third_party/blink/renderer/core/dom/scripted_animation_controller_test.cc
+++ b/third_party/blink/renderer/core/dom/scripted_animation_controller_test.cc
@@ -33,7 +33,7 @@
 };
 
 void ScriptedAnimationControllerTest::SetUp() {
-  dummy_page_holder_ = DummyPageHolder::Create(IntSize(800, 600));
+  dummy_page_holder_ = std::make_unique<DummyPageHolder>(IntSize(800, 600));
 
   // Note: The document doesn't know about this ScriptedAnimationController
   // instance, and will create another if
diff --git a/third_party/blink/renderer/core/dom/slot_assignment_test.cc b/third_party/blink/renderer/core/dom/slot_assignment_test.cc
index 65eb655..f85597d 100644
--- a/third_party/blink/renderer/core/dom/slot_assignment_test.cc
+++ b/third_party/blink/renderer/core/dom/slot_assignment_test.cc
@@ -96,7 +96,7 @@
 };
 
 void SlotAssignmentTest::SetUp() {
-  dummy_page_holder_ = DummyPageHolder::Create(IntSize(800, 600));
+  dummy_page_holder_ = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   document_ = &dummy_page_holder_->GetDocument();
   DCHECK(document_);
 }
diff --git a/third_party/blink/renderer/core/dom/user_gesture_indicator.h b/third_party/blink/renderer/core/dom/user_gesture_indicator.h
index e32295f..12736ad6 100644
--- a/third_party/blink/renderer/core/dom/user_gesture_indicator.h
+++ b/third_party/blink/renderer/core/dom/user_gesture_indicator.h
@@ -17,6 +17,8 @@
 // which propagates user gestures to the timer fire in certain situations).
 // Passing it to a UserGestureIndicator later on will cause it to be considered
 // as currently being processed.
+//
+// DEPRECATED: Use |UserActivationState| accessors in |Frame|.
 class CORE_EXPORT UserGestureToken : public RefCounted<UserGestureToken> {
   friend class UserGestureIndicator;
 
@@ -48,6 +50,7 @@
   DISALLOW_COPY_AND_ASSIGN(UserGestureToken);
 };
 
+// DEPRECATED: Use |UserActivationState| accessors in |Frame|.
 class CORE_EXPORT UserGestureIndicator final {
   USING_FAST_MALLOC(UserGestureIndicator);
 
diff --git a/third_party/blink/renderer/core/execution_context/context_lifecycle_state_observer_test.cc b/third_party/blink/renderer/core/execution_context/context_lifecycle_state_observer_test.cc
index 8a5f827..371360e 100644
--- a/third_party/blink/renderer/core/execution_context/context_lifecycle_state_observer_test.cc
+++ b/third_party/blink/renderer/core/execution_context/context_lifecycle_state_observer_test.cc
@@ -70,8 +70,8 @@
 };
 
 ContextLifecycleStateObserverTest::ContextLifecycleStateObserverTest()
-    : src_page_holder_(DummyPageHolder::Create(IntSize(800, 600))),
-      dest_page_holder_(DummyPageHolder::Create(IntSize(800, 600))),
+    : src_page_holder_(std::make_unique<DummyPageHolder>(IntSize(800, 600))),
+      dest_page_holder_(std::make_unique<DummyPageHolder>(IntSize(800, 600))),
       observer_(MakeGarbageCollected<MockContextLifecycleStateObserver>(
           &src_page_holder_->GetDocument())) {
   observer_->UpdateStateIfNeeded();
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 b716b5bb..750b55f 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
@@ -298,15 +298,14 @@
   frame->Init();
   frame->View()->SetParentVisible(true);
   frame->View()->SetSelfVisible(true);
+  if (AXObjectCache* cache =
+          popup_client_->OwnerElement().GetDocument().ExistingAXObjectCache())
+    cache->ChildrenChanged(&popup_client_->OwnerElement());
 
   DCHECK(frame->DomWindow());
   PagePopupSupplement::Install(*frame, *this, popup_client_);
   DCHECK_EQ(popup_client_->OwnerElement().GetDocument().ExistingAXObjectCache(),
             frame->GetDocument()->ExistingAXObjectCache());
-  if (AXObjectCache* cache = frame->GetDocument()->ExistingAXObjectCache()) {
-    cache->InitializePopup(frame->GetDocument());
-    cache->ChildrenChanged(&popup_client_->OwnerElement());
-  }
 
   page_->LayerTreeViewInitialized(*layer_tree_view_, *animation_host_, nullptr);
 
@@ -559,9 +558,6 @@
 
   closing_ = true;
 
-  if (AXObjectCache* cache = MainFrame().GetDocument()->ExistingAXObjectCache())
-    cache->DisposePopup(MainFrame().GetDocument());
-
   {
     // This function can be called in EventDispatchForbiddenScope for the main
     // document, and the following operations dispatch some events.  It's safe
diff --git a/third_party/blink/renderer/core/exported/web_surrounding_text_test.cc b/third_party/blink/renderer/core/exported/web_surrounding_text_test.cc
index 0a1d451..713231cf8 100644
--- a/third_party/blink/renderer/core/exported/web_surrounding_text_test.cc
+++ b/third_party/blink/renderer/core/exported/web_surrounding_text_test.cc
@@ -32,7 +32,7 @@
 };
 
 void WebSurroundingTextTest::SetUp() {
-  dummy_page_holder_ = DummyPageHolder::Create(IntSize(800, 600));
+  dummy_page_holder_ = std::make_unique<DummyPageHolder>(IntSize(800, 600));
 }
 
 void WebSurroundingTextTest::SetHTML(const String& content) {
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy_test.cc b/third_party/blink/renderer/core/feature_policy/feature_policy_test.cc
index b045cb51..b408d15 100644
--- a/third_party/blink/renderer/core/feature_policy/feature_policy_test.cc
+++ b/third_party/blink/renderer/core/feature_policy/feature_policy_test.cc
@@ -348,7 +348,7 @@
   const char* histogram_name = "Blink.UseCounter.FeaturePolicy.Allow";
   HistogramTester tester;
   Vector<String> messages;
-  std::unique_ptr<DummyPageHolder> dummy = DummyPageHolder::Create();
+  auto dummy = std::make_unique<DummyPageHolder>();
 
   ParseFeaturePolicy("payment; fullscreen", origin_a_.get(), origin_b_.get(),
                      &messages, test_feature_name_map, &dummy->GetDocument());
@@ -373,8 +373,8 @@
   const char* histogram_name = "Blink.UseCounter.FeaturePolicy.Allow";
   HistogramTester tester;
   Vector<String> messages;
-  std::unique_ptr<DummyPageHolder> dummy = DummyPageHolder::Create();
-  std::unique_ptr<DummyPageHolder> dummy2 = DummyPageHolder::Create();
+  auto dummy = std::make_unique<DummyPageHolder>();
+  auto dummy2 = std::make_unique<DummyPageHolder>();
 
   ParseFeaturePolicy("payment; fullscreen", origin_a_.get(), origin_b_.get(),
                      &messages, test_feature_name_map, &dummy->GetDocument());
@@ -657,8 +657,7 @@
   HistogramTester tester;
   const char* histogram_name =
       "Blink.UseCounter.FeaturePolicy.PotentialViolation";
-  std::unique_ptr<DummyPageHolder> dummy_page_holder_ =
-      DummyPageHolder::Create();
+  auto dummy_page_holder_ = std::make_unique<DummyPageHolder>();
   // Probing feature state should not count.
   dummy_page_holder_->GetDocument().IsFeatureEnabled(
       mojom::FeaturePolicyFeature::kPayment);
diff --git a/third_party/blink/renderer/core/fetch/response_test.cc b/third_party/blink/renderer/core/fetch/response_test.cc
index 31039afa..998abfe6 100644
--- a/third_party/blink/renderer/core/fetch/response_test.cc
+++ b/third_party/blink/renderer/core/fetch/response_test.cc
@@ -27,8 +27,7 @@
 namespace {
 
 TEST(ServiceWorkerResponseTest, FromFetchResponseData) {
-  std::unique_ptr<DummyPageHolder> page =
-      DummyPageHolder::Create(IntSize(1, 1));
+  auto page = std::make_unique<DummyPageHolder>(IntSize(1, 1));
   const KURL url("http://www.response.com");
 
   FetchResponseData* fetch_response_data = FetchResponseData::Create();
diff --git a/third_party/blink/renderer/core/frame/ad_tracker_test.cc b/third_party/blink/renderer/core/frame/ad_tracker_test.cc
index ffcb1ea..0dc78cb 100644
--- a/third_party/blink/renderer/core/frame/ad_tracker_test.cc
+++ b/third_party/blink/renderer/core/frame/ad_tracker_test.cc
@@ -107,7 +107,7 @@
 };
 
 void AdTrackerTest::SetUp() {
-  page_holder_ = DummyPageHolder::Create(IntSize(800, 600));
+  page_holder_ = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   page_holder_->GetDocument().SetURL(KURL("https://example.com/foo"));
   ad_tracker_ = MakeGarbageCollected<TestAdTracker>(GetFrame());
   ad_tracker_->SetExecutionContext(&page_holder_->GetDocument());
diff --git a/third_party/blink/renderer/core/frame/csp/content_security_policy_fuzzer.cc b/third_party/blink/renderer/core/frame/csp/content_security_policy_fuzzer.cc
index f5e347f..33ddd35 100644
--- a/third_party/blink/renderer/core/frame/csp/content_security_policy_fuzzer.cc
+++ b/third_party/blink/renderer/core/frame/csp/content_security_policy_fuzzer.cc
@@ -21,7 +21,7 @@
   // Scope cannot be created before BlinkFuzzerTestSupport because it requires
   // that Oilpan be initialized to access blink::ThreadState::Current.
   LEAK_SANITIZER_DISABLED_SCOPE;
-  g_page_holder = DummyPageHolder::Create().release();
+  g_page_holder = std::make_unique<DummyPageHolder>().release();
   return 0;
 }
 
diff --git a/third_party/blink/renderer/core/frame/frame.cc b/third_party/blink/renderer/core/frame/frame.cc
index 17a8160..b97e74d8 100644
--- a/third_party/blink/renderer/core/frame/frame.cc
+++ b/third_party/blink/renderer/core/frame/frame.cc
@@ -190,7 +190,8 @@
   for (Frame* node = this; node; node = node->Tree().Parent())
     node->user_activation_state_.Activate();
 
-  // See FrameTreeNode::NotifyUserActivation() for details about this block.
+  // See the "Same-origin Visibility" section in |UserActivationState| class
+  // doc.
   auto* local_frame = DynamicTo<LocalFrame>(this);
   if (local_frame && RuntimeEnabledFeatures::UserActivationV2Enabled() &&
       RuntimeEnabledFeatures::UserActivationSameOriginVisibilityEnabled()) {
@@ -212,9 +213,6 @@
 bool Frame::ConsumeTransientUserActivationInLocalTree() {
   bool was_active = user_activation_state_.IsActive();
 
-  // Note that consumption "touches" the whole frame tree, to guarantee that a
-  // malicious subframe can't embed sub-subframes in a way that could allow
-  // multiple consumptions per user activation.
   Frame& root = Tree().Top();
   for (Frame* node = &root; node; node = node->Tree().TraverseNext(&root))
     node->user_activation_state_.ConsumeIfActive();
diff --git a/third_party/blink/renderer/core/frame/frame.h b/third_party/blink/renderer/core/frame/frame.h
index a777236..2b477a289 100644
--- a/third_party/blink/renderer/core/frame/frame.h
+++ b/third_party/blink/renderer/core/frame/frame.h
@@ -267,8 +267,8 @@
   Member<FrameOwner> owner_;
   Member<DOMWindow> dom_window_;
 
-  // The user activation state of the current frame.  See
-  // FrameTreeNode::user_activation_state_ for details.
+  // The user activation state of the current frame.  See |UserActivationState|
+  // for details on how this state is maintained.
   UserActivationState user_activation_state_;
 
   bool has_received_user_gesture_before_nav_ = false;
diff --git a/third_party/blink/renderer/core/frame/local_frame.h b/third_party/blink/renderer/core/frame/local_frame.h
index e77b504..52c1777e 100644
--- a/third_party/blink/renderer/core/frame/local_frame.h
+++ b/third_party/blink/renderer/core/frame/local_frame.h
@@ -483,8 +483,8 @@
   // Returns the transient user activation state of this frame
   bool HasTransientUserActivation();
 
-  // Consumes and returns the transient user activation state of this frame,
-  // after updating all ancestor/descendant frames.
+  // Consumes and returns the transient user activation state this frame, after
+  // updating all other frames in the frame tree.
   bool ConsumeTransientUserActivation(UserActivationUpdateSource update_source);
 
   void SetFrameColorOverlay(SkColor color);
diff --git a/third_party/blink/renderer/core/frame/local_frame_test.cc b/third_party/blink/renderer/core/frame/local_frame_test.cc
index a3ddc968..dd3f8c9d 100644
--- a/third_party/blink/renderer/core/frame/local_frame_test.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_test.cc
@@ -61,7 +61,7 @@
   request1.SetURL(KURL("http://insecure.com"));
   request1.SetPreviewsState(WebURLRequest::kClientLoFiOn);
   FetchParameters params1(request1);
-  auto page_holder = DummyPageHolder::Create(
+  auto page_holder = std::make_unique<DummyPageHolder>(
       IntSize(800, 600), nullptr,
       MakeGarbageCollected<TestLocalFrameClient>(WebURLRequest::kPreviewsOff));
   MaybeAllowImagePlaceholder(page_holder.get(), params1);
@@ -72,7 +72,7 @@
   request2.SetURL(KURL("https://secure.com"));
   request2.SetPreviewsState(WebURLRequest::kPreviewsOff);
   FetchParameters params2(request2);
-  auto page_holder2 = DummyPageHolder::Create(
+  auto page_holder2 = std::make_unique<DummyPageHolder>(
       IntSize(800, 600), nullptr,
       MakeGarbageCollected<TestLocalFrameClient>(WebURLRequest::kClientLoFiOn));
   MaybeAllowImagePlaceholder(page_holder2.get(), params2);
@@ -84,7 +84,7 @@
   request1.SetURL(KURL("http://insecure.com"));
   request1.SetPreviewsState(WebURLRequest::kPreviewsUnspecified);
   FetchParameters params1(request1);
-  std::unique_ptr<DummyPageHolder> page_holder = DummyPageHolder::Create(
+  auto page_holder = std::make_unique<DummyPageHolder>(
       IntSize(800, 600), nullptr,
       MakeGarbageCollected<TestLocalFrameClient>(WebURLRequest::kClientLoFiOn));
   MaybeAllowImagePlaceholder(page_holder.get(), params1);
@@ -96,10 +96,10 @@
   request2.SetURL(KURL("http://insecure.com"));
   request2.SetPreviewsState(WebURLRequest::kPreviewsUnspecified);
   FetchParameters params2(request2);
-  std::unique_ptr<DummyPageHolder> page_holder2 =
-      DummyPageHolder::Create(IntSize(800, 600), nullptr,
-                              MakeGarbageCollected<TestLocalFrameClient>(
-                                  WebURLRequest::kServerLitePageOn));
+  auto page_holder2 = std::make_unique<DummyPageHolder>(
+      IntSize(800, 600), nullptr,
+      MakeGarbageCollected<TestLocalFrameClient>(
+          WebURLRequest::kServerLitePageOn));
   MaybeAllowImagePlaceholder(page_holder2.get(), params2);
   EXPECT_EQ(FetchParameters::kNone, params2.GetImageRequestOptimization());
   EXPECT_FALSE(page_holder2->GetFrame().IsUsingDataSavingPreview());
@@ -111,7 +111,7 @@
   request1.SetURL(KURL("https://secure.com"));
   request1.SetPreviewsState(WebURLRequest::kPreviewsUnspecified);
   FetchParameters params1(request1);
-  auto page_holder = DummyPageHolder::Create(
+  auto page_holder = std::make_unique<DummyPageHolder>(
       IntSize(800, 600), nullptr,
       MakeGarbageCollected<TestLocalFrameClient>(WebURLRequest::kServerLoFiOn |
                                                  WebURLRequest::kClientLoFiOn));
@@ -123,7 +123,7 @@
   request2.SetURL(KURL("http://insecure.com"));
   request2.SetPreviewsState(WebURLRequest::kPreviewsUnspecified);
   FetchParameters params2(request2);
-  auto page_holder2 = DummyPageHolder::Create(
+  auto page_holder2 = std::make_unique<DummyPageHolder>(
       IntSize(800, 600), nullptr,
       MakeGarbageCollected<TestLocalFrameClient>(WebURLRequest::kServerLoFiOn |
                                                  WebURLRequest::kClientLoFiOn));
@@ -132,54 +132,54 @@
 }
 
 TEST_F(LocalFrameTest, IsUsingDataSavingPreview) {
-  EXPECT_TRUE(
-      DummyPageHolder::Create(IntSize(800, 600), nullptr,
-                              MakeGarbageCollected<TestLocalFrameClient>(
-                                  WebURLRequest::kClientLoFiOn))
-          ->GetFrame()
-          .IsUsingDataSavingPreview());
-  EXPECT_TRUE(
-      DummyPageHolder::Create(IntSize(800, 600), nullptr,
-                              MakeGarbageCollected<TestLocalFrameClient>(
-                                  WebURLRequest::kServerLoFiOn))
-          ->GetFrame()
-          .IsUsingDataSavingPreview());
-  EXPECT_TRUE(
-      DummyPageHolder::Create(IntSize(800, 600), nullptr,
-                              MakeGarbageCollected<TestLocalFrameClient>(
-                                  WebURLRequest::kNoScriptOn))
-          ->GetFrame()
-          .IsUsingDataSavingPreview());
+  EXPECT_TRUE(std::make_unique<DummyPageHolder>(
+                  IntSize(800, 600), nullptr,
+                  MakeGarbageCollected<TestLocalFrameClient>(
+                      WebURLRequest::kClientLoFiOn))
+                  ->GetFrame()
+                  .IsUsingDataSavingPreview());
+  EXPECT_TRUE(std::make_unique<DummyPageHolder>(
+                  IntSize(800, 600), nullptr,
+                  MakeGarbageCollected<TestLocalFrameClient>(
+                      WebURLRequest::kServerLoFiOn))
+                  ->GetFrame()
+                  .IsUsingDataSavingPreview());
+  EXPECT_TRUE(std::make_unique<DummyPageHolder>(
+                  IntSize(800, 600), nullptr,
+                  MakeGarbageCollected<TestLocalFrameClient>(
+                      WebURLRequest::kNoScriptOn))
+                  ->GetFrame()
+                  .IsUsingDataSavingPreview());
 
-  EXPECT_FALSE(
-      DummyPageHolder::Create(IntSize(800, 600), nullptr,
-                              MakeGarbageCollected<TestLocalFrameClient>(
-                                  WebURLRequest::kPreviewsUnspecified))
-          ->GetFrame()
-          .IsUsingDataSavingPreview());
-  EXPECT_FALSE(
-      DummyPageHolder::Create(IntSize(800, 600), nullptr,
-                              MakeGarbageCollected<TestLocalFrameClient>(
-                                  WebURLRequest::kPreviewsOff))
-          ->GetFrame()
-          .IsUsingDataSavingPreview());
-  EXPECT_FALSE(
-      DummyPageHolder::Create(IntSize(800, 600), nullptr,
-                              MakeGarbageCollected<TestLocalFrameClient>(
-                                  WebURLRequest::kPreviewsNoTransform))
-          ->GetFrame()
-          .IsUsingDataSavingPreview());
-  EXPECT_FALSE(
-      DummyPageHolder::Create(IntSize(800, 600), nullptr,
-                              MakeGarbageCollected<TestLocalFrameClient>(
-                                  WebURLRequest::kServerLitePageOn))
-          ->GetFrame()
-          .IsUsingDataSavingPreview());
+  EXPECT_FALSE(std::make_unique<DummyPageHolder>(
+                   IntSize(800, 600), nullptr,
+                   MakeGarbageCollected<TestLocalFrameClient>(
+                       WebURLRequest::kPreviewsUnspecified))
+                   ->GetFrame()
+                   .IsUsingDataSavingPreview());
+  EXPECT_FALSE(std::make_unique<DummyPageHolder>(
+                   IntSize(800, 600), nullptr,
+                   MakeGarbageCollected<TestLocalFrameClient>(
+                       WebURLRequest::kPreviewsOff))
+                   ->GetFrame()
+                   .IsUsingDataSavingPreview());
+  EXPECT_FALSE(std::make_unique<DummyPageHolder>(
+                   IntSize(800, 600), nullptr,
+                   MakeGarbageCollected<TestLocalFrameClient>(
+                       WebURLRequest::kPreviewsNoTransform))
+                   ->GetFrame()
+                   .IsUsingDataSavingPreview());
+  EXPECT_FALSE(std::make_unique<DummyPageHolder>(
+                   IntSize(800, 600), nullptr,
+                   MakeGarbageCollected<TestLocalFrameClient>(
+                       WebURLRequest::kServerLitePageOn))
+                   ->GetFrame()
+                   .IsUsingDataSavingPreview());
 }
 
 TEST_F(LocalFrameTest, IsLazyLoadingImageAllowedWithFeatureDisabled) {
   ScopedLazyImageLoadingForTest scoped_lazy_image_loading_for_test(false);
-  std::unique_ptr<DummyPageHolder> page_holder = DummyPageHolder::Create(
+  auto page_holder = std::make_unique<DummyPageHolder>(
       IntSize(800, 600), nullptr, nullptr, &DisableDataSaverHoldbackInSettings);
   EXPECT_FALSE(page_holder->GetFrame().IsLazyLoadingImageAllowed());
 }
@@ -188,7 +188,7 @@
   ScopedLazyImageLoadingForTest scoped_lazy_image_loading_for_test(true);
   ScopedRestrictLazyImageLoadingToDataSaverForTest
       scoped_restrict_lazy_image_loading_to_data_saver_for_test_(false);
-  std::unique_ptr<DummyPageHolder> page_holder = DummyPageHolder::Create(
+  auto page_holder = std::make_unique<DummyPageHolder>(
       IntSize(800, 600), nullptr, nullptr, &DisableDataSaverHoldbackInSettings);
   EXPECT_TRUE(page_holder->GetFrame().IsLazyLoadingImageAllowed());
 }
@@ -199,7 +199,7 @@
   ScopedRestrictLazyImageLoadingToDataSaverForTest
       scoped_restrict_lazy_image_loading_to_data_saver_for_test_(true);
   GetNetworkStateNotifier().SetSaveDataEnabled(false);
-  std::unique_ptr<DummyPageHolder> page_holder = DummyPageHolder::Create(
+  auto page_holder = std::make_unique<DummyPageHolder>(
       IntSize(800, 600), nullptr, nullptr, &DisableDataSaverHoldbackInSettings);
   EXPECT_FALSE(page_holder->GetFrame().IsLazyLoadingImageAllowed());
 }
@@ -210,7 +210,7 @@
   ScopedRestrictLazyImageLoadingToDataSaverForTest
       scoped_restrict_lazy_image_loading_to_data_saver_for_test_(true);
   GetNetworkStateNotifier().SetSaveDataEnabled(true);
-  std::unique_ptr<DummyPageHolder> page_holder = DummyPageHolder::Create(
+  auto page_holder = std::make_unique<DummyPageHolder>(
       IntSize(800, 600), nullptr, nullptr, &EnableDataSaverHoldbackInSettings);
   EXPECT_FALSE(page_holder->GetFrame().IsLazyLoadingImageAllowed());
 }
@@ -221,7 +221,7 @@
   ScopedRestrictLazyImageLoadingToDataSaverForTest
       scoped_restrict_lazy_image_loading_to_data_saver_for_test_(true);
   GetNetworkStateNotifier().SetSaveDataEnabled(true);
-  std::unique_ptr<DummyPageHolder> page_holder = DummyPageHolder::Create(
+  auto page_holder = std::make_unique<DummyPageHolder>(
       IntSize(800, 600), nullptr, nullptr, &DisableDataSaverHoldbackInSettings);
   EXPECT_TRUE(page_holder->GetFrame().IsLazyLoadingImageAllowed());
 }
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
index 264d2b4..4b35f36 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -2215,7 +2215,7 @@
 
   ForAllNonThrottledLocalFrameViews([](LocalFrameView& frame_view) {
     for (auto& observer : frame_view.lifecycle_observers_)
-      observer->WillStartLifecycleUpdate(frame_view);
+      observer->WillStartLifecycleUpdate();
   });
 
   // If we're in PrintBrowser mode, setup a print context.
@@ -2231,7 +2231,7 @@
 
   ForAllNonThrottledLocalFrameViews([](LocalFrameView& frame_view) {
     for (auto& observer : frame_view.lifecycle_observers_)
-      observer->DidFinishLifecycleUpdate(frame_view);
+      observer->DidFinishLifecycleUpdate();
   });
 
   return Lifecycle().GetState() == target_state;
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.h b/third_party/blink/renderer/core/frame/local_frame_view.h
index 49fa8c1..867a0a7 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.h
+++ b/third_party/blink/renderer/core/frame/local_frame_view.h
@@ -117,8 +117,8 @@
       : public GarbageCollectedMixin {
    public:
     // These are called when the lifecycle updates start/finish.
-    virtual void WillStartLifecycleUpdate(const LocalFrameView&) = 0;
-    virtual void DidFinishLifecycleUpdate(const LocalFrameView&) = 0;
+    virtual void WillStartLifecycleUpdate() = 0;
+    virtual void DidFinishLifecycleUpdate() = 0;
   };
 
   static LocalFrameView* Create(LocalFrame&);
diff --git a/third_party/blink/renderer/core/frame/performance_monitor.cc b/third_party/blink/renderer/core/frame/performance_monitor.cc
index 83dda120..383568d 100644
--- a/third_party/blink/renderer/core/frame/performance_monitor.cc
+++ b/third_party/blink/renderer/core/frame/performance_monitor.cc
@@ -209,10 +209,9 @@
 
   if (probe.Duration() <= kLongTaskSubTaskThreshold)
     return;
-  std::unique_ptr<SubTaskAttribution> sub_task_attribution =
-      SubTaskAttribution::Create(AtomicString("script-run"),
-                                 probe.context->Url().GetString(),
-                                 probe.CaptureStartTime(), probe.Duration());
+  auto sub_task_attribution = std::make_unique<SubTaskAttribution>(
+      AtomicString("script-run"), probe.context->Url().GetString(),
+      probe.CaptureStartTime(), probe.Duration());
   sub_task_attributions_.push_back(std::move(sub_task_attribution));
 }
 
@@ -266,12 +265,11 @@
       return;
   }
 
-  std::unique_ptr<SubTaskAttribution> sub_task_attribution =
-      SubTaskAttribution::Create(
-          AtomicString("script-compile"),
-          String::Format("%s(%d, %d)", probe.file_name.Utf8().data(),
-                         probe.line, probe.column),
-          v8_compile_start_time_, v8_compile_duration);
+  auto sub_task_attribution = std::make_unique<SubTaskAttribution>(
+      AtomicString("script-compile"),
+      String::Format("%s(%d, %d)", probe.file_name.Utf8().data(), probe.line,
+                     probe.column),
+      v8_compile_start_time_, v8_compile_duration);
   sub_task_attributions_.push_back(std::move(sub_task_attribution));
 }
 
diff --git a/third_party/blink/renderer/core/frame/performance_monitor_test.cc b/third_party/blink/renderer/core/frame/performance_monitor_test.cc
index 319bd6e..692c8dd 100644
--- a/third_party/blink/renderer/core/frame/performance_monitor_test.cc
+++ b/third_party/blink/renderer/core/frame/performance_monitor_test.cc
@@ -74,12 +74,12 @@
 };
 
 void PerformanceMonitorTest::SetUp() {
-  page_holder_ = DummyPageHolder::Create(IntSize(800, 600));
+  page_holder_ = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   page_holder_->GetDocument().SetURL(KURL("https://example.com/foo"));
   monitor_ = MakeGarbageCollected<PerformanceMonitor>(GetFrame());
 
   // Create another dummy page holder and pretend this is the iframe.
-  another_page_holder_ = DummyPageHolder::Create(IntSize(400, 300));
+  another_page_holder_ = std::make_unique<DummyPageHolder>(IntSize(400, 300));
   another_page_holder_->GetDocument().SetURL(KURL("https://iframed.com/bar"));
 }
 
diff --git a/third_party/blink/renderer/core/frame/use_counter_test.cc b/third_party/blink/renderer/core/frame/use_counter_test.cc
index b0de1dd..c4f7be4 100644
--- a/third_party/blink/renderer/core/frame/use_counter_test.cc
+++ b/third_party/blink/renderer/core/frame/use_counter_test.cc
@@ -40,7 +40,7 @@
 
 class UseCounterTest : public testing::Test {
  public:
-  UseCounterTest() : dummy_(DummyPageHolder::Create()) {
+  UseCounterTest() : dummy_(std::make_unique<DummyPageHolder>()) {
     Page::InsertOrdinaryPageForTesting(&dummy_->GetPage());
   }
 
@@ -156,8 +156,7 @@
 }
 
 TEST_F(UseCounterTest, CSSSelectorPseudoWhere) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
   Document& document = dummy_page_holder->GetDocument();
   WebFeature feature = WebFeature::kCSSSelectorPseudoWhere;
@@ -181,8 +180,7 @@
  */
 
 TEST_F(UseCounterTest, CSSSelectorPseudoAnyLink) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
   Document& document = dummy_page_holder->GetDocument();
   WebFeature feature = WebFeature::kCSSSelectorPseudoAnyLink;
@@ -193,8 +191,7 @@
 }
 
 TEST_F(UseCounterTest, CSSSelectorPseudoWebkitAnyLink) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
   Document& document = dummy_page_holder->GetDocument();
   WebFeature feature = WebFeature::kCSSSelectorPseudoWebkitAnyLink;
@@ -213,8 +210,7 @@
 }
 
 TEST_F(UseCounterTest, CSSSelectorPseudoIs) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
   Document& document = dummy_page_holder->GetDocument();
   WebFeature feature = WebFeature::kCSSSelectorPseudoIs;
@@ -227,8 +223,7 @@
 }
 
 TEST_F(UseCounterTest, CSSContainLayoutNonPositionedDescendants) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
   Document& document = dummy_page_holder->GetDocument();
   WebFeature feature = WebFeature::kCSSContainLayoutPositionedDescendants;
@@ -241,8 +236,7 @@
 }
 
 TEST_F(UseCounterTest, CSSContainLayoutAbsolutelyPositionedDescendants) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
   Document& document = dummy_page_holder->GetDocument();
   WebFeature feature = WebFeature::kCSSContainLayoutPositionedDescendants;
@@ -257,8 +251,7 @@
 
 TEST_F(UseCounterTest,
        CSSContainLayoutAbsolutelyPositionedDescendantsAlreadyContainingBlock) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
   Document& document = dummy_page_holder->GetDocument();
   WebFeature feature = WebFeature::kCSSContainLayoutPositionedDescendants;
@@ -272,8 +265,7 @@
 }
 
 TEST_F(UseCounterTest, CSSContainLayoutFixedPositionedDescendants) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
   Document& document = dummy_page_holder->GetDocument();
   WebFeature feature = WebFeature::kCSSContainLayoutPositionedDescendants;
@@ -288,8 +280,7 @@
 
 TEST_F(UseCounterTest,
        CSSContainLayoutFixedPositionedDescendantsAlreadyContainingBlock) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
   Document& document = dummy_page_holder->GetDocument();
   WebFeature feature = WebFeature::kCSSContainLayoutPositionedDescendants;
@@ -303,8 +294,7 @@
 }
 
 TEST_F(UseCounterTest, CSSGridLayoutPercentageColumnIndefiniteWidth) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
   Document& document = dummy_page_holder->GetDocument();
   WebFeature feature = WebFeature::kGridRowTrackPercentIndefiniteHeight;
@@ -317,8 +307,7 @@
 }
 
 TEST_F(UseCounterTest, CSSGridLayoutPercentageRowIndefiniteHeight) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
   Document& document = dummy_page_holder->GetDocument();
   WebFeature feature = WebFeature::kGridRowTrackPercentIndefiniteHeight;
@@ -331,8 +320,7 @@
 }
 
 TEST_F(UseCounterTest, CSSFlexibleBox) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
   Document& document = dummy_page_holder->GetDocument();
   WebFeature feature = WebFeature::kCSSFlexibleBox;
@@ -344,8 +332,7 @@
 }
 
 TEST_F(UseCounterTest, CSSFlexibleBoxInline) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
   Document& document = dummy_page_holder->GetDocument();
   WebFeature feature = WebFeature::kCSSFlexibleBox;
@@ -359,8 +346,7 @@
 TEST_F(UseCounterTest, CSSFlexibleBoxButton) {
   // LayoutButton is a subclass of LayoutFlexibleBox, however we don't want it
   // to be counted as usage of flexboxes as it's an implementation detail.
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
   Document& document = dummy_page_holder->GetDocument();
   WebFeature feature = WebFeature::kCSSFlexibleBox;
@@ -373,7 +359,7 @@
 class DeprecationTest : public testing::Test {
  public:
   DeprecationTest()
-      : dummy_(DummyPageHolder::Create()),
+      : dummy_(std::make_unique<DummyPageHolder>()),
         deprecation_(dummy_->GetPage().GetDeprecation()),
         use_counter_(dummy_->GetDocument().Loader()->GetUseCounter()) {
     Page::InsertOrdinaryPageForTesting(&dummy_->GetPage());
@@ -421,8 +407,7 @@
 }
 
 TEST_F(UseCounterTest, CSSUnknownNamespacePrefixInSelector) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
   Document& document = dummy_page_holder->GetDocument();
   WebFeature feature = WebFeature::kCSSUnknownNamespacePrefixInSelector;
@@ -444,8 +429,7 @@
 }
 
 TEST_F(UseCounterTest, CSSSelectorHostContextInLiveProfile) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
   Document& document = dummy_page_holder->GetDocument();
   WebFeature feature = WebFeature::kCSSSelectorHostContextInLiveProfile;
@@ -477,8 +461,7 @@
 }
 
 TEST_F(UseCounterTest, CSSSelectorHostContextInSnapshotProfile) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
   Document& document = dummy_page_holder->GetDocument();
   WebFeature feature = WebFeature::kCSSSelectorHostContextInSnapshotProfile;
diff --git a/third_party/blink/renderer/core/html/custom/custom_element_test.cc b/third_party/blink/renderer/core/html/custom/custom_element_test.cc
index 349c793..1ea5764c 100644
--- a/third_party/blink/renderer/core/html/custom/custom_element_test.cc
+++ b/third_party/blink/renderer/core/html/custom/custom_element_test.cc
@@ -157,7 +157,7 @@
       "<div id=div></div>"
       "<a-a id=v1v0></a-a>"
       "<font-face id=v0></font-face>";
-  std::unique_ptr<DummyPageHolder> page_holder = DummyPageHolder::Create();
+  auto page_holder = std::make_unique<DummyPageHolder>();
   Document& document = page_holder->GetDocument();
   document.body()->SetInnerHTMLFromString(String::FromUTF8(body_content));
 
@@ -192,7 +192,7 @@
        Element::kV0WaitingForUpgrade},
       {"_-X", CustomElementState::kUncustomized, Element::kV0WaitingForUpgrade},
   };
-  std::unique_ptr<DummyPageHolder> page_holder = DummyPageHolder::Create();
+  auto page_holder = std::make_unique<DummyPageHolder>();
   Document& document = page_holder->GetDocument();
   for (const auto& data : create_element_data) {
     Element* element = document.CreateElementForBinding(data.name);
@@ -216,7 +216,7 @@
 TEST(CustomElementTest,
      CreateElement_TagNameCaseHandlingCreatingCustomElement) {
   // register a definition
-  std::unique_ptr<DummyPageHolder> holder(DummyPageHolder::Create());
+  auto holder(std::make_unique<DummyPageHolder>());
   ScriptState* script_state = ToScriptStateForMainWorld(&holder->GetFrame());
   CustomElementRegistry* registry =
       holder->GetFrame().DomWindow()->customElements();
diff --git a/third_party/blink/renderer/core/html/forms/file_input_type_test.cc b/third_party/blink/renderer/core/html/forms/file_input_type_test.cc
index 04c54f3..33f2d2a 100644
--- a/third_party/blink/renderer/core/html/forms/file_input_type_test.cc
+++ b/third_party/blink/renderer/core/html/forms/file_input_type_test.cc
@@ -129,7 +129,8 @@
   FillWithEmptyClients(page_clients);
   auto* chrome_client = MakeGarbageCollected<WebKitDirectoryChromeClient>();
   page_clients.chrome_client = chrome_client;
-  auto page_holder = DummyPageHolder::Create(IntSize(), &page_clients);
+  auto page_holder =
+      std::make_unique<DummyPageHolder>(IntSize(), &page_clients);
   Document& doc = page_holder->GetDocument();
 
   doc.body()->SetInnerHTMLFromString("<input type=file webkitdirectory>");
diff --git a/third_party/blink/renderer/core/html/forms/html_input_element.cc b/third_party/blink/renderer/core/html/forms/html_input_element.cc
index 76d81ef..2b6b90e5 100644
--- a/third_party/blink/renderer/core/html/forms/html_input_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_input_element.cc
@@ -1142,23 +1142,6 @@
 
   if (value_changed)
     NotifyFormStateChanged();
-
-  if (isConnected()) {
-    if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache()) {
-      auto* page = GetDocument().GetPage();
-      auto* view = GetDocument().View();
-      // Run the document lifecycle to ensure AX notifications fire,
-      // even if the value didn't change.
-      if (page && view) {
-        // TODO(aboxhall): add a lifecycle phase for accessibility updates.
-        if (!view->CanThrottleRendering())
-          page->Animator().ScheduleVisualUpdate(GetDocument().GetFrame());
-
-        GetDocument().Lifecycle().EnsureStateAtMost(
-            DocumentLifecycle::kVisualUpdatePending);
-      }
-    }
-  }
 }
 
 void HTMLInputElement::SetNonAttributeValue(const String& sanitized_value) {
diff --git a/third_party/blink/renderer/core/html/forms/html_input_element_test.cc b/third_party/blink/renderer/core/html/forms/html_input_element_test.cc
index 967bdeb..3cf36de5 100644
--- a/third_party/blink/renderer/core/html/forms/html_input_element_test.cc
+++ b/third_party/blink/renderer/core/html/forms/html_input_element_test.cc
@@ -104,7 +104,7 @@
       ->SetInnerHTMLFromString("<input type='range' />");
   document_without_frame->AppendChild(html);
 
-  std::unique_ptr<DummyPageHolder> page_holder = DummyPageHolder::Create();
+  auto page_holder = std::make_unique<DummyPageHolder>();
   auto& document = page_holder->GetDocument();
   EXPECT_NE(nullptr, document.GetPage());
 
diff --git a/third_party/blink/renderer/core/html/forms/internal_popup_menu_test.cc b/third_party/blink/renderer/core/html/forms/internal_popup_menu_test.cc
index 078c828..e2fb3ff 100644
--- a/third_party/blink/renderer/core/html/forms/internal_popup_menu_test.cc
+++ b/third_party/blink/renderer/core/html/forms/internal_popup_menu_test.cc
@@ -21,7 +21,8 @@
 #if !defined(OS_ANDROID)
 
 TEST(InternalPopupMenuTest, WriteDocumentInStyleDirtyTree) {
-  auto dummy_page_holder_ = DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder_ =
+      std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Document& document = dummy_page_holder_->GetDocument();
   document.body()->SetInnerHTMLFromString(R"HTML(
     <select id="select">
diff --git a/third_party/blink/renderer/core/html/forms/password_input_type_test.cc b/third_party/blink/renderer/core/html/forms/password_input_type_test.cc
index 7ed99e31..202bb99f 100644
--- a/third_party/blink/renderer/core/html/forms/password_input_type_test.cc
+++ b/third_party/blink/renderer/core/html/forms/password_input_type_test.cc
@@ -47,8 +47,7 @@
 // Tests that a Mojo message is sent when a password field is edited
 // on the page.
 TEST(PasswordInputTypeTest, DidEditFieldEvent) {
-  std::unique_ptr<DummyPageHolder> page_holder =
-      DummyPageHolder::Create(IntSize(2000, 2000));
+  auto page_holder = std::make_unique<DummyPageHolder>(IntSize(2000, 2000));
   MockInsecureInputService mock_service(page_holder->GetFrame());
   page_holder->GetDocument().body()->SetInnerHTMLFromString(
       "<input type='password'>");
@@ -69,8 +68,7 @@
 // Tests that a Mojo message is not sent when a password field is edited
 // in a secure context.
 TEST(PasswordInputTypeTest, DidEditFieldEventNotSentFromSecureContext) {
-  std::unique_ptr<DummyPageHolder> page_holder =
-      DummyPageHolder::Create(IntSize(2000, 2000));
+  auto page_holder = std::make_unique<DummyPageHolder>(IntSize(2000, 2000));
   MockInsecureInputService mock_service(page_holder->GetFrame());
   page_holder->GetDocument().SetURL(KURL("https://example.test"));
   page_holder->GetDocument().SetSecurityOrigin(
diff --git a/third_party/blink/renderer/core/html/forms/text_control_element_test.cc b/third_party/blink/renderer/core/html/forms/text_control_element_test.cc
index 8b55264a..5bc9d552 100644
--- a/third_party/blink/renderer/core/html/forms/text_control_element_test.cc
+++ b/third_party/blink/renderer/core/html/forms/text_control_element_test.cc
@@ -46,7 +46,7 @@
   Page::PageClients page_clients;
   FillWithEmptyClients(page_clients);
   dummy_page_holder_ =
-      DummyPageHolder::Create(IntSize(800, 600), &page_clients);
+      std::make_unique<DummyPageHolder>(IntSize(800, 600), &page_clients);
 
   document_ = &dummy_page_holder_->GetDocument();
   document_->documentElement()->SetInnerHTMLFromString(
diff --git a/third_party/blink/renderer/core/html/html_content_element_test.cc b/third_party/blink/renderer/core/html/html_content_element_test.cc
index a42f16d..765c54c 100644
--- a/third_party/blink/renderer/core/html/html_content_element_test.cc
+++ b/third_party/blink/renderer/core/html/html_content_element_test.cc
@@ -16,7 +16,7 @@
 class HTMLContentElementTest : public testing::Test {
  protected:
   void SetUp() final {
-    dummy_page_holder_ = DummyPageHolder::Create(IntSize(800, 600));
+    dummy_page_holder_ = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   }
   Document& GetDocument() { return dummy_page_holder_->GetDocument(); }
 
diff --git a/third_party/blink/renderer/core/html/html_image_element.cc b/third_party/blink/renderer/core/html/html_image_element.cc
index af20ff6..15c1a9b4 100644
--- a/third_party/blink/renderer/core/html/html_image_element.cc
+++ b/third_party/blink/renderer/core/html/html_image_element.cc
@@ -396,7 +396,7 @@
   const ContentData* content_data = style.GetContentData();
   if (content_data && content_data->IsImage()) {
     const StyleImage* content_image =
-        ToImageContentData(content_data)->GetImage();
+        To<ImageContentData>(content_data)->GetImage();
     bool error_occurred = content_image && content_image->CachedImage() &&
                           content_image->CachedImage()->ErrorOccurred();
     if (!error_occurred)
diff --git a/third_party/blink/renderer/core/html/html_object_element_test.cc b/third_party/blink/renderer/core/html/html_object_element_test.cc
index 1e73282..39e6603 100644
--- a/third_party/blink/renderer/core/html/html_object_element_test.cc
+++ b/third_party/blink/renderer/core/html/html_object_element_test.cc
@@ -16,7 +16,7 @@
 class HTMLObjectElementTest : public testing::Test {
  protected:
   void SetUp() final {
-    dummy_page_holder_ = DummyPageHolder::Create(IntSize(800, 600));
+    dummy_page_holder_ = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   }
   Document& GetDocument() { return dummy_page_holder_->GetDocument(); }
 
diff --git a/third_party/blink/renderer/core/html/html_slot_element_test.cc b/third_party/blink/renderer/core/html/html_slot_element_test.cc
index ea28042..d501b022 100644
--- a/third_party/blink/renderer/core/html/html_slot_element_test.cc
+++ b/third_party/blink/renderer/core/html/html_slot_element_test.cc
@@ -135,7 +135,7 @@
 class HTMLSlotElementReattachTest : public testing::Test {
  protected:
   void SetUp() final {
-    dummy_page_holder_ = DummyPageHolder::Create(IntSize(800, 600));
+    dummy_page_holder_ = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   }
   Document& GetDocument() { return dummy_page_holder_->GetDocument(); }
 
diff --git a/third_party/blink/renderer/core/html/image_document_test.cc b/third_party/blink/renderer/core/html/image_document_test.cc
index 38448b5..b618591 100644
--- a/third_party/blink/renderer/core/html/image_document_test.cc
+++ b/third_party/blink/renderer/core/html/image_document_test.cc
@@ -101,8 +101,8 @@
   FillWithEmptyClients(page_clients);
   chrome_client_ = MakeGarbageCollected<WindowToViewportScalingChromeClient>();
   page_clients.chrome_client = chrome_client_;
-  dummy_page_holder_ =
-      DummyPageHolder::Create(IntSize(view_width, view_height), &page_clients);
+  dummy_page_holder_ = std::make_unique<DummyPageHolder>(
+      IntSize(view_width, view_height), &page_clients);
 
   LocalFrame& frame = dummy_page_holder_->GetFrame();
   frame.GetDocument()->Shutdown();
diff --git a/third_party/blink/renderer/core/html/media/html_media_element.cc b/third_party/blink/renderer/core/html/media/html_media_element.cc
index 6d78810..002177e 100644
--- a/third_party/blink/renderer/core/html/media/html_media_element.cc
+++ b/third_party/blink/renderer/core/html/media/html_media_element.cc
@@ -608,7 +608,7 @@
 }
 
 bool HTMLMediaElement::IsMouseFocusable() const {
-  return !IsFullscreen();
+  return !IsFullscreen() && SupportsFocus();
 }
 
 void HTMLMediaElement::ParseAttribute(
diff --git a/third_party/blink/renderer/core/html/media/html_media_element_test.cc b/third_party/blink/renderer/core/html/media/html_media_element_test.cc
index 8e1e9c1d1..29146d1 100644
--- a/third_party/blink/renderer/core/html/media/html_media_element_test.cc
+++ b/third_party/blink/renderer/core/html/media/html_media_element_test.cc
@@ -91,7 +91,7 @@
     EXPECT_CALL(*mock_media_player, DidLazyLoad)
         .WillRepeatedly(testing::Return(false));
 
-    dummy_page_holder_ = DummyPageHolder::Create(
+    dummy_page_holder_ = std::make_unique<DummyPageHolder>(
         IntSize(), nullptr,
         WebMediaStubLocalFrameClient::Create(std::move(mock_media_player)),
         nullptr);
diff --git a/third_party/blink/renderer/core/html/media/media_custom_controls_fullscreen_detector_test.cc b/third_party/blink/renderer/core/html/media/media_custom_controls_fullscreen_detector_test.cc
index c8616b2..72ec1eb 100644
--- a/third_party/blink/renderer/core/html/media/media_custom_controls_fullscreen_detector_test.cc
+++ b/third_party/blink/renderer/core/html/media/media_custom_controls_fullscreen_detector_test.cc
@@ -32,9 +32,8 @@
 
  protected:
   void SetUp() override {
-
-    page_holder_ = DummyPageHolder::Create();
-    new_page_holder_ = DummyPageHolder::Create();
+    page_holder_ = std::make_unique<DummyPageHolder>();
+    new_page_holder_ = std::make_unique<DummyPageHolder>();
   }
 
   HTMLVideoElement* VideoElement() const {
diff --git a/third_party/blink/renderer/core/html/shadow/progress_shadow_element_test.cc b/third_party/blink/renderer/core/html/shadow/progress_shadow_element_test.cc
index 1dd6ea5..cb7f5beb 100644
--- a/third_party/blink/renderer/core/html/shadow/progress_shadow_element_test.cc
+++ b/third_party/blink/renderer/core/html/shadow/progress_shadow_element_test.cc
@@ -17,7 +17,7 @@
 class ProgressShadowElementTest : public testing::Test {
  protected:
   void SetUp() final {
-    dummy_page_holder_ = DummyPageHolder::Create(IntSize(800, 600));
+    dummy_page_holder_ = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   }
   Document& GetDocument() { return dummy_page_holder_->GetDocument(); }
 
diff --git a/third_party/blink/renderer/core/html/track/text_track_list_test.cc b/third_party/blink/renderer/core/html/track/text_track_list_test.cc
index 96c23ae..ea1c312 100644
--- a/third_party/blink/renderer/core/html/track/text_track_list_test.cc
+++ b/third_party/blink/renderer/core/html/track/text_track_list_test.cc
@@ -13,8 +13,8 @@
 
 TEST(TextTrackListTest, InvalidateTrackIndexes) {
   // Create and fill the list
-  TextTrackList* list = TextTrackList::Create(
-      HTMLVideoElement::Create(DummyPageHolder::Create()->GetDocument()));
+  TextTrackList* list = TextTrackList::Create(HTMLVideoElement::Create(
+      std::make_unique<DummyPageHolder>()->GetDocument()));
   const size_t kNumTextTracks = 4;
   TextTrack* text_tracks[kNumTextTracks];
   for (size_t i = 0; i < kNumTextTracks; ++i) {
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index 3d88e8a..9d4a5de 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -225,7 +225,7 @@
     // should be run once at layoutObject creation.
     image->SetStyleInternal(const_cast<ComputedStyle*>(&style));
     if (const StyleImage* style_image =
-            ToImageContentData(content_data)->GetImage()) {
+            To<ImageContentData>(content_data)->GetImage()) {
       image->SetImageResource(LayoutImageResourceStyleImage::Create(
           const_cast<StyleImage*>(style_image)));
       image->SetIsGeneratedContent();
@@ -2210,12 +2210,12 @@
   StyleImage* old_content_image =
       old_style && old_style->GetContentData() &&
               old_style->GetContentData()->IsImage()
-          ? ToImageContentData(old_style->GetContentData())->GetImage()
+          ? To<ImageContentData>(old_style->GetContentData())->GetImage()
           : nullptr;
   StyleImage* new_content_image =
       new_style && new_style->GetContentData() &&
               new_style->GetContentData()->IsImage()
-          ? ToImageContentData(new_style->GetContentData())->GetImage()
+          ? To<ImageContentData>(new_style->GetContentData())->GetImage()
           : nullptr;
   UpdateImage(old_content_image, new_content_image);
 
diff --git a/third_party/blink/renderer/core/loader/document_load_timing_test.cc b/third_party/blink/renderer/core/loader/document_load_timing_test.cc
index b3aaa33..a524ad0 100644
--- a/third_party/blink/renderer/core/loader/document_load_timing_test.cc
+++ b/third_party/blink/renderer/core/loader/document_load_timing_test.cc
@@ -14,7 +14,7 @@
 class DocumentLoadTimingTest : public testing::Test {};
 
 TEST_F(DocumentLoadTimingTest, ensureValidNavigationStartAfterEmbedder) {
-  std::unique_ptr<DummyPageHolder> dummy_page = DummyPageHolder::Create();
+  auto dummy_page = std::make_unique<DummyPageHolder>();
   DocumentLoadTiming timing(*(dummy_page->GetDocument().Loader()));
 
   double delta = -1000;
@@ -30,7 +30,7 @@
 }
 
 TEST_F(DocumentLoadTimingTest, correctTimingDeltas) {
-  std::unique_ptr<DummyPageHolder> dummy_page = DummyPageHolder::Create();
+  auto dummy_page = std::make_unique<DummyPageHolder>();
   DocumentLoadTiming timing(*(dummy_page->GetDocument().Loader()));
 
   double navigation_start_delta = -456;
@@ -61,7 +61,7 @@
 TEST_F(DocumentLoadTimingTest, ensureRedirectEndExcludesNextFetch) {
   // Regression test for https://crbug.com/823254.
 
-  std::unique_ptr<DummyPageHolder> dummy_page = DummyPageHolder::Create();
+  auto dummy_page = std::make_unique<DummyPageHolder>();
   DocumentLoadTiming timing(*(dummy_page->GetDocument().Loader()));
 
   base::TimeTicks origin;
diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context_test.cc b/third_party/blink/renderer/core/loader/frame_fetch_context_test.cc
index 9ca08af..b8fceadd 100644
--- a/third_party/blink/renderer/core/loader/frame_fetch_context_test.cc
+++ b/third_party/blink/renderer/core/loader/frame_fetch_context_test.cc
@@ -139,7 +139,7 @@
   void SetUp() override { RecreateFetchContext(); }
 
   void RecreateFetchContext() {
-    dummy_page_holder = DummyPageHolder::Create(IntSize(500, 500));
+    dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(500, 500));
     dummy_page_holder->GetPage().SetDeviceScaleFactorDeprecated(1.0);
     document = &dummy_page_holder->GetDocument();
     owner = DummyFrameOwner::Create();
@@ -290,7 +290,7 @@
     client = MakeGarbageCollected<
         testing::NiceMock<FrameFetchContextMockLocalFrameClient>>();
     dummy_page_holder =
-        DummyPageHolder::Create(IntSize(500, 500), nullptr, client);
+        std::make_unique<DummyPageHolder>(IntSize(500, 500), nullptr, client);
     dummy_page_holder->GetPage().SetDeviceScaleFactorDeprecated(1.0);
     Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
     document = &dummy_page_holder->GetDocument();
diff --git a/third_party/blink/renderer/core/loader/image_loader.cc b/third_party/blink/renderer/core/loader/image_loader.cc
index 90706eb7c..f9774028 100644
--- a/third_party/blink/renderer/core/loader/image_loader.cc
+++ b/third_party/blink/renderer/core/loader/image_loader.cc
@@ -155,15 +155,6 @@
 
 class ImageLoader::Task {
  public:
-  static std::unique_ptr<Task> Create(
-      ImageLoader* loader,
-      const KURL& request_url,
-      UpdateFromElementBehavior update_behavior,
-      network::mojom::ReferrerPolicy referrer_policy) {
-    return std::make_unique<Task>(loader, request_url, update_behavior,
-                                  referrer_policy);
-  }
-
   Task(ImageLoader* loader,
        const KURL& request_url,
        UpdateFromElementBehavior update_behavior,
@@ -449,8 +440,8 @@
     const KURL& request_url,
     UpdateFromElementBehavior update_behavior,
     network::mojom::ReferrerPolicy referrer_policy) {
-  std::unique_ptr<Task> task =
-      Task::Create(this, request_url, update_behavior, referrer_policy);
+  auto task = std::make_unique<Task>(this, request_url, update_behavior,
+                                     referrer_policy);
   pending_task_ = task->GetWeakPtr();
   Microtask::EnqueueMicrotask(
       WTF::Bind(&Task::Run, WTF::Passed(std::move(task))));
diff --git a/third_party/blink/renderer/core/loader/interactive_detector_test.cc b/third_party/blink/renderer/core/loader/interactive_detector_test.cc
index dee4e03b..f0e2cba2 100644
--- a/third_party/blink/renderer/core/loader/interactive_detector_test.cc
+++ b/third_party/blink/renderer/core/loader/interactive_detector_test.cc
@@ -39,7 +39,7 @@
  public:
   InteractiveDetectorTest() {
     platform_->AdvanceClockSeconds(1);
-    dummy_page_holder_ = DummyPageHolder::Create();
+    dummy_page_holder_ = std::make_unique<DummyPageHolder>();
 
     Document* document = &dummy_page_holder_->GetDocument();
 
diff --git a/third_party/blink/renderer/core/loader/link_loader_test.cc b/third_party/blink/renderer/core/loader/link_loader_test.cc
index 31c0c11..61f68dc 100644
--- a/third_party/blink/renderer/core/loader/link_loader_test.cc
+++ b/third_party/blink/renderer/core/loader/link_loader_test.cc
@@ -122,7 +122,7 @@
   };
 
   LinkLoaderPreloadTestBase() {
-    dummy_page_holder_ = DummyPageHolder::Create(IntSize(500, 500));
+    dummy_page_holder_ = std::make_unique<DummyPageHolder>(IntSize(500, 500));
   }
 
   ~LinkLoaderPreloadTestBase() override {
@@ -527,8 +527,7 @@
 
 TEST_P(LinkLoaderModulePreloadTest, ModulePreload) {
   const auto& test_case = GetParam();
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create();
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>();
   ModulePreloadTestModulator* modulator =
       MakeGarbageCollected<ModulePreloadTestModulator>(&test_case);
   Modulator::SetModulator(
@@ -582,8 +581,8 @@
 
   // Test the cases with a single header
   for (const auto& test_case : cases) {
-    std::unique_ptr<DummyPageHolder> dummy_page_holder =
-        DummyPageHolder::Create(IntSize(500, 500));
+    auto dummy_page_holder =
+        std::make_unique<DummyPageHolder>(IntSize(500, 500));
     dummy_page_holder->GetFrame().GetSettings()->SetScriptEnabled(true);
     Persistent<MockLinkLoaderClient> loader_client =
         MockLinkLoaderClient::Create(test_case.link_loader_should_load_value);
@@ -630,8 +629,8 @@
   // Test the cases with a single header
   for (const auto& test_case : cases) {
     platform_->GetMockPrescientNetworking().Reset();
-    std::unique_ptr<DummyPageHolder> dummy_page_holder =
-        DummyPageHolder::Create(IntSize(500, 500));
+    auto dummy_page_holder =
+        std::make_unique<DummyPageHolder>(IntSize(500, 500));
     dummy_page_holder->GetDocument().GetSettings()->SetDNSPrefetchingEnabled(
         true);
     Persistent<MockLinkLoaderClient> loader_client =
@@ -669,8 +668,8 @@
   // Test the cases with a single header
   for (const auto& test_case : cases) {
     platform_->GetMockPrescientNetworking().Reset();
-    std::unique_ptr<DummyPageHolder> dummy_page_holder =
-        DummyPageHolder::Create(IntSize(500, 500));
+    auto dummy_page_holder =
+        std::make_unique<DummyPageHolder>(IntSize(500, 500));
     Persistent<MockLinkLoaderClient> loader_client =
         MockLinkLoaderClient::Create(test_case.should_load);
     LinkLoader* loader = LinkLoader::Create(loader_client.Get());
@@ -696,8 +695,7 @@
 }
 
 TEST_F(LinkLoaderTest, PreloadAndPrefetch) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(500, 500));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(500, 500));
   ResourceFetcher* fetcher = dummy_page_holder->GetDocument().Fetcher();
   ASSERT_TRUE(fetcher);
   dummy_page_holder->GetFrame().GetSettings()->SetScriptEnabled(true);
diff --git a/third_party/blink/renderer/core/loader/mixed_content_checker_test.cc b/third_party/blink/renderer/core/loader/mixed_content_checker_test.cc
index d42a72b..6afe18f 100644
--- a/third_party/blink/renderer/core/loader/mixed_content_checker_test.cc
+++ b/third_party/blink/renderer/core/loader/mixed_content_checker_test.cc
@@ -70,8 +70,7 @@
 }
 
 TEST(MixedContentCheckerTest, ContextTypeForInspector) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(1, 1));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(1, 1));
   dummy_page_holder->GetFrame().GetDocument()->SetSecurityOrigin(
       SecurityOrigin::CreateFromString("http://example.test"));
 
@@ -116,8 +115,8 @@
 TEST(MixedContentCheckerTest, HandleCertificateError) {
   MixedContentCheckerMockLocalFrameClient* client =
       MakeGarbageCollected<MixedContentCheckerMockLocalFrameClient>();
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(1, 1), nullptr, client);
+  auto dummy_page_holder =
+      std::make_unique<DummyPageHolder>(IntSize(1, 1), nullptr, client);
 
   KURL main_resource_url(NullURL(), "https://example.test");
   KURL displayed_url(NullURL(), "https://example-displayed.test");
@@ -146,8 +145,8 @@
 TEST(MixedContentCheckerTest, DetectMixedForm) {
   MixedContentCheckerMockLocalFrameClient* client =
       MakeGarbageCollected<MixedContentCheckerMockLocalFrameClient>();
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(1, 1), nullptr, client);
+  auto dummy_page_holder =
+      std::make_unique<DummyPageHolder>(IntSize(1, 1), nullptr, client);
 
   KURL main_resource_url(NullURL(), "https://example.test/");
 
@@ -179,8 +178,8 @@
 TEST(MixedContentCheckerTest, DetectMixedFavicon) {
   MixedContentCheckerMockLocalFrameClient* client =
       MakeGarbageCollected<MixedContentCheckerMockLocalFrameClient>();
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(1, 1), nullptr, client);
+  auto dummy_page_holder =
+      std::make_unique<DummyPageHolder>(IntSize(1, 1), nullptr, client);
   dummy_page_holder->GetFrame().GetSettings()->SetAllowRunningOfInsecureContent(
       false);
 
diff --git a/third_party/blink/renderer/core/loader/previews_resource_loading_hints_test.cc b/third_party/blink/renderer/core/loader/previews_resource_loading_hints_test.cc
index 03869ad..d2380c7 100644
--- a/third_party/blink/renderer/core/loader/previews_resource_loading_hints_test.cc
+++ b/third_party/blink/renderer/core/loader/previews_resource_loading_hints_test.cc
@@ -32,7 +32,7 @@
 class PreviewsResourceLoadingHintsTest : public PageTestBase {
  public:
   PreviewsResourceLoadingHintsTest() {
-    dummy_page_holder_ = DummyPageHolder::Create(IntSize(1, 1));
+    dummy_page_holder_ = std::make_unique<DummyPageHolder>(IntSize(1, 1));
   }
 
  protected:
diff --git a/third_party/blink/renderer/core/loader/resource/font_resource_test.cc b/third_party/blink/renderer/core/loader/resource/font_resource_test.cc
index 5659be4a..6137615a 100644
--- a/third_party/blink/renderer/core/loader/resource/font_resource_test.cc
+++ b/third_party/blink/renderer/core/loader/resource/font_resource_test.cc
@@ -107,8 +107,7 @@
   Platform::Current()->GetURLLoaderMockFactory()->RegisterURL(
       url, WrappedResourceResponse(response), "");
 
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Document& document = dummy_page_holder->GetDocument();
   ResourceFetcher* fetcher = document.Fetcher();
   CSSFontFaceSrcValue* src_value = CSSFontFaceSrcValue::Create(
diff --git a/third_party/blink/renderer/core/loader/resource/image_resource_test.cc b/third_party/blink/renderer/core/loader/resource/image_resource_test.cc
index 7e230378..3b004bf 100644
--- a/third_party/blink/renderer/core/loader/resource/image_resource_test.cc
+++ b/third_party/blink/renderer/core/loader/resource/image_resource_test.cc
@@ -1861,8 +1861,10 @@
   Page::PageClients clients;
   FillWithEmptyClients(clients);
   clients.chrome_client = chrome_client;
-  std::unique_ptr<DummyPageHolder> page_holder = DummyPageHolder::Create(
-      IntSize(800, 600), &clients, EmptyLocalFrameClient::Create(), nullptr);
+  std::unique_ptr<DummyPageHolder> page_holder =
+      std::make_unique<DummyPageHolder>(IntSize(800, 600), &clients,
+                                        EmptyLocalFrameClient::Create(),
+                                        nullptr);
 
   KURL test_url(kTestURL);
   ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath());
diff --git a/third_party/blink/renderer/core/loader/text_resource_decoder_builder_test.cc b/third_party/blink/renderer/core/loader/text_resource_decoder_builder_test.cc
index ccb0bed..95db779 100644
--- a/third_party/blink/renderer/core/loader/text_resource_decoder_builder_test.cc
+++ b/third_party/blink/renderer/core/loader/text_resource_decoder_builder_test.cc
@@ -13,8 +13,7 @@
 static const WTF::TextEncoding DefaultEncodingForUrlAndContentType(
     const char* url,
     const char* content_type) {
-  std::unique_ptr<DummyPageHolder> page_holder =
-      DummyPageHolder::Create(IntSize(0, 0));
+  auto page_holder = std::make_unique<DummyPageHolder>(IntSize(0, 0));
   Document& document = page_holder->GetDocument();
   document.SetURL(KURL(NullURL(), url));
   return BuildTextResourceDecoderFor(&document, content_type, g_null_atom)
diff --git a/third_party/blink/renderer/core/loader/threadable_loader_test.cc b/third_party/blink/renderer/core/loader/threadable_loader_test.cc
index 16c40a6f..7a45776a 100644
--- a/third_party/blink/renderer/core/loader/threadable_loader_test.cc
+++ b/third_party/blink/renderer/core/loader/threadable_loader_test.cc
@@ -159,7 +159,7 @@
 class ThreadableLoaderTestHelper final {
  public:
   ThreadableLoaderTestHelper()
-      : dummy_page_holder_(DummyPageHolder::Create(IntSize(1, 1))) {
+      : dummy_page_holder_(std::make_unique<DummyPageHolder>(IntSize(1, 1))) {
     GetDocument().SetURL(KURL("http://fake.url/"));
     GetDocument().SetSecurityOrigin(
         SecurityOrigin::Create(KURL("http://fake.url/")));
diff --git a/third_party/blink/renderer/core/origin_trials/origin_trial_context_test.cc b/third_party/blink/renderer/core/origin_trials/origin_trial_context_test.cc
index 36e5d774..f2a8c38f 100644
--- a/third_party/blink/renderer/core/origin_trials/origin_trial_context_test.cc
+++ b/third_party/blink/renderer/core/origin_trials/origin_trial_context_test.cc
@@ -226,7 +226,7 @@
 
 TEST_F(OriginTrialContextTest, FeaturePolicy) {
   // Create a dummy document with an OriginTrialContext.
-  std::unique_ptr<DummyPageHolder> dummy = DummyPageHolder::Create();
+  auto dummy = std::make_unique<DummyPageHolder>();
   Document* document = &dummy->GetDocument();
   OriginTrialContext* context = OriginTrialContext::FromOrCreate(document);
 
diff --git a/third_party/blink/renderer/core/page/scrolling/snap_coordinator_test.cc b/third_party/blink/renderer/core/page/scrolling/snap_coordinator_test.cc
index ac0ba717..3a527ee 100644
--- a/third_party/blink/renderer/core/page/scrolling/snap_coordinator_test.cc
+++ b/third_party/blink/renderer/core/page/scrolling/snap_coordinator_test.cc
@@ -26,7 +26,7 @@
 class SnapCoordinatorTest : public testing::Test {
  protected:
   void SetUp() override {
-    page_holder_ = DummyPageHolder::Create();
+    page_holder_ = std::make_unique<DummyPageHolder>();
 
     SetHTML(R"HTML(
       <style>
diff --git a/third_party/blink/renderer/core/page/slot_scoped_traversal_test.cc b/third_party/blink/renderer/core/page/slot_scoped_traversal_test.cc
index 814ac9d..0a945e40 100644
--- a/third_party/blink/renderer/core/page/slot_scoped_traversal_test.cc
+++ b/third_party/blink/renderer/core/page/slot_scoped_traversal_test.cc
@@ -39,7 +39,7 @@
 };
 
 void SlotScopedTraversalTest::SetUp() {
-  dummy_page_holder_ = DummyPageHolder::Create(IntSize(800, 600));
+  dummy_page_holder_ = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   document_ = &dummy_page_holder_->GetDocument();
   DCHECK(document_);
 }
diff --git a/third_party/blink/renderer/core/script/module_script_test.cc b/third_party/blink/renderer/core/script/module_script_test.cc
index 86fc9a2..e57fcdf 100644
--- a/third_party/blink/renderer/core/script/module_script_test.cc
+++ b/third_party/blink/renderer/core/script/module_script_test.cc
@@ -47,11 +47,6 @@
 
 class MockCachedMetadataSender : public CachedMetadataSender {
  public:
-  static std::unique_ptr<MockCachedMetadataSender> Create() {
-    return base::WrapUnique(
-        new ::testing::StrictMock<MockCachedMetadataSender>);
-  }
-
   MockCachedMetadataSender() = default;
 
   MOCK_METHOD2(Send, void(const uint8_t*, size_t));
@@ -122,8 +117,7 @@
       MakeGarbageCollected<ModuleScriptTestModulator>(scope.GetScriptState());
   Modulator::SetModulator(scope.GetScriptState(), modulator);
 
-  std::unique_ptr<MockCachedMetadataSender> sender =
-      MockCachedMetadataSender::Create();
+  auto sender = std::make_unique<MockCachedMetadataSender>();
   MockCachedMetadataSender* sender_ptr = sender.get();
   SingleCachedMetadataHandler* cache_handler =
       MakeGarbageCollected<ScriptCachedMetadataHandler>(UTF8Encoding(),
diff --git a/third_party/blink/renderer/core/style/computed_style.cc b/third_party/blink/renderer/core/style/computed_style.cc
index d6377d2..a8db95e 100644
--- a/third_party/blink/renderer/core/style/computed_style.cc
+++ b/third_party/blink/renderer/core/style/computed_style.cc
@@ -1701,7 +1701,7 @@
   std::unique_ptr<StyleNonInheritedVariables>& variables =
       MutableNonInheritedVariablesInternal();
   if (!variables)
-    variables = StyleNonInheritedVariables::Create();
+    variables = std::make_unique<StyleNonInheritedVariables>();
   return *variables;
 }
 
diff --git a/third_party/blink/renderer/core/style/content_data.h b/third_party/blink/renderer/core/style/content_data.h
index b4e8924..1d113d2 100644
--- a/third_party/blink/renderer/core/style/content_data.h
+++ b/third_party/blink/renderer/core/style/content_data.h
@@ -31,6 +31,7 @@
 
 #include "third_party/blink/renderer/core/style/counter_content.h"
 #include "third_party/blink/renderer/core/style/style_image.h"
+#include "third_party/blink/renderer/platform/wtf/casting.h"
 
 namespace blink {
 
@@ -70,10 +71,6 @@
   Member<ContentData> next_;
 };
 
-#define DEFINE_CONTENT_DATA_TYPE_CASTS(typeName)                 \
-  DEFINE_TYPE_CASTS(typeName##ContentData, ContentData, content, \
-                    content->Is##typeName(), content.Is##typeName())
-
 class ImageContentData final : public ContentData {
   friend class ContentData;
 
@@ -109,7 +106,12 @@
   Member<StyleImage> image_;
 };
 
-DEFINE_CONTENT_DATA_TYPE_CASTS(Image);
+template <>
+struct DowncastTraits<ImageContentData> {
+  static bool AllowFrom(const ContentData& content) {
+    return content.IsImage();
+  }
+};
 
 class TextContentData final : public ContentData {
   friend class ContentData;
@@ -136,7 +138,10 @@
   String text_;
 };
 
-DEFINE_CONTENT_DATA_TYPE_CASTS(Text);
+template <>
+struct DowncastTraits<TextContentData> {
+  static bool AllowFrom(const ContentData& content) { return content.IsText(); }
+};
 
 class CounterContentData final : public ContentData {
   friend class ContentData;
@@ -171,7 +176,12 @@
   std::unique_ptr<CounterContent> counter_;
 };
 
-DEFINE_CONTENT_DATA_TYPE_CASTS(Counter);
+template <>
+struct DowncastTraits<CounterContentData> {
+  static bool AllowFrom(const ContentData& content) {
+    return content.IsCounter();
+  }
+};
 
 class QuoteContentData final : public ContentData {
   friend class ContentData;
@@ -198,7 +208,12 @@
   QuoteType quote_;
 };
 
-DEFINE_CONTENT_DATA_TYPE_CASTS(Quote);
+template <>
+struct DowncastTraits<QuoteContentData> {
+  static bool AllowFrom(const ContentData& content) {
+    return content.IsQuote();
+  }
+};
 
 inline bool operator==(const ContentData& a, const ContentData& b) {
   const ContentData* ptr_a = &a;
diff --git a/third_party/blink/renderer/core/style/style_non_inherited_variables.h b/third_party/blink/renderer/core/style/style_non_inherited_variables.h
index 30612c0..453d867d 100644
--- a/third_party/blink/renderer/core/style/style_non_inherited_variables.h
+++ b/third_party/blink/renderer/core/style/style_non_inherited_variables.h
@@ -23,14 +23,13 @@
   USING_FAST_MALLOC(StyleNonInheritedVariables);
 
  public:
-  static std::unique_ptr<StyleNonInheritedVariables> Create() {
-    return base::WrapUnique(new StyleNonInheritedVariables);
-  }
-
   std::unique_ptr<StyleNonInheritedVariables> Clone() {
     return base::WrapUnique(new StyleNonInheritedVariables(*this));
   }
 
+  StyleNonInheritedVariables();
+  explicit StyleNonInheritedVariables(StyleNonInheritedVariables&);
+
   bool operator==(const StyleNonInheritedVariables& other) const;
   bool operator!=(const StyleNonInheritedVariables& other) const {
     return !(*this == other);
@@ -57,9 +56,6 @@
   void ClearNeedsResolution() { needs_resolution_ = false; }
 
  private:
-  StyleNonInheritedVariables();
-  StyleNonInheritedVariables(StyleNonInheritedVariables&);
-
   friend class CSSVariableResolver;
 
   HashMap<AtomicString, scoped_refptr<CSSVariableData>> data_;
diff --git a/third_party/blink/renderer/core/svg/unsafe_svg_attribute_sanitization_test.cc b/third_party/blink/renderer/core/svg/unsafe_svg_attribute_sanitization_test.cc
index 3f7faec7..e831d5893 100644
--- a/third_party/blink/renderer/core/svg/unsafe_svg_attribute_sanitization_test.cc
+++ b/third_party/blink/renderer/core/svg/unsafe_svg_attribute_sanitization_test.cc
@@ -82,8 +82,7 @@
 // Integration tests.
 
 TEST(UnsafeSVGAttributeSanitizationTest, pasteAnchor_javaScriptHrefIsStripped) {
-  std::unique_ptr<DummyPageHolder> page_holder =
-      DummyPageHolder::Create(IntSize(1, 1));
+  auto page_holder = std::make_unique<DummyPageHolder>(IntSize(1, 1));
   static const char kUnsafeContent[] =
       "<svg xmlns='http://www.w3.org/2000/svg' "
       "     width='1cm' height='1cm'>"
@@ -103,8 +102,7 @@
 
 TEST(UnsafeSVGAttributeSanitizationTest,
      pasteAnchor_javaScriptXlinkHrefIsStripped) {
-  std::unique_ptr<DummyPageHolder> page_holder =
-      DummyPageHolder::Create(IntSize(1, 1));
+  auto page_holder = std::make_unique<DummyPageHolder>(IntSize(1, 1));
   static const char kUnsafeContent[] =
       "<svg xmlns='http://www.w3.org/2000/svg' "
       "     xmlns:xlink='http://www.w3.org/1999/xlink'"
@@ -125,8 +123,7 @@
 
 TEST(UnsafeSVGAttributeSanitizationTest,
      pasteAnchor_javaScriptHrefIsStripped_caseAndEntityInProtocol) {
-  std::unique_ptr<DummyPageHolder> page_holder =
-      DummyPageHolder::Create(IntSize(1, 1));
+  auto page_holder = std::make_unique<DummyPageHolder>(IntSize(1, 1));
   static const char kUnsafeContent[] =
       "<svg xmlns='http://www.w3.org/2000/svg' "
       "     width='1cm' height='1cm'>"
@@ -146,8 +143,7 @@
 
 TEST(UnsafeSVGAttributeSanitizationTest,
      pasteAnchor_javaScriptXlinkHrefIsStripped_caseAndEntityInProtocol) {
-  std::unique_ptr<DummyPageHolder> page_holder =
-      DummyPageHolder::Create(IntSize(1, 1));
+  auto page_holder = std::make_unique<DummyPageHolder>(IntSize(1, 1));
   static const char kUnsafeContent[] =
       "<svg xmlns='http://www.w3.org/2000/svg' "
       "     xmlns:xlink='http://www.w3.org/1999/xlink'"
@@ -168,8 +164,7 @@
 
 TEST(UnsafeSVGAttributeSanitizationTest,
      pasteAnchor_javaScriptHrefIsStripped_entityWithoutSemicolonInProtocol) {
-  std::unique_ptr<DummyPageHolder> page_holder =
-      DummyPageHolder::Create(IntSize(1, 1));
+  auto page_holder = std::make_unique<DummyPageHolder>(IntSize(1, 1));
   static const char kUnsafeContent[] =
       "<svg xmlns='http://www.w3.org/2000/svg' "
       "     width='1cm' height='1cm'>"
@@ -190,8 +185,7 @@
 TEST(
     UnsafeSVGAttributeSanitizationTest,
     pasteAnchor_javaScriptXlinkHrefIsStripped_entityWithoutSemicolonInProtocol) {
-  std::unique_ptr<DummyPageHolder> page_holder =
-      DummyPageHolder::Create(IntSize(1, 1));
+  auto page_holder = std::make_unique<DummyPageHolder>(IntSize(1, 1));
   static const char kUnsafeContent[] =
       "<svg xmlns='http://www.w3.org/2000/svg' "
       "     xmlns:xlink='http://www.w3.org/1999/xlink'"
@@ -217,8 +211,7 @@
 // web tests: there is nowhere to source the unsafe content from.
 TEST(UnsafeSVGAttributeSanitizationTest,
      pasteAnimatedAnchor_javaScriptHrefIsStripped_caseAndEntityInProtocol) {
-  std::unique_ptr<DummyPageHolder> page_holder =
-      DummyPageHolder::Create(IntSize(1, 1));
+  auto page_holder = std::make_unique<DummyPageHolder>(IntSize(1, 1));
   static const char kUnsafeContent[] =
       "<svg xmlns='http://www.w3.org/2000/svg' "
       "     width='1cm' height='1cm'>"
@@ -241,8 +234,7 @@
 TEST(
     UnsafeSVGAttributeSanitizationTest,
     pasteAnimatedAnchor_javaScriptXlinkHrefIsStripped_caseAndEntityInProtocol) {
-  std::unique_ptr<DummyPageHolder> page_holder =
-      DummyPageHolder::Create(IntSize(1, 1));
+  auto page_holder = std::make_unique<DummyPageHolder>(IntSize(1, 1));
   static const char kUnsafeContent[] =
       "<svg xmlns='http://www.w3.org/2000/svg' "
       "     xmlns:xlink='http://www.w3.org/1999/xlink'"
diff --git a/third_party/blink/renderer/core/testing/dummy_page_holder.cc b/third_party/blink/renderer/core/testing/dummy_page_holder.cc
index fa8ee152..85e5db0 100644
--- a/third_party/blink/renderer/core/testing/dummy_page_holder.cc
+++ b/third_party/blink/renderer/core/testing/dummy_page_holder.cc
@@ -57,15 +57,6 @@
 
 }  // namespace
 
-std::unique_ptr<DummyPageHolder> DummyPageHolder::Create(
-    const IntSize& initial_view_size,
-    Page::PageClients* page_clients,
-    LocalFrameClient* local_frame_client,
-    FrameSettingOverrideFunction setting_overrider) {
-  return base::WrapUnique(new DummyPageHolder(
-      initial_view_size, page_clients, local_frame_client, setting_overrider));
-}
-
 DummyPageHolder::DummyPageHolder(
     const IntSize& initial_view_size,
     Page::PageClients* page_clients_argument,
diff --git a/third_party/blink/renderer/core/testing/dummy_page_holder.h b/third_party/blink/renderer/core/testing/dummy_page_holder.h
index edb29e145..fc6ef6c 100644
--- a/third_party/blink/renderer/core/testing/dummy_page_holder.h
+++ b/third_party/blink/renderer/core/testing/dummy_page_holder.h
@@ -66,11 +66,10 @@
   USING_FAST_MALLOC(DummyPageHolder);
 
  public:
-  static std::unique_ptr<DummyPageHolder> Create(
-      const IntSize& initial_view_size = IntSize(),
-      Page::PageClients* = nullptr,
-      LocalFrameClient* = nullptr,
-      FrameSettingOverrideFunction = nullptr);
+  DummyPageHolder(const IntSize& initial_view_size = IntSize(),
+                  Page::PageClients* = nullptr,
+                  LocalFrameClient* = nullptr,
+                  FrameSettingOverrideFunction setting_overrider = nullptr);
   ~DummyPageHolder();
 
   Page& GetPage() const;
@@ -79,11 +78,6 @@
   Document& GetDocument() const;
 
  private:
-  DummyPageHolder(const IntSize& initial_view_size,
-                  Page::PageClients*,
-                  LocalFrameClient*,
-                  FrameSettingOverrideFunction setting_overrider);
-
   Persistent<Page> page_;
 
   // The LocalFrame is accessed from worker threads by unit tests
diff --git a/third_party/blink/renderer/core/testing/page_test_base.cc b/third_party/blink/renderer/core/testing/page_test_base.cc
index 8c8e8b25..f4745dc 100644
--- a/third_party/blink/renderer/core/testing/page_test_base.cc
+++ b/third_party/blink/renderer/core/testing/page_test_base.cc
@@ -22,7 +22,7 @@
 
 void PageTestBase::SetUp() {
   DCHECK(!dummy_page_holder_) << "Page should be set up only once";
-  dummy_page_holder_ = DummyPageHolder::Create(IntSize(800, 600));
+  dummy_page_holder_ = std::make_unique<DummyPageHolder>(IntSize(800, 600));
 
   // Use no-quirks (ake "strict") mode by default.
   GetDocument().SetCompatibilityMode(Document::kNoQuirksMode);
@@ -33,7 +33,7 @@
 
 void PageTestBase::SetUp(IntSize size) {
   DCHECK(!dummy_page_holder_) << "Page should be set up only once";
-  dummy_page_holder_ = DummyPageHolder::Create(size);
+  dummy_page_holder_ = std::make_unique<DummyPageHolder>(size);
 
   // Use no-quirks (ake "strict") mode by default.
   GetDocument().SetCompatibilityMode(Document::kNoQuirksMode);
@@ -47,7 +47,7 @@
     LocalFrameClient* local_frame_client,
     FrameSettingOverrideFunction setting_overrider) {
   DCHECK(!dummy_page_holder_) << "Page should be set up only once";
-  dummy_page_holder_ = DummyPageHolder::Create(
+  dummy_page_holder_ = std::make_unique<DummyPageHolder>(
       IntSize(800, 600), clients, local_frame_client, setting_overrider);
 
   // Use no-quirks (ake "strict") mode by default.
diff --git a/third_party/blink/renderer/core/timing/sub_task_attribution.h b/third_party/blink/renderer/core/timing/sub_task_attribution.h
index dc0ce764..781d3fee 100644
--- a/third_party/blink/renderer/core/timing/sub_task_attribution.h
+++ b/third_party/blink/renderer/core/timing/sub_task_attribution.h
@@ -21,14 +21,6 @@
  public:
   using EntriesVector = Vector<std::unique_ptr<SubTaskAttribution>>;
 
-  static std::unique_ptr<SubTaskAttribution> Create(
-      const AtomicString& sub_task_name,
-      const String& script_url,
-      TimeTicks start_time,
-      TimeDelta duration) {
-    return std::make_unique<SubTaskAttribution>(sub_task_name, script_url,
-                                                start_time, duration);
-  }
   SubTaskAttribution(const AtomicString& sub_task_name,
                      const String& script_url,
                      TimeTicks start_time,
diff --git a/third_party/blink/renderer/core/timing/window_performance_test.cc b/third_party/blink/renderer/core/timing/window_performance_test.cc
index 9086d51..d065896 100644
--- a/third_party/blink/renderer/core/timing/window_performance_test.cc
+++ b/third_party/blink/renderer/core/timing/window_performance_test.cc
@@ -35,7 +35,7 @@
     ResetPerformance();
 
     // Create another dummy page holder and pretend this is the iframe.
-    another_page_holder_ = DummyPageHolder::Create(IntSize(400, 300));
+    another_page_holder_ = std::make_unique<DummyPageHolder>(IntSize(400, 300));
     another_page_holder_->GetDocument().SetURL(KURL("https://iframed.com/bar"));
   }
 
@@ -86,7 +86,7 @@
   }
 
   void ResetPerformance() {
-    page_holder_ = DummyPageHolder::Create(IntSize(800, 600));
+    page_holder_ = std::make_unique<DummyPageHolder>(IntSize(800, 600));
     page_holder_->GetDocument().SetURL(KURL("https://example.com"));
     performance_ =
         WindowPerformance::Create(page_holder_->GetDocument().domWindow());
@@ -158,8 +158,7 @@
 // This happens when a page opens a new window and it navigates to a same-origin
 // document.
 TEST(PerformanceLifetimeTest, SurviveContextSwitch) {
-  std::unique_ptr<DummyPageHolder> page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
 
   WindowPerformance* perf =
       DOMWindowPerformance::performance(*page_holder->GetFrame().DomWindow());
diff --git a/third_party/blink/renderer/core/trustedtypes/trusted_types_util_test.cc b/third_party/blink/renderer/core/trustedtypes/trusted_types_util_test.cc
index ab49cd21..17f7fe76 100644
--- a/third_party/blink/renderer/core/trustedtypes/trusted_types_util_test.cc
+++ b/third_party/blink/renderer/core/trustedtypes/trusted_types_util_test.cc
@@ -41,8 +41,7 @@
 
 void GetStringFromTrustedHTMLThrows(
     const StringOrTrustedHTML& string_or_trusted_html) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Document& document = dummy_page_holder->GetDocument();
   document.GetContentSecurityPolicy()->DidReceiveHeader(
       "trusted-types *", kContentSecurityPolicyHeaderTypeEnforce,
@@ -59,8 +58,7 @@
 
 void GetStringFromTrustedScriptThrows(
     const StringOrTrustedScript& string_or_trusted_script) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Document& document = dummy_page_holder->GetDocument();
   document.GetContentSecurityPolicy()->DidReceiveHeader(
       "trusted-types *", kContentSecurityPolicyHeaderTypeEnforce,
@@ -77,8 +75,7 @@
 
 void GetStringFromTrustedScriptURLThrows(
     const StringOrTrustedScriptURL& string_or_trusted_script_url) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Document& document = dummy_page_holder->GetDocument();
   document.GetContentSecurityPolicy()->DidReceiveHeader(
       "trusted-types *", kContentSecurityPolicyHeaderTypeEnforce,
@@ -95,8 +92,7 @@
 
 void GetStringFromTrustedURLThrows(
     const USVStringOrTrustedURL& string_or_trusted_url) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Document& document = dummy_page_holder->GetDocument();
   document.GetContentSecurityPolicy()->DidReceiveHeader(
       "trusted-types *", kContentSecurityPolicyHeaderTypeEnforce,
@@ -131,8 +127,7 @@
 void GetStringFromTrustedHTMLWorks(
     const StringOrTrustedHTML& string_or_trusted_html,
     String expected) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Document& document = dummy_page_holder->GetDocument();
   document.GetContentSecurityPolicy()->DidReceiveHeader(
       "trusted-types *", kContentSecurityPolicyHeaderTypeEnforce,
@@ -147,8 +142,7 @@
 void GetStringFromTrustedScriptWorks(
     const StringOrTrustedScript& string_or_trusted_script,
     String expected) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Document& document = dummy_page_holder->GetDocument();
   document.GetContentSecurityPolicy()->DidReceiveHeader(
       "trusted-types *", kContentSecurityPolicyHeaderTypeEnforce,
@@ -163,8 +157,7 @@
 void GetStringFromTrustedScriptURLWorks(
     const StringOrTrustedScriptURL& string_or_trusted_script_url,
     String expected) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Document& document = dummy_page_holder->GetDocument();
   document.GetContentSecurityPolicy()->DidReceiveHeader(
       "trusted-types *", kContentSecurityPolicyHeaderTypeEnforce,
@@ -179,8 +172,7 @@
 void GetStringFromTrustedURLWorks(
     const USVStringOrTrustedURL& string_or_trusted_url,
     String expected) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
+  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Document& document = dummy_page_holder->GetDocument();
   document.GetContentSecurityPolicy()->DidReceiveHeader(
       "trusted-types *", kContentSecurityPolicyHeaderTypeEnforce,
diff --git a/third_party/blink/renderer/core/workers/threaded_worklet_test.cc b/third_party/blink/renderer/core/workers/threaded_worklet_test.cc
index a1e2ef0b..c53688a 100644
--- a/third_party/blink/renderer/core/workers/threaded_worklet_test.cc
+++ b/third_party/blink/renderer/core/workers/threaded_worklet_test.cc
@@ -228,7 +228,7 @@
 class ThreadedWorkletTest : public testing::Test {
  public:
   void SetUp() override {
-    page_ = DummyPageHolder::Create();
+    page_ = std::make_unique<DummyPageHolder>();
     Document* document = page_->GetFrame().GetDocument();
     document->SetURL(KURL("https://example.com/"));
     document->UpdateSecurityOrigin(SecurityOrigin::Create(document->Url()));
diff --git a/third_party/blink/renderer/modules/accessibility/accessibility_object_model_test.cc b/third_party/blink/renderer/modules/accessibility/accessibility_object_model_test.cc
index 232cf77..52b0450 100644
--- a/third_party/blink/renderer/modules/accessibility/accessibility_object_model_test.cc
+++ b/third_party/blink/renderer/modules/accessibility/accessibility_object_model_test.cc
@@ -61,7 +61,6 @@
   button->accessibleNode()->setRole("slider");
   EXPECT_EQ("slider", button->accessibleNode()->role());
 
-  GetDocument().View()->UpdateLifecycleToLayoutClean();
   axButton = cache->GetOrCreate(button);
   EXPECT_EQ(ax::mojom::Role::kSlider, axButton->RoleValue());
 }
@@ -113,7 +112,6 @@
   // Assert that the AX object was affected by ARIA attributes.
   auto* cache = AXObjectCache();
   ASSERT_NE(nullptr, cache);
-  GetDocument().View()->UpdateLifecycleToLayoutClean();
   auto* axButton = cache->GetOrCreate(button);
   EXPECT_EQ(ax::mojom::Role::kCheckBox, axButton->RoleValue());
   ax::mojom::NameFrom name_from;
@@ -125,7 +123,6 @@
   button->accessibleNode()->setRole("radio");
   button->accessibleNode()->setLabel("Radio");
   button->accessibleNode()->setDisabled(false, false);
-  GetDocument().View()->UpdateLifecycleToLayoutClean();
 
   // Assert that the AX object was affected by AOM properties.
   axButton = cache->GetOrCreate(button);
@@ -137,7 +134,6 @@
   button->accessibleNode()->setRole(g_null_atom);
   button->accessibleNode()->setLabel(g_null_atom);
   button->accessibleNode()->setDisabled(false, true);
-  GetDocument().View()->UpdateLifecycleToLayoutClean();
 
   // The AX Object should now revert to ARIA.
   axButton = cache->GetOrCreate(button);
@@ -160,7 +156,6 @@
 
   auto* cache = AXObjectCache();
   ASSERT_NE(nullptr, cache);
-  GetDocument().View()->UpdateLifecycleToLayoutClean();
   auto* ax_slider = cache->GetOrCreate(slider);
   float value = 0.0f;
   EXPECT_TRUE(ax_slider->MinValueForRange(&value));
diff --git a/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc b/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc
index 9a09a72..a65910e 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc
@@ -2588,8 +2588,7 @@
       // changed event must be fired. This ensures the AT is notified that the
       // selected state has changed, so that it does not read "unselected" as
       // the user navigates through the items.
-      AXObjectCache().HandleAriaSelectedChangedWithCleanLayout(
-          active_descendant->GetNode());
+      AXObjectCache().HandleAriaSelectedChanged(active_descendant->GetNode());
     }
 
     // Mark this node dirty. AXEventGenerator will automatically infer
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
index 64634af4..5aaa514 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
@@ -92,17 +92,6 @@
 
 using namespace html_names;
 
-namespace {
-// Return a node for the current layout object or ancestor layout object.
-Node* GetClosestNodeForLayoutObject(LayoutObject* layout_object) {
-  if (!layout_object)
-    return nullptr;
-  Node* node = layout_object->GetNode();
-  return node ? node : GetClosestNodeForLayoutObject(layout_object->Parent());
-}
-
-}  // namespace
-
 // static
 AXObjectCache* AXObjectCacheImpl::Create(Document& document) {
   return MakeGarbageCollected<AXObjectCacheImpl>(document);
@@ -114,12 +103,14 @@
       modification_count_(0),
       validation_message_axid_(0),
       relation_cache_(std::make_unique<AXRelationCache>(this)),
+      notification_post_timer_(
+          document.GetTaskRunner(TaskType::kInternalDefault),
+          this,
+          &AXObjectCacheImpl::NotificationPostTimerFired),
       accessibility_event_permission_(mojom::PermissionStatus::ASK),
       permission_observer_binding_(this) {
   if (document_->LoadEventFinished())
     AddPermissionStatusListener();
-  documents_.insert(&document);
-  document.View()->RegisterForLifecycleNotifications(this);
 }
 
 AXObjectCacheImpl::~AXObjectCacheImpl() {
@@ -129,18 +120,14 @@
 }
 
 void AXObjectCacheImpl::Dispose() {
+  notification_post_timer_.Stop();
+
   for (auto& entry : objects_) {
     AXObject* obj = entry.value;
     obj->Detach();
     RemoveAXID(obj);
   }
 
-  for (auto document : documents_) {
-    if (!document || !document->View())
-      continue;
-    document->View()->UnregisterFromLifecycleNotifications(this);
-  }
-
 #if DCHECK_IS_ON()
   has_been_disposed_ = true;
 #endif
@@ -150,22 +137,6 @@
   return GetOrCreate(document_);
 }
 
-void AXObjectCacheImpl::InitializePopup(Document* document) {
-  if (!document || documents_.Contains(document) || document->View())
-    return;
-
-  documents_.insert(document);
-  document->View()->RegisterForLifecycleNotifications(this);
-}
-
-void AXObjectCacheImpl::DisposePopup(Document* document) {
-  if (documents_.Contains(document) || document->View())
-    return;
-
-  document->View()->UnregisterFromLifecycleNotifications(this);
-  documents_.erase(document);
-}
-
 AXObject* AXObjectCacheImpl::FocusedImageMapUIElement(
     HTMLAreaElement* area_element) {
   // Find the corresponding accessibility object for the HTMLAreaElement. This
@@ -715,53 +686,10 @@
   return AXObject::InOrderTraversalIterator();
 }
 
-void AXObjectCacheImpl::DeferTreeUpdateInternal(Node* node,
-                                                base::OnceClosure callback) {
-  tree_update_callback_queue_.push_back(
-      std::make_pair(WrapWeakPersistent(node), std::move(callback)));
-}
-
-void AXObjectCacheImpl::DeferTreeUpdate(
-    void (AXObjectCacheImpl::*method)(Node*),
-    Node* node) {
-  base::OnceClosure callback =
-      WTF::Bind(method, WrapWeakPersistent(this), WrapWeakPersistent(node));
-  DeferTreeUpdateInternal(node, std::move(callback));
-}
-
-void AXObjectCacheImpl::DeferTreeUpdate(
-    void (AXObjectCacheImpl::*method)(const QualifiedName&, Element* element),
-    const QualifiedName& attr_name,
-    Element* element) {
-  base::OnceClosure callback = WTF::Bind(
-      method, WrapWeakPersistent(this), attr_name, WrapWeakPersistent(element));
-  DeferTreeUpdateInternal(element, std::move(callback));
-}
-
-void AXObjectCacheImpl::DeferTreeUpdate(
-    void (AXObjectCacheImpl::*method)(Node*, AXObject*),
-    Node* node,
-    AXObject* obj) {
-  base::OnceClosure callback =
-      WTF::Bind(method, WrapWeakPersistent(this), WrapWeakPersistent(node),
-                WrapWeakPersistent(obj));
-  DeferTreeUpdateInternal(node, std::move(callback));
-}
-
 void AXObjectCacheImpl::SelectionChanged(Node* node) {
-  if (!node)
-    return;
-
-  DeferTreeUpdate(&AXObjectCacheImpl::SelectionChangedWithCleanLayout, node);
-}
-
-void AXObjectCacheImpl::SelectionChangedWithCleanLayout(Node* node) {
-  if (!node)
-    return;
-
-  AXObject* ax_object = GetOrCreate(node);
-  if (ax_object)
-    ax_object->SelectionChanged();
+  AXObject* nearestAncestor = NearestExistingAncestor(node);
+  if (nearestAncestor)
+    nearestAncestor->SelectionChanged();
 }
 
 void AXObjectCacheImpl::UpdateReverseRelations(
@@ -771,30 +699,16 @@
 }
 
 void AXObjectCacheImpl::TextChanged(Node* node) {
-  if (!node)
-    return;
-
-  DeferTreeUpdate(&AXObjectCacheImpl::TextChangedWithCleanLayout, node);
+  TextChanged(Get(node), node);
 }
 
 void AXObjectCacheImpl::TextChanged(LayoutObject* layout_object) {
-  if (!layout_object)
-    return;
-
-  // TODO(aboxhall): audit calls to this and figure out when this is called
-  // when node might be null
-  Node* node = GetClosestNodeForLayoutObject(layout_object);
-  if (node) {
-    DeferTreeUpdate(&AXObjectCacheImpl::TextChangedWithCleanLayout, node);
-    return;
-  }
-
-  TextChanged(Get(layout_object), layout_object->GetNode());
+  if (layout_object)
+    TextChanged(Get(layout_object), layout_object->GetNode());
 }
 
 void AXObjectCacheImpl::TextChanged(AXObject* obj,
                                     Node* node_for_relation_update) {
-  // TODO(aboxhall): Figure out when this may be called with dirty layout
   if (obj)
     obj->TextChanged();
 
@@ -804,17 +718,7 @@
   PostNotification(obj, ax::mojom::Event::kTextChanged);
 }
 
-void AXObjectCacheImpl::TextChangedWithCleanLayout(Node* node) {
-  if (!node)
-    return;
-
-  DCHECK(!node->GetDocument().NeedsLayoutTreeUpdateForNode(*node));
-  TextChanged(Get(node), node);
-}
-
-void AXObjectCacheImpl::FocusableChangedWithCleanLayout(Element* element) {
-  DCHECK(element);
-  DCHECK(!element->GetDocument().NeedsLayoutTreeUpdateForNode(*element));
+void AXObjectCacheImpl::FocusableChanged(Element* element) {
   AXObject* obj = GetOrCreate(element);
   if (!obj)
     return;
@@ -823,7 +727,7 @@
     // Elements that are hidden but focusable are not ignored. Therefore, if a
     // hidden element's focusable state changes, it's ignored state must be
     // recomputed.
-    ChildrenChangedWithCleanLayout(element->parentNode());
+    ChildrenChanged(element->parentNode());
   } else {
     // Refresh the focusable state on the exposed object.
     MarkAXObjectDirty(obj, false);
@@ -832,10 +736,6 @@
 
 void AXObjectCacheImpl::DocumentTitleChanged() {
   PostNotification(Root(), ax::mojom::Event::kDocumentTitleChanged);
-  // If title changes at a point where paint is clean, send notification
-  // immediately.
-  if (document_->Lifecycle().GetState() >= DocumentLifecycle::kPaintClean)
-    PostNotificationsAfterLayout(document_);
 }
 
 void AXObjectCacheImpl::UpdateCacheAfterNodeIsAttached(Node* node) {
@@ -863,8 +763,20 @@
 void AXObjectCacheImpl::ChildrenChanged(Node* node) {
   if (!node)
     return;
+  if (node->GetDocument().IsFlatTreeTraversalForbidden() ||
+      node->GetDocument().NeedsLayoutTreeUpdateForNode(*node)) {
+    nodes_changed_during_layout_.push_back(node);
+    return;
+  }
+  ChildrenChanged(Get(node), node);
+}
 
-  DeferTreeUpdate(&AXObjectCacheImpl::ChildrenChangedWithCleanLayout, node);
+// Return a node for the current layout object or ancestor layout object.
+Node* GetClosestNodeForLayoutObject(LayoutObject* layout_object) {
+  if (!layout_object)
+    return nullptr;
+  Node* node = layout_object->GetNode();
+  return node ? node : GetClosestNodeForLayoutObject(layout_object->Parent());
 }
 
 void AXObjectCacheImpl::ChildrenChanged(LayoutObject* layout_object) {
@@ -873,8 +785,10 @@
 
   Node* node = GetClosestNodeForLayoutObject(layout_object);
 
-  if (node) {
-    DeferTreeUpdate(&AXObjectCacheImpl::ChildrenChangedWithCleanLayout, node);
+  if (node && (node->GetDocument().IsFlatTreeTraversalForbidden() ||
+               node->GetDocument().NeedsLayoutTreeUpdateForNode(*node) ||
+               node->NeedsDistributionRecalc())) {
+    nodes_changed_during_layout_.push_back(node);
     return;
   }
 
@@ -885,30 +799,18 @@
 void AXObjectCacheImpl::ChildrenChanged(AccessibleNode* accessible_node) {
   if (!accessible_node)
     return;
-
+  Element* element = accessible_node->element();
+  if (element &&
+      (element->GetDocument().NeedsLayoutTreeUpdateForNode(*element) ||
+       element->NeedsDistributionRecalc())) {
+    nodes_changed_during_layout_.push_back(element);
+    return;
+  }
   AXObject* object = Get(accessible_node);
   ChildrenChanged(object, object ? object->GetNode() : nullptr);
 }
 
-void AXObjectCacheImpl::ChildrenChangedWithCleanLayout(Node* node) {
-  if (!node)
-    return;
-
-  DCHECK(node->GetDocument().Lifecycle().GetState() >=
-         DocumentLifecycle::kLayoutClean);
-#ifndef NDEBUG
-  if (node->GetDocument().NeedsLayoutTreeUpdateForNode(*node)) {
-    LOG(ERROR) << "Node needs layout tree update: " << node;
-    node->ShowTreeForThisAcrossFrame();
-  }
-#endif
-  DCHECK(!node->GetDocument().NeedsLayoutTreeUpdateForNode(*node));
-
-  ChildrenChanged(Get(node), node);
-}
-
 void AXObjectCacheImpl::ChildrenChanged(AXObject* obj, Node* optional_node) {
-  // TODO(aboxhall): Figure out when this may be called with dirty layout
   if (obj)
     obj->ChildrenChanged();
 
@@ -921,27 +823,36 @@
 void AXObjectCacheImpl::ProcessUpdatesAfterLayout(Document& document) {
   if (document.Lifecycle().GetState() < DocumentLifecycle::kLayoutClean)
     return;
-  TreeUpdateCallbackQueue old_tree_update_callback_queue;
-  tree_update_callback_queue_.swap(old_tree_update_callback_queue);
-  for (auto& pair : old_tree_update_callback_queue) {
-    Node* node = pair.first;
-    if (!node)
-      continue;
-    base::OnceClosure& callback = pair.second;
+  VectorOf<Node> old_nodes_changed_during_layout;
+  nodes_changed_during_layout_.swap(old_nodes_changed_during_layout);
+  for (auto node : old_nodes_changed_during_layout) {
     if (node->GetDocument() != document) {
-      tree_update_callback_queue_.push_back(
-          std::make_pair(WrapWeakPersistent(node), std::move(callback)));
+      nodes_changed_during_layout_.push_back(node);
       continue;
     }
-    std::move(callback).Run();
+    ChildrenChanged(Get(node), node);
+  }
+
+  AttributesChangedVector old_attributes_changed_during_layout;
+  attributes_changed_during_layout_.swap(old_attributes_changed_during_layout);
+  for (auto pair : old_attributes_changed_during_layout) {
+    auto attribute_name = pair.first;
+    auto element = pair.second;
+    if (element->GetDocument() != document) {
+      attributes_changed_during_layout_.push_back(
+          std::make_pair(attribute_name, element));
+      continue;
+    }
+    HandleAttributeChanged(attribute_name, element);
   }
 }
 
-void AXObjectCacheImpl::PostNotificationsAfterLayout(Document* document) {
-  NotificationVector old_notifications_to_post;
-  notifications_to_post_.swap(old_notifications_to_post);
-  for (auto& pair : old_notifications_to_post) {
-    AXObject* obj = pair.first;
+void AXObjectCacheImpl::NotificationPostTimerFired(TimerBase*) {
+  notification_post_timer_.Stop();
+
+  unsigned i = 0, count = notifications_to_post_.size();
+  for (i = 0; i < count; ++i) {
+    AXObject* obj = notifications_to_post_[i].first;
 
     if (!obj->AXObjectID())
       continue;
@@ -949,12 +860,6 @@
     if (obj->IsDetached())
       continue;
 
-    ax::mojom::Event notification = pair.second;
-    if (obj->GetDocument() != document) {
-      notifications_to_post_.push_back(std::make_pair(obj, notification));
-      continue;
-    }
-
 #if DCHECK_IS_ON()
     // Make sure none of the layout views are in the process of being layed out.
     // Notifications should only be sent after the layoutObject has finished
@@ -966,6 +871,7 @@
     }
 #endif
 
+    ax::mojom::Event notification = notifications_to_post_[i].second;
     PostPlatformNotification(obj, notification);
 
     if (notification == ax::mojom::Event::kChildrenChanged &&
@@ -973,6 +879,8 @@
         obj->LastKnownIsIgnoredValue() != obj->AccessibilityIsIgnored())
       ChildrenChanged(obj->ParentObject());
   }
+
+  notifications_to_post_.clear();
 }
 
 void AXObjectCacheImpl::PostNotification(LayoutObject* layout_object,
@@ -996,6 +904,8 @@
 
   modification_count_++;
   notifications_to_post_.push_back(std::make_pair(object, notification));
+  if (!notification_post_timer_.IsActive())
+    notification_post_timer_.StartOneShot(TimeDelta(), FROM_HERE);
 }
 
 bool AXObjectCacheImpl::IsAriaOwned(const AXObject* object) const {
@@ -1083,25 +993,17 @@
 void AXObjectCacheImpl::HandleAttributeChanged(
     const QualifiedName& attr_name,
     AccessibleNode* accessible_node) {
-  if (!accessible_node)
-    return;
   modification_count_++;
   if (AXObject* obj = Get(accessible_node))
     PostNotification(obj, ax::mojom::Event::kAriaAttributeChanged);
 }
 
-void AXObjectCacheImpl::HandleAriaExpandedChangeWithCleanLayout(Node* node) {
-  if (!node)
-    return;
-
-  DCHECK(!node->GetDocument().NeedsLayoutTreeUpdateForNode(*node));
+void AXObjectCacheImpl::HandleAriaExpandedChange(Node* node) {
   if (AXObject* obj = GetOrCreate(node))
     obj->HandleAriaExpandedChanged();
 }
 
-void AXObjectCacheImpl::HandleAriaSelectedChangedWithCleanLayout(Node* node) {
-  DCHECK(node);
-  DCHECK(!node->GetDocument().NeedsLayoutTreeUpdateForNode(*node));
+void AXObjectCacheImpl::HandleAriaSelectedChanged(Node* node) {
   AXObject* obj = Get(node);
   if (!obj)
     return;
@@ -1134,10 +1036,7 @@
   }
 }
 
-void AXObjectCacheImpl::HandleActiveDescendantChangedWithCleanLayout(
-    Node* node) {
-  DCHECK(node);
-  DCHECK(!node->GetDocument().NeedsLayoutTreeUpdateForNode(*node));
+void AXObjectCacheImpl::HandleActiveDescendantChanged(Node* node) {
   // Changing the active descendant should trigger recomputing all
   // cached values even if it doesn't result in a notification, because
   // it can affect what's focusable or not.
@@ -1151,12 +1050,10 @@
 // as this may require a different subclass of AXObject.
 // Role changes are disallowed by the spec but we must handle it gracefully, see
 // https://www.w3.org/TR/wai-aria-1.1/#h-roles for more information.
-void AXObjectCacheImpl::HandleRoleChangeWithCleanLayout(Node* node) {
+void AXObjectCacheImpl::HandleRoleChange(Node* node) {
   if (!node)
     return;  // Virtual AOM node.
 
-  DCHECK(!node->GetDocument().NeedsLayoutTreeUpdateForNode(*node));
-
   // Invalidate the current object and make the parent reconsider its children.
   if (AXObject* obj = Get(node)) {
     // Save parent for later use.
@@ -1182,108 +1079,81 @@
   }
 }
 
-void AXObjectCacheImpl::HandleRoleChangeIfNotEditableWithCleanLayout(
-    Node* node) {
+void AXObjectCacheImpl::HandleRoleChangeIfNotEditable(Node* node) {
   if (!node)
     return;
 
-  DCHECK(!node->GetDocument().NeedsLayoutTreeUpdateForNode(*node));
-
   // Do not invalidate object if the role doesn't actually change when it's a
   // text control, otherwise unique id will change on platform side, and confuse
   // some screen readers as user edits.
   // TODO(aleventhal) Ideally the text control check would be removed, and
-  // HandleRoleChangeWithCleanLayout() and only ever invalidate when the role
-  // actually changes. For example:
-  // if (obj->RoleValue() == obj->ComputeAccessibilityRole())
-  //   return;
-  // However, doing that would require
-  // waiting for layout to complete, as ComputeAccessibilityRole() looks at
-  // layout objects.
+  // HandleRoleChange() and only ever invalidate when the role actually changes.
+  // For example:
+  // if (obj->RoleValue() == obj->ComputeAccessibilityRole()) return;
+  // However, doing that would require waiting for layout to complete, as
+  // ComputeAccessibilityRole() looks at layout objects.
   if (AXObject* obj = Get(node)) {
     if (!obj->IsTextControl())
-      HandleRoleChangeWithCleanLayout(node);
+      HandleRoleChange(node);
   }
 }
 
-bool AXObjectCacheImpl::HandleAttributeChanged(const QualifiedName& attr_name,
+void AXObjectCacheImpl::HandleAttributeChanged(const QualifiedName& attr_name,
                                                Element* element) {
   if (!element)
-    return false;
+    return;
 
-  DeferTreeUpdate(&AXObjectCacheImpl::HandleAttributeChangedWithCleanLayout,
-                  attr_name, element);
-
-  if (attr_name == kRoleAttr || attr_name == kTypeAttr ||
-      attr_name == kSizeAttr || attr_name == kAltAttr ||
-      attr_name == kTitleAttr ||
-      (attr_name == kForAttr && IsHTMLLabelElement(*element)) ||
-      attr_name == kIdAttr || attr_name == kTabindexAttr ||
-      attr_name == kDisabledAttr) {
-    return true;
+  if (element->GetDocument().NeedsLayoutTreeUpdateForNode(*element) ||
+      element->NeedsDistributionRecalc()) {
+    attributes_changed_during_layout_.push_back(
+        std::make_pair(attr_name, element));
+    return;
   }
-  return attr_name.LocalName().StartsWith("aria-");
-}
 
-void AXObjectCacheImpl::HandleAttributeChangedWithCleanLayout(
-    const QualifiedName& attr_name,
-    Element* element) {
-  DCHECK(element);
-  DCHECK(!element->GetDocument().NeedsLayoutTreeUpdateForNode(*element));
-  if (attr_name == kRoleAttr || attr_name == kTypeAttr) {
-    HandleRoleChangeWithCleanLayout(element);
-  } else if (attr_name == kSizeAttr || attr_name == kAriaHaspopupAttr) {
-    // Role won't change on edits.
-    HandleRoleChangeIfNotEditableWithCleanLayout(element);
-  } else if (attr_name == kAltAttr || attr_name == kTitleAttr) {
-    TextChangedWithCleanLayout(element);
-  } else if (attr_name == kForAttr && IsHTMLLabelElement(*element)) {
-    LabelChangedWithCleanLayout(element);
-  } else if (attr_name == kIdAttr) {
+  if (attr_name == kRoleAttr || attr_name == kTypeAttr)
+    HandleRoleChange(element);
+  else if (attr_name == kSizeAttr || attr_name == kAriaHaspopupAttr)
+    HandleRoleChangeIfNotEditable(element);  // Role won't change on edits.
+  else if (attr_name == kAltAttr || attr_name == kTitleAttr)
+    TextChanged(element);
+  else if (attr_name == kForAttr && IsHTMLLabelElement(*element))
+    LabelChanged(element);
+  else if (attr_name == kIdAttr)
     MaybeNewRelationTarget(element, Get(element));
-  } else if (attr_name == kTabindexAttr) {
-    FocusableChangedWithCleanLayout(element);
-  } else if (attr_name == kDisabledAttr) {
+  else if (attr_name == kTabindexAttr)
+    FocusableChanged(element);
+  else if (attr_name == kDisabledAttr)
     MarkElementDirty(element, false);
-  }
 
   if (!attr_name.LocalName().StartsWith("aria-"))
     return;
 
   // Perform updates specific to each attribute.
-  if (attr_name == kAriaActivedescendantAttr) {
-    HandleActiveDescendantChangedWithCleanLayout(element);
-  } else if (attr_name == kAriaValuenowAttr ||
-             attr_name == kAriaValuetextAttr) {
+  if (attr_name == kAriaActivedescendantAttr)
+    HandleActiveDescendantChanged(element);
+  else if (attr_name == kAriaValuenowAttr || attr_name == kAriaValuetextAttr)
     PostNotification(element, ax::mojom::Event::kValueChanged);
-  } else if (attr_name == kAriaLabelAttr || attr_name == kAriaLabeledbyAttr ||
-             attr_name == kAriaLabelledbyAttr) {
-    TextChangedWithCleanLayout(element);
-  } else if (attr_name == kAriaDescribedbyAttr) {
-    // TODO do we need a DescriptionChanged() ?
-    TextChangedWithCleanLayout(element);
-  } else if (attr_name == kAriaCheckedAttr || attr_name == kAriaPressedAttr) {
+  else if (attr_name == kAriaLabelAttr || attr_name == kAriaLabeledbyAttr ||
+           attr_name == kAriaLabelledbyAttr)
+    TextChanged(element);
+  else if (attr_name == kAriaDescribedbyAttr)
+    TextChanged(element);  // TODO do we need a DescriptionChanged() ?
+  else if (attr_name == kAriaCheckedAttr || attr_name == kAriaPressedAttr)
     CheckedStateChanged(element);
-  } else if (attr_name == kAriaSelectedAttr) {
-    HandleAriaSelectedChangedWithCleanLayout(element);
-  } else if (attr_name == kAriaExpandedAttr) {
-    HandleAriaExpandedChangeWithCleanLayout(element);
-  } else if (attr_name == kAriaHiddenAttr) {
-    ChildrenChangedWithCleanLayout(element->parentNode());
-  } else if (attr_name == kAriaInvalidAttr) {
+  else if (attr_name == kAriaSelectedAttr)
+    HandleAriaSelectedChanged(element);
+  else if (attr_name == kAriaExpandedAttr)
+    HandleAriaExpandedChange(element);
+  else if (attr_name == kAriaHiddenAttr)
+    ChildrenChanged(element->parentNode());
+  else if (attr_name == kAriaInvalidAttr)
     PostNotification(element, ax::mojom::Event::kInvalidStatusChanged);
-  } else if (attr_name == kAriaErrormessageAttr) {
+  else if (attr_name == kAriaErrormessageAttr)
     MarkElementDirty(element, false);
-  } else if (attr_name == kAriaOwnsAttr) {
-    ChildrenChangedWithCleanLayout(element);
-    // Ensure aria-owns update fires on original parent as well
-    if (AXObject* obj = GetOrCreate(element)) {
-      obj->ClearChildren();
-      obj->AddChildren();
-    }
-  } else {
+  else if (attr_name == kAriaOwnsAttr)
+    ChildrenChanged(element);
+  else
     PostNotification(element, ax::mojom::Event::kAriaAttributeChanged);
-  }
 }
 
 void AXObjectCacheImpl::HandleAutofillStateChanged(Element* elem,
@@ -1373,8 +1243,8 @@
   MarkElementDirty(form_control, false);
 }
 
-void AXObjectCacheImpl::LabelChangedWithCleanLayout(Element* element) {
-  TextChangedWithCleanLayout(ToHTMLLabelElement(element)->control());
+void AXObjectCacheImpl::LabelChanged(Element* element) {
+  TextChanged(ToHTMLLabelElement(element)->control());
 }
 
 void AXObjectCacheImpl::InlineTextBoxesUpdated(
@@ -1507,8 +1377,9 @@
     return;
 
   AXObject* old_focused_object = Get(old_focused_node);
-  PostNotification(old_focused_object, ax::mojom::Event::kBlur);
-  PostNotification(focused_object, ax::mojom::Event::kFocus);
+
+  PostPlatformNotification(old_focused_object, ax::mojom::Event::kBlur);
+  PostPlatformNotification(focused_object, ax::mojom::Event::kFocus);
 }
 
 void AXObjectCacheImpl::HandleInitialFocus() {
@@ -1585,10 +1456,6 @@
 
 void AXObjectCacheImpl::HandleLoadComplete(Document* document) {
   PostNotification(GetOrCreate(document), ax::mojom::Event::kLoadComplete);
-  // If load complete comes through after the lifecycle has finished,
-  // send notification immediately.
-  if (document->Lifecycle().GetState() >= DocumentLifecycle::kPaintClean)
-    PostNotificationsAfterLayout(document);
   AddPermissionStatusListener();
 }
 
@@ -1604,20 +1471,21 @@
     return;
   if (obj->AccessibilityIsIgnored())
     obj = obj->ParentObjectUnignored();
-  PostNotification(obj, ax::mojom::Event::kScrolledToAnchor);
+  PostPlatformNotification(obj, ax::mojom::Event::kScrolledToAnchor);
 }
 
 void AXObjectCacheImpl::HandleScrollPositionChanged(
     LocalFrameView* frame_view) {
   AXObject* target_ax_object =
       GetOrCreate(frame_view->GetFrame().GetDocument());
-  PostNotification(target_ax_object, ax::mojom::Event::kScrollPositionChanged);
+  PostPlatformNotification(target_ax_object,
+                           ax::mojom::Event::kScrollPositionChanged);
 }
 
 void AXObjectCacheImpl::HandleScrollPositionChanged(
     LayoutObject* layout_object) {
-  PostNotification(GetOrCreate(layout_object),
-                   ax::mojom::Event::kScrollPositionChanged);
+  PostPlatformNotification(GetOrCreate(layout_object),
+                           ax::mojom::Event::kScrollPositionChanged);
 }
 
 const AtomicString& AXObjectCacheImpl::ComputedRoleForNode(Node* node) {
@@ -1645,7 +1513,7 @@
         hit->GetLayoutObject()->IsLayoutEmbeddedContent())
       return;
 
-    PostNotification(hit, ax::mojom::Event::kHover);
+    PostPlatformNotification(hit, ax::mojom::Event::kHover);
   }
 }
 
@@ -1717,16 +1585,6 @@
                 WrapPersistent(this)));
 }
 
-void AXObjectCacheImpl::WillStartLifecycleUpdate(const LocalFrameView&) {}
-
-void AXObjectCacheImpl::DidFinishLifecycleUpdate(const LocalFrameView& view) {
-  Document* document = view.GetFrame().GetDocument();
-  if (document) {
-    ProcessUpdatesAfterLayout(*document);
-    PostNotificationsAfterLayout(document);
-  }
-}
-
 void AXObjectCacheImpl::ContextDestroyed(ExecutionContext*) {
   permission_service_.reset();
   permission_observer_binding_.Close();
@@ -1739,7 +1597,9 @@
 
   visitor->Trace(objects_);
   visitor->Trace(notifications_to_post_);
-  visitor->Trace(documents_);
+  visitor->Trace(nodes_changed_during_layout_);
+  visitor->Trace(attributes_changed_during_layout_);
+
   AXObjectCache::Trace(visitor);
 }
 
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h
index d65b44f5..57af4fe 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h
@@ -30,8 +30,6 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_ACCESSIBILITY_AX_OBJECT_CACHE_IMPL_H_
 
 #include <memory>
-#include <utility>
-#include <vector>
 
 #include "base/macros.h"
 #include "mojo/public/cpp/bindings/binding.h"
@@ -39,7 +37,6 @@
 #include "third_party/blink/public/mojom/permissions/permission_status.mojom-blink.h"
 #include "third_party/blink/renderer/core/accessibility/ax_object_cache_base.h"
 #include "third_party/blink/renderer/core/execution_context/context_lifecycle_observer.h"
-#include "third_party/blink/renderer/core/frame/local_frame_view.h"
 #include "third_party/blink/renderer/modules/accessibility/ax_object.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
 #include "third_party/blink/renderer/platform/wtf/hash_map.h"
@@ -56,8 +53,7 @@
 // This class should only be used from inside the accessibility directory.
 class MODULES_EXPORT AXObjectCacheImpl
     : public AXObjectCacheBase,
-      public mojom::blink::PermissionObserver,
-      public LocalFrameView::LifecycleNotificationObserver {
+      public mojom::blink::PermissionObserver {
  public:
   static AXObjectCache* Create(Document&);
 
@@ -70,10 +66,6 @@
 
   void Dispose() override;
 
-  // Register/remove popups
-  void InitializePopup(Document* document) override;
-  void DisposePopup(Document* document) override;
-
   //
   // Iterators.
   //
@@ -106,14 +98,14 @@
   // changed.
   void TextChanged(LayoutObject*) override;
   void TextChanged(AXObject*, Node* optional_node = nullptr);
-  void FocusableChangedWithCleanLayout(Element* element);
+  void FocusableChanged(Element* element);
   void DocumentTitleChanged() override;
   // Called when a node has just been attached, so we can make sure we have the
   // right subclass of AXObject.
   void UpdateCacheAfterNodeIsAttached(Node*) override;
   void DidInsertChildrenOfNode(Node*) override;
 
-  bool HandleAttributeChanged(const QualifiedName& attr_name,
+  void HandleAttributeChanged(const QualifiedName& attr_name,
                               Element*) override;
   void HandleAutofillStateChanged(Element*, bool) override;
   void HandleValidationMessageVisibilityChanged(
@@ -183,11 +175,11 @@
 
   void MaybeNewRelationTarget(Node* node, AXObject* obj);
 
-  void HandleActiveDescendantChangedWithCleanLayout(Node*);
-  void HandleRoleChangeWithCleanLayout(Node*);
-  void HandleRoleChangeIfNotEditableWithCleanLayout(Node*);
-  void HandleAriaExpandedChangeWithCleanLayout(Node*);
-  void HandleAriaSelectedChangedWithCleanLayout(Node*);
+  void HandleActiveDescendantChanged(Node*);
+  void HandleRoleChange(Node*);
+  void HandleRoleChangeIfNotEditable(Node*);
+  void HandleAriaExpandedChange(Node*);
+  void HandleAriaSelectedChanged(Node*);
 
   bool AccessibilityEnabled();
   bool InlineTextBoxAccessibilityEnabled();
@@ -243,13 +235,9 @@
   // For built-in HTML form validation messages.
   AXObject* ValidationMessageObjectIfInvalid();
 
-  // LifecycleNotificationObserver overrides.
-  void WillStartLifecycleUpdate(const LocalFrameView&) override;
-  void DidFinishLifecycleUpdate(const LocalFrameView&) override;
-
  protected:
   void PostPlatformNotification(AXObject*, ax::mojom::Event);
-  void LabelChangedWithCleanLayout(Element*);
+  void LabelChanged(Element*);
 
   AXObject* CreateFromRenderer(LayoutObject*);
   AXObject* CreateFromNode(Node*);
@@ -281,10 +269,10 @@
   bool has_been_disposed_ = false;
 #endif
 
-  typedef HeapVector<std::pair<Member<AXObject>, ax::mojom::Event>>
-      NotificationVector;
-  NotificationVector notifications_to_post_;
-  void PostNotificationsAfterLayout(Document*);
+  TaskRunnerTimer<AXObjectCacheImpl> notification_post_timer_;
+  HeapVector<std::pair<Member<AXObject>, ax::mojom::Event>>
+      notifications_to_post_;
+  void NotificationPostTimerFired(TimerBase*);
 
   // ContextLifecycleObserver overrides.
   void ContextDestroyed(ExecutionContext*) override;
@@ -318,25 +306,6 @@
   AXObject* GetOrCreateValidationMessageObject();
   void RemoveValidationMessageObject();
 
-  // Enqueue a callback to the given method to be run after layout is
-  // complete.
-  void DeferTreeUpdate(void (AXObjectCacheImpl::*method)(Node*), Node* node);
-  void DeferTreeUpdate(void (AXObjectCacheImpl::*method)(const QualifiedName&,
-                                                         Element* element),
-                       const QualifiedName& attr_name,
-                       Element* element);
-  void DeferTreeUpdate(void (AXObjectCacheImpl::*method)(Node*, AXObject*),
-                       Node* node,
-                       AXObject* obj);
-
-  void DeferTreeUpdateInternal(Node* node, base::OnceClosure callback);
-
-  void SelectionChangedWithCleanLayout(Node* node);
-  void TextChangedWithCleanLayout(Node* node);
-  void ChildrenChangedWithCleanLayout(Node* node);
-  void HandleAttributeChangedWithCleanLayout(const QualifiedName& attr_name,
-                                             Element* element);
-
   // Whether the user has granted permission for the user to install event
   // listeners for accessibility events using the AOM.
   mojom::PermissionStatus accessibility_event_permission_;
@@ -345,11 +314,9 @@
   mojom::blink::PermissionServicePtr permission_service_;
   mojo::Binding<mojom::blink::PermissionObserver> permission_observer_binding_;
 
-  // The main document, plus any page popups.
-  HeapHashSet<WeakMember<Document>> documents_;
-  typedef std::vector<std::pair<WeakPersistent<Node>, base::OnceClosure>>
-      TreeUpdateCallbackQueue;
-  TreeUpdateCallbackQueue tree_update_callback_queue_;
+  VectorOf<Node> nodes_changed_during_layout_;
+  typedef VectorOfPairs<QualifiedName, Element> AttributesChangedVector;
+  AttributesChangedVector attributes_changed_during_layout_;
 
   DISALLOW_COPY_AND_ASSIGN(AXObjectCacheImpl);
 };
diff --git a/third_party/blink/renderer/modules/exported/web_ax_object.cc b/third_party/blink/renderer/modules/exported/web_ax_object.cc
index b60cea9..421dab05 100644
--- a/third_party/blink/renderer/modules/exported/web_ax_object.cc
+++ b/third_party/blink/renderer/modules/exported/web_ax_object.cc
@@ -96,20 +96,19 @@
   }
 };
 
+#if DCHECK_IS_ON()
+// It's not safe to call some WebAXObject APIs if a layout is pending.
+// Clients should call updateLayoutAndCheckValidity first.
 static bool IsLayoutClean(Document* document) {
   if (!document || !document->View())
     return false;
-  if (document->View()->NeedsLayout())
-    return false;
-  DocumentLifecycle::LifecycleState state = document->Lifecycle().GetState();
-  if (state >= DocumentLifecycle::kLayoutClean ||
-      state == DocumentLifecycle::kStyleClean ||
-      state == DocumentLifecycle::kLayoutSubtreeChangeClean) {
-    return true;
-  }
-
-  return false;
+  return document->Lifecycle().GetState() >= DocumentLifecycle::kLayoutClean ||
+         ((document->Lifecycle().GetState() == DocumentLifecycle::kStyleClean ||
+           document->Lifecycle().GetState() ==
+               DocumentLifecycle::kLayoutSubtreeChangeClean) &&
+          !document->View()->NeedsLayout());
 }
+#endif
 
 void WebAXObject::Reset() {
   private_.Reset();
@@ -147,11 +146,8 @@
 bool WebAXObject::UpdateLayoutAndCheckValidity() {
   if (!IsDetached()) {
     Document* document = private_->GetDocument();
-    if (!document || !document->View())
-      return false;
-    if (IsLayoutClean(document))
-      return true;
-    if (!document->View()->UpdateLifecycleToCompositingCleanPlusScrolling())
+    if (!document || !document->View() ||
+        !document->View()->UpdateLifecycleToCompositingCleanPlusScrolling())
       return false;
   }
 
diff --git a/third_party/blink/renderer/modules/filesystem/local_file_system_client.cc b/third_party/blink/renderer/modules/filesystem/local_file_system_client.cc
index 48260da..488583d 100644
--- a/third_party/blink/renderer/modules/filesystem/local_file_system_client.cc
+++ b/third_party/blink/renderer/modules/filesystem/local_file_system_client.cc
@@ -44,11 +44,6 @@
 
 namespace blink {
 
-std::unique_ptr<FileSystemClient> LocalFileSystemClient::Create() {
-  return base::WrapUnique(
-      static_cast<FileSystemClient*>(new LocalFileSystemClient()));
-}
-
 LocalFileSystemClient::~LocalFileSystemClient() = default;
 
 bool LocalFileSystemClient::RequestFileSystemAccessSync(
diff --git a/third_party/blink/renderer/modules/filesystem/local_file_system_client.h b/third_party/blink/renderer/modules/filesystem/local_file_system_client.h
index 030faf9..a601dc0 100644
--- a/third_party/blink/renderer/modules/filesystem/local_file_system_client.h
+++ b/third_party/blink/renderer/modules/filesystem/local_file_system_client.h
@@ -41,16 +41,12 @@
 
 class LocalFileSystemClient final : public FileSystemClient {
  public:
-  MODULES_EXPORT static std::unique_ptr<FileSystemClient> Create();
-
+  LocalFileSystemClient();
   ~LocalFileSystemClient() override;
 
   bool RequestFileSystemAccessSync(ExecutionContext*) override;
   void RequestFileSystemAccessAsync(ExecutionContext*,
                                     base::OnceCallback<void(bool)>) override;
-
- private:
-  LocalFileSystemClient();
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/gamepad/gamepad.idl b/third_party/blink/renderer/modules/gamepad/gamepad.idl
index 574e520..86e5ab4 100644
--- a/third_party/blink/renderer/modules/gamepad/gamepad.idl
+++ b/third_party/blink/renderer/modules/gamepad/gamepad.idl
@@ -24,7 +24,9 @@
  */
 
 // https://w3c.github.io/gamepad/extensions.html#gamepadhand-enum
-enum GamepadHand {
+[
+    OriginTrialEnabled=WebXRGamepadSupport
+] enum GamepadHand {
   "left",
   "right"
 };
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_factory.cc b/third_party/blink/renderer/modules/indexeddb/idb_factory.cc
index aa4575d25..193cf5ab 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_factory.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_factory.cc
@@ -67,13 +67,6 @@
 
 class WebIDBGetDBNamesCallbacksImpl : public WebIDBCallbacks {
  public:
-  // static
-  static std::unique_ptr<WebIDBGetDBNamesCallbacksImpl> Create(
-      ScriptPromiseResolver* promise_resolver) {
-    return base::WrapUnique(
-        new WebIDBGetDBNamesCallbacksImpl(promise_resolver));
-  }
-
   WebIDBGetDBNamesCallbacksImpl(ScriptPromiseResolver* promise_resolver)
       : promise_resolver_(promise_resolver) {
     probe::AsyncTaskScheduled(
@@ -244,7 +237,8 @@
     resolver->Reject();
     return resolver->Promise();
   }
-  factory->GetDatabaseInfo(WebIDBGetDBNamesCallbacksImpl::Create(resolver));
+  factory->GetDatabaseInfo(
+      std::make_unique<WebIDBGetDBNamesCallbacksImpl>(resolver));
   ScriptPromise promise = resolver->Promise();
   return promise;
 }
diff --git a/third_party/blink/renderer/modules/media_controls/media_controls_impl_test.cc b/third_party/blink/renderer/modules/media_controls/media_controls_impl_test.cc
index d56dfda..28c4ad1 100644
--- a/third_party/blink/renderer/modules/media_controls/media_controls_impl_test.cc
+++ b/third_party/blink/renderer/modules/media_controls/media_controls_impl_test.cc
@@ -1024,7 +1024,7 @@
 
 TEST_F(MediaControlsImplTest,
        RemovingFromDocumentRemovesListenersAndCallbacks) {
-  auto page_holder = DummyPageHolder::Create();
+  auto page_holder = std::make_unique<DummyPageHolder>();
 
   HTMLMediaElement* element =
       HTMLVideoElement::Create(page_holder->GetDocument());
@@ -1056,7 +1056,7 @@
 
 TEST_F(MediaControlsImplTest,
        ReInsertingInDocumentRestoresListenersAndCallbacks) {
-  auto page_holder = DummyPageHolder::Create();
+  auto page_holder = std::make_unique<DummyPageHolder>();
 
   HTMLMediaElement* element =
       HTMLVideoElement::Create(page_holder->GetDocument());
diff --git a/third_party/blink/renderer/modules/modules_initializer.cc b/third_party/blink/renderer/modules/modules_initializer.cc
index 95fb51f..2df3b5e 100644
--- a/third_party/blink/renderer/modules/modules_initializer.cc
+++ b/third_party/blink/renderer/modules/modules_initializer.cc
@@ -175,7 +175,7 @@
   ProvideUserMediaTo(
       frame, std::make_unique<UserMediaClient>(client->UserMediaClient()));
   ProvideIndexedDBClientTo(frame, IndexedDBClient::Create(frame));
-  ProvideLocalFileSystemTo(frame, LocalFileSystemClient::Create());
+  ProvideLocalFileSystemTo(frame, std::make_unique<LocalFileSystemClient>());
   NavigatorContentUtils::ProvideTo(
       *frame.DomWindow()->navigator(),
       NavigatorContentUtilsClient::Create(web_frame));
@@ -190,8 +190,8 @@
 
 void ModulesInitializer::ProvideLocalFileSystemToWorker(
     WorkerClients& worker_clients) const {
-  ::blink::ProvideLocalFileSystemToWorker(&worker_clients,
-                                          LocalFileSystemClient::Create());
+  ::blink::ProvideLocalFileSystemToWorker(
+      &worker_clients, std::make_unique<LocalFileSystemClient>());
 }
 
 void ModulesInitializer::ProvideIndexedDBClientToWorker(
diff --git a/third_party/blink/renderer/modules/remoteplayback/remote_playback_test.cc b/third_party/blink/renderer/modules/remoteplayback/remote_playback_test.cc
index eff2992..671b1f0 100644
--- a/third_party/blink/renderer/modules/remoteplayback/remote_playback_test.cc
+++ b/third_party/blink/renderer/modules/remoteplayback/remote_playback_test.cc
@@ -79,7 +79,7 @@
 TEST_F(RemotePlaybackTest, PromptCancelledRejectsWithNotAllowedError) {
   V8TestingScope scope;
 
-  auto page_holder = DummyPageHolder::Create();
+  auto page_holder = std::make_unique<DummyPageHolder>();
 
   HTMLMediaElement* element =
       HTMLVideoElement::Create(page_holder->GetDocument());
@@ -110,7 +110,7 @@
 TEST_F(RemotePlaybackTest, PromptConnectedRejectsWhenCancelled) {
   V8TestingScope scope;
 
-  auto page_holder = DummyPageHolder::Create();
+  auto page_holder = std::make_unique<DummyPageHolder>();
 
   HTMLMediaElement* element =
       HTMLVideoElement::Create(page_holder->GetDocument());
@@ -144,7 +144,7 @@
 TEST_F(RemotePlaybackTest, PromptConnectedResolvesWhenDisconnected) {
   V8TestingScope scope;
 
-  auto page_holder = DummyPageHolder::Create();
+  auto page_holder = std::make_unique<DummyPageHolder>();
 
   HTMLMediaElement* element =
       HTMLVideoElement::Create(page_holder->GetDocument());
@@ -179,7 +179,7 @@
 TEST_F(RemotePlaybackTest, StateChangeEvents) {
   V8TestingScope scope;
 
-  auto page_holder = DummyPageHolder::Create();
+  auto page_holder = std::make_unique<DummyPageHolder>();
 
   HTMLMediaElement* element =
       HTMLVideoElement::Create(page_holder->GetDocument());
@@ -224,7 +224,7 @@
        DisableRemotePlaybackRejectsPromptWithInvalidStateError) {
   V8TestingScope scope;
 
-  auto page_holder = DummyPageHolder::Create();
+  auto page_holder = std::make_unique<DummyPageHolder>();
 
   HTMLMediaElement* element =
       HTMLVideoElement::Create(page_holder->GetDocument());
@@ -256,7 +256,7 @@
 TEST_F(RemotePlaybackTest, DisableRemotePlaybackCancelsAvailabilityCallbacks) {
   V8TestingScope scope;
 
-  auto page_holder = DummyPageHolder::Create();
+  auto page_holder = std::make_unique<DummyPageHolder>();
 
   HTMLMediaElement* element =
       HTMLVideoElement::Create(page_holder->GetDocument());
@@ -298,7 +298,7 @@
   ScopedRemotePlaybackBackendForTest remote_playback_backend(false);
   V8TestingScope scope;
 
-  auto page_holder = DummyPageHolder::Create();
+  auto page_holder = std::make_unique<DummyPageHolder>();
 
   HTMLMediaElement* element =
       HTMLVideoElement::Create(page_holder->GetDocument());
@@ -329,7 +329,7 @@
   ScopedRemotePlaybackBackendForTest remote_playback_backend(false);
   V8TestingScope scope;
 
-  auto page_holder = DummyPageHolder::Create();
+  auto page_holder = std::make_unique<DummyPageHolder>();
 
   HTMLMediaElement* element =
       HTMLVideoElement::Create(page_holder->GetDocument());
@@ -367,7 +367,7 @@
 TEST_F(RemotePlaybackTest, IsListening) {
   V8TestingScope scope;
 
-  auto page_holder = DummyPageHolder::Create();
+  auto page_holder = std::make_unique<DummyPageHolder>();
 
   HTMLMediaElement* element =
       HTMLVideoElement::Create(page_holder->GetDocument());
diff --git a/third_party/blink/renderer/modules/webaudio/audio_basic_processor_handler_test.cc b/third_party/blink/renderer/modules/webaudio/audio_basic_processor_handler_test.cc
index 1daf55e6..38380d2 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_basic_processor_handler_test.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_basic_processor_handler_test.cc
@@ -50,7 +50,7 @@
 };
 
 TEST(AudioBasicProcessorHandlerTest, ProcessorFinalization) {
-  std::unique_ptr<DummyPageHolder> page = DummyPageHolder::Create();
+  auto page = std::make_unique<DummyPageHolder>();
   OfflineAudioContext* context = OfflineAudioContext::Create(
       &page->GetDocument(), 2, 1, 48000, ASSERT_NO_EXCEPTION);
   MockProcessorNode* node = MakeGarbageCollected<MockProcessorNode>(*context);
diff --git a/third_party/blink/renderer/modules/webaudio/audio_node_input_test.cc b/third_party/blink/renderer/modules/webaudio/audio_node_input_test.cc
index 72c99cb..89f56479 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_node_input_test.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_node_input_test.cc
@@ -16,7 +16,7 @@
 namespace blink {
 
 TEST(AudioNodeInputTest, InputDestroyedBeforeOutput) {
-  auto page = DummyPageHolder::Create();
+  auto page = std::make_unique<DummyPageHolder>();
   OfflineAudioContext* context = OfflineAudioContext::Create(
       &page->GetDocument(), 2, 1, 48000, ASSERT_NO_EXCEPTION);
   DelayNode* node1 = context->createDelay(ASSERT_NO_EXCEPTION);
@@ -40,7 +40,7 @@
 }
 
 TEST(AudioNodeInputTest, OutputDestroyedBeforeInput) {
-  auto page = DummyPageHolder::Create();
+  auto page = std::make_unique<DummyPageHolder>();
   OfflineAudioContext* context = OfflineAudioContext::Create(
       &page->GetDocument(), 2, 1, 48000, ASSERT_NO_EXCEPTION);
   DelayNode* node1 = context->createDelay(ASSERT_NO_EXCEPTION);
diff --git a/third_party/blink/renderer/modules/webaudio/convolver_node_test.cc b/third_party/blink/renderer/modules/webaudio/convolver_node_test.cc
index cb5179a2..f3f85bc 100644
--- a/third_party/blink/renderer/modules/webaudio/convolver_node_test.cc
+++ b/third_party/blink/renderer/modules/webaudio/convolver_node_test.cc
@@ -12,7 +12,7 @@
 namespace blink {
 
 TEST(ConvolverNodeTest, ReverbLifetime) {
-  std::unique_ptr<DummyPageHolder> page = DummyPageHolder::Create();
+  auto page = std::make_unique<DummyPageHolder>();
   OfflineAudioContext* context = OfflineAudioContext::Create(
       &page->GetDocument(), 2, 1, 48000, ASSERT_NO_EXCEPTION);
   ConvolverNode* node = context->createConvolver(ASSERT_NO_EXCEPTION);
diff --git a/third_party/blink/renderer/modules/webaudio/dynamics_compressor_node_test.cc b/third_party/blink/renderer/modules/webaudio/dynamics_compressor_node_test.cc
index 30590d71..8ecf679 100644
--- a/third_party/blink/renderer/modules/webaudio/dynamics_compressor_node_test.cc
+++ b/third_party/blink/renderer/modules/webaudio/dynamics_compressor_node_test.cc
@@ -12,7 +12,7 @@
 namespace blink {
 
 TEST(DynamicsCompressorNodeTest, ProcessorLifetime) {
-  std::unique_ptr<DummyPageHolder> page = DummyPageHolder::Create();
+  auto page = std::make_unique<DummyPageHolder>();
   OfflineAudioContext* context = OfflineAudioContext::Create(
       &page->GetDocument(), 2, 1, 48000, ASSERT_NO_EXCEPTION);
   DynamicsCompressorNode* node =
diff --git a/third_party/blink/renderer/modules/webaudio/script_processor_node_test.cc b/third_party/blink/renderer/modules/webaudio/script_processor_node_test.cc
index d5cf3a6..be98030 100644
--- a/third_party/blink/renderer/modules/webaudio/script_processor_node_test.cc
+++ b/third_party/blink/renderer/modules/webaudio/script_processor_node_test.cc
@@ -11,7 +11,7 @@
 namespace blink {
 
 TEST(ScriptProcessorNodeTest, BufferLifetime) {
-  std::unique_ptr<DummyPageHolder> page = DummyPageHolder::Create();
+  auto page = std::make_unique<DummyPageHolder>();
   OfflineAudioContext* context = OfflineAudioContext::Create(
       &page->GetDocument(), 2, 1, 48000, ASSERT_NO_EXCEPTION);
   ScriptProcessorNode* node =
diff --git a/third_party/blink/renderer/modules/webaudio/stereo_panner_node_test.cc b/third_party/blink/renderer/modules/webaudio/stereo_panner_node_test.cc
index 36f18042..1e92d218 100644
--- a/third_party/blink/renderer/modules/webaudio/stereo_panner_node_test.cc
+++ b/third_party/blink/renderer/modules/webaudio/stereo_panner_node_test.cc
@@ -11,7 +11,7 @@
 namespace blink {
 
 TEST(StereoPannerNodeTest, StereoPannerLifetime) {
-  std::unique_ptr<DummyPageHolder> page = DummyPageHolder::Create();
+  auto page = std::make_unique<DummyPageHolder>();
   OfflineAudioContext* context = OfflineAudioContext::Create(
       &page->GetDocument(), 2, 1, 48000, ASSERT_NO_EXCEPTION);
   StereoPannerNode* node = context->createStereoPanner(ASSERT_NO_EXCEPTION);
diff --git a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge_test.cc b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge_test.cc
index a50636c3..5eabcdc0 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge_test.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge_test.cc
@@ -1208,7 +1208,6 @@
 TEST_F(Canvas2DLayerBridgeTest, EnsureCCImageCacheUse) {
   auto color_params =
       CanvasColorParams(kSRGBCanvasColorSpace, kF16CanvasPixelFormat, kOpaque);
-  ASSERT_FALSE(color_params.NeedsSkColorSpaceXformCanvas());
 
   std::unique_ptr<Canvas2DLayerBridge> bridge =
       MakeBridge(IntSize(300, 300), Canvas2DLayerBridge::kEnableAcceleration,
diff --git a/third_party/blink/renderer/platform/graphics/canvas_color_params.cc b/third_party/blink/renderer/platform/graphics/canvas_color_params.cc
index 83f5651..992508d 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_color_params.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_color_params.cc
@@ -47,31 +47,14 @@
 CanvasColorParams::CanvasColorParams(const SkImageInfo& info)
     : CanvasColorParams(info.refColorSpace(), info.colorType()) {}
 
-bool CanvasColorParams::NeedsSkColorSpaceXformCanvas() const {
-  // Part of larger effort to remove ColorSpaceXformCanvas (reed)
-  return false;
-}
-
-std::unique_ptr<cc::PaintCanvas> CanvasColorParams::WrapCanvas(
-    SkCanvas* canvas) const {
-  if (NeedsSkColorSpaceXformCanvas())
-    return std::make_unique<cc::SkiaPaintCanvas>(canvas, GetSkColorSpace());
-  // |canvas| already does its own color correction.
-  return std::make_unique<cc::SkiaPaintCanvas>(canvas);
-}
-
 sk_sp<SkColorSpace> CanvasColorParams::GetSkColorSpaceForSkSurfaces() const {
-  if (NeedsSkColorSpaceXformCanvas())
-    return nullptr;
   return GetSkColorSpace();
 }
 
 bool CanvasColorParams::NeedsColorConversion(
     const CanvasColorParams& dest_color_params) const {
   if ((color_space_ == dest_color_params.ColorSpace() &&
-       pixel_format_ == dest_color_params.PixelFormat()) ||
-      (NeedsSkColorSpaceXformCanvas() &&
-       dest_color_params.NeedsSkColorSpaceXformCanvas()))
+       pixel_format_ == dest_color_params.PixelFormat()))
     return false;
   return true;
 }
diff --git a/third_party/blink/renderer/platform/graphics/canvas_color_params.h b/third_party/blink/renderer/platform/graphics/canvas_color_params.h
index 9540a85a4..175a2b8 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_color_params.h
+++ b/third_party/blink/renderer/platform/graphics/canvas_color_params.h
@@ -13,7 +13,6 @@
 #include "third_party/skia/include/core/SkImageInfo.h"
 #include "ui/gfx/buffer_types.h"
 
-class SkCanvas;
 class SkSurfaceProps;
 
 namespace cc {
@@ -57,10 +56,6 @@
   void SetCanvasPixelFormat(CanvasPixelFormat f) { pixel_format_ = f; }
   void SetOpacityMode(OpacityMode m) { opacity_mode_ = m; }
 
-  // Indicates whether rendering needs to go through an SkColorSpaceXformCanvas
-  // in order to enforce non-gamma-aware pixel math behaviour.
-  bool NeedsSkColorSpaceXformCanvas() const;
-
   // Indicates if pixels in this canvas color settings require any color
   // conversion to be used in the passed canvas color settings.
   bool NeedsColorConversion(const CanvasColorParams&) const;
@@ -71,10 +66,6 @@
   // SkColorSpaceXformCanvas).
   sk_sp<SkColorSpace> GetSkColorSpaceForSkSurfaces() const;
 
-  // Wraps an SkCanvas into a PaintCanvas, along with an SkColorSpaceXformCanvas
-  // if necessary.
-  std::unique_ptr<cc::PaintCanvas> WrapCanvas(SkCanvas*) const;
-
   // The pixel format to use for allocating SkSurfaces.
   SkColorType GetSkColorType() const;
   static SkColorType PixelFormatToSkColorType(CanvasPixelFormat pixel_format);
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
index f248f30ed..c56be266 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
@@ -974,15 +974,9 @@
       context_flushes.max_draws_before_flush =
           canvas_heuristic_parameters::kMaxDrawsBeforeContextFlush;
     }
-    if (ColorParams().NeedsSkColorSpaceXformCanvas()) {
-      canvas_ = std::make_unique<cc::SkiaPaintCanvas>(
-          GetSkSurface()->getCanvas(), ColorParams().GetSkColorSpace(),
-          canvas_image_provider_.get(), context_flushes);
-    } else {
       canvas_ = std::make_unique<cc::SkiaPaintCanvas>(
           GetSkSurface()->getCanvas(), canvas_image_provider_.get(),
           context_flushes);
-    }
   }
 
   return canvas_.get();
@@ -1090,10 +1084,7 @@
 }
 
 cc::ImageDecodeCache* CanvasResourceProvider::ImageDecodeCacheRGBA8() {
-  auto color_space = ColorParams().ColorSpace();
-  if (!ColorParams().NeedsSkColorSpaceXformCanvas()) {
-    color_space = kSRGBCanvasColorSpace;
-  }
+  auto color_space = kSRGBCanvasColorSpace;
 
   if (use_hardware_decode_cache()) {
     return context_provider_wrapper_->ContextProvider()->ImageDecodeCache(
@@ -1105,10 +1096,7 @@
 }
 
 cc::ImageDecodeCache* CanvasResourceProvider::ImageDecodeCacheF16() {
-  auto color_space = ColorParams().ColorSpace();
-  if (!ColorParams().NeedsSkColorSpaceXformCanvas()) {
-    color_space = kSRGBCanvasColorSpace;
-  }
+  auto color_space = kSRGBCanvasColorSpace;
 
   if (use_hardware_decode_cache()) {
     return context_provider_wrapper_->ContextProvider()->ImageDecodeCache(
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 6f1f4a3..6c2b734c 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -1211,7 +1211,7 @@
     // Enables the use of the RTCSctpTransport object.
     {
       name: "RTCSctpTransport",
-      status: "test",
+      status: "experimental",
     },
     {
       name: "RTCStatsRelativePacketArrivalDelay",
diff --git a/third_party/blink/renderer/platform/wtf/text/wtf_string.cc b/third_party/blink/renderer/platform/wtf/text/wtf_string.cc
index 797a38c..a8a7c7cf 100644
--- a/third_party/blink/renderer/platform/wtf/text/wtf_string.cc
+++ b/third_party/blink/renderer/platform/wtf/text/wtf_string.cc
@@ -22,9 +22,11 @@
 
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
+#include <locale.h>
 #include <stdarg.h>
 #include <algorithm>
 #include "base/strings/string_util.h"
+#include "build/build_config.h"
 #include "third_party/blink/renderer/platform/wtf/ascii_ctype.h"
 #include "third_party/blink/renderer/platform/wtf/dtoa.h"
 #include "third_party/blink/renderer/platform/wtf/math_extras.h"
@@ -312,6 +314,16 @@
 }
 
 String String::Format(const char* format, ...) {
+  // vsnprintf is locale sensitive when converting floats to strings
+  // and we need it to always use a decimal point. Double check that
+  // the locale is compatible, and also that it is the default "C"
+  // locale so that we aren't just lucky. Android's locales work
+  // differently so can't check the same way there.
+  DCHECK_EQ(strcmp(localeconv()->decimal_point, "."), 0);
+#if !defined(OS_ANDROID)
+  DCHECK_EQ(strcmp(setlocale(LC_NUMERIC, NULL), "C"), 0);
+#endif  // !OS_ANDROID
+
   va_list args;
 
   // TODO(esprehn): base uses 1024, maybe we should use a bigger size too.
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 8abd6f0..a579b825 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -5147,8 +5147,7 @@
 crbug.com/831685 [ Linux ] external/wpt/2dcontext/compositing/2d.composite.canvas.lighter.html [ Pass Timeout ]
 crbug.com/831673 http/tests/devtools/reveal-objects.js [ Pass Timeout ]
 crbug.com/831673 virtual/binary-for-devtools/http/tests/devtools/reveal-objects.js [ Pass Timeout ]
-# Temporarily superceded to allow Failure for crbug.com/938592.
-#crbug.com/831496 virtual/gpu/fast/canvas/fillrect_gradient.html [ Pass Timeout ]
+crbug.com/831496 virtual/gpu/fast/canvas/fillrect_gradient.html [ Pass Timeout ]
 crbug.com/831482 [ Linux ] virtual/gpu-rasterization/images/cross-fade-background-size.html [ Pass Timeout ]
 crbug.com/831249 [ Linux ] virtual/gpu/fast/canvas/canvas-filter-svg-inline.html [ Pass Timeout ]
 crbug.com/829952 fast/webgl/texImage-imageBitmap-from-image-resize.html [ Pass Timeout ]
@@ -6132,10 +6131,6 @@
 crbug.com/943388 [ Win ] http/tests/devtools/network/network-recording-after-reload-with-screenshots-enabled.js [ Pass Failure ]
 crbug.com/943388 [ Win ] virtual/binary-for-devtools/http/tests/devtools/network/network-recording-after-reload-with-screenshots-enabled.js [ Pass Failure ]
 
-#Imperceptible gradient changes. Will rebaseline after Skia roll.
-crbug.com/938592 virtual/gpu/fast/canvas/canvas-text-alignment.html [ Pass Failure ]
-crbug.com/938592 virtual/gpu/fast/canvas/fillrect_gradient.html [ Pass Failure Timeout ]
-#
 # Trooper 2019-03-19
 crbug.com/941565 external/wpt/shadow-dom/untriaged/html-elements-in-shadow-trees/html-forms/test-003.html [ Pass Failure ]
 
diff --git a/third_party/blink/web_tests/accessibility/image-inside-link.html b/third_party/blink/web_tests/accessibility/image-inside-link.html
index b778902..5d2e34a0 100644
--- a/third_party/blink/web_tests/accessibility/image-inside-link.html
+++ b/third_party/blink/web_tests/accessibility/image-inside-link.html
@@ -12,8 +12,10 @@
 {
     var axImg = accessibilityController.accessibleElementById("img1");
     axImg.addNotificationListener(function(notification) {
-        if (notification == 'Clicked')
+        if (notification == 'Clicked') {
+            document.getElementById("wrapper1").style.display = "none";
             t.done();
+        }
     });
 
     var img = document.getElementById("img1");
@@ -45,8 +47,10 @@
             domEvent = true;
         }
 
-        if (axEvent && domEvent)
+        if (axEvent && domEvent) {
+            document.getElementById("wrapper2").style.display = "none";
             t.done();
+        }
     };
 
     var axImg = accessibilityController.accessibleElementById("img2");
@@ -56,3 +60,4 @@
     axImg.press();
 }, "clicking an image via accessibility sends both an accessible and a DOM click event");
 </script>
+
diff --git a/third_party/blink/web_tests/accessibility/multiselect-list-reports-active-option.html b/third_party/blink/web_tests/accessibility/multiselect-list-reports-active-option.html
index df0967f..61e516d0 100644
--- a/third_party/blink/web_tests/accessibility/multiselect-list-reports-active-option.html
+++ b/third_party/blink/web_tests/accessibility/multiselect-list-reports-active-option.html
@@ -7,7 +7,6 @@
 async_test_after_layout_and_paint((testCase) => {
     const expectedNotifications = [
         'ActiveDescendantChanged',
-        'Focus',
         'SelectedChildrenChanged',
         'SelectedChildrenChanged',
         'ActiveDescendantChanged',
diff --git a/third_party/blink/web_tests/accessibility/notification-listeners.html b/third_party/blink/web_tests/accessibility/notification-listeners.html
index 390f61a3..c0c9b08d 100644
--- a/third_party/blink/web_tests/accessibility/notification-listeners.html
+++ b/third_party/blink/web_tests/accessibility/notification-listeners.html
@@ -15,16 +15,7 @@
     window.select = accessibilityController.accessibleElementById("select");
     window.slider = accessibilityController.accessibleElementById("slider");
     let expected_notifications = [
-        [select, "Focus"],
-        ["global", "Focus", "AXRole: AXPopUpButton"],
         [select, "Blur"],
-        ["global", "Blur", "AXRole: AXPopUpButton"],
-        [slider, "Focus"],
-        ["global", "Focus", "AXRole: AXSlider"],
-        [select, "TextChanged"],
-        ["global", "TextChanged", "AXRole: AXPopUpButton"],
-        [select, "TextChanged"],
-        ["global", "TextChanged", "AXRole: AXPopUpButton"],
         [select, "InvalidStatusChanged"],
         ["global", "InvalidStatusChanged", "AXRole: AXPopUpButton"],
         [slider, "ValueChanged"],
@@ -60,9 +51,9 @@
             assert_equals(element.role, expected_notification[2]);
         }
         if (expected_notifications.length === 0) {
-            assert_equals(selectNotificationCount, 5);
-            assert_equals(sliderNotificationCount, 2);
-            assert_equals(globalNotificationCount, 7);
+            assert_equals(selectNotificationCount, 2);
+            assert_equals(sliderNotificationCount, 1);
+            assert_equals(globalNotificationCount, 2);
             accessibilityController.removeNotificationListener();
             select.removeNotificationListener();
             slider.removeNotificationListener();
diff --git a/third_party/blink/web_tests/accessibility/scroll-div-horiz-sends-notification-expected.txt b/third_party/blink/web_tests/accessibility/scroll-div-horiz-sends-notification-expected.txt
new file mode 100644
index 0000000..3e9953a
--- /dev/null
+++ b/third_party/blink/web_tests/accessibility/scroll-div-horiz-sends-notification-expected.txt
@@ -0,0 +1,12 @@
+One Two Three
+
+This test ensures that scrolling the window sends a notification.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+Got notification on container div
+PASS container.scrollLeft is 500
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/third_party/blink/web_tests/accessibility/scroll-div-horiz-sends-notification.html b/third_party/blink/web_tests/accessibility/scroll-div-horiz-sends-notification.html
index 144b5b2f..a8added 100644
--- a/third_party/blink/web_tests/accessibility/scroll-div-horiz-sends-notification.html
+++ b/third_party/blink/web_tests/accessibility/scroll-div-horiz-sends-notification.html
@@ -1,9 +1,7 @@
 <!DOCTYPE HTML>
 <html>
 <head>
-  <script src="../resources/testharness.js"></script>
-  <script src="../resources/testharnessreport.js"></script>
-  <script src="../resources/run-after-layout-and-paint.js"></script>
+<script src="../resources/js-test.js"></script>
 <style>
 .container {
   padding: 100px;
@@ -32,24 +30,30 @@
     </div>
 </div>
 
+<div id="console"></div>
 <script>
 
-async_test_after_layout_and_paint((t) => {
+description("This test ensures that scrolling the window sends a notification.");
+window.jsTestIsAsync = true;
+
+if (window.testRunner && window.accessibilityController) {
+    testRunner.dumpAsText();
+
     var container = document.getElementById('container');
 
     accessibilityController.addNotificationListener(function (target, notification) {
         if (target.role == 'AXRole: AXGenericContainer') {
-            console.log('Got notification on container div');
-            assert_equals(container.scrollLeft, 500);
-            t.done();
+            debug('Got notification on container div');
+            shouldBe("container.scrollLeft", "500");
+            accessibilityController.removeNotificationListener();
+            finishJSTest();
         }
     });
 
     window.setTimeout(function() {
         container.scrollLeft = 500;
     }, 0);
-}, "This test ensures that scrolling the window sends a notification.");
-
+}
 
 </script>
 
diff --git a/third_party/blink/web_tests/accessibility/scroll-div-sends-notification-expected.txt b/third_party/blink/web_tests/accessibility/scroll-div-sends-notification-expected.txt
new file mode 100644
index 0000000..e7a0c6e
--- /dev/null
+++ b/third_party/blink/web_tests/accessibility/scroll-div-sends-notification-expected.txt
@@ -0,0 +1,14 @@
+One
+Two
+Three
+
+This test ensures that scrolling the window sends a notification.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+Got notification on container div
+PASS container.scrollTop is 500
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/third_party/blink/web_tests/accessibility/scroll-div-sends-notification.html b/third_party/blink/web_tests/accessibility/scroll-div-sends-notification.html
index 30ed8c2..37dee5a 100644
--- a/third_party/blink/web_tests/accessibility/scroll-div-sends-notification.html
+++ b/third_party/blink/web_tests/accessibility/scroll-div-sends-notification.html
@@ -1,9 +1,7 @@
 <!DOCTYPE HTML>
 <html>
 <head>
-  <script src="../resources/testharness.js"></script>
-  <script src="../resources/testharnessreport.js"></script>
-  <script src="../resources/run-after-layout-and-paint.js"></script>
+<script src="../resources/js-test.js"></script>
 <style>
 .container {
   padding: 100px;
@@ -27,24 +25,30 @@
     <button class="bigbutton">Three</button>
 </div>
 
+<div id="console"></div>
 <script>
 
-async_test_after_layout_and_paint((t) => {
+description("This test ensures that scrolling the window sends a notification.");
+window.jsTestIsAsync = true;
+
+if (window.testRunner && window.accessibilityController) {
+    testRunner.dumpAsText();
+
     var container = document.getElementById('container');
 
     accessibilityController.addNotificationListener(function (target, notification) {
         if (target.role == 'AXRole: AXGenericContainer') {
-            console.log('Got notification on container div');
-            assert_equals(container.scrollTop, 500);
-            t.done();
+            debug('Got notification on container div');
+            shouldBe("container.scrollTop", "500");
+            accessibilityController.removeNotificationListener();
+            finishJSTest();
         }
     });
 
     window.setTimeout(function() {
         container.scrollTop = 500;
     }, 0);
-}, "This test ensures that scrolling the window sends a notification.");
-
+}
 
 </script>
 
diff --git a/third_party/blink/web_tests/accessibility/scroll-window-horiz-sends-notification-expected.txt b/third_party/blink/web_tests/accessibility/scroll-window-horiz-sends-notification-expected.txt
new file mode 100644
index 0000000..0b84dc1e
--- /dev/null
+++ b/third_party/blink/web_tests/accessibility/scroll-window-horiz-sends-notification-expected.txt
@@ -0,0 +1,15 @@
+One
+Two
+Three
+
+This test ensures that scrolling the window sends a notification.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS window.pageXOffset is 0
+Got notification on web area
+PASS window.pageXOffset is 500
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/third_party/blink/web_tests/accessibility/scroll-window-horiz-sends-notification.html b/third_party/blink/web_tests/accessibility/scroll-window-horiz-sends-notification.html
index 6fd610d9..4b1e5c7a 100644
--- a/third_party/blink/web_tests/accessibility/scroll-window-horiz-sends-notification.html
+++ b/third_party/blink/web_tests/accessibility/scroll-window-horiz-sends-notification.html
@@ -1,9 +1,7 @@
 <!DOCTYPE HTML>
 <html>
 <head>
-  <script src="../resources/testharness.js"></script>
-  <script src="../resources/testharnessreport.js"></script>
-  <script src="../resources/run-after-layout-and-paint.js"></script>
+<script src="../resources/js-test.js"></script>
 <style>
 .bigbutton {
     display:block;
@@ -18,25 +16,31 @@
 <button class="bigbutton">Two</button>
 <button class="bigbutton">Three</button>
 
+<div id="console"></div>
 <script>
 
-async_test_after_layout_and_paint((t) => {
+description("This test ensures that scrolling the window sends a notification.");
+window.jsTestIsAsync = true;
+
+if (window.testRunner && window.accessibilityController) {
+    testRunner.dumpAsText();
 
     window.scrollTo(0, 0);
-    assert_equals(window.pageXOffset, 0);
+    shouldBe("window.pageXOffset", "0");
 
     accessibilityController.addNotificationListener(function (target, notification) {
         if (target.role == 'AXRole: AXWebArea' && notification == 'ScrollPositionChanged') {
-            console.log('Got notification on web area');
-            assert_equals(window.pageXOffset, 500);
-            t.done()();
+            debug('Got notification on web area');
+            accessibilityController.removeNotificationListener();
+            shouldBe("window.pageXOffset", "500");
+            finishJSTest();
         }
     });
 
     window.setTimeout(function() {
         window.scrollTo(500, 0);
     }, 0);
-}, "This test ensures that scrolling the window sends a notification.");
+}
 
 </script>
 
diff --git a/third_party/blink/web_tests/accessibility/scroll-window-sends-notification-expected.txt b/third_party/blink/web_tests/accessibility/scroll-window-sends-notification-expected.txt
new file mode 100644
index 0000000..2ab16ef
--- /dev/null
+++ b/third_party/blink/web_tests/accessibility/scroll-window-sends-notification-expected.txt
@@ -0,0 +1,15 @@
+One
+Two
+Three
+
+This test ensures that scrolling the window sends a notification.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS window.pageYOffset is 0
+Got notification on web area
+PASS window.pageYOffset is 500
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/third_party/blink/web_tests/accessibility/scroll-window-sends-notification.html b/third_party/blink/web_tests/accessibility/scroll-window-sends-notification.html
index 4a21da6..7ff4cf6 100644
--- a/third_party/blink/web_tests/accessibility/scroll-window-sends-notification.html
+++ b/third_party/blink/web_tests/accessibility/scroll-window-sends-notification.html
@@ -1,9 +1,7 @@
 <!DOCTYPE HTML>
 <html>
 <head>
-  <script src="../resources/testharness.js"></script>
-  <script src="../resources/testharnessreport.js"></script>
-  <script src="../resources/run-after-layout-and-paint.js"></script>
+<script src="../resources/js-test.js"></script>
 <style>
 .bigbutton {
     display:block;
@@ -21,23 +19,28 @@
 <div id="console"></div>
 <script>
 
-async_test_after_layout_and_paint((t) => {
+description("This test ensures that scrolling the window sends a notification.");
+window.jsTestIsAsync = true;
+
+if (window.testRunner && window.accessibilityController) {
+    testRunner.dumpAsText();
+
     window.scrollTo(0, 0);
-    assert_equals(window.pageYOffset, 0);
+    shouldBe("window.pageYOffset", "0");
 
     accessibilityController.addNotificationListener(function (target, notification) {
         if (target.role == 'AXRole: AXWebArea' && notification == 'ScrollPositionChanged') {
-            console.log('Got notification on web area');
-            assert_equals(window.pageYOffset, 500);
-            t.done()
+            debug('Got notification on web area');
+            accessibilityController.removeNotificationListener();
+            shouldBe("window.pageYOffset", "500");
+            finishJSTest();
         }
     });
 
     window.setTimeout(function() {
         window.scrollTo(0, 500);
     }, 0);
-}, "This test ensures that scrolling the window sends a notification.");
-
+}
 
 </script>
 
diff --git a/third_party/blink/web_tests/accessibility/selection-change-notification-aria-textbox.html b/third_party/blink/web_tests/accessibility/selection-change-notification-aria-textbox.html
index 89f26ded..d72fd8e1 100644
--- a/third_party/blink/web_tests/accessibility/selection-change-notification-aria-textbox.html
+++ b/third_party/blink/web_tests/accessibility/selection-change-notification-aria-textbox.html
@@ -21,14 +21,10 @@
     accessibilityController.accessibleElementById('dummy');
 
     var element = document.getElementById('ariaTextbox');
+    var axElement = accessibilityController.accessibleElementById('ariaTextbox');
     element.focus();
 
-    var axElement = accessibilityController.accessibleElementById('ariaTextbox');
     axElement.addNotificationListener(t.step_func((notification) => {
-        // Focus notification will come asynchronously after layout
-        if (notification == 'Focus')
-            return;
-
         if (notification == 'SelectedTextChanged') {
             axElement.removeNotificationListener();
             t.done();
diff --git a/third_party/blink/web_tests/accessibility/selection-change-notification-contenteditable.html b/third_party/blink/web_tests/accessibility/selection-change-notification-contenteditable.html
index 831ad710..7701d4d 100644
--- a/third_party/blink/web_tests/accessibility/selection-change-notification-contenteditable.html
+++ b/third_party/blink/web_tests/accessibility/selection-change-notification-contenteditable.html
@@ -16,14 +16,10 @@
 <script>
 async_test_after_layout_and_paint((t) => {
     var element = document.getElementById('contentEditable');
+    var axElement = accessibilityController.accessibleElementById('contentEditable');
     element.focus();
 
-    var axElement = accessibilityController.accessibleElementById('contentEditable');
     axElement.addNotificationListener(t.step_func((notification) => {
-        // Focus notification will come asynchronously after layout
-        if (notification == 'Focus')
-            return;
-
         if (notification == 'SelectedTextChanged') {
             axElement.removeNotificationListener();
             t.done();
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json
index 69bc6e8..2ec7a5b 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json
@@ -176706,6 +176706,11 @@
      {}
     ]
    ],
+   "infrastructure/metadata/infrastructure/testdriver/generate_test_report.html.ini": [
+    [
+     {}
+    ]
+   ],
    "infrastructure/reftest-wait-ref.html": [
     [
      {}
@@ -176766,6 +176771,11 @@
      {}
     ]
    ],
+   "infrastructure/testdriver/generate_test_report-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "infrastructure/testdriver/send_keys-expected.txt": [
     [
      {}
@@ -190311,11 +190321,6 @@
      {}
     ]
    ],
-   "tools/ci/lib.sh": [
-    [
-     {}
-    ]
-   ],
    "tools/ci/make_hosts_file.py": [
     [
      {}
@@ -190326,6 +190331,11 @@
      {}
     ]
    ],
+   "tools/ci/run_tc.py": [
+    [
+     {}
+    ]
+   ],
    "tools/ci/start.sh": [
     [
      {}
@@ -260981,6 +260991,14 @@
      }
     ]
    ],
+   "infrastructure/testdriver/generate_test_report.html": [
+    [
+     "/infrastructure/testdriver/generate_test_report.html",
+     {
+      "testdriver": true
+     }
+    ]
+   ],
    "infrastructure/testdriver/send_keys.html": [
     [
      "/infrastructure/testdriver/send_keys.html",
@@ -433834,7 +433852,7 @@
    "testharness"
   ],
   "idle-detection/interceptor.https.html": [
-   "465607d33481a00ae3c0309d41d19e7c1e05a827",
+   "37922c8cf795eeeb6ec0443c3f6539aecbbce840",
    "testharness"
   ],
   "idle-detection/mock.js": [
@@ -434497,6 +434515,10 @@
    "42c98117c58bbe7b9201d1883e0c1649898b5570",
    "support"
   ],
+  "infrastructure/metadata/infrastructure/testdriver/generate_test_report.html.ini": [
+   "0b01c8369a56495d0dbf07540efb9d7a8d979790",
+   "support"
+  ],
   "infrastructure/reftest-wait-ref.html": [
    "6772c2c460e79993979688ddf46e2045b14f7d71",
    "support"
@@ -434701,6 +434723,14 @@
    "097d2a3a3f31dbd8f14cff73974cf042b6ebfcd2",
    "support"
   ],
+  "infrastructure/testdriver/generate_test_report-expected.txt": [
+   "b23b870c09bbcf76d327dca6f501375d956480a4",
+   "support"
+  ],
+  "infrastructure/testdriver/generate_test_report.html": [
+   "168c9e9956f9efae57d64948a34d7a24aa5bb44f",
+   "testharness"
+  ],
   "infrastructure/testdriver/send_keys-expected.txt": [
    "fbd705a2a1403a1c6e14a3fdfb6f7cd6a7af0193",
    "support"
@@ -437206,11 +437236,11 @@
    "support"
   ],
   "mediacapture-depth/dictionary-manual.https-expected.txt": [
-   "6f8a33ada2e7e0c9dc475d0cbd5175e087ba0faf",
+   "2b6107ccb8ba8253418492763f08bb7fc5c017d1",
    "support"
   ],
   "mediacapture-depth/dictionary-manual.https.html": [
-   "e464f293fb1d74a2820fc58217a77895396457ba",
+   "0a5b824881509baee240f385a9eeaa380a579948",
    "manual"
   ],
   "mediacapture-depth/idlharness.html": [
@@ -467478,7 +467508,7 @@
    "support"
   ],
   "tools/ci/before_install.sh": [
-   "ea53f969015f0f79a98a64abc968d4424b407a89",
+   "cde1879211b36654bc82d9a033017b514860c773",
    "support"
   ],
   "tools/ci/ci_built_diff.sh": [
@@ -467494,19 +467524,19 @@
    "support"
   ],
   "tools/ci/ci_resources_unittest.sh": [
-   "f1d0389b2d6f4f8d3ad9e34d3d23175a5ae5df3c",
+   "11190fc58d64491bae719f9ac91adb1e879886fb",
    "support"
   ],
   "tools/ci/ci_tools_unittest.sh": [
-   "55afb9f4331af97a3548550dbe8f7d4eb260cc24",
+   "366dcf66de88e6ba29fc85c9697832eead9376c3",
    "support"
   ],
   "tools/ci/ci_wpt.sh": [
-   "5af04110b20b673c3df07ba1f01a0ae6658a2968",
+   "7c37e7863e002db66c59009ae3d4360aaa717499",
    "support"
   ],
   "tools/ci/ci_wptrunner_infrastructure.sh": [
-   "267bf17525abea558833d20dca27213f6b7185c5",
+   "f3ce3e73ea796479529da554c17f61293d138d3a",
    "support"
   ],
   "tools/ci/commands.json": [
@@ -467521,10 +467551,6 @@
    "2b54327ad20506c23f6698ddd6c5c5f1def8b09f",
    "support"
   ],
-  "tools/ci/lib.sh": [
-   "8d5e6aef73b456446aaf265e35c3d0d6d252d1a7",
-   "support"
-  ],
   "tools/ci/make_hosts_file.py": [
    "d0a80ab46c7a86b476776d2dff0fcd23776dfc9e",
    "support"
@@ -467533,8 +467559,12 @@
    "5af38d0b56c1bbabb3e3748633cb7dca4f15c5e6",
    "support"
   ],
+  "tools/ci/run_tc.py": [
+   "53b0870ea9f7f7d179e1379bd6599eed3f5deca2",
+   "support"
+  ],
   "tools/ci/start.sh": [
-   "98c368427f64da37be111b1a6fcf0aa23aa63f70",
+   "18e2784e6cd01bcad8bcc556a362a019d00defdc",
    "support"
   ],
   "tools/ci/tag_master.py": [
@@ -467554,7 +467584,7 @@
    "support"
   ],
   "tools/docker/Dockerfile": [
-   "0cb2352e5fdf6117d51c9f745d18bbf453c48a1c",
+   "e60b4ea6a3a1c909c715fb7248a6f1b0cc6e9d4e",
    "support"
   ],
   "tools/docker/retry.py": [
@@ -471818,7 +471848,7 @@
    "support"
   ],
   "tools/wpt/testfiles.py": [
-   "70e695aa0100f1763233e4a680863d31f704f5d4",
+   "006e4a22d0bbc0bb2dab5a7bd237377cd77bf370",
    "support"
   ],
   "tools/wpt/tox.ini": [
@@ -478318,7 +478348,7 @@
    "support"
   ],
   "webrtc/protocol/jsep-initial-offer.https.html": [
-   "88bdfcfc2867335ca53c30f5637b38b3d12c64ca",
+   "50527f88dfe8f3f025cfffae91b347fdc2527a1d",
    "testharness"
   ],
   "webrtc/protocol/missing-fields.html": [
diff --git a/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/testdriver/generate_test_report.html.ini b/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/testdriver/generate_test_report.html.ini
new file mode 100644
index 0000000..0b01c836
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/testdriver/generate_test_report.html.ini
@@ -0,0 +1,4 @@
+[generate_test_report.html]
+  expected:
+    if product == "firefox": ERROR
+    if product == "safari": ERROR
diff --git a/third_party/blink/web_tests/external/wpt/infrastructure/testdriver/generate_test_report-expected.txt b/third_party/blink/web_tests/external/wpt/infrastructure/testdriver/generate_test_report-expected.txt
new file mode 100644
index 0000000..b23b870
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/infrastructure/testdriver/generate_test_report-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL TestDriver generate_test_report method assert_unreached: generate_test_report failed Reached unreachable code
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/infrastructure/testdriver/generate_test_report.html b/third_party/blink/web_tests/external/wpt/infrastructure/testdriver/generate_test_report.html
new file mode 100644
index 0000000..168c9e9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/infrastructure/testdriver/generate_test_report.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>TestDriver generate_test_report method</title>
+<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>
+async_test(t => {
+  test_driver
+    .generate_test_report("Test message.")
+    .then(() => t.done())
+    .catch(t.unreached_func("generate_test_report failed"));
+});
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/tools/ci/before_install.sh b/third_party/blink/web_tests/external/wpt/tools/ci/before_install.sh
index ea53f96..cde1879 100755
--- a/third_party/blink/web_tests/external/wpt/tools/ci/before_install.sh
+++ b/third_party/blink/web_tests/external/wpt/tools/ci/before_install.sh
@@ -1,6 +1,9 @@
 #!/bin/bash
 set -e
 
+export GITHUB_PULL_REQUEST=$TRAVIS_PULL_REQUEST
+export GITHUB_BRANCH=$TRAVIS_BRANCH
+
 if [[ $RUN_JOB -eq 1 ]] || ./wpt test-jobs --includes $JOB; then
     export RUN_JOB=1
     git submodule update --init --recursive 1>&2
diff --git a/third_party/blink/web_tests/external/wpt/tools/ci/ci_resources_unittest.sh b/third_party/blink/web_tests/external/wpt/tools/ci/ci_resources_unittest.sh
index f1d0389b..11190fc 100755
--- a/third_party/blink/web_tests/external/wpt/tools/ci/ci_resources_unittest.sh
+++ b/third_party/blink/web_tests/external/wpt/tools/ci/ci_resources_unittest.sh
@@ -5,13 +5,9 @@
 WPT_ROOT=$SCRIPT_DIR/../..
 cd $WPT_ROOT
 
-source tools/ci/lib.sh
-
 main() {
-    hosts_fixup
-
     cd $WPT_ROOT
-    pip install -U tox
+    pip install --user -U tox
     ./wpt install firefox browser --destination $HOME
     ./wpt install firefox webdriver --destination $HOME/firefox
     export PATH=$HOME/firefox:$PATH
diff --git a/third_party/blink/web_tests/external/wpt/tools/ci/ci_tools_unittest.sh b/third_party/blink/web_tests/external/wpt/tools/ci/ci_tools_unittest.sh
index 55afb9f..366dcf66 100755
--- a/third_party/blink/web_tests/external/wpt/tools/ci/ci_tools_unittest.sh
+++ b/third_party/blink/web_tests/external/wpt/tools/ci/ci_tools_unittest.sh
@@ -19,7 +19,7 @@
 }
 
 if ./wpt test-jobs --includes tools_unittest; then
-    pip install -U tox codecov
+    pip install --user -U tox codecov
     cd tools
     run_applicable_tox
     cd $WPT_ROOT
diff --git a/third_party/blink/web_tests/external/wpt/tools/ci/ci_wpt.sh b/third_party/blink/web_tests/external/wpt/tools/ci/ci_wpt.sh
index 5af04110..7c37e78 100755
--- a/third_party/blink/web_tests/external/wpt/tools/ci/ci_wpt.sh
+++ b/third_party/blink/web_tests/external/wpt/tools/ci/ci_wpt.sh
@@ -5,13 +5,9 @@
 WPT_ROOT=$SCRIPT_DIR/../..
 cd $WPT_ROOT
 
-source tools/ci/lib.sh
-
 main() {
     git fetch --quiet --unshallow https://github.com/web-platform-tests/wpt.git +refs/heads/*:refs/remotes/origin/*
-    hosts_fixup
-    install_chrome unstable
-    pip install -U tox codecov
+    pip install --user -U tox codecov
     cd tools/wpt
     tox
 }
diff --git a/third_party/blink/web_tests/external/wpt/tools/ci/ci_wptrunner_infrastructure.sh b/third_party/blink/web_tests/external/wpt/tools/ci/ci_wptrunner_infrastructure.sh
index 267bf17..f3ce3e7 100755
--- a/third_party/blink/web_tests/external/wpt/tools/ci/ci_wptrunner_infrastructure.sh
+++ b/third_party/blink/web_tests/external/wpt/tools/ci/ci_wptrunner_infrastructure.sh
@@ -5,7 +5,9 @@
 WPT_ROOT=$SCRIPT_DIR/../..
 cd $WPT_ROOT
 
-source tools/ci/lib.sh
+add_wpt_hosts() {
+    ./wpt make-hosts-file | sudo tee -a /etc/hosts
+}
 
 test_infrastructure() {
     local ARGS="";
@@ -21,13 +23,8 @@
     PRODUCTS=( "firefox" "chrome" )
     ./wpt manifest --rebuild -p ~/meta/MANIFEST.json
     for PRODUCT in "${PRODUCTS[@]}"; do
-        if [ "$PRODUCT" != "firefox" ]; then
-            # Firefox is expected to work using pref settings for DNS
-            # Don't adjust the hostnames in that case to ensure this keeps working
-            hosts_fixup
-        fi
         if [[ "$PRODUCT" == "chrome" ]]; then
-            install_chrome unstable
+            add_wpt_hosts
             test_infrastructure "--binary=$(which google-chrome-unstable)"
         else
             test_infrastructure
diff --git a/third_party/blink/web_tests/external/wpt/tools/ci/lib.sh b/third_party/blink/web_tests/external/wpt/tools/ci/lib.sh
deleted file mode 100644
index 8d5e6ae..0000000
--- a/third_party/blink/web_tests/external/wpt/tools/ci/lib.sh
+++ /dev/null
@@ -1,35 +0,0 @@
-hosts_fixup() {
-    echo "travis_fold:start:hosts_fixup"
-    echo "Rewriting hosts file"
-    echo "## /etc/hosts ##"
-    cat /etc/hosts
-    sudo sed -i 's/^::1\s*localhost/::1/' /etc/hosts
-    ./wpt make-hosts-file | sudo tee -a /etc/hosts
-    echo "== /etc/hosts =="
-    cat /etc/hosts
-    echo "----------------"
-    echo "travis_fold:end:hosts_fixup"
-}
-
-install_chrome() {
-    channel=$1
-    deb_archive=google-chrome-${channel}_current_amd64.deb
-    wget -q https://dl.google.com/linux/direct/$deb_archive
-
-    # If the environment provides an installation of Google Chrome, the
-    # existing binary may take precedence over the one introduced in this
-    # script. Remove any previously-existing "alternatives" prior to
-    # installation in order to ensure that the new binary is installed as
-    # intended.
-    if sudo update-alternatives --list google-chrome; then
-        sudo update-alternatives --remove-all google-chrome
-    fi
-
-    # Installation will fail in cases where the package has unmet dependencies.
-    # When this occurs, attempt to use the system package manager to fetch the
-    # required packages and retry.
-    if ! sudo dpkg --install $deb_archive; then
-      sudo apt-get install --fix-broken
-      sudo dpkg --install $deb_archive
-    fi
-}
diff --git a/third_party/blink/web_tests/external/wpt/tools/ci/run_tc.py b/third_party/blink/web_tests/external/wpt/tools/ci/run_tc.py
new file mode 100755
index 0000000..53b0870e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/tools/ci/run_tc.py
@@ -0,0 +1,261 @@
+#!/usr/bin/env python
+
+"""Wrapper script for running jobs in TaskCluster
+
+This is intended for running test jobs in TaskCluster. The script
+takes a two positional arguments which are the name of the test job
+and the script to actually run.
+
+The name of the test job is used to determine whether the script should be run
+for this push (this is in lieu of having a proper decision task). There are
+several ways that the script can be scheduled to run
+
+1. The output of wpt test-jobs includes the job name
+2. The job name is included in a job declaration (see below)
+3. The string "all" is included in the job declaration
+4. The job name is set to "all"
+
+A job declaration is a line appearing in the pull request body (for
+pull requests) or first commit message (for pushes) of the form:
+
+tc-jobs: job1,job2,[...]
+
+In addition, there are a number of keyword arguments used to set options for the
+environment in which the jobs run. Documentation for these is in the command help.
+
+As well as running the script, the script sets two environment variables;
+GITHUB_BRANCH which is the branch that the commits will merge into (if it's a PR)
+or the branch that the commits are on (if it's a push), and GITHUB_PULL_REQUEST
+which is the string "false" if the event triggering this job wasn't a pull request
+or the pull request number if it was. The semantics of these variables are chosen
+to match the corresponding TRAVIS_* variables.
+
+Note: for local testing in the Docker image the script ought to still work, but
+full functionality requires that the TASK_EVENT environment variable is set to
+the serialization of a GitHub event payload.
+"""
+
+import argparse
+import json
+import os
+import re
+import subprocess
+import sys
+try:
+    from urllib2 import urlopen
+except ImportError:
+    # Python 3 case
+    from urllib.request import urlopen
+
+
+root = os.path.abspath(
+    os.path.join(os.path.dirname(__file__),
+                 os.pardir,
+                 os.pardir))
+
+
+def run(cmd, return_stdout=False, **kwargs):
+    print(" ".join(cmd))
+    if return_stdout:
+        f = subprocess.check_output
+    else:
+        f = subprocess.check_call
+    return f(cmd, **kwargs)
+
+
+def start(cmd):
+    print(" ".join(cmd))
+    subprocess.Popen(cmd)
+
+
+def get_parser():
+    p = argparse.ArgumentParser()
+    p.add_argument("--oom-killer",
+                   action="store_true",
+                   default=False,
+                   help="Run userspace OOM killer")
+    p.add_argument("--hosts",
+                   dest="hosts_file",
+                   action="store_true",
+                   default=True,
+                   help="Setup wpt entries in hosts file")
+    p.add_argument("--no-hosts",
+                   dest="hosts_file",
+                   action="store_false",
+                   help="Don't setup wpt entries in hosts file")
+    p.add_argument("--browser",
+                   action="append",
+                   default=[],
+                   help="Browsers that will be used in the job")
+    p.add_argument("--channel",
+                   default=None,
+                   choices=["experimental", "dev", "nightly", "beta", "stable"],
+                   help="Chrome browser channel")
+    p.add_argument("--xvfb",
+                   action="store_true",
+                   help="Start xvfb")
+    p.add_argument("--checkout",
+                   help="Revision to checkout before starting job")
+    p.add_argument("job",
+                   help="Name of the job associated with the current event")
+    p.add_argument("script",
+                   help="Script to run for the job")
+    p.add_argument("script_args",
+                   nargs=argparse.REMAINDER,
+                   help="Additional arguments to pass to the script")
+    return p
+
+
+def start_userspace_oom_killer():
+    # Start userspace OOM killer: https://github.com/rfjakob/earlyoom
+    # It will report memory usage every minute and prefer to kill browsers.
+    start(["sudo", "earlyoom", "-p", "-r", "60" "--prefer=(chrome|firefox)", "--avoid=python"])
+
+
+def make_hosts_file():
+    subprocess.check_call(["sudo", "sh", "-c", "./wpt make-hosts-file >> /etc/hosts"])
+
+
+def checkout_revision(rev):
+    subprocess.check_call(["git", "checkout", "-q", rev])
+
+
+def install_chrome(channel):
+    if channel in ("experimental", "dev", "nightly"):
+        deb_archive = "google-chrome-unstable_current_amd64.deb"
+    elif channel == "beta":
+        deb_archive = "google-chrome-beta_current_amd64.deb"
+    elif channel == "stable":
+        deb_archive = "google-chrome-stable_current_amd64.deb"
+    else:
+        raise ValueError("Unrecognized release channel: %s" % channel)
+
+    dest = os.path.join("/tmp", deb_archive)
+    resp = urlopen("https://dl.google.com/linux/direct/%s" % deb_archive)
+    with open(dest, "w") as f:
+        f.write(resp.read())
+
+    subprocess.check_call(["sudo", "apt-get", "-qqy", "update"])
+    subprocess.check_call(["sudo", "gdebi", "-n", "/tmp/%s" % deb_archive])
+
+
+def start_xvfb():
+    start(["sudo", "Xvfb", os.environ["DISPLAY"], "-screen", "0",
+           "%sx%sx%s" % (os.environ["SCREEN_WIDTH"],
+                         os.environ["SCREEN_HEIGHT"],
+                         os.environ["SCREEN_DEPTH"])])
+    start(["sudo", "fluxbox", "-display", os.environ["DISPLAY"]])
+
+
+def get_extra_jobs(event):
+    body = None
+    jobs = set()
+    if "commits" in event:
+        body = event["commits"][0]["message"]
+    elif "pull_request" in event:
+        body = event["pull_request"]["body"]
+
+    if not body:
+        return jobs
+
+    regexp = re.compile(r"\s*tc-jobs:(.*)$")
+
+    for line in body.splitlines():
+        m = regexp.match(line)
+        if m:
+            items = m.group(1)
+            for item in items.split(","):
+                jobs.add(item.strip())
+            break
+    return jobs
+
+
+def set_variables(event):
+    # Set some variables that we use to get the commits on the current branch
+    ref_prefix = "refs/heads/"
+    pull_request = "false"
+    branch = None
+    if "pull_request" in event:
+        pull_request = str(event["pull_request"]["number"])
+        # Note that this is the branch that a PR will merge to,
+        # not the branch name for the PR
+        branch = event["pull_request"]["base"]["ref"]
+    elif "ref" in event:
+        branch = event["ref"]
+        if branch.startswith(ref_prefix):
+            branch = branch[len(ref_prefix):]
+
+    os.environ["GITHUB_PULL_REQUEST"] = pull_request
+    if branch:
+        os.environ["GITHUB_BRANCH"] = branch
+
+
+def include_job(job):
+    jobs_str = run([os.path.join(root, "wpt"),
+                    "test-jobs"], return_stdout=True)
+    print(jobs_str)
+    return job in set(jobs_str.splitlines())
+
+
+def setup_environment(args):
+    if args.hosts_file:
+        make_hosts_file()
+
+    if "chrome" in args.browser:
+        assert args.channel is not None
+        install_chrome(args.channel)
+
+    if args.xvfb:
+        start_xvfb()
+
+    if args.oom_killer:
+        start_userspace_oom_killer()
+
+    if args.checkout:
+        checkout_revision(args.checkout)
+
+
+def main():
+    args = get_parser().parse_args()
+    try:
+        event = json.loads(os.environ["TASK_EVENT"])
+    except KeyError:
+        # For example under local testing
+        event = {}
+
+    if event:
+        set_variables(event)
+
+    if os.environ.get("GITHUB_BRANCH"):
+        # Ensure that the remote base branch exists
+        # TODO: move this somewhere earlier in the task
+        run(["git", "fetch", "origin", "%s:%s" % (os.environ["GITHUB_BRANCH"],
+                                                  os.environ["GITHUB_BRANCH"])])
+
+    extra_jobs = get_extra_jobs(event)
+
+    job = args.job
+
+    run_if = [(lambda: job == "all", "job set to 'all'"),
+              (lambda:"all" in extra_jobs, "Manually specified jobs includes 'all'"),
+              (lambda:job in extra_jobs, "Manually specified jobs includes '%s'" % job),
+              (lambda:include_job(job), "CI required jobs includes '%s'" % job)]
+
+    for fn, msg in run_if:
+        if fn():
+            print(msg)
+            break
+    else:
+        print("Job not scheduled for this push")
+        return
+
+    # Run the job
+    setup_environment(args)
+    os.chdir(root)
+    cmd = [args.script] + args.script_args
+    print(cmd)
+    sys.exit(subprocess.call(cmd))
+
+
+if __name__ == "__main__":
+    main()
diff --git a/third_party/blink/web_tests/external/wpt/tools/ci/start.sh b/third_party/blink/web_tests/external/wpt/tools/ci/start.sh
index 98c3684..18e2784 100644
--- a/third_party/blink/web_tests/external/wpt/tools/ci/start.sh
+++ b/third_party/blink/web_tests/external/wpt/tools/ci/start.sh
@@ -1,31 +1 @@
-# This script is designed to be sourced from tools/docker/start.sh
-
-# Start userspace OOM killer: https://github.com/rfjakob/earlyoom
-# It will report memory usage every minute and prefer to kill browsers.
-sudo earlyoom -p -r 60 --prefer '(chrome|firefox)' --avoid 'python' &
-
-sudo sh -c './wpt make-hosts-file >> /etc/hosts'
-
-if [[ $BROWSER == "chrome" ]] || [[ "$BROWSER" == all ]]
-then
-    # Install Chrome dev
-    if [[ "$CHANNEL" == "dev" ]] || [[ "$CHANNEL" == "nightly" ]]
-    then
-       deb_archive=google-chrome-unstable_current_amd64.deb
-    elif [[ "$CHANNEL" == "beta" ]]
-    then
-        deb_archive=google-chrome-beta_current_amd64.deb
-    elif [[ "$CHANNEL" == "stable" ]]
-    then
-        deb_archive=google-chrome-stable_current_amd64.deb
-    else
-        echo Unrecognized release channel: $CHANNEL >&2
-        exit 1
-    fi
-    wget -O /tmp/$deb_archive https://dl.google.com/linux/direct/$deb_archive
-
-    sudo apt-get -qqy update && sudo gdebi -n /tmp/$deb_archive
-fi
-
-sudo Xvfb $DISPLAY -screen 0 ${SCREEN_WIDTH}x${SCREEN_HEIGHT}x${SCREEN_DEPTH} &
-sudo fluxbox -display $DISPLAY &
+# Contents of this script superceeded by tools/ci/run_tc.py
diff --git a/third_party/blink/web_tests/external/wpt/tools/docker/Dockerfile b/third_party/blink/web_tests/external/wpt/tools/docker/Dockerfile
index 0cb2352..e60b4ea 100644
--- a/third_party/blink/web_tests/external/wpt/tools/docker/Dockerfile
+++ b/third_party/blink/web_tests/external/wpt/tools/docker/Dockerfile
@@ -46,6 +46,12 @@
 RUN echo "${TZ}" > /etc/timezone \
   && dpkg-reconfigure --frontend noninteractive tzdata
 
+# Set the locale
+RUN locale-gen en_US.UTF-8
+ENV LANG en_US.UTF-8
+ENV LANGUAGE en_US:en
+ENV LC_ALL en_US.UTF-8
+
 RUN useradd test \
          --shell /bin/bash  \
          --create-home \
@@ -68,7 +74,7 @@
 RUN mkdir -p /home/test/artifacts
 RUN mkdir -p /home/test/bin
 
-ENV PATH="/home/test/bin:${PATH}"
+ENV PATH="/home/test/bin:/home/test/.local/bin:${PATH}"
 
 WORKDIR /home/test/
 
diff --git a/third_party/blink/web_tests/external/wpt/tools/wpt/testfiles.py b/third_party/blink/web_tests/external/wpt/tools/wpt/testfiles.py
index 70e695a..006e4a22d 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wpt/testfiles.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wpt/testfiles.py
@@ -34,15 +34,15 @@
 
 def branch_point():
     git = get_git_cmd(wpt_root)
-    if (os.environ.get("TRAVIS_PULL_REQUEST", "false") == "false" and
-        os.environ.get("TRAVIS_BRANCH") == "master"):
+    if (os.environ.get("GITHUB_PULL_REQUEST", "false") == "false" and
+        os.environ.get("GITHUB_BRANCH") == "master"):
         # For builds on the master branch just return the HEAD commit
         return git("rev-parse", "HEAD")
-    elif os.environ.get("TRAVIS_PULL_REQUEST", "false") != "false":
-        # This is a PR, so the base branch is in TRAVIS_BRANCH
-        travis_branch = os.environ.get("TRAVIS_BRANCH")
-        assert travis_branch, "TRAVIS_BRANCH environment variable is defined"
-        branch_point = git("merge-base", "HEAD", travis_branch)
+    elif os.environ.get("GITHUB_PULL_REQUEST", "false") != "false":
+        # This is a PR, so the base branch is in GITHUB_BRANCH
+        base_branch = os.environ.get("GITHUB_BRANCH")
+        assert base_branch, "GITHUB_BRANCH environment variable is defined"
+        branch_point = git("merge-base", "HEAD", base_branch)
     else:
         # Otherwise we aren't on a PR, so we try to find commits that are only in the
         # current branch c.f.
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/protocol/jsep-initial-offer.https.html b/third_party/blink/web_tests/external/wpt/webrtc/protocol/jsep-initial-offer.https.html
index 88bdfcfc2..50527f8 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc/protocol/jsep-initial-offer.https.html
+++ b/third_party/blink/web_tests/external/wpt/webrtc/protocol/jsep-initial-offer.https.html
@@ -16,19 +16,25 @@
     // The first 3 lines are dictated by JSEP.
     assert_equals(offer_lines[0], "v=0");
     assert_equals(offer_lines[1].slice(0, 2), "o=");
-    // JSEP says that the address part SHOULD be a meaningless address
-    // "such as" IN IP4 127.0.0.1. We do strict matching here in order
-    // to detect if anyone ever uses something different.
-    assert_regexp_match(offer_lines[1], /^o=- \d+ \d+ IN IP4 127.0.0.1$/);
-    const fields = RegExp(/^o=- (\d+) (\d+)/).exec(offer_lines[1]);
+
+    assert_regexp_match(offer_lines[1], /^o=\S+ \d+ \d+ IN IP4 \S+$/);
+    const fields = RegExp(/^o=\S+ (\d+) (\d+) IN IP4 (\S+)/).exec(offer_lines[1]);
     // Per RFC 3264, the sess-id should be representable in an uint64
     // Note: JSEP -24 has this wrong - see bug:
     // https://github.com/rtcweb-wg/jsep/issues/855
     assert_less_than(Number(fields[1]), 2**64);
     // Per RFC 3264, the version should be less than 2^62 to avoid overflow
     assert_less_than(Number(fields[2]), 2**62);
-    // Note: using - in s=- is a SHOULD in JSEP, not a MUST.
-    assert_equals(offer_lines[2], "s=-");
+    // JSEP says that the address part SHOULD be a meaningless address
+    // "such as" IN IP4 0.0.0.0. This is to prevent unintentional disclosure
+    // of IP addresses, so this is important enough to verify. Right now we
+    // allow 127.0.0.1 and 0.0.0.0, but there are other things we could allow.
+    // Maybe 0.0.0.0/8, 127.0.0.0/8, 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24?
+    // (See RFC 3330, RFC 5737)
+    assert_true(fields[3] == "0.0.0.0" || fields[3] == "127.0.0.1",
+      fields[3] + " must be a meaningless IPV4 address")
+
+    assert_regexp_match(offer_lines[2], /^s=\S+$/);
     // After this, the order is not dictated by JSEP.
     // TODO: Check lines subsequent to the s= line.
   }, 'Offer conforms to basic SDP requirements');
diff --git a/third_party/blink/web_tests/fast/css/opacity-float.html b/third_party/blink/web_tests/fast/css/opacity-float.html
index c6473d6a..57c9abc 100644
--- a/third_party/blink/web_tests/fast/css/opacity-float.html
+++ b/third_party/blink/web_tests/fast/css/opacity-float.html
@@ -20,4 +20,9 @@
     document.write('PASS');
 else
     document.write('FAIL: ' + opacity);
+
+if (window.testRunner) {
+    // Put ourselves back(?) in a locale where 0.9 is written as "0.9".
+    testRunner.setPOSIXLocale("C");
+}
 </script>
diff --git a/third_party/blink/web_tests/fast/forms/calendar-picker/date-picker-ax.html b/third_party/blink/web_tests/fast/forms/calendar-picker/date-picker-ax.html
index 8df3af5c..7063a0a 100644
--- a/third_party/blink/web_tests/fast/forms/calendar-picker/date-picker-ax.html
+++ b/third_party/blink/web_tests/fast/forms/calendar-picker/date-picker-ax.html
@@ -7,7 +7,8 @@
   <script src="resources/calendar-picker-common.js"></script>
 </head>
 <body>
-
+<p id="description"></p>
+<div id="console"></div>
 <input type="date" id="date1" value="2000-01-02">
 
 <script>
@@ -17,10 +18,10 @@
 
     accessibilityController.setNotificationListener(function(axnode, type) {
         if (type == 'Focus') {
-            console.log('Focused: ' + accessibilityController.focusedElement.name.replace(/,/g, ''));
+            console.log('Focused: ' + escapeHTML(accessibilityController.focusedElement.name.replace(/,/g, '')));
         } else if (type == 'MarkDirty') {
             if (++markDirtyCounter == 2) {
-                setTimeout(testButtonDescription, 0);
+                testButtonDescription();
             } else if (markDirtyCounter == 3) {
                 // Highlight 2000-02 in the month popup.
                 setTimeout(function() { eventSender.keyDown('ArrowRight'); }, 0);
diff --git a/third_party/blink/web_tests/fast/forms/calendar-picker/month-picker-ax-expected.txt b/third_party/blink/web_tests/fast/forms/calendar-picker/month-picker-ax-expected.txt
new file mode 100644
index 0000000..c462e39
--- /dev/null
+++ b/third_party/blink/web_tests/fast/forms/calendar-picker/month-picker-ax-expected.txt
@@ -0,0 +1,12 @@
+Tests if typing an arrow key dispatches |Focus| and |MarkDirty| a11y events.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+Focused
+Focused
+PASS Received MarkDirty
+PASS Received MarkDirty
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/third_party/blink/web_tests/fast/forms/calendar-picker/month-picker-ax.html b/third_party/blink/web_tests/fast/forms/calendar-picker/month-picker-ax.html
index e2f9fbad..13a8921 100644
--- a/third_party/blink/web_tests/fast/forms/calendar-picker/month-picker-ax.html
+++ b/third_party/blink/web_tests/fast/forms/calendar-picker/month-picker-ax.html
@@ -1,39 +1,32 @@
 <!DOCTYPE html>
 <html>
-<head>
-  <script src="../../../resources/testharness.js"></script>
-  <script src="../../../resources/testharnessreport.js"></script>
-  <script src="../../forms/resources/picker-common.js"></script>
-  <script src="resources/calendar-picker-common.js"></script>
-</head>
-
 <body>
+<script src="../../../resources/js-test.js"></script>
+<script src="../../forms/resources/picker-common.js"></script>
+<script src="resources/calendar-picker-common.js"></script>
+<p id="description"></p>
+<div id="console"></div>
 <input type="month" id="month1" value="2000-01">
 
 <script>
-async_test((t) => {
-    var markDirtyCounter = 0;
-    var focusCounter = 0;
-    accessibilityController.setNotificationListener(function(axnode, type) {
-        if (type == 'Focus') {
-            console.log('Focused: ' + accessibilityController.focusedElement.name.replace(/,/g, ''));
-            focusCounter++;
-        } else if (type == 'MarkDirty') {
-            console.log('Received MarkDirty');
-            markDirtyCounter++;
-            if (focusCounter == 2 && markDirtyCounter == 2) {
-                t.done();
-            }
-        }
-    });
+description('Tests if typing an arrow key dispatches |Focus| and |MarkDirty| a11y events.');
 
-    var month1 = document.getElementById('month1');
-    function test1() {
-        eventSender.keyDown('ArrowDown');
+accessibilityController.setNotificationListener(function(axnode, type) {
+    if (type == 'Focus') {
+        debug('Focused');
+    } else if (type == 'MarkDirty') {
+        testPassed('Received MarkDirty');
+        if (++markDirtyCounter == 2)
+            finishJSTest();
     }
+});
+var markDirtyCounter = 0;
+var month1 = document.getElementById('month1');
+openPicker(month1, test1);
 
-    openPicker(month1, test1);
-}, 'Tests if typing an arrow key dispatches |Focus| and |MarkDirty| a11y events.');
+function test1() {
+    eventSender.keyDown('ArrowRight');
+}
 
 </script>
 </body>
diff --git a/third_party/blink/web_tests/fast/forms/calendar-picker/week-picker-ax-expected.txt b/third_party/blink/web_tests/fast/forms/calendar-picker/week-picker-ax-expected.txt
new file mode 100644
index 0000000..c462e39
--- /dev/null
+++ b/third_party/blink/web_tests/fast/forms/calendar-picker/week-picker-ax-expected.txt
@@ -0,0 +1,12 @@
+Tests if typing an arrow key dispatches |Focus| and |MarkDirty| a11y events.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+Focused
+Focused
+PASS Received MarkDirty
+PASS Received MarkDirty
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/third_party/blink/web_tests/fast/forms/calendar-picker/week-picker-ax.html b/third_party/blink/web_tests/fast/forms/calendar-picker/week-picker-ax.html
index 1b3172f..aeef1f7 100644
--- a/third_party/blink/web_tests/fast/forms/calendar-picker/week-picker-ax.html
+++ b/third_party/blink/web_tests/fast/forms/calendar-picker/week-picker-ax.html
@@ -1,41 +1,32 @@
 <!DOCTYPE html>
 <html>
-<head>
-  <script src="../../../resources/testharness.js"></script>
-  <script src="../../../resources/testharnessreport.js"></script>
-  <script src="../../forms/resources/picker-common.js"></script>
-  <script src="resources/calendar-picker-common.js"></script>
-</head>
-
 <body>
+<script src="../../../resources/js-test.js"></script>
+<script src="../../forms/resources/picker-common.js"></script>
+<script src="resources/calendar-picker-common.js"></script>
+<p id="description"></p>
+<div id="console"></div>
 <input type="week" id="week1" value="2000-W13">
 
 <script>
-async_test((t) => {
-    var markDirtyCounter = 0;
-    var focusCounter = 0;
-    accessibilityController.setNotificationListener(function(axnode, type) {
-        if (type == 'Focus') {
-            console.log('Focused: ' + accessibilityController.focusedElement.name.replace(/,/g, ''));
-            focusCounter++;
-        } else if (type == 'MarkDirty') {
-            console.log('Received MarkDirty');
-            markDirtyCounter++;
-            if (focusCounter == 2 && markDirtyCounter == 2) {
-                t.done();
-            }
-        }
-    });
+description('Tests if typing an arrow key dispatches |Focus| and |MarkDirty| a11y events.');
 
-    var week1 = document.getElementById('week1');
-    function test1() {
-        eventSender.keyDown('ArrowDown');
+accessibilityController.setNotificationListener(function(axnode, type) {
+    if (type == 'Focus') {
+        debug('Focused');
+    } else if (type == 'MarkDirty') {
+        testPassed('Received MarkDirty');
+        if (++markDirtyCounter == 2)
+            finishJSTest();
     }
+});
+var markDirtyCounter = 0;
+var week1 = document.getElementById('week1');
+openPicker(week1, test1);
 
-    openPicker(week1, test1);
-}, 'Tests if typing an arrow key dispatches |Focus| and |MarkDirty| a11y events.');
-
-
+function test1() {
+    eventSender.keyDown('ArrowDown');
+}
 
 </script>
 </body>
diff --git a/third_party/blink/web_tests/fast/forms/datetimelocal-multiple-fields/datetimelocal-multiple-fields-ax-value-changed-notification.html b/third_party/blink/web_tests/fast/forms/datetimelocal-multiple-fields/datetimelocal-multiple-fields-ax-value-changed-notification.html
index 255b11e..7026b596 100644
--- a/third_party/blink/web_tests/fast/forms/datetimelocal-multiple-fields/datetimelocal-multiple-fields-ax-value-changed-notification.html
+++ b/third_party/blink/web_tests/fast/forms/datetimelocal-multiple-fields/datetimelocal-multiple-fields-ax-value-changed-notification.html
@@ -13,12 +13,12 @@
         [ "Focus", "Month", "10" ],
         [ "Focus", "Day", "09" ],
         [ "Focus", "Year", "2012" ],
-        [ "ValueChanged", "Day", "04" ],
-        [ "ValueChanged", "Day", "04" ],
-        [ "ValueChanged", "Year", "2013" ],
-        [ "ValueChanged", "Year", "2013" ],
         [ "Focus", "Hours", "12" ],
         [ "Focus", "Minutes", "34" ],
+        [ "ValueChanged", "Day", "04" ],
+        [ "ValueChanged", "Day", "04" ],
+        [ "ValueChanged", "Year", "2013" ],
+        [ "ValueChanged", "Year", "2013" ],
         [ "ValueChanged", "Hours", "02" ],
         [ "ValueChanged", "Hours", "02" ],
         [ "ValueChanged", "Minutes", "33" ],
@@ -29,6 +29,8 @@
         t.step_func((element, notification) => {
             if (notification == 'Focus' || notification == 'ValueChanged') {
                 var next_expectation = expected.shift();
+                console.log('next_expectation: ' + JSON.stringify(next_expectation));
+                console.log('actual: [' + notification + ', ' + element.name.trim() + ', ' + element.valueDescription.substr(20) + ']');
                 assert_equals(notification, next_expectation[0]);
                 assert_equals(element.name.trim(), next_expectation[1]);
                 assert_equals(element.valueDescription.substr(20), next_expectation[2]);
diff --git a/third_party/blink/web_tests/fast/forms/month-multiple-fields/month-multiple-fields-ax-value-changed-notification.html b/third_party/blink/web_tests/fast/forms/month-multiple-fields/month-multiple-fields-ax-value-changed-notification.html
index 4849cb3..9d99741 100644
--- a/third_party/blink/web_tests/fast/forms/month-multiple-fields/month-multiple-fields-ax-value-changed-notification.html
+++ b/third_party/blink/web_tests/fast/forms/month-multiple-fields/month-multiple-fields-ax-value-changed-notification.html
@@ -12,8 +12,6 @@
     var expected = [
         [ "Focus", "Month", "October" ],
         [ "Focus", "Year", "2012" ],
-        [ "ValueChanged", "Year", "0004" ],
-        [ "ValueChanged", "Year", "0004" ],
         [ "ValueChanged", "Year", "0005" ],
         [ "ValueChanged", "Year", "0005" ] ];
 
@@ -22,6 +20,8 @@
         t.step_func((element, notification) => {
             if (notification == 'Focus' || notification == 'ValueChanged') {
                 var next_expectation = expected.shift();
+                console.log('next_expectation: ' + JSON.stringify(next_expectation));
+                console.log('actual: [' + notification + ', ' + element.name.trim() + ', ' + element.valueDescription.substr(20) + ']');
                 assert_equals(notification, next_expectation[0]);
                 assert_equals(element.name.trim(), next_expectation[1]);
                 assert_equals(element.valueDescription.substr(20), next_expectation[2]);
diff --git a/third_party/blink/web_tests/fast/forms/time-multiple-fields/time-multiple-fields-ax-value-changed-notification.html b/third_party/blink/web_tests/fast/forms/time-multiple-fields/time-multiple-fields-ax-value-changed-notification.html
index 59b833e8..7ac02605 100644
--- a/third_party/blink/web_tests/fast/forms/time-multiple-fields/time-multiple-fields-ax-value-changed-notification.html
+++ b/third_party/blink/web_tests/fast/forms/time-multiple-fields/time-multiple-fields-ax-value-changed-notification.html
@@ -12,8 +12,6 @@
     var expected = [
         [ "Focus", "Hours", "12" ],
         [ "Focus", "Minutes", "34" ],
-        [ "ValueChanged", "Minutes", "04" ],
-        [ "ValueChanged", "Minutes", "04" ],
         [ "ValueChanged", "Minutes", "05" ],
         [ "ValueChanged", "Minutes", "05" ] ];
 
@@ -21,6 +19,8 @@
         t.step_func((element, notification) => {
             if (notification == 'Focus' || notification == 'ValueChanged') {
                 var next_expectation = expected.shift();
+                console.log('next_expectation: ' + JSON.stringify(next_expectation));
+                console.log('actual: [' + notification + ', ' + element.name.trim() + ', ' + element.valueDescription.substr(20) + ']');
                 assert_equals(notification, next_expectation[0]);
                 assert_equals(element.name.trim(), next_expectation[1]);
                 assert_equals(element.valueDescription.substr(20), next_expectation[2]);
diff --git a/third_party/blink/web_tests/fast/forms/week-multiple-fields/week-multiple-fields-ax-value-changed-notification.html b/third_party/blink/web_tests/fast/forms/week-multiple-fields/week-multiple-fields-ax-value-changed-notification.html
index a72e6da3..9b8280d 100644
--- a/third_party/blink/web_tests/fast/forms/week-multiple-fields/week-multiple-fields-ax-value-changed-notification.html
+++ b/third_party/blink/web_tests/fast/forms/week-multiple-fields/week-multiple-fields-ax-value-changed-notification.html
@@ -12,8 +12,6 @@
     var expected = [
         [ "Focus", "Week", "10" ],
         [ "Focus", "Year", "2012" ],
-        [ "ValueChanged", "Year", "0004" ],
-        [ "ValueChanged", "Year", "0004" ],
         [ "ValueChanged", "Year", "0005" ],
         [ "ValueChanged", "Year", "0005" ] ];
 
@@ -21,6 +19,8 @@
         t.step_func((element, notification) => {
             if (notification == 'Focus' || notification == 'ValueChanged') {
                 var next_expectation = expected.shift();
+                console.log('next_expectation: ' + JSON.stringify(next_expectation));
+                console.log('actual: [' + notification + ', ' + element.name.trim() + ', ' + element.valueDescription.substr(20) + ']');
                 assert_equals(notification, next_expectation[0]);
                 assert_equals(element.name.trim(), next_expectation[1]);
                 assert_equals(element.valueDescription.substr(20), next_expectation[2]);
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/accessibility/edit-aria-attributes.js b/third_party/blink/web_tests/http/tests/devtools/elements/accessibility/edit-aria-attributes.js
index 41bc4b7..ec999f70c 100644
--- a/third_party/blink/web_tests/http/tests/devtools/elements/accessibility/edit-aria-attributes.js
+++ b/third_party/blink/web_tests/http/tests/devtools/elements/accessibility/edit-aria-attributes.js
@@ -34,14 +34,9 @@
     treeElement._startEditing();
     treeElement._prompt._element.textContent = 'radio';
     treeElement._prompt._element.dispatchEvent(TestRunner.createKeyEvent('Enter'));
-    // Give the document lifecycle a chance to run before updating the view.
-    window.setTimeout(() => {
-      self.runtime.sharedInstance(Accessibility.AccessibilitySidebarView)
-          .doUpdate()
-          .then(() => {
-            postRoleChange();
-          });
-    }, 0);
+    self.runtime.sharedInstance(Accessibility.AccessibilitySidebarView).doUpdate().then(() => {
+      postRoleChange();
+    });
   }
 
   function postRoleChange() {
diff --git a/third_party/blink/web_tests/media/video-without-controls-not-selected.html b/third_party/blink/web_tests/media/video-without-controls-not-selected.html
new file mode 100644
index 0000000..bc633d1
--- /dev/null
+++ b/third_party/blink/web_tests/media/video-without-controls-not-selected.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<title>Tests that the containing focusable div is selected instead of the video when there are no controls.</title>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="media-controls.js"></script>
+<div id="container" tabindex=0>
+  <video></video>
+</div>
+<script>
+async_test(t => {
+  const video = document.querySelector("video");
+  window.addEventListener('load', t.step_func(() => {
+    // Click on the video.
+    var coords = elementCoordinates(video);
+    singleTapAtCoordinates(coords[0], coords[1], t.step_func_done(() => {
+
+      // There should be no focus on the video, only the containing div.
+      assert_equals(document.activeElement, document.getElementById("container"));
+    }));
+  }));
+});
+</script>
diff --git a/third_party/blink/web_tests/platform/android/fast/forms/date-multiple-fields/date-multiple-fields-ax-value-changed-notification-expected.txt b/third_party/blink/web_tests/platform/android/fast/forms/date-multiple-fields/date-multiple-fields-ax-value-changed-notification-expected.txt
new file mode 100644
index 0000000..b13f6d6b
--- /dev/null
+++ b/third_party/blink/web_tests/platform/android/fast/forms/date-multiple-fields/date-multiple-fields-ax-value-changed-notification-expected.txt
@@ -0,0 +1,11 @@
+This test checks value changed accessibility notifications.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+Focus =AXValueDescription: 
+
+PASS successfullyParsed is true
+
+
+TEST COMPLETE
+
diff --git a/third_party/blink/web_tests/platform/linux/fast/css/opacity-float-expected.txt b/third_party/blink/web_tests/platform/linux/fast/css/opacity-float-expected.txt
deleted file mode 100644
index f65ad1f..0000000
--- a/third_party/blink/web_tests/platform/linux/fast/css/opacity-float-expected.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-This test verifies that reading a floating-point opacity from CSS attributes gets back a properly-formatted float. Improperly handling locales that cause decimals to be written as commas might break it.
-
-PASS
diff --git a/third_party/blink/web_tests/platform/linux/virtual/gpu/fast/canvas/canvas-text-alignment-expected.png b/third_party/blink/web_tests/platform/linux/virtual/gpu/fast/canvas/canvas-text-alignment-expected.png
index fec54baf..800982e 100644
--- a/third_party/blink/web_tests/platform/linux/virtual/gpu/fast/canvas/canvas-text-alignment-expected.png
+++ b/third_party/blink/web_tests/platform/linux/virtual/gpu/fast/canvas/canvas-text-alignment-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/gpu/fast/canvas/fillrect_gradient-expected.png b/third_party/blink/web_tests/platform/linux/virtual/gpu/fast/canvas/fillrect_gradient-expected.png
index aa339c7..d5ce25a0 100644
--- a/third_party/blink/web_tests/platform/linux/virtual/gpu/fast/canvas/fillrect_gradient-expected.png
+++ b/third_party/blink/web_tests/platform/linux/virtual/gpu/fast/canvas/fillrect_gradient-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/gpu/fast/canvas/canvas-text-alignment-expected.png b/third_party/blink/web_tests/platform/mac/virtual/gpu/fast/canvas/canvas-text-alignment-expected.png
index 31d31aed..837d1a904 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/gpu/fast/canvas/canvas-text-alignment-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/gpu/fast/canvas/canvas-text-alignment-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/gpu/fast/canvas/fillrect_gradient-expected.png b/third_party/blink/web_tests/platform/mac/virtual/gpu/fast/canvas/fillrect_gradient-expected.png
index fe14384..820eb3d2 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/gpu/fast/canvas/fillrect_gradient-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/gpu/fast/canvas/fillrect_gradient-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/css/opacity-float-expected.txt b/third_party/blink/web_tests/platform/win/fast/css/opacity-float-expected.txt
deleted file mode 100644
index b81983d..0000000
--- a/third_party/blink/web_tests/platform/win/fast/css/opacity-float-expected.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-This test verifies that reading a floating-point opacity from CSS attributes gets back a properly-formatted float. Improperly handling locales that cause decimals to be written as commas might break it.
-
-FAIL: 0,9
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/canvas-text-alignment-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/canvas-text-alignment-expected.png
index f9312c47..8df81605 100644
--- a/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/canvas-text-alignment-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/canvas-text-alignment-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/fillrect_gradient-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/fillrect_gradient-expected.png
index 2ba127b..4cb14a4 100644
--- a/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/fillrect_gradient-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/fillrect_gradient-expected.png
Binary files differ
diff --git a/tools/licenses.py b/tools/licenses.py
index f893756..975d1ff 100755
--- a/tools/licenses.py
+++ b/tools/licenses.py
@@ -289,6 +289,8 @@
     os.path.join('base', 'third_party', 'symbolize'),
     os.path.join('base', 'third_party', 'xdg_mime'),
     os.path.join('base', 'third_party', 'xdg_user_dirs'),
+    os.path.join('buildtools', 'third_party', 'libc++'),
+    os.path.join('buildtools', 'third_party', 'libc++abi'),
     os.path.join('chrome', 'installer', 'mac', 'third_party', 'bsdiff'),
     os.path.join('chrome', 'installer', 'mac', 'third_party', 'xz'),
     os.path.join('chrome', 'test', 'data', 'third_party', 'kraken'),
@@ -510,7 +512,7 @@
     return os.path.join(_REPOSITORY_ROOT, 'buildtools', subdir, exe)
 
 
-def GetThirdPartyDepsFromGNDepsOutput(gn_deps):
+def GetThirdPartyDepsFromGNDepsOutput(gn_deps, target_os):
     """Returns third_party/foo directories given the output of "gn desc deps".
 
     Note that it always returns the direct sub-directory of third_party
@@ -525,13 +527,22 @@
         relative_build_dep = os.path.relpath(
             absolute_build_dep, _REPOSITORY_ROOT)
         m = re.search(
-            r'^((.+/)?third_party/[^/]+)/(.+/)?BUILD\.gn$', relative_build_dep)
-        if m and not os.path.join('build', 'secondary') in relative_build_dep:
-            third_party_deps.add(m.group(1))
+            r'^((.+/)?third_party/[^/]+/)(.+/)?BUILD\.gn$', relative_build_dep)
+        if not m:
+            continue
+        third_party_path = m.group(1)
+        if any(third_party_path.startswith(p + '/') for p in PRUNE_PATHS):
+            continue
+        if (target_os == 'ios' and
+            any(third_party_path.startswith(p + '/')
+                for p in KNOWN_NON_IOS_LIBRARIES)):
+            # Skip over files that are known not to be used on iOS.
+            continue
+        third_party_deps.add(third_party_path[:-1])
     return third_party_deps
 
 
-def FindThirdPartyDeps(gn_out_dir, gn_target):
+def FindThirdPartyDeps(gn_out_dir, gn_target, target_os):
     if not gn_out_dir:
         raise RuntimeError("--gn-out-dir is required if --gn-target is used.")
 
@@ -551,7 +562,7 @@
         if tmp_dir and os.path.exists(tmp_dir):
             shutil.rmtree(tmp_dir)
 
-    return GetThirdPartyDepsFromGNDepsOutput(gn_deps)
+    return GetThirdPartyDepsFromGNDepsOutput(gn_deps, target_os)
 
 
 def ScanThirdPartyDirs(root=None):
@@ -601,7 +612,7 @@
         }
 
     if gn_target:
-        third_party_dirs = FindThirdPartyDeps(gn_out_dir, gn_target)
+        third_party_dirs = FindThirdPartyDeps(gn_out_dir, gn_target, target_os)
 
         # Sanity-check to raise a build error if invalid gn_... settings are
         # somehow passed to this script.
@@ -700,7 +711,7 @@
         return f.read()
 
 
-def GenerateLicenseFile(output_file, gn_out_dir, gn_target):
+def GenerateLicenseFile(output_file, gn_out_dir, gn_target, target_os):
     """Generate a plain-text LICENSE file which can be used when you ship a part
     of Chromium code (specified by gn_target) as a stand-alone library
     (e.g., //ios/web_view).
@@ -708,7 +719,7 @@
     The LICENSE file contains licenses of both Chromium and third-party
     libraries which gn_target depends on. """
 
-    third_party_dirs = FindThirdPartyDeps(gn_out_dir, gn_target)
+    third_party_dirs = FindThirdPartyDeps(gn_out_dir, gn_target, target_os)
 
     # Start with Chromium's LICENSE file.
     content = [_ReadFile('LICENSE')]
@@ -763,7 +774,8 @@
             return 1
     elif args.command == 'license_file':
         if not GenerateLicenseFile(
-                args.output_file, args.gn_out_dir, args.gn_target):
+                args.output_file, args.gn_out_dir, args.gn_target,
+                args.target_os):
             return 1
     else:
         print __doc__
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index dda3435..ba50196 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -1822,6 +1822,7 @@
       label="No gesture associated with call to prompt (removed Aug 2018)"/>
   <int value="33" label="Waiting for native data"/>
   <int value="34" label="App install dialog shown"/>
+  <int value="35" label="Cannot check service worker for null start URL"/>
 </enum>
 
 <enum name="AppBannersInstallEvent">
@@ -57253,6 +57254,38 @@
   <int value="4" label="Constant."/>
 </enum>
 
+<enum name="WebRtcEventLoggingApiEnum">
+  <int value="0" label="kSuccess"/>
+  <int value="1" label="kDeadRph"/>
+  <int value="2" label="kFeatureDisabled"/>
+  <int value="3" label="kIncognito"/>
+  <int value="4" label="kInvalidArguments"/>
+  <int value="5" label="kIllegalSessionId"/>
+  <int value="6" label="kDisabledBrowserContext"/>
+  <int value="7" label="kUnknownOrInvalidPeerConnection"/>
+  <int value="8" label="kAlreadyLogging"/>
+  <int value="9" label="kNoAdditionalLogsAllowed"/>
+  <int value="10" label="kLogPathNotAvailable"/>
+  <int value="11" label="kHistoryPathNotAvailable"/>
+  <int value="12" label="kFileCreationError"/>
+</enum>
+
+<enum name="WebRtcEventLoggingUploadEnum">
+  <int value="0" label="kSuccess"/>
+  <int value="1" label="kLogFileWriteError"/>
+  <int value="2" label="kActiveLogCancelledDueToCacheClear"/>
+  <int value="3" label="kPendingLogDeletedDueToCacheClear"/>
+  <int value="4" label="kHistoryFileCreationError"/>
+  <int value="5" label="kHistoryFileWriteError"/>
+  <int value="6" label="kLogFileReadError"/>
+  <int value="7" label="kLogFileNameError"/>
+  <int value="8" label="kUploadCancelled"/>
+  <int value="9" label="kUploadFailure"/>
+  <int value="10" label="kIncompletePastUpload"/>
+  <int value="11" label="kExpiredLogFileAtChromeStart"/>
+  <int value="12" label="kExpiredLogFileDuringSession"/>
+</enum>
+
 <enum name="WebRtcH264DecoderImplEvent">
   <int value="0" label="Init"/>
   <int value="1" label="Error"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 0df95417..be48f9d12 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -134191,6 +134191,30 @@
   <summary>Time for capturing one frame in window capturing.</summary>
 </histogram>
 
+<histogram name="WebRtcEventLogging.Api" enum="WebRtcEventLoggingApiEnum"
+    expires_after="2019-12-31">
+  <owner>eladalon@chromium.org</owner>
+  <owner>saeedj@google.com</owner>
+  <owner>manj@google.com</owner>
+  <owner>dmitriyg@google.com</owner>
+  <summary>
+    The result of calls to the API for the collection and uploading of WebRTC
+    event logs.
+  </summary>
+</histogram>
+
+<histogram name="WebRtcEventLogging.Upload" enum="WebRtcEventLoggingUploadEnum"
+    expires_after="2019-12-31">
+  <owner>eladalon@chromium.org</owner>
+  <owner>saeedj@google.com</owner>
+  <owner>manj@google.com</owner>
+  <owner>dmitriyg@google.com</owner>
+  <summary>
+    Tracks the uploading or discarding of WebRTC event logs that were previously
+    collected.
+  </summary>
+</histogram>
+
 <histogram base="true" name="WebRtcTextLogging"
     enum="WebRtcLoggingWebAppIdHash" expires_after="2019-07-01">
   <owner>grunell@chromium.org</owner>
diff --git a/ui/accessibility/platform/ax_platform_node_textprovider_win.cc b/ui/accessibility/platform/ax_platform_node_textprovider_win.cc
index 89d9996c..761b2ed 100644
--- a/ui/accessibility/platform/ax_platform_node_textprovider_win.cc
+++ b/ui/accessibility/platform/ax_platform_node_textprovider_win.cc
@@ -13,6 +13,11 @@
 #define UIA_VALIDATE_TEXTPROVIDER_CALL() \
   if (!owner()->GetDelegate())           \
     return UIA_E_ELEMENTNOTAVAILABLE;
+#define UIA_VALIDATE_TEXTPROVIDER_CALL_1_ARG(arg) \
+  if (!owner()->GetDelegate())                    \
+    return UIA_E_ELEMENTNOTAVAILABLE;             \
+  if (!arg)                                       \
+    return E_INVALIDARG;
 
 namespace ui {
 
@@ -56,8 +61,32 @@
 STDMETHODIMP AXPlatformNodeTextProviderWin::RangeFromChild(
     IRawElementProviderSimple* child,
     ITextRangeProvider** range) {
-  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXT_RANGEFROMCHILD);
-  return E_NOTIMPL;
+  UIA_VALIDATE_TEXTPROVIDER_CALL_1_ARG(child);
+
+  DVLOG(1) << __func__;
+
+  *range = nullptr;
+
+  Microsoft::WRL::ComPtr<ui::AXPlatformNodeWin> child_platform_node;
+  if (child->QueryInterface(IID_PPV_ARGS(&child_platform_node)) != S_OK)
+    return UIA_E_INVALIDOPERATION;
+
+  if (!owner()->IsDescendant(child_platform_node.Get()))
+    return E_INVALIDARG;
+
+  // Start and end should be leaf text positions.
+  AXNodePosition::AXPositionInstance start = child_platform_node->GetDelegate()
+                                                 ->CreateTextPositionAt(0)
+                                                 ->AsLeafTextPosition();
+
+  AXNodePosition::AXPositionInstance end =
+      child_platform_node->GetDelegate()
+          ->CreateTextPositionAt(start->MaxTextOffset())
+          ->AsLeafTextPosition()
+          ->CreatePositionAtEndOfAnchor();
+
+  return AXPlatformNodeTextRangeProviderWin::CreateTextRangeProvider(
+      owner_, std::move(start), std::move(end), range);
 }
 
 STDMETHODIMP AXPlatformNodeTextProviderWin::RangeFromPoint(
diff --git a/ui/accessibility/platform/ax_platform_node_textprovider_win_unittest.cc b/ui/accessibility/platform/ax_platform_node_textprovider_win_unittest.cc
index cfe4a9fd..2f9953d 100644
--- a/ui/accessibility/platform/ax_platform_node_textprovider_win_unittest.cc
+++ b/ui/accessibility/platform/ax_platform_node_textprovider_win_unittest.cc
@@ -5,6 +5,8 @@
 #include "ui/accessibility/platform/ax_platform_node_win_unittest.h"
 
 #include <UIAutomationClient.h>
+#include <UIAutomationCoreApi.h>
+
 #include "base/win/scoped_bstr.h"
 #include "ui/accessibility/platform/ax_fragment_root_win.h"
 #include "ui/base/win/accessibility_misc_utils.h"
@@ -13,8 +15,148 @@
 
 namespace ui {
 
+// Helper macros for UIAutomation HRESULT expectations
+#define EXPECT_UIA_INVALIDOPERATION(expr) \
+  EXPECT_EQ(static_cast<HRESULT>(UIA_E_INVALIDOPERATION), (expr))
+#define EXPECT_INVALIDARG(expr) \
+  EXPECT_EQ(static_cast<HRESULT>(E_INVALIDARG), (expr))
+
 class AXPlatformNodeTextProviderTest : public ui::AXPlatformNodeWinTest {};
 
+class MockIRawElementProviderSimple
+    : public CComObjectRootEx<CComMultiThreadModel>,
+      public IRawElementProviderSimple {
+ public:
+  BEGIN_COM_MAP(MockIRawElementProviderSimple)
+  COM_INTERFACE_ENTRY(IRawElementProviderSimple)
+  END_COM_MAP()
+
+  MockIRawElementProviderSimple() {}
+  ~MockIRawElementProviderSimple() {}
+
+  static HRESULT CreateMockIRawElementProviderSimple(
+      IRawElementProviderSimple** provider) {
+    CComObject<MockIRawElementProviderSimple>* raw_element_provider = nullptr;
+    HRESULT hr = CComObject<MockIRawElementProviderSimple>::CreateInstance(
+        &raw_element_provider);
+    if (SUCCEEDED(hr)) {
+      *provider = raw_element_provider;
+    }
+
+    return hr;
+  }
+
+  //
+  // IRawElementProviderSimple methods.
+  //
+  IFACEMETHODIMP GetPatternProvider(PATTERNID pattern_id,
+                                    IUnknown** result) override {
+    return E_NOTIMPL;
+  }
+
+  IFACEMETHODIMP GetPropertyValue(PROPERTYID property_id,
+                                  VARIANT* result) override {
+    return E_NOTIMPL;
+  }
+
+  IFACEMETHODIMP
+  get_ProviderOptions(enum ProviderOptions* ret) override { return E_NOTIMPL; }
+
+  IFACEMETHODIMP
+  get_HostRawElementProvider(IRawElementProviderSimple** provider) override {
+    return E_NOTIMPL;
+  }
+};
+
+TEST_F(AXPlatformNodeTextProviderTest, TestITextProviderRangeFromChild) {
+  ui::AXNodeData text_data;
+  text_data.id = 2;
+  text_data.role = ax::mojom::Role::kStaticText;
+  text_data.SetName("some text");
+
+  ui::AXNodeData empty_text_data;
+  empty_text_data.id = 3;
+  empty_text_data.role = ax::mojom::Role::kStaticText;
+
+  ui::AXNodeData root_data;
+  root_data.id = 1;
+  root_data.SetName("Document");
+  root_data.role = ax::mojom::Role::kRootWebArea;
+  root_data.child_ids.push_back(2);
+  root_data.child_ids.push_back(3);
+
+  ui::AXTreeUpdate update;
+  ui::AXTreeData tree_data;
+  tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
+  update.tree_data = tree_data;
+  update.has_tree_data = true;
+  update.root_id = root_data.id;
+  update.nodes.push_back(root_data);
+  update.nodes.push_back(text_data);
+  update.nodes.push_back(empty_text_data);
+
+  Init(update);
+
+  AXNode* root_node = GetRootNode();
+  AXNodePosition::SetTreeForTesting(tree_.get());
+  AXNode* text_node = root_node->children()[0];
+  AXNode* empty_text_node = root_node->children()[1];
+
+  ComPtr<IRawElementProviderSimple> root_node_raw =
+      QueryInterfaceFromNode<IRawElementProviderSimple>(root_node);
+  ComPtr<IRawElementProviderSimple> text_node_raw =
+      QueryInterfaceFromNode<IRawElementProviderSimple>(text_node);
+  ComPtr<IRawElementProviderSimple> empty_text_node_raw =
+      QueryInterfaceFromNode<IRawElementProviderSimple>(empty_text_node);
+
+  // Call RangeFromChild on the root with the text child passed in.
+  ComPtr<ITextProvider> text_provider;
+  EXPECT_HRESULT_SUCCEEDED(
+      root_node_raw->GetPatternProvider(UIA_TextPatternId, &text_provider));
+
+  ComPtr<ITextRangeProvider> text_range_provider;
+  EXPECT_HRESULT_SUCCEEDED(
+      text_provider->RangeFromChild(text_node_raw.Get(), &text_range_provider));
+
+  base::win::ScopedBstr text_content;
+  EXPECT_HRESULT_SUCCEEDED(
+      text_range_provider->GetText(-1, text_content.Receive()));
+  EXPECT_EQ(0, wcscmp(static_cast<BSTR>(text_content), L"some text"));
+
+  // Now test that the reverse relation doesn't return a valid
+  // ITextRangeProvider, and instead returns E_INVALIDARG.
+  EXPECT_HRESULT_SUCCEEDED(
+      text_node_raw->GetPatternProvider(UIA_TextPatternId, &text_provider));
+
+  EXPECT_INVALIDARG(
+      text_provider->RangeFromChild(root_node_raw.Get(), &text_range_provider));
+
+  // Now test that a child with no text returns a degenerate range.
+  EXPECT_HRESULT_SUCCEEDED(
+      root_node_raw->GetPatternProvider(UIA_TextPatternId, &text_provider));
+
+  EXPECT_HRESULT_SUCCEEDED(text_provider->RangeFromChild(
+      empty_text_node_raw.Get(), &text_range_provider));
+
+  base::win::ScopedBstr empty_text_content;
+  EXPECT_HRESULT_SUCCEEDED(
+      text_range_provider->GetText(-1, empty_text_content.Receive()));
+  EXPECT_EQ(0, wcscmp(static_cast<BSTR>(empty_text_content), L""));
+
+  // Test that passing in an object from a different instance of
+  // IRawElementProviderSimple than that of the valid text provider
+  // returns UIA_E_INVALIDOPERATION.
+  ComPtr<IRawElementProviderSimple> other_root_node_raw;
+  MockIRawElementProviderSimple::CreateMockIRawElementProviderSimple(
+      &other_root_node_raw);
+
+  EXPECT_HRESULT_SUCCEEDED(
+      root_node_raw->GetPatternProvider(UIA_TextPatternId, &text_provider));
+
+  EXPECT_UIA_INVALIDOPERATION(text_provider->RangeFromChild(
+      other_root_node_raw.Get(), &text_range_provider));
+}
+
 TEST_F(AXPlatformNodeTextProviderTest, TestITextProviderDocumentRange) {
   ui::AXNodeData text_data;
   text_data.id = 2;
diff --git a/ui/aura/test/aura_test_base.h b/ui/aura/test/aura_test_base.h
index c185cf86..f13b480 100644
--- a/ui/aura/test/aura_test_base.h
+++ b/ui/aura/test/aura_test_base.h
@@ -10,12 +10,17 @@
 #include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "base/test/scoped_task_environment.h"
+#include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/aura/env.h"
 #include "ui/aura/mus/property_converter.h"
 #include "ui/aura/mus/window_tree_client_delegate.h"
 #include "ui/aura/test/aura_test_helper.h"
 
+#if defined(OS_WIN)
+#include "base/win/scoped_com_initializer.h"
+#endif
+
 namespace ws {
 namespace mojom {
 class WindowTreeClient;
@@ -98,6 +103,10 @@
  private:
   base::test::ScopedTaskEnvironment scoped_task_environment_;
 
+#if defined(OS_WIN)
+  base::win::ScopedCOMInitializer com_initializer_;
+#endif
+
   // Only used for mus, initially set to this, but may be reset.
   WindowTreeClientDelegate* window_tree_client_delegate_;
 
diff --git a/ui/gfx/font_fallback_win.cc b/ui/gfx/font_fallback_win.cc
index 6d6fe87..b53f366c 100644
--- a/ui/gfx/font_fallback_win.cc
+++ b/ui/gfx/font_fallback_win.cc
@@ -125,7 +125,7 @@
                         resolved_font.GetFontName().c_str());
   }
 
-  TRACE_EVENT1("ui", "QueryLinkedFontsFromRegistry", "results", logging_str);
+  TRACE_EVENT1("fonts", "QueryLinkedFontsFromRegistry", "results", logging_str);
 }
 
 // CachedFontLinkSettings is a singleton cache of the Windows font settings
@@ -170,7 +170,7 @@
   if (it != cached_linked_fonts_.end())
     return &it->second;
 
-  TRACE_EVENT1("ui", "CachedFontLinkSettings::GetLinkedFonts", "font_name",
+  TRACE_EVENT1("fonts", "CachedFontLinkSettings::GetLinkedFonts", "font_name",
                font_name);
 
   SCOPED_UMA_HISTOGRAM_LONG_TIMER(
@@ -292,6 +292,7 @@
 }  // namespace internal
 
 std::vector<Font> GetFallbackFonts(const Font& font) {
+  TRACE_EVENT0("fonts", "gfx::GetFallbackFonts");
   std::string font_family = font.GetFontName();
   CachedFontLinkSettings* font_link = CachedFontLinkSettings::GetInstance();
   // GetLinkedFonts doesn't care about the font size, so we always pass 10.
@@ -302,6 +303,7 @@
                      const base::char16* text,
                      int text_length,
                      Font* result) {
+  TRACE_EVENT0("fonts", "gfx::GetFallbackFont");
   // Creating a DirectWrite font fallback can be expensive. It's ok in the
   // browser process because we can use the shared system fallback, but in the
   // renderer this can cause hangs. Code that needs font fallback in the
diff --git a/ui/gfx/image/image_skia_operations.cc b/ui/gfx/image/image_skia_operations.cc
index 9a05026f..bb5fa62 100644
--- a/ui/gfx/image/image_skia_operations.cc
+++ b/ui/gfx/image/image_skia_operations.cc
@@ -5,6 +5,7 @@
 #include "ui/gfx/image/image_skia_operations.h"
 
 #include <stddef.h>
+#include <memory>
 
 #include "base/command_line.h"
 #include "base/logging.h"
@@ -477,6 +478,30 @@
   DISALLOW_COPY_AND_ASSIGN(IconWithBadgeSource);
 };
 
+// ImageSkiaSource which uses SkBitmapOperations::CreateColorMask
+// to generate image reps for the target image.
+class ColorMaskSource : public gfx::ImageSkiaSource {
+ public:
+  ColorMaskSource(const ImageSkia& image, SkColor color)
+      : image_(image), color_(color) {}
+
+  ~ColorMaskSource() override {}
+
+  // gfx::ImageSkiaSource overrides:
+  ImageSkiaRep GetImageForScale(float scale) override {
+    ImageSkiaRep image_rep = image_.GetRepresentation(scale);
+    return ImageSkiaRep(
+        SkBitmapOperations::CreateColorMask(image_rep.GetBitmap(), color_),
+        image_rep.scale());
+  }
+
+ private:
+  const ImageSkia image_;
+  const SkColor color_;
+
+  DISALLOW_COPY_AND_ASSIGN(ColorMaskSource);
+};
+
 }  // namespace
 
 // static
@@ -633,4 +658,13 @@
                    icon.size());
 }
 
+// static
+ImageSkia ImageSkiaOperations::CreateColorMask(const ImageSkia& image,
+                                               SkColor color) {
+  if (image.isNull())
+    return ImageSkia();
+
+  return ImageSkia(std::make_unique<ColorMaskSource>(image, color),
+                   image.size());
+}
 }  // namespace gfx
diff --git a/ui/gfx/image/image_skia_operations.h b/ui/gfx/image/image_skia_operations.h
index 92fc9b8..7f3a4b00 100644
--- a/ui/gfx/image/image_skia_operations.h
+++ b/ui/gfx/image/image_skia_operations.h
@@ -109,6 +109,10 @@
   static ImageSkia CreateIconWithBadge(const ImageSkia& icon,
                                        const ImageSkia& badge);
 
+  // Creates an image by combining |image| and color |color|.
+  // The image must use the kARGB_8888_Config config.
+  static ImageSkia CreateColorMask(const gfx::ImageSkia& image, SkColor color);
+
  private:
   ImageSkiaOperations();  // Class for scoping only.
 };
diff --git a/ui/gfx/platform_font_win.cc b/ui/gfx/platform_font_win.cc
index 4623463..0c2ed0e 100644
--- a/ui/gfx/platform_font_win.cc
+++ b/ui/gfx/platform_font_win.cc
@@ -24,6 +24,7 @@
 #include "base/strings/string_util.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/trace_event/trace_event.h"
 #include "base/win/scoped_gdi_object.h"
 #include "base/win/scoped_hdc.h"
 #include "base/win/scoped_select_object.h"
@@ -58,6 +59,8 @@
 HRESULT FindDirectWriteFontForLOGFONT(IDWriteFactory* factory,
                                       LOGFONT* font_info,
                                       IDWriteFont** dwrite_font) {
+  TRACE_EVENT0("fonts", "gfx::FindDirectWriteFontForLOGFONT");
+
   Microsoft::WRL::ComPtr<IDWriteGdiInterop> gdi_interop;
   HRESULT hr = factory->GetGdiInterop(gdi_interop.GetAddressOf());
   if (FAILED(hr)) {
@@ -110,6 +113,8 @@
                                    bool italic,
                                    IDWriteFactory* factory,
                                    IDWriteFont** dwrite_font) {
+  TRACE_EVENT0("fonts", "gfx::GetMatchingDirectWriteFont");
+
   // First try the GDI compat route to get a matching DirectWrite font.
   // If that succeeds then we are good. If that fails then try and find a
   // match from the DirectWrite font collection.
@@ -234,6 +239,8 @@
 class SystemFonts {
  public:
   SystemFonts() {
+    TRACE_EVENT0("fonts", "gfx::SystemFonts::SystemFonts");
+
     NONCLIENTMETRICS_XP metrics;
     base::win::GetNonClientMetrics(&metrics);
 
@@ -312,6 +319,8 @@
   void AddFont(gfx::PlatformFontWin::SystemFont system_font,
                const gfx::PlatformFontWin::FontAdjustment& font_adjustment,
                LOGFONT* logfont) {
+    TRACE_EVENT0("fonts", "gfx::SystemFonts::AddFont");
+
     // Make adjustments to the font as necessary.
     PlatformFontWin::AdjustLOGFONT(font_adjustment, logfont);
 
@@ -580,6 +589,7 @@
 }
 
 PlatformFontWin::HFontRef* PlatformFontWin::CreateHFontRef(HFONT font) {
+  TRACE_EVENT0("fonts", "PlatformFont::CreateHFontRef");
   TEXTMETRIC font_metrics;
 
   {
@@ -597,6 +607,8 @@
 PlatformFontWin::HFontRef* PlatformFontWin::CreateHFontRefFromGDI(
     HFONT font,
     const TEXTMETRIC& font_metrics) {
+  TRACE_EVENT0("fonts", "PlatformFontWin::CreateHFontRefFromGDI");
+
   const int height = std::max<int>(1, font_metrics.tmHeight);
   const int baseline = std::max<int>(1, font_metrics.tmAscent);
   const int cap_height =
@@ -619,6 +631,8 @@
 PlatformFontWin::HFontRef* PlatformFontWin::CreateHFontRefFromSkia(
     HFONT gdi_font,
     const TEXTMETRIC& font_metrics) {
+  TRACE_EVENT0("fonts", "PlatformFontWin::CreateHFontRefFromSkia");
+
   LOGFONT font_info = {0};
   GetObject(gdi_font, sizeof(LOGFONT), &font_info);
 
@@ -848,6 +862,7 @@
 // static
 PlatformFont* PlatformFont::CreateFromNameAndSize(const std::string& font_name,
                                                   int font_size) {
+  TRACE_EVENT0("fonts", "PlatformFont::CreateFromNameAndSize");
   return new PlatformFontWin(font_name, font_size);
 }
 
diff --git a/ui/gfx/win/direct_write.cc b/ui/gfx/win/direct_write.cc
index bc38edd..8671cd84 100644
--- a/ui/gfx/win/direct_write.cc
+++ b/ui/gfx/win/direct_write.cc
@@ -9,6 +9,7 @@
 #include "base/command_line.h"
 #include "base/debug/alias.h"
 #include "base/metrics/field_trial.h"
+#include "base/trace_event/trace_event.h"
 #include "base/win/registry.h"
 #include "base/win/windows_version.h"
 #include "skia/ext/fontmgr_default.h"
@@ -39,6 +40,8 @@
     return;
   tried_dwrite_initialize = true;
 
+  TRACE_EVENT0("fonts", "gfx::MaybeInitializeDirectWrite");
+
   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
           switches::kDisableDirectWriteForUI)) {
     return;
diff --git a/ui/ozone/platform/wayland/wayland_connection.cc b/ui/ozone/platform/wayland/wayland_connection.cc
index 2044e47..bbcb18e 100644
--- a/ui/ozone/platform/wayland/wayland_connection.cc
+++ b/ui/ozone/platform/wayland/wayland_connection.cc
@@ -270,7 +270,8 @@
 
   DCHECK(data_map);
   data_map_ = data_map;
-  data_device_->RequestSelectionData(mime_type);
+  if (!data_device_->RequestSelectionData(mime_type))
+    SetClipboardData({}, mime_type);
 }
 
 bool WaylandConnection::IsSelectionOwner() {
@@ -310,7 +311,7 @@
     dragdrop_data_source_ = data_device_manager_->CreateSource();
   dragdrop_data_source_->Offer(data);
   dragdrop_data_source_->SetAction(operation);
-  data_device_->StartDrag(*(dragdrop_data_source_->data_source()), data);
+  data_device_->StartDrag(dragdrop_data_source_->data_source(), data);
 }
 
 void WaylandConnection::FinishDragSession(uint32_t dnd_action,
diff --git a/ui/ozone/platform/wayland/wayland_data_device.cc b/ui/ozone/platform/wayland/wayland_data_device.cc
index 2c0143eb..ab5df3f4 100644
--- a/ui/ozone/platform/wayland/wayland_data_device.cc
+++ b/ui/ozone/platform/wayland/wayland_data_device.cc
@@ -87,11 +87,14 @@
   shared_memory_->Close();
 }
 
-void WaylandDataDevice::RequestSelectionData(const std::string& mime_type) {
+bool WaylandDataDevice::RequestSelectionData(const std::string& mime_type) {
+  if (!selection_offer_)
+    return false;
+
   base::ScopedFD fd = selection_offer_->Receive(mime_type);
   if (!fd.is_valid()) {
     LOG(ERROR) << "Failed to open file descriptor.";
-    return;
+    return false;
   }
 
   // Ensure there is not pending operation to be performed by the compositor,
@@ -100,6 +103,7 @@
       base::BindOnce(&WaylandDataDevice::ReadClipboardDataFromFD,
                      base::Unretained(this), std::move(fd), mime_type);
   RegisterDeferredReadCallback();
+  return true;
 }
 
 void WaylandDataDevice::RequestDragData(
@@ -146,23 +150,20 @@
   }
 }
 
-void WaylandDataDevice::StartDrag(const wl_data_source& data_source,
+void WaylandDataDevice::StartDrag(wl_data_source* data_source,
                                   const ui::OSExchangeData& data) {
+  DCHECK(data_source);
   WaylandWindow* window = connection_->GetCurrentFocusedWindow();
   if (!window) {
     LOG(ERROR) << "Failed to get focused window.";
     return;
   }
-
-  wl_surface* surface = window->surface();
-  const SkBitmap* icon = data.provider().GetDragImage().bitmap();
-  if (icon && !icon->empty())
-    CreateDragImage(icon);
-
+  const SkBitmap* icon = PrepareDragIcon(data);
   source_data_ = std::make_unique<ui::OSExchangeData>(data.provider().Clone());
-  wl_data_device_start_drag(data_device_.get(),
-                            const_cast<wl_data_source*>(&data_source), surface,
+  wl_data_device_start_drag(data_device_.get(), data_source, window->surface(),
                             icon_surface_.get(), connection_->serial());
+  if (icon)
+    DrawDragIcon(icon);
   connection_->ScheduleFlush();
 }
 
@@ -372,22 +373,32 @@
   data_device->deferred_read_callback_.reset();
 }
 
-void WaylandDataDevice::CreateDragImage(const SkBitmap* bitmap) {
-  DCHECK(bitmap);
-  gfx::Size size(bitmap->width(), bitmap->height());
+const SkBitmap* WaylandDataDevice::PrepareDragIcon(const OSExchangeData& data) {
+  const SkBitmap* icon_bitmap = data.provider().GetDragImage().bitmap();
+  if (!icon_bitmap || icon_bitmap->empty())
+    return nullptr;
+  icon_surface_.reset(wl_compositor_create_surface(connection_->compositor()));
+  DCHECK(icon_surface_);
+  return icon_bitmap;
+}
+
+void WaylandDataDevice::DrawDragIcon(const SkBitmap* icon_bitmap) {
+  DCHECK(icon_bitmap);
+  DCHECK(!icon_bitmap->empty());
+  gfx::Size size(icon_bitmap->width(), icon_bitmap->height());
 
   if (size != icon_buffer_size_) {
     wl_buffer* buffer =
         wl::CreateSHMBuffer(size, shared_memory_.get(), connection_->shm());
-    if (!buffer)
+    if (!buffer) {
+      LOG(ERROR) << "Failed to create drag icon buffer.";
       return;
-
+    }
     buffer_.reset(buffer);
     icon_buffer_size_ = size;
   }
-  wl::DrawBitmapToSHMB(icon_buffer_size_, *shared_memory_, *bitmap);
+  wl::DrawBitmapToSHMB(icon_buffer_size_, *shared_memory_, *icon_bitmap);
 
-  icon_surface_.reset(wl_compositor_create_surface(connection_->compositor()));
   wl_surface_attach(icon_surface_.get(), buffer_.get(), 0, 0);
   wl_surface_damage(icon_surface_.get(), 0, 0, icon_buffer_size_.width(),
                     icon_buffer_size_.height());
diff --git a/ui/ozone/platform/wayland/wayland_data_device.h b/ui/ozone/platform/wayland/wayland_data_device.h
index 7a0f36a..dcfcd83 100644
--- a/ui/ozone/platform/wayland/wayland_data_device.h
+++ b/ui/ozone/platform/wayland/wayland_data_device.h
@@ -39,7 +39,7 @@
   WaylandDataDevice(WaylandConnection* connection, wl_data_device* data_device);
   ~WaylandDataDevice();
 
-  void RequestSelectionData(const std::string& mime_type);
+  bool RequestSelectionData(const std::string& mime_type);
 
   // Requests the data to the platform when Chromium gets drag-and-drop started
   // by others. Once reading the data from platform is done, |callback| should
@@ -52,8 +52,7 @@
   void DeliverDragData(const std::string& mime_type, std::string* buffer);
   // Starts drag with |data| to be delivered, |operation| supported by the
   // source side initiated the dragging.
-  void StartDrag(const wl_data_source& data_source,
-                 const ui::OSExchangeData& data);
+  void StartDrag(wl_data_source* data_source, const ui::OSExchangeData& data);
   // Resets |source_data_| when the dragging is finished.
   void ResetSourceData();
 
@@ -121,8 +120,10 @@
                                    struct wl_callback* cb,
                                    uint32_t time);
 
-  bool CreateSHMBuffer(const gfx::Size& size);
-  void CreateDragImage(const SkBitmap* bitmap);
+  // Returns the drag icon bitmap and creates and wayland surface to draw it
+  // on, if a valid drag image is present in |data|; otherwise returns null.
+  const SkBitmap* PrepareDragIcon(const OSExchangeData& data);
+  void DrawDragIcon(const SkBitmap* bitmap);
 
   void OnDragDataReceived(const std::string& contents);
 
diff --git a/ui/ozone/platform/wayland/wayland_data_device_unittest.cc b/ui/ozone/platform/wayland/wayland_data_device_unittest.cc
index 54c5905..b09b8cb 100644
--- a/ui/ozone/platform/wayland/wayland_data_device_unittest.cc
+++ b/ui/ozone/platform/wayland/wayland_data_device_unittest.cc
@@ -110,7 +110,7 @@
   run_loop.Run();
 }
 
-TEST_P(WaylandDataDeviceManagerTest, ReadFromClibpard) {
+TEST_P(WaylandDataDeviceManagerTest, ReadFromClipboard) {
   // TODO(nickdiego): implement this in terms of an actual wl_surface that
   // gets focused and compositor sends data_device data to it.
   auto* data_offer = data_device_manager_->data_device()->OnDataOffer();
@@ -130,6 +130,18 @@
   Sync();
 }
 
+TEST_P(WaylandDataDeviceManagerTest, ReadFromClipboardWithoutOffer) {
+  // When no data offer is advertised and client requests clipboard data
+  // from the server, the response callback should be gracefully called with
+  // an empty string.
+  auto callback =
+      base::BindOnce([](const base::Optional<std::vector<uint8_t>>& data) {
+        std::string string_data = std::string(data->begin(), data->end());
+        EXPECT_EQ("", string_data);
+      });
+  clipboard_client_->ReadData(wl::kTextMimeTypeUtf8, std::move(callback));
+}
+
 TEST_P(WaylandDataDeviceManagerTest, IsSelectionOwner) {
   auto callback = base::BindOnce([]() {});
   clipboard_client_->SetData(wl::kSampleClipboardText, wl::kTextMimeTypeUtf8,
diff --git a/ui/ozone/platform/wayland/wayland_data_source.h b/ui/ozone/platform/wayland/wayland_data_source.h
index 1ed5732..34fb885b 100644
--- a/ui/ozone/platform/wayland/wayland_data_source.h
+++ b/ui/ozone/platform/wayland/wayland_data_source.h
@@ -48,7 +48,7 @@
   void SetAction(int operation);
   void SetDragData(const DragDataMap& data_map);
 
-  const wl_data_source* data_source() const { return data_source_.get(); }
+  wl_data_source* data_source() const { return data_source_.get(); }
 
  private:
   static void OnTarget(void* data,
diff --git a/ui/views/accessibility/view_ax_platform_node_delegate_unittest.cc b/ui/views/accessibility/view_ax_platform_node_delegate_unittest.cc
index fc8f6cf..6df1963 100644
--- a/ui/views/accessibility/view_ax_platform_node_delegate_unittest.cc
+++ b/ui/views/accessibility/view_ax_platform_node_delegate_unittest.cc
@@ -174,7 +174,7 @@
 #if defined(USE_AURA)
 class DerivedTestView : public View {
  public:
-  DerivedTestView() : View() {}
+  DerivedTestView() = default;
   ~DerivedTestView() override = default;
 
   void OnBlur() override { SetVisible(false); }
diff --git a/ui/views/animation/ink_drop_event_handler.cc b/ui/views/animation/ink_drop_event_handler.cc
index 8eabb46..5597d5f 100644
--- a/ui/views/animation/ink_drop_event_handler.cc
+++ b/ui/views/animation/ink_drop_event_handler.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "build/build_config.h"
 #include "ui/events/scoped_target_handler.h"
 #include "ui/views/animation/ink_drop.h"
 #include "ui/views/animation/ink_drop_state.h"
@@ -24,6 +25,30 @@
   host_view_->RemoveObserver(this);
 }
 
+void InkDropEventHandler::AnimateInkDrop(InkDropState state,
+                                         const ui::LocatedEvent* event) {
+#if defined(OS_WIN)
+  // On Windows, don't initiate ink-drops for touch/gesture events.
+  // Additionally, certain event states should dismiss existing ink-drop
+  // animations. If the state is already other than HIDDEN, presumably from
+  // a mouse or keyboard event, then the state should be allowed. Conversely,
+  // if the requested state is ACTIVATED, then it should always be allowed.
+  if (event && (event->IsTouchEvent() || event->IsGestureEvent()) &&
+      delegate_->GetInkDrop()->GetTargetInkDropState() ==
+          InkDropState::HIDDEN &&
+      state != InkDropState::ACTIVATED) {
+    return;
+  }
+#endif
+  last_ripple_triggering_event_.reset(
+      event ? ui::Event::Clone(*event).release()->AsLocatedEvent() : nullptr);
+  delegate_->GetInkDrop()->AnimateToState(state);
+}
+
+ui::LocatedEvent* InkDropEventHandler::GetLastRippleTriggeringEvent() const {
+  return last_ripple_triggering_event_.get();
+}
+
 void InkDropEventHandler::OnGestureEvent(ui::GestureEvent* event) {
   if (!host_view_->enabled() || !delegate_->SupportsGestureEvents())
     return;
@@ -70,7 +95,7 @@
     // case would prematurely pre-empt these animations.
     return;
   }
-  delegate_->AnimateInkDrop(ink_drop_state, event);
+  AnimateInkDrop(ink_drop_state, event);
 }
 
 void InkDropEventHandler::OnMouseEvent(ui::MouseEvent* event) {
diff --git a/ui/views/animation/ink_drop_event_handler.h b/ui/views/animation/ink_drop_event_handler.h
index 29e25e6..5538114 100644
--- a/ui/views/animation/ink_drop_event_handler.h
+++ b/ui/views/animation/ink_drop_event_handler.h
@@ -34,13 +34,6 @@
 
     virtual bool HasInkDrop() const = 0;
 
-    // Start animating the InkDrop to another target state.
-    // TODO(pbos): Consider moving the implementation of
-    // InkDropHostView::AnimateInkDrop into InkDropEventHandler. In this case
-    // InkDropHostView would forward AnimateInkDrop into
-    // InkDropEventHandler::AnimateInkDrop.
-    virtual void AnimateInkDrop(InkDropState state,
-                                const ui::LocatedEvent* event) = 0;
     // Returns true if gesture events should affect the InkDrop.
     virtual bool SupportsGestureEvents() const = 0;
   };
@@ -48,6 +41,9 @@
   InkDropEventHandler(View* host_view, Delegate* delegate);
   ~InkDropEventHandler() override;
 
+  void AnimateInkDrop(InkDropState state, const ui::LocatedEvent* event);
+  ui::LocatedEvent* GetLastRippleTriggeringEvent() const;
+
  private:
   // ui::EventHandler:
   void OnGestureEvent(ui::GestureEvent* event) override;
@@ -71,6 +67,9 @@
   // Delegate used to get the InkDrop, etc.
   Delegate* const delegate_;
 
+  // The last user Event to trigger an InkDrop-ripple animation.
+  std::unique_ptr<ui::LocatedEvent> last_ripple_triggering_event_;
+
   DISALLOW_COPY_AND_ASSIGN(InkDropEventHandler);
 };
 
diff --git a/ui/views/animation/ink_drop_host_view.cc b/ui/views/animation/ink_drop_host_view.cc
index f524f1e4..8d2afbb 100644
--- a/ui/views/animation/ink_drop_host_view.cc
+++ b/ui/views/animation/ink_drop_host_view.cc
@@ -4,7 +4,6 @@
 
 #include "ui/views/animation/ink_drop_host_view.h"
 
-#include "build/build_config.h"
 #include "ui/events/event.h"
 #include "ui/events/scoped_target_handler.h"
 #include "ui/gfx/color_palette.h"
@@ -34,12 +33,6 @@
   return host_view_->GetInkDrop();
 }
 
-void InkDropHostView::InkDropHostViewEventHandlerDelegate::AnimateInkDrop(
-    InkDropState state,
-    const ui::LocatedEvent* event) {
-  host_view_->AnimateInkDrop(state, event);
-}
-
 bool InkDropHostView::InkDropHostViewEventHandlerDelegate::
     SupportsGestureEvents() const {
   return host_view_->ink_drop_mode_ == InkDropMode::ON;
@@ -120,20 +113,7 @@
 
 void InkDropHostView::AnimateInkDrop(InkDropState state,
                                      const ui::LocatedEvent* event) {
-#if defined(OS_WIN)
-  // On Windows, don't initiate ink-drops for touch/gesture events.
-  // Additionally, certain event states should dismiss existing ink-drop
-  // animations. If the state is already other than HIDDEN, presumably from
-  // a mouse or keyboard event, then the state should be allowed. Conversely,
-  // if the requested state is ACTIVATED, then it should always be allowed.
-  if (event && (event->IsTouchEvent() || event->IsGestureEvent()) &&
-      GetInkDrop()->GetTargetInkDropState() == InkDropState::HIDDEN &&
-      state != InkDropState::ACTIVATED)
-    return;
-#endif
-  last_ripple_triggering_event_.reset(
-      event ? ui::Event::Clone(*event).release()->AsLocatedEvent() : nullptr);
-  GetInkDrop()->AnimateToState(state);
+  ink_drop_event_handler_.AnimateInkDrop(state, event);
 }
 
 std::unique_ptr<InkDropImpl> InkDropHostView::CreateDefaultInkDropImpl() {
@@ -198,8 +178,9 @@
 }
 
 gfx::Point InkDropHostView::GetInkDropCenterBasedOnLastEvent() const {
-  return last_ripple_triggering_event_
-             ? last_ripple_triggering_event_->location()
+  return ink_drop_event_handler_.GetLastRippleTriggeringEvent()
+             ? ink_drop_event_handler_.GetLastRippleTriggeringEvent()
+                   ->location()
              : GetMirroredRect(GetContentsBounds()).CenterPoint();
 }
 
diff --git a/ui/views/animation/ink_drop_host_view.h b/ui/views/animation/ink_drop_host_view.h
index d638cdf..cb9ef74 100644
--- a/ui/views/animation/ink_drop_host_view.h
+++ b/ui/views/animation/ink_drop_host_view.h
@@ -196,8 +196,6 @@
     // InkDropEventHandler:
     InkDrop* GetInkDrop() override;
     bool HasInkDrop() const override;
-    void AnimateInkDrop(InkDropState state,
-                        const ui::LocatedEvent* event) override;
 
     bool SupportsGestureEvents() const override;
 
@@ -206,9 +204,6 @@
     InkDropHostView* const host_view_;
   };
 
-  // The last user Event to trigger an ink drop ripple animation.
-  std::unique_ptr<ui::LocatedEvent> last_ripple_triggering_event_;
-
   // Defines what type of |ink_drop_| to create.
   InkDropMode ink_drop_mode_ = InkDropMode::OFF;
 
diff --git a/ui/views/controls/button/label_button.cc b/ui/views/controls/button/label_button.cc
index 5889401a4..2be096e 100644
--- a/ui/views/controls/button/label_button.cc
+++ b/ui/views/controls/button/label_button.cc
@@ -48,7 +48,6 @@
           style::GetFont(button_context, style::STYLE_PRIMARY)),
       cached_default_button_font_list_(
           style::GetFont(button_context, style::STYLE_DIALOG_BUTTON_DEFAULT)),
-      button_state_images_(),
       button_state_colors_(),
       explicitly_set_colors_(),
       is_default_(false),
diff --git a/ui/views/controls/menu/menu_controller_unittest.cc b/ui/views/controls/menu/menu_controller_unittest.cc
index 4d39e49..f86feacf 100644
--- a/ui/views/controls/menu/menu_controller_unittest.cc
+++ b/ui/views/controls/menu/menu_controller_unittest.cc
@@ -110,8 +110,7 @@
     : on_menu_closed_called_(0),
       on_menu_closed_notify_type_(NOTIFY_DELEGATE),
       on_menu_closed_menu_(nullptr),
-      on_menu_closed_mouse_event_flags_(0),
-      on_menu_closed_callback_() {}
+      on_menu_closed_mouse_event_flags_(0) {}
 
 void TestMenuControllerDelegate::OnMenuClosed(NotifyType type,
                                               MenuItemView* menu,
diff --git a/ui/views/controls/textfield/textfield_model_unittest.cc b/ui/views/controls/textfield/textfield_model_unittest.cc
index a252cd1f1..a4b71a9c 100644
--- a/ui/views/controls/textfield/textfield_model_unittest.cc
+++ b/ui/views/controls/textfield/textfield_model_unittest.cc
@@ -45,10 +45,7 @@
 class TextfieldModelTest : public ViewsTestBase,
                            public TextfieldModel::Delegate {
  public:
-  TextfieldModelTest()
-      : ViewsTestBase(),
-        composition_text_confirmed_or_cleared_(false) {
-  }
+  TextfieldModelTest() : composition_text_confirmed_or_cleared_(false) {}
 
   // ::testing::Test:
   void TearDown() override {
diff --git a/ui/views/controls/tree/tree_view.cc b/ui/views/controls/tree/tree_view.cc
index 3e3d357..9dbb3b4 100644
--- a/ui/views/controls/tree/tree_view.cc
+++ b/ui/views/controls/tree/tree_view.cc
@@ -5,6 +5,7 @@
 #include "ui/views/controls/tree/tree_view.h"
 
 #include <algorithm>
+#include <utility>
 
 #include "base/i18n/rtl.h"
 #include "base/memory/ptr_util.h"
@@ -120,9 +121,11 @@
   }
 }
 
-View* TreeView::CreateParentIfNecessary() {
-  auto* scroll_view = ScrollView::CreateScrollViewWithBorder();
-  scroll_view->SetContents(base::WrapUnique(this));
+// static
+std::unique_ptr<ScrollView> TreeView::CreateScrollViewWithTree(
+    std::unique_ptr<TreeView> tree) {
+  auto scroll_view = base::WrapUnique(ScrollView::CreateScrollViewWithBorder());
+  scroll_view->SetContents(std::move(tree));
   return scroll_view;
 }
 
diff --git a/ui/views/controls/tree/tree_view.h b/ui/views/controls/tree/tree_view.h
index 81f76ce9..343325d 100644
--- a/ui/views/controls/tree/tree_view.h
+++ b/ui/views/controls/tree/tree_view.h
@@ -26,6 +26,7 @@
 namespace views {
 
 class PrefixSelector;
+class ScrollView;
 class Textfield;
 class TreeViewController;
 
@@ -48,8 +49,9 @@
   TreeView();
   ~TreeView() override;
 
-  // Returns new ScrollPane that contains the receiver.
-  View* CreateParentIfNecessary();
+  // Returns a new ScrollView that contains the given |tree|.
+  static std::unique_ptr<ScrollView> CreateScrollViewWithTree(
+      std::unique_ptr<TreeView> tree);
 
   // Sets the model. TreeView does not take ownership of the model.
   void SetModel(ui::TreeModel* model);
diff --git a/ui/views/examples/label_example.cc b/ui/views/examples/label_example.cc
index 4151a4be..108d29e 100644
--- a/ui/views/examples/label_example.cc
+++ b/ui/views/examples/label_example.cc
@@ -35,9 +35,7 @@
 // A Label with a clamped preferred width to demonstrate eliding or wrapping.
 class ExamplePreferredSizeLabel : public Label {
  public:
-  ExamplePreferredSizeLabel() : Label() {
-    SetBorder(CreateSolidBorder(1, SK_ColorGRAY));
-  }
+  ExamplePreferredSizeLabel() { SetBorder(CreateSolidBorder(1, SK_ColorGRAY)); }
   ~ExamplePreferredSizeLabel() override = default;
 
   // Label:
diff --git a/ui/views/examples/layout_example_base.cc b/ui/views/examples/layout_example_base.cc
index b1bb2cef..f7ffe0185 100644
--- a/ui/views/examples/layout_example_base.cc
+++ b/ui/views/examples/layout_example_base.cc
@@ -61,7 +61,7 @@
 
 LayoutExampleBase::ChildPanel::ChildPanel(LayoutExampleBase* example,
                                           const gfx::Size& preferred_size)
-    : View(), example_(example), preferred_size_(preferred_size) {
+    : example_(example), preferred_size_(preferred_size) {
   SetBorder(CreateSolidBorder(1, SK_ColorGRAY));
   for (unsigned i = 0; i < sizeof(margin_) / sizeof(margin_[0]); ++i)
     margin_[i] = CreateTextfield();
diff --git a/ui/views/examples/multiline_example.cc b/ui/views/examples/multiline_example.cc
index be9acf1a..e77367a 100644
--- a/ui/views/examples/multiline_example.cc
+++ b/ui/views/examples/multiline_example.cc
@@ -34,7 +34,7 @@
 // A Label with a clamped preferred width to demonstrate wrapping.
 class PreferredSizeLabel : public Label {
  public:
-  PreferredSizeLabel() : Label() {}
+  PreferredSizeLabel() = default;
   ~PreferredSizeLabel() override = default;
 
   // Label:
diff --git a/ui/views/examples/tree_view_example.cc b/ui/views/examples/tree_view_example.cc
index a85ac00e..114841c94 100644
--- a/ui/views/examples/tree_view_example.cc
+++ b/ui/views/examples/tree_view_example.cc
@@ -4,12 +4,13 @@
 
 #include "ui/views/examples/tree_view_example.h"
 
-#include <memory>
+#include <utility>
 
 #include "base/strings/utf_string_conversions.h"
 #include "ui/views/controls/button/label_button.h"
 #include "ui/views/controls/menu/menu_model_adapter.h"
 #include "ui/views/controls/menu/menu_runner.h"
+#include "ui/views/controls/scroll_view.h"
 #include "ui/views/controls/tree/tree_view.h"
 #include "ui/views/controls/tree/tree_view_drawing_provider.h"
 #include "ui/views/layout/grid_layout.h"
@@ -47,8 +48,8 @@
       model_(std::make_unique<NodeType>(ASCIIToUTF16("root"), 1)) {}
 
 TreeViewExample::~TreeViewExample() {
-  // Delete the view before the model.
-  tree_view_.reset();
+  // Remove the model from the view.
+  tree_view_->SetModel(nullptr);
 }
 
 void TreeViewExample::CreateExampleView(View* container) {
@@ -64,12 +65,12 @@
   sheep_node->Add(std::make_unique<NodeType>(ASCIIToUTF16("Sheep 1"), 1), 0);
   sheep_node->Add(std::make_unique<NodeType>(ASCIIToUTF16("Sheep 2"), 1), 1);
 
-  tree_view_ = std::make_unique<TreeView>();
-  tree_view_->set_context_menu_controller(this);
-  tree_view_->SetRootShown(false);
-  tree_view_->SetModel(&model_);
-  tree_view_->SetController(this);
-  tree_view_->SetDrawingProvider(
+  auto tree_view = std::make_unique<TreeView>();
+  tree_view->set_context_menu_controller(this);
+  tree_view->SetRootShown(false);
+  tree_view->SetModel(&model_);
+  tree_view->SetController(this);
+  tree_view->SetDrawingProvider(
       std::make_unique<ExampleTreeViewDrawingProvider>());
   add_ = new LabelButton(this, ASCIIToUTF16("Add"));
   add_->SetFocusForPlatform();
@@ -89,7 +90,9 @@
   column_set->AddColumn(GridLayout::FILL, GridLayout::FILL,
                         1.0f, GridLayout::USE_PREF, 0, 0);
   layout->StartRow(1 /* expand */, tree_view_column);
-  layout->AddView(tree_view_->CreateParentIfNecessary());
+  tree_view_ = tree_view.get();
+  layout->AddView(
+      TreeView::CreateScrollViewWithTree(std::move(tree_view)).release());
 
   // Add control buttons horizontally.
   const int button_column = 1;
diff --git a/ui/views/examples/tree_view_example.h b/ui/views/examples/tree_view_example.h
index c158190f..e47e366 100644
--- a/ui/views/examples/tree_view_example.h
+++ b/ui/views/examples/tree_view_example.h
@@ -5,6 +5,8 @@
 #ifndef UI_VIEWS_EXAMPLES_TREE_VIEW_EXAMPLE_H_
 #define UI_VIEWS_EXAMPLES_TREE_VIEW_EXAMPLE_H_
 
+#include <memory>
+
 #include "base/macros.h"
 #include "ui/base/models/simple_menu_model.h"
 #include "ui/base/models/tree_node_model.h"
@@ -70,12 +72,12 @@
   void ExecuteCommand(int command_id, int event_flags) override;
 
   // The tree view to be tested.
-  std::unique_ptr<TreeView> tree_view_;
+  TreeView* tree_view_ = nullptr;
 
   // Control buttons to modify the model.
-  LabelButton* add_;
-  LabelButton* remove_;
-  LabelButton* change_title_;
+  LabelButton* add_ = nullptr;
+  LabelButton* remove_ = nullptr;
+  LabelButton* change_title_ = nullptr;
 
   using NodeType = ui::TreeNodeWithValue<int>;
 
diff --git a/ui/views/mus/desktop_window_tree_host_mus.cc b/ui/views/mus/desktop_window_tree_host_mus.cc
index 9c886a85..fa32589 100644
--- a/ui/views/mus/desktop_window_tree_host_mus.cc
+++ b/ui/views/mus/desktop_window_tree_host_mus.cc
@@ -294,6 +294,8 @@
   window_tree_host_window_observer_->set_is_waiting_for_restore(true);
   base::AutoReset<bool> setter(&is_updating_window_visibility_, true);
   window()->Show();
+  if (compositor())
+    compositor()->SetVisible(true);
 }
 
 void DesktopWindowTreeHostMus::OnWindowTreeHostWindowVisibilityChanged(
diff --git a/ui/views/mus/desktop_window_tree_host_mus_unittest.cc b/ui/views/mus/desktop_window_tree_host_mus_unittest.cc
index ad0913a..e906986 100644
--- a/ui/views/mus/desktop_window_tree_host_mus_unittest.cc
+++ b/ui/views/mus/desktop_window_tree_host_mus_unittest.cc
@@ -735,6 +735,7 @@
   EXPECT_TRUE(widget->IsActive());
   EXPECT_TRUE(widget->IsVisible());
   EXPECT_FALSE(widget->IsMinimized());
+  EXPECT_TRUE(widget->GetNativeWindow()->GetHost()->compositor()->IsVisible());
 }
 
 TEST_F(DesktopWindowTreeHostMusTest, MaximizeMinimizeRestore) {
diff --git a/ui/views/view_unittest.cc b/ui/views/view_unittest.cc
index 9b0b53d..f28bd195 100644
--- a/ui/views/view_unittest.cc
+++ b/ui/views/view_unittest.cc
@@ -206,8 +206,7 @@
 class TestView : public View {
  public:
   TestView()
-      : View(),
-        did_layout_(false),
+      : did_layout_(false),
         delete_on_pressed_(false),
         did_paint_(false),
         native_theme_(nullptr),
@@ -1271,7 +1270,7 @@
 // A derived class for testing paint.
 class TestPaintView : public TestView {
  public:
-  TestPaintView() : TestView(), canvas_bounds_(gfx::Rect()) {}
+  TestPaintView() : canvas_bounds_(gfx::Rect()) {}
   ~TestPaintView() override = default;
 
   void OnPaint(gfx::Canvas* canvas) override {
@@ -4910,7 +4909,7 @@
   // ID used by the children that are stacked above other children.
   static constexpr int VIEW_ID_RAISED = 1000;
 
-  OrderableView() : View() {}
+  OrderableView() = default;
   ~OrderableView() override = default;
 
   View::Views GetChildrenInZOrder() override {
diff --git a/ui/views/views_test_suite.h b/ui/views/views_test_suite.h
index 8673c84..265de530 100644
--- a/ui/views/views_test_suite.h
+++ b/ui/views/views_test_suite.h
@@ -9,10 +9,6 @@
 
 #include "build/build_config.h"
 
-#if defined(OS_WIN)
-#include "base/win/scoped_com_initializer.h"
-#endif
-
 #if defined(USE_AURA) && !defined(OS_CHROMEOS)
 #include <memory>
 
@@ -49,10 +45,6 @@
   std::unique_ptr<aura::Env> env_;
 #endif
 
-#if defined(OS_WIN)
-  base::win::ScopedCOMInitializer com_initializer_;
-#endif
-
   int argc_;
   char** argv_;
 
diff --git a/ui/views/widget/desktop_aura/desktop_screen_position_client.cc b/ui/views/widget/desktop_aura/desktop_screen_position_client.cc
index 5b3e940..b889969e 100644
--- a/ui/views/widget/desktop_aura/desktop_screen_position_client.cc
+++ b/ui/views/widget/desktop_aura/desktop_screen_position_client.cc
@@ -24,7 +24,7 @@
 
 DesktopScreenPositionClient::DesktopScreenPositionClient(
     aura::Window* root_window)
-    : wm::DefaultScreenPositionClient(), root_window_(root_window) {
+    : root_window_(root_window) {
   aura::client::SetScreenPositionClient(root_window_, this);
 }
 
diff --git a/ui/views/widget/widget_interactive_uitest.cc b/ui/views/widget/widget_interactive_uitest.cc
index 3ade17fb..b040b3ae 100644
--- a/ui/views/widget/widget_interactive_uitest.cc
+++ b/ui/views/widget/widget_interactive_uitest.cc
@@ -97,12 +97,7 @@
 // A view that always processes all mouse events.
 class MouseView : public View {
  public:
-  MouseView()
-      : View(),
-        entered_(0),
-        exited_(0),
-        pressed_(0) {
-  }
+  MouseView() : entered_(0), exited_(0), pressed_(0) {}
   ~MouseView() override = default;
 
   bool OnMousePressed(const ui::MouseEvent& event) override {
diff --git a/ui/views/widget/widget_unittest.cc b/ui/views/widget/widget_unittest.cc
index f70f193c..e48c2f6 100644
--- a/ui/views/widget/widget_unittest.cc
+++ b/ui/views/widget/widget_unittest.cc
@@ -2382,7 +2382,7 @@
 // A view that consumes mouse-pressed event and gesture-tap-down events.
 class RootViewTestView : public View {
  public:
-  RootViewTestView(): View() {}
+  RootViewTestView() = default;
 
  private:
   bool OnMousePressed(const ui::MouseEvent& event) override { return true; }
diff --git a/ui/views/window/native_frame_view.cc b/ui/views/window/native_frame_view.cc
index cbe33286..020d9da3 100644
--- a/ui/views/window/native_frame_view.cc
+++ b/ui/views/window/native_frame_view.cc
@@ -20,10 +20,7 @@
 // static
 const char NativeFrameView::kViewClassName[] = "NativeFrameView";
 
-NativeFrameView::NativeFrameView(Widget* frame)
-    : NonClientFrameView(),
-      frame_(frame) {
-}
+NativeFrameView::NativeFrameView(Widget* frame) : frame_(frame) {}
 
 NativeFrameView::~NativeFrameView() = default;