diff --git a/DEPS b/DEPS
index cd8e5c1..0d454b9 100644
--- a/DEPS
+++ b/DEPS
@@ -206,7 +206,7 @@
   # 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': '56957d9ccfde015a603f55a589c74d55eae181b6',
+  'skia_revision': '624482861e16c00dbfa15de62061de0c2dbf526f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -218,7 +218,7 @@
   # 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': '68b95b6330b48e05db7b4bcd1d1713951219c173',
+  'angle_revision': '646cf5b2dabdb3aa066f24841abc9261f9de60a7',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -226,7 +226,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': '2d61ff8f6c1143c2b005747f99fced9a03218d92',
+  'pdfium_revision': '415d1ec806bb37d92cb8deee893791c682cb54d5',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -269,7 +269,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': '22b0de95cd01b9ea77252ac0dc3c793cb07fe8d3',
+  'catapult_revision': '24bd418544c6f5a68a023fdb51e12ff9b757b743',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -277,7 +277,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': 'ea2a4adced5c03647c350c8d4a2aa82419c02dde',
+  'devtools_frontend_revision': 'e3c2eb0f04c8aae1326a8b51d7dfafee4a44ffc7',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -329,7 +329,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': 'af64c73c174ce666d7fe7f11427c14b439b62515',
+  'dawn_revision': '25eb373eede4aaa8ed069457e2c05b9c9eab9db2',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -368,7 +368,7 @@
   'ukey2_revision': '0275885d8e6038c39b8a8ca55e75d1d4d1727f47',
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'tint_revision': 'c8b2d23e9de73c2ff47635dd35de30a5a3049ab1',
+  'tint_revision': '5d40a5621b7d0609c07a254c967763d1307b8dda',
 
   # TODO(crbug.com/941824): The values below need to be kept in sync
   # between //DEPS and //buildtools/DEPS, so if you're updating one,
@@ -1343,7 +1343,7 @@
       'packages': [
           {
               'package': 'fuchsia/third_party/aemu/linux-amd64',
-              'version': '5NnLo-cXdmYFIzPY4rZEivBCdo7jYrgKQx3j1gQI1ScC'
+              'version': 'uqYfh08DTmvjUgXl-HxNY1ptIb0KpyuY0I9MFaci9-AC'
           },
       ],
       'condition': 'host_os == "linux" and checkout_fuchsia',
@@ -1569,7 +1569,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@5e0515c39cc5229a05ce2fa82320757d0cbb07dd',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@85b44b21edd6cdd84bae3d36f71e28934fb3f0c3',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 9f90298..363ffb94 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -386,9 +386,9 @@
   '^extensions/renderer/',
   '^google_apis/drive/',
   '^ios/chrome/',
-  '^ios/web/',
-  '^ios/web_view/',
+  '^media/blink/',
   '^media/cast/',
+  '^media/cdm/',
   '^media/filters/',
   '^media/gpu/',
   '^media/mojo/',
diff --git a/android_webview/browser/gfx/aw_gl_surface_external_stencil.cc b/android_webview/browser/gfx/aw_gl_surface_external_stencil.cc
index 1abc9733..f589a2e2 100644
--- a/android_webview/browser/gfx/aw_gl_surface_external_stencil.cc
+++ b/android_webview/browser/gfx/aw_gl_surface_external_stencil.cc
@@ -82,12 +82,6 @@
       glDisableVertexAttribArray(i);
     }
 
-    // Note that function is not ANGLE only.
-    if (gl::g_current_gl_driver->fn.glVertexAttribDivisorANGLEFn) {
-      glVertexAttribDivisorANGLE(0, 0);
-      glVertexAttribDivisorANGLE(1, 0);
-    }
-
     glEnableVertexAttribArray(0);
     glEnableVertexAttribArray(1);
 
@@ -226,8 +220,6 @@
 
     glActiveTexture(GL_TEXTURE0);
     glBindTexture(GL_TEXTURE_2D, framebuffer_->texture_id());
-    if (gl::g_current_gl_driver->fn.glBindSamplerFn)
-      glBindSampler(0, 0);
 
     // We need to restore viewport as it might have changed by renderer
     glViewport(0, 0, viewport_.width(), viewport_.height());
@@ -238,15 +230,6 @@
     // Restore color mask in case.
     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
 
-    glDisable(GL_CULL_FACE);
-    glDisable(GL_DEPTH_TEST);
-    glDisable(GL_BLEND);
-    glFrontFace(GL_CCW);
-    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
-
-    if (gl::g_current_gl_driver->fn.glWindowRectanglesEXTFn)
-      glWindowRectanglesEXT(GL_EXCLUSIVE_EXT, 0, nullptr);
-
     glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
   }
 
diff --git a/android_webview/browser/gfx/scoped_app_gl_state_restore.cc b/android_webview/browser/gfx/scoped_app_gl_state_restore.cc
index 0e751fa..358eaad 100644
--- a/android_webview/browser/gfx/scoped_app_gl_state_restore.cc
+++ b/android_webview/browser/gfx/scoped_app_gl_state_restore.cc
@@ -8,28 +8,11 @@
 
 #include "base/lazy_instance.h"
 #include "base/trace_event/trace_event.h"
-#include "components/viz/common/features.h"
 #include "ui/gl/gl_bindings.h"
 #include "ui/gl/gl_context.h"
 #include "ui/gl/gl_surface_stub.h"
 #include "ui/gl/init/gl_factory.h"
 
-// We can't include khronos headers because of conflict with gl_bindings.h, but
-// we need this constant for restoring state.
-#ifndef GL_ARM_shader_framebuffer_fetch
-#define GL_ARM_shader_framebuffer_fetch 1
-#define GL_FETCH_PER_SAMPLE_ARM 0x8F65
-#define GL_FRAGMENT_SHADER_FRAMEBUFFER_FETCH_MRT_ARM 0x8F66
-#endif /* GL_ARM_shader_framebuffer_fetch */
-
-#ifndef GL_NV_conservative_raster
-#define GL_NV_conservative_raster 1
-#define GL_CONSERVATIVE_RASTERIZATION_NV 0x9346
-#define GL_SUBPIXEL_PRECISION_BIAS_X_BITS_NV 0x9347
-#define GL_SUBPIXEL_PRECISION_BIAS_Y_BITS_NV 0x9348
-#define GL_MAX_SUBPIXEL_PRECISION_BIAS_BITS_NV 0x9349
-#endif /* GL_NV_conservative_raster */
-
 namespace android_webview {
 
 namespace {
@@ -84,10 +67,6 @@
 GLint g_gl_max_texture_units = 0;
 GLint g_gl_max_vertex_attribs = 0;
 bool g_supports_oes_vertex_array_object = false;
-bool g_supports_arm_shader_framebuffer_fetch = false;
-bool g_supports_nv_concervative_raster = false;
-bool g_supports_disable_multisample = false;
-bool g_use_skia_renderer = false;
 ScopedAppGLStateRestore* g_current_instance = nullptr;
 
 }  // namespace
@@ -153,9 +132,6 @@
   GLboolean enable_polygon_offset_fill_;
   GLboolean enable_sample_alpha_to_coverage_;
   GLboolean enable_sample_coverage_;
-  GLboolean multisample_enabled_;
-  // ARM_shader_framebuffer_fetch
-  GLboolean fetch_per_sample_arm_enabled_;
 
   // Not saved/restored in MODE_DRAW.
   GLboolean blend_enabled_;
@@ -219,29 +195,6 @@
 
   glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &framebuffer_binding_ext_);
 
-  if (!g_globals_initialized) {
-    g_globals_initialized = true;
-
-    glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &g_gl_max_vertex_attribs);
-    glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &g_gl_max_texture_units);
-
-    std::string extensions;
-    const char* extensions_c_str =
-        reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS));
-    if (extensions_c_str)
-      extensions = extensions_c_str;
-    g_supports_oes_vertex_array_object =
-        extensions.find("GL_OES_vertex_array_object") != std::string::npos;
-    g_supports_arm_shader_framebuffer_fetch =
-        extensions.find("GL_ARM_shader_framebuffer_fetch") != std::string::npos;
-    g_supports_nv_concervative_raster =
-        extensions.find("GL_NV_conservative_raster") != std::string::npos;
-    g_supports_disable_multisample =
-        extensions.find("GL_EXT_multisample_compatibility") !=
-        std::string::npos;
-    g_use_skia_renderer = features::IsUsingSkiaRenderer();
-  }
-
   if (save_restore_) {
     SaveHWUIState();
   }
@@ -258,6 +211,21 @@
 }
 
 void ScopedAppGLStateRestoreImpl::SaveHWUIState() {
+  if (!g_globals_initialized) {
+    g_globals_initialized = true;
+
+    glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &g_gl_max_vertex_attribs);
+    glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &g_gl_max_texture_units);
+
+    std::string extensions;
+    const char* extensions_c_str =
+        reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS));
+    if (extensions_c_str)
+      extensions = extensions_c_str;
+    g_supports_oes_vertex_array_object =
+        extensions.find("GL_OES_vertex_array_object") != std::string::npos;
+  }
+
   glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &vertex_array_buffer_binding_);
   glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &index_array_buffer_binding_);
 
@@ -344,14 +312,6 @@
     glGetVertexAttribfv(
         i, GL_CURRENT_VERTEX_ATTRIB, vertex_attrib_[i].current_vertex_attrib);
   }
-
-  if (g_use_skia_renderer) {
-    if (g_supports_arm_shader_framebuffer_fetch)
-      glGetBooleanv(GL_FETCH_PER_SAMPLE_ARM, &fetch_per_sample_arm_enabled_);
-
-    if (g_supports_disable_multisample)
-      glGetBooleanv(GL_MULTISAMPLE, &multisample_enabled_);
-  }
 }
 
 ScopedAppGLStateRestoreImpl::~ScopedAppGLStateRestoreImpl() {
@@ -362,11 +322,6 @@
     RestoreHWUIState();
   }
 
-  // We do restore it even with Skia on the other side because it's new
-  // extension that skia on Android P and Q didn't use.
-  if (g_use_skia_renderer && g_supports_nv_concervative_raster)
-    glDisable(GL_CONSERVATIVE_RASTERIZATION_NV);
-
   // Do not leak GLError out of chromium.
   ClearGLErrors(true, "Chromium GLError");
 }
@@ -390,15 +345,6 @@
 
     glVertexAttrib4fv(i, vertex_attrib_[i].current_vertex_attrib);
 
-    // We don't query this values as this code intends to run on older (O and
-    // below) Android versions and HWUI there doesn't use the state, while
-    // SkiaRenderer does, so we just reset it to default value.
-    // Note despite the name function is no ANGLE specific.
-    if (g_use_skia_renderer &&
-        gl::g_current_gl_driver->fn.glVertexAttribDivisorANGLEFn) {
-      glVertexAttribDivisorANGLE(i, 0);
-    }
-
     if (vertex_attrib_[i].enabled) {
       glEnableVertexAttribArray(i);
     } else {
@@ -417,10 +363,6 @@
     glBindTexture(GL_TEXTURE_2D, bindings.texture_2d);
     glBindTexture(GL_TEXTURE_CUBE_MAP, bindings.texture_cube_map);
     glBindTexture(GL_TEXTURE_EXTERNAL_OES, bindings.texture_external_oes);
-
-    // reset glSamplers if supported.
-    if (g_use_skia_renderer && gl::g_current_gl_driver->fn.glBindSamplerFn)
-      glBindSampler(ii, 0);
   }
   glActiveTexture(active_texture_);
 
@@ -492,25 +434,6 @@
   glStencilOpSeparate(GL_BACK, stencil_state_.stencil_back_fail_op,
                       stencil_state_.stencil_back_z_fail_op,
                       stencil_state_.stencil_back_z_pass_op);
-
-  if (g_use_skia_renderer) {
-    // This code is triggered only for older Android version when it didn't use
-    // Skia for compositing. We restore state to default value instead of
-    // querying the value because older HWUI didn't use it.
-    if (gl::g_current_gl_driver->fn.glWindowRectanglesEXTFn)
-      glWindowRectanglesEXT(GL_EXCLUSIVE_EXT, 0, nullptr);
-
-    if (gl::g_current_gl_driver->fn.glCoverageModulationNVFn)
-      glCoverageModulationNV(GL_NONE);
-
-    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
-
-    if (g_supports_arm_shader_framebuffer_fetch)
-      GLEnableDisable(GL_FETCH_PER_SAMPLE_ARM, fetch_per_sample_arm_enabled_);
-
-    if (g_supports_disable_multisample)
-      GLEnableDisable(GL_MULTISAMPLE, multisample_enabled_);
-  }
 }
 
 }  // namespace internal
diff --git a/android_webview/docs/navbar.md b/android_webview/docs/navbar.md
index b4e0392a..42eb8568 100644
--- a/android_webview/docs/navbar.md
+++ b/android_webview/docs/navbar.md
@@ -16,6 +16,7 @@
 * [Threading](/android_webview/docs/threading.md)
   <!-- Link new docs above this line. -->
 * [More docs](/android_webview/docs/)
+* [**File a docs bug or request**](https://bugs.chromium.org/p/chromium/issues/entry?labels=OS-Android,Pri-3,Type-Task,Documentation&components=Mobile%3EWebView&status=Available&description=What+WebView+documentation+would+you+like+to+see%3F)
 
 [home]: /android_webview/docs/README.md
 [logo]: /android_webview/docs/images/webview_logo.png
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 0ff9e68..df19aa88 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -2500,6 +2500,8 @@
     "accessibility/test_accessibility_controller_client.h",
     "ambient/test/ambient_ash_test_base.cc",
     "ambient/test/ambient_ash_test_base.h",
+    "ambient/test/test_ambient_client.cc",
+    "ambient/test/test_ambient_client.h",
     "app_list/app_list_test_api.cc",
     "app_list/test/app_list_test_helper.cc",
     "app_list/test/app_list_test_helper.h",
diff --git a/ash/ambient/ambient_controller.cc b/ash/ambient/ambient_controller.cc
index 28713ab..cd6d2ec 100644
--- a/ash/ambient/ambient_controller.cc
+++ b/ash/ambient/ambient_controller.cc
@@ -112,10 +112,9 @@
   if (!AmbientClient::Get()->IsAmbientModeAllowed())
     return false;
 
-  ash::SessionControllerImpl* controller = Shell::Get()->session_controller();
-  PrefService* prefs = controller->GetActivePrefService();
-  DCHECK(prefs);
-  return prefs->GetBoolean(ambient::prefs::kAmbientModeEnabled);
+  PrefService* prefs =
+      Shell::Get()->session_controller()->GetPrimaryUserPrefService();
+  return prefs && prefs->GetBoolean(ambient::prefs::kAmbientModeEnabled);
 }
 
 class AmbientWidgetDelegate : public views::WidgetDelegate {
diff --git a/ash/ambient/ambient_controller_unittest.cc b/ash/ambient/ambient_controller_unittest.cc
index d9a8152..1ba42f6 100644
--- a/ash/ambient/ambient_controller_unittest.cc
+++ b/ash/ambient/ambient_controller_unittest.cc
@@ -170,6 +170,10 @@
   EXPECT_TRUE(ambient_controller()->IsShown());
 
   SimulateUserLogin(kUser2);
+  SetAmbientModeEnabled(true);
+
+  // Ambient mode should not show for second user even if that user has the pref
+  // turned on.
   EXPECT_EQ(AmbientUiModel::Get()->ui_visibility(),
             AmbientUiVisibility::kClosed);
   EXPECT_FALSE(ambient_controller()->IsShown());
diff --git a/ash/ambient/test/ambient_ash_test_base.cc b/ash/ambient/test/ambient_ash_test_base.cc
index 632804c3..37b2051 100644
--- a/ash/ambient/test/ambient_ash_test_base.cc
+++ b/ash/ambient/test/ambient_ash_test_base.cc
@@ -168,7 +168,7 @@
 }
 
 void AmbientAshTestBase::SetAmbientModeEnabled(bool enabled) {
-  Shell::Get()->session_controller()->GetPrimaryUserPrefService()->SetBoolean(
+  Shell::Get()->session_controller()->GetActivePrefService()->SetBoolean(
       ambient::prefs::kAmbientModeEnabled, enabled);
 }
 
diff --git a/ash/ambient/test/ambient_ash_test_base.h b/ash/ambient/test/ambient_ash_test_base.h
index 2bb84a3..dad5613 100644
--- a/ash/ambient/test/ambient_ash_test_base.h
+++ b/ash/ambient/test/ambient_ash_test_base.h
@@ -10,8 +10,8 @@
 
 #include "ash/ambient/ambient_access_token_controller.h"
 #include "ash/ambient/ambient_controller.h"
+#include "ash/ambient/test/test_ambient_client.h"
 #include "ash/ambient/ui/ambient_background_image_view.h"
-#include "ash/public/cpp/test/test_ambient_client.h"
 #include "ash/public/cpp/test/test_image_downloader.h"
 #include "ash/test/ash_test_base.h"
 #include "base/test/scoped_feature_list.h"
@@ -41,7 +41,7 @@
   void SetUp() override;
   void TearDown() override;
 
-  // Enables/disables ambient mode.
+  // Enables/disables ambient mode for the currently active user session.
   void SetAmbientModeEnabled(bool enabled);
 
   // Creates ambient screen in its own widget.
diff --git a/ash/public/cpp/test/test_ambient_client.cc b/ash/ambient/test/test_ambient_client.cc
similarity index 87%
rename from ash/public/cpp/test/test_ambient_client.cc
rename to ash/ambient/test/test_ambient_client.cc
index c578b308..1dded4f 100644
--- a/ash/public/cpp/test/test_ambient_client.cc
+++ b/ash/ambient/test/test_ambient_client.cc
@@ -2,10 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ash/public/cpp/test/test_ambient_client.h"
+#include "ash/ambient/test/test_ambient_client.h"
 
 #include <utility>
 
+#include "ash/session/session_controller_impl.h"
+#include "ash/shell.h"
 #include "base/time/time.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
@@ -27,7 +29,8 @@
 TestAmbientClient::~TestAmbientClient() = default;
 
 bool TestAmbientClient::IsAmbientModeAllowed() {
-  return true;
+  // Only enable ambient mode for primary user to test multi login.
+  return Shell::Get()->session_controller()->IsUserPrimary();
 }
 
 void TestAmbientClient::RequestAccessToken(GetAccessTokenCallback callback) {
diff --git a/ash/public/cpp/test/test_ambient_client.h b/ash/ambient/test/test_ambient_client.h
similarity index 89%
rename from ash/public/cpp/test/test_ambient_client.h
rename to ash/ambient/test/test_ambient_client.h
index 75173d9f..f1e19e97 100644
--- a/ash/public/cpp/test/test_ambient_client.h
+++ b/ash/ambient/test/test_ambient_client.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 ASH_PUBLIC_CPP_TEST_TEST_AMBIENT_CLIENT_H_
-#define ASH_PUBLIC_CPP_TEST_TEST_AMBIENT_CLIENT_H_
+#ifndef ASH_AMBIENT_TEST_TEST_AMBIENT_CLIENT_H_
+#define ASH_AMBIENT_TEST_TEST_AMBIENT_CLIENT_H_
 
 #include <string>
 
@@ -43,4 +43,4 @@
 
 }  // namespace ash
 
-#endif  // ASH_PUBLIC_CPP_TEST_TEST_AMBIENT_CLIENT_H_
+#endif  // ASH_AMBIENT_TEST_TEST_AMBIENT_CLIENT_H_
diff --git a/ash/clipboard/clipboard_history_controller_impl.cc b/ash/clipboard/clipboard_history_controller_impl.cc
index 4c38afbb4..c90bfb3 100644
--- a/ash/clipboard/clipboard_history_controller_impl.cc
+++ b/ash/clipboard/clipboard_history_controller_impl.cc
@@ -11,6 +11,7 @@
 #include "ash/clipboard/clipboard_history_menu_model_adapter.h"
 #include "ash/clipboard/clipboard_history_resource_manager.h"
 #include "ash/clipboard/clipboard_history_util.h"
+#include "ash/clipboard/clipboard_nudge_controller.h"
 #include "ash/public/cpp/window_tree_host_lookup.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/shell.h"
@@ -191,11 +192,20 @@
           clipboard_history_.get())),
       accelerator_target_(std::make_unique<AcceleratorTarget>(this)),
       menu_delegate_(std::make_unique<MenuDelegate>(this)),
-      nudge_controller_(std::make_unique<ClipboardNudgeController>(
-          clipboard_history_.get())) {}
+      nudge_controller_(
+          std::make_unique<ClipboardNudgeController>(clipboard_history_.get(),
+                                                     this)) {}
 
 ClipboardHistoryControllerImpl::~ClipboardHistoryControllerImpl() = default;
 
+void ClipboardHistoryControllerImpl::AddObserver(Observer* observer) const {
+  observers_.AddObserver(observer);
+}
+
+void ClipboardHistoryControllerImpl::RemoveObserver(Observer* observer) const {
+  observers_.RemoveObserver(observer);
+}
+
 bool ClipboardHistoryControllerImpl::IsMenuShowing() const {
   return context_menu_ && context_menu_->IsRunning();
 }
@@ -240,6 +250,8 @@
   // history menu shows.
   context_menu_->SelectMenuItemWithCommandId(
       ClipboardHistoryUtil::kFirstItemCommandId);
+  for (auto& observer : observers_)
+    observer.OnClipboardHistoryMenuShown();
 }
 
 bool ClipboardHistoryControllerImpl::CanShowMenu() const {
@@ -249,11 +261,15 @@
 
 void ClipboardHistoryControllerImpl::ExecuteSelectedMenuItem(int event_flags) {
   DCHECK(IsMenuShowing());
+
   auto command = context_menu_->GetSelectedMenuItemCommand();
 
   // If no menu item is currently selected, we'll fallback to the first item.
   menu_delegate_->ExecuteCommand(
       command.value_or(ClipboardHistoryUtil::kFirstItemCommandId), event_flags);
+
+  for (auto& observer : observers_)
+    observer.OnClipboardHistoryPasted();
 }
 
 void ClipboardHistoryControllerImpl::ExecuteCommand(int command_id,
diff --git a/ash/clipboard/clipboard_history_controller_impl.h b/ash/clipboard/clipboard_history_controller_impl.h
index b7348c4..b2f99e1a 100644
--- a/ash/clipboard/clipboard_history_controller_impl.h
+++ b/ash/clipboard/clipboard_history_controller_impl.h
@@ -10,9 +10,9 @@
 
 #include "ash/ash_export.h"
 #include "ash/clipboard/clipboard_history_item.h"
-#include "ash/clipboard/clipboard_nudge_controller.h"
 #include "ash/public/cpp/clipboard_history_controller.h"
 #include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
 
 namespace views {
 enum class MenuAnchorPosition;
@@ -27,12 +27,21 @@
 class ClipboardHistory;
 class ClipboardHistoryMenuModelAdapter;
 class ClipboardHistoryResourceManager;
+class ClipboardNudgeController;
 
 // Shows a menu with the last few things saved in the clipboard when the
 // keyboard shortcut is pressed.
 class ASH_EXPORT ClipboardHistoryControllerImpl
     : public ClipboardHistoryController {
  public:
+  class Observer : public base::CheckedObserver {
+   public:
+    // Called when the clipboard history menu is shown.
+    virtual void OnClipboardHistoryMenuShown() = 0;
+    // Called when the user pastes from the clipboard history menu.
+    virtual void OnClipboardHistoryPasted() = 0;
+  };
+
   ClipboardHistoryControllerImpl();
   ClipboardHistoryControllerImpl(const ClipboardHistoryControllerImpl&) =
       delete;
@@ -40,6 +49,9 @@
       const ClipboardHistoryControllerImpl&) = delete;
   ~ClipboardHistoryControllerImpl() override;
 
+  void AddObserver(Observer* observer) const;
+  void RemoveObserver(Observer* observer) const;
+
   // Returns if the contextual menu is currently showing.
   bool IsMenuShowing() const;
 
@@ -98,6 +110,10 @@
   // Called when the contextual menu is closed.
   void OnMenuClosed();
 
+  // Mutable to allow adding/removing from |observers_| through a const
+  // ClipboardHistoryControllerImpl.
+  mutable base::ObserverList<Observer> observers_;
+
   // The menu being shown.
   std::unique_ptr<ClipboardHistoryMenuModelAdapter> context_menu_;
   // Used to keep track of what is being copied to the clipboard.
diff --git a/ash/clipboard/clipboard_history_controller_unittest.cc b/ash/clipboard/clipboard_history_controller_unittest.cc
index 2a94bb3..f171fe3 100644
--- a/ash/clipboard/clipboard_history_controller_unittest.cc
+++ b/ash/clipboard/clipboard_history_controller_unittest.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "ash/app_list/app_list_controller_impl.h"
+#include "ash/clipboard/clipboard_history.h"
 #include "ash/public/cpp/clipboard_image_model_factory.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
diff --git a/ash/clipboard/clipboard_nudge_controller.cc b/ash/clipboard/clipboard_nudge_controller.cc
index b93b2dcf..3450027 100644
--- a/ash/clipboard/clipboard_nudge_controller.cc
+++ b/ash/clipboard/clipboard_nudge_controller.cc
@@ -11,6 +11,8 @@
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "base/logging.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
 #include "base/no_destructor.h"
 #include "base/util/values/values_util.h"
 #include "chromeos/constants/chromeos_features.h"
@@ -25,14 +27,21 @@
 constexpr char kShownCount[] = "shown_count";
 constexpr char kLastTimeShown[] = "last_time_shown";
 
+// The maximum number of 1 second buckets used to record the time between
+// showing the nudge and recording the feature being opened/used.
+constexpr int kBucketCount = 61;
+
 }  // namespace
 
 namespace ash {
 
 ClipboardNudgeController::ClipboardNudgeController(
-    ClipboardHistory* clipboard_history)
-    : clipboard_history_(clipboard_history) {
+    ClipboardHistory* clipboard_history,
+    ClipboardHistoryControllerImpl* clipboard_history_controller)
+    : clipboard_history_(clipboard_history),
+      clipboard_history_controller_(clipboard_history_controller) {
   clipboard_history_->AddObserver(this);
+  clipboard_history_controller_->AddObserver(this);
   ui::ClipboardMonitor::GetInstance()->AddObserver(this);
   if (chromeos::features::IsClipboardHistoryNudgeSessionResetEnabled())
     Shell::Get()->session_controller()->AddObserver(this);
@@ -40,6 +49,7 @@
 
 ClipboardNudgeController::~ClipboardNudgeController() {
   clipboard_history_->RemoveObserver(this);
+  clipboard_history_controller_->RemoveObserver(this);
   ui::ClipboardMonitor::GetInstance()->RemoveObserver(this);
   if (chromeos::features::IsClipboardHistoryNudgeSessionResetEnabled())
     Shell::Get()->session_controller()->RemoveObserver(this);
@@ -119,6 +129,13 @@
   hide_nudge_timer_.Start(FROM_HERE, kNudgeShowTime,
                           base::BindOnce(&ClipboardNudgeController::HideNudge,
                                          weak_ptr_factory_.GetWeakPtr()));
+  last_shown_time_ = GetTime();
+
+  // Tracks the number of times the ClipboardHistory nudge is shown.
+  // This allows us to understand the conversion rate of showing a nudge to
+  // a user opening and then using the clipboard history feature.
+  base::UmaHistogramExactLinear(
+      "Ash.ClipboardHistory.ContextualNudge.ShownCount", 1, 1);
 }
 
 void ClipboardNudgeController::HideNudge() {
@@ -136,6 +153,30 @@
   update->SetPath(kLastTimeShown, util::TimeToValue(GetTime()));
 }
 
+void ClipboardNudgeController::OnClipboardHistoryMenuShown() {
+  if (last_shown_time_.is_null())
+    return;
+  base::TimeDelta time_since_shown = GetTime() - last_shown_time_;
+
+  // Tracks the amount of time between showing the user a nudge and the user
+  // opening the ClipboardHistory menu.
+  base::UmaHistogramExactLinear(
+      "Ash.ClipboardHistory.ContextualNudge.NudgeToFeatureOpenTime",
+      time_since_shown.InSeconds(), kBucketCount);
+}
+
+void ClipboardNudgeController::OnClipboardHistoryPasted() {
+  if (last_shown_time_.is_null())
+    return;
+  base::TimeDelta time_since_shown = GetTime() - last_shown_time_;
+
+  // Tracks the amount of time between showing the user a nudge and the user
+  // using the ClipboardHistory feature.
+  base::UmaHistogramExactLinear(
+      "Ash.ClipboardHistory.ContextualNudge.NudgeToFeatureUseTime",
+      time_since_shown.InSeconds(), kBucketCount);
+}
+
 void ClipboardNudgeController::OverrideClockForTesting(
     base::Clock* test_clock) {
   DCHECK(!g_clock_override);
diff --git a/ash/clipboard/clipboard_nudge_controller.h b/ash/clipboard/clipboard_nudge_controller.h
index 27d6966e..2cea7576 100644
--- a/ash/clipboard/clipboard_nudge_controller.h
+++ b/ash/clipboard/clipboard_nudge_controller.h
@@ -7,6 +7,7 @@
 
 #include "ash/ash_export.h"
 #include "ash/clipboard/clipboard_history.h"
+#include "ash/clipboard/clipboard_history_controller_impl.h"
 #include "ash/public/cpp/session/session_observer.h"
 #include "base/memory/weak_ptr.h"
 #include "base/time/clock.h"
@@ -31,11 +32,15 @@
   kShouldShowNudge = 4,
 };
 
-class ASH_EXPORT ClipboardNudgeController : public ClipboardHistory::Observer,
-                                            public ui::ClipboardObserver,
-                                            public SessionObserver {
+class ASH_EXPORT ClipboardNudgeController
+    : public ClipboardHistory::Observer,
+      public ui::ClipboardObserver,
+      public SessionObserver,
+      public ClipboardHistoryControllerImpl::Observer {
  public:
-  ClipboardNudgeController(ClipboardHistory* clipboard_history);
+  ClipboardNudgeController(
+      ClipboardHistory* clipboard_history,
+      ClipboardHistoryControllerImpl* clipboard_history_controller);
   ClipboardNudgeController(const ClipboardNudgeController&) = delete;
   ClipboardNudgeController& operator=(const ClipboardNudgeController&) = delete;
   ~ClipboardNudgeController() override;
@@ -55,6 +60,10 @@
   // Resets nudge state and show nudge timer.
   void HandleNudgeShown();
 
+  // ClipboardHistoryControllerImpl:
+  void OnClipboardHistoryMenuShown() override;
+  void OnClipboardHistoryPasted() override;
+
   // Test methods for overriding and resetting the clock used by GetTime.
   void OverrideClockForTesting(base::Clock* test_clock);
   void ClearClockOverrideForTesting();
@@ -78,9 +87,15 @@
   // Hides the nudge widget.
   void HideNudge();
 
+  // Time the nudge was last shown.
+  base::Time last_shown_time_;
+
   // Owned by ClipboardHistoryController.
   const ClipboardHistory* clipboard_history_;
 
+  // Owned by ash/Shell.
+  const ClipboardHistoryControllerImpl* const clipboard_history_controller_;
+
   // Current clipboard state.
   ClipboardState clipboard_state_ = ClipboardState::kInit;
   // The timestamp of the most recent paste.
diff --git a/ash/public/cpp/BUILD.gn b/ash/public/cpp/BUILD.gn
index e017dd49..33022846 100644
--- a/ash/public/cpp/BUILD.gn
+++ b/ash/public/cpp/BUILD.gn
@@ -392,8 +392,6 @@
 source_set("test_support") {
   testonly = true
   sources = [
-    "test/test_ambient_client.cc",
-    "test/test_ambient_client.h",
     "test/test_image_downloader.cc",
     "test/test_image_downloader.h",
     "test/test_keyboard_controller_observer.cc",
diff --git a/ash/public/cpp/app_list/app_list_metrics.h b/ash/public/cpp/app_list/app_list_metrics.h
index 6c8b0c5..a32e494 100644
--- a/ash/public/cpp/app_list/app_list_metrics.h
+++ b/ash/public/cpp/app_list/app_list_metrics.h
@@ -92,6 +92,8 @@
   REMOTE_APP,
   // A Borealis App Result.
   BOREALIS_APP,
+  // A Help App (aka Explore) Result.
+  HELP_APP,
   // Boundary is always last.
   SEARCH_RESULT_TYPE_BOUNDARY
 };
diff --git a/ash/public/cpp/app_list/app_list_types.h b/ash/public/cpp/app_list/app_list_types.h
index 5f42b3d..50acd00 100644
--- a/ash/public/cpp/app_list/app_list_types.h
+++ b/ash/public/cpp/app_list/app_list_types.h
@@ -152,6 +152,7 @@
   kOsSettings,             // OS settings results.
   kInternalPrivacyInfo,    // Result used internally by privacy notices.
   kAssistantText,          // Assistant text results.
+  kHelpApp,                // Help App (aka Explore) results.
   // Add new values here.
 };
 
diff --git a/ash/public/cpp/holding_space/holding_space_metrics.cc b/ash/public/cpp/holding_space/holding_space_metrics.cc
index 43d2e5ad..5b6d27c 100644
--- a/ash/public/cpp/holding_space/holding_space_metrics.cc
+++ b/ash/public/cpp/holding_space/holding_space_metrics.cc
@@ -79,19 +79,18 @@
 }
 
 void RecordItemCounts(const std::vector<const HoldingSpaceItem*>& items) {
-  if (items.empty())
-    return;
-
   base::UmaHistogramCounts1000("HoldingSpace.Item.Count.All", items.size());
 
   std::map<HoldingSpaceItem::Type, int> counts_by_type;
   for (const HoldingSpaceItem* item : items)
     ++counts_by_type[item->type()];
 
-  for (const auto& count_by_type : counts_by_type) {
+  for (int i = 0; i <= static_cast<int>(HoldingSpaceItem::Type::kMaxValue);
+       ++i) {
+    const auto type = static_cast<HoldingSpaceItem::Type>(i);
     base::UmaHistogramCounts1000(
-        "HoldingSpace.Item.Count." + ItemTypeToString(count_by_type.first),
-        count_by_type.second);
+        "HoldingSpace.Item.Count." + ItemTypeToString(type),
+        counts_by_type[type]);
   }
 }
 
diff --git a/ash/shelf/assistant_overlay.cc b/ash/shelf/assistant_overlay.cc
index 7b3e7e49..6f609fb 100644
--- a/ash/shelf/assistant_overlay.cc
+++ b/ash/shelf/assistant_overlay.cc
@@ -16,6 +16,7 @@
 #include "ash/shelf/shelf_view.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_provider.h"
 #include "ash/system/tray/tray_popup_utils.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "base/command_line.h"
@@ -57,7 +58,6 @@
 constexpr float kRippleCircleStartRadiusDip = 1.f;
 constexpr float kRippleCircleRadiusDip = 77.f;
 constexpr float kRippleCircleBurstRadiusDip = 96.f;
-constexpr SkColor kRippleColor = SK_ColorWHITE;
 constexpr int kRippleExpandDurationMs = 400;
 constexpr int kRippleOpacityDurationMs = 100;
 constexpr int kRippleOpacityRetractDurationMs = 200;
@@ -67,11 +67,12 @@
 
 }  // namespace
 
-
 AssistantOverlay::AssistantOverlay(HomeButton* host_view)
     : ripple_layer_(std::make_unique<ui::Layer>()),
       host_view_(host_view),
-      circle_layer_delegate_(kRippleColor, kRippleCircleInitRadiusDip) {
+      circle_layer_delegate_(
+          AshColorProvider::Get()->GetRippleAttributes().base_color,
+          kRippleCircleInitRadiusDip) {
   SetPaintToLayer(ui::LAYER_NOT_DRAWN);
   layer()->SetName("AssistantOverlay:ROOT_LAYER");
   layer()->SetMasksToBounds(false);
diff --git a/ash/system/palette/palette_tray.cc b/ash/system/palette/palette_tray.cc
index ebd69ad2..10f9681 100644
--- a/ash/system/palette/palette_tray.cc
+++ b/ash/system/palette/palette_tray.cc
@@ -88,7 +88,7 @@
   return display.IsInternal();
 }
 
-class TitleView : public views::View, public views::ButtonListener {
+class TitleView : public views::View {
  public:
   explicit TitleView(PaletteTray* palette_tray) : palette_tray_(palette_tray) {
     // TODO(tdanderson|jdufault): Use TriView to handle the layout of the title.
@@ -108,10 +108,22 @@
                              true /* use_unified_theme */);
     style.SetupLabel(title_label);
     layout_ptr->SetFlexForView(title_label, 1);
-    help_button_ = new TopShortcutButton(this, kSystemMenuHelpIcon,
-                                         IDS_ASH_STATUS_TRAY_HELP);
-    settings_button_ = new TopShortcutButton(this, kSystemMenuSettingsIcon,
-                                             IDS_ASH_PALETTE_SETTINGS);
+    help_button_ = new TopShortcutButton(
+        base::BindRepeating(
+            &TitleView::ButtonPressed, base::Unretained(this),
+            PaletteTrayOptions::PALETTE_HELP_BUTTON,
+            base::BindRepeating(
+                &SystemTrayClient::ShowPaletteHelp,
+                base::Unretained(Shell::Get()->system_tray_model()->client()))),
+        kSystemMenuHelpIcon, IDS_ASH_STATUS_TRAY_HELP);
+    settings_button_ = new TopShortcutButton(
+        base::BindRepeating(
+            &TitleView::ButtonPressed, base::Unretained(this),
+            PaletteTrayOptions::PALETTE_SETTINGS_BUTTON,
+            base::BindRepeating(
+                &SystemTrayClient::ShowPaletteSettings,
+                base::Unretained(Shell::Get()->system_tray_model()->client()))),
+        kSystemMenuSettingsIcon, IDS_ASH_PALETTE_SETTINGS);
 
     AddChildView(help_button_);
     AddChildView(settings_button_);
@@ -123,23 +135,12 @@
   const char* GetClassName() const override { return "TitleView"; }
 
  private:
-  // views::ButtonListener:
-  void ButtonPressed(views::Button* sender, const ui::Event& event) override {
-    if (sender == settings_button_) {
-      palette_tray_->RecordPaletteOptionsUsage(
-          PaletteTrayOptions::PALETTE_SETTINGS_BUTTON,
-          PaletteInvocationMethod::MENU);
-      Shell::Get()->system_tray_model()->client()->ShowPaletteSettings();
-      palette_tray_->HidePalette();
-    } else if (sender == help_button_) {
-      palette_tray_->RecordPaletteOptionsUsage(
-          PaletteTrayOptions::PALETTE_HELP_BUTTON,
-          PaletteInvocationMethod::MENU);
-      Shell::Get()->system_tray_model()->client()->ShowPaletteHelp();
-      palette_tray_->HidePalette();
-    } else {
-      NOTREACHED();
-    }
+  void ButtonPressed(PaletteTrayOptions option,
+                     base::RepeatingClosure callback) {
+    palette_tray_->RecordPaletteOptionsUsage(option,
+                                             PaletteInvocationMethod::MENU);
+    std::move(callback).Run();
+    palette_tray_->HidePalette();
   }
 
   // Unowned pointers to button views so we can determine which button was
diff --git a/ash/test/ash_test_helper.h b/ash/test/ash_test_helper.h
index 52b515d2..1704c83 100644
--- a/ash/test/ash_test_helper.h
+++ b/ash/test/ash_test_helper.h
@@ -11,7 +11,6 @@
 #include <utility>
 
 #include "ash/assistant/test/test_assistant_service.h"
-#include "ash/public/cpp/test/test_ambient_client.h"
 #include "ash/public/cpp/test/test_image_downloader.h"
 #include "ash/public/cpp/test/test_system_tray_client.h"
 #include "ash/session/test_pref_service_provider.h"
diff --git a/ash/wm/desks/root_window_desk_switch_animator.cc b/ash/wm/desks/root_window_desk_switch_animator.cc
index b9bb51b..5ed6cf3 100644
--- a/ash/wm/desks/root_window_desk_switch_animator.cc
+++ b/ash/wm/desks/root_window_desk_switch_animator.cc
@@ -191,7 +191,6 @@
 
 void RootWindowDeskSwitchAnimator::StartAnimation() {
   DCHECK(starting_desk_screenshot_taken_);
-  DCHECK(ending_desk_screenshot_taken_);
   DCHECK(!animation_finished_);
 
   // Set a transform so that the ending desk will be visible.
@@ -322,9 +321,22 @@
 }
 
 void RootWindowDeskSwitchAnimator::EndSwipeAnimation() {
-  // TODO(crbug.com/1134390): Convert back to DCHECK when the issue is fixed.
-  CHECK(starting_desk_screenshot_taken_);
-  CHECK(ending_desk_screenshot_taken_);
+  // If the starting screenshot has not finished, just let our delegate know
+  // that the desk animation is finished (and |this| will soon be deleted), and
+  // go back to the starting desk.
+  if (!starting_desk_screenshot_taken_) {
+    animation_finished_ = true;
+    ending_desk_index_ = starting_desk_index_;
+    delegate_->OnDeskSwitchAnimationFinished();
+    return;
+  }
+
+  // If the ending desk screenshot has not finished,
+  // GetIndexOfMostVisibleDeskScreenshot will still return a valid desk index
+  // that we can animate to, but we need to make sure the ending desk screenshot
+  // callback does not get called.
+  if (!ending_desk_screenshot_taken_)
+    weak_ptr_factory_.InvalidateWeakPtrs();
 
   ending_desk_index_ = GetIndexOfMostVisibleDeskScreenshot();
   StartAnimation();
diff --git a/ash/wm/desks/root_window_desk_switch_animator_unittest.cc b/ash/wm/desks/root_window_desk_switch_animator_unittest.cc
index 1662993..a85ae73 100644
--- a/ash/wm/desks/root_window_desk_switch_animator_unittest.cc
+++ b/ash/wm/desks/root_window_desk_switch_animator_unittest.cc
@@ -13,6 +13,7 @@
 #include "ash/wm/desks/desks_histogram_enums.h"
 #include "ash/wm/desks/root_window_desk_switch_animator_test_api.h"
 #include "base/callback_forward.h"
+#include "base/run_loop.h"
 #include "base/test/scoped_feature_list.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
@@ -446,4 +447,21 @@
                              animation_layer));
 }
 
+// Test that there is no crash if we end swiping before the desk animation
+// screenshots are finished taking. Regression test for
+// https://crbug.com/1134390.
+TEST_F(RootWindowDeskSwitchAnimatorTest,
+       EndSwipeAnimationBeforeScreenshotTaken) {
+  InitAnimator(0, 1);
+  animator()->TakeStartingDeskScreenshot();
+  animator()->EndSwipeAnimation();
+
+  // Reinitialize the animator as each animator only supports one
+  // EndSwipeAnimation during its lifetime.
+  InitAnimator(0, 1);
+  TakeStartingDeskScreenshotAndWait();
+  animator()->TakeEndingDeskScreenshot();
+  animator()->EndSwipeAnimation();
+}
+
 }  // namespace ash
diff --git a/base/BUILD.gn b/base/BUILD.gn
index d8ef814..e23d3cda 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -783,6 +783,8 @@
     "trace_event/heap_profiler_allocation_context_tracker.h",
     "trace_event/memory_allocator_dump_guid.cc",
     "trace_event/memory_allocator_dump_guid.h",
+    "trace_event/trace_id_helper.cc",
+    "trace_event/trace_id_helper.h",
     "traits_bag.h",
     "tuple.h",
     "unguessable_token.cc",
diff --git a/base/trace_event/trace_id_helper.cc b/base/trace_event/trace_id_helper.cc
new file mode 100644
index 0000000..36be40d
--- /dev/null
+++ b/base/trace_event/trace_id_helper.cc
@@ -0,0 +1,19 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/trace_id_helper.h"
+#include "base/atomic_sequence_num.h"
+#include "base/rand_util.h"
+
+namespace base {
+namespace trace_event {
+
+uint64_t GetNextGlobalTraceId() {
+  static const uint64_t kPerProcessRandomValue = base::RandUint64();
+  static base::AtomicSequenceNumber counter;
+  return kPerProcessRandomValue ^ counter.GetNext();
+}
+
+}  // namespace trace_event
+}  // namespace base
diff --git a/base/trace_event/trace_id_helper.h b/base/trace_event/trace_id_helper.h
new file mode 100644
index 0000000..e54eca86
--- /dev/null
+++ b/base/trace_event/trace_id_helper.h
@@ -0,0 +1,25 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_TRACE_ID_HELPER_H_
+#define BASE_TRACE_EVENT_TRACE_ID_HELPER_H_
+
+#include <cstdint>
+
+#include "base/base_export.h"
+
+namespace base {
+namespace trace_event {
+
+// Returns an globally-unique id which can be used as a flow id or async event
+// id. This is fast (powered by an memory-order-relaxed atomic int), so use this
+// function instead of implementing your own counter and hashing it with a
+// random value. However, consider using TRACE_ID_LOCAL(this) to avoid storing
+// additional data if possible.
+BASE_EXPORT uint64_t GetNextGlobalTraceId();
+
+}  // namespace trace_event
+}  // namespace base
+
+#endif  // BASE_TRACE_EVENT_TRACE_ID_HELPER_H_
diff --git a/build/android/gyp/proguard.py b/build/android/gyp/proguard.py
index 0fbc0b5a..565a546 100755
--- a/build/android/gyp/proguard.py
+++ b/build/android/gyp/proguard.py
@@ -217,6 +217,7 @@
 
     cmd = build_utils.JavaCmd(options.warnings_as_errors) + [
         '-Dcom.android.tools.r8.allowTestProguardOptions=1',
+        '-Dcom.android.tools.r8.verticalClassMerging=1',
     ]
     if options.disable_outlining:
       cmd += ['-Dcom.android.tools.r8.disableOutlining=1']
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index 82474ee..a961055 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -2355,24 +2355,14 @@
       swiftflags = [ "-g" ]
     }
 
-    if (use_debug_fission && !is_nacl &&
-        (!is_android || target_os == "android")) {
-      # NOTE: Some Chrome OS builds globally set |use_debug_fission| to true,
-      # but they also build some targets whose toolchains aren't
-      # compatible with it. In particular don't turn it on for Android
-      # toolchain if not building for Android OS.
-      #
-      # TODO(https://crbug.com/837032): See if we can clean this up by e.g. not
-      # setting use_debug_fission globally.
+    if (use_debug_fission) {
       cflags += [ "-gsplit-dwarf" ]
     }
     asmflags = cflags
     ldflags = []
 
-    if (use_debug_fission && is_android && target_os == "android") {
-      # NOTE: This flag is for use by the link wrapper scripts. They are also
-      # expected to remove it before sending the rest of ldflags to the actual
-      # linker. Only expect Android Chrome build on Android OS to need this.
+    # TODO(agrieve): Not sure why this is Android-specific.
+    if (use_debug_fission && use_thin_lto && is_android) {
       ldflags += [ "-gsplit-dwarf" ]
     }
 
diff --git a/build/config/compiler/compiler.gni b/build/config/compiler/compiler.gni
index d6a3a9a..12ca7df 100644
--- a/build/config/compiler/compiler.gni
+++ b/build/config/compiler/compiler.gni
@@ -273,6 +273,9 @@
   }
 }
 
+# Split dwarf works only for symbol_level == 2.
+use_debug_fission = use_debug_fission && symbol_level == 2
+
 # Non-component debug builds with symbol_level = 2 are an undesirable (very slow
 # build times) and unsupported (some test binaries will fail with > 4 GB PDBs)
 # combination. This is only checked when current_toolchain == default_toolchain
diff --git a/build/partitioned_shared_library.gni b/build/partitioned_shared_library.gni
index e6d3e938..2ea32ce 100644
--- a/build/partitioned_shared_library.gni
+++ b/build/partitioned_shared_library.gni
@@ -100,12 +100,9 @@
         ]
       }
 
-      # Restrict to Android OS because ChromeOS uses the Android toolchain with
-      # use_debug_fission set globally, which isn't reliable across all
-      # possible build configs with Android.
-      if (is_android && target_os == "android" && use_debug_fission) {
-        dwp = rebase_path("${android_tool_prefix}dwp", root_build_dir)
-        args += [ "--dwp=${dwp}" ]
+      if (use_debug_fission) {
+        _dwp = rebase_path("${android_tool_prefix}dwp", root_build_dir)
+        args += [ "--dwp=${_dwp}" ]
         outputs += [ invoker.unstripped_output + ".dwp" ]
       }
       args += [ rebase_path(sources[0], root_build_dir) ]
diff --git a/build/toolchain/android/BUILD.gn b/build/toolchain/android/BUILD.gn
index b8966e1..65be997 100644
--- a/build/toolchain/android/BUILD.gn
+++ b/build/toolchain/android/BUILD.gn
@@ -4,6 +4,7 @@
 
 import("//build/config/android/config.gni")
 import("//build/config/clang/clang.gni")
+import("//build/config/compiler/compiler.gni")
 import("//build/config/ozone.gni")
 import("//build/config/sysroot.gni")  # Imports android/config.gni.
 import("//build/toolchain/gcc_toolchain.gni")
@@ -24,8 +25,20 @@
   gcc_toolchain(target_name) {
     assert(defined(invoker.toolchain_args),
            "toolchain_args must be defined for android_clang_toolchain()")
-    toolchain_args = invoker.toolchain_args
-    toolchain_args.current_os = "android"
+
+    # Android toolchains need to declare .dwp files as outputs, so need to know
+    # the value of "use_debug_fission" when defining them.
+    # The derived value of "use_debug_fission" varies based on current_os, but
+    # toolchain definitions are evaluated under the default toolchain.
+    # Rather than computing the value under current_os="android", just disable
+    # it if target_os != "android".
+    _use_debug_fission = use_debug_fission && target_os == "android"
+
+    toolchain_args = {
+      forward_variables_from(invoker.toolchain_args, "*")
+      current_os = "android"
+      use_debug_fission = _use_debug_fission
+    }
 
     # Output linker map files for binary size analysis.
     enable_linker_map = true
@@ -43,9 +56,11 @@
     ld = cxx
     readelf = _tool_prefix + "readelf"
     nm = _tool_prefix + "nm"
-    dwp = _tool_prefix + "dwp"
     strip = rebase_path("//buildtools/third_party/eu-strip/bin/eu-strip",
                         root_build_dir)
+    if (_use_debug_fission) {
+      dwp = _tool_prefix + "dwp"
+    }
     use_unstripped_as_runtime_outputs = android_unstripped_runtime_outputs
 
     # Don't use .cr.so for loadable_modules since they are always loaded via
diff --git a/build/toolchain/gcc_link_wrapper.py b/build/toolchain/gcc_link_wrapper.py
index 6cb3f3a..43aff8ae 100755
--- a/build/toolchain/gcc_link_wrapper.py
+++ b/build/toolchain/gcc_link_wrapper.py
@@ -81,6 +81,7 @@
   if dwp_proc:
     dwp_result = dwp_proc.wait()
     if dwp_result != 0:
+      sys.stderr.write('dwp failed with error code {}\n'.format(dwp_result))
       return dwp_result
 
   return result
diff --git a/build/toolchain/gcc_toolchain.gni b/build/toolchain/gcc_toolchain.gni
index 86851928..6e3c024 100644
--- a/build/toolchain/gcc_toolchain.gni
+++ b/build/toolchain/gcc_toolchain.gni
@@ -252,9 +252,9 @@
       nm = "nm"
     }
     if (defined(invoker.dwp)) {
-      dwp = invoker.dwp
+      dwp_switch = " --dwp=\"${invoker.dwp}\""
     } else {
-      dwp = "dwp"
+      dwp_switch = ""
     }
 
     if (defined(invoker.shlib_extension)) {
@@ -417,11 +417,6 @@
       # The host might not have a POSIX shell and utilities (e.g. Windows).
       solink_wrapper =
           rebase_path("//build/toolchain/gcc_solink_wrapper.py", root_build_dir)
-      dwp_switch = ""
-      if (is_android && target_os == "android" && use_debug_fission &&
-          symbol_level == 2) {
-        dwp_switch = " --dwp=\"${dwp}\""
-      }
       command = "$python_path \"$solink_wrapper\" --readelf=\"$readelf\" --nm=\"$nm\" $strip_switch$dwp_switch --sofile=\"$unstripped_sofile\" --tocfile=\"$tocfile\"$map_switch --output=\"$sofile\" -- $link_command"
 
       if (target_cpu == "mipsel" && is_component_build && is_android) {
@@ -463,8 +458,7 @@
       # Clank build will generate DWP files when Fission is used.
       # Other builds generate DWP files outside of the gn link targets, if at
       # all.
-      if (is_android && target_os == "android" && use_debug_fission &&
-          symbol_level == 2) {
+      if (defined(invoker.dwp)) {
         outputs += [ unstripped_sofile + ".dwp" ]
         if (defined(invoker.use_unstripped_as_runtime_outputs) &&
             invoker.use_unstripped_as_runtime_outputs) {
@@ -564,14 +558,6 @@
         strip_switch = " --strip=\"${invoker.strip}\" --unstripped-file=\"$unstripped_outfile\""
       }
 
-      dwp_switch = ""
-      if (is_android && target_os == "android" && use_debug_fission &&
-          symbol_level == 2) {
-        dwp_switch = " --dwp=\"${dwp}\""
-      } else {
-        not_needed([ "dwp" ])
-      }
-
       link_wrapper =
           rebase_path("//build/toolchain/gcc_link_wrapper.py", root_build_dir)
       command = "$python_path \"$link_wrapper\" --output=\"$outfile\"$strip_switch$map_switch$dwp_switch -- $link_command"
@@ -590,8 +576,7 @@
       # Clank build will generate DWP files when Fission is used.
       # Other builds generate DWP files outside of the gn link targets, if at
       # all.
-      if (is_android && target_os == "android" && use_debug_fission &&
-          symbol_level == 2) {
+      if (defined(invoker.dwp)) {
         outputs += [ unstripped_outfile + ".dwp" ]
         if (defined(invoker.use_unstripped_as_runtime_outputs) &&
             invoker.use_unstripped_as_runtime_outputs) {
diff --git a/chrome/VERSION b/chrome/VERSION
index 028d79e..5f7dcdf 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=88
 MINOR=0
-BUILD=4308
+BUILD=4309
 PATCH=0
diff --git a/chrome/android/DEPS b/chrome/android/DEPS
index ad99435..6c074d5 100644
--- a/chrome/android/DEPS
+++ b/chrome/android/DEPS
@@ -138,9 +138,6 @@
   "TasksSurfaceCoordinator\.java": [
     "+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
   ],
-  "TabGroupUi\.java": [
-    "+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
-  ],
   "TabGroupUiCoordinator\.java": [
     "+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
   ],
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantInputActionIntegrationTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantInputActionIntegrationTest.java
index 2b0db37..d94204c 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantInputActionIntegrationTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantInputActionIntegrationTest.java
@@ -28,6 +28,7 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.DisabledTest;
 import org.chromium.chrome.browser.autofill_assistant.proto.ActionProto;
 import org.chromium.chrome.browser.autofill_assistant.proto.ChipProto;
 import org.chromium.chrome.browser.autofill_assistant.proto.ChipType;
@@ -74,6 +75,7 @@
 
     @Test
     @MediumTest
+    @DisabledTest(message = "https://crbug.com/1143805")
     public void fillFormFieldWithText() throws Exception {
         ArrayList<ActionProto> list = new ArrayList<>();
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUi.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUi.java
index b3f1b26..f9407d4 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUi.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUi.java
@@ -4,8 +4,9 @@
 
 package org.chromium.chrome.browser.tasks.tab_management;
 
+import android.app.Activity;
+
 import org.chromium.base.supplier.Supplier;
-import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.chrome.browser.toolbar.bottom.BottomControlsCoordinator;
 
 /**
@@ -19,7 +20,7 @@
      */
     boolean onBackPressed();
 
-    void initializeWithNative(ChromeActivity activity,
+    void initializeWithNative(Activity activity,
             BottomControlsCoordinator.BottomControlsVisibilityController visibilityController);
 
     /**
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java
index 6584448..aa98544 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java
@@ -6,6 +6,7 @@
 
 import static org.chromium.chrome.browser.tasks.tab_management.TabManagementModuleProvider.SYNTHETIC_TRIAL_POSTFIX;
 
+import android.app.Activity;
 import android.content.Context;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -86,7 +87,7 @@
      * Handle any initialization that occurs once native has been loaded.
      */
     @Override
-    public void initializeWithNative(ChromeActivity activity,
+    public void initializeWithNative(Activity activity,
             BottomControlsCoordinator.BottomControlsVisibilityController visibilityController) {
         if (UmaSessionStats.isMetricsServiceAvailable()) {
             UmaSessionStats.registerSyntheticFieldTrial(
@@ -94,9 +95,9 @@
                     "Downloaded_Enabled");
         }
         assert activity instanceof ChromeTabbedActivity;
-        mActivity = activity;
-        TabModelSelector tabModelSelector = activity.getTabModelSelector();
-        TabContentManager tabContentManager = activity.getTabContentManager();
+        mActivity = (ChromeActivity) activity;
+        TabModelSelector tabModelSelector = mActivity.getTabModelSelector();
+        TabContentManager tabContentManager = mActivity.getTabContentManager();
 
         boolean actionOnAllRelatedTabs = TabUiFeatureUtilities.isConditionalTabStripEnabled();
         mTabStripCoordinator = new TabListCoordinator(TabListCoordinator.TabListMode.STRIP,
@@ -115,24 +116,25 @@
         TabGridDialogMediator.DialogController dialogController = null;
         if (TabUiFeatureUtilities.isTabGroupsAndroidEnabled() && mScrimCoordinator != null) {
             mTabGridDialogCoordinator = new TabGridDialogCoordinator(mContext, tabModelSelector,
-                    tabContentManager, activity, activity.findViewById(R.id.coordinator), null,
-                    null, null, mActivity.getShareDelegateSupplier(), mScrimCoordinator);
+                    tabContentManager, /* tabCreatorManager= */ mActivity,
+                    activity.findViewById(R.id.coordinator), null, null, null,
+                    mActivity.getShareDelegateSupplier(), mScrimCoordinator);
             mTabGridDialogCoordinator.initWithNative(mContext, tabModelSelector, tabContentManager,
                     mTabStripCoordinator.getTabGroupTitleEditor());
             dialogController = mTabGridDialogCoordinator.getDialogController();
         }
 
         mMediator = new TabGroupUiMediator(activity, visibilityController, this, mModel,
-                tabModelSelector, activity,
-                ((ChromeTabbedActivity) activity).getOverviewModeBehaviorSupplier(),
-                mThemeColorProvider, dialogController, activity.getLifecycleDispatcher(), activity,
+                tabModelSelector, /* tabCreatorManager= */ mActivity,
+                mActivity.getOverviewModeBehaviorSupplier(), mThemeColorProvider, dialogController,
+                mActivity.getLifecycleDispatcher(), /* snackbarManageable= */ mActivity,
                 mOmniboxFocusStateSupplier);
 
         TabGroupUtils.startObservingForCreationIPH();
 
         if (TabUiFeatureUtilities.isConditionalTabStripEnabled()) return;
 
-        mActivityLifecycleDispatcher = activity.getLifecycleDispatcher();
+        mActivityLifecycleDispatcher = mActivity.getLifecycleDispatcher();
         mActivityLifecycleDispatcher.register(this);
 
         // TODO(meiliang): Potential leak if the observer is added after restoreCompleted. Fix it.
diff --git a/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/network_fetch/FeedNewTabPageCardInstrumentationTest.java b/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/network_fetch/FeedNewTabPageCardInstrumentationTest.java
index e874430..7c3c87a 100644
--- a/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/network_fetch/FeedNewTabPageCardInstrumentationTest.java
+++ b/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/network_fetch/FeedNewTabPageCardInstrumentationTest.java
@@ -15,6 +15,7 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
@@ -56,6 +57,7 @@
     @Feature({"FeedNewTabPage", "WPRRecordReplayTest", "RenderTest"})
     @WPRArchiveDirectory("chrome/android/feed/core/javatests/src/org/chromium/chrome/"
             + "browser/feed/wpr_tests")
+    @DisabledTest(message = "https://crbug.com/1143781")
     public void
     launchNTP_withMultipleFeedCardsRendered() throws IOException, InterruptedException {
         mActivityTestRule.loadUrlInNewTab(UrlConstants.NTP_URL);
diff --git a/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/v1/FeedNewTabPageTest.java b/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/v1/FeedNewTabPageTest.java
index 84081363..131d50dd 100644
--- a/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/v1/FeedNewTabPageTest.java
+++ b/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/v1/FeedNewTabPageTest.java
@@ -307,6 +307,7 @@
     @Features.DisableFeatures(ChromeFeatureList.INTEREST_FEED_V2)
     @Feature({"NewTabPage", "FeedNewTabPage"})
     @ParameterAnnotations.UseMethodParameter(SigninPromoParams.class)
+    @DisabledTest(message = "https://crbug.com/1143974")
     public void testArticleSectionHeaderWithMenu(boolean disableSigninPromoCard) throws Exception {
         openNewTabPage();
         // Scroll to the article section header in case it is not visible.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/DEPS b/chrome/android/java/src/org/chromium/chrome/browser/DEPS
index e8fe000..011dfee1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/DEPS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/DEPS
@@ -356,12 +356,6 @@
   "ToolbarButtonInProductHelpController\.java": [
     "+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
   ],
-  "ToolbarManager\.java": [
-    "+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
-  ],
-  "BottomControlsCoordinator\.java": [
-    "+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
-  ],
   "CustomTabToolbar\.java": [
     "+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
   ],
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java
index b0f06d70..c75555f1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java
@@ -151,10 +151,6 @@
         mLocationBarLayout.updateLoadingState(updateUrl);
     }
 
-    public LocationBarDataProvider getLocationBarDataProvider() {
-        return mLocationBarLayout.getLocationBarDataProvider();
-    }
-
     @Override
     public void showUrlBarCursorWithoutFocusAnimations() {
         mLocationBarLayout.showUrlBarCursorWithoutFocusAnimations();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarPhone.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarPhone.java
index 5bc54a3..f687a79 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarPhone.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarPhone.java
@@ -65,7 +65,7 @@
 
         // The search engine icon will be the first visible focused view when it's showing.
         shouldShowSearchEngineLogo = SearchEngineLogoUtils.shouldShowSearchEngineLogo(
-                getLocationBarDataProvider().isIncognito());
+                mLocationBarDataProvider.isIncognito());
 
         // This branch will be hit if the search engine logo experiment is enabled.
         if (SearchEngineLogoUtils.isSearchEngineLogoEnabled()) {
@@ -180,7 +180,7 @@
     @Override
     public void updateVisualsForState() {
         super.updateVisualsForState();
-        boolean isIncognito = getLocationBarDataProvider().isIncognito();
+        boolean isIncognito = mLocationBarDataProvider.isIncognito();
         setShowIconsWhenUrlFocused(SearchEngineLogoUtils.shouldShowSearchEngineLogo(isIncognito));
         updateStatusVisibility();
     }
@@ -264,7 +264,7 @@
 
         // No offset is required if the experiment is disabled.
         if (!SearchEngineLogoUtils.shouldShowSearchEngineLogo(
-                    getLocationBarDataProvider().isIncognito())) {
+                    mLocationBarDataProvider.isIncognito())) {
             return 0;
         }
 
@@ -299,7 +299,7 @@
 
         // No offset is required if the experiment is disabled.
         if (!SearchEngineLogoUtils.shouldShowSearchEngineLogo(
-                    getLocationBarDataProvider().isIncognito())) {
+                    mLocationBarDataProvider.isIncognito())) {
             return 0;
         }
 
@@ -342,7 +342,7 @@
 
     /** Update the status visibility according to the current state held in LocationBar. */
     private void updateStatusVisibility() {
-        boolean incognito = getLocationBarDataProvider().isIncognito();
+        boolean incognito = mLocationBarDataProvider.isIncognito();
         if (!SearchEngineLogoUtils.shouldShowSearchEngineLogo(incognito)) {
             return;
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarTablet.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarTablet.java
index 687c364..bb797db6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarTablet.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarTablet.java
@@ -150,7 +150,7 @@
             mUrlFocusChangeAnimator = null;
         }
 
-        if (getLocationBarDataProvider().getNewTabPageForCurrentTab() == null) {
+        if (mLocationBarDataProvider.getNewTabPageForCurrentTab() == null) {
             finishUrlFocusChange(hasFocus);
             return;
         }
@@ -187,7 +187,7 @@
     public void setUrlFocusChangeFraction(float fraction) {
         super.setUrlFocusChangeFraction(fraction);
 
-        NewTabPage ntp = getLocationBarDataProvider().getNewTabPageForCurrentTab();
+        NewTabPage ntp = mLocationBarDataProvider.getNewTabPageForCurrentTab();
         if (ntp != null) ntp.setUrlFocusChangeAnimationPercent(fraction);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
index 4942ed4..1e5e7c5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
@@ -19,6 +19,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AppCompatActivity;
 
 import com.google.android.material.appbar.AppBarLayout;
 
@@ -33,11 +34,11 @@
 import org.chromium.chrome.browser.ActivityTabProvider;
 import org.chromium.chrome.browser.TabLoadStatus;
 import org.chromium.chrome.browser.WindowDelegate;
-import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.chrome.browser.bookmarks.BookmarkBridge;
 import org.chromium.chrome.browser.browser_controls.BrowserControlsSizer;
 import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
 import org.chromium.chrome.browser.browser_controls.BrowserStateBrowserControlsVisibilityDelegate;
+import org.chromium.chrome.browser.compositor.CompositorViewHolder;
 import org.chromium.chrome.browser.compositor.Invalidator;
 import org.chromium.chrome.browser.compositor.layouts.EmptyOverviewModeObserver;
 import org.chromium.chrome.browser.compositor.layouts.Layout;
@@ -56,6 +57,7 @@
 import org.chromium.chrome.browser.homepage.HomepagePolicyManager;
 import org.chromium.chrome.browser.identity_disc.IdentityDiscController;
 import org.chromium.chrome.browser.intent.IntentMetadata;
+import org.chromium.chrome.browser.night_mode.NightModeStateProvider;
 import org.chromium.chrome.browser.ntp.FakeboxDelegate;
 import org.chromium.chrome.browser.ntp.IncognitoNewTabPage;
 import org.chromium.chrome.browser.ntp.NewTabPage;
@@ -99,8 +101,10 @@
 import org.chromium.chrome.browser.toolbar.top.ViewShiftingActionBarDelegate;
 import org.chromium.chrome.browser.ui.TabObscuringHandler;
 import org.chromium.chrome.browser.ui.appmenu.AppMenuCoordinator;
+import org.chromium.chrome.browser.ui.appmenu.AppMenuDelegate;
 import org.chromium.chrome.browser.ui.appmenu.MenuButtonDelegate;
 import org.chromium.chrome.browser.ui.native_page.NativePage;
+import org.chromium.chrome.browser.ui.system.StatusBarColorController;
 import org.chromium.chrome.browser.user_education.UserEducationHelper;
 import org.chromium.chrome.browser.util.ChromeAccessibilityUtil;
 import org.chromium.chrome.features.start_surface.StartSurface;
@@ -118,6 +122,9 @@
 import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.net.NetError;
+import org.chromium.ui.base.DeviceFormFactor;
+import org.chromium.ui.base.WindowAndroid;
+import org.chromium.ui.modaldialog.ModalDialogManager;
 import org.chromium.ui.util.TokenHolder;
 
 import java.util.List;
@@ -176,7 +183,10 @@
     private ActionModeController mActionModeController;
     private final Callback<Boolean> mUrlFocusChangedCallback;
     private final Handler mHandler = new Handler();
-    private final ChromeActivity mActivity;
+    private final AppCompatActivity mActivity;
+    private final WindowAndroid mWindowAndroid;
+    private final AppMenuDelegate mAppMenuDelegate;
+    private final CompositorViewHolder mCompositorViewHolder;
     private final BrowserControlsSizer mBrowserControlsSizer;
     private final FullscreenManager mFullscreenManager;
     private LocationBarFocusScrimHandler mLocationBarFocusHandler;
@@ -185,6 +195,7 @@
     private final ToolbarTabControllerImpl mToolbarTabController;
     private MenuButtonCoordinator mMenuButtonCoordinator;
     private HomepageManager.HomepageStateListener mHomepageStateListener;
+    private StatusBarColorController mStatusBarColorController;
 
     private HomeButtonCoordinator mHomeButtonCoordinator;
     private ToggleTabStackButtonCoordinator mToggleTabStackButtonCoordinator;
@@ -223,10 +234,12 @@
 
     /**
      * Creates a ToolbarManager object.
+     *
+     * @param activity The Android activity.
      * @param controlsSizer The {@link BrowserControlsSizer} for the activity.
      * @param fullscreenManager The {@link FullscreenManager} for the activity.
      * @param controlContainer The container of the toolbar.
-     * @param invalidator Handler for synchronizing invalidations across UI elements.
+     * @param compositorViewHolder Class that holds a {@link CompositorView}.
      * @param urlFocusChangedCallback The callback to be notified when the URL focus changes.
      * @param themeColorProvider The ThemeColorProvider object.
      * @param tabObscuringHandler Delegate object handling obscuring views.
@@ -250,10 +263,17 @@
      * @param intentMetadataOneshotSupplier Supplier with info about the launching intent.
      * @param promoShownOneshotSupplier Supplier for whether a promo was shown on startup. Will only
      *                                  be fulfilled when feature TOOLBAR_IPH_ANDROID is enabled.
+     * @param windowAndroid The {@link WindowAndroid} associated with the ToolbarManager.
+     * @param isInOverviewModeSupplier Supplies whether the app is currently in overview mode.
+     * @param isCustomTab Whether the toolbar is for a custom tab.
+     * @param modalDialogManagerSupplier Supplies the {@link ModalDialogManager}.
+     * @param nightModeStateProvider Provides the state of night mode.
+     * @param statusBarColorController The {@link StatusBarColorController} for the app.
+     * @param appMenuDelegate Allows interacting with the app menu.
      */
-    public ToolbarManager(ChromeActivity activity, BrowserControlsSizer controlsSizer,
+    public ToolbarManager(AppCompatActivity activity, BrowserControlsSizer controlsSizer,
             FullscreenManager fullscreenManager, ToolbarControlContainer controlContainer,
-            Invalidator invalidator, Callback<Boolean> urlFocusChangedCallback,
+            CompositorViewHolder compositorViewHolder, Callback<Boolean> urlFocusChangedCallback,
             ThemeColorProvider themeColorProvider, TabObscuringHandler tabObscuringHandler,
             ObservableSupplier<ShareDelegate> shareDelegateSupplier,
             IdentityDiscController identityDiscController,
@@ -269,9 +289,15 @@
             OneshotSupplier<StartSurface> startSurfaceSupplier,
             ObservableSupplier<Boolean> omniboxFocusStateSupplier,
             OneshotSupplier<IntentMetadata> intentMetadataOneshotSupplier,
-            OneshotSupplier<Boolean> promoShownOneshotSupplier) {
+            OneshotSupplier<Boolean> promoShownOneshotSupplier, WindowAndroid windowAndroid,
+            Supplier<Boolean> isInOverviewModeSupplier, boolean isCustomTab,
+            Supplier<ModalDialogManager> modalDialogManagerSupplier,
+            NightModeStateProvider nightModeStateProvider,
+            StatusBarColorController statusBarColorController, AppMenuDelegate appMenuDelegate) {
         TraceEvent.begin("ToolbarManager.ToolbarManager");
         mActivity = activity;
+        mWindowAndroid = windowAndroid;
+        mCompositorViewHolder = compositorViewHolder;
         mBrowserControlsSizer = controlsSizer;
         mFullscreenManager = fullscreenManager;
         mActionBarDelegate = new ViewShiftingActionBarDelegate(activity.getSupportActionBar(),
@@ -283,13 +309,14 @@
         mOmniboxFocusStateSupplier = omniboxFocusStateSupplier;
         mIntentMetadataOneshotSupplier = intentMetadataOneshotSupplier;
         mPromoShownOneshotSupplier = promoShownOneshotSupplier;
+        mAppMenuDelegate = appMenuDelegate;
+        mStatusBarColorController = statusBarColorController;
+        mUrlFocusChangedCallback = urlFocusChangedCallback;
 
         mLocationBarModel = new LocationBarModel(activity);
         mControlContainer = controlContainer;
         assert mControlContainer != null;
 
-        mUrlFocusChangedCallback = urlFocusChangedCallback;
-
         mBookmarkBridgeSupplier = bookmarkBridgeSupplier;
         // We need to capture a reference to setBookmarkBridge/setCurrentProfile in order to remove
         // them later; there is no guarantee in the JLS that referencing the same method later will
@@ -321,10 +348,10 @@
         mTabThemeColorProvider = themeColorProvider;
         mTabThemeColorProvider.addThemeColorObserver(this);
 
-        mAppThemeColorProvider = new AppThemeColorProvider(mActivity);
+        mAppThemeColorProvider = new AppThemeColorProvider(/* context= */ mActivity);
         // Observe tint changes to update sub-components that rely on the tint (crbug.com/1077684).
         mAppThemeColorProvider.addTintObserver(this);
-        mCustomTabThemeColorProvider = new SettableThemeColorProvider(mActivity);
+        mCustomTabThemeColorProvider = new SettableThemeColorProvider(/* context= */ mActivity);
 
         mActivityTabProvider = tabProvider;
         mToolbarTabController = new ToolbarTabControllerImpl(mLocationBarModel::getTab,
@@ -338,29 +365,30 @@
         assert controlsVisibilityDelegate != null;
         mControlsVisibilityDelegate = controlsVisibilityDelegate;
         ThemeColorProvider browsingModeThemeColorProvider =
-                mActivity.isTablet() ? mAppThemeColorProvider : mTabThemeColorProvider;
+                DeviceFormFactor.isNonMultiDisplayContextOnTablet(mActivity)
+                ? mAppThemeColorProvider
+                : mTabThemeColorProvider;
         ThemeColorProvider overviewModeThemeColorProvider = mAppThemeColorProvider;
         ToolbarLayout toolbarLayout = mActivity.findViewById(R.id.toolbar);
 
+        Runnable requestFocusRunnable = compositorViewHolder::requestFocus;
         mMenuButtonCoordinator = new MenuButtonCoordinator(appMenuCoordinatorSupplier,
                 mControlsVisibilityDelegate, mActivity,
                 (focus, type)
                         -> setUrlBarFocus(focus, type),
-                mActivity.getCompositorViewHolder()::requestFocus, shouldShowUpdateBadge,
-                mActivity::isInOverviewMode,
-                mActivity.isCustomTab() ? mCustomTabThemeColorProvider
-                                        : browsingModeThemeColorProvider,
+                requestFocusRunnable, shouldShowUpdateBadge, isInOverviewModeSupplier,
+                isCustomTab ? mCustomTabThemeColorProvider : browsingModeThemeColorProvider,
                 R.id.menu_button_wrapper);
         MenuButtonCoordinator startSurfaceMenuButtonCoordinator = new MenuButtonCoordinator(
                 appMenuCoordinatorSupplier, mControlsVisibilityDelegate, mActivity,
                 (focus, type)
                         -> setUrlBarFocus(focus, type),
-                mActivity.getCompositorViewHolder()::requestFocus, shouldShowUpdateBadge,
-                mActivity::isInOverviewMode, overviewModeThemeColorProvider, R.id.none);
+                requestFocusRunnable, shouldShowUpdateBadge, isInOverviewModeSupplier,
+                overviewModeThemeColorProvider, R.id.none);
 
         mToolbar = createTopToolbarCoordinator(controlContainer, toolbarLayout, buttonDataProviders,
-                browsingModeThemeColorProvider, startSurfaceMenuButtonCoordinator, invalidator,
-                identityDiscController);
+                browsingModeThemeColorProvider, startSurfaceMenuButtonCoordinator,
+                mCompositorViewHolder.getInvalidator(), identityDiscController);
         mActionModeController =
                 new ActionModeController(mActivity, mActionBarDelegate, toolbarActionModeCallback);
 
@@ -375,9 +403,8 @@
             LocationBarCoordinator locationBarCoordinator = new LocationBarCoordinator(
                     mActivity.findViewById(R.id.location_bar), profileSupplier, mLocationBarModel,
                     mActionModeController.getActionModeCallback(),
-                    new WindowDelegate(mActivity.getWindow()), mActivity.getWindowAndroid(),
-                    mActivityTabProvider, mActivity::getModalDialogManager,
-                    mActivity.getShareDelegateSupplier(), mIncognitoStateProvider);
+                    new WindowDelegate(mActivity.getWindow()), mWindowAndroid, mActivityTabProvider,
+                    modalDialogManagerSupplier, mShareDelegateSupplier, mIncognitoStateProvider);
             toolbarLayout.setLocationBarCoordinator(locationBarCoordinator);
             mLocationBar = locationBarCoordinator;
         }
@@ -387,9 +414,9 @@
         }
         Runnable clickDelegate =
                 () -> setUrlBarFocus(false, OmniboxFocusReason.UNFOCUS);
-        View scrimTarget = mActivity.getCompositorViewHolder();
+        View scrimTarget = mCompositorViewHolder;
         mLocationBarFocusHandler = new LocationBarFocusScrimHandler(scrimCoordinator,
-                tabObscuringHandler, activity, activity.getNightModeStateProvider(),
+                tabObscuringHandler, /* context= */ activity, nightModeStateProvider,
                 mLocationBarModel, clickDelegate, scrimTarget);
         if (mLocationBar.getFakeboxDelegate() != null) {
             mLocationBar.getFakeboxDelegate().addUrlFocusChangeListener(mLocationBarFocusHandler);
@@ -398,7 +425,7 @@
         mProgressBarCoordinator =
                 new LoadProgressCoordinator(mActivityTabProvider, mToolbar.getProgressBar());
 
-        mToolbar.addUrlExpansionObserver(activity.getStatusBarColorController());
+        mToolbar.addUrlExpansionObserver(statusBarColorController);
 
         mActivityTabTabObserver = new ActivityTabProvider.ActivityTabTabObserver(
                 mActivityTabProvider) {
@@ -731,7 +758,7 @@
     public void onActionBarVisibilityChanged(boolean visible) {
         ActionBar actionBar = mActionBarDelegate.getSupportActionBar();
         if (!visible && actionBar != null) actionBar.hide();
-        if (mActivity.isTablet()) {
+        if (DeviceFormFactor.isNonMultiDisplayContextOnTablet(mActivity)) {
             if (visible) {
                 mActionModeController.startShowAnimation();
             } else {
@@ -798,7 +825,7 @@
 
         OnLongClickListener tabSwitcherLongClickHandler =
                 TabSwitcherActionMenuCoordinator.createOnLongClickListener(
-                        (id) -> mActivity.onOptionsItemSelected(id, null));
+                        (id) -> mAppMenuDelegate.onOptionsItemSelected(id, null));
 
         mToolbar.initializeWithNative(layoutManager::requestUpdate, tabSwitcherClickHandler,
                 tabSwitcherLongClickHandler, newTabClickHandler, bookmarkClickHandler,
@@ -835,11 +862,10 @@
                         .closeAllTabs();
             };
             mBottomControlsCoordinator.initializeWithNative(mActivity,
-                    mActivity.getCompositorViewHolder().getResourceManager(),
-                    mActivity.getCompositorViewHolder().getLayoutManager(), tabSwitcherClickHandler,
-                    newTabClickHandler, mActivity.getWindowAndroid(), mTabCountProvider,
-                    mIncognitoStateProvider, mActivity.findViewById(R.id.control_container),
-                    closeAllTabsAction);
+                    mCompositorViewHolder.getResourceManager(),
+                    mCompositorViewHolder.getLayoutManager(), tabSwitcherClickHandler,
+                    newTabClickHandler, mWindowAndroid, mTabCountProvider, mIncognitoStateProvider,
+                    mActivity.findViewById(R.id.control_container), closeAllTabsAction);
         }
 
         TemplateUrlServiceFactory.get().runWhenLoaded(this::registerTemplateUrlObserver);
@@ -975,7 +1001,7 @@
             mBottomControlsCoordinator = null;
         }
 
-        mToolbar.removeUrlExpansionObserver(mActivity.getStatusBarColorController());
+        mToolbar.removeUrlExpansionObserver(mStatusBarColorController);
         mToolbar.destroy();
 
         if (mTabObserver != null) {
@@ -1333,7 +1359,8 @@
 
         // This method is called prior to action mode destroy callback for incognito <-> normal
         // tab switch. Makes sure the action mode toolbar is hidden before selecting the new tab.
-        if (previousTab != null && wasIncognito != isIncognito && mActivity.isTablet()) {
+        if (previousTab != null && wasIncognito != isIncognito
+                && DeviceFormFactor.isNonMultiDisplayContextOnTablet(mActivity)) {
             mActionModeController.startHideAnimation();
         }
         if (previousTab != tab || wasIncognito != isIncognito) {
@@ -1410,7 +1437,7 @@
             return false;
         }
 
-        return mActivity.isTablet()
+        return DeviceFormFactor.isNonMultiDisplayContextOnTablet(mActivity)
                 && mActivity.getResources().getConfiguration().keyboard
                 == Configuration.KEYBOARD_QWERTY;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsCoordinator.java
index 16658d2..e4aa2f1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsCoordinator.java
@@ -5,6 +5,7 @@
 package org.chromium.chrome.browser.toolbar.bottom;
 
 import android.annotation.SuppressLint;
+import android.app.Activity;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
@@ -18,7 +19,6 @@
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ActivityTabProvider;
-import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.chrome.browser.browser_controls.BrowserControlsSizer;
 import org.chromium.chrome.browser.compositor.layouts.LayoutManager;
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
@@ -125,7 +125,7 @@
      * dependencies.
      * <p>
      * Calling this must occur after the native library have completely loaded.
-     * @param chromeActivity ChromeActivity instance to use.
+     * @param activity Activity instance to use.
      * @param resourceManager A {@link ResourceManager} for loading textures into the compositor.
      * @param layoutManager A {@link LayoutManager} to attach overlays to.
      * @param tabSwitcherListener An {@link OnClickListener} that is triggered when the
@@ -139,7 +139,7 @@
      * @param topToolbarRoot The root {@link ViewGroup} of the top toolbar.
      * @param closeAllTabsAction The runnable that closes all tabs in the current tab model.
      */
-    public void initializeWithNative(ChromeActivity chromeActivity, ResourceManager resourceManager,
+    public void initializeWithNative(Activity activity, ResourceManager resourceManager,
             LayoutManager layoutManager, OnClickListener tabSwitcherListener,
             OnClickListener newTabClickListener, WindowAndroid windowAndroid,
             TabCountProvider tabCountProvider, IncognitoStateProvider incognitoStateProvider,
@@ -149,7 +149,7 @@
         mMediator.setWindowAndroid(windowAndroid);
 
         if (mTabGroupUi != null) {
-            mTabGroupUi.initializeWithNative(chromeActivity, mMediator::setBottomControlsVisible);
+            mTabGroupUi.initializeWithNative(activity, mMediator::setBottomControlsVisible);
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
index 550fbfa..c1ecbd7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
@@ -583,14 +583,18 @@
             mButtonDataProviders = Arrays.asList(mIdentityDiscController, shareButtonController);
             mToolbarManager = new ToolbarManager(mActivity, mActivity.getBrowserControlsManager(),
                     mActivity.getFullscreenManager(), toolbarContainer,
-                    mActivity.getCompositorViewHolder().getInvalidator(), urlFocusChangedCallback,
+                    mActivity.getCompositorViewHolder(), urlFocusChangedCallback,
                     mTabThemeColorProvider, mTabObscuringHandler, mShareDelegateSupplier,
                     mIdentityDiscController, mButtonDataProviders, mActivityTabProvider,
                     mScrimCoordinator, mActionModeControllerCallback, mFindToolbarManager,
                     mProfileSupplier, mBookmarkBridgeSupplier, mCanAnimateBrowserControls,
                     mOverviewModeBehaviorSupplier, mAppMenuSupplier, shouldShowMenuUpdateBadge(),
                     mTabModelSelectorSupplier, mStartSurfaceSupplier, mOmniboxFocusStateSupplier,
-                    mIntentMetadataOneshotSupplier, mPromoShownOneshotSupplier);
+                    mIntentMetadataOneshotSupplier, mPromoShownOneshotSupplier,
+                    mActivity.getWindowAndroid(), mActivity::isInOverviewMode,
+                    mActivity.isCustomTab(), mActivity.getModalDialogManagerSupplier(),
+                    mActivity.getNightModeStateProvider(), mActivity.getStatusBarColorController(),
+                    /* appMenuDelegate= */ mActivity);
             if (!mActivity.supportsAppMenu()) {
                 mToolbarManager.getToolbar().disableMenuButton();
             }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/usage_stats/UsageStatsService.java b/chrome/android/java/src/org/chromium/chrome/browser/usage_stats/UsageStatsService.java
index 7439e1a..2ce387b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/usage_stats/UsageStatsService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/usage_stats/UsageStatsService.java
@@ -5,16 +5,15 @@
 package org.chromium.chrome.browser.usage_stats;
 
 import android.app.Activity;
+import android.os.Build;
 
 import androidx.annotation.VisibleForTesting;
 
-import org.chromium.base.BuildInfo;
 import org.chromium.base.CollectionUtil;
 import org.chromium.base.Log;
 import org.chromium.base.Promise;
 import org.chromium.base.ThreadUtils;
 import org.chromium.chrome.browser.AppHooks;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.preferences.Pref;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
@@ -49,7 +48,7 @@
 
     /** Returns if the UsageStatsService is enabled on this device */
     public static boolean isEnabled() {
-        return BuildInfo.isAtLeastQ() && ChromeFeatureList.isEnabled(ChromeFeatureList.USAGE_STATS);
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
     }
 
     /** Get the global instance of UsageStatsService */
@@ -101,17 +100,7 @@
     /** @return Whether the user has authorized DW to access usage stats data. */
     boolean getOptInState() {
         ThreadUtils.assertOnUiThread();
-        boolean enabledByPref = UserPrefs.get(mProfile).getBoolean(Pref.USAGE_STATS_ENABLED);
-        boolean enabledByFeature = ChromeFeatureList.isEnabled(ChromeFeatureList.USAGE_STATS);
-        // If the user has previously opted in, but the feature has been turned off, we need to
-        // treat it as if they opted out; otherwise they'll have no UI affordance for clearing
-        // whatever data Digital Wellbeing has stored.
-        if (enabledByPref && !enabledByFeature) {
-            onAllHistoryDeleted();
-            setOptInState(false);
-        }
-
-        return enabledByPref && enabledByFeature;
+        return UserPrefs.get(mProfile).getBoolean(Pref.USAGE_STATS_ENABLED);
     }
 
     /** Sets the user's opt in state. */
diff --git a/chrome/app/chrome_main_delegate.cc b/chrome/app/chrome_main_delegate.cc
index 7ae85069..3d00d26 100644
--- a/chrome/app/chrome_main_delegate.cc
+++ b/chrome/app/chrome_main_delegate.cc
@@ -65,6 +65,7 @@
 #include "content/public/common/service_names.mojom.h"
 #include "content/public/common/url_constants.h"
 #include "extensions/common/constants.h"
+#include "net/http/http_cache.h"
 #include "net/url_request/url_request.h"
 #include "pdf/buildflags.h"
 #include "ppapi/buildflags/buildflags.h"
@@ -587,6 +588,11 @@
           switches::kProcessType);
   bool is_browser_process = process_type.empty();
 
+  // Enable Split cache by default here and not in content/ so as to not
+  // impact non-Chrome embedders like WebView, Cronet etc. This only enables
+  // it if not already overridden by command line, field trial etc.
+  net::HttpCache::SplitCacheFeatureEnableByDefault();
+
 #if defined(OS_ANDROID)
   // For child processes, this requires allowlisting of the sched_setaffinity()
   // syscall in the sandbox (baseline_policy_android.cc). When this call is
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index a51a294..440dffc 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -3586,18 +3586,6 @@
      flag_descriptions::kImeEmojiSuggestAdditionName,
      flag_descriptions::kImeEmojiSuggestAdditionDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(chromeos::features::kEmojiSuggestAddition)},
-    {"enable-cros-ime-input-logic-fst",
-     flag_descriptions::kImeInputLogicFstName,
-     flag_descriptions::kImeInputLogicFstDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(chromeos::features::kImeInputLogicFst)},
-    {"enable-cros-ime-input-logic-hmm",
-     flag_descriptions::kImeInputLogicHmmName,
-     flag_descriptions::kImeInputLogicHmmDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(chromeos::features::kImeInputLogicHmm)},
-    {"enable-cros-ime-input-logic-mozc",
-     flag_descriptions::kImeInputLogicMozcName,
-     flag_descriptions::kImeInputLogicMozcDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(chromeos::features::kImeInputLogicMozc)},
     {"enable-cros-ime-mozc-proto", flag_descriptions::kImeMozcProtoName,
      flag_descriptions::kImeMozcProtoDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(chromeos::features::kImeMozcProto)},
@@ -5721,9 +5709,6 @@
      kOsAndroid, MULTI_VALUE_TYPE(kNotificationSchedulerChoices)},
 
 #if defined(OS_ANDROID)
-    {"usage-stats", flag_descriptions::kUsageStatsName,
-     flag_descriptions::kUsageStatsDescription, kOsAndroid,
-     FEATURE_VALUE_TYPE(chrome::android::kUsageStatsFeature)},
 
     {"use-chime-android-sdk", flag_descriptions::kUseChimeAndroidSdkName,
      flag_descriptions::kUseChimeAndroidSdkDescription, kOsAndroid,
diff --git a/chrome/browser/android/tab_web_contents_delegate_android.cc b/chrome/browser/android/tab_web_contents_delegate_android.cc
index 96c15e90..616d1531 100644
--- a/chrome/browser/android/tab_web_contents_delegate_android.cc
+++ b/chrome/browser/android/tab_web_contents_delegate_android.cc
@@ -536,8 +536,8 @@
     content::RenderFrameHost* render_frame_host) {
   auto* client =
       paint_preview::PaintPreviewClient::FromWebContents(web_contents);
-  DCHECK(client);
-  client->CaptureSubframePaintPreview(guid, rect, render_frame_host);
+  if (client)
+    client->CaptureSubframePaintPreview(guid, rect, render_frame_host);
 }
 #endif
 
diff --git a/chrome/browser/android/vr/arcore_device/fake_arcore.cc b/chrome/browser/android/vr/arcore_device/fake_arcore.cc
index 1b2e10c..ce32c1f 100644
--- a/chrome/browser/android/vr/arcore_device/fake_arcore.cc
+++ b/chrome/browser/android/vr/arcore_device/fake_arcore.cc
@@ -23,8 +23,8 @@
 
 bool FakeArCore::Initialize(
     base::android::ScopedJavaLocalRef<jobject> application_context,
-    const std::unordered_set<device::mojom::XRSessionFeature>&
-        enabled_features) {
+    const std::unordered_set<device::mojom::XRSessionFeature>& enabled_features,
+    const std::vector<device::mojom::XRTrackedImagePtr>& tracked_images) {
   DCHECK(IsOnGlThread());
   return true;
 }
@@ -341,6 +341,11 @@
   DCHECK_EQ(1u, count);
 }
 
+mojom::XRTrackedImagesDataPtr FakeArCore::GetTrackedImages() {
+  std::vector<mojom::XRTrackedImageDataPtr> images_data;
+  return mojom::XRTrackedImagesData::New(std::move(images_data), base::nullopt);
+}
+
 void FakeArCore::Pause() {
   DCHECK(IsOnGlThread());
 }
diff --git a/chrome/browser/android/vr/arcore_device/fake_arcore.h b/chrome/browser/android/vr/arcore_device/fake_arcore.h
index 4235342..c84ba18 100644
--- a/chrome/browser/android/vr/arcore_device/fake_arcore.h
+++ b/chrome/browser/android/vr/arcore_device/fake_arcore.h
@@ -25,7 +25,9 @@
   bool Initialize(
       base::android::ScopedJavaLocalRef<jobject> application_context,
       const std::unordered_set<device::mojom::XRSessionFeature>&
-          enabled_features) override;
+          enabled_features,
+      const std::vector<device::mojom::XRTrackedImagePtr>& tracked_images)
+      override;
   MinMaxRange GetTargetFramerateRange() override;
   void SetCameraTexture(uint32_t texture) override;
   void SetDisplayGeometry(const gfx::Size& frame_size,
@@ -80,6 +82,8 @@
 
   void DetachAnchor(uint64_t anchor_id) override;
 
+  mojom::XRTrackedImagesDataPtr GetTrackedImages() override;
+
   void SetCameraAspect(float aspect) { camera_aspect_ = aspect; }
 
  protected:
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 92c19ce..13155b5 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -1425,6 +1425,8 @@
     "input_method/assistive_window_controller_delegate.h",
     "input_method/assistive_window_properties.cc",
     "input_method/assistive_window_properties.h",
+    "input_method/autocorrect_manager.cc",
+    "input_method/autocorrect_manager.h",
     "input_method/candidate_window_controller.cc",
     "input_method/candidate_window_controller.h",
     "input_method/candidate_window_controller_impl.cc",
@@ -2981,6 +2983,8 @@
       "//chromeos/resources:telemetry_extension_resources",
     ]
     sources += [
+      "web_applications/chrome_file_manager_ui_delegate.cc",
+      "web_applications/chrome_file_manager_ui_delegate.h",
       "web_applications/file_manager_web_app_info.cc",
       "web_applications/file_manager_web_app_info.h",
       "web_applications/sample_system_web_app_info.cc",
diff --git a/chrome/browser/chromeos/extensions/input_method_api.cc b/chrome/browser/chromeos/extensions/input_method_api.cc
index 43a1efc0..1297123 100644
--- a/chrome/browser/chromeos/extensions/input_method_api.cc
+++ b/chrome/browser/chromeos/extensions/input_method_api.cc
@@ -19,6 +19,8 @@
 #include "chrome/browser/chromeos/extensions/dictionary_event_router.h"
 #include "chrome/browser/chromeos/extensions/ime_menu_event_router.h"
 #include "chrome/browser/chromeos/extensions/input_method_event_router.h"
+#include "chrome/browser/chromeos/input_method/autocorrect_manager.h"
+#include "chrome/browser/chromeos/input_method/native_input_method_engine.h"
 #include "chrome/browser/extensions/api/input_ime/input_ime_api.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/spellchecker/spellcheck_factory.h"
@@ -79,6 +81,7 @@
     extensions::api::input_method_private::SetSelectionRange;
 namespace OnInputMethodOptionsChanged =
     extensions::api::input_method_private::OnInputMethodOptionsChanged;
+namespace OnAutocorrect = extensions::api::input_method_private::OnAutocorrect;
 
 using chromeos::InputMethodEngineBase;
 
@@ -609,6 +612,24 @@
   return RespondNow(NoArguments());
 }
 
+ExtensionFunction::ResponseAction
+InputMethodPrivateOnAutocorrectFunction::Run() {
+  std::unique_ptr<OnAutocorrect::Params> parent_params(
+      OnAutocorrect::Params::Create(*args_));
+  const OnAutocorrect::Params::Parameters& params = parent_params->parameters;
+  std::string error;
+  chromeos::NativeInputMethodEngine* engine =
+      static_cast<chromeos::NativeInputMethodEngine*>(
+          GetEngineIfActive(Profile::FromBrowserContext(browser_context()),
+                            extension_id(), &error));
+  if (!engine)
+    return RespondNow(Error(InformativeError(error, static_function_name())));
+
+  engine->OnAutocorrect(params.typed_word, params.corrected_word,
+                        params.start_index);
+  return RespondNow(NoArguments());
+}
+
 InputMethodAPI::InputMethodAPI(content::BrowserContext* context)
     : context_(context) {
   EventRouter::Get(context_)->RegisterObserver(this, OnChanged::kEventName);
diff --git a/chrome/browser/chromeos/extensions/input_method_api.h b/chrome/browser/chromeos/extensions/input_method_api.h
index 0c257cec3..2f794ac 100644
--- a/chrome/browser/chromeos/extensions/input_method_api.h
+++ b/chrome/browser/chromeos/extensions/input_method_api.h
@@ -369,6 +369,24 @@
                              INPUTMETHODPRIVATE_RESET)
 };
 
+class InputMethodPrivateOnAutocorrectFunction : public ExtensionFunction {
+ public:
+  InputMethodPrivateOnAutocorrectFunction(
+      const InputMethodPrivateOnAutocorrectFunction&) = delete;
+  InputMethodPrivateOnAutocorrectFunction& operator=(
+      const InputMethodPrivateOnAutocorrectFunction&) = delete;
+  InputMethodPrivateOnAutocorrectFunction() = default;
+
+ protected:
+  ~InputMethodPrivateOnAutocorrectFunction() override = default;
+  // ExtensionFunction:
+  ResponseAction Run() override;
+
+ private:
+  DECLARE_EXTENSION_FUNCTION("inputMethodPrivate.onAutocorrect",
+                             INPUTMETHODPRIVATE_ONAUTOCORRECT)
+};
+
 class InputMethodAPI : public BrowserContextKeyedAPI,
                        public extensions::EventRouter::Observer {
  public:
diff --git a/chrome/browser/chromeos/input_method/autocorrect_manager.cc b/chrome/browser/chromeos/input_method/autocorrect_manager.cc
new file mode 100644
index 0000000..be05736
--- /dev/null
+++ b/chrome/browser/chromeos/input_method/autocorrect_manager.cc
@@ -0,0 +1,54 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/input_method/autocorrect_manager.h"
+
+#include "base/strings/utf_string_conversions.h"
+
+namespace chromeos {
+
+const int kKeysUntilAutocorrectWindowHides = 4;
+
+AutocorrectManager::AutocorrectManager(InputMethodEngineBase* engine)
+    : engine_(engine) {}
+
+void AutocorrectManager::MarkAutocorrectRange(const std::string& corrected_word,
+                                              int start_index) {
+  // TODO(crbug/1111135): call setAutocorrectTime() (for metrics)
+  // TODO(crbug/1111135): record metric (coverage)
+  key_presses_until_underline_hide_ = kKeysUntilAutocorrectWindowHides;
+  ClearUnderline();
+  if (context_id_ != -1) {
+    std::string error;
+    // TODO(crbug/1111135): error handling
+    engine_->SetAutocorrectRange(context_id_, base::UTF8ToUTF16(corrected_word),
+                                 start_index, start_index, &error);
+  }
+}
+
+void AutocorrectManager::OnKeyEvent() {
+  if (key_presses_until_underline_hide_ < 0) {
+    return;
+  }
+  --key_presses_until_underline_hide_;
+  if (key_presses_until_underline_hide_ < 1) {
+    ClearUnderline();
+  }
+}
+
+void AutocorrectManager::ClearUnderline() {
+  // TODO(crbug/1111135): error handling
+  // TODO(b/171924347): expose engine->clearAutocorrectRange() and use it here.
+  std::string error;
+  engine_->SetAutocorrectRange(
+      context_id_,
+      /*autocorrect_text=*/base::string16(),
+      /*start=*/0, /*end=*/std::numeric_limits<uint32_t>::max(), &error);
+}
+
+void AutocorrectManager::OnFocus(int context_id) {
+  context_id_ = context_id;
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/input_method/autocorrect_manager.h b/chrome/browser/chromeos/input_method/autocorrect_manager.h
new file mode 100644
index 0000000..fc91fba
--- /dev/null
+++ b/chrome/browser/chromeos/input_method/autocorrect_manager.h
@@ -0,0 +1,52 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_INPUT_METHOD_AUTOCORRECT_MANAGER_H_
+#define CHROME_BROWSER_CHROMEOS_INPUT_METHOD_AUTOCORRECT_MANAGER_H_
+
+#include <string>
+
+#include "chrome/browser/chromeos/input_method/emoji_suggester.h"
+#include "chrome/browser/chromeos/input_method/input_method_engine.h"
+#include "chrome/browser/chromeos/input_method/input_method_engine_base.h"
+#include "chrome/browser/chromeos/input_method/personal_info_suggester.h"
+#include "chrome/browser/chromeos/input_method/suggester.h"
+#include "chrome/browser/chromeos/input_method/suggestion_enums.h"
+
+namespace chromeos {
+
+// Implements functionality for chrome.input.ime.autocorrect() extension API.
+// This function shows UI to indicate that autocorrect has happened and allows
+// it to be undone easily.
+// TODO(b/171920749): Add unit tests.
+class AutocorrectManager {
+ public:
+  // Engine is used to interact with the text field, and is assumed to be
+  // valid for the entire lifetime of the autocorrect manager.
+  explicit AutocorrectManager(InputMethodEngineBase* engine);
+
+  AutocorrectManager(const AutocorrectManager&) = delete;
+  AutocorrectManager& operator=(const AutocorrectManager&) = delete;
+
+  // Called by input method engine on autocorrect to initially show underline.
+  // Needs to be called after the autocorrected text (corrected_word, offset by
+  // start_index code points in SurroundingInfo) has been committed.
+  void MarkAutocorrectRange(const std::string& corrected_word, int start_index);
+  // To hide the underline after enough keypresses, this class intercepts
+  // keystrokes.
+  void OnKeyEvent();
+  // Indicates a new text field is focused, used to save context ID.
+  void OnFocus(int context_id);
+
+ private:
+  void ClearUnderline();
+
+  int key_presses_until_underline_hide_ = 0;
+  int context_id_ = -1;
+  InputMethodEngineBase* const engine_;
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_INPUT_METHOD_AUTOCORRECT_MANAGER_H_:w
diff --git a/chrome/browser/chromeos/input_method/input_method_engine.h b/chrome/browser/chromeos/input_method/input_method_engine.h
index 0068922..ea82eea 100644
--- a/chrome/browser/chromeos/input_method/input_method_engine.h
+++ b/chrome/browser/chromeos/input_method/input_method_engine.h
@@ -175,6 +175,16 @@
   // event handler.
   bool IsValidKeyEvent(const ui::KeyEvent* ui_event) override;
 
+  // Sets a range as autocorrected to display a special dashed underline.  Start
+  // and end are code point offsets in the surroundingTextInfo which control the
+  // start and end point of the underline which is added to the text to show a
+  // word was autocorrected.
+  // TODO(b/171924748): Improve documentation for this function all the way down
+  // the stack.
+  bool SetAutocorrectRange(const base::string16& autocorrect_text,
+                           uint32_t start,
+                           uint32_t end) override;
+
  private:
   // InputMethodEngineBase:
   void UpdateComposition(const ui::CompositionText& composition_text,
@@ -193,9 +203,6 @@
 
   gfx::Rect GetAutocorrectCharacterBounds() override;
 
-  bool SetAutocorrectRange(const base::string16& autocorrect_text,
-                           uint32_t start,
-                           uint32_t end) override;
 
   bool SetSelectionRange(uint32_t start, uint32_t end) override;
 
diff --git a/chrome/browser/chromeos/input_method/input_method_manager_impl.cc b/chrome/browser/chromeos/input_method/input_method_manager_impl.cc
index 3fcb50b..9261a17 100644
--- a/chrome/browser/chromeos/input_method/input_method_manager_impl.cc
+++ b/chrome/browser/chromeos/input_method/input_method_manager_impl.cc
@@ -130,7 +130,7 @@
 
 InputMethodManagerImpl::StateImpl::StateImpl(InputMethodManagerImpl* manager,
                                              Profile* profile)
-    : profile(profile), manager_(manager), menu_activated(false) {}
+    : profile(profile), manager_(manager) {}
 
 InputMethodManagerImpl::StateImpl::~StateImpl() = default;
 
@@ -139,15 +139,16 @@
   current_input_method = other.current_input_method;
 
   active_input_method_ids = other.active_input_method_ids;
+  allowed_keyboard_layout_input_method_ids =
+      other.allowed_keyboard_layout_input_method_ids;
 
   pending_input_method_id = other.pending_input_method_id;
 
   enabled_extension_imes = other.enabled_extension_imes;
   extra_input_methods = other.extra_input_methods;
   menu_activated = other.menu_activated;
-  allowed_keyboard_layout_input_method_ids =
-      other.allowed_keyboard_layout_input_method_ids;
   input_view_url = other.input_view_url;
+  input_view_url_overridden = other.input_view_url_overridden;
   ui_style_ = other.ui_style_;
 }
 
@@ -172,6 +173,14 @@
     os << " '" << active_input_method_id << "',";
   }
   os << "\n";
+  os << "allowed_keyboard_layout_input_method_ids (size="
+     << allowed_keyboard_layout_input_method_ids.size() << "):";
+  for (const auto& allowed_keyboard_layout_input_method_id :
+       allowed_keyboard_layout_input_method_ids) {
+    os << " '" << allowed_keyboard_layout_input_method_id << "',";
+  }
+  os << "\n";
+  os << "pending_input_method_id: '" << pending_input_method_id << "'\n";
   os << "enabled_extension_imes (size=" << enabled_extension_imes.size()
      << "):";
   for (const auto& enabled_extension_ime : enabled_extension_imes) {
@@ -182,8 +191,9 @@
   for (const auto& entry : extra_input_methods) {
     os << " '" << entry.first << "' => '" << entry.second.id() << "',\n";
   }
-  os << "pending_input_method_id: '" << pending_input_method_id << "'\n";
+  os << "menu_activated: '" << menu_activated << "'\n";
   os << "input_view_url: '" << input_view_url << "'\n";
+  os << "input_view_url_overridden: '" << input_view_url_overridden << "'\n";
   os << "ui_style_: '" << static_cast<int>(ui_style_) << "'\n";
 
   return os.str();
diff --git a/chrome/browser/chromeos/input_method/input_method_manager_impl.h b/chrome/browser/chromeos/input_method/input_method_manager_impl.h
index db06071..458a209 100644
--- a/chrome/browser/chromeos/input_method/input_method_manager_impl.h
+++ b/chrome/browser/chromeos/input_method/input_method_manager_impl.h
@@ -160,7 +160,7 @@
     InputMethodManagerImpl* const manager_;
 
     // True if the opt-in IME menu is activated.
-    bool menu_activated;
+    bool menu_activated = false;
 
    protected:
     friend base::RefCounted<chromeos::input_method::InputMethodManager::State>;
@@ -189,6 +189,9 @@
         InputMethodManager::UIStyle::kNormal;
 
     std::unique_ptr<ImeServiceConnector> ime_service_connector_;
+
+    // Do not forget to update StateImpl::InitFrom(const StateImpl& other) and
+    // StateImpl::Dump() when adding new data members!!!
   };
 
   // Constructs an InputMethodManager instance. The client is responsible for
diff --git a/chrome/browser/chromeos/input_method/native_input_method_engine.cc b/chrome/browser/chromeos/input_method/native_input_method_engine.cc
index d3331d0..173fc18a 100644
--- a/chrome/browser/chromeos/input_method/native_input_method_engine.cc
+++ b/chrome/browser/chromeos/input_method/native_input_method_engine.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/chromeos/input_method/native_input_method_engine.h"
+
 #include "base/feature_list.h"
 #include "base/i18n/i18n_constants.h"
 #include "base/i18n/icu_string_conversions.h"
@@ -11,6 +12,7 @@
 #include "base/metrics/user_metrics.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/chromeos/input_method/autocorrect_manager.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/settings_window_manager_chromeos.h"
 #include "chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom.h"
@@ -124,14 +126,19 @@
     std::unique_ptr<InputMethodEngineBase::Observer> observer,
     const char* extension_id,
     Profile* profile) {
+  // TODO(crbug/1141231): refactor the mix of unique and raw ptr here.
   std::unique_ptr<AssistiveSuggester> assistive_suggester =
       std::make_unique<AssistiveSuggester>(this, profile);
+  assistive_suggester_ = assistive_suggester.get();
+  std::unique_ptr<AutocorrectManager> autocorrect_manager =
+      std::make_unique<AutocorrectManager>(this);
+  autocorrect_manager_ = autocorrect_manager.get();
   // Wrap the given observer in our observer that will decide whether to call
   // Mojo directly or forward to the extension.
-  assistive_suggester_ = assistive_suggester.get();
   auto native_observer =
       std::make_unique<chromeos::NativeInputMethodEngine::ImeObserver>(
-          std::move(observer), std::move(assistive_suggester));
+          std::move(observer), std::move(assistive_suggester),
+          std::move(autocorrect_manager));
   InputMethodEngine::Initialize(std::move(native_observer), extension_id,
                                 profile);
 }
@@ -144,6 +151,12 @@
   return GetNativeObserver()->IsConnectedForTesting();
 }
 
+void NativeInputMethodEngine::OnAutocorrect(std::string typed_word,
+                                            std::string corrected_word,
+                                            int start_index) {
+  autocorrect_manager_->MarkAutocorrectRange(corrected_word, start_index);
+}
+
 NativeInputMethodEngine::ImeObserver*
 NativeInputMethodEngine::GetNativeObserver() const {
   return static_cast<ImeObserver*>(observer_.get());
@@ -151,11 +164,12 @@
 
 NativeInputMethodEngine::ImeObserver::ImeObserver(
     std::unique_ptr<InputMethodEngineBase::Observer> base_observer,
-    std::unique_ptr<AssistiveSuggester> assistive_suggester)
+    std::unique_ptr<AssistiveSuggester> assistive_suggester,
+    std::unique_ptr<AutocorrectManager> autocorrect_manager)
     : base_observer_(std::move(base_observer)),
       receiver_from_engine_(this),
-      assistive_suggester_(std::move(assistive_suggester)) {
-}
+      assistive_suggester_(std::move(assistive_suggester)),
+      autocorrect_manager_(std::move(autocorrect_manager)) {}
 
 NativeInputMethodEngine::ImeObserver::~ImeObserver() = default;
 
@@ -203,9 +217,10 @@
 
 void NativeInputMethodEngine::ImeObserver::OnFocus(
     const IMEEngineHandlerInterface::InputContext& context) {
-  if (assistive_suggester_->IsAssistiveFeatureEnabled())
+  if (assistive_suggester_->IsAssistiveFeatureEnabled()) {
     assistive_suggester_->OnFocus(context.id);
-
+  }
+  autocorrect_manager_->OnFocus(context.id);
   if (active_engine_id_ && ShouldUseFstMojoEngine(*active_engine_id_) &&
       remote_to_engine_.is_bound()) {
     remote_to_engine_->OnFocus(ime::mojom::InputFieldInfo::New(
@@ -241,7 +256,7 @@
       return;
     }
   }
-
+  autocorrect_manager_->OnKeyEvent();
   auto key_event = ime::mojom::PhysicalKeyEvent::New(
       event.type == "keydown" ? ime::mojom::KeyEventType::kKeyDown
                               : ime::mojom::KeyEventType::kKeyUp,
diff --git a/chrome/browser/chromeos/input_method/native_input_method_engine.h b/chrome/browser/chromeos/input_method/native_input_method_engine.h
index 929b776..2085b820 100644
--- a/chrome/browser/chromeos/input_method/native_input_method_engine.h
+++ b/chrome/browser/chromeos/input_method/native_input_method_engine.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_CHROMEOS_INPUT_METHOD_NATIVE_INPUT_METHOD_ENGINE_H_
 
 #include "chrome/browser/chromeos/input_method/assistive_suggester.h"
+#include "chrome/browser/chromeos/input_method/autocorrect_manager.h"
 #include "chrome/browser/chromeos/input_method/input_method_engine.h"
 #include "chromeos/services/ime/public/mojom/input_engine.mojom-forward.h"
 #include "mojo/public/cpp/bindings/remote.h"
@@ -46,6 +47,11 @@
     return assistive_suggester_;
   }
 
+  // Used to show special UI to user for interacting with autocorrected text.
+  void OnAutocorrect(std::string typed_word,
+                     std::string corrected_word,
+                     int start_index);
+
  private:
   class ImeObserver : public InputMethodEngineBase::Observer,
                       public ime::mojom::InputChannel {
@@ -53,7 +59,8 @@
     // |base_observer| is to forward events to extension during this migration.
     // It will be removed when the official extension is completely migrated.
     ImeObserver(std::unique_ptr<InputMethodEngineBase::Observer> base_observer,
-                std::unique_ptr<AssistiveSuggester> assistive_suggester);
+                std::unique_ptr<AssistiveSuggester> assistive_suggester,
+                std::unique_ptr<AutocorrectManager> autocorrect_manager);
     ~ImeObserver() override;
 
     // InputMethodEngineBase::Observer:
@@ -136,10 +143,13 @@
     base::Optional<std::string> active_engine_id_;
 
     std::unique_ptr<AssistiveSuggester> assistive_suggester_;
+    std::unique_ptr<AutocorrectManager> autocorrect_manager_;
   };
 
   ImeObserver* GetNativeObserver() const;
-  AssistiveSuggester* assistive_suggester_;
+
+  AssistiveSuggester* assistive_suggester_ = nullptr;
+  AutocorrectManager* autocorrect_manager_ = nullptr;
 };
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/policy/active_directory_policy_manager.cc b/chrome/browser/chromeos/policy/active_directory_policy_manager.cc
index 17728f3..ef9a225 100644
--- a/chrome/browser/chromeos/policy/active_directory_policy_manager.cc
+++ b/chrome/browser/chromeos/policy/active_directory_policy_manager.cc
@@ -97,17 +97,6 @@
   return true;
 }
 
-bool ActiveDirectoryPolicyManager::IsFirstPolicyLoadComplete(
-    PolicyDomain domain) const {
-  if (domain == POLICY_DOMAIN_CHROME)
-    return store()->first_policies_loaded();
-  if (domain == extension_policy_domain_) {
-    return extension_policy_service_ &&
-           extension_policy_service_->policy() != nullptr;
-  }
-  return true;
-}
-
 void ActiveDirectoryPolicyManager::RefreshPolicies() {
   scheduler_->ScheduleTaskNow();
 }
diff --git a/chrome/browser/chromeos/policy/active_directory_policy_manager.h b/chrome/browser/chromeos/policy/active_directory_policy_manager.h
index d83919d..b29b2cf 100644
--- a/chrome/browser/chromeos/policy/active_directory_policy_manager.h
+++ b/chrome/browser/chromeos/policy/active_directory_policy_manager.h
@@ -45,7 +45,6 @@
   void Init(SchemaRegistry* registry) override;
   void Shutdown() override;
   bool IsInitializationComplete(PolicyDomain domain) const override;
-  bool IsFirstPolicyLoadComplete(PolicyDomain domain) const override;
   void RefreshPolicies() override;
 
   // CloudPolicyStore::Observer:
diff --git a/chrome/browser/chromeos/policy/device_local_account_policy_provider.cc b/chrome/browser/chromeos/policy/device_local_account_policy_provider.cc
index c99e51b..508122d 100644
--- a/chrome/browser/chromeos/policy/device_local_account_policy_provider.cc
+++ b/chrome/browser/chromeos/policy/device_local_account_policy_provider.cc
@@ -90,11 +90,6 @@
   return true;
 }
 
-bool DeviceLocalAccountPolicyProvider::IsFirstPolicyLoadComplete(
-    PolicyDomain domain) const {
-  return IsInitializationComplete(domain);
-}
-
 void DeviceLocalAccountPolicyProvider::RefreshPolicies() {
   DeviceLocalAccountPolicyBroker* broker = GetBroker();
   if (broker && broker->core()->service()) {
diff --git a/chrome/browser/chromeos/policy/device_local_account_policy_provider.h b/chrome/browser/chromeos/policy/device_local_account_policy_provider.h
index bfe124c..84db045f 100644
--- a/chrome/browser/chromeos/policy/device_local_account_policy_provider.h
+++ b/chrome/browser/chromeos/policy/device_local_account_policy_provider.h
@@ -48,7 +48,6 @@
 
   // ConfigurationPolicyProvider:
   bool IsInitializationComplete(PolicyDomain domain) const override;
-  bool IsFirstPolicyLoadComplete(PolicyDomain domain) const override;
   void RefreshPolicies() override;
 
   // DeviceLocalAccountPolicyService::Observer:
diff --git a/chrome/browser/chromeos/policy/network_configuration_updater_unittest.cc b/chrome/browser/chromeos/policy/network_configuration_updater_unittest.cc
index 19c5917..66201c0 100644
--- a/chrome/browser/chromeos/policy/network_configuration_updater_unittest.cc
+++ b/chrome/browser/chromeos/policy/network_configuration_updater_unittest.cc
@@ -344,8 +344,6 @@
 
     EXPECT_CALL(provider_, IsInitializationComplete(_))
         .WillRepeatedly(Return(false));
-    EXPECT_CALL(provider_, IsFirstPolicyLoadComplete(_))
-        .WillRepeatedly(Return(false));
     provider_.Init();
     PolicyServiceImpl::Providers providers;
     providers.push_back(&provider_);
@@ -400,8 +398,6 @@
     Mock::VerifyAndClearExpectations(&provider_);
     EXPECT_CALL(provider_, IsInitializationComplete(_))
         .WillRepeatedly(Return(true));
-    EXPECT_CALL(provider_, IsFirstPolicyLoadComplete(_))
-        .WillRepeatedly(Return(true));
     provider_.SetAutoRefresh();
     provider_.RefreshPolicies();
     base::RunLoop().RunUntilIdle();
diff --git a/chrome/browser/chromeos/release_notes/release_notes_storage.cc b/chrome/browser/chromeos/release_notes/release_notes_storage.cc
index cd863d6..fda15565 100644
--- a/chrome/browser/chromeos/release_notes/release_notes_storage.cc
+++ b/chrome/browser/chromeos/release_notes/release_notes_storage.cc
@@ -24,7 +24,7 @@
 
 namespace {
 
-constexpr int kTimesToShowSuggestionChip = 6;
+constexpr int kTimesToShowSuggestionChip = 3;
 
 int GetMilestone() {
   return version_info::GetVersion().components()[0];
@@ -91,8 +91,14 @@
 }
 
 bool ReleaseNotesStorage::ShouldShowSuggestionChip() {
-  // TODO(b/169711884): Re-enable this when we have a working version.
-  return false;
+  if (!base::FeatureList::IsEnabled(
+          chromeos::features::kReleaseNotesSuggestionChip)) {
+    return false;
+  }
+
+  const int times_left_to_show = profile_->GetPrefs()->GetInteger(
+      prefs::kReleaseNotesSuggestionChipTimesLeftToShow);
+  return times_left_to_show > 0;
 }
 
 void ReleaseNotesStorage::DecreaseTimesLeftToShowSuggestionChip() {
diff --git a/chrome/browser/chromeos/release_notes/release_notes_storage_unittest.cc b/chrome/browser/chromeos/release_notes/release_notes_storage_unittest.cc
index 808fe54..66354280 100644
--- a/chrome/browser/chromeos/release_notes/release_notes_storage_unittest.cc
+++ b/chrome/browser/chromeos/release_notes/release_notes_storage_unittest.cc
@@ -44,7 +44,8 @@
     scoped_feature_list_.InitWithFeatures(
         /*enabled_features=*/{chromeos::features::kReleaseNotesNotification,
                               chromeos::features::
-                                  kReleaseNotesNotificationAllChannels},
+                                  kReleaseNotesNotificationAllChannels,
+                              chromeos::features::kReleaseNotesSuggestionChip},
         /*disabled_features=*/{});
     std::unique_ptr<Profile> profile = CreateProfile(email);
     profile->GetProfilePolicyConnector()->OverrideIsManagedForTesting(
@@ -150,8 +151,7 @@
 // Tests that when kReleaseNotesSuggestionChipTimesLeftToShow is greater than 0,
 // ReleaseNotesStorage::ShouldShowSuggestionChip returns true, and when
 // decreased the method returns false again.
-// TODO(b/169711884): Re-enable when suggestion chips are re-enabled.
-TEST_F(ReleaseNotesStorageTest, DISABLED_ShowReleaseNotesSuggestionChip) {
+TEST_F(ReleaseNotesStorageTest, ShowReleaseNotesSuggestionChip) {
   std::unique_ptr<Profile> profile =
       SetupStandardEnvironmentAndProfile("test@gmail.com", false);
   std::unique_ptr<ReleaseNotesStorage> release_notes_storage =
@@ -169,9 +169,7 @@
 
 // Tests that when we mark a notification as shown, we also show the suggestion
 // chip.
-// TODO(b/169711884): Re-enable when suggestion chips are re-enabled.
-TEST_F(ReleaseNotesStorageTest,
-       DISABLED_ShowSuggestionChipWhenNotificationShown) {
+TEST_F(ReleaseNotesStorageTest, ShowSuggestionChipWhenNotificationShown) {
   std::unique_ptr<Profile> profile =
       SetupStandardEnvironmentAndProfile("test@gmail.com", false);
   std::unique_ptr<ReleaseNotesStorage> release_notes_storage =
@@ -179,7 +177,7 @@
 
   release_notes_storage->MarkNotificationShown();
 
-  EXPECT_EQ(6, profile.get()->GetPrefs()->GetInteger(
+  EXPECT_EQ(3, profile.get()->GetPrefs()->GetInteger(
                    prefs::kReleaseNotesSuggestionChipTimesLeftToShow));
   EXPECT_EQ(true, release_notes_storage->ShouldShowSuggestionChip());
 }
diff --git a/chrome/browser/chromeos/web_applications/OWNERS b/chrome/browser/chromeos/web_applications/OWNERS
index 0c26b7e..7a9b4eb 100644
--- a/chrome/browser/chromeos/web_applications/OWNERS
+++ b/chrome/browser/chromeos/web_applications/OWNERS
@@ -5,6 +5,8 @@
 
 per-file chrome_camera_app*=file://chromeos/components/camera_app_ui/OWNERS
 per-file chrome_help_app*=carpenterr@chromium.org
+per-file chrome_file_manager*=file://chromeos/components/file_manager/OWNERS
+per-file file_manager*=file://chromeos/components/file_manager/OWNERS
 per-file terminal_source*=calamity@chromium.org
 per-file terminal_source*=joelhockey@chromium.org
 per-file default_web_app_ids.h=jshikaram@chromium.org
diff --git a/chrome/browser/chromeos/web_applications/chrome_file_manager_ui_delegate.cc b/chrome/browser/chromeos/web_applications/chrome_file_manager_ui_delegate.cc
new file mode 100644
index 0000000..b579e317
--- /dev/null
+++ b/chrome/browser/chromeos/web_applications/chrome_file_manager_ui_delegate.cc
@@ -0,0 +1,7 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/web_applications/chrome_file_manager_ui_delegate.h"
+
+ChromeFileManagerUIDelegate::ChromeFileManagerUIDelegate() = default;
diff --git a/chrome/browser/chromeos/web_applications/chrome_file_manager_ui_delegate.h b/chrome/browser/chromeos/web_applications/chrome_file_manager_ui_delegate.h
new file mode 100644
index 0000000..1498a11
--- /dev/null
+++ b/chrome/browser/chromeos/web_applications/chrome_file_manager_ui_delegate.h
@@ -0,0 +1,23 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_WEB_APPLICATIONS_CHROME_FILE_MANAGER_UI_DELEGATE_H_
+#define CHROME_BROWSER_CHROMEOS_WEB_APPLICATIONS_CHROME_FILE_MANAGER_UI_DELEGATE_H_
+
+#include "chromeos/components/file_manager/file_manager_ui_delegate.h"
+
+/**
+ * Implementation of the FileManagerUIDelegate interface. Provides the file
+ * manager code in //chromeos with functions that only exist in //chrome.
+ */
+class ChromeFileManagerUIDelegate : public FileManagerUIDelegate {
+ public:
+  ChromeFileManagerUIDelegate();
+
+  ChromeFileManagerUIDelegate(const ChromeFileManagerUIDelegate&) = delete;
+  ChromeFileManagerUIDelegate& operator=(const ChromeFileManagerUIDelegate&) =
+      delete;
+};
+
+#endif  // CHROME_BROWSER_CHROMEOS_WEB_APPLICATIONS_CHROME_FILE_MANAGER_UI_DELEGATE_H_
diff --git a/chrome/browser/chromeos/web_applications/default_web_app_ids.h b/chrome/browser/chromeos/web_applications/default_web_app_ids.h
index 4d07623..5a26d584 100644
--- a/chrome/browser/chromeos/web_applications/default_web_app_ids.h
+++ b/chrome/browser/chromeos/web_applications/default_web_app_ids.h
@@ -8,59 +8,64 @@
 namespace chromeos {
 namespace default_web_apps {
 
-// Generated as web_app::GenerateAppIdFromURL(GURL("https://tv.youtube.com/")).
-constexpr char kYoutubeTVAppId[] = "kiemjbkkegajmpbobdfngbmjccjhnofh";
+// The URLs used to generate the app IDs MUST match the start_url field of the
+// manifest served by the PWA.
 
-// Generated as
-// web_app::GenerateAppIdFromURL(GURL("https://www.showtime.com/")).
-constexpr char kShowtimeAppId[] = "eoccpgmpiempcflglfokeengliildkag";
-
-// Generated as
-// web_app::GenerateAppIdFromURL(GURL("https://canvas.apps.chrome/")).
-constexpr char kCanvasAppId[] = "ieailfmhaghpphfffooibmlghaeopach";
-
-// Generated as web_app::GenerateAppIdFromURL(GURL("chrome://help-app/")).
-constexpr char kHelpAppId[] = "nbljnnecbjbmifnoehiemkgefbnpoeak";
-
-// Generated as
-// web_app::GenerateAppIdFromURL(GURL(
-// "chrome://camera-app/src/views/main.html")).
+// Generated as: web_app::GenerateAppIdFromURL(GURL(
+//     "chrome://camera-app/src/views/main.html"))
 constexpr char kCameraAppId[] = "lokiojgebppilomhkceogdnchlbpcoaj";
 
-// Generated as
-// web_app::GenerateAppIdFromURL(GURL(
-// "https://google.com/chromebook/whatsnew/embedded/")).
-constexpr char kReleaseNotesAppId[] = "lddhblppcjmenljhdleiahjighahdcje";
+// Generated as: // web_app::GenerateAppIdFromURL(GURL(
+//     "https://canvas.apps.chrome/"))
+constexpr char kCanvasAppId[] = "ieailfmhaghpphfffooibmlghaeopach";
 
-// Generated as web_app::GenerateAppIdFromURL(GURL("chrome://settings/")).
-constexpr char kSettingsAppId[] = "inogagmajamaleonmanpkpkkigmklfad";
-
-// Generated as web_app::GenerateAppIdFromURL(GURL("chrome://os-settings/")).
-constexpr char kOsSettingsAppId[] = "odknhmnlageboeamepcngndbggdpaobj";
-
-// Generated as
-// web_app::GenerateAppIdFromURL(GURL("chrome://test-system-app/pwa.html")).
-constexpr char kMockSystemAppId[] = "maphiehpiinjgiaepbljmopkodkadcbh";
-
-// Generated as
-// web_app::GenerateAppIdFromURL(GURL("https://news.google.com/?lfhs=2")).
-constexpr char kGoogleNewsAppId[] = "kfgapjallbhpciobgmlhlhokknljkgho";
-
-// Generated as
-// web_app::GenerateAppIdFromURL(
-//     GURL("https://www.google.com/maps/_/sw/tt-install.html")).
+// Generated as: web_app::GenerateAppIdFromURL(GURL(
+//     "https://www.google.com/maps/_/sw/tt-install.html"))
 constexpr char kGoogleMapsAppId[] = "mnhkaebcjjhencmpkapnbdaogjamfbcj";
 
-// Generated as web_app::GenerateAppIdFromURL(GURL("chrome://media-app/")).
+// Generated as: web_app::GenerateAppIdFromURL(GURL(
+//     "https://news.google.com/?lfhs=2"))
+constexpr char kGoogleNewsAppId[] = "kfgapjallbhpciobgmlhlhokknljkgho";
+
+// Generated as: web_app::GenerateAppIdFromURL(GURL(
+//     "chrome://help-app/"))
+constexpr char kHelpAppId[] = "nbljnnecbjbmifnoehiemkgefbnpoeak";
+
+// Generated as: web_app::GenerateAppIdFromURL(GURL(
+//     "chrome://media-app/"))
 constexpr char kMediaAppId[] = "jhdjimmaggjajfjphpljagpgkidjilnj";
 
-// Generated as
-// web_app::GenerateAppIdFromURL(GURL("https://music.youtube.com/?source=pwa")).
+// Generated as: web_app::GenerateAppIdFromURL(GURL(
+//     "chrome://test-system-app/pwa.html"))
+constexpr char kMockSystemAppId[] = "maphiehpiinjgiaepbljmopkodkadcbh";
+
+// Generated as: web_app::GenerateAppIdFromURL(GURL(
+//     "chrome://os-settings/"))
+constexpr char kOsSettingsAppId[] = "odknhmnlageboeamepcngndbggdpaobj";
+
+// Generated as: web_app::GenerateAppIdFromURL(GURL(
+//     "https://google.com/chromebook/whatsnew/embedded/"))
+constexpr char kReleaseNotesAppId[] = "lddhblppcjmenljhdleiahjighahdcje";
+
+// Generated as: web_app::GenerateAppIdFromURL(GURL(
+//     "chrome://settings/"))
+constexpr char kSettingsAppId[] = "inogagmajamaleonmanpkpkkigmklfad";
+
+// Generated as: web_app::GenerateAppIdFromURL(GURL(
+//     "https://www.showtime.com/"))
+constexpr char kShowtimeAppId[] = "eoccpgmpiempcflglfokeengliildkag";
+
+// Generated as: web_app::GenerateAppIdFromURL(GURL(
+//     "https://stadia.google.com/?lfhs=2"))
+constexpr char kStadiaAppId[] = "pnkcfpnngfokcnnijgkllghjlhkailce";
+
+// Generated as: web_app::GenerateAppIdFromURL(GURL(
+//     "https://music.youtube.com/?source=pwa"))
 constexpr char kYoutubeMusicAppId[] = "cinhimbnkkaeohfgghhklpknlkffjgod";
 
-// Generated as
-// web_app::GenerateAppIdFromURL(GURL("https://stadia.google.com/?lfhs=2")).
-constexpr char kStadiaAppId[] = "pnkcfpnngfokcnnijgkllghjlhkailce";
+// Generated as: web_app::GenerateAppIdFromURL(GURL(
+//     "https://tv.youtube.com/"))
+constexpr char kYoutubeTVAppId[] = "kiemjbkkegajmpbobdfngbmjccjhnofh";
 
 }  // namespace default_web_apps
 }  // namespace chromeos
diff --git a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadLocationCustomView.java b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadLocationCustomView.java
index 55ba886..4a968c6 100644
--- a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadLocationCustomView.java
+++ b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadLocationCustomView.java
@@ -182,6 +182,7 @@
      * @param  availableSpace The available space of the file location.
      */
     private void setLocationAvailableSpace(long availableSpace) {
+        if (mDialogType != DownloadLocationDialogType.LOCATION_SUGGESTION) return;
         String locationAvailableSpaceText =
                 StringUtils.getAvailableBytesForUi(getContext(), availableSpace);
         int textColor = ContextCompat.getColor(getContext(), R.color.default_text_color);
diff --git a/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc b/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc
index 19f5426..97d57a44 100644
--- a/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc
+++ b/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc
@@ -410,23 +410,21 @@
       "floatingkeyboarddefault",
       base::FeatureList::IsEnabled(
           chromeos::features::kVirtualKeyboardFloatingDefault)));
+
+  // Flag used to enable system built-in IME decoder instead of NaCl.
+  bool mojoDecoder =
+      base::FeatureList::IsEnabled(chromeos::features::kImeMojoDecoder);
+  features->AppendString(GenerateFeatureFlag("usemojodecoder", mojoDecoder));
+  // Enabling MojoDecoder implies the 2 previous flags are auto-enabled.
+  //   * fstinputlogic
+  //   * hmminputlogic
+  // TODO(b/171846787): Remove the 3 flags after they are removed from clients.
+  features->AppendString(GenerateFeatureFlag("fstinputlogic", mojoDecoder));
+  features->AppendString(GenerateFeatureFlag("hmminputlogic", mojoDecoder));
   features->AppendString(GenerateFeatureFlag(
       "imemozcproto",
       base::FeatureList::IsEnabled(chromeos::features::kImeMozcProto)));
-  // 3 flags below are used to enable IME new APIs on each decoder.
-  features->AppendString(GenerateFeatureFlag(
-      "fstinputlogic",
-      base::FeatureList::IsEnabled(chromeos::features::kImeInputLogicFst)));
-  features->AppendString(GenerateFeatureFlag(
-      "hmminputlogic",
-      base::FeatureList::IsEnabled(chromeos::features::kImeInputLogicHmm)));
-  features->AppendString(GenerateFeatureFlag(
-      "mozcinputlogic",
-      base::FeatureList::IsEnabled(chromeos::features::kImeInputLogicMozc)));
-  // Flag used to enable system built-in IME decoder instead of NaCl.
-  features->AppendString(GenerateFeatureFlag(
-      "usemojodecoder",
-      base::FeatureList::IsEnabled(chromeos::features::kImeMojoDecoder)));
+
   features->AppendString(GenerateFeatureFlag(
       "borderedkey", base::FeatureList::IsEnabled(
                          chromeos::features::kVirtualKeyboardBorderedKey)));
diff --git a/chrome/browser/external_protocol/external_protocol_handler_browsertest.cc b/chrome/browser/external_protocol/external_protocol_handler_browsertest.cc
index 6705cf7..a4ea1a6c 100644
--- a/chrome/browser/external_protocol/external_protocol_handler_browsertest.cc
+++ b/chrome/browser/external_protocol/external_protocol_handler_browsertest.cc
@@ -78,8 +78,15 @@
   EXPECT_EQ(browser()->tab_strip_model()->count(), 1);
 }
 
+// Flaky on Mac: https://crbug.com/1143762:
+#if defined(OS_MAC)
+#define MAYBE_ProtocolLaunchEmitsConsoleLog \
+  DISABLED_ProtocolLaunchEmitsConsoleLog
+#else
+#define MAYBE_ProtocolLaunchEmitsConsoleLog ProtocolLaunchEmitsConsoleLog
+#endif
 IN_PROC_BROWSER_TEST_F(ExternalProtocolHandlerBrowserTest,
-                       ProtocolLaunchEmitsConsoleLog) {
+                       MAYBE_ProtocolLaunchEmitsConsoleLog) {
 #if defined(OS_WIN)
   // On Win 7 the protocol is registered to be handled by Chrome and thus never
   // reaches the ExternalProtocolHandler so we skip the test. For
diff --git a/chrome/browser/federated_learning/floc_id_provider_unittest.cc b/chrome/browser/federated_learning/floc_id_provider_unittest.cc
index b493bea..ab0b37ae 100644
--- a/chrome/browser/federated_learning/floc_id_provider_unittest.cc
+++ b/chrome/browser/federated_learning/floc_id_provider_unittest.cc
@@ -8,6 +8,7 @@
 #include "base/strings/strcat.h"
 #include "base/test/bind_test_util.h"
 #include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/federated_learning/floc_remote_permission_service.h"
 #include "chrome/common/chrome_features.h"
@@ -830,9 +831,17 @@
   EXPECT_EQ(FlocId::CreateFromHistory({"a.test", "b.test"}), floc_id());
 }
 
+// TODO(crbug.com/1143855): Flaky on Linux TSAN.
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(THREAD_SANITIZER)
+#define MAYBE_SortingLshPostProcessingEnabled_SyncHistoryEnabledFollowedBySortingLshLoaded \
+  DISABLED_SortingLshPostProcessingEnabled_SyncHistoryEnabledFollowedBySortingLshLoaded
+#else
+#define MAYBE_SortingLshPostProcessingEnabled_SyncHistoryEnabledFollowedBySortingLshLoaded \
+  SortingLshPostProcessingEnabled_SyncHistoryEnabledFollowedBySortingLshLoaded
+#endif
 TEST_F(
     FlocIdProviderUnitTest,
-    SortingLshPostProcessingEnabled_SyncHistoryEnabledFollowedBySortingLshLoaded) {
+    MAYBE_SortingLshPostProcessingEnabled_SyncHistoryEnabledFollowedBySortingLshLoaded) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeature(
       features::kFlocIdSortingLshBasedComputation);
@@ -853,9 +862,17 @@
   EXPECT_TRUE(first_floc_computation_triggered());
 }
 
+// TODO(crbug.com/1143855): Flaky on Linux TSAN.
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(THREAD_SANITIZER)
+#define MAYBE_SortingLshPostProcessingEnabled_SortingLshLoadedFollowedBySyncHistoryEnabled \
+  DISABLED_SortingLshPostProcessingEnabled_SortingLshLoadedFollowedBySyncHistoryEnabled
+#else
+#define MAYBE_SortingLshPostProcessingEnabled_SortingLshLoadedFollowedBySyncHistoryEnabled \
+  SortingLshPostProcessingEnabled_SortingLshLoadedFollowedBySyncHistoryEnabled
+#endif
 TEST_F(
     FlocIdProviderUnitTest,
-    SortingLshPostProcessingEnabled_SortingLshLoadedFollowedBySyncHistoryEnabled) {
+    MAYBE_SortingLshPostProcessingEnabled_SortingLshLoadedFollowedBySyncHistoryEnabled) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeature(
       features::kFlocIdSortingLshBasedComputation);
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index efda45a..2f47faf6 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1350,21 +1350,6 @@
     "expiry_milestone": 90
   },
   {
-    "name": "enable-cros-ime-input-logic-fst",
-    "owners": [ "essential-inputs-team@google.com" ],
-    "expiry_milestone": 90
-  },
-  {
-    "name": "enable-cros-ime-input-logic-hmm",
-    "owners": [ "essential-inputs-team@google.com" ],
-    "expiry_milestone": 90
-  },
-  {
-    "name": "enable-cros-ime-input-logic-mozc",
-    "owners": [ "essential-inputs-team@google.com" ],
-    "expiry_milestone": 90
-  },
-  {
     "name": "enable-cros-ime-mozc-proto",
     "owners": [ "essential-inputs-team@google.com" ],
     "expiry_milestone": 90
@@ -1812,7 +1797,7 @@
   },
   {
     "name": "enable-mygoogle",
-    "owners": [ "fernandex", "chrome-ios-signin@google.com" ],
+    "owners": [ "fernandex", "chrome-signin-ios@google.com" ],
     "expiry_milestone": 87
   },
   {
@@ -1842,7 +1827,7 @@
   },
   {
     "name": "enable-network-logging-to-file",
-    "owners": [ "eroman", "net-dev" ],
+    "owners": [ "net-dev" ],
     // This flag is used to capture early-browser network logging on platforms
     // without easy access to startup time configuration.
     "expiry_milestone": -1
@@ -3943,7 +3928,7 @@
   },
   {
     "name": "read-later",
-    "owners": [ "chrome-desktop-ui-seattle@google.com", "corising" ],
+    "owners": [ "chrome-desktop-ui-sea@google.com", "corising" ],
     "expiry_milestone": 90
   },
   {
@@ -4315,7 +4300,7 @@
   },
   {
     "name" : "side-panel",
-    "owners": [ "chrome-desktop-ui-seattle@google.com", "pbos" ],
+    "owners": [ "chrome-desktop-ui-sea@google.com", "pbos" ],
     "expiry_milestone" : 92
   },
   {
@@ -4439,27 +4424,27 @@
   },
   {
     "name": "tab-groups",
-    "owners": [ "chrome-desktop-ui-seattle@google.com", "connily" ],
+    "owners": [ "chrome-desktop-ui-sea@google.com", "connily" ],
     "expiry_milestone": 88
   },
   {
     "name": "tab-groups-auto-create",
-    "owners": [ "chrome-desktop-ui-seattle@google.com", "xialinyan" ],
+    "owners": [ "chrome-desktop-ui-sea@google.com", "cyan" ],
     "expiry_milestone": 89
   },
   {
     "name": "tab-groups-collapse",
-    "owners": [ "chrome-desktop-ui-seattle@google.com", "xialinyan" ],
+    "owners": [ "chrome-desktop-ui-sea@google.com", "cyan" ],
     "expiry_milestone": 89
   },
   {
     "name": "tab-groups-collapse-freezing",
-    "owners": [ "chrome-desktop-ui-seattle@google.com", "xialinyan" ],
+    "owners": [ "chrome-desktop-ui-sea@google.com", "cyan" ],
     "expiry_milestone": 89
   },
   {
     "name": "tab-groups-feedback",
-    "owners": [ "chrome-desktop-ui-seattle@google.com", "xialinyan" ],
+    "owners": [ "chrome-desktop-ui-sea@google.com", "cyan" ],
     "expiry_milestone": 86
   },
   {
@@ -4638,11 +4623,6 @@
     "expiry_milestone": 90
   },
   {
-    "name": "usage-stats",
-    "owners": [ "pnoland", "chromeshine@google.com" ],
-    "expiry_milestone": 80
-  },
-  {
     "name": "use-angle",
     "owners": [ "angle-team@google.com" ],
     // This flag is used by certain customers to set ANGLE to use its OpenGL
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index cd51cb8..1de19f53 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1137,7 +1137,7 @@
 const char kFileHandlingAPIName[] = "File Handling API";
 const char kFileHandlingAPIDescription[] =
     "Enables the file handling API, allowing websites to register as file "
-    "handlers. This depends on native-file-system";
+    "handlers.";
 
 const char kFillOnAccountSelectName[] = "Fill passwords on account selection";
 const char kFillOnAccountSelectDescription[] =
@@ -3192,12 +3192,6 @@
 const char kUpdateNotificationServiceImmediateShowOptionDescription[] =
     "Show update notification right immediately";
 
-const char kUsageStatsDescription[] =
-    "When set, enables sharing of per-domain usage stats with the Digital "
-    "Wellbeing app on Android, and allows Digital Wellbeing to suspend access "
-    "to websites in order to enforce user-defined time limits.";
-const char kUsageStatsName[] = "Share Usage Stats with Digital Wellbeing";
-
 const char kUseChimeAndroidSdkDescription[] =
     "Enable Chime SDK to receive push notification.";
 const char kUseChimeAndroidSdkName[] = "Use Chime SDK";
@@ -4234,18 +4228,6 @@
 const char kImeEmojiSuggestAdditionDescription[] =
     "Enable emoji suggestion as addition to the text written for native IME.";
 
-const char kImeInputLogicFstName[] = "Enable FST Input Logic on IME";
-const char kImeInputLogicFstDescription[] =
-    "Enable FST Input Logic to replace the IME legacy input logic on NaCl";
-
-const char kImeInputLogicHmmName[] = "Enable HMM Input Logic on IME";
-const char kImeInputLogicHmmDescription[] =
-    "Enable HMM Input Logic to replace the IME legacy input logic on NaCl";
-
-const char kImeInputLogicMozcName[] = "Enable MOZC Input Logic on IME";
-const char kImeInputLogicMozcDescription[] =
-    "Enable MOZC Input Logic to replace the IME legacy input logic on NaCl";
-
 const char kImeMozcProtoName[] = "Enable protobuf on Japanese IME";
 const char kImeMozcProtoDescription[] =
     "Enable Japanese IME to use protobuf as interactive message format to "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 10de50c..3961239 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1842,9 +1842,6 @@
 extern const char kPrefetchNotificationSchedulingIntegrationName[];
 extern const char kPrefetchNotificationSchedulingIntegrationDescription[];
 
-extern const char kUsageStatsDescription[];
-extern const char kUsageStatsName[];
-
 extern const char kUseChimeAndroidSdkDescription[];
 extern const char kUseChimeAndroidSdkName[];
 
@@ -2466,15 +2463,6 @@
 extern const char kImeEmojiSuggestAdditionName[];
 extern const char kImeEmojiSuggestAdditionDescription[];
 
-extern const char kImeInputLogicFstName[];
-extern const char kImeInputLogicFstDescription[];
-
-extern const char kImeInputLogicHmmName[];
-extern const char kImeInputLogicHmmDescription[];
-
-extern const char kImeInputLogicMozcName[];
-extern const char kImeInputLogicMozcDescription[];
-
 extern const char kImeMozcProtoName[];
 extern const char kImeMozcProtoDescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index 0d5d345..f0f3a8d 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -221,7 +221,6 @@
     &kUmaBackgroundSessions,
     &kUpdateNotificationSchedulingIntegration,
     &kUpdateNotificationScheduleServiceImmediateShowOption,
-    &kUsageStatsFeature,
     &kVrBrowsingFeedback,
     &kWebApkAdaptiveIcon,
     &kPrefetchNotificationSchedulingIntegration,
@@ -652,8 +651,8 @@
     "UpdateNotificationScheduleServiceImmediateShowOption",
     base::FEATURE_DISABLED_BY_DEFAULT};
 
-const base::Feature kUsageStatsFeature{"UsageStats",
-                                       base::FEATURE_ENABLED_BY_DEFAULT};
+const base::Feature kUserMediaScreenCapturing{
+    "UserMediaScreenCapturing", base::FEATURE_DISABLED_BY_DEFAULT};
 
 const base::Feature kVrBrowsingFeedback{"VrBrowsingFeedback",
                                         base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index b4daf76..dfa46c0 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -132,7 +132,7 @@
 extern const base::Feature kUpdateNotificationSchedulingIntegration;
 extern const base::Feature
     kUpdateNotificationScheduleServiceImmediateShowOption;
-extern const base::Feature kUsageStatsFeature;
+extern const base::Feature kUserMediaScreenCapturing;
 extern const base::Feature kVrBrowsingFeedback;
 extern const base::Feature kWebApkAdaptiveIcon;
 extern const base::Feature kPrefetchNotificationSchedulingIntegration;
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index f985dd3f..8c4e7c6 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -445,7 +445,6 @@
             "UpdateNotificationSchedulingIntegration";
     public static final String UPDATE_NOTIFICATION_IMMEDIATE_SHOW_OPTION =
             "UpdateNotificationScheduleServiceImmediateShowOption";
-    public static final String USAGE_STATS = "UsageStats";
     public static final String USE_CHIME_ANDROID_SDK = "UseChimeAndroidSdk";
     public static final String VR_BROWSING_FEEDBACK = "VrBrowsingFeedback";
     public static final String WEBAPK_ADAPTIVE_ICON = "WebApkAdaptiveIcon";
diff --git a/chrome/browser/policy/cloud/user_policy_signin_service_base.cc b/chrome/browser/policy/cloud/user_policy_signin_service_base.cc
index e172a58..bbdc6b9 100644
--- a/chrome/browser/policy/cloud/user_policy_signin_service_base.cc
+++ b/chrome/browser/policy/cloud/user_policy_signin_service_base.cc
@@ -201,14 +201,13 @@
     const AccountId& account_id,
     scoped_refptr<network::SharedURLLoaderFactory> profile_url_loader_factory) {
   DCHECK(account_id.is_valid());
-  UserCloudPolicyManager* manager = policy_manager();
   if (!ShouldLoadPolicyForUser(account_id.GetUserEmail())) {
-    manager->SetPoliciesRequired(false);
     DVLOG(1) << "Policy load not enabled for user: "
              << account_id.GetUserEmail();
     return;
   }
 
+  UserCloudPolicyManager* manager = policy_manager();
   // Initialize the UCPM if it is not already initialized.
   if (!manager->core()->service()) {
     // If there is no cached DMToken then we can detect this when the
diff --git a/chrome/browser/policy/policy_browsertest.cc b/chrome/browser/policy/policy_browsertest.cc
index dacac5b..84facfc 100644
--- a/chrome/browser/policy/policy_browsertest.cc
+++ b/chrome/browser/policy/policy_browsertest.cc
@@ -153,6 +153,7 @@
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_paths.h"
 #include "content/public/common/content_switches.h"
+#include "content/public/common/navigation_policy.h"
 #include "content/public/common/url_constants.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
@@ -694,6 +695,8 @@
   // Crash and reload the tab to get a new renderer.
   content::CrashTab(contents);
   EXPECT_TRUE(chrome::ExecuteCommand(browser(), IDC_RELOAD));
+  if (content::ShouldSkipEarlyCommitPendingForCrashedFrame())
+    EXPECT_TRUE(content::WaitForLoadStop(contents));
   EXPECT_FALSE(IsWebGLEnabled(contents));
   // Enable with a policy.
   policies.Set(key::kDisable3DAPIs, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
@@ -701,6 +704,8 @@
   UpdateProviderPolicy(policies);
   content::CrashTab(contents);
   EXPECT_TRUE(chrome::ExecuteCommand(browser(), IDC_RELOAD));
+  if (content::ShouldSkipEarlyCommitPendingForCrashedFrame())
+    EXPECT_TRUE(content::WaitForLoadStop(contents));
   EXPECT_TRUE(IsWebGLEnabled(contents));
 }
 
diff --git a/chrome/browser/policy/policy_prefs_browsertest.cc b/chrome/browser/policy/policy_prefs_browsertest.cc
index 09944f9..35abd129 100644
--- a/chrome/browser/policy/policy_prefs_browsertest.cc
+++ b/chrome/browser/policy/policy_prefs_browsertest.cc
@@ -80,8 +80,6 @@
   void SetUpInProcessBrowserTestFixture() override {
     EXPECT_CALL(provider_, IsInitializationComplete(_))
         .WillRepeatedly(Return(true));
-    EXPECT_CALL(provider_, IsFirstPolicyLoadComplete(testing::_))
-        .WillRepeatedly(testing::Return(true));
     BrowserPolicyConnector::SetPolicyProviderForTesting(&provider_);
   }
 
diff --git a/chrome/browser/resources/BUILD.gn b/chrome/browser/resources/BUILD.gn
index 398c465..08cde4a 100644
--- a/chrome/browser/resources/BUILD.gn
+++ b/chrome/browser/resources/BUILD.gn
@@ -52,7 +52,7 @@
         "settings:closure_compile",
         "settings:closure_compile_module",
         "signin:closure_compile",
-        "tab_search_merge:closure_compile",
+        "tab_search:closure_compile",
         "usb_internals:closure_compile",
         "user_manager:closure_compile",
         "web_app_internals:closure_compile",
@@ -225,9 +225,9 @@
       "-E",
       "root_gen_dir=" + rebase_path(root_gen_dir, root_build_dir),
     ]
-    source = "tab_search_merge/tab_search_resources.grd"
+    source = "tab_search/tab_search_resources.grd"
     deps = [
-      "//chrome/browser/resources/tab_search_merge:web_components",
+      "//chrome/browser/resources/tab_search:web_components",
       "//chrome/browser/ui/webui/tab_search:mojo_bindings_js",
     ]
     defines = chrome_grit_defines
diff --git a/chrome/browser/resources/bookmarks/command_manager.js b/chrome/browser/resources/bookmarks/command_manager.js
index 380391c0..de2338fb 100644
--- a/chrome/browser/resources/bookmarks/command_manager.js
+++ b/chrome/browser/resources/bookmarks/command_manager.js
@@ -423,9 +423,6 @@
             assert(state.selectedFolder));
         getToastManager().querySelector('dom-if').if = true;
         getToastManager().show(loadTimeData.getString('toastFolderSorted'));
-        this.fire('iron-announce', {
-          text: loadTimeData.getString('undoDescription'),
-        });
         break;
       case Command.ADD_BOOKMARK:
         /** @type {!BookmarksEditDialogElement} */ (this.$.editDialog.get())
@@ -843,11 +840,6 @@
         });
     getToastManager().querySelector('dom-if').if = canUndo;
     getToastManager().showForStringPieces(pieces);
-    if (canUndo) {
-      this.fire('iron-announce', {
-        text: loadTimeData.getString('undoDescription'),
-      });
-    }
   },
 
   /**
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/i_tutorial/components/i_tutorial.js b/chrome/browser/resources/chromeos/accessibility/chromevox/i_tutorial/components/i_tutorial.js
index 24765fac4..2e4ede2 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/i_tutorial/components/i_tutorial.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/i_tutorial/components/i_tutorial.js
@@ -289,9 +289,8 @@
               or Search + Space. Then use the Up and Down arrow keys to select
               an item. Finally, collapse the list by pressing Enter or Search +
               Space.`,
-            `You can try this out in the Practice Area. To continue, use Search
-              + Right or Tab to find the Next lesson button. Then press Search
-              + Space or Enter to activate.`
+            `Press Search + Right arrow to find the practice area or the Next
+              lesson button. Then press Search + Space to activate.`
           ],
           medium: InteractionMedium.KEYBOARD,
           curriculums: [Curriculum.QUICK_ORIENTATION],
diff --git a/chrome/browser/resources/downloads/BUILD.gn b/chrome/browser/resources/downloads/BUILD.gn
index c16e142..d0f66c2 100644
--- a/chrome/browser/resources/downloads/BUILD.gn
+++ b/chrome/browser/resources/downloads/BUILD.gn
@@ -26,7 +26,7 @@
     out_manifest = "$target_gen_dir/$build_manifest"
     excludes = [
       "chrome://resources/js/cr.m.js",
-      "chrome://resources/mojo/mojo/public/js/mojo_bindings_lite.js",
+      "chrome://resources/mojo/mojo/public/js/bindings.js",
     ]
 
     deps = [
@@ -93,11 +93,11 @@
 }
 
 preprocess_grit("preprocess_mojo") {
-  deps = [ "//chrome/browser/ui/webui/downloads:mojo_bindings_js" ]
-  in_folder = get_path_info("../../ui/webui/downloads/", "gen_dir")
+  deps = [ "//chrome/browser/ui/webui/downloads:mojo_bindings_webui_js" ]
+  in_folder = "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/downloads/"
   out_folder = "$target_gen_dir/$preprocess_folder"
   out_manifest = "$target_gen_dir/$preprocess_mojo_manifest"
-  in_files = [ "downloads.mojom-lite.js" ]
+  in_files = [ "downloads.mojom-webui.js" ]
 }
 
 grit("downloads_resources") {
@@ -135,11 +135,12 @@
     ":search_service",
     ":toolbar",
   ]
+  closure_flags = mojom_js_args
 }
 
 js_library("browser_proxy") {
   deps = [
-    "//chrome/browser/ui/webui/downloads:mojo_bindings_js_library_for_compile",
+    "//chrome/browser/ui/webui/downloads:mojo_bindings_webui_js",
     "//ui/webui/resources/js:cr.m",
   ]
   externs_list = [ "externs.js" ]
@@ -177,7 +178,6 @@
     ":item",
     ":search_service",
     ":toolbar",
-    "//third_party/polymer/v3_0/components-chromium/iron-a11y-announcer",
     "//third_party/polymer/v3_0/components-chromium/iron-list",
     "//ui/webui/resources/cr_elements:find_shortcut_behavior.m",
     "//ui/webui/resources/cr_elements/cr_toast:cr_toast_manager.m",
diff --git a/chrome/browser/resources/downloads/browser_proxy.js b/chrome/browser/resources/downloads/browser_proxy.js
index d607f35f..8384cc6 100644
--- a/chrome/browser/resources/downloads/browser_proxy.js
+++ b/chrome/browser/resources/downloads/browser_proxy.js
@@ -2,20 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'chrome://resources/mojo/mojo/public/js/mojo_bindings_lite.js';
-import './downloads.mojom-lite.js';
-
 import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
 
+import {PageCallbackRouter, PageHandlerFactory, PageHandlerRemote} from './downloads.mojom-webui.js';
+
 export class BrowserProxy {
   constructor() {
-    /** @type {downloads.mojom.PageCallbackRouter} */
-    this.callbackRouter = new downloads.mojom.PageCallbackRouter();
+    /** @type {PageCallbackRouter} */
+    this.callbackRouter = new PageCallbackRouter();
 
-    /** @type {downloads.mojom.PageHandlerRemote} */
-    this.handler = new downloads.mojom.PageHandlerRemote();
+    /** @type {PageHandlerRemote} */
+    this.handler = new PageHandlerRemote();
 
-    const factory = downloads.mojom.PageHandlerFactory.getRemote();
+    const factory = PageHandlerFactory.getRemote();
     factory.createPageHandler(
         this.callbackRouter.$.bindNewPipeAndPassRemote(),
         this.handler.$.bindNewPipeAndPassReceiver());
diff --git a/chrome/browser/resources/downloads/downloads.js b/chrome/browser/resources/downloads/downloads.js
index 39dc5d6..9f65f8a1 100644
--- a/chrome/browser/resources/downloads/downloads.js
+++ b/chrome/browser/resources/downloads/downloads.js
@@ -6,5 +6,6 @@
 
 export {BrowserProxy} from './browser_proxy.js';
 export {DangerType, States} from './constants.js';
+export {PageCallbackRouter, PageHandlerInterface, PageInterface, PageRemote} from './downloads.mojom-webui.js';
 export {IconLoader} from './icon_loader.js';
 export {SearchService} from './search_service.js';
diff --git a/chrome/browser/resources/downloads/item.js b/chrome/browser/resources/downloads/item.js
index 1d5b3e4..774b345 100644
--- a/chrome/browser/resources/downloads/item.js
+++ b/chrome/browser/resources/downloads/item.js
@@ -22,11 +22,11 @@
 import {focusWithoutInk} from 'chrome://resources/js/cr/ui/focus_without_ink.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {HTMLEscape} from 'chrome://resources/js/util.m.js';
-import {IronA11yAnnouncer} from 'chrome://resources/polymer/v3_0/iron-a11y-announcer/iron-a11y-announcer.js';
-import {afterNextRender, beforeNextRender, html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {beforeNextRender, html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {BrowserProxy} from './browser_proxy.js';
 import {DangerType, States} from './constants.js';
+import {PageHandlerInterface} from './downloads.mojom-webui.js';
 import {IconLoader} from './icon_loader.js';
 
 Polymer({
@@ -138,20 +138,13 @@
     'restoreFocusAfterCancelIfNeeded_(data)',
   ],
 
-  /** @private {downloads.mojom.PageHandlerInterface} */
+  /** @private {PageHandlerInterface} */
   mojoHandler_: null,
 
   /** @private {boolean} */
   restoreFocusAfterCancel_: false,
 
   /** @override */
-  attached() {
-    afterNextRender(this, function() {
-      IronA11yAnnouncer.requestAvailability();
-    });
-  },
-
-  /** @override */
   ready() {
     this.mojoHandler_ = BrowserProxy.getInstance().handler;
     this.content = this.$.content;
@@ -640,11 +633,6 @@
          *                 arg: (string|null)}>}
          */
         (pieces), /* hideSlotted= */ !canUndo);
-    if (canUndo) {
-      this.fire('iron-announce', {
-        text: loadTimeData.getString('undoDescription'),
-      });
-    }
   },
 
   /** @private */
diff --git a/chrome/browser/resources/downloads/manager.js b/chrome/browser/resources/downloads/manager.js
index b918c1b..d955ee5 100644
--- a/chrome/browser/resources/downloads/manager.js
+++ b/chrome/browser/resources/downloads/manager.js
@@ -19,11 +19,11 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {PromiseResolver} from 'chrome://resources/js/promise_resolver.m.js';
 import {queryRequiredElement} from 'chrome://resources/js/util.m.js';
-import {IronA11yAnnouncer} from 'chrome://resources/polymer/v3_0/iron-a11y-announcer/iron-a11y-announcer.js';
-import {afterNextRender, html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {BrowserProxy} from './browser_proxy.js';
 import {States} from './constants.js';
+import {PageCallbackRouter, PageHandlerInterface} from './downloads.mojom-webui.js';
 import {SearchService} from './search_service.js';
 
 Polymer({
@@ -85,10 +85,10 @@
     'itemsChanged_(items_.*)',
   ],
 
-  /** @private {downloads.mojom.PageCallbackRouter} */
+  /** @private {PageCallbackRouter} */
   mojoEventTarget_: null,
 
-  /** @private {downloads.mojom.PageHandlerInterface} */
+  /** @private {PageHandlerInterface} */
   mojoHandler_: null,
 
   /** @private {?SearchService} */
@@ -143,10 +143,6 @@
     });
 
     this.searchService_.loadMore();
-
-    afterNextRender(this, function() {
-      IronA11yAnnouncer.requestAvailability();
-    });
   },
 
   /** @override */
@@ -270,11 +266,6 @@
         this.items_.some(data => !data.isDangerous && !data.isMixedContent);
     getToastManager().show(loadTimeData.getString('toastClearedAll'),
         /* hideSlotted= */ !canUndo);
-    if (canUndo) {
-      this.fire('iron-announce', {
-        text: loadTimeData.getString('undoDescription'),
-      });
-    }
   },
 
   /** @private */
diff --git a/chrome/browser/resources/downloads/search_service.js b/chrome/browser/resources/downloads/search_service.js
index 5a92fa29..2353afc 100644
--- a/chrome/browser/resources/downloads/search_service.js
+++ b/chrome/browser/resources/downloads/search_service.js
@@ -6,13 +6,14 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 
 import {BrowserProxy} from './browser_proxy.js';
+import {PageHandlerInterface} from './downloads.mojom-webui.js';
 
 export class SearchService {
   constructor() {
     /** @private {!Array<string>} */
     this.searchTerms_ = [];
 
-    /** @private {downloads.mojom.PageHandlerInterface} */
+    /** @private {PageHandlerInterface} */
     this.mojoHandler_ = BrowserProxy.getInstance().handler;
   }
 
diff --git a/chrome/browser/resources/downloads/toolbar.js b/chrome/browser/resources/downloads/toolbar.js
index 2dfdab1..a14fbf8 100644
--- a/chrome/browser/resources/downloads/toolbar.js
+++ b/chrome/browser/resources/downloads/toolbar.js
@@ -18,6 +18,7 @@
 import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {BrowserProxy} from './browser_proxy.js';
+import {PageHandlerInterface} from './downloads.mojom-webui.js';
 import {SearchService} from './search_service.js';
 
 Polymer({
@@ -44,7 +45,7 @@
     },
   },
 
-  /** @private {?downloads.mojom.PageHandlerInterface} */
+  /** @private {?PageHandlerInterface} */
   mojoHandler_: null,
 
   /** @override */
@@ -90,11 +91,6 @@
         this.items.some(data => !data.isDangerous && !data.isMixedContent);
     getToastManager().show(loadTimeData.getString('toastClearedAll'),
         /* hideSlotted= */ !canUndo);
-    if (canUndo) {
-      this.fire('iron-announce', {
-        text: loadTimeData.getString('undoDescription'),
-      });
-    }
   },
 
   /** @private */
diff --git a/chrome/browser/resources/new_tab_page/most_visited.html b/chrome/browser/resources/new_tab_page/most_visited.html
index 08bac346e..7df3737 100644
--- a/chrome/browser/resources/new_tab_page/most_visited.html
+++ b/chrome/browser/resources/new_tab_page/most_visited.html
@@ -112,6 +112,8 @@
     border-radius: 12px;
     color: var(--ntp-theme-text-color);
     display: flex;
+    height: var(--title-height);
+    line-height: calc(var(--title-height) / 2);
     margin-top: 16px;
     padding: 0 8px;
     width: 88px;
@@ -124,8 +126,6 @@
 
   .tile-title span {
     font-weight: 400;
-    height: var(--title-height);
-    line-height: calc(var(--title-height) / 2);
     overflow: hidden;
     text-align: center;
     text-overflow: ellipsis;
diff --git a/chrome/browser/resources/settings/autofill_page/passwords_list_handler.js b/chrome/browser/resources/settings/autofill_page/passwords_list_handler.js
index 0082dea..3cb775a 100644
--- a/chrome/browser/resources/settings/autofill_page/passwords_list_handler.js
+++ b/chrome/browser/resources/settings/autofill_page/passwords_list_handler.js
@@ -334,8 +334,6 @@
       }
     }
     this.$.toast.show();
-    this.fire('iron-announce', {text: this.removalNotification_});
-    this.fire('iron-announce', {text: this.i18n('undoDescription')});
   },
 
   /** @private */
diff --git a/chrome/browser/resources/settings/chromeos/.eslintrc.js b/chrome/browser/resources/settings/chromeos/.eslintrc.js
deleted file mode 100644
index f5f3adc..0000000
--- a/chrome/browser/resources/settings/chromeos/.eslintrc.js
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-module.exports = {
-  'env': {
-    'browser': true,
-    'es6': true,
-  },
-  'rules': {'eqeqeq': 'off'},
-};
diff --git a/chrome/browser/resources/settings/chromeos/bluetooth_page/bluetooth_device_list_item.js b/chrome/browser/resources/settings/chromeos/bluetooth_page/bluetooth_device_list_item.js
index 1a9cf089..66fbce9 100644
--- a/chrome/browser/resources/settings/chromeos/bluetooth_page/bluetooth_device_list_item.js
+++ b/chrome/browser/resources/settings/chromeos/bluetooth_page/bluetooth_device_list_item.js
@@ -38,7 +38,7 @@
    * @private
    */
   ignoreEnterKey_(event) {
-    if (event.key == 'Enter') {
+    if (event.key === 'Enter') {
       event.stopPropagation();
     }
   },
@@ -65,7 +65,7 @@
    * @private
    */
   onKeyDown_(e) {
-    if (e.key == 'Enter' || e.key == ' ') {
+    if (e.key === 'Enter' || e.key === ' ') {
       this.tryConnect_();
       e.preventDefault();
     }
diff --git a/chrome/browser/resources/settings/chromeos/bluetooth_page/bluetooth_page.js b/chrome/browser/resources/settings/chromeos/bluetooth_page/bluetooth_page.js
index c04234c..18de73c 100644
--- a/chrome/browser/resources/settings/chromeos/bluetooth_page/bluetooth_page.js
+++ b/chrome/browser/resources/settings/chromeos/bluetooth_page/bluetooth_page.js
@@ -185,7 +185,7 @@
    */
   currentRouteChanged(newRoute, oldRoute) {
     // Does not apply to this page.
-    if (newRoute != settings.routes.BLUETOOTH) {
+    if (newRoute !== settings.routes.BLUETOOTH) {
       return;
     }
 
@@ -274,7 +274,7 @@
   /** @private */
   bluetoothToggleStateChanged_() {
     if (!this.adapterState_ || !this.isToggleEnabled_() ||
-        this.bluetoothToggleState_ == this.adapterState_.powered) {
+        this.bluetoothToggleState_ === this.adapterState_.powered) {
       return;
     }
     this.stateChangeInProgress_ = true;
@@ -285,7 +285,7 @@
           this.stateChangeInProgress_ = false;
 
           const error = chrome.runtime.lastError;
-          if (error && error != 'Error setting adapter properties: powered') {
+          if (error && error !== 'Error setting adapter properties: powered') {
             console.error('Error enabling bluetooth: ' + error.message);
             return;
           }
diff --git a/chrome/browser/resources/settings/chromeos/bluetooth_page/bluetooth_subpage.js b/chrome/browser/resources/settings/chromeos/bluetooth_page/bluetooth_subpage.js
index bad71e8c..5dc8636 100644
--- a/chrome/browser/resources/settings/chromeos/bluetooth_page/bluetooth_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/bluetooth_page/bluetooth_subpage.js
@@ -202,12 +202,12 @@
     // button on a paired device), FocusRowBehavior prevents the Focus Row from
     // being focused. We clear lastFocused_ so that we can focus the row (such
     // as a paired/unpaired device).
-    if (settingId ==
+    if (settingId ===
             chromeos.settings.mojom.Setting.kBluetoothConnectToDevice ||
-        settingId ==
+        settingId ===
             chromeos.settings.mojom.Setting.kBluetoothDisconnectFromDevice ||
-        settingId == chromeos.settings.mojom.Setting.kBluetoothPairDevice ||
-        settingId == chromeos.settings.mojom.Setting.kBluetoothUnpairDevice) {
+        settingId === chromeos.settings.mojom.Setting.kBluetoothPairDevice ||
+        settingId === chromeos.settings.mojom.Setting.kBluetoothUnpairDevice) {
       this.lastFocused_ = null;
     }
     // Should continue with deep link attempt.
@@ -235,7 +235,7 @@
     this.startOrStopRefreshingDeviceList_();
 
     // Does not apply to this page.
-    if (route != settings.routes.BLUETOOTH_DEVICES) {
+    if (route !== settings.routes.BLUETOOTH_DEVICES) {
       return;
     }
 
@@ -315,7 +315,7 @@
     if (!this.adapterState || !this.adapterState.powered) {
       return;
     }
-    if (settings.Router.getInstance().getCurrentRoute() ==
+    if (settings.Router.getInstance().getCurrentRoute() ===
         settings.routes.BLUETOOTH_DEVICES) {
       this.startDiscovery_();
     } else {
@@ -332,7 +332,7 @@
     this.bluetooth.startDiscovery(function() {
       const lastError = chrome.runtime.lastError;
       if (lastError) {
-        if (lastError.message == 'Starting discovery failed') {
+        if (lastError.message === 'Starting discovery failed') {
           return;
         }  // May happen if also started elsewhere, ignore.
         console.error('startDiscovery Error: ' + lastError.message);
@@ -349,7 +349,7 @@
     this.bluetooth.stopDiscovery(function() {
       const lastError = chrome.runtime.lastError;
       if (lastError) {
-        if (lastError.message == 'Failed to stop discovery') {
+        if (lastError.message === 'Failed to stop discovery') {
           return;
         }  // May happen if also stopped elsewhere, ignore.
         console.error('stopDiscovery Error: ' + lastError.message);
@@ -367,11 +367,11 @@
   onDeviceEvent_(e) {
     const action = e.detail.action;
     const device = e.detail.device;
-    if (action == 'connect') {
+    if (action === 'connect') {
       this.connectDevice_(device);
-    } else if (action == 'disconnect') {
+    } else if (action === 'disconnect') {
       this.disconnectDevice_(device);
-    } else if (action == 'remove') {
+    } else if (action === 'remove') {
       this.forgetDevice_(device);
     } else {
       console.error('Unexected action: ' + action);
@@ -428,7 +428,7 @@
    * @private
    */
   showNoDevices_(bluetoothToggleState, deviceList) {
-    return bluetoothToggleState && deviceList.length == 0;
+    return bluetoothToggleState && deviceList.length === 0;
   },
 
   /**
@@ -460,7 +460,7 @@
       }
 
       // If |pairingDevice_| has changed, ignore the connect result.
-      if (this.pairingDevice_ && address != this.pairingDevice_.address) {
+      if (this.pairingDevice_ && address !== this.pairingDevice_.address) {
         return;
       }
 
@@ -471,7 +471,7 @@
               result)) {
         this.openDialog_();
       } else if (
-          result != chrome.bluetoothPrivate.ConnectResultType.IN_PROGRESS) {
+          result !== chrome.bluetoothPrivate.ConnectResultType.IN_PROGRESS) {
         this.$.deviceDialog.close();
       }
     });
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_page.js b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_page.js
index 4d16a4b..a5847f5 100644
--- a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_page.js
+++ b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_page.js
@@ -123,7 +123,7 @@
   /** @private */
   onSubpageTap_(event) {
     // We do not open the subpage if the click was on a link.
-    if (event.target && event.target.tagName == 'A') {
+    if (event.target && event.target.tagName === 'A') {
       event.stopPropagation();
       return;
     }
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_port_forwarding.html b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_port_forwarding.html
index ab2bd73..9c9708e 100644
--- a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_port_forwarding.html
+++ b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_port_forwarding.html
@@ -180,7 +180,7 @@
         </cr-action-menu>
       </template>
     </cr-lazy-render>
-    <cr-toast id="errorToast" duration="3000" role="alert">
+    <cr-toast id="errorToast" duration="3000">
       <div class="error-message" id="errorMessage">
         <iron-icon id="errorIcon" icon="cr:error-outline"></iron-icon>
         $i18n{crostiniPortForwardingActivatePortError}
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_port_forwarding_add_port_dialog.js b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_port_forwarding_add_port_dialog.js
index 45b30b1b..d4e98810 100644
--- a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_port_forwarding_add_port_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_port_forwarding_add_port_dialog.js
@@ -120,9 +120,9 @@
       return PortState.INVALID;
     }
     if (this.allPorts.find(
-            portSetting =>
-                portSetting.port_number == this.$.portNumberInput.value &&
-                portSetting.protocol_type == this.inputProtocolIndex_)) {
+            portSetting => portSetting.port_number ===
+                    Number(this.$.portNumberInput.value) &&
+                portSetting.protocol_type === this.inputProtocolIndex_)) {
       return PortState.DUPLICATE;
     }
     return PortState.VALID;
@@ -146,7 +146,7 @@
   /** @private */
   onAddTap_: function() {
     this.portState_ = this.computePortState_();
-    if (!this.portState_ == PortState.VALID) {
+    if (this.portState_ !== PortState.VALID) {
       return;
     }
     const portNumber = +this.$.portNumberInput.value;
@@ -170,7 +170,7 @@
 
   /** @private */
   onPortStateChanged_: function() {
-    if (this.portState_ == PortState.VALID) {
+    if (this.portState_ === PortState.VALID) {
       this.$.portNumberInput.invalid = false;
       this.$.continue.disabled = false;
       return;
@@ -178,4 +178,4 @@
     this.$.portNumberInput.invalid = true;
     this.$.continue.disabled = true;
   }
-});
\ No newline at end of file
+});
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_subpage.js b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_subpage.js
index 4f06ab0..4f220d9 100644
--- a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_subpage.js
@@ -263,7 +263,7 @@
   /** @private */
   onCrostiniEnabledChanged_(enabled) {
     if (!enabled &&
-        settings.Router.getInstance().getCurrentRoute() ==
+        settings.Router.getInstance().getCurrentRoute() ===
             settings.routes.CROSTINI_DETAILS) {
       settings.Router.getInstance().navigateToPreviousRoute();
     }
diff --git a/chrome/browser/resources/settings/chromeos/date_time_page/timezone_selector.js b/chrome/browser/resources/settings/chromeos/date_time_page/timezone_selector.js
index 09a74deb..634767d 100644
--- a/chrome/browser/resources/settings/chromeos/date_time_page/timezone_selector.js
+++ b/chrome/browser/resources/settings/chromeos/date_time_page/timezone_selector.js
@@ -79,7 +79,7 @@
       /* This method is called as observer. Skip if if current mode does not
        * match expected.
        */
-      if (perUserTimeZoneMode !=
+      if (perUserTimeZoneMode !==
           this.getPref('cros.flags.per_user_timezone_enabled').value) {
         return;
       }
@@ -99,7 +99,7 @@
             .value) {
       const isPerUserTimezone =
           this.getPref('cros.flags.per_user_timezone_enabled').value;
-      if (this.timeZoneList_[0].value ==
+      if (this.timeZoneList_[0].value ===
           (isPerUserTimezone ? this.getPref('settings.timezone').value :
                                this.getPref('cros.system.timezone').value)) {
         return;
@@ -156,7 +156,7 @@
    */
   updateActiveTimeZoneName_(activeTimeZoneId) {
     const activeTimeZone = this.timeZoneList_.find(
-        (timeZone) => timeZone.value == activeTimeZoneId);
+        (timeZone) => timeZone.value.toString() === activeTimeZoneId);
     if (activeTimeZone) {
       this.activeTimeZoneDisplayName = activeTimeZone.name;
     }
diff --git a/chrome/browser/resources/settings/chromeos/date_time_page/timezone_subpage.js b/chrome/browser/resources/settings/chromeos/date_time_page/timezone_subpage.js
index fdbc059..a75ba34 100644
--- a/chrome/browser/resources/settings/chromeos/date_time_page/timezone_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/date_time_page/timezone_subpage.js
@@ -80,7 +80,7 @@
         this.getPref('generated.resolve_timezone_by_geolocation_method_short');
     // Make sure current value is in the list, even if it is not
     // user-selectable.
-    if (pref.value == settings.TimeZoneAutoDetectMethod.DISABLED) {
+    if (pref.value === settings.TimeZoneAutoDetectMethod.DISABLED) {
       // If disabled by policy, show the 'Automatic timezone disabled' label.
       // Otherwise, just show the default string, since the control will be
       // disabled as well.
@@ -95,7 +95,7 @@
       name: loadTimeData.getString('setTimeZoneAutomaticallyIpOnlyDefault')
     });
 
-    if (pref.value ==
+    if (pref.value ===
         settings.TimeZoneAutoDetectMethod.SEND_WIFI_ACCESS_POINTS) {
       result.push({
         value: settings.TimeZoneAutoDetectMethod.SEND_WIFI_ACCESS_POINTS,
diff --git a/chrome/browser/resources/settings/chromeos/device_page/device_page.js b/chrome/browser/resources/settings/chromeos/device_page/device_page.js
index 1146521..fec7724d 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/device_page.js
+++ b/chrome/browser/resources/settings/chromeos/device_page/device_page.js
@@ -219,7 +219,7 @@
     // Check that the properties have explicitly been set to false.
     if (this.hasMouse_ === false && this.hasPointingStick_ === false &&
         this.hasTouchpad_ === false &&
-        settings.Router.getInstance().getCurrentRoute() ==
+        settings.Router.getInstance().getCurrentRoute() ===
             settings.routes.POINTERS) {
       settings.Router.getInstance().navigateTo(settings.routes.DEVICE);
     }
diff --git a/chrome/browser/resources/settings/chromeos/device_page/display.js b/chrome/browser/resources/settings/chromeos/device_page/display.js
index c7090d68..325ad9f 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/display.js
+++ b/chrome/browser/resources/settings/chromeos/device_page/display.js
@@ -363,8 +363,8 @@
       this.currentRoute_ = opt_newRoute;
 
       // When navigating away from the page, deselect any selected display.
-      if (opt_newRoute != settings.routes.DISPLAY &&
-          opt_oldRoute == settings.routes.DISPLAY) {
+      if (opt_newRoute !== settings.routes.DISPLAY &&
+          opt_oldRoute === settings.routes.DISPLAY) {
         this.browserProxy_.highlightDisplay(this.invalidDisplayId_);
         return;
       }
@@ -647,7 +647,7 @@
             this.getParentModeIndex_(modes.get(mode.width).get(mode.height));
         this.modeToParentModeMap_.set(i, parentModeIndex);
       }
-      assert(this.modeToParentModeMap_.size == selectedDisplay.modes.length);
+      assert(this.modeToParentModeMap_.size === selectedDisplay.modes.length);
 
       // Use the new sort order.
       this.sortResolutionList_();
@@ -815,7 +815,7 @@
       this.currentSelectedParentModeIndex_ = -1;
       const numModes = selectedDisplay.modes.length;
       this.modeValues_ =
-          numModes == 0 ? [] : Array.from(Array(numModes).keys());
+          numModes === 0 ? [] : Array.from(Array(numModes).keys());
 
       // Note that the display zoom values has the same number of ticks for all
       // displays, so the above problem doesn't apply here.
@@ -944,7 +944,7 @@
      * @private
      */
     getDisplaySelectMenuIndex_(selectedDisplay, primaryDisplayId) {
-      if (selectedDisplay && selectedDisplay.id == primaryDisplayId) {
+      if (selectedDisplay && selectedDisplay.id === primaryDisplayId) {
         return 0;
       }
       return 1;
@@ -1020,7 +1020,7 @@
      * @private
      */
     isSelected_(display, selectedDisplay) {
-      return display.id == selectedDisplay.id;
+      return display.id === selectedDisplay.id;
     },
 
     /**
@@ -1056,12 +1056,12 @@
 
       // Things work differently for full HD devices(1080p). The best mode is
       // the one with 1.25 device scale factor and 0.8 ui scale.
-      if (mode.heightInNativePixels == 1080) {
+      if (mode.heightInNativePixels === 1080) {
         return Math.abs(mode.uiScale - 0.8) < 0.001 &&
             Math.abs(mode.deviceScaleFactor - 1.25) < 0.001;
       }
 
-      return mode.uiScale == 1.0;
+      return mode.uiScale === 1.0;
     },
 
     /**
@@ -1069,8 +1069,8 @@
      * @private
      */
     getResolutionText_() {
-      if (this.selectedDisplay.modes.length == 0 ||
-          this.currentSelectedModeIndex_ == -1) {
+      if (this.selectedDisplay.modes.length === 0 ||
+          this.currentSelectedModeIndex_ === -1) {
         // If currentSelectedModeIndex_ == -1, selectedDisplay and
         // |selectedModePref_.value| are not in sync.
         return this.i18n(
@@ -1140,7 +1140,7 @@
       const mode = this.selectedDisplay.modes[this.currentSelectedModeIndex_];
       const bounds = this.selectedDisplay.bounds;
 
-      return bounds.width > bounds.height !=
+      return bounds.width > bounds.height !==
           mode.widthInNativePixels > mode.heightInNativePixels;
     },
 
@@ -1170,8 +1170,8 @@
       const id = e.detail;
       for (let i = 0; i < this.displays.length; ++i) {
         const display = this.displays[i];
-        if (id == display.id) {
-          if (this.selectedDisplay != display) {
+        if (id === display.id) {
+          if (this.selectedDisplay !== display) {
             this.setSelectedDisplay_(display);
           }
           return;
@@ -1182,7 +1182,7 @@
     /** @private */
     onSelectDisplayTab_() {
       const {selected} = this.$$('cr-tabs');
-      if (this.selectedTab_ != selected) {
+      if (this.selectedTab_ !== selected) {
         this.setSelectedDisplay_(this.displays[selected]);
       }
     },
@@ -1207,10 +1207,10 @@
       if (!this.selectedDisplay) {
         return;
       }
-      if (this.selectedDisplay.id == this.primaryDisplayId) {
+      if (this.selectedDisplay.id === this.primaryDisplayId) {
         return;
       }
-      if (e.target.value != PRIMARY_DISP_IDX) {
+      if (!e.target.value) {
         return;
       }
 
@@ -1228,7 +1228,7 @@
      * @private
      */
     onSelectedParentModeChange_(newModeIndex) {
-      if (this.currentSelectedParentModeIndex_ == newModeIndex) {
+      if (this.currentSelectedParentModeIndex_ === newModeIndex) {
         return;
       }
 
@@ -1249,11 +1249,11 @@
      * @private
      */
     hasNewParentModeBeenSet() {
-      if (this.currentSelectedParentModeIndex_ == -1) {
+      if (this.currentSelectedParentModeIndex_ === -1) {
         return false;
       }
 
-      return this.currentSelectedParentModeIndex_ !=
+      return this.currentSelectedParentModeIndex_ !==
           this.selectedParentModePref_.value;
     },
 
@@ -1264,16 +1264,16 @@
      * @private
      */
     hasNewModeBeenSet() {
-      if (this.currentSelectedModeIndex_ == -1) {
+      if (this.currentSelectedModeIndex_ === -1) {
         return false;
       }
 
-      if (this.currentSelectedParentModeIndex_ !=
+      if (this.currentSelectedParentModeIndex_ !==
           this.selectedParentModePref_.value) {
         return true;
       }
 
-      return this.currentSelectedModeIndex_ != this.selectedModePref_.value;
+      return this.currentSelectedModeIndex_ !== this.selectedModePref_.value;
     },
 
     /**
@@ -1284,7 +1284,7 @@
     onSelectedModeChange_(newModeIndex) {
       // We want to ignore all value changes to the pref due to the slider being
       // dragged. See http://crbug/845712 for more info.
-      if (this.currentSelectedModeIndex_ == newModeIndex) {
+      if (this.currentSelectedModeIndex_ === newModeIndex) {
         return;
       }
 
@@ -1313,7 +1313,7 @@
      * @private
      */
     onSelectedZoomChange_() {
-      if (this.currentSelectedModeIndex_ == -1 || !this.selectedDisplay) {
+      if (this.currentSelectedModeIndex_ === -1 || !this.selectedDisplay) {
         return;
       }
 
@@ -1347,7 +1347,7 @@
       const target = /** @type {!HTMLSelectElement} */ (event.target);
       const value = /** @type {number} */ (parseInt(target.value, 10));
 
-      assert(value != -1 || this.selectedDisplay.isInTabletPhysicalState);
+      assert(value !== -1 || this.selectedDisplay.isInTabletPhysicalState);
 
       /** @type {!chrome.system.display.DisplayProperties} */ const properties =
           {rotation: value};
@@ -1416,7 +1416,7 @@
         if (display.isPrimary && !primaryDisplay) {
           primaryDisplay = display;
         }
-        if (this.selectedDisplay && display.id == this.selectedDisplay.id) {
+        if (this.selectedDisplay && display.id === this.selectedDisplay.id) {
           selectedDisplay = display;
         }
       }
@@ -1457,9 +1457,9 @@
     updateNightLightScheduleSettings_() {
       const scheduleType = this.getPref('ash.night_light.schedule_type').value;
       this.shouldOpenCustomScheduleCollapse_ =
-          scheduleType == NightLightScheduleType.CUSTOM;
+          scheduleType === NightLightScheduleType.CUSTOM;
 
-      if (scheduleType == NightLightScheduleType.SUNSET_TO_SUNRISE) {
+      if (scheduleType === NightLightScheduleType.SUNSET_TO_SUNRISE) {
         const nightLightStatus = this.getPref('ash.night_light.enabled').value;
         this.nightLightScheduleSubLabel_ = nightLightStatus ?
             this.i18n('displayNightLightOffAtSunrise') :
diff --git a/chrome/browser/resources/settings/chromeos/device_page/display_layout.js b/chrome/browser/resources/settings/chromeos/device_page/display_layout.js
index 735ea80..5aac980 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/display_layout.js
+++ b/chrome/browser/resources/settings/chromeos/device_page/display_layout.js
@@ -248,7 +248,7 @@
    * @private
    */
   isSelected_(display, selectedDisplay) {
-    return display.id == selectedDisplay.id;
+    return display.id === selectedDisplay.id;
   },
 
   /**
@@ -284,7 +284,7 @@
         this.browserProxy_.highlightDisplay(id);
       }
       // Make sure the dragged display is also selected.
-      if (id != this.selectedDisplay.id) {
+      if (id !== this.selectedDisplay.id) {
         this.fire('select-display', id);
       }
 
@@ -315,7 +315,7 @@
         this.lastDragCoordinates_.y = newBounds.top;
 
         // Only call dragDisplayDelta() when there is a change in position.
-        if (deltaX != 0 || deltaY != 0) {
+        if (deltaX !== 0 || deltaY !== 0) {
           this.browserProxy_.dragDisplayDelta(
               id, Math.round(deltaX), Math.round(deltaY));
         }
diff --git a/chrome/browser/resources/settings/chromeos/device_page/drag_behavior.js b/chrome/browser/resources/settings/chromeos/device_page/drag_behavior.js
index 674e857..64577a13 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/drag_behavior.js
+++ b/chrome/browser/resources/settings/chromeos/device_page/drag_behavior.js
@@ -131,7 +131,7 @@
    * @private
    */
   onMouseDown_(e) {
-    if (e.button != 0 || !e.target.getAttribute('draggable')) {
+    if (e.button !== 0 || !e.target.getAttribute('draggable')) {
       return true;
     }
     e.preventDefault();
@@ -155,7 +155,7 @@
    * @private
    */
   onTouchStart_(e) {
-    if (e.touches.length != 1) {
+    if (e.touches.length !== 1) {
       return false;
     }
 
@@ -172,7 +172,7 @@
    * @private
    */
   onTouchMove_(e) {
-    if (e.touches.length != 1) {
+    if (e.touches.length !== 1) {
       return true;
     }
 
diff --git a/chrome/browser/resources/settings/chromeos/device_page/layout_behavior.js b/chrome/browser/resources/settings/chromeos/device_page/layout_behavior.js
index a768dfd..280d2f1d 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/layout_behavior.js
+++ b/chrome/browser/resources/settings/chromeos/device_page/layout_behavior.js
@@ -113,8 +113,8 @@
     this.collideAndModifyDelta_(id, oldBounds, deltaPos);
 
     // If the edge changed, update and highlight it.
-    if (layoutPosition != this.dragLayoutPosition_ ||
-        closestId != this.dragParentId_) {
+    if (layoutPosition !== this.dragLayoutPosition_ ||
+        closestId !== this.dragParentId_) {
       this.dragLayoutPosition_ = layoutPosition;
       this.dragParentId_ = closestId;
       this.highlightEdge_(closestId, layoutPosition);
@@ -134,7 +134,7 @@
    */
   finishUpdateDisplayBounds(id) {
     this.highlightEdge_('', undefined);  // Remove any highlights.
-    if (id != this.dragLayoutId_ || !this.dragBounds_ ||
+    if (id !== this.dragLayoutId_ || !this.dragBounds_ ||
         !this.dragLayoutPosition_) {
       return;
     }
@@ -142,7 +142,7 @@
     const layout = this.displayLayoutMap_.get(id);
 
     let orphanIds;
-    if (!layout || layout.parentId == '') {
+    if (!layout || layout.parentId === '') {
       // Primary display. Set the calculated position to |dragBounds_|.
       this.setCalculatedDisplayBounds_(id, this.dragBounds_);
 
@@ -161,8 +161,8 @@
       // When re-parenting to a descendant, also parent any immediate child to
       // drag display's current parent.
       let topLayout = this.displayLayoutMap_.get(this.dragParentId_);
-      while (topLayout && topLayout.parentId != '') {
-        if (topLayout.parentId == id) {
+      while (topLayout && topLayout.parentId !== '') {
+        if (topLayout.parentId === id) {
           topLayout.parentId = layout.parentId;
           break;
         }
@@ -251,7 +251,7 @@
   reparentOrphan_(orphanId, otherOrphanIds) {
     const layout = this.displayLayoutMap_.get(orphanId);
     assert(layout);
-    if (orphanId == this.dragId && layout.parentId != '') {
+    if (orphanId === this.dragId && layout.parentId !== '') {
       this.setCalculatedDisplayBounds_(orphanId, this.dragBounds_);
       return;
     }
@@ -259,7 +259,7 @@
 
     // Find the closest parent.
     const newParentId = this.findClosest_(orphanId, bounds, otherOrphanIds);
-    assert(newParentId != '');
+    assert(newParentId !== '');
     layout.parentId = newParentId;
 
     // Find the closest edge.
@@ -297,7 +297,7 @@
     let children = [];
     this.displayLayoutMap_.forEach((value, key) => {
       const childId = key;
-      if (childId != parentId && value.parentId == parentId) {
+      if (childId !== parentId && value.parentId === parentId) {
         // Insert immediate children at the front of the array.
         children.unshift(childId);
         if (recurse) {
@@ -375,7 +375,7 @@
     const keys = this.calculatedBoundsMap_.keys();
     for (let iter = keys.next(); !iter.done; iter = keys.next()) {
       const otherId = iter.value;
-      if (otherId == displayId) {
+      if (otherId === displayId) {
         continue;
       }
       if (opt_ignoreIds && opt_ignoreIds.includes(otherId)) {
@@ -405,7 +405,7 @@
         dy = 0;
       }
       const delta2 = dx * dx + dy * dy;
-      if (closestId == '' || delta2 < closestDelta2) {
+      if (closestId === '' || delta2 < closestDelta2) {
         closestId = otherId;
         closestDelta2 = delta2;
       }
@@ -465,18 +465,18 @@
     const parentBounds = this.getCalculatedDisplayBounds(parentId);
 
     let x;
-    if (layoutPosition == chrome.system.display.LayoutPosition.LEFT) {
+    if (layoutPosition === chrome.system.display.LayoutPosition.LEFT) {
       x = parentBounds.left - bounds.width;
-    } else if (layoutPosition == chrome.system.display.LayoutPosition.RIGHT) {
+    } else if (layoutPosition === chrome.system.display.LayoutPosition.RIGHT) {
       x = parentBounds.left + parentBounds.width;
     } else {
       x = this.snapToX_(bounds, parentBounds);
     }
 
     let y;
-    if (layoutPosition == chrome.system.display.LayoutPosition.TOP) {
+    if (layoutPosition === chrome.system.display.LayoutPosition.TOP) {
       y = parentBounds.top - bounds.height;
-    } else if (layoutPosition == chrome.system.display.LayoutPosition.BOTTOM) {
+    } else if (layoutPosition === chrome.system.display.LayoutPosition.BOTTOM) {
       y = parentBounds.top + parentBounds.height;
     } else {
       y = this.snapToY_(bounds, parentBounds);
@@ -570,7 +570,7 @@
         const otherBounds = this.getCalculatedDisplayBounds(otherId);
         if (this.collideWithBoundsAndModifyDelta_(
                 bounds, otherBounds, deltaPos)) {
-          if (deltaPos.x == 0 && deltaPos.y == 0) {
+          if (deltaPos.x === 0 && deltaPos.y === 0) {
             return;
           }
           others.delete(otherId);
@@ -650,8 +650,8 @@
     // Offset is calculated from top or left edge.
     const parentBounds = this.getCalculatedDisplayBounds(layout.parentId);
     let offset, minOffset, maxOffset;
-    if (position == chrome.system.display.LayoutPosition.LEFT ||
-        position == chrome.system.display.LayoutPosition.RIGHT) {
+    if (position === chrome.system.display.LayoutPosition.LEFT ||
+        position === chrome.system.display.LayoutPosition.RIGHT) {
       offset = bounds.top - parentBounds.top;
       minOffset = -bounds.height;
       maxOffset = parentBounds.height;
@@ -708,22 +708,22 @@
   highlightEdge_(id, layoutPosition) {
     for (let i = 0; i < this.layouts.length; ++i) {
       const layout = this.layouts[i];
-      const highlight = (layout.id == id || layout.parentId == id) ?
+      const highlight = (layout.id === id || layout.parentId === id) ?
           layoutPosition :
           undefined;
       const div = id ? this.$$('#_' + id) : this.$$('#_' + layout.id);
       div.classList.toggle(
           'highlight-right',
-          highlight == chrome.system.display.LayoutPosition.RIGHT);
+          highlight === chrome.system.display.LayoutPosition.RIGHT);
       div.classList.toggle(
           'highlight-left',
-          highlight == chrome.system.display.LayoutPosition.LEFT);
+          highlight === chrome.system.display.LayoutPosition.LEFT);
       div.classList.toggle(
           'highlight-top',
-          highlight == chrome.system.display.LayoutPosition.TOP);
+          highlight === chrome.system.display.LayoutPosition.TOP);
       div.classList.toggle(
           'highlight-bottom',
-          highlight == chrome.system.display.LayoutPosition.BOTTOM);
+          highlight === chrome.system.display.LayoutPosition.BOTTOM);
     }
   },
 };
diff --git a/chrome/browser/resources/settings/chromeos/device_page/night_light_slider.js b/chrome/browser/resources/settings/chromeos/device_page/night_light_slider.js
index e8958cb..be94b004 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/night_light_slider.js
+++ b/chrome/browser/resources/settings/chromeos/device_page/night_light_slider.js
@@ -91,7 +91,7 @@
 
   /** @override */
   attached() {
-    this.isRTL_ = window.getComputedStyle(this).direction == 'rtl';
+    this.isRTL_ = window.getComputedStyle(this).direction === 'rtl';
 
     this.$.sliderContainer.addEventListener('contextmenu', function(e) {
       // Prevent the context menu from interfering with dragging the knobs using
@@ -116,7 +116,7 @@
   prefsAvailable() {
     return ['custom_start_time', 'custom_end_time']
         .map(key => `prefs.ash.night_light.${key}.value`)
-        .every(path => this.get(path) != undefined);
+        .every(path => this.get(path) !== undefined);
   },
 
   /** @private */
@@ -192,7 +192,8 @@
    */
   blurAnyFocusedKnob_() {
     const activeElement = this.shadowRoot.activeElement;
-    if (activeElement == this.$.startKnob || activeElement == this.$.endKnob) {
+    if (activeElement === this.$.startKnob ||
+        activeElement === this.$.endKnob) {
       activeElement.blur();
     }
   },
@@ -207,12 +208,12 @@
 
     // Only handle start or end knobs. Use the "knob-inner" divs just to display
     // the knobs.
-    if (event.target == this.$.startKnob ||
-        event.target == this.$.startKnob.firstElementChild) {
+    if (event.target === this.$.startKnob ||
+        event.target === this.$.startKnob.firstElementChild) {
       this.dragObject_ = this.$.startKnob;
     } else if (
-        event.target == this.$.endKnob ||
-        event.target == this.$.endKnob.firstElementChild) {
+        event.target === this.$.endKnob ||
+        event.target === this.$.endKnob.firstElementChild) {
       this.dragObject_ = this.$.endKnob;
     } else {
       return;
@@ -347,7 +348,7 @@
    */
   updateKnobs_() {
     if (!this.isReady_ || !this.prefsAvailable() ||
-        this.$.sliderBar.offsetWidth == 0) {
+        this.$.sliderBar.offsetWidth === 0) {
       return;
     }
     const startOffsetMinutes = /** @type {number} */ (
@@ -372,7 +373,7 @@
         TOTAL_MINUTES_PER_DAY;
     let ratio = offsetAfter6pm / TOTAL_MINUTES_PER_DAY;
 
-    if (ratio == 0) {
+    if (ratio === 0) {
       // If the ratio is 0, then there are two possibilities:
       // - The knob time is 6:00 PM on the left side of the slider.
       // - The knob time is 6:00 PM on the right side of the slider.
@@ -455,7 +456,7 @@
    * @private
    */
   getOtherKnobPrefValue_(prefPath) {
-    if (prefPath == 'ash.night_light.custom_start_time') {
+    if (prefPath === 'ash.night_light.custom_start_time') {
       return /** @type {number} */ (
           this.getPref('ash.night_light.custom_end_time').value);
     }
@@ -514,11 +515,11 @@
    * @private
    */
   getPrefPath_(knob) {
-    if (knob == this.$.startKnob) {
+    if (knob === this.$.startKnob) {
       return 'ash.night_light.custom_start_time';
     }
 
-    if (knob == this.$.endKnob) {
+    if (knob === this.$.endKnob) {
       return 'ash.night_light.custom_end_time';
     }
 
@@ -551,7 +552,8 @@
    */
   isEitherKnobFocused_() {
     const activeElement = this.shadowRoot.activeElement;
-    return activeElement == this.$.startKnob || activeElement == this.$.endKnob;
+    return activeElement === this.$.startKnob ||
+        activeElement === this.$.endKnob;
   },
 
   /**
@@ -601,7 +603,7 @@
       return;
     }
 
-    if (this._rippleContainer != knob) {
+    if (this._rippleContainer !== knob) {
       this.removeRipple_();
       knob.focus();
     }
@@ -639,14 +641,14 @@
    */
   onKeyDown_(event) {
     const activeElement = this.shadowRoot.activeElement;
-    if (event.key == 'Tab') {
-      if (event.shiftKey && this.$.endKnob == activeElement) {
+    if (event.key === 'Tab') {
+      if (event.shiftKey && this.$.endKnob === activeElement) {
         event.preventDefault();
         this.handleKnobEvent_(event, this.$.startKnob);
         return;
       }
 
-      if (!event.shiftKey && this.$.startKnob == activeElement) {
+      if (!event.shiftKey && this.$.startKnob === activeElement) {
         event.preventDefault();
         this.handleKnobEvent_(event, this.$.endKnob);
       }
diff --git a/chrome/browser/resources/settings/chromeos/device_page/pointers.js b/chrome/browser/resources/settings/chromeos/device_page/pointers.js
index 88d4767e..3c4cbd3 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/pointers.js
+++ b/chrome/browser/resources/settings/chromeos/device_page/pointers.js
@@ -204,7 +204,7 @@
       return;
     }
 
-    if (event.path[0].tagName == 'A') {
+    if (event.path[0].tagName === 'A') {
       // Do not toggle reverse scrolling if the contained link is clicked.
       event.stopPropagation();
     }
diff --git a/chrome/browser/resources/settings/chromeos/device_page/power.js b/chrome/browser/resources/settings/chromeos/device_page/power.js
index 438ba2e..ca7be24e8 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/power.js
+++ b/chrome/browser/resources/settings/chromeos/device_page/power.js
@@ -234,7 +234,7 @@
    * @private
    */
   hasSingleOption_(idleOptions) {
-    return idleOptions.length == 1;
+    return idleOptions.length === 1;
   },
 
   /** @private */
@@ -295,7 +295,7 @@
         // "Suspend" and "do nothing" share the "sleep" label and communicate
         // their state via the toggle state.
         this.lidClosedLabel_ = loadTimeData.getString('powerLidSleepLabel');
-        pref.value = behavior == settings.LidClosedBehavior.SUSPEND;
+        pref.value = behavior === settings.LidClosedBehavior.SUSPEND;
         break;
       case settings.LidClosedBehavior.STOP_SESSION:
         this.lidClosedLabel_ = loadTimeData.getString('powerLidSignOutLabel');
@@ -321,7 +321,7 @@
    * @private
    */
   getIdleOption_(idleBehavior, currIdleBehavior) {
-    const selected = idleBehavior == currIdleBehavior;
+    const selected = idleBehavior === currIdleBehavior;
     switch (idleBehavior) {
       case settings.IdleBehavior.DISPLAY_OFF_SLEEP:
         return {
diff --git a/chrome/browser/resources/settings/chromeos/device_page/storage.js b/chrome/browser/resources/settings/chromeos/device_page/storage.js
index 9fbf62d1..dc46333 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/storage.js
+++ b/chrome/browser/resources/settings/chromeos/device_page/storage.js
@@ -117,7 +117,7 @@
       settings.RouteOriginBehaviorImpl.currentRouteChanged.call(
           this, newRoute, oldRoute);
 
-      if (settings.Router.getInstance().getCurrentRoute() !=
+      if (settings.Router.getInstance().getCurrentRoute() !==
           settings.routes.STORAGE) {
         return;
       }
@@ -272,9 +272,9 @@
      */
     startPeriodicUpdate_() {
       // We update the storage usage every 5 seconds.
-      if (this.updateTimerId_ == -1) {
+      if (this.updateTimerId_ === -1) {
         this.updateTimerId_ = window.setInterval(() => {
-          if (settings.Router.getInstance().getCurrentRoute() !=
+          if (settings.Router.getInstance().getCurrentRoute() !==
               settings.routes.STORAGE) {
             this.stopPeriodicUpdate_();
             return;
@@ -289,7 +289,7 @@
      * @private
      */
     stopPeriodicUpdate_() {
-      if (this.updateTimerId_ != -1) {
+      if (this.updateTimerId_ !== -1) {
         window.clearInterval(this.updateTimerId_);
         this.updateTimerId_ = -1;
       }
@@ -302,7 +302,7 @@
      * @private
      */
     isSpaceLow_(spaceState) {
-      return spaceState == settings.StorageSpaceState.LOW;
+      return spaceState === settings.StorageSpaceState.LOW;
     },
 
     /**
@@ -312,7 +312,7 @@
      * @private
      */
     isSpaceCriticallyLow_(spaceState) {
-      return spaceState == settings.StorageSpaceState.CRITICALLY_LOW;
+      return spaceState === settings.StorageSpaceState.CRITICALLY_LOW;
     },
 
     /**
diff --git a/chrome/browser/resources/settings/chromeos/device_page/storage_external.js b/chrome/browser/resources/settings/chromeos/device_page/storage_external.js
index fdcd1c8..e54b882 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/storage_external.js
+++ b/chrome/browser/resources/settings/chromeos/device_page/storage_external.js
@@ -67,7 +67,7 @@
    */
   computeStorageListHeader_(externalStorages) {
     return this.i18n(
-        !externalStorages || externalStorages.length == 0 ?
+        !externalStorages || externalStorages.length === 0 ?
             'storageExternalStorageEmptyListHeader' :
             'storageExternalStorageListHeader');
   },
diff --git a/chrome/browser/resources/settings/chromeos/device_page/stylus.js b/chrome/browser/resources/settings/chromeos/device_page/stylus.js
index 87f182c..33f6a5d 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/stylus.js
+++ b/chrome/browser/resources/settings/chromeos/device_page/stylus.js
@@ -113,7 +113,7 @@
    */
   supportsLockScreen_() {
     return !!this.selectedApp_ &&
-        this.selectedApp_.lockScreenSupport !=
+        this.selectedApp_.lockScreenSupport !==
         settings.NoteAppLockScreenSupport.NOT_SUPPORTED;
   },
 
@@ -124,7 +124,7 @@
    */
   disallowedOnLockScreenByPolicy_() {
     return !!this.selectedApp_ &&
-        this.selectedApp_.lockScreenSupport ==
+        this.selectedApp_.lockScreenSupport ===
         settings.NoteAppLockScreenSupport.NOT_ALLOWED_BY_POLICY;
   },
 
@@ -135,7 +135,7 @@
    */
   lockScreenSupportEnabled_() {
     return !!this.selectedApp_ &&
-        this.selectedApp_.lockScreenSupport ==
+        this.selectedApp_.lockScreenSupport ===
         settings.NoteAppLockScreenSupport.ENABLED;
   },
 
@@ -162,7 +162,7 @@
    */
   findApp_(id) {
     return this.appChoices_.find(function(app) {
-      return app.value == id;
+      return app.value === id;
     }) ||
         null;
   },
@@ -174,15 +174,15 @@
    */
   toggleLockScreenSupport_() {
     assert(this.selectedApp_);
-    if (this.selectedApp_.lockScreenSupport !=
+    if (this.selectedApp_.lockScreenSupport !==
             settings.NoteAppLockScreenSupport.ENABLED &&
-        this.selectedApp_.lockScreenSupport !=
+        this.selectedApp_.lockScreenSupport !==
             settings.NoteAppLockScreenSupport.SUPPORTED) {
       return;
     }
 
     this.browserProxy_.setPreferredNoteTakingAppEnabledOnLockScreen(
-        this.selectedApp_.lockScreenSupport ==
+        this.selectedApp_.lockScreenSupport ===
         settings.NoteAppLockScreenSupport.SUPPORTED);
     settings.recordSettingChange();
   },
@@ -217,7 +217,7 @@
    * @private
    */
   showNoApps_(apps, waitingForAndroid) {
-    return apps.length == 0 && !waitingForAndroid;
+    return apps.length === 0 && !waitingForAndroid;
   },
 
   /**
diff --git a/chrome/browser/resources/settings/chromeos/google_assistant_page/google_assistant_page.js b/chrome/browser/resources/settings/chromeos/google_assistant_page/google_assistant_page.js
index b32b2197..a588aaf2 100644
--- a/chrome/browser/resources/settings/chromeos/google_assistant_page/google_assistant_page.js
+++ b/chrome/browser/resources/settings/chromeos/google_assistant_page/google_assistant_page.js
@@ -42,11 +42,8 @@
   is: 'settings-google-assistant-page',
 
   behaviors: [
-    DeepLinkingBehavior,
-    I18nBehavior,
-    PrefsBehavior,
-    settings.RouteObserverBehavior,
-    WebUIListenerBehavior
+    DeepLinkingBehavior, I18nBehavior, PrefsBehavior,
+    settings.RouteObserverBehavior, WebUIListenerBehavior
   ],
 
   properties: {
@@ -220,7 +217,7 @@
    * @private
    */
   isDspHotwordStateMatch_(state) {
-    return state == this.dspHotwordState_;
+    return state === this.dspHotwordState_;
   },
 
   /** @private */
@@ -239,7 +236,7 @@
     const hotwordEnabled =
         this.getPref('settings.voice_interaction.hotword.enabled');
 
-    this.hotwordEnforced_ = hotwordEnabled.enforcement ==
+    this.hotwordEnforced_ = hotwordEnabled.enforcement ===
         chrome.settingsPrivate.Enforcement.ENFORCED;
 
     this.quickAnswersAvailable_ =
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_menu.js b/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_menu.js
index 0e75a5e7..c6ccd81 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_menu.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_menu.js
@@ -35,7 +35,7 @@
    */
   currentRouteChanged(route, oldRoute) {
     this.showDotsButton_ = false;
-    if (route != settings.routes.NETWORK_DETAIL ||
+    if (route !== settings.routes.NETWORK_DETAIL ||
         !loadTimeData.getBoolean('updatedCellularActivationUi')) {
       return;
     }
@@ -55,7 +55,7 @@
     networkConfig.getNetworkState(guid).then(response => {
       // TODO(crbug.com/1093185): Add check for specifically eSIM when cellular
       // has an EID property.
-      this.showDotsButton_ = response.result.type ==
+      this.showDotsButton_ = response.result.type ===
           chromeos.networkConfig.mojom.NetworkType.kCellular;
     });
   },
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_page.js b/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_page.js
index db8262e3..442e40e 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_page.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_page.js
@@ -410,7 +410,7 @@
    * @protected
    */
   currentRouteChanged(route, oldRoute) {
-    if (route != settings.routes.NETWORK_DETAIL) {
+    if (route !== settings.routes.NETWORK_DETAIL) {
       return;
     }
 
@@ -422,7 +422,7 @@
     }
 
     this.shouldShowConfigureWhenNetworkLoaded_ =
-        queryParams.get('showConfigure') == 'true';
+        queryParams.get('showConfigure') === 'true';
     const type = queryParams.get('type') || 'WiFi';
     const name = queryParams.get('name') || type;
     this.init(guid, type, name);
@@ -493,9 +493,9 @@
       return;
     }
     // If the network was or is active, request an update.
-    if (this.managedProperties_.connectionState !=
+    if (this.managedProperties_.connectionState !==
             chromeos.networkConfig.mojom.ConnectionStateType.kNotConnected ||
-        networks.find(network => network.guid == this.guid)) {
+        networks.find(network => network.guid === this.guid)) {
       this.getNetworkDetails_();
     }
   },
@@ -508,7 +508,7 @@
     if (!this.guid || !this.managedProperties_) {
       return;
     }
-    if (network.guid == this.guid) {
+    if (network.guid === this.guid) {
       this.getNetworkDetails_();
     }
   },
@@ -537,14 +537,14 @@
     this.updateAutoConnectPref_();
 
     const metered = this.managedProperties_.metered;
-    if (metered && metered.activeValue != this.meteredOverride_) {
+    if (metered && metered.activeValue !== this.meteredOverride_) {
       this.meteredOverride_ = metered.activeValue;
     }
 
     const priority = this.managedProperties_.priority;
     if (priority) {
       const preferNetwork = priority.activeValue > 0;
-      if (preferNetwork != this.preferNetwork_) {
+      if (preferNetwork !== this.preferNetwork_) {
         this.preferNetwork_ = preferNetwork;
       }
     }
@@ -571,7 +571,7 @@
     }
 
     if (this.shouldShowConfigureWhenNetworkLoaded_ &&
-        this.managedProperties_.type ==
+        this.managedProperties_.type ===
             chromeos.networkConfig.mojom.NetworkType.kTether) {
       // Set |this.shouldShowConfigureWhenNetworkLoaded_| back to false to
       // ensure that the Tether dialog is only shown once.
@@ -607,7 +607,7 @@
     this.networkConfig_.getDeviceStateList().then(response => {
       const devices = response.result;
       const newDeviceState =
-          devices.find(device => device.type == type) || null;
+          devices.find(device => device.type === type) || null;
       let shouldGetNetworkDetails = false;
       if (!this.deviceState_ || !newDeviceState) {
         this.deviceState_ = newDeviceState;
@@ -615,9 +615,9 @@
       } else if (!this.deviceStatesMatch_(this.deviceState_, newDeviceState)) {
         // Only request a network state update if the deviceState changed.
         shouldGetNetworkDetails =
-            this.deviceState_.deviceState != newDeviceState.deviceState;
+            this.deviceState_.deviceState !== newDeviceState.deviceState;
         this.deviceState_ = newDeviceState;
-      } else if (this.deviceState_.scanning != newDeviceState.scanning) {
+      } else if (this.deviceState_.scanning !== newDeviceState.scanning) {
         // Update just the scanning state to avoid interrupting other parts of
         // the UI (e.g. custom IP addresses or nameservers).
         this.deviceState_.scanning = newDeviceState.scanning;
@@ -696,9 +696,9 @@
     }
 
     if (this.autoConnectPref_ &&
-        this.autoConnectPref_.value == autoConnect.activeValue &&
-        enforcement == this.autoConnectPref_.enforcement &&
-        controlledBy == this.autoConnectPref_.controlledBy) {
+        this.autoConnectPref_.value === autoConnect.activeValue &&
+        enforcement === this.autoConnectPref_.enforcement &&
+        controlledBy === this.autoConnectPref_.controlledBy) {
       return;
     }
 
@@ -915,13 +915,13 @@
     }
 
     if (this.isOutOfRangeOrNotEnabled_(outOfRange, deviceState)) {
-      return managedProperties.type ==
+      return managedProperties.type ===
               chromeos.networkConfig.mojom.NetworkType.kTether ?
           this.i18n('tetherPhoneOutOfRange') :
           this.i18n('networkOutOfRange');
     }
 
-    if (managedProperties.type ==
+    if (managedProperties.type ===
             chromeos.networkConfig.mojom.NetworkType.kCellular &&
         !managedProperties.connectable) {
       if (managedProperties.typeProperties.cellular.homeProvider &&
@@ -961,7 +961,8 @@
       return this.i18n('networkAllowDataRoamingDisabled');
     }
 
-    return managedProperties.typeProperties.cellular.roamingState == 'Roaming' ?
+    return managedProperties.typeProperties.cellular.roamingState ===
+            'Roaming' ?
         this.i18n('networkAllowDataRoamingEnabledRoaming') :
         this.i18n('networkAllowDataRoamingEnabledHome');
   },
@@ -998,7 +999,7 @@
     // connectable as long as the network has an associated configuration flow.
     // Cellular networks do not have a configuration flow, so a Cellular network
     // that is not connectable represents an error state.
-    return managedProperties.type ==
+    return managedProperties.type ===
         chromeos.networkConfig.mojom.NetworkType.kCellular &&
         !managedProperties.connectable;
   },
@@ -1010,7 +1011,7 @@
    */
   isRemembered_(managedProperties) {
     return !!managedProperties &&
-        managedProperties.source !=
+        managedProperties.source !==
         chromeos.networkConfig.mojom.OncSource.kNone;
   },
 
@@ -1031,7 +1032,7 @@
    */
   isCellular_(managedProperties) {
     return !!managedProperties &&
-        managedProperties.type ==
+        managedProperties.type ===
         chromeos.networkConfig.mojom.NetworkType.kCellular;
   },
 
@@ -1042,7 +1043,7 @@
    */
   isTether_(managedProperties) {
     return !!managedProperties &&
-        managedProperties.type ==
+        managedProperties.type ===
         chromeos.networkConfig.mojom.NetworkType.kTether;
   },
 
@@ -1055,7 +1056,7 @@
    */
   isBlockedByPolicy_(managedProperties, globalPolicy, managedNetworkAvailable) {
     if (!managedProperties || !globalPolicy ||
-        managedProperties.type !=
+        managedProperties.type !==
             chromeos.networkConfig.mojom.NetworkType.kWiFi ||
         this.isPolicySource(managedProperties.source)) {
       return false;
@@ -1095,27 +1096,27 @@
       return false;
     }
 
-    if (managedProperties.connectionState !=
+    if (managedProperties.connectionState !==
         chromeos.networkConfig.mojom.ConnectionStateType.kNotConnected) {
       return false;
     }
 
     if (deviceState &&
-        deviceState.deviceState !=
+        deviceState.deviceState !==
             chromeos.networkConfig.mojom.DeviceStateType.kEnabled) {
       return false;
     }
 
     // Cellular is not configurable, so we always show the connect button, and
     // disable it if 'connectable' is false.
-    if (managedProperties.type ==
+    if (managedProperties.type ===
         chromeos.networkConfig.mojom.NetworkType.kCellular) {
       return true;
     }
 
     // If 'connectable' is false we show the configure button.
     return managedProperties.connectable &&
-        managedProperties.type !=
+        managedProperties.type !==
         chromeos.networkConfig.mojom.NetworkType.kEthernet;
   },
 
@@ -1127,11 +1128,11 @@
    */
   showDisconnect_(managedProperties) {
     if (!managedProperties ||
-        managedProperties.type ==
+        managedProperties.type ===
             chromeos.networkConfig.mojom.NetworkType.kEthernet) {
       return false;
     }
-    return managedProperties.connectionState !=
+    return managedProperties.connectionState !==
         chromeos.networkConfig.mojom.ConnectionStateType.kNotConnected;
   },
 
@@ -1145,8 +1146,8 @@
       return false;
     }
     const type = managedProperties.type;
-    if (type != chromeos.networkConfig.mojom.NetworkType.kWiFi &&
-        type != chromeos.networkConfig.mojom.NetworkType.kVPN) {
+    if (type !== chromeos.networkConfig.mojom.NetworkType.kWiFi &&
+        type !== chromeos.networkConfig.mojom.NetworkType.kVPN) {
       return false;
     }
     if (this.isArcVpn_(managedProperties)) {
@@ -1170,9 +1171,9 @@
     }
     const activation =
         managedProperties.typeProperties.cellular.activationState;
-    return activation ==
+    return activation ===
         chromeos.networkConfig.mojom.ActivationStateType.kNotActivated ||
-        activation ==
+        activation ===
         chromeos.networkConfig.mojom.ActivationStateType.kPartiallyActivated;
   },
 
@@ -1192,17 +1193,17 @@
       return false;
     }
     const type = managedProperties.type;
-    if (type == chromeos.networkConfig.mojom.NetworkType.kCellular ||
-        type == chromeos.networkConfig.mojom.NetworkType.kTether) {
+    if (type === chromeos.networkConfig.mojom.NetworkType.kCellular ||
+        type === chromeos.networkConfig.mojom.NetworkType.kTether) {
       return false;
     }
-    if (type == chromeos.networkConfig.mojom.NetworkType.kWiFi &&
-        managedProperties.typeProperties.wifi.security ==
+    if (type === chromeos.networkConfig.mojom.NetworkType.kWiFi &&
+        managedProperties.typeProperties.wifi.security ===
             chromeos.networkConfig.mojom.SecurityType.kNone) {
       return false;
     }
-    if (type == chromeos.networkConfig.mojom.NetworkType.kWiFi &&
-        (managedProperties.connectionState !=
+    if (type === chromeos.networkConfig.mojom.NetworkType.kWiFi &&
+        (managedProperties.connectionState !==
          chromeos.networkConfig.mojom.ConnectionStateType.kNotConnected)) {
       return false;
     }
@@ -1223,7 +1224,7 @@
     if (!managedProperties) {
       return true;
     }
-    return managedProperties.type ==
+    return managedProperties.type ===
         chromeos.networkConfig.mojom.NetworkType.kVPN &&
         vpnConfigAllowed && !vpnConfigAllowed.value;
   },
@@ -1238,7 +1239,7 @@
     if (!managedProperties) {
       return true;
     }
-    if (managedProperties.type ==
+    if (managedProperties.type ===
             chromeos.networkConfig.mojom.NetworkType.kVPN &&
         vpnConfigAllowed && !vpnConfigAllowed.value) {
       return true;
@@ -1257,7 +1258,7 @@
       return false;
     }
     for (const value of Object.values(managedProperties)) {
-      if (typeof value != 'object' || value === null) {
+      if (typeof value !== 'object' || value === null) {
         continue;
       }
       if ('activeValue' in value) {
@@ -1301,7 +1302,7 @@
     if (!this.isConnectedState_(managedProperties)) {
       const technology =
           managedProperties.typeProperties.cellular.networkTechnology;
-      if (technology != 'LTE' && technology != 'LTEAdvanced') {
+      if (technology !== 'LTE' && technology !== 'LTEAdvanced') {
         return false;
       }
       if (!managedProperties.typeProperties.cellular.mdn) {
@@ -1337,12 +1338,12 @@
     }
     // Cellular networks are not configurable, so we show a disabled 'Connect'
     // button when not connectable.
-    if (managedProperties.type ==
+    if (managedProperties.type ===
             chromeos.networkConfig.mojom.NetworkType.kCellular &&
         !managedProperties.connectable) {
       return false;
     }
-    if (managedProperties.type ==
+    if (managedProperties.type ===
             chromeos.networkConfig.mojom.NetworkType.kVPN &&
         !defaultNetwork) {
       return false;
@@ -1371,7 +1372,7 @@
     // policy indicator on the connect/disconnect buttons, so it shouldn't be
     // shown on non-VPN networks.
     if (this.managedProperties_ &&
-        this.managedProperties_.type ==
+        this.managedProperties_.type ===
             chromeos.networkConfig.mojom.NetworkType.kVPN &&
         this.prefs && this.prefs.vpn_config_allowed &&
         !this.prefs.vpn_config_allowed.value) {
@@ -1401,7 +1402,7 @@
 
   /** @private */
   handleConnectTap_() {
-    if (this.managedProperties_.type ==
+    if (this.managedProperties_.type ===
             chromeos.networkConfig.mojom.NetworkType.kTether &&
         (!this.managedProperties_.typeProperties.tether.hasConnectedToHost)) {
       this.showTetherDialog_();
@@ -1553,7 +1554,7 @@
     return loadTimeData.getBoolean('showHiddenNetworkWarning') &&
         !!this.autoConnectPref_ && !!this.autoConnectPref_.value &&
         !!this.managedProperties_ &&
-        this.managedProperties_.type ==
+        this.managedProperties_.type ===
         chromeos.networkConfig.mojom.NetworkType.kWiFi &&
         !!OncMojo.getActiveValue(
             this.managedProperties_.typeProperties.wifi.hiddenSsid);
@@ -1575,8 +1576,8 @@
     const value = e.detail.value;
     const config = this.getDefaultConfigProperties_();
     const valueType = typeof value;
-    if (valueType != 'string' && valueType != 'number' &&
-        valueType != 'boolean' && !Array.isArray(value)) {
+    if (valueType !== 'string' && valueType !== 'number' &&
+        valueType !== 'boolean' && !Array.isArray(value)) {
       console.error(
           'Unexpected property change event, Key: ' + field +
           ' Value: ' + JSON.stringify(value));
@@ -1587,10 +1588,11 @@
     // configurations are set.
     const vpnConfig = config.typeConfig.vpn;
     if (vpnConfig) {
-      if (vpnConfig.openVpn && vpnConfig.openVpn.saveCredentials == undefined) {
+      if (vpnConfig.openVpn &&
+          vpnConfig.openVpn.saveCredentials === undefined) {
         vpnConfig.openVpn.saveCredentials = false;
       }
-      if (vpnConfig.l2tp && vpnConfig.l2tp.saveCredentials == undefined) {
+      if (vpnConfig.l2tp && vpnConfig.l2tp.saveCredentials === undefined) {
         vpnConfig.l2tp.saveCredentials = false;
       }
     }
@@ -1683,7 +1685,7 @@
     } else if (!managedProperties.typeProperties.wifi.isSyncable) {
       return this.i18nAdvanced('networkNotSynced');
     } else if (
-        managedProperties.source ==
+        managedProperties.source ===
         chromeos.networkConfig.mojom.OncSource.kUser) {
       return this.i18nAdvanced('networkSyncedUser');
     } else {
@@ -1745,9 +1747,9 @@
    */
   showShared_(managedProperties, globalPolicy, managedNetworkAvailable) {
     return !this.propertiesMissingOrBlockedByPolicy_() &&
-        (managedProperties.source ==
+        (managedProperties.source ===
              chromeos.networkConfig.mojom.OncSource.kDevice ||
-         managedProperties.source ==
+         managedProperties.source ===
              chromeos.networkConfig.mojom.OncSource.kDevicePolicy);
   },
 
@@ -1760,7 +1762,7 @@
    */
   showAutoConnect_(managedProperties, globalPolicy, managedNetworkAvailable) {
     return !!managedProperties &&
-        managedProperties.type !=
+        managedProperties.type !==
         chromeos.networkConfig.mojom.NetworkType.kEthernet &&
         this.isRemembered_(managedProperties) &&
         !this.isArcVpn_(managedProperties) &&
@@ -1776,9 +1778,9 @@
     const managedProperties = this.managedProperties_;
     return this.showMeteredToggle_ && !!managedProperties &&
         this.isRemembered_(managedProperties) &&
-        (managedProperties.type ==
+        (managedProperties.type ===
              chromeos.networkConfig.mojom.NetworkType.kCellular ||
-         managedProperties.type ==
+         managedProperties.type ===
              chromeos.networkConfig.mojom.NetworkType.kWiFi);
   },
 
@@ -1819,8 +1821,8 @@
     }
 
     const type = managedProperties.type;
-    if (type == chromeos.networkConfig.mojom.NetworkType.kEthernet ||
-        type == chromeos.networkConfig.mojom.NetworkType.kCellular ||
+    if (type === chromeos.networkConfig.mojom.NetworkType.kEthernet ||
+        type === chromeos.networkConfig.mojom.NetworkType.kCellular ||
         this.isArcVpn_(managedProperties)) {
       return false;
     }
@@ -1936,12 +1938,12 @@
 
     /** @dict */ const editFields = {};
     const type = this.managedProperties_.type;
-    if (type == chromeos.networkConfig.mojom.NetworkType.kVPN) {
+    if (type === chromeos.networkConfig.mojom.NetworkType.kVPN) {
       const vpnType = this.managedProperties_.typeProperties.vpn.type;
-      if (vpnType != chromeos.networkConfig.mojom.VpnType.kExtension) {
+      if (vpnType !== chromeos.networkConfig.mojom.VpnType.kExtension) {
         editFields['vpn.host'] = 'String';
       }
-      if (vpnType == chromeos.networkConfig.mojom.VpnType.kOpenVPN) {
+      if (vpnType === chromeos.networkConfig.mojom.VpnType.kOpenVPN) {
         editFields['vpn.openVpn.username'] = 'String';
         editFields['vpn.openVpn.extraHosts'] = 'StringArray';
       }
@@ -2008,7 +2010,7 @@
     if (this.showMetered_()) {
       return true;
     }
-    if (this.managedProperties_.type ==
+    if (this.managedProperties_.type ===
         chromeos.networkConfig.mojom.NetworkType.kTether) {
       // These properties apply to the underlying WiFi network, not the Tether
       // network.
@@ -2042,7 +2044,7 @@
    */
   hasNetworkSection_(managedProperties, globalPolicy, managedNetworkAvailable) {
     if (!managedProperties ||
-        managedProperties.type ==
+        managedProperties.type ===
             chromeos.networkConfig.mojom.NetworkType.kTether) {
       // These settings apply to the underlying WiFi network, not the Tether
       // network.
@@ -2052,7 +2054,7 @@
             managedProperties, globalPolicy, managedNetworkAvailable)) {
       return false;
     }
-    if (managedProperties.type ==
+    if (managedProperties.type ===
         chromeos.networkConfig.mojom.NetworkType.kCellular) {
       return true;
     }
@@ -2068,7 +2070,7 @@
    */
   hasProxySection_(managedProperties, globalPolicy, managedNetworkAvailable) {
     if (!managedProperties ||
-        managedProperties.type ==
+        managedProperties.type ===
             chromeos.networkConfig.mojom.NetworkType.kTether) {
       // Proxy settings apply to the underlying WiFi network, not the Tether
       // network.
@@ -2088,7 +2090,7 @@
    */
   showCellularChooseNetwork_(managedProperties) {
     return !!managedProperties &&
-        managedProperties.type ==
+        managedProperties.type ===
         chromeos.networkConfig.mojom.NetworkType.kCellular &&
         managedProperties.typeProperties.cellular.supportNetworkScan;
   },
@@ -2099,7 +2101,7 @@
    */
   showScanningSpinner_() {
     if (!this.managedProperties_ ||
-        this.managedProperties_.type !=
+        this.managedProperties_.type !==
             chromeos.networkConfig.mojom.NetworkType.kCellular) {
       return false;
     }
@@ -2113,9 +2115,9 @@
    */
   showCellularSim_(managedProperties) {
     return !!managedProperties &&
-        managedProperties.type ==
+        managedProperties.type ===
         chromeos.networkConfig.mojom.NetworkType.kCellular &&
-        managedProperties.typeProperties.cellular.family != 'CDMA';
+        managedProperties.typeProperties.cellular.family !== 'CDMA';
   },
 
   /**
@@ -2126,9 +2128,9 @@
    */
   isArcVpn_(managedProperties) {
     return !!managedProperties &&
-        managedProperties.type ==
+        managedProperties.type ===
         chromeos.networkConfig.mojom.NetworkType.kVPN &&
-        managedProperties.typeProperties.vpn.type ==
+        managedProperties.typeProperties.vpn.type ===
         chromeos.networkConfig.mojom.VpnType.kArc;
   },
 
@@ -2140,9 +2142,9 @@
    */
   isThirdPartyVpn_(managedProperties) {
     return !!managedProperties &&
-        managedProperties.type ==
+        managedProperties.type ===
         chromeos.networkConfig.mojom.NetworkType.kVPN &&
-        managedProperties.typeProperties.vpn.type ==
+        managedProperties.typeProperties.vpn.type ===
         chromeos.networkConfig.mojom.VpnType.kExtension;
   },
 
@@ -2180,7 +2182,7 @@
    */
   allPropertiesMatch_(curValue, newValue) {
     for (const key in newValue) {
-      if (newValue[key] != curValue[key]) {
+      if (newValue[key] !== curValue[key]) {
         return false;
       }
     }
@@ -2196,7 +2198,7 @@
   isOutOfRangeOrNotEnabled_(outOfRange, deviceState) {
     return outOfRange ||
         (!!deviceState &&
-         deviceState.deviceState !=
+         deviceState.deviceState !==
              chromeos.networkConfig.mojom.DeviceStateType.kEnabled);
   },
 });
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/internet_known_networks_page.js b/chrome/browser/resources/settings/chromeos/internet_page/internet_known_networks_page.js
index cd8a2c5..372a4e1 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/internet_known_networks_page.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/internet_known_networks_page.js
@@ -95,7 +95,7 @@
    */
   currentRouteChanged(route, oldRoute) {
     // Does not apply to this page.
-    if (route != settings.routes.KNOWN_NETWORKS) {
+    if (route !== settings.routes.KNOWN_NETWORKS) {
       return;
     }
 
@@ -164,7 +164,7 @@
    * @private
    */
   networkIsNotPreferred_(networkState) {
-    return networkState.priority == 0;
+    return networkState.priority === 0;
   },
 
   /**
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/internet_page.js b/chrome/browser/resources/settings/chromeos/internet_page/internet_page.js
index 9eba778d..1446c75 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/internet_page.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/internet_page.js
@@ -217,7 +217,7 @@
    * @protected
    */
   currentRouteChanged(route, oldRoute) {
-    if (route == settings.routes.INTERNET_NETWORKS) {
+    if (route === settings.routes.INTERNET_NETWORKS) {
       // Handle direct navigation to the networks page,
       // e.g. chrome://settings/internet/networks?type=WiFi
       const queryParams = settings.Router.getInstance().getQueryParameters();
@@ -225,7 +225,7 @@
       if (type) {
         this.subpageType_ = OncMojo.getNetworkTypeFromString(type);
       }
-    } else if (route == settings.routes.KNOWN_NETWORKS) {
+    } else if (route === settings.routes.KNOWN_NETWORKS) {
       // Handle direct navigation to the known networks page,
       // e.g. chrome://settings/internet/knownNetworks?type=WiFi
       const queryParams = settings.Router.getInstance().getQueryParameters();
@@ -233,10 +233,10 @@
       if (type) {
         this.knownNetworksType_ = OncMojo.getNetworkTypeFromString(type);
       }
-    } else if (route == settings.routes.INTERNET) {
+    } else if (route === settings.routes.INTERNET) {
       // Show deep links for the internet page.
       this.attemptDeepLink();
-    } else if (route != settings.routes.BASIC) {
+    } else if (route !== settings.routes.BASIC) {
       // If we are navigating to a non internet section, do not set focus.
       return;
     }
@@ -248,7 +248,7 @@
 
     // Focus the subpage arrow where appropriate.
     let element;
-    if (route == settings.routes.INTERNET_NETWORKS) {
+    if (route === settings.routes.INTERNET_NETWORKS) {
       // iron-list makes the correct timing to focus an item in the list
       // very complicated, and the item may not exist, so just focus the
       // entire list for now.
@@ -332,8 +332,8 @@
    */
   showConfig_(configAndConnect, type, opt_guid, opt_name) {
     assert(
-        type != chromeos.networkConfig.mojom.NetworkType.kCellular &&
-        type != chromeos.networkConfig.mojom.NetworkType.kTether);
+        type !== chromeos.networkConfig.mojom.NetworkType.kCellular &&
+        type !== chromeos.networkConfig.mojom.NetworkType.kTether);
     if (this.showInternetConfig_) {
       return;
     }
@@ -387,8 +387,8 @@
     // The shared Cellular/Tether subpage is referred to as "Mobile".
     // TODO(khorimoto): Remove once Cellular/Tether are split into their own
     // sections.
-    if (this.subpageType_ == mojom.NetworkType.kCellular ||
-        this.subpageType_ == mojom.NetworkType.kTether) {
+    if (this.subpageType_ === mojom.NetworkType.kCellular ||
+        this.subpageType_ === mojom.NetworkType.kTether) {
       return this.i18n('OncTypeMobile');
     }
     return this.i18n(
@@ -407,7 +407,7 @@
     }
     // If both Tether and Cellular are enabled, use the Cellular device state
     // when directly navigating to the Tether page.
-    if (subpageType == mojom.NetworkType.kTether &&
+    if (subpageType === mojom.NetworkType.kTether &&
         this.deviceStates[mojom.NetworkType.kCellular]) {
       subpageType = mojom.NetworkType.kCellular;
     }
@@ -436,7 +436,7 @@
       managedNetworkAvailable = !!wifiDeviceState.managedNetworkAvailable;
     }
 
-    if (this.managedNetworkAvailable != managedNetworkAvailable) {
+    if (this.managedNetworkAvailable !== managedNetworkAvailable) {
       this.managedNetworkAvailable = managedNetworkAvailable;
     }
 
@@ -543,7 +543,7 @@
   wifiIsEnabled_(deviceStates) {
     const wifi = deviceStates[mojom.NetworkType.kWiFi];
     return !!wifi &&
-        wifi.deviceState ==
+        wifi.deviceState ===
         chromeos.networkConfig.mojom.DeviceStateType.kEnabled;
   },
 
@@ -585,7 +585,7 @@
     const displayName = OncMojo.getNetworkStateDisplayName(networkState);
 
     if (!event.detail.bypassConnectionDialog &&
-        type == mojom.NetworkType.kTether &&
+        type === mojom.NetworkType.kTether &&
         !networkState.typeState.tether.hasConnectedToHost) {
       const params = new URLSearchParams;
       params.append('guid', networkState.guid);
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/internet_subpage.js b/chrome/browser/resources/settings/chromeos/internet_page/internet_subpage.js
index 31271015..fd877e31 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/internet_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/internet_subpage.js
@@ -232,7 +232,7 @@
    * @protected
    */
   currentRouteChanged(newRoute, oldRoute) {
-    if (newRoute != settings.routes.INTERNET_NETWORKS) {
+    if (newRoute !== settings.routes.INTERNET_NETWORKS) {
       this.stopScanning_();
       return;
     }
@@ -276,7 +276,7 @@
 
   /** NetworkListenerBehavior override */
   onVpnProvidersChanged() {
-    if (this.deviceState.type != mojom.NetworkType.kVPN) {
+    if (this.deviceState.type !== mojom.NetworkType.kVPN) {
       return;
     }
     this.getNetworkStateList_();
@@ -295,12 +295,12 @@
       // active) and the device is no longer scanning.
       this.hasCompletedScanSinceLastEnabled_ = this.showSpinner &&
           !this.deviceState.scanning &&
-          this.deviceState.deviceState == mojom.DeviceStateType.kEnabled;
+          this.deviceState.deviceState === mojom.DeviceStateType.kEnabled;
       this.showSpinner = !!this.deviceState.scanning;
     }
 
     // Scans should only be triggered by the "networks" subpage.
-    if (settings.Router.getInstance().getCurrentRoute() !=
+    if (settings.Router.getInstance().getCurrentRoute() !==
         settings.routes.INTERNET_NETWORKS) {
       this.stopScanning_();
       return;
@@ -328,14 +328,14 @@
    */
   shouldStartScan_() {
     // Scans should be kicked off from the Wi-Fi networks subpage.
-    if (this.deviceState.type == mojom.NetworkType.kWiFi) {
+    if (this.deviceState.type === mojom.NetworkType.kWiFi) {
       return true;
     }
 
     // Scans should be kicked off from the Mobile data subpage, as long as it
     // includes Tether networks.
-    if (this.deviceState.type == mojom.NetworkType.kTether ||
-        (this.deviceState.type == mojom.NetworkType.kCellular &&
+    if (this.deviceState.type === mojom.NetworkType.kTether ||
+        (this.deviceState.type === mojom.NetworkType.kCellular &&
          this.tetherDeviceState)) {
       return true;
     }
@@ -350,7 +350,7 @@
     }
     const INTERVAL_MS = 10 * 1000;
     let type = this.deviceState.type;
-    if (type == mojom.NetworkType.kCellular && this.tetherDeviceState) {
+    if (type === mojom.NetworkType.kCellular && this.tetherDeviceState) {
       // Only request tether scan. Cellular scan is disruptive and should
       // only be triggered by explicit user action.
       type = mojom.NetworkType.kTether;
@@ -407,7 +407,7 @@
     }
 
     // For the Cellular/Mobile subpage, also request Tether networks.
-    if (this.deviceState.type == mojom.NetworkType.kCellular &&
+    if (this.deviceState.type === mojom.NetworkType.kCellular &&
         this.tetherDeviceState) {
       const filter = {
         filter: chromeos.networkConfig.mojom.FilterType.kVisible,
@@ -422,11 +422,11 @@
     }
 
     // For VPNs, separate out third party (Extension + Arc) VPNs.
-    if (this.deviceState.type == mojom.NetworkType.kVPN) {
+    if (this.deviceState.type === mojom.NetworkType.kVPN) {
       const builtinNetworkStates = [];
       const thirdPartyVpns = {};
       networkStates.forEach(state => {
-        assert(state.type == mojom.NetworkType.kVPN);
+        assert(state.type === mojom.NetworkType.kVPN);
         switch (state.typeState.vpn.type) {
           case mojom.VpnType.kL2TPIPsec:
           case mojom.VpnType.kOpenVPN:
@@ -481,7 +481,7 @@
     const unconfiguredProviders = [];
     for (const provider of vpnProviders) {
       const idx = configuredProviders.findIndex(
-          p => p.providerId == provider.providerId);
+          p => p.providerId === provider.providerId);
       if (idx >= 0) {
         configuredProviders[idx] = provider;
       } else {
@@ -512,8 +512,8 @@
    */
   deviceIsEnabled_(deviceState) {
     return !!deviceState &&
-        (deviceState.type == mojom.NetworkType.kVPN ||
-         deviceState.deviceState == mojom.DeviceStateType.kEnabled);
+        (deviceState.type === mojom.NetworkType.kVPN ||
+         deviceState.deviceState === mojom.DeviceStateType.kEnabled);
   },
 
   /**
@@ -533,8 +533,8 @@
    * @private
    */
   enableToggleIsVisible_(deviceState) {
-    return !!deviceState && deviceState.type != mojom.NetworkType.kEthernet &&
-        deviceState.type != mojom.NetworkType.kVPN;
+    return !!deviceState && deviceState.type !== mojom.NetworkType.kEthernet &&
+        deviceState.type !== mojom.NetworkType.kVPN;
   },
 
   /**
@@ -544,7 +544,7 @@
    */
   enableToggleIsEnabled_(deviceState) {
     return !!deviceState &&
-        deviceState.deviceState !=
+        deviceState.deviceState !==
         chromeos.networkConfig.mojom.DeviceStateType.kProhibited &&
         !OncMojo.deviceStateIsIntermediate(deviceState.deviceState);
   },
@@ -598,7 +598,7 @@
    * @private
    */
   showAddWifiButton_(deviceState, globalPolicy) {
-    if (!deviceState || deviceState.type != mojom.NetworkType.kWiFi) {
+    if (!deviceState || deviceState.type !== mojom.NetworkType.kWiFi) {
       return false;
     }
     return this.allowAddConnection_(deviceState, globalPolicy);
@@ -615,7 +615,7 @@
       return false;
     }
 
-    if (!deviceState || deviceState.type != mojom.NetworkType.kCellular) {
+    if (!deviceState || deviceState.type !== mojom.NetworkType.kCellular) {
       return false;
     }
     return this.allowAddConnection_(deviceState, globalPolicy);
@@ -661,7 +661,7 @@
    * @private
    */
   knownNetworksIsVisible_(deviceState) {
-    return !!deviceState && deviceState.type == mojom.NetworkType.kWiFi;
+    return !!deviceState && deviceState.type === mojom.NetworkType.kWiFi;
   },
 
   /**
@@ -669,7 +669,7 @@
    * @private
    */
   onKnownNetworksTap_() {
-    assert(this.deviceState.type == mojom.NetworkType.kWiFi);
+    assert(this.deviceState.type === mojom.NetworkType.kWiFi);
     this.fire('show-known-networks', this.deviceState.type);
   },
 
@@ -731,7 +731,7 @@
    * @private
    */
   isBlockedByPolicy_(state) {
-    if (state.type != mojom.NetworkType.kWiFi ||
+    if (state.type !== mojom.NetworkType.kWiFi ||
         this.isPolicySource(state.source) || !this.globalPolicy) {
       return false;
     }
@@ -751,13 +751,13 @@
    * @private
    */
   canAttemptConnection_(state) {
-    if (state.connectionState != mojom.ConnectionStateType.kNotConnected) {
+    if (state.connectionState !== mojom.ConnectionStateType.kNotConnected) {
       return false;
     }
     if (this.isBlockedByPolicy_(state)) {
       return false;
     }
-    if (state.type == mojom.NetworkType.kVPN &&
+    if (state.type === mojom.NetworkType.kVPN &&
         (!this.defaultNetwork ||
          !OncMojo.connectionStateIsConnected(
              this.defaultNetwork.connectionState))) {
@@ -765,7 +765,7 @@
     }
     // Cellular networks do not have a configuration flow, so it's not possible
     // to attempt a connection if the network is not conncetable.
-    if (state.type == mojom.NetworkType.kCellular && !state.connectable) {
+    if (state.type === mojom.NetworkType.kCellular && !state.connectable) {
       return false;
     }
     return true;
@@ -784,7 +784,7 @@
       return false;
     }
 
-    return !!deviceState && deviceState.type == mojom.NetworkType.kCellular &&
+    return !!deviceState && deviceState.type === mojom.NetworkType.kCellular &&
         !!tetherDeviceState;
   },
 
@@ -797,7 +797,7 @@
   tetherToggleIsEnabled_(deviceState, tetherDeviceState) {
     return this.tetherToggleIsVisible_(deviceState, tetherDeviceState) &&
         this.enableToggleIsEnabled_(tetherDeviceState) &&
-        tetherDeviceState.deviceState !=
+        tetherDeviceState.deviceState !==
         chromeos.networkConfig.mojom.DeviceStateType.kUninitialized;
   },
 
@@ -821,7 +821,7 @@
    */
   matchesType_(typeString, device) {
     return !!device &&
-        device.type == OncMojo.getNetworkTypeFromString(typeString);
+        device.type === OncMojo.getNetworkTypeFromString(typeString);
   },
 
   /**
@@ -855,12 +855,12 @@
    */
   getNoNetworksInnerHtml_(deviceState, tetherDeviceState) {
     const type = deviceState.type;
-    if (type == mojom.NetworkType.kTether ||
-        (type == mojom.NetworkType.kCellular && this.tetherDeviceState)) {
+    if (type === mojom.NetworkType.kTether ||
+        (type === mojom.NetworkType.kCellular && this.tetherDeviceState)) {
       return this.i18nAdvanced('internetNoNetworksMobileData');
     }
 
-    if (type == mojom.NetworkType.kVPN) {
+    if (type === mojom.NetworkType.kVPN) {
       return this.i18n('internetNoNetworks');
     }
 
@@ -891,13 +891,13 @@
    * @private
    */
   getGmsCoreNotificationsDevicesString_(notificationsDisabledDeviceNames) {
-    if (notificationsDisabledDeviceNames.length == 1) {
+    if (notificationsDisabledDeviceNames.length === 1) {
       return this.i18n(
           'gmscoreNotificationsOneDeviceSubtitle',
           notificationsDisabledDeviceNames[0]);
     }
 
-    if (notificationsDisabledDeviceNames.length == 2) {
+    if (notificationsDisabledDeviceNames.length === 2) {
       return this.i18n(
           'gmscoreNotificationsTwoDevicesSubtitle',
           notificationsDisabledDeviceNames[0],
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/network_proxy_section.js b/chrome/browser/resources/settings/chromeos/internet_page/network_proxy_section.js
index 25367d1..31d5376 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/network_proxy_section.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/network_proxy_section.js
@@ -43,7 +43,7 @@
 
   /** @protected settings.RouteObserverBehavior */
   currentRouteChanged(newRoute) {
-    if (newRoute == settings.routes.NETWORK_DETAIL) {
+    if (newRoute === settings.routes.NETWORK_DETAIL) {
       /** @type {NetworkProxyElement} */ (this.$$('network-proxy')).reset();
     }
   },
@@ -60,8 +60,8 @@
    */
   isShared_() {
     const mojom = chromeos.networkConfig.mojom;
-    return this.managedProperties.source == mojom.OncSource.kDevice ||
-        this.managedProperties.source == mojom.OncSource.kDevicePolicy;
+    return this.managedProperties.source === mojom.OncSource.kDevice ||
+        this.managedProperties.source === mojom.OncSource.kDevicePolicy;
   },
 
   /**
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/network_summary.js b/chrome/browser/resources/settings/chromeos/internet_page/network_summary.js
index 4a245c6..af3de0a 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/network_summary.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/network_summary.js
@@ -108,8 +108,8 @@
     }
     networks.forEach(network => {
       const index = this.activeNetworkStates_.findIndex(
-          state => state.guid == network.guid);
-      if (index != -1) {
+          state => state.guid === network.guid);
+      if (index !== -1) {
         this.set(['activeNetworkStates_', index], network);
       }
     });
@@ -206,7 +206,7 @@
       if (!activeNetworkStatesByType.has(type)) {
         activeNetworkStatesByType.set(type, networkState);
         if (!firstConnectedNetwork &&
-            networkState.type != mojom.NetworkType.kVPN &&
+            networkState.type !== mojom.NetworkType.kVPN &&
             OncMojo.connectionStateIsConnected(networkState.connectionState)) {
           firstConnectedNetwork = networkState;
         }
@@ -238,7 +238,7 @@
       // If both 'Tether' and 'Cellular' technologies exist, merge the network
       // lists and do not add an active network for 'Tether' so that there is
       // only one 'Mobile data' section / subpage.
-      if (type == mojom.NetworkType.kTether &&
+      if (type === mojom.NetworkType.kTether &&
           newDeviceStates[mojom.NetworkType.kCellular]) {
         newNetworkStateLists[mojom.NetworkType.kCellular] =
             newNetworkStateLists[mojom.NetworkType.kCellular].concat(
@@ -250,8 +250,8 @@
       // types are enabled but no Cellular network exists (edge case).
       const networkState =
           this.getActiveStateForType_(activeNetworkStatesByType, type);
-      if (networkState.source == mojom.OncSource.kNone &&
-          device.deviceState == mojom.DeviceStateType.kProhibited) {
+      if (networkState.source === mojom.OncSource.kNone &&
+          device.deviceState === mojom.DeviceStateType.kProhibited) {
         // Prohibited technologies are enforced by the device policy.
         networkState.source =
             chromeos.networkConfig.mojom.OncSource.kDevicePolicy;
@@ -279,7 +279,7 @@
    */
   getActiveStateForType_(activeStatesByType, type) {
     let activeState = activeStatesByType.get(type);
-    if (!activeState && type == mojom.NetworkType.kCellular) {
+    if (!activeState && type === mojom.NetworkType.kCellular) {
       activeState = activeStatesByType.get(mojom.NetworkType.kTether);
     }
     return activeState || OncMojo.getDefaultNetworkState(type);
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.js b/chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.js
index 1d913f9..ee83a0a 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.js
@@ -98,29 +98,29 @@
     const deviceState = this.deviceState;
     if (deviceState) {
       // Type specific scanning or initialization states.
-      if (deviceState.type == mojom.NetworkType.kCellular) {
+      if (deviceState.type === mojom.NetworkType.kCellular) {
         if (deviceState.scanning) {
           return this.i18n('internetMobileSearching');
         }
-        if (deviceState.deviceState == mojom.DeviceStateType.kUninitialized) {
+        if (deviceState.deviceState === mojom.DeviceStateType.kUninitialized) {
           return this.i18n('internetDeviceInitializing');
         }
-        if (deviceState.deviceState == mojom.DeviceStateType.kDisabling) {
+        if (deviceState.deviceState === mojom.DeviceStateType.kDisabling) {
           return this.i18n('internetDeviceDisabling');
         }
-      } else if (deviceState.type == mojom.NetworkType.kTether) {
-        if (deviceState.deviceState == mojom.DeviceStateType.kUninitialized) {
+      } else if (deviceState.type === mojom.NetworkType.kTether) {
+        if (deviceState.deviceState === mojom.DeviceStateType.kUninitialized) {
           return this.i18n('tetherEnableBluetooth');
         }
       }
       // Enabled or enabling states.
-      if (deviceState.deviceState == mojom.DeviceStateType.kEnabled) {
+      if (deviceState.deviceState === mojom.DeviceStateType.kEnabled) {
         if (this.networkStateList.length > 0) {
           return this.i18n('networkListItemNotConnected');
         }
         return this.i18n('networkListItemNoNetwork');
       }
-      if (deviceState.deviceState == mojom.DeviceStateType.kEnabling) {
+      if (deviceState.deviceState === mojom.DeviceStateType.kEnabling) {
         return this.i18n('internetDeviceEnabling');
       }
     }
@@ -144,15 +144,15 @@
       // Ethernet networks always have the display name 'Ethernet' so we use the
       // state text 'Connected' to avoid repeating the label in the sublabel.
       // See http://crbug.com/989907 for details.
-      return networkState.type == mojom.NetworkType.kEthernet ?
+      return networkState.type === mojom.NetworkType.kEthernet ?
           this.i18n('networkListItemConnected') :
           name;
     }
-    if (connectionState == mojom.ConnectionStateType.kConnecting) {
+    if (connectionState === mojom.ConnectionStateType.kConnecting) {
       return name ? this.i18n('networkListItemConnectingTo', name) :
                     this.i18n('networkListItemConnecting');
     }
-    if (networkState.type == mojom.NetworkType.kCellular && deviceState &&
+    if (networkState.type === mojom.NetworkType.kCellular && deviceState &&
         deviceState.scanning) {
       return this.i18n('internetMobileSearching');
     }
@@ -193,7 +193,7 @@
    * @private
    */
   showSimInfo_(deviceState) {
-    if (!deviceState || deviceState.type != mojom.NetworkType.kCellular) {
+    if (!deviceState || deviceState.type !== mojom.NetworkType.kCellular) {
       return false;
     }
     return this.simLockedOrAbsent_(deviceState);
@@ -215,7 +215,7 @@
       return false;
     }
     const simLockType = deviceState.simLockStatus.lockType;
-    return simLockType == 'sim-pin' || simLockType == 'sim-puk';
+    return simLockType === 'sim-pin' || simLockType === 'sim-puk';
   },
 
   /**
@@ -229,8 +229,8 @@
    */
   deviceIsEnabled_(deviceState) {
     return !!deviceState &&
-        (deviceState.type == mojom.NetworkType.kVPN ||
-         deviceState.deviceState == mojom.DeviceStateType.kEnabled);
+        (deviceState.type === mojom.NetworkType.kVPN ||
+         deviceState.deviceState === mojom.DeviceStateType.kEnabled);
   },
 
   /**
@@ -249,9 +249,9 @@
       case mojom.NetworkType.kTether:
         return true;
       case mojom.NetworkType.kWiFi:
-        return deviceState.deviceState != mojom.DeviceStateType.kUninitialized;
+        return deviceState.deviceState !== mojom.DeviceStateType.kUninitialized;
       case mojom.NetworkType.kCellular:
-        return deviceState.deviceState !=
+        return deviceState.deviceState !==
             mojom.DeviceStateType.kUninitialized &&
             !this.simLockedOrAbsent_(deviceState);
     }
@@ -266,7 +266,7 @@
    */
   enableToggleIsEnabled_(deviceState) {
     return this.enableToggleIsVisible_(deviceState) &&
-        deviceState.deviceState != mojom.DeviceStateType.kProhibited &&
+        deviceState.deviceState !== mojom.DeviceStateType.kProhibited &&
         !OncMojo.deviceStateIsIntermediate(deviceState.deviceState);
   },
 
@@ -300,7 +300,7 @@
     // device. This announces details about enabling bluetooth.
     if (this.enableToggleIsVisible_(deviceState) &&
         deviceState.type === mojom.NetworkType.kTether &&
-        deviceState.deviceState == mojom.DeviceStateType.kUninitialized) {
+        deviceState.deviceState === mojom.DeviceStateType.kUninitialized) {
       return 'networkState';
     }
     return '';
@@ -372,7 +372,7 @@
    * @private
    */
   shouldShowDetails_(activeNetworkState, deviceState, networkStateList) {
-    if (!!deviceState && deviceState.type == mojom.NetworkType.kVPN) {
+    if (!!deviceState && deviceState.type === mojom.NetworkType.kVPN) {
       return this.anyVpnExists_(deviceState, networkStateList);
     }
 
@@ -391,8 +391,8 @@
       return false;
     }
     const type = deviceState.type;
-    if (type == mojom.NetworkType.kTether ||
-        (type == mojom.NetworkType.kCellular && this.tetherDeviceState)) {
+    if (type === mojom.NetworkType.kTether ||
+        (type === mojom.NetworkType.kCellular && this.tetherDeviceState)) {
       // The "Mobile data" subpage should always be shown if Tether is
       // available, even if there are currently no associated networks.
       return true;
@@ -403,7 +403,7 @@
     }
 
     let minlen;
-    if (type == mojom.NetworkType.kWiFi) {
+    if (type === mojom.NetworkType.kWiFi) {
       // WiFi subpage includes 'Known Networks' so always show, even if the
       // technology is still enabling / scanning, or none are visible.
       minlen = 0;
@@ -527,8 +527,8 @@
     // The shared Cellular/Tether subpage is referred to as "Mobile".
     // TODO(khorimoto): Remove once Cellular/Tether are split into their own
     // sections.
-    if (type == mojom.NetworkType.kCellular ||
-        type == mojom.NetworkType.kTether) {
+    if (type === mojom.NetworkType.kCellular ||
+        type === mojom.NetworkType.kTether) {
       type = mojom.NetworkType.kMobile;
     }
     return this.i18n('OncType' + OncMojo.getNetworkTypeString(type));
diff --git a/chrome/browser/resources/settings/chromeos/localized_link/localized_link.js b/chrome/browser/resources/settings/chromeos/localized_link/localized_link.js
index 1ae4452..d09e3a9 100644
--- a/chrome/browser/resources/settings/chromeos/localized_link/localized_link.js
+++ b/chrome/browser/resources/settings/chromeos/localized_link/localized_link.js
@@ -57,7 +57,7 @@
     tempEl.childNodes.forEach((node, index) => {
       // Text nodes should be aria-hidden and associated with an element id
       // that the anchor element can be aria-labelledby.
-      if (node.nodeType == Node.TEXT_NODE) {
+      if (node.nodeType === Node.TEXT_NODE) {
         const spanNode = document.createElement('span');
         spanNode.textContent = node.textContent;
         spanNode.id = `id${index}`;
@@ -68,7 +68,7 @@
       }
       // The single element node with anchor tags should also be aria-labelledby
       // itself in-order with respect to the entire string.
-      if (node.nodeType == Node.ELEMENT_NODE && node.nodeName == 'A') {
+      if (node.nodeType === Node.ELEMENT_NODE && node.nodeName === 'A') {
         node.id = `id${index}`;
         ariaLabelledByIds.push(node.id);
         return;
@@ -81,16 +81,17 @@
     const anchorTags = tempEl.getElementsByTagName('a');
     // In the event the provided localizedString contains only text nodes,
     // populate the contents with the provided localizedString.
-    if (anchorTags.length == 0) {
+    if (anchorTags.length === 0) {
       return localizedString;
     }
 
-    assert(anchorTags.length == 1,
+    assert(
+        anchorTags.length === 1,
         'settings-localized-link should contain exactly one anchor tag');
     const anchorTag = anchorTags[0];
     anchorTag.setAttribute('aria-labelledby', ariaLabelledByIds.join(' '));
 
-    if (linkUrl != '') {
+    if (linkUrl !== '') {
       anchorTag.href = linkUrl;
       anchorTag.target = '_blank';
     }
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.js
index 852862c9..bcbc403 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.js
@@ -369,13 +369,13 @@
    */
   isAuthenticationRequiredToEnable_(feature) {
     // Enabling SmartLock always requires authentication.
-    if (feature == settings.MultiDeviceFeature.SMART_LOCK) {
+    if (feature === settings.MultiDeviceFeature.SMART_LOCK) {
       return true;
     }
 
     // Enabling any feature besides SmartLock and the Better Together suite does
     // not require authentication.
-    if (feature != settings.MultiDeviceFeature.BETTER_TOGETHER_SUITE) {
+    if (feature !== settings.MultiDeviceFeature.BETTER_TOGETHER_SUITE) {
       return false;
     }
 
@@ -387,9 +387,9 @@
     // SmartLock is implicitly enabled if it is only currently not enabled due
     // to the suite being disabled or due to the SmartLock host device not
     // having a lock screen set.
-    return smartLockState ==
+    return smartLockState ===
         settings.MultiDeviceFeatureState.UNAVAILABLE_SUITE_DISABLED ||
-        smartLockState ==
+        smartLockState ===
         settings.MultiDeviceFeatureState.UNAVAILABLE_INSUFFICIENT_SECURITY;
   },
 
@@ -413,7 +413,7 @@
 
     // Host status doesn't matter if we are navigating to Nearby Share
     // settings.
-    if (settings.routes.NEARBY_SHARE ==
+    if (settings.routes.NEARBY_SHARE ===
         settings.Router.getInstance().getCurrentRoute()) {
       return;
     }
@@ -421,7 +421,7 @@
     // If the user gets to the a nested page without a host (e.g. by clicking a
     // stale 'existing user' notifications after forgetting their host) we
     // direct them back to the main settings page.
-    if (settings.routes.MULTIDEVICE !=
+    if (settings.routes.MULTIDEVICE !==
             settings.Router.getInstance().getCurrentRoute() &&
         settings.routes.MULTIDEVICE.contains(
             settings.Router.getInstance().getCurrentRoute()) &&
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_subpage.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_subpage.js
index 6f5099c..82200e5c 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_subpage.js
@@ -120,7 +120,7 @@
    */
   computeIsSmartLockEnabled_() {
     return !!this.pageContentData &&
-        this.getFeatureState(settings.MultiDeviceFeature.SMART_LOCK) ==
+        this.getFeatureState(settings.MultiDeviceFeature.SMART_LOCK) ===
         settings.MultiDeviceFeatureState.ENABLED_BY_USER;
   },
 
@@ -155,7 +155,8 @@
    */
   onSmartLockSignInEnabledChanged_() {
     const radioGroup = this.$$('cr-radio-group');
-    const enabled = radioGroup.selected == settings.SmartLockSignInEnabledState.ENABLED;
+    const enabled =
+        radioGroup.selected === settings.SmartLockSignInEnabledState.ENABLED;
 
     if (!enabled) {
       // No authentication check is required to disable.
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_tether_item.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_tether_item.js
index c8a80061..ca19941 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_tether_item.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_tether_item.js
@@ -88,7 +88,7 @@
    */
   onActiveNetworksChanged(networks) {
     const guid = this.activeNetworkState_.guid;
-    if (!networks.find(network => network.guid == guid)) {
+    if (!networks.find(network => network.guid === guid)) {
       return;
     }
     this.networkConfig_.getNetworkState(guid).then(response => {
@@ -121,7 +121,7 @@
       const kTether = chromeos.networkConfig.mojom.NetworkType.kTether;
       const deviceStates = response.result;
       const deviceState =
-          deviceStates.find(deviceState => deviceState.type == kTether);
+          deviceStates.find(deviceState => deviceState.type === kTether);
       this.deviceState_ = deviceState || {
         deviceState: chromeos.networkConfig.mojom.DeviceStateType.kDisabled,
         managedNetworkAvailable: false,
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_wifi_sync_disabled_link.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_wifi_sync_disabled_link.js
index a45a364..aa5f0c0 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_wifi_sync_disabled_link.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_wifi_sync_disabled_link.js
@@ -25,7 +25,7 @@
 
     tempEl.childNodes.forEach((node, index) => {
       // Text nodes should be aria-hidden
-      if (node.nodeType == Node.TEXT_NODE) {
+      if (node.nodeType === Node.TEXT_NODE) {
         const spanNode = document.createElement('span');
         spanNode.textContent = node.textContent;
         spanNode.id = `id${index}`;
diff --git a/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_receive_dialog.js b/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_receive_dialog.js
index 116928e..caa6c08 100644
--- a/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_receive_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_receive_dialog.js
@@ -114,7 +114,7 @@
    * @param {boolean} inHighVisibility
    */
   onHighVisibilityChanged(inHighVisibility) {
-    if (inHighVisibility == false) {
+    if (inHighVisibility === false) {
       // TODO(crbug/1134745): Exiting high visibility can happen for multiple
       // reasons (timeout, user cancel, etc). During a receive transfer, it
       // happens before we start connecting (because we need to stop
@@ -149,7 +149,7 @@
    * @private
    */
   onSettingsChanged_(change) {
-    if (change.path != 'settings.enabled') {
+    if (change.path !== 'settings.enabled') {
       return;
     }
 
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/manage_a11y_page.js b/chrome/browser/resources/settings/chromeos/os_a11y_page/manage_a11y_page.js
index 0eb1bf6..98952cec 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/manage_a11y_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/manage_a11y_page.js
@@ -508,7 +508,7 @@
   onA11yCursorColorChange_() {
     // Custom cursor color is enabled when the color is not set to black.
     const a11yCursorColorOn =
-        this.get('prefs.settings.a11y.cursor_color.value') !=
+        this.get('prefs.settings.a11y.cursor_color.value') !==
         DEFAULT_BLACK_CURSOR_COLOR;
     this.set(
         'prefs.settings.a11y.cursor_color_enabled.value', a11yCursorColorOn);
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/switch_access_subpage.js b/chrome/browser/resources/settings/chromeos/os_a11y_page/switch_access_subpage.js
index 99a5ef5b..c1c40b9 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/switch_access_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/switch_access_subpage.js
@@ -245,7 +245,7 @@
    */
   currentSpeed_() {
     const speed = this.getPref(PREFIX + 'auto_scan.speed_ms').value;
-    if (typeof speed != 'number') {
+    if (typeof speed !== 'number') {
       return '';
     }
     return this.scanSpeedStringInSec_(speed);
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/tts_subpage.js b/chrome/browser/resources/settings/chromeos/os_a11y_page/tts_subpage.js
index e1621c2..2065384 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/tts_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/tts_subpage.js
@@ -257,7 +257,7 @@
       // locale of the device.
       result[voice.languageCode].preferred =
           result[voice.languageCode].preferred ||
-          preferredLangs.indexOf(voice.fullLanguageCode) != -1;
+          preferredLangs.indexOf(voice.fullLanguageCode) !== -1;
       languageCodeMap[voice.fullLanguageCode] = voice.languageCode;
     });
     this.updateLangToVoicePrefs_(result);
@@ -327,7 +327,7 @@
    * @private
    */
   hasOneLanguage_(lang) {
-    return lang['voices'].length == 1;
+    return lang['voices'].length === 1;
   },
 
   /**
@@ -352,7 +352,7 @@
    * @private
    */
   updateLangToVoicePrefs_(langToVoices) {
-    if (langToVoices.length == 0) {
+    if (langToVoices.length === 0) {
       return;
     }
     const allCodes = new Set(
@@ -397,7 +397,7 @@
    * @private
    */
   setDefaultPreviewVoiceForLocale_(allVoices, languageCodeMap) {
-    if (!allVoices || allVoices.length == 0) {
+    if (!allVoices || allVoices.length === 0) {
       return;
     }
 
@@ -414,7 +414,7 @@
     this.langBrowserProxy_.getProspectiveUILanguage().then(
         prospectiveUILanguage => {
           let result;
-          if (prospectiveUILanguage && prospectiveUILanguage != '' &&
+          if (prospectiveUILanguage && prospectiveUILanguage !== '' &&
               languageCodeMap[prospectiveUILanguage]) {
             const code = languageCodeMap[prospectiveUILanguage];
             // First try the pref value.
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.js b/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.js
index 206fcda7..e39e438 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.js
@@ -97,7 +97,7 @@
   updateButtons_(changeChannel, changeChannelAndPowerwash) {
     if (changeChannel || changeChannelAndPowerwash) {
       // Ensure that at most one button is visible at any given time.
-      assert(changeChannel != changeChannelAndPowerwash);
+      assert(changeChannel !== changeChannelAndPowerwash);
     }
 
     this.shouldShowButtons_ = {
@@ -111,7 +111,7 @@
     const selectedChannel = this.$$('cr-radio-group').selected;
 
     // Selected channel is the same as the target channel so only show 'cancel'.
-    if (selectedChannel == this.targetChannel_) {
+    if (selectedChannel === this.targetChannel_) {
       this.shouldShowButtons_ = null;
       this.$.warningSelector.select(WarningMessage.NONE);
       return;
@@ -119,7 +119,7 @@
 
     // Selected channel is the same as the current channel, allow the user to
     // change without warnings.
-    if (selectedChannel == this.currentChannel_) {
+    if (selectedChannel === this.currentChannel_) {
       this.updateButtons_(true, false);
       this.$.warningSelector.select(WarningMessage.NONE);
       return;
@@ -137,7 +137,7 @@
         this.updateButtons_(false, true);
       }
     } else {
-      if (selectedChannel == BrowserChannel.DEV) {
+      if (selectedChannel === BrowserChannel.DEV) {
         // Dev channel selected, warn the user.
         this.$.warningSelector.select(WarningMessage.UNSTABLE);
       } else {
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.js b/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.js
index 3d55cee4..916177b 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.js
@@ -220,7 +220,7 @@
     });
 
     if (settings.Router.getInstance().getQueryParameters().get(
-            'checkForUpdate') == 'true') {
+            'checkForUpdate') === 'true') {
       this.onCheckUpdatesClick_();
     }
   },
@@ -271,9 +271,9 @@
    * @private
    */
   onUpdateStatusChanged_(event) {
-    if (event.status == UpdateStatus.CHECKING) {
+    if (event.status === UpdateStatus.CHECKING) {
       this.hasCheckedForUpdates_ = true;
-    } else if (event.status == UpdateStatus.NEED_PERMISSION_TO_UPDATE) {
+    } else if (event.status === UpdateStatus.NEED_PERMISSION_TO_UPDATE) {
       this.showUpdateWarningDialog_ = true;
       this.updateInfo_ = {version: event.version, size: event.size};
     }
@@ -310,7 +310,7 @@
   updateShowUpdateStatus_() {
     // Do not show the "updated" status if we haven't checked yet or the update
     // warning dialog is shown to user.
-    if (this.currentUpdateStatusEvent_.status == UpdateStatus.UPDATED &&
+    if (this.currentUpdateStatusEvent_.status === UpdateStatus.UPDATED &&
         (!this.hasCheckedForUpdates_ || this.showUpdateWarningDialog_)) {
       this.showUpdateStatus_ = false;
       return;
@@ -323,7 +323,7 @@
     }
 
     this.showUpdateStatus_ =
-        this.currentUpdateStatusEvent_.status != UpdateStatus.DISABLED;
+        this.currentUpdateStatusEvent_.status !== UpdateStatus.DISABLED;
   },
 
   /**
@@ -357,7 +357,7 @@
    * @private
    */
   shouldShowLearnMoreLink_() {
-    return this.currentUpdateStatusEvent_.status == UpdateStatus.FAILED;
+    return this.currentUpdateStatusEvent_.status === UpdateStatus.FAILED;
   },
 
 
@@ -371,7 +371,7 @@
       case UpdateStatus.NEED_PERMISSION_TO_UPDATE:
         return this.i18nAdvanced('aboutUpgradeCheckStarted');
       case UpdateStatus.NEARLY_UPDATED:
-        if (this.currentChannel_ != this.targetChannel_) {
+        if (this.currentChannel_ !== this.targetChannel_) {
           return this.i18nAdvanced('aboutUpgradeSuccessChannelSwitch');
         }
         if (this.currentUpdateStatusEvent_.rollback) {
@@ -381,10 +381,10 @@
       case UpdateStatus.UPDATED:
         return this.i18nAdvanced('aboutUpgradeUpToDate');
       case UpdateStatus.UPDATING:
-        assert(typeof this.currentUpdateStatusEvent_.progress == 'number');
+        assert(typeof this.currentUpdateStatusEvent_.progress === 'number');
         const progressPercent = this.currentUpdateStatusEvent_.progress + '%';
 
-        if (this.currentChannel_ != this.targetChannel_) {
+        if (this.currentChannel_ !== this.targetChannel_) {
           return this.i18nAdvanced('aboutUpgradeUpdatingChannelSwitch', {
             substitutions: [
               this.i18nAdvanced(settings.browserChannelToI18nId(
@@ -476,7 +476,7 @@
    * @private
    */
   checkStatus_(status) {
-    return this.currentUpdateStatusEvent_.status == status;
+    return this.currentUpdateStatusEvent_.status === status;
   },
 
   /** @private */
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/android_apps_subpage.js b/chrome/browser/resources/settings/chromeos/os_apps_page/android_apps_subpage.js
index 42f9b5d..1f95636c 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/android_apps_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/android_apps_subpage.js
@@ -72,7 +72,7 @@
   /** @private */
   onPlayStoreEnabledChanged_(enabled) {
     if (!enabled &&
-        settings.Router.getInstance().getCurrentRoute() ==
+        settings.Router.getInstance().getCurrentRoute() ===
             settings.routes.ANDROID_APPS_DETAILS) {
       settings.Router.getInstance().navigateToPreviousRoute();
     }
@@ -91,7 +91,7 @@
    * @private
    */
   allowRemove_() {
-    return this.prefs.arc.enabled.enforcement !=
+    return this.prefs.arc.enabled.enforcement !==
         chrome.settingsPrivate.Enforcement.ENFORCED;
   },
 
@@ -134,7 +134,7 @@
    */
   onManageAndroidAppsTap_(event) {
     // |event.detail| is the click count. Keyboard events will have 0 clicks.
-    const isKeyboardAction = event.detail == 0;
+    const isKeyboardAction = event.detail === 0;
     settings.AndroidAppsBrowserProxyImpl.getInstance().showAndroidAppsSettings(
         isKeyboardAction);
   },
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/os_apps_page.js b/chrome/browser/resources/settings/chromeos/os_apps_page/os_apps_page.js
index 75cfec0c..c32a3a2 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/os_apps_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/os_apps_page.js
@@ -142,7 +142,7 @@
    * @private
    */
   isEnforced_(pref) {
-    return pref.enforcement == chrome.settingsPrivate.Enforcement.ENFORCED;
+    return pref.enforcement === chrome.settingsPrivate.Enforcement.ENFORCED;
   },
 
   /** @private */
@@ -159,7 +159,7 @@
    */
   onManageAndroidAppsTap_(event) {
     // |event.detail| is the click count. Keyboard events will have 0 clicks.
-    const isKeyboardAction = event.detail == 0;
+    const isKeyboardAction = event.detail === 0;
     settings.AndroidAppsBrowserProxyImpl.getInstance().showAndroidAppsSettings(
         isKeyboardAction);
   },
diff --git a/chrome/browser/resources/settings/chromeos/os_files_page/smb_shares_page.js b/chrome/browser/resources/settings/chromeos/os_files_page/smb_shares_page.js
index 6751283..b51c90a 100644
--- a/chrome/browser/resources/settings/chromeos/os_files_page/smb_shares_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_files_page/smb_shares_page.js
@@ -28,10 +28,10 @@
    * @protected
    */
   currentRouteChanged(route) {
-    if (route == settings.routes.SMB_SHARES) {
+    if (route === settings.routes.SMB_SHARES) {
       this.showAddSmbDialog_ =
           settings.Router.getInstance().getQueryParameters().get(
-              'showAddShare') == 'true';
+              'showAddShare') === 'true';
     }
   },
 
diff --git a/chrome/browser/resources/settings/chromeos/os_languages_page/change_device_language_dialog.js b/chrome/browser/resources/settings/chromeos/os_languages_page/change_device_language_dialog.js
index cd257e5..4475592c 100644
--- a/chrome/browser/resources/settings/chromeos/os_languages_page/change_device_language_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/os_languages_page/change_device_language_dialog.js
@@ -96,7 +96,7 @@
   getDisplayText_(language) {
     let displayText = language.displayName;
     // If the native name is different, add it.
-    if (language.displayName != language.nativeDisplayName) {
+    if (language.displayName !== language.nativeDisplayName) {
       displayText += ' - ' + language.nativeDisplayName;
     }
     return displayText;
diff --git a/chrome/browser/resources/settings/chromeos/os_languages_page/input_method_options_page.js b/chrome/browser/resources/settings/chromeos/os_languages_page/input_method_options_page.js
index 6bf4ecf..9a9cd78 100644
--- a/chrome/browser/resources/settings/chromeos/os_languages_page/input_method_options_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_languages_page/input_method_options_page.js
@@ -62,7 +62,7 @@
    * @protected
    */
   currentRouteChanged(route, oldRoute) {
-    if (route != settings.routes.OS_LANGUAGES_INPUT_METHOD_OPTIONS) {
+    if (route !== settings.routes.OS_LANGUAGES_INPUT_METHOD_OPTIONS) {
       this.id_ = '';
       this.parentNode.pageTitle = '';
       this.optionSections_ = [];
diff --git a/chrome/browser/resources/settings/chromeos/os_languages_page/input_page.js b/chrome/browser/resources/settings/chromeos/os_languages_page/input_page.js
index 06435da..470f9313 100644
--- a/chrome/browser/resources/settings/chromeos/os_languages_page/input_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_languages_page/input_page.js
@@ -261,7 +261,7 @@
     // Disable remove if there is no other component IME (pre-installed
     // system IMES) enabled.
     return !this.languages.inputMethods.enabled.some(
-        inputMethod => inputMethod.id != targetInputMethod.id &&
+        inputMethod => inputMethod.id !== targetInputMethod.id &&
             this.languageHelper.isComponentIme(inputMethod));
   },
 
diff --git a/chrome/browser/resources/settings/chromeos/os_languages_page/manage_input_methods_page.js b/chrome/browser/resources/settings/chromeos/os_languages_page/manage_input_methods_page.js
index 1f723e2..ef29c04 100644
--- a/chrome/browser/resources/settings/chromeos/os_languages_page/manage_input_methods_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_languages_page/manage_input_methods_page.js
@@ -91,7 +91,7 @@
 
     // Can be removed as long as there is another component IME.
     return this.languages.inputMethods.enabled.some(function(inputMethod) {
-      return inputMethod != targetInputMethod &&
+      return inputMethod !== targetInputMethod &&
           this.languageHelper.isComponentIme(inputMethod);
     }, this);
   },
@@ -128,7 +128,7 @@
       const languageFamilyCodes = [languageState.language.code];
       for (let j = i + 1; j < this.languages.enabled.length; j++) {
         const otherCode = this.languages.enabled[j].language.code;
-        if (this.languageHelper.getLanguageCodeWithoutRegion(otherCode) ==
+        if (this.languageHelper.getLanguageCodeWithoutRegion(otherCode) ===
             baseLanguageCode) {
           languageFamilyCodes.push(this.languages.enabled[j].language.code);
         }
diff --git a/chrome/browser/resources/settings/chromeos/os_languages_page/os_add_languages_dialog.js b/chrome/browser/resources/settings/chromeos/os_languages_page/os_add_languages_dialog.js
index db28100..88c7e2d 100644
--- a/chrome/browser/resources/settings/chromeos/os_languages_page/os_add_languages_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/os_languages_page/os_add_languages_dialog.js
@@ -63,7 +63,7 @@
 
   // Override FindShortcutBehavior methods.
   searchInputHasFocus() {
-    return this.$.search.getSearchInput() ==
+    return this.$.search.getSearchInput() ===
         this.$.search.shadowRoot.activeElement;
   },
 
@@ -105,7 +105,7 @@
   getDisplayText_(language) {
     let displayText = language.displayName;
     // If the native name is different, add it.
-    if (language.displayName != language.nativeDisplayName) {
+    if (language.displayName !== language.nativeDisplayName) {
       displayText += ' - ' + language.nativeDisplayName;
     }
     return displayText;
@@ -164,9 +164,9 @@
    */
   onKeydown_(e) {
     // Close dialog if 'esc' is pressed and the search box is already empty.
-    if (e.key == 'Escape' && !this.$.search.getValue().trim()) {
+    if (e.key === 'Escape' && !this.$.search.getValue().trim()) {
       this.$.dialog.close();
-    } else if (e.key != 'PageDown' && e.key != 'PageUp') {
+    } else if (e.key !== 'PageDown' && e.key !== 'PageUp') {
       this.$.search.scrollIntoViewIfNeeded();
     }
   },
diff --git a/chrome/browser/resources/settings/chromeos/os_languages_page/os_languages_page.js b/chrome/browser/resources/settings/chromeos/os_languages_page/os_languages_page.js
index 9be60df7..cfaf030 100644
--- a/chrome/browser/resources/settings/chromeos/os_languages_page/os_languages_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_languages_page/os_languages_page.js
@@ -156,7 +156,7 @@
      * @private
      */
     canEnableSomeSupportedLanguage_(languages) {
-      return languages == undefined || languages.supported.some(language => {
+      return languages === undefined || languages.supported.some(language => {
         return this.languageHelper.canEnableLanguage(language);
       });
     },
@@ -169,8 +169,8 @@
      * @private
      */
     shouldShowDialogSeparator_() {
-      return this.languages != undefined && this.languages.enabled.length > 1 &&
-          !this.isGuest_;
+      return this.languages !== undefined &&
+          this.languages.enabled.length > 1 && !this.isGuest_;
     },
 
     /**
@@ -182,7 +182,7 @@
      * @private
      */
     isNthLanguage_(n) {
-      if (this.languages == undefined || this.detailLanguage_ == undefined) {
+      if (this.languages === undefined || this.detailLanguage_ === undefined) {
         return false;
       }
 
@@ -191,7 +191,7 @@
       }
 
       const compareLanguage = assert(this.languages.enabled[n]);
-      return this.detailLanguage_.language == compareLanguage.language;
+      return this.detailLanguage_.language === compareLanguage.language;
     },
 
     /**
@@ -211,7 +211,7 @@
      * @private
      */
     showMoveDown_() {
-      return this.languages != undefined &&
+      return this.languages !== undefined &&
           !this.isNthLanguage_(this.languages.enabled.length - 1);
     },
 
@@ -220,7 +220,7 @@
      * @return {boolean} True if there are less than 2 languages.
      */
     isHelpTextHidden_(change) {
-      return this.languages != undefined && this.languages.enabled.length <= 1;
+      return this.languages !== undefined && this.languages.enabled.length <= 1;
     },
 
     /**
@@ -245,12 +245,12 @@
       // Taps on the button are handled in onInputMethodOptionsTap_.
       // TODO(dschuyler): The row has two operations that are not clearly
       // delineated. crbug.com/740691
-      if (e.target.tagName == 'CR-ICON-BUTTON') {
+      if (e.target.tagName === 'CR-ICON-BUTTON') {
         return;
       }
 
       // Ignore key presses other than <Enter>.
-      if (e.type == 'keypress' && e.key != 'Enter') {
+      if (e.type === 'keypress' && e.key !== 'Enter') {
         return;
       }
 
@@ -308,7 +308,7 @@
      * @private
      */
     isRestartRequired_(languageCode, prospectiveUILanguage) {
-      return prospectiveUILanguage == languageCode &&
+      return prospectiveUILanguage === languageCode &&
           this.languageHelper.requiresRestart();
     },
 
@@ -350,7 +350,7 @@
       }
 
       // Unchecking the currently chosen language doesn't make much sense.
-      if (languageState.language.code == prospectiveUILanguage) {
+      if (languageState.language.code === prospectiveUILanguage) {
         return true;
       }
 
@@ -437,7 +437,7 @@
      * @private
      */
     isProspectiveUILanguage_(languageCode, prospectiveUILanguage) {
-      return languageCode == prospectiveUILanguage;
+      return languageCode === prospectiveUILanguage;
     },
 
     /**
@@ -449,7 +449,7 @@
      * @private
      */
     getLanguageItemClass_(languageCode, prospectiveUILanguage) {
-      if (languageCode == prospectiveUILanguage) {
+      if (languageCode === prospectiveUILanguage) {
         return 'selected';
       }
       return '';
@@ -462,7 +462,7 @@
      * @private
      */
     isCurrentInputMethod_(id, currentId) {
-      return id == currentId;
+      return id === currentId;
     },
 
     /**
@@ -537,7 +537,7 @@
     toggleExpandButton_(e) {
       // The expand button handles toggling itself.
       const expandButtonTag = 'CR-EXPAND-BUTTON';
-      if (e.target.tagName == expandButtonTag) {
+      if (e.target.tagName === expandButtonTag) {
         return;
       }
 
@@ -561,7 +561,7 @@
      * @private
      */
     getInputMethodTabIndex_(id, currentId) {
-      return id == currentId ? '' : '0';
+      return id === currentId ? '' : '0';
     },
 
     /**
diff --git a/chrome/browser/resources/settings/chromeos/os_languages_page/os_languages_section.js b/chrome/browser/resources/settings/chromeos/os_languages_page/os_languages_section.js
index f3872458..ff9644a 100644
--- a/chrome/browser/resources/settings/chromeos/os_languages_page/os_languages_section.js
+++ b/chrome/browser/resources/settings/chromeos/os_languages_page/os_languages_section.js
@@ -102,7 +102,7 @@
         this.languageHelper.getLanguage(uiLanguage).displayName;
     const inputMethod =
         this.languages.inputMethods.enabled.find(function(inputMethod) {
-          return inputMethod.id == id;
+          return inputMethod.id === id;
         });
     const inputMethodDisplayName = inputMethod ? inputMethod.displayName : '';
     // It is OK to use string concatenation here because it is just joining a 2
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/fingerprint_list.js b/chrome/browser/resources/settings/chromeos/os_people_page/fingerprint_list.js
index 0f21a119..2c83063 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/fingerprint_list.js
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/fingerprint_list.js
@@ -104,7 +104,7 @@
    * @protected
    */
   currentRouteChanged(newRoute, oldRoute) {
-    if (newRoute != settings.routes.FINGERPRINT) {
+    if (newRoute !== settings.routes.FINGERPRINT) {
       if (this.browserProxy_) {
         this.browserProxy_.endCurrentAuthentication();
       }
@@ -112,7 +112,7 @@
       return;
     }
 
-    if (oldRoute == settings.routes.LOCK_SCREEN) {
+    if (oldRoute === settings.routes.LOCK_SCREEN) {
       // Start fingerprint authentication when going from LOCK_SCREEN to
       // FINGERPRINT page.
       this.browserProxy_.startAuthentication();
@@ -226,7 +226,7 @@
    */
   onScreenLocked_(screenIsLocked) {
     if (!screenIsLocked &&
-        settings.Router.getInstance().getCurrentRoute() ==
+        settings.Router.getInstance().getCurrentRoute() ===
             settings.routes.FINGERPRINT) {
       this.onSetupFingerprintDialogClose_();
     }
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/kerberos_accounts.js b/chrome/browser/resources/settings/chromeos/os_people_page/kerberos_accounts.js
index df9d6c42..7e30325 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/kerberos_accounts.js
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/kerberos_accounts.js
@@ -89,7 +89,7 @@
       const queryParams = settings.Router.getInstance().getQueryParameters();
       const reauthPrincipal = queryParams.get('kerberos_reauth');
       const reauthAccount = this.accounts_.find(account => {
-        return account.principalName == reauthPrincipal;
+        return account.principalName === reauthPrincipal;
       });
       if (reauthAccount) {
         this.selectedAccount_ = reauthAccount;
@@ -192,7 +192,7 @@
         .removeAccount(
             /** @type {!settings.KerberosAccount} */ (this.selectedAccount_))
         .then(error => {
-          if (error == settings.KerberosErrorType.kNone) {
+          if (error === settings.KerberosErrorType.kNone) {
             this.showToast_('kerberosAccountsAccountRemovedTip');
           } else {
             console.error('Unexpected error removing account: ' + error);
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/kerberos_add_account_dialog.js b/chrome/browser/resources/settings/chromeos/os_people_page/kerberos_add_account_dialog.js
index f57dfc9a..fd15600 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/kerberos_add_account_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/kerberos_add_account_dialog.js
@@ -199,7 +199,7 @@
           this.inProgress_ = false;
 
           // Success case. Close dialog.
-          if (error == settings.KerberosErrorType.kNone) {
+          if (error === settings.KerberosErrorType.kNone) {
             this.accountWasRefreshed = this.presetAccount != null;
             this.$.addDialog.close();
             return;
@@ -243,7 +243,7 @@
       this.inProgress_ = false;
 
       // Success case. Close dialog.
-      if (result.error == settings.KerberosErrorType.kNone) {
+      if (result.error === settings.KerberosErrorType.kNone) {
         this.showAdvancedConfig_ = false;
         this.config_ = this.editableConfig_;
         this.configErrorText_ = '';
@@ -321,11 +321,11 @@
    */
   updateConfigErrorMessage_(result) {
     // There should be an error at this point.
-    assert(result.error != settings.KerberosErrorType.kNone);
+    assert(result.error !== settings.KerberosErrorType.kNone);
 
     // Only handle kBadConfig here. Display generic error otherwise. Should only
     // occur if something is wrong with D-Bus, but nothing user-induced.
-    if (result.error != settings.KerberosErrorType.kBadConfig) {
+    if (result.error !== settings.KerberosErrorType.kBadConfig) {
       this.configErrorText_ =
           this.i18n('kerberosErrorGeneral', result.error.toString());
       return;
@@ -334,13 +334,13 @@
     let errorLine = '';
 
     // Don't fall for the classical blunder 0 == false.
-    if (result.errorInfo.lineIndex != undefined) {
+    if (result.errorInfo.lineIndex !== undefined) {
       const textArea = this.$$('#config').shadowRoot.querySelector('#input');
       errorLine = this.selectAndScrollTo_(textArea, result.errorInfo.lineIndex);
     }
 
     // If kBadConfig, the error code should be set.
-    assert(result.errorInfo.code != settings.KerberosConfigErrorCode.kNone);
+    assert(result.errorInfo.code !== settings.KerberosConfigErrorCode.kNone);
     this.configErrorText_ =
         this.getConfigErrorString_(result.errorInfo.code, errorLine);
   },
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.js b/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.js
index 3aed3253..4f35007 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.js
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.js
@@ -182,7 +182,7 @@
    * @protected
    */
   currentRouteChanged(newRoute, oldRoute) {
-    if (newRoute == settings.routes.LOCK_SCREEN) {
+    if (newRoute === settings.routes.LOCK_SCREEN) {
       this.updateUnlockType(/*activeModesChanged=*/ false);
       this.updateNumFingerprints_();
       this.attemptDeepLink();
@@ -239,11 +239,11 @@
    * @private
    */
   selectedUnlockTypeChanged_(selected) {
-    if (selected == LockScreenUnlockType.VALUE_PENDING) {
+    if (selected === LockScreenUnlockType.VALUE_PENDING) {
       return;
     }
 
-    if (selected != LockScreenUnlockType.PIN_PASSWORD && this.setModes) {
+    if (selected !== LockScreenUnlockType.PIN_PASSWORD && this.setModes) {
       // If the user selects PASSWORD only (which sends an asynchronous
       // setModes.call() to clear the quick unlock capability), indicate to the
       // user immediately that the quick unlock capability is cleared by setting
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/lock_state_behavior.js b/chrome/browser/resources/settings/chromeos/os_people_page/lock_state_behavior.js
index 554207c3..b3433c9 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/lock_state_behavior.js
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/lock_state_behavior.js
@@ -117,7 +117,7 @@
         // user wishes to set a pin, they will have to click the set pin button.
         // See https://crbug.com/1054327 for details.
         if (activeModesChanged && !this.hasPin &&
-            this.selectedUnlockType == LockScreenUnlockType.PIN_PASSWORD) {
+            this.selectedUnlockType === LockScreenUnlockType.PIN_PASSWORD) {
           return;
         }
         this.hasPin = false;
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/os_people_page.js b/chrome/browser/resources/settings/chromeos/os_people_page/os_people_page.js
index 0ead4de..02749848 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/os_people_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/os_people_page.js
@@ -314,7 +314,7 @@
    * @protected
    */
   currentRouteChanged(route, oldRoute) {
-    if (settings.Router.getInstance().getCurrentRoute() ==
+    if (settings.Router.getInstance().getCurrentRoute() ===
         settings.routes.OS_SIGN_OUT) {
       // If the sync status has not been fetched yet, optimistically display
       // the sign-out dialog. There is another check when the sync status is
@@ -403,7 +403,7 @@
     // The user might not have any GAIA accounts (e.g. guest mode or Active
     // Directory). In these cases the profile row is hidden, so there's nothing
     // to do.
-    if (accounts.length == 0) {
+    if (accounts.length === 0) {
       return;
     }
     this.profileName_ = accounts[0].fullName;
@@ -447,7 +447,7 @@
     this.showSignoutDialog_ = false;
     cr.ui.focusWithoutInk(assert(this.$$('#disconnectButton')));
 
-    if (settings.Router.getInstance().getCurrentRoute() ==
+    if (settings.Router.getInstance().getCurrentRoute() ===
         settings.routes.OS_SIGN_OUT) {
       settings.Router.getInstance().navigateToPreviousRoute();
     }
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_controls.js b/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_controls.js
index a48d652..e84a3faa 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_controls.js
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_controls.js
@@ -127,11 +127,11 @@
    * @protected
    */
   currentRouteChanged(newRoute, oldRoute) {
-    if (newRoute == settings.routes.OS_SYNC) {
+    if (newRoute === settings.routes.OS_SYNC) {
       this.browserProxy_.didNavigateToOsSyncPage();
       this.attemptDeepLink();
     }
-    if (oldRoute == settings.routes.OS_SYNC) {
+    if (oldRoute === settings.routes.OS_SYNC) {
       this.browserProxy_.didNavigateAwayFromOsSyncPage();
     }
   },
@@ -191,7 +191,7 @@
     if (this.syncStatus.hasUnrecoverableError) {
       return 'sync-problem';
     }
-    if (this.syncStatus.statusAction == settings.StatusAction.REAUTHENTICATE) {
+    if (this.syncStatus.statusAction === settings.StatusAction.REAUTHENTICATE) {
       return 'sync-paused';
     }
     return 'sync-problem';
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/setup_fingerprint_dialog.js b/chrome/browser/resources/settings/chromeos/os_people_page/setup_fingerprint_dialog.js
index 8554a23..b129d94 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/setup_fingerprint_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/setup_fingerprint_dialog.js
@@ -127,7 +127,7 @@
           const fingerprintLocation =
               loadTimeData.getInteger('fingerprintReaderLocation');
           const isTabletPowerButton =
-              FingerprintLocation.TABLET_POWER_BUTTON == fingerprintLocation;
+              FingerprintLocation.TABLET_POWER_BUTTON === fingerprintLocation;
           return isTabletPowerButton;
         },
         readOnly: true,
@@ -166,7 +166,7 @@
 
       // Note: Reset resets |step_| back to the default, so handle anything that
       // checks |step_| before resetting.
-      if (this.step_ != FingerprintSetupStep.READY) {
+      if (this.step_ !== FingerprintSetupStep.READY) {
         this.browserProxy_.cancelCurrentEnroll();
       }
 
@@ -175,7 +175,7 @@
 
     /** private */
     clearSensorMessageTimeout_() {
-      if (this.tapSensorMessageTimeoutId_ != 0) {
+      if (this.tapSensorMessageTimeoutId_ !== 0) {
         clearTimeout(this.tapSensorMessageTimeoutId_);
         this.tapSensorMessageTimeoutId_ = 0;
       }
@@ -294,7 +294,7 @@
      * @private
      */
     getCloseButtonText_(step) {
-      if (step == FingerprintSetupStep.READY) {
+      if (step === FingerprintSetupStep.READY) {
         return this.i18n('done');
       }
 
@@ -306,7 +306,7 @@
      * @private
      */
     getCloseButtonClass_(step) {
-      if (step == FingerprintSetupStep.READY) {
+      if (step === FingerprintSetupStep.READY) {
         return 'action-button';
       }
 
@@ -319,7 +319,7 @@
      * @private
      */
     hideAddAnother_(step, allowAddAnotherFinger) {
-      return step != FingerprintSetupStep.READY || !allowAddAnotherFinger;
+      return step !== FingerprintSetupStep.READY || !allowAddAnotherFinger;
     },
 
     /**
@@ -340,7 +340,7 @@
      * @private
      */
     showScannerLocation_() {
-      return this.step_ == FingerprintSetupStep.LOCATE_SCANNER;
+      return this.step_ === FingerprintSetupStep.LOCATE_SCANNER;
     },
 
     /**
@@ -348,8 +348,8 @@
      * @private
      */
     showArc_() {
-      return this.step_ == FingerprintSetupStep.MOVE_FINGER ||
-          this.step_ == FingerprintSetupStep.READY;
+      return this.step_ === FingerprintSetupStep.MOVE_FINGER ||
+          this.step_ === FingerprintSetupStep.READY;
     },
 
     /**
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/user_list.js b/chrome/browser/resources/settings/chromeos/os_people_page/user_list.js
index dd03645..9843759 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/user_list.js
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/user_list.js
@@ -53,7 +53,7 @@
   ready() {
     chrome.settingsPrivate.onPrefsChanged.addListener(prefs => {
       prefs.forEach(function(pref) {
-        if (pref.key == 'cros.accounts.users') {
+        if (pref.key === 'cros.accounts.users') {
           this.usersPrivate_.getUsers(
               (/** !Array<!chrome.usersPrivate.User> */ users) => {
                 this.setUsers_(users);
@@ -65,7 +65,7 @@
 
   /** @protected */
   currentRouteChanged() {
-    if (settings.Router.getInstance().getCurrentRoute() ==
+    if (settings.Router.getInstance().getCurrentRoute() ===
         settings.routes.ACCOUNTS) {
       this.usersPrivate_.getUsers(
           (/** !Array<!chrome.usersPrivate.User> */ users) => {
@@ -90,7 +90,7 @@
   setUsers_(users) {
     this.users_ = users;
     this.users_.sort(function(a, b) {
-      if (a.isOwner != b.isOwner) {
+      if (a.isOwner !== b.isOwner) {
         return b.isOwner ? 1 : -1;
       } else {
         return -1;
@@ -133,7 +133,7 @@
    * @private
    */
   shouldShowEmail_(user) {
-    return !user.isSupervised && user.name != user.displayEmail;
+    return !user.isSupervised && user.name !== user.displayEmail;
   },
 
   /**
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/users_add_user_dialog.js b/chrome/browser/resources/settings/chromeos/os_people_page/users_add_user_dialog.js
index 299a4ab..2384d027 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/users_add_user_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/users_add_user_dialog.js
@@ -124,7 +124,7 @@
   onInput_() {
     const input = this.$.addUserInput.value;
     this.isEmail_ = NAME_ONLY_REGEX.test(input) || EMAIL_REGEX.test(input);
-    this.isEmpty_ = input.length == 0;
+    this.isEmpty_ = input.length === 0;
 
     if (!this.isEmail_ && !this.isEmpty_) {
       this.errorCode_ = UserAddError.INVALID_EMAIL;
@@ -139,7 +139,7 @@
    * @return {boolean}
    */
   shouldShowError_() {
-    return this.errorCode_ != UserAddError.NO_ERROR;
+    return this.errorCode_ !== UserAddError.NO_ERROR;
   },
 
   /**
@@ -147,7 +147,7 @@
    * @return {string}
    */
   getErrorString_(errorCode_) {
-    if (errorCode_ == UserAddError.USER_EXISTS) {
+    if (errorCode_ === UserAddError.USER_EXISTS) {
       return this.i18n('userExistsError');
     }
     // TODO errorString for UserAddError.INVALID_EMAIL crbug/1007481
diff --git a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_add_print_server_dialog.js b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_add_print_server_dialog.js
index 1207be0f..c2763201c 100644
--- a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_add_print_server_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_add_print_server_dialog.js
@@ -62,7 +62,7 @@
    */
   onPrintServerAddedFailed_: function(addPrintServerError) {
     this.inProgress_ = false;
-    if (addPrintServerError == PrintServerResult.INCORRECT_URL) {
+    if (addPrintServerError === PrintServerResult.INCORRECT_URL) {
       this.$$('#printServerAddressInput').invalid = true;
       return;
     }
@@ -76,7 +76,7 @@
    * @private
    */
   onKeypress_: function(event) {
-    if (event.key != 'Enter') {
+    if (event.key !== 'Enter') {
       return;
     }
     event.stopPropagation();
diff --git a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_add_printer_manually_dialog.js b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_add_printer_manually_dialog.js
index 03f79b4..a5bc5fe 100644
--- a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_add_printer_manually_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_add_printer_manually_dialog.js
@@ -134,7 +134,7 @@
    */
   infoFailed_(result) {
     this.addPrinterInProgress_ = false;
-    if (result == PrinterSetupResult.PRINTER_UNREACHABLE) {
+    if (result === PrinterSetupResult.PRINTER_UNREACHABLE) {
       this.$.printerAddressInput.invalid = true;
       return;
     }
@@ -146,8 +146,8 @@
   addPressed_() {
     this.addPrinterInProgress_ = true;
 
-    if (this.newPrinter.printerProtocol == 'ipp' ||
-        this.newPrinter.printerProtocol == 'ipps') {
+    if (this.newPrinter.printerProtocol === 'ipp' ||
+        this.newPrinter.printerProtocol === 'ipps') {
       settings.CupsPrintersBrowserProxyImpl.getInstance()
           .getPrinterInfo(this.newPrinter)
           .then(this.onPrinterFound_.bind(this), this.infoFailed_.bind(this));
@@ -195,7 +195,7 @@
    * @private
    */
   onKeypress_(event) {
-    if (event.key != 'Enter') {
+    if (event.key !== 'Enter') {
       return;
     }
     event.stopPropagation();
diff --git a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_add_printer_manufacturer_model_dialog.js b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_add_printer_manufacturer_model_dialog.js
index 4cbd9f6..6064779 100644
--- a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_add_printer_manufacturer_model_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_add_printer_manufacturer_model_dialog.js
@@ -143,7 +143,7 @@
     // Reset model if manufacturer is changed.
     this.set('activePrinter.ppdModel', '');
     this.modelList = [];
-    if (manufacturer && manufacturer.length != 0) {
+    if (manufacturer && manufacturer.length !== 0) {
       settings.CupsPrintersBrowserProxyImpl.getInstance()
           .getCupsPrinterModelsList(manufacturer)
           .then(this.modelListChanged_.bind(this));
@@ -186,7 +186,7 @@
       return;
     }
     this.manufacturerList = manufacturersInfo.manufacturers;
-    if (this.activePrinter.ppdManufacturer.length != 0) {
+    if (this.activePrinter.ppdManufacturer.length !== 0) {
       settings.CupsPrintersBrowserProxyImpl.getInstance()
           .getCupsPrinterModelsList(this.activePrinter.ppdManufacturer)
           .then(this.modelListChanged_.bind(this));
diff --git a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_edit_printer_dialog.js b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_edit_printer_dialog.js
index 1f0af46..aafca231 100644
--- a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_edit_printer_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_edit_printer_dialog.js
@@ -190,7 +190,7 @@
    * @private
    */
   printerPathChanged_(change) {
-    if (change.path != 'pendingPrinter_.printerName') {
+    if (change.path !== 'pendingPrinter_.printerName') {
       this.needsReconfigured_ = true;
     }
   },
@@ -336,7 +336,7 @@
     // Reset model if manufacturer is changed.
     this.set('pendingPrinter_.ppdModel', '');
     this.modelList = [];
-    if (!!manufacturer && manufacturer.length != 0) {
+    if (!!manufacturer && manufacturer.length !== 0) {
       settings.CupsPrintersBrowserProxyImpl.getInstance()
           .getCupsPrinterModelsList(manufacturer)
           .then(this.modelListChanged_.bind(this));
@@ -388,7 +388,7 @@
       return;
     }
     this.manufacturerList = manufacturersInfo.manufacturers;
-    if (this.pendingPrinter_.ppdManufacturer.length != 0) {
+    if (this.pendingPrinter_.ppdManufacturer.length !== 0) {
       settings.CupsPrintersBrowserProxyImpl.getInstance()
           .getCupsPrinterModelsList(this.pendingPrinter_.ppdManufacturer)
           .then(this.modelListChanged_.bind(this));
diff --git a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_nearby_printers.js b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_nearby_printers.js
index fcf7ba3..0f09391c 100644
--- a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_nearby_printers.js
+++ b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_nearby_printers.js
@@ -164,7 +164,8 @@
    */
   setActivePrinter_(item) {
     this.activePrinterListEntryIndex_ = this.nearbyPrinters.findIndex(
-        printer => printer.printerInfo.printerId == item.printerInfo.printerId);
+        printer =>
+            printer.printerInfo.printerId === item.printerInfo.printerId);
 
     this.activePrinter =
         this.get(['nearbyPrinters', this.activePrinterListEntryIndex_])
diff --git a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printer_dialog_util.js b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printer_dialog_util.js
index baa2573..c988f42 100644
--- a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printer_dialog_util.js
+++ b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printer_dialog_util.js
@@ -173,7 +173,7 @@
    * @return {number}
    */
   /* #export */ function sortPrinters(first, second) {
-    if (first.printerType == second.printerType) {
+    if (first.printerType === second.printerType) {
       return settings.printing.alphabeticalSort(
           first.printerInfo, second.printerInfo);
     }
@@ -196,7 +196,7 @@
    * @return {boolean}
    */
   function arePrinterIdsEqual(first, second) {
-    return first.printerInfo.printerId == second.printerInfo.printerId;
+    return first.printerInfo.printerId === second.printerInfo.printerId;
   }
 
   /**
@@ -208,7 +208,7 @@
   /* #export */ function findDifference(firstArr, secondArr) {
     return firstArr.filter(p1 => {
       return !secondArr.some(
-          p2 => p2.printerInfo.printerId == p1.printerInfo.printerId);
+          p2 => p2.printerInfo.printerId === p1.printerInfo.printerId);
     });
   }
 
diff --git a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printers.html b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printers.html
index 2c9a1e1..438fe8f 100644
--- a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printers.html
+++ b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printers.html
@@ -167,13 +167,13 @@
       </settings-cups-edit-printer-dialog>
     </template>
 
-    <cr-toast id="errorToast" duration="3000" role="alert">
+    <cr-toast id="errorToast" duration="3000">
       <div class="error-message" id="addPrinterDoneMessage">
         [[addPrinterResultText_]]
       </div>
     </cr-toast>
 
-    <cr-toast id="printServerErrorToast" duration="3000" role="alert">
+    <cr-toast id="printServerErrorToast" duration="3000">
       <div class="error-message" id="addPrintServerDoneMessage">
         [[addPrintServerResultText_]]
       </div>
diff --git a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printers.js b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printers.js
index 4c1151e..0b2390b 100644
--- a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printers.js
+++ b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printers.js
@@ -179,7 +179,7 @@
    * @protected
    */
   currentRouteChanged(route) {
-    if (route != settings.routes.CUPS_PRINTERS) {
+    if (route !== settings.routes.CUPS_PRINTERS) {
       if (this.onPrintersChangedListener_) {
         cr.removeWebUIListener(
             /** @type {WebUIListener} */ (this.onPrintersChangedListener_));
@@ -207,7 +207,7 @@
       // Note: Check for kOnline rather than using
       // OncMojo.connectionStateIsConnected() since the latter could return true
       // for networks without connectivity (e.g., captive portals).
-      return network.connectionState ==
+      return network.connectionState ===
           chromeos.networkConfig.mojom.ConnectionStateType.kOnline;
     });
   },
@@ -351,20 +351,20 @@
 
   /** @private */
   getSavedPrintersAriaLabel_() {
-    const printerLabel = this.savedPrinterCount_ == 0 ?
+    const printerLabel = this.savedPrinterCount_ === 0 ?
         'savedPrintersCountNone' :
-        this.savedPrinterCount_ == 1 ? 'savedPrintersCountOne' :
-                                       'savedPrintersCountMany';
+        this.savedPrinterCount_ === 1 ? 'savedPrintersCountOne' :
+                                        'savedPrintersCountMany';
 
     return loadTimeData.getStringF(printerLabel, this.savedPrinterCount_);
   },
 
   /** @private */
   getNearbyPrintersAriaLabel_() {
-    const printerLabel = this.nearbyPrinterCount_ == 0 ?
+    const printerLabel = this.nearbyPrinterCount_ === 0 ?
         'nearbyPrintersCountNone' :
-        this.nearbyPrinterCount_ == 1 ? 'nearbyPrintersCountOne' :
-                                        'nearbyPrintersCountMany';
+        this.nearbyPrinterCount_ === 1 ? 'nearbyPrintersCountOne' :
+                                         'nearbyPrintersCountMany';
 
     return loadTimeData.getStringF(printerLabel, this.nearbyPrinterCount_);
   },
diff --git a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printers_entry.js b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printers_entry.js
index 7b2faed..60e08d7 100644
--- a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printers_entry.js
+++ b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printers_entry.js
@@ -56,7 +56,7 @@
    * @private
    */
   isSavedPrinter_() {
-    return this.printerEntry.printerType == PrinterType.SAVED;
+    return this.printerEntry.printerType === PrinterType.SAVED;
   },
 
   /**
@@ -64,7 +64,7 @@
    * @private
    */
   isDiscoveredPrinter_() {
-    return this.printerEntry.printerType == PrinterType.DISCOVERED;
+    return this.printerEntry.printerType === PrinterType.DISCOVERED;
   },
 
   /**
@@ -72,7 +72,7 @@
    * @private
    */
   isAutomaticPrinter_() {
-    return this.printerEntry.printerType == PrinterType.AUTOMATIC;
+    return this.printerEntry.printerType === PrinterType.AUTOMATIC;
   },
 
   /**
@@ -80,7 +80,7 @@
    * @private
    */
   isPrintServerPrinter_() {
-    return this.printerEntry.printerType == PrinterType.PRINTSERVER;
+    return this.printerEntry.printerType === PrinterType.PRINTSERVER;
   },
 
   getSaveButtonAria_() {
diff --git a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printers_entry_manager.js b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printers_entry_manager.js
index 327de6d..fb130b0 100644
--- a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printers_entry_manager.js
+++ b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printers_entry_manager.js
@@ -92,7 +92,7 @@
     /** @param {PrintersListWithDeltasCallback} listener */
     removeOnSavedPrintersChangedListener(listener) {
       this.onSavedPrintersChangedListeners_ =
-          this.onSavedPrintersChangedListeners_.filter(lis => lis != listener);
+          this.onSavedPrintersChangedListeners_.filter(lis => lis !== listener);
     }
 
     /** @param {PrintersListCallback} listener */
@@ -103,7 +103,8 @@
     /** @param {PrintersListCallback} listener */
     removeOnNearbyPrintersChangedListener(listener) {
       this.onNearbyPrintersChangedListeners_ =
-          this.onNearbyPrintersChangedListeners_.filter(lis => lis != listener);
+          this.onNearbyPrintersChangedListeners_.filter(
+              lis => lis !== listener);
     }
 
     /**
@@ -173,7 +174,7 @@
       // found printers.
       const newPrinters = foundPrinters.printerList.filter(p1 => {
         return !this.printServerPrinters.some(
-            p2 => p2.printerInfo.printerId == p1.printerId);
+            p2 => p2.printerInfo.printerId === p1.printerId);
       });
 
       for (const printer of newPrinters) {
diff --git a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_saved_printers.js b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_saved_printers.js
index 51f0a94..85597c7 100644
--- a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_saved_printers.js
+++ b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_saved_printers.js
@@ -156,7 +156,8 @@
   onOpenActionMenu_(e) {
     const item = /** @type {!PrinterListEntry} */ (e.detail.item);
     this.activePrinterListEntryIndex_ = this.savedPrinters.findIndex(
-        printer => printer.printerInfo.printerId == item.printerInfo.printerId);
+        printer =>
+            printer.printerInfo.printerId === item.printerInfo.printerId);
     this.activePrinter =
         this.get(['savedPrinters', this.activePrinterListEntryIndex_])
             .printerInfo;
@@ -245,7 +246,7 @@
     const currArr = this.newPrinters_.slice();
     for (const printer of removedPrinters) {
       const newPrinterRemovedIdx = currArr.findIndex(
-          p => p.printerInfo.printerId == printer.printerInfo.printerId);
+          p => p.printerInfo.printerId === printer.printerInfo.printerId);
       // If the removed printer is a recently added printer, remove it from
       // |currArr|.
       if (newPrinterRemovedIdx > -1) {
@@ -303,7 +304,7 @@
     // We have newly added printers, move them to the top of the list.
     for (const printer of this.newPrinters_) {
       const idx = printerArr.findIndex(
-          p => p.printerInfo.printerId == printer.printerInfo.printerId);
+          p => p.printerInfo.printerId === printer.printerInfo.printerId);
       if (idx > -1) {
         moveEntryInPrinters(printerArr, idx, toIndex);
       }
diff --git a/chrome/browser/resources/settings/chromeos/os_reset_page/os_reset_page.js b/chrome/browser/resources/settings/chromeos/os_reset_page/os_reset_page.js
index 85804ef..2e2e721 100644
--- a/chrome/browser/resources/settings/chromeos/os_reset_page/os_reset_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_reset_page/os_reset_page.js
@@ -50,7 +50,7 @@
    */
   currentRouteChanged(newRoute, oldRoute) {
     // Does not apply to this page.
-    if (newRoute != settings.routes.OS_RESET) {
+    if (newRoute !== settings.routes.OS_RESET) {
       return;
     }
 
diff --git a/chrome/browser/resources/settings/chromeos/os_search_page/os_search_page.js b/chrome/browser/resources/settings/chromeos/os_search_page/os_search_page.js
index c7a6b4e..9b40481 100644
--- a/chrome/browser/resources/settings/chromeos/os_search_page/os_search_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_search_page/os_search_page.js
@@ -122,7 +122,8 @@
    * @private
    */
   isDefaultSearchControlledByPolicy_(pref) {
-    return pref.controlledBy == chrome.settingsPrivate.ControlledBy.USER_POLICY;
+    return pref.controlledBy ===
+        chrome.settingsPrivate.ControlledBy.USER_POLICY;
   },
 
   /**
@@ -131,6 +132,6 @@
    * @private
    */
   isDefaultSearchEngineEnforced_(pref) {
-    return pref.enforcement == chrome.settingsPrivate.Enforcement.ENFORCED;
+    return pref.enforcement === chrome.settingsPrivate.Enforcement.ENFORCED;
   },
 });
diff --git a/chrome/browser/resources/settings/chromeos/os_search_page/os_search_selection_dialog.js b/chrome/browser/resources/settings/chromeos/os_search_page/os_search_selection_dialog.js
index 9d88c4e..52652c8 100644
--- a/chrome/browser/resources/settings/chromeos/os_search_page/os_search_selection_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/os_search_page/os_search_selection_dialog.js
@@ -63,7 +63,7 @@
    * @private
    */
   onKeydown_(e) {
-    if (e.key == 'Escape') {
+    if (e.key === 'Escape') {
       this.onCancelButtonClick_();
     }
   },
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.js b/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.js
index 2ff2bfa..f5a0d37 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.js
@@ -142,7 +142,7 @@
     if (oldRoute && oldRoute.isSubpage()) {
       // If the new route isn't the same expanded section, reset
       // hasExpandedSection_ for the next transition.
-      if (!newRoute.isSubpage() || newRoute.section != oldRoute.section) {
+      if (!newRoute.isSubpage() || newRoute.section !== oldRoute.section) {
         this.hasExpandedSection_ = false;
       }
     } else {
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_search_result_row.js b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_search_result_row.js
index 6ea231c..aded9f64 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_search_result_row.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_search_result_row.js
@@ -25,7 +25,7 @@
 
     for (let i = string1.length - 1; i >= 0; i--) {
       for (let j = string2.length - 1; j >= 0; j--) {
-        if (string1[i] != string2[j]) {
+        if (string1[i] !== string2[j]) {
           continue;
         }
         dp[i][j] = dp[i + 1][j + 1] + 1;
@@ -497,7 +497,7 @@
       }
 
       if (this.resultText_.match(/\s/) ||
-          this.resultText_.toLocaleLowerCase() !=
+          this.resultText_.toLocaleLowerCase() !==
               this.resultText_.toLocaleUpperCase()) {
         // If the result text includes blankspaces (as they commonly will in
         // languages like Arabic and Hindi), or if the result text includes
@@ -590,7 +590,7 @@
           'Supplied path does not map to an existing route.');
 
       const paramsString = `search=${encodeURIComponent(this.searchQuery)}` +
-          (pathAndOptParams.length == 2 ? `&${pathAndOptParams[1]}` : ``);
+          (pathAndOptParams.length === 2 ? `&${pathAndOptParams[1]}` : ``);
       const params = new URLSearchParams(paramsString);
       settings.Router.getInstance().navigateTo(route, params);
       this.fire('navigated-to-result-route');
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.js b/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.js
index ace4f90..b593d44 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.js
@@ -266,7 +266,7 @@
      * @param {!settings.Route} oldRoute
      */
     currentRouteChanged(newRoute, oldRoute) {
-      if (oldRoute && newRoute != oldRoute) {
+      if (oldRoute && newRoute !== oldRoute) {
         // Search triggers route changes and currentRouteChanged() is called
         // in attached() state which is extraneous for this metric.
         settings.recordNavigation();
@@ -291,7 +291,7 @@
           settings.Router.getInstance().getQueryParameters().get('search') ||
           '';
 
-      if (urlSearchQuery == this.lastSearchQuery_) {
+      if (urlSearchQuery === this.lastSearchQuery_) {
         return;
       }
 
@@ -319,7 +319,7 @@
 
       // If the search was initiated by directly entering a search URL, need to
       // sync the URL parameter to the textbox.
-      if (urlSearchQuery != searchField.getValue()) {
+      if (urlSearchQuery !== searchField.getValue()) {
         // Setting the search box value without triggering a 'search-changed'
         // event, to prevent an unnecessary duplicate entry in |window.history|.
         searchField.setValue(urlSearchQuery, true /* noEvent */);
diff --git a/chrome/browser/resources/settings/chromeos/personalization_page/change_picture.js b/chrome/browser/resources/settings/chromeos/personalization_page/change_picture.js
index b6d63f5..b32dc1c 100644
--- a/chrome/browser/resources/settings/chromeos/personalization_page/change_picture.js
+++ b/chrome/browser/resources/settings/chromeos/personalization_page/change_picture.js
@@ -137,7 +137,7 @@
 
   /** @protected */
   currentRouteChanged(newRoute) {
-    if (newRoute == settings.routes.CHANGE_PICTURE) {
+    if (newRoute === settings.routes.CHANGE_PICTURE) {
       this.browserProxy_.initialize();
       this.browserProxy_.requestSelectedImage();
       this.pictureList_.setFocus();
@@ -338,7 +338,7 @@
    */
   isAuthorCreditShown_(selectedItem) {
     return !!selectedItem &&
-        (selectedItem.dataset.type == CrPicture.SelectionTypes.DEFAULT ||
+        (selectedItem.dataset.type === CrPicture.SelectionTypes.DEFAULT ||
          (selectedItem.dataset.imageIndex !== undefined &&
           selectedItem.dataset.imageIndex >= 0));
   },
diff --git a/chrome/browser/resources/settings/chromeos/route_origin_behavior.js b/chrome/browser/resources/settings/chromeos/route_origin_behavior.js
index d536f56..91e7c3a 100644
--- a/chrome/browser/resources/settings/chromeos/route_origin_behavior.js
+++ b/chrome/browser/resources/settings/chromeos/route_origin_behavior.js
@@ -59,7 +59,7 @@
       }
       const focusSelector = this.focusConfig_.get(oldRoute.path);
 
-      if (this.route_ != newRoute || !focusSelector) {
+      if (this.route_ !== newRoute || !focusSelector) {
         return;
       }
 
diff --git a/chrome/browser/resources/tab_search_merge/BUILD.gn b/chrome/browser/resources/tab_search/BUILD.gn
similarity index 100%
rename from chrome/browser/resources/tab_search_merge/BUILD.gn
rename to chrome/browser/resources/tab_search/BUILD.gn
diff --git a/chrome/browser/resources/tab_search_merge/OWNERS b/chrome/browser/resources/tab_search/OWNERS
similarity index 100%
rename from chrome/browser/resources/tab_search_merge/OWNERS
rename to chrome/browser/resources/tab_search/OWNERS
diff --git a/chrome/browser/resources/tab_search_merge/app.html b/chrome/browser/resources/tab_search/app.html
similarity index 100%
rename from chrome/browser/resources/tab_search_merge/app.html
rename to chrome/browser/resources/tab_search/app.html
diff --git a/chrome/browser/resources/tab_search_merge/app.js b/chrome/browser/resources/tab_search/app.js
similarity index 100%
rename from chrome/browser/resources/tab_search_merge/app.js
rename to chrome/browser/resources/tab_search/app.js
diff --git a/chrome/browser/resources/tab_search_merge/fuzzy_search.js b/chrome/browser/resources/tab_search/fuzzy_search.js
similarity index 98%
rename from chrome/browser/resources/tab_search_merge/fuzzy_search.js
rename to chrome/browser/resources/tab_search/fuzzy_search.js
index ec1ef3e..4d27c5b 100644
--- a/chrome/browser/resources/tab_search_merge/fuzzy_search.js
+++ b/chrome/browser/resources/tab_search/fuzzy_search.js
@@ -90,7 +90,7 @@
 
   // Perform an exact match search with range discovery.
   const exactMatches = [];
-  for (let tab of records) {
+  for (const tab of records) {
     const titleHighlightRanges = getRanges(tab.tab.title, searchText);
     const hostnameHighlightRanges = getRanges(tab.hostname, searchText);
     if (!titleHighlightRanges.length && !hostnameHighlightRanges.length) {
@@ -117,7 +117,7 @@
   const itemsMatchingWordStart = [];
   const others = [];
   const wordStartRegexp = new RegExp(`\\b${quoteString(searchText)}`, 'i');
-  for (let {tab} of exactMatches) {
+  for (const {tab} of exactMatches) {
     // Find matches that occur at the beginning of the string.
     if (hasMatchStringStart(tab)) {
       itemsMatchingStringStart.push(tab);
@@ -167,7 +167,7 @@
  */
 function getRanges(target, searchText) {
   const escapedText = quoteString(searchText);
-  let ranges = [];
+  const ranges = [];
   let match = null;
   for (const re = new RegExp(escapedText, 'gi'); match = re.exec(target);) {
     ranges.push({
diff --git a/chrome/browser/resources/tab_search_merge/tab_data.js b/chrome/browser/resources/tab_search/tab_data.js
similarity index 100%
rename from chrome/browser/resources/tab_search_merge/tab_data.js
rename to chrome/browser/resources/tab_search/tab_data.js
diff --git a/chrome/browser/resources/tab_search_merge/tab_search_api_proxy.js b/chrome/browser/resources/tab_search/tab_search_api_proxy.js
similarity index 100%
rename from chrome/browser/resources/tab_search_merge/tab_search_api_proxy.js
rename to chrome/browser/resources/tab_search/tab_search_api_proxy.js
diff --git a/chrome/browser/resources/tab_search_merge/tab_search_item.html b/chrome/browser/resources/tab_search/tab_search_item.html
similarity index 100%
rename from chrome/browser/resources/tab_search_merge/tab_search_item.html
rename to chrome/browser/resources/tab_search/tab_search_item.html
diff --git a/chrome/browser/resources/tab_search_merge/tab_search_item.js b/chrome/browser/resources/tab_search/tab_search_item.js
similarity index 100%
rename from chrome/browser/resources/tab_search_merge/tab_search_item.js
rename to chrome/browser/resources/tab_search/tab_search_item.js
diff --git a/chrome/browser/resources/tab_search_merge/tab_search_page.html b/chrome/browser/resources/tab_search/tab_search_page.html
similarity index 100%
rename from chrome/browser/resources/tab_search_merge/tab_search_page.html
rename to chrome/browser/resources/tab_search/tab_search_page.html
diff --git a/chrome/browser/resources/tab_search_merge/tab_search_resources.grd b/chrome/browser/resources/tab_search/tab_search_resources.grd
similarity index 94%
rename from chrome/browser/resources/tab_search_merge/tab_search_resources.grd
rename to chrome/browser/resources/tab_search/tab_search_resources.grd
index cab83ff..aa4978ea 100644
--- a/chrome/browser/resources/tab_search_merge/tab_search_resources.grd
+++ b/chrome/browser/resources/tab_search/tab_search_resources.grd
@@ -13,7 +13,7 @@
   <release seq="1">
     <includes>
       <include name="IDR_APP_JS"
-               file="${root_gen_dir}/chrome/browser/resources/tab_search_merge/app.js"
+               file="${root_gen_dir}/chrome/browser/resources/tab_search/app.js"
                type="BINDATA"
                use_base_dir="false" />
       <include name="IDR_FUSE_JS"
@@ -29,7 +29,7 @@
                file="tab_search_api_proxy.js"
                type="BINDATA" />
       <include name="IDR_TAB_SEARCH_ITEM_JS"
-               file="${root_gen_dir}/chrome/browser/resources/tab_search_merge/tab_search_item.js"
+               file="${root_gen_dir}/chrome/browser/resources/tab_search/tab_search_item.js"
                type="BINDATA"
                use_base_dir="false"/>
       <include name="IDR_TAB_SEARCH_MOJO_LITE_JS"
@@ -40,7 +40,7 @@
                file="tab_search_page.html"
                type="BINDATA" />
       <include name="IDR_TAB_SEARCH_SEARCH_FIELD_JS"
-               file="${root_gen_dir}/chrome/browser/resources/tab_search_merge/tab_search_search_field.js"
+               file="${root_gen_dir}/chrome/browser/resources/tab_search/tab_search_search_field.js"
                type="BINDATA"
                use_base_dir="false" />
     </includes>
diff --git a/chrome/browser/resources/tab_search_merge/tab_search_search_field.html b/chrome/browser/resources/tab_search/tab_search_search_field.html
similarity index 97%
rename from chrome/browser/resources/tab_search_merge/tab_search_search_field.html
rename to chrome/browser/resources/tab_search/tab_search_search_field.html
index 983f9b77..ebda6ff5 100644
--- a/chrome/browser/resources/tab_search_merge/tab_search_search_field.html
+++ b/chrome/browser/resources/tab_search/tab_search_search_field.html
@@ -40,4 +40,4 @@
 <input id="searchInput" on-search="onSearchTermSearch"
     on-input="onSearchTermInput" aria-label$="[[label]]" type="search"
     autofocus="[[autofocus]]" placeholder="[[label]]" autocomplete="off"
-    spellcheck="false" />
+    spellcheck="false">
diff --git a/chrome/browser/resources/tab_search_merge/tab_search_search_field.js b/chrome/browser/resources/tab_search/tab_search_search_field.js
similarity index 100%
rename from chrome/browser/resources/tab_search_merge/tab_search_search_field.js
rename to chrome/browser/resources/tab_search/tab_search_search_field.js
diff --git a/chrome/browser/sessions/tab_restore_browsertest.cc b/chrome/browser/sessions/tab_restore_browsertest.cc
index 970dfc4..a49e36a 100644
--- a/chrome/browser/sessions/tab_restore_browsertest.cc
+++ b/chrome/browser/sessions/tab_restore_browsertest.cc
@@ -195,6 +195,13 @@
     observer.Wait();
   }
 
+  void EnableSessionService(
+      SessionStartupPref::Type type = SessionStartupPref::Type::DEFAULT) {
+    SessionStartupPref pref(type);
+    Profile* profile = browser()->profile();
+    SessionStartupPref::SetStartupPref(profile, pref);
+  }
+
   GURL url1_;
   GURL url2_;
 
@@ -1409,3 +1416,37 @@
       histogram_tester.GetAllSamples(kTimeSinceTabClosedUntilRestored).size(),
       0U);
 }
+
+IN_PROC_BROWSER_TEST_F(TabRestoreTest, PRE_PRE_RestoreAfterMultipleRestarts) {
+  // Enable session service in default mode.
+  EnableSessionService();
+
+  // Navigate to url1 in the current tab.
+  ui_test_utils::NavigateToURLWithDisposition(
+      browser(), url1_, WindowOpenDisposition::CURRENT_TAB,
+      ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
+}
+
+IN_PROC_BROWSER_TEST_F(TabRestoreTest, PRE_RestoreAfterMultipleRestarts) {
+  // Enable session service in default mode.
+  EnableSessionService();
+
+  // Navigate to url2 in the current tab.
+  ui_test_utils::NavigateToURLWithDisposition(
+      browser(), url2_, WindowOpenDisposition::CURRENT_TAB,
+      ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
+}
+
+// Verifies restoring tabs from previous sessions.
+IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreAfterMultipleRestarts) {
+  // Enable session service in default mode.
+  EnableSessionService();
+
+  // Restore url2 from one session ago.
+  ASSERT_NO_FATAL_FAILURE(RestoreTab(0, 1));
+  EXPECT_EQ(url2_, browser()->tab_strip_model()->GetWebContentsAt(1)->GetURL());
+
+  // Restore url1 from two sessions ago.
+  ASSERT_NO_FATAL_FAILURE(RestoreTab(0, 2));
+  EXPECT_EQ(url1_, browser()->tab_strip_model()->GetWebContentsAt(2)->GetURL());
+}
diff --git a/chrome/browser/sync/test/integration/two_client_bookmarks_sync_test.cc b/chrome/browser/sync/test/integration/two_client_bookmarks_sync_test.cc
index 98d4481..6641589 100644
--- a/chrome/browser/sync/test/integration/two_client_bookmarks_sync_test.cc
+++ b/chrome/browser/sync/test/integration/two_client_bookmarks_sync_test.cc
@@ -1969,8 +1969,6 @@
   // Make sure the first Profile has an overridden policy provider.
   EXPECT_CALL(policy_provider_, IsInitializationComplete(testing::_))
       .WillRepeatedly(testing::Return(true));
-  EXPECT_CALL(policy_provider_, IsFirstPolicyLoadComplete(testing::_))
-      .WillRepeatedly(testing::Return(true));
   policy::PushProfilePolicyConnectorProviderForTesting(&policy_provider_);
 
   // Set up sync.
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 7f062dc..a1d9f43 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1749,6 +1749,8 @@
       "app_list/search/files/file_result.h",
       "app_list/search/files/item_suggest_cache.cc",
       "app_list/search/files/item_suggest_cache.h",
+      "app_list/search/help_app_provider.cc",
+      "app_list/search/help_app_provider.h",
       "app_list/search/launcher_search/launcher_search_icon_image_loader.cc",
       "app_list/search/launcher_search/launcher_search_icon_image_loader.h",
       "app_list/search/launcher_search/launcher_search_icon_image_loader_impl.cc",
diff --git a/chrome/browser/ui/android/appmenu/test/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuTestSupport.java b/chrome/browser/ui/android/appmenu/test/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuTestSupport.java
index 29dac94..204ee91 100644
--- a/chrome/browser/ui/android/appmenu/test/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuTestSupport.java
+++ b/chrome/browser/ui/android/appmenu/test/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuTestSupport.java
@@ -15,11 +15,6 @@
 
 /**
  * Utility methods for performing operations on the app menu needed for testing.
- *
- * TODO(https://crbug.com/956260): This will live in a support/ package once app menu code
- * is migrated to have its own build target. For now it lives here so this class may access package
- * protected appmenu classes while still allowing classes in chrome_java_test_support may access
- * AppMenuTestSupport.
  */
 public class AppMenuTestSupport {
     /**
diff --git a/chrome/browser/ui/app_list/internal_app/internal_app_metadata.cc b/chrome/browser/ui/app_list/internal_app/internal_app_metadata.cc
index 1a264d74..4a47fad 100644
--- a/chrome/browser/ui/app_list/internal_app/internal_app_metadata.cc
+++ b/chrome/browser/ui/app_list/internal_app/internal_app_metadata.cc
@@ -96,18 +96,8 @@
   return GetInternalAppListImpl(false, profile);
 }
 
-bool IsSuggestionChip(const std::string& app_id, Profile* profile) {
-  if (base::LowerCaseEqualsASCII(app_id, ash::kInternalAppIdContinueReading))
-    return true;
-
-  // We show the Help App as a release notes suggestion chip a certain
-  // number of times.
-  if (chromeos::ReleaseNotesStorage(profile).ShouldShowSuggestionChip() &&
-      base::LowerCaseEqualsASCII(app_id,
-                                 chromeos::default_web_apps::kHelpAppId)) {
-    return true;
-  }
-  return false;
+bool IsSuggestionChip(const std::string& app_id) {
+  return base::LowerCaseEqualsASCII(app_id, ash::kInternalAppIdContinueReading);
 }
 
 const InternalApp* FindInternalApp(const std::string& app_id) {
diff --git a/chrome/browser/ui/app_list/internal_app/internal_app_metadata.h b/chrome/browser/ui/app_list/internal_app/internal_app_metadata.h
index 5803e7a..5e9c59cd19 100644
--- a/chrome/browser/ui/app_list/internal_app/internal_app_metadata.h
+++ b/chrome/browser/ui/app_list/internal_app/internal_app_metadata.h
@@ -54,7 +54,7 @@
 const std::vector<InternalApp>& GetInternalAppList(const Profile* profile);
 
 // Returns true if the app should only be shown as a suggestion chip.
-bool IsSuggestionChip(const std::string& app_id, Profile* profile);
+bool IsSuggestionChip(const std::string& app_id);
 
 // Returns InternalApp by |app_id|.
 // Returns nullptr if |app_id| does not correspond to an internal app.
diff --git a/chrome/browser/ui/app_list/search/app_list_search_browsertest.cc b/chrome/browser/ui/app_list/search/app_list_search_browsertest.cc
index 8d8c142..28309f2 100644
--- a/chrome/browser/ui/app_list/search/app_list_search_browsertest.cc
+++ b/chrome/browser/ui/app_list/search/app_list_search_browsertest.cc
@@ -8,6 +8,7 @@
 #include "base/run_loop.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/bind_test_util.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/chromeos/web_applications/default_web_app_ids.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
@@ -22,6 +23,7 @@
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/in_process_browser_test.h"
+#include "chromeos/constants/chromeos_features.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/test/browser_test.h"
 
@@ -128,46 +130,56 @@
 
 // Test that Help App shows up as Release notes if pref shows we have some times
 // left to show it.
-// TODO(b/169711884): Re-enable when suggestion chips are re-enabled.
 IN_PROC_BROWSER_TEST_F(AppListSearchBrowserTest,
-                       DISABLED_AppListSearchHasReleaseNotesSuggestionChip) {
+                       AppListSearchHasReleaseNotesSuggestionChip) {
   web_app::WebAppProvider::Get(GetProfile())
       ->system_web_app_manager()
       .InstallSystemAppsForTesting();
   GetProfile()->GetPrefs()->SetInteger(
       prefs::kReleaseNotesSuggestionChipTimesLeftToShow, 3);
 
-  SearchAndWaitForProviders("",
-                            {ResultType::kInstalledApp, ResultType::kLauncher});
+  SearchAndWaitForProviders("", {ResultType::kHelpApp});
 
-  // Note: SearchAndWaitForProviders decreases the count multiple times.
-  // TODO(b/169711884): Decrease times left only when the chip becomes visible.
-  const int times_left_to_show = GetProfile()->GetPrefs()->GetInteger(
-      prefs::kReleaseNotesSuggestionChipTimesLeftToShow);
-  EXPECT_EQ(times_left_to_show, 1);
-  auto* result = FindResult(chromeos::default_web_apps::kHelpAppId);
+  auto* result = FindResult("help-app://updates");
   ASSERT_TRUE(result);
   // Has Release notes title.
   EXPECT_EQ(base::UTF16ToASCII(result->title()),
             "See what's new on your Chrome device");
   // Displayed in first position.
   EXPECT_EQ(result->position_priority(), 1.0f);
-  // Has override url defined for updates tab.
-  EXPECT_EQ(result->query_url(), GURL("chrome://help-app/updates"));
   EXPECT_EQ(result->display_type(), DisplayType::kChip);
 }
 
-// Test that Help App shows up normally if pref shows we should no longer show
-// as suggestion chip.
-IN_PROC_BROWSER_TEST_F(AppListSearchBrowserTest, AppListSearchHasHelpApp) {
+// Test that the number of times the suggestion chip should show decreases when
+// the chip is shown.
+IN_PROC_BROWSER_TEST_F(AppListSearchBrowserTest,
+                       ReleaseNotesDecreasesTimesShownOnAppListOpen) {
   web_app::WebAppProvider::Get(GetProfile())
       ->system_web_app_manager()
       .InstallSystemAppsForTesting();
   GetProfile()->GetPrefs()->SetInteger(
-      prefs::kReleaseNotesSuggestionChipTimesLeftToShow, 0);
+      prefs::kReleaseNotesSuggestionChipTimesLeftToShow, 3);
 
-  SearchAndWaitForProviders("",
-                            {ResultType::kInstalledApp, ResultType::kLauncher});
+  // ShowAppList actually opens the app list and triggers |AppListShown| which
+  // is where we decrease |kReleaseNotesSuggestionChipTimesLeftToShow|.
+  GetClient()->ShowAppList();
+  SearchAndWaitForProviders("", {ResultType::kHelpApp});
+
+  const int times_left_to_show = GetProfile()->GetPrefs()->GetInteger(
+      prefs::kReleaseNotesSuggestionChipTimesLeftToShow);
+  EXPECT_EQ(times_left_to_show, 2);
+}
+
+// Test that Help App shows up normally even when suggestion chip should show.
+IN_PROC_BROWSER_TEST_F(AppListSearchBrowserTest, AppListSearchHasApp) {
+  web_app::WebAppProvider::Get(GetProfile())
+      ->system_web_app_manager()
+      .InstallSystemAppsForTesting();
+  GetProfile()->GetPrefs()->SetInteger(
+      prefs::kReleaseNotesSuggestionChipTimesLeftToShow, 3);
+
+  SearchAndWaitForProviders("", {ResultType::kInstalledApp,
+                                 ResultType::kLauncher, ResultType::kHelpApp});
 
   auto* result = FindResult(chromeos::default_web_apps::kHelpAppId);
   ASSERT_TRUE(result);
diff --git a/chrome/browser/ui/app_list/search/app_search_provider.cc b/chrome/browser/ui/app_list/search/app_search_provider.cc
index d431a975..d2994f25 100644
--- a/chrome/browser/ui/app_list/search/app_search_provider.cc
+++ b/chrome/browser/ui/app_list/search/app_search_provider.cc
@@ -513,18 +513,6 @@
         app->data_source()->CreateResult(app->id(), list_controller_, true);
     result->SetTitle(title);
 
-    if (app->id() == chromeos::default_web_apps::kHelpAppId) {
-      auto release_notes_storage =
-          std::make_unique<chromeos::ReleaseNotesStorage>(profile_);
-      // If we should show the release notes suggestion chip, change the title
-      // and url of the Help App. Otherwise leave as normal.
-      if (release_notes_storage->ShouldShowSuggestionChip()) {
-        result->SetTitle(ui::SubstituteChromeOSDeviceType(
-            IDS_RELEASE_NOTES_DEVICE_SPECIFIC_NOTIFICATION_TITLE));
-        result->SetQueryUrl(GURL("chrome://help-app/updates"));
-      }
-    }
-
     const auto find_in_app_list = id_to_app_list_index.find(app->id());
     const base::Time time = app->GetLastActivityTime();
 
diff --git a/chrome/browser/ui/app_list/search/app_service_app_result.cc b/chrome/browser/ui/app_list/search/app_service_app_result.cc
index a86b605..04df4052 100644
--- a/chrome/browser/ui/app_list/search/app_service_app_result.cc
+++ b/chrome/browser/ui/app_list/search/app_service_app_result.cc
@@ -80,7 +80,7 @@
       break;
   }
 
-  if (IsSuggestionChip(id(), profile))
+  if (IsSuggestionChip(id()))
     HandleSuggestionChip(profile);
 }
 
@@ -154,20 +154,6 @@
   apps::AppServiceProxy* proxy =
       apps::AppServiceProxyFactory::GetForProfile(profile());
 
-  if (id() == chromeos::default_web_apps::kHelpAppId &&
-      query_url().has_value()) {
-    // This matches the logging of the release notes app in
-    // chrome/browser/apps/app_service/built_in_chromeos_apps.cc.
-    // TODO(carpenterr): Have more consistent logging of the places Help App can
-    // be opened to/from.
-    base::RecordAction(
-        base::UserMetricsAction("ReleaseNotes.SuggestionChipLaunched"));
-    proxy->LaunchAppWithUrl(app_id(), event_flags, query_url().value(),
-                            launch_source, controller()->GetAppListDisplayId());
-    chromeos::ReleaseNotesStorage(profile()).StopShowingSuggestionChip();
-    return;
-  }
-
   // For Chrome apps or Web apps, if it is non-platform app, it could be
   // selecting an existing delegate for the app, so call
   // ChromeLauncherController's ActivateApp interface. Platform apps or ARC
@@ -250,9 +236,7 @@
   SetDisplayIndex(ash::SearchResultDisplayIndex::kFirstIndex);
   SetDisplayType(ash::SearchResultDisplayType::kChip);
 
-  // Either of these apps could be shown as the release notes suggestion chip.
-  if (id() == ash::kReleaseNotesAppId ||
-      id() == chromeos::default_web_apps::kHelpAppId) {
+  if (id() == ash::kReleaseNotesAppId) {
     // TODO(b/169711884): Decrease times left only when the chip becomes
     // visible.
     chromeos::ReleaseNotesStorage(profile)
diff --git a/chrome/browser/ui/app_list/search/help_app_provider.cc b/chrome/browser/ui/app_list/search/help_app_provider.cc
new file mode 100644
index 0000000..86226f8
--- /dev/null
+++ b/chrome/browser/ui/app_list/search/help_app_provider.cc
@@ -0,0 +1,136 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/app_list/search/help_app_provider.h"
+
+#include <string>
+
+#include "ash/public/cpp/app_list/app_list_config.h"
+#include "ash/public/cpp/app_list/app_list_features.h"
+#include "base/macros.h"
+#include "base/metrics/user_metrics.h"
+#include "chrome/browser/apps/app_service/app_service_proxy.h"
+#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
+#include "chrome/browser/chromeos/release_notes/release_notes_storage.h"
+#include "chrome/browser/chromeos/web_applications/default_web_app_ids.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/grit/generated_resources.h"
+#include "ui/chromeos/devicetype_utils.h"
+#include "ui/gfx/image/image_skia.h"
+#include "url/gurl.h"
+
+namespace app_list {
+namespace {
+
+constexpr char kHelpAppResult[] = "help-app://updates";
+
+}  // namespace
+
+HelpAppResult::HelpAppResult(float relevance,
+                             Profile* profile,
+                             const gfx::ImageSkia& icon)
+    : profile_(profile) {
+  DCHECK(profile_);
+  set_id(kHelpAppResult);
+  SetTitle(ui::SubstituteChromeOSDeviceType(
+      IDS_RELEASE_NOTES_DEVICE_SPECIFIC_NOTIFICATION_TITLE));
+  // Show this in the first position, in front of any other chips that may be
+  // also claiming the first slot.
+  SetDisplayIndex(DisplayIndex::kFirstIndex);
+  SetPositionPriority(1.0f);
+  SetResultType(ResultType::kHelpApp);
+  SetDisplayType(DisplayType::kChip);
+  SetMetricsType(ash::HELP_APP);
+  SetChipIcon(icon);
+}
+
+HelpAppResult::~HelpAppResult() = default;
+
+void HelpAppResult::Open(int event_flags) {
+  apps::AppServiceProxy* proxy =
+      apps::AppServiceProxyFactory::GetForProfile(profile_);
+  base::RecordAction(
+      base::UserMetricsAction("ReleaseNotes.SuggestionChipLaunched"));
+  proxy->LaunchAppWithUrl(chromeos::default_web_apps::kHelpAppId, event_flags,
+                          GURL("chrome://help-app/updates"),
+                          apps::mojom::LaunchSource::kFromAppListRecommendation,
+                          display::kDefaultDisplayId);
+  chromeos::ReleaseNotesStorage(profile_).StopShowingSuggestionChip();
+}
+
+HelpAppProvider::HelpAppProvider(Profile* profile) : profile_(profile) {
+  DCHECK(profile_);
+
+  app_service_proxy_ = apps::AppServiceProxyFactory::GetForProfile(profile_);
+  Observe(&app_service_proxy_->AppRegistryCache());
+  LoadIcon();
+}
+
+HelpAppProvider::~HelpAppProvider() = default;
+
+void HelpAppProvider::Start(const base::string16& query) {
+  // This provider doesn't handle searches, if there is any query just clear the
+  // results and return.
+  if (!query.empty()) {
+    ClearResultsSilently();
+    return;
+  }
+
+  SearchProvider::Results search_results;
+  if (chromeos::ReleaseNotesStorage(profile_).ShouldShowSuggestionChip()) {
+    search_results.emplace_back(
+        std::make_unique<HelpAppResult>(1.0f, profile_, icon_));
+  }
+  SwapResults(&search_results);
+}
+
+// TODO(b/171828539): Consider using AppListNotifier for better proxy of
+// impressions.
+void HelpAppProvider::AppListShown() {
+  chromeos::ReleaseNotesStorage(profile_)
+      .DecreaseTimesLeftToShowSuggestionChip();
+}
+
+ash::AppListSearchResultType HelpAppProvider::ResultType() {
+  return ash::AppListSearchResultType::kHelpApp;
+}
+
+void HelpAppProvider::OnAppUpdate(const apps::AppUpdate& update) {
+  if (update.AppId() == chromeos::default_web_apps::kHelpAppId &&
+      update.ReadinessChanged() &&
+      update.Readiness() == apps::mojom::Readiness::kReady) {
+    LoadIcon();
+  }
+}
+
+void HelpAppProvider::OnAppRegistryCacheWillBeDestroyed(
+    apps::AppRegistryCache* cache) {
+  Observe(nullptr);
+}
+
+void HelpAppProvider::OnLoadIcon(apps::mojom::IconValuePtr icon_value) {
+  auto icon_type =
+      (base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon))
+          ? apps::mojom::IconType::kStandard
+          : apps::mojom::IconType::kUncompressed;
+  if (icon_value->icon_type == icon_type) {
+    icon_ = icon_value->uncompressed;
+  }
+}
+
+void HelpAppProvider::LoadIcon() {
+  auto icon_type =
+      (base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon))
+          ? apps::mojom::IconType::kStandard
+          : apps::mojom::IconType::kUncompressed;
+  app_service_proxy_->LoadIcon(
+      apps::mojom::AppType::kWeb, chromeos::default_web_apps::kHelpAppId,
+      icon_type,
+      ash::AppListConfig::instance().suggestion_chip_icon_dimension(),
+      /*allow_placeholder_icon=*/false,
+      base::BindOnce(&HelpAppProvider::OnLoadIcon, weak_factory_.GetWeakPtr()));
+}
+
+}  // namespace app_list
diff --git a/chrome/browser/ui/app_list/search/help_app_provider.h b/chrome/browser/ui/app_list/search/help_app_provider.h
new file mode 100644
index 0000000..1544d08
--- /dev/null
+++ b/chrome/browser/ui/app_list/search/help_app_provider.h
@@ -0,0 +1,77 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_APP_LIST_SEARCH_HELP_APP_PROVIDER_H_
+#define CHROME_BROWSER_UI_APP_LIST_SEARCH_HELP_APP_PROVIDER_H_
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ui/app_list/search/search_provider.h"
+#include "chrome/browser/ui/webui/settings/chromeos/search/search.mojom.h"
+#include "components/services/app_service/public/cpp/app_registry_cache.h"
+#include "components/services/app_service/public/mojom/types.mojom.h"
+
+class Profile;
+
+namespace apps {
+class AppServiceProxy;
+}  // namespace apps
+
+namespace gfx {
+class ImageSkia;
+}  // namespace gfx
+
+namespace app_list {
+
+// Search results for the Help App (aka Explore).
+// TODO(b/171519930): This is still a WIP, and needs to have results added.
+class HelpAppResult : public ChromeSearchResult {
+ public:
+  HelpAppResult(float relevance, Profile* profile, const gfx::ImageSkia& icon);
+  ~HelpAppResult() override;
+
+  HelpAppResult(const HelpAppResult&) = delete;
+  HelpAppResult& operator=(const HelpAppResult&) = delete;
+
+  // ChromeSearchResult overrides:
+  void Open(int event_flags) override;
+
+ private:
+  Profile* const profile_;
+};
+
+// Provider results for Help App.
+class HelpAppProvider : public SearchProvider,
+                        public apps::AppRegistryCache::Observer {
+ public:
+  explicit HelpAppProvider(Profile* profile);
+  ~HelpAppProvider() override;
+
+  HelpAppProvider(const HelpAppProvider&) = delete;
+  HelpAppProvider& operator=(const HelpAppProvider&) = delete;
+
+  // SearchProvider:
+  void Start(const base::string16& query) override;
+  void AppListShown() override;
+  ash::AppListSearchResultType ResultType() override;
+
+  // apps::AppRegistryCache::Observer:
+  void OnAppUpdate(const apps::AppUpdate& update) override;
+  void OnAppRegistryCacheWillBeDestroyed(
+      apps::AppRegistryCache* cache) override;
+
+ private:
+  void OnLoadIcon(apps::mojom::IconValuePtr icon_value);
+  void LoadIcon();
+
+  apps::AppServiceProxy* app_service_proxy_;
+  gfx::ImageSkia icon_;
+  Profile* const profile_;
+  base::WeakPtrFactory<HelpAppProvider> weak_factory_{this};
+};
+
+}  // namespace app_list
+
+#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_HELP_APP_PROVIDER_H_
diff --git a/chrome/browser/ui/app_list/search/search_controller_factory.cc b/chrome/browser/ui/app_list/search/search_controller_factory.cc
index f9ed1fff..a691cec3 100644
--- a/chrome/browser/ui/app_list/search/search_controller_factory.cc
+++ b/chrome/browser/ui/app_list/search/search_controller_factory.cc
@@ -25,6 +25,7 @@
 #include "chrome/browser/ui/app_list/search/assistant_text_search_provider.h"
 #include "chrome/browser/ui/app_list/search/drive_quick_access_provider.h"
 #include "chrome/browser/ui/app_list/search/files/drive_zero_state_provider.h"
+#include "chrome/browser/ui/app_list/search/help_app_provider.h"
 #include "chrome/browser/ui/app_list/search/launcher_search/launcher_search_provider.h"
 #include "chrome/browser/ui/app_list/search/mixer.h"
 #include "chrome/browser/ui/app_list/search/omnibox_provider.h"
@@ -205,6 +206,10 @@
                             std::make_unique<OsSettingsProvider>(profile));
   }
 
+  size_t help_app_group_id = controller->AddGroup(kGenericMaxResults);
+  controller->AddProvider(help_app_group_id,
+                          std::make_unique<HelpAppProvider>(profile));
+
   return controller;
 }
 
diff --git a/chrome/browser/ui/app_list/search/search_result_ranker/chip_ranker.cc b/chrome/browser/ui/app_list/search/search_result_ranker/chip_ranker.cc
index dc9814ed..9e0413d9 100644
--- a/chrome/browser/ui/app_list/search/search_result_ranker/chip_ranker.cc
+++ b/chrome/browser/ui/app_list/search/search_result_ranker/chip_ranker.cc
@@ -243,7 +243,7 @@
     const auto type = result.result->result_type();
     if (type == ash::AppListSearchResultType::kAssistantChip ||
         type == ash::AppListSearchResultType::kPlayStoreReinstallApp ||
-        IsSuggestionChip(result.result->id(), profile_)) {
+        IsSuggestionChip(result.result->id())) {
       --num_chips;
     }
   }
diff --git a/chrome/browser/ui/app_list/search/search_result_ranker/ranking_item_util.cc b/chrome/browser/ui/app_list/search/search_result_ranker/ranking_item_util.cc
index e9ccb19..52ab647 100644
--- a/chrome/browser/ui/app_list/search/search_result_ranker/ranking_item_util.cc
+++ b/chrome/browser/ui/app_list/search/search_result_ranker/ranking_item_util.cc
@@ -34,6 +34,7 @@
     case ash::AppListSearchResultType::kOsSettings:
     case ash::AppListSearchResultType::kInternalPrivacyInfo:
     case ash::AppListSearchResultType::kAssistantText:
+    case ash::AppListSearchResultType::kHelpApp:
       // NOTE: We don't rank results of type kAssistantChip, kAssistantText
       // as those results, if present, are shown in a dedicated slot.
       return RankingItemType::kIgnored;
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_view.cc b/chrome/browser/ui/views/extensions/extensions_menu_view.cc
index d9b68e2..f3ce313e 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_view.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_view.cc
@@ -87,7 +87,7 @@
   SetShowCloseButton(true);
   SetTitle(IDS_EXTENSIONS_MENU_TITLE);
 
-  EnableArrowKeyTraversal();
+  SetEnableArrowKeyTraversal(true);
 
   // Let anchor view's MenuButtonController handle the highlight.
   set_highlight_button_when_shown(false);
diff --git a/chrome/browser/ui/views/find_bar_view.cc b/chrome/browser/ui/views/find_bar_view.cc
index b40c4e6..bd5b8b3 100644
--- a/chrome/browser/ui/views/find_bar_view.cc
+++ b/chrome/browser/ui/views/find_bar_view.cc
@@ -118,7 +118,9 @@
 };
 
 BEGIN_VIEW_BUILDER(/* No Export */, FindBarMatchCountLabel, views::Label)
-END_VIEW_BUILDER(/* No Export */, FindBarMatchCountLabel)
+END_VIEW_BUILDER
+
+DEFINE_VIEW_BUILDER(/* No Export */, FindBarMatchCountLabel)
 
 BEGIN_METADATA(FindBarMatchCountLabel, views::Label)
 END_METADATA
diff --git a/chrome/browser/ui/views/find_bar_view.h b/chrome/browser/ui/views/find_bar_view.h
index b77735e1..0876b6b2 100644
--- a/chrome/browser/ui/views/find_bar_view.h
+++ b/chrome/browser/ui/views/find_bar_view.h
@@ -125,6 +125,8 @@
 
 BEGIN_VIEW_BUILDER(/* no export */, FindBarView, views::BoxLayoutView)
 VIEW_BUILDER_PROPERTY(FindBarHost*, Host)
-END_VIEW_BUILDER(/* no export */, FindBarView)
+END_VIEW_BUILDER
+
+DEFINE_VIEW_BUILDER(/* no export */, FindBarView)
 
 #endif  // CHROME_BROWSER_UI_VIEWS_FIND_BAR_VIEW_H_
diff --git a/chrome/browser/ui/views/profiles/profile_menu_view_base.cc b/chrome/browser/ui/views/profiles/profile_menu_view_base.cc
index eac0d0b8..27324127 100644
--- a/chrome/browser/ui/views/profiles/profile_menu_view_base.cc
+++ b/chrome/browser/ui/views/profiles/profile_menu_view_base.cc
@@ -522,7 +522,7 @@
   DCHECK(anchor_button);
   anchor_button->AnimateInkDrop(views::InkDropState::ACTIVATED, nullptr);
 
-  EnableArrowKeyTraversal();
+  SetEnableArrowKeyTraversal(true);
   GetViewAccessibility().OverrideRole(ax::mojom::Role::kMenu);
 }
 
diff --git a/chrome/browser/ui/views/select_file_dialog_extension.cc b/chrome/browser/ui/views/select_file_dialog_extension.cc
index 3d9b9f5..ba95a96e4 100644
--- a/chrome/browser/ui/views/select_file_dialog_extension.cc
+++ b/chrome/browser/ui/views/select_file_dialog_extension.cc
@@ -402,7 +402,9 @@
   dialog_params.is_modal = (owner.window != nullptr);
   dialog_params.min_size = {kFileManagerMinimumWidth,
                             kFileManagerMinimumHeight};
-  dialog_params.title = file_manager::util::GetSelectFileDialogTitle(type);
+  dialog_params.title =
+      !title.empty() ? title
+                     : file_manager::util::GetSelectFileDialogTitle(type);
   if (base::FeatureList::IsEnabled(chromeos::features::kFilesNG)) {
     dialog_params.title_color = kFilePickerActiveTitleColor;
     dialog_params.title_inactive_color = kFilePickerInactiveTitleColor;
diff --git a/chrome/browser/ui/views/toolbar/home_button.h b/chrome/browser/ui/views/toolbar/home_button.h
index a520d546..832da9a 100644
--- a/chrome/browser/ui/views/toolbar/home_button.h
+++ b/chrome/browser/ui/views/toolbar/home_button.h
@@ -35,6 +35,8 @@
 };
 
 BEGIN_VIEW_BUILDER(/* no export */, HomeButton, ToolbarButton)
-END_VIEW_BUILDER(/* no export */, HomeButton)
+END_VIEW_BUILDER
+
+DEFINE_VIEW_BUILDER(/* no export */, HomeButton)
 
 #endif  // CHROME_BROWSER_UI_VIEWS_TOOLBAR_HOME_BUTTON_H_
diff --git a/chrome/browser/ui/views/toolbar/reload_button.h b/chrome/browser/ui/views/toolbar/reload_button.h
index 4526c34..f9924ca 100644
--- a/chrome/browser/ui/views/toolbar/reload_button.h
+++ b/chrome/browser/ui/views/toolbar/reload_button.h
@@ -109,6 +109,8 @@
 
 BEGIN_VIEW_BUILDER(CHROME_VIEWS_EXPORT, ReloadButton, ToolbarButton)
 VIEW_BUILDER_PROPERTY(bool, MenuEnabled)
-END_VIEW_BUILDER(VIEWS_EXPORT, ReloadButton)
+END_VIEW_BUILDER
+
+DEFINE_VIEW_BUILDER(CHROME_VIEWS_EXPORT, ReloadButton)
 
 #endif  // CHROME_BROWSER_UI_VIEWS_TOOLBAR_RELOAD_BUTTON_H_
diff --git a/chrome/browser/ui/views/toolbar/toolbar_button.h b/chrome/browser/ui/views/toolbar/toolbar_button.h
index 7e03ddff..044fbb1 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_button.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_button.h
@@ -291,6 +291,8 @@
 
 BEGIN_VIEW_BUILDER(CHROME_VIEWS_EXPORT, ToolbarButton, views::LabelButton)
 VIEW_BUILDER_PROPERTY(base::Optional<gfx::Insets>, LayoutInsets)
-END_VIEW_BUILDER(CHROME_VIEWS_EXPORT, ToolbarButton)
+END_VIEW_BUILDER
+
+DEFINE_VIEW_BUILDER(CHROME_VIEWS_EXPORT, ToolbarButton)
 
 #endif  // CHROME_BROWSER_UI_VIEWS_TOOLBAR_TOOLBAR_BUTTON_H_
diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
index 4d932c7..eeec195 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -223,6 +223,7 @@
 #endif
 
 #if defined(OS_CHROMEOS) && !defined(OFFICIAL_BUILD)
+#include "chrome/browser/chromeos/web_applications/chrome_file_manager_ui_delegate.h"
 #include "chrome/browser/ui/webui/chromeos/emulator/device_emulator_ui.h"
 #include "chromeos/components/file_manager/file_manager_ui.h"
 #include "chromeos/components/file_manager/url_constants.h"
@@ -343,6 +344,16 @@
   return new chromeos::CameraAppUI(web_ui, std::move(delegate));
 }
 
+#if !defined(OFFICIAL_BUILD)
+template <>
+WebUIController* NewWebUI<chromeos::file_manager::FileManagerUI>(
+    WebUI* web_ui,
+    const GURL& url) {
+  auto delegate = std::make_unique<ChromeFileManagerUIDelegate>();
+  return new chromeos::file_manager::FileManagerUI(web_ui, std::move(delegate));
+}
+#endif  // !defined(OFFICIAL_BUILD)
+
 template <>
 WebUIController* NewWebUI<chromeos::HelpAppUI>(WebUI* web_ui, const GURL& url) {
   auto delegate = std::make_unique<ChromeHelpAppUIDelegate>(web_ui);
diff --git a/chrome/browser/ui/webui/downloads/BUILD.gn b/chrome/browser/ui/webui/downloads/BUILD.gn
index 5e66ed8..627774c 100644
--- a/chrome/browser/ui/webui/downloads/BUILD.gn
+++ b/chrome/browser/ui/webui/downloads/BUILD.gn
@@ -6,4 +6,5 @@
 
 mojom("mojo_bindings") {
   sources = [ "downloads.mojom" ]
+  webui_module_path = "/"
 }
diff --git a/chrome/browser/ui/webui/tab_search/tab_search_ui.cc b/chrome/browser/ui/webui/tab_search/tab_search_ui.cc
index 91e5a3a..209e4b6 100644
--- a/chrome/browser/ui/webui/tab_search/tab_search_ui.cc
+++ b/chrome/browser/ui/webui/tab_search/tab_search_ui.cc
@@ -24,7 +24,7 @@
 
 namespace {
 constexpr char kGeneratedPath[] =
-    "@out_folder@/gen/chrome/browser/resources/tab_search_merge/";
+    "@out_folder@/gen/chrome/browser/resources/tab_search/";
 }
 
 TabSearchUI::TabSearchUI(content::WebUI* web_ui)
diff --git a/chrome/browser/video_tutorials/internal/BUILD.gn b/chrome/browser/video_tutorials/internal/BUILD.gn
index 148be4a..4091ffa 100644
--- a/chrome/browser/video_tutorials/internal/BUILD.gn
+++ b/chrome/browser/video_tutorials/internal/BUILD.gn
@@ -146,6 +146,7 @@
     bypass_platform_checks = true
     testonly = true
     sources = [
+      "android/java/src/org/chromium/chrome/browser/video_tutorials/PlaybackStateObserverUnitTest.java",
       "android/java/src/org/chromium/chrome/browser/video_tutorials/languages/LanguagePickerMediatorUnitTest.java",
       "android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerMediatorUnitTest.java",
     ]
@@ -157,6 +158,7 @@
       "//chrome/browser/video_tutorials:java",
       "//chrome/browser/video_tutorials:test_support_java",
       "//content/public/android:content_java",
+      "//services/media_session/public/cpp/android:media_session_java",
       "//third_party/android_deps:robolectric_all_java",
       "//third_party/hamcrest:hamcrest_core_java",
       "//third_party/junit",
diff --git a/chrome/browser/video_tutorials/internal/android/java/res/layout/video_player_controls.xml b/chrome/browser/video_tutorials/internal/android/java/res/layout/video_player_controls.xml
index fe72d3ea..259f719f 100644
--- a/chrome/browser/video_tutorials/internal/android/java/res/layout/video_player_controls.xml
+++ b/chrome/browser/video_tutorials/internal/android/java/res/layout/video_player_controls.xml
@@ -30,39 +30,48 @@
         android:scaleType="center"
         android:src="@drawable/ic_share_white_24dp" />
 
-    <View
-        android:id="@+id/dummy_center"
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:layout_centerInParent="true" />
-
     <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_below="@id/dummy_center"
-        android:layout_centerHorizontal="true"
-        android:layout_marginTop="60dp"
-        android:orientation="vertical">
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical" >
+        <Space
+            android:id="@+id/top_half"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="0.5" />
 
-        <org.chromium.ui.widget.ButtonCompat
-            android:id="@+id/try_now"
-            android:layout_width="100dp"
-            android:layout_height="wrap_content"
-            android:text="@string/video_tutorials_try_now"
-            style="@style/FilledButton.Flat" />
+        <LinearLayout
+            android:id="@+id/bottom_half"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="0.5"
+            android:paddingTop="40dp"
+            android:orientation="vertical">
 
-        <TextView
-            android:id="@+id/watch_next"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            style="@style/TextButton"
-            android:text="@string/video_tutorials_watch_next_video" />
+            <org.chromium.ui.widget.ButtonCompat
+                android:id="@+id/try_now"
+                android:layout_width="100dp"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:text="@string/video_tutorials_try_now"
+                style="@style/FilledButton.Flat" />
 
-        <TextView
-            android:id="@+id/change_language"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            style="@style/TextButton" />
+            <TextView
+                android:id="@+id/watch_next"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                style="@style/TextButton"
+                android:text="@string/video_tutorials_watch_next_video" />
+
+            <TextView
+                android:id="@+id/change_language"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                style="@style/TextButton" />
+        </LinearLayout>
+
     </LinearLayout>
 
 </RelativeLayout>
\ No newline at end of file
diff --git a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/PlaybackStateObserver.java b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/PlaybackStateObserver.java
index 863334d4..348715c0 100644
--- a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/PlaybackStateObserver.java
+++ b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/PlaybackStateObserver.java
@@ -4,11 +4,12 @@
 
 package org.chromium.chrome.browser.video_tutorials;
 
+import androidx.annotation.VisibleForTesting;
+
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.browser.video_tutorials.PlaybackStateObserver.WatchStateInfo.State;
 import org.chromium.content_public.browser.MediaSession;
 import org.chromium.content_public.browser.MediaSessionObserver;
-import org.chromium.content_public.browser.WebContents;
 import org.chromium.services.media_session.MediaPosition;
 
 /**
@@ -34,7 +35,7 @@
         }
 
         /** The current state. */
-        public State state;
+        public State state = State.INITIAL;
 
         /** The duration of the video. */
         public long videoLength;
@@ -68,16 +69,16 @@
         void onError();
     }
 
+    private static Long sCurrentSystemTimeForTesting;
     private final Supplier<Observer> mObserver;
-    private long mLastUpdateTime;
     private WatchStateInfo mWatchStateInfo = new WatchStateInfo();
     private MediaPosition mLastPosition;
     private boolean mIsControllable;
     private boolean mIsSuspended;
 
     /** Constructor. */
-    public PlaybackStateObserver(WebContents webContents, Supplier<Observer> observer) {
-        super(MediaSession.fromWebContents(webContents));
+    public PlaybackStateObserver(MediaSession mediaSession, Supplier<Observer> observer) {
+        super(mediaSession);
         mObserver = observer;
     }
 
@@ -95,16 +96,18 @@
         mLastPosition = null;
         mIsControllable = false;
         mIsSuspended = false;
-        mLastUpdateTime = 0;
         mWatchStateInfo = new WatchStateInfo();
     }
 
     @Override
     public void mediaSessionPositionChanged(MediaPosition position) {
+        if (position != null) {
+            mWatchStateInfo.videoLength = position.getDuration();
+        }
+
         updateState(position);
         mWatchStateInfo.currentPosition =
-                computeCurrentTime(position == null ? mLastPosition : position, mLastUpdateTime);
-        mLastUpdateTime = System.currentTimeMillis();
+                computeCurrentPosition(position == null ? mLastPosition : position);
         mLastPosition = position;
     }
 
@@ -122,8 +125,7 @@
             // TODO(shaktisahu): Determine error state.
             if (mLastPosition == null) {
                 nextState = State.INITIAL;
-            } else if (mLastPosition.getDuration()
-                    == computeCurrentTime(mLastPosition, mLastUpdateTime)) {
+            } else if (mLastPosition.getDuration() == computeCurrentPosition(mLastPosition)) {
                 nextState = State.ENDED;
             }
         }
@@ -155,12 +157,23 @@
         }
     }
 
-    private static long computeCurrentTime(MediaPosition mediaPosition, long lastUpdateTime) {
+    private static long computeCurrentPosition(MediaPosition mediaPosition) {
         if (mediaPosition == null) return 0;
-        long elapsedTime = System.currentTimeMillis() - lastUpdateTime;
+
+        long elapsedTime = getCurrentSystemTime() - mediaPosition.getLastUpdatedTime();
         long updatedPosition = (long) (mediaPosition.getPosition()
                 + (elapsedTime * mediaPosition.getPlaybackRate()));
         updatedPosition = Math.min(updatedPosition, mediaPosition.getDuration());
         return updatedPosition;
     }
+
+    private static long getCurrentSystemTime() {
+        if (sCurrentSystemTimeForTesting != null) return sCurrentSystemTimeForTesting;
+        return System.currentTimeMillis();
+    }
+
+    @VisibleForTesting
+    protected static void setCurrentSystemTimeForTesting(Long currentTimeForTesting) {
+        sCurrentSystemTimeForTesting = currentTimeForTesting;
+    }
 }
diff --git a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/PlaybackStateObserverUnitTest.java b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/PlaybackStateObserverUnitTest.java
new file mode 100644
index 0000000..31c6fbc
--- /dev/null
+++ b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/PlaybackStateObserverUnitTest.java
@@ -0,0 +1,112 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.video_tutorials;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import org.chromium.base.metrics.test.ShadowRecordHistogram;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.video_tutorials.PlaybackStateObserver.WatchStateInfo;
+import org.chromium.chrome.browser.video_tutorials.PlaybackStateObserver.WatchStateInfo.State;
+import org.chromium.content_public.browser.MediaSession;
+import org.chromium.services.media_session.MediaPosition;
+
+/**
+ * Tests for {@link PlaybackStateObserver}.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+public class PlaybackStateObserverUnitTest {
+    private static final long DURATION_MS = 10000L;
+
+    @Mock
+    private MediaSession mMediaSession;
+    @Mock
+    private PlaybackStateObserver.Observer mObserver;
+
+    private PlaybackStateObserver mPlaybackStateObserver;
+
+    @Before
+    public void setUp() {
+        ShadowRecordHistogram.reset();
+        MockitoAnnotations.initMocks(this);
+
+        mPlaybackStateObserver =
+                new PlaybackStateObserver(mMediaSession, () -> { return mObserver; });
+    }
+
+    @Test
+    public void testInitialState() {
+        WatchStateInfo info = mPlaybackStateObserver.getWatchStateInfo();
+        assertEquals(State.INITIAL, info.state);
+    }
+
+    @Test
+    public void testPlaybackStarted() {
+        MediaPosition position = new MediaPosition(DURATION_MS, 0, 1.f, 2);
+        mPlaybackStateObserver.mediaSessionStateChanged(true, false);
+        mPlaybackStateObserver.mediaSessionPositionChanged(position);
+        WatchStateInfo info = mPlaybackStateObserver.getWatchStateInfo();
+        assertEquals(State.PLAYING, info.state);
+        Mockito.verify(mObserver).onPlay();
+    }
+
+    @Test
+    public void testPlaybackPaused() {
+        MediaPosition position = new MediaPosition(DURATION_MS, 100, 1.f, 2);
+        mPlaybackStateObserver.mediaSessionStateChanged(true, true);
+        mPlaybackStateObserver.mediaSessionPositionChanged(position);
+        WatchStateInfo info = mPlaybackStateObserver.getWatchStateInfo();
+        assertEquals(State.PAUSED, info.state);
+        Mockito.verify(mObserver).onPause();
+    }
+
+    @Test
+    public void testPlaybackComplete() {
+        long initialSystemTime = 30000L;
+        PlaybackStateObserver.setCurrentSystemTimeForTesting(initialSystemTime);
+        MediaPosition position = new MediaPosition(DURATION_MS, 0, 1.f, initialSystemTime);
+        mPlaybackStateObserver.mediaSessionStateChanged(true, false);
+        mPlaybackStateObserver.mediaSessionPositionChanged(position);
+        verifyWatchState(State.PLAYING, false, DURATION_MS, 0);
+        Mockito.verify(mObserver).onPlay();
+
+        // Slightly advance the playback to 100ms.
+        PlaybackStateObserver.setCurrentSystemTimeForTesting(initialSystemTime + 100);
+        MediaPosition position2 = new MediaPosition(DURATION_MS, 100, 1.f, initialSystemTime + 100);
+        mPlaybackStateObserver.mediaSessionStateChanged(true, false);
+        mPlaybackStateObserver.mediaSessionPositionChanged(position2);
+        Mockito.verify(mObserver).onPlay();
+
+        // Advance playback to almost completion with 100ms remaining.
+        PlaybackStateObserver.setCurrentSystemTimeForTesting(initialSystemTime + DURATION_MS - 100);
+        MediaPosition position3 = new MediaPosition(
+                DURATION_MS, DURATION_MS - 100, 1.f, initialSystemTime + DURATION_MS - 100);
+        mPlaybackStateObserver.mediaSessionStateChanged(true, false);
+        mPlaybackStateObserver.mediaSessionPositionChanged(position3);
+        Mockito.verify(mObserver).onPlay();
+
+        // Complete the video.
+        PlaybackStateObserver.setCurrentSystemTimeForTesting(initialSystemTime + DURATION_MS);
+        mPlaybackStateObserver.mediaSessionStateChanged(false, false);
+        mPlaybackStateObserver.mediaSessionPositionChanged(null);
+        verifyWatchState(State.ENDED, true, DURATION_MS, DURATION_MS);
+        Mockito.verify(mObserver).onEnded();
+    }
+
+    private void verifyWatchState(
+            State state, boolean videoWatched, long duration, long currentPosition) {
+        assertEquals(state, mPlaybackStateObserver.getWatchStateInfo().state);
+        assertEquals(duration, mPlaybackStateObserver.getWatchStateInfo().videoLength);
+        assertEquals(currentPosition, mPlaybackStateObserver.getWatchStateInfo().currentPosition);
+        assertEquals(videoWatched, mPlaybackStateObserver.getWatchStateInfo().videoWatched());
+    }
+}
diff --git a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerCoordinatorImpl.java b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerCoordinatorImpl.java
index 3588ae6..e9c319d 100644
--- a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerCoordinatorImpl.java
+++ b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerCoordinatorImpl.java
@@ -21,6 +21,7 @@
 import org.chromium.components.thinwebview.ThinWebView;
 import org.chromium.components.thinwebview.ThinWebViewConstraints;
 import org.chromium.components.thinwebview.ThinWebViewFactory;
+import org.chromium.content_public.browser.MediaSession;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
@@ -95,8 +96,8 @@
         mWebContents = pair.first;
         ContentView webContentView = pair.second;
         mWebContentsDelegate = new WebContentsDelegateAndroid();
-        mMediaSessionObserver =
-                new PlaybackStateObserver(mWebContents, () -> { return mMediator; });
+        mMediaSessionObserver = new PlaybackStateObserver(
+                MediaSession.fromWebContents(mWebContents), () -> { return mMediator; });
 
         ThinWebView thinWebView = ThinWebViewFactory.create(mContext, new ThinWebViewConstraints());
         thinWebView.attachWebContents(mWebContents, webContentView, mWebContentsDelegate);
diff --git a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerMediator.java b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerMediator.java
index 694b13745..4cd3970 100644
--- a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerMediator.java
+++ b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerMediator.java
@@ -11,6 +11,7 @@
 import org.chromium.chrome.browser.video_tutorials.Language;
 import org.chromium.chrome.browser.video_tutorials.LanguageInfoProvider;
 import org.chromium.chrome.browser.video_tutorials.PlaybackStateObserver;
+import org.chromium.chrome.browser.video_tutorials.PlaybackStateObserver.WatchStateInfo.State;
 import org.chromium.chrome.browser.video_tutorials.R;
 import org.chromium.chrome.browser.video_tutorials.Tutorial;
 import org.chromium.chrome.browser.video_tutorials.VideoTutorialService;
@@ -123,6 +124,7 @@
         mModel.set(VideoPlayerProperties.SHOW_CHANGE_LANGUAGE, false);
         mModel.set(VideoPlayerProperties.SHOW_TRY_NOW,
                 VideoTutorialUtils.shouldShowTryNow(mTutorial.featureType));
+        mModel.set(VideoPlayerProperties.WATCH_STATE_FOR_TRY_NOW, State.PAUSED);
     }
 
     @Override
@@ -133,6 +135,7 @@
         maybeShowWatchNextVideoButton();
         mModel.set(VideoPlayerProperties.SHOW_TRY_NOW,
                 VideoTutorialUtils.shouldShowTryNow(mTutorial.featureType));
+        mModel.set(VideoPlayerProperties.WATCH_STATE_FOR_TRY_NOW, State.ENDED);
         updateChangeLanguageButtonText();
     }
 
diff --git a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerMediatorUnitTest.java b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerMediatorUnitTest.java
index ff144ea..c4c2309 100644
--- a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerMediatorUnitTest.java
+++ b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerMediatorUnitTest.java
@@ -11,6 +11,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -23,8 +24,11 @@
 import org.chromium.base.Callback;
 import org.chromium.base.metrics.test.ShadowRecordHistogram;
 import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.video_tutorials.FeatureType;
+import org.chromium.chrome.browser.video_tutorials.Language;
 import org.chromium.chrome.browser.video_tutorials.LanguageInfoProvider;
 import org.chromium.chrome.browser.video_tutorials.PlaybackStateObserver;
+import org.chromium.chrome.browser.video_tutorials.PlaybackStateObserver.WatchStateInfo;
 import org.chromium.chrome.browser.video_tutorials.Tutorial;
 import org.chromium.chrome.browser.video_tutorials.VideoTutorialUtils;
 import org.chromium.chrome.browser.video_tutorials.languages.LanguagePickerCoordinator;
@@ -145,6 +149,55 @@
     }
 
     @Test
+    public void testChangeLanguage() {
+        Tutorial tutorial = mTestVideoTutorialService.getTestTutorials().get(0);
+        mMediator.playVideoTutorial(tutorial);
+        mMediator.onPlay();
+        mMediator.onEnded();
+
+        Language language = new Language("en", "English", "English native");
+        Mockito.when(mLanguageProvider.getLanguageInfo("en")).thenReturn(language);
+        mModel.get(VideoPlayerProperties.CALLBACK_CHANGE_LANGUAGE).run();
+        Mockito.verify(mLanguagePicker, Mockito.times(1))
+                .showLanguagePicker(mLanguagePickerCallback.capture(), any());
+        mTestVideoTutorialService.setPreferredLocale("en");
+        ((Runnable) mLanguagePickerCallback.getValue()).run();
+    }
+
+    @Test
+    public void verifyButtonCallbacks() {
+        Tutorial tutorial = mTestVideoTutorialService.getTestTutorials().get(0);
+        mMediator.playVideoTutorial(tutorial);
+        mMediator.onPlay();
+        mMediator.onPause();
+
+        mModel.get(VideoPlayerProperties.CALLBACK_TRY_NOW).run();
+        Mockito.verify(mTryNowCallback).onResult(tutorial);
+
+        mModel.get(VideoPlayerProperties.CALLBACK_CLOSE).run();
+        Mockito.verify(mCloseCallback).run();
+
+        mModel.get(VideoPlayerProperties.CALLBACK_SHARE).run();
+        mModel.get(VideoPlayerProperties.CALLBACK_CHANGE_LANGUAGE).run();
+        Mockito.verify(mLanguagePicker).showLanguagePicker(any(), any());
+
+        WatchStateInfo watchStateInfo = new WatchStateInfo();
+        watchStateInfo.videoLength = 10;
+        watchStateInfo.currentPosition = 8;
+        Mockito.doReturn(watchStateInfo).when(mPlaybackStateObserver).getWatchStateInfo();
+        mModel.get(VideoPlayerProperties.CALLBACK_WATCH_NEXT).run();
+        mMediator.destroy();
+    }
+
+    @Test
+    public void testHandleBackPressed() {
+        Tutorial tutorial = mTestVideoTutorialService.getTestTutorials().get(0);
+        mMediator.playVideoTutorial(tutorial);
+        mMediator.onPlay();
+        Assert.assertFalse(mMediator.handleBackPressed());
+    }
+
+    @Test
     public void testVideoLengthString() {
         assertThat(VideoTutorialUtils.getVideoLengthString(0), equalTo("0:00"));
         assertThat(VideoTutorialUtils.getVideoLengthString(5), equalTo("0:05"));
@@ -153,4 +206,13 @@
         assertThat(VideoTutorialUtils.getVideoLengthString(1200), equalTo("20:00"));
         assertThat(VideoTutorialUtils.getVideoLengthString(3615), equalTo("1:00:15"));
     }
+
+    @Test
+    public void testTryNowEnabledForFeatures() {
+        Assert.assertFalse(VideoTutorialUtils.shouldShowTryNow(FeatureType.CHROME_INTRO));
+        Assert.assertTrue(VideoTutorialUtils.shouldShowTryNow(FeatureType.DOWNLOAD));
+        Assert.assertTrue(VideoTutorialUtils.shouldShowTryNow(FeatureType.SEARCH));
+        Assert.assertTrue(VideoTutorialUtils.shouldShowTryNow(FeatureType.VOICE_SEARCH));
+        Assert.assertFalse(VideoTutorialUtils.shouldShowTryNow(99));
+    }
 }
diff --git a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerProperties.java b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerProperties.java
index 151d44a..481a7745 100644
--- a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerProperties.java
+++ b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerProperties.java
@@ -4,6 +4,7 @@
 
 package org.chromium.chrome.browser.video_tutorials.player;
 
+import org.chromium.chrome.browser.video_tutorials.PlaybackStateObserver.WatchStateInfo.State;
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel.WritableBooleanPropertyKey;
 import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey;
@@ -28,9 +29,10 @@
     WritableObjectPropertyKey<Runnable> CALLBACK_TRY_NOW = new WritableObjectPropertyKey<>();
     WritableObjectPropertyKey<Runnable> CALLBACK_SHARE = new WritableObjectPropertyKey<>();
     WritableObjectPropertyKey<Runnable> CALLBACK_CLOSE = new WritableObjectPropertyKey<>();
+    WritableObjectPropertyKey<State> WATCH_STATE_FOR_TRY_NOW = new WritableObjectPropertyKey<>();
 
     PropertyKey[] ALL_KEYS = new PropertyKey[] {SHOW_LOADING_SCREEN, SHOW_MEDIA_CONTROLS,
             SHOW_LANGUAGE_PICKER, SHOW_TRY_NOW, SHOW_WATCH_NEXT, SHOW_CHANGE_LANGUAGE,
             CHANGE_LANGUAGE_BUTTON_TEXT, CALLBACK_WATCH_NEXT, CALLBACK_CHANGE_LANGUAGE,
-            CALLBACK_TRY_NOW, CALLBACK_SHARE, CALLBACK_CLOSE};
+            CALLBACK_TRY_NOW, CALLBACK_SHARE, CALLBACK_CLOSE, WATCH_STATE_FOR_TRY_NOW};
 }
diff --git a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerView.java b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerView.java
index e3461895..e0d5658 100644
--- a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerView.java
+++ b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerView.java
@@ -8,13 +8,17 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
 
+import org.chromium.chrome.browser.video_tutorials.PlaybackStateObserver.WatchStateInfo;
+import org.chromium.chrome.browser.video_tutorials.PlaybackStateObserver.WatchStateInfo.State;
 import org.chromium.chrome.browser.video_tutorials.R;
 import org.chromium.components.thinwebview.ThinWebView;
 import org.chromium.ui.modelutil.PropertyModel;
 
 /**
- *  Represents the view component of the media player. Contains loading screen, language picker, and
+ * Represents the view component of the media player. Contains loading screen, language picker, and
  * media controls.
  */
 class VideoPlayerView {
@@ -60,4 +64,20 @@
     void showLanguagePicker(boolean show) {
         mLanguagePickerView.setVisibility(show ? View.VISIBLE : View.GONE);
     }
+
+    void setTryNowButtonPosition(WatchStateInfo.State state) {
+        View topHalf = mControls.findViewById(R.id.top_half);
+        View bottomHalf = mControls.findViewById(R.id.bottom_half);
+        LinearLayout.LayoutParams topLayoutParams = (LayoutParams) topHalf.getLayoutParams();
+        LinearLayout.LayoutParams bottomLayoutParams = (LayoutParams) bottomHalf.getLayoutParams();
+        if (state == State.PAUSED) {
+            topLayoutParams.weight = 0.5f;
+            bottomLayoutParams.weight = 0.5f;
+        } else if (state == State.ENDED) {
+            topLayoutParams.weight = 0.62f;
+            bottomLayoutParams.weight = 0.38f;
+        } else {
+            assert false : "Unexpected state " + state;
+        }
+    }
 }
diff --git a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerViewBinder.java b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerViewBinder.java
index 3f64124c..bbc0e920 100644
--- a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerViewBinder.java
+++ b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerViewBinder.java
@@ -63,6 +63,8 @@
             view.getView().findViewById(R.id.change_language).setOnClickListener(v -> {
                 model.get(VideoPlayerProperties.CALLBACK_CHANGE_LANGUAGE).run();
             });
+        } else if (propertyKey == VideoPlayerProperties.WATCH_STATE_FOR_TRY_NOW) {
+            view.setTryNowButtonPosition(model.get(VideoPlayerProperties.WATCH_STATE_FOR_TRY_NOW));
         }
     }
 }
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index aed9d854..543d32f9 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-master-1603994501-28d36cf57d6466dbb811553e28b04bff48def90b.profdata
+chrome-linux-master-1604015897-cbf2d5639eb5b5579fe2658cc59cbce3c1075df1.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 5c53a26..478ed6a 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-master-1603972752-eb96abf6b462a9960649372890eeb96e7f549740.profdata
+chrome-mac-master-1604015897-a1b8341d837100918c78553c7c6bd57ce99f9313.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 01ed051..38d0e2b 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-master-1603907949-4ffda8056377a01132d9724a51a14693bc70ab3c.profdata
+chrome-win64-master-1603983512-c60ff6e0a6baf9584b1a93640671c446cc84aec9.profdata
diff --git a/chrome/common/extensions/api/input_method_private.json b/chrome/common/extensions/api/input_method_private.json
index 6ea5642..2620c5b 100644
--- a/chrome/common/extensions/api/input_method_private.json
+++ b/chrome/common/extensions/api/input_method_private.json
@@ -755,7 +755,35 @@
         "type": "function",
         "description": "Resets the current engine to its initial state. Fires an OnReset event.",
         "parameters": []
-      }
+      },
+      {
+        "name": "onAutocorrect",
+        "type": "function",
+        "description": "Called after a word has been autocorrected to show some UI for autocorrect.",
+        "platforms": ["chromeos"],
+      "parameters": [{
+            "name": "parameters",
+            "type": "object",
+            "properties": {
+                "contextID": {
+                    "description": "ID of the context where the autocorrect occurred.",
+                    "type": "integer"
+                },
+                "typedWord": {
+                    "type": "string",
+                    "description": "Corrected word will be replaced by this when clicking undo"
+                },
+                "correctedWord": {
+                    "type": "string",
+                    "description": "Needed to know the length of the autocorrected text to show the correct length of underline."
+                },
+                "startIndex": {
+                    "type": "integer",
+                    "description": "Offset index (in code units) in surroundingInfo (see onSurroundingTextChanged) for the start of the autocorrected text"
+                }
+            }
+        }]
+    }
     ],
     "events": [
       {
diff --git a/chrome/services/printing/BUILD.gn b/chrome/services/printing/BUILD.gn
index c514242..9c223f5 100644
--- a/chrome/services/printing/BUILD.gn
+++ b/chrome/services/printing/BUILD.gn
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//printing/buildflags/buildflags.gni")
 import("//testing/test.gni")
 
 source_set("lib") {
@@ -16,13 +17,16 @@
 
   deps = [
     "//components/crash/core/common:crash_key",
-    "//components/pwg_encoder",
     "//pdf",
     "//printing",
     "//printing/mojom",
     "//ui/gfx",
   ]
 
+  if (enable_print_preview) {
+    deps += [ "//components/pwg_encoder" ]
+  }
+
   public_deps = [
     "//base",
     "//chrome/services/printing/public/mojom",
diff --git a/chrome/services/sharing/nearby/platform/webrtc.cc b/chrome/services/sharing/nearby/platform/webrtc.cc
index 990c9e1..18d3b773 100644
--- a/chrome/services/sharing/nearby/platform/webrtc.cc
+++ b/chrome/services/sharing/nearby/platform/webrtc.cc
@@ -10,6 +10,7 @@
 #include "chrome/services/sharing/webrtc/p2p_port_allocator.h"
 #include "jingle/glue/thread_wrapper.h"
 #include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "third_party/nearby/src/cpp/platform/public/future.h"
 #include "third_party/webrtc/api/jsep.h"
 #include "third_party/webrtc/api/peer_connection_interface.h"
@@ -74,23 +75,47 @@
   sharing::IpcPacketSocketFactory* socket_factory_;
 };
 
+// This object only exists to forward incoming mojo messages. It will be created
+// as a SelfOwnedReceiver on a separate sequence and will be cleaned up when the
+// connection goes down. This is necessary to keep it pumping messages while the
+// the main WebRtc thread is blocked on a future.
+class IncomingMessageListener
+    : public sharing::mojom::IncomingMessagesListener {
+ public:
+  explicit IncomingMessageListener(
+      api::WebRtcSignalingMessenger::OnSignalingMessageCallback
+          signaling_message_callback)
+      : signaling_message_callback_(std::move(signaling_message_callback)) {
+    DCHECK(signaling_message_callback_);
+  }
+
+  ~IncomingMessageListener() override = default;
+
+  // mojom::IncomingMessagesListener:
+  void OnMessage(const std::string& message) override {
+    signaling_message_callback_(ByteArray(message));
+  }
+
+ private:
+  api::WebRtcSignalingMessenger::OnSignalingMessageCallback
+      signaling_message_callback_;
+};
+
 // Used as a messenger in sending and receiving WebRTC messages between devices.
 // The messages sent and received are considered untrusted since they
 // originate in an untrusted sandboxed process on device.
-class WebRtcSignalingMessengerImpl
-    : public api::WebRtcSignalingMessenger,
-      public sharing::mojom::IncomingMessagesListener {
+class WebRtcSignalingMessengerImpl : public api::WebRtcSignalingMessenger {
  public:
-  using OnSignalingMessageCallback =
-      api::WebRtcSignalingMessenger::OnSignalingMessageCallback;
-
   WebRtcSignalingMessengerImpl(
       const std::string& self_id,
       const mojo::SharedRemote<sharing::mojom::WebRtcSignalingMessenger>&
           messenger)
-      : self_id_(self_id), messenger_(messenger) {}
+      : self_id_(self_id),
+        messenger_(messenger),
+        task_runner_(
+            base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})) {}
 
-  ~WebRtcSignalingMessengerImpl() override = default;
+  ~WebRtcSignalingMessengerImpl() override { StopReceivingMessages(); }
 
   WebRtcSignalingMessengerImpl(const WebRtcSignalingMessengerImpl& other) =
       delete;
@@ -109,49 +134,59 @@
     return success;
   }
 
+  void BindIncomingReceiver(
+      mojo::PendingReceiver<sharing::mojom::IncomingMessagesListener>
+          pending_receiver,
+      api::WebRtcSignalingMessenger::OnSignalingMessageCallback callback) {
+    auto receiver = mojo::MakeSelfOwnedReceiver(
+        std::make_unique<IncomingMessageListener>(std::move(callback)),
+        std::move(pending_receiver), task_runner_);
+    receiver->set_connection_error_handler(base::BindOnce(
+        [](mojo::SharedRemote<sharing::mojom::WebRtcSignalingMessenger>
+               messenger) { messenger->StopReceivingMessages(); },
+        messenger_));
+  }
+
   // api::WebRtcSignalingMessenger:
   bool StartReceivingMessages(OnSignalingMessageCallback callback) override {
-    signaling_message_callback_ = std::move(callback);
-    incoming_messages_receiver_.reset();
     bool success = false;
-
-    if (!messenger_->StartReceivingMessages(
-            self_id_, incoming_messages_receiver_.BindNewPipeAndPassRemote(),
-            &success) ||
+    mojo::PendingRemote<sharing::mojom::IncomingMessagesListener>
+        pending_remote;
+    mojo::PendingReceiver<sharing::mojom::IncomingMessagesListener>
+        pending_receiver = pending_remote.InitWithNewPipeAndPassReceiver();
+    if (!messenger_->StartReceivingMessages(self_id_, std::move(pending_remote),
+                                            &success) ||
         !success) {
-      incoming_messages_receiver_.reset();
-      signaling_message_callback_ = nullptr;
+      receiving_messages_ = false;
       return false;
     }
 
-    incoming_messages_receiver_.set_disconnect_handler(
-        base::BindOnce(&WebRtcSignalingMessengerImpl::StopReceivingMessages,
-                       base::Unretained(this)));
+    // Do the pending_receiver Bind call on the task runner itself so it can
+    // receive messages while the WebRtc thread is waiting. Any incoming
+    // messages will be queued until the Bind happens.
+    task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(&WebRtcSignalingMessengerImpl::BindIncomingReceiver,
+                       base::Unretained(this), std::move(pending_receiver),
+                       std::move(callback)));
 
-    return success;
+    receiving_messages_ = true;
+    return true;
   }
 
   // api::WebRtcSignalingMessenger:
   void StopReceivingMessages() override {
-    incoming_messages_receiver_.reset();
-    signaling_message_callback_ = nullptr;
-    messenger_->StopReceivingMessages();
+    if (receiving_messages_) {
+      receiving_messages_ = false;
+      messenger_->StopReceivingMessages();
+    }
   }
 
  private:
-  // mojom::IncomingMessagesListener:
-  void OnMessage(const std::string& message) override {
-    if (signaling_message_callback_)
-      signaling_message_callback_(ByteArray(message));
-  }
-
+  bool receiving_messages_ = false;
   std::string self_id_;
   mojo::SharedRemote<sharing::mojom::WebRtcSignalingMessenger> messenger_;
-  mojo::Receiver<sharing::mojom::IncomingMessagesListener>
-      incoming_messages_receiver_{this};
-  OnSignalingMessageCallback signaling_message_callback_;
-
-  base::WeakPtrFactory<WebRtcSignalingMessengerImpl> weak_ptr_factory_{this};
+  scoped_refptr<base::SequencedTaskRunner> task_runner_;
 };
 
 }  // namespace
diff --git a/chrome/services/sharing/nearby/platform/webrtc_test.cc b/chrome/services/sharing/nearby/platform/webrtc_test.cc
index ab09535..26f2888 100644
--- a/chrome/services/sharing/nearby/platform/webrtc_test.cc
+++ b/chrome/services/sharing/nearby/platform/webrtc_test.cc
@@ -66,7 +66,7 @@
   }
 
  private:
-  base::test::SingleThreadTaskEnvironment task_environment_;
+  base::test::TaskEnvironment task_environment_;
   testing::NiceMock<sharing::MockWebRtcDependencies> mojo_impl_;
 
   mojo::SharedRemote<network::mojom::P2PSocketManager> socket_manager_;
@@ -178,6 +178,12 @@
             remote.Bind(std::move(listener));
             remote->OnMessage(std::string(message));
           }));
+  EXPECT_CALL(GetMockWebRtcDependencies(), StopReceivingMessages())
+      .WillRepeatedly(testing::Invoke([&]() {
+        if (remote.is_bound()) {
+          remote.reset();
+        }
+      }));
 
   // TODO(https://crbug.com/1142001): Test with non-trivial |location_hint|.
   std::unique_ptr<api::WebRtcSignalingMessenger> messenger =
@@ -194,10 +200,9 @@
   EXPECT_TRUE(remote.is_connected());
 
   messenger->StopReceivingMessages();
-
   // Run mojo disconnect handlers.
   base::RunLoop().RunUntilIdle();
-  EXPECT_FALSE(remote.is_connected());
+  EXPECT_FALSE(remote.is_bound());
 }
 
 TEST_F(WebRtcMediumTest, GetMessengerAndStartReceivingMessagesTwice) {
diff --git a/chrome/test/data/webui/BUILD.gn b/chrome/test/data/webui/BUILD.gn
index dd234d6..cfed0fb 100644
--- a/chrome/test/data/webui/BUILD.gn
+++ b/chrome/test/data/webui/BUILD.gn
@@ -26,7 +26,7 @@
       "history/history_focus_test.js",
       "print_preview/print_preview_interactive_ui_tests.js",
       "settings/cr_settings_v3_interactive_ui_tests.js",
-      "tab_search_merge/tab_search_interactive_ui_tests.js",
+      "tab_search/tab_search_interactive_ui_tests.js",
     ]
 
     gen_include_files = [
@@ -97,7 +97,7 @@
       "settings/a11y/v3_a11y_browsertest.js",
       "settings/cr_settings_v3_browsertest.js",
       "settings/settings_idle_load_v3_browsertest.js",
-      "tab_search_merge/tab_search_browsertest.js",
+      "tab_search/tab_search_browsertest.js",
       "text_defaults_browsertest.js",
       "webui_resource_async_browsertest.js",
     ]
@@ -353,7 +353,6 @@
     sources = [
       "bluetooth_internals_browsertest.js",
       "cr_components/cr_components_mojo_browsertest.js",
-      "downloads/downloads_browsertest.js",
       "engagement/site_engagement_browsertest.js",
       "interventions_internals_browsertest.js",
       "media/media_engagement_browsertest.js",
@@ -381,25 +380,18 @@
       deps += [ "//chromeos/services/machine_learning/public/cpp:test_support" ]
     }
 
-    extra_js_files = [ "//chrome/browser/resources/downloads/constants.js" ]
-
     defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
   }
 
-  if (is_win || is_mac || is_linux || is_chromeos) {
-    # discards_browsertest.js is the only test suite here for now, and it's
-    # desktop-only. When more test suites are converted to type "mojo_webui",
-    # this target should be defined unconditionally and this outer condition
-    # used only to constrain the inclusion of discards_browsertest.js.
-    js2gtest("browser_tests_js_mojo_webui") {
-      test_type = "mojo_webui"
-      sources = [ "discards/discards_browsertest.js" ]
-      defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
-      data = [ "mojo_webui_test_support.js" ]
+  js2gtest("browser_tests_js_mojo_webui") {
+    test_type = "mojo_webui"
+    sources = [ "downloads/downloads_browsertest.js" ]
+    if (is_win || is_mac || is_linux || is_chromeos) {
+      sources += [ "discards/discards_browsertest.js" ]
     }
-  } else {
-    group("browser_tests_js_mojo_webui") {
-    }
+    defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
+    data = [ "mojo_webui_test_support.js" ]
+    extra_js_files = [ "//chrome/browser/resources/downloads/constants.js" ]
   }
 
   js2gtest("interactive_ui_tests_js_mojo_lite_webui") {
@@ -488,7 +480,7 @@
     "read_later:closure_compile",
     "settings:closure_compile",
     "signin:closure_compile",
-    "tab_search_merge:closure_compile",
+    "tab_search:closure_compile",
     "tab_strip:closure_compile",
 
     # TODO(crbug.com/1000989): Add page specific targets here.
diff --git a/chrome/test/data/webui/chromeos/diagnostics/routine_result_list_test.js b/chrome/test/data/webui/chromeos/diagnostics/routine_result_list_test.js
index e763b90..777fff1f 100644
--- a/chrome/test/data/webui/chromeos/diagnostics/routine_result_list_test.js
+++ b/chrome/test/data/webui/chromeos/diagnostics/routine_result_list_test.js
@@ -4,7 +4,7 @@
 
 import 'chrome://diagnostics/routine_result_list.js';
 
-import {RoutineName} from 'chrome://diagnostics/diagnostics_types.js';
+import {RoutineName, StandardRoutineResult} from 'chrome://diagnostics/diagnostics_types.js';
 import {ExecutionProgress, ResultStatusItem} from 'chrome://diagnostics/routine_list_executor.js';
 import {flushTasks} from 'chrome://test/test_util.m.js';
 
@@ -110,4 +110,82 @@
           assertEquals(0, getEntries().length);
         });
   });
+
+  test('VerifyStatusUpdates', () => {
+    /** @type {!Array<!RoutineName>} */
+    const routines = [
+      RoutineName.kCpuCache,
+      RoutineName.kFloatingPoint,
+    ];
+
+    return initializeRoutineResultList(routines).then(() => {
+      // Verify the starting state.
+      assertEquals(routines.length, getEntries().length);
+      getEntries().forEach((entry, index) => {
+        // Routines are initialized in the unstarted state.
+        let status = new ResultStatusItem(routines[index]);
+        status.progress = ExecutionProgress.kNotStarted;
+        assertDeepEquals(status, entry.item);
+      });
+
+      let status = new ResultStatusItem(routines[0]);
+      status.progress = ExecutionProgress.kRunning;
+      routineResultListElement.onStatusUpdate(status);
+      return flushTasks()
+          .then(() => {
+            // Verify first routine is running.
+            assertEquals(
+                ExecutionProgress.kRunning, getEntries()[0].item.progress);
+            assertEquals(null, getEntries()[0].item.result);
+
+            // Move the first routine to completed state.
+            status = new ResultStatusItem(routines[0]);
+            status.progress = ExecutionProgress.kCompleted;
+            status.result = {simple_result: StandardRoutineResult.kTestPassed};
+            routineResultListElement.onStatusUpdate(status);
+
+            return flushTasks();
+          })
+          .then(() => {
+            // Verify the first routine is completed.
+            assertEquals(
+                ExecutionProgress.kCompleted, getEntries()[0].item.progress);
+            assertNotEquals(null, getEntries()[0].item.result);
+            assertEquals(
+                StandardRoutineResult.kTestPassed,
+                getEntries()[0].item.result.simple_result);
+
+            status = new ResultStatusItem(routines[1]);
+            status.progress = ExecutionProgress.kRunning;
+            routineResultListElement.onStatusUpdate(status);
+
+            return flushTasks();
+          })
+          .then(() => {
+            // Verify second routine is running.
+            assertEquals(
+                ExecutionProgress.kRunning, getEntries()[1].item.progress);
+            assertEquals(null, getEntries()[1].item.result);
+
+            // Move the second routine to completed state.
+            status = new ResultStatusItem(routines[1]);
+            status.progress = ExecutionProgress.kCompleted;
+            status.result = {simple_result: StandardRoutineResult.kTestPassed};
+            routineResultListElement.onStatusUpdate(status);
+
+            return flushTasks();
+          })
+          .then(() => {
+            // Verify the second routine is completed.
+            assertEquals(
+                ExecutionProgress.kCompleted, getEntries()[1].item.progress);
+            assertNotEquals(null, getEntries()[1].item.result);
+            assertEquals(
+                StandardRoutineResult.kTestPassed,
+                getEntries()[0].item.result.simple_result);
+
+            return flushTasks();
+          });
+    });
+  });
 }
diff --git a/chrome/test/data/webui/chromeos/diagnostics/routine_section_test.js b/chrome/test/data/webui/chromeos/diagnostics/routine_section_test.js
index a7e626d2..7814365 100644
--- a/chrome/test/data/webui/chromeos/diagnostics/routine_section_test.js
+++ b/chrome/test/data/webui/chromeos/diagnostics/routine_section_test.js
@@ -6,6 +6,8 @@
 import 'chrome://diagnostics/routine_section.js';
 
 import {RoutineName} from 'chrome://diagnostics/diagnostics_types.js';
+import {FakeSystemRoutineController} from 'chrome://diagnostics/fake_system_routine_controller.js';
+import {setSystemRoutineControllerForTesting} from 'chrome://diagnostics/mojo_interface_provider.js';
 import {ExecutionProgress} from 'chrome://diagnostics/routine_list_executor.js';
 import {flushTasks} from 'chrome://test/test_util.m.js';
 
@@ -15,8 +17,17 @@
   /** @type {?HTMLElement} */
   let routineSectionElement = null;
 
+  /** @type {!FakeSystemRoutineController} */
+  let routineController;
+
   setup(function() {
     PolymerTest.clearBody();
+
+    // Setup a fake routine controller so that nothing resolves unless
+    // done explicitly.
+    routineController = new FakeSystemRoutineController();
+    routineController.setDelayTimeInMillisecondsForTesting(-1);
+    setSystemRoutineControllerForTesting(routineController);
   });
 
   teardown(function() {
@@ -129,10 +140,50 @@
         .then(() => {
           const entries = getEntries();
           assertEquals(routines.length, entries.length);
-          entries.forEach((entry, index) => {
-            assertEquals(routines[index], entry.item.routine);
-            assertEquals(ExecutionProgress.kNotStarted, entry.item.progress);
-          });
+
+          // First routine should be running.
+          assertEquals(routines[0], entries[0].item.routine);
+          assertEquals(ExecutionProgress.kRunning, entries[0].item.progress);
+
+          // Second routine is not started.
+          assertEquals(routines[1], entries[1].item.routine);
+          assertEquals(ExecutionProgress.kNotStarted, entries[1].item.progress);
+
+          // Resolve the running test.
+          return routineController.resolveRoutineForTesting();
+        })
+        .then(() => {
+          return flushTasks();
+        })
+        .then(() => {
+          const entries = getEntries();
+          assertEquals(routines.length, entries.length);
+
+          // First routine should be completed.
+          assertEquals(routines[0], entries[0].item.routine);
+          assertEquals(ExecutionProgress.kCompleted, entries[0].item.progress);
+
+          // Second routine should be running.
+          assertEquals(routines[1], entries[1].item.routine);
+          assertEquals(ExecutionProgress.kRunning, entries[1].item.progress);
+
+          // Resolve the running test.
+          return routineController.resolveRoutineForTesting();
+        })
+        .then(() => {
+          return flushTasks();
+        })
+        .then(() => {
+          const entries = getEntries();
+          assertEquals(routines.length, entries.length);
+
+          // First routine should be completed.
+          assertEquals(routines[0], entries[0].item.routine);
+          assertEquals(ExecutionProgress.kCompleted, entries[0].item.progress);
+
+          // Second routine should be completed.
+          assertEquals(routines[1], entries[1].item.routine);
+          assertEquals(ExecutionProgress.kCompleted, entries[1].item.progress);
         });
   });
 }
diff --git a/chrome/test/data/webui/cr_elements/cr_toast_test.js b/chrome/test/data/webui/cr_elements/cr_toast_test.js
index e80c797..9828ef83 100644
--- a/chrome/test/data/webui/cr_elements/cr_toast_test.js
+++ b/chrome/test/data/webui/cr_elements/cr_toast_test.js
@@ -48,20 +48,10 @@
     assertFalse(toast.open);
   });
 
-  test('auto hide with (open = true)', function() {
-    const duration = 100;
-    toast.duration = duration;
-
-    toast.open = true;
-
-    mockTimer.tick(duration);
-    assertFalse(toast.open);
-  });
-
   test('show() clears auto-hide', function() {
     const duration = 70;
     toast.duration = duration;
-    toast.open = true;
+    toast.show();
     mockTimer.tick(duration - 1);
     toast.show();
 
@@ -77,20 +67,11 @@
     assertFalse(toast.open);
   });
 
-  test('(open = true) does not clear auto-hide', function() {
-    const duration = 70;
-    toast.duration = duration;
-    toast.open = true;
-    mockTimer.tick(duration - 1);
-    toast.open = true;
-    mockTimer.tick(1);
-    assertFalse(toast.open);
-  });
 
   test('clearing duration clears timeout', function() {
     const nonZeroDuration = 30;
     toast.duration = nonZeroDuration;
-    toast.open = true;
+    toast.show();
     assertTrue(toast.open);
 
     const zeroDuration = 0;
@@ -115,7 +96,7 @@
   test('setting duration clears auto-hide', function() {
     const oldDuration = 30;
     toast.duration = oldDuration;
-    toast.open = true;
+    toast.show();
 
     mockTimer.tick(oldDuration - 1);
     assertTrue(toast.open);
diff --git a/chrome/test/data/webui/downloads/downloads_browsertest.js b/chrome/test/data/webui/downloads/downloads_browsertest.js
index a45b661..f76c8551 100644
--- a/chrome/test/data/webui/downloads/downloads_browsertest.js
+++ b/chrome/test/data/webui/downloads/downloads_browsertest.js
@@ -69,7 +69,8 @@
   }
 };
 
-TEST_F('DownloadsUrlTest', 'All', function() {
+TEST_F('DownloadsUrlTest', 'All', async function() {
+  await import('chrome://test/mojo_webui_test_support.js');
   suite('loading a nonexistent URL of /a/b/', function() {
     test('should load main page with no console errors', function() {
       return customElements.whenDefined('downloads-manager').then(() => {
diff --git a/chrome/test/data/webui/downloads/item_tests.js b/chrome/test/data/webui/downloads/item_tests.js
index 1881485..fe49fef1 100644
--- a/chrome/test/data/webui/downloads/item_tests.js
+++ b/chrome/test/data/webui/downloads/item_tests.js
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import '../mojo_webui_test_support.js';
+
 import {BrowserProxy, DangerType, IconLoader, States} from 'chrome://downloads/downloads.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {createDownload, TestDownloadsProxy, TestIconLoader} from 'chrome://test/downloads/test_support.js';
diff --git a/chrome/test/data/webui/downloads/manager_tests.js b/chrome/test/data/webui/downloads/manager_tests.js
index 61f76db..3a0de84 100644
--- a/chrome/test/data/webui/downloads/manager_tests.js
+++ b/chrome/test/data/webui/downloads/manager_tests.js
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import '../mojo_webui_test_support.js';
+
 import {BrowserProxy, DangerType, States} from 'chrome://downloads/downloads.js';
 import {isMac} from 'chrome://resources/js/cr.m.js';
 import {keyDownOn} from 'chrome://resources/polymer/v3_0/iron-test-helpers/mock-interactions.js';
diff --git a/chrome/test/data/webui/downloads/search_service_test.js b/chrome/test/data/webui/downloads/search_service_test.js
index 5b21eee6..41c632e 100644
--- a/chrome/test/data/webui/downloads/search_service_test.js
+++ b/chrome/test/data/webui/downloads/search_service_test.js
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import '../mojo_webui_test_support.js';
+
 import {BrowserProxy, SearchService} from 'chrome://downloads/downloads.js';
 import {TestDownloadsProxy} from 'chrome://test/downloads/test_support.js';
 
diff --git a/chrome/test/data/webui/downloads/test_support.js b/chrome/test/data/webui/downloads/test_support.js
index 64fa94c..03321df 100644
--- a/chrome/test/data/webui/downloads/test_support.js
+++ b/chrome/test/data/webui/downloads/test_support.js
@@ -2,29 +2,29 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {DangerType, States} from 'chrome://downloads/downloads.js';
+import {DangerType, PageCallbackRouter, PageHandlerInterface, PageInterface, PageRemote, States} from 'chrome://downloads/downloads.js';
 
 import {TestBrowserProxy} from 'chrome://test/test_browser_proxy.m.js';
 
 export class TestDownloadsProxy {
   constructor() {
-    /** @type {downloads.mojom.PageCallbackRouter} */
-    this.callbackRouter = new downloads.mojom.PageCallbackRouter();
+    /** @type {PageCallbackRouter} */
+    this.callbackRouter = new PageCallbackRouter();
 
-    /** @type {!downloads.mojom.PageRemote} */
+    /** @type {!PageRemote} */
     this.callbackRouterRemote =
         this.callbackRouter.$.bindNewPipeAndPassRemote();
 
-    /** @type {downloads.mojom.PageHandlerInterface} */
+    /** @type {PageHandlerInterface} */
     this.handler = new FakePageHandler(this.callbackRouterRemote);
   }
 }
 
-/** @implements {downloads.mojom.PageHandlerInterface} */
+/** @implements {PageHandlerInterface} */
 class FakePageHandler {
-  /** @param {downloads.mojom.PageInterface} */
+  /** @param {PageInterface} */
   constructor(callbackRouterRemote) {
-    /** @private {downloads.mojom.PageInterface} */
+    /** @private {PageInterface} */
     this.callbackRouterRemote_ = callbackRouterRemote;
 
     /** @private {TestBrowserProxy} */
diff --git a/chrome/test/data/webui/downloads/toolbar_tests.js b/chrome/test/data/webui/downloads/toolbar_tests.js
index e9dab19..b2ff8ab 100644
--- a/chrome/test/data/webui/downloads/toolbar_tests.js
+++ b/chrome/test/data/webui/downloads/toolbar_tests.js
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import '../mojo_webui_test_support.js';
+
 import {SearchService} from 'chrome://downloads/downloads.js';
 import {createDownload} from 'chrome://test/downloads/test_support.js';
 
diff --git a/chrome/test/data/webui/js/cr/ui/BUILD.gn b/chrome/test/data/webui/js/cr/ui/BUILD.gn
index aaa92afd..edcc4ea8 100644
--- a/chrome/test/data/webui/js/cr/ui/BUILD.gn
+++ b/chrome/test/data/webui/js/cr/ui/BUILD.gn
@@ -29,7 +29,10 @@
   sources = [
     "$root_gen_dir/chrome/test/data/webui/js/cr/ui/array_data_model_test.m.js",
   ]
-  deps = [ "//ui/webui/resources/js/cr/ui:array_data_model.m" ]
+  deps = [
+    "../../..:chai_assert",
+    "//ui/webui/resources/js/cr/ui:array_data_model.m",
+  ]
   extra_deps = [ ":modulize" ]
 }
 
@@ -44,12 +47,79 @@
   extra_deps = [ ":modulize" ]
 }
 
+js_library("context_menu_handler_test.m") {
+  sources = [ "$root_gen_dir/chrome/test/data/webui/js/cr/ui/context_menu_handler_test.m.js" ]
+  deps = [
+    "../../..:chai_assert",
+    "//ui/webui/resources/js/cr:ui.m",
+    "//ui/webui/resources/js/cr/ui:context_menu_handler.m",
+    "//ui/webui/resources/js/cr/ui:menu.m",
+  ]
+  extra_deps = [ ":modulize" ]
+}
+
+js_library("grid_test.m") {
+  sources = [ "$root_gen_dir/chrome/test/data/webui/js/cr/ui/grid_test.m.js" ]
+  deps = [
+    "../../..:chai_assert",
+    "//ui/webui/resources/js/cr/ui:array_data_model.m",
+    "//ui/webui/resources/js/cr/ui:grid.m",
+  ]
+  extra_deps = [ ":modulize" ]
+}
+
+js_library("list_selection_model_test.m") {
+  sources = [ "$root_gen_dir/chrome/test/data/webui/js/cr/ui/list_selection_model_test.m.js" ]
+  deps = [
+    ":list_selection_model_test_util.m",
+    "../../..:chai_assert",
+    "//ui/webui/resources/js/cr/ui:list_selection_model.m",
+  ]
+  extra_deps = [ ":modulize" ]
+}
+
+js_library("list_selection_model_test_util.m") {
+  sources = [ "$root_gen_dir/chrome/test/data/webui/js/cr/ui/list_selection_model_test_util.m.js" ]
+  deps = [
+    "../../..:chai_assert",
+    "//ui/webui/resources/js/cr/ui:list_selection_model.m",
+    "//ui/webui/resources/js/cr/ui:list_single_selection_model.m",
+  ]
+  extra_deps = [ ":modulize" ]
+}
+
+js_library("list_single_selection_model_test.m") {
+  sources = [ "$root_gen_dir/chrome/test/data/webui/js/cr/ui/list_single_selection_model_test.m.js" ]
+  deps = [
+    ":list_selection_model_test_util.m",
+    "../../..:chai_assert",
+    "//ui/webui/resources/js/cr/ui:list_single_selection_model.m",
+  ]
+  extra_deps = [ ":modulize" ]
+}
+
+js_library("list_test.m") {
+  sources = [ "$root_gen_dir/chrome/test/data/webui/js/cr/ui/list_test.m.js" ]
+  deps = [
+    "../../..:chai_assert",
+    "//ui/webui/resources/js/cr:ui.m",
+    "//ui/webui/resources/js/cr/ui:array_data_model.m",
+    "//ui/webui/resources/js/cr/ui:list.m",
+  ]
+  extra_deps = [ ":modulize" ]
+}
+
 js_type_check("closure_compile") {
   uses_js_modules = true
 
   deps = [
     ":array_data_model_test.m",
     ":command_test.m",
-    "../../..:chai_assert",
+    ":context_menu_handler_test.m",
+    ":grid_test.m",
+    ":list_selection_model_test.m",
+    ":list_selection_model_test_util.m",
+    ":list_single_selection_model_test.m",
+    ":list_test.m",
   ]
 }
diff --git a/chrome/test/data/webui/js/cr/ui/context_menu_handler_test.js b/chrome/test/data/webui/js/cr/ui/context_menu_handler_test.js
index aa78e007..a3b9487 100644
--- a/chrome/test/data/webui/js/cr/ui/context_menu_handler_test.js
+++ b/chrome/test/data/webui/js/cr/ui/context_menu_handler_test.js
@@ -3,12 +3,13 @@
 // found in the LICENSE file.
 
 // clang-format off
+// #import {assertEquals} from '../../../chai_assert.js';
 // #import {contextMenuHandler} from 'chrome://resources/js/cr/ui/context_menu_handler.m.js';
 // #import {decorate} from 'chrome://resources/js/cr/ui.m.js';
 // #import {Menu} from 'chrome://resources/js/cr/ui/menu.m.js';
 // clang-format on
 
-/* #export */ function testShowAndHideEvents() {
+function testShowAndHideEvents() {
   // Keep original Date.now not to affect other code.
   var originalDateNow = Date.now;
 
@@ -71,4 +72,6 @@
   Date.now = originalDateNow;
 }
 
-window.testShowAndHideEvents = testShowAndHideEvents;
+Object.assign(window, {
+  testShowAndHideEvents,
+});
diff --git a/chrome/test/data/webui/js/cr/ui/grid_test.js b/chrome/test/data/webui/js/cr/ui/grid_test.js
index 656f592..66c59b96 100644
--- a/chrome/test/data/webui/js/cr/ui/grid_test.js
+++ b/chrome/test/data/webui/js/cr/ui/grid_test.js
@@ -3,11 +3,15 @@
 // found in the LICENSE file.
 
 // clang-format off
+// #import {assertEquals} from '../../../chai_assert.js';
 // #import {Grid} from 'chrome://resources/js/cr/ui/grid.m.js';
 // #import {ArrayDataModel} from 'chrome://resources/js/cr/ui/array_data_model.m.js';
 // clang-format on
 
-/* #export */ function testGetColumnCount() {
+/**
+ * @suppress {visibility} Allow test to reach to private properties.
+ */
+function testGetColumnCount() {
   var g = cr.ui.Grid.prototype;
   g.measured_ = {
     height: 8,
@@ -84,4 +88,6 @@
   assertEquals(1, columns);
 }
 
-window.testGetColumnCount = testGetColumnCount;
+Object.assign(window, {
+  testGetColumnCount,
+});
diff --git a/chrome/test/data/webui/js/cr/ui/list_selection_model_test.js b/chrome/test/data/webui/js/cr/ui/list_selection_model_test.js
index 6de9562..1b923fc 100644
--- a/chrome/test/data/webui/js/cr/ui/list_selection_model_test.js
+++ b/chrome/test/data/webui/js/cr/ui/list_selection_model_test.js
@@ -3,17 +3,24 @@
 // found in the LICENSE file.
 
 // clang-format off
+// #import {assertEquals, assertArrayEquals} from '../../../chai_assert.js';
 // #import {ListSelectionModel} from 'chrome://resources/js/cr/ui/list_selection_model.m.js';
 // #import {adjust, range} from './list_selection_model_test_util.m.js';
 // clang-format on
 
-function createSelectionModel(len, opt_dependentLeadItem) {
+/**
+ * @param {number} len size of the selection model.
+ * @param {boolean=} dependentLeadItem inverse value for `independentLeadItem_`
+ *     defaults to true.
+ * @return {!cr.ui.ListSelectionModel}
+ */
+function createSelectionModel(len, dependentLeadItem) {
   var sm = new cr.ui.ListSelectionModel(len);
-  sm.independentLeadItem_ = !opt_dependentLeadItem;
+  sm.independentLeadItem_ = !dependentLeadItem;
   return sm;
 }
 
-/* #export */ function testAdjust1() {
+function testAdjust1() {
   var sm = createSelectionModel(200);
 
   sm.leadIndex = sm.anchorIndex = sm.selectedIndex = 100;
@@ -24,7 +31,7 @@
   assertEquals(90, sm.selectedIndex);
 }
 
-/* #export */ function testAdjust2() {
+function testAdjust2() {
   var sm = createSelectionModel(200);
 
   sm.leadIndex = sm.anchorIndex = sm.selectedIndex = 50;
@@ -35,7 +42,7 @@
   assertEquals(50, sm.selectedIndex);
 }
 
-/* #export */ function testAdjust3() {
+function testAdjust3() {
   var sm = createSelectionModel(200);
 
   sm.leadIndex = sm.anchorIndex = sm.selectedIndex = 100;
@@ -46,7 +53,7 @@
   assertEquals(110, sm.selectedIndex);
 }
 
-/* #export */ function testAdjust4() {
+function testAdjust4() {
   var sm = createSelectionModel(200);
 
   sm.leadIndex = sm.anchorIndex = 100;
@@ -59,7 +66,7 @@
   assertArrayEquals(range(95, 105), sm.selectedIndexes);
 }
 
-/* #export */ function testAdjust5() {
+function testAdjust5() {
   var sm = createSelectionModel(100);
 
   sm.leadIndex = sm.anchorIndex = sm.selectedIndex = 99;
@@ -71,7 +78,7 @@
   assertArrayEquals([98], sm.selectedIndexes);
 }
 
-/* #export */ function testAdjust6() {
+function testAdjust6() {
   var sm = createSelectionModel(200);
 
   sm.leadIndex = sm.anchorIndex = 105;
@@ -85,7 +92,7 @@
   assertArrayEquals(range(100, 105), sm.selectedIndexes);
 }
 
-/* #export */ function testAdjust7() {
+function testAdjust7() {
   var sm = createSelectionModel(1);
 
   sm.leadIndex = sm.anchorIndex = sm.selectedIndex = 0;
@@ -97,7 +104,7 @@
   assertArrayEquals([10], sm.selectedIndexes);
 }
 
-/* #export */ function testAdjust8() {
+function testAdjust8() {
   var sm = createSelectionModel(100);
 
   sm.leadIndex = sm.anchorIndex = 50;
@@ -110,7 +117,7 @@
   assertArrayEquals(range(0, 19), sm.selectedIndexes);
 }
 
-/* #export */ function testAdjust9() {
+function testAdjust9() {
   var sm = createSelectionModel(10);
 
   sm.leadIndex = sm.anchorIndex = 5;
@@ -124,7 +131,7 @@
   assertArrayEquals([], sm.selectedIndexes);
 }
 
-/* #export */ function testAdjust10() {
+function testAdjust10() {
   var sm = createSelectionModel(10);
 
   sm.leadIndex = sm.anchorIndex = 5;
@@ -137,7 +144,7 @@
   assertArrayEquals([5], sm.selectedIndexes);
 }
 
-/* #export */ function testAdjust11() {
+function testAdjust11() {
   var sm = createSelectionModel(20);
 
   sm.leadIndex = sm.anchorIndex = 10;
@@ -150,7 +157,7 @@
   assertArrayEquals(range(0, 4), sm.selectedIndexes);
 }
 
-/* #export */ function testAdjust12() {
+function testAdjust12() {
   var sm = createSelectionModel(20, true);
 
   sm.selectAll();
@@ -163,7 +170,7 @@
   assertArrayEquals(range(0, 4), sm.selectedIndexes);
 }
 
-/* #export */ function testAdjust13() {
+function testAdjust13() {
   var sm = createSelectionModel(20, true);
 
   sm.selectAll();
@@ -176,7 +183,7 @@
   assertArrayEquals(range(0, 14), sm.selectedIndexes);
 }
 
-/* #export */ function testAdjust14() {
+function testAdjust14() {
   var sm = createSelectionModel(5, true);
 
   sm.selectedIndexes = [2, 3];
@@ -189,7 +196,7 @@
   assertArrayEquals(range(2, 2), sm.selectedIndexes);
 }
 
-/* #export */ function testAdjust15() {
+function testAdjust15() {
   var sm = createSelectionModel(7, true);
 
   sm.selectedIndexes = [1, 3, 5];
@@ -204,7 +211,7 @@
   assertArrayEquals(range(3, 3), sm.selectedIndexes);
 }
 
-/* #export */ function testAdjust16() {
+function testAdjust16() {
   var sm = createSelectionModel(7, true);
 
   sm.selectedIndexes = [1, 3, 5];
@@ -219,7 +226,7 @@
   assertArrayEquals(range(3, 3), sm.selectedIndexes);
 }
 
-/* #export */ function testAdjust17() {
+function testAdjust17() {
   var sm = createSelectionModel(7, true);
 
   sm.selectedIndexes = [1, 3, 5];
@@ -234,7 +241,7 @@
   assertArrayEquals(range(3, 3), sm.selectedIndexes);
 }
 
-/* #export */ function testLeadAndAnchor1() {
+function testLeadAndAnchor1() {
   var sm = createSelectionModel(20, true);
 
   sm.selectAll();
@@ -244,7 +251,7 @@
   assertEquals(10, sm.anchorIndex, 'anchor');
 }
 
-/* #export */ function testLeadAndAnchor2() {
+function testLeadAndAnchor2() {
   var sm = createSelectionModel(20, true);
 
   sm.leadIndex = sm.anchorIndex = 10;
@@ -254,7 +261,7 @@
   assertEquals(0, sm.anchorIndex, 'anchor');
 }
 
-/* #export */ function testSelectAll() {
+function testSelectAll() {
   var sm = createSelectionModel(10);
 
   var changes = null;
@@ -270,7 +277,7 @@
   }));
 }
 
-/* #export */ function testSelectAllOnEmptyList() {
+function testSelectAllOnEmptyList() {
   var sm = createSelectionModel(0);
 
   var changes = null;
diff --git a/chrome/test/data/webui/js/cr/ui/list_selection_model_test_util.js b/chrome/test/data/webui/js/cr/ui/list_selection_model_test_util.js
index 1a18080..659c682f 100644
--- a/chrome/test/data/webui/js/cr/ui/list_selection_model_test_util.js
+++ b/chrome/test/data/webui/js/cr/ui/list_selection_model_test_util.js
@@ -2,6 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {ListSelectionModel} from 'chrome://resources/js/cr/ui/list_selection_model.m.js';
+// #import {ListSingleSelectionModel} from 'chrome://resources/js/cr/ui/list_single_selection_model.m.js';
+// clang-format on
+
 /**
  * Creates an array spanning a range of integer values.
  * @param {number} start The first number in the range.
@@ -18,7 +23,8 @@
 
 /**
  * Modifies a selection model.
- * @param {!ListSelectionModel} model The selection model to adjust.
+ * @param {!cr.ui.ListSelectionModel|!cr.ui.ListSingleSelectionModel} model The
+ * selection model to adjust.
  * @param {number} index Starting index of the edit.
  * @param {number} removed Number of entries to remove from the list.
  * @param {number} added Number of entries to add to the list.
diff --git a/chrome/test/data/webui/js/cr/ui/list_single_selection_model_test.js b/chrome/test/data/webui/js/cr/ui/list_single_selection_model_test.js
index 40f5d96a..6c966d4 100644
--- a/chrome/test/data/webui/js/cr/ui/list_single_selection_model_test.js
+++ b/chrome/test/data/webui/js/cr/ui/list_single_selection_model_test.js
@@ -3,13 +3,20 @@
 // found in the LICENSE file.
 
 // clang-format off
+// #import {assertEquals, assertArrayEquals} from '../../../chai_assert.js';
 // #import {ListSingleSelectionModel} from 'chrome://resources/js/cr/ui/list_single_selection_model.m.js';
 // #import {adjust} from './list_selection_model_test_util.m.js';
 // clang-format on
 
-function createSelectionModel(len, opt_dependentLeadItem) {
+/**
+ * @param {number} len size of the selection model.
+ * @param {boolean=} dependentLeadItem inverse value for `independentLeadItem_`
+ *     defaults to true.
+ * @return {!cr.ui.ListSingleSelectionModel}
+ */
+function createSelectionModel(len, dependentLeadItem) {
   var sm = new cr.ui.ListSingleSelectionModel(len);
-  sm.independentLeadItem_ = !opt_dependentLeadItem;
+  sm.independentLeadItem_ = !dependentLeadItem;
   return sm;
 }
 
diff --git a/chrome/test/data/webui/js/cr/ui/list_test.js b/chrome/test/data/webui/js/cr/ui/list_test.js
index f941e2e..72942cf0 100644
--- a/chrome/test/data/webui/js/cr/ui/list_test.js
+++ b/chrome/test/data/webui/js/cr/ui/list_test.js
@@ -3,16 +3,18 @@
 // found in the LICENSE file.
 
 // clang-format off
+// #import {assertEquals} from '../../../chai_assert.js';
 // #import {List} from 'chrome://resources/js/cr/ui/list.m.js';
+// #import {decorate} from 'chrome://resources/js/cr/ui.m.js';
 // #import {ArrayDataModel} from 'chrome://resources/js/cr/ui/array_data_model.m.js';
 // clang-format on
 
-/* #export */ function testClearPinnedItem() {
+function testClearPinnedItem() {
   var list = document.createElement('ul');
   list.style.position = 'absolute';
   list.style.width = '800px';
   list.style.height = '800px';
-  cr.ui.List.decorate(list);
+  cr.ui.decorate(list, cr.ui.List);
   document.body.appendChild(list);
 
   var model = new cr.ui.ArrayDataModel(['Item A', 'Item B']);
@@ -29,12 +31,12 @@
   assertEquals('Item B', list.querySelectorAll('li')[0].textContent);
 }
 
-/* #export */ function testClickOutsideListItem() {
+function testClickOutsideListItem() {
   const list = document.createElement('ul');
   list.style.position = 'absolute';
   list.style.width = '800px';
   list.style.height = '800px';
-  cr.ui.List.decorate(list);
+  cr.ui.decorate(list, cr.ui.List);
   document.body.appendChild(list);
 
   // Add a header inside the list.
@@ -63,5 +65,7 @@
   assertEquals(item, list.getListItemAncestor(span));
 }
 
-window.testClearPinnedItem = testClearPinnedItem;
-window.testClickOutsideListItem = testClickOutsideListItem;
+Object.assign(window, {
+  testClearPinnedItem,
+  testClickOutsideListItem,
+});
diff --git a/chrome/test/data/webui/settings/cr_settings_v3_browsertest.js b/chrome/test/data/webui/settings/cr_settings_v3_browsertest.js
index 9df479f..27123b2b 100644
--- a/chrome/test/data/webui/settings/cr_settings_v3_browsertest.js
+++ b/chrome/test/data/webui/settings/cr_settings_v3_browsertest.js
@@ -264,9 +264,16 @@
   }
 };
 
-TEST_F('CrSettingsPasswordsCheckV3Test', 'All', function() {
+// Flaky on Mac builds https://crbug.com/1143801
+GEN('#if defined(OS_MAC)');
+GEN('#define MAYBE_All DISABLED_All');
+GEN('#else');
+GEN('#define MAYBE_All All');
+GEN('#endif');
+TEST_F('CrSettingsPasswordsCheckV3Test', 'MAYBE_All', function() {
   mocha.run();
 });
+GEN('#undef MAYBE_All');
 
 // eslint-disable-next-line no-var
 var CrSettingsSafetyCheckPageV3Test = class extends CrSettingsV3BrowserTest {
diff --git a/chrome/test/data/webui/tab_search_merge/BUILD.gn b/chrome/test/data/webui/tab_search/BUILD.gn
similarity index 81%
rename from chrome/test/data/webui/tab_search_merge/BUILD.gn
rename to chrome/test/data/webui/tab_search/BUILD.gn
index 917b099..f7531bd 100644
--- a/chrome/test/data/webui/tab_search_merge/BUILD.gn
+++ b/chrome/test/data/webui/tab_search/BUILD.gn
@@ -7,7 +7,7 @@
 js_type_check("closure_compile") {
   is_polymer3 = true
   closure_flags = default_closure_args + [
-                    "browser_resolver_prefix_replacements=\"chrome://tab-search/=../../chrome/browser/resources/tab-search-merge/\"",
+                    "browser_resolver_prefix_replacements=\"chrome://tab-search/=../../chrome/browser/resources/tab-search/\"",
                     "js_module_root=../../chrome/test/data/webui/",
                     "js_module_root=./gen/chrome/test/data/webui/",
                   ]
@@ -31,7 +31,7 @@
   deps = [
     ":test_tab_search_api_proxy",
     "..:chai_assert",
-    "//chrome/browser/resources/tab_search_merge:app",
+    "//chrome/browser/resources/tab_search:app",
   ]
   externs_list = [ "$externs_path/mocha-2.5.js" ]
 }
@@ -40,7 +40,7 @@
   deps = [
     ":test_tab_search_api_proxy",
     "..:chai_assert",
-    "//chrome/browser/resources/tab_search_merge:app",
+    "//chrome/browser/resources/tab_search:app",
   ]
   externs_list = [ "$externs_path/mocha-2.5.js" ]
 }
@@ -48,7 +48,7 @@
 js_library("test_tab_search_api_proxy") {
   deps = [
     "..:test_browser_proxy.m",
-    "//chrome/browser/resources/tab_search_merge:tab_search_api_proxy",
+    "//chrome/browser/resources/tab_search:tab_search_api_proxy",
     "//chrome/browser/ui/webui/tab_search:mojo_bindings_js_library_for_compile",
   ]
 }
@@ -56,7 +56,7 @@
 js_library("tab_search_item_test") {
   deps = [
     "..:chai_assert",
-    "//chrome/browser/resources/tab_search_merge:tab_search_item",
+    "//chrome/browser/resources/tab_search:tab_search_item",
   ]
   externs_list = [ "$externs_path/mocha-2.5.js" ]
 }
diff --git a/chrome/test/data/webui/tab_search_merge/fuzzy_search_test.js b/chrome/test/data/webui/tab_search/fuzzy_search_test.js
similarity index 100%
rename from chrome/test/data/webui/tab_search_merge/fuzzy_search_test.js
rename to chrome/test/data/webui/tab_search/fuzzy_search_test.js
diff --git a/chrome/test/data/webui/tab_search_merge/tab_search_app_focus_test.js b/chrome/test/data/webui/tab_search/tab_search_app_focus_test.js
similarity index 100%
rename from chrome/test/data/webui/tab_search_merge/tab_search_app_focus_test.js
rename to chrome/test/data/webui/tab_search/tab_search_app_focus_test.js
diff --git a/chrome/test/data/webui/tab_search_merge/tab_search_app_test.js b/chrome/test/data/webui/tab_search/tab_search_app_test.js
similarity index 100%
rename from chrome/test/data/webui/tab_search_merge/tab_search_app_test.js
rename to chrome/test/data/webui/tab_search/tab_search_app_test.js
diff --git a/chrome/test/data/webui/tab_search_merge/tab_search_browsertest.js b/chrome/test/data/webui/tab_search/tab_search_browsertest.js
similarity index 94%
rename from chrome/test/data/webui/tab_search_merge/tab_search_browsertest.js
rename to chrome/test/data/webui/tab_search/tab_search_browsertest.js
index 0c54d86..3c53a61 100644
--- a/chrome/test/data/webui/tab_search_merge/tab_search_browsertest.js
+++ b/chrome/test/data/webui/tab_search/tab_search_browsertest.js
@@ -36,7 +36,7 @@
 var TabSearchAppTest = class extends TabSearchBrowserTest {
   /** @override */
   get browsePreload() {
-    return 'chrome://tab-search/test_loader.html?module=tab_search_merge/tab_search_app_test.js';
+    return 'chrome://tab-search/test_loader.html?module=tab_search/tab_search_app_test.js';
   }
 };
 
@@ -48,7 +48,7 @@
 var FuzzySearchTest = class extends TabSearchBrowserTest {
   /** @override */
   get browsePreload() {
-    return 'chrome://tab-search/test_loader.html?module=tab_search_merge/fuzzy_search_test.js';
+    return 'chrome://tab-search/test_loader.html?module=tab_search/fuzzy_search_test.js';
   }
 };
 
@@ -60,7 +60,7 @@
 var TabSearchItemTest = class extends TabSearchBrowserTest {
   /** @override */
   get browsePreload() {
-    return 'chrome://tab-search/test_loader.html?module=tab_search_merge/tab_search_item_test.js';
+    return 'chrome://tab-search/test_loader.html?module=tab_search/tab_search_item_test.js';
   }
 };
 
diff --git a/chrome/test/data/webui/tab_search_merge/tab_search_interactive_ui_tests.js b/chrome/test/data/webui/tab_search/tab_search_interactive_ui_tests.js
similarity index 96%
rename from chrome/test/data/webui/tab_search_merge/tab_search_interactive_ui_tests.js
rename to chrome/test/data/webui/tab_search/tab_search_interactive_ui_tests.js
index 24d7dc38..bb7b7b9 100644
--- a/chrome/test/data/webui/tab_search_merge/tab_search_interactive_ui_tests.js
+++ b/chrome/test/data/webui/tab_search/tab_search_interactive_ui_tests.js
@@ -14,7 +14,7 @@
 var TabSearchInteractiveUITest = class extends PolymerInteractiveUITest {
   /** @override */
   get browsePreload() {
-    return 'chrome://tab-search/test_loader.html?module=tab_search_merge/tab_search_app_focus_test.js';
+    return 'chrome://tab-search/test_loader.html?module=tab_search/tab_search_app_focus_test.js';
   }
 
   get extraLibraries() {
diff --git a/chrome/test/data/webui/tab_search_merge/tab_search_item_test.js b/chrome/test/data/webui/tab_search/tab_search_item_test.js
similarity index 100%
rename from chrome/test/data/webui/tab_search_merge/tab_search_item_test.js
rename to chrome/test/data/webui/tab_search/tab_search_item_test.js
diff --git a/chrome/test/data/webui/tab_search_merge/tab_search_test_data.js b/chrome/test/data/webui/tab_search/tab_search_test_data.js
similarity index 100%
rename from chrome/test/data/webui/tab_search_merge/tab_search_test_data.js
rename to chrome/test/data/webui/tab_search/tab_search_test_data.js
diff --git a/chrome/test/data/webui/tab_search_merge/tab_search_test_helper.js b/chrome/test/data/webui/tab_search/tab_search_test_helper.js
similarity index 100%
rename from chrome/test/data/webui/tab_search_merge/tab_search_test_helper.js
rename to chrome/test/data/webui/tab_search/tab_search_test_helper.js
diff --git a/chrome/test/data/webui/tab_search_merge/test_tab_search_api_proxy.js b/chrome/test/data/webui/tab_search/test_tab_search_api_proxy.js
similarity index 100%
rename from chrome/test/data/webui/tab_search_merge/test_tab_search_api_proxy.js
rename to chrome/test/data/webui/tab_search/test_tab_search_api_proxy.js
diff --git a/chromecast/media/audio/wav_header.h b/chromecast/media/audio/wav_header.h
index ffcfd3e..5a17adb 100644
--- a/chromecast/media/audio/wav_header.h
+++ b/chromecast/media/audio/wav_header.h
@@ -76,7 +76,7 @@
   }
 };
 
-WavHeader::WavHeader() = default;
+inline WavHeader::WavHeader() = default;
 
 }  // namespace media
 }  // namespace chromecast
diff --git a/chromeos/components/camera_app_ui/DEPS b/chromeos/components/camera_app_ui/DEPS
index 944c321..1f4dcc934 100644
--- a/chromeos/components/camera_app_ui/DEPS
+++ b/chromeos/components/camera_app_ui/DEPS
@@ -3,6 +3,7 @@
   "+components/arc/intent_helper",
   "+components/content_settings/core/common",
   "+content/public/browser",
+  "+content/public/common",
   "+media/capture/video/chromeos",
   "+ui/aura",
   "+ui/display",
diff --git a/chromeos/components/camera_app_ui/camera_app_ui.cc b/chromeos/components/camera_app_ui/camera_app_ui.cc
index 2b3f2fe..cfddc3e 100644
--- a/chromeos/components/camera_app_ui/camera_app_ui.cc
+++ b/chromeos/components/camera_app_ui/camera_app_ui.cc
@@ -18,6 +18,7 @@
 #include "content/public/browser/video_capture_service.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_ui_data_source.h"
+#include "content/public/common/url_constants.h"
 #include "media/capture/video/chromeos/camera_app_device_provider_impl.h"
 #include "media/capture/video/chromeos/mojom/camera_app.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -58,10 +59,25 @@
   source->OverrideContentSecurityPolicy(
       network::mojom::CSPDirectiveName::WorkerSrc,
       std::string("worker-src 'self';"));
+  source->OverrideContentSecurityPolicy(
+      network::mojom::CSPDirectiveName::ChildSrc,
+      std::string("frame-src ") + kChromeUIUntrustedCameraAppURL + ";");
 
   return source;
 }
 
+content::WebUIDataSource* CreateUntrustedCameraAppUIHTMLSource() {
+  content::WebUIDataSource* untrusted_source =
+      content::WebUIDataSource::Create(kChromeUIUntrustedCameraAppURL);
+  for (size_t i = 0; i < kChromeosCameraAppResourcesSize; i++) {
+    untrusted_source->AddResourcePath(kChromeosCameraAppResources[i].name,
+                                      kChromeosCameraAppResources[i].value);
+  }
+  untrusted_source->AddFrameAncestor(GURL(kChromeUICameraAppURL));
+
+  return untrusted_source;
+}
+
 // Translates the renderer-side source ID to video device id.
 void TranslateVideoDeviceId(
     const std::string& salt,
@@ -182,9 +198,13 @@
   delegate_->SetLaunchDirectory();
 
   // Set up the data source.
-  content::WebUIDataSource* source =
-      CreateCameraAppUIHTMLSource(delegate_.get());
-  content::WebUIDataSource::Add(browser_context, source);
+  content::WebUIDataSource::Add(browser_context,
+                                CreateCameraAppUIHTMLSource(delegate_.get()));
+  content::WebUIDataSource::Add(browser_context,
+                                CreateUntrustedCameraAppUIHTMLSource());
+
+  // Add ability to request chrome-untrusted: URLs
+  web_ui->AddRequestableScheme(content::kChromeUIUntrustedScheme);
 }
 
 CameraAppUI::~CameraAppUI() = default;
diff --git a/chromeos/components/camera_app_ui/resources/BUILD.gn b/chromeos/components/camera_app_ui/resources/BUILD.gn
index f13631f7..f121c6b 100644
--- a/chromeos/components/camera_app_ui/resources/BUILD.gn
+++ b/chromeos/components/camera_app_ui/resources/BUILD.gn
@@ -103,6 +103,7 @@
     "js/background_ops.js",
     "js/chrome_util.js",
     "js/dom.js",
+    "js/dynamic_import.js",
     "js/error.js",
     "js/gallerybutton.js",
     "js/init.js",
@@ -117,6 +118,7 @@
     "js/toast.js",
     "js/tooltip.js",
     "js/type.js",
+    "js/untrusted_script_loader.js",
     "js/util.js",
     "js/waitable_event.js",
   ]
@@ -250,6 +252,7 @@
   sources = [
     "views/background.html",
     "views/main.html",
+    "views/untrusted_script_loader.html",
   ]
 
   outputs = [ "$out_camera_app_dir/views/{{source_file_part}}" ]
diff --git a/chromeos/components/camera_app_ui/resources/camera_app_resources.grd b/chromeos/components/camera_app_ui/resources/camera_app_resources.grd
index 522b7a0..58ff9e4 100644
--- a/chromeos/components/camera_app_ui/resources/camera_app_resources.grd
+++ b/chromeos/components/camera_app_ui/resources/camera_app_resources.grd
@@ -31,6 +31,7 @@
       <structure name="IDR_CAMERA_DEVICE_OPERATOR_JS" file="js/mojo/device_operator.js" type="chrome_html" />
       <structure name="IDR_CAMERA_DIALOG_JS" file="js/views/dialog.js" type="chrome_html" />
       <structure name="IDR_CAMERA_DOM_JS" file="js/dom.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_DYNAMIC_IMPORT_JS" file="js/dynamic_import.js" type="chrome_html" />
       <structure name="IDR_CAMERA_ERROR_JS" file="js/error.js" type="chrome_html" />
       <structure name="IDR_CAMERA_FILE_NAMER_JS" file="js/models/file_namer.js" type="chrome_html" />
       <structure name="IDR_CAMERA_FILE_SYSTEM_ENTRY_JS" file="js/models/file_system_entry.js" type="chrome_html" />
@@ -72,6 +73,8 @@
       <structure name="IDR_CAMERA_TOAST_JS" file="js/toast.js" type="chrome_html" />
       <structure name="IDR_CAMERA_TOOLTIP_JS" file="js/tooltip.js" type="chrome_html" />
       <structure name="IDR_CAMERA_TYPE_JS" file="js/type.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_UNTRUSTED_SCRIPT_LOADER_HTML" file="views/untrusted_script_loader.html" type="chrome_html" />
+      <structure name="IDR_CAMERA_UNTRUSTED_SCRIPT_LOADER_JS" file="js/untrusted_script_loader.js" type="chrome_html" />
       <structure name="IDR_CAMERA_UTIL_JS" file="js/util.js" type="chrome_html" />
       <structure name="IDR_CAMERA_VIDEO_SAVER_JS" file="js/models/video_saver.js" type="chrome_html" />
       <structure name="IDR_CAMERA_VIEW_JS" file="js/views/view.js" type="chrome_html" />
diff --git a/chromeos/components/camera_app_ui/resources/js/BUILD.gn b/chromeos/components/camera_app_ui/resources/js/BUILD.gn
index d7518606..98fbd2a 100644
--- a/chromeos/components/camera_app_ui/resources/js/BUILD.gn
+++ b/chromeos/components/camera_app_ui/resources/js/BUILD.gn
@@ -65,6 +65,7 @@
     "toast.js",
     "tooltip.js",
     "type.js",
+    "untrusted_script_loader.js",
     "util.js",
     "views/camera.js",
     "views/camera/layout.js",
diff --git a/chromeos/components/camera_app_ui/resources/js/browser_proxy/browser_proxy.js b/chromeos/components/camera_app_ui/resources/js/browser_proxy/browser_proxy.js
index 7129a90..ca19721 100644
--- a/chromeos/components/camera_app_ui/resources/js/browser_proxy/browser_proxy.js
+++ b/chromeos/components/camera_app_ui/resources/js/browser_proxy/browser_proxy.js
@@ -5,7 +5,7 @@
 import {promisify} from '../chrome_util.js';
 import {ChromeDirectoryEntry} from '../models/chrome_file_system_entry.js';
 import {getMaybeLazyDirectory} from '../models/lazy_directory_entry.js';
-import {Resolution} from '../type.js';
+import {Resolution, UntrustedOrigin} from '../type.js';
 
 // eslint-disable-next-line no-unused-vars
 import {BrowserProxy} from './browser_proxy_interface.js';
@@ -237,6 +237,11 @@
     // For platform app, the start time of window creation is recorded by
     // background page so we don't need to trigger it here.
   }
+
+  /** @override */
+  getUntrustedOrigin() {
+    return UntrustedOrigin.CHROME_EXTENSION;
+  }
 }
 
 export const browserProxy = new ChromeAppBrowserProxy();
diff --git a/chromeos/components/camera_app_ui/resources/js/browser_proxy/browser_proxy_interface.js b/chromeos/components/camera_app_ui/resources/js/browser_proxy/browser_proxy_interface.js
index 077d3dc..5e716abc 100644
--- a/chromeos/components/camera_app_ui/resources/js/browser_proxy/browser_proxy_interface.js
+++ b/chromeos/components/camera_app_ui/resources/js/browser_proxy/browser_proxy_interface.js
@@ -7,6 +7,8 @@
   AbstractFileEntry,        // eslint-disable-line no-unused-vars
   AbstractFileSystemEntry,  // eslint-disable-line no-unused-vars
 } from '../models/file_system_entry.js';
+// eslint-disable-next-line no-unused-vars
+import {UntrustedOrigin} from '../type.js';
 
 /**
  * The abstract interface for the CCA's interaction with the browser.
@@ -138,4 +140,10 @@
    * @abstract
    */
   async setLaunchingFromWindowCreationStartTime(callback) {}
+
+  /**
+   * @return {!UntrustedOrigin}
+   * @abstract
+   */
+  getUntrustedOrigin() {}
 }
diff --git a/chromeos/components/camera_app_ui/resources/js/browser_proxy/webui_browser_proxy.js b/chromeos/components/camera_app_ui/resources/js/browser_proxy/webui_browser_proxy.js
index 113347601..894de70 100644
--- a/chromeos/components/camera_app_ui/resources/js/browser_proxy/webui_browser_proxy.js
+++ b/chromeos/components/camera_app_ui/resources/js/browser_proxy/webui_browser_proxy.js
@@ -15,6 +15,7 @@
 import {getMaybeLazyDirectory} from '../models/lazy_directory_entry.js';
 import {NativeDirectoryEntry} from '../models/native_file_system_entry.js';
 import {ChromeHelper} from '../mojo/chrome_helper.js';
+import {UntrustedOrigin} from '../type.js';
 
 // eslint-disable-next-line no-unused-vars
 import {BrowserProxy} from './browser_proxy_interface.js';
@@ -172,6 +173,11 @@
   async setLaunchingFromWindowCreationStartTime(callback) {
     await callback();
   }
+
+  /** @override */
+  getUntrustedOrigin() {
+    return UntrustedOrigin.CHROME_UNTRUSTED;
+  }
 }
 
 export const browserProxy = new WebUIBrowserProxy();
diff --git a/chromeos/components/camera_app_ui/resources/js/dynamic_import.js b/chromeos/components/camera_app_ui/resources/js/dynamic_import.js
new file mode 100644
index 0000000..d243faae
--- /dev/null
+++ b/chromeos/components/camera_app_ui/resources/js/dynamic_import.js
@@ -0,0 +1,19 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview
+ * This file is only to avoid syntax error from closure compiler since it does
+ * not support dynamic imports currently. Once it supports, we should remove
+ * this file and import it directly.
+ */
+
+/**
+ * Imports module dynamically.
+ * @param {string} scriptUrl The URL of the script.
+ * @return {!Promise}
+ */
+export async function dynamicImport(scriptUrl) {
+  return import(scriptUrl);
+}
diff --git a/chromeos/components/camera_app_ui/resources/js/type.js b/chromeos/components/camera_app_ui/resources/js/type.js
index 0db416a..6213bf54 100644
--- a/chromeos/components/camera_app_ui/resources/js/type.js
+++ b/chromeos/components/camera_app_ui/resources/js/type.js
@@ -242,3 +242,12 @@
     this.name = this.constructor.name;
   }
 }
+
+/**
+ * The possible scheme to load untrusted context.
+ * @enum {string}
+ */
+export const UntrustedOrigin = {
+  CHROME_EXTENSION: 'chrome-extension://hfhhnacclhffhdffklopdkcgdhifgngh',
+  CHROME_UNTRUSTED: 'chrome-untrusted://camera-app',
+};
diff --git a/chromeos/components/camera_app_ui/resources/js/untrusted_script_loader.js b/chromeos/components/camera_app_ui/resources/js/untrusted_script_loader.js
new file mode 100644
index 0000000..93cf97d4
--- /dev/null
+++ b/chromeos/components/camera_app_ui/resources/js/untrusted_script_loader.js
@@ -0,0 +1,38 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview
+ * dynamic_import.js is used to avoid syntax failure of closure compiler since
+ * it currently does not support the dynamic imports feature.
+ * @suppress {moduleLoad}
+ */
+
+import {dynamicImport} from './dynamic_import.js';
+import * as Comlink from './lib/comlink.js';
+import {WaitableEvent} from './waitable_event.js';
+
+/**
+ * @type {!WaitableEvent}
+ */
+const domReady = new WaitableEvent();
+
+const exposedObjects = {loadScript};
+
+/**
+ * Loads given script into the untrusted context.
+ * @param {string} scriptUrl
+ * @return {!Promise}
+ */
+async function loadScript(scriptUrl) {
+  await domReady.wait();
+  const module = await dynamicImport(scriptUrl);
+  Object.assign(exposedObjects, module);
+}
+
+document.addEventListener('DOMContentLoaded', () => {
+  domReady.signal();
+});
+
+Comlink.expose(exposedObjects, Comlink.windowEndpoint(self.parent, self));
diff --git a/chromeos/components/camera_app_ui/resources/js/util.js b/chromeos/components/camera_app_ui/resources/js/util.js
index 2472d3a..71df0d2a 100644
--- a/chromeos/components/camera_app_ui/resources/js/util.js
+++ b/chromeos/components/camera_app_ui/resources/js/util.js
@@ -6,9 +6,16 @@
 import {assertInstanceof} from './chrome_util.js';
 import * as dom from './dom.js';
 import {reportError} from './error.js';
+import * as Comlink from './lib/comlink.js';
 import * as state from './state.js';
 import * as tooltip from './tooltip.js';
-import {ErrorLevel, ErrorType, Facing} from './type.js';
+import {
+  ErrorLevel,
+  ErrorType,
+  Facing,
+  UntrustedOrigin,  // eslint-disable-line no-unused-vars
+} from './type.js';
+import {WaitableEvent} from './waitable_event.js';
 
 /**
  * Creates a canvas element for 2D drawing.
@@ -454,3 +461,26 @@
   setupI18nElements(node);
   return node;
 }
+
+/**
+ * Creates JS module by given |scriptUrl| under untrusted context with given
+ * origin and returns its proxy.
+ * @param {string} scriptUrl The URL of the script to load.
+ * @param {!UntrustedOrigin} origin The origin of the untrusted context.
+ * @return {!Promise<!Object>}
+ */
+export async function createUntrustedJSModule(scriptUrl, origin) {
+  const untrustedPageReady = new WaitableEvent();
+  const iFrame =
+      /** @type {!HTMLIFrameElement} */ (document.createElement('iframe'));
+  iFrame.addEventListener('load', () => untrustedPageReady.signal());
+  iFrame.setAttribute('src', `${origin}/views/untrusted_script_loader.html`);
+  iFrame.hidden = true;
+  document.body.appendChild(iFrame);
+  await untrustedPageReady.wait();
+
+  const untrustedRemote =
+      await Comlink.wrap(Comlink.windowEndpoint(iFrame.contentWindow, self));
+  await untrustedRemote.loadScript(scriptUrl);
+  return untrustedRemote;
+}
diff --git a/chromeos/components/camera_app_ui/resources/views/untrusted_script_loader.html b/chromeos/components/camera_app_ui/resources/views/untrusted_script_loader.html
new file mode 100644
index 0000000..e7e5161
--- /dev/null
+++ b/chromeos/components/camera_app_ui/resources/views/untrusted_script_loader.html
@@ -0,0 +1,8 @@
+<!-- Copyright 2020 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script type="module" src="/js/untrusted_script_loader.js"></script>
+<body></body>
\ No newline at end of file
diff --git a/chromeos/components/camera_app_ui/url_constants.cc b/chromeos/components/camera_app_ui/url_constants.cc
index 8cd5969..6e1a8b9 100644
--- a/chromeos/components/camera_app_ui/url_constants.cc
+++ b/chromeos/components/camera_app_ui/url_constants.cc
@@ -9,5 +9,6 @@
 const char kChromeUICameraAppHost[] = "camera-app";
 const char kChromeUICameraAppMainURL[] = "chrome://camera-app/views/main.html";
 const char kChromeUICameraAppURL[] = "chrome://camera-app/";
+const char kChromeUIUntrustedCameraAppURL[] = "chrome-untrusted://camera-app/";
 
 }  // namespace chromeos
diff --git a/chromeos/components/camera_app_ui/url_constants.h b/chromeos/components/camera_app_ui/url_constants.h
index a7ad45a..4a81ad5e 100644
--- a/chromeos/components/camera_app_ui/url_constants.h
+++ b/chromeos/components/camera_app_ui/url_constants.h
@@ -10,6 +10,7 @@
 extern const char kChromeUICameraAppHost[];
 extern const char kChromeUICameraAppMainURL[];
 extern const char kChromeUICameraAppURL[];
+extern const char kChromeUIUntrustedCameraAppURL[];
 
 }  // namespace chromeos
 
diff --git a/chromeos/components/diagnostics_ui/resources/routine_result_list.js b/chromeos/components/diagnostics_ui/resources/routine_result_list.js
index 3c54a9e..bb35e96 100644
--- a/chromeos/components/diagnostics_ui/resources/routine_result_list.js
+++ b/chromeos/components/diagnostics_ui/resources/routine_result_list.js
@@ -6,6 +6,7 @@
 import './diagnostics_shared_css.js';
 import './routine_result_entry.js';
 
+import {assert} from 'chrome://resources/js/assert.m.js';
 import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {RoutineName} from './diagnostics_types.js';
 import {ResultStatusItem} from './routine_list_executor.js'
@@ -56,6 +57,32 @@
     this.push('results_', new ResultStatusItem(routine));
   },
 
+  /**
+   * Updates the routine's status in the results_ list.
+   * @param {number} index
+   * @param {!ResultStatusItem} status
+   * @private
+   */
+  updateRoutineStatus_(index, status) {
+    assert(index < this.results_.length);
+    this.splice('results_', index, 1, status);
+  },
+
+  /**
+   * Receives the callback from RoutineListExecutor whenever the status of a
+   * routine changed.
+   * @param {!ResultStatusItem} status
+   */
+  onStatusUpdate(status) {
+    assert(this.results_.length > 0);
+    this.results_.forEach((result, index) => {
+      if (result.routine == status.routine) {
+        this.updateRoutineStatus_(index, status);
+        return;
+      }
+    });
+  },
+
   /** @override */
   created() {},
 });
diff --git a/chromeos/components/diagnostics_ui/resources/routine_section.js b/chromeos/components/diagnostics_ui/resources/routine_section.js
index f314603..d346765 100644
--- a/chromeos/components/diagnostics_ui/resources/routine_section.js
+++ b/chromeos/components/diagnostics_ui/resources/routine_section.js
@@ -9,6 +9,8 @@
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {RoutineName} from './diagnostics_types.js';
+import {getSystemRoutineController} from './mojo_interface_provider.js';
+import {RoutineListExecutor} from './routine_list_executor.js'
 
 /**
  * @fileoverview
@@ -21,6 +23,11 @@
 
   _template: html`{__html_template__}`,
 
+  /**
+   * @private {?RoutineListExecutor}
+   */
+  executor_: null,
+
   properties: {
     /** @type {!Array<!RoutineName>} */
     routines: {
@@ -38,13 +45,23 @@
   /** @private */
   onRunTestsClicked_() {
     this.isRunTestsDisabled_ = true;
-    this.getListElem_().initializeTestRun(this.routines);
+    const resultListElem = this.getResultListElem_();
+    resultListElem.initializeTestRun(this.routines);
 
-    // TODO(zentaro): Run tests which will also reenable button on completion.
+    this.executor_ = new RoutineListExecutor(getSystemRoutineController());
+    this.executor_
+        .runRoutines(
+            this.routines, resultListElem.onStatusUpdate.bind(resultListElem))
+        .then(() => {
+          this.isRunTestsDisabled_ = false;
+        });
   },
 
-  /** @return {!HTMLElement} */
-  getListElem_() {
+  /**
+   * @return {!HTMLElement}
+   * @private
+   **/
+  getResultListElem_() {
     return /** @type {!HTMLElement} */ (this.$$('routine-result-list'));
   },
 
diff --git a/chromeos/components/file_manager/BUILD.gn b/chromeos/components/file_manager/BUILD.gn
index 92b78fe..338d509e 100644
--- a/chromeos/components/file_manager/BUILD.gn
+++ b/chromeos/components/file_manager/BUILD.gn
@@ -18,6 +18,7 @@
   sources = [
     "file_manager_ui.cc",
     "file_manager_ui.h",
+    "file_manager_ui_delegate.h",
     "url_constants.cc",
     "url_constants.h",
   ]
diff --git a/chromeos/components/file_manager/file_manager_ui.cc b/chromeos/components/file_manager/file_manager_ui.cc
index 0977c539..4eba6fb 100644
--- a/chromeos/components/file_manager/file_manager_ui.cc
+++ b/chromeos/components/file_manager/file_manager_ui.cc
@@ -15,8 +15,9 @@
 namespace chromeos {
 namespace file_manager {
 
-FileManagerUI::FileManagerUI(content::WebUI* web_ui)
-    : MojoWebUIController(web_ui) {
+FileManagerUI::FileManagerUI(content::WebUI* web_ui,
+                             std::unique_ptr<FileManagerUIDelegate> delegate)
+    : MojoWebUIController(web_ui), delegate_(std::move(delegate)) {
   auto source = base::WrapUnique(content::WebUIDataSource::Create(
       chromeos::file_manager::kChromeUIFileManagerHost));
   // The HTML content loaded on chrome://file-manager.
diff --git a/chromeos/components/file_manager/file_manager_ui.h b/chromeos/components/file_manager/file_manager_ui.h
index fd71622a..a7f6981 100644
--- a/chromeos/components/file_manager/file_manager_ui.h
+++ b/chromeos/components/file_manager/file_manager_ui.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "chromeos/components/file_manager/file_manager_ui_delegate.h"
 #include "chromeos/components/file_manager/mojom/file_manager.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
@@ -22,7 +23,8 @@
 class FileManagerUI : public ui::MojoWebUIController,
                       public mojom::PageHandlerFactory {
  public:
-  explicit FileManagerUI(content::WebUI* web_ui);
+  FileManagerUI(content::WebUI* web_ui,
+                std::unique_ptr<FileManagerUIDelegate> delegate);
   ~FileManagerUI() override;
 
   // Disallow copy and assign.
@@ -32,12 +34,16 @@
   void BindInterface(
       mojo::PendingReceiver<mojom::PageHandlerFactory> pending_receiver);
 
+  const FileManagerUIDelegate* delegate() { return delegate_.get(); }
+
  private:
   // mojom::PageHandlerFactory:
   void CreatePageHandler(
       mojo::PendingRemote<mojom::Page> pending_page,
       mojo::PendingReceiver<mojom::PageHandler> pending_page_handler) override;
 
+  std::unique_ptr<FileManagerUIDelegate> delegate_;
+
   mojo::Receiver<mojom::PageHandlerFactory> page_factory_receiver_{this};
   std::unique_ptr<FileManagerPageHandler> page_handler_;
 
diff --git a/chromeos/components/file_manager/file_manager_ui_delegate.h b/chromeos/components/file_manager/file_manager_ui_delegate.h
new file mode 100644
index 0000000..dc1a840
--- /dev/null
+++ b/chromeos/components/file_manager/file_manager_ui_delegate.h
@@ -0,0 +1,15 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_COMPONENTS_FILE_MANAGER_FILE_MANAGER_UI_DELEGATE_H_
+#define CHROMEOS_COMPONENTS_FILE_MANAGER_FILE_MANAGER_UI_DELEGATE_H_
+
+// A delegate which exposes browser functionality from //chrome to the
+// FileManagerUI handler.
+class FileManagerUIDelegate {
+ public:
+  virtual ~FileManagerUIDelegate() = default;
+};
+
+#endif  // CHROMEOS_COMPONENTS_FILE_MANAGER_FILE_MANAGER_UI_DELEGATE_H_
diff --git a/chromeos/constants/chromeos_features.cc b/chromeos/constants/chromeos_features.cc
index 9070f04..f8c959bb 100644
--- a/chromeos/constants/chromeos_features.cc
+++ b/chromeos/constants/chromeos_features.cc
@@ -257,8 +257,8 @@
 
 // Enables policy that controls feature to allow Family Link accounts on school
 // owned devices.
-const base::Feature kFamilyLinkOnSchoolDevice{
-    "FamilyLinkOnSchoolDevice", base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kFamilyLinkOnSchoolDevice{"FamilyLinkOnSchoolDevice",
+                                              base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Enables the camera folder handling in files app.
 const base::Feature kFilesCameraFolder{"FilesCameraFolder",
@@ -367,21 +367,6 @@
 const base::Feature kHelpAppSearchServiceIntegration{
     "HelpAppSearchServiceIntegration", base::FEATURE_DISABLED_BY_DEFAULT};
 
-// Enable or disable Unified Input Logic for HMM decoder in the IME extension
-// on Chrome OS.
-const base::Feature kImeInputLogicHmm{"ImeInputLogicHmm",
-                                      base::FEATURE_DISABLED_BY_DEFAULT};
-
-// Enable or disable Unified Input Logic for FST decoder in the IME extension
-// on Chrome OS.
-const base::Feature kImeInputLogicFst{"ImeInputLogicFst",
-                                      base::FEATURE_ENABLED_BY_DEFAULT};
-
-// Enable or disable Unified Input Logic for Mozc decoder in the IME extension
-// on Chrome OS.
-const base::Feature kImeInputLogicMozc{"ImeInputLogicMozc",
-                                       base::FEATURE_DISABLED_BY_DEFAULT};
-
 // Enable or disable IME decoder via Mojo connection on Chrome OS.
 const base::Feature kImeMojoDecoder{"ImeMojoDecoder",
                                     base::FEATURE_DISABLED_BY_DEFAULT};
@@ -537,6 +522,10 @@
 const base::Feature kReleaseNotesNotificationAllChannels{
     "ReleaseNotesNotificationAllChannels", base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enables or disables Release Notes suggestion chip on Chrome OS.
+const base::Feature kReleaseNotesSuggestionChip{
+    "ReleaseNotesSuggestionChip", base::FEATURE_ENABLED_BY_DEFAULT};
+
 // Enables or disables an experimental scanning UI on Chrome OS.
 const base::Feature kScanningUI{"ScanningUI",
                                 base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/chromeos/constants/chromeos_features.h b/chromeos/constants/chromeos_features.h
index a0275b98..b93d23c 100644
--- a/chromeos/constants/chromeos_features.h
+++ b/chromeos/constants/chromeos_features.h
@@ -170,12 +170,6 @@
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kHelpAppSearchServiceIntegration;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
-extern const base::Feature kImeInputLogicHmm;
-COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
-extern const base::Feature kImeInputLogicFst;
-COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
-extern const base::Feature kImeInputLogicMozc;
-COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kImeMojoDecoder;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kImeOptionsInSettings;
@@ -247,6 +241,8 @@
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kReleaseNotesNotificationAllChannels;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
+extern const base::Feature kReleaseNotesSuggestionChip;
+COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kFiltersInRecents;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const base::Feature kScanningUI;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
diff --git a/components/cast/message_port/BUILD.gn b/components/cast/message_port/BUILD.gn
index 0884ff2..faeb4c8 100644
--- a/components/cast/message_port/BUILD.gn
+++ b/components/cast/message_port/BUILD.gn
@@ -5,34 +5,59 @@
 import("//build/config/features.gni")
 
 source_set("message_port") {
+  if (is_fuchsia) {
+    public_deps = [ ":message_port_fuchsia" ]
+  } else if (is_chromecast) {
+    public_deps = [ ":message_port_cast" ]
+  }
+}
+
+source_set("public") {
   sources = [
     "message_port.cc",
     "message_port.h",
   ]
+
   deps = [
     "//base",
     "//components/cast:export",
   ]
+
   defines = [ "CAST_COMPONENT_IMPLEMENTATION" ]
 
-  if (is_fuchsia) {
-    sources += [
-      "message_port_fuchsia.cc",
-      "message_port_fuchsia.h",
-    ]
+  visibility = [ ":*" ]
+}
+
+if (is_fuchsia) {
+  source_set("message_port_fuchsia") {
+    public = [ "message_port_fuchsia.h" ]
+
+    sources = [ "message_port_fuchsia.cc" ]
+
     public_deps = [
+      ":public",
       "//fuchsia:cast_fidl",
       "//fuchsia/base",
     ]
-  } else if (is_chromecast) {
-    sources += [
-      "message_port_cast.cc",
-      "message_port_cast.h",
-    ]
-    deps += [ "//third_party/blink/public/common" ]
+
+    deps = [ "//base" ]
   }
 }
 
+source_set("message_port_cast") {
+  public = [ "message_port_cast.h" ]
+
+  sources = [ "message_port_cast.cc" ]
+
+  public_deps = [ ":public" ]
+
+  deps = [
+    ":public",
+    "//base",
+    "//third_party/blink/public/common",
+  ]
+}
+
 source_set("test_message_port_receiver") {
   testonly = true
   sources = [
diff --git a/components/cast/message_port/message_port.h b/components/cast/message_port/message_port.h
index 2c0b40b..ef5b924 100644
--- a/components/cast/message_port/message_port.h
+++ b/components/cast/message_port/message_port.h
@@ -9,13 +9,14 @@
 #include <vector>
 
 #include "base/strings/string_piece.h"
+#include "components/cast/cast_component_export.h"
 
 namespace cast_api_bindings {
 
 // HTML5 MessagePort abstraction; allows usage of the platform MessagePort type
 // without exposing details of the message format, paired port creation, or
 // transfer of ports.
-class MessagePort {
+class CAST_COMPONENT_EXPORT MessagePort {
  public:
   // Implemented by receivers of messages from the MessagePort class.
   class Receiver {
diff --git a/components/cast/message_port/message_port_fuchsia.cc b/components/cast/message_port/message_port_fuchsia.cc
index c59c8bb..61a7589 100644
--- a/components/cast/message_port/message_port_fuchsia.cc
+++ b/components/cast/message_port/message_port_fuchsia.cc
@@ -196,6 +196,15 @@
 }  // namespace
 
 // static
+void MessagePort::CreatePair(std::unique_ptr<MessagePort>* client,
+                             std::unique_ptr<MessagePort>* server) {
+  fidl::InterfaceHandle<fuchsia::web::MessagePort> port0;
+  fidl::InterfaceRequest<fuchsia::web::MessagePort> port1 = port0.NewRequest();
+  *client = MessagePortFuchsia::Create(std::move(port0));
+  *server = MessagePortFuchsia::Create(std::move(port1));
+}
+
+// static
 std::unique_ptr<MessagePort> MessagePortFuchsia::Create(
     fidl::InterfaceHandle<::fuchsia::web::MessagePort> handle) {
   return std::make_unique<MessagePortFuchsiaClient>(std::move(handle));
@@ -215,15 +224,6 @@
 }
 
 // static
-void MessagePort::CreatePair(std::unique_ptr<MessagePort>* client,
-                             std::unique_ptr<MessagePort>* server) {
-  fidl::InterfaceHandle<fuchsia::web::MessagePort> port0;
-  fidl::InterfaceRequest<fuchsia::web::MessagePort> port1 = port0.NewRequest();
-  *client = MessagePortFuchsia::Create(std::move(port0));
-  *server = MessagePortFuchsia::Create(std::move(port1));
-}
-
-// static
 fuchsia::web::WebMessage MessagePortFuchsia::CreateWebMessage(
     base::StringPiece message,
     std::vector<std::unique_ptr<MessagePort>> ports) {
diff --git a/components/cronet/android/cronet_url_request_adapter.cc b/components/cronet/android/cronet_url_request_adapter.cc
index baceb916..447b5a1 100644
--- a/components/cronet/android/cronet_url_request_adapter.cc
+++ b/components/cronet/android/cronet_url_request_adapter.cc
@@ -112,7 +112,8 @@
                                jtraffic_stats_tag_set == JNI_TRUE,
                                jtraffic_stats_tag,
                                jtraffic_stats_uid_set == JNI_TRUE,
-                               jtraffic_stats_uid)) {
+                               jtraffic_stats_uid,
+                               /*idempotency=*/net::DEFAULT_IDEMPOTENCY)) {
   owner_.Reset(env, jurl_request);
 }
 
diff --git a/components/cronet/cronet_url_request.cc b/components/cronet/cronet_url_request.cc
index b0476e53..2f56be3c 100644
--- a/components/cronet/cronet_url_request.cc
+++ b/components/cronet/cronet_url_request.cc
@@ -13,6 +13,7 @@
 #include "base/logging.h"
 #include "build/build_config.h"
 #include "components/cronet/cronet_url_request_context.h"
+#include "net/base/idempotency.h"
 #include "net/base/load_flags.h"
 #include "net/base/load_states.h"
 #include "net/base/net_errors.h"
@@ -62,7 +63,8 @@
                                    bool traffic_stats_tag_set,
                                    int32_t traffic_stats_tag,
                                    bool traffic_stats_uid_set,
-                                   int32_t traffic_stats_uid)
+                                   int32_t traffic_stats_uid,
+                                   net::Idempotency idempotency)
     : context_(context),
       network_tasks_(std::move(callback),
                      url,
@@ -74,7 +76,8 @@
                      traffic_stats_tag_set,
                      traffic_stats_tag,
                      traffic_stats_uid_set,
-                     traffic_stats_uid),
+                     traffic_stats_uid,
+                     idempotency),
       initial_method_("GET"),
       initial_request_headers_(std::make_unique<net::HttpRequestHeaders>()) {
   DCHECK(!context_->IsOnNetworkThread());
@@ -174,7 +177,8 @@
                                              bool traffic_stats_tag_set,
                                              int32_t traffic_stats_tag,
                                              bool traffic_stats_uid_set,
-                                             int32_t traffic_stats_uid)
+                                             int32_t traffic_stats_uid,
+                                             net::Idempotency idempotency)
     : callback_(std::move(callback)),
       initial_url_(url),
       initial_priority_(priority),
@@ -186,7 +190,8 @@
       traffic_stats_tag_set_(traffic_stats_tag_set),
       traffic_stats_tag_(traffic_stats_tag),
       traffic_stats_uid_set_(traffic_stats_uid_set),
-      traffic_stats_uid_(traffic_stats_uid) {
+      traffic_stats_uid_(traffic_stats_uid),
+      idempotency_(idempotency) {
   DETACH_FROM_THREAD(network_thread_checker_);
 }
 
@@ -283,6 +288,7 @@
   url_request_->set_method(method);
   url_request_->SetExtraRequestHeaders(*request_headers);
   url_request_->SetPriority(initial_priority_);
+  url_request_->SetIdempotency(idempotency_);
   if (upload)
     url_request_->set_upload(std::move(upload));
   if (traffic_stats_tag_set_ || traffic_stats_uid_set_) {
diff --git a/components/cronet/cronet_url_request.h b/components/cronet/cronet_url_request.h
index 9896d3e..120a6dd9 100644
--- a/components/cronet/cronet_url_request.h
+++ b/components/cronet/cronet_url_request.h
@@ -13,6 +13,7 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/time/time.h"
+#include "net/base/idempotency.h"
 #include "net/base/request_priority.h"
 #include "net/url_request/url_request.h"
 #include "url/gurl.h"
@@ -149,7 +150,8 @@
                    bool traffic_stats_tag_set,
                    int32_t traffic_stats_tag,
                    bool traffic_stats_uid_set,
-                   int32_t traffic_stats_uid);
+                   int32_t traffic_stats_uid,
+                   net::Idempotency idempotency);
 
   // Methods called prior to Start are never called on network thread.
 
@@ -207,7 +209,8 @@
                  bool traffic_stats_tag_set,
                  int32_t traffic_stats_tag,
                  bool traffic_stats_uid_set,
-                 int32_t traffic_stats_uid);
+                 int32_t traffic_stats_uid,
+                 net::Idempotency idempotency);
 
     // Invoked on the network thread.
     ~NetworkTasks() override;
@@ -285,6 +288,8 @@
     const bool traffic_stats_uid_set_;
     // UID to be applied to URLRequest.
     const int32_t traffic_stats_uid_;
+    // Idempotency of the request.
+    const net::Idempotency idempotency_;
 
     scoped_refptr<net::IOBuffer> read_buffer_;
     std::unique_ptr<net::URLRequest> url_request_;
diff --git a/components/cronet/native/cronet.idl b/components/cronet/native/cronet.idl
index 8fc14fb3..f72d885 100644
--- a/components/cronet/native/cronet.idl
+++ b/components/cronet/native/cronet.idl
@@ -1228,6 +1228,23 @@
    * start running in the future.
    */
   Executor? request_finished_executor;
+
+  enum IDEMPOTENCY {
+    DEFAULT_IDEMPOTENCY = 0,
+    IDEMPOTENT = 1,
+    NOT_IDEMPOTENT = 2,
+  };
+
+ /**
+   * Idempotency of the request, which determines that if it is safe to enable
+   * 0-RTT for the Cronet request. By default, 0-RTT is only enabled for safe
+   * HTTP methods, i.e., GET, HEAD, OPTIONS, and TRACE. For other methods,
+   * enabling 0-RTT may cause security issues since a network observer can
+   * replay the request. If the request has any side effects, those effects can
+   * happen multiple times. It is only safe to enable the 0-RTT if it is known
+   * that the request is idempotent.
+   */
+  IDEMPOTENCY idempotency = DEFAULT_IDEMPOTENCY;
 };
 
 /**
diff --git a/components/cronet/native/generated/cronet.idl_c.h b/components/cronet/native/generated/cronet.idl_c.h
index 80a9efe..041fd8f58 100644
--- a/components/cronet/native/generated/cronet.idl_c.h
+++ b/components/cronet/native/generated/cronet.idl_c.h
@@ -132,6 +132,12 @@
   Cronet_UrlRequestParams_REQUEST_PRIORITY_REQUEST_PRIORITY_HIGHEST = 4,
 } Cronet_UrlRequestParams_REQUEST_PRIORITY;
 
+typedef enum Cronet_UrlRequestParams_IDEMPOTENCY {
+  Cronet_UrlRequestParams_IDEMPOTENCY_DEFAULT_IDEMPOTENCY = 0,
+  Cronet_UrlRequestParams_IDEMPOTENCY_IDEMPOTENT = 1,
+  Cronet_UrlRequestParams_IDEMPOTENCY_NOT_IDEMPOTENT = 2,
+} Cronet_UrlRequestParams_IDEMPOTENCY;
+
 typedef enum Cronet_RequestFinishedInfo_FINISHED_REASON {
   Cronet_RequestFinishedInfo_FINISHED_REASON_SUCCEEDED = 0,
   Cronet_RequestFinishedInfo_FINISHED_REASON_FAILED = 1,
@@ -1023,6 +1029,10 @@
 void Cronet_UrlRequestParams_request_finished_executor_set(
     Cronet_UrlRequestParamsPtr self,
     const Cronet_ExecutorPtr request_finished_executor);
+CRONET_EXPORT
+void Cronet_UrlRequestParams_idempotency_set(
+    Cronet_UrlRequestParamsPtr self,
+    const Cronet_UrlRequestParams_IDEMPOTENCY idempotency);
 // Cronet_UrlRequestParams getters.
 CRONET_EXPORT
 Cronet_String Cronet_UrlRequestParams_http_method_get(
@@ -1068,6 +1078,9 @@
 CRONET_EXPORT
 Cronet_ExecutorPtr Cronet_UrlRequestParams_request_finished_executor_get(
     const Cronet_UrlRequestParamsPtr self);
+CRONET_EXPORT
+Cronet_UrlRequestParams_IDEMPOTENCY Cronet_UrlRequestParams_idempotency_get(
+    const Cronet_UrlRequestParamsPtr self);
 
 ///////////////////////
 // Struct Cronet_DateTime.
diff --git a/components/cronet/native/generated/cronet.idl_impl_struct.cc b/components/cronet/native/generated/cronet.idl_impl_struct.cc
index 7288afc..e7d6ebd31 100644
--- a/components/cronet/native/generated/cronet.idl_impl_struct.cc
+++ b/components/cronet/native/generated/cronet.idl_impl_struct.cc
@@ -715,6 +715,13 @@
   self->request_finished_executor = request_finished_executor;
 }
 
+void Cronet_UrlRequestParams_idempotency_set(
+    Cronet_UrlRequestParamsPtr self,
+    const Cronet_UrlRequestParams_IDEMPOTENCY idempotency) {
+  DCHECK(self);
+  self->idempotency = idempotency;
+}
+
 // Struct Cronet_UrlRequestParams getters.
 Cronet_String Cronet_UrlRequestParams_http_method_get(
     const Cronet_UrlRequestParamsPtr self) {
@@ -801,6 +808,12 @@
   return self->request_finished_executor;
 }
 
+Cronet_UrlRequestParams_IDEMPOTENCY Cronet_UrlRequestParams_idempotency_get(
+    const Cronet_UrlRequestParamsPtr self) {
+  DCHECK(self);
+  return self->idempotency;
+}
+
 // Struct Cronet_DateTime.
 Cronet_DateTime::Cronet_DateTime() = default;
 
diff --git a/components/cronet/native/generated/cronet.idl_impl_struct.h b/components/cronet/native/generated/cronet.idl_impl_struct.h
index 6a10c1ab..06a7dec7 100644
--- a/components/cronet/native/generated/cronet.idl_impl_struct.h
+++ b/components/cronet/native/generated/cronet.idl_impl_struct.h
@@ -151,6 +151,8 @@
   std::vector<Cronet_RawDataPtr> annotations;
   Cronet_RequestFinishedInfoListenerPtr request_finished_listener = nullptr;
   Cronet_ExecutorPtr request_finished_executor = nullptr;
+  Cronet_UrlRequestParams_IDEMPOTENCY idempotency =
+      Cronet_UrlRequestParams_IDEMPOTENCY_DEFAULT_IDEMPOTENCY;
 
  private:
   DISALLOW_ASSIGN(Cronet_UrlRequestParams);
diff --git a/components/cronet/native/generated/cronet.idl_impl_struct_unittest.cc b/components/cronet/native/generated/cronet.idl_impl_struct_unittest.cc
index 3091405..d048bc6 100644
--- a/components/cronet/native/generated/cronet.idl_impl_struct_unittest.cc
+++ b/components/cronet/native/generated/cronet.idl_impl_struct_unittest.cc
@@ -256,6 +256,10 @@
       second, Cronet_UrlRequestParams_request_finished_executor_get(first));
   EXPECT_EQ(Cronet_UrlRequestParams_request_finished_executor_get(first),
             Cronet_UrlRequestParams_request_finished_executor_get(second));
+  Cronet_UrlRequestParams_idempotency_set(
+      second, Cronet_UrlRequestParams_idempotency_get(first));
+  EXPECT_EQ(Cronet_UrlRequestParams_idempotency_get(first),
+            Cronet_UrlRequestParams_idempotency_get(second));
   Cronet_UrlRequestParams_Destroy(first);
   Cronet_UrlRequestParams_Destroy(second);
 }
diff --git a/components/cronet/native/url_request.cc b/components/cronet/native/url_request.cc
index 95e6270..d440c02 100644
--- a/components/cronet/native/url_request.cc
+++ b/components/cronet/native/url_request.cc
@@ -52,6 +52,19 @@
   return net::DEFAULT_PRIORITY;
 }
 
+net::Idempotency ConvertIdempotency(
+    Cronet_UrlRequestParams_IDEMPOTENCY idempotency) {
+  switch (idempotency) {
+    case Cronet_UrlRequestParams_IDEMPOTENCY_DEFAULT_IDEMPOTENCY:
+      return net::DEFAULT_IDEMPOTENCY;
+    case Cronet_UrlRequestParams_IDEMPOTENCY_IDEMPOTENT:
+      return net::IDEMPOTENT;
+    case Cronet_UrlRequestParams_IDEMPOTENCY_NOT_IDEMPOTENT:
+      return net::NOT_IDEMPOTENT;
+  }
+  return net::DEFAULT_IDEMPOTENCY;
+}
+
 scoped_refptr<UrlResponseInfo> CreateCronet_UrlResponseInfo(
     const std::vector<std::string>& url_chain,
     int http_status_code,
@@ -363,7 +376,8 @@
           engine_->HasRequestFinishedListener() /* params->enableMetrics */,
       // TODO(pauljensen): Consider exposing TrafficStats API via C++ API.
       false /* traffic_stats_tag_set */, 0 /* traffic_stats_tag */,
-      false /* traffic_stats_uid_set */, 0 /* traffic_stats_uid */);
+      false /* traffic_stats_uid_set */, 0 /* traffic_stats_uid */,
+      ConvertIdempotency(params->idempotency));
 
   if (params->upload_data_provider) {
     upload_data_sink_ = std::make_unique<Cronet_UploadDataSinkImpl>(
diff --git a/components/feed/core/v2/feed_network.h b/components/feed/core/v2/feed_network.h
index b0836218..a5676de45 100644
--- a/components/feed/core/v2/feed_network.h
+++ b/components/feed/core/v2/feed_network.h
@@ -30,6 +30,8 @@
     NetworkResponseInfo response_info;
     // Response body if one was received.
     std::unique_ptr<feedwire::Response> response_body;
+    // Whether the request was signed in.
+    bool was_signed_in;
   };
 
   // Result of SendActionRequest.
diff --git a/components/feed/core/v2/feed_network_impl.cc b/components/feed/core/v2/feed_network_impl.cc
index 4cc86a94..73d8f56 100644
--- a/components/feed/core/v2/feed_network_impl.cc
+++ b/components/feed/core/v2/feed_network_impl.cc
@@ -329,6 +329,7 @@
         tick_clock_->NowTicks() - entire_send_start_ticks_;
     response_info.fetch_time = base::Time::Now();
     response_info.base_request_url = GetUrlWithoutQuery(url_);
+    response_info.was_signed_in = !access_token_.empty();
 
     // If overriding the feed host, try to grab the Bless nonce. This is
     // strictly informational, and only displayed in snippets-internals.
diff --git a/components/feed/core/v2/feed_network_impl_unittest.cc b/components/feed/core/v2/feed_network_impl_unittest.cc
index 6d75977..bddc71c 100644
--- a/components/feed/core/v2/feed_network_impl_unittest.cc
+++ b/components/feed/core/v2/feed_network_impl_unittest.cc
@@ -277,6 +277,7 @@
       "https://www.google.com/httpservice/retry/TrellisClankService/FeedQuery",
       result.response_info.base_request_url);
   EXPECT_NE(base::Time(), result.response_info.fetch_time);
+  EXPECT_TRUE(result.response_info.was_signed_in);
   EXPECT_EQ(GetTestFeedResponse().response_version(),
             result.response_body->response_version());
 }
diff --git a/components/feed/core/v2/feed_stream.cc b/components/feed/core/v2/feed_stream.cc
index cc3f575..c7d9df0 100644
--- a/components/feed/core/v2/feed_stream.cc
+++ b/components/feed/core/v2/feed_stream.cc
@@ -760,11 +760,16 @@
 void FeedStream::UpdateShownSlicesUploadCondition(int viewed_slice_index) {
   constexpr int kShownSlicesThreshold = 2;
 
+  DCHECK(model_) << "Model was unloaded while handling a viewed slice";
+
   // Don't take shown slices into consideration when the upload conditions has
   // already been reached.
   if (HasReachedConditionsToUploadActionsWithNoticeCard())
     return;
 
+  if (!model_->signed_in())
+    return;
+
   if (viewed_slice_index + 1 >= kShownSlicesThreshold)
     DeclareHasReachedConditionsToUploadActionsWithNoticeCard();
 }
diff --git a/components/feed/core/v2/feed_stream_unittest.cc b/components/feed/core/v2/feed_stream_unittest.cc
index ffbb96cc..fd040f12 100644
--- a/components/feed/core/v2/feed_stream_unittest.cc
+++ b/components/feed/core/v2/feed_stream_unittest.cc
@@ -303,6 +303,7 @@
     result.response_info.status_code = 200;
     result.response_info.response_body_bytes = 100;
     result.response_info.fetch_duration = base::TimeDelta::FromMilliseconds(42);
+    result.response_info.was_signed_in = true;
     if (injected_response_) {
       result.response_body = std::make_unique<feedwire::Response>(
           std::move(injected_response_.value()));
@@ -1809,6 +1810,29 @@
                                1, 1);
 }
 
+TEST_F(FeedStreamConditionalActionsUploadTest,
+       DontTriggerActionsUploadWhenWasNotSignedIn) {
+  auto update_request = MakeTypicalInitialModelState();
+  update_request->stream_data.set_signed_in(false);
+  response_translator_.InjectResponse(std::move(update_request));
+  TestSurface surface(stream_.get());
+  WaitForIdleTaskQueue();
+
+  // Try to reach conditions.
+  stream_->ReportSliceViewed(
+      surface.GetSurfaceId(),
+      surface.initial_state->updated_slices(1).slice().slice_id());
+
+  // Try to trigger an upload through a query.
+  stream_->OnEnterBackground();
+  WaitForIdleTaskQueue();
+
+  // Verify that even if the conditions were reached, the pref that enables the
+  // upload wasn't set to true because the latest refresh request wasn't signed
+  // in.
+  ASSERT_FALSE(stream_->CanUploadActions());
+}
+
 TEST_F(FeedStreamTest, LoadStreamFromNetworkUploadsActions) {
   stream_->UploadAction(MakeFeedAction(99ul), false, base::DoNothing());
   WaitForIdleTaskQueue();
@@ -1893,7 +1917,9 @@
         CallbackReceiver<bool> callback;
         stream_->LoadMore(surface.GetSurfaceId(), callback.Bind());
         WaitForIdleTaskQueue();
-        EXPECT_EQ(stream_->IsActivityLoggingEnabled(), signed_in && waa_on);
+        EXPECT_EQ(stream_->IsActivityLoggingEnabled(), signed_in && waa_on)
+            << "signed_in=" << signed_in << " waa_on=" << waa_on
+            << " privacy_notice_fulfilled=" << privacy_notice_fulfilled;
       }
     }
   }
@@ -1911,7 +1937,6 @@
   response_translator_.InjectResponse(MakeTypicalNextPageState(
       /* first_cluster_id= */ 0,
       /* last_added_time= */ kTestTimeEpoch,
-      /* signed_in= */ true,
       /* logging_enabled= */ true,
       /* privacy_notice_fulfilled= */ false));
 
diff --git a/components/feed/core/v2/public/types.h b/components/feed/core/v2/public/types.h
index 7680ef7..0bdc72e 100644
--- a/components/feed/core/v2/public/types.h
+++ b/components/feed/core/v2/public/types.h
@@ -48,6 +48,7 @@
   std::string bless_nonce;
   GURL base_request_url;
   size_t response_body_bytes = 0;
+  bool was_signed_in = false;
 };
 
 struct NetworkResponse {
diff --git a/components/feed/core/v2/tasks/load_more_task.cc b/components/feed/core/v2/tasks/load_more_task.cc
index 8c6dc637..079e04c2 100644
--- a/components/feed/core/v2/tasks/load_more_task.cc
+++ b/components/feed/core/v2/tasks/load_more_task.cc
@@ -48,9 +48,23 @@
 }
 
 void LoadMoreTask::UploadActionsComplete(UploadActionsTask::Result result) {
+  StreamModel* model = stream_->GetModel();
+  DCHECK(model) << "Model was unloaded outside of a Task";
+
+  // Determine whether the load more request should be forced signed-out
+  // regardless of the live sign-in state of the client.
+  //
+  // The signed-in state of the model is used instead of using
+  // FeedStream#ShouldForceSignedOutFeedQueryRequest because the load more
+  // requests should be in the same signed-in state as the prior requests that
+  // filled the model to have consistent data.
+  //
+  // The sign-in state of the load stream request that brings the initial
+  // content determines the sign-in state of the subsequent load more requests.
+  // This avoids a possible situation where there would be a mix of signed-in
+  // and signed-out content, which we don't want.
+  bool force_signed_out_request = !model->signed_in();
   // Send network request.
-  bool force_signed_out_request =
-      stream_->ShouldForceSignedOutFeedQueryRequest();
   fetch_start_time_ = stream_->GetTickClock()->NowTicks();
   stream_->GetNetwork()->SendQueryRequest(
       CreateFeedQueryLoadMoreRequest(
@@ -58,12 +72,10 @@
           stream_->GetMetadata()->GetConsistencyToken(),
           stream_->GetModel()->GetNextPageToken()),
       force_signed_out_request,
-      base::BindOnce(&LoadMoreTask::QueryRequestComplete, GetWeakPtr(),
-                     force_signed_out_request));
+      base::BindOnce(&LoadMoreTask::QueryRequestComplete, GetWeakPtr()));
 }
 
 void LoadMoreTask::QueryRequestComplete(
-    bool was_forced_signed_out_request,
     FeedNetwork::QueryRequestResult result) {
   StreamModel* model = stream_->GetModel();
   DCHECK(model) << "Model was unloaded outside of a Task";
@@ -71,14 +83,11 @@
   if (!result.response_body)
     return Done(LoadStreamStatus::kNoResponseBody);
 
-  bool was_signed_in_request =
-      !was_forced_signed_out_request && stream_->IsSignedIn();
-
   RefreshResponseData translated_response =
       stream_->GetWireResponseTranslator()->TranslateWireResponse(
           *result.response_body,
           StreamModelUpdateRequest::Source::kNetworkLoadMore,
-          was_signed_in_request, stream_->GetClock()->Now());
+          result.response_info.was_signed_in, stream_->GetClock()->Now());
 
   if (!translated_response.model_update_request)
     return Done(LoadStreamStatus::kProtoTranslationFailed);
diff --git a/components/feed/core/v2/tasks/load_more_task.h b/components/feed/core/v2/tasks/load_more_task.h
index 397c4eb..538fcf9 100644
--- a/components/feed/core/v2/tasks/load_more_task.h
+++ b/components/feed/core/v2/tasks/load_more_task.h
@@ -44,8 +44,7 @@
   }
 
   void UploadActionsComplete(UploadActionsTask::Result result);
-  void QueryRequestComplete(bool was_signed_in_request,
-                            FeedNetwork::QueryRequestResult result);
+  void QueryRequestComplete(FeedNetwork::QueryRequestResult result);
   void Done(LoadStreamStatus status);
 
   FeedStream* stream_;  // Unowned.
diff --git a/components/feed/core/v2/tasks/load_stream_task.cc b/components/feed/core/v2/tasks/load_stream_task.cc
index c061f9d..63b6169 100644
--- a/components/feed/core/v2/tasks/load_stream_task.cc
+++ b/components/feed/core/v2/tasks/load_stream_task.cc
@@ -126,12 +126,10 @@
           GetRequestReason(load_type_), stream_->GetRequestMetadata(),
           stream_->GetMetadata()->GetConsistencyToken()),
       force_signed_out_request,
-      base::BindOnce(&LoadStreamTask::QueryRequestComplete, GetWeakPtr(),
-                     force_signed_out_request));
+      base::BindOnce(&LoadStreamTask::QueryRequestComplete, GetWeakPtr()));
 }
 
 void LoadStreamTask::QueryRequestComplete(
-    bool was_forced_signed_out_request,
     FeedNetwork::QueryRequestResult result) {
   latencies_->StepComplete(LoadLatencyTimes::kQueryRequest);
 
@@ -149,14 +147,11 @@
       return Done(LoadStreamStatus::kNoResponseBody);
   }
 
-  bool was_signed_in_request =
-      !was_forced_signed_out_request && stream_->IsSignedIn();
-
   RefreshResponseData response_data =
       stream_->GetWireResponseTranslator()->TranslateWireResponse(
           *result.response_body,
           StreamModelUpdateRequest::Source::kNetworkUpdate,
-          was_signed_in_request, stream_->GetClock()->Now());
+          result.response_info.was_signed_in, stream_->GetClock()->Now());
   if (!response_data.model_update_request)
     return Done(LoadStreamStatus::kProtoTranslationFailed);
 
diff --git a/components/feed/core/v2/tasks/load_stream_task.h b/components/feed/core/v2/tasks/load_stream_task.h
index 6e89939..dd4c210 100644
--- a/components/feed/core/v2/tasks/load_stream_task.h
+++ b/components/feed/core/v2/tasks/load_stream_task.h
@@ -74,8 +74,7 @@
 
   void LoadFromStoreComplete(LoadStreamFromStoreTask::Result result);
   void UploadActionsComplete(UploadActionsTask::Result result);
-  void QueryRequestComplete(bool was_signed_in_request,
-                            FeedNetwork::QueryRequestResult result);
+  void QueryRequestComplete(FeedNetwork::QueryRequestResult result);
   void Done(LoadStreamStatus status);
 
   LoadType load_type_;
diff --git a/components/paint_preview/browser/paint_preview_client.cc b/components/paint_preview/browser/paint_preview_client.cc
index 81523c1..a68a494 100644
--- a/components/paint_preview/browser/paint_preview_client.cc
+++ b/components/paint_preview/browser/paint_preview_client.cc
@@ -281,7 +281,7 @@
                             mojom::PaintPreviewStatus::kGuidCollision, {});
     return;
   }
-  if (!render_frame_host) {
+  if (!render_frame_host || params.inner.document_guid.is_empty()) {
     std::move(callback).Run(params.inner.document_guid,
                             mojom::PaintPreviewStatus::kFailed, {});
     return;
@@ -318,6 +318,9 @@
     const base::UnguessableToken& guid,
     const gfx::Rect& rect,
     content::RenderFrameHost* render_subframe_host) {
+  if (guid.is_empty())
+    return;
+
   auto it = all_document_data_.find(guid);
   if (it == all_document_data_.end())
     return;
diff --git a/components/policy/core/common/BUILD.gn b/components/policy/core/common/BUILD.gn
index 43ffb518..a7310be 100644
--- a/components/policy/core/common/BUILD.gn
+++ b/components/policy/core/common/BUILD.gn
@@ -75,6 +75,8 @@
     "cloud/policy_value_validator.h",
     "cloud/realtime_reporting_job_configuration.cc",
     "cloud/realtime_reporting_job_configuration.h",
+    "cloud/reporting_job_configuration_base.cc",
+    "cloud/reporting_job_configuration_base.h",
     "cloud/resource_cache.cc",
     "cloud/resource_cache.h",
     "cloud/signing_service.h",
diff --git a/components/policy/core/common/android/android_combined_policy_provider.cc b/components/policy/core/common/android/android_combined_policy_provider.cc
index de66439..ddfcd4ee 100644
--- a/components/policy/core/common/android/android_combined_policy_provider.cc
+++ b/components/policy/core/common/android/android_combined_policy_provider.cc
@@ -61,10 +61,5 @@
   return initialized_;
 }
 
-bool AndroidCombinedPolicyProvider::IsFirstPolicyLoadComplete(
-    PolicyDomain domain) const {
-  return IsInitializationComplete(domain);
-}
-
 }  // namespace android
 }  // namespace policy
diff --git a/components/policy/core/common/android/android_combined_policy_provider.h b/components/policy/core/common/android/android_combined_policy_provider.h
index cc4d6c1..bafa14a 100644
--- a/components/policy/core/common/android/android_combined_policy_provider.h
+++ b/components/policy/core/common/android/android_combined_policy_provider.h
@@ -43,7 +43,6 @@
 
   // ConfigurationPolicyProvider:
   bool IsInitializationComplete(PolicyDomain domain) const override;
-  bool IsFirstPolicyLoadComplete(PolicyDomain domain) const override;
   void RefreshPolicies() override;
 
   // For testing
diff --git a/components/policy/core/common/async_policy_provider.cc b/components/policy/core/common/async_policy_provider.cc
index 98f75ad..f15f1b90 100644
--- a/components/policy/core/common/async_policy_provider.cc
+++ b/components/policy/core/common/async_policy_provider.cc
@@ -21,7 +21,7 @@
 AsyncPolicyProvider::AsyncPolicyProvider(
     SchemaRegistry* registry,
     std::unique_ptr<AsyncPolicyLoader> loader)
-    : loader_(std::move(loader)), first_policies_loaded_(false) {
+    : loader_(std::move(loader)) {
   // Make an immediate synchronous load on startup.
   OnLoaderReloaded(loader_->InitialLoad(registry->schema_map()));
 }
@@ -85,11 +85,6 @@
                                            refresh_callback_.callback());
 }
 
-bool AsyncPolicyProvider::IsFirstPolicyLoadComplete(PolicyDomain domain) const {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  return first_policies_loaded_;
-}
-
 void AsyncPolicyProvider::ReloadAfterRefreshSync() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   // There can't be another refresh callback pending now, since its creation
@@ -109,7 +104,6 @@
 void AsyncPolicyProvider::OnLoaderReloaded(
     std::unique_ptr<PolicyBundle> bundle) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  first_policies_loaded_ = true;
   // Only propagate policy updates if there are no pending refreshes, and if
   // Shutdown() hasn't been called yet.
   if (refresh_callback_.IsCancelled() && loader_)
diff --git a/components/policy/core/common/async_policy_provider.h b/components/policy/core/common/async_policy_provider.h
index e78a09d..0c5f117 100644
--- a/components/policy/core/common/async_policy_provider.h
+++ b/components/policy/core/common/async_policy_provider.h
@@ -41,7 +41,6 @@
   void Init(SchemaRegistry* registry) override;
   void Shutdown() override;
   void RefreshPolicies() override;
-  bool IsFirstPolicyLoadComplete(PolicyDomain domain) const override;
 
  private:
   // Helper for RefreshPolicies().
@@ -67,8 +66,6 @@
   // thread. See the implementation for the details.
   base::CancelableOnceClosure refresh_callback_;
 
-  bool first_policies_loaded_;
-
   SEQUENCE_CHECKER(sequence_checker_);
 
   // Used to get a WeakPtr to |this| for the update callback given to the
diff --git a/components/policy/core/common/cloud/cloud_policy_client_unittest.cc b/components/policy/core/common/cloud/cloud_policy_client_unittest.cc
index d754371..d7b58171 100644
--- a/components/policy/core/common/cloud/cloud_policy_client_unittest.cc
+++ b/components/policy/core/common/cloud/cloud_policy_client_unittest.cc
@@ -28,6 +28,7 @@
 #include "components/policy/core/common/cloud/mock_device_management_service.h"
 #include "components/policy/core/common/cloud/mock_signing_service.h"
 #include "components/policy/core/common/cloud/realtime_reporting_job_configuration.h"
+#include "components/policy/core/common/cloud/reporting_job_configuration_base.h"
 #include "components/policy/proto/device_management_backend.pb.h"
 #include "components/version_info/version_info.h"
 #include "google_apis/gaia/gaia_urls.h"
@@ -1528,28 +1529,35 @@
   ASSERT_TRUE(payload);
 
   EXPECT_EQ(kDMToken, *payload->FindStringPath(
-                          RealtimeReportingJobConfiguration::kDmTokenKey));
+                          ReportingJobConfigurationBase::
+                              DeviceDictionaryBuilder::GetDMTokenPath()));
   EXPECT_EQ(client_id_, *payload->FindStringPath(
-                            RealtimeReportingJobConfiguration::kClientIdKey));
+                            ReportingJobConfigurationBase::
+                                DeviceDictionaryBuilder::GetClientIdPath()));
   EXPECT_EQ(policy::GetOSUsername(),
             *payload->FindStringPath(
-                RealtimeReportingJobConfiguration::kMachineUserKey));
+                ReportingJobConfigurationBase::BrowserDictionaryBuilder::
+                    GetMachineUserPath()));
   EXPECT_EQ(version_info::GetVersionNumber(),
             *payload->FindStringPath(
-                RealtimeReportingJobConfiguration::kChromeVersionKey));
+                ReportingJobConfigurationBase::BrowserDictionaryBuilder::
+                    GetChromeVersionPath()));
   EXPECT_EQ(policy::GetOSPlatform(),
             *payload->FindStringPath(
-                RealtimeReportingJobConfiguration::kOsPlatformKey));
+                ReportingJobConfigurationBase::DeviceDictionaryBuilder::
+                    GetOSPlatformPath()));
   EXPECT_EQ(policy::GetOSVersion(),
             *payload->FindStringPath(
-                RealtimeReportingJobConfiguration::kOsVersionKey));
+                ReportingJobConfigurationBase::DeviceDictionaryBuilder::
+                    GetOSVersionPath()));
   EXPECT_FALSE(policy::GetDeviceName().empty());
-  EXPECT_EQ(policy::GetDeviceName(),
-            *payload->FindStringPath(
-                RealtimeReportingJobConfiguration::kDeviceNameKey));
+  EXPECT_EQ(
+      policy::GetDeviceName(),
+      *payload->FindStringPath(ReportingJobConfigurationBase::
+                                   DeviceDictionaryBuilder::GetNamePath()));
 
   base::Value* events =
-      payload->FindPath(RealtimeReportingJobConfiguration::kEventsKey);
+      payload->FindPath(RealtimeReportingJobConfiguration::kEventListKey);
   EXPECT_EQ(base::Value::Type::LIST, events->type());
   EXPECT_EQ(1u, events->GetList().size());
 }
@@ -1558,7 +1566,7 @@
   auto config = std::make_unique<RealtimeReportingJobConfiguration>(
       client_.get(), DMAuth::FromDMToken(kDMToken),
       service_.configuration()->GetRealtimeReportingServerUrl(), false,
-      RealtimeReportingJobConfiguration::Callback());
+      RealtimeReportingJobConfiguration::UploadCompleteCallback());
 
   // Add one report to the config.
   {
@@ -1623,10 +1631,11 @@
   ASSERT_EQ("C:\\User Data\\Profile 1",
             *payload->FindStringPath("profile.profilePath"));
   ASSERT_EQ("1.0.0.0", *payload->FindStringPath("browser.version"));
-  ASSERT_EQ(2u,
-            payload->FindListPath(RealtimeReportingJobConfiguration::kEventsKey)
-                ->GetList()
-                .size());
+  ASSERT_EQ(
+      2u,
+      payload->FindListPath(RealtimeReportingJobConfiguration::kEventListKey)
+          ->GetList()
+          .size());
 }
 
 TEST_F(CloudPolicyClientTest, UploadAppInstallReport) {
diff --git a/components/policy/core/common/cloud/cloud_policy_manager.cc b/components/policy/core/common/cloud/cloud_policy_manager.cc
index 59637e4..0cef3db 100644
--- a/components/policy/core/common/cloud/cloud_policy_manager.cc
+++ b/components/policy/core/common/cloud/cloud_policy_manager.cc
@@ -73,10 +73,6 @@
   return true;
 }
 
-bool CloudPolicyManager::IsFirstPolicyLoadComplete(PolicyDomain domain) const {
-  return store()->first_policies_loaded();
-}
-
 void CloudPolicyManager::RefreshPolicies() {
   if (service()) {
     waiting_for_policy_refresh_ = true;
diff --git a/components/policy/core/common/cloud/cloud_policy_manager.h b/components/policy/core/common/cloud/cloud_policy_manager.h
index c095c37..9626b1b8 100644
--- a/components/policy/core/common/cloud/cloud_policy_manager.h
+++ b/components/policy/core/common/cloud/cloud_policy_manager.h
@@ -58,7 +58,6 @@
   void Init(SchemaRegistry* registry) override;
   void Shutdown() override;
   bool IsInitializationComplete(PolicyDomain domain) const override;
-  bool IsFirstPolicyLoadComplete(PolicyDomain domain) const override;
   void RefreshPolicies() override;
 
   // CloudPolicyStore::Observer:
diff --git a/components/policy/core/common/cloud/cloud_policy_service.cc b/components/policy/core/common/cloud/cloud_policy_service.cc
index d6f0fb2..4cfc856 100644
--- a/components/policy/core/common/cloud/cloud_policy_service.cc
+++ b/components/policy/core/common/cloud/cloud_policy_service.cc
@@ -237,11 +237,6 @@
   if (!initial_policy_refresh_result_.has_value())
     initial_policy_refresh_result_ = success;
 
-  // If there was an error while fetching the policies the first time, assume
-  // that there are no policies until the next retry.
-  if (!success)
-    store_->SetFirstPoliciesLoaded(true);
-
   // Clear state and |refresh_callbacks_| before actually invoking them, s.t.
   // triggering new policy fetches behaves as expected.
   std::vector<RefreshPolicyCallback> callbacks;
diff --git a/components/policy/core/common/cloud/cloud_policy_store.cc b/components/policy/core/common/cloud/cloud_policy_store.cc
index 69bc1694..0144de5 100644
--- a/components/policy/core/common/cloud/cloud_policy_store.cc
+++ b/components/policy/core/common/cloud/cloud_policy_store.cc
@@ -12,7 +12,10 @@
 
 CloudPolicyStore::Observer::~Observer() {}
 
-CloudPolicyStore::CloudPolicyStore() = default;
+CloudPolicyStore::CloudPolicyStore()
+    : status_(STATUS_OK),
+      invalidation_version_(0),
+      is_initialized_(false) {}
 
 CloudPolicyStore::~CloudPolicyStore() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -49,7 +52,6 @@
 
 void CloudPolicyStore::NotifyStoreLoaded() {
   is_initialized_ = true;
-  first_policies_loaded_ |= has_policy();
   // The |external_data_manager_| must be notified first so that when other
   // observers are informed about the changed policies and try to fetch external
   // data referenced by these, the |external_data_manager_| has the required
@@ -62,7 +64,6 @@
 
 void CloudPolicyStore::NotifyStoreError() {
   is_initialized_ = true;
-  first_policies_loaded_ |= has_policy();
   for (auto& observer : observers_)
     observer.OnStoreError(this);
 }
@@ -84,7 +85,4 @@
   NotifyStoreLoaded();
 }
 
-void CloudPolicyStore::SetFirstPoliciesLoaded(bool loaded) {
-  first_policies_loaded_ = loaded;
-}
 }  // namespace policy
diff --git a/components/policy/core/common/cloud/cloud_policy_store.h b/components/policy/core/common/cloud/cloud_policy_store.h
index 9be6d44..af01fee 100644
--- a/components/policy/core/common/cloud/cloud_policy_store.h
+++ b/components/policy/core/common/cloud/cloud_policy_store.h
@@ -94,10 +94,6 @@
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
     return status_;
   }
-  bool first_policies_loaded() const {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    return first_policies_loaded_;
-  }
   CloudPolicyValidatorBase::Status validation_status() const {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
     return validation_result_.get() ? validation_result_->status
@@ -158,9 +154,6 @@
   // tests and remove the override.
   void SetPolicyMapForTesting(const PolicyMap& policy_map);
 
-  // Sets whether or not the first policies for this policy store were loaded.
-  void SetFirstPoliciesLoaded(bool loaded);
-
  protected:
   // Invokes the corresponding callback on all registered observers.
   void NotifyStoreLoaded();
@@ -179,16 +172,14 @@
   std::unique_ptr<enterprise_management::PolicyData> policy_;
 
   // Latest status code.
-  Status status_ = STATUS_OK;
-
-  bool first_policies_loaded_ = false;
+  Status status_;
 
   // Latest validation result.
   std::unique_ptr<CloudPolicyValidatorBase::ValidationResult>
       validation_result_;
 
   // The invalidation version of the last policy stored.
-  int64_t invalidation_version_ = 0;
+  int64_t invalidation_version_;
 
   // The public part of signing key that is used by the currently effective
   // policy. The subclasses should keep its value up to date to correspond to
@@ -200,7 +191,7 @@
  private:
   // Whether the store has completed asynchronous initialization, which is
   // triggered by calling Load().
-  bool is_initialized_ = false;
+  bool is_initialized_;
 
   base::ObserverList<Observer, true>::Unchecked observers_;
 
diff --git a/components/policy/core/common/cloud/device_management_service.h b/components/policy/core/common/cloud/device_management_service.h
index 31b37e81..7da7dc7 100644
--- a/components/policy/core/common/cloud/device_management_service.h
+++ b/components/policy/core/common/cloud/device_management_service.h
@@ -375,7 +375,6 @@
                        scoped_refptr<network::SharedURLLoaderFactory> factory);
   ~JobConfigurationBase() override;
 
- protected:
   // Adds the query parameter to the network request's URL.  If the parameter
   // already exists its value is replaced.
   void AddParameter(const std::string& name, const std::string& value);
@@ -395,7 +394,7 @@
       const std::string& response_body) override;
 
   // Derived classes should return the base URL for the request.
-  virtual GURL GetURL(int last_error) = 0;
+  virtual GURL GetURL(int last_error) const = 0;
 
  private:
   JobType type_;
diff --git a/components/policy/core/common/cloud/dmserver_job_configurations.cc b/components/policy/core/common/cloud/dmserver_job_configurations.cc
index 571c40a..4af4226 100644
--- a/components/policy/core/common/cloud/dmserver_job_configurations.cc
+++ b/components/policy/core/common/cloud/dmserver_job_configurations.cc
@@ -257,7 +257,7 @@
   std::move(callback_).Run(job, code, net_error, response);
 }
 
-GURL DMServerJobConfiguration::GetURL(int last_error) {
+GURL DMServerJobConfiguration::GetURL(int last_error) const {
   // DM server requests always expect a dm_protocol::kParamRetry URL parameter
   // to indicate if this request is a retry.  Furthermore, if so then the
   // dm_protocol::kParamLastError URL parameter is also expected with the value
diff --git a/components/policy/core/common/cloud/dmserver_job_configurations.h b/components/policy/core/common/cloud/dmserver_job_configurations.h
index 7b1d8e5..4c286d93 100644
--- a/components/policy/core/common/cloud/dmserver_job_configurations.h
+++ b/components/policy/core/common/cloud/dmserver_job_configurations.h
@@ -78,7 +78,7 @@
                          const std::string& response_body) override;
 
   // JobConfigurationBase overrides.
-  GURL GetURL(int last_error) override;
+  GURL GetURL(int last_error) const override;
 
   std::string server_url_;
   enterprise_management::DeviceManagementRequest request_;
diff --git a/components/policy/core/common/cloud/realtime_reporting_job_configuration.cc b/components/policy/core/common/cloud/realtime_reporting_job_configuration.cc
index 09844e97..47d22e01 100644
--- a/components/policy/core/common/cloud/realtime_reporting_job_configuration.cc
+++ b/components/policy/core/common/cloud/realtime_reporting_job_configuration.cc
@@ -23,28 +23,13 @@
 const char RealtimeReportingJobConfiguration::kContextKey[] = "context";
 const char RealtimeReportingJobConfiguration::kEventListKey[] = "events";
 
-const char RealtimeReportingJobConfiguration::kBrowserIdKey[] =
-    "browser.browserId";
-const char RealtimeReportingJobConfiguration::kChromeVersionKey[] =
-    "browser.chromeVersion";
-const char RealtimeReportingJobConfiguration::kClientIdKey[] =
-    "device.clientId";
-const char RealtimeReportingJobConfiguration::kDmTokenKey[] = "device.dmToken";
-const char RealtimeReportingJobConfiguration::kEventsKey[] = "events";
-const char RealtimeReportingJobConfiguration::kMachineUserKey[] =
-    "browser.machineUser";
-const char RealtimeReportingJobConfiguration::kOsPlatformKey[] =
-    "device.osPlatform";
-const char RealtimeReportingJobConfiguration::kOsVersionKey[] =
-    "device.osVersion";
+const char RealtimeReportingJobConfiguration::kEventIdKey[] = "eventId";
 const char RealtimeReportingJobConfiguration::kUploadedEventsKey[] =
     "uploadedEventIds";
 const char RealtimeReportingJobConfiguration::kFailedUploadsKey[] =
     "failedUploads";
 const char RealtimeReportingJobConfiguration::kPermanentFailedUploadsKey[] =
     "permanentFailedUploads";
-const char RealtimeReportingJobConfiguration::kEventIdKey[] = "eventId";
-const char RealtimeReportingJobConfiguration::kDeviceNameKey[] = "device.name";
 
 base::Value RealtimeReportingJobConfiguration::BuildReport(
     base::Value events,
@@ -60,14 +45,14 @@
     std::unique_ptr<DMAuth> auth_data,
     const std::string& server_url,
     bool add_connector_url_params,
-    Callback callback)
-    : JobConfigurationBase(TYPE_UPLOAD_REAL_TIME_REPORT,
-                           std::move(auth_data),
-                           base::nullopt,
-                           client->GetURLLoaderFactory()),
-      server_url_(server_url),
-      payload_(base::Value::Type::DICTIONARY),
-      callback_(std::move(callback)) {
+    UploadCompleteCallback callback)
+    : ReportingJobConfigurationBase(TYPE_UPLOAD_REAL_TIME_REPORT,
+                                    std::move(auth_data),
+                                    base::nullopt,
+                                    client->GetURLLoaderFactory(),
+                                    client,
+                                    server_url,
+                                    std::move(callback)) {
   DCHECK(GetAuth().has_dm_token());
 
   AddParameter("key", google_apis::GetAPIKey());
@@ -78,7 +63,7 @@
     AddParameter(enterprise::kUrlParamDeviceToken, client->dm_token());
   }
 
-  InitializePayload(client);
+  InitializePayloadInternal();
 }
 
 RealtimeReportingJobConfiguration::~RealtimeReportingJobConfiguration() {}
@@ -97,100 +82,49 @@
   payload_.MergeDictionary(&*context);
 
   // Append event_list to the payload.
-  base::Value* to = payload_.FindListKey(kEventsKey);
+  base::Value* to = payload_.FindListKey(kEventListKey);
   for (auto& event : event_list->GetList())
     to->Append(std::move(event));
   return true;
 }
 
-void RealtimeReportingJobConfiguration::InitializePayload(
-    CloudPolicyClient* client) {
-  base::FilePath browser_id;
-  if (base::PathService::Get(base::DIR_EXE, &browser_id))
-    payload_.SetStringPath(kBrowserIdKey, browser_id.value());
-
-  payload_.SetStringPath(kDmTokenKey, GetAuth().dm_token());
-  payload_.SetStringPath(kClientIdKey, client->client_id());
-  payload_.SetStringPath(kMachineUserKey, GetOSUsername());
-  payload_.SetStringPath(kChromeVersionKey, version_info::GetVersionNumber());
-  payload_.SetStringPath(kOsPlatformKey, GetOSPlatform());
-  payload_.SetStringPath(kOsVersionKey, GetOSVersion());
-  payload_.SetStringPath(kDeviceNameKey, GetDeviceName());
-  payload_.SetPath(kEventsKey, base::Value(base::Value::Type::LIST));
-}
-
-std::string RealtimeReportingJobConfiguration::GetPayload() {
-  std::string payload_string;
-  base::JSONWriter::Write(payload_, &payload_string);
-  return payload_string;
-}
-
-std::string RealtimeReportingJobConfiguration::GetUmaName() {
-  return "Enterprise.RealtimeReportingSuccess." + GetJobTypeAsString(GetType());
-}
-
-void RealtimeReportingJobConfiguration::OnURLLoadComplete(
-    DeviceManagementService::Job* job,
-    int net_error,
-    int response_code,
-    const std::string& response_body) {
-  base::Optional<base::Value> response = base::JSONReader::Read(response_body);
-
-  // Parse the response even if |response_code| is not a success since the
-  // response data may contain an error message.
-  // Map the net_error/response_code to a DeviceManagementStatus.
-  DeviceManagementStatus code;
-  if (net_error != net::OK) {
-    code = DM_STATUS_REQUEST_FAILED;
-  } else {
-    switch (response_code) {
-      case DeviceManagementService::kSuccess:
-        code = DM_STATUS_SUCCESS;
-        break;
-      case DeviceManagementService::kInvalidArgument:
-        code = DM_STATUS_REQUEST_INVALID;
-        break;
-      case DeviceManagementService::kInvalidAuthCookieOrDMToken:
-        code = DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID;
-        break;
-      case DeviceManagementService::kDeviceManagementNotAllowed:
-        code = DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED;
-        break;
-      default:
-        // Handle all unknown 5xx HTTP error codes as temporary and any other
-        // unknown error as one that needs more time to recover.
-        if (response_code >= 500 && response_code <= 599)
-          code = DM_STATUS_TEMPORARY_UNAVAILABLE;
-        else
-          code = DM_STATUS_HTTP_STATUS_ERROR;
-        break;
-    }
-  }
-
-  base::Value response_value = response ? std::move(*response) : base::Value();
-  std::move(callback_).Run(job, code, net_error, response_value);
-}
-
-GURL RealtimeReportingJobConfiguration::GetURL(int last_error) {
-  return GURL(server_url_);
+void RealtimeReportingJobConfiguration::InitializePayloadInternal() {
+  payload_.SetPath(kEventListKey, base::Value(base::Value::Type::LIST));
 }
 
 DeviceManagementService::Job::RetryMethod
-RealtimeReportingJobConfiguration::ShouldRetry(
+RealtimeReportingJobConfiguration::ShouldRetryInternal(
     int response_code,
     const std::string& response_body) {
-  if (response_code == DeviceManagementService::kSuccess) {
-    const auto failedIds = GetFailedUploadIds(response_body);
-    if (!failedIds.empty()) {
-      return DeviceManagementService::Job::RETRY_WITH_DELAY;
-    }
+  DeviceManagementService::Job::RetryMethod retry_method =
+      DeviceManagementService::Job::NO_RETRY;
+  const auto failedIds = GetFailedUploadIds(response_body);
+  if (!failedIds.empty()) {
+    retry_method = DeviceManagementService::Job::RETRY_WITH_DELAY;
   }
+  return retry_method;
+}
 
-  return JobConfigurationBase::ShouldRetry(response_code, response_body);
+void RealtimeReportingJobConfiguration::OnBeforeRetryInternal(
+    int response_code,
+    const std::string& response_body) {
+  const auto& failedIds = GetFailedUploadIds(response_body);
+  if (!failedIds.empty()) {
+    auto* events = payload_.FindListKey(kEventListKey);
+    // Only keep the elements that temporarily failed their uploads.
+    events->EraseListValueIf([&failedIds](const base::Value& entry) {
+      auto* id = entry.FindStringKey(kEventIdKey);
+      return id && failedIds.find(*id) == failedIds.end();
+    });
+  }
+}
+
+std::string RealtimeReportingJobConfiguration::GetUmaString() const {
+  return "Enterprise.RealtimeReportingSuccess";
 }
 
 std::set<std::string> RealtimeReportingJobConfiguration::GetFailedUploadIds(
-    const std::string& response_body) {
+    const std::string& response_body) const {
   std::set<std::string> failedIds;
   base::Optional<base::Value> response = base::JSONReader::Read(response_body);
   base::Value response_value = response ? std::move(*response) : base::Value();
@@ -206,21 +140,4 @@
   return failedIds;
 }
 
-void RealtimeReportingJobConfiguration::OnBeforeRetry(
-    int response_code,
-    const std::string& response_body) {
-  if (response_code != DeviceManagementService::kSuccess) {
-    return;
-  }
-  const auto& failedIds = GetFailedUploadIds(response_body);
-  if (!failedIds.empty()) {
-    auto* events = payload_.FindListKey(kEventsKey);
-    // Only keep the elements that temporarily failed their uploads.
-    events->EraseListValueIf([&failedIds](const base::Value& entry) {
-      auto* id = entry.FindStringKey(kEventIdKey);
-      return id && failedIds.find(*id) == failedIds.end();
-    });
-  }
-}
-
 }  // namespace policy
diff --git a/components/policy/core/common/cloud/realtime_reporting_job_configuration.h b/components/policy/core/common/cloud/realtime_reporting_job_configuration.h
index 374dd89a..595556b 100644
--- a/components/policy/core/common/cloud/realtime_reporting_job_configuration.h
+++ b/components/policy/core/common/cloud/realtime_reporting_job_configuration.h
@@ -12,6 +12,7 @@
 #include "base/macros.h"
 #include "base/values.h"
 #include "components/policy/core/common/cloud/device_management_service.h"
+#include "components/policy/core/common/cloud/reporting_job_configuration_base.h"
 #include "components/policy/policy_export.h"
 
 namespace policy {
@@ -20,32 +21,17 @@
 class DMAuth;
 
 class POLICY_EXPORT RealtimeReportingJobConfiguration
-    : public JobConfigurationBase {
+    : public ReportingJobConfigurationBase {
  public:
   // Keys used in report dictionary.
   static const char kContextKey[];
   static const char kEventListKey[];
 
-  // Keys used in request payload dictionary.  Public for testing.
-  static const char kBrowserIdKey[];
-  static const char kChromeVersionKey[];
-  static const char kClientIdKey[];
-  static const char kDmTokenKey[];
-  static const char kEventsKey[];
-  static const char kMachineUserKey[];
-  static const char kOsPlatformKey[];
-  static const char kOsVersionKey[];
+  // Keys used to parse the response.
+  static const char kEventIdKey[];
   static const char kUploadedEventsKey[];
   static const char kFailedUploadsKey[];
   static const char kPermanentFailedUploadsKey[];
-  static const char kEventIdKey[];
-  static const char kDeviceNameKey[];
-
-  typedef base::OnceCallback<void(DeviceManagementService::Job* job,
-                                  DeviceManagementStatus code,
-                                  int net_error,
-                                  const base::Value&)>
-      Callback;
 
   // Combines the info given in |events| that corresponds to Event proto, and
   // info given in |context| that corresponds to the Device, Browser and Profile
@@ -61,7 +47,7 @@
                                     std::unique_ptr<DMAuth> auth_data,
                                     const std::string& server_url,
                                     bool add_connector_url_params,
-                                    Callback callback);
+                                    UploadCompleteCallback callback);
 
   ~RealtimeReportingJobConfiguration() override;
 
@@ -76,33 +62,24 @@
   // Returns true if the report was added successfully.
   bool AddReport(base::Value report);
 
-  // DeviceManagementService::JobConfiguration.
-  std::string GetPayload() override;
-  std::string GetUmaName() override;
-  DeviceManagementService::Job::RetryMethod ShouldRetry(
+ protected:
+  // ReportingJobConfigurationBase
+  DeviceManagementService::Job::RetryMethod ShouldRetryInternal(
       int response_code,
-      const std::string& response_body) override;
-  void OnBeforeRetry(int response_code,
-                     const std::string& response_body) override;
-  void OnURLLoadComplete(DeviceManagementService::Job* job,
-                         int net_error,
-                         int response_code,
-                         const std::string& response_body) override;
+      const std::string& response) override;
+  void OnBeforeRetryInternal(int response_code,
+                             const std::string& response_body) override;
 
-  // JobConfigurationBase overrides.
-  GURL GetURL(int last_error) override;
+  std::string GetUmaString() const override;
 
  private:
   // Does one time initialization of the payload when the configuration is
   // created.
-  void InitializePayload(CloudPolicyClient* client);
+  void InitializePayloadInternal();
 
   // Gathers the ids of the uploads that failed
-  std::set<std::string> GetFailedUploadIds(const std::string& response_body);
-
-  std::string server_url_;
-  base::Value payload_;
-  Callback callback_;
+  std::set<std::string> GetFailedUploadIds(
+      const std::string& response_body) const;
 
   DISALLOW_COPY_AND_ASSIGN(RealtimeReportingJobConfiguration);
 };
diff --git a/components/policy/core/common/cloud/realtime_reporting_job_configuration_unittest.cc b/components/policy/core/common/cloud/realtime_reporting_job_configuration_unittest.cc
index 9387fb0b..5641a78a 100644
--- a/components/policy/core/common/cloud/realtime_reporting_job_configuration_unittest.cc
+++ b/components/policy/core/common/cloud/realtime_reporting_job_configuration_unittest.cc
@@ -175,29 +175,35 @@
       base::JSONReader::Read(configuration_.GetPayload());
   EXPECT_TRUE(payload.has_value());
   EXPECT_EQ(kDummyToken, *payload->FindStringPath(
-                             RealtimeReportingJobConfiguration::kDmTokenKey));
-  EXPECT_EQ(client_.client_id(),
-            *payload->FindStringPath(
-                RealtimeReportingJobConfiguration::kClientIdKey));
+                             ReportingJobConfigurationBase::
+                                 DeviceDictionaryBuilder::GetDMTokenPath()));
+  EXPECT_EQ(
+      client_.client_id(),
+      *payload->FindStringPath(ReportingJobConfigurationBase::
+                                   DeviceDictionaryBuilder::GetClientIdPath()));
   EXPECT_EQ(GetOSUsername(),
             *payload->FindStringPath(
-                RealtimeReportingJobConfiguration::kMachineUserKey));
+                ReportingJobConfigurationBase::BrowserDictionaryBuilder::
+                    GetMachineUserPath()));
   EXPECT_EQ(version_info::GetVersionNumber(),
             *payload->FindStringPath(
-                RealtimeReportingJobConfiguration::kChromeVersionKey));
+                ReportingJobConfigurationBase::BrowserDictionaryBuilder::
+                    GetChromeVersionPath()));
   EXPECT_EQ(GetOSPlatform(),
             *payload->FindStringPath(
-                RealtimeReportingJobConfiguration::kOsPlatformKey));
+                ReportingJobConfigurationBase::DeviceDictionaryBuilder::
+                    GetOSPlatformPath()));
   EXPECT_EQ(GetOSVersion(),
             *payload->FindStringPath(
-                RealtimeReportingJobConfiguration::kOsVersionKey));
+                ReportingJobConfigurationBase::DeviceDictionaryBuilder::
+                    GetOSVersionPath()));
   EXPECT_FALSE(GetDeviceName().empty());
-  EXPECT_EQ(GetDeviceName(),
-            *payload->FindStringPath(
-                RealtimeReportingJobConfiguration::kDeviceNameKey));
+  EXPECT_EQ(GetDeviceName(), *payload->FindStringPath(
+                                 ReportingJobConfigurationBase::
+                                     DeviceDictionaryBuilder::GetNamePath()));
 
   base::Value* events =
-      payload->FindListKey(RealtimeReportingJobConfiguration::kEventsKey);
+      payload->FindListKey(RealtimeReportingJobConfiguration::kEventListKey);
   EXPECT_EQ(ids.size(), events->GetList().size());
   int i = -1;
   for (const auto& event : events->GetList()) {
@@ -314,7 +320,7 @@
   base::Optional<base::Value> payload =
       base::JSONReader::Read(configuration_.GetPayload());
   base::Value* events =
-      payload->FindListKey(RealtimeReportingJobConfiguration::kEventsKey);
+      payload->FindListKey(RealtimeReportingJobConfiguration::kEventListKey);
   EXPECT_EQ(1u, events->GetList().size());
   auto& event = events->GetList()[0];
   EXPECT_EQ(ids[1], *event.FindStringKey(kEventId));
diff --git a/components/policy/core/common/cloud/reporting_job_configuration_base.cc b/components/policy/core/common/cloud/reporting_job_configuration_base.cc
new file mode 100644
index 0000000..aa0f0e8b
--- /dev/null
+++ b/components/policy/core/common/cloud/reporting_job_configuration_base.cc
@@ -0,0 +1,279 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/reporting_job_configuration_base.h"
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/macros.h"
+#include "base/optional.h"
+#include "base/path_service.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "components/policy/core/common/cloud/cloud_policy_client.h"
+#include "components/policy/core/common/cloud/cloud_policy_util.h"
+#include "components/policy/core/common/cloud/device_management_service.h"
+#include "components/policy/core/common/cloud/dm_auth.h"
+#include "components/policy/policy_export.h"
+#include "components/version_info/version_info.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+
+namespace policy {
+
+// Strings for |DeviceDictionaryBuilder|.
+const char
+    ReportingJobConfigurationBase::DeviceDictionaryBuilder::kDeviceKey[] =
+        "device";
+const char ReportingJobConfigurationBase::DeviceDictionaryBuilder::kDMToken[] =
+    "dmToken";
+const char ReportingJobConfigurationBase::DeviceDictionaryBuilder::kClientId[] =
+    "clientId";
+const char
+    ReportingJobConfigurationBase::DeviceDictionaryBuilder::kOSVersion[] =
+        "osVersion";
+const char
+    ReportingJobConfigurationBase::DeviceDictionaryBuilder::kOSPlatform[] =
+        "osPlatform";
+const char ReportingJobConfigurationBase::DeviceDictionaryBuilder::kName[] =
+    "name";
+
+// static
+base::Value
+ReportingJobConfigurationBase::DeviceDictionaryBuilder::BuildDeviceDictionary(
+    const std::string& dm_token,
+    const std::string& client_id) {
+  base::Value device_dictionary{base::Value::Type::DICTIONARY};
+  device_dictionary.SetStringKey(kDMToken, dm_token);
+  device_dictionary.SetStringKey(kClientId, client_id);
+  device_dictionary.SetStringKey(kOSVersion, GetOSVersion());
+  device_dictionary.SetStringKey(kOSPlatform, GetOSPlatform());
+  device_dictionary.SetStringKey(kName, GetDeviceName());
+  return device_dictionary;
+}
+
+// static
+std::string
+ReportingJobConfigurationBase::DeviceDictionaryBuilder::GetDMTokenPath() {
+  return GetStringPath(kDMToken);
+}
+
+// static
+std::string
+ReportingJobConfigurationBase::DeviceDictionaryBuilder::GetClientIdPath() {
+  return GetStringPath(kClientId);
+}
+
+// static
+std::string
+ReportingJobConfigurationBase::DeviceDictionaryBuilder::GetOSVersionPath() {
+  return GetStringPath(kOSVersion);
+}
+
+// static
+std::string
+ReportingJobConfigurationBase::DeviceDictionaryBuilder::GetOSPlatformPath() {
+  return GetStringPath(kOSPlatform);
+}
+
+// static
+std::string
+ReportingJobConfigurationBase::DeviceDictionaryBuilder::GetNamePath() {
+  return GetStringPath(kName);
+}
+
+// static
+std::string
+ReportingJobConfigurationBase::DeviceDictionaryBuilder::GetStringPath(
+    base::StringPiece leaf_name) {
+  return base::JoinString({kDeviceKey, leaf_name}, ".");
+}
+
+// Strings for |BrowserDictionaryBuilder|.
+const char
+    ReportingJobConfigurationBase::BrowserDictionaryBuilder::kBrowserKey[] =
+        "browser";
+const char
+    ReportingJobConfigurationBase::BrowserDictionaryBuilder::kBrowserId[] =
+        "browserId";
+const char
+    ReportingJobConfigurationBase::BrowserDictionaryBuilder::kUserAgent[] =
+        "userAgent";
+const char
+    ReportingJobConfigurationBase::BrowserDictionaryBuilder::kMachineUser[] =
+        "machineUser";
+const char
+    ReportingJobConfigurationBase::BrowserDictionaryBuilder::kChromeVersion[] =
+        "chromeVersion";
+
+// static
+base::Value ReportingJobConfigurationBase::BrowserDictionaryBuilder::
+    BuildBrowserDictionary() {
+  base::Value browser_dictionary{base::Value::Type::DICTIONARY};
+
+  base::FilePath browser_id;
+  if (base::PathService::Get(base::DIR_EXE, &browser_id)) {
+    browser_dictionary.SetStringKey(kBrowserId, browser_id.value());
+  }
+
+  browser_dictionary.SetStringKey(kMachineUser, GetOSUsername());
+  browser_dictionary.SetStringKey(kChromeVersion,
+                                  version_info::GetVersionNumber());
+  return browser_dictionary;
+}
+
+// static
+std::string
+ReportingJobConfigurationBase::BrowserDictionaryBuilder::GetBrowserIdPath() {
+  return GetStringPath(kBrowserId);
+}
+
+// static
+std::string
+ReportingJobConfigurationBase::BrowserDictionaryBuilder::GetUserAgentPath() {
+  return GetStringPath(kUserAgent);
+}
+
+// static
+std::string
+ReportingJobConfigurationBase::BrowserDictionaryBuilder::GetMachineUserPath() {
+  return GetStringPath(kMachineUser);
+}
+
+// static
+std::string ReportingJobConfigurationBase::BrowserDictionaryBuilder::
+    GetChromeVersionPath() {
+  return GetStringPath(kChromeVersion);
+}
+
+// static
+std::string
+ReportingJobConfigurationBase::BrowserDictionaryBuilder::GetStringPath(
+    base::StringPiece leaf_name) {
+  return base::JoinString({kBrowserKey, leaf_name}, ".");
+}
+
+std::string ReportingJobConfigurationBase::GetPayload() {
+  std::string payload_string;
+  base::JSONWriter::Write(payload_, &payload_string);
+  return payload_string;
+}
+
+std::string ReportingJobConfigurationBase::GetUmaName() {
+  return GetUmaString() + GetJobTypeAsString(GetType());
+}
+
+DeviceManagementService::Job::RetryMethod
+ReportingJobConfigurationBase::ShouldRetry(int response_code,
+                                           const std::string& response_body) {
+  // If the request wasn't successfully processed at all, resending it won't do
+  // anything. Don't retry.
+  if (response_code != DeviceManagementService::kSuccess) {
+    return DeviceManagementService::Job::NO_RETRY;
+  }
+
+  // Allow child to determine if any portion of the message should be retried.
+  return ShouldRetryInternal(response_code, response_body);
+}
+
+void ReportingJobConfigurationBase::OnBeforeRetry(
+    int response_code,
+    const std::string& response_body) {
+  // If the request wasn't successful, don't try to retry.
+  if (response_code != DeviceManagementService::kSuccess) {
+    return;
+  }
+
+  OnBeforeRetryInternal(response_code, response_body);
+}
+
+void ReportingJobConfigurationBase::OnURLLoadComplete(
+    DeviceManagementService::Job* job,
+    int net_error,
+    int response_code,
+    const std::string& response_body) {
+  base::Optional<base::Value> response = base::JSONReader::Read(response_body);
+
+  // Parse the response even if |response_code| is not a success since the
+  // response data may contain an error message.
+  // Map the net_error/response_code to a DeviceManagementStatus.
+  DeviceManagementStatus code;
+  if (net_error != net::OK) {
+    code = DM_STATUS_REQUEST_FAILED;
+  } else {
+    switch (response_code) {
+      case DeviceManagementService::kSuccess:
+        code = DM_STATUS_SUCCESS;
+        break;
+      case DeviceManagementService::kInvalidArgument:
+        code = DM_STATUS_REQUEST_INVALID;
+        break;
+      case DeviceManagementService::kInvalidAuthCookieOrDMToken:
+        code = DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID;
+        break;
+      case DeviceManagementService::kDeviceManagementNotAllowed:
+        code = DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED;
+        break;
+      default:
+        // Handle all unknown 5xx HTTP error codes as temporary and any other
+        // unknown error as one that needs more time to recover.
+        if (response_code >= 500 && response_code <= 599)
+          code = DM_STATUS_TEMPORARY_UNAVAILABLE;
+        else
+          code = DM_STATUS_HTTP_STATUS_ERROR;
+        break;
+    }
+  }
+
+  base::Value response_value = response ? std::move(*response) : base::Value();
+  std::move(callback_).Run(job, code, net_error, response_value);
+}
+
+DeviceManagementService::Job::RetryMethod
+ReportingJobConfigurationBase::ShouldRetryInternal(
+    int response_code,
+    const std::string& response_body) {
+  return JobConfigurationBase::ShouldRetry(response_code, response_body);
+}
+
+void ReportingJobConfigurationBase::OnBeforeRetryInternal(
+    int response_code,
+    const std::string& response_body) {}
+
+GURL ReportingJobConfigurationBase::GetURL(int last_error) const {
+  return GURL(server_url_);
+}
+
+ReportingJobConfigurationBase::ReportingJobConfigurationBase(
+    JobType type,
+    std::unique_ptr<DMAuth> auth_data,
+    base::Optional<std::string> oauth_token,
+    scoped_refptr<network::SharedURLLoaderFactory> factory,
+    CloudPolicyClient* client,
+    const std::string& server_url,
+    UploadCompleteCallback callback)
+    : JobConfigurationBase(type,
+                           std::move(auth_data),
+                           std::move(oauth_token),
+                           factory),
+      payload_(base::Value::Type::DICTIONARY),
+      server_url_(server_url),
+      callback_(std::move(callback)) {
+  InitializePayload(client);
+}
+
+ReportingJobConfigurationBase::~ReportingJobConfigurationBase() = default;
+
+void ReportingJobConfigurationBase::InitializePayload(
+    CloudPolicyClient* client) {
+  payload_.SetKey(DeviceDictionaryBuilder::kDeviceKey,
+                  DeviceDictionaryBuilder::BuildDeviceDictionary(
+                      GetAuth().dm_token(), client->client_id()));
+  payload_.SetKey(BrowserDictionaryBuilder::kBrowserKey,
+                  BrowserDictionaryBuilder::BuildBrowserDictionary());
+}
+
+}  // namespace policy
diff --git a/components/policy/core/common/cloud/reporting_job_configuration_base.h b/components/policy/core/common/cloud/reporting_job_configuration_base.h
new file mode 100644
index 0000000..5ddd361
--- /dev/null
+++ b/components/policy/core/common/cloud/reporting_job_configuration_base.h
@@ -0,0 +1,153 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_REPORTING_JOB_CONFIGURATION_BASE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_REPORTING_JOB_CONFIGURATION_BASE_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/optional.h"
+#include "base/values.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/device_management_service.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+class CloudPolicyClient;
+class DMAuth;
+
+// Base for common elements in JobConfigurations for the Reporting pipeline.
+// Ensures the following elements are added to each request.
+// Device dictionary:
+// "device": {
+//   "dmToken": "abcdef1234",
+//   "clientId": "abcdef1234",
+//   "osVersion": "10.0.0.0",
+//   "osPlatform": "Windows",
+//   "name": "George"
+// }
+//
+// Browser dictionary:
+// "browser": {
+//   "browserId": "abcdef1234",
+//   "chromeVersion": "10.0.0.0",
+//   "machineUser": "abcdef1234"
+// }
+class POLICY_EXPORT ReportingJobConfigurationBase
+    : public JobConfigurationBase {
+ public:
+  // Callback used once the job is complete.
+  using UploadCompleteCallback =
+      base::OnceCallback<void(DeviceManagementService::Job* job,
+                              DeviceManagementStatus code,
+                              int net_error,
+                              const base::Value&)>;
+
+  // Builds a Device dictionary for uploading information about the device to
+  // the server.
+  class POLICY_EXPORT DeviceDictionaryBuilder {
+   public:
+    // Dictionary Key Name
+    static const char kDeviceKey[];
+
+    static base::Value BuildDeviceDictionary(const std::string& dm_token,
+                                             const std::string& client_id);
+
+    static std::string GetDMTokenPath();
+    static std::string GetClientIdPath();
+    static std::string GetOSVersionPath();
+    static std::string GetOSPlatformPath();
+    static std::string GetNamePath();
+
+   private:
+    static std::string GetStringPath(base::StringPiece leaf_name);
+
+    // Keys used in Device dictionary.
+    static const char kDMToken[];
+    static const char kClientId[];
+    static const char kOSVersion[];
+    static const char kOSPlatform[];
+    static const char kName[];
+  };
+
+  // Builds a Browser dictionary for uploading information about the browser to
+  // the server.
+  class POLICY_EXPORT BrowserDictionaryBuilder {
+   public:
+    // Dictionary Key Name
+    static const char kBrowserKey[];
+
+    static base::Value BuildBrowserDictionary();
+
+    static std::string GetBrowserIdPath();
+    static std::string GetUserAgentPath();
+    static std::string GetMachineUserPath();
+    static std::string GetChromeVersionPath();
+
+   private:
+    static std::string GetStringPath(base::StringPiece leaf_name);
+
+    // Keys used in Browser dictionary.
+    static const char kBrowserId[];
+    static const char kUserAgent[];
+    static const char kMachineUser[];
+    static const char kChromeVersion[];
+  };
+
+  // DeviceManagementService::JobConfiguration
+  std::string GetPayload() override;
+  std::string GetUmaName() override;
+  DeviceManagementService::Job::RetryMethod ShouldRetry(
+      int response_code,
+      const std::string& response_body) override;
+  void OnBeforeRetry(int reponse_code,
+                     const std::string& response_body) override;
+  void OnURLLoadComplete(DeviceManagementService::Job* job,
+                         int net_error,
+                         int response_code,
+                         const std::string& response_body) override;
+  GURL GetURL(int last_error) const override;
+
+ protected:
+  ReportingJobConfigurationBase(
+      JobType type,
+      std::unique_ptr<DMAuth> auth_data,
+      base::Optional<std::string> oauth_token,
+      scoped_refptr<network::SharedURLLoaderFactory> factory,
+      CloudPolicyClient* client,
+      const std::string& server_url,
+      UploadCompleteCallback callback);
+  ~ReportingJobConfigurationBase() override;
+
+  // Allows children to determine if a retry should be done.
+  virtual DeviceManagementService::Job::RetryMethod ShouldRetryInternal(
+      int response_code,
+      const std::string& response_body);
+
+  // Allows children to perform actions before a retry.
+  virtual void OnBeforeRetryInternal(int response_code,
+                                     const std::string& response_body);
+
+  // Returns an identifying string for UMA.
+  virtual std::string GetUmaString() const = 0;
+
+  base::Value payload_;
+
+ private:
+  // Initializes request payload.
+  void InitializePayload(CloudPolicyClient* client);
+
+  const std::string server_url_;
+  UploadCompleteCallback callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(ReportingJobConfigurationBase);
+};
+
+}  // namespace policy
+
+#endif  // COMPONENTS_POLICY_CORE_COMMON_CLOUD_REPORTING_JOB_CONFIGURATION_BASE_H_
diff --git a/components/policy/core/common/cloud/user_cloud_policy_manager.cc b/components/policy/core/common/cloud/user_cloud_policy_manager.cc
index 3a86a3e..4201a729 100644
--- a/components/policy/core/common/cloud/user_cloud_policy_manager.cc
+++ b/components/policy/core/common/cloud/user_cloud_policy_manager.cc
@@ -52,11 +52,6 @@
   store_->SetSigninAccountId(account_id);
 }
 
-void UserCloudPolicyManager::SetPoliciesRequired(bool required) {
-  policies_required_ = required;
-  RefreshPolicies();
-}
-
 void UserCloudPolicyManager::Connect(
     PrefService* local_state,
     std::unique_ptr<CloudPolicyClient> client) {
@@ -100,7 +95,6 @@
   // all external data references have been removed, causing the
   // |external_data_manager_| to clear its cache as well.
   store_->Clear();
-  SetPoliciesRequired(false);
 }
 
 bool UserCloudPolicyManager::IsClientRegistered() const {
@@ -125,10 +119,4 @@
 #endif
 }
 
-bool UserCloudPolicyManager::IsFirstPolicyLoadComplete(
-    PolicyDomain domain) const {
-  return !policies_required_ ||
-         CloudPolicyManager::IsFirstPolicyLoadComplete(domain);
-}
-
 }  // namespace policy
diff --git a/components/policy/core/common/cloud/user_cloud_policy_manager.h b/components/policy/core/common/cloud/user_cloud_policy_manager.h
index 2841f96..d3c188b9 100644
--- a/components/policy/core/common/cloud/user_cloud_policy_manager.h
+++ b/components/policy/core/common/cloud/user_cloud_policy_manager.h
@@ -50,11 +50,6 @@
 
   void SetSigninAccountId(const AccountId& account_id);
 
-  // Sets whether or not policies are required for this policy manager.
-  // This might be set to false if the user profile is an unmanaged consumer
-  // profile.
-  void SetPoliciesRequired(bool required);
-
   // Initializes the cloud connection. |local_state| must stay valid until this
   // object is deleted or DisconnectAndRemovePolicy() gets called. Virtual for
   // mocking.
@@ -80,15 +75,10 @@
       DeviceManagementService* device_management_service,
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
 
-  // ConfigurationPolicyProvider:
-  bool IsFirstPolicyLoadComplete(PolicyDomain domain) const override;
-
  private:
   // CloudPolicyManager:
   void GetChromePolicy(PolicyMap* policy_map) override;
 
-  bool policies_required_;
-
   // Typed pointer to the store owned by UserCloudPolicyManager. Note that
   // CloudPolicyManager only keeps a plain CloudPolicyStore pointer.
   std::unique_ptr<UserCloudPolicyStore> store_;
diff --git a/components/policy/core/common/cloud/user_cloud_policy_manager_unittest.cc b/components/policy/core/common/cloud/user_cloud_policy_manager_unittest.cc
index 89fcc7d0..001aeef 100644
--- a/components/policy/core/common/cloud/user_cloud_policy_manager_unittest.cc
+++ b/components/policy/core/common/cloud/user_cloud_policy_manager_unittest.cc
@@ -82,7 +82,7 @@
   // called.
   CreateManager();
   store_->policy_map_.CopyFrom(policy_map_);
-  EXPECT_CALL(observer_, OnUpdatePolicy(manager_.get())).Times(2);
+  EXPECT_CALL(observer_, OnUpdatePolicy(manager_.get()));
   store_->NotifyStoreLoaded();
   EXPECT_TRUE(expected_bundle_.Equals(manager_->policies()));
   EXPECT_TRUE(manager_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
diff --git a/components/policy/core/common/command_line_policy_provider.cc b/components/policy/core/common/command_line_policy_provider.cc
index 7b262cc..7814a44 100644
--- a/components/policy/core/common/command_line_policy_provider.cc
+++ b/components/policy/core/common/command_line_policy_provider.cc
@@ -48,15 +48,9 @@
 
 void CommandLinePolicyProvider::RefreshPolicies() {
   std::unique_ptr<PolicyBundle> bundle = loader_.Load();
-  first_policies_loaded_ = true;
   UpdatePolicy(std::move(bundle));
 }
 
-bool CommandLinePolicyProvider::IsFirstPolicyLoadComplete(
-    PolicyDomain domain) const {
-  return first_policies_loaded_;
-}
-
 CommandLinePolicyProvider::CommandLinePolicyProvider(
     const base::CommandLine& command_line)
     : loader_(command_line) {
diff --git a/components/policy/core/common/command_line_policy_provider.h b/components/policy/core/common/command_line_policy_provider.h
index fb6cade..b1af43f 100644
--- a/components/policy/core/common/command_line_policy_provider.h
+++ b/components/policy/core/common/command_line_policy_provider.h
@@ -35,12 +35,10 @@
 
   // ConfigurationPolicyProvider implementation.
   void RefreshPolicies() override;
-  bool IsFirstPolicyLoadComplete(PolicyDomain domain) const override;
 
  private:
   explicit CommandLinePolicyProvider(const base::CommandLine& command_line);
 
-  bool first_policies_loaded_;
   PolicyLoaderCommandLine loader_;
 };
 
diff --git a/components/policy/core/common/configuration_policy_provider.cc b/components/policy/core/common/configuration_policy_provider.cc
index 4396245..0a3fa12 100644
--- a/components/policy/core/common/configuration_policy_provider.cc
+++ b/components/policy/core/common/configuration_policy_provider.cc
@@ -41,11 +41,6 @@
   return true;
 }
 
-bool ConfigurationPolicyProvider::IsFirstPolicyLoadComplete(
-    PolicyDomain domain) const {
-  return true;
-}
-
 void ConfigurationPolicyProvider::UpdatePolicy(
     std::unique_ptr<PolicyBundle> bundle) {
   if (bundle) {
diff --git a/components/policy/core/common/configuration_policy_provider.h b/components/policy/core/common/configuration_policy_provider.h
index f92875c..9f49b261 100644
--- a/components/policy/core/common/configuration_policy_provider.h
+++ b/components/policy/core/common/configuration_policy_provider.h
@@ -60,12 +60,6 @@
   // case implementations need to do asynchronous operations for initialization.
   virtual bool IsInitializationComplete(PolicyDomain domain) const;
 
-  // Check whether this provider has loaded its first policies for the given
-  // policy |domain|. This is used to detect whether policies have been loaded
-  // is done in case implementations need to do asynchronous operations to get
-  // the policies.
-  virtual bool IsFirstPolicyLoadComplete(PolicyDomain domain) const;
-
   // Asks the provider to refresh its policies. All the updates caused by this
   // call will be visible on the next call of OnUpdatePolicy on the observers,
   // which are guaranteed to happen even if the refresh fails.
diff --git a/components/policy/core/common/mock_configuration_policy_provider.h b/components/policy/core/common/mock_configuration_policy_provider.h
index ebba2345..76dbdb0f 100644
--- a/components/policy/core/common/mock_configuration_policy_provider.h
+++ b/components/policy/core/common/mock_configuration_policy_provider.h
@@ -24,7 +24,6 @@
   ~MockConfigurationPolicyProvider() override;
 
   MOCK_CONST_METHOD1(IsInitializationComplete, bool(PolicyDomain domain));
-  MOCK_CONST_METHOD1(IsFirstPolicyLoadComplete, bool(PolicyDomain domain));
   MOCK_METHOD0(RefreshPolicies, void());
 
   // Make public for tests.
diff --git a/components/policy/core/common/mock_policy_service.h b/components/policy/core/common/mock_policy_service.h
index 738be0e..9c62bbf5 100644
--- a/components/policy/core/common/mock_policy_service.h
+++ b/components/policy/core/common/mock_policy_service.h
@@ -20,7 +20,6 @@
                                      const PolicyMap& previous,
                                      const PolicyMap& current));
   MOCK_METHOD1(OnPolicyServiceInitialized, void(PolicyDomain));
-  MOCK_METHOD1(OnFirstPoliciesLoaded, void(PolicyDomain));
 };
 
 class MockPolicyServiceProviderUpdateObserver
@@ -46,7 +45,6 @@
 
   MOCK_CONST_METHOD1(GetPolicies, const PolicyMap&(const PolicyNamespace&));
   MOCK_CONST_METHOD1(IsInitializationComplete, bool(PolicyDomain domain));
-  MOCK_CONST_METHOD1(IsFirstPolicyLoadComplete, bool(PolicyDomain domain));
   MOCK_METHOD1(RefreshPolicies, void(base::OnceClosure));
 
 #if defined(OS_ANDROID)
diff --git a/components/policy/core/common/policy_service.h b/components/policy/core/common/policy_service.h
index a17c497..d6eb4bb 100644
--- a/components/policy/core/common/policy_service.h
+++ b/components/policy/core/common/policy_service.h
@@ -48,19 +48,9 @@
     // Invoked at most once for each |domain|, when the PolicyService becomes
     // ready. If IsInitializationComplete() is false, then this will be invoked
     // once all the policy providers have finished loading their policies for
-    // |domain|. This does not handle failure to load policies from some
-    // providers, so it is possible for for the policy service to be initialised
-    // if the providers failed for example to load its policies cache.
+    // |domain|.
     virtual void OnPolicyServiceInitialized(PolicyDomain domain) {}
 
-    // Invoked at most once for each |domain|, when the PolicyService becomes
-    // ready. If IsFirstPolicyLoadComplete() is false, then this will be invoked
-    // once all the policy providers have finished loading their policies for
-    // |domain|. The difference from |OnPolicyServiceInitialized| is that this
-    // will wait for cloud policies to be fetched when the local cache is not
-    // available, which may take some time depending on user's network.
-    virtual void OnFirstPoliciesLoaded(PolicyDomain domain) {}
-
    protected:
     virtual ~Observer() {}
   };
@@ -98,7 +88,7 @@
 
   // The PolicyService loads policy from several sources, and some require
   // asynchronous loads. IsInitializationComplete() returns true once all
-  // sources have been initialized for the given |domain|.
+  // sources have loaded their policies for the given |domain|.
   // It is safe to read policy from the PolicyService even if
   // IsInitializationComplete() is false; there will be an OnPolicyUpdated()
   // notification once new policies become available.
@@ -110,20 +100,6 @@
   // OnPolicyServiceInitialized() notification.
   virtual bool IsInitializationComplete(PolicyDomain domain) const = 0;
 
-  // The PolicyService loads policy from several sources, and some require
-  // asynchronous loads. IsFirstPolicyLoadComplete() returns true once all
-  // sources have loaded their initial policies for the given |domain|.
-  // It is safe to read policy from the PolicyService even if
-  // IsFirstPolicyLoadComplete() is false; there will be an OnPolicyUpdated()
-  // notification once new policies become available.
-  //
-  // OnFirstPoliciesLoaded() is called when IsFirstPolicyLoadComplete()
-  // becomes true, which happens at most once for each domain.
-  // If IsFirstPolicyLoadComplete() is already true for |domain| when an
-  // Observer is registered, then that Observer will not receive an
-  // OnFirstPoliciesLoaded() notification.
-  virtual bool IsFirstPolicyLoadComplete(PolicyDomain domain) const = 0;
-
   // Asks the PolicyService to reload policy from all available policy sources.
   // |callback| is invoked once every source has reloaded its policies, and
   // GetPolicies() is guaranteed to return the updated values at that point.
diff --git a/components/policy/core/common/policy_service_impl.cc b/components/policy/core/common/policy_service_impl.cc
index 8c42e01..a1055d6 100644
--- a/components/policy/core/common/policy_service_impl.cc
+++ b/components/policy/core/common/policy_service_impl.cc
@@ -102,11 +102,14 @@
       migrators_(std::move(migrators)),
       initialization_throttled_(initialization_throttled) {
   for (int domain = 0; domain < POLICY_DOMAIN_SIZE; ++domain)
-    policy_domain_status_[domain] = PolicyDomainStatus::kUninitialized;
-
-  for (auto* provider : providers_)
+    initialization_complete_[domain] = true;
+  for (auto* provider : providers_) {
     provider->AddObserver(this);
-  CheckPolicyDomainStatus();
+    for (int domain = 0; domain < POLICY_DOMAIN_SIZE; ++domain) {
+      initialization_complete_[domain] &=
+          provider->IsInitializationComplete(static_cast<PolicyDomain>(domain));
+    }
+  }
   // There are no observers yet, but calls to GetPolicies() should already get
   // the processed policy values.
   MergeAndTriggerUpdates();
@@ -175,15 +178,9 @@
 bool PolicyServiceImpl::IsInitializationComplete(PolicyDomain domain) const {
   DCHECK(thread_checker_.CalledOnValidThread());
   DCHECK(domain >= 0 && domain < POLICY_DOMAIN_SIZE);
-  return !initialization_throttled_ &&
-         policy_domain_status_[domain] != PolicyDomainStatus::kUninitialized;
-}
-
-bool PolicyServiceImpl::IsFirstPolicyLoadComplete(PolicyDomain domain) const {
-  DCHECK(thread_checker_.CalledOnValidThread());
-  DCHECK(domain >= 0 && domain < POLICY_DOMAIN_SIZE);
-  return !initialization_throttled_ &&
-         policy_domain_status_[domain] == PolicyDomainStatus::kPolicyReady;
+  if (initialization_throttled_)
+    return false;
+  return initialization_complete_[domain];
 }
 
 void PolicyServiceImpl::RefreshPolicies(base::OnceClosure callback) {
@@ -225,7 +222,7 @@
 
   initialization_throttled_ = false;
   for (int domain = 0; domain < POLICY_DOMAIN_SIZE; ++domain)
-    MaybeNotifyPolicyDomainStatusChange(static_cast<PolicyDomain>(domain));
+    MaybeNotifyInitializationComplete(static_cast<PolicyDomain>(domain));
 }
 
 void PolicyServiceImpl::OnUpdatePolicy(ConfigurationPolicyProvider* provider) {
@@ -360,56 +357,46 @@
   for (; it_old != end_old; ++it_old)
     NotifyNamespaceUpdated(it_old->first, *it_old->second, kEmpty);
 
-  CheckPolicyDomainStatus();
+  CheckInitializationComplete();
   CheckRefreshComplete();
   NotifyProviderUpdatesPropagated();
 }
 
-void PolicyServiceImpl::CheckPolicyDomainStatus() {
+void PolicyServiceImpl::CheckInitializationComplete() {
   DCHECK(thread_checker_.CalledOnValidThread());
 
   // Check if all the providers just became initialized for each domain; if so,
-  // notify that domain's observers. If they were initialized, check if they had
-  // their first policies loaded.
+  // notify that domain's observers.
   for (int domain = 0; domain < POLICY_DOMAIN_SIZE; ++domain) {
-    PolicyDomain policy_domain = static_cast<PolicyDomain>(domain);
-    if (policy_domain_status_[domain] == PolicyDomainStatus::kPolicyReady)
+    if (initialization_complete_[domain])
       continue;
 
-    PolicyDomainStatus new_status = PolicyDomainStatus::kPolicyReady;
+    PolicyDomain policy_domain = static_cast<PolicyDomain>(domain);
 
+    bool all_complete = true;
     for (auto* provider : providers_) {
       if (!provider->IsInitializationComplete(policy_domain)) {
-        new_status = PolicyDomainStatus::kUninitialized;
+        all_complete = false;
         break;
-      } else if (!provider->IsFirstPolicyLoadComplete(policy_domain)) {
-        new_status = PolicyDomainStatus::kInitialized;
       }
     }
-
-    if (new_status == policy_domain_status_[domain])
-      continue;
-
-    policy_domain_status_[domain] = new_status;
-    MaybeNotifyPolicyDomainStatusChange(policy_domain);
+    if (all_complete) {
+      initialization_complete_[domain] = true;
+      MaybeNotifyInitializationComplete(policy_domain);
+    }
   }
 }
-void PolicyServiceImpl::MaybeNotifyPolicyDomainStatusChange(
+
+void PolicyServiceImpl::MaybeNotifyInitializationComplete(
     PolicyDomain policy_domain) {
-  if (initialization_throttled_ || policy_domain_status_[policy_domain] ==
-                                       PolicyDomainStatus::kUninitialized) {
+  if (initialization_throttled_)
     return;
-  }
-
+  if (!initialization_complete_[policy_domain])
+    return;
   auto iter = observers_.find(policy_domain);
-  if (iter == observers_.end())
-    return;
-
-  for (auto& observer : *iter->second) {
-    observer.OnPolicyServiceInitialized(policy_domain);
-    if (policy_domain_status_[policy_domain] ==
-        PolicyDomainStatus::kPolicyReady)
-      observer.OnFirstPoliciesLoaded(policy_domain);
+  if (iter != observers_.end()) {
+    for (auto& observer : *iter->second)
+      observer.OnPolicyServiceInitialized(policy_domain);
   }
 }
 
diff --git a/components/policy/core/common/policy_service_impl.h b/components/policy/core/common/policy_service_impl.h
index dc7189d..dde427f 100644
--- a/components/policy/core/common/policy_service_impl.h
+++ b/components/policy/core/common/policy_service_impl.h
@@ -67,7 +67,6 @@
   bool HasProvider(ConfigurationPolicyProvider* provider) const override;
   const PolicyMap& GetPolicies(const PolicyNamespace& ns) const override;
   bool IsInitializationComplete(PolicyDomain domain) const override;
-  bool IsFirstPolicyLoadComplete(PolicyDomain domain) const override;
   void RefreshPolicies(base::OnceClosure callback) override;
 #if defined(OS_ANDROID)
   android::PolicyServiceAndroid* GetPolicyServiceAndroid() override;
@@ -82,8 +81,6 @@
   void UnthrottleInitialization();
 
  private:
-  enum class PolicyDomainStatus { kUninitialized, kInitialized, kPolicyReady };
-
   using Observers =
       base::ObserverList<PolicyService::Observer, true>::Unchecked;
 
@@ -112,17 +109,16 @@
   // of namespaces whose policies have been modified.
   void MergeAndTriggerUpdates();
 
-  // Checks if all providers are initialized or have loaded their policies and
-  // sets |policy_domain_status_| accordingly. If initialization is not
-  // throttled, will also notify the observers of the appropriate status.
-  void CheckPolicyDomainStatus();
+  // Checks if all providers are initialized and sets |initialization_complete_|
+  // accordingly. If initialization is not throttled, will also notify the
+  // observers if the service just became initialized.
+  void CheckInitializationComplete();
 
-  // If initialization is not throttled, observers of |policy_domain| of the
-  // initialization will be notified of the domains' initialization and of the
-  // first policies being loaded. This function should only be called when
-  // |policy_domain| just became initialized, just got its first policies or
-  // when initialization has been unthrottled.
-  void MaybeNotifyPolicyDomainStatusChange(PolicyDomain policy_domain);
+  // If initialization is complete for |policy_domain| and initialization is not
+  // throttled, will notify obserers for |policy_domain| that it has been
+  // initialized. This function should only be called when |policy_domain| just
+  // became initialized or when initialization has been unthrottled.
+  void MaybeNotifyInitializationComplete(PolicyDomain policy_domain);
 
   // Invokes all the refresh callbacks if there are no more refreshes pending.
   void CheckRefreshComplete();
@@ -138,8 +134,8 @@
   // Maps each policy domain to its observer list.
   std::map<PolicyDomain, std::unique_ptr<Observers>> observers_;
 
-  // The status of all the providers for the indexed policy domain.
-  PolicyDomainStatus policy_domain_status_[POLICY_DOMAIN_SIZE];
+  // True if all the providers are initialized for the indexed policy domain.
+  bool initialization_complete_[POLICY_DOMAIN_SIZE];
 
   // Set of providers that have a pending update that was triggered by a
   // call to RefreshPolicies().
diff --git a/components/policy/core/common/policy_service_impl_unittest.cc b/components/policy/core/common/policy_service_impl_unittest.cc
index ad388015..704913f 100644
--- a/components/policy/core/common/policy_service_impl_unittest.cc
+++ b/components/policy/core/common/policy_service_impl_unittest.cc
@@ -113,12 +113,6 @@
         .WillRepeatedly(Return(true));
     EXPECT_CALL(provider2_, IsInitializationComplete(_))
         .WillRepeatedly(Return(true));
-    EXPECT_CALL(provider0_, IsFirstPolicyLoadComplete(_))
-        .WillRepeatedly(Return(true));
-    EXPECT_CALL(provider1_, IsFirstPolicyLoadComplete(_))
-        .WillRepeatedly(Return(true));
-    EXPECT_CALL(provider2_, IsFirstPolicyLoadComplete(_))
-        .WillRepeatedly(Return(true));
 
     provider0_.Init();
     provider1_.Init();
@@ -641,10 +635,6 @@
       .WillRepeatedly(Return(false));
   EXPECT_CALL(provider2_, IsInitializationComplete(_))
       .WillRepeatedly(Return(false));
-  EXPECT_CALL(provider1_, IsFirstPolicyLoadComplete(_))
-      .WillRepeatedly(Return(false));
-  EXPECT_CALL(provider2_, IsFirstPolicyLoadComplete(_))
-      .WillRepeatedly(Return(false));
   PolicyServiceImpl::Providers providers;
   providers.push_back(&provider0_);
   providers.push_back(&provider1_);
@@ -664,8 +654,6 @@
   policy_service_->AddObserver(POLICY_DOMAIN_SIGNIN_EXTENSIONS, &observer);
   EXPECT_CALL(observer, OnPolicyServiceInitialized(_)).Times(0);
   Mock::VerifyAndClearExpectations(&provider1_);
-  EXPECT_CALL(provider1_, IsFirstPolicyLoadComplete(_))
-      .WillRepeatedly(Return(false));
   EXPECT_CALL(provider1_, IsInitializationComplete(POLICY_DOMAIN_CHROME))
       .WillRepeatedly(Return(true));
   EXPECT_CALL(provider1_, IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS))
@@ -685,8 +673,6 @@
   // Same if |provider1_| doesn't have POLICY_DOMAIN_EXTENSIONS initialized.
   EXPECT_CALL(observer, OnPolicyServiceInitialized(_)).Times(0);
   Mock::VerifyAndClearExpectations(&provider2_);
-  EXPECT_CALL(provider2_, IsFirstPolicyLoadComplete(_))
-      .WillRepeatedly(Return(false));
   EXPECT_CALL(provider2_, IsInitializationComplete(POLICY_DOMAIN_CHROME))
       .WillRepeatedly(Return(false));
   EXPECT_CALL(provider2_, IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS))
@@ -705,8 +691,6 @@
   // Now initialize POLICY_DOMAIN_CHROME on all the providers.
   EXPECT_CALL(observer, OnPolicyServiceInitialized(POLICY_DOMAIN_CHROME));
   Mock::VerifyAndClearExpectations(&provider2_);
-  EXPECT_CALL(provider2_, IsFirstPolicyLoadComplete(_))
-      .WillRepeatedly(Return(false));
   EXPECT_CALL(provider2_, IsInitializationComplete(POLICY_DOMAIN_CHROME))
       .WillRepeatedly(Return(true));
   EXPECT_CALL(provider2_, IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS))
@@ -728,8 +712,6 @@
   EXPECT_CALL(observer,
               OnPolicyServiceInitialized(POLICY_DOMAIN_SIGNIN_EXTENSIONS));
   Mock::VerifyAndClearExpectations(&provider1_);
-  EXPECT_CALL(provider1_, IsFirstPolicyLoadComplete(_))
-      .WillRepeatedly(Return(false));
   EXPECT_CALL(provider1_, IsInitializationComplete(POLICY_DOMAIN_CHROME))
       .WillRepeatedly(Return(true));
   EXPECT_CALL(provider1_, IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS))
@@ -754,7 +736,7 @@
 // Tests initialization throttling of PolicyServiceImpl.
 // This actually tests two cases:
 // (1) A domain was initialized before UnthrottleInitialization is called.
-//     Observers only get notified after calling UnthrottleInitialization.
+//     Observers only get notified after calling UntrhottleInitialization.
 //     This is tested on POLICY_DOMAIN_CHROME.
 // (2) A domain becomes initialized after UnthrottleInitialization has already
 //     been called. Because initialization is not throttled anymore, observers
@@ -767,8 +749,6 @@
   Mock::VerifyAndClearExpectations(&provider2_);
   EXPECT_CALL(provider2_, IsInitializationComplete(_))
       .WillRepeatedly(Return(false));
-  EXPECT_CALL(provider2_, IsFirstPolicyLoadComplete(_))
-      .WillRepeatedly(Return(false));
   PolicyServiceImpl::Providers providers;
   providers.push_back(&provider0_);
   providers.push_back(&provider1_);
@@ -781,13 +761,6 @@
   EXPECT_FALSE(policy_service_->IsInitializationComplete(
       POLICY_DOMAIN_SIGNIN_EXTENSIONS));
 
-  EXPECT_FALSE(
-      policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME));
-  EXPECT_FALSE(
-      policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS));
-  EXPECT_FALSE(policy_service_->IsFirstPolicyLoadComplete(
-      POLICY_DOMAIN_SIGNIN_EXTENSIONS));
-
   MockPolicyServiceObserver observer;
   policy_service_->AddObserver(POLICY_DOMAIN_CHROME, &observer);
   policy_service_->AddObserver(POLICY_DOMAIN_EXTENSIONS, &observer);
@@ -795,8 +768,7 @@
 
   // Now additionally initialize POLICY_DOMAIN_CHROME on |provider2_|.
   // Note: VerifyAndClearExpectations is called to reset the previously set
-  // action for IsInitializationComplete and IsFirstPolicyLoadComplete on
-  // |provider_2|.
+  // action for IsInitializtionComplete on |provider_2|.
   Mock::VerifyAndClearExpectations(&provider2_);
   EXPECT_CALL(provider2_, IsInitializationComplete(POLICY_DOMAIN_CHROME))
       .WillRepeatedly(Return(true));
@@ -806,17 +778,8 @@
               IsInitializationComplete(POLICY_DOMAIN_SIGNIN_EXTENSIONS))
       .WillRepeatedly(Return(false));
 
-  EXPECT_CALL(provider2_, IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME))
-      .WillRepeatedly(Return(true));
-  EXPECT_CALL(provider2_, IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS))
-      .WillRepeatedly(Return(false));
-  EXPECT_CALL(provider2_,
-              IsFirstPolicyLoadComplete(POLICY_DOMAIN_SIGNIN_EXTENSIONS))
-      .WillRepeatedly(Return(false));
-
   // Nothing will happen because initialization is still throttled.
   EXPECT_CALL(observer, OnPolicyServiceInitialized(_)).Times(0);
-  EXPECT_CALL(observer, OnFirstPoliciesLoaded(_)).Times(0);
   const PolicyMap kPolicyMap;
   provider2_.UpdateChromePolicy(kPolicyMap);
   Mock::VerifyAndClearExpectations(&observer);
@@ -826,19 +789,10 @@
   EXPECT_FALSE(policy_service_->IsInitializationComplete(
       POLICY_DOMAIN_SIGNIN_EXTENSIONS));
 
-  EXPECT_FALSE(
-      policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME));
-  EXPECT_FALSE(
-      policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS));
-  EXPECT_FALSE(policy_service_->IsFirstPolicyLoadComplete(
-      POLICY_DOMAIN_SIGNIN_EXTENSIONS));
-
   // Unthrottle initialization. This will signal that POLICY_DOMAIN_CHROME is
   // initialized, the other domains should still not be initialized because
-  // |provider2_| is returning false in IsInitializationComplete and
-  // IsFirstPolicyLoadComplete for them.
+  // |provider2_| is returning false in IsInitializationComplete for them.
   EXPECT_CALL(observer, OnPolicyServiceInitialized(POLICY_DOMAIN_CHROME));
-  EXPECT_CALL(observer, OnFirstPoliciesLoaded(POLICY_DOMAIN_CHROME));
   policy_service_->UnthrottleInitialization();
   Mock::VerifyAndClearExpectations(&observer);
   EXPECT_TRUE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
@@ -847,27 +801,16 @@
   EXPECT_FALSE(policy_service_->IsInitializationComplete(
       POLICY_DOMAIN_SIGNIN_EXTENSIONS));
 
-  EXPECT_TRUE(policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME));
-  EXPECT_FALSE(
-      policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS));
-  EXPECT_FALSE(policy_service_->IsFirstPolicyLoadComplete(
-      POLICY_DOMAIN_SIGNIN_EXTENSIONS));
-
   // Initialize the remaining domains.
   // Note: VerifyAndClearExpectations is called to reset the previously set
-  // action for IsInitializationComplete and IsFirstPolicyLoadComplete on
-  // |provider_2|.
+  // action for IsInitializtionComplete on |provider_2|.
   Mock::VerifyAndClearExpectations(&provider2_);
   EXPECT_CALL(provider2_, IsInitializationComplete(_))
       .WillRepeatedly(Return(true));
-  EXPECT_CALL(provider2_, IsFirstPolicyLoadComplete(_))
-      .WillRepeatedly(Return(true));
 
   EXPECT_CALL(observer, OnPolicyServiceInitialized(POLICY_DOMAIN_EXTENSIONS));
   EXPECT_CALL(observer,
               OnPolicyServiceInitialized(POLICY_DOMAIN_SIGNIN_EXTENSIONS));
-  EXPECT_CALL(observer, OnFirstPoliciesLoaded(POLICY_DOMAIN_EXTENSIONS));
-  EXPECT_CALL(observer, OnFirstPoliciesLoaded(POLICY_DOMAIN_SIGNIN_EXTENSIONS));
   provider2_.UpdateChromePolicy(kPolicyMap);
   Mock::VerifyAndClearExpectations(&observer);
   EXPECT_TRUE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
@@ -876,12 +819,6 @@
   EXPECT_TRUE(policy_service_->IsInitializationComplete(
       POLICY_DOMAIN_SIGNIN_EXTENSIONS));
 
-  EXPECT_TRUE(policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME));
-  EXPECT_TRUE(
-      policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS));
-  EXPECT_TRUE(policy_service_->IsFirstPolicyLoadComplete(
-      POLICY_DOMAIN_SIGNIN_EXTENSIONS));
-
   // Cleanup.
   policy_service_->RemoveObserver(POLICY_DOMAIN_CHROME, &observer);
   policy_service_->RemoveObserver(POLICY_DOMAIN_EXTENSIONS, &observer);
@@ -902,13 +839,6 @@
   EXPECT_FALSE(policy_service_->IsInitializationComplete(
       POLICY_DOMAIN_SIGNIN_EXTENSIONS));
 
-  EXPECT_FALSE(
-      policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME));
-  EXPECT_FALSE(
-      policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS));
-  EXPECT_FALSE(policy_service_->IsFirstPolicyLoadComplete(
-      POLICY_DOMAIN_SIGNIN_EXTENSIONS));
-
   MockPolicyServiceObserver observer;
   policy_service_->AddObserver(POLICY_DOMAIN_CHROME, &observer);
   policy_service_->AddObserver(POLICY_DOMAIN_EXTENSIONS, &observer);
@@ -920,9 +850,6 @@
   EXPECT_CALL(observer, OnPolicyServiceInitialized(POLICY_DOMAIN_EXTENSIONS));
   EXPECT_CALL(observer,
               OnPolicyServiceInitialized(POLICY_DOMAIN_SIGNIN_EXTENSIONS));
-  EXPECT_CALL(observer, OnFirstPoliciesLoaded(POLICY_DOMAIN_CHROME));
-  EXPECT_CALL(observer, OnFirstPoliciesLoaded(POLICY_DOMAIN_EXTENSIONS));
-  EXPECT_CALL(observer, OnFirstPoliciesLoaded(POLICY_DOMAIN_SIGNIN_EXTENSIONS));
   policy_service_->UnthrottleInitialization();
   Mock::VerifyAndClearExpectations(&observer);
   EXPECT_TRUE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
@@ -931,132 +858,6 @@
   EXPECT_TRUE(policy_service_->IsInitializationComplete(
       POLICY_DOMAIN_SIGNIN_EXTENSIONS));
 
-  EXPECT_TRUE(policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME));
-  EXPECT_TRUE(
-      policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS));
-  EXPECT_TRUE(policy_service_->IsFirstPolicyLoadComplete(
-      POLICY_DOMAIN_SIGNIN_EXTENSIONS));
-
-  // Cleanup.
-  policy_service_->RemoveObserver(POLICY_DOMAIN_CHROME, &observer);
-  policy_service_->RemoveObserver(POLICY_DOMAIN_EXTENSIONS, &observer);
-  policy_service_->RemoveObserver(POLICY_DOMAIN_SIGNIN_EXTENSIONS, &observer);
-}
-
-TEST_F(PolicyServiceTest, IsFirstPolicyLoadComplete) {
-  // |provider0_| has all domains initialized.
-  Mock::VerifyAndClearExpectations(&provider1_);
-  Mock::VerifyAndClearExpectations(&provider2_);
-  EXPECT_CALL(provider1_, IsInitializationComplete(_))
-      .WillRepeatedly(Return(true));
-  EXPECT_CALL(provider2_, IsInitializationComplete(_))
-      .WillRepeatedly(Return(true));
-  EXPECT_CALL(provider1_, IsFirstPolicyLoadComplete(_))
-      .WillRepeatedly(Return(false));
-  EXPECT_CALL(provider2_, IsFirstPolicyLoadComplete(_))
-      .WillRepeatedly(Return(false));
-  PolicyServiceImpl::Providers providers;
-  providers.push_back(&provider0_);
-  providers.push_back(&provider1_);
-  providers.push_back(&provider2_);
-  policy_service_ = std::make_unique<PolicyServiceImpl>(std::move(providers));
-  EXPECT_FALSE(
-      policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME));
-  EXPECT_FALSE(
-      policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS));
-  EXPECT_FALSE(policy_service_->IsFirstPolicyLoadComplete(
-      POLICY_DOMAIN_SIGNIN_EXTENSIONS));
-
-  // |provider2_| still doesn't have POLICY_DOMAIN_CHROME initialized, so
-  // the initialization status of that domain won't change.
-  MockPolicyServiceObserver observer;
-  policy_service_->AddObserver(POLICY_DOMAIN_CHROME, &observer);
-  policy_service_->AddObserver(POLICY_DOMAIN_EXTENSIONS, &observer);
-  policy_service_->AddObserver(POLICY_DOMAIN_SIGNIN_EXTENSIONS, &observer);
-  EXPECT_CALL(observer, OnFirstPoliciesLoaded(_)).Times(0);
-  Mock::VerifyAndClearExpectations(&provider1_);
-  EXPECT_CALL(provider1_, IsInitializationComplete(_))
-      .WillRepeatedly(Return(true));
-  EXPECT_CALL(provider1_, IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME))
-      .WillRepeatedly(Return(true));
-  EXPECT_CALL(provider1_, IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS))
-      .WillRepeatedly(Return(false));
-  EXPECT_CALL(provider1_,
-              IsFirstPolicyLoadComplete(POLICY_DOMAIN_SIGNIN_EXTENSIONS))
-      .WillRepeatedly(Return(false));
-  const PolicyMap kPolicyMap;
-  provider1_.UpdateChromePolicy(kPolicyMap);
-  Mock::VerifyAndClearExpectations(&observer);
-  EXPECT_FALSE(
-      policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME));
-  EXPECT_FALSE(
-      policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS));
-  EXPECT_FALSE(policy_service_->IsFirstPolicyLoadComplete(
-      POLICY_DOMAIN_SIGNIN_EXTENSIONS));
-
-  // Same if |provider1_| doesn't have POLICY_DOMAIN_EXTENSIONS initialized.
-  EXPECT_CALL(observer, OnFirstPoliciesLoaded(_)).Times(0);
-  Mock::VerifyAndClearExpectations(&provider2_);
-  EXPECT_CALL(provider2_, IsInitializationComplete(_))
-      .WillRepeatedly(Return(true));
-  EXPECT_CALL(provider2_, IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME))
-      .WillRepeatedly(Return(false));
-  EXPECT_CALL(provider2_, IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS))
-      .WillRepeatedly(Return(true));
-  EXPECT_CALL(provider2_,
-              IsFirstPolicyLoadComplete(POLICY_DOMAIN_SIGNIN_EXTENSIONS))
-      .WillRepeatedly(Return(true));
-  provider2_.UpdateChromePolicy(kPolicyMap);
-  Mock::VerifyAndClearExpectations(&observer);
-  EXPECT_FALSE(
-      policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME));
-  EXPECT_FALSE(
-      policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS));
-  EXPECT_FALSE(policy_service_->IsFirstPolicyLoadComplete(
-      POLICY_DOMAIN_SIGNIN_EXTENSIONS));
-
-  // Now initialize POLICY_DOMAIN_CHROME on all the providers.
-  EXPECT_CALL(observer, OnFirstPoliciesLoaded(POLICY_DOMAIN_CHROME));
-  Mock::VerifyAndClearExpectations(&provider2_);
-  EXPECT_CALL(provider2_, IsInitializationComplete(_))
-      .WillRepeatedly(Return(true));
-  EXPECT_CALL(provider2_, IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME))
-      .WillRepeatedly(Return(true));
-  EXPECT_CALL(provider2_, IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS))
-      .WillRepeatedly(Return(true));
-  EXPECT_CALL(provider2_,
-              IsFirstPolicyLoadComplete(POLICY_DOMAIN_SIGNIN_EXTENSIONS))
-      .WillRepeatedly(Return(true));
-  provider2_.UpdateChromePolicy(kPolicyMap);
-  Mock::VerifyAndClearExpectations(&observer);
-  EXPECT_TRUE(policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME));
-  // Other domains are still not initialized.
-  EXPECT_FALSE(
-      policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS));
-  EXPECT_FALSE(policy_service_->IsFirstPolicyLoadComplete(
-      POLICY_DOMAIN_SIGNIN_EXTENSIONS));
-
-  // Initialize the remaining domains.
-  EXPECT_CALL(observer, OnFirstPoliciesLoaded(POLICY_DOMAIN_EXTENSIONS));
-  EXPECT_CALL(observer, OnFirstPoliciesLoaded(POLICY_DOMAIN_SIGNIN_EXTENSIONS));
-  Mock::VerifyAndClearExpectations(&provider1_);
-  EXPECT_CALL(provider1_, IsInitializationComplete(_))
-      .WillRepeatedly(Return(true));
-  EXPECT_CALL(provider1_, IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME))
-      .WillRepeatedly(Return(true));
-  EXPECT_CALL(provider1_, IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS))
-      .WillRepeatedly(Return(true));
-  EXPECT_CALL(provider1_,
-              IsFirstPolicyLoadComplete(POLICY_DOMAIN_SIGNIN_EXTENSIONS))
-      .WillRepeatedly(Return(true));
-  provider1_.UpdateChromePolicy(kPolicyMap);
-  Mock::VerifyAndClearExpectations(&observer);
-  EXPECT_TRUE(policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME));
-  EXPECT_TRUE(
-      policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS));
-  EXPECT_TRUE(policy_service_->IsFirstPolicyLoadComplete(
-      POLICY_DOMAIN_SIGNIN_EXTENSIONS));
-
   // Cleanup.
   policy_service_->RemoveObserver(POLICY_DOMAIN_CHROME, &observer);
   policy_service_->RemoveObserver(POLICY_DOMAIN_EXTENSIONS, &observer);
diff --git a/components/policy/core/common/proxy_policy_provider.cc b/components/policy/core/common/proxy_policy_provider.cc
index a5cf64435..6d6072f 100644
--- a/components/policy/core/common/proxy_policy_provider.cc
+++ b/components/policy/core/common/proxy_policy_provider.cc
@@ -54,10 +54,6 @@
   }
 }
 
-bool ProxyPolicyProvider::IsFirstPolicyLoadComplete(PolicyDomain domain) const {
-  return delegate_ && delegate_->IsInitializationComplete(domain);
-}
-
 void ProxyPolicyProvider::OnUpdatePolicy(
     ConfigurationPolicyProvider* provider) {
   if (block_policy_updates_for_testing_)
diff --git a/components/policy/core/common/proxy_policy_provider.h b/components/policy/core/common/proxy_policy_provider.h
index 599de9f..683cd13 100644
--- a/components/policy/core/common/proxy_policy_provider.h
+++ b/components/policy/core/common/proxy_policy_provider.h
@@ -49,7 +49,6 @@
   // ConfigurationPolicyProvider:
   void Shutdown() override;
   void RefreshPolicies() override;
-  bool IsFirstPolicyLoadComplete(PolicyDomain domain) const override;
 
   // ConfigurationPolicyProvider::Observer:
   void OnUpdatePolicy(ConfigurationPolicyProvider* provider) override;
diff --git a/components/policy/core/common/schema_registry_tracking_policy_provider.cc b/components/policy/core/common/schema_registry_tracking_policy_provider.cc
index 8af6029..8dc00b93 100644
--- a/components/policy/core/common/schema_registry_tracking_policy_provider.cc
+++ b/components/policy/core/common/schema_registry_tracking_policy_provider.cc
@@ -37,14 +37,6 @@
   return state_ == READY;
 }
 
-bool SchemaRegistryTrackingPolicyProvider::IsFirstPolicyLoadComplete(
-    PolicyDomain domain) const {
-  if (domain == POLICY_DOMAIN_CHROME)
-    return delegate_->IsFirstPolicyLoadComplete(domain);
-  // This provider keeps its own state for all the other domains.
-  return state_ == READY;
-}
-
 void SchemaRegistryTrackingPolicyProvider::RefreshPolicies() {
   delegate_->RefreshPolicies();
 }
diff --git a/components/policy/core/common/schema_registry_tracking_policy_provider.h b/components/policy/core/common/schema_registry_tracking_policy_provider.h
index 592be9d..1ec64c3b 100644
--- a/components/policy/core/common/schema_registry_tracking_policy_provider.h
+++ b/components/policy/core/common/schema_registry_tracking_policy_provider.h
@@ -67,7 +67,6 @@
   // provider doesn't have a "real" policy source of its own.
   void Init(SchemaRegistry* registry) override;
   bool IsInitializationComplete(PolicyDomain domain) const override;
-  bool IsFirstPolicyLoadComplete(PolicyDomain domain) const override;
   void RefreshPolicies() override;
   void OnSchemaRegistryReady() override;
   void OnSchemaRegistryUpdated(bool has_new_schemas) override;
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index 522f7bf..41107c71 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -14778,7 +14778,7 @@
     },
     {
       'name': 'PacHttpsUrlStrippingEnabled',
-      'owners': ['eroman@chromium.org'],
+      'owners': ['net-dev@chromium.org'],
       'type': 'main',
       'schema': { 'type': 'boolean' },
       'supported_on': [ 'chrome.*:52-74', 'chrome_os:52-74' ],
@@ -23504,7 +23504,7 @@
       'owners': ['agawronska@chromium.org', 'cros-families-eng@google.com'],
       'type': 'main',
       'schema': { 'type': 'boolean' },
-      'future_on': ['chrome_os'],
+      'supported_on': ['chrome_os:87-'],
       'device_only': True,
       'features': {
         'dynamic_refresh': True,
diff --git a/components/previous_session_info/previous_session_info.h b/components/previous_session_info/previous_session_info.h
index d87ef15..a9aa2540 100644
--- a/components/previous_session_info/previous_session_info.h
+++ b/components/previous_session_info/previous_session_info.h
@@ -139,6 +139,10 @@
 // Memory footprint in bytes of the browser process.
 @property(nonatomic, readonly) NSInteger memoryFootprint;
 
+// YES if ApplicationWillTerminate notification was posted for the previous
+// session.
+@property(nonatomic, readonly) BOOL applicationWillTerminateWasReceived;
+
 // Singleton PreviousSessionInfo. During the lifetime of the app, the returned
 // object is the same, and describes the previous session, even after a new
 // session has started (by calling beginRecordingCurrentSession).
diff --git a/components/previous_session_info/previous_session_info.mm b/components/previous_session_info/previous_session_info.mm
index bf5401a..1391e979 100644
--- a/components/previous_session_info/previous_session_info.mm
+++ b/components/previous_session_info/previous_session_info.mm
@@ -96,6 +96,10 @@
 //   version of the application.
 NSString* const kPreviousSessionInfoMultiWindowEnabled =
     @"PreviousSessionInfoMultiWindowEnabled";
+// - A (boolean) describing whether the last session received
+// ApplicationWillTerminate Notification.
+NSString* const kPreviousSessionInfoAppWillTerminate =
+    @"PreviousSessionInfoAppWillTerminate";
 }  // namespace
 
 namespace previous_session_info_constants {
@@ -143,6 +147,7 @@
 @property(nonatomic, strong) NSMutableSet<NSString*>* connectedSceneSessionsIDs;
 @property(nonatomic, copy) NSDictionary<NSString*, NSString*>* reportParameters;
 @property(nonatomic, assign) NSInteger memoryFootprint;
+@property(nonatomic, assign) BOOL applicationWillTerminateWasReceived;
 @end
 
 @implementation PreviousSessionInfo {
@@ -237,6 +242,9 @@
     gSharedInstance.memoryFootprint =
         [defaults integerForKey:previous_session_info_constants::
                                     kPreviousSessionInfoMemoryFootprint];
+
+    gSharedInstance.applicationWillTerminateWasReceived =
+        [defaults boolForKey:kPreviousSessionInfoAppWillTerminate];
   }
   return gSharedInstance;
 }
@@ -274,6 +282,9 @@
       removeObjectForKey:previous_session_info_constants::
                              kDidSeeMemoryWarningShortlyBeforeTerminating];
 
+  [[NSUserDefaults standardUserDefaults]
+      removeObjectForKey:kPreviousSessionInfoAppWillTerminate];
+
   [defaults setObject:[NSDate date] forKey:kPreviousSessionInfoStartTime];
 
   [[NSNotificationCenter defaultCenter]
@@ -333,6 +344,12 @@
              name:NSProcessInfoThermalStateDidChangeNotification
            object:nil];
 
+  [[NSNotificationCenter defaultCenter]
+      addObserver:self
+         selector:@selector(applicationWillTerminate)
+             name:UIApplicationWillTerminateNotification
+           object:nil];
+
   [self resumeRecordingCurrentSession];
 }
 
@@ -460,6 +477,13 @@
   [self updateSessionEndTime];
 }
 
+- (void)applicationWillTerminate {
+  [NSUserDefaults.standardUserDefaults
+      setBool:YES
+       forKey:kPreviousSessionInfoAppWillTerminate];
+  [NSUserDefaults.standardUserDefaults synchronize];
+}
+
 - (void)updateMemoryFootprint {
   if (!self.recordingCurrentSession)
     return;
diff --git a/components/sessions/core/tab_restore_service_impl.cc b/components/sessions/core/tab_restore_service_impl.cc
index e6ae900..7d149c1 100644
--- a/components/sessions/core/tab_restore_service_impl.cc
+++ b/components/sessions/core/tab_restore_service_impl.cc
@@ -1184,9 +1184,12 @@
   }
 
   staging_entries_.clear();
-  entries_to_write_ = 0;
 
   tab_restore_service_helper_->PruneEntries();
+
+  // Write the loaded entries into the current session.
+  entries_to_write_ = tab_restore_service_helper_->entries().size();
+
   tab_restore_service_helper_->NotifyTabsChanged();
 
   tab_restore_service_helper_->NotifyLoaded();
diff --git a/components/viz/common/gpu/context_provider.cc b/components/viz/common/gpu/context_provider.cc
index 8079a85..180c530 100644
--- a/components/viz/common/gpu/context_provider.cc
+++ b/components/viz/common/gpu/context_provider.cc
@@ -19,8 +19,4 @@
   // Let ContextCacheController know we are no longer busy.
   context_provider_->CacheController()->ClientBecameNotBusy(std::move(busy_));
 }
-
-gpu::SharedImageManager* ContextProvider::GetSharedImageManager() {
-  return nullptr;
-}
 }  // namespace viz
diff --git a/components/viz/common/gpu/context_provider.h b/components/viz/common/gpu/context_provider.h
index c7354cf..140edc6 100644
--- a/components/viz/common/gpu/context_provider.h
+++ b/components/viz/common/gpu/context_provider.h
@@ -29,7 +29,6 @@
 class ContextSupport;
 struct GpuFeatureInfo;
 class SharedImageInterface;
-class SharedImageManager;
 
 namespace gles2 {
 class GLES2Interface;
@@ -111,9 +110,6 @@
   // been successfully bound to a thread before calling this.
   virtual gpu::gles2::GLES2Interface* ContextGL() = 0;
 
-  // Returns the SharedImageManager. Only available inside the GPU process.
-  virtual gpu::SharedImageManager* GetSharedImageManager();
-
  protected:
   virtual ~ContextProvider() = default;
 };
diff --git a/components/viz/service/compositor_frame_fuzzer/fuzzer_software_output_surface_provider.cc b/components/viz/service/compositor_frame_fuzzer/fuzzer_software_output_surface_provider.cc
index 41c6bb28..34014e91 100644
--- a/components/viz/service/compositor_frame_fuzzer/fuzzer_software_output_surface_provider.cc
+++ b/components/viz/service/compositor_frame_fuzzer/fuzzer_software_output_surface_provider.cc
@@ -108,12 +108,4 @@
   return std::make_unique<SoftwareOutputSurface>(
       std::move(software_output_device));
 }
-
-gpu::SharedImageManager*
-FuzzerSoftwareOutputSurfaceProvider::GetSharedImageManager() {
-  // This is used for creating overlay processor. Software compositor does not
-  // support overlay.
-  return nullptr;
-}
-
 }  // namespace viz
diff --git a/components/viz/service/compositor_frame_fuzzer/fuzzer_software_output_surface_provider.h b/components/viz/service/compositor_frame_fuzzer/fuzzer_software_output_surface_provider.h
index 3e9505c1..83014ff 100644
--- a/components/viz/service/compositor_frame_fuzzer/fuzzer_software_output_surface_provider.h
+++ b/components/viz/service/compositor_frame_fuzzer/fuzzer_software_output_surface_provider.h
@@ -34,8 +34,6 @@
       const RendererSettings& renderer_settings,
       const DebugRendererSettings* debug_settings) override;
 
-  gpu::SharedImageManager* GetSharedImageManager() override;
-
  private:
   base::Optional<base::FilePath> png_dir_path_;
 
diff --git a/components/viz/service/display/overlay_processor_android.cc b/components/viz/service/display/overlay_processor_android.cc
index 8c1ab6b..cdf4ebc 100644
--- a/components/viz/service/display/overlay_processor_android.cc
+++ b/components/viz/service/display/overlay_processor_android.cc
@@ -10,6 +10,7 @@
 
 #include "base/synchronization/waitable_event.h"
 #include "components/viz/common/quads/stream_video_draw_quad.h"
+#include "components/viz/service/display/display_compositor_memory_and_task_controller.h"
 #include "components/viz/service/display/overlay_processor_on_gpu.h"
 #include "components/viz/service/display/overlay_strategy_underlay.h"
 #include "components/viz/service/display/skia_output_surface.h"
@@ -18,10 +19,9 @@
 
 namespace viz {
 OverlayProcessorAndroid::OverlayProcessorAndroid(
-    gpu::SharedImageManager* shared_image_manager,
-    gpu::MemoryTracker* memory_tracker,
-    gpu::GpuTaskSchedulerHelper* gpu_task_scheduler)
-    : OverlayProcessorUsingStrategy(), gpu_task_scheduler_(gpu_task_scheduler) {
+    DisplayCompositorMemoryAndTaskController* display_controller)
+    : OverlayProcessorUsingStrategy(),
+      gpu_task_scheduler_(display_controller->gpu_task_scheduler()) {
   // In unittests, we don't have the gpu_task_scheduler_ set up, but still want
   // to test ProcessForOverlays functionalities where we are making overlay
   // candidates correctly.
@@ -33,7 +33,8 @@
                               base::WaitableEvent::InitialState::NOT_SIGNALED);
     auto callback = base::BindOnce(
         &OverlayProcessorAndroid::InitializeOverlayProcessorOnGpu,
-        base::Unretained(this), shared_image_manager, memory_tracker, &event);
+        base::Unretained(this), display_controller->controller_on_gpu(),
+        &event);
     gpu_task_scheduler_->ScheduleGpuTask(std::move(callback), {});
     event.Wait();
   }
@@ -67,11 +68,11 @@
 }
 
 void OverlayProcessorAndroid::InitializeOverlayProcessorOnGpu(
-    gpu::SharedImageManager* shared_image_manager,
-    gpu::MemoryTracker* memory_tracker,
+    gpu::DisplayCompositorMemoryAndTaskControllerOnGpu*
+        display_controller_on_gpu,
     base::WaitableEvent* event) {
-  processor_on_gpu_ = std::make_unique<OverlayProcessorOnGpu>(
-      shared_image_manager, memory_tracker);
+  processor_on_gpu_ =
+      std::make_unique<OverlayProcessorOnGpu>(display_controller_on_gpu);
   DCHECK(event);
   event->Signal();
 }
diff --git a/components/viz/service/display/overlay_processor_android.h b/components/viz/service/display/overlay_processor_android.h
index 911f647d..461b890 100644
--- a/components/viz/service/display/overlay_processor_android.h
+++ b/components/viz/service/display/overlay_processor_android.h
@@ -18,7 +18,7 @@
 }
 
 namespace gpu {
-class MemoryTracker;
+class DisplayCompositorMemoryAndTaskControllerOnGpu;
 }
 
 namespace viz {
@@ -36,9 +36,8 @@
 class VIZ_SERVICE_EXPORT OverlayProcessorAndroid
     : public OverlayProcessorUsingStrategy {
  public:
-  OverlayProcessorAndroid(gpu::SharedImageManager* shared_image_manager,
-                          gpu::MemoryTracker* memory_tracker,
-                          gpu::GpuTaskSchedulerHelper* gpu_task_scheduler);
+  explicit OverlayProcessorAndroid(
+      DisplayCompositorMemoryAndTaskController* display_controller);
   ~OverlayProcessorAndroid() override;
 
   bool IsOverlaySupported() const override;
@@ -64,8 +63,8 @@
   // thread. These two methods are scheduled on the gpu thread to setup and
   // teardown the gpu side receiver.
   void InitializeOverlayProcessorOnGpu(
-      gpu::SharedImageManager* shared_image_manager,
-      gpu::MemoryTracker* memory_tracker,
+      gpu::DisplayCompositorMemoryAndTaskControllerOnGpu*
+          display_controller_on_gpu,
       base::WaitableEvent* event);
   void DestroyOverlayProcessorOnGpu(base::WaitableEvent* event);
   void TakeOverlayCandidates(CandidateList* candidate_list) override;
diff --git a/components/viz/service/display/overlay_processor_interface.cc b/components/viz/service/display/overlay_processor_interface.cc
index ed60ac3..dd3fddf5 100644
--- a/components/viz/service/display/overlay_processor_interface.cc
+++ b/components/viz/service/display/overlay_processor_interface.cc
@@ -78,7 +78,6 @@
     OutputSurface* output_surface,
     gpu::SurfaceHandle surface_handle,
     const OutputSurface::Capabilities& capabilities,
-    gpu::SharedImageManager* shared_image_manager,
     DisplayCompositorMemoryAndTaskController* display_controller,
     gpu::SharedImageInterface* shared_image_interface,
     const RendererSettings& renderer_settings,
@@ -142,10 +141,7 @@
     if (capabilities.android_surface_control_feature_enabled)
       return std::make_unique<OverlayProcessorStub>();
 
-    return std::make_unique<OverlayProcessorAndroid>(
-        shared_image_manager,
-        display_controller->controller_on_gpu()->memory_tracker(),
-        display_controller->gpu_task_scheduler());
+    return std::make_unique<OverlayProcessorAndroid>(display_controller);
   }
 #else  // Default
   return std::make_unique<OverlayProcessorStub>();
diff --git a/components/viz/service/display/overlay_processor_interface.h b/components/viz/service/display/overlay_processor_interface.h
index 64c6a09..2c7514b 100644
--- a/components/viz/service/display/overlay_processor_interface.h
+++ b/components/viz/service/display/overlay_processor_interface.h
@@ -101,7 +101,6 @@
       OutputSurface* output_surface,
       gpu::SurfaceHandle surface_handle,
       const OutputSurface::Capabilities& capabilities,
-      gpu::SharedImageManager* shared_image_manager,
       DisplayCompositorMemoryAndTaskController* display_controller,
       gpu::SharedImageInterface* shared_image_interface,
       const RendererSettings& renderer_settings,
diff --git a/components/viz/service/display/overlay_processor_on_gpu.cc b/components/viz/service/display/overlay_processor_on_gpu.cc
index 7d41dc47..26ec610 100644
--- a/components/viz/service/display/overlay_processor_on_gpu.cc
+++ b/components/viz/service/display/overlay_processor_on_gpu.cc
@@ -5,17 +5,18 @@
 #include "components/viz/service/display/overlay_processor_on_gpu.h"
 #include "gpu/command_buffer/service/shared_image_factory.h"
 #include "gpu/command_buffer/service/shared_image_manager.h"
+#include "gpu/ipc/display_compositor_memory_and_task_controller_on_gpu.h"
 #include "ui/gfx/geometry/rect_conversions.h"
 
 namespace viz {
 
 OverlayProcessorOnGpu::OverlayProcessorOnGpu(
-    gpu::SharedImageManager* shared_image_manager,
-    gpu::MemoryTracker* memory_tracker)
+    gpu::DisplayCompositorMemoryAndTaskControllerOnGpu*
+        display_controller_on_gpu)
     : shared_image_representation_factory_(
           std::make_unique<gpu::SharedImageRepresentationFactory>(
-              shared_image_manager,
-              memory_tracker)) {
+              display_controller_on_gpu->shared_image_manager(),
+              display_controller_on_gpu->memory_tracker())) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 }
 
diff --git a/components/viz/service/display/overlay_processor_on_gpu.h b/components/viz/service/display/overlay_processor_on_gpu.h
index 86c3f69..46d0f345 100644
--- a/components/viz/service/display/overlay_processor_on_gpu.h
+++ b/components/viz/service/display/overlay_processor_on_gpu.h
@@ -5,6 +5,8 @@
 #ifndef COMPONENTS_VIZ_SERVICE_DISPLAY_OVERLAY_PROCESSOR_ON_GPU_H_
 #define COMPONENTS_VIZ_SERVICE_DISPLAY_OVERLAY_PROCESSOR_ON_GPU_H_
 
+#include <memory>
+
 #include "base/threading/thread_checker.h"
 #include "build/build_config.h"
 #include "components/viz/service/display/overlay_candidate.h"
@@ -19,8 +21,7 @@
 #endif
 
 namespace gpu {
-class MemoryTracker;
-class SharedImageManager;
+class DisplayCompositorMemoryAndTaskControllerOnGpu;
 class SharedImageRepresentationFactory;
 }  // namespace gpu
 
@@ -40,8 +41,9 @@
   using CandidateList = OverlayCandidateList;
 #endif
 
-  OverlayProcessorOnGpu(gpu::SharedImageManager* shared_image_manager,
-                        gpu::MemoryTracker* memory_tracker);
+  explicit OverlayProcessorOnGpu(
+      gpu::DisplayCompositorMemoryAndTaskControllerOnGpu*
+          display_controller_on_gpu);
   ~OverlayProcessorOnGpu();
 
   // This function takes the overlay candidates, and schedule them for
diff --git a/components/viz/service/display_embedder/output_surface_provider.h b/components/viz/service/display_embedder/output_surface_provider.h
index b3f8056b..77d463e6 100644
--- a/components/viz/service/display_embedder/output_surface_provider.h
+++ b/components/viz/service/display_embedder/output_surface_provider.h
@@ -12,10 +12,6 @@
 #include "gpu/ipc/gpu_task_scheduler_helper.h"
 #include "services/viz/privileged/mojom/compositing/display_private.mojom.h"
 
-namespace gpu {
-class SharedImageManager;
-}
-
 namespace viz {
 
 struct DebugRendererSettings;
@@ -44,12 +40,6 @@
       DisplayCompositorMemoryAndTaskController* gpu_dependency,
       const RendererSettings& renderer_settings,
       const DebugRendererSettings* debug_settings) = 0;
-
-  // TODO(weiliangc): This API is unfortunately located since this is the
-  // overlapping place that both GLOutputSurface and SkiaOutputSurface code path
-  // has access to SharedImageManager. Refactor so that OverlayProcessor and
-  // OutputSurface could be initialized together at appropriate place.
-  virtual gpu::SharedImageManager* GetSharedImageManager() = 0;
 };
 
 }  // namespace viz
diff --git a/components/viz/service/display_embedder/output_surface_provider_impl.cc b/components/viz/service/display_embedder/output_surface_provider_impl.cc
index f403ee5..cd43e14 100644
--- a/components/viz/service/display_embedder/output_surface_provider_impl.cc
+++ b/components/viz/service/display_embedder/output_surface_provider_impl.cc
@@ -237,13 +237,6 @@
   return output_surface;
 }
 
-gpu::SharedImageManager* OutputSurfaceProviderImpl::GetSharedImageManager() {
-  if (!gpu_service_impl_)
-    return nullptr;
-
-  return gpu_service_impl_->shared_image_manager();
-}
-
 std::unique_ptr<SoftwareOutputDevice>
 OutputSurfaceProviderImpl::CreateSoftwareOutputDeviceForPlatform(
     gpu::SurfaceHandle surface_handle,
diff --git a/components/viz/service/display_embedder/output_surface_provider_impl.h b/components/viz/service/display_embedder/output_surface_provider_impl.h
index 752af2e..a6bb42cd 100644
--- a/components/viz/service/display_embedder/output_surface_provider_impl.h
+++ b/components/viz/service/display_embedder/output_surface_provider_impl.h
@@ -63,8 +63,6 @@
       const RendererSettings& renderer_settings,
       const DebugRendererSettings* debug_settings) override;
 
-  gpu::SharedImageManager* GetSharedImageManager() override;
-
  private:
   std::unique_ptr<SoftwareOutputDevice> CreateSoftwareOutputDeviceForPlatform(
       gpu::SurfaceHandle surface_handle,
diff --git a/components/viz/service/display_embedder/viz_process_context_provider.cc b/components/viz/service/display_embedder/viz_process_context_provider.cc
index e219466..c623fc0 100644
--- a/components/viz/service/display_embedder/viz_process_context_provider.cc
+++ b/components/viz/service/display_embedder/viz_process_context_provider.cc
@@ -339,10 +339,6 @@
   return command_buffer_->GetCacheBackBufferCb();
 }
 
-gpu::SharedImageManager* VizProcessContextProvider::GetSharedImageManager() {
-  return command_buffer_->GetSharedImageManager();
-}
-
 void VizProcessContextProvider::SetNeedsMeasureNextDrawLatency() {
   return command_buffer_->SetNeedsMeasureNextDrawLatency();
 }
diff --git a/components/viz/service/display_embedder/viz_process_context_provider.h b/components/viz/service/display_embedder/viz_process_context_provider.h
index 040797c3..065ec67 100644
--- a/components/viz/service/display_embedder/viz_process_context_provider.h
+++ b/components/viz/service/display_embedder/viz_process_context_provider.h
@@ -78,7 +78,6 @@
   const gpu::GpuFeatureInfo& GetGpuFeatureInfo() const override;
   void AddObserver(ContextLostObserver* obs) override;
   void RemoveObserver(ContextLostObserver* obs) override;
-  gpu::SharedImageManager* GetSharedImageManager() override;
 
   virtual void SetUpdateVSyncParametersCallback(
       UpdateVSyncParametersCallback callback);
diff --git a/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc b/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
index 7040a7c..892734a 100644
--- a/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
+++ b/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
@@ -138,7 +138,6 @@
   auto overlay_processor = OverlayProcessorInterface::CreateOverlayProcessor(
       output_surface.get(), output_surface->GetSurfaceHandle(),
       output_surface->capabilities(),
-      output_surface_provider->GetSharedImageManager(),
       display_controller.get(), sii, params->renderer_settings, debug_settings);
 
   auto display = std::make_unique<Display>(
diff --git a/components/viz/test/test_output_surface_provider.cc b/components/viz/test/test_output_surface_provider.cc
index a89c26d..947d86e 100644
--- a/components/viz/test/test_output_surface_provider.cc
+++ b/components/viz/test/test_output_surface_provider.cc
@@ -38,8 +38,4 @@
         std::make_unique<SoftwareOutputDevice>());
   }
 }
-
-gpu::SharedImageManager* TestOutputSurfaceProvider::GetSharedImageManager() {
-  return nullptr;
-}
 }  // namespace viz
diff --git a/components/viz/test/test_output_surface_provider.h b/components/viz/test/test_output_surface_provider.h
index b1769d9..66e671a 100644
--- a/components/viz/test/test_output_surface_provider.h
+++ b/components/viz/test/test_output_surface_provider.h
@@ -9,10 +9,6 @@
 
 #include "components/viz/service/display_embedder/output_surface_provider.h"
 
-namespace gpu {
-class SharedImageManager;
-}
-
 namespace viz {
 
 // Test implementation that creates a FakeOutputSurface.
@@ -34,8 +30,6 @@
       const RendererSettings& renderer_settings,
       const DebugRendererSettings* debug_settings) override;
 
-  gpu::SharedImageManager* GetSharedImageManager() override;
-
  private:
   DISALLOW_COPY_AND_ASSIGN(TestOutputSurfaceProvider);
 };
diff --git a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
index a5a59fd..513a31a 100644
--- a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
@@ -547,47 +547,13 @@
 #define MAYBE_AccessibilityAriaCombobox AccessibilityAriaCombobox
 #endif
 
-// DISABLE A BUNCH OF TESTS FOR ANDROID
-// ------------------------------------
-// TODO(crbug.com/1137967): tests are flaky on android.
-#if defined(OS_ANDROID)
-#define MAYBE_AccessibilityAriaMenuItemRadio \
-  DISABLED_AccessibilityAriaMenuItemRadio
-#define MAYBE_AccessibilityAriaComboboxUneditable \
-  DISABLED_AccessibilityAriaComboboxUneditable
-#define MAYBE_AccessibilityAriaListBox DISABLED_AccessibilityAriaListBox
-#define MAYBE_AccessibilityAriaListBoxDisabled \
-  DISABLED_AccessibilityAriaListBoxDisabled
-#define MAYBE_AccessibilityAriaOption DISABLED_AccessibilityAriaOption
-#define MAYBE_AccessibilityAriaPosinset DISABLED_AccessibilityAriaPosinset
-#define MAYBE_AccessibilityAriaSelected DISABLED_AccessibilityAriaSelected
-#define MAYBE_AccessibilityAriaSetsize DISABLED_AccessibilityAriaSetsize
-#define MAYBE_AccessibilityAriaTree DISABLED_AccessibilityAriaTree
-#define MAYBE_AccessibilityButtonWithListboxPopup \
-  DISABLED_AccessibilityButtonWithListboxPopup
-#else
-#define MAYBE_AccessibilityAriaMenuItemRadio AccessibilityAriaMenuItemRadio
-#define MAYBE_AccessibilityAriaComboboxUneditable \
-  AccessibilityAriaComboboxUneditable
-#define MAYBE_AccessibilityAriaListBox AccessibilityAriaListBox
-#define MAYBE_AccessibilityAriaListBoxDisabled AccessibilityAriaListBoxDisabled
-#define MAYBE_AccessibilityAriaOption AccessibilityAriaOption
-#define MAYBE_AccessibilityAriaPosinset AccessibilityAriaPosinset
-#define MAYBE_AccessibilityAriaSelected AccessibilityAriaSelected
-#define MAYBE_AccessibilityAriaSetsize AccessibilityAriaSetsize
-#define MAYBE_AccessibilityAriaTree AccessibilityAriaTree
-#define MAYBE_AccessibilityButtonWithListboxPopup \
-  AccessibilityButtonWithListboxPopup
-#endif
-// ------------------------------------
-
 IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
                        MAYBE_AccessibilityAriaCombobox) {
   RunAriaTest(FILE_PATH_LITERAL("aria-combobox.html"));
 }
 
 IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
-                       MAYBE_AccessibilityAriaComboboxUneditable) {
+                       AccessibilityAriaComboboxUneditable) {
   RunAriaTest(FILE_PATH_LITERAL("aria-combobox-uneditable.html"));
 }
 
@@ -852,13 +818,12 @@
   RunAriaTest(FILE_PATH_LITERAL("aria-list.html"));
 }
 
-IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
-                       MAYBE_AccessibilityAriaListBox) {
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityAriaListBox) {
   RunAriaTest(FILE_PATH_LITERAL("aria-listbox.html"));
 }
 
 IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
-                       MAYBE_AccessibilityAriaListBoxDisabled) {
+                       AccessibilityAriaListBoxDisabled) {
   RunAriaTest(FILE_PATH_LITERAL("aria-listbox-disabled.html"));
 }
 // TODO(crbug.com/983802): Flaky.
@@ -938,7 +903,7 @@
 // crbug.com/442278 will stop creating new text elements representing title.
 // Re-baseline after the Blink change goes in
 IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
-                       MAYBE_AccessibilityAriaMenuItemRadio) {
+                       AccessibilityAriaMenuItemRadio) {
   RunAriaTest(FILE_PATH_LITERAL("aria-menuitemradio.html"));
 }
 
@@ -1027,8 +992,7 @@
   RunAriaTest(FILE_PATH_LITERAL("aria-none.html"));
 }
 
-IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
-                       MAYBE_AccessibilityAriaOption) {
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityAriaOption) {
   RunAriaTest(FILE_PATH_LITERAL("aria-option.html"));
 }
 
@@ -1036,8 +1000,7 @@
   RunAriaTest(FILE_PATH_LITERAL("aria-paragraph.html"));
 }
 
-IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
-                       MAYBE_AccessibilityAriaPosinset) {
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityAriaPosinset) {
   RunAriaTest(FILE_PATH_LITERAL("aria-posinset.html"));
 }
 
@@ -1133,8 +1096,7 @@
   RunAriaTest(FILE_PATH_LITERAL("aria-searchbox-with-selection.html"));
 }
 
-IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
-                       MAYBE_AccessibilityAriaSelected) {
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityAriaSelected) {
   RunAriaTest(FILE_PATH_LITERAL("aria-selected.html"));
 }
 
@@ -1142,8 +1104,7 @@
   RunAriaTest(FILE_PATH_LITERAL("aria-separator.html"));
 }
 
-IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
-                       MAYBE_AccessibilityAriaSetsize) {
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityAriaSetsize) {
   RunAriaTest(FILE_PATH_LITERAL("aria-setsize.html"));
 }
 
@@ -1256,7 +1217,7 @@
   RunAriaTest(FILE_PATH_LITERAL("aria-tooltip.html"));
 }
 
-IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, MAYBE_AccessibilityAriaTree) {
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityAriaTree) {
   RunAriaTest(FILE_PATH_LITERAL("aria-tree.html"));
 }
 
@@ -2562,7 +2523,7 @@
 }
 
 IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
-                       MAYBE_AccessibilityButtonWithListboxPopup) {
+                       AccessibilityButtonWithListboxPopup) {
   RunHtmlTest(FILE_PATH_LITERAL("button-with-listbox-popup.html"));
 }
 
diff --git a/content/browser/download/download_browsertest.cc b/content/browser/download/download_browsertest.cc
index 1139a99..bd8868e 100644
--- a/content/browser/download/download_browsertest.cc
+++ b/content/browser/download/download_browsertest.cc
@@ -4324,8 +4324,8 @@
 
 // Verify that if the second request fails after the beginning request takes
 // over and completes its slice, download should complete.
-// Flaky on Linux.  http://crbug.com/1106059
-#if defined(OS_LINUX) || defined(OS_CHROMEOS)
+// Flaky on Windows and Linux.  http://crbug.com/1106059
+#if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_CHROMEOS)
 #define MAYBE_MiddleSliceDelayedError DISABLED_MiddleSliceDelayedError
 #else
 #define MAYBE_MiddleSliceDelayedError MiddleSliceDelayedError
diff --git a/content/browser/renderer_host/navigation_request_browsertest.cc b/content/browser/renderer_host/navigation_request_browsertest.cc
index a5c4e431..440bc00 100644
--- a/content/browser/renderer_host/navigation_request_browsertest.cc
+++ b/content/browser/renderer_host/navigation_request_browsertest.cc
@@ -1445,9 +1445,13 @@
       navigation_b.GetNavigationHandle()->GetStartingSiteInstance();
   EXPECT_EQ(shell()->web_contents()->GetMainFrame()->GetSiteInstance(),
             starting_site_instance);
-  // Because of the sad tab, this is actually the b.com SiteInstance, which
-  // commits immediately after starting the navigation and has a process.
-  EXPECT_EQ(GURL("http://b.com"), starting_site_instance->GetSiteURL());
+  if (ShouldSkipEarlyCommitPendingForCrashedFrame()) {
+    EXPECT_EQ(GURL("http://a.com"), starting_site_instance->GetSiteURL());
+  } else {
+    // Because of the sad tab, this is actually the b.com SiteInstance, which
+    // commits immediately after starting the navigation and has a process.
+    EXPECT_EQ(GURL("http://b.com"), starting_site_instance->GetSiteURL());
+  }
   EXPECT_TRUE(starting_site_instance->HasProcess());
 
   // In https://crbug.com/949977, we used the a.com SiteInstance here and didn't
diff --git a/content/browser/renderer_host/render_document_host_user_data_browsertest.cc b/content/browser/renderer_host/render_document_host_user_data_browsertest.cc
index 0460624..e083696 100644
--- a/content/browser/renderer_host/render_document_host_user_data_browsertest.cc
+++ b/content/browser/renderer_host/render_document_host_user_data_browsertest.cc
@@ -362,6 +362,9 @@
 // RenderFrameHost (of old URL) not alive.
 IN_PROC_BROWSER_TEST_F(RenderDocumentHostUserDataTest,
                        CheckWithFrameCrashBeforeNavigation) {
+  if (ShouldSkipEarlyCommitPendingForCrashedFrame())
+    return;
+
   ASSERT_TRUE(embedded_test_server()->Start());
   GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
   GURL url_b(embedded_test_server()->GetURL("b.com", "/title2.html"));
diff --git a/content/browser/renderer_host/render_frame_host_impl_browsertest.cc b/content/browser/renderer_host/render_frame_host_impl_browsertest.cc
index 69331b04..c120700 100644
--- a/content/browser/renderer_host/render_frame_host_impl_browsertest.cc
+++ b/content/browser/renderer_host/render_frame_host_impl_browsertest.cc
@@ -3804,8 +3804,13 @@
   RenderFrameHostImpl* current_rfh =
       root->render_manager()->current_frame_host();
   NavigationRequest* navigation_request = root->navigation_request();
-  EXPECT_EQ(navigation_request->associated_site_instance_type(),
-            NavigationRequest::AssociatedSiteInstanceType::CURRENT);
+  if (ShouldSkipEarlyCommitPendingForCrashedFrame()) {
+    EXPECT_EQ(navigation_request->associated_site_instance_type(),
+              NavigationRequest::AssociatedSiteInstanceType::SPECULATIVE);
+  } else {
+    EXPECT_EQ(navigation_request->associated_site_instance_type(),
+              NavigationRequest::AssociatedSiteInstanceType::CURRENT);
+  }
 
   // 4) Check the LifecycleState of B's RFH.
   EXPECT_EQ(LifecycleState::kActive, current_rfh->lifecycle_state());
diff --git a/content/browser/service_worker/service_worker_context_core.cc b/content/browser/service_worker/service_worker_context_core.cc
index e58b69d..83ed170 100644
--- a/content/browser/service_worker/service_worker_context_core.cc
+++ b/content/browser/service_worker/service_worker_context_core.cc
@@ -555,7 +555,7 @@
 void ServiceWorkerContextCore::PerformStorageCleanup(
     base::OnceClosure callback) {
   DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
-  GetStorageControl()->PerformStorageCleanup(std::move(callback));
+  registry()->PerformStorageCleanup(std::move(callback));
 }
 
 void ServiceWorkerContextCore::DidGetRegistrationsForDeleteForOrigin(
diff --git a/content/browser/service_worker/service_worker_context_wrapper.cc b/content/browser/service_worker/service_worker_context_wrapper.cc
index c1aff885..06249c4c 100644
--- a/content/browser/service_worker/service_worker_context_wrapper.cc
+++ b/content/browser/service_worker/service_worker_context_wrapper.cc
@@ -596,11 +596,10 @@
     return;
   }
 
-  context()->registry()->GetRemoteStorageControl()->GetRegisteredOrigins(
-      base::BindOnce(
-          &ServiceWorkerContextWrapper::
-              DidGetRegisteredOriginsForGetInstalledRegistrationOrigins,
-          host_filter, std::move(callback), task_runner_for_callback));
+  context()->registry()->GetRegisteredOrigins(base::BindOnce(
+      &ServiceWorkerContextWrapper::
+          DidGetRegisteredOriginsForGetInstalledRegistrationOrigins,
+      host_filter, std::move(callback), task_runner_for_callback));
 }
 
 void ServiceWorkerContextWrapper::GetAllOriginsInfo(
@@ -1568,9 +1567,8 @@
       core_observer_list_.get(), this);
 
   if (storage_partition_) {
-    context()->registry()->GetRemoteStorageControl()->GetRegisteredOrigins(
-        base::BindOnce(&ServiceWorkerContextWrapper::DidGetRegisteredOrigins,
-                       this));
+    context()->registry()->GetRegisteredOrigins(base::BindOnce(
+        &ServiceWorkerContextWrapper::DidGetRegisteredOrigins, this));
   }
 }
 
diff --git a/content/browser/service_worker/service_worker_registry.cc b/content/browser/service_worker/service_worker_registry.cc
index e704aff..cef001f 100644
--- a/content/browser/service_worker/service_worker_registry.cc
+++ b/content/browser/service_worker/service_worker_registry.cc
@@ -645,6 +645,17 @@
                      weak_factory_.GetWeakPtr(), std::move(callback)));
 }
 
+void ServiceWorkerRegistry::GetRegisteredOrigins(
+    GetRegisteredOriginsCallback callback) {
+  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  GetRemoteStorageControl()->GetRegisteredOrigins(std::move(callback));
+}
+
+void ServiceWorkerRegistry::PerformStorageCleanup(base::OnceClosure callback) {
+  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  GetRemoteStorageControl()->PerformStorageCleanup(std::move(callback));
+}
+
 void ServiceWorkerRegistry::PrepareForDeleteAndStarOver() {
   should_schedule_delete_and_start_over_ = false;
   GetRemoteStorageControl()->Disable();
diff --git a/content/browser/service_worker/service_worker_registry.h b/content/browser/service_worker/service_worker_registry.h
index 230acc1..e01237d 100644
--- a/content/browser/service_worker/service_worker_registry.h
+++ b/content/browser/service_worker/service_worker_registry.h
@@ -47,6 +47,8 @@
   using FindRegistrationCallback = base::OnceCallback<void(
       blink::ServiceWorkerStatusCode status,
       scoped_refptr<ServiceWorkerRegistration> registration)>;
+  using GetRegisteredOriginsCallback =
+      storage::mojom::ServiceWorkerStorageControl::GetRegisteredOriginsCallback;
   using GetRegistrationsCallback = base::OnceCallback<void(
       blink::ServiceWorkerStatusCode status,
       const std::vector<scoped_refptr<ServiceWorkerRegistration>>&
@@ -229,17 +231,17 @@
   void GetUserDataForAllRegistrationsByKeyPrefix(
       const std::string& key_prefix,
       GetUserDataForAllRegistrationsCallback callback);
-
-  mojo::Remote<storage::mojom::ServiceWorkerStorageControl>&
-  GetRemoteStorageControl();
-
+  void GetRegisteredOrigins(GetRegisteredOriginsCallback callback);
+  void PerformStorageCleanup(base::OnceClosure callback);
   // Disables the internal storage to prepare for error recovery.
   void PrepareForDeleteAndStarOver();
-
   // Deletes this registry and internal storage, then starts over for error
   // recovery.
   void DeleteAndStartOver(StatusCallback callback);
 
+  mojo::Remote<storage::mojom::ServiceWorkerStorageControl>&
+  GetRemoteStorageControl();
+
   void SimulateStorageRestartForTesting();
 
  private:
diff --git a/content/browser/service_worker/service_worker_registry_unittest.cc b/content/browser/service_worker/service_worker_registry_unittest.cc
index f969079..08ed528b 100644
--- a/content/browser/service_worker/service_worker_registry_unittest.cc
+++ b/content/browser/service_worker/service_worker_registry_unittest.cc
@@ -304,12 +304,11 @@
   std::vector<url::Origin> GetRegisteredOrigins() {
     std::vector<url::Origin> result;
     base::RunLoop loop;
-    registry()->GetRemoteStorageControl()->GetRegisteredOrigins(
-        base::BindLambdaForTesting(
-            [&](const std::vector<url::Origin>& origins) {
-              result = origins;
-              loop.Quit();
-            }));
+    registry()->GetRegisteredOrigins(base::BindLambdaForTesting(
+        [&](const std::vector<url::Origin>& origins) {
+          result = origins;
+          loop.Quit();
+        }));
     loop.Run();
     return result;
   }
diff --git a/content/browser/webauth/authenticator_impl_unittest.cc b/content/browser/webauth/authenticator_impl_unittest.cc
index 45766ca..74539f6 100644
--- a/content/browser/webauth/authenticator_impl_unittest.cc
+++ b/content/browser/webauth/authenticator_impl_unittest.cc
@@ -3872,6 +3872,23 @@
             virtual_device_factory_->mutable_state()->pin_retries);
 }
 
+TEST_F(PINAuthenticatorImplTest, MakeCredentialAlwaysUv) {
+  // Test that if an authenticator is reporting alwaysUv = 1, UV is attempted
+  // even if the user verification requirement is discouraged.
+  device::VirtualCtap2Device::Config config;
+  config.pin_support = true;
+  config.always_uv = true;
+  virtual_device_factory_->SetCtap2Config(config);
+  virtual_device_factory_->mutable_state()->pin = kTestPIN;
+  test_client_.expected = {{device::kMaxPinRetries, kTestPIN}};
+
+  MakeCredentialResult result =
+      AuthenticatorMakeCredential(make_credential_options(
+          device::UserVerificationRequirement::kDiscouraged));
+  EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
+  EXPECT_TRUE(HasUV(result.response));
+}
+
 TEST_F(PINAuthenticatorImplTest, GetAssertion) {
   typedef int Expectations[3][3];
   // kExpectedWithUISupport enumerates the expected behaviour when the embedder
@@ -4004,6 +4021,25 @@
             *test_client_.failure_reason);
 }
 
+TEST_F(PINAuthenticatorImplTest, GetAssertionAlwaysUv) {
+  // Test that if an authenticator is reporting alwaysUv = 1, UV is attempted
+  // even if the user verification requirement is discouraged.
+  device::VirtualCtap2Device::Config config;
+  config.pin_support = true;
+  config.always_uv = true;
+  virtual_device_factory_->SetCtap2Config(config);
+  virtual_device_factory_->mutable_state()->pin = kTestPIN;
+  PublicKeyCredentialRequestOptionsPtr options =
+      get_credential_options(device::UserVerificationRequirement::kDiscouraged);
+  ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
+      options->allow_credentials[0].id(), kTestRelyingPartyId));
+  test_client_.expected = {{device::kMaxPinRetries, kTestPIN}};
+
+  GetAssertionResult result = AuthenticatorGetAssertion(std::move(options));
+  EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
+  EXPECT_TRUE(HasUV(result.response));
+}
+
 TEST_F(PINAuthenticatorImplTest, MakeCredentialNoSupportedAlgorithm) {
   NavigateAndCommit(GURL(kTestOrigin1));
 
diff --git a/content/browser/xr/metrics/webxr_session_tracker.cc b/content/browser/xr/metrics/webxr_session_tracker.cc
index 5da8bde7..a782231 100644
--- a/content/browser/xr/metrics/webxr_session_tracker.cc
+++ b/content/browser/xr/metrics/webxr_session_tracker.cc
@@ -76,6 +76,7 @@
     case XRSessionFeature::CAMERA_ACCESS:
     case XRSessionFeature::PLANE_DETECTION:
     case XRSessionFeature::DEPTH:
+    case XRSessionFeature::IMAGE_TRACKING:
       // Not recording metrics for these features currently.
       // TODO(https://crbug.com/965729): Add metrics for the AR-related features
       // that are enabled by default.
@@ -116,6 +117,7 @@
     case XRSessionFeature::CAMERA_ACCESS:
     case XRSessionFeature::PLANE_DETECTION:
     case XRSessionFeature::DEPTH:
+    case XRSessionFeature::IMAGE_TRACKING:
       // Not recording metrics for these features currently.
       // TODO(https://crbug.com/965729): Add metrics for the AR-related features
       // that are enabled by default.
diff --git a/content/browser/xr/service/browser_xr_runtime_impl.cc b/content/browser/xr/service/browser_xr_runtime_impl.cc
index 9b3b4d2..08af1c2 100644
--- a/content/browser/xr/service/browser_xr_runtime_impl.cc
+++ b/content/browser/xr/service/browser_xr_runtime_impl.cc
@@ -145,6 +145,7 @@
     device::mojom::XRSessionFeature::ANCHORS,
     device::mojom::XRSessionFeature::PLANE_DETECTION,
     device::mojom::XRSessionFeature::DEPTH,
+    device::mojom::XRSessionFeature::IMAGE_TRACKING,
 };
 
 #if BUILDFLAG(ENABLE_WINDOWS_MR)
diff --git a/content/browser/xr/service/vr_service_impl.cc b/content/browser/xr/service/vr_service_impl.cc
index cee6ad2..0804b79 100644
--- a/content/browser/xr/service/vr_service_impl.cc
+++ b/content/browser/xr/service/vr_service_impl.cc
@@ -527,9 +527,21 @@
   }
 
   if (device::XRSessionModeUtils::IsImmersive(runtime_options->mode)) {
+    if (!request.options->tracked_images.empty()) {
+      DVLOG(3) << __func__ << ": request.options->tracked_images.size()="
+               << request.options->tracked_images.size();
+      runtime_options->tracked_images.resize(
+          request.options->tracked_images.size());
+      for (std::size_t i = 0; i < request.options->tracked_images.size(); ++i) {
+        runtime_options->tracked_images[i] =
+            request.options->tracked_images[i].Clone();
+      }
+    }
+
     base::OnceCallback<void(device::mojom::XRSessionPtr)> immersive_callback =
         base::BindOnce(&VRServiceImpl::OnImmersiveSessionCreated,
                        weak_ptr_factory_.GetWeakPtr(), std::move(request));
+
     runtime->RequestSession(this, std::move(runtime_options),
                             std::move(immersive_callback));
   } else {
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index 767a877..e5b3456 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -214,6 +214,7 @@
     {wf::EnableWebXRAnchors, features::kWebXrIncubations},
     {wf::EnableWebXRCameraAccess, features::kWebXrIncubations},
     {wf::EnableWebXRDepth, features::kWebXrIncubations},
+    {wf::EnableWebXRImageTracking, features::kWebXrIncubations},
     {wf::EnableWebXRLightEstimation, features::kWebXrIncubations},
     {wf::EnableWebXRPlaneDetection, features::kWebXrIncubations},
     {wf::EnableWebXRReflectionEstimation, features::kWebXrIncubations},
diff --git a/content/public/browser/gpu_utils.cc b/content/public/browser/gpu_utils.cc
index c072216b..a5b5117 100644
--- a/content/public/browser/gpu_utils.cc
+++ b/content/public/browser/gpu_utils.cc
@@ -99,6 +99,8 @@
 
   gpu_preferences.enable_oop_rasterization_ddl =
       base::FeatureList::IsEnabled(features::kOopRasterizationDDL);
+  gpu_preferences.enable_vulkan_protected_memory =
+      command_line->HasSwitch(switches::kEnableVulkanProtectedMemory);
   gpu_preferences.enforce_vulkan_protected_memory =
       command_line->HasSwitch(switches::kEnforceVulkanProtectedMemory);
   gpu_preferences.disable_vulkan_fallback_to_gl_for_testing =
diff --git a/content/public/test/render_view_test.cc b/content/public/test/render_view_test.cc
index 142acf4..14ed985 100644
--- a/content/public/test/render_view_test.cc
+++ b/content/public/test/render_view_test.cc
@@ -54,7 +54,7 @@
 #include "third_party/blink/public/platform/scheduler/web_thread_scheduler.h"
 #include "third_party/blink/public/platform/web_runtime_features.h"
 #include "third_party/blink/public/platform/web_url_loader_client.h"
-#include "third_party/blink/public/platform/web_url_request.h"
+#include "third_party/blink/public/platform/web_url_request_extra_data.h"
 #include "third_party/blink/public/web/blink.h"
 #include "third_party/blink/public/web/web_document.h"
 #include "third_party/blink/public/web/web_frame_widget.h"
@@ -126,7 +126,7 @@
 
   void LoadSynchronously(
       std::unique_ptr<network::ResourceRequest> request,
-      scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
+      scoped_refptr<blink::WebURLRequestExtraData> url_request_extra_data,
       int requestor_id,
       bool pass_response_pipe_to_client,
       bool no_mime_sniffing,
@@ -144,7 +144,7 @@
 
   void LoadAsynchronously(
       std::unique_ptr<network::ResourceRequest> request,
-      scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
+      scoped_refptr<blink::WebURLRequestExtraData> url_request_extra_data,
       int requestor_id,
       bool no_mime_sniffing,
       std::unique_ptr<blink::ResourceLoadInfoNotifierWrapper>,
diff --git a/content/renderer/BUILD.gn b/content/renderer/BUILD.gn
index de552149..68f62b0 100644
--- a/content/renderer/BUILD.gn
+++ b/content/renderer/BUILD.gn
@@ -83,8 +83,6 @@
     "loader/child_url_loader_factory_bundle.h",
     "loader/navigation_body_loader.cc",
     "loader/navigation_body_loader.h",
-    "loader/request_extra_data.cc",
-    "loader/request_extra_data.h",
     "loader/resource_dispatcher.cc",
     "loader/resource_dispatcher.h",
     "loader/sync_load_context.cc",
diff --git a/content/renderer/loader/request_extra_data.cc b/content/renderer/loader/request_extra_data.cc
deleted file mode 100644
index 90e0f6b..0000000
--- a/content/renderer/loader/request_extra_data.cc
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) 2012 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 "content/renderer/loader/request_extra_data.h"
-
-#include "services/network/public/cpp/resource_request.h"
-
-using blink::WebString;
-
-namespace content {
-
-RequestExtraData::RequestExtraData() = default;
-RequestExtraData::~RequestExtraData() = default;
-
-void RequestExtraData::CopyToResourceRequest(
-    network::ResourceRequest* request) const {
-  request->render_frame_id = render_frame_id_;
-  request->is_main_frame = is_main_frame_;
-
-  request->transition_type = transition_type_;
-  request->originated_from_service_worker = originated_from_service_worker_;
-
-  request->force_ignore_site_for_cookies = force_ignore_site_for_cookies_;
-}
-
-}  // namespace content
diff --git a/content/renderer/loader/request_extra_data.h b/content/renderer/loader/request_extra_data.h
deleted file mode 100644
index c7e4451..0000000
--- a/content/renderer/loader/request_extra_data.h
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright (c) 2012 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 CONTENT_CHILD_REQUEST_EXTRA_DATA_H_
-#define CONTENT_CHILD_REQUEST_EXTRA_DATA_H_
-
-#include <utility>
-
-#include "base/compiler_specific.h"
-#include "base/macros.h"
-#include "content/common/content_export.h"
-#include "content/common/navigation_params.h"
-#include "content/renderer/loader/web_url_loader_impl.h"
-#include "third_party/blink/public/common/loader/url_loader_throttle.h"
-#include "third_party/blink/public/platform/web_frame_request_blocker.h"
-#include "third_party/blink/public/platform/web_string.h"
-#include "third_party/blink/public/platform/web_url_request.h"
-
-namespace network {
-struct ResourceRequest;
-}
-
-namespace content {
-
-// Can be used by callers to store extra data on every ResourceRequest
-// which will be incorporated into the ResourceHostMsg_RequestResource message
-// sent by ResourceDispatcher.
-class CONTENT_EXPORT RequestExtraData : public blink::WebURLRequest::ExtraData {
- public:
-  RequestExtraData();
-
-  // |custom_user_agent| is used to communicate an overriding custom user agent
-  // to |RenderViewImpl::willSendRequest()|; set to a null string to indicate no
-  // override and an empty string to indicate that there should be no user
-  // agent.
-  const blink::WebString& custom_user_agent() const {
-    return custom_user_agent_;
-  }
-  void set_custom_user_agent(const blink::WebString& custom_user_agent) {
-    custom_user_agent_ = custom_user_agent;
-  }
-
-  std::vector<std::unique_ptr<blink::URLLoaderThrottle>>
-  TakeURLLoaderThrottles() {
-    return std::move(url_loader_throttles_);
-  }
-  void set_url_loader_throttles(
-      std::vector<std::unique_ptr<blink::URLLoaderThrottle>> throttles) {
-    url_loader_throttles_ = std::move(throttles);
-  }
-  void set_frame_request_blocker(
-      scoped_refptr<blink::WebFrameRequestBlocker> frame_request_blocker) {
-    frame_request_blocker_ = frame_request_blocker;
-  }
-  scoped_refptr<blink::WebFrameRequestBlocker> frame_request_blocker() {
-    return frame_request_blocker_;
-  }
-  bool allow_cross_origin_auth_prompt() const {
-    return allow_cross_origin_auth_prompt_;
-  }
-  void set_allow_cross_origin_auth_prompt(bool allow_cross_origin_auth_prompt) {
-    allow_cross_origin_auth_prompt_ = allow_cross_origin_auth_prompt;
-  }
-
-  void CopyToResourceRequest(network::ResourceRequest* request) const;
-
- private:
-  ~RequestExtraData() override;
-
-  blink::WebString custom_user_agent_;
-  std::vector<std::unique_ptr<blink::URLLoaderThrottle>> url_loader_throttles_;
-  scoped_refptr<blink::WebFrameRequestBlocker> frame_request_blocker_;
-  bool allow_cross_origin_auth_prompt_ = false;
-
-  DISALLOW_COPY_AND_ASSIGN(RequestExtraData);
-};
-
-}  // namespace content
-
-#endif  // CONTENT_CHILD_REQUEST_EXTRA_DATA_H_
diff --git a/content/renderer/loader/resource_dispatcher.cc b/content/renderer/loader/resource_dispatcher.cc
index 3300c56..c11c0a99 100644
--- a/content/renderer/loader/resource_dispatcher.cc
+++ b/content/renderer/loader/resource_dispatcher.cc
@@ -25,7 +25,6 @@
 #include "content/common/navigation_params.h"
 #include "content/public/renderer/request_peer.h"
 #include "content/public/renderer/resource_dispatcher_delegate.h"
-#include "content/renderer/loader/request_extra_data.h"
 #include "content/renderer/loader/sync_load_context.h"
 #include "content/renderer/loader/url_loader_client_impl.h"
 #include "content/renderer/render_frame_impl.h"
diff --git a/content/renderer/loader/resource_dispatcher_unittest.cc b/content/renderer/loader/resource_dispatcher_unittest.cc
index 49d8473..e101ed1 100644
--- a/content/renderer/loader/resource_dispatcher_unittest.cc
+++ b/content/renderer/loader/resource_dispatcher_unittest.cc
@@ -23,7 +23,6 @@
 #include "content/public/common/referrer.h"
 #include "content/public/renderer/request_peer.h"
 #include "content/public/renderer/resource_dispatcher_delegate.h"
-#include "content/renderer/loader/request_extra_data.h"
 #include "content/renderer/loader/test_request_peer.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
@@ -41,6 +40,7 @@
 #include "third_party/blink/public/common/loader/referrer_utils.h"
 #include "third_party/blink/public/platform/resource_load_info_notifier_wrapper.h"
 #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
+#include "third_party/blink/public/platform/web_url_request_extra_data.h"
 #include "url/gurl.h"
 
 namespace content {
@@ -129,8 +129,9 @@
     request->priority = net::LOW;
     request->mode = network::mojom::RequestMode::kNoCors;
 
-    auto extra_data = base::MakeRefCounted<RequestExtraData>();
-    extra_data->CopyToResourceRequest(request.get());
+    auto url_request_extra_data =
+        base::MakeRefCounted<blink::WebURLRequestExtraData>();
+    url_request_extra_data->CopyToResourceRequest(request.get());
 
     return request;
   }
diff --git a/content/renderer/loader/web_url_loader_impl.cc b/content/renderer/loader/web_url_loader_impl.cc
index 55645c20..e2edca9 100644
--- a/content/renderer/loader/web_url_loader_impl.cc
+++ b/content/renderer/loader/web_url_loader_impl.cc
@@ -36,7 +36,6 @@
 #include "content/public/common/content_features.h"
 #include "content/public/common/navigation_policy.h"
 #include "content/public/renderer/request_peer.h"
-#include "content/renderer/loader/request_extra_data.h"
 #include "content/renderer/loader/resource_dispatcher.h"
 #include "content/renderer/variations_render_thread_observer.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
@@ -84,6 +83,7 @@
 #include "third_party/blink/public/platform/web_url_error.h"
 #include "third_party/blink/public/platform/web_url_loader_client.h"
 #include "third_party/blink/public/platform/web_url_request.h"
+#include "third_party/blink/public/platform/web_url_request_extra_data.h"
 #include "third_party/blink/public/platform/web_url_response.h"
 #include "third_party/blink/public/web/web_local_frame.h"
 #include "third_party/blink/public/web/web_security_policy.h"
@@ -300,17 +300,19 @@
   response->SetSecurityDetails(webSecurityDetails);
 }
 
-bool IsBannedCrossSiteAuth(network::ResourceRequest* resource_request,
-                           WebURLRequest::ExtraData* passed_extra_data) {
+bool IsBannedCrossSiteAuth(
+    network::ResourceRequest* resource_request,
+    blink::WebURLRequestExtraData* passed_url_request_extra_data) {
   auto& request_url = resource_request->url;
   auto& first_party = resource_request->site_for_cookies;
 
   bool allow_cross_origin_auth_prompt = false;
-  if (passed_extra_data) {
-    RequestExtraData* extra_data =
-        static_cast<RequestExtraData*>(passed_extra_data);
+  if (passed_url_request_extra_data) {
+    blink::WebURLRequestExtraData* url_request_extra_data =
+        static_cast<blink::WebURLRequestExtraData*>(
+            passed_url_request_extra_data);
     allow_cross_origin_auth_prompt =
-        extra_data->allow_cross_origin_auth_prompt();
+        url_request_extra_data->allow_cross_origin_auth_prompt();
   }
 
   if (first_party.IsFirstPartyWithSchemefulMode(
@@ -378,15 +380,16 @@
   void SetDefersLoading(bool value);
   void DidChangePriority(WebURLRequest::Priority new_priority,
                          int intra_priority_value);
-  void Start(std::unique_ptr<network::ResourceRequest> request,
-             scoped_refptr<blink::WebURLRequest::ExtraData> request_extra_data,
-             int requestor_id,
-             bool pass_response_pipe_to_client,
-             bool no_mime_sniffing,
-             base::TimeDelta timeout_interval,
-             blink::SyncLoadResponse* sync_load_response,
-             std::unique_ptr<blink::ResourceLoadInfoNotifierWrapper>
-                 resource_load_info_notifier_wrapper);
+  void Start(
+      std::unique_ptr<network::ResourceRequest> request,
+      scoped_refptr<blink::WebURLRequestExtraData> url_request_extra_data,
+      int requestor_id,
+      bool pass_response_pipe_to_client,
+      bool no_mime_sniffing,
+      base::TimeDelta timeout_interval,
+      blink::SyncLoadResponse* sync_load_response,
+      std::unique_ptr<blink::ResourceLoadInfoNotifierWrapper>
+          resource_load_info_notifier_wrapper);
 
   void OnUploadProgress(uint64_t position, uint64_t size);
   bool OnReceivedRedirect(const net::RedirectInfo& redirect_info,
@@ -535,7 +538,7 @@
 
 void WebURLLoaderImpl::Context::Start(
     std::unique_ptr<network::ResourceRequest> request,
-    scoped_refptr<blink::WebURLRequest::ExtraData> passed_extra_data,
+    scoped_refptr<blink::WebURLRequestExtraData> passed_url_request_extra_data,
     int requestor_id,
     bool pass_response_pipe_to_client,
     bool no_mime_sniffing,
@@ -559,7 +562,8 @@
 
   // TODO(yhirano): Move the logic below to blink/platform/loader.
   if (resource_type == blink::mojom::ResourceType::kImage &&
-      IsBannedCrossSiteAuth(request.get(), passed_extra_data.get())) {
+      IsBannedCrossSiteAuth(request.get(),
+                            passed_url_request_extra_data.get())) {
     // Prevent third-party image content from prompting for login, as this
     // is often a scam to extract credentials for another domain from the
     // user. Only block image loads, as the attack applies largely to the
@@ -572,15 +576,17 @@
     request->load_flags |= net::LOAD_DO_NOT_USE_EMBEDDED_IDENTITY;
   }
 
-  scoped_refptr<RequestExtraData> empty_extra_data;
-  RequestExtraData* extra_data;
-  if (passed_extra_data) {
-    extra_data = static_cast<RequestExtraData*>(passed_extra_data.get());
+  scoped_refptr<blink::WebURLRequestExtraData> empty_url_request_extra_data;
+  blink::WebURLRequestExtraData* url_request_extra_data;
+  if (passed_url_request_extra_data) {
+    url_request_extra_data = static_cast<blink::WebURLRequestExtraData*>(
+        passed_url_request_extra_data.get());
   } else {
-    empty_extra_data = base::MakeRefCounted<RequestExtraData>();
-    extra_data = empty_extra_data.get();
+    empty_url_request_extra_data =
+        base::MakeRefCounted<blink::WebURLRequestExtraData>();
+    url_request_extra_data = empty_url_request_extra_data.get();
   }
-  extra_data->CopyToResourceRequest(request.get());
+  url_request_extra_data->CopyToResourceRequest(request.get());
 
   auto peer = std::make_unique<WebURLLoaderImpl::RequestPeerImpl>(this);
 
@@ -592,12 +598,13 @@
     request->corb_excluded = true;
   }
 
-  auto throttles = extra_data->TakeURLLoaderThrottles();
+  auto throttles =
+      url_request_extra_data->TakeURLLoaderThrottles().ReleaseVector();
   // The frame request blocker is only for a frame's subresources.
-  if (extra_data->frame_request_blocker() &&
+  if (url_request_extra_data->frame_request_blocker() &&
       !blink::IsResourceTypeFrame(resource_type)) {
-    auto throttle =
-        extra_data->frame_request_blocker()->GetThrottleIfRequestsBlocked();
+    auto throttle = url_request_extra_data->frame_request_blocker()
+                        ->GetThrottleIfRequestsBlocked();
     if (throttle)
       throttles.push_back(std::move(throttle));
   }
@@ -997,7 +1004,7 @@
 
 void WebURLLoaderImpl::LoadSynchronously(
     std::unique_ptr<network::ResourceRequest> request,
-    scoped_refptr<blink::WebURLRequest::ExtraData> request_extra_data,
+    scoped_refptr<blink::WebURLRequestExtraData> url_request_extra_data,
     int requestor_id,
     bool pass_response_pipe_to_client,
     bool no_mime_sniffing,
@@ -1018,7 +1025,7 @@
   context_->set_client(client);
 
   const bool report_raw_headers = request->report_raw_headers;
-  context_->Start(std::move(request), std::move(request_extra_data),
+  context_->Start(std::move(request), std::move(url_request_extra_data),
                   requestor_id, pass_response_pipe_to_client, no_mime_sniffing,
                   timeout_interval, &sync_load_response,
                   std::move(resource_load_info_notifier_wrapper));
@@ -1064,7 +1071,7 @@
 
 void WebURLLoaderImpl::LoadAsynchronously(
     std::unique_ptr<network::ResourceRequest> request,
-    scoped_refptr<blink::WebURLRequest::ExtraData> request_extra_data,
+    scoped_refptr<blink::WebURLRequestExtraData> url_request_extra_data,
     int requestor_id,
     bool no_mime_sniffing,
     std::unique_ptr<blink::ResourceLoadInfoNotifierWrapper>
@@ -1075,7 +1082,7 @@
   DCHECK(!context_->client());
 
   context_->set_client(client);
-  context_->Start(std::move(request), std::move(request_extra_data),
+  context_->Start(std::move(request), std::move(url_request_extra_data),
                   requestor_id,
                   /*pass_response_pipe_to_client=*/false, no_mime_sniffing,
                   base::TimeDelta(), nullptr,
diff --git a/content/renderer/loader/web_url_loader_impl.h b/content/renderer/loader/web_url_loader_impl.h
index 6d5c806..0af3bef 100644
--- a/content/renderer/loader/web_url_loader_impl.h
+++ b/content/renderer/loader/web_url_loader_impl.h
@@ -22,6 +22,7 @@
 
 namespace blink {
 class ResourceLoadInfoNotifierWrapper;
+class WebURLRequestExtraData;
 }  // namespace blink
 
 namespace content {
@@ -72,7 +73,7 @@
   // WebURLLoader methods:
   void LoadSynchronously(
       std::unique_ptr<network::ResourceRequest> request,
-      scoped_refptr<blink::WebURLRequest::ExtraData> request_extra_data,
+      scoped_refptr<blink::WebURLRequestExtraData> url_request_extra_data,
       int requestor_id,
       bool pass_response_pipe_to_client,
       bool no_mime_sniffing,
@@ -88,7 +89,7 @@
           resource_load_info_notifier_wrapper) override;
   void LoadAsynchronously(
       std::unique_ptr<network::ResourceRequest> request,
-      scoped_refptr<blink::WebURLRequest::ExtraData> request_extra_data,
+      scoped_refptr<blink::WebURLRequestExtraData> url_request_extra_data,
       int requestor_id,
       bool no_mime_sniffing,
       std::unique_ptr<blink::ResourceLoadInfoNotifierWrapper>
diff --git a/content/renderer/loader/web_url_loader_impl_unittest.cc b/content/renderer/loader/web_url_loader_impl_unittest.cc
index 4e9c8735..b4585fe 100644
--- a/content/renderer/loader/web_url_loader_impl_unittest.cc
+++ b/content/renderer/loader/web_url_loader_impl_unittest.cc
@@ -20,7 +20,6 @@
 #include "base/time/time.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/renderer/request_peer.h"
-#include "content/renderer/loader/request_extra_data.h"
 #include "content/renderer/loader/resource_dispatcher.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/system/data_pipe.h"
@@ -46,6 +45,7 @@
 #include "third_party/blink/public/platform/web_url_error.h"
 #include "third_party/blink/public/platform/web_url_loader_client.h"
 #include "third_party/blink/public/platform/web_url_request.h"
+#include "third_party/blink/public/platform/web_url_request_extra_data.h"
 #include "third_party/blink/public/platform/web_url_response.h"
 #include "third_party/blink/public/platform/web_vector.h"
 #include "url/gurl.h"
@@ -307,7 +307,8 @@
         static_cast<int>(blink::mojom::ResourceType::kSubResource);
     request->priority = net::IDLE;
     client()->loader()->LoadAsynchronously(
-        std::move(request), /*extra_data=*/nullptr, /*requestor_id=*/0,
+        std::move(request), /*url_request_extra_data=*/nullptr,
+        /*requestor_id=*/0,
         /*no_mime_sniffing=*/false,
         std::make_unique<blink::ResourceLoadInfoNotifierWrapper>(
             /*resource_load_info_notifier=*/nullptr),
@@ -667,7 +668,8 @@
   blink::WebBlobInfo downloaded_blob;
 
   client()->loader()->LoadSynchronously(
-      std::move(request), /*extra_data=*/nullptr, /*requestor_id=*/0,
+      std::move(request), /*url_request_extra_data=*/nullptr,
+      /*requestor_id=*/0,
       /*pass_response_pipe_to_client=*/false, /*no_mime_sniffing=*/false,
       base::TimeDelta(), nullptr, response, error, data, encoded_data_length,
       encoded_body_length, downloaded_blob,
diff --git a/content/renderer/loader/web_worker_fetch_context_impl.cc b/content/renderer/loader/web_worker_fetch_context_impl.cc
index 43294fc..4884da7 100644
--- a/content/renderer/loader/web_worker_fetch_context_impl.cc
+++ b/content/renderer/loader/web_worker_fetch_context_impl.cc
@@ -21,7 +21,6 @@
 #include "content/public/renderer/url_loader_throttle_provider.h"
 #include "content/public/renderer/websocket_handshake_throttle_provider.h"
 #include "content/renderer/loader/child_url_loader_factory_bundle.h"
-#include "content/renderer/loader/request_extra_data.h"
 #include "content/renderer/loader/resource_dispatcher.h"
 #include "content/renderer/loader/web_url_loader_impl.h"
 #include "content/renderer/service_worker/controller_service_worker_connector.h"
@@ -36,6 +35,7 @@
 #include "third_party/blink/public/platform/web_code_cache_loader.h"
 #include "third_party/blink/public/platform/web_frame_request_blocker.h"
 #include "third_party/blink/public/platform/web_security_origin.h"
+#include "third_party/blink/public/platform/web_url_request_extra_data.h"
 
 namespace content {
 
@@ -450,14 +450,15 @@
                                "1");
   }
 
-  auto extra_data = base::MakeRefCounted<RequestExtraData>();
-  extra_data->set_render_frame_id(ancestor_frame_id_);
-  extra_data->set_frame_request_blocker(frame_request_blocker_);
+  auto url_request_extra_data =
+      base::MakeRefCounted<blink::WebURLRequestExtraData>();
+  url_request_extra_data->set_render_frame_id(ancestor_frame_id_);
+  url_request_extra_data->set_frame_request_blocker(frame_request_blocker_);
   if (throttle_provider_) {
-    extra_data->set_url_loader_throttles(
+    url_request_extra_data->set_url_loader_throttles(
         throttle_provider_->CreateThrottles(ancestor_frame_id_, request));
   }
-  request.SetExtraData(std::move(extra_data));
+  request.SetURLRequestExtraData(std::move(url_request_extra_data));
 
   if (g_rewrite_url)
     request.SetUrl(g_rewrite_url(request.Url().GetString().Utf8(), false));
diff --git a/content/renderer/pepper/url_request_info_util.cc b/content/renderer/pepper/url_request_info_util.cc
index e005b4a0..ce63ec4 100644
--- a/content/renderer/pepper/url_request_info_util.cc
+++ b/content/renderer/pepper/url_request_info_util.cc
@@ -11,7 +11,6 @@
 #include "base/notreached.h"
 #include "base/strings/string_util.h"
 #include "content/public/common/service_names.mojom.h"
-#include "content/renderer/loader/request_extra_data.h"
 #include "content/renderer/pepper/host_globals.h"
 #include "content/renderer/pepper/pepper_file_ref_renderer_host.h"
 #include "content/renderer/pepper/pepper_plugin_instance_impl.h"
@@ -33,6 +32,7 @@
 #include "third_party/blink/public/platform/web_http_body.h"
 #include "third_party/blink/public/platform/web_url.h"
 #include "third_party/blink/public/platform/web_url_request.h"
+#include "third_party/blink/public/platform/web_url_request_extra_data.h"
 #include "third_party/blink/public/web/web_document.h"
 #include "third_party/blink/public/web/web_local_frame.h"
 #include "url/gurl.h"
@@ -247,10 +247,11 @@
     dest->SetRequestedWithHeader(WebString::FromUTF8(name_version));
 
   if (data->has_custom_user_agent) {
-    auto extra_data = base::MakeRefCounted<RequestExtraData>();
-    extra_data->set_custom_user_agent(
+    auto url_request_extra_data =
+        base::MakeRefCounted<blink::WebURLRequestExtraData>();
+    url_request_extra_data->set_custom_user_agent(
         WebString::FromUTF8(data->custom_user_agent));
-    dest->SetExtraData(std::move(extra_data));
+    dest->SetURLRequestExtraData(std::move(url_request_extra_data));
   }
 
   dest->SetRequestContext(blink::mojom::RequestContextType::PLUGIN);
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 4f39cd2..2f4775b1 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -97,7 +97,6 @@
 #include "content/renderer/impression_conversions.h"
 #include "content/renderer/internal_document_state_data.h"
 #include "content/renderer/loader/navigation_body_loader.h"
-#include "content/renderer/loader/request_extra_data.h"
 #include "content/renderer/loader/resource_dispatcher.h"
 #include "content/renderer/loader/tracked_child_url_loader_factory_bundle.h"
 #include "content/renderer/loader/web_url_loader_impl.h"
@@ -180,6 +179,7 @@
 #include "third_party/blink/public/platform/web_string.h"
 #include "third_party/blink/public/platform/web_url.h"
 #include "third_party/blink/public/platform/web_url_error.h"
+#include "third_party/blink/public/platform/web_url_request_extra_data.h"
 #include "third_party/blink/public/platform/web_url_request_util.h"
 #include "third_party/blink/public/platform/web_url_response.h"
 #include "third_party/blink/public/platform/web_vector.h"
@@ -554,9 +554,10 @@
       info->source_location.url.Latin1(), info->source_location.line_number,
       info->source_location.column_number);
 
-  const RequestExtraData* extra_data =
-      static_cast<RequestExtraData*>(info->url_request.GetExtraData().get());
-  DCHECK(extra_data);
+  const blink::WebURLRequestExtraData* url_request_extra_data =
+      static_cast<blink::WebURLRequestExtraData*>(
+          info->url_request.GetURLRequestExtraData().get());
+  DCHECK(url_request_extra_data);
 
   // Convert from WebVector<int> to std::vector<int>.
   std::vector<int> initiator_origin_trial_features(
@@ -581,8 +582,8 @@
 
   return mojom::CommonNavigationParams::New(
       info->url_request.Url(), info->url_request.RequestorOrigin(),
-      std::move(referrer), extra_data->transition_type(), navigation_type,
-      download_policy,
+      std::move(referrer), url_request_extra_data->transition_type(),
+      navigation_type, download_policy,
       info->frame_load_type == WebFrameLoadType::kReplaceCurrentItem, GURL(),
       GURL(),
       static_cast<blink::PreviewsState>(info->url_request.GetPreviewsState()),
@@ -4737,11 +4738,12 @@
   // agent. This needs to be done here, after WebKit is through with setting the
   // user agent on its own.
   WebString custom_user_agent;
-  if (request.GetExtraData()) {
-    RequestExtraData* old_extra_data =
-        static_cast<RequestExtraData*>(request.GetExtraData().get());
+  if (request.GetURLRequestExtraData()) {
+    blink::WebURLRequestExtraData* old_request_extra_data =
+        static_cast<blink::WebURLRequestExtraData*>(
+            request.GetURLRequestExtraData().get());
 
-    custom_user_agent = old_extra_data->custom_user_agent();
+    custom_user_agent = old_request_extra_data->custom_user_agent();
     if (!custom_user_agent.IsNull()) {
       if (custom_user_agent.IsEmpty())
         request.ClearHttpHeaderField("User-Agent");
@@ -4751,20 +4753,23 @@
   }
 
   WebDocument frame_document = frame_->GetDocument();
-  if (!request.GetExtraData())
-    request.SetExtraData(base::MakeRefCounted<RequestExtraData>());
-  auto* extra_data =
-      static_cast<RequestExtraData*>(request.GetExtraData().get());
-  extra_data->set_custom_user_agent(custom_user_agent);
-  extra_data->set_render_frame_id(routing_id_);
-  extra_data->set_is_main_frame(IsMainFrame());
-  extra_data->set_transition_type(transition_type);
+  if (!request.GetURLRequestExtraData())
+    request.SetURLRequestExtraData(
+        base::MakeRefCounted<blink::WebURLRequestExtraData>());
+  auto* url_request_extra_data = static_cast<blink::WebURLRequestExtraData*>(
+      request.GetURLRequestExtraData().get());
+  url_request_extra_data->set_custom_user_agent(custom_user_agent);
+  url_request_extra_data->set_render_frame_id(routing_id_);
+  url_request_extra_data->set_is_main_frame(IsMainFrame());
+  url_request_extra_data->set_transition_type(transition_type);
   bool is_for_no_state_prefetch =
       GetContentClient()->renderer()->IsPrefetchOnly(this, request);
-  extra_data->set_is_for_no_state_prefetch(is_for_no_state_prefetch);
-  extra_data->set_force_ignore_site_for_cookies(force_ignore_site_for_cookies);
-  extra_data->set_frame_request_blocker(frame_request_blocker_);
-  extra_data->set_allow_cross_origin_auth_prompt(
+  url_request_extra_data->set_is_for_no_state_prefetch(
+      is_for_no_state_prefetch);
+  url_request_extra_data->set_force_ignore_site_for_cookies(
+      force_ignore_site_for_cookies);
+  url_request_extra_data->set_frame_request_blocker(frame_request_blocker_);
+  url_request_extra_data->set_allow_cross_origin_auth_prompt(
       render_view_->GetRendererPreferences().allow_cross_origin_auth_prompt);
 
   request.SetDownloadToNetworkCacheOnly(is_for_no_state_prefetch &&
@@ -4775,7 +4780,7 @@
   RenderThreadImpl* render_thread = RenderThreadImpl::current();
   if (!for_redirect && render_thread &&
       render_thread->url_loader_throttle_provider()) {
-    extra_data->set_url_loader_throttles(
+    url_request_extra_data->set_url_loader_throttles(
         render_thread->url_loader_throttle_provider()->CreateThrottles(
             routing_id_, request));
   }
@@ -6167,8 +6172,10 @@
   WillSendRequestInternal(request, for_main_frame, transition_type,
                           ForRedirect(false));
 
-  if (!info->url_request.GetExtraData())
-    info->url_request.SetExtraData(base::MakeRefCounted<RequestExtraData>());
+  if (!info->url_request.GetURLRequestExtraData()) {
+    info->url_request.SetURLRequestExtraData(
+        base::MakeRefCounted<blink::WebURLRequestExtraData>());
+  }
 
   // TODO(clamy): Same-document navigations should not be sent back to the
   // browser.
@@ -6233,7 +6240,8 @@
           searchable_form_encoding, client_side_redirect_url,
           initiator ? base::make_optional<base::Value>(std::move(*initiator))
                     : base::nullopt,
-          info->url_request.GetExtraData()->force_ignore_site_for_cookies(),
+          info->url_request.GetURLRequestExtraData()
+              ->force_ignore_site_for_cookies(),
           info->url_request.TrustTokenParams()
               ? info->url_request.TrustTokenParams()->Clone()
               : nullptr,
diff --git a/content/renderer/render_frame_proxy.cc b/content/renderer/render_frame_proxy.cc
index a82e1ff3..d8fc105 100644
--- a/content/renderer/render_frame_proxy.cc
+++ b/content/renderer/render_frame_proxy.cc
@@ -742,7 +742,9 @@
         routing_id_, remote_interfaces.InitWithNewEndpointAndPassReceiver());
     remote_associated_interfaces_ =
         std::make_unique<blink::AssociatedInterfaceProvider>(
-            std::move(remote_interfaces));
+            std::move(remote_interfaces),
+            agent_scheduling_group_.agent_group_scheduler()
+                .DefaultTaskRunner());
   }
   return remote_associated_interfaces_.get();
 }
diff --git a/content/renderer/render_view_browsertest.cc b/content/renderer/render_view_browsertest.cc
index 876227b..60af6a0 100644
--- a/content/renderer/render_view_browsertest.cc
+++ b/content/renderer/render_view_browsertest.cc
@@ -49,7 +49,6 @@
 #include "content/renderer/accessibility/render_accessibility_impl.h"
 #include "content/renderer/accessibility/render_accessibility_manager.h"
 #include "content/renderer/agent_scheduling_group.h"
-#include "content/renderer/loader/request_extra_data.h"
 #include "content/renderer/navigation_state.h"
 #include "content/renderer/render_frame_proxy.h"
 #include "content/renderer/render_process.h"
diff --git a/content/renderer/service_worker/service_worker_fetch_context_impl.cc b/content/renderer/service_worker/service_worker_fetch_context_impl.cc
index 7ad2dd9..aad3637 100644
--- a/content/renderer/service_worker/service_worker_fetch_context_impl.cc
+++ b/content/renderer/service_worker/service_worker_fetch_context_impl.cc
@@ -10,13 +10,13 @@
 #include "content/public/common/content_features.h"
 #include "content/public/renderer/url_loader_throttle_provider.h"
 #include "content/public/renderer/websocket_handshake_throttle_provider.h"
-#include "content/renderer/loader/request_extra_data.h"
 #include "content/renderer/loader/resource_dispatcher.h"
 #include "content/renderer/loader/web_url_loader_impl.h"
 #include "ipc/ipc_message.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "services/network/public/cpp/wrapper_shared_url_loader_factory.h"
 #include "third_party/blink/public/platform/internet_disconnected_web_url_loader.h"
+#include "third_party/blink/public/platform/web_url_request_extra_data.h"
 
 namespace content {
 
@@ -118,9 +118,10 @@
     request.SetHttpHeaderField(blink::WebString::FromUTF8(kDoNotTrackHeader),
                                "1");
   }
-  auto extra_data = base::MakeRefCounted<RequestExtraData>();
-  extra_data->set_originated_from_service_worker(true);
-  extra_data->set_render_frame_id(service_worker_route_id_);
+  auto url_request_extra_data =
+      base::MakeRefCounted<blink::WebURLRequestExtraData>();
+  url_request_extra_data->set_originated_from_service_worker(true);
+  url_request_extra_data->set_render_frame_id(service_worker_route_id_);
 
   const bool needs_to_skip_throttling =
       static_cast<GURL>(request.Url()) == script_url_to_skip_throttling_ &&
@@ -138,11 +139,11 @@
     // worker scripts.
     script_url_to_skip_throttling_ = GURL();
   } else if (throttle_provider_) {
-    extra_data->set_url_loader_throttles(
+    url_request_extra_data->set_url_loader_throttles(
         throttle_provider_->CreateThrottles(MSG_ROUTING_NONE, request));
   }
 
-  request.SetExtraData(std::move(extra_data));
+  request.SetURLRequestExtraData(std::move(url_request_extra_data));
 
   if (!renderer_preferences_.enable_referrers) {
     request.SetReferrerString(blink::WebString());
diff --git a/content/renderer/service_worker/service_worker_fetch_context_impl_unittest.cc b/content/renderer/service_worker/service_worker_fetch_context_impl_unittest.cc
index 34bbca3..1575c72 100644
--- a/content/renderer/service_worker/service_worker_fetch_context_impl_unittest.cc
+++ b/content/renderer/service_worker/service_worker_fetch_context_impl_unittest.cc
@@ -6,9 +6,9 @@
 
 #include "content/public/renderer/url_loader_throttle_provider.h"
 #include "content/public/renderer/websocket_handshake_throttle_provider.h"
-#include "content/renderer/loader/request_extra_data.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/renderer_preferences/renderer_preferences.h"
+#include "third_party/blink/public/platform/web_url_request_extra_data.h"
 
 namespace content {
 
@@ -60,11 +60,11 @@
     context->WillSendRequest(request);
 
     // Throttles should be created by the provider.
-    auto* extra_data =
-        static_cast<RequestExtraData*>(request.GetExtraData().get());
-    ASSERT_TRUE(extra_data);
-    std::vector<std::unique_ptr<blink::URLLoaderThrottle>> throttles =
-        extra_data->TakeURLLoaderThrottles();
+    auto* url_request_extra_data = static_cast<blink::WebURLRequestExtraData*>(
+        request.GetURLRequestExtraData().get());
+    ASSERT_TRUE(url_request_extra_data);
+    blink::WebVector<std::unique_ptr<blink::URLLoaderThrottle>> throttles =
+        url_request_extra_data->TakeURLLoaderThrottles();
     EXPECT_EQ(1u, throttles.size());
   }
   {
@@ -75,11 +75,11 @@
     context->WillSendRequest(request);
 
     // Throttles should not be created by the provider.
-    auto* extra_data =
-        static_cast<RequestExtraData*>(request.GetExtraData().get());
-    ASSERT_TRUE(extra_data);
-    std::vector<std::unique_ptr<blink::URLLoaderThrottle>> throttles =
-        extra_data->TakeURLLoaderThrottles();
+    auto* url_request_extra_data = static_cast<blink::WebURLRequestExtraData*>(
+        request.GetURLRequestExtraData().get());
+    ASSERT_TRUE(url_request_extra_data);
+    blink::WebVector<std::unique_ptr<blink::URLLoaderThrottle>> throttles =
+        url_request_extra_data->TakeURLLoaderThrottles();
     EXPECT_TRUE(throttles.empty());
   }
 }
diff --git a/content/test/gpu/PRESUBMIT.py b/content/test/gpu/PRESUBMIT.py
index 0f96e7e..6245af2 100644
--- a/content/test/gpu/PRESUBMIT.py
+++ b/content/test/gpu/PRESUBMIT.py
@@ -9,7 +9,9 @@
 """
 
 def CommonChecks(input_api, output_api):
-  commands = [
+  results = []
+
+  gpu_tests = [
       input_api.Command(
           name='run_content_test_gpu_unittests',
           cmd=[input_api.python_executable, 'run_unittests.py', 'gpu_tests'],
@@ -24,7 +26,13 @@
                         kwargs={},
                         message=output_api.PresubmitError)
   ]
-  return input_api.RunTests(commands)
+  results.extend(input_api.RunTests(gpu_tests))
+
+  pylint_checks = input_api.canned_checks.GetPylint(input_api, output_api)
+  results.extend(input_api.RunTests(pylint_checks))
+
+  return results
+
 
 def CheckChangeOnUpload(input_api, output_api):
   return CommonChecks(input_api, output_api)
diff --git a/content/test/gpu/find_unexpected_passing_tests.py b/content/test/gpu/find_unexpected_passing_tests.py
index c9f6a1c..7fb19ed2 100755
--- a/content/test/gpu/find_unexpected_passing_tests.py
+++ b/content/test/gpu/find_unexpected_passing_tests.py
@@ -171,17 +171,16 @@
   """
   suite_name = variant.get('test_suite', 'default_suite')
   gpu = variant.get('gpu')
-  os = variant.get('os')
+  os_dimension = variant.get('os')
   gpu = ConvertGpuToVendorName(gpu)
-  return '%s on %s on %s' % (suite_name, gpu, os)
+  return '%s on %s on %s' % (suite_name, gpu, os_dimension)
 
 
-def GetUnexpectedPasses(builds, args):
+def GetUnexpectedPasses(builds):
   """Gets the unexpected test passes from the given builds.
 
   Args:
     builds: The output of GetBuildbucketIds().
-    args: The parsed arguments from an argparse.ArgumentParser.
 
   Returns:
     A dict in the following form:
@@ -284,7 +283,7 @@
 def main():
   args = ParseArgs()
   builds = GetBuildbucketIds(args)
-  unexpected_passes = GetUnexpectedPasses(builds, args)
+  unexpected_passes = GetUnexpectedPasses(builds)
   PrintUnexpectedPasses(unexpected_passes, args)
 
 
diff --git a/content/test/gpu/gold_inexact_matching/base_parameter_optimizer.py b/content/test/gpu/gold_inexact_matching/base_parameter_optimizer.py
index 50bee97..8534399a 100644
--- a/content/test/gpu/gold_inexact_matching/base_parameter_optimizer.py
+++ b/content/test/gpu/gold_inexact_matching/base_parameter_optimizer.py
@@ -15,7 +15,7 @@
 import sys
 import tempfile
 
-import parameter_set
+from gold_inexact_matching import parameter_set
 
 CHROMIUM_SRC_DIR = os.path.realpath(
     os.path.join(os.path.dirname(__file__), '..', '..', '..', '..'))
diff --git a/content/test/gpu/gold_inexact_matching/binary_search_parameter_optimizer.py b/content/test/gpu/gold_inexact_matching/binary_search_parameter_optimizer.py
index 7ae42b4f..b900ecd6 100644
--- a/content/test/gpu/gold_inexact_matching/binary_search_parameter_optimizer.py
+++ b/content/test/gpu/gold_inexact_matching/binary_search_parameter_optimizer.py
@@ -4,8 +4,8 @@
 
 import logging
 
-import base_parameter_optimizer as base_optimizer
-import parameter_set
+import gold_inexact_matching.base_parameter_optimizer as base_optimizer
+from gold_inexact_matching import parameter_set
 
 
 class BinarySearchParameterOptimizer(base_optimizer.BaseParameterOptimizer):
@@ -50,8 +50,7 @@
     while (abs(known_good - known_bad)) > 1:
       midpoint = (known_good + known_bad) / 2
       parameters = self._CreateParameterSet(midpoint)
-      success, num_pixels, max_diff = self._RunComparisonForParameters(
-          parameters)
+      success, _, _ = self._RunComparisonForParameters(parameters)
       if success:
         logging.info('Found good parameters %s', parameters)
         known_good = midpoint
diff --git a/content/test/gpu/gold_inexact_matching/brute_force_parameter_optimizer.py b/content/test/gpu/gold_inexact_matching/brute_force_parameter_optimizer.py
index 16285b2a..131efaf 100644
--- a/content/test/gpu/gold_inexact_matching/brute_force_parameter_optimizer.py
+++ b/content/test/gpu/gold_inexact_matching/brute_force_parameter_optimizer.py
@@ -4,8 +4,9 @@
 
 import logging
 
-import iterative_parameter_optimizer as iterative_optimizer
-import parameter_set
+import gold_inexact_matching.iterative_parameter_optimizer\
+    as iterative_optimizer
+from gold_inexact_matching import parameter_set
 
 
 class BruteForceParameterOptimizer(
diff --git a/content/test/gpu/gold_inexact_matching/determine_gold_inexact_parameters.py b/content/test/gpu/gold_inexact_matching/determine_gold_inexact_parameters.py
index 7e5ce8e..f49b177 100755
--- a/content/test/gpu/gold_inexact_matching/determine_gold_inexact_parameters.py
+++ b/content/test/gpu/gold_inexact_matching/determine_gold_inexact_parameters.py
@@ -7,34 +7,36 @@
 import logging
 import sys
 
-import base_parameter_optimizer as base_optimizer
-import binary_search_parameter_optimizer as binary_optimizer
-import brute_force_parameter_optimizer as brute_optimizer
-import local_minima_parameter_optimizer as local_optimizer
-import optimizer_set
-"""Script to find suitable values for Skia Gold inexact matching.
+import gold_inexact_matching.base_parameter_optimizer as base_optimizer
+import gold_inexact_matching.binary_search_parameter_optimizer\
+    as binary_optimizer
+import gold_inexact_matching.brute_force_parameter_optimizer as brute_optimizer
+import gold_inexact_matching.local_minima_parameter_optimizer\
+    as local_optimizer
+from gold_inexact_matching import optimizer_set
 
-Inexact matching in Skia Gold has three tunable parameters:
-  1. The max number of differing pixels.
-  2. The max delta for any single pixel.
-  3. The threshold for a Sobel filter.
-
-Ideally, we use the following hierarchy of comparison approaches:
-  1. Exact matching.
-  2. Exact matching after a Sobel filter is applied.
-  3. Fuzzy matching after a Sobel filter is applied.
-
-However, there may be cases where only using a Sobel filter requires masking a
-very large amount of the image compared to Sobel + very conservative fuzzy
-matching.
-
-Even if such cases are not hit, the process of determining good values for the
-parameters is quite tedious since it requires downloading images from Gold and
-manually running multiple calls to `goldctl match`.
-
-This script attempts to remedy both issues by handling all of the trial and
-error and suggesting potential parameter values for the user to choose from.
-"""
+# Script to find suitable values for Skia Gold inexact matching.
+#
+# Inexact matching in Skia Gold has three tunable parameters:
+#   1. The max number of differing pixels.
+#   2. The max delta for any single pixel.
+#   3. The threshold for a Sobel filter.
+#
+# Ideally, we use the following hierarchy of comparison approaches:
+#   1. Exact matching.
+#   2. Exact matching after a Sobel filter is applied.
+#   3. Fuzzy matching after a Sobel filter is applied.
+#
+# However, there may be cases where only using a Sobel filter requires masking a
+# very large amount of the image compared to Sobel + very conservative fuzzy
+# matching.
+#
+# Even if such cases are not hit, the process of determining good values for the
+# parameters is quite tedious since it requires downloading images from Gold and
+# manually running multiple calls to `goldctl match`.
+#
+# This script attempts to remedy both issues by handling all of the trial and
+# error and suggesting potential parameter values for the user to choose from.
 
 
 def CreateArgumentParser():
diff --git a/content/test/gpu/gold_inexact_matching/iterative_parameter_optimizer.py b/content/test/gpu/gold_inexact_matching/iterative_parameter_optimizer.py
index 2aed9f5b..d979731 100644
--- a/content/test/gpu/gold_inexact_matching/iterative_parameter_optimizer.py
+++ b/content/test/gpu/gold_inexact_matching/iterative_parameter_optimizer.py
@@ -2,9 +2,12 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import base_parameter_optimizer as base_optimizer
+import gold_inexact_matching.base_parameter_optimizer as base_optimizer
 
 
+# This is an abstract class itself, so it's fine that it doesn't implement
+# all of BaseParameterOptimizer's abstract methods.
+# pylint: disable=abstract-method
 class IterativeParameterOptimizer(base_optimizer.BaseParameterOptimizer):
   """Abstract ParameterOptimizer class for running an iterative algorithm."""
 
@@ -49,3 +52,4 @@
 
     assert self._args.max_diff_step >= self.MIN_MAX_DIFF_STEP
     assert self._args.delta_threshold_step >= self.MIN_DELTA_THRESHOLD_STEP
+#pylint: enable=abstract-method
diff --git a/content/test/gpu/gold_inexact_matching/local_minima_parameter_optimizer.py b/content/test/gpu/gold_inexact_matching/local_minima_parameter_optimizer.py
index 350659b..c6a6a09 100644
--- a/content/test/gpu/gold_inexact_matching/local_minima_parameter_optimizer.py
+++ b/content/test/gpu/gold_inexact_matching/local_minima_parameter_optimizer.py
@@ -7,8 +7,9 @@
 import logging
 import sys
 
-import iterative_parameter_optimizer as iterative_optimizer
-import parameter_set
+import gold_inexact_matching.iterative_parameter_optimizer\
+    as iterative_optimizer
+from gold_inexact_matching import parameter_set
 
 
 class LocalMinimaParameterOptimizer(
diff --git a/content/test/gpu/gpu_tests/gpu_integration_test_unittest.py b/content/test/gpu/gpu_tests/gpu_integration_test_unittest.py
index bb31d49..cc08d719 100644
--- a/content/test/gpu/gpu_tests/gpu_integration_test_unittest.py
+++ b/content/test/gpu/gpu_tests/gpu_integration_test_unittest.py
@@ -126,7 +126,11 @@
       temp_file.close()
       try:
         test_argv = [
-            test_name, '--write-full-results-to=%s' % temp_file.name
+            test_name,
+            '--write-full-results-to=%s' % temp_file.name,
+            # We don't want the underlying typ-based tests to report their
+            # results to ResultDB.
+            '--disable-resultsink',
         ] + extra_args
         processed_args = run_gpu_integration_test.ProcessArgs(test_argv)
         telemetry_args = browser_test_runner.ProcessConfig(
@@ -424,11 +428,16 @@
       # list. Then we pass it directly to run_browser_tests.RunTests. If
       # we called browser_test_runner.Run, then it would spawn another
       # subprocess which is less efficient.
-      args = browser_test_runner.ProcessConfig(config, [
-          test_args.test_name,
-          '--write-full-results-to=%s' % test_results_path,
-          '--test-state-json-path=%s' % test_state_path
-      ] + test_args.additional_args)
+      args = browser_test_runner.ProcessConfig(
+          config,
+          [
+              test_args.test_name,
+              '--write-full-results-to=%s' % test_results_path,
+              '--test-state-json-path=%s' % test_state_path,
+              # We don't want the underlying typ-based tests to report their
+              # results to ResultDB.
+              '--disable-resultsink',
+          ] + test_args.additional_args)
       run_browser_tests.RunTests(args)
       with open(test_results_path) as f:
         self._test_result = json.load(f)
diff --git a/content/test/gpu/gpu_tests/info_collection_test.py b/content/test/gpu/gpu_tests/info_collection_test.py
index 6d5fab9..94bee28 100644
--- a/content/test/gpu/gpu_tests/info_collection_test.py
+++ b/content/test/gpu/gpu_tests/info_collection_test.py
@@ -138,11 +138,11 @@
 
   @staticmethod
   def _ValueToStr(value):
-    if type(value) is str:
+    if isinstance(value, str):
       return value
-    if type(value) is unicode:
+    if isinstance(value, unicode):
       return str(value)
-    if type(value) is bool:
+    if isinstance(value, bool):
       return 'supported' if value else 'unsupported'
     assert False
 
diff --git a/content/test/gpu/gpu_tests/lint_unittest.py b/content/test/gpu/gpu_tests/lint_unittest.py
deleted file mode 100644
index 4350b1d5..0000000
--- a/content/test/gpu/gpu_tests/lint_unittest.py
+++ /dev/null
@@ -1,42 +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.
-
-import unittest
-import os
-
-from gpu_tests import path_util
-
-path_util.AddDirToPathIfNeeded(path_util.GetChromiumSrcDir(), 'third_party',
-                               'logilab')
-
-path_util.AddDirToPathIfNeeded(path_util.GetChromiumSrcDir(), 'third_party',
-                               'logilab', 'logilab')
-
-path_util.AddDirToPathIfNeeded(path_util.GetChromiumSrcDir(), 'third_party',
-                               'pylint')
-
-try:
-  from pylint import lint
-except ImportError:
-  lint = None
-
-_RC_FILE = os.path.join(path_util.GetGpuTestDir(), 'pylintrc')
-
-
-def LintCheckPassed(directory):
-  args = [directory, '--rcfile=%s' % _RC_FILE]
-  try:
-    assert lint, 'pylint module cannot be found'
-    lint.Run(args)
-    assert False, (
-        'This should not be reached as lint.Run always raise SystemExit')
-  except SystemExit as err:
-    if err.code == 0:
-      return True
-    return False
-
-
-class LintTest(unittest.TestCase):
-  def testPassingPylintCheckForGpuTestsDir(self):
-    self.assertTrue(LintCheckPassed(os.path.abspath(os.path.dirname(__file__))))
diff --git a/content/test/gpu/gpu_tests/power_measurement_integration_test.py b/content/test/gpu/gpu_tests/power_measurement_integration_test.py
index 084702e5..1bdff328 100644
--- a/content/test/gpu/gpu_tests/power_measurement_integration_test.py
+++ b/content/test/gpu/gpu_tests/power_measurement_integration_test.py
@@ -396,7 +396,7 @@
 
   @staticmethod
   def _AppendResults(results_sum, results):
-    assert type(results_sum) is dict and type(results) is dict
+    assert isinstance(results_sum, dict) and isinstance(results, dict)
     assert results
     first_append = not results_sum
     for key, value in results.items():
@@ -404,7 +404,7 @@
         results_sum[key] = [value]
       else:
         assert key in results_sum
-        assert type(results_sum[key]) is list
+        assert isinstance(results_sum[key], list)
         results_sum[key].append(value)
     return results_sum
 
diff --git a/content/test/gpu/gpu_tests/webgl_test_util.py b/content/test/gpu/gpu_tests/webgl_test_util.py
index 8ebc3ae..53aa690 100644
--- a/content/test/gpu/gpu_tests/webgl_test_util.py
+++ b/content/test/gpu/gpu_tests/webgl_test_util.py
@@ -10,10 +10,8 @@
 
 extensions_relcomps = ('content', 'test', 'data', 'gpu')
 
-# pylint: disable=star-args
 conformance_relpath = os.path.join(*conformance_relcomps)
 extensions_relpath = os.path.join(*extensions_relcomps)
-# pylint: enable=star-args
 conformance_path = os.path.join(path_util.GetChromiumSrcDir(),
                                 conformance_relpath)
 
diff --git a/content/test/gpu/measure_power_intel.py b/content/test/gpu/measure_power_intel.py
index e4e4abda..5769984 100644
--- a/content/test/gpu/measure_power_intel.py
+++ b/content/test/gpu/measure_power_intel.py
@@ -52,6 +52,7 @@
 
 try:
   from selenium import webdriver
+  from selenium.common import exceptions
 except ImportError as error:
   logging.error(
       "This script needs selenium and appropriate web drivers to be installed.")
@@ -169,7 +170,7 @@
       actions.move_to_element(video_el)
       actions.double_click(video_el)
       actions.perform()
-    except:
+    except exceptions.InvalidSelectorException:
       logging.warning('Could not locate video element to make fullscreen')
   return driver
 
@@ -282,9 +283,9 @@
       logging.error("Can't locate file at %s",
                     options.extra_browser_args_filename)
     else:
-      with open(options.extra_browser_args_filename, 'r') as file:
-        extra_browser_args.extend(file.read().split())
-        file.close()
+      with open(options.extra_browser_args_filename, 'r') as f:
+        extra_browser_args.extend(f.read().split())
+        f.close()
 
   for run in range(1, options.repeat + 1):
     logfile = ipg_utils.GenerateIPGLogFilename(log_prefix, options.logdir, run,
diff --git a/content/test/gpu/power_measurement_results/analyze_power_measurement_results.py b/content/test/gpu/power_measurement_results/analyze_power_measurement_results.py
index 0af0b28..e6a6115 100755
--- a/content/test/gpu/power_measurement_results/analyze_power_measurement_results.py
+++ b/content/test/gpu/power_measurement_results/analyze_power_measurement_results.py
@@ -88,17 +88,20 @@
 
 
 def ProcessJsonData(jsons,
-                    measurement_names=_MEASUREMENTS,
+                    measurement_names=None,
                     per_bot=False,
                     repeat_strategy=RepeatStrategy.COUNT_MINIMUM,
-                    bot_allowlist=[],
-                    bot_blocklist=[]):
+                    bot_allowlist=None,
+                    bot_blocklist=None):
+  measurement_names = measurement_names or _MEASUREMENTS
+  bot_allowlist = bot_allowlist or []
+  bot_blocklist = bot_blocklist or []
   min_build = None
   max_build = None
   results = {}
   bots = []
-  for json in jsons:
-    builds = json.get('builds', [])
+  for j in jsons:
+    builds = j.get('builds', [])
     for build in builds:
       build_number = build.get('number', -1)
       if build_number > 0:
@@ -192,8 +195,8 @@
 
 def GetBotBuilds(jsons, bot_name):
   build_numbers = []
-  for json in jsons:
-    builds = json.get('builds', [])
+  for j in jsons:
+    builds = j.get('builds', [])
     for build in builds:
       build_number = build.get('number', -1)
       if build_number > 0:
@@ -215,8 +218,8 @@
 
 
 def FindBuild(jsons, selected_bots, test_name, result):
-  for json in jsons:
-    builds = json.get('builds', [])
+  for j in jsons:
+    builds = j.get('builds', [])
     for build in builds:
       build_number = build.get('number', -1)
       if build_number < 0:
@@ -299,8 +302,9 @@
 
 
 def RunExperiment_GoodBots(jsons,
-                           bad_bots=[],
+                           bad_bots=None,
                            repeat_strategy=RepeatStrategy.COUNT_MINIMUM):
+  bad_bots = bad_bots or []
   MIN_RUNS_PER_BOT = 8
   STDEV_GOOD_BOT_THRESHOLD = 0.2
   GOOD_BOT_RANGE_PERC = 0.08
diff --git a/content/test/gpu/run_gpu_integration_test_fuchsia.py b/content/test/gpu/run_gpu_integration_test_fuchsia.py
index 0e24c52..7e33505 100755
--- a/content/test/gpu/run_gpu_integration_test_fuchsia.py
+++ b/content/test/gpu/run_gpu_integration_test_fuchsia.py
@@ -73,8 +73,8 @@
           lambda package_name: os.path.join(
               web_engine_dir, package_name, 'ids.txt'),
           package_names)
-      symbolizer = RunSymbolizer(listener.stdout, open(args.system_log_file,
-                                                       'w'), build_ids_paths)
+      RunSymbolizer(listener.stdout, open(args.system_log_file, 'w'),
+                    build_ids_paths)
 
       # Keep the Amber repository live while the test runs.
       with target.GetAmberRepo():
diff --git a/content/test/gpu/trim_culprit_cls.py b/content/test/gpu/trim_culprit_cls.py
index 3ae6df6..892f16919 100755
--- a/content/test/gpu/trim_culprit_cls.py
+++ b/content/test/gpu/trim_culprit_cls.py
@@ -38,12 +38,14 @@
 import re
 import subprocess
 
+# pylint: disable=line-too-long
 # Schemas:
 # - go/buildbucket-bq and go/buildbucket-proto/build.proto
 # - go/luci/cq/bq and
 #   https://source.chromium.org/chromium/infra/infra/+/master:go/src/go.chromium.org/luci/cv/api/bigquery/v1/attempt.proto
 #
 # Original author: maruel@
+# pylint: enable=line-too-long
 QUERY_TEMPLATE = """\
 WITH cq_builds AS (
   SELECT
diff --git a/content/utility/utility_thread_impl.cc b/content/utility/utility_thread_impl.cc
index 3cefdc4..f65abba 100644
--- a/content/utility/utility_thread_impl.cc
+++ b/content/utility/utility_thread_impl.cc
@@ -172,6 +172,11 @@
 }
 
 void UtilityThreadImpl::ReleaseProcess() {
+  // Ensure all main-thread services are destroyed before releasing the process.
+  // This limits the risk of services incorrectly attempting to post
+  // shutdown-blocking tasks once shutdown has already begun.
+  main_thread_services_.reset();
+
   if (!IsInBrowserProcess()) {
     ChildProcess::current()->ReleaseProcess();
     return;
diff --git a/device/fido/authenticator_supported_options.cc b/device/fido/authenticator_supported_options.cc
index f0d4948..bf1d9c4 100644
--- a/device/fido/authenticator_supported_options.cc
+++ b/device/fido/authenticator_supported_options.cc
@@ -100,6 +100,10 @@
     option_map.emplace(kLargeBlobsKey, true);
   }
 
+  if (options.always_uv) {
+    option_map.emplace(kAlwaysUvKey, true);
+  }
+
   return cbor::Value(std::move(option_map));
 }
 
diff --git a/device/fido/authenticator_supported_options.h b/device/fido/authenticator_supported_options.h
index 2b0e115..d04fbcc 100644
--- a/device/fido/authenticator_supported_options.h
+++ b/device/fido/authenticator_supported_options.h
@@ -100,6 +100,11 @@
   // Indicates whether the authenticator supports the authenticatorLargeBlobs
   // command.
   bool supports_large_blobs = false;
+  // Indicates whether user verification must be used for make credential, final
+  // (i.e. not pre-flight) get assertion requests, and writing large blobs. An
+  // |always_uv| value of true will make uv=0 get assertion requests return
+  // invalid signatures, which is okay for pre-flighting.
+  bool always_uv = false;
 };
 
 COMPONENT_EXPORT(DEVICE_FIDO)
diff --git a/device/fido/device_response_converter.cc b/device/fido/device_response_converter.cc
index 525830a..917fbe3 100644
--- a/device/fido/device_response_converter.cc
+++ b/device/fido/device_response_converter.cc
@@ -431,6 +431,14 @@
       options.supports_large_blobs = option_map_it->second.GetBool();
     }
 
+    option_map_it = option_map.find(CBOR(kAlwaysUvKey));
+    if (option_map_it != option_map.end()) {
+      if (!option_map_it->second.is_bool()) {
+        return base::nullopt;
+      }
+      options.always_uv = option_map_it->second.GetBool();
+    }
+
     response.options = std::move(options);
   }
 
diff --git a/device/fido/fido_constants.cc b/device/fido/fido_constants.cc
index 84b41ea..820b8032 100644
--- a/device/fido/fido_constants.cc
+++ b/device/fido/fido_constants.cc
@@ -37,6 +37,7 @@
 const char kDefaultCredProtectKey[] = "defaultCredProtect";
 const char kEnterpriseAttestationKey[] = "ep";
 const char kLargeBlobsKey[] = "largeBlobs";
+const char kAlwaysUvKey[] = "alwaysUv";
 
 const base::TimeDelta kDeviceTimeout = base::TimeDelta::FromSeconds(20);
 const base::TimeDelta kU2fRetryDelay = base::TimeDelta::FromMilliseconds(200);
diff --git a/device/fido/fido_constants.h b/device/fido/fido_constants.h
index c2ddc9d..348340e 100644
--- a/device/fido/fido_constants.h
+++ b/device/fido/fido_constants.h
@@ -335,6 +335,7 @@
 extern const char kDefaultCredProtectKey[];
 extern const char kEnterpriseAttestationKey[];
 extern const char kLargeBlobsKey[];
+extern const char kAlwaysUvKey[];
 
 // HID transport specific constants.
 constexpr uint32_t kHidBroadcastChannel = 0xffffffff;
diff --git a/device/fido/get_assertion_request_handler.cc b/device/fido/get_assertion_request_handler.cc
index 0598fdb..bfa1b9c 100644
--- a/device/fido/get_assertion_request_handler.cc
+++ b/device/fido/get_assertion_request_handler.cc
@@ -269,6 +269,10 @@
     specialized_request.large_blob_read = false;
     specialized_request.large_blob_write.reset();
   }
+  if (authenticator.Options() && authenticator.Options()->always_uv) {
+    specialized_request.user_verification =
+        UserVerificationRequirement::kRequired;
+  }
   return specialized_request;
 }
 
@@ -335,7 +339,9 @@
     return;
   }
 
-  switch (authenticator->WillNeedPINToGetAssertion(request_, observer())) {
+  CtapGetAssertionRequest request =
+      SpecializeRequestForAuthenticator(request_, *authenticator);
+  switch (authenticator->WillNeedPINToGetAssertion(request, observer())) {
     case PINDisposition::kUsePIN:
       // Skip asking for touch if this is the only available authenticator.
       if (active_authenticators().size() == 1 && allow_skipping_pin_touch_) {
@@ -364,7 +370,7 @@
       break;
   }
 
-  if (request_.user_verification != UserVerificationRequirement::kDiscouraged &&
+  if (request.user_verification != UserVerificationRequirement::kDiscouraged &&
       authenticator->CanGetUvToken()) {
     authenticator->GetUvRetries(
         base::BindOnce(&GetAssertionRequestHandler::OnStartUvTokenOrFallback,
@@ -376,8 +382,6 @@
 
   FIDO_LOG(DEBUG) << "Asking for assertion from "
                   << authenticator->GetDisplayName();
-  CtapGetAssertionRequest request =
-      SpecializeRequestForAuthenticator(request_, *authenticator);
   CtapGetAssertionRequest request_copy(request);
   authenticator->GetAssertion(
       std::move(request_copy), options_,
@@ -613,9 +617,10 @@
   if (state_ != State::kWaitingForTouch) {
     return;
   }
-  DCHECK(authenticator->WillNeedPINToGetAssertion(request_, observer()) !=
-         PINDisposition::kNoPIN);
-
+  DCHECK_NE(authenticator->WillNeedPINToGetAssertion(
+                SpecializeRequestForAuthenticator(request_, *authenticator),
+                observer()),
+            PINDisposition::kNoPIN);
   DCHECK(observer());
   state_ = State::kGettingRetries;
   CancelActiveAuthenticators(authenticator->GetId());
@@ -627,8 +632,10 @@
 
 void GetAssertionRequestHandler::StartPINFallbackForInternalUv(
     FidoAuthenticator* authenticator) {
-  DCHECK(authenticator->WillNeedPINToGetAssertion(request_, observer()) ==
-         PINDisposition::kUsePINForFallback);
+  DCHECK_EQ(authenticator->WillNeedPINToGetAssertion(
+                SpecializeRequestForAuthenticator(request_, *authenticator),
+                observer()),
+            PINDisposition::kUsePINForFallback);
   observer()->OnInternalUserVerificationLocked();
   CollectPINThenSendRequest(authenticator);
 }
@@ -740,7 +747,9 @@
   }
 
   if (retries == 0) {
-    if (authenticator->WillNeedPINToGetAssertion(request_, observer()) ==
+    CtapGetAssertionRequest request =
+        SpecializeRequestForAuthenticator(request_, *authenticator);
+    if (authenticator->WillNeedPINToGetAssertion(request, observer()) ==
         PINDisposition::kUsePINForFallback) {
       authenticator->GetTouch(base::BindOnce(
           &GetAssertionRequestHandler::StartPINFallbackForInternalUv,
@@ -772,7 +781,9 @@
   }
   state_ = State::kWaitingForTouch;
   if (response->retries == 0) {
-    if (authenticator_->WillNeedPINToGetAssertion(request_, observer()) ==
+    CtapGetAssertionRequest request =
+        SpecializeRequestForAuthenticator(request_, *authenticator_);
+    if (authenticator_->WillNeedPINToGetAssertion(request, observer()) ==
         PINDisposition::kUsePINForFallback) {
       // Fall back to PIN.
       StartPINFallbackForInternalUv(authenticator_);
@@ -805,7 +816,9 @@
       status == CtapDeviceResponseCode::kCtap2ErrOperationDenied ||
       status == CtapDeviceResponseCode::kCtap2ErrUvBlocked) {
     if (status == CtapDeviceResponseCode::kCtap2ErrUvBlocked) {
-      if (authenticator->WillNeedPINToGetAssertion(request_, observer()) ==
+      CtapGetAssertionRequest request =
+          SpecializeRequestForAuthenticator(request_, *authenticator_);
+      if (authenticator->WillNeedPINToGetAssertion(request, observer()) ==
           PINDisposition::kUsePINForFallback) {
         StartPINFallbackForInternalUv(authenticator);
         return;
diff --git a/device/fido/hid/fido_hid_device.h b/device/fido/hid/fido_hid_device.h
index 36a200c..84b8c10 100644
--- a/device/fido/hid/fido_hid_device.h
+++ b/device/fido/hid/fido_hid_device.h
@@ -29,7 +29,7 @@
 
 class FidoHidMessage;
 
-class COMPONENT_EXPORT(DEVICE_FIDO) FidoHidDevice : public FidoDevice {
+class COMPONENT_EXPORT(DEVICE_FIDO) FidoHidDevice final : public FidoDevice {
  public:
   FidoHidDevice(device::mojom::HidDeviceInfoPtr device_info,
                 device::mojom::HidManager* hid_manager);
diff --git a/device/fido/hid/fido_hid_packet.h b/device/fido/hid/fido_hid_packet.h
index 2c3fbbc..e51a461 100644
--- a/device/fido/hid/fido_hid_packet.h
+++ b/device/fido/hid/fido_hid_packet.h
@@ -51,7 +51,8 @@
 // determine the type of message the packet corresponds to. Payload length
 // is the length of the entire message payload, and the data is only the portion
 // of the payload that will fit into the HidInitPacket.
-class COMPONENT_EXPORT(DEVICE_FIDO) FidoHidInitPacket : public FidoHidPacket {
+class COMPONENT_EXPORT(DEVICE_FIDO) FidoHidInitPacket final
+    : public FidoHidPacket {
  public:
   // Creates a packet from the serialized data of an initialization packet. As
   // this is the first packet, the payload length of the entire message will be
@@ -83,7 +84,7 @@
 // will be identical to the identifier in all other packets of the message. The
 // packet sequence will be the sequence number of this particular packet, from
 // 0x00 to 0x7f.
-class COMPONENT_EXPORT(DEVICE_FIDO) FidoHidContinuationPacket
+class COMPONENT_EXPORT(DEVICE_FIDO) FidoHidContinuationPacket final
     : public FidoHidPacket {
  public:
   // Creates a packet from the serialized data of a continuation packet. As an
diff --git a/device/fido/make_credential_request_handler.cc b/device/fido/make_credential_request_handler.cc
index b9e330d5..839ef9cf 100644
--- a/device/fido/make_credential_request_handler.cc
+++ b/device/fido/make_credential_request_handler.cc
@@ -810,7 +810,6 @@
   switch (options_.resident_key) {
     case ResidentKeyRequirement::kRequired:
       request->resident_key_required = true;
-      request->user_verification = UserVerificationRequirement::kRequired;
       break;
     case ResidentKeyRequirement::kPreferred: {
       // Create a resident key if the authenticator supports it, has sufficient
@@ -848,7 +847,8 @@
       break;
   }
 
-  request->user_verification = request->resident_key_required
+  request->user_verification = request->resident_key_required ||
+                                       (auth_options && auth_options->always_uv)
                                    ? UserVerificationRequirement::kRequired
                                    : options_.user_verification;
 
diff --git a/device/fido/virtual_ctap2_device.cc b/device/fido/virtual_ctap2_device.cc
index f72f26b0..09c2144f 100644
--- a/device/fido/virtual_ctap2_device.cc
+++ b/device/fido/virtual_ctap2_device.cc
@@ -472,6 +472,8 @@
   Init({ProtocolVersion::kCtap2});
   std::vector<ProtocolVersion> versions = {ProtocolVersion::kCtap2};
   if (config.u2f_support) {
+    // Devices with alwaysUv may disable u2f. Let's be strict here.
+    DCHECK(!config.always_uv);
     versions.emplace_back(ProtocolVersion::kU2f);
     u2f_device_ = std::make_unique<VirtualU2fDevice>(NewReferenceToState());
   }
@@ -572,6 +574,12 @@
     options.supports_large_blobs = true;
   }
 
+  if (config.always_uv) {
+    DCHECK(config.pin_support || config.internal_uv_support);
+    options_updated = true;
+    options.always_uv = true;
+  }
+
   if (options_updated) {
     device_info_->options = std::move(options);
   }
@@ -745,6 +753,7 @@
     base::span<const uint8_t> pin_token,
     base::span<const uint8_t> client_data_hash,
     UserVerificationRequirement user_verification,
+    bool user_presence_required,
     bool* out_user_verified) {
   const AuthenticatorSupportedOptions& options = authenticator_info.options;
 
@@ -785,9 +794,6 @@
     return CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid;
   }
 
-  // 3. "If authenticator is not protected by some form of user verification and
-  // platform has set "uv" or pinAuth to get the user verification, return
-  // CTAP2_ERR_INVALID_OPTION."
   const bool can_do_uv =
       options.user_verification_availability ==
           AuthenticatorSupportedOptions::UserVerificationAvailability::
@@ -795,6 +801,45 @@
       options.client_pin_availability ==
           AuthenticatorSupportedOptions::ClientPinAvailability::
               kSupportedAndPinSet;
+
+  // (CTAP2.1) 5. "If the alwaysUv option ID is present and true and the "up"
+  // option is present and true then:"
+  if (options.always_uv && user_presence_required) {
+    // 5.1 "If the authenticator is not protected by some form of user
+    // verification:"
+    if (!can_do_uv) {
+      // 5.1.1 "If the clientPin option ID is present: (clientPin is supported)"
+      if (options.client_pin_availability ==
+          AuthenticatorSupportedOptions::ClientPinAvailability::
+              kSupportedAndPinSet) {
+        return CtapDeviceResponseCode::kCtap2ErrPinRequired;
+      } else {
+        return CtapDeviceResponseCode::kCtap2ErrOperationDenied;
+      }
+    }
+    // 5.4 "If the "uv" option is false and the authenticator supports a
+    // built-in user verification method, and the user verification method is
+    // enabled then:"
+    if (user_verification == UserVerificationRequirement::kDiscouraged &&
+        options.user_verification_availability ==
+            AuthenticatorSupportedOptions::UserVerificationAvailability::
+                kSupportedAndConfigured) {
+      user_verification = UserVerificationRequirement::kRequired;
+    }
+    // 5.5 "If the clientPin option ID is present and the pinUvAuthParam
+    // parameter is not present, then end the operation by returning
+    // CTAP2_ERR_PIN_REQUIRED."
+    if (options.client_pin_availability !=
+            AuthenticatorSupportedOptions::ClientPinAvailability::
+                kNotSupported &&
+        !pin_auth) {
+      return CtapDeviceResponseCode::kCtap2ErrPinRequired;
+    }
+  }
+
+  // 3. "If authenticator is not protected by some form of user verification and
+  // platform has set "uv" or pinAuth to get the user verification, return
+  // CTAP2_ERR_INVALID_OPTION."
   if (!can_do_uv &&
       (user_verification == UserVerificationRequirement::kRequired ||
        pin_auth)) {
@@ -896,7 +941,8 @@
   const base::Optional<CtapDeviceResponseCode> uv_error = CheckUserVerification(
       /*is_make_credential=*/true, *device_info_, request.rp.id,
       request.pin_auth, request.pin_protocol, mutable_state()->pin_token,
-      request.client_data_hash, request.user_verification, &user_verified);
+      request.client_data_hash, request.user_verification,
+      /*user_presence_required=*/true, &user_verified);
   if (uv_error != CtapDeviceResponseCode::kSuccess) {
     return uv_error;
   }
@@ -1187,7 +1233,8 @@
   const base::Optional<CtapDeviceResponseCode> uv_error = CheckUserVerification(
       /*is_make_credential=*/false, *device_info_, request.rp_id,
       request.pin_auth, request.pin_protocol, mutable_state()->pin_token,
-      request.client_data_hash, request.user_verification, &user_verified);
+      request.client_data_hash, request.user_verification,
+      request.user_presence_required, &user_verified);
   if (uv_error != CtapDeviceResponseCode::kSuccess) {
     return uv_error;
   }
@@ -1398,7 +1445,7 @@
     }
 
     AuthenticatorData authenticator_data(
-        rp_id_hash, /*user_present=*/true, user_verified,
+        rp_id_hash, request.user_presence_required, user_verified,
         registration.second->counter, std::move(opt_attested_cred_data),
         std::move(extensions));
 
@@ -1415,8 +1462,15 @@
                                       *opt_android_client_data_json)
                                 : request.client_data_hash);
 
-    std::vector<uint8_t> signature =
-        registration.second->private_key->Sign(signature_buffer);
+    std::vector<uint8_t> signature;
+    if (config_.always_uv && !user_verified) {
+      // Requests without user presence and with up=0 produce bogus signatures.
+      DCHECK(!request.user_presence_required);
+      signature =
+          registration.second->private_key->Sign(std::vector<uint8_t>{0});
+    } else {
+      signature = registration.second->private_key->Sign(signature_buffer);
+    }
 
     AuthenticatorGetAssertionResponse assertion(
         std::move(authenticator_data),
@@ -2335,13 +2389,15 @@
       return CtapDeviceResponseCode::kCtap1ErrInvalidSeq;
     }
 
-    // If the device is protected by some sort of user verification:
+    // If the device is protected by some sort of user verification or alwaysUv
+    // is true.
     if (device_info_->options.client_pin_availability ==
             AuthenticatorSupportedOptions::ClientPinAvailability::
                 kSupportedAndPinSet ||
         device_info_->options.user_verification_availability ==
             AuthenticatorSupportedOptions::UserVerificationAvailability::
-                kSupportedAndConfigured) {
+                kSupportedAndConfigured ||
+        config_.always_uv) {
       // verify(pinUvAuthToken,
       //        32×0xff || h’0c00' || uint32LittleEndian(offset) || SHA-256(
       //          contents of set byte string, i.e. not including an outer CBOR
diff --git a/device/fido/virtual_ctap2_device.h b/device/fido/virtual_ctap2_device.h
index 36281dd..6d204ed 100644
--- a/device/fido/virtual_ctap2_device.h
+++ b/device/fido/virtual_ctap2_device.h
@@ -70,6 +70,7 @@
     bool cred_protect_support = false;
     bool hmac_secret_support = false;
     bool large_blob_support = false;
+    bool always_uv = false;
     // The space available to store a large blob. In real authenticators this
     // may change depending on the number of resident credentials. We treat this
     // as a fixed size area for the large blob.
@@ -215,6 +216,7 @@
       base::span<const uint8_t> pin_token,
       base::span<const uint8_t> client_data_hash,
       UserVerificationRequirement user_verification,
+      bool user_presence_required,
       bool* out_user_verified);
   base::Optional<CtapDeviceResponseCode> OnMakeCredential(
       base::span<const uint8_t> request,
diff --git a/device/vr/android/arcore/DEPS b/device/vr/android/arcore/DEPS
index 5e9ba5929..e9ac427 100644
--- a/device/vr/android/arcore/DEPS
+++ b/device/vr/android/arcore/DEPS
@@ -1,3 +1,4 @@
 include_rules = [
   "+third_party/arcore-android-sdk/src",
-  ]
\ No newline at end of file
+  "+third_party/skia/include",
+  ]
diff --git a/device/vr/android/arcore/arcore.h b/device/vr/android/arcore/arcore.h
index 317c2698..e0d305a 100644
--- a/device/vr/android/arcore/arcore.h
+++ b/device/vr/android/arcore/arcore.h
@@ -13,6 +13,7 @@
 #include "base/macros.h"
 #include "base/optional.h"
 #include "base/time/time.h"
+#include "device/vr/public/mojom/isolated_xr_service.mojom.h"
 #include "device/vr/public/mojom/vr_service.mojom.h"
 #include "ui/display/display.h"
 #include "ui/gfx/transform.h"
@@ -38,7 +39,8 @@
   virtual bool Initialize(
       base::android::ScopedJavaLocalRef<jobject> application_context,
       const std::unordered_set<device::mojom::XRSessionFeature>&
-          enabled_features) = 0;
+          enabled_features,
+      const std::vector<device::mojom::XRTrackedImagePtr>& tracked_images) = 0;
 
   // Returns the target framerate range in Hz. Actual capture frame rate will
   // vary within this range, i.e. lower in low light to increase exposure time.
@@ -78,6 +80,8 @@
 
   virtual mojom::XRDepthDataPtr GetDepthData() = 0;
 
+  virtual mojom::XRTrackedImagesDataPtr GetTrackedImages() = 0;
+
   virtual bool RequestHitTest(
       const mojom::XRRayPtr& ray,
       std::vector<mojom::XRHitResultPtr>* hit_results) = 0;
diff --git a/device/vr/android/arcore/arcore_device.cc b/device/vr/android/arcore/arcore_device.cc
index 8b32589..a8338e6 100644
--- a/device/vr/android/arcore/arcore_device.cc
+++ b/device/vr/android/arcore/arcore_device.cc
@@ -128,6 +128,12 @@
   // OnSessionEnded().
   DCHECK(mailbox_bridge_);
 
+  for (auto& image : options->tracked_images) {
+    DVLOG(3) << __func__ << ": tracked image size_in_pixels="
+             << image->size_in_pixels.ToString();
+    session_state_->tracked_images_.push_back(std::move(image));
+  }
+
   session_state_->arcore_gl_thread_ = std::make_unique<ArCoreGlThread>(
       std::move(ar_image_transport_factory_), std::move(mailbox_bridge_),
       CreateMainThreadCallback(
@@ -330,6 +336,7 @@
         session_state_->arcore_gl_thread_->GetArCoreGl()->GetWeakPtr(),
         arcore_session_utils_.get(), arcore_factory_.get(), drawing_widget,
         frame_size, rotation, session_state_->enabled_features_,
+        std::move(session_state_->tracked_images_),
         CreateMainThreadCallback(base::BindOnce(
             &ArCoreDevice::OnArCoreGlInitializationComplete, GetWeakPtr()))));
     return;
diff --git a/device/vr/android/arcore/arcore_device.h b/device/vr/android/arcore/arcore_device.h
index 1e9858e1..65d41bc4 100644
--- a/device/vr/android/arcore/arcore_device.h
+++ b/device/vr/android/arcore/arcore_device.h
@@ -135,6 +135,8 @@
 
     // List of features that are enabled on the session.
     std::vector<device::mojom::XRSessionFeature> enabled_features_;
+
+    std::vector<device::mojom::XRTrackedImagePtr> tracked_images_;
   };
 
   // This object is reset to initial values when ending a session. This helps
diff --git a/device/vr/android/arcore/arcore_gl.cc b/device/vr/android/arcore/arcore_gl.cc
index fdc6791c..81faa64 100644
--- a/device/vr/android/arcore/arcore_gl.cc
+++ b/device/vr/android/arcore/arcore_gl.cc
@@ -113,6 +113,7 @@
     const gfx::Size& frame_size,
     display::Display::Rotation display_rotation,
     const std::vector<device::mojom::XRSessionFeature>& enabled_features,
+    const std::vector<device::mojom::XRTrackedImagePtr>& tracked_images,
     base::OnceCallback<void(bool)> callback) {
   DVLOG(3) << __func__;
 
@@ -143,7 +144,8 @@
   }
 
   arcore_ = arcore_factory->Create();
-  if (!arcore_->Initialize(application_context, enabled_features_)) {
+  if (!arcore_->Initialize(application_context, enabled_features_,
+                           tracked_images)) {
     DLOG(ERROR) << "ARCore failed to initialize";
     std::move(callback).Run(false);
     return;
@@ -1143,6 +1145,10 @@
     frame_data->depth_data = arcore_->GetDepthData();
   }
 
+  if (IsFeatureEnabled(device::mojom::XRSessionFeature::IMAGE_TRACKING)) {
+    frame_data->tracked_images = arcore_->GetTrackedImages();
+  }
+
   // Running this callback after resolving all the hit-test requests ensures
   // that we satisfy the guarantee of the WebXR hit-test spec - that the
   // hit-test promise resolves immediately prior to the frame for which it is
diff --git a/device/vr/android/arcore/arcore_gl.h b/device/vr/android/arcore/arcore_gl.h
index 8447cc11..ebf5ced 100644
--- a/device/vr/android/arcore/arcore_gl.h
+++ b/device/vr/android/arcore/arcore_gl.h
@@ -76,6 +76,7 @@
       const gfx::Size& frame_size,
       display::Display::Rotation display_rotation,
       const std::vector<device::mojom::XRSessionFeature>& enabled_features,
+      const std::vector<device::mojom::XRTrackedImagePtr>& tracked_images,
       base::OnceCallback<void(bool)> callback);
 
   void CreateSession(mojom::VRDisplayInfoPtr display_info,
diff --git a/device/vr/android/arcore/arcore_impl.cc b/device/vr/android/arcore/arcore_impl.cc
index fb24f0f56..8bcff96 100644
--- a/device/vr/android/arcore/arcore_impl.cc
+++ b/device/vr/android/arcore/arcore_impl.cc
@@ -10,6 +10,7 @@
 #include "base/numerics/checked_math.h"
 #include "base/numerics/math_constants.h"
 #include "base/optional.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/trace_event/trace_event.h"
 #include "base/util/type_safety/pass_key.h"
 #include "device/vr/android/arcore/arcore_math_utils.h"
@@ -17,6 +18,12 @@
 #include "device/vr/android/arcore/type_converters.h"
 #include "device/vr/public/mojom/pose.h"
 #include "device/vr/public/mojom/vr_service.mojom.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkImage.h"
+#include "third_party/skia/include/core/SkMatrix44.h"
+#include "third_party/skia/include/core/SkPaint.h"
+#include "third_party/skia/include/core/SkPixmap.h"
 #include "ui/display/display.h"
 #include "ui/gfx/geometry/point3_f.h"
 #include "ui/gfx/geometry/point_f.h"
@@ -340,8 +347,8 @@
 
 bool ArCoreImpl::Initialize(
     base::android::ScopedJavaLocalRef<jobject> context,
-    const std::unordered_set<device::mojom::XRSessionFeature>&
-        enabled_features) {
+    const std::unordered_set<device::mojom::XRSessionFeature>& enabled_features,
+    const std::vector<device::mojom::XRTrackedImagePtr>& tracked_images) {
   DCHECK(IsOnGlThread());
   DCHECK(!arcore_session_.is_valid());
 
@@ -385,6 +392,37 @@
                                   AR_LIGHT_ESTIMATION_MODE_ENVIRONMENTAL_HDR);
 
   if (base::Contains(enabled_features,
+                     device::mojom::XRSessionFeature::IMAGE_TRACKING)) {
+    internal::ScopedArCoreObject<ArAugmentedImageDatabase*> image_db;
+    ArAugmentedImageDatabase_create(
+        session.get(),
+        internal::ScopedArCoreObject<ArAugmentedImageDatabase*>::Receiver(
+            image_db)
+            .get());
+    if (!image_db.is_valid()) {
+      DLOG(ERROR) << "ArAugmentedImageDatabase creation failed";
+      return false;
+    }
+
+    // Populate the image tracking database and set up data structures,
+    // this doesn't modify the ArConfig or session yet.
+    BuildImageDatabase(session.get(), image_db.get(), tracked_images);
+
+    if (!tracked_image_arcore_id_to_index_.empty()) {
+      // Image tracking with a non-empty image DB adds a few frames of
+      // synchronization delay internally in ARCore, has a high CPU cost, and
+      // reconfigures its graphics pipeline. Only activate it if we got images.
+      // (Apparently an empty image db is equivalent, but that seems fragile.)
+      ArConfig_setAugmentedImageDatabase(session.get(), arcore_config.get(),
+                                         image_db.get());
+      // Switch to autofocus mode when tracking images. The default fixed focus
+      // mode has trouble tracking close images since they end up blurry.
+      ArConfig_setFocusMode(session.get(), arcore_config.get(),
+                            AR_FOCUS_MODE_AUTO);
+    }
+  }
+
+  if (base::Contains(enabled_features,
                      device::mojom::XRSessionFeature::DEPTH)) {
     ArConfig_setDepthMode(session.get(), arcore_config.get(),
                           AR_DEPTH_MODE_AUTOMATIC);
@@ -646,6 +684,59 @@
   return target_framerate_range_;
 }
 
+void ArCoreImpl::BuildImageDatabase(
+    const ArSession* session,
+    ArAugmentedImageDatabase* image_db,
+    const std::vector<device::mojom::XRTrackedImagePtr>& tracked_images) {
+  for (std::size_t index = 0; index < tracked_images.size(); ++index) {
+    const device::mojom::XRTrackedImage* image = tracked_images[index].get();
+    gfx::Size size = image->size_in_pixels;
+
+    // Use Skia to convert the image to grayscale.
+    const SkBitmap& src_bitmap = image->bitmap;
+    SkBitmap canvas_bitmap;
+    canvas_bitmap.allocPixelsFlags(
+        SkImageInfo::Make(size.width(), size.height(), kGray_8_SkColorType,
+                          kOpaque_SkAlphaType),
+        SkBitmap::kZeroPixels_AllocFlag);
+    SkCanvas gray_canvas(canvas_bitmap);
+    SkPaint paint;
+    sk_sp<SkImage> src_image = SkImage::MakeFromBitmap(src_bitmap);
+    gray_canvas.drawImage(src_image, 0, 0, &paint);
+    SkPixmap gray_pixmap;
+    if (!gray_canvas.peekPixels(&gray_pixmap)) {
+      DLOG(WARNING) << __func__ << ": failed to access grayscale bitmap";
+      image_trackable_scores_.push_back(false);
+      continue;
+    }
+
+    const SkPixmap& pixmap = gray_pixmap;
+    float width_in_meters = image->width_in_meters;
+    DVLOG(3) << __func__ << " tracked image index=" << index
+             << " size=" << pixmap.width() << "x" << pixmap.height()
+             << " width_in_meters=" << width_in_meters;
+    int32_t arcore_id = -1;
+    std::string id_name = base::NumberToString(index);
+    ArStatus status = ArAugmentedImageDatabase_addImageWithPhysicalSize(
+        session, image_db, id_name.c_str(), pixmap.addr8(), pixmap.width(),
+        pixmap.height(), pixmap.rowBytesAsPixels(), width_in_meters,
+        &arcore_id);
+    if (status != AR_SUCCESS) {
+      DVLOG(2) << __func__ << ": add image failed";
+      image_trackable_scores_.push_back(false);
+      continue;
+    }
+
+    // ARCore uses internal IDs for images, these only include the trackable
+    // images. The tracking results need to refer to the original image index
+    // corresponding to its position in the input tracked_images array.
+    tracked_image_arcore_id_to_index_[arcore_id] = index;
+    DVLOG(2) << __func__ << ": added image, index=" << index
+             << " arcore_id=" << arcore_id;
+    image_trackable_scores_.push_back(true);
+  }
+}
+
 void ArCoreImpl::SetCameraTexture(uint32_t camera_texture_id) {
   DCHECK(IsOnGlThread());
   DCHECK(arcore_session_.is_valid());
@@ -753,6 +844,89 @@
   return mojo_from_viewer;
 }
 
+mojom::XRTrackedImagesDataPtr ArCoreImpl::GetTrackedImages() {
+  std::vector<mojom::XRTrackedImageDataPtr> images_data;
+
+  internal::ScopedArCoreObject<ArTrackableList*> updated_images;
+  ArTrackableList_create(
+      arcore_session_.get(),
+      internal::ScopedArCoreObject<ArTrackableList*>::Receiver(updated_images)
+          .get());
+  ArFrame_getUpdatedTrackables(arcore_session_.get(), arcore_frame_.get(),
+                               AR_TRACKABLE_AUGMENTED_IMAGE,
+                               updated_images.get());
+
+  int32_t images_count = 0;
+  ArTrackableList_getSize(arcore_session_.get(), updated_images.get(),
+                          &images_count);
+  DVLOG(2) << __func__ << ": trackable images count=" << images_count;
+
+  for (int32_t i = 0; i < images_count; ++i) {
+    internal::ScopedArCoreObject<ArTrackable*> trackable;
+    ArTrackableList_acquireItem(
+        arcore_session_.get(), updated_images.get(), i,
+        internal::ScopedArCoreObject<ArTrackable*>::Receiver(trackable).get());
+    ArTrackingState tracking_state;
+    ArTrackable_getTrackingState(arcore_session_.get(), trackable.get(),
+                                 &tracking_state);
+    ArAugmentedImage* image = ArAsAugmentedImage(trackable.get());
+
+    // Get the original image index from ARCore's internal ID for use in the
+    // returned results.
+    int32_t arcore_id;
+    ArAugmentedImage_getIndex(arcore_session_.get(), image, &arcore_id);
+    uint64_t index = tracked_image_arcore_id_to_index_[arcore_id];
+    DVLOG(3) << __func__ << ": #" << i << " tracked image index=" << index
+             << " arcore_id=" << arcore_id << " state=" << tracking_state;
+
+    if (tracking_state == AR_TRACKING_STATE_TRACKING) {
+      internal::ScopedArCoreObject<ArPose*> arcore_pose;
+      ArPose_create(
+          arcore_session_.get(), nullptr,
+          internal::ScopedArCoreObject<ArPose*>::Receiver(arcore_pose).get());
+      if (!arcore_pose.is_valid()) {
+        DLOG(ERROR) << "ArPose_create failed!";
+        continue;
+      }
+      ArAugmentedImage_getCenterPose(arcore_session_.get(), image,
+                                     arcore_pose.get());
+      float pose_raw[7];
+      ArPose_getPoseRaw(arcore_session_.get(), arcore_pose.get(), &pose_raw[0]);
+      DVLOG(3) << __func__ << ": tracked image pose_raw pos=(" << pose_raw[4]
+               << ", " << pose_raw[5] << ", " << pose_raw[6] << ")";
+
+      device::Pose device_pose =
+          GetPoseFromArPose(arcore_session_.get(), arcore_pose.get());
+
+      ArAugmentedImageTrackingMethod tracking_method;
+      ArAugmentedImage_getTrackingMethod(arcore_session_.get(), image,
+                                         &tracking_method);
+      bool actively_tracked =
+          tracking_method == AR_AUGMENTED_IMAGE_TRACKING_METHOD_FULL_TRACKING;
+
+      float width_in_meters;
+      ArAugmentedImage_getExtentX(arcore_session_.get(), image,
+                                  &width_in_meters);
+
+      images_data.push_back(mojom::XRTrackedImageData::New(
+          index, device_pose, actively_tracked, width_in_meters));
+    }
+  }
+
+  // Include information about each image's trackability status in the first
+  // returned result list.
+  if (!image_trackable_scores_sent_) {
+    auto ret = mojom::XRTrackedImagesData::New(
+        std::move(images_data), std::move(image_trackable_scores_));
+    image_trackable_scores_sent_ = true;
+    image_trackable_scores_.clear();
+    return ret;
+  } else {
+    return mojom::XRTrackedImagesData::New(std::move(images_data),
+                                           base::nullopt);
+  }
+}
+
 base::TimeDelta ArCoreImpl::GetFrameTimestamp() {
   DCHECK(arcore_session_.is_valid());
   DCHECK(arcore_frame_.is_valid());
diff --git a/device/vr/android/arcore/arcore_impl.h b/device/vr/android/arcore/arcore_impl.h
index 43a5539..153ba29 100644
--- a/device/vr/android/arcore/arcore_impl.h
+++ b/device/vr/android/arcore/arcore_impl.h
@@ -109,7 +109,9 @@
   bool Initialize(
       base::android::ScopedJavaLocalRef<jobject> application_context,
       const std::unordered_set<device::mojom::XRSessionFeature>&
-          enabled_features) override;
+          enabled_features,
+      const std::vector<device::mojom::XRTrackedImagePtr>& tracked_images)
+      override;
   MinMaxRange GetTargetFramerateRange() override;
   void SetDisplayGeometry(const gfx::Size& frame_size,
                           display::Display::Rotation display_rotation) override;
@@ -173,11 +175,17 @@
 
   mojom::XRDepthDataPtr GetDepthData() override;
 
+  mojom::XRTrackedImagesDataPtr GetTrackedImages() override;
+
  protected:
   std::vector<float> TransformDisplayUvCoords(
       const base::span<const float> uvs) const override;
 
  private:
+  void BuildImageDatabase(
+      const ArSession*,
+      ArAugmentedImageDatabase*,
+      const std::vector<device::mojom::XRTrackedImagePtr>& tracked_images);
   bool IsOnGlThread() const;
   base::WeakPtr<ArCoreImpl> GetWeakPtr() {
     return weak_ptr_factory_.GetWeakPtr();
@@ -202,6 +210,16 @@
   // Anchor manager. Valid after a call to Initialize.
   std::unique_ptr<ArCoreAnchorManager> anchor_manager_;
 
+  // For each image in the input list of images to track, store a true/false
+  // score to indicate if it's trackable by ARCore or not. These are sent
+  // to Blink only once, for the first frame, and the boolean tracks that.
+  std::vector<bool> image_trackable_scores_;
+  bool image_trackable_scores_sent_ = false;
+
+  // Map from ARCore's internal image IDs to the index position in the input
+  // list of images. The index values are needed for blink communication.
+  std::unordered_map<int32_t, uint64_t> tracked_image_arcore_id_to_index_;
+
   uint64_t next_id_ = 1;
 
   std::map<HitTestSubscriptionId, HitTestSubscriptionData>
diff --git a/device/vr/android/arcore/arcore_shim.cc b/device/vr/android/arcore/arcore_shim.cc
index c07a0f1..50299c0 100644
--- a/device/vr/android/arcore/arcore_shim.cc
+++ b/device/vr/android/arcore/arcore_shim.cc
@@ -23,6 +23,13 @@
   DO(ArAnchorList_create)                                          \
   DO(ArAnchorList_destroy)                                         \
   DO(ArAnchorList_getSize)                                         \
+  DO(ArAugmentedImage_getCenterPose)                               \
+  DO(ArAugmentedImage_getExtentX)                                  \
+  DO(ArAugmentedImage_getIndex)                                    \
+  DO(ArAugmentedImage_getTrackingMethod)                           \
+  DO(ArAugmentedImageDatabase_addImageWithPhysicalSize)            \
+  DO(ArAugmentedImageDatabase_create)                              \
+  DO(ArAugmentedImageDatabase_getNumImages)                        \
   DO(ArCamera_getDisplayOrientedPose)                              \
   DO(ArCamera_getProjectionMatrix)                                 \
   DO(ArCamera_getTrackingState)                                    \
@@ -46,7 +53,9 @@
   DO(ArConfig_destroy)                                             \
   DO(ArConfig_getDepthMode)                                        \
   DO(ArConfig_getLightEstimationMode)                              \
+  DO(ArConfig_setAugmentedImageDatabase)                           \
   DO(ArConfig_setDepthMode)                                        \
+  DO(ArConfig_setFocusMode)                                        \
   DO(ArConfig_setLightEstimationMode)                              \
   DO(ArFrame_acquireCamera)                                        \
   DO(ArFrame_acquireDepthImage)                                    \
@@ -223,6 +232,66 @@
                                                  out_size);
 }
 
+void ArAugmentedImage_getCenterPose(const ArSession* session,
+                                    const ArAugmentedImage* augmented_image,
+                                    ArPose* out_pose) {
+  g_arcore_api->impl_ArAugmentedImage_getCenterPose(session, augmented_image,
+                                                    out_pose);
+}
+
+void ArAugmentedImage_getExtentX(const ArSession* session,
+                                 const ArAugmentedImage* augmented_image,
+                                 float* out_extent_x) {
+  g_arcore_api->impl_ArAugmentedImage_getExtentX(session, augmented_image,
+                                                 out_extent_x);
+}
+
+void ArAugmentedImage_getIndex(const ArSession* session,
+                               const ArAugmentedImage* augmented_image,
+                               int32_t* out_index) {
+  g_arcore_api->impl_ArAugmentedImage_getIndex(session, augmented_image,
+                                               out_index);
+}
+
+void ArAugmentedImage_getTrackingMethod(
+    const ArSession* session,
+    const ArAugmentedImage* image,
+    ArAugmentedImageTrackingMethod* out_tracking_method) {
+  return g_arcore_api->impl_ArAugmentedImage_getTrackingMethod(
+      session, image, out_tracking_method);
+}
+
+ArStatus ArAugmentedImageDatabase_addImageWithPhysicalSize(
+    const ArSession* session,
+    ArAugmentedImageDatabase* augmented_image_database,
+    const char* image_name,
+    const uint8_t* image_grayscale_pixels,
+    int32_t image_width_in_pixels,
+    int32_t image_height_in_pixels,
+    int32_t image_stride_in_pixels,
+    float image_width_in_meters,
+    int32_t* out_index) {
+  return g_arcore_api->impl_ArAugmentedImageDatabase_addImageWithPhysicalSize(
+      session, augmented_image_database, image_name, image_grayscale_pixels,
+      image_width_in_pixels, image_height_in_pixels, image_stride_in_pixels,
+      image_width_in_meters, out_index);
+}
+
+void ArAugmentedImageDatabase_create(
+    const ArSession* session,
+    ArAugmentedImageDatabase** out_augmented_image_database) {
+  g_arcore_api->impl_ArAugmentedImageDatabase_create(
+      session, out_augmented_image_database);
+}
+
+void ArAugmentedImageDatabase_getNumImages(
+    const ArSession* session,
+    const ArAugmentedImageDatabase* augmented_image_database,
+    int32_t* out_number_of_images) {
+  g_arcore_api->impl_ArAugmentedImageDatabase_getNumImages(
+      session, augmented_image_database, out_number_of_images);
+}
+
 void ArCamera_getDisplayOrientedPose(const ArSession* session,
                                      const ArCamera* camera,
                                      ArPose* out_pose) {
@@ -370,12 +439,26 @@
       session, config, light_estimation_mode);
 }
 
+void ArConfig_setAugmentedImageDatabase(
+    const ArSession* session,
+    ArConfig* config,
+    const ArAugmentedImageDatabase* augmented_image_database) {
+  g_arcore_api->impl_ArConfig_setAugmentedImageDatabase(
+      session, config, augmented_image_database);
+}
+
 void ArConfig_setDepthMode(const ArSession* session,
                            ArConfig* config,
                            ArDepthMode mode) {
   return g_arcore_api->impl_ArConfig_setDepthMode(session, config, mode);
 }
 
+void ArConfig_setFocusMode(const ArSession* session,
+                           ArConfig* config,
+                           ArFocusMode focus_mode) {
+  g_arcore_api->impl_ArConfig_setFocusMode(session, config, focus_mode);
+}
+
 void ArConfig_setLightEstimationMode(
     const ArSession* session,
     ArConfig* config,
diff --git a/device/vr/public/mojom/BUILD.gn b/device/vr/public/mojom/BUILD.gn
index e9d75bc..437506f3 100644
--- a/device/vr/public/mojom/BUILD.gn
+++ b/device/vr/public/mojom/BUILD.gn
@@ -19,6 +19,7 @@
     "//device/gamepad/public/mojom",
     "//gpu/ipc/common:interfaces",
     "//mojo/public/mojom/base",
+    "//skia/public/mojom",
     "//ui/display/mojom:mojom",
     "//ui/gfx/geometry/mojom",
     "//ui/gfx/mojom",
diff --git a/device/vr/public/mojom/isolated_xr_service.mojom b/device/vr/public/mojom/isolated_xr_service.mojom
index ca43122..d9bec09 100644
--- a/device/vr/public/mojom/isolated_xr_service.mojom
+++ b/device/vr/public/mojom/isolated_xr_service.mojom
@@ -51,6 +51,8 @@
   // module install UI in the browser process before calling out to devices.
   int32 render_process_id;
   int32 render_frame_id;
+
+  array<XRTrackedImage> tracked_images;
 };
 
 // An XRRuntime may live in the browser process or a utility process.  The
diff --git a/device/vr/public/mojom/vr_service.mojom b/device/vr/public/mojom/vr_service.mojom
index f299f91e..5d9f34d6 100644
--- a/device/vr/public/mojom/vr_service.mojom
+++ b/device/vr/public/mojom/vr_service.mojom
@@ -9,6 +9,7 @@
 import "mojo/public/mojom/base/time.mojom";
 import "gpu/ipc/common/mailbox_holder.mojom";
 import "gpu/ipc/common/sync_token.mojom";
+import "skia/public/mojom/bitmap.mojom";
 import "ui/display/mojom/display.mojom";
 import "ui/gfx/geometry/mojom/geometry.mojom";
 import "ui/gfx/mojom/gpu_fence_handle.mojom";
@@ -64,6 +65,7 @@
   CAMERA_ACCESS = 10, // Experimental feature.
   PLANE_DETECTION = 11, // Experimental feature.
   DEPTH = 12, // Experimental feature.
+  IMAGE_TRACKING = 13, // Experimental feature.
 };
 
 // These values are persisted to logs. Entries should not be renumbered and
@@ -108,6 +110,12 @@
   kWorldSpace = 2,
 };
 
+struct XRTrackedImage {
+  skia.mojom.Bitmap bitmap;
+  gfx.mojom.Size size_in_pixels;
+  float width_in_meters;
+};
+
 struct XRSessionOptions {
   // Represents the type of session that is being requested.
   XRSessionMode mode;
@@ -121,6 +129,8 @@
   // the feature will not be added to the enabled_features set on the XRSession
   // struct that is returned.
   array<XRSessionFeature> optional_features;
+
+  array<XRTrackedImage> tracked_images;
 };
 
 // This structure contains a description of the device's active configuration
@@ -610,6 +620,35 @@
   XRDepthDataUpdated updated_depth_data;
 };
 
+struct XRTrackedImageData {
+  // Index of the image within the tracked images array passed to requestSession
+  uint32 index;
+
+  // Pose of the image. Must be non-null, images are only included in results
+  // if they have a pose.
+  Pose mojo_from_image;
+
+  // If true, the image is currently actively tracked. If false, the pose is
+  // inferred from previous tracking results. In that case, it's likely
+  // accurate for stationary images, but may be stale for a moving image.
+  bool actively_tracked;
+
+  // The best-effort measured width in meters for the physical image. Zero
+  // if the width is unknown due to the image never having been tracked
+  // successfully.
+  float width_in_meters;
+};
+
+struct XRTrackedImagesData {
+  array<XRTrackedImageData> images_data;
+
+  // The first returned result includes an array of trackable/untrackable
+  // scores, in the same order as tracked_images in the session request.
+  // (This is unambiguous since only used for immersive sessions, and
+  // there can only be one immersive session at a time.)
+  array<bool>? image_trackable_scores;
+};
+
 // The data needed for each animation frame of an XRSession.
 struct XRFrameData {
   // General XRSession value
@@ -699,6 +738,8 @@
   // intended to be used as input for renderer-side adaptive viewport sizing.
   // A value of zero means the ratio is unknown and must not be used.
   float rendering_time_ratio;
+
+  XRTrackedImagesData? tracked_images;
 };
 
 // Used primarily in logging to indicate why a session was rejecting to aid
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index 2051360..42b83f0a 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -1586,6 +1586,7 @@
   CHROMEOSINFOPRIVATE_ISTABLETMODEENABLED = 1523,
   FILEMANAGERPRIVATEINTERNAL_GETARCDOCUMENTSPROVIDERTHUMBNAIL = 1524,
   ACCESSIBILITY_PRIVATE_ISFEATUREENABLED = 1525,
+  INPUTMETHODPRIVATE_ONAUTOCORRECT = 1526,
   // Last entry: Add new entries above, then run:
   // python tools/metrics/histograms/update_extension_histograms.py
   ENUM_BOUNDARY
diff --git a/fuchsia/engine/browser/accessibility_bridge_browsertest.cc b/fuchsia/engine/browser/accessibility_bridge_browsertest.cc
index 9bada58f..7d9f5ed 100644
--- a/fuchsia/engine/browser/accessibility_bridge_browsertest.cc
+++ b/fuchsia/engine/browser/accessibility_bridge_browsertest.cc
@@ -221,19 +221,22 @@
 }
 
 // Checks that the correct node ID is returned when performing hit testing.
-// TODO(https://crbug.com/1050049): Re-enable once flake is fixed.
-IN_PROC_BROWSER_TEST_F(AccessibilityBridgeTest, DISABLED_HitTest) {
+IN_PROC_BROWSER_TEST_F(AccessibilityBridgeTest, HitTest) {
   LoadPage(kPage1Path, kPage1Title);
+  semantics_manager_.semantic_tree()->RunUntilNodeCountAtLeast(kPage1NodeCount);
 
-  fuchsia::accessibility::semantics::Node* hit_test_node =
+  fuchsia::accessibility::semantics::Node* target_node =
       semantics_manager_.semantic_tree()->GetNodeFromLabel(kParagraphName);
-  EXPECT_TRUE(hit_test_node);
+  EXPECT_TRUE(target_node);
 
-  fuchsia::math::PointF target_point =
-      GetCenterOfBox(hit_test_node->location());
+  fuchsia::math::PointF target_point = GetCenterOfBox(target_node->location());
 
-  EXPECT_EQ(hit_test_node->node_id(),
-            semantics_manager_.HitTestAtPointSync(std::move(target_point)));
+  uint32_t hit_node_id =
+      semantics_manager_.HitTestAtPointSync(std::move(target_point));
+  fuchsia::accessibility::semantics::Node* hit_node =
+      semantics_manager_.semantic_tree()->GetNodeWithId(hit_node_id);
+
+  EXPECT_EQ(hit_node->attributes().label(), kParagraphName);
 
   // Expect hit testing to return the root when the point given is out of
   // bounds or there is no semantic node at that position.
diff --git a/fuchsia/engine/browser/frame_impl.cc b/fuchsia/engine/browser/frame_impl.cc
index 7a95d74..3616236 100644
--- a/fuchsia/engine/browser/frame_impl.cc
+++ b/fuchsia/engine/browser/frame_impl.cc
@@ -974,9 +974,6 @@
       return false;
   }
 
-  if (console_log_message_hook_)
-    console_log_message_hook_.Run(formatted_message);
-
   return true;
 }
 
diff --git a/fuchsia/engine/browser/frame_impl.h b/fuchsia/engine/browser/frame_impl.h
index e693482..e9659cf 100644
--- a/fuchsia/engine/browser/frame_impl.h
+++ b/fuchsia/engine/browser/frame_impl.h
@@ -79,10 +79,6 @@
     return web_contents_.get();
   }
   bool has_view_for_test() const { return window_tree_host_ != nullptr; }
-  void set_javascript_console_message_hook_for_test(
-      base::RepeatingCallback<void(base::StringPiece)> hook) {
-    console_log_message_hook_ = std::move(hook);
-  }
   AccessibilityBridge* accessibility_bridge_for_test() const {
     return accessibility_bridge_.get();
   }
@@ -259,7 +255,6 @@
   EventFilter event_filter_;
   NavigationControllerImpl navigation_controller_;
   logging::LogSeverity log_level_;
-  base::RepeatingCallback<void(base::StringPiece)> console_log_message_hook_;
   UrlRequestRewriteRulesManager url_request_rewrite_rules_manager_;
   FramePermissionController permission_controller_;
 
diff --git a/fuchsia/engine/context_provider_impl.cc b/fuchsia/engine/context_provider_impl.cc
index dadf825a0..f0df5a768 100644
--- a/fuchsia/engine/context_provider_impl.cc
+++ b/fuchsia/engine/context_provider_impl.cc
@@ -392,6 +392,8 @@
       web_engine_config.FindBoolPath("use-overlays-for-video").value_or(false);
 
   if (enable_protected_graphics) {
+    launch_command.AppendSwitch(switches::kEnableVulkanProtectedMemory);
+    // TODO(crbug.com/1143764): Remove this after underlays are stable.
     if (force_protected_graphics || !use_overlays_for_video) {
       launch_command.AppendSwitch(switches::kEnforceVulkanProtectedMemory);
     }
diff --git a/gpu/command_buffer/service/gpu_switches.cc b/gpu/command_buffer/service/gpu_switches.cc
index 886e0f0..a12e9533 100644
--- a/gpu/command_buffer/service/gpu_switches.cc
+++ b/gpu/command_buffer/service/gpu_switches.cc
@@ -77,16 +77,8 @@
 const char kVulkanImplementationNameNative[] = "native";
 const char kVulkanImplementationNameSwiftshader[] = "swiftshader";
 
-// Forces to use protected memory for vulkan compositing.
-const char kEnforceVulkanProtectedMemory[] = "enforce-vulkan-protected-memory";
-
 // Disables VK_KHR_surface extension. Instead of using swapchain, bitblt will be
 // used for present render result on screen.
 const char kDisableVulkanSurface[] = "disable-vulkan-surface";
 
-// Disables falling back to GL based hardware rendering if initializing Vulkan
-// fails. This is to allow tests to catch regressions in Vulkan.
-const char kDisableVulkanFallbackToGLForTesting[] =
-    "disable-vulkan-fallback-to-gl-for-testing";
-
 }  // namespace switches
diff --git a/gpu/command_buffer/service/gpu_switches.h b/gpu/command_buffer/service/gpu_switches.h
index 1eca57d..b3b7d99 100644
--- a/gpu/command_buffer/service/gpu_switches.h
+++ b/gpu/command_buffer/service/gpu_switches.h
@@ -33,9 +33,7 @@
 GPU_EXPORT extern const char kUseVulkan[];
 GPU_EXPORT extern const char kVulkanImplementationNameNative[];
 GPU_EXPORT extern const char kVulkanImplementationNameSwiftshader[];
-GPU_EXPORT extern const char kEnforceVulkanProtectedMemory[];
 GPU_EXPORT extern const char kDisableVulkanSurface[];
-GPU_EXPORT extern const char kDisableVulkanFallbackToGLForTesting[];
 
 }  // namespace switches
 
diff --git a/gpu/config/gpu_preferences.h b/gpu/config/gpu_preferences.h
index e30c44d..bb58618 100644
--- a/gpu/config/gpu_preferences.h
+++ b/gpu/config/gpu_preferences.h
@@ -215,6 +215,9 @@
   // Use Vulkan for rasterization and display compositing.
   VulkanImplementationName use_vulkan = VulkanImplementationName::kNone;
 
+  // Enable using vulkan protected memory.
+  bool enable_vulkan_protected_memory = false;
+
   // Enforce using vulkan protected memory.
   bool enforce_vulkan_protected_memory = false;
 
diff --git a/gpu/config/gpu_preferences_unittest.cc b/gpu/config/gpu_preferences_unittest.cc
index 7378dc4..9bf5c3b 100644
--- a/gpu/config/gpu_preferences_unittest.cc
+++ b/gpu/config/gpu_preferences_unittest.cc
@@ -75,6 +75,8 @@
             right.watchdog_starts_backgrounded);
   EXPECT_EQ(left.gr_context_type, right.gr_context_type);
   EXPECT_EQ(left.use_vulkan, right.use_vulkan);
+  EXPECT_EQ(left.enable_vulkan_protected_memory,
+            right.enable_vulkan_protected_memory);
   EXPECT_EQ(left.enable_gpu_benchmarking_extension,
             right.enable_gpu_benchmarking_extension);
   EXPECT_EQ(left.enable_webgpu, right.enable_webgpu);
@@ -115,12 +117,6 @@
     GpuPreferences default_prefs;
     mojom::GpuPreferences prefs_mojom;
 
-    // Make sure all fields are included in mojo struct.
-    // TODO(zmo): This test isn't perfect. If a field isn't included in
-    // mojom::GpuPreferences, the two struct sizes might still be equal due to
-    // alignment.
-    EXPECT_EQ(sizeof(default_prefs), sizeof(prefs_mojom));
-
 #define GPU_PREFERENCES_FIELD(name, value)         \
   input_prefs.name = value;                        \
   EXPECT_NE(default_prefs.name, input_prefs.name); \
diff --git a/gpu/config/gpu_switches.cc b/gpu/config/gpu_switches.cc
index 1813f1f..9365ded 100644
--- a/gpu/config/gpu_switches.cc
+++ b/gpu/config/gpu_switches.cc
@@ -91,4 +91,15 @@
 // Used to enable vulkan draw mode instead of interop draw mode for webview.
 const char kWebViewEnableVulkan[] = "webview-enable-vulkan";
 
+// Enables using protected memory for vulkan resources.
+const char kEnableVulkanProtectedMemory[] = "enable-vulkan-protected-memory";
+
+// Forces vulkan resources to use protected memory for vulkan compositing.
+const char kEnforceVulkanProtectedMemory[] = "enforce-vulkan-protected-memory";
+
+// Disables falling back to GL based hardware rendering if initializing Vulkan
+// fails. This is to allow tests to catch regressions in Vulkan.
+const char kDisableVulkanFallbackToGLForTesting[] =
+    "disable-vulkan-fallback-to-gl-for-testing";
+
 }  // namespace switches
diff --git a/gpu/config/gpu_switches.h b/gpu/config/gpu_switches.h
index 5a355cc7..f7ffaf82 100644
--- a/gpu/config/gpu_switches.h
+++ b/gpu/config/gpu_switches.h
@@ -31,6 +31,9 @@
 GPU_EXPORT extern const char kGpuRevision[];
 GPU_EXPORT extern const char kGpuDriverVersion[];
 GPU_EXPORT extern const char kWebViewEnableVulkan[];
+GPU_EXPORT extern const char kEnableVulkanProtectedMemory[];
+GPU_EXPORT extern const char kEnforceVulkanProtectedMemory[];
+GPU_EXPORT extern const char kDisableVulkanFallbackToGLForTesting[];
 
 }  // namespace switches
 
diff --git a/gpu/ipc/common/gpu_preferences.mojom b/gpu/ipc/common/gpu_preferences.mojom
index 1058bab..559eeb5 100644
--- a/gpu/ipc/common/gpu_preferences.mojom
+++ b/gpu/ipc/common/gpu_preferences.mojom
@@ -77,6 +77,7 @@
   bool watchdog_starts_backgrounded;
   GrContextType gr_context_type;
   VulkanImplementationName use_vulkan;
+  bool enable_vulkan_protected_memory;
   bool enforce_vulkan_protected_memory;
   bool disable_vulkan_surface;
   bool disable_vulkan_fallback_to_gl_for_testing;
diff --git a/gpu/ipc/common/gpu_preferences_mojom_traits.h b/gpu/ipc/common/gpu_preferences_mojom_traits.h
index 0ee87a8..4ab8c11 100644
--- a/gpu/ipc/common/gpu_preferences_mojom_traits.h
+++ b/gpu/ipc/common/gpu_preferences_mojom_traits.h
@@ -163,6 +163,8 @@
       return false;
     if (!prefs.ReadUseVulkan(&out->use_vulkan))
       return false;
+    out->enable_vulkan_protected_memory =
+        prefs.enable_vulkan_protected_memory();
     out->enforce_vulkan_protected_memory =
         prefs.enforce_vulkan_protected_memory();
     out->disable_vulkan_surface = prefs.disable_vulkan_surface();
@@ -331,6 +333,9 @@
       const gpu::GpuPreferences& prefs) {
     return prefs.use_vulkan;
   }
+  static bool enable_vulkan_protected_memory(const gpu::GpuPreferences& prefs) {
+    return prefs.enable_vulkan_protected_memory;
+  }
   static bool enforce_vulkan_protected_memory(
       const gpu::GpuPreferences& prefs) {
     return prefs.enforce_vulkan_protected_memory;
diff --git a/gpu/ipc/in_process_command_buffer.h b/gpu/ipc/in_process_command_buffer.h
index bf1514f9..82d4dc1 100644
--- a/gpu/ipc/in_process_command_buffer.h
+++ b/gpu/ipc/in_process_command_buffer.h
@@ -247,10 +247,6 @@
   // and |surface_handle| provided in Initialize outlive this callback.
   base::ScopedClosureRunner GetCacheBackBufferCb();
 
-  gpu::SharedImageManager* GetSharedImageManager() {
-    return task_executor_->shared_image_manager();
-  }
-
  private:
   struct InitializeOnGpuThreadParams {
     SurfaceHandle surface_handle;
diff --git a/gpu/ipc/service/gpu_init.cc b/gpu/ipc/service/gpu_init.cc
index 310170a..779da15 100644
--- a/gpu/ipc/service/gpu_init.cc
+++ b/gpu/ipc/service/gpu_init.cc
@@ -773,12 +773,13 @@
       gpu_preferences_.use_vulkan == VulkanImplementationName::kForcedNative;
   bool use_swiftshader = gl_use_swiftshader_ || vulkan_use_swiftshader;
 
-  const bool enforce_protected_memory =
-      gpu_preferences_.enforce_vulkan_protected_memory;
+  // If |enforce_vulkan_protected_memory| is true, then we expect
+  // |enable_vulkan_protected_memory| to be true.
+  DCHECK(!gpu_preferences_.enforce_vulkan_protected_memory ||
+         gpu_preferences_.enable_vulkan_protected_memory);
   vulkan_implementation_ = CreateVulkanImplementation(
-      vulkan_use_swiftshader,
-      enforce_protected_memory ? true : false /* allow_protected_memory */,
-      enforce_protected_memory);
+      vulkan_use_swiftshader, gpu_preferences_.enable_vulkan_protected_memory,
+      gpu_preferences_.enforce_vulkan_protected_memory);
   if (!vulkan_implementation_ ||
       !vulkan_implementation_->InitializeVulkanInstance(
           !gpu_preferences_.disable_vulkan_surface)) {
diff --git a/headless/BUILD.gn b/headless/BUILD.gn
index 1d099ce..20d5bf7b 100644
--- a/headless/BUILD.gn
+++ b/headless/BUILD.gn
@@ -638,7 +638,11 @@
 
   # TODO(https://crbug.com/1107396): These tests flakily timeout on fuchsia.
   if (!is_fuchsia) {
-    sources += [ "test/headless_protocol_browsertest.cc" ]
+    sources += [
+      "test/headless_compositor_browsertest.cc",
+      "test/headless_protocol_browsertest.cc",
+      "test/headless_protocol_browsertest.h",
+    ]
   }
 
   data = [
diff --git a/headless/test/DEPS b/headless/test/DEPS
index 48997c4..61bacd7 100644
--- a/headless/test/DEPS
+++ b/headless/test/DEPS
@@ -1,7 +1,6 @@
 specific_include_rules = {
-  "headless_protocol_browsertest.cc": [
+  "headless_compositor_browsertest.cc": [
     "+cc/base/switches.h",
-    "+components/viz/common/features.h",
     "+components/viz/common/switches.h",
     "+third_party/blink/public/common/switches.h",
   ]
diff --git a/headless/test/data/protocol/sanity/browser-set-proxy-config-expected.txt b/headless/test/data/protocol/sanity/browser-set-proxy-config-expected.txt
new file mode 100644
index 0000000..ffaeaae
--- /dev/null
+++ b/headless/test/data/protocol/sanity/browser-set-proxy-config-expected.txt
@@ -0,0 +1,5 @@
+Tests that headless session can configure proxy.
+
+No proxy: Page with body
+Proxied to itself: Page with body
+Proxied to another server: Default response given for path: http://not-an-actual-domain.tld/hello.html
\ No newline at end of file
diff --git a/headless/test/data/protocol/sanity/browser-set-proxy-config.js b/headless/test/data/protocol/sanity/browser-set-proxy-config.js
new file mode 100644
index 0000000..b0ab227e
--- /dev/null
+++ b/headless/test/data/protocol/sanity/browser-set-proxy-config.js
@@ -0,0 +1,46 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+(async function(testRunner) {
+  testRunner.log('Tests that headless session can configure proxy.\n');
+  const { result: { sessionId } } =
+      await testRunner.browserP().Target.attachToBrowserTarget({});
+  const { protocol: bProtocol } = new TestRunner.Session(testRunner, sessionId);
+
+  async function dumpWithProxyServer(targetUrl, proxyServer) {
+    const { result: { browserContextId } } =
+        await bProtocol.Target.createBrowserContext({ proxyServer });
+    const { result: { targetId }} =
+        await bProtocol.Target.createTarget({
+      browserContextId: browserContextId,
+      url: 'about:blank'
+    });
+
+    const { result: { sessionId } } =
+        await bProtocol.Target.attachToTarget({ targetId, flatten: true });
+    const { protocol: pProtocol } =
+        new TestRunner.Session(testRunner, sessionId);
+    await pProtocol.Page.enable({});
+    await pProtocol.Page.navigate({ url: targetUrl });
+    await pProtocol.Page.onceLoadEventFired();
+    const { result: { result: { value } } } =
+        await pProtocol.Runtime.evaluate(
+            { expression: 'document.body.innerText' });
+    return value;
+  }
+
+  testRunner.log(`No proxy: ${await dumpWithProxyServer(
+      testRunner._testBaseURL + 'resources/body.html'
+  )}`);
+
+  testRunner.log(`Proxied to itself: ${await dumpWithProxyServer(
+      testRunner._testBaseURL + 'resources/body.html',
+      new URL(testRunner._targetBaseURL).host)}`);
+
+  testRunner.log(`Proxied to another server: ${await dumpWithProxyServer(
+      'http://not-an-actual-domain.tld/hello.html',
+      testRunner.params('proxy'))}`);
+
+  testRunner.completeTest();
+})
diff --git a/headless/test/data/protocol/sanity/resources/body.html b/headless/test/data/protocol/sanity/resources/body.html
new file mode 100644
index 0000000..1c70319
--- /dev/null
+++ b/headless/test/data/protocol/sanity/resources/body.html
@@ -0,0 +1,3 @@
+<html>
+	<body>Page with body</body>
+</html>
\ No newline at end of file
diff --git a/headless/test/headless_compositor_browsertest.cc b/headless/test/headless_compositor_browsertest.cc
new file mode 100644
index 0000000..3eddc3b
--- /dev/null
+++ b/headless/test/headless_compositor_browsertest.cc
@@ -0,0 +1,160 @@
+// 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 "headless/test/headless_protocol_browsertest.h"
+
+#include "build/build_config.h"
+#include "cc/base/switches.h"
+#include "components/viz/common/switches.h"
+#include "third_party/blink/public/common/switches.h"
+
+namespace headless {
+
+class HeadlessCompositorBrowserTest : public HeadlessProtocolBrowserTest {
+ public:
+  HeadlessCompositorBrowserTest() = default;
+
+ private:
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    HeadlessProtocolBrowserTest::SetUpCommandLine(command_line);
+    // The following switches are recommended for BeginFrameControl required by
+    // compositor tests, see https://goo.gl/3zHXhB for details
+    static const char* const compositor_switches[] = {
+        // We control BeginFrames ourselves and need all compositing stages to
+        // run.
+        ::switches::kRunAllCompositorStagesBeforeDraw,
+        ::switches::kDisableNewContentRenderingTimeout,
+
+        // Animtion-only BeginFrames are only supported when updates from the
+        // impl-thread are disabled, see go/headless-rendering.
+        cc::switches::kDisableThreadedAnimation,
+        cc::switches::kDisableCheckerImaging,
+        blink::switches::kDisableThreadedScrolling,
+
+        // Ensure that image animations don't resync their animation timestamps
+        // when looping back around.
+        blink::switches::kDisableImageAnimationResync,
+    };
+
+    for (auto* compositor_switch : compositor_switches) {
+      command_line->AppendSwitch(compositor_switch);
+    }
+  }
+};
+
+// BeginFrameControl is not supported on MacOS yet, see: https://cs.chromium.org
+// chromium/src/headless/lib/browser/protocol/target_handler.cc?
+// rcl=5811aa08e60ba5ac7622f029163213cfbdb682f7&l=32
+// TODO(crbug.com/954398): Suite is timeout-flaky on Windows.
+// TODO(crbug.com/1020046): Suite is flaky on TSan Linux.
+#if defined(OS_MAC) || defined(NO_WIN_FLAKES) || \
+    ((defined(OS_LINUX) || defined(OS_CHROMEOS)) && defined(THREAD_SANITIZER))
+#define HEADLESS_COMPOSITOR_TEST(TEST_NAME, SCRIPT_NAME) \
+  IN_PROC_BROWSER_TEST_F(HeadlessCompositorBrowserTest,  \
+                         DISABLED_##TEST_NAME) {         \
+    test_folder_ = "/protocol/";                         \
+    script_name_ = SCRIPT_NAME;                          \
+    RunTest();                                           \
+  }
+#else
+#define HEADLESS_COMPOSITOR_TEST(TEST_NAME, SCRIPT_NAME)             \
+  IN_PROC_BROWSER_TEST_F(HeadlessCompositorBrowserTest, TEST_NAME) { \
+    test_folder_ = "/protocol/";                                     \
+    script_name_ = SCRIPT_NAME;                                      \
+    RunTest();                                                       \
+  }
+#endif
+
+HEADLESS_COMPOSITOR_TEST(CompositorBasicRaf,
+                         "emulation/compositor-basic-raf.js")
+HEADLESS_COMPOSITOR_TEST(CompositorImageAnimation,
+                         "emulation/compositor-image-animation-test.js")
+
+// Flaky on all platforms. TODO(crbug.com/986027): Re-enable.
+#if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_CHROMEOS) || \
+    defined(OS_FUCHSIA)
+#define MAYBE_CompositorCssAnimation DISABLED_CompositorCssAnimation
+#else
+#define MAYBE_CompositorCssAnimation CompositorCssAnimation
+#endif
+HEADLESS_COMPOSITOR_TEST(MAYBE_CompositorCssAnimation,
+                         "emulation/compositor-css-animation-test.js")
+HEADLESS_COMPOSITOR_TEST(VirtualTimeCancelClientRedirect,
+                         "emulation/virtual-time-cancel-client-redirect.js")
+HEADLESS_COMPOSITOR_TEST(DoubleBeginFrame, "emulation/double-begin-frame.js")
+HEADLESS_COMPOSITOR_TEST(VirtualTimeControllerTest,
+                         "helpers/virtual-time-controller-test.js")
+HEADLESS_COMPOSITOR_TEST(RendererHelloWorld, "sanity/renderer-hello-world.js")
+HEADLESS_COMPOSITOR_TEST(RendererOverrideTitleJsEnabled,
+                         "sanity/renderer-override-title-js-enabled.js")
+HEADLESS_COMPOSITOR_TEST(RendererOverrideTitleJsDisabled,
+                         "sanity/renderer-override-title-js-disabled.js")
+HEADLESS_COMPOSITOR_TEST(RendererJavaScriptConsoleErrors,
+                         "sanity/renderer-javascript-console-errors.js")
+HEADLESS_COMPOSITOR_TEST(RendererDelayedCompletion,
+                         "sanity/renderer-delayed-completion.js")
+HEADLESS_COMPOSITOR_TEST(RendererClientRedirectChain,
+                         "sanity/renderer-client-redirect-chain.js")
+HEADLESS_COMPOSITOR_TEST(RendererClientRedirectChainNoJs,
+                         "sanity/renderer-client-redirect-chain-no-js.js")
+HEADLESS_COMPOSITOR_TEST(RendererServerRedirectChain,
+                         "sanity/renderer-server-redirect-chain.js")
+HEADLESS_COMPOSITOR_TEST(RendererServerRedirectToFailure,
+                         "sanity/renderer-server-redirect-to-failure.js")
+HEADLESS_COMPOSITOR_TEST(RendererServerRedirectRelativeChain,
+                         "sanity/renderer-server-redirect-relative-chain.js")
+HEADLESS_COMPOSITOR_TEST(RendererMixedRedirectChain,
+                         "sanity/renderer-mixed-redirect-chain.js")
+HEADLESS_COMPOSITOR_TEST(RendererFramesRedirectChain,
+                         "sanity/renderer-frames-redirect-chain.js")
+HEADLESS_COMPOSITOR_TEST(RendererDoubleRedirect,
+                         "sanity/renderer-double-redirect.js")
+HEADLESS_COMPOSITOR_TEST(RendererRedirectAfterCompletion,
+                         "sanity/renderer-redirect-after-completion.js")
+HEADLESS_COMPOSITOR_TEST(RendererRedirect307PostMethod,
+                         "sanity/renderer-redirect-307-post-method.js")
+HEADLESS_COMPOSITOR_TEST(RendererRedirectPostChain,
+                         "sanity/renderer-redirect-post-chain.js")
+HEADLESS_COMPOSITOR_TEST(RendererRedirect307PutMethod,
+                         "sanity/renderer-redirect-307-put-method.js")
+HEADLESS_COMPOSITOR_TEST(RendererRedirect303PutGet,
+                         "sanity/renderer-redirect-303-put-get.js")
+HEADLESS_COMPOSITOR_TEST(RendererRedirectBaseUrl,
+                         "sanity/renderer-redirect-base-url.js")
+HEADLESS_COMPOSITOR_TEST(RendererRedirectNonAsciiUrl,
+                         "sanity/renderer-redirect-non-ascii-url.js")
+HEADLESS_COMPOSITOR_TEST(RendererRedirectEmptyUrl,
+                         "sanity/renderer-redirect-empty-url.js")
+HEADLESS_COMPOSITOR_TEST(RendererRedirectInvalidUrl,
+                         "sanity/renderer-redirect-invalid-url.js")
+HEADLESS_COMPOSITOR_TEST(RendererRedirectKeepsFragment,
+                         "sanity/renderer-redirect-keeps-fragment.js")
+HEADLESS_COMPOSITOR_TEST(RendererRedirectReplacesFragment,
+                         "sanity/renderer-redirect-replaces-fragment.js")
+HEADLESS_COMPOSITOR_TEST(RendererRedirectNewFragment,
+                         "sanity/renderer-redirect-new-fragment.js")
+HEADLESS_COMPOSITOR_TEST(RendererWindowLocationFragments,
+                         "sanity/renderer-window-location-fragments.js")
+HEADLESS_COMPOSITOR_TEST(RendererCookieSetFromJs,
+                         "sanity/renderer-cookie-set-from-js.js")
+HEADLESS_COMPOSITOR_TEST(RendererCookieSetFromJsNoCookies,
+                         "sanity/renderer-cookie-set-from-js-no-cookies.js")
+HEADLESS_COMPOSITOR_TEST(RendererCookieUpdatedFromJs,
+                         "sanity/renderer-cookie-updated-from-js.js")
+HEADLESS_COMPOSITOR_TEST(RendererInCrossOriginObject,
+                         "sanity/renderer-in-cross-origin-object.js")
+
+HEADLESS_COMPOSITOR_TEST(RendererContentSecurityPolicy,
+                         "sanity/renderer-content-security-policy.js")
+
+HEADLESS_COMPOSITOR_TEST(RendererFrameLoadEvents,
+                         "sanity/renderer-frame-load-events.js")
+HEADLESS_COMPOSITOR_TEST(RendererCssUrlFilter,
+                         "sanity/renderer-css-url-filter.js")
+HEADLESS_COMPOSITOR_TEST(RendererCanvas, "sanity/renderer-canvas.js")
+
+HEADLESS_COMPOSITOR_TEST(RendererOpacityAnimation,
+                         "sanity/renderer-opacity-animation.js")
+
+}  // namespace headless
diff --git a/headless/test/headless_protocol_browsertest.cc b/headless/test/headless_protocol_browsertest.cc
index b9c0f8b..1609bc7c 100644
--- a/headless/test/headless_protocol_browsertest.cc
+++ b/headless/test/headless_protocol_browsertest.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 <memory>
+#include "headless/test/headless_protocol_browsertest.h"
 
 #include "base/base64.h"
 #include "base/base_paths.h"
@@ -10,25 +10,9 @@
 #include "base/files/file_util.h"
 #include "base/json/json_reader.h"
 #include "base/path_service.h"
-#include "base/threading/thread_restrictions.h"
-#include "build/build_config.h"
-#include "cc/base/switches.h"
-#include "components/viz/common/switches.h"
-#include "content/public/common/content_switches.h"
-#include "content/public/test/browser_test.h"
 #include "headless/app/headless_shell_switches.h"
-#include "headless/public/devtools/domains/runtime.h"
-#include "headless/public/headless_browser.h"
-#include "headless/public/headless_browser_context.h"
-#include "headless/public/headless_devtools_client.h"
-#include "headless/public/headless_devtools_target.h"
-#include "headless/public/headless_web_contents.h"
-#include "headless/test/headless_browser_test.h"
 #include "net/test/spawned_test_server/spawned_test_server.h"
 #include "services/network/public/cpp/network_switches.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/public/common/switches.h"
-#include "url/url_util.h"
 
 namespace headless {
 
@@ -37,161 +21,159 @@
 static const char kDumpDevToolsProtocol[] = "dump-devtools-protocol";
 }  // namespace
 
-class HeadlessProtocolBrowserTest
-    : public HeadlessAsyncDevTooledBrowserTest,
-      public HeadlessDevToolsClient::RawProtocolListener,
-      public runtime::ExperimentalObserver {
- public:
-  HeadlessProtocolBrowserTest() {
-    embedded_test_server()->ServeFilesFromSourceDirectory(
-        "third_party/blink/web_tests/http/tests/inspector-protocol");
-    EXPECT_TRUE(embedded_test_server()->Start());
-  }
+HeadlessProtocolBrowserTest::HeadlessProtocolBrowserTest() {
+  embedded_test_server()->ServeFilesFromSourceDirectory(
+      "third_party/blink/web_tests/http/tests/inspector-protocol");
+  EXPECT_TRUE(embedded_test_server()->Start());
+}
 
- protected:
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    command_line->AppendSwitchASCII(::network::switches::kHostResolverRules,
-                                    "MAP *.test 127.0.0.1");
-    HeadlessAsyncDevTooledBrowserTest::SetUpCommandLine(command_line);
+void HeadlessProtocolBrowserTest::SetUpCommandLine(
+    base::CommandLine* command_line) {
+  command_line->AppendSwitchASCII(::network::switches::kHostResolverRules,
+                                  "MAP *.test 127.0.0.1");
+  HeadlessAsyncDevTooledBrowserTest::SetUpCommandLine(command_line);
 
-    // Make sure the navigations spawn new processes. We run test harness
-    // in one process (harness.test) and tests in another.
-    command_line->AppendSwitch(::switches::kSitePerProcess);
+  // Make sure the navigations spawn new processes. We run test harness
+  // in one process (harness.test) and tests in another.
+  command_line->AppendSwitch(::switches::kSitePerProcess);
 
-    // Make sure proxy related tests are not affected by a platform specific
-    // system proxy configuration service.
-    command_line->AppendSwitch(switches::kNoSystemProxyConfigService);
-  }
+  // Make sure proxy related tests are not affected by a platform specific
+  // system proxy configuration service.
+  command_line->AppendSwitch(switches::kNoSystemProxyConfigService);
+}
 
- private:
-  // HeadlessWebContentsObserver implementation.
-  void RunDevTooledTest() override {
-    browser_devtools_client_->SetRawProtocolListener(this);
-    devtools_client_->GetRuntime()->GetExperimental()->AddObserver(this);
-    devtools_client_->GetRuntime()->Enable();
-    devtools_client_->GetRuntime()->GetExperimental()->AddBinding(
-        headless::runtime::AddBindingParams::Builder()
-            .SetName("sendProtocolMessage")
-            .Build(),
-        base::BindOnce(&HeadlessProtocolBrowserTest::BindingCreated,
-                       base::Unretained(this)));
-  }
+std::vector<std::string> HeadlessProtocolBrowserTest::GetPageUrlExtraParams() {
+  return {};
+}
 
-  void BindingCreated(std::unique_ptr<headless::runtime::AddBindingResult>) {
-    base::ScopedAllowBlockingForTesting allow_blocking;
-    base::FilePath src_dir;
-    CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &src_dir));
-    static const base::FilePath kTestsDirectory(
-        FILE_PATH_LITERAL("headless/test/data/protocol"));
-    base::FilePath test_path =
-        src_dir.Append(kTestsDirectory).AppendASCII(script_name_);
-    std::string script;
-    if (!base::ReadFileToString(test_path, &script)) {
-      ADD_FAILURE() << "Unable to read test at " << test_path;
-      FinishTest();
-      return;
-    }
-    GURL test_url = embedded_test_server()->GetURL("harness.test",
-                                                   "/protocol/" + script_name_);
-    GURL target_url = embedded_test_server()->GetURL(
-        "127.0.0.1", "/protocol/" + script_name_);
-    GURL page_url = embedded_test_server()->GetURL(
-        "harness.test", "/protocol/inspector-protocol-test.html?test=" +
-                            test_url.spec() + "&target=" + target_url.spec());
-    devtools_client_->GetPage()->Navigate(page_url.spec());
-  }
+void HeadlessProtocolBrowserTest::RunDevTooledTest() {
+  browser_devtools_client_->SetRawProtocolListener(this);
+  devtools_client_->GetRuntime()->GetExperimental()->AddObserver(this);
+  devtools_client_->GetRuntime()->Enable();
+  devtools_client_->GetRuntime()->GetExperimental()->AddBinding(
+      headless::runtime::AddBindingParams::Builder()
+          .SetName("sendProtocolMessage")
+          .Build(),
+      base::BindOnce(&HeadlessProtocolBrowserTest::BindingCreated,
+                     base::Unretained(this)));
+}
 
-  // runtime::Observer implementation.
-  void OnBindingCalled(const runtime::BindingCalledParams& params) override {
-    std::string json_message = params.GetPayload();
-    std::unique_ptr<base::Value> message =
-        base::JSONReader::ReadDeprecated(json_message);
-    const base::DictionaryValue* message_dict;
-    const base::DictionaryValue* params_dict;
-    std::string method;
-    int id;
-    if (!message || !message->GetAsDictionary(&message_dict) ||
-        !message_dict->GetString("method", &method) ||
-        !message_dict->GetDictionary("params", &params_dict) ||
-        !message_dict->GetInteger("id", &id)) {
-      LOG(ERROR) << "Poorly formed message " << json_message;
-      FinishTest();
-      return;
-    }
-
-    if (method != "DONE") {
-      if (base::CommandLine::ForCurrentProcess()->HasSwitch(
-              kDumpDevToolsProtocol)) {
-        LOG(INFO) << "FromJS: " << json_message;
-      }
-      // Pass unhandled commands onto the inspector.
-      browser_devtools_client_->SendRawDevToolsMessage(json_message);
-      return;
-    }
-
-    std::string test_result;
-    message_dict->GetString("result", &test_result);
-    static const base::FilePath kTestsDirectory(
-        FILE_PATH_LITERAL("headless/test/data/protocol"));
-
-    base::ScopedAllowBlockingForTesting allow_blocking;
-
-    base::FilePath src_dir;
-    CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &src_dir));
-    base::FilePath expectation_path =
-        src_dir.Append(kTestsDirectory)
-            .AppendASCII(script_name_.substr(0, script_name_.length() - 3) +
-                         "-expected.txt");
-
-    if (base::CommandLine::ForCurrentProcess()->HasSwitch(kResetResults)) {
-      LOG(INFO) << "Updating expectations at " << expectation_path;
-      int result = base::WriteFile(expectation_path, test_result.data(),
-                                   static_cast<int>(test_result.size()));
-      CHECK(test_result.size() == static_cast<size_t>(result));
-    }
-
-    std::string expectation;
-    if (!base::ReadFileToString(expectation_path, &expectation)) {
-      ADD_FAILURE() << "Unable to read expectations at " << expectation_path;
-    }
-    EXPECT_EQ(test_result, expectation);
+void HeadlessProtocolBrowserTest::BindingCreated(
+    std::unique_ptr<headless::runtime::AddBindingResult>) {
+  base::ScopedAllowBlockingForTesting allow_blocking;
+  base::FilePath src_dir;
+  CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &src_dir));
+  static const base::FilePath kTestsDirectory(
+      FILE_PATH_LITERAL("headless/test/data/protocol"));
+  base::FilePath test_path =
+      src_dir.Append(kTestsDirectory).AppendASCII(script_name_);
+  std::string script;
+  if (!base::ReadFileToString(test_path, &script)) {
+    ADD_FAILURE() << "Unable to read test at " << test_path;
     FinishTest();
+    return;
+  }
+  GURL test_url = embedded_test_server()->GetURL("harness.test",
+                                                 "/protocol/" + script_name_);
+  GURL target_url =
+      embedded_test_server()->GetURL("127.0.0.1", "/protocol/" + script_name_);
+
+  std::string extra_params;
+  for (const auto& param : GetPageUrlExtraParams()) {
+    extra_params += "&" + param;
   }
 
-  // HeadlessDevToolsClient::RawProtocolListener
-  bool OnProtocolMessage(base::span<const uint8_t> json_message,
-                         const base::DictionaryValue& parsed_message) override {
-    SendMessageToJS(
-        base::StringPiece(reinterpret_cast<const char*>(json_message.data()),
-                          json_message.size()));
-    return true;
+  GURL page_url = embedded_test_server()->GetURL(
+      "harness.test",
+      "/protocol/inspector-protocol-test.html?test=" + test_url.spec() +
+          "&target=" + target_url.spec() + extra_params);
+  devtools_client_->GetPage()->Navigate(page_url.spec());
+}
+
+void HeadlessProtocolBrowserTest::OnBindingCalled(
+    const runtime::BindingCalledParams& params) {
+  std::string json_message = params.GetPayload();
+  std::unique_ptr<base::Value> message =
+      base::JSONReader::ReadDeprecated(json_message);
+  const base::DictionaryValue* message_dict;
+  const base::DictionaryValue* params_dict;
+  std::string method;
+  int id;
+  if (!message || !message->GetAsDictionary(&message_dict) ||
+      !message_dict->GetString("method", &method) ||
+      !message_dict->GetDictionary("params", &params_dict) ||
+      !message_dict->GetInteger("id", &id)) {
+    LOG(ERROR) << "Poorly formed message " << json_message;
+    FinishTest();
+    return;
   }
 
-  void SendMessageToJS(base::StringPiece message) {
-    if (test_finished_)
-      return;
-
+  if (method != "DONE") {
     if (base::CommandLine::ForCurrentProcess()->HasSwitch(
             kDumpDevToolsProtocol)) {
-      LOG(INFO) << "ToJS: " << message;
+      LOG(INFO) << "FromJS: " << json_message;
     }
-
-    std::string encoded;
-    base::Base64Encode(message, &encoded);
-    devtools_client_->GetRuntime()->Evaluate("onmessage(atob(\"" + encoded +
-                                             "\"))");
+    // Pass unhandled commands onto the inspector.
+    browser_devtools_client_->SendRawDevToolsMessage(json_message);
+    return;
   }
 
-  void FinishTest() {
-    test_finished_ = true;
-    FinishAsynchronousTest();
+  std::string test_result;
+  message_dict->GetString("result", &test_result);
+  static const base::FilePath kTestsDirectory(
+      FILE_PATH_LITERAL("headless/test/data/protocol"));
+
+  base::ScopedAllowBlockingForTesting allow_blocking;
+
+  base::FilePath src_dir;
+  CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &src_dir));
+  base::FilePath expectation_path =
+      src_dir.Append(kTestsDirectory)
+          .AppendASCII(script_name_.substr(0, script_name_.length() - 3) +
+                       "-expected.txt");
+
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(kResetResults)) {
+    LOG(INFO) << "Updating expectations at " << expectation_path;
+    int result = base::WriteFile(expectation_path, test_result.data(),
+                                 static_cast<int>(test_result.size()));
+    CHECK(test_result.size() == static_cast<size_t>(result));
   }
 
- protected:
-  bool test_finished_ = false;
-  std::string test_folder_;
-  std::string script_name_;
-};
+  std::string expectation;
+  if (!base::ReadFileToString(expectation_path, &expectation)) {
+    ADD_FAILURE() << "Unable to read expectations at " << expectation_path;
+  }
+  EXPECT_EQ(test_result, expectation);
+  FinishTest();
+}
+
+bool HeadlessProtocolBrowserTest::OnProtocolMessage(
+    base::span<const uint8_t> json_message,
+    const base::DictionaryValue& parsed_message) {
+  SendMessageToJS(base::StringPiece(
+      reinterpret_cast<const char*>(json_message.data()), json_message.size()));
+  return true;
+}
+
+void HeadlessProtocolBrowserTest::SendMessageToJS(base::StringPiece message) {
+  if (test_finished_)
+    return;
+
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          kDumpDevToolsProtocol)) {
+    LOG(INFO) << "ToJS: " << message;
+  }
+
+  std::string encoded;
+  base::Base64Encode(message, &encoded);
+  devtools_client_->GetRuntime()->Evaluate("onmessage(atob(\"" + encoded +
+                                           "\"))");
+}
+
+void HeadlessProtocolBrowserTest::FinishTest() {
+  test_finished_ = true;
+  FinishAsynchronousTest();
+}
 
 // TODO(crbug.com/867447): The whole test suite is extremely flaky on Win dbg.
 // TODO(crbug.com/1086872): The whole test suite is flaky on Mac ASAN.
@@ -278,37 +260,37 @@
 HEADLESS_PROTOCOL_TEST(HeadlessSessionCreateContextDisposeOnDetach,
                        "sessions/headless-createContext-disposeOnDetach.js")
 
-class HeadlessProtocolCompositorBrowserTest
+HEADLESS_PROTOCOL_TEST(BrowserSetInitialProxyConfig,
+                       "sanity/browser-set-initial-proxy-config.js")
+
+class HeadlessProtocolBrowserTestWithProxy
     : public HeadlessProtocolBrowserTest {
  public:
-  HeadlessProtocolCompositorBrowserTest() = default;
+  HeadlessProtocolBrowserTestWithProxy()
+      : proxy_server_(net::SpawnedTestServer::TYPE_HTTP,
+                      base::FilePath(FILE_PATH_LITERAL("headless/test/data"))) {
+  }
+
+  void SetUp() override {
+    ASSERT_TRUE(proxy_server_.Start());
+    HeadlessProtocolBrowserTest::SetUp();
+  }
+
+  void TearDown() override {
+    proxy_server_.Stop();
+    HeadlessProtocolBrowserTest::TearDown();
+  }
+
+  net::SpawnedTestServer* proxy_server() { return &proxy_server_; }
+
+ protected:
+  std::vector<std::string> GetPageUrlExtraParams() override {
+    std::string proxy = proxy_server()->host_port_pair().ToString();
+    return {"&proxy=" + proxy};
+  }
 
  private:
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    HeadlessProtocolBrowserTest::SetUpCommandLine(command_line);
-    // The following switches are recommended for BeginFrameControl required by
-    // compositor tests, see https://goo.gl/3zHXhB for details
-    static const char* const compositor_switches[] = {
-        // We control BeginFrames ourselves and need all compositing stages to
-        // run.
-        ::switches::kRunAllCompositorStagesBeforeDraw,
-        ::switches::kDisableNewContentRenderingTimeout,
-
-        // Animtion-only BeginFrames are only supported when updates from the
-        // impl-thread are disabled, see go/headless-rendering.
-        cc::switches::kDisableThreadedAnimation,
-        cc::switches::kDisableCheckerImaging,
-        blink::switches::kDisableThreadedScrolling,
-
-        // Ensure that image animations don't resync their animation timestamps
-        // when looping back around.
-        blink::switches::kDisableImageAnimationResync,
-    };
-
-    for (auto* compositor_switch : compositor_switches) {
-      command_line->AppendSwitch(compositor_switch);
-    }
-  }
+  net::SpawnedTestServer proxy_server_;
 };
 
 // BeginFrameControl is not supported on MacOS yet, see: https://cs.chromium.org
@@ -318,127 +300,23 @@
 // TODO(crbug.com/1020046): Suite is flaky on TSan Linux.
 #if defined(OS_MAC) || defined(NO_WIN_FLAKES) || \
     ((defined(OS_LINUX) || defined(OS_CHROMEOS)) && defined(THREAD_SANITIZER))
-#define HEADLESS_PROTOCOL_COMPOSITOR_TEST(TEST_NAME, SCRIPT_NAME) \
-  IN_PROC_BROWSER_TEST_F(HeadlessProtocolCompositorBrowserTest,   \
+#define HEADLESS_PROTOCOL_TEST_WITH_PROXY(TEST_NAME, SCRIPT_NAME) \
+  IN_PROC_BROWSER_TEST_F(HeadlessProtocolBrowserTestWithProxy,    \
                          DISABLED_##TEST_NAME) {                  \
     test_folder_ = "/protocol/";                                  \
     script_name_ = SCRIPT_NAME;                                   \
     RunTest();                                                    \
   }
 #else
-#define HEADLESS_PROTOCOL_COMPOSITOR_TEST(TEST_NAME, SCRIPT_NAME)            \
-  IN_PROC_BROWSER_TEST_F(HeadlessProtocolCompositorBrowserTest, TEST_NAME) { \
-    test_folder_ = "/protocol/";                                             \
-    script_name_ = SCRIPT_NAME;                                              \
-    RunTest();                                                               \
+#define HEADLESS_PROTOCOL_TEST_WITH_PROXY(TEST_NAME, SCRIPT_NAME)           \
+  IN_PROC_BROWSER_TEST_F(HeadlessProtocolBrowserTestWithProxy, TEST_NAME) { \
+    test_folder_ = "/protocol/";                                            \
+    script_name_ = SCRIPT_NAME;                                             \
+    RunTest();                                                              \
   }
 #endif
 
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(CompositorBasicRaf,
-                                  "emulation/compositor-basic-raf.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(
-    CompositorImageAnimation,
-    "emulation/compositor-image-animation-test.js")
-
-// Flaky on all platforms. TODO(crbug.com/986027): Re-enable.
-#if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_CHROMEOS) || \
-    defined(OS_FUCHSIA)
-#define MAYBE_CompositorCssAnimation DISABLED_CompositorCssAnimation
-#else
-#define MAYBE_CompositorCssAnimation CompositorCssAnimation
-#endif
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(MAYBE_CompositorCssAnimation,
-                                  "emulation/compositor-css-animation-test.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(
-    VirtualTimeCancelClientRedirect,
-    "emulation/virtual-time-cancel-client-redirect.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(DoubleBeginFrame,
-                                  "emulation/double-begin-frame.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(VirtualTimeControllerTest,
-                                  "helpers/virtual-time-controller-test.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(RendererHelloWorld,
-                                  "sanity/renderer-hello-world.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(
-    RendererOverrideTitleJsEnabled,
-    "sanity/renderer-override-title-js-enabled.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(
-    RendererOverrideTitleJsDisabled,
-    "sanity/renderer-override-title-js-disabled.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(
-    RendererJavaScriptConsoleErrors,
-    "sanity/renderer-javascript-console-errors.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(RendererDelayedCompletion,
-                                  "sanity/renderer-delayed-completion.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(RendererClientRedirectChain,
-                                  "sanity/renderer-client-redirect-chain.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(
-    RendererClientRedirectChainNoJs,
-    "sanity/renderer-client-redirect-chain-no-js.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(RendererServerRedirectChain,
-                                  "sanity/renderer-server-redirect-chain.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(
-    RendererServerRedirectToFailure,
-    "sanity/renderer-server-redirect-to-failure.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(
-    RendererServerRedirectRelativeChain,
-    "sanity/renderer-server-redirect-relative-chain.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(RendererMixedRedirectChain,
-                                  "sanity/renderer-mixed-redirect-chain.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(RendererFramesRedirectChain,
-                                  "sanity/renderer-frames-redirect-chain.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(RendererDoubleRedirect,
-                                  "sanity/renderer-double-redirect.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(
-    RendererRedirectAfterCompletion,
-    "sanity/renderer-redirect-after-completion.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(RendererRedirect307PostMethod,
-                                  "sanity/renderer-redirect-307-post-method.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(RendererRedirectPostChain,
-                                  "sanity/renderer-redirect-post-chain.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(RendererRedirect307PutMethod,
-                                  "sanity/renderer-redirect-307-put-method.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(RendererRedirect303PutGet,
-                                  "sanity/renderer-redirect-303-put-get.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(RendererRedirectBaseUrl,
-                                  "sanity/renderer-redirect-base-url.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(RendererRedirectNonAsciiUrl,
-                                  "sanity/renderer-redirect-non-ascii-url.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(RendererRedirectEmptyUrl,
-                                  "sanity/renderer-redirect-empty-url.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(RendererRedirectInvalidUrl,
-                                  "sanity/renderer-redirect-invalid-url.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(RendererRedirectKeepsFragment,
-                                  "sanity/renderer-redirect-keeps-fragment.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(
-    RendererRedirectReplacesFragment,
-    "sanity/renderer-redirect-replaces-fragment.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(RendererRedirectNewFragment,
-                                  "sanity/renderer-redirect-new-fragment.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(
-    RendererWindowLocationFragments,
-    "sanity/renderer-window-location-fragments.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(RendererCookieSetFromJs,
-                                  "sanity/renderer-cookie-set-from-js.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(
-    RendererCookieSetFromJsNoCookies,
-    "sanity/renderer-cookie-set-from-js-no-cookies.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(RendererCookieUpdatedFromJs,
-                                  "sanity/renderer-cookie-updated-from-js.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(RendererInCrossOriginObject,
-                                  "sanity/renderer-in-cross-origin-object.js")
-
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(RendererContentSecurityPolicy,
-                                  "sanity/renderer-content-security-policy.js")
-
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(RendererFrameLoadEvents,
-                                  "sanity/renderer-frame-load-events.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(RendererCssUrlFilter,
-                                  "sanity/renderer-css-url-filter.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(RendererCanvas, "sanity/renderer-canvas.js")
-
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(RendererOpacityAnimation,
-                                  "sanity/renderer-opacity-animation.js")
-HEADLESS_PROTOCOL_COMPOSITOR_TEST(BrowserSetInitialProxyConfig,
-                                  "sanity/browser-set-initial-proxy-config.js")
+HEADLESS_PROTOCOL_TEST_WITH_PROXY(BrowserSetProxyConfig,
+                                  "sanity/browser-set-proxy-config.js")
 
 }  // namespace headless
diff --git a/headless/test/headless_protocol_browsertest.h b/headless/test/headless_protocol_browsertest.h
new file mode 100644
index 0000000..69008fd
--- /dev/null
+++ b/headless/test/headless_protocol_browsertest.h
@@ -0,0 +1,54 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef HEADLESS_TEST_HEADLESS_PROTOCOL_BROWSERTEST_H_
+#define HEADLESS_TEST_HEADLESS_PROTOCOL_BROWSERTEST_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "content/public/test/browser_test.h"
+#include "headless/public/devtools/domains/runtime.h"
+#include "headless/public/headless_devtools_client.h"
+#include "headless/test/headless_browser_test.h"
+
+namespace headless {
+
+class HeadlessProtocolBrowserTest
+    : public HeadlessAsyncDevTooledBrowserTest,
+      public HeadlessDevToolsClient::RawProtocolListener,
+      public runtime::ExperimentalObserver {
+ public:
+  HeadlessProtocolBrowserTest();
+
+ protected:
+  void SetUpCommandLine(base::CommandLine* command_line) override;
+
+  virtual std::vector<std::string> GetPageUrlExtraParams();
+
+ private:
+  // HeadlessWebContentsObserver implementation.
+  void RunDevTooledTest() override;
+  void BindingCreated(std::unique_ptr<headless::runtime::AddBindingResult>);
+
+  // runtime::Observer implementation.
+  void OnBindingCalled(const runtime::BindingCalledParams& params) override;
+
+  // HeadlessDevToolsClient::RawProtocolListener
+  bool OnProtocolMessage(base::span<const uint8_t> json_message,
+                         const base::DictionaryValue& parsed_message) override;
+
+  void SendMessageToJS(base::StringPiece message);
+  void FinishTest();
+
+ protected:
+  bool test_finished_ = false;
+  std::string test_folder_;
+  std::string script_name_;
+};
+
+}  // namespace headless
+
+#endif  // HEADLESS_TEST_HEADLESS_PROTOCOL_BROWSERTEST_H_
diff --git a/ios/chrome/app/resources/inspect/inspect.html b/ios/chrome/app/resources/inspect/inspect.html
index efb9b28b..d21d33e 100644
--- a/ios/chrome/app/resources/inspect/inspect.html
+++ b/ios/chrome/app/resources/inspect/inspect.html
@@ -7,6 +7,7 @@
   <script src="chrome://resources/js/ios/web_ui.js"></script>
 
   <script src="chrome://resources/js/load_time_data.js"></script>
+  <script src="chrome://resources/js/assert.js"></script>
   <script src="chrome://resources/js/util.js"></script>
   <script src="strings.js"></script>
   <script src="inspect.js"></script>
diff --git a/ios/chrome/app/resources/omaha/omaha.html b/ios/chrome/app/resources/omaha/omaha.html
index 0293d49a..5e158a94 100644
--- a/ios/chrome/app/resources/omaha/omaha.html
+++ b/ios/chrome/app/resources/omaha/omaha.html
@@ -9,6 +9,7 @@
   <link rel="stylesheet" href="omaha.css">
   <script src="chrome://resources/js/ios/web_ui.js"></script>
   <script src="chrome://resources/js/load_time_data.js"></script>
+  <script src="chrome://resources/js/assert.js"></script>
   <script src="chrome://resources/js/util.js"></script>
   <script src="chrome://omaha/omaha.js"></script>
   <script src="chrome://omaha/strings.js"></script>
diff --git a/ios/chrome/browser/metrics/mobile_session_shutdown_metrics_provider.mm b/ios/chrome/browser/metrics/mobile_session_shutdown_metrics_provider.mm
index 9bb1ce76..71314c5 100644
--- a/ios/chrome/browser/metrics/mobile_session_shutdown_metrics_provider.mm
+++ b/ios/chrome/browser/metrics/mobile_session_shutdown_metrics_provider.mm
@@ -61,6 +61,21 @@
   kMaxValue = SessionRestorationXte
 };
 
+// Values of the Stability.iOS.UTE.MobileSessionAppWillTerminateReceived
+// histogram. These values are persisted to logs. Entries should not be
+// renumbered and numeric values should never be reused.
+enum class MobileSessionAppWillTerminateReceived {
+  // ApplicationWillTerminate notification was not received for this XTE.
+  WasNotReceivedForXte = 0,
+  // ApplicationWillTerminate notification was not received for this UTE.
+  WasNotReceivedForUte = 1,
+  // ApplicationWillTerminate notification was received for this XTE.
+  WasReceivedForXte = 2,
+  // ApplicationWillTerminate notification was received for this UTE.
+  WasReceivedForUte = 3,
+  kMaxValue = WasReceivedForUte
+};
+
 // Values of the Stability.iOS.UTE.MobileSessionAppState histogram.
 // These values are persisted to logs. Entries should not be renumbered and
 // numeric values should never be reused.
@@ -127,6 +142,21 @@
   NOTREACHED();
 }
 
+// Returns value to record for
+// Stability.iOS.UTE.MobileSessionAppWillTerminateReceived histogram.
+MobileSessionAppWillTerminateReceived GetMobileSessionAppWillTerminateReceived(
+    bool has_possible_explanation) {
+  if (!PreviousSessionInfo.sharedInstance.applicationWillTerminateWasReceived) {
+    return has_possible_explanation
+               ? MobileSessionAppWillTerminateReceived::WasNotReceivedForXte
+               : MobileSessionAppWillTerminateReceived::WasNotReceivedForUte;
+  }
+
+  return has_possible_explanation
+             ? MobileSessionAppWillTerminateReceived::WasReceivedForXte
+             : MobileSessionAppWillTerminateReceived::WasReceivedForUte;
+}
+
 // Logs |type| in the shutdown type histogram.
 void LogShutdownType(MobileSessionShutdownType type) {
   UMA_STABILITY_HISTOGRAM_ENUMERATION("Stability.MobileSessionShutdownType",
@@ -310,6 +340,11 @@
         GetMobileSessionAppState(possible_explanation),
         MobileSessionAppState::kMaxValue);
 
+    UMA_STABILITY_HISTOGRAM_ENUMERATION(
+        "Stability.iOS.UTE.MobileSessionAppWillTerminateWasReceived",
+        GetMobileSessionAppWillTerminateReceived(possible_explanation),
+        MobileSessionAppWillTerminateReceived::kMaxValue);
+
     if (!possible_explanation && EnableSyntheticCrashReportsForUte() &&
         GetApplicationContext()->GetLocalState()->GetBoolean(
             metrics::prefs::kMetricsReportingEnabled)) {
diff --git a/ios/chrome/browser/ui/bookmarks/bookmarks_entries_egtest.mm b/ios/chrome/browser/ui/bookmarks/bookmarks_entries_egtest.mm
index 03b44c6..2931d73 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmarks_entries_egtest.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmarks_entries_egtest.mm
@@ -52,9 +52,12 @@
 
 // Tear down called once per test.
 - (void)tearDown {
-  [super tearDown];
+  // No-op if only one window presents.
+  [ChromeEarlGrey closeAllExtraWindowsAndForceRelaunchWithAppConfig:
+                      [self appConfigurationForTestCase]];
   [ChromeEarlGrey clearBookmarks];
   [BookmarkEarlGrey clearBookmarksPositionCache];
+  [super tearDown];
 }
 
 #pragma mark - BookmarksEntriesTestCase Tests
@@ -197,23 +200,22 @@
 
 // Tests display and selection of 'Open in New Window' in a context menu on a
 // bookmarks entry.
-// TODO(crbug.com/1126893): reenable this test once EG multiwindow support is
-// available.
-- (void)DISABLED_testContextMenuOpenInNewWindow {
+- (void)testContextMenuOpenInNewWindow {
   // TODO(crbug.com/1035764): EG1 Test fails on iOS 12.
   if (!base::ios::IsRunningOnIOS13OrLater()) {
     EARL_GREY_TEST_DISABLED(@"EG1 Fails on iOS 12.");
   }
 
-  if (!IsMultipleScenesSupported()) {
-    EARL_GREY_TEST_DISABLED(@"Multiple scenes can't be opened.");
+  if (![ChromeEarlGrey areMultipleWindowsSupported]) {
+    EARL_GREY_TEST_DISABLED(@"Multiple windows can't be opened.");
   }
 
+  [BookmarkEarlGrey clearBookmarksPositionCache];
   [BookmarkEarlGrey setupStandardBookmarks];
   [BookmarkEarlGreyUI openBookmarks];
   [BookmarkEarlGreyUI openMobileBookmarks];
 
-  [ChromeEarlGrey waitForBrowserCount:1];
+  [ChromeEarlGrey waitForForegroundWindowCount:1];
 
   // Open a bookmark in a new window (through a long press).
   [[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"First URL")]
@@ -224,10 +226,9 @@
   [[EarlGrey selectElementWithMatcher:chrome_test_util::OmniboxText(
                                           GetFirstUrl().GetContent())]
       assertWithMatcher:grey_notNil()];
-  [ChromeEarlGrey waitForBrowserCount:2];
-
-  [ChromeEarlGrey closeCurrentTab];
-  [ChromeEarlGrey waitForBrowserCount:1];
+  [ChromeEarlGrey waitForForegroundWindowCount:2];
+  [ChromeEarlGrey closeAllExtraWindowsAndForceRelaunchWithAppConfig:
+                      [self appConfigurationForTestCase]];
 }
 
 // Verify Edit Text functionality on single URL selection.
diff --git a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
index 8f1c4ab2..6c462a5d 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
@@ -3519,11 +3519,11 @@
                                                    NSString* password))handler {
   DCHECK(handler);
   web::WebStateDelegate::AuthCallback callback =
-      base::BindRepeating(^(NSString* user, NSString* password) {
+      base::BindOnce(^(NSString* user, NSString* password) {
         handler(user, password);
       });
   WebStateDelegateTabHelper::FromWebState(webState)->OnAuthRequired(
-      webState, protectionSpace, proposedCredential, callback);
+      webState, protectionSpace, proposedCredential, std::move(callback));
 }
 
 - (BOOL)webState:(web::WebState*)webState
diff --git a/ios/chrome/browser/ui/history/history_ui_egtest.mm b/ios/chrome/browser/ui/history/history_ui_egtest.mm
index f6d4ba34..a842547 100644
--- a/ios/chrome/browser/ui/history/history_ui_egtest.mm
+++ b/ios/chrome/browser/ui/history/history_ui_egtest.mm
@@ -15,7 +15,6 @@
 #import "ios/chrome/browser/ui/table_view/feature_flags.h"
 #import "ios/chrome/browser/ui/table_view/table_view_constants.h"
 #import "ios/chrome/browser/ui/ui_feature_flags.h"
-#import "ios/chrome/browser/ui/util/multi_window_support.h"
 #include "ios/chrome/common/string_util.h"
 #include "ios/chrome/grit/ios_strings.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
@@ -138,6 +137,9 @@
 }
 
 - (void)tearDown {
+  // No-op if only one window presents.
+  [ChromeEarlGrey closeAllExtraWindowsAndForceRelaunchWithAppConfig:
+                      [self appConfigurationForTestCase]];
   NSError* error = nil;
   // Dismiss search bar by pressing cancel, if present. Passing error prevents
   // failure if the element is not found.
@@ -395,16 +397,14 @@
 
 // Tests display and selection of 'Open in New Window' in a context menu on a
 // history entry.
-// TODO(crbug.com/1126893): reenable this test once EG multiwindow support is
-// available.
-- (void)DISABLED_testContextMenuOpenInNewWindow {
-  if (!IsMultipleScenesSupported())
-    return;
+- (void)testContextMenuOpenInNewWindow {
+  if (![ChromeEarlGrey areMultipleWindowsSupported])
+    EARL_GREY_TEST_DISABLED(@"Multiple windows can't be opened.");
 
   [self loadTestURLs];
   [self openHistoryPanel];
 
-  [ChromeEarlGrey waitForBrowserCount:1];
+  [ChromeEarlGrey waitForForegroundWindowCount:1];
 
   // Long press on the history element.
   [[EarlGrey
@@ -415,13 +415,13 @@
   // selected URL in the new window.
   [[EarlGrey selectElementWithMatcher:OpenLinkInNewWindowButton()]
       performAction:grey_tap()];
+  [ChromeEarlGrey waitForForegroundWindowCount:2];
   [[EarlGrey selectElementWithMatcher:chrome_test_util::OmniboxText(
                                           _URL1.GetContent())]
       assertWithMatcher:grey_notNil()];
-  [ChromeEarlGrey waitForBrowserCount:2];
 
-  [ChromeEarlGrey closeCurrentTab];
-  [ChromeEarlGrey waitForBrowserCount:1];
+  [ChromeEarlGrey closeAllExtraWindowsAndForceRelaunchWithAppConfig:
+                      [self appConfigurationForTestCase]];
 }
 
 // Tests display and selection of 'Open in New Incognito Tab' in a context menu
diff --git a/ios/chrome/browser/ui/popup_menu/popup_menu_constants.h b/ios/chrome/browser/ui/popup_menu/popup_menu_constants.h
index e935e2d..0c7c5af 100644
--- a/ios/chrome/browser/ui/popup_menu/popup_menu_constants.h
+++ b/ios/chrome/browser/ui/popup_menu/popup_menu_constants.h
@@ -21,7 +21,7 @@
 // New Tab item accessibility Identifier.
 extern NSString* const kToolsMenuNewTabId;
 // New Tab item accessibility Identifier.
-extern NSString* const kToolsMenuNewWindow;
+extern NSString* const kToolsMenuNewWindowId;
 // New incognito Tab item accessibility Identifier.
 extern NSString* const kToolsMenuNewIncognitoTabId;
 // Close all Tabs item accessibility Identifier.
diff --git a/ios/chrome/browser/ui/popup_menu/popup_menu_constants.mm b/ios/chrome/browser/ui/popup_menu/popup_menu_constants.mm
index 631d763..aa3ba5e 100644
--- a/ios/chrome/browser/ui/popup_menu/popup_menu_constants.mm
+++ b/ios/chrome/browser/ui/popup_menu/popup_menu_constants.mm
@@ -17,7 +17,7 @@
 NSString* const kToolsMenuReload = @"kToolsMenuReload";
 NSString* const kToolsMenuStop = @"kToolsMenuStop";
 NSString* const kToolsMenuNewTabId = @"kToolsMenuNewTabId";
-NSString* const kToolsMenuNewWindow = @"kToolsMenuNewWindow";
+NSString* const kToolsMenuNewWindowId = @"kToolsMenuNewWindowId";
 NSString* const kToolsMenuNewIncognitoTabId = @"kToolsMenuNewIncognitoTabId";
 NSString* const kToolsMenuCloseAllTabsId = @"kToolsMenuCloseAllTabsId";
 NSString* const kToolsMenuCloseAllIncognitoTabsId =
diff --git a/ios/chrome/browser/ui/popup_menu/popup_menu_egtest.mm b/ios/chrome/browser/ui/popup_menu/popup_menu_egtest.mm
index 9cccd3e..fe12428 100644
--- a/ios/chrome/browser/ui/popup_menu/popup_menu_egtest.mm
+++ b/ios/chrome/browser/ui/popup_menu/popup_menu_egtest.mm
@@ -33,6 +33,9 @@
 // Rotate the device back to portrait if needed, since some tests attempt to run
 // in landscape.
 - (void)tearDown {
+  // No-op if only one window presents.
+  [ChromeEarlGrey closeAllExtraWindowsAndForceRelaunchWithAppConfig:
+                      [self appConfigurationForTestCase]];
   [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationPortrait error:nil];
   [super tearDown];
 }
@@ -147,6 +150,20 @@
       assertWithMatcher:grey_notVisible()];
 }
 
+- (void)testNewWindowFromToolsMenu {
+  if (![ChromeEarlGrey areMultipleWindowsSupported])
+    EARL_GREY_TEST_DISABLED(@"Multiple windows can't be opened.");
+
+  [ChromeEarlGreyUI openToolsMenu];
+  [ChromeEarlGreyUI
+      tapToolsMenuButton:chrome_test_util::OpenNewWindowMenuButton()];
+
+  // Verify the second window.
+  [ChromeEarlGrey waitForForegroundWindowCount:2];
+  [ChromeEarlGrey closeAllExtraWindowsAndForceRelaunchWithAppConfig:
+                      [self appConfigurationForTestCase]];
+}
+
 // Navigates to a pdf page and verifies that the "Find in Page..." tool
 // is not enabled
 - (void)testNoSearchForPDF {
diff --git a/ios/chrome/browser/ui/popup_menu/popup_menu_mediator.mm b/ios/chrome/browser/ui/popup_menu/popup_menu_mediator.mm
index e953dea..977dca7 100644
--- a/ios/chrome/browser/ui/popup_menu/popup_menu_mediator.mm
+++ b/ios/chrome/browser/ui/popup_menu/popup_menu_mediator.mm
@@ -911,7 +911,7 @@
   // Create the menu item -- hardcoded string and no accessibility ID.
   PopupMenuToolsItem* openNewWindowItem = CreateTableViewItem(
       IDS_IOS_TOOLS_MENU_NEW_WINDOW, PopupMenuActionOpenNewWindow,
-      @"popup_menu_new_window", kToolsMenuNewWindow);
+      @"popup_menu_new_window", kToolsMenuNewWindowId);
 
   return @[ openNewWindowItem ];
 }
diff --git a/ios/chrome/browser/web/chrome_web_client.h b/ios/chrome/browser/web/chrome_web_client.h
index 86e5182..faa8742 100644
--- a/ios/chrome/browser/web/chrome_web_client.h
+++ b/ios/chrome/browser/web/chrome_web_client.h
@@ -43,14 +43,13 @@
       web::BrowserState* browser_state) const override;
   NSString* GetDocumentStartScriptForMainFrame(
       web::BrowserState* browser_state) const override;
-  void AllowCertificateError(
-      web::WebState* web_state,
-      int cert_error,
-      const net::SSLInfo& ssl_info,
-      const GURL& request_url,
-      bool overridable,
-      int64_t navigation_id,
-      const base::Callback<void(bool)>& callback) override;
+  void AllowCertificateError(web::WebState* web_state,
+                             int cert_error,
+                             const net::SSLInfo& ssl_info,
+                             const GURL& request_url,
+                             bool overridable,
+                             int64_t navigation_id,
+                             base::OnceCallback<void(bool)> callback) override;
   bool IsLegacyTLSAllowedForHost(web::WebState* web_state,
                                  const std::string& hostname) override;
   void PrepareErrorPage(web::WebState* web_state,
diff --git a/ios/chrome/browser/web/chrome_web_client.mm b/ios/chrome/browser/web/chrome_web_client.mm
index ff59099..d157912 100644
--- a/ios/chrome/browser/web/chrome_web_client.mm
+++ b/ios/chrome/browser/web/chrome_web_client.mm
@@ -299,15 +299,15 @@
     const GURL& request_url,
     bool overridable,
     int64_t navigation_id,
-    const base::Callback<void(bool)>& callback) {
+    base::OnceCallback<void(bool)> callback) {
   base::OnceCallback<void(NSString*)> null_callback;
   // TODO(crbug.com/760873): IOSSSLErrorHandler will present an interstitial
   // for the user to decide if it is safe to proceed.
   // Handle the case of web_state not presenting UI to users like prerender tabs
   // or web_state used to fetch offline content in Reading List.
-  IOSSSLErrorHandler::HandleSSLError(web_state, cert_error, info, request_url,
-                                     overridable, navigation_id, callback,
-                                     std::move(null_callback));
+  IOSSSLErrorHandler::HandleSSLError(
+      web_state, cert_error, info, request_url, overridable, navigation_id,
+      std::move(callback), std::move(null_callback));
 }
 
 bool ChromeWebClient::IsLegacyTLSAllowedForHost(web::WebState* web_state,
diff --git a/ios/chrome/browser/web/web_state_delegate_tab_helper.h b/ios/chrome/browser/web/web_state_delegate_tab_helper.h
index 2137de4..8236d2a 100644
--- a/ios/chrome/browser/web/web_state_delegate_tab_helper.h
+++ b/ios/chrome/browser/web/web_state_delegate_tab_helper.h
@@ -24,11 +24,10 @@
   // web::WebStateDelegate:
   web::JavaScriptDialogPresenter* GetJavaScriptDialogPresenter(
       web::WebState* source) override;
-  void OnAuthRequired(
-      web::WebState* source,
-      NSURLProtectionSpace* protection_space,
-      NSURLCredential* proposed_credential,
-      const web::WebStateDelegate::AuthCallback& callback) override;
+  void OnAuthRequired(web::WebState* source,
+                      NSURLProtectionSpace* protection_space,
+                      NSURLCredential* proposed_credential,
+                      web::WebStateDelegate::AuthCallback callback) override;
 
  private:
   explicit WebStateDelegateTabHelper(web::WebState* web_state);
diff --git a/ios/chrome/browser/web/web_state_delegate_tab_helper.mm b/ios/chrome/browser/web/web_state_delegate_tab_helper.mm
index cb67c71..8715bb3 100644
--- a/ios/chrome/browser/web/web_state_delegate_tab_helper.mm
+++ b/ios/chrome/browser/web/web_state_delegate_tab_helper.mm
@@ -28,12 +28,12 @@
     HTTPAuthOverlayResponseInfo* auth_info =
         response->GetInfo<HTTPAuthOverlayResponseInfo>();
     if (auth_info) {
-      callback.Run(base::SysUTF8ToNSString(auth_info->username()),
-                   base::SysUTF8ToNSString(auth_info->password()));
+      std::move(callback).Run(base::SysUTF8ToNSString(auth_info->username()),
+                              base::SysUTF8ToNSString(auth_info->password()));
       return;
     }
   }
-  callback.Run(nil, nil);
+  std::move(callback).Run(nil, nil);
 }
 }  // namespace
 
@@ -54,8 +54,7 @@
     web::WebState* source,
     NSURLProtectionSpace* protection_space,
     NSURLCredential* proposed_credential,
-    const web::WebStateDelegate::AuthCallback& callback) {
-  AuthCallback local_callback(callback);
+    web::WebStateDelegate::AuthCallback callback) {
   std::string message = base::SysNSStringToUTF8(
       nsurlprotectionspace_util::MessageForHTTPAuth(protection_space));
   std::string default_username;
@@ -66,7 +65,7 @@
           nsurlprotectionspace_util::RequesterOrigin(protection_space), message,
           default_username);
   request->GetCallbackManager()->AddCompletionCallback(
-      base::BindOnce(&OnHTTPAuthOverlayFinished, callback));
+      base::BindOnce(&OnHTTPAuthOverlayFinished, std::move(callback)));
   OverlayRequestQueue::FromWebState(source, OverlayModality::kWebContentArea)
       ->AddRequest(std::move(request));
 }
diff --git a/ios/chrome/browser/web/web_state_delegate_tab_helper_unittest.mm b/ios/chrome/browser/web/web_state_delegate_tab_helper_unittest.mm
index 89c6c71..41647e5 100644
--- a/ios/chrome/browser/web/web_state_delegate_tab_helper_unittest.mm
+++ b/ios/chrome/browser/web/web_state_delegate_tab_helper_unittest.mm
@@ -52,10 +52,10 @@
                                    password:@""
                                 persistence:NSURLCredentialPersistenceNone];
   web::WebStateDelegate::AuthCallback callback =
-      base::BindRepeating(^(NSString* user, NSString* password){
+      base::BindOnce(^(NSString* user, NSString* password){
       });
   delegate()->OnAuthRequired(web_state(), protection_space, credential,
-                             callback);
+                             std::move(callback));
 
   // Verify that an HTTP auth overlay request has been created for the WebState.
   OverlayRequestQueue* queue = OverlayRequestQueue::FromWebState(
diff --git a/ios/chrome/test/earl_grey/BUILD.gn b/ios/chrome/test/earl_grey/BUILD.gn
index 461e8ec..6bf0cd18 100644
--- a/ios/chrome/test/earl_grey/BUILD.gn
+++ b/ios/chrome/test/earl_grey/BUILD.gn
@@ -148,6 +148,7 @@
     "//ios/chrome/browser/ui/toolbar/public",
     "//ios/chrome/browser/ui/util",
     "//ios/chrome/browser/ui/util:eg_app_support+eg2",
+    "//ios/chrome/browser/ui/util:multiwindow_util",
     "//ios/chrome/browser/unified_consent",
     "//ios/chrome/browser/web:eg_app_support+eg2",
     "//ios/chrome/browser/web:tab_id_tab_helper",
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey.h b/ios/chrome/test/earl_grey/chrome_earl_grey.h
index fd63cc3..e54bf058 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey.h
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey.h
@@ -12,6 +12,7 @@
 #include "base/compiler_specific.h"
 #import "components/content_settings/core/common/content_settings.h"
 #include "components/sync/base/model_type.h"
+#import "ios/testing/earl_grey/app_launch_configuration.h"
 #import "ios/testing/earl_grey/base_eg_test_helper_impl.h"
 #include "third_party/metrics_proto/user_demographics.pb.h"
 #include "url/gurl.h"
@@ -132,10 +133,6 @@
 // GREYAssert is induced.
 - (void)waitForIncognitoTabCount:(NSUInteger)count;
 
-// Waits for there to be |count| number of browsers within a timeout,
-// or a GREYAssert is induced.
-- (void)waitForBrowserCount:(NSUInteger)count;
-
 // Loads |URL| as if it was opened from an external application.
 - (void)openURLFromExternalApp:(const GURL&)URL;
 
@@ -336,6 +333,26 @@
 // and tablet.
 - (void)showTabSwitcher;
 
+#pragma mark - Window utilities (EG2)
+
+// Returns the number of windows, including background and disconnected or
+// archived windows.
+- (NSUInteger)windowCount WARN_UNUSED_RESULT;
+
+// Returns the number of foreground (visible on screen) windows.
+- (NSUInteger)foregroundWindowCount WARN_UNUSED_RESULT;
+
+// Waits for there to be |count| number of browsers within a timeout,
+// or a GREYAssert is induced.
+- (void)waitForForegroundWindowCount:(NSUInteger)count;
+
+// Closes all but one window, including all non-foreground windows. Then kills
+// and relaunches app with launch args specified in |appConfig|. No-op if only
+// one window presents.
+// TODO(crbug.com/1143708): Remove the relaunch when EG2 slowness is fixed.
+- (void)closeAllExtraWindowsAndForceRelaunchWithAppConfig:
+    (AppLaunchConfiguration)appConfig;
+
 #pragma mark - SignIn Utilities (EG2)
 
 // Signs the user out, clears the known accounts entirely and checks whether the
@@ -513,6 +530,10 @@
 // Returns whether the native context menus feature is enabled or not.
 - (BOOL)isNativeContextMenusEnabled;
 
+// Returns whether the app is configured to, and running in an environment which
+// can, open multiple windows.
+- (BOOL)areMultipleWindowsSupported;
+
 #pragma mark - Popup Blocking
 
 // Gets the current value of the popup content setting preference for the
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey.mm b/ios/chrome/test/earl_grey/chrome_earl_grey.mm
index cfe6845..b2c9933 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey.mm
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey.mm
@@ -13,6 +13,8 @@
 #import "base/test/ios/wait_util.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h"
 #import "ios/chrome/test/earl_grey/chrome_matchers.h"
+#import "ios/testing/earl_grey/app_launch_configuration.h"
+#import "ios/testing/earl_grey/app_launch_manager.h"
 #import "ios/testing/earl_grey/earl_grey_test.h"
 #import "ios/testing/nserror_util.h"
 #include "ios/web/public/test/element_selector.h"
@@ -474,30 +476,6 @@
   EG_TEST_HELPER_ASSERT_TRUE(tabCountEqual, errorString);
 }
 
-- (void)waitForBrowserCount:(NSUInteger)count {
-  __block NSUInteger actualCount = [ChromeEarlGreyAppInterface browserCount];
-  NSString* conditionName = [NSString
-      stringWithFormat:@"Waiting for window count to become %" PRIuNS, count];
-
-  // Allow the UI to become idle, in case any tabs are being opened or closed.
-  GREYWaitForAppToIdle(@"App failed to idle");
-
-  GREYCondition* browserCountCheck = [GREYCondition
-      conditionWithName:conditionName
-                  block:^{
-                    actualCount = [ChromeEarlGreyAppInterface browserCount];
-                    return actualCount == count;
-                  }];
-  bool browserCountEqual =
-      [browserCountCheck waitWithTimeout:kWaitForUIElementTimeout];
-
-  NSString* errorString = [NSString
-      stringWithFormat:@"Failed waiting for window count to become %" PRIuNS
-                        "; actual count: %" PRIuNS,
-                       count, actualCount];
-  EG_TEST_HELPER_ASSERT_TRUE(browserCountEqual, errorString);
-}
-
 - (NSUInteger)indexOfActiveNormalTab {
   return [ChromeEarlGreyAppInterface indexOfActiveNormalTab];
 }
@@ -779,6 +757,64 @@
   EG_TEST_HELPER_ASSERT_TRUE(success, errorString);
 }
 
+#pragma mark - Window utilities (EG2)
+
+// Returns the number of windows, including background and disconnected or
+// archived windows.
+- (NSUInteger)windowCount WARN_UNUSED_RESULT {
+  return [ChromeEarlGreyAppInterface windowCount];
+}
+
+// Returns the number of foreground (visible on screen) windows.
+- (NSUInteger)foregroundWindowCount WARN_UNUSED_RESULT {
+  return [ChromeEarlGreyAppInterface foregroundWindowCount];
+}
+
+// Closes all but one window, including all non-foreground windows. Then kills
+// and relaunches app with launch args specified in |appConfig|. No-op if only
+// one window presents.
+// TODO(crbug.com/1143708): Remove the relaunch when EG2 slowness is fixed.
+- (void)closeAllExtraWindowsAndForceRelaunchWithAppConfig:
+    (AppLaunchConfiguration)appConfig {
+  if ([self windowCount] <= 1) {
+    return;
+  }
+  [ChromeEarlGreyAppInterface closeAllExtraWindows];
+  // Tab changes are initiated through |WebStateList|. Need to wait its
+  // obeservers to complete UI changes at app.
+  GREYWaitForAppToIdle(@"App failed to idle");
+
+  appConfig.relaunch_policy = ForceRelaunchByKilling;
+  [[AppLaunchManager sharedManager]
+      ensureAppLaunchedWithConfiguration:appConfig];
+}
+
+- (void)waitForForegroundWindowCount:(NSUInteger)count {
+  __block NSUInteger actualCount =
+      [ChromeEarlGreyAppInterface foregroundWindowCount];
+  NSString* conditionName = [NSString
+      stringWithFormat:@"Waiting for window count to become %" PRIuNS, count];
+
+  // Allow the UI to become idle, in case any tabs are being opened or closed.
+  GREYWaitForAppToIdle(@"App failed to idle");
+
+  GREYCondition* browserCountCheck = [GREYCondition
+      conditionWithName:conditionName
+                  block:^{
+                    actualCount =
+                        [ChromeEarlGreyAppInterface foregroundWindowCount];
+                    return actualCount == count;
+                  }];
+  bool browserCountEqual =
+      [browserCountCheck waitWithTimeout:kWaitForUIElementTimeout];
+
+  NSString* errorString = [NSString
+      stringWithFormat:@"Failed waiting for window count to become %" PRIuNS
+                        "; actual count: %" PRIuNS,
+                       count, actualCount];
+  EG_TEST_HELPER_ASSERT_TRUE(browserCountEqual, errorString);
+}
+
 #pragma mark - SignIn Utilities (EG2)
 
 - (void)signOutAndClearIdentities {
@@ -889,6 +925,10 @@
   return [ChromeEarlGreyAppInterface isNativeContextMenusEnabled];
 }
 
+- (BOOL)areMultipleWindowsSupported {
+  return [ChromeEarlGreyAppInterface areMultipleWindowsSupported];
+}
+
 #pragma mark - ScopedBlockPopupsPref
 
 - (ContentSetting)popupPrefValue {
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h
index a9c60b56..8bf2192 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h
@@ -162,6 +162,18 @@
 // Returns the index of active tab in normal mode.
 + (NSUInteger)indexOfActiveNormalTab;
 
+#pragma mark - Window utilities (EG2)
+
+// Returns the number of windows, including background and disconnected or
+// archived windows.
++ (NSUInteger)windowCount WARN_UNUSED_RESULT;
+
+// Returns the number of foreground (visible on screen) windows.
++ (NSUInteger)foregroundWindowCount WARN_UNUSED_RESULT;
+
+// Closes all but one window, including all non-foreground windows.
++ (void)closeAllExtraWindows;
+
 #pragma mark - WebState Utilities (EG2)
 
 // Attempts to tap the element with |element_id| within window.frames[0] of the
@@ -433,6 +445,10 @@
 // Returns whether the native context menus feature is enabled or not.
 + (BOOL)isNativeContextMenusEnabled;
 
+// Returns whether the app is configured to, and running in an environment which
+// can, open multiple windows.
++ (BOOL)areMultipleWindowsSupported;
+
 #pragma mark - Popup Blocking
 
 // Gets the current value of the popup content setting preference for the
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
index 2fd871e..dd97f64 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
@@ -30,6 +30,7 @@
 #import "ios/chrome/browser/ui/table_view/feature_flags.h"
 #import "ios/chrome/browser/ui/ui_feature_flags.h"
 #import "ios/chrome/browser/ui/util/menu_util.h"
+#import "ios/chrome/browser/ui/util/multi_window_support.h"
 #import "ios/chrome/browser/ui/util/named_guide.h"
 #import "ios/chrome/browser/unified_consent/unified_consent_service_factory.h"
 #import "ios/chrome/browser/web/tab_id_tab_helper.h"
@@ -298,6 +299,68 @@
   return chrome_test_util::GetIndexOfActiveNormalTab();
 }
 
+#pragma mark - Window utilities (EG2)
+
++ (NSUInteger)windowCount WARN_UNUSED_RESULT {
+  // If the scene API is in use, return the count of open sessions.
+  if (@available(iOS 13, *)) {
+    return UIApplication.sharedApplication.openSessions.count;
+  }
+
+  // Otherwise, there's always exectly one window;
+  return 1;
+}
+
++ (NSUInteger)foregroundWindowCount WARN_UNUSED_RESULT {
+  // If the scene API is in use, look at all the connected scenes and count
+  // those in the foreground.
+  if (@available(iOS 13, *)) {
+    NSUInteger count = 0;
+    for (UIScene* scene in UIApplication.sharedApplication.connectedScenes) {
+      if (scene.activationState == UISceneActivationStateForegroundActive ||
+          scene.activationState == UISceneActivationStateForegroundInactive) {
+        count++;
+      }
+    }
+    return count;
+  }
+
+  // Otherwise, there's always exectly one window;
+  return 1;
+}
+
++ (void)closeAllExtraWindows {
+  if (!IsMultipleScenesSupported())
+    return;
+
+  if (@available(iOS 13, *)) {
+    NSSet<UISceneSession*>* sessions =
+        UIApplication.sharedApplication.openSessions;
+    if (sessions.count <= 1)
+      return;
+    BOOL foundForegroundScene = NO;
+    for (UISceneSession* session in sessions) {
+      UIScene* scene = session.scene;
+      if (!foundForegroundScene && scene &&
+          (scene.activationState == UISceneActivationStateForegroundActive ||
+           scene.activationState == UISceneActivationStateForegroundInactive)) {
+        foundForegroundScene = YES;
+        // Leave the first foreground scene connected, so there's one open
+        // window left.
+        continue;
+      }
+      // If this isn't the first foreground scene, destroy it.
+      UIWindowSceneDestructionRequestOptions* options =
+          [[UIWindowSceneDestructionRequestOptions alloc] init];
+      options.windowDismissalAnimation =
+          UIWindowSceneDismissalAnimationStandard;
+      [UIApplication.sharedApplication requestSceneSessionDestruction:session
+                                                              options:options
+                                                         errorHandler:nil];
+    }
+  }
+}
+
 #pragma mark - WebState Utilities (EG2)
 
 + (NSError*)tapWebStateElementInIFrameWithID:(NSString*)elementID {
@@ -769,6 +832,10 @@
   return IsNativeContextMenuEnabled();
 }
 
++ (BOOL)areMultipleWindowsSupported {
+  return IsMultipleScenesSupported();
+}
+
 #pragma mark - ScopedBlockPopupsPref
 
 + (ContentSetting)popupPrefValue {
diff --git a/ios/chrome/test/earl_grey/chrome_matchers.h b/ios/chrome/test/earl_grey/chrome_matchers.h
index 0300c2cd..15d6afa7 100644
--- a/ios/chrome/test/earl_grey/chrome_matchers.h
+++ b/ios/chrome/test/earl_grey/chrome_matchers.h
@@ -323,6 +323,9 @@
 // Returns matcher for the payment request search bar.
 id<GREYMatcher> PaymentRequestPickerSearchBar();
 
+// Returns matcher for the New Window button on the Tools menu.
+id<GREYMatcher> OpenNewWindowMenuButton();
+
 // Returns matcher for the reading list on the Tools menu.
 id<GREYMatcher> ReadingListMenuButton();
 
diff --git a/ios/chrome/test/earl_grey/chrome_matchers.mm b/ios/chrome/test/earl_grey/chrome_matchers.mm
index c44a2422..c2af9a6 100644
--- a/ios/chrome/test/earl_grey/chrome_matchers.mm
+++ b/ios/chrome/test/earl_grey/chrome_matchers.mm
@@ -409,6 +409,10 @@
   return [ChromeMatchersAppInterface paymentRequestPickerSearchBar];
 }
 
+id<GREYMatcher> OpenNewWindowMenuButton() {
+  return [ChromeMatchersAppInterface openNewWindowMenuButton];
+}
+
 id<GREYMatcher> ReadingListMenuButton() {
   return [ChromeMatchersAppInterface readingListMenuButton];
 }
diff --git a/ios/chrome/test/earl_grey/chrome_matchers_app_interface.h b/ios/chrome/test/earl_grey/chrome_matchers_app_interface.h
index 6658ba0b..6d8f24f 100644
--- a/ios/chrome/test/earl_grey/chrome_matchers_app_interface.h
+++ b/ios/chrome/test/earl_grey/chrome_matchers_app_interface.h
@@ -323,6 +323,9 @@
 // Returns matcher for the payment request search bar.
 + (id<GREYMatcher>)paymentRequestPickerSearchBar;
 
+// Returns matcher for the New Window button on the Tools menu.
++ (id<GREYMatcher>)openNewWindowMenuButton;
+
 // Returns matcher for the reading list button on the Tools menu.
 + (id<GREYMatcher>)readingListMenuButton;
 
diff --git a/ios/chrome/test/earl_grey/chrome_matchers_app_interface.mm b/ios/chrome/test/earl_grey/chrome_matchers_app_interface.mm
index 4a82bbac..b10c84b 100644
--- a/ios/chrome/test/earl_grey/chrome_matchers_app_interface.mm
+++ b/ios/chrome/test/earl_grey/chrome_matchers_app_interface.mm
@@ -676,6 +676,10 @@
   return nil;
 }
 
++ (id<GREYMatcher>)openNewWindowMenuButton {
+  return grey_accessibilityID(kToolsMenuNewWindowId);
+}
+
 + (id<GREYMatcher>)readingListMenuButton {
   return grey_accessibilityID(kToolsMenuReadingListId);
 }
diff --git a/ios/web/download/download_task_impl.h b/ios/web/download/download_task_impl.h
index 88d78a7..9a74943 100644
--- a/ios/web/download/download_task_impl.h
+++ b/ios/web/download/download_task_impl.h
@@ -90,13 +90,13 @@
 
   // Asynchronously returns cookies for WebState associated with this task.
   // Must be called on UI thread. The callback will be invoked on the UI thread.
-  void GetCookies(base::Callback<void(NSArray<NSHTTPCookie*>*)> callback);
+  void GetCookies(base::OnceCallback<void(NSArray<NSHTTPCookie*>*)> callback);
 
   // Asynchronously returns cookies for |context_getter|. Must
   // be called on IO thread. The callback will be invoked on the UI thread.
   static void GetCookiesFromContextGetter(
       scoped_refptr<net::URLRequestContextGetter> context_getter,
-      base::Callback<void(NSArray<NSHTTPCookie*>*)> callback);
+      base::OnceCallback<void(NSArray<NSHTTPCookie*>*)> callback);
 
   // Starts the download with given cookies.
   void StartWithCookies(NSArray<NSHTTPCookie*>* cookies);
diff --git a/ios/web/download/download_task_impl.mm b/ios/web/download/download_task_impl.mm
index ae004cf..d32568a 100644
--- a/ios/web/download/download_task_impl.mm
+++ b/ios/web/download/download_task_impl.mm
@@ -411,7 +411,7 @@
 }
 
 void DownloadTaskImpl::GetCookies(
-    base::Callback<void(NSArray<NSHTTPCookie*>*)> callback) {
+    base::OnceCallback<void(NSArray<NSHTTPCookie*>*)> callback) {
   DCHECK_CURRENTLY_ON(WebThread::UI);
   scoped_refptr<net::URLRequestContextGetter> context_getter(
       web_state_->GetBrowserState()->GetRequestContext());
@@ -419,23 +419,23 @@
   // net::URLRequestContextGetter must be used in the IO thread.
   base::PostTask(FROM_HERE, {WebThread::IO},
                  base::BindOnce(&DownloadTaskImpl::GetCookiesFromContextGetter,
-                                context_getter, callback));
+                                context_getter, std::move(callback)));
 }
 
 void DownloadTaskImpl::GetCookiesFromContextGetter(
     scoped_refptr<net::URLRequestContextGetter> context_getter,
-    base::Callback<void(NSArray<NSHTTPCookie*>*)> callback) {
+    base::OnceCallback<void(NSArray<NSHTTPCookie*>*)> callback) {
   DCHECK_CURRENTLY_ON(WebThread::IO);
   context_getter->GetURLRequestContext()->cookie_store()->GetAllCookiesAsync(
       base::BindOnce(
-          [](base::Callback<void(NSArray<NSHTTPCookie*>*)> callback,
+          [](base::OnceCallback<void(NSArray<NSHTTPCookie*>*)> callback,
              const net::CookieList& cookie_list) {
             NSArray<NSHTTPCookie*>* cookies =
                 SystemCookiesFromCanonicalCookieList(cookie_list);
             base::PostTask(FROM_HERE, {WebThread::UI},
-                           base::BindOnce(callback, cookies));
+                           base::BindOnce(std::move(callback), cookies));
           },
-          callback));
+          std::move(callback)));
 }
 
 void DownloadTaskImpl::StartWithCookies(NSArray<NSHTTPCookie*>* cookies) {
diff --git a/ios/web/public/test/fakes/test_web_client.h b/ios/web/public/test/fakes/test_web_client.h
index 012c7f3e..50bf14c5 100644
--- a/ios/web/public/test/fakes/test_web_client.h
+++ b/ios/web/public/test/fakes/test_web_client.h
@@ -53,7 +53,7 @@
                              const GURL&,
                              bool overridable,
                              int64_t navigation_id,
-                             const base::Callback<void(bool)>&) override;
+                             base::OnceCallback<void(bool)>) override;
   void PrepareErrorPage(WebState* web_state,
                         const GURL& url,
                         NSError* error,
diff --git a/ios/web/public/test/fakes/test_web_client.mm b/ios/web/public/test/fakes/test_web_client.mm
index 1c2c434..0e045f0 100644
--- a/ios/web/public/test/fakes/test_web_client.mm
+++ b/ios/web/public/test/fakes/test_web_client.mm
@@ -87,15 +87,16 @@
     const GURL& request_url,
     bool overridable,
     int64_t navigation_id,
-    const base::Callback<void(bool)>& callback) {
+    base::OnceCallback<void(bool)> callback) {
   last_cert_error_code_ = cert_error;
   last_cert_error_ssl_info_ = ssl_info;
   last_cert_error_request_url_ = request_url;
   last_cert_error_overridable_ = overridable;
 
   // Embedder should consult the user, so reply is asynchronous.
-  base::PostTask(FROM_HERE, {WebThread::UI},
-                 base::BindOnce(callback, allow_certificate_errors_));
+  base::PostTask(
+      FROM_HERE, {WebThread::UI},
+      base::BindOnce(std::move(callback), allow_certificate_errors_));
 }
 
 void TestWebClient::SetAllowCertificateErrors(bool flag) {
diff --git a/ios/web/public/test/fakes/test_web_state.h b/ios/web/public/test/fakes/test_web_state.h
index d60a582..4c882cf5 100644
--- a/ios/web/public/test/fakes/test_web_state.h
+++ b/ios/web/public/test/fakes/test_web_state.h
@@ -164,7 +164,7 @@
   UIView* view_;
   CRWWebViewProxyType web_view_proxy_;
   NSData* last_loaded_data_;
-  base::CallbackList<ScriptCommandCallbackSignature> callback_list_;
+  base::RepeatingCallbackList<ScriptCommandCallbackSignature> callback_list_;
 
   // A list of observers notified when page state changes. Weak references.
   base::ObserverList<WebStateObserver, true>::Unchecked observers_;
diff --git a/ios/web/public/test/fakes/test_web_state_delegate.h b/ios/web/public/test/fakes/test_web_state_delegate.h
index 118b38d1..18fddbf 100644
--- a/ios/web/public/test/fakes/test_web_state_delegate.h
+++ b/ios/web/public/test/fakes/test_web_state_delegate.h
@@ -48,7 +48,7 @@
 // Encapsulates parameters passed to OnAuthRequired.
 struct TestAuthenticationRequest {
   TestAuthenticationRequest();
-  TestAuthenticationRequest(const TestAuthenticationRequest&);
+  TestAuthenticationRequest(TestAuthenticationRequest&&);
   ~TestAuthenticationRequest();
   WebState* web_state = nullptr;
   NSURLProtectionSpace* protection_space;
@@ -88,7 +88,7 @@
   void OnAuthRequired(WebState* source,
                       NSURLProtectionSpace* protection_space,
                       NSURLCredential* proposed_credential,
-                      const AuthCallback& callback) override;
+                      AuthCallback callback) override;
   bool ShouldPreviewLink(WebState* source, const GURL& link_url) override;
   UIViewController* GetPreviewingViewController(WebState* source,
                                                 const GURL& link_url) override;
diff --git a/ios/web/public/test/fakes/test_web_state_delegate.mm b/ios/web/public/test/fakes/test_web_state_delegate.mm
index acbce70..dbc5cf46 100644
--- a/ios/web/public/test/fakes/test_web_state_delegate.mm
+++ b/ios/web/public/test/fakes/test_web_state_delegate.mm
@@ -31,7 +31,7 @@
 TestAuthenticationRequest::~TestAuthenticationRequest() = default;
 
 TestAuthenticationRequest::TestAuthenticationRequest(
-    const TestAuthenticationRequest&) = default;
+    TestAuthenticationRequest&&) = default;
 
 TestWebStateDelegate::TestWebStateDelegate() {}
 
@@ -113,12 +113,12 @@
     WebState* source,
     NSURLProtectionSpace* protection_space,
     NSURLCredential* credential,
-    const AuthCallback& callback) {
+    AuthCallback callback) {
   last_authentication_request_ = std::make_unique<TestAuthenticationRequest>();
   last_authentication_request_->web_state = source;
   last_authentication_request_->protection_space = protection_space;
   last_authentication_request_->credential = credential;
-  last_authentication_request_->auth_callback = callback;
+  last_authentication_request_->auth_callback = std::move(callback);
 }
 
 bool TestWebStateDelegate::ShouldPreviewLink(WebState* source,
diff --git a/ios/web/public/web_client.h b/ios/web/public/web_client.h
index 6042364..d9e79aa 100644
--- a/ios/web/public/web_client.h
+++ b/ios/web/public/web_client.h
@@ -154,14 +154,13 @@
   // indicates which navigation triggered the certificate error. The embedder
   // can call the |callback| asynchronously (an argument of true means that
   // |cert_error| should be ignored and web// should load the page).
-  virtual void AllowCertificateError(
-      WebState* web_state,
-      int cert_error,
-      const net::SSLInfo& ssl_info,
-      const GURL& request_url,
-      bool overridable,
-      int64_t navigation_id,
-      const base::Callback<void(bool)>& callback);
+  virtual void AllowCertificateError(WebState* web_state,
+                                     int cert_error,
+                                     const net::SSLInfo& ssl_info,
+                                     const GURL& request_url,
+                                     bool overridable,
+                                     int64_t navigation_id,
+                                     base::OnceCallback<void(bool)> callback);
 
   // Allows the embedder to specify legacy TLS enforcement on a per-host basis,
   // for example to allow users to bypass interstitial warnings on affected
diff --git a/ios/web/public/web_state.h b/ios/web/public/web_state.h
index e4b1a844..39f009f 100644
--- a/ios/web/public/web_state.h
+++ b/ios/web/public/web_state.h
@@ -323,7 +323,7 @@
   using ScriptCommandCallback =
       base::RepeatingCallback<ScriptCommandCallbackSignature>;
   using ScriptCommandSubscription =
-      base::CallbackList<ScriptCommandCallbackSignature>::Subscription;
+      base::RepeatingCallbackList<ScriptCommandCallbackSignature>::Subscription;
   // Registers |callback| for JS message whose 'command' matches
   // |command_prefix|. The returned ScriptCommandSubscription should be stored
   // by the caller. When the description object is destroyed, it will unregister
diff --git a/ios/web/public/web_state_delegate.h b/ios/web/public/web_state_delegate.h
index 2d46e03..289b3cad 100644
--- a/ios/web/public/web_state_delegate.h
+++ b/ios/web/public/web_state_delegate.h
@@ -68,12 +68,12 @@
   // |protection_space|, and is unable to respond using cached credentials.
   // Clients must call |callback| even if they want to cancel authentication
   // (in which case |username| or |password| should be nil).
-  typedef base::Callback<void(NSString* username, NSString* password)>
+  typedef base::OnceCallback<void(NSString* username, NSString* password)>
       AuthCallback;
   virtual void OnAuthRequired(WebState* source,
                               NSURLProtectionSpace* protection_space,
                               NSURLCredential* proposed_credential,
-                              const AuthCallback& callback) = 0;
+                              AuthCallback callback) = 0;
 
   // Determines whether the given link with |link_url| should show a preview on
   // force touch.
diff --git a/ios/web/public/web_state_delegate_bridge.h b/ios/web/public/web_state_delegate_bridge.h
index 071c187..08fb280a 100644
--- a/ios/web/public/web_state_delegate_bridge.h
+++ b/ios/web/public/web_state_delegate_bridge.h
@@ -136,7 +136,7 @@
   void OnAuthRequired(WebState* source,
                       NSURLProtectionSpace* protection_space,
                       NSURLCredential* proposed_credential,
-                      const AuthCallback& callback) override;
+                      AuthCallback callback) override;
   bool ShouldPreviewLink(WebState* web_state, const GURL& link_url) override;
   UIViewController* GetPreviewingViewController(WebState* source,
                                                 const GURL& link_url) override;
diff --git a/ios/web/public/webui/url_data_source_ios.h b/ios/web/public/webui/url_data_source_ios.h
index 28d70ed44..4387df2 100644
--- a/ios/web/public/webui/url_data_source_ios.h
+++ b/ios/web/public/webui/url_data_source_ios.h
@@ -42,7 +42,7 @@
 
   // Used by StartDataRequest so that the child class can return the data when
   // it's available.
-  typedef base::Callback<void(scoped_refptr<base::RefCountedMemory>)>
+  typedef base::OnceCallback<void(scoped_refptr<base::RefCountedMemory>)>
       GotDataCallback;
 
   // Called by URLDataSourceIOS to request data at |path|. The string parameter
diff --git a/ios/web/shell/shell_web_client.h b/ios/web/shell/shell_web_client.h
index 4bccb93..4a21d43 100644
--- a/ios/web/shell/shell_web_client.h
+++ b/ios/web/shell/shell_web_client.h
@@ -30,14 +30,13 @@
   void BindInterfaceReceiverFromMainFrame(
       WebState* web_state,
       mojo::GenericPendingReceiver receiver) override;
-  void AllowCertificateError(
-      WebState* web_state,
-      int cert_error,
-      const net::SSLInfo& ssl_info,
-      const GURL& request_url,
-      bool overridable,
-      int64_t navigation_id,
-      const base::Callback<void(bool)>& callback) override;
+  void AllowCertificateError(WebState* web_state,
+                             int cert_error,
+                             const net::SSLInfo& ssl_info,
+                             const GURL& request_url,
+                             bool overridable,
+                             int64_t navigation_id,
+                             base::OnceCallback<void(bool)> callback) override;
 
   ShellBrowserState* browser_state() const;
 
diff --git a/ios/web/shell/shell_web_client.mm b/ios/web/shell/shell_web_client.mm
index ee56686..02f813d1 100644
--- a/ios/web/shell/shell_web_client.mm
+++ b/ios/web/shell/shell_web_client.mm
@@ -90,23 +90,27 @@
     const GURL&,
     bool overridable,
     int64_t /*navigation_id*/,
-    const base::Callback<void(bool)>& callback) {
-  base::Callback<void(bool)> block_callback(callback);
+    base::OnceCallback<void(bool)> callback) {
   UIAlertController* alert = [UIAlertController
       alertControllerWithTitle:@"Your connection is not private"
                        message:nil
                 preferredStyle:UIAlertControllerStyleActionSheet];
+
+  __block base::OnceCallback<void(bool)> local_callback = std::move(callback);
+  void (^callback_block)(bool result) = ^(bool result) {
+    std::move(local_callback).Run(result);
+  };
   [alert addAction:[UIAlertAction actionWithTitle:@"Go Back"
                                             style:UIAlertActionStyleCancel
                                           handler:^(UIAlertAction*) {
-                                            block_callback.Run(false);
+                                            callback_block(false);
                                           }]];
 
   if (overridable) {
     [alert addAction:[UIAlertAction actionWithTitle:@"Continue"
                                               style:UIAlertActionStyleDefault
                                             handler:^(UIAlertAction*) {
-                                              block_callback.Run(true);
+                                              callback_block(true);
                                             }]];
   }
   [[UIApplication sharedApplication].keyWindow.rootViewController
diff --git a/ios/web/web_client.mm b/ios/web/web_client.mm
index f7b923d0..e405ab5 100644
--- a/ios/web/web_client.mm
+++ b/ios/web/web_client.mm
@@ -85,15 +85,14 @@
   return @"";
 }
 
-void WebClient::AllowCertificateError(
-    WebState* web_state,
-    int cert_error,
-    const net::SSLInfo& ssl_info,
-    const GURL& request_url,
-    bool overridable,
-    int64_t navigation_id,
-    const base::Callback<void(bool)>& callback) {
-  callback.Run(false);
+void WebClient::AllowCertificateError(WebState* web_state,
+                                      int cert_error,
+                                      const net::SSLInfo& ssl_info,
+                                      const GURL& request_url,
+                                      bool overridable,
+                                      int64_t navigation_id,
+                                      base::OnceCallback<void(bool)> callback) {
+  std::move(callback).Run(false);
 }
 
 bool WebClient::IsLegacyTLSAllowedForHost(WebState* web_state,
diff --git a/ios/web/web_state/http_auth_inttest.mm b/ios/web/web_state/http_auth_inttest.mm
index 3f15a84..e12f4e80 100644
--- a/ios/web/web_state/http_auth_inttest.mm
+++ b/ios/web/web_state/http_auth_inttest.mm
@@ -71,7 +71,7 @@
   ASSERT_TRUE(web_state()->IsLoading());
   auth_request = delegate_.last_authentication_request();
   ASSERT_TRUE(auth_request);
-  auth_request->auth_callback.Run(@"me", @"goodpass");
+  std::move(auth_request->auth_callback).Run(@"me", @"goodpass");
   ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
     return web_state()->GetTitle() == base::ASCIIToUTF16("me/goodpass");
   }));
@@ -86,7 +86,7 @@
 
   // Make sure that incorrect credentials request authentication again.
   auto* auth_request = delegate_.last_authentication_request();
-  auth_request->auth_callback.Run(@"me", @"badpass");
+  std::move(auth_request->auth_callback).Run(@"me", @"badpass");
   ASSERT_TRUE(WaitForOnAuthRequiredCallback());
 
   // Verify that callback receives correct WebState.
@@ -107,7 +107,8 @@
               protection_space.authenticationMethod);
 
   // Cancel authentication and make sure that authentication is denied.
-  auth_request->auth_callback.Run(/*username=*/nil, /*password=*/nil);
+  std::move(auth_request->auth_callback)
+      .Run(/*username=*/nil, /*password=*/nil);
   ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
     return web_state()->GetTitle() ==
            base::ASCIIToUTF16("Denied: Missing Authorization Header");
diff --git a/ios/web/web_state/web_state_delegate.mm b/ios/web/web_state/web_state_delegate.mm
index bbedb35..4b14a1a 100644
--- a/ios/web/web_state/web_state_delegate.mm
+++ b/ios/web/web_state/web_state_delegate.mm
@@ -51,8 +51,8 @@
 void WebStateDelegate::OnAuthRequired(WebState* source,
                                       NSURLProtectionSpace* protection_space,
                                       NSURLCredential* proposed_credential,
-                                      const AuthCallback& callback) {
-  callback.Run(nil, nil);
+                                      AuthCallback callback) {
+  std::move(callback).Run(nil, nil);
 }
 
 bool WebStateDelegate::ShouldPreviewLink(WebState* source,
diff --git a/ios/web/web_state/web_state_delegate_bridge.mm b/ios/web/web_state/web_state_delegate_bridge.mm
index e9ca245..e81b638 100644
--- a/ios/web/web_state/web_state_delegate_bridge.mm
+++ b/ios/web/web_state/web_state_delegate_bridge.mm
@@ -82,22 +82,22 @@
     WebState* source,
     NSURLProtectionSpace* protection_space,
     NSURLCredential* proposed_credential,
-    const AuthCallback& callback) {
-  AuthCallback local_callback(callback);
+    AuthCallback callback) {
   if ([delegate_
           respondsToSelector:@selector(webState:
                                  didRequestHTTPAuthForProtectionSpace:
                                                    proposedCredential:
                                                     completionHandler:)]) {
+    __block AuthCallback local_callback = std::move(callback);
     [delegate_ webState:source
         didRequestHTTPAuthForProtectionSpace:protection_space
                           proposedCredential:proposed_credential
                            completionHandler:^(NSString* username,
                                                NSString* password) {
-                             local_callback.Run(username, password);
+                             std::move(local_callback).Run(username, password);
                            }];
   } else {
-    local_callback.Run(nil, nil);
+    std::move(callback).Run(nil, nil);
   }
 }
 
diff --git a/ios/web/web_state/web_state_delegate_bridge_unittest.mm b/ios/web/web_state/web_state_delegate_bridge_unittest.mm
index 89f1cb19..9cf0225 100644
--- a/ios/web/web_state/web_state_delegate_bridge_unittest.mm
+++ b/ios/web/web_state/web_state_delegate_bridge_unittest.mm
@@ -134,8 +134,8 @@
 TEST_F(WebStateDelegateBridgeTest, ShowRepostFormWarningDialog) {
   EXPECT_FALSE([delegate_ repostFormWarningRequested]);
   EXPECT_FALSE([delegate_ webState]);
-  base::Callback<void(bool)> callback;
-  bridge_->ShowRepostFormWarningDialog(&test_web_state_, callback);
+  base::OnceCallback<void(bool)> callback;
+  bridge_->ShowRepostFormWarningDialog(&test_web_state_, std::move(callback));
   EXPECT_TRUE([delegate_ repostFormWarningRequested]);
   EXPECT_EQ(&test_web_state_, [delegate_ webState]);
 }
@@ -165,9 +165,9 @@
   EXPECT_FALSE([delegate_ webState]);
   NSURLProtectionSpace* protection_space = [[NSURLProtectionSpace alloc] init];
   NSURLCredential* credential = [[NSURLCredential alloc] init];
-  WebStateDelegate::AuthCallback callback;
+  WebStateDelegate::AuthCallback callback = base::DoNothing();
   bridge_->OnAuthRequired(&test_web_state_, protection_space, credential,
-                          callback);
+                          std::move(callback));
   EXPECT_TRUE([delegate_ authenticationRequested]);
   EXPECT_EQ(&test_web_state_, [delegate_ webState]);
 }
diff --git a/ios/web/web_state/web_state_impl.h b/ios/web/web_state/web_state_impl.h
index 594da72..8b24eb72 100644
--- a/ios/web/web_state/web_state_impl.h
+++ b/ios/web/web_state/web_state_impl.h
@@ -277,7 +277,7 @@
   // and is unable to respond using cached credentials.
   void OnAuthRequired(NSURLProtectionSpace* protection_space,
                       NSURLCredential* proposed_credential,
-                      const WebStateDelegate::AuthCallback& callback);
+                      WebStateDelegate::AuthCallback callback);
 
   // Cancels all dialogs associated with this web_state.
   void CancelDialogs();
@@ -380,7 +380,8 @@
   base::string16 empty_string16_;
 
   // Callbacks associated to command prefixes.
-  std::map<std::string, base::CallbackList<ScriptCommandCallbackSignature>>
+  std::map<std::string,
+           base::RepeatingCallbackList<ScriptCommandCallbackSignature>>
       script_command_callbacks_;
 
   // Whether this WebState has an opener.  See
diff --git a/ios/web/web_state/web_state_impl.mm b/ios/web/web_state/web_state_impl.mm
index 3067be1..b395cfe 100644
--- a/ios/web/web_state/web_state_impl.mm
+++ b/ios/web/web_state/web_state_impl.mm
@@ -447,15 +447,14 @@
   user_agent_type_ = user_agent;
 }
 
-void WebStateImpl::OnAuthRequired(
-    NSURLProtectionSpace* protection_space,
-    NSURLCredential* proposed_credential,
-    const WebStateDelegate::AuthCallback& callback) {
+void WebStateImpl::OnAuthRequired(NSURLProtectionSpace* protection_space,
+                                  NSURLCredential* proposed_credential,
+                                  WebStateDelegate::AuthCallback callback) {
   if (delegate_) {
     delegate_->OnAuthRequired(this, protection_space, proposed_credential,
-                              callback);
+                              std::move(callback));
   } else {
-    callback.Run(nil, nil);
+    std::move(callback).Run(nil, nil);
   }
 }
 
diff --git a/ios/web/web_state/web_state_impl_unittest.mm b/ios/web/web_state/web_state_impl_unittest.mm
index dd29fcc..e2dcc03 100644
--- a/ios/web/web_state/web_state_impl_unittest.mm
+++ b/ios/web/web_state/web_state_impl_unittest.mm
@@ -497,8 +497,8 @@
 
   // Test that ShowRepostFormWarningDialog() is called.
   EXPECT_FALSE(delegate.last_repost_form_request());
-  base::Callback<void(bool)> repost_callback;
-  web_state_->ShowRepostFormWarningDialog(repost_callback);
+  base::OnceCallback<void(bool)> repost_callback;
+  web_state_->ShowRepostFormWarningDialog(std::move(repost_callback));
   ASSERT_TRUE(delegate.last_repost_form_request());
   EXPECT_EQ(delegate.last_repost_form_request()->web_state, web_state_.get());
 
@@ -528,7 +528,7 @@
   NSURLProtectionSpace* protection_space = [[NSURLProtectionSpace alloc] init];
   NSURLCredential* credential = [[NSURLCredential alloc] init];
   WebStateDelegate::AuthCallback callback;
-  web_state_->OnAuthRequired(protection_space, credential, callback);
+  web_state_->OnAuthRequired(protection_space, credential, std::move(callback));
   ASSERT_TRUE(delegate.last_authentication_request());
   EXPECT_EQ(delegate.last_authentication_request()->web_state,
             web_state_.get());
diff --git a/ios/web/web_view/wk_web_view_util_unittest.mm b/ios/web/web_view/wk_web_view_util_unittest.mm
index 9f8ddfa..32770716 100644
--- a/ios/web/web_view/wk_web_view_util_unittest.mm
+++ b/ios/web/web_view/wk_web_view_util_unittest.mm
@@ -81,7 +81,7 @@
     __block bool callback_called = false;
     __block NSData* callback_data = nil;
 
-    CreateFullPagePdf(web_view_mock, base::Bind(^(NSData* data) {
+    CreateFullPagePdf(web_view_mock, base::BindOnce(^(NSData* data) {
                         callback_called = true;
                         callback_data = [data copy];
                       }));
@@ -119,7 +119,7 @@
     __block bool callback_called = false;
     __block NSData* callback_data = nil;
 
-    CreateFullPagePdf(web_view_mock, base::Bind(^(NSData* data) {
+    CreateFullPagePdf(web_view_mock, base::BindOnce(^(NSData* data) {
                         callback_called = true;
                         callback_data = [data copy];
                       }));
@@ -146,7 +146,7 @@
   __block bool callback_called = false;
   __block NSData* callback_data = nil;
 
-  CreateFullPagePdf(web_view, base::Bind(^(NSData* data) {
+  CreateFullPagePdf(web_view, base::BindOnce(^(NSData* data) {
                       callback_called = true;
                       callback_data = [data copy];
                     }));
@@ -165,7 +165,7 @@
   __block bool callback_called = false;
   __block NSData* callback_data = nil;
 
-  CreateFullPagePdf(nil, base::Bind(^(NSData* data) {
+  CreateFullPagePdf(nil, base::BindOnce(^(NSData* data) {
                       callback_called = true;
                       callback_data = [data copy];
                     }));
diff --git a/ios/web/webui/web_ui_ios_impl.h b/ios/web/webui/web_ui_ios_impl.h
index e8f79c33..48d8bacc 100644
--- a/ios/web/webui/web_ui_ios_impl.h
+++ b/ios/web/webui/web_ui_ios_impl.h
@@ -34,7 +34,7 @@
   void SetController(std::unique_ptr<WebUIIOSController> controller) override;
   void AddMessageHandler(
       std::unique_ptr<WebUIIOSMessageHandler> handler) override;
-  typedef base::Callback<void(const base::ListValue*)> MessageCallback;
+  typedef base::RepeatingCallback<void(const base::ListValue*)> MessageCallback;
   void RegisterMessageCallback(const std::string& message,
                                const MessageCallback& callback) override;
   void ProcessWebUIIOSMessage(const GURL& source_url,
diff --git a/ios/web_view/internal/autofill/web_view_autofill_client_ios.mm b/ios/web_view/internal/autofill/web_view_autofill_client_ios.mm
index d583bba..9795180 100644
--- a/ios/web_view/internal/autofill/web_view_autofill_client_ios.mm
+++ b/ios/web_view/internal/autofill/web_view_autofill_client_ios.mm
@@ -58,7 +58,7 @@
       LogManager::Create(
           autofill::WebViewAutofillLogRouterFactory::GetForBrowserState(
               browser_state),
-          base::Closure()));
+          base::RepeatingClosure()));
 }
 
 WebViewAutofillClientIOS::WebViewAutofillClientIOS(
diff --git a/ios/web_view/internal/web_view_web_client.h b/ios/web_view/internal/web_view_web_client.h
index 4178907..07d5f037 100644
--- a/ios/web_view/internal/web_view_web_client.h
+++ b/ios/web_view/internal/web_view_web_client.h
@@ -32,14 +32,13 @@
   NSString* GetDocumentStartScriptForMainFrame(
       web::BrowserState* browser_state) const override;
   base::string16 GetPluginNotSupportedText() const override;
-  void AllowCertificateError(
-      web::WebState* web_state,
-      int cert_error,
-      const net::SSLInfo& ssl_info,
-      const GURL& request_url,
-      bool overridable,
-      int64_t navigation_id,
-      const base::RepeatingCallback<void(bool)>& callback) override;
+  void AllowCertificateError(web::WebState* web_state,
+                             int cert_error,
+                             const net::SSLInfo& ssl_info,
+                             const GURL& request_url,
+                             bool overridable,
+                             int64_t navigation_id,
+                             base::OnceCallback<void(bool)> callback) override;
   bool EnableLongPressAndForceTouchHandling() const override;
 
  private:
diff --git a/ios/web_view/internal/web_view_web_client.mm b/ios/web_view/internal/web_view_web_client.mm
index a53b2fb..c1787b2d 100644
--- a/ios/web_view/internal/web_view_web_client.mm
+++ b/ios/web_view/internal/web_view_web_client.mm
@@ -118,9 +118,8 @@
     const GURL& request_url,
     bool overridable,
     int64_t navigation_id,
-    const base::RepeatingCallback<void(bool)>& callback) {
+    base::OnceCallback<void(bool)> callback) {
   CWVWebView* web_view = [CWVWebView webViewForWebState:web_state];
-  base::RepeatingCallback<void(bool)> callback_copy = callback;
 
   SEL selector = @selector
       (webView:didFailNavigationWithSSLError:overridable:decisionHandler:);
@@ -140,15 +139,16 @@
                           CWVCertStatusKey : @(cert_status),
                         }];
 
+    __block base::OnceCallback<void(bool)> local_callback = std::move(callback);
     void (^decisionHandler)(CWVSSLErrorDecision) =
         ^(CWVSSLErrorDecision decision) {
           switch (decision) {
             case CWVSSLErrorDecisionOverrideErrorAndReload: {
-              callback_copy.Run(true);
+              std::move(local_callback).Run(true);
               break;
             }
             case CWVSSLErrorDecisionDoNothing: {
-              callback_copy.Run(false);
+              std::move(local_callback).Run(false);
               break;
             }
           }
@@ -159,7 +159,7 @@
                              overridable:overridable
                          decisionHandler:decisionHandler];
   } else {
-    callback_copy.Run(false);
+    std::move(callback).Run(false);
   }
 }
 
diff --git a/media/base/BUILD.gn b/media/base/BUILD.gn
index 4999dd4..f3ec9d9 100644
--- a/media/base/BUILD.gn
+++ b/media/base/BUILD.gn
@@ -230,6 +230,8 @@
     "multi_channel_resampler.h",
     "null_video_sink.cc",
     "null_video_sink.h",
+    "offloading_video_encoder.cc",
+    "offloading_video_encoder.h",
     "output_device_info.cc",
     "output_device_info.h",
     "overlay_info.cc",
@@ -576,6 +578,7 @@
     "moving_average_unittest.cc",
     "multi_channel_resampler_unittest.cc",
     "null_video_sink_unittest.cc",
+    "offloading_video_encoder_unittest.cc",
     "pipeline_impl_unittest.cc",
     "ranges_unittest.cc",
     "reentrancy_checker_unittest.cc",
diff --git a/media/base/mock_filters.cc b/media/base/mock_filters.cc
index 34821e90..f7fc5c0 100644
--- a/media/base/mock_filters.cc
+++ b/media/base/mock_filters.cc
@@ -100,6 +100,11 @@
   return decoder_name_;
 }
 
+MockVideoEncoder::MockVideoEncoder() = default;
+MockVideoEncoder::~MockVideoEncoder() {
+  Dtor();
+}
+
 MockAudioDecoder::MockAudioDecoder() : MockAudioDecoder("MockAudioDecoder") {}
 
 MockAudioDecoder::MockAudioDecoder(std::string decoder_name)
diff --git a/media/base/mock_filters.h b/media/base/mock_filters.h
index 25a78b0..8b56c0e 100644
--- a/media/base/mock_filters.h
+++ b/media/base/mock_filters.h
@@ -42,6 +42,7 @@
 #include "media/base/time_source.h"
 #include "media/base/video_decoder.h"
 #include "media/base/video_decoder_config.h"
+#include "media/base/video_encoder.h"
 #include "media/base/video_frame.h"
 #include "media/base/video_renderer.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -261,6 +262,42 @@
   DISALLOW_COPY_AND_ASSIGN(MockVideoDecoder);
 };
 
+class MockVideoEncoder : public VideoEncoder {
+ public:
+  MockVideoEncoder();
+  ~MockVideoEncoder() override;
+
+  // VideoEncoder implementation.
+  MOCK_METHOD(void,
+              Initialize,
+              (VideoCodecProfile profile,
+               const VideoEncoder::Options& options,
+               VideoEncoder::OutputCB output_cb,
+               VideoEncoder::StatusCB done_cb),
+              (override));
+
+  MOCK_METHOD(void,
+              Encode,
+              (scoped_refptr<VideoFrame> frame,
+               bool key_frame,
+               VideoEncoder::StatusCB done_cb),
+              (override));
+
+  MOCK_METHOD(void,
+              ChangeOptions,
+              (const VideoEncoder::Options& options,
+               VideoEncoder::StatusCB done_cb),
+              (override));
+
+  MOCK_METHOD(void, Flush, (VideoEncoder::StatusCB done_cb), (override));
+
+  // A function for mocking destructor calls
+  MOCK_METHOD(void, Dtor, ());
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockVideoEncoder);
+};
+
 class MockAudioDecoder : public AudioDecoder {
  public:
   MockAudioDecoder();
diff --git a/media/base/offloading_video_encoder.cc b/media/base/offloading_video_encoder.cc
new file mode 100644
index 0000000..50e48595
--- /dev/null
+++ b/media/base/offloading_video_encoder.cc
@@ -0,0 +1,87 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/base/offloading_video_encoder.h"
+
+#include "base/sequenced_task_runner.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "media/base/bind_to_current_loop.h"
+#include "media/base/video_frame.h"
+
+namespace media {
+
+OffloadingVideoEncoder::OffloadingVideoEncoder(
+    std::unique_ptr<VideoEncoder> wrapped_encoder,
+    const scoped_refptr<base::SequencedTaskRunner> work_runner,
+    const scoped_refptr<base::SequencedTaskRunner> callback_runner)
+    : wrapped_encoder_(std::move(wrapped_encoder)),
+      work_runner_(std::move(work_runner)),
+      callback_runner_(std::move(callback_runner)) {
+  DCHECK(wrapped_encoder_);
+  DCHECK(work_runner_);
+  DCHECK(callback_runner_);
+  DCHECK_NE(callback_runner_, work_runner_);
+}
+
+OffloadingVideoEncoder::OffloadingVideoEncoder(
+    std::unique_ptr<VideoEncoder> wrapped_encoder)
+    : OffloadingVideoEncoder(std::move(wrapped_encoder),
+                             base::ThreadPool::CreateSequencedTaskRunner(
+                                 {base::TaskPriority::USER_VISIBLE}),
+                             base::SequencedTaskRunnerHandle::Get()) {}
+
+void OffloadingVideoEncoder::Initialize(VideoCodecProfile profile,
+                                        const Options& options,
+                                        OutputCB output_cb,
+                                        StatusCB done_cb) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  work_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&VideoEncoder::Initialize,
+                     base::Unretained(wrapped_encoder_.get()), profile, options,
+                     WrapCallback(std::move(output_cb)),
+                     WrapCallback(std::move(done_cb))));
+}
+
+void OffloadingVideoEncoder::Encode(scoped_refptr<VideoFrame> frame,
+                                    bool key_frame,
+                                    StatusCB done_cb) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  work_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&VideoEncoder::Encode,
+                     base::Unretained(wrapped_encoder_.get()), std::move(frame),
+                     key_frame, WrapCallback(std::move(done_cb))));
+}
+
+void OffloadingVideoEncoder::ChangeOptions(const Options& options,
+                                           StatusCB done_cb) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  work_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&VideoEncoder::ChangeOptions,
+                                base::Unretained(wrapped_encoder_.get()),
+                                options, WrapCallback(std::move(done_cb))));
+}
+
+void OffloadingVideoEncoder::Flush(StatusCB done_cb) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  work_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&VideoEncoder::Flush,
+                                base::Unretained(wrapped_encoder_.get()),
+                                WrapCallback(std::move(done_cb))));
+}
+
+OffloadingVideoEncoder::~OffloadingVideoEncoder() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  work_runner_->DeleteSoon(FROM_HERE, std::move(wrapped_encoder_));
+}
+
+template <class T>
+T OffloadingVideoEncoder::WrapCallback(T cb) {
+  DCHECK(callback_runner_);
+  return media::BindToLoop(callback_runner_.get(), std::move(cb));
+}
+
+}  // namespace media
\ No newline at end of file
diff --git a/media/base/offloading_video_encoder.h b/media/base/offloading_video_encoder.h
new file mode 100644
index 0000000..b4ceed5
--- /dev/null
+++ b/media/base/offloading_video_encoder.h
@@ -0,0 +1,65 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_BASE_OFFLOADING_VIDEO_ENCODER_H_
+#define MEDIA_BASE_OFFLOADING_VIDEO_ENCODER_H_
+
+#include <memory>
+#include <type_traits>
+
+#include "base/sequence_checker.h"
+#include "media/base/video_encoder.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace media {
+
+// A wrapper around video encoder that offloads all the calls to a dedicated
+// task runner. It's used to move synchronous software encoding work off the
+// current (main) thread.
+class MEDIA_EXPORT OffloadingVideoEncoder final : public VideoEncoder {
+ public:
+  // |work_runner| - task runner for encoding work
+  // |callback_runner| - all encoder's callbacks will be executed on this task
+  // runner.
+  OffloadingVideoEncoder(
+      std::unique_ptr<VideoEncoder> wrapped_encoder,
+      const scoped_refptr<base::SequencedTaskRunner> work_runner,
+      const scoped_refptr<base::SequencedTaskRunner> callback_runner);
+
+  // Uses current task runner for callbacks and asks thread pool for a new task
+  // runner to do actual encoding work.
+  explicit OffloadingVideoEncoder(
+      std::unique_ptr<VideoEncoder> wrapped_encoder);
+
+  ~OffloadingVideoEncoder() override;
+
+  void Initialize(VideoCodecProfile profile,
+                  const Options& options,
+                  OutputCB output_cb,
+                  StatusCB done_cb) override;
+
+  void Encode(scoped_refptr<VideoFrame> frame,
+              bool key_frame,
+              StatusCB done_cb) override;
+
+  void ChangeOptions(const Options& options, StatusCB done_cb) override;
+
+  void Flush(StatusCB done_cb) override;
+
+ private:
+  template <class T>
+  T WrapCallback(T cb);
+
+  std::unique_ptr<VideoEncoder> wrapped_encoder_;
+  const scoped_refptr<base::SequencedTaskRunner> work_runner_;
+  const scoped_refptr<base::SequencedTaskRunner> callback_runner_;
+  SEQUENCE_CHECKER(sequence_checker_);
+};
+
+}  // namespace media
+
+#endif  // MEDIA_BASE_OFFLOADING_VIDEO_ENCODER_H_
diff --git a/media/base/offloading_video_encoder_unittest.cc b/media/base/offloading_video_encoder_unittest.cc
new file mode 100644
index 0000000..5eb8c12
--- /dev/null
+++ b/media/base/offloading_video_encoder_unittest.cc
@@ -0,0 +1,142 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/run_loop.h"
+#include "base/sequenced_task_runner.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/gmock_callback_support.h"
+#include "base/test/task_environment.h"
+#include "media/base/media_util.h"
+#include "media/base/mock_filters.h"
+#include "media/base/offloading_video_encoder.h"
+#include "media/base/video_types.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::base::test::RunCallback;
+using ::base::test::RunOnceCallback;
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::Invoke;
+using ::testing::Return;
+
+namespace media {
+
+class OffloadingVideoEncoderTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    auto mock_video_encoder = std::make_unique<MockVideoEncoder>();
+    mock_video_encoder_ = mock_video_encoder.get();
+    work_runner_ = base::ThreadPool::CreateSequencedTaskRunner({});
+    callback_runner_ = base::SequencedTaskRunnerHandle::Get();
+    offloading_encoder_ = std::make_unique<OffloadingVideoEncoder>(
+        std::move(mock_video_encoder), work_runner_, callback_runner_);
+    EXPECT_CALL(*mock_video_encoder_, Dtor()).WillOnce(Invoke([this]() {
+      EXPECT_TRUE(work_runner_->RunsTasksInCurrentSequence());
+    }));
+  }
+
+  void RunLoop() { task_environment_.RunUntilIdle(); }
+
+  base::test::TaskEnvironment task_environment_;
+  scoped_refptr<base::SequencedTaskRunner> work_runner_;
+  scoped_refptr<base::SequencedTaskRunner> callback_runner_;
+  MockVideoEncoder* mock_video_encoder_;
+  std::unique_ptr<OffloadingVideoEncoder> offloading_encoder_;
+};
+
+TEST_F(OffloadingVideoEncoderTest, Initialize) {
+  bool called_done = false;
+  bool called_output = false;
+  VideoEncoder::Options options;
+  VideoCodecProfile profile = VIDEO_CODEC_PROFILE_UNKNOWN;
+  VideoEncoder::OutputCB output_cb = base::BindLambdaForTesting(
+      [&](VideoEncoderOutput, base::Optional<VideoEncoder::CodecDescription>) {
+        EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
+        called_output = true;
+      });
+  VideoEncoder::StatusCB done_cb = base::BindLambdaForTesting([&](Status s) {
+    EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
+    called_done = true;
+  });
+
+  EXPECT_CALL(*mock_video_encoder_, Initialize(_, _, _, _))
+      .WillOnce(Invoke([this](VideoCodecProfile profile,
+                              const VideoEncoder::Options& options,
+                              VideoEncoder::OutputCB output_cb,
+                              VideoEncoder::StatusCB done_cb) {
+        EXPECT_TRUE(work_runner_->RunsTasksInCurrentSequence());
+        std::move(done_cb).Run(Status());
+        std::move(output_cb).Run(VideoEncoderOutput(), {});
+      }));
+
+  offloading_encoder_->Initialize(profile, options, std::move(output_cb),
+                                  std::move(done_cb));
+  RunLoop();
+  EXPECT_TRUE(called_done);
+  EXPECT_TRUE(called_output);
+}
+
+TEST_F(OffloadingVideoEncoderTest, Encode) {
+  bool called_done = false;
+  VideoEncoder::StatusCB done_cb = base::BindLambdaForTesting([&](Status s) {
+    EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
+    called_done = true;
+  });
+
+  EXPECT_CALL(*mock_video_encoder_, Encode(_, _, _))
+      .WillOnce(Invoke([this](scoped_refptr<VideoFrame> frame, bool key_frame,
+                              VideoEncoder::StatusCB done_cb) {
+        EXPECT_TRUE(work_runner_->RunsTasksInCurrentSequence());
+        std::move(done_cb).Run(Status());
+      }));
+
+  offloading_encoder_->Encode(nullptr, false, std::move(done_cb));
+  RunLoop();
+  EXPECT_TRUE(called_done);
+}
+
+TEST_F(OffloadingVideoEncoderTest, ChangeOptions) {
+  bool called_done = false;
+  VideoEncoder::Options options;
+  VideoEncoder::StatusCB done_cb = base::BindLambdaForTesting([&](Status s) {
+    EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
+    called_done = true;
+  });
+
+  EXPECT_CALL(*mock_video_encoder_, ChangeOptions(_, _))
+      .WillOnce(Invoke([this](const VideoEncoder::Options& options,
+                              VideoEncoder::StatusCB done_cb) {
+        EXPECT_TRUE(work_runner_->RunsTasksInCurrentSequence());
+        std::move(done_cb).Run(Status());
+      }));
+
+  offloading_encoder_->ChangeOptions(options, std::move(done_cb));
+  RunLoop();
+  EXPECT_TRUE(called_done);
+}
+
+TEST_F(OffloadingVideoEncoderTest, Flush) {
+  bool called_done = false;
+  VideoEncoder::StatusCB done_cb = base::BindLambdaForTesting([&](Status s) {
+    EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
+    called_done = true;
+  });
+
+  EXPECT_CALL(*mock_video_encoder_, Flush(_))
+      .WillOnce(Invoke([this](VideoEncoder::StatusCB done_cb) {
+        EXPECT_TRUE(work_runner_->RunsTasksInCurrentSequence());
+        std::move(done_cb).Run(Status());
+      }));
+
+  offloading_encoder_->Flush(std::move(done_cb));
+  RunLoop();
+  EXPECT_TRUE(called_done);
+}
+
+}  // namespace media
diff --git a/media/blink/webmediaplayer_impl.cc b/media/blink/webmediaplayer_impl.cc
index 4475980..db89028 100644
--- a/media/blink/webmediaplayer_impl.cc
+++ b/media/blink/webmediaplayer_impl.cc
@@ -2919,9 +2919,8 @@
       // base::Unretained is safe because |this| owns memory_pressure_listener_.
       memory_pressure_listener_ =
           std::make_unique<base::MemoryPressureListener>(
-              FROM_HERE,
-              base::BindRepeating(&WebMediaPlayerImpl::OnMemoryPressure,
-                                  base::Unretained(this)));
+              FROM_HERE, base::Bind(&WebMediaPlayerImpl::OnMemoryPressure,
+                                    base::Unretained(this)));
     }
   }
 
@@ -3639,8 +3638,8 @@
     } else if (update_background_status_cb_.IsCancelled()) {
       // Only trigger updates when we don't have one already scheduled.
       update_background_status_cb_.Reset(
-          base::BindOnce(&WebMediaPlayerImpl::DisableVideoTrackIfNeeded,
-                         base::Unretained(this)));
+          base::Bind(&WebMediaPlayerImpl::DisableVideoTrackIfNeeded,
+                     base::Unretained(this)));
 
       // Defer disable track until we're sure the clip will be backgrounded for
       // some time. Resuming may take half a second, so frequent tab switches
diff --git a/media/blink/webmediaplayer_impl.h b/media/blink/webmediaplayer_impl.h
index 9cafe63..c8ed323 100644
--- a/media/blink/webmediaplayer_impl.h
+++ b/media/blink/webmediaplayer_impl.h
@@ -999,7 +999,7 @@
 
   OverlayInfo overlay_info_;
 
-  base::CancelableOnceClosure update_background_status_cb_;
+  base::CancelableClosure update_background_status_cb_;
 
   mojo::Remote<mojom::MediaMetricsProvider> media_metrics_provider_;
   mojo::Remote<mojom::PlaybackEventsRecorder> playback_events_recorder_;
diff --git a/media/cdm/aes_decryptor_unittest.cc b/media/cdm/aes_decryptor_unittest.cc
index 49a298e..f2b32bb 100644
--- a/media/cdm/aes_decryptor_unittest.cc
+++ b/media/cdm/aes_decryptor_unittest.cc
@@ -252,15 +252,14 @@
   void SetUp() override {
     if (GetParam() == TestType::kAesDecryptor) {
       OnCdmCreated(
-          new AesDecryptor(
-              base::BindRepeating(&MockCdmClient::OnSessionMessage,
-                                  base::Unretained(&cdm_client_)),
-              base::BindRepeating(&MockCdmClient::OnSessionClosed,
-                                  base::Unretained(&cdm_client_)),
-              base::BindRepeating(&MockCdmClient::OnSessionKeysChange,
-                                  base::Unretained(&cdm_client_)),
-              base::BindRepeating(&MockCdmClient::OnSessionExpirationUpdate,
-                                  base::Unretained(&cdm_client_))),
+          new AesDecryptor(base::Bind(&MockCdmClient::OnSessionMessage,
+                                      base::Unretained(&cdm_client_)),
+                           base::Bind(&MockCdmClient::OnSessionClosed,
+                                      base::Unretained(&cdm_client_)),
+                           base::Bind(&MockCdmClient::OnSessionKeysChange,
+                                      base::Unretained(&cdm_client_)),
+                           base::Bind(&MockCdmClient::OnSessionExpirationUpdate,
+                                      base::Unretained(&cdm_client_))),
           std::string());
     } else if (GetParam() == TestType::kCdmAdapter) {
 #if BUILDFLAG(ENABLE_LIBRARY_CDMS)
@@ -284,19 +283,18 @@
       std::unique_ptr<CdmAllocator> allocator(new SimpleCdmAllocator());
       std::unique_ptr<CdmAuxiliaryHelper> cdm_helper(
           new MockCdmAuxiliaryHelper(std::move(allocator)));
-      CdmAdapter::Create(
-          helper_->KeySystemName(), cdm_config, create_cdm_func,
-          std::move(cdm_helper),
-          base::BindRepeating(&MockCdmClient::OnSessionMessage,
-                              base::Unretained(&cdm_client_)),
-          base::BindRepeating(&MockCdmClient::OnSessionClosed,
-                              base::Unretained(&cdm_client_)),
-          base::BindRepeating(&MockCdmClient::OnSessionKeysChange,
-                              base::Unretained(&cdm_client_)),
-          base::BindRepeating(&MockCdmClient::OnSessionExpirationUpdate,
-                              base::Unretained(&cdm_client_)),
-          base::BindOnce(&AesDecryptorTest::OnCdmCreated,
-                         base::Unretained(this)));
+      CdmAdapter::Create(helper_->KeySystemName(),
+                         cdm_config, create_cdm_func, std::move(cdm_helper),
+                         base::Bind(&MockCdmClient::OnSessionMessage,
+                                    base::Unretained(&cdm_client_)),
+                         base::Bind(&MockCdmClient::OnSessionClosed,
+                                    base::Unretained(&cdm_client_)),
+                         base::Bind(&MockCdmClient::OnSessionKeysChange,
+                                    base::Unretained(&cdm_client_)),
+                         base::Bind(&MockCdmClient::OnSessionExpirationUpdate,
+                                    base::Unretained(&cdm_client_)),
+                         base::BindOnce(&AesDecryptorTest::OnCdmCreated,
+                                        base::Unretained(this)));
 
       base::RunLoop().RunUntilIdle();
 #else
diff --git a/media/cdm/cdm_adapter.cc b/media/cdm/cdm_adapter.cc
index e62c8404..af83941 100644
--- a/media/cdm/cdm_adapter.cc
+++ b/media/cdm/cdm_adapter.cc
@@ -226,7 +226,7 @@
   DCHECK(session_expiration_update_cb_);
 
   helper_->SetFileReadCB(
-      base::BindRepeating(&CdmAdapter::OnFileRead, weak_factory_.GetWeakPtr()));
+      base::Bind(&CdmAdapter::OnFileRead, weak_factory_.GetWeakPtr()));
 }
 
 CdmAdapter::~CdmAdapter() {
diff --git a/media/cdm/cdm_adapter_unittest.cc b/media/cdm/cdm_adapter_unittest.cc
index 6e27a40..3b8cc935 100644
--- a/media/cdm/cdm_adapter_unittest.cc
+++ b/media/cdm/cdm_adapter_unittest.cc
@@ -140,19 +140,18 @@
     std::unique_ptr<StrictMock<MockCdmAuxiliaryHelper>> cdm_helper(
         new StrictMock<MockCdmAuxiliaryHelper>(std::move(allocator)));
     cdm_helper_ = cdm_helper.get();
-    CdmAdapter::Create(
-        GetKeySystemName(), cdm_config, GetCreateCdmFunc(),
-        std::move(cdm_helper),
-        base::BindRepeating(&MockCdmClient::OnSessionMessage,
-                            base::Unretained(&cdm_client_)),
-        base::BindRepeating(&MockCdmClient::OnSessionClosed,
-                            base::Unretained(&cdm_client_)),
-        base::BindRepeating(&MockCdmClient::OnSessionKeysChange,
-                            base::Unretained(&cdm_client_)),
-        base::BindRepeating(&MockCdmClient::OnSessionExpirationUpdate,
-                            base::Unretained(&cdm_client_)),
-        base::BindOnce(&CdmAdapterTestBase::OnCdmCreated,
-                       base::Unretained(this), expected_result));
+    CdmAdapter::Create(GetKeySystemName(), cdm_config, GetCreateCdmFunc(),
+                       std::move(cdm_helper),
+                       base::Bind(&MockCdmClient::OnSessionMessage,
+                                  base::Unretained(&cdm_client_)),
+                       base::Bind(&MockCdmClient::OnSessionClosed,
+                                  base::Unretained(&cdm_client_)),
+                       base::Bind(&MockCdmClient::OnSessionKeysChange,
+                                  base::Unretained(&cdm_client_)),
+                       base::Bind(&MockCdmClient::OnSessionExpirationUpdate,
+                                  base::Unretained(&cdm_client_)),
+                       base::BindOnce(&CdmAdapterTestBase::OnCdmCreated,
+                                      base::Unretained(this), expected_result));
     RunUntilIdle();
     ASSERT_EQ(expected_result == SUCCESS, !!cdm_);
   }
diff --git a/media/cdm/library_cdm/clear_key_cdm/clear_key_cdm.cc b/media/cdm/library_cdm/clear_key_cdm/clear_key_cdm.cc
index 1211f1b..5ad5056e 100644
--- a/media/cdm/library_cdm/clear_key_cdm/clear_key_cdm.cc
+++ b/media/cdm/library_cdm/clear_key_cdm/clear_key_cdm.cc
@@ -327,14 +327,11 @@
       cdm_host_proxy_(new CdmHostProxyImpl<HostInterface>(host)),
       cdm_(new ClearKeyPersistentSessionCdm(
           cdm_host_proxy_.get(),
-          base::BindRepeating(&ClearKeyCdm::OnSessionMessage,
-                              base::Unretained(this)),
-          base::BindRepeating(&ClearKeyCdm::OnSessionClosed,
-                              base::Unretained(this)),
-          base::BindRepeating(&ClearKeyCdm::OnSessionKeysChange,
-                              base::Unretained(this)),
-          base::BindRepeating(&ClearKeyCdm::OnSessionExpirationUpdate,
-                              base::Unretained(this)))),
+          base::Bind(&ClearKeyCdm::OnSessionMessage, base::Unretained(this)),
+          base::Bind(&ClearKeyCdm::OnSessionClosed, base::Unretained(this)),
+          base::Bind(&ClearKeyCdm::OnSessionKeysChange, base::Unretained(this)),
+          base::Bind(&ClearKeyCdm::OnSessionExpirationUpdate,
+                     base::Unretained(this)))),
       key_system_(key_system) {
   DCHECK(g_is_cdm_module_initialized);
 }
diff --git a/media/cdm/library_cdm/clear_key_cdm/clear_key_persistent_session_cdm.cc b/media/cdm/library_cdm/clear_key_cdm/clear_key_persistent_session_cdm.cc
index c342812..6e2dcba4 100644
--- a/media/cdm/library_cdm/clear_key_cdm/clear_key_persistent_session_cdm.cc
+++ b/media/cdm/library_cdm/clear_key_cdm/clear_key_persistent_session_cdm.cc
@@ -99,10 +99,10 @@
       session_message_cb_(session_message_cb),
       session_closed_cb_(session_closed_cb) {
   cdm_ = base::MakeRefCounted<AesDecryptor>(
-      base::BindRepeating(&ClearKeyPersistentSessionCdm::OnSessionMessage,
-                          weak_factory_.GetWeakPtr()),
-      base::BindRepeating(&ClearKeyPersistentSessionCdm::OnSessionClosed,
-                          weak_factory_.GetWeakPtr()),
+      base::Bind(&ClearKeyPersistentSessionCdm::OnSessionMessage,
+                 weak_factory_.GetWeakPtr()),
+      base::Bind(&ClearKeyPersistentSessionCdm::OnSessionClosed,
+                 weak_factory_.GetWeakPtr()),
       session_keys_change_cb, session_expiration_update_cb);
 }
 
@@ -129,8 +129,8 @@
     // Since it's a persistent session, we need to save the session ID after
     // it's been created.
     new_promise = std::make_unique<NewPersistentSessionCdmPromise>(
-        base::BindOnce(&ClearKeyPersistentSessionCdm::AddPersistentSession,
-                       weak_factory_.GetWeakPtr()),
+        base::Bind(&ClearKeyPersistentSessionCdm::AddPersistentSession,
+                   weak_factory_.GetWeakPtr()),
         std::move(promise));
   }
   cdm_->CreateSessionAndGenerateRequest(session_type, init_data_type, init_data,
diff --git a/media/gpu/vaapi/vaapi_wrapper.cc b/media/gpu/vaapi/vaapi_wrapper.cc
index 29e37e0..f958857a 100644
--- a/media/gpu/vaapi/vaapi_wrapper.cc
+++ b/media/gpu/vaapi/vaapi_wrapper.cc
@@ -43,6 +43,7 @@
 #include "media/base/media_switches.h"
 #include "media/base/video_frame.h"
 #include "media/base/video_types.h"
+#include "media/gpu/macros.h"
 
 // Auto-generated for dlopen libva libraries
 #include "media/gpu/vaapi/va_stubs.h"
@@ -548,7 +549,7 @@
   int major_version, minor_version;
   VAStatus va_res = vaInitialize(va_display_, &major_version, &minor_version);
   if (va_res != VA_STATUS_SUCCESS) {
-    LOG(ERROR) << "vaInitialize failed: " << vaErrorStr(va_res);
+    VLOGF(1) << "vaInitialize failed: " << vaErrorStr(va_res);
     return false;
   }
   const std::string va_vendor_string = vaQueryVendorString(va_display_);
@@ -568,9 +569,9 @@
   // guaranteed to be backward (and not forward) compatible.
   if (VA_MAJOR_VERSION > major_version ||
       (VA_MAJOR_VERSION == major_version && VA_MINOR_VERSION > minor_version)) {
-    LOG(ERROR) << "The system version " << major_version << "." << minor_version
-               << " should be greater than or equal to "
-               << VA_MAJOR_VERSION << "." << VA_MINOR_VERSION;
+    VLOGF(1) << "The system version " << major_version << "." << minor_version
+             << " should be greater than or equal to " << VA_MAJOR_VERSION
+             << "." << VA_MINOR_VERSION;
     return false;
   }
   return true;
diff --git a/media/video/h265_parser.cc b/media/video/h265_parser.cc
index 6b1d400..7c9c9e0 100644
--- a/media/video/h265_parser.cc
+++ b/media/video/h265_parser.cc
@@ -180,6 +180,18 @@
   memset(reinterpret_cast<void*>(this), 0, sizeof(*this));
 }
 
+H265RefPicListsModifications::H265RefPicListsModifications() {
+  memset(reinterpret_cast<void*>(this), 0, sizeof(*this));
+}
+
+H265PredWeightTable::H265PredWeightTable() {
+  memset(reinterpret_cast<void*>(this), 0, sizeof(*this));
+}
+
+H265SliceHeader::H265SliceHeader() {
+  memset(reinterpret_cast<void*>(this), 0, sizeof(*this));
+}
+
 H265Parser::H265Parser() {
   Reset();
 }
@@ -250,6 +262,18 @@
                                            : gfx::ColorSpace::RangeID::LIMITED);
 }
 
+bool H265SliceHeader::IsISlice() const {
+  return slice_type == kSliceTypeI;
+}
+
+bool H265SliceHeader::IsPSlice() const {
+  return slice_type == kSliceTypeP;
+}
+
+bool H265SliceHeader::IsBSlice() const {
+  return slice_type == kSliceTypeB;
+}
+
 void H265Parser::Reset() {
   stream_ = NULL;
   bytes_left_ = 0;
@@ -727,9 +751,11 @@
   READ_BOOL_OR_RETURN(&pps->sign_data_hiding_enabled_flag);
   READ_BOOL_OR_RETURN(&pps->cabac_init_present_flag);
   READ_UE_OR_RETURN(&pps->num_ref_idx_l0_default_active_minus1);
-  IN_RANGE_OR_RETURN(pps->num_ref_idx_l0_default_active_minus1, 0, 14);
+  IN_RANGE_OR_RETURN(pps->num_ref_idx_l0_default_active_minus1, 0,
+                     kMaxRefIdxActive - 1);
   READ_UE_OR_RETURN(&pps->num_ref_idx_l1_default_active_minus1);
-  IN_RANGE_OR_RETURN(pps->num_ref_idx_l1_default_active_minus1, 0, 14);
+  IN_RANGE_OR_RETURN(pps->num_ref_idx_l1_default_active_minus1, 0,
+                     kMaxRefIdxActive - 1);
   READ_SE_OR_RETURN(&pps->init_qp_minus26);
   pps->qp_bd_offset_y = 6 * sps->bit_depth_luma_minus8;
   IN_RANGE_OR_RETURN(pps->init_qp_minus26, -(26 + pps->qp_bd_offset_y), 25);
@@ -864,6 +890,311 @@
   return it->second.get();
 }
 
+H265Parser::Result H265Parser::ParseSliceHeader(const H265NALU& nalu,
+                                                H265SliceHeader* shdr) {
+  // 7.4.7 Slice segment header
+  DVLOG(4) << "Parsing slice header";
+  Result res = kOk;
+  const H265SPS* sps;
+  const H265PPS* pps;
+
+  DCHECK(shdr);
+  shdr->nal_unit_type = nalu.nal_unit_type;
+  shdr->nalu_data = nalu.data;
+  shdr->nalu_size = nalu.size;
+
+  READ_BOOL_OR_RETURN(&shdr->first_slice_segment_in_pic_flag);
+  shdr->irap_pic = (shdr->nal_unit_type >= H265NALU::BLA_W_LP &&
+                    shdr->nal_unit_type <= H265NALU::RSV_IRAP_VCL23);
+  if (shdr->irap_pic) {
+    READ_BOOL_OR_RETURN(&shdr->no_output_of_prior_pics_flag);
+  }
+  READ_UE_OR_RETURN(&shdr->slice_pic_parameter_set_id);
+  IN_RANGE_OR_RETURN(shdr->slice_pic_parameter_set_id, 0, 63);
+  pps = GetPPS(shdr->slice_pic_parameter_set_id);
+  if (!pps) {
+    return kMissingParameterSet;
+  }
+  sps = GetSPS(pps->pps_seq_parameter_set_id);
+  DCHECK(sps);  // We already validated this when we parsed the PPS.
+
+  // Set these defaults if they are not present here.
+  shdr->pic_output_flag = 1;
+  shdr->num_ref_idx_l0_active_minus1 =
+      pps->num_ref_idx_l0_default_active_minus1;
+  shdr->num_ref_idx_l1_active_minus1 =
+      pps->num_ref_idx_l1_default_active_minus1;
+  shdr->collocated_from_l0_flag = 1;
+  shdr->slice_deblocking_filter_disabled_flag =
+      pps->pps_deblocking_filter_disabled_flag;
+  shdr->slice_beta_offset_div2 = pps->pps_beta_offset_div2;
+  shdr->slice_tc_offset_div2 = pps->pps_tc_offset_div2;
+  shdr->slice_loop_filter_across_slices_enabled_flag =
+      pps->pps_loop_filter_across_slices_enabled_flag;
+
+  if (!shdr->first_slice_segment_in_pic_flag) {
+    if (pps->dependent_slice_segments_enabled_flag)
+      READ_BOOL_OR_RETURN(&shdr->dependent_slice_segment_flag);
+    READ_BITS_OR_RETURN(base::bits::Log2Ceiling(sps->pic_size_in_ctbs_y),
+                        &shdr->slice_segment_address);
+    IN_RANGE_OR_RETURN(shdr->slice_segment_address, 0,
+                       sps->pic_size_in_ctbs_y - 1);
+  }
+  shdr->curr_rps_idx = sps->num_short_term_ref_pic_sets;
+  if (!shdr->dependent_slice_segment_flag) {
+    // slice_reserved_flag
+    SKIP_BITS_OR_RETURN(pps->num_extra_slice_header_bits);
+    READ_UE_OR_RETURN(&shdr->slice_type);
+    if ((shdr->irap_pic ||
+         sps->sps_max_dec_pic_buffering_minus1[pps->temporal_id] == 0) &&
+        nalu.nuh_layer_id == 0) {
+      TRUE_OR_RETURN(shdr->slice_type == 2);
+    }
+    if (pps->output_flag_present_flag)
+      READ_BOOL_OR_RETURN(&shdr->pic_output_flag);
+    if (sps->separate_colour_plane_flag) {
+      READ_BITS_OR_RETURN(2, &shdr->colour_plane_id);
+      IN_RANGE_OR_RETURN(shdr->colour_plane_id, 0, 2);
+    }
+    if (shdr->nal_unit_type != H265NALU::IDR_W_RADL &&
+        shdr->nal_unit_type != H265NALU::IDR_N_LP) {
+      READ_BITS_OR_RETURN(sps->log2_max_pic_order_cnt_lsb_minus4 + 4,
+                          &shdr->slice_pic_order_cnt_lsb);
+      IN_RANGE_OR_RETURN(shdr->slice_pic_order_cnt_lsb, 0,
+                         sps->max_pic_order_cnt_lsb - 1);
+      READ_BOOL_OR_RETURN(&shdr->short_term_ref_pic_set_sps_flag);
+      if (!shdr->short_term_ref_pic_set_sps_flag) {
+        off_t bits_left_prior = br_.NumBitsLeft();
+        size_t num_epb_prior = br_.NumEmulationPreventionBytesRead();
+        res = ParseStRefPicSet(sps->num_short_term_ref_pic_sets, *sps,
+                               &shdr->st_ref_pic_set);
+        if (res != kOk)
+          return res;
+        shdr->st_rps_bits =
+            (bits_left_prior - br_.NumBitsLeft()) -
+            8 * (br_.NumEmulationPreventionBytesRead() - num_epb_prior);
+      } else if (sps->num_short_term_ref_pic_sets > 1) {
+        READ_BITS_OR_RETURN(
+            base::bits::Log2Ceiling(sps->num_short_term_ref_pic_sets),
+            &shdr->short_term_ref_pic_set_idx);
+        IN_RANGE_OR_RETURN(shdr->short_term_ref_pic_set_idx, 0,
+                           sps->num_short_term_ref_pic_sets - 1);
+      }
+
+      if (shdr->short_term_ref_pic_set_sps_flag)
+        shdr->curr_rps_idx = shdr->short_term_ref_pic_set_idx;
+
+      if (sps->long_term_ref_pics_present_flag) {
+        if (sps->num_long_term_ref_pics_sps > 0) {
+          READ_UE_OR_RETURN(&shdr->num_long_term_sps);
+          IN_RANGE_OR_RETURN(shdr->num_long_term_sps, 0,
+                             sps->num_long_term_ref_pics_sps);
+        }
+        READ_UE_OR_RETURN(&shdr->num_long_term_pics);
+        if (nalu.nuh_layer_id == 0) {
+          TRUE_OR_RETURN(
+              shdr->num_long_term_pics <=
+              (sps->sps_max_dec_pic_buffering_minus1[pps->temporal_id] -
+               shdr->GetStRefPicSet(sps).num_negative_pics -
+               shdr->GetStRefPicSet(sps).num_positive_pics -
+               shdr->num_long_term_sps));
+        }
+        IN_RANGE_OR_RETURN(shdr->num_long_term_sps + shdr->num_long_term_pics,
+                           0, kMaxLongTermRefPicSets);
+        for (int i = 0; i < shdr->num_long_term_sps + shdr->num_long_term_pics;
+             ++i) {
+          if (i < shdr->num_long_term_sps) {
+            int lt_idx_sps = 0;
+            if (sps->num_long_term_ref_pics_sps > 1) {
+              READ_BITS_OR_RETURN(
+                  base::bits::Log2Ceiling(sps->num_long_term_ref_pics_sps),
+                  &lt_idx_sps);
+              IN_RANGE_OR_RETURN(lt_idx_sps, 0,
+                                 sps->num_long_term_ref_pics_sps - 1);
+            }
+            shdr->poc_lsb_lt[i] = sps->lt_ref_pic_poc_lsb_sps[lt_idx_sps];
+            shdr->used_by_curr_pic_lt[i] =
+                sps->used_by_curr_pic_lt_sps_flag[lt_idx_sps];
+          } else {
+            READ_BITS_OR_RETURN(sps->log2_max_pic_order_cnt_lsb_minus4 + 4,
+                                &shdr->poc_lsb_lt[i]);
+            READ_BOOL_OR_RETURN(&shdr->used_by_curr_pic_lt[i]);
+          }
+          READ_BOOL_OR_RETURN(&shdr->delta_poc_msb_present_flag[i]);
+          if (shdr->delta_poc_msb_present_flag[i]) {
+            READ_UE_OR_RETURN(&shdr->delta_poc_msb_cycle_lt[i]);
+            IN_RANGE_OR_RETURN(
+                shdr->delta_poc_msb_cycle_lt[i], 0,
+                std::pow(2, 32 - sps->log2_max_pic_order_cnt_lsb_minus4 - 4));
+            // Equation 7-52.
+            if (i != 0 && i != shdr->num_long_term_sps) {
+              shdr->delta_poc_msb_cycle_lt[i] =
+                  shdr->delta_poc_msb_cycle_lt[i] +
+                  shdr->delta_poc_msb_cycle_lt[i - 1];
+            }
+          }
+        }
+      }
+      if (sps->sps_temporal_mvp_enabled_flag)
+        READ_BOOL_OR_RETURN(&shdr->slice_temporal_mvp_enabled_flag);
+    }
+    if (sps->sample_adaptive_offset_enabled_flag) {
+      READ_BOOL_OR_RETURN(&shdr->slice_sao_luma_flag);
+      if (sps->chroma_array_type != 0)
+        READ_BOOL_OR_RETURN(&shdr->slice_sao_chroma_flag);
+    }
+    if (shdr->IsPSlice() || shdr->IsBSlice()) {
+      READ_BOOL_OR_RETURN(&shdr->num_ref_idx_active_override_flag);
+      if (shdr->num_ref_idx_active_override_flag) {
+        READ_UE_OR_RETURN(&shdr->num_ref_idx_l0_active_minus1);
+        IN_RANGE_OR_RETURN(shdr->num_ref_idx_l0_active_minus1, 0,
+                           kMaxRefIdxActive - 1);
+        if (shdr->IsBSlice()) {
+          READ_UE_OR_RETURN(&shdr->num_ref_idx_l1_active_minus1);
+          IN_RANGE_OR_RETURN(shdr->num_ref_idx_l1_active_minus1, 0,
+                             kMaxRefIdxActive - 1);
+        }
+      }
+
+      shdr->num_pic_total_curr = 0;
+      const H265StRefPicSet& st_ref_pic = shdr->GetStRefPicSet(sps);
+      for (int i = 0; i < st_ref_pic.num_negative_pics; ++i) {
+        if (st_ref_pic.used_by_curr_pic_s0[i])
+          shdr->num_pic_total_curr++;
+      }
+      for (int i = 0; i < st_ref_pic.num_positive_pics; ++i) {
+        if (st_ref_pic.used_by_curr_pic_s1[i])
+          shdr->num_pic_total_curr++;
+      }
+      for (int i = 0; i < shdr->num_long_term_sps + shdr->num_long_term_pics;
+           ++i) {
+        if (shdr->used_by_curr_pic_lt[i])
+          shdr->num_pic_total_curr++;
+      }
+
+      if (pps->lists_modification_present_flag &&
+          shdr->num_pic_total_curr > 1) {
+        res = ParseRefPicListsModifications(*shdr,
+                                            &shdr->ref_pic_lists_modification);
+        if (res != kOk)
+          return res;
+      }
+      if (shdr->IsBSlice())
+        READ_BOOL_OR_RETURN(&shdr->mvd_l1_zero_flag);
+      if (pps->cabac_init_present_flag)
+        READ_BOOL_OR_RETURN(&shdr->cabac_init_flag);
+      if (shdr->slice_temporal_mvp_enabled_flag) {
+        if (shdr->IsBSlice())
+          READ_BOOL_OR_RETURN(&shdr->collocated_from_l0_flag);
+        if ((shdr->collocated_from_l0_flag &&
+             shdr->num_ref_idx_l0_active_minus1 > 0) ||
+            (!shdr->collocated_from_l0_flag &&
+             shdr->num_ref_idx_l1_active_minus1 > 0)) {
+          READ_UE_OR_RETURN(&shdr->collocated_ref_idx);
+          if ((shdr->IsPSlice() || shdr->IsBSlice()) &&
+              shdr->collocated_from_l0_flag) {
+            IN_RANGE_OR_RETURN(shdr->collocated_ref_idx, 0,
+                               shdr->num_ref_idx_l0_active_minus1);
+          }
+          if (shdr->IsBSlice() && !shdr->collocated_from_l0_flag) {
+            IN_RANGE_OR_RETURN(shdr->collocated_ref_idx, 0,
+                               shdr->num_ref_idx_l1_active_minus1);
+          }
+        }
+      }
+
+      if ((pps->weighted_pred_flag && shdr->IsPSlice()) ||
+          (pps->weighted_bipred_flag && shdr->IsBSlice())) {
+        res = ParsePredWeightTable(*sps, *shdr, &shdr->pred_weight_table);
+        if (res != kOk)
+          return res;
+      }
+      READ_UE_OR_RETURN(&shdr->five_minus_max_num_merge_cand);
+      IN_RANGE_OR_RETURN(5 - shdr->five_minus_max_num_merge_cand, 1, 5);
+    }
+    READ_SE_OR_RETURN(&shdr->slice_qp_delta);
+    IN_RANGE_OR_RETURN(26 + pps->init_qp_minus26 + shdr->slice_qp_delta,
+                       -pps->qp_bd_offset_y, 51);
+
+    if (pps->pps_slice_chroma_qp_offsets_present_flag) {
+      READ_SE_OR_RETURN(&shdr->slice_cb_qp_offset);
+      IN_RANGE_OR_RETURN(shdr->slice_cb_qp_offset, -12, 12);
+      IN_RANGE_OR_RETURN(pps->pps_cb_qp_offset + shdr->slice_cb_qp_offset, -12,
+                         12);
+      READ_SE_OR_RETURN(&shdr->slice_cr_qp_offset);
+      IN_RANGE_OR_RETURN(shdr->slice_cr_qp_offset, -12, 12);
+      IN_RANGE_OR_RETURN(pps->pps_cr_qp_offset + shdr->slice_cr_qp_offset, -12,
+                         12);
+    }
+
+    // pps_slice_act_qp_offsets_present_flag is zero, we don't support SCC ext.
+
+    // chroma_qp_offset_list_enabled_flag is zero, we don't support range ext.
+
+    bool deblocking_filter_override_flag = false;
+    if (pps->deblocking_filter_override_enabled_flag)
+      READ_BOOL_OR_RETURN(&deblocking_filter_override_flag);
+    if (deblocking_filter_override_flag) {
+      READ_BOOL_OR_RETURN(&shdr->slice_deblocking_filter_disabled_flag);
+      if (!shdr->slice_deblocking_filter_disabled_flag) {
+        READ_SE_OR_RETURN(&shdr->slice_beta_offset_div2);
+        IN_RANGE_OR_RETURN(shdr->slice_beta_offset_div2, -6, 6);
+        READ_SE_OR_RETURN(&shdr->slice_tc_offset_div2);
+        IN_RANGE_OR_RETURN(shdr->slice_tc_offset_div2, -6, 6);
+      }
+    }
+    if (pps->pps_loop_filter_across_slices_enabled_flag &&
+        (shdr->slice_sao_luma_flag || shdr->slice_sao_chroma_flag ||
+         !shdr->slice_deblocking_filter_disabled_flag)) {
+      READ_BOOL_OR_RETURN(&shdr->slice_loop_filter_across_slices_enabled_flag);
+    }
+  }
+
+  if (pps->tiles_enabled_flag || pps->entropy_coding_sync_enabled_flag) {
+    int num_entry_point_offsets;
+    READ_UE_OR_RETURN(&num_entry_point_offsets);
+    if (!pps->tiles_enabled_flag) {
+      IN_RANGE_OR_RETURN(num_entry_point_offsets, 0,
+                         sps->pic_height_in_ctbs_y - 1);
+    } else if (!pps->entropy_coding_sync_enabled_flag) {
+      IN_RANGE_OR_RETURN(
+          num_entry_point_offsets, 0,
+          (pps->num_tile_columns_minus1 + 1) * (pps->num_tile_rows_minus1 + 1) -
+              1);
+    } else {  // both are true
+      IN_RANGE_OR_RETURN(
+          num_entry_point_offsets, 0,
+          (pps->num_tile_columns_minus1 + 1) * sps->pic_height_in_ctbs_y - 1);
+    }
+    if (num_entry_point_offsets > 0) {
+      int offset_len_minus1;
+      READ_UE_OR_RETURN(&offset_len_minus1);
+      IN_RANGE_OR_RETURN(offset_len_minus1, 0, 31);
+      SKIP_BITS_OR_RETURN(num_entry_point_offsets * (offset_len_minus1 + 1));
+    }
+  }
+
+  if (pps->slice_segment_header_extension_present_flag) {
+    int slice_segment_header_extension_length;
+    READ_UE_OR_RETURN(&slice_segment_header_extension_length);
+    IN_RANGE_OR_RETURN(slice_segment_header_extension_length, 0, 256);
+    SKIP_BITS_OR_RETURN(slice_segment_header_extension_length * 8);
+  }
+
+  // byte_alignment()
+  SKIP_BITS_OR_RETURN(1);  // alignment bit
+  int bits_left_to_align = br_.NumBitsLeft() % 8;
+  if (bits_left_to_align)
+    SKIP_BITS_OR_RETURN(bits_left_to_align);
+
+  shdr->header_emulation_prevention_bytes =
+      br_.NumEmulationPreventionBytesRead();
+  shdr->header_size = shdr->nalu_size -
+                      shdr->header_emulation_prevention_bytes -
+                      br_.NumBitsLeft() / 8;
+  return res;
+}
+
 // static
 VideoCodecProfile H265Parser::ProfileIDCToVideoCodecProfile(int profile_idc) {
   switch (profile_idc) {
@@ -1341,4 +1672,122 @@
   return kOk;
 }
 
+H265Parser::Result H265Parser::ParseRefPicListsModifications(
+    const H265SliceHeader& shdr,
+    H265RefPicListsModifications* rpl_mod) {
+  READ_BOOL_OR_RETURN(&rpl_mod->ref_pic_list_modification_flag_l0);
+  if (rpl_mod->ref_pic_list_modification_flag_l0) {
+    for (int i = 0; i <= shdr.num_ref_idx_l0_active_minus1; ++i) {
+      READ_BITS_OR_RETURN(base::bits::Log2Ceiling(shdr.num_pic_total_curr),
+                          &rpl_mod->list_entry_l0[i]);
+      IN_RANGE_OR_RETURN(rpl_mod->list_entry_l0[i], 0,
+                         shdr.num_pic_total_curr - 1);
+    }
+  }
+  if (shdr.IsBSlice()) {
+    READ_BOOL_OR_RETURN(&rpl_mod->ref_pic_list_modification_flag_l1);
+    if (rpl_mod->ref_pic_list_modification_flag_l1) {
+      for (int i = 0; i <= shdr.num_ref_idx_l1_active_minus1; ++i) {
+        READ_BITS_OR_RETURN(base::bits::Log2Ceiling(shdr.num_pic_total_curr),
+                            &rpl_mod->list_entry_l1[i]);
+        IN_RANGE_OR_RETURN(rpl_mod->list_entry_l1[i], 0,
+                           shdr.num_pic_total_curr - 1);
+      }
+    }
+  }
+  return kOk;
+}
+
+H265Parser::Result H265Parser::ParsePredWeightTable(
+    const H265SPS& sps,
+    const H265SliceHeader& shdr,
+    H265PredWeightTable* pred_weight_table) {
+  // 7.4.6.3 Weighted prediction parameters semantics
+  READ_UE_OR_RETURN(&pred_weight_table->luma_log2_weight_denom);
+  IN_RANGE_OR_RETURN(pred_weight_table->luma_log2_weight_denom, 0, 7);
+  if (sps.chroma_array_type) {
+    READ_SE_OR_RETURN(&pred_weight_table->delta_chroma_log2_weight_denom);
+    pred_weight_table->chroma_log2_weight_denom =
+        pred_weight_table->delta_chroma_log2_weight_denom +
+        pred_weight_table->luma_log2_weight_denom;
+    IN_RANGE_OR_RETURN(pred_weight_table->chroma_log2_weight_denom, 0, 7);
+  }
+  bool luma_weight_flag[kMaxRefIdxActive];
+  bool chroma_weight_flag[kMaxRefIdxActive];
+  memset(chroma_weight_flag, 0, sizeof(chroma_weight_flag));
+  for (int i = 0; i <= shdr.num_ref_idx_l0_active_minus1; ++i) {
+    READ_BOOL_OR_RETURN(&luma_weight_flag[i]);
+  }
+  if (sps.chroma_array_type) {
+    for (int i = 0; i <= shdr.num_ref_idx_l0_active_minus1; ++i) {
+      READ_BOOL_OR_RETURN(&chroma_weight_flag[i]);
+    }
+  }
+  int sum_weight_l0_flags = 0;
+  for (int i = 0; i <= shdr.num_ref_idx_l0_active_minus1; ++i) {
+    if (luma_weight_flag[i]) {
+      sum_weight_l0_flags++;
+      READ_SE_OR_RETURN(&pred_weight_table->delta_luma_weight_l0[i]);
+      IN_RANGE_OR_RETURN(pred_weight_table->delta_luma_weight_l0[i], -128, 127);
+      READ_SE_OR_RETURN(&pred_weight_table->luma_offset_l0[i]);
+      IN_RANGE_OR_RETURN(pred_weight_table->luma_offset_l0[i],
+                         -sps.wp_offset_half_range_y,
+                         sps.wp_offset_half_range_y - 1);
+    }
+    if (chroma_weight_flag[i]) {
+      sum_weight_l0_flags += 2;
+      for (int j = 0; j < 2; ++j) {
+        READ_SE_OR_RETURN(&pred_weight_table->delta_chroma_weight_l0[i][j]);
+        IN_RANGE_OR_RETURN(pred_weight_table->delta_chroma_weight_l0[i][j],
+                           -128, 127);
+        READ_SE_OR_RETURN(&pred_weight_table->delta_chroma_offset_l0[i][j]);
+        IN_RANGE_OR_RETURN(pred_weight_table->delta_chroma_offset_l0[i][j],
+                           -4 * sps.wp_offset_half_range_c,
+                           4 * sps.wp_offset_half_range_c - 1);
+      }
+    }
+  }
+  if (shdr.IsPSlice())
+    TRUE_OR_RETURN(sum_weight_l0_flags <= 24);
+  if (shdr.IsBSlice()) {
+    memset(chroma_weight_flag, 0, sizeof(chroma_weight_flag));
+    int sum_weight_l1_flags = 0;
+    for (int i = 0; i <= shdr.num_ref_idx_l1_active_minus1; ++i) {
+      READ_BOOL_OR_RETURN(&luma_weight_flag[i]);
+    }
+    if (sps.chroma_array_type) {
+      for (int i = 0; i <= shdr.num_ref_idx_l1_active_minus1; ++i) {
+        READ_BOOL_OR_RETURN(&chroma_weight_flag[i]);
+      }
+    }
+    for (int i = 0; i <= shdr.num_ref_idx_l1_active_minus1; ++i) {
+      if (luma_weight_flag[i]) {
+        sum_weight_l1_flags++;
+        READ_SE_OR_RETURN(&pred_weight_table->delta_luma_weight_l1[i]);
+        IN_RANGE_OR_RETURN(pred_weight_table->delta_luma_weight_l1[i], -128,
+                           127);
+        READ_SE_OR_RETURN(&pred_weight_table->luma_offset_l1[i]);
+        IN_RANGE_OR_RETURN(pred_weight_table->luma_offset_l1[i],
+                           -sps.wp_offset_half_range_y,
+                           sps.wp_offset_half_range_y - 1);
+      }
+      if (chroma_weight_flag[i]) {
+        sum_weight_l1_flags += 2;
+        for (int j = 0; j < 2; ++j) {
+          READ_SE_OR_RETURN(&pred_weight_table->delta_chroma_weight_l1[i][j]);
+          IN_RANGE_OR_RETURN(pred_weight_table->delta_chroma_weight_l1[i][j],
+                             -128, 127);
+          READ_SE_OR_RETURN(&pred_weight_table->delta_chroma_offset_l1[i][j]);
+          IN_RANGE_OR_RETURN(pred_weight_table->delta_chroma_offset_l1[i][j],
+                             -4 * sps.wp_offset_half_range_c,
+                             4 * sps.wp_offset_half_range_c - 1);
+        }
+      }
+    }
+    TRUE_OR_RETURN(sum_weight_l0_flags + sum_weight_l1_flags <= 24);
+  }
+
+  return kOk;
+}
+
 }  // namespace media
diff --git a/media/video/h265_parser.h b/media/video/h265_parser.h
index 15d6f586..3bc29a1 100644
--- a/media/video/h265_parser.h
+++ b/media/video/h265_parser.h
@@ -319,6 +319,108 @@
   int qp_bd_offset_y;
 };
 
+struct MEDIA_EXPORT H265RefPicListsModifications {
+  H265RefPicListsModifications();
+
+  // Syntax elements.
+  bool ref_pic_list_modification_flag_l0;
+  int list_entry_l0[kMaxRefIdxActive];
+  bool ref_pic_list_modification_flag_l1;
+  int list_entry_l1[kMaxRefIdxActive];
+};
+
+struct MEDIA_EXPORT H265PredWeightTable {
+  H265PredWeightTable();
+
+  // Syntax elements.
+  int luma_log2_weight_denom;
+  int delta_chroma_log2_weight_denom;
+  int chroma_log2_weight_denom;
+  int delta_luma_weight_l0[kMaxRefIdxActive];
+  int luma_offset_l0[kMaxRefIdxActive];
+  int delta_chroma_weight_l0[kMaxRefIdxActive][2];
+  int delta_chroma_offset_l0[kMaxRefIdxActive][2];
+  int delta_luma_weight_l1[kMaxRefIdxActive];
+  int luma_offset_l1[kMaxRefIdxActive];
+  int delta_chroma_weight_l1[kMaxRefIdxActive][2];
+  int delta_chroma_offset_l1[kMaxRefIdxActive][2];
+};
+
+struct MEDIA_EXPORT H265SliceHeader {
+  H265SliceHeader();
+
+  enum {
+    kSliceTypeB = 0,  // Table 7-7
+    kSliceTypeP = 1,  // Table 7-7
+    kSliceTypeI = 2,  // Table 7-7
+  };
+
+  int nal_unit_type;         // from NAL header
+  const uint8_t* nalu_data;  // from NAL header
+  size_t nalu_size;          // from NAL header
+  size_t header_size;  // calculated, not including emulation prevention bytes
+  size_t header_emulation_prevention_bytes;
+
+  // Syntax elements.
+  bool first_slice_segment_in_pic_flag;
+  bool no_output_of_prior_pics_flag;
+  int slice_pic_parameter_set_id;
+  bool dependent_slice_segment_flag;
+  int slice_segment_address;
+  int slice_type;
+  bool pic_output_flag;
+  int colour_plane_id;
+  int slice_pic_order_cnt_lsb;
+  bool short_term_ref_pic_set_sps_flag;
+  H265StRefPicSet st_ref_pic_set;
+  int short_term_ref_pic_set_idx;
+  int num_long_term_sps;
+  int num_long_term_pics;
+  int poc_lsb_lt[kMaxLongTermRefPicSets];
+  bool used_by_curr_pic_lt[kMaxLongTermRefPicSets];
+  bool delta_poc_msb_present_flag[kMaxLongTermRefPicSets];
+  int delta_poc_msb_cycle_lt[kMaxLongTermRefPicSets];
+  bool slice_temporal_mvp_enabled_flag;
+  bool slice_sao_luma_flag;
+  bool slice_sao_chroma_flag;
+  bool num_ref_idx_active_override_flag;
+  int num_ref_idx_l0_active_minus1;
+  int num_ref_idx_l1_active_minus1;
+  H265RefPicListsModifications ref_pic_lists_modification;
+  bool mvd_l1_zero_flag;
+  bool cabac_init_flag;
+  bool collocated_from_l0_flag;
+  int collocated_ref_idx;
+  H265PredWeightTable pred_weight_table;
+  int five_minus_max_num_merge_cand;
+  int slice_qp_delta;
+  int slice_cb_qp_offset;
+  int slice_cr_qp_offset;
+  bool slice_deblocking_filter_disabled_flag;
+  int slice_beta_offset_div2;
+  int slice_tc_offset_div2;
+  bool slice_loop_filter_across_slices_enabled_flag;
+
+  // Calculated.
+  int curr_rps_idx;
+  int num_pic_total_curr;
+  bool irap_pic;
+  // Number of bits st_ref_pic_set takes after removing emulation prevention
+  // bytes.
+  int st_rps_bits;
+
+  bool IsISlice() const;
+  bool IsPSlice() const;
+  bool IsBSlice() const;
+
+  const H265StRefPicSet& GetStRefPicSet(const H265SPS* sps) const {
+    if (curr_rps_idx == sps->num_short_term_ref_pic_sets)
+      return st_ref_pic_set;
+
+    return sps->st_ref_pic_set[curr_rps_idx];
+  }
+};
+
 // Class to parse an Annex-B H.265 stream.
 class MEDIA_EXPORT H265Parser {
  public:
@@ -370,6 +472,14 @@
   const H265SPS* GetSPS(int sps_id) const;
   const H265PPS* GetPPS(int pps_id) const;
 
+  // Slice headers and are not used across NALUs by the parser and can be
+  // discarded after current NALU, so the parser does not store them, nor does
+  // it manage their memory. The caller has to provide and manage it instead.
+
+  // Parse a slice header, returning it in |*shdr|. |*nalu| must be set to
+  // the NALU returned from AdvanceToNextNALU() and corresponding to |*shdr|.
+  Result ParseSliceHeader(const H265NALU& nalu, H265SliceHeader* shdr);
+
   static VideoCodecProfile ProfileIDCToVideoCodecProfile(int profile_idc);
 
  private:
@@ -402,6 +512,11 @@
   Result ParseAndIgnoreSubLayerHrdParameters(
       int cpb_cnt,
       bool sub_pic_hrd_params_present_flag);
+  Result ParseRefPicListsModifications(const H265SliceHeader& shdr,
+                                       H265RefPicListsModifications* rpl_mod);
+  Result ParsePredWeightTable(const H265SPS& sps,
+                              const H265SliceHeader& shdr,
+                              H265PredWeightTable* pred_weight_table);
 
   // Pointer to the current NALU in the stream.
   const uint8_t* stream_;
diff --git a/media/video/h265_parser_fuzzertest.cc b/media/video/h265_parser_fuzzertest.cc
index c2a882a..4c6aa82 100644
--- a/media/video/h265_parser_fuzzertest.cc
+++ b/media/video/h265_parser_fuzzertest.cc
@@ -26,6 +26,7 @@
     if (res != media::H265Parser::kOk)
       break;
 
+    media::H265SliceHeader shdr;
     switch (nalu.nal_unit_type) {
       case media::H265NALU::SPS_NUT:
         int sps_id;
@@ -35,6 +36,24 @@
         int pps_id;
         res = parser.ParsePPS(nalu, &pps_id);
         break;
+      case media::H265NALU::TRAIL_N:
+      case media::H265NALU::TRAIL_R:
+      case media::H265NALU::TSA_N:
+      case media::H265NALU::TSA_R:
+      case media::H265NALU::STSA_N:
+      case media::H265NALU::STSA_R:
+      case media::H265NALU::RADL_N:
+      case media::H265NALU::RADL_R:
+      case media::H265NALU::RASL_N:
+      case media::H265NALU::RASL_R:
+      case media::H265NALU::BLA_W_LP:
+      case media::H265NALU::BLA_W_RADL:
+      case media::H265NALU::BLA_N_LP:
+      case media::H265NALU::IDR_W_RADL:
+      case media::H265NALU::IDR_N_LP:
+      case media::H265NALU::CRA_NUT:  // fallthrough
+        res = parser.ParseSliceHeader(nalu, &shdr);
+        break;
       default:
         // Skip any other NALU.
         break;
diff --git a/media/video/h265_parser_unittest.cc b/media/video/h265_parser_unittest.cc
index bfe0625..a77d229 100644
--- a/media/video/h265_parser_unittest.cc
+++ b/media/video/h265_parser_unittest.cc
@@ -77,6 +77,7 @@
       ++num_parsed_nalus;
       DVLOG(4) << "Found NALU " << nalu.nal_unit_type;
 
+      H265SliceHeader shdr;
       switch (nalu.nal_unit_type) {
         case H265NALU::SPS_NUT:
           int sps_id;
@@ -88,6 +89,24 @@
           res = parser_.ParsePPS(nalu, &pps_id);
           EXPECT_TRUE(!!parser_.GetPPS(pps_id));
           break;
+        case H265NALU::TRAIL_N:
+        case H265NALU::TRAIL_R:
+        case H265NALU::TSA_N:
+        case H265NALU::TSA_R:
+        case H265NALU::STSA_N:
+        case H265NALU::STSA_R:
+        case H265NALU::RADL_N:
+        case H265NALU::RADL_R:
+        case H265NALU::RASL_N:
+        case H265NALU::RASL_R:
+        case H265NALU::BLA_W_LP:
+        case H265NALU::BLA_W_RADL:
+        case H265NALU::BLA_N_LP:
+        case H265NALU::IDR_W_RADL:
+        case H265NALU::IDR_N_LP:
+        case H265NALU::CRA_NUT:  // fallthrough
+          res = parser_.ParseSliceHeader(nalu, &shdr);
+          break;
         default:
           break;
       }
@@ -202,4 +221,59 @@
   EXPECT_FALSE(pps->slice_segment_header_extension_present_flag);
 }
 
+TEST_F(H265ParserTest, SliceHeaderParsing) {
+  LoadParserFile("bear.hevc");
+  H265NALU target_nalu;
+  EXPECT_TRUE(ParseNalusUntilNut(&target_nalu, H265NALU::SPS_NUT));
+  int sps_id;
+  // We need to parse the SPS/PPS so the slice header can find them.
+  EXPECT_EQ(H265Parser::kOk, parser_.ParseSPS(&sps_id));
+  EXPECT_TRUE(ParseNalusUntilNut(&target_nalu, H265NALU::PPS_NUT));
+  int pps_id;
+  EXPECT_EQ(H265Parser::kOk, parser_.ParsePPS(target_nalu, &pps_id));
+
+  // Do an IDR slice header first.
+  EXPECT_TRUE(ParseNalusUntilNut(&target_nalu, H265NALU::IDR_W_RADL));
+  H265SliceHeader shdr;
+  EXPECT_EQ(H265Parser::kOk, parser_.ParseSliceHeader(target_nalu, &shdr));
+  EXPECT_TRUE(shdr.first_slice_segment_in_pic_flag);
+  EXPECT_FALSE(shdr.no_output_of_prior_pics_flag);
+  EXPECT_EQ(shdr.slice_pic_parameter_set_id, 0);
+  EXPECT_FALSE(shdr.dependent_slice_segment_flag);
+  EXPECT_EQ(shdr.slice_type, H265SliceHeader::kSliceTypeI);
+  EXPECT_TRUE(shdr.slice_sao_luma_flag);
+  EXPECT_TRUE(shdr.slice_sao_chroma_flag);
+  EXPECT_EQ(shdr.slice_qp_delta, 8);
+  EXPECT_TRUE(shdr.slice_loop_filter_across_slices_enabled_flag);
+
+  // Then do a non-IDR slice header.
+  EXPECT_TRUE(ParseNalusUntilNut(&target_nalu, H265NALU::TRAIL_R));
+  EXPECT_EQ(H265Parser::kOk, parser_.ParseSliceHeader(target_nalu, &shdr));
+  EXPECT_TRUE(shdr.first_slice_segment_in_pic_flag);
+  EXPECT_EQ(shdr.slice_pic_parameter_set_id, 0);
+  EXPECT_FALSE(shdr.dependent_slice_segment_flag);
+  EXPECT_EQ(shdr.slice_type, H265SliceHeader::kSliceTypeP);
+  EXPECT_EQ(shdr.slice_pic_order_cnt_lsb, 4);
+  EXPECT_FALSE(shdr.short_term_ref_pic_set_sps_flag);
+  EXPECT_EQ(shdr.st_ref_pic_set.num_negative_pics, 1);
+  EXPECT_EQ(shdr.st_ref_pic_set.num_positive_pics, 0);
+  EXPECT_EQ(shdr.st_ref_pic_set.delta_poc_s0[0], -4);
+  EXPECT_EQ(shdr.st_ref_pic_set.used_by_curr_pic_s0[0], 1);
+  EXPECT_TRUE(shdr.slice_temporal_mvp_enabled_flag);
+  EXPECT_TRUE(shdr.slice_sao_luma_flag);
+  EXPECT_TRUE(shdr.slice_sao_chroma_flag);
+  EXPECT_FALSE(shdr.num_ref_idx_active_override_flag);
+  EXPECT_EQ(shdr.pred_weight_table.luma_log2_weight_denom, 0);
+  EXPECT_EQ(shdr.pred_weight_table.delta_chroma_log2_weight_denom, 7);
+  EXPECT_EQ(shdr.pred_weight_table.delta_luma_weight_l0[0], 0);
+  EXPECT_EQ(shdr.pred_weight_table.luma_offset_l0[0], -2);
+  EXPECT_EQ(shdr.pred_weight_table.delta_chroma_weight_l0[0][0], -9);
+  EXPECT_EQ(shdr.pred_weight_table.delta_chroma_weight_l0[0][1], -9);
+  EXPECT_EQ(shdr.pred_weight_table.delta_chroma_offset_l0[0][0], 0);
+  EXPECT_EQ(shdr.pred_weight_table.delta_chroma_offset_l0[0][1], 0);
+  EXPECT_EQ(shdr.five_minus_max_num_merge_cand, 3);
+  EXPECT_EQ(shdr.slice_qp_delta, 8);
+  EXPECT_TRUE(shdr.slice_loop_filter_across_slices_enabled_flag);
+}
+
 }  // namespace media
diff --git a/mojo/public/cpp/bindings/BUILD.gn b/mojo/public/cpp/bindings/BUILD.gn
index a219fea..77efb306 100644
--- a/mojo/public/cpp/bindings/BUILD.gn
+++ b/mojo/public/cpp/bindings/BUILD.gn
@@ -80,7 +80,6 @@
     "lib/serialization_util.h",
     "lib/string_serialization.h",
     "lib/template_util.h",
-    "lib/tracing_helper.h",
     "lib/unserialized_message_context.cc",
     "lib/unserialized_message_context.h",
     "lib/validate_params.h",
diff --git a/mojo/public/cpp/bindings/lib/connector.cc b/mojo/public/cpp/bindings/lib/connector.cc
index ebed370..2766f8d 100644
--- a/mojo/public/cpp/bindings/lib/connector.cc
+++ b/mojo/public/cpp/bindings/lib/connector.cc
@@ -26,7 +26,6 @@
 #include "mojo/public/cpp/bindings/features.h"
 #include "mojo/public/cpp/bindings/lib/may_auto_lock.h"
 #include "mojo/public/cpp/bindings/lib/message_quota_checker.h"
-#include "mojo/public/cpp/bindings/lib/tracing_helper.h"
 #include "mojo/public/cpp/bindings/mojo_buildflags.h"
 #include "mojo/public/cpp/bindings/sync_handle_watcher.h"
 #include "mojo/public/cpp/system/wait.h"
@@ -492,8 +491,7 @@
   }
 
   TRACE_EVENT_WITH_FLOW0("toplevel.flow", "mojo::Message Receive",
-                         MANGLE_MESSAGE_ID(message.header()->trace_id),
-                         TRACE_EVENT_FLAG_FLOW_IN);
+                         message.header()->trace_id, TRACE_EVENT_FLAG_FLOW_IN);
 #if !BUILDFLAG(MOJO_TRACE_ENABLED)
   // This emits just full class name, and is inferior to mojo tracing.
   TRACE_EVENT("toplevel", "Connector::DispatchMessage",
diff --git a/mojo/public/cpp/bindings/lib/message.cc b/mojo/public/cpp/bindings/lib/message.cc
index 8c489947..d5403f93 100644
--- a/mojo/public/cpp/bindings/lib/message.cc
+++ b/mojo/public/cpp/bindings/lib/message.cc
@@ -20,9 +20,9 @@
 #include "base/strings/stringprintf.h"
 #include "base/threading/sequence_local_storage_slot.h"
 #include "base/trace_event/trace_event.h"
+#include "base/trace_event/trace_id_helper.h"
 #include "mojo/public/cpp/bindings/associated_group_controller.h"
 #include "mojo/public/cpp/bindings/lib/array_internal.h"
-#include "mojo/public/cpp/bindings/lib/tracing_helper.h"
 #include "mojo/public/cpp/bindings/lib/unserialized_message_context.h"
 
 namespace mojo {
@@ -47,19 +47,6 @@
   (*header)->num_bytes = sizeof(HeaderType);
 }
 
-uint32_t GetTraceId(void* object) {
-  // |object| is a pointer to some object, which we are going to use as
-  // a hopefully unique id for this message.
-  // Additionally xor it with a counter to protect against the situations when
-  // a new object is allocated with the same address.
-  // The counter alone is not sufficient because we also have to deal with
-  // different processes, and the counter is only process-unique.
-  static std::atomic<int> counter{0};
-  uint64_t value = reinterpret_cast<intptr_t>(object);
-  return static_cast<uint32_t>(counter.fetch_add(1, std::memory_order_relaxed) ^
-                               (value >> 32) ^ ((value << 32) >> 32));
-}
-
 void WriteMessageHeader(uint32_t name,
                         uint32_t flags,
                         uint32_t trace_id,
@@ -96,15 +83,15 @@
 
 void CreateSerializedMessageObject(uint32_t name,
                                    uint32_t flags,
-                                   uint32_t trace_id,
                                    size_t payload_size,
                                    size_t payload_interface_id_count,
                                    MojoCreateMessageFlags create_message_flags,
                                    std::vector<ScopedHandle>* handles,
                                    ScopedMessageHandle* out_handle,
                                    internal::Buffer* out_buffer) {
-  TRACE_EVENT_WITH_FLOW0("toplevel.flow", "mojo::Message Send",
-                         MANGLE_MESSAGE_ID(trace_id),
+  uint32_t trace_id =
+      static_cast<uint32_t>(base::trace_event::GetNextGlobalTraceId());
+  TRACE_EVENT_WITH_FLOW0("toplevel.flow", "mojo::Message Send", trace_id,
                          TRACE_EVENT_FLAG_FLOW_OUT);
 
   ScopedMessageHandle handle;
@@ -147,10 +134,10 @@
                                   uintptr_t context_value) {
   auto* context =
       reinterpret_cast<internal::UnserializedMessageContext*>(context_value);
-  uint32_t trace_id = GetTraceId(context);
+  uint32_t trace_id =
+      static_cast<uint32_t>(base::trace_event::GetNextGlobalTraceId());
 
-  TRACE_EVENT_WITH_FLOW0("toplevel.flow", "mojo::Message Send",
-                         MANGLE_MESSAGE_ID(trace_id),
+  TRACE_EVENT_WITH_FLOW0("toplevel.flow", "mojo::Message Send", trace_id,
                          TRACE_EVENT_FLAG_FLOW_OUT);
 
   void* buffer;
@@ -244,7 +231,7 @@
                  MojoCreateMessageFlags create_message_flags,
                  std::vector<ScopedHandle>* handles) {
   CreateSerializedMessageObject(
-      name, flags, GetTraceId(this), payload_size, payload_interface_id_count,
+      name, flags, payload_size, payload_interface_id_count,
       create_message_flags, handles, &handle_, &payload_buffer_);
   transferable_ = true;
   serialized_ = true;
diff --git a/mojo/public/cpp/bindings/lib/tracing_helper.h b/mojo/public/cpp/bindings/lib/tracing_helper.h
deleted file mode 100644
index ebc5cc2..0000000
--- a/mojo/public/cpp/bindings/lib/tracing_helper.h
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_TRACING_HELPER_H_
-#define MOJO_PUBLIC_CPP_BINDINGS_LIB_TRACING_HELPER_H_
-
-#define MANGLE_MESSAGE_ID(id) (id ^ ::mojo::internal::kMojoMessageMangleMask)
-
-namespace mojo {
-namespace internal {
-
-// Mojo message id is 32-bit, but for tracing we ensure that mojo messages
-// don't collide with other trace events.
-constexpr uint64_t kMojoMessageMangleMask = 0x655b2a8e8efdf27f;
-
-}  // namespace internal
-}  // namespace mojo
-
-#endif  // MOJO_PUBLIC_CPP_BINDINGS_LIB_TRACING_HELPER_H_
diff --git a/net/BUILD.gn b/net/BUILD.gn
index 46693b3c..63d21c0 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -439,6 +439,7 @@
       "base/host_mapping_rules.cc",
       "base/host_mapping_rules.h",
       "base/http_user_agent_settings.h",
+      "base/idempotency.h",
       "base/isolation_info.cc",
       "base/isolation_info.h",
       "base/load_flags.h",
diff --git a/net/base/idempotency.h b/net/base/idempotency.h
new file mode 100644
index 0000000..ca5c6730
--- /dev/null
+++ b/net/base/idempotency.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2012 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 NET_BASE_IDEMPOTENCY_H_
+#define NET_BASE_IDEMPOTENCY_H_
+
+namespace net {
+
+// Idempotency of the request, which determines that if it is safe to enable
+// 0-RTT for the request. By default, 0-RTT is only enabled for safe
+// HTTP methods, i.e., GET, HEAD, OPTIONS, and TRACE. For other methods,
+// enabling 0-RTT may cause security issues since a network observer can replay
+// the request. If the request has any side effects, those effects can happen
+// multiple times. It is only safe to enable the 0-RTT if it is known that
+// the request is idempotent.
+enum Idempotency {
+  DEFAULT_IDEMPOTENCY = 0,
+  IDEMPOTENT = 1,
+  NOT_IDEMPOTENT = 2,
+};
+
+}  // namespace net
+
+#endif  // NET_BASE_IDEMPOTENCY_H_
diff --git a/net/http/http_cache.cc b/net/http/http_cache.cc
index 3a1346d..0848401 100644
--- a/net/http/http_cache.cc
+++ b/net/http/http_cache.cc
@@ -11,6 +11,7 @@
 #include "base/bind_helpers.h"
 #include "base/callback.h"
 #include "base/compiler_specific.h"
+#include "base/feature_list.h"
 #include "base/files/file_util.h"
 #include "base/format_macros.h"
 #include "base/location.h"
@@ -56,6 +57,15 @@
 
 namespace net {
 
+namespace {
+// True if any HTTP cache has been initialized.
+bool g_init_cache = false;
+
+// True if split cache is enabled by default. Must be set before any HTTP cache
+// has been initialized.
+bool g_enable_split_cache = false;
+}  // namespace
+
 const char HttpCache::kDoubleKeyPrefix[] = "_dk_";
 const char HttpCache::kDoubleKeySeparator[] = " ";
 
@@ -245,7 +255,9 @@
                      bool is_main_cache)
     : HttpCache(std::make_unique<HttpNetworkLayer>(session),
                 std::move(backend_factory),
-                is_main_cache) {}
+                is_main_cache) {
+  g_init_cache = true;
+}
 
 HttpCache::HttpCache(std::unique_ptr<HttpTransactionFactory> network_layer,
                      std::unique_ptr<BackendFactory> backend_factory,
@@ -259,6 +271,7 @@
       mode_(NORMAL),
       network_layer_(std::move(network_layer)),
       clock_(base::DefaultClock::GetInstance()) {
+  g_init_cache = true;
   HttpNetworkSession* session = network_layer_->GetSession();
   // Session may be NULL in unittests.
   // TODO(mmenke): Seems like tests could be changed to provide a session,
@@ -455,6 +468,29 @@
   return GenerateCacheKey(request);
 }
 
+// static
+void HttpCache::SplitCacheFeatureEnableByDefault() {
+  CHECK(!g_enable_split_cache && !g_init_cache);
+  if (!base::FeatureList::GetInstance()->IsFeatureOverridden(
+          "SplitCacheByNetworkIsolationKey")) {
+    g_enable_split_cache = true;
+  }
+}
+
+// static
+bool HttpCache::IsSplitCacheEnabled() {
+  return base::FeatureList::IsEnabled(
+             features::kSplitCacheByNetworkIsolationKey) ||
+         g_enable_split_cache;
+}
+
+// static
+void HttpCache::ClearGlobalsForTesting() {
+  // Reset these so that unit tests can work.
+  g_init_cache = false;
+  g_enable_split_cache = false;
+}
+
 //-----------------------------------------------------------------------------
 
 net::Error HttpCache::CreateAndSetWorkItem(ActiveEntry** entry,
@@ -532,8 +568,7 @@
 std::string HttpCache::GenerateCacheKey(const HttpRequestInfo* request) {
   std::string isolation_key;
 
-  if (base::FeatureList::IsEnabled(
-          features::kSplitCacheByNetworkIsolationKey)) {
+  if (IsSplitCacheEnabled()) {
     // Prepend the key with |kDoubleKeyPrefix| = "_dk_" to mark it as
     // double-keyed (and makes it an invalid url so that it doesn't get
     // confused with a single-keyed entry). Separate the origin and url
diff --git a/net/http/http_cache.h b/net/http/http_cache.h
index 9351e11a..a2d0baee 100644
--- a/net/http/http_cache.h
+++ b/net/http/http_cache.h
@@ -272,6 +272,18 @@
   // Function to generate cache key for testing.
   static std::string GenerateCacheKeyForTest(const HttpRequestInfo* request);
 
+  // Enable split cache feature if not already overridden in the feature list.
+  // Should only be invoked during process initialization before the HTTP
+  // cache is initialized.
+  static void SplitCacheFeatureEnableByDefault();
+
+  // Returns true if split cache is enabled either by default or by other means
+  // like command line or field trials.
+  static bool IsSplitCacheEnabled();
+
+  // Resets g_init_cache and g_enable_split_cache for tests.
+  static void ClearGlobalsForTesting();
+
  private:
   // Types --------------------------------------------------------------------
 
diff --git a/net/http/http_cache_transaction.cc b/net/http/http_cache_transaction.cc
index a34fbc5..eb702e9 100644
--- a/net/http/http_cache_transaction.cc
+++ b/net/http/http_cache_transaction.cc
@@ -1831,8 +1831,7 @@
   // invalidate in the cache.
   if (!(effective_load_flags_ & LOAD_DISABLE_CACHE) && method_ == "POST" &&
       NonErrorResponse(new_response_->headers->response_code()) &&
-      (!base::FeatureList::IsEnabled(
-           net::features::kSplitCacheByNetworkIsolationKey) ||
+      (!HttpCache::IsSplitCacheEnabled() ||
        request_->network_isolation_key.IsFullyPopulated())) {
     cache_->DoomMainEntryForUrl(request_->url, request_->network_isolation_key);
   }
@@ -2441,8 +2440,7 @@
   // again. Also, if the request does not have a top frame origin, bypass the
   // cache otherwise resources from different pages could share a cached entry
   // in such cases.
-  else if (base::FeatureList::IsEnabled(
-               features::kSplitCacheByNetworkIsolationKey) &&
+  else if (HttpCache::IsSplitCacheEnabled() &&
            request_->network_isolation_key.IsTransient()) {
     cacheable = false;
   } else if (method_ == "GET" || method_ == "HEAD") {
diff --git a/net/http/http_cache_unittest.cc b/net/http/http_cache_unittest.cc
index 5c884dc..191e85e 100644
--- a/net/http/http_cache_unittest.cc
+++ b/net/http/http_cache_unittest.cc
@@ -10758,6 +10758,47 @@
   EXPECT_FALSE(response.was_cached);
 }
 
+TEST_F(HttpCacheTest, SplitCacheEnabledByDefault) {
+  HttpCache::ClearGlobalsForTesting();
+  HttpCache::SplitCacheFeatureEnableByDefault();
+  EXPECT_TRUE(HttpCache::IsSplitCacheEnabled());
+
+  MockHttpCache cache;
+  HttpResponseInfo response;
+
+  url::Origin origin_a = url::Origin::Create(GURL("http://a.com"));
+  url::Origin origin_b = url::Origin::Create(GURL("http://b.com"));
+  MockHttpRequest trans_info = MockHttpRequest(kSimpleGET_Transaction);
+  net::NetworkIsolationKey key_a(origin_a, origin_a);
+  trans_info.network_isolation_key = key_a;
+  RunTransactionTestWithRequest(cache.http_cache(), kSimpleGET_Transaction,
+                                trans_info, &response);
+  EXPECT_FALSE(response.was_cached);
+
+  // Subsequent requests with the same NIK and different NIK will be a cache hit
+  // and miss respectively.
+  RunTransactionTestWithRequest(cache.http_cache(), kSimpleGET_Transaction,
+                                trans_info, &response);
+  EXPECT_TRUE(response.was_cached);
+
+  net::NetworkIsolationKey key_b(origin_b, origin_b);
+  trans_info.network_isolation_key = key_b;
+  RunTransactionTestWithRequest(cache.http_cache(), kSimpleGET_Transaction,
+                                trans_info, &response);
+  EXPECT_FALSE(response.was_cached);
+}
+
+TEST_F(HttpCacheTest, SplitCacheEnabledByDefaultButOverridden) {
+  HttpCache::ClearGlobalsForTesting();
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndDisableFeature(
+      net::features::kSplitCacheByNetworkIsolationKey);
+
+  // Enabling it here should have no effect as it is already overridden.
+  HttpCache::SplitCacheFeatureEnableByDefault();
+  EXPECT_FALSE(HttpCache::IsSplitCacheEnabled());
+}
+
 TEST_F(HttpCacheTest, SplitCacheUsesRegistrableDomain) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeature(
diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc
index ecca1c2..07e461a 100644
--- a/net/http/http_network_transaction.cc
+++ b/net/http/http_network_transaction.cc
@@ -192,7 +192,9 @@
     proxy_ssl_config_.disable_cert_verification_network_fetches = true;
   }
 
-  if (HttpUtil::IsMethodSafe(request_info->method)) {
+  if (request_->idempotency == IDEMPOTENT ||
+      (request_->idempotency == DEFAULT_IDEMPOTENCY &&
+       HttpUtil::IsMethodSafe(request_info->method))) {
     can_send_early_data_ = true;
   }
 
diff --git a/net/http/http_request_info.cc b/net/http/http_request_info.cc
index 1410e3b1a..9e12e79 100644
--- a/net/http/http_request_info.cc
+++ b/net/http/http_request_info.cc
@@ -11,7 +11,8 @@
       load_flags(0),
       privacy_mode(PRIVACY_MODE_DISABLED),
       disable_secure_dns(false),
-      reporting_upload_depth(0) {}
+      reporting_upload_depth(0),
+      idempotency(net::DEFAULT_IDEMPOTENCY) {}
 
 HttpRequestInfo::HttpRequestInfo(const HttpRequestInfo& other) = default;
 
diff --git a/net/http/http_request_info.h b/net/http/http_request_info.h
index 8771765..c75fc1fe 100644
--- a/net/http/http_request_info.h
+++ b/net/http/http_request_info.h
@@ -8,6 +8,7 @@
 #include <string>
 
 #include "base/optional.h"
+#include "net/base/idempotency.h"
 #include "net/base/net_export.h"
 #include "net/base/network_isolation_key.h"
 #include "net/base/privacy_mode.h"
@@ -73,6 +74,15 @@
   // this to NetworkIsolationKey::TopFrameSite().  That gives more consistent
   /// behavior, and may still provide useful metrics.
   base::Optional<url::Origin> possibly_top_frame_origin;
+
+  // Idempotency of the request, which determines that if it is safe to enable
+  // 0-RTT for the request. By default, 0-RTT is only enabled for safe
+  // HTTP methods, i.e., GET, HEAD, OPTIONS, and TRACE. For other methods,
+  // enabling 0-RTT may cause security issues since a network observer can
+  // replay the request. If the request has any side effects, those effects can
+  // happen multiple times. It is only safe to enable the 0-RTT if it is known
+  // that the request is idempotent.
+  net::Idempotency idempotency;
 };
 
 }  // namespace net
diff --git a/net/url_request/url_request.h b/net/url_request/url_request.h
index adeb342..eed0ae9 100644
--- a/net/url_request/url_request.h
+++ b/net/url_request/url_request.h
@@ -19,6 +19,7 @@
 #include "base/threading/thread_checker.h"
 #include "base/time/time.h"
 #include "net/base/auth.h"
+#include "net/base/idempotency.h"
 #include "net/base/ip_endpoint.h"
 #include "net/base/isolation_info.h"
 #include "net/base/load_states.h"
@@ -711,6 +712,9 @@
     send_client_certs_ = send_client_certs;
   }
 
+  void SetIdempotency(Idempotency idempotency) { idempotency_ = idempotency; }
+  Idempotency GetIdempotency() const { return idempotency_; }
+
   base::WeakPtr<URLRequest> GetWeakPtr();
 
  protected:
@@ -948,6 +952,9 @@
 
   bool send_client_certs_ = true;
 
+  // Idempotency of the request.
+  Idempotency idempotency_ = DEFAULT_IDEMPOTENCY;
+
   THREAD_CHECKER(thread_checker_);
 
   base::WeakPtrFactory<URLRequest> weak_factory_{this};
diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_request_http_job.cc
index 2112797e..c9985fd 100644
--- a/net/url_request/url_request_http_job.cc
+++ b/net/url_request/url_request_http_job.cc
@@ -299,6 +299,7 @@
   request_info_.traffic_annotation =
       net::MutableNetworkTrafficAnnotationTag(request_->traffic_annotation());
   request_info_.socket_tag = request_->socket_tag();
+  request_info_.idempotency = request_->GetIdempotency();
 #if BUILDFLAG(ENABLE_REPORTING)
   request_info_.reporting_upload_depth = request_->reporting_upload_depth();
 #endif
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc
index 16df2a1..9bfa245 100644
--- a/net/url_request/url_request_unittest.cc
+++ b/net/url_request/url_request_unittest.cc
@@ -12231,6 +12231,122 @@
   }
 }
 
+// TLSEarlyDataTest tests that the 0-RTT is enabled for idempotent POST request.
+TEST_F(HTTPSEarlyDataTest, TLSEarlyDataIdempotentPOSTTest) {
+  ASSERT_TRUE(test_server_.Start());
+  context_.http_transaction_factory()->GetSession()->ClearSSLSessionCache();
+  const int kParamSize = 4 * 1024;
+  const GURL kUrl =
+      test_server_.GetURL("/zerortt?" + std::string(kParamSize, 'a'));
+
+  {
+    TestDelegate d;
+    std::unique_ptr<URLRequest> r(context_.CreateRequest(
+        kUrl, DEFAULT_PRIORITY, &d, TRAFFIC_ANNOTATION_FOR_TESTS));
+    r->Start();
+    EXPECT_TRUE(r->is_pending());
+
+    base::RunLoop().Run();
+
+    EXPECT_EQ(1, d.response_started_count());
+
+    EXPECT_EQ(SSL_CONNECTION_VERSION_TLS1_3,
+              SSLConnectionStatusToVersion(r->ssl_info().connection_status));
+    EXPECT_TRUE(r->ssl_info().unverified_cert.get());
+    EXPECT_TRUE(test_server_.GetCertificate()->EqualsIncludingChain(
+        r->ssl_info().cert.get()));
+
+    // The Early-Data header should be omitted in the initial request, and the
+    // handler should return "0".
+    EXPECT_EQ("0", d.data_received());
+  }
+
+  context_.http_transaction_factory()->GetSession()->CloseAllConnections(
+      ERR_FAILED, "Very good reason");
+  listener_.BufferNextConnection(kParamSize);
+
+  {
+    TestDelegate d;
+    std::unique_ptr<URLRequest> r(context_.CreateRequest(
+        kUrl, DEFAULT_PRIORITY, &d, TRAFFIC_ANNOTATION_FOR_TESTS));
+    r->set_method("POST");
+    r->SetIdempotency(net::IDEMPOTENT);
+    r->Start();
+    EXPECT_TRUE(r->is_pending());
+
+    base::RunLoop().Run();
+
+    EXPECT_EQ(1, d.response_started_count());
+
+    EXPECT_EQ(SSL_CONNECTION_VERSION_TLS1_3,
+              SSLConnectionStatusToVersion(r->ssl_info().connection_status));
+    EXPECT_TRUE(r->ssl_info().unverified_cert.get());
+    EXPECT_TRUE(test_server_.GetCertificate()->EqualsIncludingChain(
+        r->ssl_info().cert.get()));
+
+    // The Early-Data header should be set since the request is set as an
+    // idempotent POST request.
+    EXPECT_EQ("1", d.data_received());
+  }
+}
+
+// TLSEarlyDataTest tests that the 0-RTT is disabled for non-idempotent request.
+TEST_F(HTTPSEarlyDataTest, TLSEarlyDataNonIdempotentRequestTest) {
+  ASSERT_TRUE(test_server_.Start());
+  context_.http_transaction_factory()->GetSession()->ClearSSLSessionCache();
+
+  {
+    TestDelegate d;
+    std::unique_ptr<URLRequest> r(context_.CreateRequest(
+        test_server_.GetURL("/zerortt"), DEFAULT_PRIORITY, &d,
+        TRAFFIC_ANNOTATION_FOR_TESTS));
+    r->Start();
+    EXPECT_TRUE(r->is_pending());
+
+    base::RunLoop().Run();
+
+    EXPECT_EQ(1, d.response_started_count());
+
+    EXPECT_EQ(SSL_CONNECTION_VERSION_TLS1_3,
+              SSLConnectionStatusToVersion(r->ssl_info().connection_status));
+    EXPECT_TRUE(r->ssl_info().unverified_cert.get());
+    EXPECT_TRUE(test_server_.GetCertificate()->EqualsIncludingChain(
+        r->ssl_info().cert.get()));
+
+    // The Early-Data header should be omitted in the initial request, and the
+    // handler should return "0".
+    EXPECT_EQ("0", d.data_received());
+  }
+
+  context_.http_transaction_factory()->GetSession()->CloseAllConnections(
+      ERR_FAILED, "Very good reason");
+
+  {
+    TestDelegate d;
+    std::unique_ptr<URLRequest> r(context_.CreateRequest(
+        test_server_.GetURL("/zerortt"), DEFAULT_PRIORITY, &d,
+        TRAFFIC_ANNOTATION_FOR_TESTS));
+    // Sets the GET request as not idempotent.
+    r->SetIdempotency(net::NOT_IDEMPOTENT);
+    r->Start();
+    EXPECT_TRUE(r->is_pending());
+
+    base::RunLoop().Run();
+
+    EXPECT_EQ(1, d.response_started_count());
+
+    EXPECT_EQ(SSL_CONNECTION_VERSION_TLS1_3,
+              SSLConnectionStatusToVersion(r->ssl_info().connection_status));
+    EXPECT_TRUE(r->ssl_info().unverified_cert.get());
+    EXPECT_TRUE(test_server_.GetCertificate()->EqualsIncludingChain(
+        r->ssl_info().cert.get()));
+
+    // The Early-Data header should be omitted in the initial request even
+    // though it is a GET request, since the request is set as not idempotent.
+    EXPECT_EQ("0", d.data_received());
+  }
+}
+
 std::unique_ptr<test_server::HttpResponse> HandleTooEarly(
     bool* sent_425,
     const test_server::HttpRequest& request) {
diff --git a/services/network/trust_tokens/BUILD.gn b/services/network/trust_tokens/BUILD.gn
index 7f2b69a..c22a337 100644
--- a/services/network/trust_tokens/BUILD.gn
+++ b/services/network/trust_tokens/BUILD.gn
@@ -31,6 +31,8 @@
     "in_memory_trust_token_persister.cc",
     "in_memory_trust_token_persister.h",
     "local_trust_token_operation_delegate.h",
+    "local_trust_token_operation_delegate_impl.cc",
+    "local_trust_token_operation_delegate_impl.h",
     "operating_system_matching.cc",
     "operating_system_matching.h",
     "operation_timing_request_helper_wrapper.cc",
@@ -136,6 +138,7 @@
     "ed25519_trust_token_request_signer_unittest.cc",
     "expiry_inspecting_record_expiry_delegate_unittest.cc",
     "has_trust_tokens_answerer_unittest.cc",
+    "local_trust_token_operation_delegate_impl_unittest.cc",
     "pending_trust_token_store_unittest.cc",
     "signed_redemption_record_serialization_unittest.cc",
     "sqlite_trust_token_persister_unittest.cc",
diff --git a/services/network/trust_tokens/local_trust_token_operation_delegate_impl.cc b/services/network/trust_tokens/local_trust_token_operation_delegate_impl.cc
new file mode 100644
index 0000000..7afbd1a
--- /dev/null
+++ b/services/network/trust_tokens/local_trust_token_operation_delegate_impl.cc
@@ -0,0 +1,31 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/network/trust_tokens/local_trust_token_operation_delegate_impl.h"
+
+namespace network {
+
+LocalTrustTokenOperationDelegateImpl::LocalTrustTokenOperationDelegateImpl(
+    base::RepeatingCallback<mojom::NetworkContextClient*(void)> client_provider)
+    : client_provider_(std::move(client_provider)) {}
+
+LocalTrustTokenOperationDelegateImpl::~LocalTrustTokenOperationDelegateImpl() =
+    default;
+
+void LocalTrustTokenOperationDelegateImpl::FulfillIssuance(
+    mojom::FulfillTrustTokenIssuanceRequestPtr request,
+    base::OnceCallback<void(mojom::FulfillTrustTokenIssuanceAnswerPtr)> done) {
+  mojom::NetworkContextClient* client = client_provider_.Run();
+  if (!client) {
+    auto answer = mojom::FulfillTrustTokenIssuanceAnswer::New();
+    answer->status = mojom::FulfillTrustTokenIssuanceAnswer::Status::kNotFound;
+    std::move(done).Run(std::move(answer));
+    return;
+  }
+
+  client->OnTrustTokenIssuanceDivertedToSystem(std::move(request),
+                                               std::move(done));
+}
+
+}  // namespace network
diff --git a/services/network/trust_tokens/local_trust_token_operation_delegate_impl.h b/services/network/trust_tokens/local_trust_token_operation_delegate_impl.h
new file mode 100644
index 0000000..93c3521
--- /dev/null
+++ b/services/network/trust_tokens/local_trust_token_operation_delegate_impl.h
@@ -0,0 +1,49 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_NETWORK_TRUST_TOKENS_LOCAL_TRUST_TOKEN_OPERATION_DELEGATE_IMPL_H_
+#define SERVICES_NETWORK_TRUST_TOKENS_LOCAL_TRUST_TOKEN_OPERATION_DELEGATE_IMPL_H_
+
+#include "base/callback.h"
+#include "services/network/public/mojom/network_context.mojom.h"
+#include "services/network/public/mojom/trust_tokens.mojom.h"
+#include "services/network/trust_tokens/local_trust_token_operation_delegate.h"
+
+namespace network {
+
+// LocalTrustTokenOperationDelegateImpl provides an interface for executing
+// Trust Tokens "against the platform," i.e. via some kind of operating system
+// mediation rather than through the typical method of a direct HTTP request to
+// a counterparty.
+class LocalTrustTokenOperationDelegateImpl
+    : public LocalTrustTokenOperationDelegate {
+ public:
+  // |client_provider| provides a handle to a NetworkContextClient that will be
+  // used for requesting Trust Tokens operations' local execution.
+  //
+  // client_provider.Run() will be called before each attempt to delegate a
+  // Trust Tokens operation. It is permitted to return nullptr; in this case,
+  // the operation will be cancelled.
+  explicit LocalTrustTokenOperationDelegateImpl(
+      base::RepeatingCallback<mojom::NetworkContextClient*(void)>
+          client_provider);
+
+  ~LocalTrustTokenOperationDelegateImpl() override;
+
+  // FulfillIssuance attempts to execute the given Trust Tokens operation
+  // locally, on conclusion populating |done| with either a response or a
+  // suitable status as described in FulfillTrustTokenIssuanceAnswer's struct
+  // comments.
+  void FulfillIssuance(
+      mojom::FulfillTrustTokenIssuanceRequestPtr request,
+      base::OnceCallback<void(mojom::FulfillTrustTokenIssuanceAnswerPtr)> done)
+      override;
+
+ private:
+  base::RepeatingCallback<mojom::NetworkContextClient*(void)> client_provider_;
+};
+
+}  // namespace network
+
+#endif  // SERVICES_NETWORK_TRUST_TOKENS_LOCAL_TRUST_TOKEN_OPERATION_DELEGATE_IMPL_H_
diff --git a/services/network/trust_tokens/local_trust_token_operation_delegate_impl_unittest.cc b/services/network/trust_tokens/local_trust_token_operation_delegate_impl_unittest.cc
new file mode 100644
index 0000000..9957fcd8
--- /dev/null
+++ b/services/network/trust_tokens/local_trust_token_operation_delegate_impl_unittest.cc
@@ -0,0 +1,91 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/network/trust_tokens/local_trust_token_operation_delegate_impl.h"
+
+#include "base/callback.h"
+#include "base/run_loop.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/mock_callback.h"
+#include "base/test/task_environment.h"
+#include "services/network/public/mojom/trust_tokens.mojom.h"
+#include "services/network/test/test_network_context_client.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace network {
+
+namespace {
+
+using ::testing::Field;
+using ::testing::Pointee;
+
+TEST(LocalTrustTokenOperationDelegateImpl, HandlesNullClient) {
+  LocalTrustTokenOperationDelegateImpl delegate(base::BindRepeating(
+      []() -> mojom::NetworkContextClient* { return nullptr; }));
+
+  auto request = mojom::FulfillTrustTokenIssuanceRequest::New();
+
+  base::MockOnceCallback<void(mojom::FulfillTrustTokenIssuanceAnswerPtr)>
+      callback;
+
+  EXPECT_CALL(callback,
+              Run(Pointee(Field(
+                  &mojom::FulfillTrustTokenIssuanceAnswer::status,
+                  mojom::FulfillTrustTokenIssuanceAnswer::Status::kNotFound))));
+
+  delegate.FulfillIssuance(std::move(request), callback.Get());
+}
+
+class RequestCapturingNetworkContextClient : public TestNetworkContextClient {
+ public:
+  explicit RequestCapturingNetworkContextClient(
+      mojom::FulfillTrustTokenIssuanceRequestPtr& request_out)
+      : request_out_(request_out) {}
+
+  void OnTrustTokenIssuanceDivertedToSystem(
+      mojom::FulfillTrustTokenIssuanceRequestPtr request,
+      OnTrustTokenIssuanceDivertedToSystemCallback callback) override {
+    request_out_ = std::move(request);
+    std::move(callback).Run(mojom::FulfillTrustTokenIssuanceAnswer::New(
+        mojom::FulfillTrustTokenIssuanceAnswer::Status::kOk,
+        "Here's your answer"));
+  }
+
+ private:
+  mojom::FulfillTrustTokenIssuanceRequestPtr& request_out_;
+};
+
+TEST(LocalTrustTokenOperationDelegateImpl, RelaysRequestAndAnswer) {
+  base::test::TaskEnvironment task_environment;
+  mojom::FulfillTrustTokenIssuanceRequestPtr relayed_request;
+  auto client =
+      std::make_unique<RequestCapturingNetworkContextClient>(relayed_request);
+  base::RunLoop run_loop;
+
+  LocalTrustTokenOperationDelegateImpl delegate(base::BindLambdaForTesting(
+      [&client]() -> mojom::NetworkContextClient* { return client.get(); }));
+
+  auto request = mojom::FulfillTrustTokenIssuanceRequest::New();
+  request->request = "Please give me an answer";
+
+  delegate.FulfillIssuance(
+      std::move(request),
+      base::BindLambdaForTesting(
+          [&run_loop](mojom::FulfillTrustTokenIssuanceAnswerPtr answer) {
+            run_loop.Quit();
+            ASSERT_TRUE(answer);
+            EXPECT_EQ(answer->status,
+                      mojom::FulfillTrustTokenIssuanceAnswer::Status::kOk);
+            EXPECT_EQ(answer->response, "Here's your answer");
+          }));
+  run_loop.Run();
+
+  ASSERT_TRUE(relayed_request);
+  EXPECT_EQ(relayed_request->request, "Please give me an answer");
+}
+
+}  // namespace
+
+}  // namespace network
diff --git a/services/network/trust_tokens/trust_token_request_helper_factory.cc b/services/network/trust_tokens/trust_token_request_helper_factory.cc
index 9a482d2..21d53e7 100644
--- a/services/network/trust_tokens/trust_token_request_helper_factory.cc
+++ b/services/network/trust_tokens/trust_token_request_helper_factory.cc
@@ -22,13 +22,13 @@
 #include "services/network/trust_tokens/ed25519_key_pair_generator.h"
 #include "services/network/trust_tokens/ed25519_trust_token_request_signer.h"
 #include "services/network/trust_tokens/local_trust_token_operation_delegate.h"
+#include "services/network/trust_tokens/local_trust_token_operation_delegate_impl.h"
 #include "services/network/trust_tokens/operating_system_matching.h"
 #include "services/network/trust_tokens/suitable_trust_token_origin.h"
 #include "services/network/trust_tokens/trust_token_http_headers.h"
 #include "services/network/trust_tokens/trust_token_key_commitment_controller.h"
 #include "services/network/trust_tokens/trust_token_parameterization.h"
 #include "services/network/trust_tokens/trust_token_request_canonicalizer.h"
-#include "services/network/trust_tokens/trust_token_request_issuance_helper.h"
 #include "services/network/trust_tokens/trust_token_request_redemption_helper.h"
 #include "services/network/trust_tokens/trust_token_request_signing_helper.h"
 #include "services/network/trust_tokens/types.h"
@@ -80,9 +80,12 @@
 TrustTokenRequestHelperFactory::TrustTokenRequestHelperFactory(
     PendingTrustTokenStore* store,
     const TrustTokenKeyCommitmentGetter* key_commitment_getter,
+    base::RepeatingCallback<mojom::NetworkContextClient*(void)>
+        context_client_provider,
     base::RepeatingCallback<bool(void)> authorizer)
     : store_(store),
       key_commitment_getter_(key_commitment_getter),
+      context_client_provider_(std::move(context_client_provider)),
       authorizer_(std::move(authorizer)) {}
 TrustTokenRequestHelperFactory::~TrustTokenRequestHelperFactory() = default;
 
@@ -129,24 +132,6 @@
       base::Passed(params.Clone()), request.net_log(), std::move(done)));
 }
 
-namespace {
-
-// TODO(crbug.com/1130272): Delete this upon adding a concrete instantiation
-// of the delegate.
-class NotImplementedLocalTrustTokenOperationDelegate
-    : public LocalTrustTokenOperationDelegate {
-  void FulfillIssuance(
-      mojom::FulfillTrustTokenIssuanceRequestPtr request,
-      base::OnceCallback<void(mojom::FulfillTrustTokenIssuanceAnswerPtr)> done)
-      override {
-    auto answer = mojom::FulfillTrustTokenIssuanceAnswer::New();
-    answer->status = mojom::FulfillTrustTokenIssuanceAnswer::Status::kNotFound;
-    std::move(done).Run(std::move(answer));
-  }
-};
-
-}  // namespace
-
 void TrustTokenRequestHelperFactory::ConstructHelperUsingStore(
     SuitableTrustTokenOrigin top_frame_origin,
     mojom::TrustTokenParamsPtr params,
@@ -163,8 +148,8 @@
           new TrustTokenRequestIssuanceHelper(
               std::move(top_frame_origin), store, key_commitment_getter_,
               std::make_unique<BoringsslTrustTokenIssuanceCryptographer>(),
-              std::make_unique<
-                  NotImplementedLocalTrustTokenOperationDelegate>(),
+              std::make_unique<LocalTrustTokenOperationDelegateImpl>(
+                  context_client_provider_),
               base::BindRepeating(&IsCurrentOperatingSystem),
               std::move(net_log))));
       return;
diff --git a/services/network/trust_tokens/trust_token_request_helper_factory.h b/services/network/trust_tokens/trust_token_request_helper_factory.h
index 3bbbd47..27d00ea 100644
--- a/services/network/trust_tokens/trust_token_request_helper_factory.h
+++ b/services/network/trust_tokens/trust_token_request_helper_factory.h
@@ -13,10 +13,12 @@
 #include "net/log/net_log_with_source.h"
 #include "services/network/public/cpp/features.h"
 #include "services/network/public/mojom/trust_tokens.mojom.h"
+#include "services/network/trust_tokens/local_trust_token_operation_delegate.h"
 #include "services/network/trust_tokens/pending_trust_token_store.h"
 #include "services/network/trust_tokens/suitable_trust_token_origin.h"
 #include "services/network/trust_tokens/trust_token_key_commitment_getter.h"
 #include "services/network/trust_tokens/trust_token_request_helper.h"
+#include "services/network/trust_tokens/trust_token_request_issuance_helper.h"
 
 namespace network {
 
@@ -55,12 +57,20 @@
   // Tokens state and |key_commitment_getter| to obtain keys; consequently, both
   // arguments must outlive all of the created helpers.
   //
+  // |context_client_provider| provides a handle to a NetworkContextClient that
+  // will be used for requesting Trust Tokens operations' local execution.
+  // context_client_provider.Run() will be called before each attempt to
+  // delegate a Trust Tokens operation. It is permitted to return nullptr; in
+  // this case, the operation will be cancelled.
+  //
   // Each decision whether to vend a helper will first query |authorizer| to
   // determine whether it's currently allowed to execute Trust Tokens
   // operations.
   TrustTokenRequestHelperFactory(
       PendingTrustTokenStore* store,
       const TrustTokenKeyCommitmentGetter* key_commitment_getter,
+      base::RepeatingCallback<mojom::NetworkContextClient*(void)>
+          context_client_provider,
       base::RepeatingCallback<bool(void)> authorizer);
 
   TrustTokenRequestHelperFactory(const TrustTokenRequestHelperFactory&) =
@@ -102,6 +112,8 @@
 
   PendingTrustTokenStore* store_;
   const TrustTokenKeyCommitmentGetter* key_commitment_getter_;
+  base::RepeatingCallback<mojom::NetworkContextClient*(void)>
+      context_client_provider_;
   base::RepeatingCallback<bool(void)> authorizer_;
 
   base::WeakPtrFactory<TrustTokenRequestHelperFactory> weak_factory_{this};
diff --git a/services/network/trust_tokens/trust_token_request_helper_factory_unittest.cc b/services/network/trust_tokens/trust_token_request_helper_factory_unittest.cc
index e5fce76..586f334 100644
--- a/services/network/trust_tokens/trust_token_request_helper_factory_unittest.cc
+++ b/services/network/trust_tokens/trust_token_request_helper_factory_unittest.cc
@@ -95,8 +95,11 @@
     store.OnStoreReady(TrustTokenStore::CreateForTesting());
     NoopTrustTokenKeyCommitmentGetter getter;
 
-    TrustTokenRequestHelperFactory(&store, &getter,
-                                   base::BindRepeating([]() { return true; }))
+    TrustTokenRequestHelperFactory(
+        &store, &getter,
+        base::BindRepeating(
+            []() -> mojom::NetworkContextClient* { return nullptr; }),
+        base::BindRepeating([]() { return true; }))
         .CreateTrustTokenHelperForRequest(
             request, params,
             base::BindLambdaForTesting(
@@ -269,8 +272,11 @@
   store.OnStoreReady(TrustTokenStore::CreateForTesting());
   NoopTrustTokenKeyCommitmentGetter getter;
 
-  TrustTokenRequestHelperFactory(&store, &getter,
-                                 base::BindRepeating([]() { return false; }))
+  TrustTokenRequestHelperFactory(
+      &store, &getter,
+      base::BindRepeating(
+          []() -> mojom::NetworkContextClient* { return nullptr; }),
+      base::BindRepeating([]() { return false; }))
       .CreateTrustTokenHelperForRequest(
           suitable_request(), suitable_signing_params(),
           base::BindLambdaForTesting(
diff --git a/services/network/url_loader_factory.cc b/services/network/url_loader_factory.cc
index 7b9ec04..2b18ba12 100644
--- a/services/network/url_loader_factory.cc
+++ b/services/network/url_loader_factory.cc
@@ -26,6 +26,7 @@
 #include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/mojom/network_context.mojom.h"
 #include "services/network/resource_scheduler/resource_scheduler_client.h"
+#include "services/network/trust_tokens/local_trust_token_operation_delegate_impl.h"
 #include "services/network/trust_tokens/trust_token_request_helper_factory.h"
 #include "services/network/url_loader.h"
 #include "url/gurl.h"
@@ -220,6 +221,11 @@
     trust_token_factory = std::make_unique<TrustTokenRequestHelperFactory>(
         context_->trust_token_store(),
         context_->network_service()->trust_token_key_commitments(),
+        // It's safe to use Unretained because |context_| is guaranteed to
+        // outlive the URLLoader that will own this
+        // TrustTokenRequestHelperFactory.
+        base::BindRepeating(&NetworkContext::client,
+                            base::Unretained(context_)),
         // It's safe to use Unretained here because
         // NetworkContext::CookieManager outlives the URLLoaders associated with
         // the NetworkContext.
diff --git a/services/network/url_loader_unittest.cc b/services/network/url_loader_unittest.cc
index 4385e39..1f9181a 100644
--- a/services/network/url_loader_unittest.cc
+++ b/services/network/url_loader_unittest.cc
@@ -81,6 +81,7 @@
 #include "services/network/public/mojom/cookie_access_observer.mojom-forward.h"
 #include "services/network/public/mojom/cookie_access_observer.mojom.h"
 #include "services/network/public/mojom/ip_address_space.mojom.h"
+#include "services/network/public/mojom/network_context.mojom.h"
 #include "services/network/public/mojom/origin_policy_manager.mojom.h"
 #include "services/network/public/mojom/trust_tokens.mojom-shared.h"
 #include "services/network/public/mojom/url_loader.mojom.h"
@@ -5539,15 +5540,21 @@
 base::NoDestructor<NoopTrustTokenKeyCommitmentGetter>
     noop_key_commitment_getter{};
 
+mojom::NetworkContextClient* ReturnNullNetworkContextClient() {
+  return nullptr;
+}
+
 class MockTrustTokenRequestHelperFactory
     : public TrustTokenRequestHelperFactory {
  public:
   MockTrustTokenRequestHelperFactory(
       mojom::TrustTokenOperationStatus creation_failure_error,
       SyncOrAsync sync_or_async)
-      : TrustTokenRequestHelperFactory(nullptr,
-                                       noop_key_commitment_getter.get(),
-                                       {}),
+      : TrustTokenRequestHelperFactory(
+            nullptr,
+            noop_key_commitment_getter.get(),
+            base::BindRepeating(&ReturnNullNetworkContextClient),
+            {}),
         sync_or_async_(sync_or_async),
         creation_failure_error_(creation_failure_error) {}
 
@@ -5556,9 +5563,11 @@
       base::Optional<mojom::TrustTokenOperationStatus> on_finalize,
       SyncOrAsync sync_or_async,
       bool* begin_done_flag)
-      : TrustTokenRequestHelperFactory(nullptr,
-                                       noop_key_commitment_getter.get(),
-                                       {}),
+      : TrustTokenRequestHelperFactory(
+            nullptr,
+            noop_key_commitment_getter.get(),
+            base::BindRepeating(&ReturnNullNetworkContextClient),
+            {}),
         sync_or_async_(sync_or_async),
         helper_(
             std::make_unique<MockTrustTokenRequestHelper>(on_begin,
diff --git a/testing/trigger_scripts/base_test_triggerer.py b/testing/trigger_scripts/base_test_triggerer.py
index ba5ec42e..252d87e 100755
--- a/testing/trigger_scripts/base_test_triggerer.py
+++ b/testing/trigger_scripts/base_test_triggerer.py
@@ -304,7 +304,10 @@
                                         suffix='.json')
         args_to_pass = self.modify_args(filtered_remaining_args, bot_index,
                                         shard_index, args.shards, json_temp)
-        ret = self.run_swarming(args_to_pass, verbose)
+        if args.use_swarming_go:
+          ret = self.run_swarming_go(args_to_pass, verbose)
+        else:
+          ret = self.run_swarming(args_to_pass, verbose)
         if ret:
           sys.stderr.write('Failed to trigger a task, aborting\n')
           return ret
@@ -345,5 +348,14 @@
     parser.add_argument('--shard-index', type=int, default=None,
                         help='Which shard to trigger. Duplicated from the '
                              '`swarming.py trigger` command.')
+    BaseTestTriggerer.add_use_swarming_go_arg(parser)
     return parser
 
+  @staticmethod
+  def add_use_swarming_go_arg(parser):
+    parser.add_argument(
+        '--use-swarming-go',
+        default=False,
+        action='store_true',
+        help='Uses swarming Go CLI to trigger tasks.')
+
diff --git a/testing/trigger_scripts/base_test_triggerer_unittest.py b/testing/trigger_scripts/base_test_triggerer_unittest.py
index 607fcae0..deaf5a7 100644
--- a/testing/trigger_scripts/base_test_triggerer_unittest.py
+++ b/testing/trigger_scripts/base_test_triggerer_unittest.py
@@ -4,6 +4,7 @@
 
 """Tests for base_device_trigger.py."""
 
+import argparse
 import unittest
 
 import base_test_triggerer
@@ -56,5 +57,16 @@
       self.assertRaises(ex, base_test_triggerer._convert_to_go_swarming_args,
                         args)
 
+  def test_arg_parser(self):
+    # Added for https://crbug.com/1143224
+    parser = argparse.ArgumentParser()
+    base_test_triggerer.BaseTestTriggerer.add_use_swarming_go_arg(parser)
+    swarming_args = ['--server', 'x.apphost.com', '--dimension', 'os', 'Linux']
+    args, _ = parser.parse_known_args(swarming_args)
+    self.assertFalse(args.use_swarming_go)
+
+    args, _ = parser.parse_known_args(swarming_args + ['--use-swarming-go'])
+    self.assertTrue(args.use_swarming_go)
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/testing/trigger_scripts/chromeos_device_trigger.py b/testing/trigger_scripts/chromeos_device_trigger.py
index f835802d9..49c22528 100755
--- a/testing/trigger_scripts/chromeos_device_trigger.py
+++ b/testing/trigger_scripts/chromeos_device_trigger.py
@@ -75,6 +75,7 @@
     additional_args = triggerer.modify_args(
         additional_args, 0, args.shard_index, args.shards, args.dump_json)
   else:
+    base_test_triggerer.BaseTestTriggerer.add_use_swarming_go_arg(parser)
     args, additional_args = parser.parse_known_args()
 
   if additional_args[0] != 'trigger':
@@ -128,6 +129,8 @@
   ])
   new_args += additional_args[1:]
 
+  if args.use_swarming_go:
+    return triggerer.run_swarming_go(new_args, True)
   return triggerer.run_swarming(new_args, True)
 
 
diff --git a/testing/trigger_scripts/perf_device_trigger_unittest.py b/testing/trigger_scripts/perf_device_trigger_unittest.py
index c2dbd53..62980df9 100755
--- a/testing/trigger_scripts/perf_device_trigger_unittest.py
+++ b/testing/trigger_scripts/perf_device_trigger_unittest.py
@@ -16,6 +16,7 @@
     self.dump_json = ''
     self.multiple_trigger_configs = None
     self.multiple_dimension_script_verbose = False
+    self.use_swarming_go = False
 
 
 class FakeTriggerer(perf_device_trigger.PerfDeviceTriggerer):
@@ -24,6 +25,7 @@
     self._swarming_runs = []
     self._files = files
     self._temp_file_id = 0
+    self._triggered_with_swarming_go = 0
     super(FakeTriggerer, self).__init__(args, swarming_args)
 
 
@@ -51,14 +53,21 @@
     del verbose #unused
     self._swarming_runs.append(args)
 
+  def run_swarming_go(self, args, verbose):
+    self._triggered_with_swarming_go += 1
+    self.run_swarming(args, verbose)
 
 class UnitTest(unittest.TestCase):
-  def setup_and_trigger(
-      self, previous_task_assignment_map, alive_bots, dead_bots):
+  def setup_and_trigger(self,
+                        previous_task_assignment_map,
+                        alive_bots,
+                        dead_bots,
+                        use_swarming_go=False):
     args = Args()
     args.shards = len(previous_task_assignment_map)
     args.dump_json = 'output.json'
     args.multiple_dimension_script_verbose = True
+    args.use_swarming_go = use_swarming_go
     swarming_args = [
         'trigger',
         '--swarming',
@@ -291,6 +300,15 @@
     self.assertEquals(set(expected_task_assignment.values()),
         {'build3', 'build4', 'build5', 'build7'})
 
+  def test_use_swarming_go_to_trigger(self):
+    triggerer = self.setup_and_trigger(
+        previous_task_assignment_map={0: 'build1'},
+        alive_bots=['build1'],
+        dead_bots=[],
+        use_swarming_go=True)
+
+    self.assertEquals(triggerer._triggered_with_swarming_go, 1)
+
 
 if __name__ == '__main__':
   unittest.main()
diff --git a/third_party/blink/public/BUILD.gn b/third_party/blink/public/BUILD.gn
index 7523b33..44248792 100644
--- a/third_party/blink/public/BUILD.gn
+++ b/third_party/blink/public/BUILD.gn
@@ -262,6 +262,7 @@
     "platform/web_url_loader_mock_factory.h",
     "platform/web_url_loader_test_delegate.h",
     "platform/web_url_request.h",
+    "platform/web_url_request_extra_data.h",
     "platform/web_url_request_util.h",
     "platform/web_url_response.h",
     "platform/web_vector.h",
diff --git a/third_party/blink/public/platform/internet_disconnected_web_url_loader.h b/third_party/blink/public/platform/internet_disconnected_web_url_loader.h
index 01683dd..33531a0 100644
--- a/third_party/blink/public/platform/internet_disconnected_web_url_loader.h
+++ b/third_party/blink/public/platform/internet_disconnected_web_url_loader.h
@@ -15,6 +15,8 @@
 
 namespace blink {
 
+class WebURLRequestExtraData;
+
 // WebURLLoaderFactory for InternetDisconnectedWebURLLoader.
 class BLINK_PLATFORM_EXPORT InternetDisconnectedWebURLLoaderFactory final
     : public WebURLLoaderFactory {
@@ -37,7 +39,7 @@
   // WebURLLoader implementation:
   void LoadSynchronously(
       std::unique_ptr<network::ResourceRequest> request,
-      scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
+      scoped_refptr<WebURLRequestExtraData> url_request_extra_data,
       int requestor_id,
       bool pass_response_pipe_to_client,
       bool no_mime_sniffing,
@@ -53,7 +55,7 @@
           resource_load_info_notifier_wrapper) override;
   void LoadAsynchronously(
       std::unique_ptr<network::ResourceRequest> request,
-      scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
+      scoped_refptr<WebURLRequestExtraData> url_request_extra_data,
       int requestor_id,
       bool no_mime_sniffing,
       std::unique_ptr<blink::ResourceLoadInfoNotifierWrapper>
diff --git a/third_party/blink/public/platform/web_runtime_features.h b/third_party/blink/public/platform/web_runtime_features.h
index 1c4db6c..daae27f 100644
--- a/third_party/blink/public/platform/web_runtime_features.h
+++ b/third_party/blink/public/platform/web_runtime_features.h
@@ -191,6 +191,7 @@
   BLINK_PLATFORM_EXPORT static void EnableWebXRCameraAccess(bool);
   BLINK_PLATFORM_EXPORT static void EnableWebXRDepth(bool);
   BLINK_PLATFORM_EXPORT static void EnableWebXRHitTest(bool);
+  BLINK_PLATFORM_EXPORT static void EnableWebXRImageTracking(bool);
   BLINK_PLATFORM_EXPORT static void EnableWebXRLightEstimation(bool);
   BLINK_PLATFORM_EXPORT static void EnableWebXRPlaneDetection(bool);
   BLINK_PLATFORM_EXPORT static void EnableWebXRReflectionEstimation(bool);
diff --git a/third_party/blink/public/platform/web_url_loader.h b/third_party/blink/public/platform/web_url_loader.h
index 24c7218..00f006fe 100644
--- a/third_party/blink/public/platform/web_url_loader.h
+++ b/third_party/blink/public/platform/web_url_loader.h
@@ -50,6 +50,7 @@
 
 class ResourceLoadInfoNotifierWrapper;
 class WebData;
+class WebURLRequestExtraData;
 class WebURLLoaderClient;
 class WebURLResponse;
 struct WebURLError;
@@ -67,7 +68,7 @@
   // |downloaded_blob|.
   virtual void LoadSynchronously(
       std::unique_ptr<network::ResourceRequest> request,
-      scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
+      scoped_refptr<WebURLRequestExtraData> url_request_extra_data,
       int requestor_id,
       bool pass_response_pipe_to_client,
       bool no_mime_sniffing,
@@ -87,7 +88,7 @@
   // loader is disposed before it completes its work.
   virtual void LoadAsynchronously(
       std::unique_ptr<network::ResourceRequest> request,
-      scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
+      scoped_refptr<WebURLRequestExtraData> url_request_extra_data,
       int requestor_id,
       bool no_mime_sniffing,
       std::unique_ptr<ResourceLoadInfoNotifierWrapper>,
diff --git a/third_party/blink/public/platform/web_url_request.h b/third_party/blink/public/platform/web_url_request.h
index 9b92647..fc06765 100644
--- a/third_party/blink/public/platform/web_url_request.h
+++ b/third_party/blink/public/platform/web_url_request.h
@@ -69,6 +69,7 @@
 class ResourceRequest;
 class WebHTTPBody;
 class WebHTTPHeaderVisitor;
+class WebURLRequestExtraData;
 class WebSecurityOrigin;
 class WebString;
 class WebURL;
@@ -88,56 +89,6 @@
     kHighest = kVeryHigh,
   };
 
-  class ExtraData : public base::RefCounted<ExtraData> {
-   public:
-    void set_render_frame_id(int render_frame_id) {
-      render_frame_id_ = render_frame_id;
-    }
-    void set_is_main_frame(bool is_main_frame) {
-      is_main_frame_ = is_main_frame;
-    }
-    ui::PageTransition transition_type() const { return transition_type_; }
-    void set_transition_type(ui::PageTransition transition_type) {
-      transition_type_ = transition_type;
-    }
-
-    // The request is for a prefetch-only client (i.e. running NoStatePrefetch)
-    // and should use LOAD_PREFETCH network flags.
-    bool is_for_no_state_prefetch() const { return is_for_no_state_prefetch_; }
-    void set_is_for_no_state_prefetch(bool prefetch) {
-      is_for_no_state_prefetch_ = prefetch;
-    }
-
-    // true if the request originated from within a service worker e.g. due to
-    // a fetch() in the service worker script.
-    void set_originated_from_service_worker(
-        bool originated_from_service_worker) {
-      originated_from_service_worker_ = originated_from_service_worker;
-    }
-
-    // Determines whether SameSite cookies will be attached to the request
-    // even when the request looks cross-site.
-    bool force_ignore_site_for_cookies() const {
-      return force_ignore_site_for_cookies_;
-    }
-    void set_force_ignore_site_for_cookies(bool attach) {
-      force_ignore_site_for_cookies_ = attach;
-    }
-
-   protected:
-    friend class base::RefCounted<ExtraData>;
-    virtual ~ExtraData() = default;
-
-    BLINK_PLATFORM_EXPORT ExtraData();
-
-    int render_frame_id_;
-    bool is_main_frame_ = false;
-    ui::PageTransition transition_type_ = ui::PAGE_TRANSITION_LINK;
-    bool is_for_no_state_prefetch_ = false;
-    bool originated_from_service_worker_ = false;
-    bool force_ignore_site_for_cookies_ = false;
-  };
-
   BLINK_PLATFORM_EXPORT ~WebURLRequest();
   BLINK_PLATFORM_EXPORT WebURLRequest();
   WebURLRequest(const WebURLRequest&) = delete;
@@ -291,8 +242,10 @@
   // deleted when the last resource request is destroyed. Setting the extra
   // data pointer will cause the underlying resource request to be
   // dissociated from any existing non-null extra data pointer.
-  BLINK_PLATFORM_EXPORT const scoped_refptr<ExtraData>& GetExtraData() const;
-  BLINK_PLATFORM_EXPORT void SetExtraData(scoped_refptr<ExtraData>);
+  BLINK_PLATFORM_EXPORT const scoped_refptr<WebURLRequestExtraData>&
+  GetURLRequestExtraData() const;
+  BLINK_PLATFORM_EXPORT void SetURLRequestExtraData(
+      scoped_refptr<WebURLRequestExtraData>);
 
   // The request is downloaded to the network cache, but not rendered or
   // executed.
diff --git a/third_party/blink/public/platform/web_url_request_extra_data.h b/third_party/blink/public/platform/web_url_request_extra_data.h
new file mode 100644
index 0000000..a1075d8
--- /dev/null
+++ b/third_party/blink/public/platform/web_url_request_extra_data.h
@@ -0,0 +1,114 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_PUBLIC_PLATFORM_WEB_URL_REQUEST_EXTRA_DATA_H_
+#define THIRD_PARTY_BLINK_PUBLIC_PLATFORM_WEB_URL_REQUEST_EXTRA_DATA_H_
+
+#include <memory>
+
+#include "base/memory/ref_counted.h"
+#include "base/optional.h"
+#include "third_party/blink/public/common/loader/url_loader_throttle.h"
+#include "third_party/blink/public/platform/web_common.h"
+#include "third_party/blink/public/platform/web_frame_request_blocker.h"
+#include "third_party/blink/public/platform/web_string.h"
+#include "third_party/blink/public/platform/web_vector.h"
+#include "ui/base/page_transition_types.h"
+
+namespace network {
+struct ResourceRequest;
+}
+
+namespace blink {
+
+class BLINK_PLATFORM_EXPORT WebURLRequestExtraData
+    : public base::RefCounted<WebURLRequestExtraData> {
+ public:
+  WebURLRequestExtraData();
+  WebURLRequestExtraData(const WebURLRequestExtraData&) = delete;
+  WebURLRequestExtraData& operator=(const WebURLRequestExtraData&) = delete;
+
+  void set_render_frame_id(int render_frame_id) {
+    render_frame_id_ = render_frame_id;
+  }
+  void set_is_main_frame(bool is_main_frame) { is_main_frame_ = is_main_frame; }
+  ui::PageTransition transition_type() const { return transition_type_; }
+  void set_transition_type(ui::PageTransition transition_type) {
+    transition_type_ = transition_type;
+  }
+
+  // The request is for a prefetch-only client (i.e. running NoStatePrefetch)
+  // and should use LOAD_PREFETCH network flags.
+  bool is_for_no_state_prefetch() const { return is_for_no_state_prefetch_; }
+  void set_is_for_no_state_prefetch(bool prefetch) {
+    is_for_no_state_prefetch_ = prefetch;
+  }
+
+  // true if the request originated from within a service worker e.g. due to
+  // a fetch() in the service worker script.
+  void set_originated_from_service_worker(bool originated_from_service_worker) {
+    originated_from_service_worker_ = originated_from_service_worker;
+  }
+
+  // Determines whether SameSite cookies will be attached to the request
+  // even when the request looks cross-site.
+  bool force_ignore_site_for_cookies() const {
+    return force_ignore_site_for_cookies_;
+  }
+  void set_force_ignore_site_for_cookies(bool attach) {
+    force_ignore_site_for_cookies_ = attach;
+  }
+
+  // |custom_user_agent| is used to communicate an overriding custom user agent
+  // to |RenderViewImpl::willSendRequest()|; set to a null string to indicate no
+  // override and an empty string to indicate that there should be no user
+  // agent.
+  const WebString& custom_user_agent() const { return custom_user_agent_; }
+  void set_custom_user_agent(const WebString& custom_user_agent) {
+    custom_user_agent_ = custom_user_agent;
+  }
+
+  WebVector<std::unique_ptr<URLLoaderThrottle>> TakeURLLoaderThrottles() {
+    return std::move(url_loader_throttles_);
+  }
+  void set_url_loader_throttles(
+      WebVector<std::unique_ptr<URLLoaderThrottle>> throttles) {
+    url_loader_throttles_ = std::move(throttles);
+  }
+  void set_frame_request_blocker(
+      scoped_refptr<WebFrameRequestBlocker> frame_request_blocker) {
+    frame_request_blocker_ = frame_request_blocker;
+  }
+  scoped_refptr<WebFrameRequestBlocker> frame_request_blocker() {
+    return frame_request_blocker_;
+  }
+  bool allow_cross_origin_auth_prompt() const {
+    return allow_cross_origin_auth_prompt_;
+  }
+  void set_allow_cross_origin_auth_prompt(bool allow_cross_origin_auth_prompt) {
+    allow_cross_origin_auth_prompt_ = allow_cross_origin_auth_prompt;
+  }
+
+  void CopyToResourceRequest(network::ResourceRequest* request) const;
+
+ protected:
+  friend class base::RefCounted<WebURLRequestExtraData>;
+  virtual ~WebURLRequestExtraData();
+
+ private:
+  base::Optional<int> render_frame_id_;
+  bool is_main_frame_ = false;
+  ui::PageTransition transition_type_ = ui::PAGE_TRANSITION_LINK;
+  bool is_for_no_state_prefetch_ = false;
+  bool originated_from_service_worker_ = false;
+  bool force_ignore_site_for_cookies_ = false;
+  WebString custom_user_agent_;
+  WebVector<std::unique_ptr<URLLoaderThrottle>> url_loader_throttles_;
+  scoped_refptr<WebFrameRequestBlocker> frame_request_blocker_;
+  bool allow_cross_origin_auth_prompt_ = false;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_PUBLIC_PLATFORM_WEB_URL_REQUEST_EXTRA_DATA_H_
diff --git a/third_party/blink/renderer/bindings/core/v8/script_streamer_test.cc b/third_party/blink/renderer/bindings/core/v8/script_streamer_test.cc
index cfb8433..86068b84 100644
--- a/third_party/blink/renderer/bindings/core/v8/script_streamer_test.cc
+++ b/third_party/blink/renderer/bindings/core/v8/script_streamer_test.cc
@@ -14,6 +14,7 @@
 #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
 #include "third_party/blink/public/platform/web_url_loader.h"
 #include "third_party/blink/public/platform/web_url_loader_mock_factory.h"
+#include "third_party/blink/public/platform/web_url_request_extra_data.h"
 #include "third_party/blink/renderer/bindings/core/v8/referrer_script_info.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_source_code.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
@@ -82,7 +83,7 @@
     ~NoopWebURLLoader() override = default;
     void LoadSynchronously(
         std::unique_ptr<network::ResourceRequest> request,
-        scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
+        scoped_refptr<WebURLRequestExtraData> url_request_extra_data,
         int requestor_id,
         bool pass_response_pipe_to_client,
         bool no_mime_sniffing,
@@ -100,7 +101,7 @@
     }
     void LoadAsynchronously(
         std::unique_ptr<network::ResourceRequest> request,
-        scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
+        scoped_refptr<WebURLRequestExtraData> url_request_extra_data,
         int requestor_id,
         bool no_mime_sniffing,
         std::unique_ptr<blink::ResourceLoadInfoNotifierWrapper>
diff --git a/third_party/blink/renderer/bindings/generated_in_modules.gni b/third_party/blink/renderer/bindings/generated_in_modules.gni
index b4080f8..65b0525216 100644
--- a/third_party/blink/renderer/bindings/generated_in_modules.gni
+++ b/third_party/blink/renderer/bindings/generated_in_modules.gni
@@ -2157,6 +2157,8 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_hit_test_result.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_hit_test_source.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_hit_test_source.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_image_tracking_result.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_image_tracking_result.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_input_source.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_input_source.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_input_source_array.cc",
diff --git a/third_party/blink/renderer/bindings/idl_in_modules.gni b/third_party/blink/renderer/bindings/idl_in_modules.gni
index 00eec643..c0b35d1 100644
--- a/third_party/blink/renderer/bindings/idl_in_modules.gni
+++ b/third_party/blink/renderer/bindings/idl_in_modules.gni
@@ -979,6 +979,7 @@
           "//third_party/blink/renderer/modules/xr/xr_hit_test_options_init.idl",
           "//third_party/blink/renderer/modules/xr/xr_hit_test_result.idl",
           "//third_party/blink/renderer/modules/xr/xr_hit_test_source.idl",
+          "//third_party/blink/renderer/modules/xr/xr_image_tracking_result.idl",
           "//third_party/blink/renderer/modules/xr/xr_input_source.idl",
           "//third_party/blink/renderer/modules/xr/xr_input_source_array.idl",
           "//third_party/blink/renderer/modules/xr/xr_input_source_event.idl",
@@ -1006,6 +1007,7 @@
           "//third_party/blink/renderer/modules/xr/xr_session_init.idl",
           "//third_party/blink/renderer/modules/xr/xr_space.idl",
           "//third_party/blink/renderer/modules/xr/xr_system.idl",
+          "//third_party/blink/renderer/modules/xr/xr_tracked_image_init.idl",
           "//third_party/blink/renderer/modules/xr/xr_transient_input_hit_test_options_init.idl",
           "//third_party/blink/renderer/modules/xr/xr_transient_input_hit_test_result.idl",
           "//third_party/blink/renderer/modules/xr/xr_transient_input_hit_test_source.idl",
diff --git a/third_party/blink/renderer/core/editing/ime/input_method_controller.cc b/third_party/blink/renderer/core/editing/ime/input_method_controller.cc
index 7a077d0..e9a1e00 100644
--- a/third_party/blink/renderer/core/editing/ime/input_method_controller.cc
+++ b/third_party/blink/renderer/core/editing/ime/input_method_controller.cc
@@ -1559,8 +1559,9 @@
 
   // Only gets ime text spans when there is no selection range.
   // ie. the selection range is just a cursor position.
-  if (info.selection_start == info.selection_end) {
-    info.ime_text_spans = GetImeTextSpansAroundPosition(info.selection_start);
+  if (first_range.IsCollapsed()) {
+    info.ime_text_spans =
+        GetImeTextSpansAroundPosition(first_range.StartPosition());
   }
 
   EphemeralRange range = CompositionEphemeralRange();
@@ -1791,7 +1792,7 @@
 }
 
 WebVector<ui::ImeTextSpan> InputMethodController::GetImeTextSpansAroundPosition(
-    unsigned position) const {
+    const Position& position) const {
   DCHECK(!GetDocument().NeedsLayoutTreeUpdate());
   Element* target = GetDocument().FocusedElement();
   if (!target)
@@ -1805,14 +1806,12 @@
     return WebVector<ui::ImeTextSpan>();
 
   WebVector<ui::ImeTextSpan> ime_text_spans;
-  const EphemeralRange range =
-      PlainTextRange(position, position).CreateRange(*editable);
   // Only queries Suggestion markers for now.
   // This can be expanded when browser needs information for
   // other types of markers.
   const HeapVector<std::pair<Member<const Text>, Member<DocumentMarker>>>&
       node_marker_pairs = GetDocument().Markers().MarkersAroundPosition(
-          ToPositionInFlatTree(range.StartPosition()),
+          ToPositionInFlatTree(position),
           DocumentMarker::MarkerTypes::Suggestion());
 
   for (const std::pair<Member<const Text>, Member<DocumentMarker>>&
@@ -1826,6 +1825,9 @@
       const EphemeralRange& marker_ephemeral_range =
           EphemeralRange(Position(node, marker->StartOffset()),
                          Position(node, marker->EndOffset()));
+      // TODO(yosin): We should have another way to converting DOM position
+      // to offset in root editable, because root editable has big text
+      // content, |PlainTextRangeForEphemeralRange()| is slow.
       PlainTextRange marker_plain_text_range =
           PlainTextRangeForEphemeralRange(marker_ephemeral_range).second;
 
diff --git a/third_party/blink/renderer/core/editing/ime/input_method_controller.h b/third_party/blink/renderer/core/editing/ime/input_method_controller.h
index 1de392c..1b247dd 100644
--- a/third_party/blink/renderer/core/editing/ime/input_method_controller.h
+++ b/third_party/blink/renderer/core/editing/ime/input_method_controller.h
@@ -239,7 +239,7 @@
 
   // Gets ime text spans of interest at the cursor position.
   WebVector<ui::ImeTextSpan> GetImeTextSpansAroundPosition(
-      unsigned position) const;
+      const Position& position) const;
 
   FRIEND_TEST_ALL_PREFIXES(InputMethodControllerTest,
                            InputModeOfFocusedElement);
diff --git a/third_party/blink/renderer/core/frame/frame_overlay.cc b/third_party/blink/renderer/core/frame/frame_overlay.cc
index c473886..43acfeb 100644
--- a/third_party/blink/renderer/core/frame/frame_overlay.cc
+++ b/third_party/blink/renderer/core/frame/frame_overlay.cc
@@ -85,7 +85,6 @@
     parent_layer->AddChild(layer_.get());
   layer_->SetLayerState(DefaultPropertyTreeState(), IntPoint());
   layer_->SetSize(gfx::Size(Size()));
-  layer_->SetNeedsDisplay();
 }
 
 IntSize FrameOverlay::Size() const {
diff --git a/third_party/blink/renderer/core/frame/local_dom_window.cc b/third_party/blink/renderer/core/frame/local_dom_window.cc
index 2940caaf..8249c145 100644
--- a/third_party/blink/renderer/core/frame/local_dom_window.cc
+++ b/third_party/blink/renderer/core/frame/local_dom_window.cc
@@ -182,7 +182,7 @@
   DispatchEvent(*Event::Create(event_type_names::kLanguagechange));
 }
 
-ScriptValue LocalDOMWindow::event(ScriptState* script_state) const {
+ScriptValue LocalDOMWindow::event(ScriptState* script_state) {
   // If current event is null, return undefined.
   if (!current_event_) {
     return ScriptValue(script_state->GetIsolate(),
@@ -194,7 +194,7 @@
   if (current_event_->target()) {
     Node* target_node = current_event_->target()->ToNode();
     if (target_node && target_node->IsInV0ShadowTree()) {
-      UseCounter::Count(document(), WebFeature::kWindowEventInV0ShadowTree);
+      UseCounter::Count(this, WebFeature::kWindowEventInV0ShadowTree);
     }
   }
 
@@ -771,8 +771,7 @@
 }
 
 MediaQueryList* LocalDOMWindow::matchMedia(const String& media) {
-  return document() ? document()->GetMediaQueryMatcher().MatchMedia(media)
-                    : nullptr;
+  return document()->GetMediaQueryMatcher().MatchMedia(media);
 }
 
 void LocalDOMWindow::FrameDestroyed() {
@@ -1014,7 +1013,7 @@
   if (event->IsLockedToAgentCluster()) {
     if (!IsSameAgentCluster(source_agent_cluster_id)) {
       UseCounter::Count(
-          document(),
+          this,
           WebFeature::kMessageEventSharedArrayBufferDifferentAgentCluster);
       event = MessageEvent::CreateError(event->origin(), event->source());
     } else {
@@ -1022,10 +1021,9 @@
           SecurityOrigin::Create(sender);
       if (!sender_origin->IsSameOriginWith(GetSecurityOrigin())) {
         UseCounter::Count(
-            document(),
-            WebFeature::kMessageEventSharedArrayBufferSameAgentCluster);
+            this, WebFeature::kMessageEventSharedArrayBufferSameAgentCluster);
       } else {
-        UseCounter::Count(document(),
+        UseCounter::Count(this,
                           WebFeature::kMessageEventSharedArrayBufferSameOrigin);
       }
     }
@@ -1077,7 +1075,7 @@
 
   if (script_state &&
       v8::MicrotasksScope::IsRunningMicrotasks(script_state->GetIsolate())) {
-    UseCounter::Count(document(), WebFeature::kDuring_Microtask_Print);
+    UseCounter::Count(this, WebFeature::kDuring_Microtask_Print);
   }
 
   if (GetFrame()->IsLoading()) {
@@ -1105,7 +1103,7 @@
     return;
 
   if (IsSandboxed(network::mojom::blink::WebSandboxFlags::kModals)) {
-    UseCounter::Count(document(), WebFeature::kDialogInSandboxedContext);
+    UseCounter::Count(this, WebFeature::kDialogInSandboxedContext);
     GetFrameConsole()->AddMessage(MakeGarbageCollected<ConsoleMessage>(
         mojom::ConsoleMessageSource::kSecurity,
         mojom::ConsoleMessageLevel::kError,
@@ -1115,7 +1113,7 @@
   }
 
   if (v8::MicrotasksScope::IsRunningMicrotasks(script_state->GetIsolate())) {
-    UseCounter::Count(document(), WebFeature::kDuring_Microtask_Alert);
+    UseCounter::Count(this, WebFeature::kDuring_Microtask_Alert);
   }
 
   document()->UpdateStyleAndLayoutTree();
@@ -1137,7 +1135,7 @@
     return false;
 
   if (IsSandboxed(network::mojom::blink::WebSandboxFlags::kModals)) {
-    UseCounter::Count(document(), WebFeature::kDialogInSandboxedContext);
+    UseCounter::Count(this, WebFeature::kDialogInSandboxedContext);
     GetFrameConsole()->AddMessage(MakeGarbageCollected<ConsoleMessage>(
         mojom::ConsoleMessageSource::kSecurity,
         mojom::ConsoleMessageLevel::kError,
@@ -1147,7 +1145,7 @@
   }
 
   if (v8::MicrotasksScope::IsRunningMicrotasks(script_state->GetIsolate())) {
-    UseCounter::Count(document(), WebFeature::kDuring_Microtask_Confirm);
+    UseCounter::Count(this, WebFeature::kDuring_Microtask_Confirm);
   }
 
   document()->UpdateStyleAndLayoutTree();
@@ -1171,7 +1169,7 @@
     return String();
 
   if (IsSandboxed(network::mojom::blink::WebSandboxFlags::kModals)) {
-    UseCounter::Count(document(), WebFeature::kDialogInSandboxedContext);
+    UseCounter::Count(this, WebFeature::kDialogInSandboxedContext);
     GetFrameConsole()->AddMessage(MakeGarbageCollected<ConsoleMessage>(
         mojom::ConsoleMessageSource::kSecurity,
         mojom::ConsoleMessageLevel::kError,
@@ -1181,7 +1179,7 @@
   }
 
   if (v8::MicrotasksScope::IsRunningMicrotasks(script_state->GetIsolate())) {
-    UseCounter::Count(document(), WebFeature::kDuring_Microtask_Prompt);
+    UseCounter::Count(this, WebFeature::kDuring_Microtask_Prompt);
   }
 
   document()->UpdateStyleAndLayoutTree();
@@ -1662,27 +1660,20 @@
 }
 
 int LocalDOMWindow::requestAnimationFrame(V8FrameRequestCallback* callback) {
-  if (Document* doc = document()) {
-    auto* frame_callback = MakeGarbageCollected<V8FrameCallback>(callback);
-    frame_callback->SetUseLegacyTimeBase(false);
-    return doc->RequestAnimationFrame(frame_callback);
-  }
-  return 0;
+  auto* frame_callback = MakeGarbageCollected<V8FrameCallback>(callback);
+  frame_callback->SetUseLegacyTimeBase(false);
+  return document()->RequestAnimationFrame(frame_callback);
 }
 
 int LocalDOMWindow::webkitRequestAnimationFrame(
     V8FrameRequestCallback* callback) {
-  if (Document* doc = document()) {
-    auto* frame_callback = MakeGarbageCollected<V8FrameCallback>(callback);
-    frame_callback->SetUseLegacyTimeBase(true);
-    return doc->RequestAnimationFrame(frame_callback);
-  }
-  return 0;
+  auto* frame_callback = MakeGarbageCollected<V8FrameCallback>(callback);
+  frame_callback->SetUseLegacyTimeBase(true);
+  return document()->RequestAnimationFrame(frame_callback);
 }
 
 void LocalDOMWindow::cancelAnimationFrame(int id) {
-  if (Document* document = this->document())
-    document->CancelAnimationFrame(id);
+  document()->CancelAnimationFrame(id);
 }
 
 void LocalDOMWindow::queueMicrotask(V8VoidFunction* callback) {
@@ -1711,8 +1702,7 @@
 }
 
 void LocalDOMWindow::cancelIdleCallback(int id) {
-  if (Document* document = this->document())
-    document->CancelIdleCallback(id);
+  document()->CancelIdleCallback(id);
 }
 
 CustomElementRegistry* LocalDOMWindow::customElements(
@@ -1760,25 +1750,22 @@
         *this, event_type, registered_listener.Options());
   }
 
-  if (Document* document = this->document())
-    document->AddListenerTypeIfNeeded(event_type, *this);
+  document()->AddListenerTypeIfNeeded(event_type, *this);
 
   for (auto& it : event_listener_observers_) {
     it->DidAddEventListener(this, event_type);
   }
 
   if (event_type == event_type_names::kUnload) {
-    UseCounter::Count(document(), WebFeature::kDocumentUnloadRegistered);
+    UseCounter::Count(this, WebFeature::kDocumentUnloadRegistered);
   } else if (event_type == event_type_names::kBeforeunload) {
-    UseCounter::Count(document(), WebFeature::kDocumentBeforeUnloadRegistered);
-    if (GetFrame() && !GetFrame()->IsMainFrame()) {
-      UseCounter::Count(document(),
-                        WebFeature::kSubFrameBeforeUnloadRegistered);
-    }
+    UseCounter::Count(this, WebFeature::kDocumentBeforeUnloadRegistered);
+    if (GetFrame() && !GetFrame()->IsMainFrame())
+      UseCounter::Count(this, WebFeature::kSubFrameBeforeUnloadRegistered);
   } else if (event_type == event_type_names::kPagehide) {
-    UseCounter::Count(document(), WebFeature::kDocumentPageHideRegistered);
+    UseCounter::Count(this, WebFeature::kDocumentPageHideRegistered);
   } else if (event_type == event_type_names::kPageshow) {
-    UseCounter::Count(document(), WebFeature::kDocumentPageShowRegistered);
+    UseCounter::Count(this, WebFeature::kDocumentPageShowRegistered);
   }
 
   if (!GetFrame() || (event_type != event_type_names::kUnload &&
@@ -1937,20 +1924,16 @@
 
   if (!IsCurrentlyDisplayedInFrame())
     return nullptr;
-  if (!incumbent_window->GetFrame())
-    return nullptr;
-  LocalFrame* entered_window_frame = entered_window->GetFrame();
-  if (!entered_window_frame)
+  if (!incumbent_window->GetFrame() || !entered_window->GetFrame())
     return nullptr;
 
   UseCounter::Count(*incumbent_window, WebFeature::kDOMWindowOpen);
   if (!features.IsEmpty())
     UseCounter::Count(*incumbent_window, WebFeature::kDOMWindowOpenFeatures);
 
-  KURL completed_url =
-      url_string.IsEmpty()
-          ? KURL(g_empty_string)
-          : entered_window_frame->GetDocument()->CompleteURL(url_string);
+  KURL completed_url = url_string.IsEmpty()
+                           ? KURL(g_empty_string)
+                           : entered_window->CompleteURL(url_string);
   if (!completed_url.IsEmpty() && !completed_url.IsValid()) {
     UseCounter::Count(incumbent_window, WebFeature::kWindowOpenWithInvalidURL);
     exception_state.ThrowDOMException(
diff --git a/third_party/blink/renderer/core/frame/local_dom_window.h b/third_party/blink/renderer/core/frame/local_dom_window.h
index f52dea4..07101cf 100644
--- a/third_party/blink/renderer/core/frame/local_dom_window.h
+++ b/third_party/blink/renderer/core/frame/local_dom_window.h
@@ -387,7 +387,7 @@
   void AcceptLanguagesChanged();
 
   // https://dom.spec.whatwg.org/#dom-window-event
-  ScriptValue event(ScriptState*) const;
+  ScriptValue event(ScriptState*);
   Event* CurrentEvent() const;
   void SetCurrentEvent(Event*);
 
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 856a3c16..e0e48fe 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -1855,19 +1855,12 @@
 
   base_background_color_ = background_color;
 
-  if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
-    DisableCompositingQueryAsserts disabler;
-    if (auto* layout_view = GetLayoutView()) {
-      if (layout_view->Layer()->HasCompositedLayerMapping()) {
-        CompositedLayerMapping* composited_layer_mapping =
-            layout_view->Layer()->GetCompositedLayerMapping();
-        composited_layer_mapping->UpdateContentsOpaque();
-        if (composited_layer_mapping->MainGraphicsLayer())
-          composited_layer_mapping->MainGraphicsLayer()->SetNeedsDisplay();
-        if (composited_layer_mapping->ScrollingContentsLayer())
-          composited_layer_mapping->ScrollingContentsLayer()->SetNeedsDisplay();
-      }
+  if (auto* layout_view = GetLayoutView()) {
+    if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
+      if (auto* mapping = layout_view->Layer()->GetCompositedLayerMapping())
+        mapping->UpdateContentsOpaque();
     }
+    layout_view->SetBackgroundNeedsFullPaintInvalidation();
   }
 
   if (!ShouldThrottleRendering())
diff --git a/third_party/blink/renderer/core/layout/custom_scrollbar.cc b/third_party/blink/renderer/core/layout/custom_scrollbar.cc
index 002b693..86b0988 100644
--- a/third_party/blink/renderer/core/layout/custom_scrollbar.cc
+++ b/third_party/blink/renderer/core/layout/custom_scrollbar.cc
@@ -417,9 +417,10 @@
 
 void CustomScrollbar::InvalidateDisplayItemClientsOfScrollbarParts() {
   for (auto& part : parts_) {
+    DCHECK(!part.value->PaintingLayer());
     ObjectPaintInvalidator(*part.value)
-        .SlowSetPaintingLayerNeedsRepaintAndInvalidateDisplayItemClient(
-            *part.value, PaintInvalidationReason::kScrollControl);
+        .InvalidateDisplayItemClient(*part.value,
+                                     PaintInvalidationReason::kScrollControl);
   }
 }
 
diff --git a/third_party/blink/renderer/core/loader/prefetched_signed_exchange_manager.cc b/third_party/blink/renderer/core/loader/prefetched_signed_exchange_manager.cc
index e7e2c11..e8d458c 100644
--- a/third_party/blink/renderer/core/loader/prefetched_signed_exchange_manager.cc
+++ b/third_party/blink/renderer/core/loader/prefetched_signed_exchange_manager.cc
@@ -18,6 +18,7 @@
 #include "third_party/blink/public/platform/web_url_loader.h"
 #include "third_party/blink/public/platform/web_url_loader_client.h"
 #include "third_party/blink/public/platform/web_url_loader_factory.h"
+#include "third_party/blink/public/platform/web_url_request_extra_data.h"
 #include "third_party/blink/public/platform/web_url_response.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
@@ -67,7 +68,7 @@
   // WebURLLoader methods:
   void LoadSynchronously(
       std::unique_ptr<network::ResourceRequest> request,
-      scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
+      scoped_refptr<WebURLRequestExtraData> url_request_extra_data,
       int requestor_id,
       bool pass_response_pipe_to_client,
       bool no_mime_sniffing,
@@ -85,7 +86,7 @@
   }
   void LoadAsynchronously(
       std::unique_ptr<network::ResourceRequest> request,
-      scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
+      scoped_refptr<WebURLRequestExtraData> url_request_extra_data,
       int requestor_id,
       bool no_mime_sniffing,
       std::unique_ptr<blink::ResourceLoadInfoNotifierWrapper>
@@ -93,7 +94,7 @@
       WebURLLoaderClient* client) override {
     if (url_loader_) {
       url_loader_->LoadAsynchronously(
-          std::move(request), std::move(request_extra_data), requestor_id,
+          std::move(request), std::move(url_request_extra_data), requestor_id,
           no_mime_sniffing, std::move(resource_load_info_notifier_wrapper),
           client);
       return;
@@ -103,7 +104,7 @@
     // |this| here.
     pending_method_calls_.push(WTF::Bind(
         &PrefetchedSignedExchangeLoader::LoadAsynchronously, GetWeakPtr(),
-        std::move(request), std::move(request_extra_data), requestor_id,
+        std::move(request), std::move(url_request_extra_data), requestor_id,
         no_mime_sniffing, std::move(resource_load_info_notifier_wrapper),
         WTF::Unretained(client)));
   }
diff --git a/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc b/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
index 779d5c2..d113242 100644
--- a/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
+++ b/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
@@ -509,17 +509,16 @@
   }
 
   // Invalidate the whole layer when subpixel accumulation changes, since
-  // the previous subpixel accumulation is baked into the dispay list.
+  // the previous subpixel accumulation is baked into the display list.
   // However, don't do so for directly composited layers, to avoid impacting
   // performance.
   if (subpixel_accumulation != owning_layer_.SubpixelAccumulation()) {
     // Always invalidate if under-invalidation checking is on, to avoid
     // false positives.
-    if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled())
-      SetContentsNeedDisplay();
-    else if (!(owning_layer_.GetCompositingReasons() &
-               CompositingReason::kComboAllDirectReasons))
-      SetContentsNeedDisplay();
+    if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled() ||
+        !(owning_layer_.GetCompositingReasons() &
+          CompositingReason::kComboAllDirectReasons))
+      GetLayoutObject().SetShouldCheckForPaintInvalidation();
   }
 
   // Otherwise discard the sub-pixel remainder because paint offset can't be
@@ -759,10 +758,7 @@
   if (!mask_layer_)
     return;
 
-  if (mask_layer_->Size() != graphics_layer_->Size()) {
-    mask_layer_->SetSize(graphics_layer_->Size());
-    mask_layer_->SetNeedsDisplay();
-  }
+  mask_layer_->SetSize(graphics_layer_->Size());
   mask_layer_->SetOffsetFromLayoutObject(
       graphics_layer_->OffsetFromLayoutObject());
 }
@@ -864,26 +860,17 @@
                 IntSize(scrolling_contents_layer_->Size()));
   }
 
-  IntRect old_compositing_bounds(
-      IntPoint(foreground_layer_->OffsetFromLayoutObject()),
-      IntSize(foreground_layer_->Size()));
-  if (compositing_bounds != old_compositing_bounds) {
-    foreground_layer_->SetOffsetFromLayoutObject(
-        ToIntSize(compositing_bounds.Location()));
-    foreground_layer_->SetSize(gfx::Size(compositing_bounds.Size()));
-    foreground_layer_->SetNeedsDisplay();
-  }
+  foreground_layer_->SetOffsetFromLayoutObject(
+      ToIntSize(compositing_bounds.Location()));
+  foreground_layer_->SetSize(gfx::Size(compositing_bounds.Size()));
 }
 
 void CompositedLayerMapping::UpdateDecorationOutlineLayerGeometry(
     const IntSize& relative_compositing_bounds_size) {
   if (!decoration_outline_layer_)
     return;
-  const auto& decoration_size = relative_compositing_bounds_size;
-  if (gfx::Size(decoration_size) != decoration_outline_layer_->Size()) {
-    decoration_outline_layer_->SetSize(gfx::Size(decoration_size));
-    decoration_outline_layer_->SetNeedsDisplay();
-  }
+  decoration_outline_layer_->SetSize(
+      gfx::Size(relative_compositing_bounds_size));
   decoration_outline_layer_->SetOffsetFromLayoutObject(
       graphics_layer_->OffsetFromLayoutObject());
 }
@@ -1131,15 +1118,9 @@
   }
 }
 
-enum ApplyToGraphicsLayersMode {
-  kApplyToContentLayers,
-  kApplyToAllGraphicsLayers,
-};
-
 template <typename Function>
 static void ApplyToGraphicsLayers(const CompositedLayerMapping* mapping,
-                                  const Function& function,
-                                  ApplyToGraphicsLayersMode mode) {
+                                  const Function& function) {
   auto null_checking_function = [&function](GraphicsLayer* layer) {
     if (layer)
       function(layer);
@@ -1150,13 +1131,10 @@
   null_checking_function(mapping->ForegroundLayer());
   null_checking_function(mapping->MaskLayer());
   null_checking_function(mapping->DecorationOutlineLayer());
-
-  if (mode == kApplyToAllGraphicsLayers) {
-    null_checking_function(mapping->NonScrollingSquashingLayer());
-    null_checking_function(mapping->LayerForHorizontalScrollbar());
-    null_checking_function(mapping->LayerForVerticalScrollbar());
-    null_checking_function(mapping->LayerForScrollCorner());
-  }
+  null_checking_function(mapping->NonScrollingSquashingLayer());
+  null_checking_function(mapping->LayerForHorizontalScrollbar());
+  null_checking_function(mapping->LayerForVerticalScrollbar());
+  null_checking_function(mapping->LayerForScrollCorner());
 }
 
 // You receive an element id if you have an animation, or you're a scroller (and
@@ -1403,7 +1381,7 @@
 
 void CompositedLayerMapping::ContentChanged(ContentChangeType change_type) {
   if (change_type == kCanvasChanged && IsTextureLayerCanvas(GetLayoutObject()))
-    graphics_layer_->SetContentsNeedsDisplay();
+    graphics_layer_->InvalidateContents();
 }
 
 // Return the offset from the top-left of this compositing layer at which the
@@ -1499,31 +1477,18 @@
   return NonScrollingSquashingLayer();
 }
 
-struct SetContentsNeedsDisplayFunctor {
-  void operator()(GraphicsLayer* layer) const {
-    if (layer->PaintsContentOrHitTest())
-      layer->SetNeedsDisplay();
-  }
-};
-
 void CompositedLayerMapping::SetAllLayersNeedDisplay() {
-  ApplyToGraphicsLayers(this, SetContentsNeedsDisplayFunctor(),
-                        kApplyToAllGraphicsLayers);
-}
-
-void CompositedLayerMapping::SetContentsNeedDisplay() {
-  ApplyToGraphicsLayers(this, SetContentsNeedsDisplayFunctor(),
-                        kApplyToContentLayers);
+  ApplyToGraphicsLayers(this, [](GraphicsLayer* graphics_layer) {
+    if (graphics_layer->PaintsContentOrHitTest())
+      graphics_layer->SetNeedsDisplay();
+  });
 }
 
 void CompositedLayerMapping::SetNeedsCheckRasterInvalidation() {
-  ApplyToGraphicsLayers(
-      this,
-      [](GraphicsLayer* graphics_layer) {
-        if (graphics_layer->DrawsContent())
-          graphics_layer->SetNeedsCheckRasterInvalidation();
-      },
-      kApplyToAllGraphicsLayers);
+  ApplyToGraphicsLayers(this, [](GraphicsLayer* graphics_layer) {
+    if (graphics_layer->DrawsContent())
+      graphics_layer->SetNeedsCheckRasterInvalidation();
+  });
 }
 
 const GraphicsLayerPaintInfo* CompositedLayerMapping::ContainingSquashedLayer(
diff --git a/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h b/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h
index d591090..a886866 100644
--- a/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h
+++ b/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h
@@ -387,8 +387,6 @@
       wtf_size_t next_squashed_layer_index);
   void RemoveSquashedLayers(Vector<GraphicsLayerPaintInfo>& squashed_layers);
 
-  void SetContentsNeedDisplay();
-
   PaintLayer& owning_layer_;
 
   // The hierarchy of layers that is maintained by the CompositedLayerMapping
diff --git a/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping_test.cc b/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping_test.cc
index e0d9128..660698a 100644
--- a/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping_test.cc
+++ b/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping_test.cc
@@ -87,47 +87,55 @@
 };
 
 TEST_F(CompositedLayerMappingTest, SubpixelAccumulationChange) {
-  SetBodyInnerHTML(
-      "<div id='target' style='will-change: opacity; background: lightblue; "
-      "position: relative; left: 0.4px; width: 100px; height: 100px'>");
+  SetBodyInnerHTML(R"HTML(
+    <div id='target' style='will-change: opacity; background: lightblue;
+        position: relative; left: 0.4px; width: 100px; height: 100px'>
+      <!-- This div would be snapped to a different pixel -->
+      <div style='position: relative; left: 0.3px; width: 50px; height: 50px;
+           background: green'></div>
+    </div>
+  )HTML");
 
+  GetDocument().View()->SetTracksRasterInvalidations(true);
   Element* target = GetDocument().getElementById("target");
   target->SetInlineStyleProperty(CSSPropertyID::kLeft, "0.6px");
-
-  GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint(
-      DocumentUpdateReason::kTest);
-
-  PaintLayer* paint_layer =
-      ToLayoutBoxModelObject(target->GetLayoutObject())->Layer();
+  UpdateAllLifecyclePhasesForTest();
   // Directly composited layers are not invalidated on subpixel accumulation
   // change.
-  EXPECT_FALSE(paint_layer->GraphicsLayerBacking()
-                   ->GetPaintController()
-                   .GetPaintArtifact()
-                   .IsEmpty());
+  EXPECT_TRUE(target->GetLayoutBox()
+                  ->Layer()
+                  ->GraphicsLayerBacking()
+                  ->GetRasterInvalidationTracking()
+                  ->Invalidations()
+                  .IsEmpty());
+  GetDocument().View()->SetTracksRasterInvalidations(false);
 }
 
 TEST_F(CompositedLayerMappingTest,
        SubpixelAccumulationChangeUnderInvalidation) {
   ScopedPaintUnderInvalidationCheckingForTest test(true);
-  SetBodyInnerHTML(
-      "<div id='target' style='will-change: opacity; background: lightblue; "
-      "position: relative; left: 0.4px; width: 100px; height: 100px'>");
+  SetBodyInnerHTML(R"HTML(
+    <div id='target' style='will-change: opacity; background: lightblue;
+        position: relative; left: 0.4px; width: 100px; height: 100px'>
+      <!-- This div will be snapped to a different pixel -->
+      <div style='position: relative; left: 0.3px; width: 50px; height: 50px;
+           background: green'></div>
+    </div>
+  )HTML");
 
+  GetDocument().View()->SetTracksRasterInvalidations(true);
   Element* target = GetDocument().getElementById("target");
   target->SetInlineStyleProperty(CSSPropertyID::kLeft, "0.6px");
-
-  GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint(
-      DocumentUpdateReason::kTest);
-
-  PaintLayer* paint_layer =
-      ToLayoutBoxModelObject(target->GetLayoutObject())->Layer();
+  UpdateAllLifecyclePhasesForTest();
   // Invalidate directly composited layers on subpixel accumulation change
   // when PaintUnderInvalidationChecking is enabled.
-  EXPECT_TRUE(paint_layer->GraphicsLayerBacking()
-                  ->GetPaintController()
-                  .GetPaintArtifact()
-                  .IsEmpty());
+  EXPECT_FALSE(target->GetLayoutBox()
+                   ->Layer()
+                   ->GraphicsLayerBacking()
+                   ->GetRasterInvalidationTracking()
+                   ->Invalidations()
+                   .IsEmpty());
+  GetDocument().View()->SetTracksRasterInvalidations(false);
 }
 
 TEST_F(CompositedLayerMappingTest,
@@ -158,20 +166,18 @@
     </div>
   )HTML");
 
+  GetDocument().View()->SetTracksRasterInvalidations(true);
   Element* target = GetDocument().getElementById("target");
   target->SetInlineStyleProperty(CSSPropertyID::kLeft, "0.6px");
-
-  GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint(
-      DocumentUpdateReason::kTest);
-
-  PaintLayer* paint_layer =
-      ToLayoutBoxModelObject(target->GetLayoutObject())->Layer();
-  // The PaintArtifact should have been deleted because paint was
-  // invalidated for subpixel accumulation change.
-  EXPECT_TRUE(paint_layer->GraphicsLayerBacking()
-                  ->GetPaintController()
-                  .GetPaintArtifact()
-                  .IsEmpty());
+  UpdateAllLifecyclePhasesForTest();
+  // Invalidate indirectly composited layers on subpixel accumulation change.
+  EXPECT_FALSE(target->GetLayoutBox()
+                   ->Layer()
+                   ->GraphicsLayerBacking()
+                   ->GetRasterInvalidationTracking()
+                   ->Invalidations()
+                   .IsEmpty());
+  GetDocument().View()->SetTracksRasterInvalidations(false);
 }
 
 TEST_F(CompositedLayerMappingTest, SimpleInterestRect) {
diff --git a/third_party/blink/renderer/core/paint/link_highlight_impl.cc b/third_party/blink/renderer/core/paint/link_highlight_impl.cc
index 8e0ccc7b..f987f72 100644
--- a/third_party/blink/renderer/core/paint/link_highlight_impl.cc
+++ b/third_party/blink/renderer/core/paint/link_highlight_impl.cc
@@ -322,7 +322,7 @@
     auto bounding_rect = EnclosingIntRect(new_path.BoundingRect());
     new_path.Translate(-FloatSize(ToIntSize(bounding_rect.Location())));
 
-    auto* layer = link_highlight_fragment.Layer();
+    cc::Layer* layer = link_highlight_fragment.Layer();
     DCHECK(layer);
     if (link_highlight_fragment.GetPath() != new_path) {
       link_highlight_fragment.SetPath(new_path);
diff --git a/third_party/blink/renderer/core/paint/paint_invalidator.cc b/third_party/blink/renderer/core/paint/paint_invalidator.cc
index 49affbe1..c0c3b74 100644
--- a/third_party/blink/renderer/core/paint/paint_invalidator.cc
+++ b/third_party/blink/renderer/core/paint/paint_invalidator.cc
@@ -150,10 +150,11 @@
   // For performance, we ignore subpixel movement of composited layers for paint
   // invalidation. This will result in imperfect pixel-snapped painting.
   // See crbug.com/833083 for details.
-  if (tree_builder_context.current
-          .directly_composited_container_paint_offset_subpixel_delta ==
-      tree_builder_context.current.paint_offset -
-          tree_builder_context.old_paint_offset) {
+  if (!RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled() &&
+      tree_builder_context.current
+              .directly_composited_container_paint_offset_subpixel_delta ==
+          tree_builder_context.current.paint_offset -
+              tree_builder_context.old_paint_offset) {
     context.old_paint_offset = tree_builder_context.current.paint_offset;
   } else {
     context.old_paint_offset = tree_builder_context.old_paint_offset;
diff --git a/third_party/blink/renderer/core/paint/paint_layer.cc b/third_party/blink/renderer/core/paint/paint_layer.cc
index da19b31..217e3937 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer.cc
@@ -1957,7 +1957,7 @@
 
   // For the global root scroller, hit test the layout viewport scrollbars
   // first, as they are visually presented on top of the content.
-  if (GetLayoutObject().IsGlobalRootScroller()) {
+  if (layout_object.IsGlobalRootScroller()) {
     // There are a number of early outs below that don't apply to the the
     // global root scroller.
     DCHECK(!Transform());
@@ -1966,7 +1966,10 @@
     if (scrollable_area_) {
       IntPoint point = scrollable_area_->ConvertFromRootFrameToVisualViewport(
           RoundedIntPoint(recursion_data.location.Point()));
-      if (scrollable_area_->HitTestOverflowControls(result, point))
+
+      DCHECK(GetLayoutBox());
+      if (GetLayoutBox()->HitTestOverflowControl(result, HitTestLocation(point),
+                                                 PhysicalOffset()))
         return this;
     }
   }
diff --git a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
index 65b698c..5947db76 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
@@ -429,17 +429,12 @@
   if (auto* graphics_layer = orientation == kHorizontalScrollbar
                                  ? GraphicsLayerForHorizontalScrollbar()
                                  : GraphicsLayerForVerticalScrollbar()) {
-    graphics_layer->SetNeedsDisplay();
-    graphics_layer->SetContentsNeedsDisplay();
+    graphics_layer->InvalidateContents();
   }
   ScrollableArea::SetScrollbarNeedsPaintInvalidation(orientation);
 }
 
 void PaintLayerScrollableArea::SetScrollCornerNeedsPaintInvalidation() {
-  if (GraphicsLayer* graphics_layer = GraphicsLayerForScrollCorner()) {
-    graphics_layer->SetNeedsDisplay();
-    return;
-  }
   ScrollableArea::SetScrollCornerNeedsPaintInvalidation();
 }
 
@@ -2084,14 +2079,6 @@
 
 void PaintLayerScrollableArea::UpdateResizerStyle(
     const ComputedStyle* old_style) {
-  if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled() && old_style &&
-      old_style->UnresolvedResize() !=
-          GetLayoutBox()->StyleRef().UnresolvedResize()) {
-    // Invalidate the composited scroll corner layer on resize style change.
-    if (auto* graphics_layer = GraphicsLayerForScrollCorner())
-      graphics_layer->SetNeedsDisplay();
-  }
-
   if (!resizer_ && !GetLayoutBox()->CanResize())
     return;
 
@@ -2995,16 +2982,6 @@
         RoundedIntPoint(context.fragment_data->PaintOffset()));
   }
 
-  if (needs_paint_invalidation && graphics_layer) {
-    // If the scrollbar needs paint invalidation but didn't change location/size
-    // or the scrollbar is an overlay scrollbar (visual rect is empty),
-    // invalidating the graphics layer is enough (which has been done in
-    // ScrollableArea::setScrollbarNeedsPaintInvalidation()).
-    needs_paint_invalidation = false;
-    DCHECK(!graphics_layer->PaintsContentOrHitTest() ||
-           graphics_layer->GetPaintController().GetPaintArtifact().IsEmpty());
-  }
-
   // Invalidate the box's display item client if the box's padding box size is
   // affected by change of the non-overlay scrollbar width. We detect change of
   // visual rect size instead of change of scrollbar width, which may have some
@@ -3032,18 +3009,18 @@
 
   previously_was_overlay = is_overlay;
 
-  if (!scrollbar || graphics_layer ||
+  if (!scrollbar ||
       !ScrollControlNeedsPaintInvalidation(
           new_visual_rect, previous_visual_rect, needs_paint_invalidation))
     return new_visual_rect;
 
-  context.painting_layer->SetNeedsRepaint();
-  ObjectPaintInvalidator(box).InvalidateDisplayItemClient(
-      *scrollbar, PaintInvalidationReason::kScrollControl);
-  if (scrollbar->IsCustomScrollbar()) {
-    To<CustomScrollbar>(scrollbar)
-        ->InvalidateDisplayItemClientsOfScrollbarParts();
-  }
+  if (graphics_layer)
+    graphics_layer->Invalidate(PaintInvalidationReason::kScrollControl);
+  else
+    context.painting_layer->SetNeedsRepaint();
+  scrollbar->Invalidate(PaintInvalidationReason::kScrollControl);
+  if (auto* custom_scrollbar = DynamicTo<CustomScrollbar>(scrollbar))
+    custom_scrollbar->InvalidateDisplayItemClientsOfScrollbarParts();
 
   return new_visual_rect;
 }
@@ -3078,16 +3055,19 @@
     scroll_corner_and_resizer_visual_rect_ =
         new_scroll_corner_and_resizer_visual_rect;
     if (LayoutCustomScrollbarPart* scroll_corner = ScrollCorner()) {
+      DCHECK(!scroll_corner->PaintingLayer());
       ObjectPaintInvalidator(*scroll_corner)
-          .SlowSetPaintingLayerNeedsRepaintAndInvalidateDisplayItemClient(
-              *scroll_corner, PaintInvalidationReason::kScrollControl);
+          .InvalidateDisplayItemClient(*scroll_corner,
+                                       PaintInvalidationReason::kScrollControl);
     }
     if (LayoutCustomScrollbarPart* resizer = Resizer()) {
-      ObjectPaintInvalidator(*resizer)
-          .SlowSetPaintingLayerNeedsRepaintAndInvalidateDisplayItemClient(
-              *resizer, PaintInvalidationReason::kScrollControl);
+      DCHECK(!resizer->PaintingLayer());
+      ObjectPaintInvalidator(*resizer).InvalidateDisplayItemClient(
+          *resizer, PaintInvalidationReason::kScrollControl);
     }
-    if (!GraphicsLayerForScrollCorner()) {
+    if (auto* graphics_layer = GraphicsLayerForScrollCorner()) {
+      graphics_layer->Invalidate(PaintInvalidationReason::kScrollControl);
+    } else {
       context.painting_layer->SetNeedsRepaint();
       ObjectPaintInvalidator(box).InvalidateDisplayItemClient(
           GetScrollCornerDisplayItemClient(),
diff --git a/third_party/blink/renderer/core/paint/paint_layer_test.cc b/third_party/blink/renderer/core/paint/paint_layer_test.cc
index da52532..4af3b80 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_test.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_test.cc
@@ -2891,6 +2891,7 @@
 }
 
 TEST_P(PaintLayerTest, GlobalRootScrollerHitTest) {
+  USE_NON_OVERLAY_SCROLLBARS();
   SetBodyInnerHTML(R"HTML(
     <style>
       :root {
@@ -2898,6 +2899,7 @@
         background:blue;
         transform: rotate(30deg);
         transform-style: preserve-3d;
+        overflow-x: hidden;
       }
       #perspective {
         perspective:100px;
@@ -2910,6 +2912,7 @@
     <div id="perspective">
       <div id="threedee"></div>
     </div>
+    <div style="height:1000px"></div>
   )HTML");
   GetDocument().GetPage()->SetPageScaleFactor(2);
   UpdateAllLifecyclePhasesForTest();
@@ -2921,12 +2924,13 @@
   EXPECT_EQ(result.InnerNode(), GetDocument().documentElement());
   EXPECT_EQ(result.GetScrollbar(), nullptr);
 
-  if (GetDocument().GetPage()->GetScrollbarTheme().AllowsHitTest()) {
-    const HitTestLocation location_scrollbar(IntPoint(790, 300));
-    HitTestResult result_scrollbar;
-    EXPECT_EQ(result_scrollbar.InnerNode(), &GetDocument());
-    EXPECT_NE(result_scrollbar.GetScrollbar(), nullptr);
-  }
+  const HitTestLocation location_scrollbar(IntPoint(790, 300));
+  HitTestResult result_scrollbar;
+  GetLayoutView().HitTestNoLifecycleUpdate(location_scrollbar,
+                                           result_scrollbar);
+  EXPECT_EQ(result_scrollbar.InnerNode(), GetDocument().documentElement());
+  EXPECT_NE(result_scrollbar.GetScrollbar(), nullptr);
+  EXPECT_EQ(result_scrollbar.LocalPoint(), location_scrollbar.Point());
 }
 
 TEST_P(PaintLayerTest, HasNonEmptyChildLayoutObjectsZeroSizeOverflowVisible) {
diff --git a/third_party/blink/renderer/core/scroll/scrollbar_theme_aura.cc b/third_party/blink/renderer/core/scroll/scrollbar_theme_aura.cc
index 49df53e..75baf5d 100644
--- a/third_party/blink/renderer/core/scroll/scrollbar_theme_aura.cc
+++ b/third_party/blink/renderer/core/scroll/scrollbar_theme_aura.cc
@@ -31,7 +31,6 @@
 #include "third_party/blink/renderer/core/scroll/scrollbar_theme_aura.h"
 
 #include "build/build_config.h"
-#include "build/chromeos_buildflags.h"
 #include "cc/input/scrollbar.h"
 #include "third_party/blink/public/common/input/web_mouse_event.h"
 #include "third_party/blink/public/platform/platform.h"
@@ -138,7 +137,7 @@
 // Disable snapback on desktop Linux to better integrate with the desktop
 // behavior. Typically, Linux apps do not implement scrollbar snapback (this
 // is true for at least GTK and QT apps).
-#if (defined(OS_LINUX) || BUILDFLAG(IS_LACROS))
+#if defined(OS_LINUX)
   return false;
 #else
   return true;
@@ -304,7 +303,7 @@
 
 bool ScrollbarThemeAura::ShouldCenterOnThumb(const Scrollbar& scrollbar,
                                              const WebMouseEvent& event) {
-#if (defined(OS_LINUX) || BUILDFLAG(IS_LACROS))
+#if defined(OS_LINUX)
   if (event.button == WebPointerProperties::Button::kMiddle)
     return true;
 #endif
diff --git a/third_party/blink/renderer/core/scroll/scrollbar_theme_overlay_test.cc b/third_party/blink/renderer/core/scroll/scrollbar_theme_overlay_test.cc
index d5e4b4a..d077aa1e 100644
--- a/third_party/blink/renderer/core/scroll/scrollbar_theme_overlay_test.cc
+++ b/third_party/blink/renderer/core/scroll/scrollbar_theme_overlay_test.cc
@@ -120,8 +120,8 @@
   vertical_scrollbar->ClearThumbNeedsRepaint();
   mock_scrollable_area->ClearNeedsPaintInvalidationForScrollControls();
 
-  // Hiding the scrollbar should invalidate the layer (SetNeedsDisplay) but not
-  // trigger repaint of the thumb resouce, since the compositor will give the
+  // Hiding the scrollbar should invalidate the layer (InvalidateAll) but not
+  // trigger repaint of the thumb resource, since the compositor will give the
   // entire layer opacity 0.
   EXPECT_CALL(*mock_scrollable_area, ScrollbarsHiddenIfOverlay())
       .WillOnce(Return(true));
diff --git a/third_party/blink/renderer/core/svg/graphics/svg_image.cc b/third_party/blink/renderer/core/svg/graphics/svg_image.cc
index f809cd3..7d2ba0a0 100644
--- a/third_party/blink/renderer/core/svg/graphics/svg_image.cc
+++ b/third_party/blink/renderer/core/svg/graphics/svg_image.cc
@@ -34,6 +34,7 @@
 #include "third_party/blink/public/platform/web_url_loader.h"
 #include "third_party/blink/public/platform/web_url_loader_client.h"
 #include "third_party/blink/public/platform/web_url_loader_factory.h"
+#include "third_party/blink/public/platform/web_url_request_extra_data.h"
 #include "third_party/blink/renderer/core/animation/document_animations.h"
 #include "third_party/blink/renderer/core/animation/document_timeline.h"
 #include "third_party/blink/renderer/core/dom/document_parser.h"
@@ -88,7 +89,7 @@
   // WebURLLoader implementation:
   void LoadSynchronously(
       std::unique_ptr<network::ResourceRequest> request,
-      scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
+      scoped_refptr<WebURLRequestExtraData> url_request_extra_data,
       int requestor_id,
       bool pass_response_pipe_to_client,
       bool no_mime_sniffing,
@@ -106,7 +107,7 @@
   }
   void LoadAsynchronously(
       std::unique_ptr<network::ResourceRequest> request,
-      scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
+      scoped_refptr<WebURLRequestExtraData> url_request_extra_data,
       int requestor_id,
       bool no_mime_sniffing,
       std::unique_ptr<blink::ResourceLoadInfoNotifierWrapper>
diff --git a/third_party/blink/renderer/modules/service_worker/web_embedded_worker_impl_test.cc b/third_party/blink/renderer/modules/service_worker/web_embedded_worker_impl_test.cc
index ea22d0f..0ba8a947 100644
--- a/third_party/blink/renderer/modules/service_worker/web_embedded_worker_impl_test.cc
+++ b/third_party/blink/renderer/modules/service_worker/web_embedded_worker_impl_test.cc
@@ -23,6 +23,7 @@
 #include "third_party/blink/public/platform/web_content_settings_client.h"
 #include "third_party/blink/public/platform/web_url_loader_client.h"
 #include "third_party/blink/public/platform/web_url_loader_mock_factory.h"
+#include "third_party/blink/public/platform/web_url_request_extra_data.h"
 #include "third_party/blink/public/platform/web_url_response.h"
 #include "third_party/blink/public/web/modules/service_worker/web_service_worker_context_client.h"
 #include "third_party/blink/public/web/modules/service_worker/web_service_worker_context_proxy.h"
@@ -48,7 +49,7 @@
 
   void LoadSynchronously(
       std::unique_ptr<network::ResourceRequest> request,
-      scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
+      scoped_refptr<WebURLRequestExtraData> url_request_extra_data,
       int requestor_id,
       bool pass_response_pipe_to_client,
       bool no_mime_sniffing,
@@ -67,7 +68,7 @@
 
   void LoadAsynchronously(
       std::unique_ptr<network::ResourceRequest> request,
-      scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
+      scoped_refptr<WebURLRequestExtraData> url_request_extra_data,
       int requestor_id,
       bool no_mime_sniffing,
       std::unique_ptr<blink::ResourceLoadInfoNotifierWrapper>
diff --git a/third_party/blink/renderer/modules/webcodecs/video_encoder.cc b/third_party/blink/renderer/modules/webcodecs/video_encoder.cc
index 488ba741..62e262e4 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_encoder.cc
+++ b/third_party/blink/renderer/modules/webcodecs/video_encoder.cc
@@ -11,7 +11,9 @@
 #include "base/logging.h"
 #include "base/macros.h"
 #include "build/build_config.h"
+#include "media/base/async_destroy_video_encoder.h"
 #include "media/base/mime_util.h"
+#include "media/base/offloading_video_encoder.h"
 #include "media/base/video_codecs.h"
 #include "media/base/video_color_space.h"
 #include "media/base/video_encoder.h"
@@ -21,7 +23,6 @@
 #if BUILDFLAG(ENABLE_LIBVPX)
 #include "media/video/vpx_video_encoder.h"
 #endif
-#include "media/base/async_destroy_video_encoder.h"
 #include "media/video/gpu_video_accelerator_factories.h"
 #include "media/video/video_encode_accelerator_adapter.h"
 #include "third_party/blink/public/mojom/web_feature/web_feature.mojom-blink.h"
@@ -288,31 +289,27 @@
   switch (config.acc_pref) {
     case AccelerationPreference::kRequire:
       return CreateAcceleratedVideoEncoder(config.profile, config.options);
-    case AccelerationPreference::kAllow: {
-      auto result =
-          CreateAcceleratedVideoEncoder(config.profile, config.options);
-      if (result)
+    case AccelerationPreference::kAllow:
+      if (auto result =
+              CreateAcceleratedVideoEncoder(config.profile, config.options))
         return result;
-      switch (config.codec) {
-        case media::kCodecVP8:
-        case media::kCodecVP9:
-          return CreateVpxVideoEncoder();
-        case media::kCodecH264:
-          return CreateOpenH264VideoEncoder();
-        default:
-          return nullptr;
-      }
-    }
+      FALLTHROUGH;
     case AccelerationPreference::kDeny: {
+      std::unique_ptr<media::VideoEncoder> result;
       switch (config.codec) {
         case media::kCodecVP8:
         case media::kCodecVP9:
-          return CreateVpxVideoEncoder();
+          result = CreateVpxVideoEncoder();
+          break;
         case media::kCodecH264:
-          return CreateOpenH264VideoEncoder();
+          result = CreateOpenH264VideoEncoder();
+          break;
         default:
           return nullptr;
       }
+      if (!result)
+        return nullptr;
+      return std::make_unique<media::OffloadingVideoEncoder>(std::move(result));
     }
 
     default:
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
index 40589b2..8b81ab9 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
@@ -1899,8 +1899,7 @@
   return true;
 }
 
-void WebGLRenderingContextBase::bindBufferImpl(GLenum target,
-                                               WebGLBuffer* buffer) {
+void WebGLRenderingContextBase::bindBuffer(GLenum target, WebGLBuffer* buffer) {
   if (!ValidateNullableWebGLObject("bindBuffer", buffer))
     return;
   if (!ValidateAndUpdateBufferBindTarget("bindBuffer", target, buffer))
@@ -1908,20 +1907,6 @@
   ContextGL()->BindBuffer(target, ObjectOrZero(buffer));
 }
 
-void WebGLRenderingContextBase::bindBuffer(GLenum target, WebGLBuffer* buffer) {
-  fast_call_.FlushDeferredEvents(this);
-
-  bindBufferImpl(target, buffer);
-}
-
-void WebGLRenderingContextBase::bindBuffer(
-    GLenum target,
-    WebGLBuffer* buffer,
-    v8::FastApiCallbackOptions& options) {
-  auto scoped_call = fast_call_.EnterScoped(&options.fallback);
-  bindBufferImpl(target, buffer);
-}
-
 void WebGLRenderingContextBase::bindFramebuffer(GLenum target,
                                                 WebGLFramebuffer* buffer) {
   if (!ValidateNullableWebGLObject("bindFramebuffer", buffer))
@@ -1950,8 +1935,8 @@
     render_buffer->SetHasEverBeenBound();
 }
 
-void WebGLRenderingContextBase::bindTextureImpl(GLenum target,
-                                                WebGLTexture* texture) {
+void WebGLRenderingContextBase::bindTexture(GLenum target,
+                                            WebGLTexture* texture) {
   if (!ValidateNullableWebGLObject("bindTexture", texture))
     return;
   if (texture && texture->GetTarget() && texture->GetTarget() != target) {
@@ -2026,21 +2011,6 @@
   // been removed.
 }
 
-void WebGLRenderingContextBase::bindTexture(GLenum target,
-                                            WebGLTexture* texture) {
-  fast_call_.FlushDeferredEvents(this);
-
-  bindTextureImpl(target, texture);
-}
-
-void WebGLRenderingContextBase::bindTexture(
-    GLenum target,
-    WebGLTexture* texture,
-    v8::FastApiCallbackOptions& options) {
-  auto scoped_call = fast_call_.EnterScoped(&options.fallback);
-  bindTextureImpl(target, texture);
-}
-
 void WebGLRenderingContextBase::blendColor(GLfloat red,
                                            GLfloat green,
                                            GLfloat blue,
@@ -2743,9 +2713,9 @@
   return true;
 }
 
-void WebGLRenderingContextBase::drawArrays(GLenum mode,
-                                           GLint first,
-                                           GLsizei count) {
+void WebGLRenderingContextBase::drawArraysImpl(GLenum mode,
+                                               GLint first,
+                                               GLsizei count) {
   if (!ValidateDrawArrays("drawArrays"))
     return;
 
@@ -2761,18 +2731,10 @@
   ContextGL()->DrawArrays(mode, first, count);
 }
 
-void WebGLRenderingContextBase::drawArrays(
-    GLenum mode,
-    GLint first,
-    GLsizei count,
-    v8::FastApiCallbackOptions& options) {
-  drawArrays(mode, first, count);
-}
-
-void WebGLRenderingContextBase::drawElements(GLenum mode,
-                                             GLsizei count,
-                                             GLenum type,
-                                             int64_t offset) {
+void WebGLRenderingContextBase::drawElementsImpl(GLenum mode,
+                                                 GLsizei count,
+                                                 GLenum type,
+                                                 int64_t offset) {
   if (!ValidateDrawElements("drawElements", type, offset))
     return;
 
@@ -2790,15 +2752,6 @@
       reinterpret_cast<void*>(static_cast<intptr_t>(offset)));
 }
 
-void WebGLRenderingContextBase::drawElements(
-    GLenum mode,
-    GLsizei count,
-    GLenum type,
-    int64_t offset,
-    v8::FastApiCallbackOptions& options) {
-  drawElements(mode, count, type, offset);
-}
-
 void WebGLRenderingContextBase::DrawArraysInstancedANGLE(GLenum mode,
                                                          GLint first,
                                                          GLsizei count,
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h
index 8d9cf49..16a6625 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h
@@ -167,16 +167,10 @@
   void activeTexture(GLenum texture);
   void attachShader(WebGLProgram*, WebGLShader*);
   void bindAttribLocation(WebGLProgram*, GLuint index, const String& name);
-  void bindBuffer(GLenum target, WebGLBuffer*);
-  void bindBuffer(GLenum target,
-                  WebGLBuffer*,
-                  v8::FastApiCallbackOptions& options);
+  void bindBuffer(GLenum target, WebGLBuffer* buffer);
   virtual void bindFramebuffer(GLenum target, WebGLFramebuffer*);
   void bindRenderbuffer(GLenum target, WebGLRenderbuffer*);
   void bindTexture(GLenum target, WebGLTexture*);
-  void bindTexture(GLenum target,
-                   WebGLTexture*,
-                   v8::FastApiCallbackOptions& options);
   void blendColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
   void blendEquation(GLenum mode);
   void blendEquationSeparate(GLenum mode_rgb, GLenum mode_alpha);
@@ -262,17 +256,40 @@
   void detachShader(WebGLProgram*, WebGLShader*);
   void disable(GLenum cap);
   void disableVertexAttribArray(GLuint index);
-  void drawArrays(GLenum mode, GLint first, GLsizei count);
+
+  void drawArraysImpl(GLenum mode, GLint first, GLsizei count);
+  void drawArrays(GLenum mode, GLint first, GLsizei count) {
+    if (fast_call_.FlushDeferredEvents(this)) {
+      return;
+    }
+    drawArraysImpl(mode, first, count);
+  }
   void drawArrays(GLenum mode,
                   GLint first,
                   GLsizei count,
-                  v8::FastApiCallbackOptions& options);
-  void drawElements(GLenum mode, GLsizei count, GLenum type, int64_t offset);
+                  v8::FastApiCallbackOptions& options) {
+    auto scoped_call = fast_call_.EnterScoped(&options.fallback);
+    drawArraysImpl(mode, first, count);
+  }
+
+  void drawElementsImpl(GLenum mode,
+                        GLsizei count,
+                        GLenum type,
+                        int64_t offset);
+  void drawElements(GLenum mode, GLsizei count, GLenum type, int64_t offset) {
+    if (fast_call_.FlushDeferredEvents(this)) {
+      return;
+    }
+    drawElementsImpl(mode, count, type, offset);
+  }
   void drawElements(GLenum mode,
                     GLsizei count,
                     GLenum type,
                     int64_t offset,
-                    v8::FastApiCallbackOptions& options);
+                    v8::FastApiCallbackOptions& options) {
+    auto scoped_call = fast_call_.EnterScoped(&options.fallback);
+    drawElementsImpl(mode, count, type, offset);
+  }
 
   void DrawArraysInstancedANGLE(GLenum mode,
                                 GLint first,
@@ -1811,9 +1828,6 @@
                                            GLenum precision_type,
                                            WebGLShaderPrecisionFormat* format);
 
-  void bindBufferImpl(GLenum target, WebGLBuffer*);
-  void bindTextureImpl(GLenum target, WebGLTexture*);
-
   static bool webgl_context_limits_initialized_;
   static unsigned max_active_webgl_contexts_;
   static unsigned max_active_webgl_contexts_on_worker_;
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.idl b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.idl
index 920091a..4886751 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.idl
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.idl
@@ -468,10 +468,10 @@
     void activeTexture(GLenum texture);
     void attachShader(WebGLProgram program, WebGLShader shader);
     void bindAttribLocation(WebGLProgram program, GLuint index, DOMString name);
-    [NoAllocDirectCall] void bindBuffer(GLenum target, WebGLBuffer? buffer);
+    void bindBuffer(GLenum target, WebGLBuffer? buffer);
     void bindFramebuffer(GLenum target, WebGLFramebuffer? framebuffer);
     void bindRenderbuffer(GLenum target, WebGLRenderbuffer? renderbuffer);
-    [NoAllocDirectCall] void bindTexture(GLenum target, WebGLTexture? texture);
+    void bindTexture(GLenum target, WebGLTexture? texture);
     void blendColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha);
     void blendEquation(GLenum mode);
     void blendEquationSeparate(GLenum modeRGB, GLenum modeAlpha);
diff --git a/third_party/blink/renderer/modules/webgpu/dawn_conversions.cc b/third_party/blink/renderer/modules/webgpu/dawn_conversions.cc
index 0faaefd7..60dc608 100644
--- a/third_party/blink/renderer/modules/webgpu/dawn_conversions.cc
+++ b/third_party/blink/renderer/modules/webgpu/dawn_conversions.cc
@@ -852,6 +852,7 @@
   dawn_view.texture = webgpu_view->texture()->GetHandle();
   dawn_view.mipLevel = webgpu_view->mipLevel();
   dawn_view.origin = AsDawnType(&webgpu_view->origin());
+  dawn_view.aspect = AsDawnEnum<WGPUTextureAspect>(webgpu_view->aspect());
 
   return dawn_view;
 }
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_texture_copy_view.idl b/third_party/blink/renderer/modules/webgpu/gpu_texture_copy_view.idl
index 16e2295..474ad13 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_texture_copy_view.idl
+++ b/third_party/blink/renderer/modules/webgpu/gpu_texture_copy_view.idl
@@ -8,4 +8,5 @@
     required GPUTexture texture;
     GPUSize32 mipLevel = 0;
     GPUOrigin3D origin = {};
+    GPUTextureAspect aspect = "all";
 };
diff --git a/third_party/blink/renderer/modules/xr/BUILD.gn b/third_party/blink/renderer/modules/xr/BUILD.gn
index df6b2dc..cca92207 100644
--- a/third_party/blink/renderer/modules/xr/BUILD.gn
+++ b/third_party/blink/renderer/modules/xr/BUILD.gn
@@ -35,6 +35,8 @@
     "xr_hit_test_result.h",
     "xr_hit_test_source.cc",
     "xr_hit_test_source.h",
+    "xr_image_tracking_result.cc",
+    "xr_image_tracking_result.h",
     "xr_input_source.cc",
     "xr_input_source.h",
     "xr_input_source_array.cc",
diff --git a/third_party/blink/renderer/modules/xr/idls.gni b/third_party/blink/renderer/modules/xr/idls.gni
index 0346ec6..5343d495 100644
--- a/third_party/blink/renderer/modules/xr/idls.gni
+++ b/third_party/blink/renderer/modules/xr/idls.gni
@@ -11,6 +11,7 @@
   "xr_depth_information.idl",
   "xr_dom_overlay_state.idl",
   "xr_frame.idl",
+  "xr_image_tracking_result.idl",
   "xr_input_source.idl",
   "xr_input_source_array.idl",
   "xr_input_source_event.idl",
@@ -56,6 +57,7 @@
   "xr_render_state_init.idl",
   "xr_session_event_init.idl",
   "xr_session_init.idl",
+  "xr_tracked_image_init.idl",
   "xr_transient_input_hit_test_options_init.idl",
   "xr_webgl_layer_init.idl",
 ]
diff --git a/third_party/blink/renderer/modules/xr/xr_frame.cc b/third_party/blink/renderer/modules/xr/xr_frame.cc
index 1bc6f453..2617193 100644
--- a/third_party/blink/renderer/modules/xr/xr_frame.cc
+++ b/third_party/blink/renderer/modules/xr/xr_frame.cc
@@ -347,6 +347,11 @@
       reference_space_information->native_origin, exception_state);
 }
 
+HeapVector<Member<XRImageTrackingResult>> XRFrame::getImageTrackingResults(
+    ExceptionState& exception_state) {
+  return session_->ImageTrackingResults(exception_state);
+}
+
 void XRFrame::Trace(Visitor* visitor) const {
   visitor->Trace(session_);
   visitor->Trace(world_information_);
diff --git a/third_party/blink/renderer/modules/xr/xr_frame.h b/third_party/blink/renderer/modules/xr/xr_frame.h
index 1865317b..2ec60de 100644
--- a/third_party/blink/renderer/modules/xr/xr_frame.h
+++ b/third_party/blink/renderer/modules/xr/xr_frame.h
@@ -22,6 +22,7 @@
 class XRDepthInformation;
 class XRHitTestResult;
 class XRHitTestSource;
+class XRImageTrackingResult;
 class XRInputSource;
 class XRLightEstimate;
 class XRLightProbe;
@@ -77,6 +78,9 @@
                              XRSpace* space,
                              ExceptionState& exception_state);
 
+  HeapVector<Member<XRImageTrackingResult>> getImageTrackingResults(
+      ExceptionState&);
+
  private:
   std::unique_ptr<TransformationMatrix> GetAdjustedPoseMatrix(XRSpace*) const;
   XRPose* GetTargetRayPose(XRInputSource*, XRSpace*) const;
diff --git a/third_party/blink/renderer/modules/xr/xr_frame.idl b/third_party/blink/renderer/modules/xr/xr_frame.idl
index 0149aa5..d7839af 100644
--- a/third_party/blink/renderer/modules/xr/xr_frame.idl
+++ b/third_party/blink/renderer/modules/xr/xr_frame.idl
@@ -32,4 +32,7 @@
 
   [RuntimeEnabled=WebXRDepth, RaisesException, MeasureAs=XRFrameGetDepthInformation]
   XRDepthInformation getDepthInformation(XRView view);
+
+  [RuntimeEnabled=WebXRImageTracking, RaisesException]
+  FrozenArray<XRImageTrackingResult> getImageTrackingResults();
 };
diff --git a/third_party/blink/renderer/modules/xr/xr_image_tracking_result.cc b/third_party/blink/renderer/modules/xr/xr_image_tracking_result.cc
new file mode 100644
index 0000000..5e59609
--- /dev/null
+++ b/third_party/blink/renderer/modules/xr/xr_image_tracking_result.cc
@@ -0,0 +1,62 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/modules/xr/xr_image_tracking_result.h"
+
+#include "third_party/blink/renderer/core/html/html_image_element.h"
+#include "third_party/blink/renderer/modules/xr/xr_object_space.h"
+#include "third_party/blink/renderer/modules/xr/xr_pose.h"
+#include "third_party/blink/renderer/modules/xr/xr_session.h"
+#include "third_party/blink/renderer/modules/xr/xr_space.h"
+#include "third_party/blink/renderer/platform/transforms/transformation_matrix.h"
+
+namespace blink {
+
+class XRImageSpace : public XRObjectSpace<XRImageTrackingResult> {
+ public:
+  XRImageSpace(XRSession* session, const XRImageTrackingResult* object)
+      : XRObjectSpace<XRImageTrackingResult>(session, object) {}
+
+  bool IsStationary() const override { return false; }
+};
+
+XRImageTrackingResult::XRImageTrackingResult(
+    XRSession* session,
+    const device::mojom::blink::XRTrackedImageData& result)
+    : session_(session),
+      index_(result.index),
+      mojo_from_this_(result.mojo_from_image),
+      width_in_meters_(result.width_in_meters) {
+  DVLOG(2) << __func__ << ": image index=" << index_;
+  if (result.actively_tracked) {
+    tracking_state_string_ = "tracked";
+  } else {
+    tracking_state_string_ = "emulated";
+  }
+}
+
+base::Optional<TransformationMatrix> XRImageTrackingResult::MojoFromObject()
+    const {
+  if (!mojo_from_this_) {
+    return base::nullopt;
+  }
+
+  return mojo_from_this_->ToTransform().matrix();
+}
+
+XRSpace* XRImageTrackingResult::imageSpace() const {
+  if (!image_space_) {
+    image_space_ = MakeGarbageCollected<XRImageSpace>(session_, this);
+  }
+
+  return image_space_;
+}
+
+void XRImageTrackingResult::Trace(Visitor* visitor) const {
+  visitor->Trace(session_);
+  visitor->Trace(image_space_);
+  ScriptWrappable::Trace(visitor);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/xr/xr_image_tracking_result.h b/third_party/blink/renderer/modules/xr/xr_image_tracking_result.h
new file mode 100644
index 0000000..26eed7d
--- /dev/null
+++ b/third_party/blink/renderer/modules/xr/xr_image_tracking_result.h
@@ -0,0 +1,52 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_IMAGE_TRACKING_RESULT_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_IMAGE_TRACKING_RESULT_H_
+
+#include "base/optional.h"
+#include "device/vr/public/mojom/pose.h"
+#include "device/vr/public/mojom/vr_service.mojom-blink-forward.h"
+#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+
+namespace blink {
+
+class TransformationMatrix;
+class XRSession;
+class XRSpace;
+
+class XRImageTrackingResult : public ScriptWrappable {
+  DEFINE_WRAPPERTYPEINFO();
+
+ public:
+  explicit XRImageTrackingResult(
+      XRSession* session,
+      const device::mojom::blink::XRTrackedImageData& image_tracking_result);
+
+  XRSpace* imageSpace() const;
+  base::Optional<TransformationMatrix> MojoFromObject() const;
+
+  uint32_t index() { return index_; }
+
+  float measuredWidthInMeters() { return width_in_meters_; }
+
+  const String& trackingState() { return tracking_state_string_; }
+
+  void Trace(Visitor* visitor) const override;
+
+ private:
+  Member<XRSession> session_;
+  uint32_t index_;
+  String tracking_state_string_;
+  base::Optional<device::Pose> mojo_from_this_;
+  float width_in_meters_;
+
+  // Cached image space - it will be created by `imageSpace()` if it's not set.
+  mutable Member<XRSpace> image_space_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_IMAGE_TRACKING_RESULT_H_
diff --git a/third_party/blink/renderer/modules/xr/xr_image_tracking_result.idl b/third_party/blink/renderer/modules/xr/xr_image_tracking_result.idl
new file mode 100644
index 0000000..ca231aa
--- /dev/null
+++ b/third_party/blink/renderer/modules/xr/xr_image_tracking_result.idl
@@ -0,0 +1,25 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// PRELIMINARY draft explainer, this is still in ongoing discussions
+// with the immersive-web group and likely to change:
+//
+//   https://github.com/klausw/webxr-image-tracking/blob/main/explainer.md
+
+enum XRImageTrackingState {
+  "tracked",
+  "emulated",
+};
+
+[
+    RuntimeEnabled=WebXRImageTracking,
+    SecureContext,
+    Exposed=Window
+]
+interface XRImageTrackingResult {
+  [SameObject] readonly attribute XRSpace imageSpace;
+  readonly attribute unsigned long index;
+  readonly attribute XRImageTrackingState trackingState;
+  readonly attribute float measuredWidthInMeters;
+};
diff --git a/third_party/blink/renderer/modules/xr/xr_native_origin_information.cc b/third_party/blink/renderer/modules/xr/xr_native_origin_information.cc
index b29f4c0..a0662f9a 100644
--- a/third_party/blink/renderer/modules/xr/xr_native_origin_information.cc
+++ b/third_party/blink/renderer/modules/xr/xr_native_origin_information.cc
@@ -25,6 +25,17 @@
 }
 
 device::mojom::blink::XRNativeOriginInformation Create(
+    const XRImageTrackingResult* image) {
+  DCHECK(image);
+
+  // TODO(https://crbug.com/1143575): We'll want these to correspond to an
+  // actual, independent space eventually, but at the moment it's sufficient for
+  // the ARCore implementation to have it be equivalent to the local reference
+  // space.
+  return Create(device::mojom::XRReferenceSpaceType::kLocal);
+}
+
+device::mojom::blink::XRNativeOriginInformation Create(
     const XRInputSource* input_source) {
   DCHECK(input_source);
 
diff --git a/third_party/blink/renderer/modules/xr/xr_native_origin_information.h b/third_party/blink/renderer/modules/xr/xr_native_origin_information.h
index 368b542..179424e5 100644
--- a/third_party/blink/renderer/modules/xr/xr_native_origin_information.h
+++ b/third_party/blink/renderer/modules/xr/xr_native_origin_information.h
@@ -10,6 +10,7 @@
 namespace blink {
 
 class XRAnchor;
+class XRImageTrackingResult;
 class XRInputSource;
 class XRLightProbe;
 class XRPlane;
@@ -19,6 +20,8 @@
 
 device::mojom::blink::XRNativeOriginInformation Create(const XRAnchor* anchor);
 device::mojom::blink::XRNativeOriginInformation Create(
+    const XRImageTrackingResult* image);
+device::mojom::blink::XRNativeOriginInformation Create(
     const XRInputSource* input_source);
 device::mojom::blink::XRNativeOriginInformation Create(const XRPlane* plane);
 device::mojom::blink::XRNativeOriginInformation Create(
diff --git a/third_party/blink/renderer/modules/xr/xr_session.cc b/third_party/blink/renderer/modules/xr/xr_session.cc
index dd50620..e0bacfc 100644
--- a/third_party/blink/renderer/modules/xr/xr_session.cc
+++ b/third_party/blink/renderer/modules/xr/xr_session.cc
@@ -17,6 +17,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_xr_frame_request_callback.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_xr_hit_test_options_init.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_xr_image_tracking_result.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_xr_render_state_init.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_xr_transient_input_hit_test_options_init.h"
 #include "third_party/blink/renderer/core/dom/dom_exception.h"
@@ -97,6 +98,9 @@
 const char kLightEstimationFeatureNotSupported[] =
     "Light estimation feature is not supported.";
 
+const char kImageTrackingFeatureNotSupported[] =
+    "Image tracking feature is not supported.";
+
 const char kEntityTypesNotSpecified[] =
     "No entityTypes specified: the array cannot be empty!";
 
@@ -298,6 +302,7 @@
     case XRSessionFeature::CAMERA_ACCESS:
     case XRSessionFeature::PLANE_DETECTION:
     case XRSessionFeature::DEPTH:
+    case XRSessionFeature::IMAGE_TRACKING:
       // Not recording metrics for these features currently.
       break;
   }
@@ -1024,6 +1029,13 @@
     resolver->Reject(MakeGarbageCollected<DOMException>(
         DOMExceptionCode::kInvalidStateError, kDeviceDisconnected));
   }
+
+  HeapVector<Member<ScriptPromiseResolver>> image_score_promises;
+  image_scores_resolvers_.swap(image_score_promises);
+  for (ScriptPromiseResolver* resolver : image_score_promises) {
+    resolver->Reject(MakeGarbageCollected<DOMException>(
+        DOMExceptionCode::kInvalidStateError, kDeviceDisconnected));
+  }
 }
 
 void XRSession::ProcessAnchorsData(
@@ -1638,6 +1650,83 @@
   }
 }
 
+ScriptPromise XRSession::getTrackedImageScores(
+    ScriptState* script_state,
+    ExceptionState& exception_state) {
+  DVLOG(3) << __func__;
+  if (ended_) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                      kSessionEnded);
+    return ScriptPromise();
+  }
+
+  if (!IsFeatureEnabled(device::mojom::XRSessionFeature::IMAGE_TRACKING)) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
+                                      kImageTrackingFeatureNotSupported);
+    return ScriptPromise();
+  }
+
+  auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
+  ScriptPromise promise = resolver->Promise();
+
+  if (tracked_image_scores_available_) {
+    DVLOG(3) << __func__ << ": returning existing results";
+    resolver->Resolve(tracked_image_scores_);
+  } else {
+    DVLOG(3) << __func__ << ": storing promise";
+    image_scores_resolvers_.push_back(resolver);
+  }
+
+  return promise;
+}
+
+void XRSession::ProcessTrackedImagesData(
+    const device::mojom::blink::XRTrackedImagesData* images_data) {
+  DVLOG(3) << __func__;
+  frame_tracked_images_.clear();
+
+  if (!images_data) {
+    return;
+  }
+
+  for (const auto& image : images_data->images_data) {
+    DVLOG(3) << __func__ << ": image index=" << image->index;
+    XRImageTrackingResult* result =
+        MakeGarbageCollected<XRImageTrackingResult>(this, *image);
+    frame_tracked_images_.push_back(result);
+  }
+
+  if (images_data->image_trackable_scores) {
+    DVLOG(3) << ": got image_trackable_scores";
+    DCHECK(!tracked_image_scores_available_);
+    auto& scores = images_data->image_trackable_scores.value();
+    for (WTF::wtf_size_t index = 0; index < scores.size(); ++index) {
+      tracked_image_scores_.push_back(scores[index] ? "trackable"
+                                                    : "untrackable");
+      DVLOG(3) << __func__ << ": score[" << index
+               << "]=" << tracked_image_scores_[index];
+    }
+    HeapVector<Member<ScriptPromiseResolver>> image_score_promises;
+    image_scores_resolvers_.swap(image_score_promises);
+    for (ScriptPromiseResolver* resolver : image_score_promises) {
+      DVLOG(3) << __func__ << ": resolving promise";
+      resolver->Resolve(tracked_image_scores_);
+    }
+    tracked_image_scores_available_ = true;
+  }
+}
+
+HeapVector<Member<XRImageTrackingResult>> XRSession::ImageTrackingResults(
+    ExceptionState& exception_state) {
+  if (!IsFeatureEnabled(device::mojom::XRSessionFeature::IMAGE_TRACKING)) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
+                                      kImageTrackingFeatureNotSupported);
+    return {};
+  }
+
+  return frame_tracked_images_;
+}
+
 void XRSession::UpdateWorldUnderstandingStateForFrame(
     double timestamp,
     const device::mojom::blink::XRFrameDataPtr& frame_data) {
@@ -1648,6 +1737,7 @@
     ProcessAnchorsData(frame_data->anchors_data.get(), timestamp);
     ProcessHitTestData(frame_data->hit_test_subscription_results.get());
     ProcessDepthData(std::move(frame_data->depth_data));
+    ProcessTrackedImagesData(frame_data->tracked_images.get());
 
     const device::mojom::blink::XRLightEstimationData* light_data =
         frame_data->light_estimation_data.get();
@@ -1659,6 +1749,7 @@
     ProcessAnchorsData(nullptr, timestamp);
     ProcessHitTestData(nullptr);
     ProcessDepthData(nullptr);
+    ProcessTrackedImagesData(nullptr);
 
     if (world_light_probe_) {
       world_light_probe_->ProcessLightEstimationData(nullptr, timestamp);
@@ -2203,6 +2294,8 @@
   visitor->Trace(hit_test_source_ids_to_hit_test_sources_);
   visitor->Trace(hit_test_source_ids_to_transient_input_hit_test_sources_);
   visitor->Trace(views_);
+  visitor->Trace(frame_tracked_images_);
+  visitor->Trace(image_scores_resolvers_);
   EventTargetWithInlineData::Trace(visitor);
 }
 
diff --git a/third_party/blink/renderer/modules/xr/xr_session.h b/third_party/blink/renderer/modules/xr/xr_session.h
index dcb678a..149b06a 100644
--- a/third_party/blink/renderer/modules/xr/xr_session.h
+++ b/third_party/blink/renderer/modules/xr/xr_session.h
@@ -45,6 +45,7 @@
 class XRDOMOverlayState;
 class XRHitTestOptionsInit;
 class XRHitTestSource;
+class XRImageTrackingResult;
 class XRLightProbe;
 class XRReferenceSpace;
 class XRRenderState;
@@ -200,6 +201,9 @@
 
   ScriptPromise requestLightProbe(ScriptState* script_state, ExceptionState&);
 
+  ScriptPromise getTrackedImageScores(ScriptState* script_state,
+                                      ExceptionState&);
+
   // Called by JavaScript to manually end the session.
   ScriptPromise end(ScriptState* script_state, ExceptionState&);
 
@@ -356,6 +360,9 @@
   // a specific HTMLVideoELement, for the next requestAnimationFrame() call.
   void ScheduleVideoFrameCallbacksExecution(ExecuteVfcCallback);
 
+  HeapVector<Member<XRImageTrackingResult>> ImageTrackingResults(
+      ExceptionState&);
+
  private:
   class XRSessionResizeObserverDelegate;
 
@@ -423,6 +430,13 @@
 
   void ProcessDepthData(device::mojom::blink::XRDepthDataPtr depth_data);
 
+  void ProcessTrackedImagesData(
+      const device::mojom::blink::XRTrackedImagesData*);
+  HeapVector<Member<XRImageTrackingResult>> frame_tracked_images_;
+  bool tracked_image_scores_available_ = false;
+  Vector<String> tracked_image_scores_;
+  HeapVector<Member<ScriptPromiseResolver>> image_scores_resolvers_;
+
   void HandleShutdown();
 
   void ExecuteVideoFrameCallbacks(double timestamp);
diff --git a/third_party/blink/renderer/modules/xr/xr_session.idl b/third_party/blink/renderer/modules/xr/xr_session.idl
index d78413b1..82959646 100644
--- a/third_party/blink/renderer/modules/xr/xr_session.idl
+++ b/third_party/blink/renderer/modules/xr/xr_session.idl
@@ -27,6 +27,11 @@
   "world-space"
 };
 
+enum XRImageTrackingScore {
+  "untrackable",
+  "trackable",
+};
+
 [
     ActiveScriptWrappable,
     SecureContext,
@@ -70,4 +75,7 @@
 
   [RuntimeEnabled=WebXRLightEstimation, CallWith=ScriptState, RaisesException]
   Promise<XRLightProbe> requestLightProbe();
+
+  [RuntimeEnabled=WebXRImageTracking, CallWith=ScriptState, RaisesException]
+  Promise<FrozenArray<XRImageTrackingScore>> getTrackedImageScores();
 };
diff --git a/third_party/blink/renderer/modules/xr/xr_session_init.idl b/third_party/blink/renderer/modules/xr/xr_session_init.idl
index 11d315b..2e1b5d7 100644
--- a/third_party/blink/renderer/modules/xr/xr_session_init.idl
+++ b/third_party/blink/renderer/modules/xr/xr_session_init.idl
@@ -7,4 +7,5 @@
   sequence<any> requiredFeatures;
   sequence<any> optionalFeatures;
   XRDOMOverlayInit domOverlay;
+  [RuntimeEnabled=WebXRImageTracking] sequence<XRTrackedImageInit> trackedImages;
 };
diff --git a/third_party/blink/renderer/modules/xr/xr_system.cc b/third_party/blink/renderer/modules/xr/xr_system.cc
index eaac9f36..c86112f 100644
--- a/third_party/blink/renderer/modules/xr/xr_system.cc
+++ b/third_party/blink/renderer/modules/xr/xr_system.cc
@@ -11,6 +11,7 @@
 #include "third_party/blink/public/mojom/feature_policy/feature_policy.mojom-blink.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_fullscreen_options.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_xr_tracked_image_init.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/dom_exception.h"
 #include "third_party/blink/renderer/core/dom/element.h"
@@ -21,6 +22,7 @@
 #include "third_party/blink/renderer/core/fullscreen/fullscreen.h"
 #include "third_party/blink/renderer/core/fullscreen/scoped_allow_fullscreen.h"
 #include "third_party/blink/renderer/core/html/html_element.h"
+#include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h"
 #include "third_party/blink/renderer/core/inspector/console_message.h"
 #include "third_party/blink/renderer/core/layout/layout_view.h"
 #include "third_party/blink/renderer/core/loader/document_loader.h"
@@ -29,6 +31,7 @@
 #include "third_party/blink/renderer/modules/xr/xr_frame_provider.h"
 #include "third_party/blink/renderer/modules/xr/xr_session.h"
 #include "third_party/blink/renderer/platform/bindings/v8_throw_exception.h"
+#include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h"
 #include "third_party/blink/renderer/platform/heap/heap.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_view.h"
@@ -141,6 +144,9 @@
   } else if (RuntimeEnabledFeatures::WebXRDepthEnabled(context) &&
              feature_string == "depth-sensing") {
     return device::mojom::XRSessionFeature::DEPTH;
+  } else if (RuntimeEnabledFeatures::WebXRImageTrackingEnabled(context) &&
+             feature_string == "image-tracking") {
+    return device::mojom::XRSessionFeature::IMAGE_TRACKING;
   }
 
   return base::nullopt;
@@ -173,6 +179,17 @@
         return false;
       }
       return true;
+    case device::mojom::XRSessionFeature::IMAGE_TRACKING:
+      if (mode != device::mojom::blink::XRSessionMode::kImmersiveAr)
+        return false;
+      if (!session_init->hasTrackedImages()) {
+        execution_context->AddConsoleMessage(
+            MakeGarbageCollected<ConsoleMessage>(
+                mojom::blink::ConsoleMessageSource::kJavaScript, error_level,
+                "Must specify trackedImages in XRSessionInit"));
+        return false;
+      }
+      return true;
     case device::mojom::XRSessionFeature::LIGHT_ESTIMATION:
     case device::mojom::XRSessionFeature::CAMERA_ACCESS:
     case device::mojom::XRSessionFeature::PLANE_DETECTION:
@@ -202,6 +219,7 @@
     case device::mojom::XRSessionFeature::CAMERA_ACCESS:
     case device::mojom::XRSessionFeature::PLANE_DETECTION:
     case device::mojom::XRSessionFeature::DEPTH:
+    case device::mojom::XRSessionFeature::IMAGE_TRACKING:
       return context->IsFeatureEnabled(
           mojom::blink::FeaturePolicyFeature::kWebXr,
           ReportOptions::kReportOnFailure);
@@ -756,6 +774,12 @@
   CopyToVector(query.RequiredFeatures(), session_options->required_features);
   CopyToVector(query.OptionalFeatures(), session_options->optional_features);
 
+  session_options->tracked_images.resize(query.TrackedImages().size());
+  for (unsigned i = 0; i < query.TrackedImages().size(); ++i) {
+    session_options->tracked_images[i] =
+        device::mojom::blink::XRTrackedImage::New();
+    *session_options->tracked_images[i] = query.TrackedImages()[i];
+  }
   return session_options;
 }
 
@@ -850,6 +874,10 @@
   return environment_provider_.get();
 }
 
+device::mojom::blink::VRService* XRSystem::BrowserService() {
+  return service_.get();
+}
+
 void XRSystem::AddEnvironmentProviderErrorHandler(
     EnvironmentProviderErrorCallback callback) {
   environment_provider_error_callbacks_.push_back(std::move(callback));
@@ -1302,6 +1330,25 @@
     query->SetDOMOverlayElement(session_init->domOverlay()->root());
   }
 
+  if (query->HasFeature(device::mojom::XRSessionFeature::IMAGE_TRACKING)) {
+    // Prerequisites were checked by IsFeatureValidForMode.
+    DCHECK(session_init);
+    DCHECK(session_init->hasTrackedImages());
+    DVLOG(3) << __func__ << ": set up trackedImages";
+    Vector<device::mojom::blink::XRTrackedImage> images;
+    for (auto& image : session_init->trackedImages()) {
+      // Extract an SkBitmap snapshot for each image.
+      scoped_refptr<StaticBitmapImage> static_bitmap_image =
+          image->image()->BitmapImage();
+      SkBitmap sk_bitmap = static_bitmap_image->AsSkBitmapForCurrentFrame(
+          kRespectImageOrientation);
+      IntSize int_size = static_bitmap_image->Size();
+      gfx::Size size(int_size.Width(), int_size.Height());
+      images.emplace_back(sk_bitmap, size, image->widthInMeters());
+    }
+    query->SetTrackedImages(images);
+  }
+
   // The various session request methods may have other checks that would reject
   // before needing to create the vr service, so we don't try to create it here.
   switch (session_mode) {
diff --git a/third_party/blink/renderer/modules/xr/xr_system.h b/third_party/blink/renderer/modules/xr/xr_system.h
index f3158b9..c9290f0 100644
--- a/third_party/blink/renderer/modules/xr/xr_system.h
+++ b/third_party/blink/renderer/modules/xr/xr_system.h
@@ -97,6 +97,8 @@
   device::mojom::blink::XREnvironmentIntegrationProvider*
   xrEnvironmentProviderRemote();
 
+  device::mojom::blink::VRService* BrowserService();
+
   // VRServiceClient overrides.
   void OnDeviceChanged() override;
 
@@ -218,6 +220,14 @@
     }
     Element* DOMOverlayElement() { return dom_overlay_element_; }
 
+    void SetTrackedImages(
+        const Vector<device::mojom::blink::XRTrackedImage>& images) {
+      tracked_images_ = images;
+    }
+    Vector<device::mojom::blink::XRTrackedImage> TrackedImages() const {
+      return tracked_images_;
+    }
+
     virtual void Trace(Visitor*) const;
 
    private:
@@ -240,6 +250,9 @@
     const int64_t ukm_source_id_;
 
     Member<Element> dom_overlay_element_;
+
+    Vector<device::mojom::blink::XRTrackedImage> tracked_images_;
+
     DISALLOW_COPY_AND_ASSIGN(PendingRequestSessionQuery);
   };
 
diff --git a/third_party/blink/renderer/modules/xr/xr_tracked_image_init.idl b/third_party/blink/renderer/modules/xr/xr_tracked_image_init.idl
new file mode 100644
index 0000000..2437e3e
--- /dev/null
+++ b/third_party/blink/renderer/modules/xr/xr_tracked_image_init.idl
@@ -0,0 +1,13 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// PRELIMINARY draft explainer, this is still in ongoing discussions
+// with the immersive-web group and likely to change:
+//
+//   https://github.com/klausw/webxr-image-tracking/blob/main/explainer.md
+
+dictionary XRTrackedImageInit {
+  ImageBitmap image;
+  float widthInMeters;
+};
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 46d98d8..0ffb1ff 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//base/trace_event/tracing.gni")
 import("//build/buildflag_header.gni")
 import("//build/compiled_action.gni")
 import("//build/config/compiler/compiler.gni")
@@ -187,8 +188,9 @@
 declare_args() {
   runtime_call_stats_count_everything = false
 
-  # Enable TRACE_EVENT instrumentation for Blink bindings. Disabled by default as it increases binary size.
-  enable_blink_bindings_tracing = false
+  # Enable TRACE_EVENT instrumentation for Blink bindings.
+  # Disabled by default as it increases binary size.
+  enable_blink_bindings_tracing = extended_tracing_enabled
 }
 
 buildflag_header("bindings_buildflags") {
@@ -2465,7 +2467,7 @@
     "graphics/test/stub_image.h",
 
     # Tests migrated from the web/tests directory.
-    "exported/web_url_request_test.cc",
+    "exported/web_url_request_extra_data_test.cc",
     "exported/web_url_response_test.cc",
     "timer_perf_test.cc",
   ]
diff --git a/third_party/blink/renderer/platform/exported/web_runtime_features.cc b/third_party/blink/renderer/platform/exported/web_runtime_features.cc
index 369b86b4..779dcce 100644
--- a/third_party/blink/renderer/platform/exported/web_runtime_features.cc
+++ b/third_party/blink/renderer/platform/exported/web_runtime_features.cc
@@ -473,6 +473,10 @@
   RuntimeEnabledFeatures::SetWebXRHitTestEnabled(enable);
 }
 
+void WebRuntimeFeatures::EnableWebXRImageTracking(bool enable) {
+  RuntimeEnabledFeatures::SetWebXRImageTrackingEnabled(enable);
+}
+
 void WebRuntimeFeatures::EnableWebXRLightEstimation(bool enable) {
   RuntimeEnabledFeatures::SetWebXRLightEstimationEnabled(enable);
 }
diff --git a/third_party/blink/renderer/platform/exported/web_url_request.cc b/third_party/blink/renderer/platform/exported/web_url_request.cc
index 1f8b233..986f0e80 100644
--- a/third_party/blink/renderer/platform/exported/web_url_request.cc
+++ b/third_party/blink/renderer/platform/exported/web_url_request.cc
@@ -42,6 +42,7 @@
 #include "third_party/blink/public/platform/web_http_header_visitor.h"
 #include "third_party/blink/public/platform/web_security_origin.h"
 #include "third_party/blink/public/platform/web_url.h"
+#include "third_party/blink/public/platform/web_url_request_extra_data.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
 #include "third_party/blink/renderer/platform/loader/fetch/trust_token_params_conversion.h"
 #include "third_party/blink/renderer/platform/network/encoded_form_data.h"
@@ -80,8 +81,6 @@
   }
 }
 
-WebURLRequest::ExtraData::ExtraData() : render_frame_id_(MSG_ROUTING_NONE) {}
-
 WebURLRequest::~WebURLRequest() = default;
 
 WebURLRequest::WebURLRequest()
@@ -380,13 +379,14 @@
   return resource_request_->SetPreviewsState(previews_state);
 }
 
-const scoped_refptr<WebURLRequest::ExtraData>& WebURLRequest::GetExtraData()
-    const {
-  return resource_request_->GetExtraData();
+const scoped_refptr<WebURLRequestExtraData>&
+WebURLRequest::GetURLRequestExtraData() const {
+  return resource_request_->GetURLRequestExtraData();
 }
 
-void WebURLRequest::SetExtraData(scoped_refptr<ExtraData> extra_data) {
-  resource_request_->SetExtraData(std::move(extra_data));
+void WebURLRequest::SetURLRequestExtraData(
+    scoped_refptr<WebURLRequestExtraData> extra_data) {
+  resource_request_->SetURLRequestExtraData(std::move(extra_data));
 }
 
 bool WebURLRequest::IsDownloadToNetworkCacheOnly() const {
@@ -503,8 +503,8 @@
       blink::mojom::blink::RequestContextType::PREFETCH)
     load_flags |= net::LOAD_PREFETCH;
 
-  if (resource_request_->GetExtraData()) {
-    if (resource_request_->GetExtraData()->is_for_no_state_prefetch())
+  if (resource_request_->GetURLRequestExtraData()) {
+    if (resource_request_->GetURLRequestExtraData()->is_for_no_state_prefetch())
       load_flags |= net::LOAD_PREFETCH;
   }
   if (resource_request_->AllowsStaleResponse()) {
diff --git a/third_party/blink/renderer/platform/exported/web_url_request_test.cc b/third_party/blink/renderer/platform/exported/web_url_request_extra_data_test.cc
similarity index 72%
rename from third_party/blink/renderer/platform/exported/web_url_request_test.cc
rename to third_party/blink/renderer/platform/exported/web_url_request_extra_data_test.cc
index c03d48e..ae5094e8 100644
--- a/third_party/blink/renderer/platform/exported/web_url_request_test.cc
+++ b/third_party/blink/renderer/platform/exported/web_url_request_extra_data_test.cc
@@ -28,6 +28,7 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include "third_party/blink/public/platform/web_url_request_extra_data.h"
 #include "third_party/blink/public/platform/web_url_request.h"
 
 #include "testing/gtest/include/gtest/gtest.h"
@@ -36,7 +37,7 @@
 
 namespace {
 
-class RequestTestExtraData : public WebURLRequest::ExtraData {
+class RequestTestExtraData : public WebURLRequestExtraData {
  public:
   explicit RequestTestExtraData(bool* alive) : alive_(alive) { *alive = true; }
 
@@ -48,25 +49,30 @@
 
 }  // anonymous namespace
 
-TEST(WebURLRequestTest, ExtraData) {
+TEST(WebURLRequestExtraDataTest, ExtraData) {
   bool alive = false;
   {
     WebURLRequest url_request;
-    auto extra_data = base::MakeRefCounted<RequestTestExtraData>(&alive);
+    auto url_request_extra_data =
+        base::MakeRefCounted<RequestTestExtraData>(&alive);
     EXPECT_TRUE(alive);
 
-    auto* raw_extra_data_pointer = extra_data.get();
-    url_request.SetExtraData(std::move(extra_data));
-    EXPECT_EQ(raw_extra_data_pointer, url_request.GetExtraData());
+    auto* raw_request_extra_data_pointer = url_request_extra_data.get();
+    url_request.SetURLRequestExtraData(std::move(url_request_extra_data));
+    EXPECT_EQ(raw_request_extra_data_pointer,
+              url_request.GetURLRequestExtraData());
     {
       WebURLRequest other_url_request;
       other_url_request.CopyFrom(url_request);
       EXPECT_TRUE(alive);
-      EXPECT_EQ(raw_extra_data_pointer, other_url_request.GetExtraData());
-      EXPECT_EQ(raw_extra_data_pointer, url_request.GetExtraData());
+      EXPECT_EQ(raw_request_extra_data_pointer,
+                other_url_request.GetURLRequestExtraData());
+      EXPECT_EQ(raw_request_extra_data_pointer,
+                url_request.GetURLRequestExtraData());
     }
     EXPECT_TRUE(alive);
-    EXPECT_EQ(raw_extra_data_pointer, url_request.GetExtraData());
+    EXPECT_EQ(raw_request_extra_data_pointer,
+              url_request.GetURLRequestExtraData());
   }
   EXPECT_FALSE(alive);
 }
diff --git a/third_party/blink/renderer/platform/graphics/graphics_layer.cc b/third_party/blink/renderer/platform/graphics/graphics_layer.cc
index 2cb9257..096a36a4 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_layer.cc
+++ b/third_party/blink/renderer/platform/graphics/graphics_layer.cc
@@ -79,7 +79,7 @@
       painting_phase_(kGraphicsLayerPaintAllWithOverflowClip),
       parent_(nullptr),
       raster_invalidation_function_(
-          base::BindRepeating(&GraphicsLayer::SetNeedsDisplayInRect,
+          base::BindRepeating(&GraphicsLayer::InvalidateRaster,
                               base::Unretained(this))) {
   // TODO(crbug.com/1033240): Debugging information for the referenced bug.
   // Remove when it is fixed.
@@ -261,9 +261,7 @@
     return;
 
   offset_from_layout_object_ = offset;
-
-  // If the compositing layer offset changes, we need to repaint.
-  SetNeedsDisplay();
+  Invalidate(PaintInvalidationReason::kFullLayer);  // As DisplayItemClient.
 }
 
 IntRect GraphicsLayer::InterestRect() {
@@ -359,7 +357,9 @@
                                                              benchmark_mode);
   bool cached = !paint_controller.ShouldForcePaintForBenchmark() &&
                 !client_.NeedsRepaint(*this) &&
-                !paint_controller.CacheIsAllInvalid() &&
+                // TODO(wangxianzhu): This will be replaced by subsequence
+                // caching when unifying PaintController.
+                paint_controller.ClientCacheIsValid(*this) &&
                 previous_interest_rect_ == new_interest_rect;
   if (!cached) {
     GraphicsContext context(paint_controller);
@@ -369,7 +369,9 @@
     previous_interest_rect_ = new_interest_rect;
     client_.PaintContents(this, context, painting_phase_, new_interest_rect);
     paint_controller.CommitNewDisplayItems();
-
+    // TODO(wangxianzhu): Remove this and friend class in DisplayItemClient
+    // when unifying PaintController.
+    Validate();
     DVLOG(2) << "Painted GraphicsLayer: " << DebugName()
              << " interest_rect=" << InterestRect().ToString();
   }
@@ -426,15 +428,9 @@
     // Depending on |should_create_layers_after_paint_|, raster invalidation
     // will happen in via two different code paths. When it changes we need to
     // fully invalidate because the incremental raster invalidations of these
-    // code paths will not work. Nor calling this->SetNeedsDisplay() because it
-    // will also clear PaintController which contains what we have just painted.
-    CcLayer().SetNeedsDisplay();
+    // code paths will not work.
     if (raster_invalidator_)
       raster_invalidator_->ClearOldStates();
-    // TODO(wangxianzhu): This is to let LocalFrameView recollect the graphics
-    // layers to regenerate pre_composited_layers_. Will remove when unifying
-    // PaintController for CAP and pre-CAP.
-    NotifyChildListChange();
   }
 }
 
@@ -562,7 +558,8 @@
   Invalidate(PaintInvalidationReason::kIncremental);  // as DisplayItemClient.
 
   CcLayer().SetBounds(size);
-  // Note that we don't resize m_contentsLayer. It's up the caller to do that.
+  SetNeedsCheckRasterInvalidation();
+  // Note that we don't resize contents_layer_. It's up the caller to do that.
 }
 
 void GraphicsLayer::SetDrawsContent(bool draws_content) {
@@ -635,9 +632,8 @@
   CcLayer().SetHitTestable(should_hit_test);
 }
 
-void GraphicsLayer::SetContentsNeedsDisplay() {
+void GraphicsLayer::InvalidateContents() {
   if (contents_layer_) {
-    raster_invalidated_ = true;
     contents_layer_->SetNeedsDisplay();
     TrackRasterInvalidation(*this, contents_rect_,
                             PaintInvalidationReason::kFullLayer);
@@ -663,7 +659,7 @@
                           PaintInvalidationReason::kFullLayer);
 }
 
-void GraphicsLayer::SetNeedsDisplayInRect(const IntRect& rect) {
+void GraphicsLayer::InvalidateRaster(const IntRect& rect) {
   DCHECK(PaintsContentOrHitTest());
   raster_invalidated_ = true;
   CcLayer().SetNeedsDisplayRect(rect);
@@ -682,7 +678,7 @@
   if (painting_phase_ == phase)
     return;
   painting_phase_ = phase;
-  SetNeedsDisplay();
+  Invalidate(PaintInvalidationReason::kFullLayer);  // As DisplayItemClient.
 }
 
 PaintController& GraphicsLayer::GetPaintController() const {
diff --git a/third_party/blink/renderer/platform/graphics/graphics_layer.h b/third_party/blink/renderer/platform/graphics/graphics_layer.h
index f44e7df..13697642 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_layer.h
+++ b/third_party/blink/renderer/platform/graphics/graphics_layer.h
@@ -154,7 +154,7 @@
   void SetPaintingPhase(GraphicsLayerPaintingPhase);
 
   void SetNeedsDisplay();
-  void SetContentsNeedsDisplay();
+  void InvalidateContents();
 
   // Set that the position/size of the contents (image or video).
   void SetContentsRect(const IntRect&);
@@ -270,7 +270,7 @@
                      bool prevent_contents_opaque_changes);
 
   RasterInvalidator& EnsureRasterInvalidator();
-  void SetNeedsDisplayInRect(const IntRect&);
+  void InvalidateRaster(const IntRect&);
 
   GraphicsLayerClient& client_;
 
diff --git a/third_party/blink/renderer/platform/graphics/paint/display_item_client.h b/third_party/blink/renderer/platform/graphics/paint/display_item_client.h
index a7d7b94..a22a27f 100644
--- a/third_party/blink/renderer/platform/graphics/paint/display_item_client.h
+++ b/third_party/blink/renderer/platform/graphics/paint/display_item_client.h
@@ -112,6 +112,7 @@
   friend class FakeDisplayItemClient;
   friend class ObjectPaintInvalidatorTest;
   friend class PaintController;
+  friend class GraphicsLayer;  // Temporary for Validate().
 
   void Validate() const {
     paint_invalidation_reason_ = PaintInvalidationReason::kNone;
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc b/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc
index f5164a5..5d64f3b 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc
@@ -358,12 +358,6 @@
   cache_is_all_invalid_ = true;
 }
 
-bool PaintController::CacheIsAllInvalid() const {
-  DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled());
-  DCHECK(!cache_is_all_invalid_ || current_paint_artifact_->IsEmpty());
-  return cache_is_all_invalid_;
-}
-
 void PaintController::UpdateCurrentPaintChunkProperties(
     const PaintChunk::Id* id,
     const PropertyTreeStateOrAlias& properties) {
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_controller.h b/third_party/blink/renderer/platform/graphics/paint/paint_controller.h
index 1faddddf..0cf7b9e 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_controller.h
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_controller.h
@@ -83,7 +83,6 @@
 
   // For pre-PaintAfterPaint only.
   void InvalidateAll();
-  bool CacheIsAllInvalid() const;
 
   // These methods are called during painting.
 
@@ -267,6 +266,7 @@
  private:
   friend class PaintControllerTestBase;
   friend class PaintControllerPaintTestBase;
+  friend class GraphicsLayer;  // Temporary for ClientCacheIsValid().
 
   // True if all display items associated with the client are validly cached.
   // However, the current algorithm allows the following situations even if
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_controller_test.cc b/third_party/blink/renderer/platform/graphics/paint/paint_controller_test.cc
index f7b3edc..e028ae5 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_controller_test.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_controller_test.cc
@@ -1494,35 +1494,6 @@
   EXPECT_FALSE(result.image_painted);
 }
 
-TEST_P(PaintControllerTest, InvalidateAll) {
-  if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
-    return;
-
-  EXPECT_TRUE(GetPaintController().CacheIsAllInvalid());
-  CommitAndFinishCycle();
-  EXPECT_TRUE(GetPaintController().GetPaintArtifact().IsEmpty());
-  EXPECT_FALSE(GetPaintController().CacheIsAllInvalid());
-
-  InvalidateAll();
-  EXPECT_TRUE(GetPaintController().CacheIsAllInvalid());
-  CommitAndFinishCycle();
-  EXPECT_TRUE(GetPaintController().GetPaintArtifact().IsEmpty());
-  EXPECT_FALSE(GetPaintController().CacheIsAllInvalid());
-
-  FakeDisplayItemClient client("client");
-  GraphicsContext context(GetPaintController());
-
-  InitRootChunk();
-  DrawRect(context, client, kBackgroundType, IntRect(1, 2, 3, 4));
-  CommitAndFinishCycle();
-  EXPECT_FALSE(GetPaintController().GetPaintArtifact().IsEmpty());
-  EXPECT_FALSE(GetPaintController().CacheIsAllInvalid());
-
-  InvalidateAll();
-  EXPECT_TRUE(GetPaintController().GetPaintArtifact().IsEmpty());
-  EXPECT_TRUE(GetPaintController().CacheIsAllInvalid());
-}
-
 TEST_P(PaintControllerTest, InsertValidItemInFront) {
   FakeDisplayItemClient first("first");
   FakeDisplayItemClient second("second");
diff --git a/third_party/blink/renderer/platform/graphics/paint/raster_invalidator.cc b/third_party/blink/renderer/platform/graphics/paint/raster_invalidator.cc
index bdf0142..f995fc3 100644
--- a/third_party/blink/renderer/platform/graphics/paint/raster_invalidator.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/raster_invalidator.cc
@@ -321,9 +321,8 @@
   new_chunks_info.ReserveCapacity(new_chunks.size());
 
   if (layer_bounds_was_empty || layer_bounds_.IsEmpty()) {
-    // No raster invalidation is needed if either the old bounds or the new
-    // bounds is empty, but we still need to update new_chunks_info for the
-    // next cycle.
+    // Fast path if either the old bounds or the new bounds is empty. We still
+    // need to update new_chunks_info for the next cycle.
     ChunkToLayerMapper mapper(layer_state, layer_bounds.OffsetFromOrigin());
     for (auto it = new_chunks.begin(); it != new_chunks.end(); ++it) {
       if (ShouldSkipForRasterInvalidation(it))
@@ -332,13 +331,12 @@
       new_chunks_info.emplace_back(*this, mapper, it);
     }
 
-    if (tracking_info_ && layer_bounds_was_empty && !layer_bounds.IsEmpty() &&
-        !new_chunks.IsEmpty()) {
-      // This is useful to track the implicit raster invalidations caused by
-      // change of layerization which has performance impact, especially for
-      // tests under web_tests/paint/invalidation.
-      TrackImplicitFullLayerInvalidation(
-          layer_client ? *layer_client : new_chunks.begin()->id.client);
+    if (!layer_bounds.IsEmpty() && !new_chunks.IsEmpty()) {
+      AddRasterInvalidation(
+          raster_invalidation_function,
+          IntRect(IntPoint(), IntSize(layer_bounds.size())),
+          layer_client ? *layer_client : new_chunks.begin()->id.client,
+          PaintInvalidationReason::kFullLayer, kClientIsNew);
     }
   } else {
     GenerateRasterInvalidations(raster_invalidation_function, new_chunks,
@@ -352,15 +350,6 @@
     UpdateClientDebugNames();
 }
 
-void RasterInvalidator::TrackImplicitFullLayerInvalidation(
-    const DisplayItemClient& layer_client) {
-  DCHECK(tracking_info_);
-  tracking_info_->tracking.AddInvalidation(
-      &layer_client, layer_client.DebugName(),
-      IntRect(IntPoint(), IntSize(layer_bounds_.size())),
-      PaintInvalidationReason::kFullLayer);
-}
-
 size_t RasterInvalidator::ApproximateUnsharedMemoryUsage() const {
   return sizeof(*this) +
          old_paint_chunks_info_.capacity() * sizeof(PaintChunkInfo);
diff --git a/third_party/blink/renderer/platform/graphics/paint/raster_invalidator.h b/third_party/blink/renderer/platform/graphics/paint/raster_invalidator.h
index 3efb0a5..18ff35b 100644
--- a/third_party/blink/renderer/platform/graphics/paint/raster_invalidator.h
+++ b/third_party/blink/renderer/platform/graphics/paint/raster_invalidator.h
@@ -133,8 +133,6 @@
         r, Rect(0, 0, layer_bounds_.width(), layer_bounds_.height()));
   }
 
-  void TrackImplicitFullLayerInvalidation(const DisplayItemClient&);
-
   gfx::Rect layer_bounds_;
   Vector<PaintChunkInfo> old_paint_chunks_info_;
   scoped_refptr<const PaintArtifact> old_paint_artifact_;
diff --git a/third_party/blink/renderer/platform/loader/BUILD.gn b/third_party/blink/renderer/platform/loader/BUILD.gn
index 2953d589e..0fb32f3 100644
--- a/third_party/blink/renderer/platform/loader/BUILD.gn
+++ b/third_party/blink/renderer/platform/loader/BUILD.gn
@@ -135,6 +135,7 @@
     "static_data_navigation_body_loader.h",
     "subresource_integrity.cc",
     "subresource_integrity.h",
+    "web_url_request_extra_data.cc",
     "web_url_request_util.cc",
   ]
 
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc b/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
index 4c94074b..161e8c5 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
@@ -1235,7 +1235,7 @@
     bool no_mime_sniffing = request.GetRequestContext() ==
                             blink::mojom::blink::RequestContextType::FETCH;
     loader_->LoadSynchronously(
-        std::move(network_resource_request), request.GetExtraData(),
+        std::move(network_resource_request), request.GetURLRequestExtraData(),
         request.RequestorID(), request.DownloadToBlob(), no_mime_sniffing,
         request.TimeoutInterval(), this, response_out, error_out, data_out,
         encoded_data_length, encoded_body_length, downloaded_blob,
@@ -1299,7 +1299,7 @@
   if (form_body)
     request_body_ = ResourceRequestBody(std::move(form_body));
   loader_->LoadAsynchronously(
-      std::move(network_resource_request), request.GetExtraData(),
+      std::move(network_resource_request), request.GetURLRequestExtraData(),
       request.RequestorID(), no_mime_sniffing,
       Context().CreateResourceLoadInfoNotifierWrapper(), this);
   if (code_cache_request_) {
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_loader_defer_loading_test.cc b/third_party/blink/renderer/platform/loader/fetch/resource_loader_defer_loading_test.cc
index cad97b5f..e956f91 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_loader_defer_loading_test.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_loader_defer_loading_test.cc
@@ -12,6 +12,7 @@
 #include "third_party/blink/public/platform/web_runtime_features.h"
 #include "third_party/blink/public/platform/web_url_loader.h"
 #include "third_party/blink/public/platform/web_url_loader_factory.h"
+#include "third_party/blink/public/platform/web_url_request_extra_data.h"
 #include "third_party/blink/renderer/platform/loader/fetch/raw_resource.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
 #include "third_party/blink/renderer/platform/loader/fetch/unique_identifier.h"
@@ -61,7 +62,7 @@
 
   void LoadSynchronously(
       std::unique_ptr<network::ResourceRequest> request,
-      scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
+      scoped_refptr<WebURLRequestExtraData> url_request_extra_data,
       int requestor_id,
       bool pass_response_pipe_to_client,
       bool no_mime_sniffing,
@@ -79,7 +80,7 @@
   }
   void LoadAsynchronously(
       std::unique_ptr<network::ResourceRequest> request,
-      scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
+      scoped_refptr<WebURLRequestExtraData> url_request_extra_data,
       int requestor_id,
       bool no_mime_sniffing,
       std::unique_ptr<blink::ResourceLoadInfoNotifierWrapper>
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_loader_test.cc b/third_party/blink/renderer/platform/loader/fetch/resource_loader_test.cc
index d497e728..606cca1 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_loader_test.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_loader_test.cc
@@ -14,6 +14,7 @@
 #include "third_party/blink/public/platform/resource_load_info_notifier_wrapper.h"
 #include "third_party/blink/public/platform/web_url_loader.h"
 #include "third_party/blink/public/platform/web_url_loader_factory.h"
+#include "third_party/blink/public/platform/web_url_request_extra_data.h"
 #include "third_party/blink/renderer/platform/exported/wrapped_resource_response.h"
 #include "third_party/blink/renderer/platform/loader/fetch/raw_resource.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
@@ -83,7 +84,7 @@
     ~NoopWebURLLoader() override = default;
     void LoadSynchronously(
         std::unique_ptr<network::ResourceRequest> request,
-        scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
+        scoped_refptr<WebURLRequestExtraData> url_request_extra_data,
         int requestor_id,
         bool pass_response_pipe_to_client,
         bool no_mime_sniffing,
@@ -101,7 +102,7 @@
     }
     void LoadAsynchronously(
         std::unique_ptr<network::ResourceRequest> request,
-        scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
+        scoped_refptr<WebURLRequestExtraData> url_request_extra_data,
         int requestor_id,
         bool no_mime_sniffing,
         std::unique_ptr<blink::ResourceLoadInfoNotifierWrapper>
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_request.h b/third_party/blink/renderer/platform/loader/fetch/resource_request.h
index 454ce63..d55e1fa 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_request.h
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_request.h
@@ -43,7 +43,7 @@
 #include "services/network/public/mojom/trust_tokens.mojom-blink.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink-forward.h"
 #include "third_party/blink/public/platform/resource_request_blocked_reason.h"
-#include "third_party/blink/public/platform/web_url_request.h"
+#include "third_party/blink/public/platform/web_url_request_extra_data.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_load_priority.h"
 #include "third_party/blink/renderer/platform/network/http_header_map.h"
 #include "third_party/blink/renderer/platform/network/http_names.h"
@@ -257,11 +257,12 @@
   }
 
   // Extra data associated with this request.
-  const scoped_refptr<WebURLRequest::ExtraData>& GetExtraData() const {
-    return extra_data_;
+  const scoped_refptr<WebURLRequestExtraData>& GetURLRequestExtraData() const {
+    return url_request_extra_data_;
   }
-  void SetExtraData(scoped_refptr<WebURLRequest::ExtraData> extra_data) {
-    extra_data_ = extra_data;
+  void SetURLRequestExtraData(
+      scoped_refptr<WebURLRequestExtraData> url_request_extra_data) {
+    url_request_extra_data_ = std::move(url_request_extra_data);
   }
 
   bool IsDownloadToNetworkCacheOnly() const { return download_to_cache_only_; }
@@ -520,7 +521,7 @@
   int intra_priority_value_;
   int requestor_id_;
   PreviewsState previews_state_;
-  scoped_refptr<WebURLRequest::ExtraData> extra_data_;
+  scoped_refptr<WebURLRequestExtraData> url_request_extra_data_;
   mojom::blink::RequestContextType request_context_;
   network::mojom::RequestDestination destination_;
   network::mojom::RequestMode mode_;
diff --git a/third_party/blink/renderer/platform/loader/internet_disconnected_web_url_loader.cc b/third_party/blink/renderer/platform/loader/internet_disconnected_web_url_loader.cc
index 93b247f..f006568 100644
--- a/third_party/blink/renderer/platform/loader/internet_disconnected_web_url_loader.cc
+++ b/third_party/blink/renderer/platform/loader/internet_disconnected_web_url_loader.cc
@@ -11,6 +11,7 @@
 #include "third_party/blink/public/platform/web_url.h"
 #include "third_party/blink/public/platform/web_url_loader_client.h"
 #include "third_party/blink/public/platform/web_url_request.h"
+#include "third_party/blink/public/platform/web_url_request_extra_data.h"
 
 namespace blink {
 
@@ -33,7 +34,7 @@
 
 void InternetDisconnectedWebURLLoader::LoadSynchronously(
     std::unique_ptr<network::ResourceRequest> request,
-    scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
+    scoped_refptr<WebURLRequestExtraData> url_request_extra_data,
     int requestor_id,
     bool pass_response_pipe_to_client,
     bool no_mime_sniffing,
@@ -52,7 +53,7 @@
 
 void InternetDisconnectedWebURLLoader::LoadAsynchronously(
     std::unique_ptr<network::ResourceRequest> request,
-    scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
+    scoped_refptr<WebURLRequestExtraData> url_request_extra_data,
     int requestor_id,
     bool no_mime_sniffing,
     std::unique_ptr<blink::ResourceLoadInfoNotifierWrapper>
diff --git a/third_party/blink/renderer/platform/loader/web_url_request_extra_data.cc b/third_party/blink/renderer/platform/loader/web_url_request_extra_data.cc
new file mode 100644
index 0000000..dffa242
--- /dev/null
+++ b/third_party/blink/renderer/platform/loader/web_url_request_extra_data.cc
@@ -0,0 +1,25 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/public/platform/web_url_request_extra_data.h"
+
+#include "services/network/public/cpp/resource_request.h"
+
+namespace blink {
+
+WebURLRequestExtraData::WebURLRequestExtraData()
+    : render_frame_id_(MSG_ROUTING_NONE) {}
+
+WebURLRequestExtraData::~WebURLRequestExtraData() = default;
+
+void WebURLRequestExtraData::CopyToResourceRequest(
+    network::ResourceRequest* request) const {
+  request->render_frame_id = render_frame_id_.value();
+  request->is_main_frame = is_main_frame_;
+  request->transition_type = transition_type_;
+  request->originated_from_service_worker = originated_from_service_worker_;
+  request->force_ignore_site_for_cookies = force_ignore_site_for_cookies_;
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 51b5093..c51e9b9 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -2195,6 +2195,11 @@
       status: "experimental"
     },
     {
+      name: "WebXRImageTracking",
+      depends_on: ["WebXRARModule"],
+      status: "experimental",
+    },
+    {
       name: "WebXRLightEstimation",
       depends_on: ["WebXRARModule"],
       status: "experimental",
diff --git a/third_party/blink/renderer/platform/testing/weburl_loader_mock.cc b/third_party/blink/renderer/platform/testing/weburl_loader_mock.cc
index f5a74dae..7207d8e9 100644
--- a/third_party/blink/renderer/platform/testing/weburl_loader_mock.cc
+++ b/third_party/blink/renderer/platform/testing/weburl_loader_mock.cc
@@ -14,6 +14,7 @@
 #include "third_party/blink/public/platform/web_security_origin.h"
 #include "third_party/blink/public/platform/web_url_error.h"
 #include "third_party/blink/public/platform/web_url_loader_client.h"
+#include "third_party/blink/public/platform/web_url_request_extra_data.h"
 #include "third_party/blink/renderer/platform/scheduler/test/fake_task_runner.h"
 #include "third_party/blink/renderer/platform/testing/weburl_loader_mock_factory_impl.h"
 #include "third_party/blink/renderer/platform/wtf/shared_buffer.h"
@@ -96,7 +97,7 @@
 
 void WebURLLoaderMock::LoadSynchronously(
     std::unique_ptr<network::ResourceRequest> request,
-    scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
+    scoped_refptr<WebURLRequestExtraData> url_request_extra_data,
     int requestor_id,
     bool pass_response_pipe_to_client,
     bool no_mime_sniffing,
@@ -117,7 +118,7 @@
 
 void WebURLLoaderMock::LoadAsynchronously(
     std::unique_ptr<network::ResourceRequest> request,
-    scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
+    scoped_refptr<WebURLRequestExtraData> url_request_extra_data,
     int requestor_id,
     bool no_mime_sniffing,
     std::unique_ptr<blink::ResourceLoadInfoNotifierWrapper>
diff --git a/third_party/blink/renderer/platform/testing/weburl_loader_mock.h b/third_party/blink/renderer/platform/testing/weburl_loader_mock.h
index 5e818447..b0df16d 100644
--- a/third_party/blink/renderer/platform/testing/weburl_loader_mock.h
+++ b/third_party/blink/renderer/platform/testing/weburl_loader_mock.h
@@ -15,6 +15,7 @@
 namespace blink {
 
 class WebData;
+class WebURLRequestExtraData;
 class WebURLLoaderClient;
 class WebURLLoaderMockFactoryImpl;
 class WebURLLoaderTestDelegate;
@@ -45,7 +46,7 @@
   // WebURLLoader methods:
   void LoadSynchronously(
       std::unique_ptr<network::ResourceRequest> request,
-      scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
+      scoped_refptr<WebURLRequestExtraData> url_request_extra_data,
       int requestor_id,
       bool pass_response_pipe_to_client,
       bool no_mime_sniffing,
@@ -61,7 +62,7 @@
           resource_load_info_notifier_wrapper) override;
   void LoadAsynchronously(
       std::unique_ptr<network::ResourceRequest> request,
-      scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
+      scoped_refptr<WebURLRequestExtraData> url_request_extra_data,
       int requestor_id,
       bool no_mime_sniffing,
       std::unique_ptr<blink::ResourceLoadInfoNotifierWrapper>
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 15964b1..feeac618 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -1193,8 +1193,7 @@
 crbug.com/1140307 accessibility/inline-text-textarea.html [ Failure ]
 
 ### Textfield NG
-crbug.com/1040826 external/wpt/css/css-pseudo/first-line-and-placeholder.html [ Failure ]
-crbug.com/1040826 fast/events/mouse-relative-position.html [ Failure ]
+crbug.com/1143621 external/wpt/css/css-pseudo/first-line-and-placeholder.html [ Failure ]
 crbug.com/1040826 fast/forms/input-in-table-cell-no-value.html [ Failure ]
 crbug.com/1040826 fast/forms/placeholder-position.html [ Failure ]
 crbug.com/1040826 [ Linux ] fast/forms/text/input-placeholder-paint-order.html [ Failure ]
@@ -6221,9 +6220,6 @@
 crbug.com/1140329 http/tests/devtools/network/network-filter-service-worker.js [ Pass Timeout Failure ]
 crbug.com/1140329 virtual/threaded/external/wpt/web-animations/timing-model/animations/updating-the-finished-state.html [ Pass Failure Timeout ]
 
-# DevTools: TypeScriptify elements/StylesSidebarPane.js
-crbug.com/1011811 http/tests/devtools/elements/styles-1/add-new-rule-keyboard.js [ Pass Failure Timeout ]
-
 #Sheriff 2020-10-21
 crbug.com/1141206 scrollbars/overflow-scrollbar-combinations.html [ Pass Failure ]
 
@@ -6231,4 +6227,4 @@
 crbug.com/1142877 external/wpt/mediacapture-fromelement/capture.html [ Pass Failure Crash ]
 
 #Sheriff 2020-10-29
-crbug.com/1143720 [ Win7 ] external/wpt/media-source/mediasource-detach.html [ Pass Failure ]
\ No newline at end of file
+crbug.com/1143720 [ Win7 ] external/wpt/media-source/mediasource-detach.html [ Pass Failure ]
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index ee92e66..bfc85d5 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -234140,7 +234140,23 @@
     "spec.src.json": [
      "b001317d5784de8d7e0b15e4cdcf616c1022c84b",
      []
-    ]
+    ],
+    "tentative": {
+     "resources": {
+      "pass.png": [
+       "2fa1e0ac0663a65deae6602621521cc2844b93de",
+       []
+      ],
+      "test.ogv": [
+       "0f83996e5dcf31a32a801b6800315bd91642d29c",
+       []
+      ],
+      "test.wav": [
+       "85dc1ea9044e28eeeac6176bae61285c25ebf711",
+       []
+      ]
+     }
+    }
    },
    "mst-content-hint": {
     "META.yml": [
@@ -249795,7 +249811,7 @@
         []
        ],
        "base.py": [
-        "99f6e5cff4c8b826d1390b482eb686da5915083c",
+        "f2205b160f61023f4757732243ca2eade32a3713",
         []
        ],
        "chrome.py": [
@@ -249831,11 +249847,11 @@
         []
        ],
        "firefox.py": [
-        "151530897d9b1f77e9dcd4f1971bed1c4d6ff8b0",
+        "75cebb9f7f103daa0e35d96fddc67d7a1d49fd27",
         []
        ],
        "firefox_android.py": [
-        "a6cb8f0dd3e0f669d47b4a9a4a728d99fc648988",
+        "bea0d9f4ee33ab051b00cba5b8a4afc017c37501",
         []
        ],
        "ie.py": [
@@ -249909,7 +249925,7 @@
         []
        ],
        "executormarionette.py": [
-        "7e2cc8e38a44c06a0d6c726fc917ca36776c0861",
+        "da8941b69e33b0986f999f286c4a64e584349ecc",
         []
        ],
        "executoropera.py": [
@@ -250084,7 +250100,7 @@
        []
       ],
       "testrunner.py": [
-       "0bde5ba348ea08984c1b95f78d926218b3713890",
+       "961c40fc5b8099136f341d1263cd855180bff9bd",
        []
       ],
       "update": {
@@ -250126,7 +250142,7 @@
        []
       ],
       "wptcommandline.py": [
-       "9d1d4048095435de35f00d65e6d4d7de3f427e95",
+       "15d2322494dbe5c281cb77db7a1c0dd754e7db2a",
        []
       ],
       "wptlogging.py": [
@@ -250200,7 +250216,7 @@
        []
       ],
       "wpttest.py": [
-       "aafe3fa6e00e0233e530f1693e83c9caa8c2ced7",
+       "e21041b6430a2661e437f3e4a9a73def63ee6f1b",
        []
       ]
      },
@@ -281310,7 +281326,7 @@
       ]
      ],
      "schemeful-websockets.sub.tentative.html": [
-      "2626d1cd9e8e9eaebf6548f37acfcb47827b1818",
+      "7095eee21e048e34334873605e7968f96fcdfd93",
       [
        null,
        {}
@@ -382406,7 +382422,32 @@
       null,
       {}
      ]
-    ]
+    ],
+    "tentative": {
+     "autoupgrades": {
+      "audio-upgrade.https.sub.html": [
+       "0731ef69c15f29d5a93999dabee9bf5dea2cbc17",
+       [
+        null,
+        {}
+       ]
+      ],
+      "image-upgrade.https.sub.html": [
+       "03b5c3e69dea8e640ab4ed560d4a4d7d278d4853",
+       [
+        null,
+        {}
+       ]
+      ],
+      "video-upgrade.https.sub.html": [
+       "45ec71848d02c0b8608dd895a04e9afe494fd4a3",
+       [
+        null,
+        {}
+       ]
+      ]
+     }
+    }
    },
    "mst-content-hint": {
     "MediaStreamTrack-contentHint.html": [
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/browsers/base.py b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/browsers/base.py
index 99f6e5cf..f2205b16 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/browsers/base.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/browsers/base.py
@@ -126,6 +126,12 @@
         pass
 
     def settings(self, test):
+        """Dictionary of metadata that is constant for a specific launch of a browser.
+
+        This is used to determine when the browser instance configuration changes, requiring
+        a relaunch of the browser. The test runner calls this method for each test, and if the
+        returned value differs from that for the previous test, the browser is relaunched.
+        """
         return {}
 
     @abstractmethod
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/browsers/firefox.py b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/browsers/firefox.py
index 1515308..75cebb9f 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/browsers/firefox.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/browsers/firefox.py
@@ -101,7 +101,8 @@
             "config": config,
             "browser_channel": kwargs["browser_channel"],
             "headless": kwargs["headless"],
-            "preload_browser": kwargs["preload_browser"]}
+            "preload_browser": kwargs["preload_browser"],
+            "specialpowers_path": kwargs["specialpowers_path"]}
 
 
 def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
@@ -540,12 +541,16 @@
         self.certutil_binary = certutil_binary
         self.ca_certificate_path = ca_certificate_path
 
-    def create(self):
+    def create(self, **kwargs):
         """Create a Firefox profile and return the mozprofile Profile object pointing at that
-        profile"""
+        profile
+
+        :param kwargs: Additional arguments to pass into the profile constructor
+        """
         preferences = self._load_prefs()
 
-        profile = FirefoxProfile(preferences=preferences)
+        profile = FirefoxProfile(preferences=preferences,
+                                 **kwargs)
         self._set_required_prefs(profile)
         if self.ca_certificate_path is not None:
             self._setup_ssl(profile)
@@ -670,7 +675,6 @@
         certutil("-L", "-d", cert_db_path)
 
 
-
 class FirefoxBrowser(Browser):
     init_timeout = 70
 
@@ -679,7 +683,8 @@
                  ca_certificate_path=None, e10s=False, enable_webrender=False, enable_fission=False,
                  stackfix_dir=None, binary_args=None, timeout_multiplier=None, leak_check=False,
                  asan=False, stylo_threads=1, chaos_mode_flags=None, config=None,
-                 browser_channel="nightly", headless=None, preload_browser=False, **kwargs):
+                 browser_channel="nightly", headless=None, preload_browser=False,
+                 specialpowers_path=None, **kwargs):
         Browser.__init__(self, logger)
 
         self.logger = logger
@@ -688,6 +693,7 @@
             self.init_timeout = self.init_timeout * timeout_multiplier
 
         self.instance = None
+        self._settings = None
 
         self.stackfix_dir = stackfix_dir
         self.symbols_path = symbols_path
@@ -696,6 +702,8 @@
         self.asan = asan
         self.leak_check = leak_check
 
+        self.specialpowers_path = specialpowers_path
+
         profile_creator = ProfileCreator(logger,
                                          prefs_root,
                                          config,
@@ -726,14 +734,15 @@
                                                      symbols_path,
                                                      asan)
 
-
     def settings(self, test):
-        return {"check_leaks": self.leak_check and not test.leaks,
-                "lsan_disabled": test.lsan_disabled,
-                "lsan_allowed": test.lsan_allowed,
-                "lsan_max_stack_depth": test.lsan_max_stack_depth,
-                "mozleak_allowed": self.leak_check and test.mozleak_allowed,
-                "mozleak_thresholds": self.leak_check and test.mozleak_threshold}
+        self._settings = {"check_leaks": self.leak_check and not test.leaks,
+                          "lsan_disabled": test.lsan_disabled,
+                          "lsan_allowed": test.lsan_allowed,
+                          "lsan_max_stack_depth": test.lsan_max_stack_depth,
+                          "mozleak_allowed": self.leak_check and test.mozleak_allowed,
+                          "mozleak_thresholds": self.leak_check and test.mozleak_threshold,
+                          "special_powers": self.specialpowers_path and test.url_base == "/_mozilla/"}
+        return self._settings
 
     def start(self, group_metadata=None, **kwargs):
         self.instance = self.instance_manager.get()
@@ -756,7 +765,11 @@
 
     def executor_browser(self):
         assert self.instance is not None
-        return ExecutorBrowser, {"marionette_port": self.instance.marionette_port}
+        extensions = []
+        if self._settings.get("special_powers", False):
+            extensions.append(self.specialpowers_path)
+        return ExecutorBrowser, {"marionette_port": self.instance.marionette_port,
+                                 "extensions": extensions}
 
     def check_crash(self, process, test):
         dump_dir = os.path.join(self.instance.runner.profile.profile, "minidumps")
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/browsers/firefox_android.py b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/browsers/firefox_android.py
index a6cb8f0..bea0d9f4 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/browsers/firefox_android.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/browsers/firefox_android.py
@@ -62,7 +62,8 @@
             "chaos_mode_flags": kwargs["chaos_mode_flags"],
             "config": config,
             "install_fonts": kwargs["install_fonts"],
-            "tests_root": config.doc_root}
+            "tests_root": config.doc_root,
+            "specialpowers_path": kwargs["specialpowers_path"]}
 
 
 def env_extras(**kwargs):
@@ -119,7 +120,6 @@
             })
 
 
-
 class FirefoxAndroidBrowser(Browser):
     init_timeout = 300
     shutdown_timeout = 60
@@ -130,7 +130,7 @@
                  ca_certificate_path=None, e10s=False, enable_webrender=False, stackfix_dir=None,
                  binary_args=None, timeout_multiplier=None, leak_check=False, asan=False,
                  stylo_threads=1, chaos_mode_flags=None, config=None, browser_channel="nightly",
-                 install_fonts=False, tests_root=None, **kwargs):
+                 install_fonts=False, tests_root=None, specialpowers_path=None, **kwargs):
 
         super(FirefoxAndroidBrowser, self).__init__(logger)
         self.prefs_root = prefs_root
@@ -155,6 +155,7 @@
         self.browser_channel = browser_channel
         self.install_fonts = install_fonts
         self.tests_root = tests_root
+        self.specialpowers_path = specialpowers_path
 
         self.profile_creator = ProfileCreator(logger,
                                               prefs_root,
@@ -170,19 +171,23 @@
         self.marionette_port = None
         self.profile = None
         self.runner = None
+        self._settings = {}
 
     def settings(self, test):
-        return {"check_leaks": self.leak_check and not test.leaks,
-                "lsan_allowed": test.lsan_allowed,
-                "lsan_max_stack_depth": test.lsan_max_stack_depth,
-                "mozleak_allowed": self.leak_check and test.mozleak_allowed,
-                "mozleak_thresholds": self.leak_check and test.mozleak_threshold}
+        self._settings = {"check_leaks": self.leak_check and not test.leaks,
+                          "lsan_allowed": test.lsan_allowed,
+                          "lsan_max_stack_depth": test.lsan_max_stack_depth,
+                          "mozleak_allowed": self.leak_check and test.mozleak_allowed,
+                          "mozleak_thresholds": self.leak_check and test.mozleak_threshold,
+                          "special_powers": self.specialpowers_path and test.url_base == "/_mozilla/"}
+        return self._settings
 
     def start(self, **kwargs):
         if self.marionette_port is None:
             self.marionette_port = get_free_port()
 
-        self.profile = self.profile_creator.create()
+        addons = [self.specialpowers_path] if self._settings.get("special_powers") else None
+        self.profile = self.profile_creator.create(addons=addons)
         self.profile.set_preferences({"marionette.port": self.marionette_port})
 
         if self.install_fonts:
@@ -273,7 +278,10 @@
         self.stop(force)
 
     def executor_browser(self):
-        return ExecutorBrowser, {"marionette_port": self.marionette_port}
+        return ExecutorBrowser, {"marionette_port": self.marionette_port,
+                                 # We never want marionette to install extensions because
+                                 # that doesn't work on Android; instead they are in the profile
+                                 "extensions": []}
 
     def check_crash(self, process, test):
         if not os.environ.get("MINIDUMP_STACKWALK", "") and self.stackwalk_binary:
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/executors/executormarionette.py b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/executors/executormarionette.py
index 7e2cc8e..da8941b 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/executors/executormarionette.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/executors/executormarionette.py
@@ -45,15 +45,10 @@
 
 
 def do_delayed_imports():
-    global errors, marionette
+    global errors, marionette, Addons
 
-    # Marionette client used to be called marionette, recently it changed
-    # to marionette_driver for unfathomable reasons
-    try:
-        import marionette
-        from marionette import errors
-    except ImportError:
-        from marionette_driver import marionette, errors
+    from marionette_driver import marionette, errors
+    from marionette_driver.addons import Addons
 
 
 def _switch_to_window(marionette, handle):
@@ -709,7 +704,6 @@
 
 
 class ExecuteAsyncScriptRun(TimedRunner):
-
     def set_timeout(self):
         timeout = self.timeout
 
@@ -782,6 +776,8 @@
         self.window_id = str(uuid.uuid4())
         self.debug = debug
 
+        self.install_extensions = browser.extensions
+
         self.original_pref_values = {}
 
         if marionette is None:
@@ -789,6 +785,11 @@
 
     def setup(self, runner):
         super(MarionetteTestharnessExecutor, self).setup(runner)
+        for extension_path in self.install_extensions:
+            self.logger.info("Installing extension from %s" % extension_path)
+            addons = Addons(self.protocol.marionette)
+            addons.install(extension_path)
+
         self.protocol.testharness.load_runner(self.last_environment["protocol"])
 
     def is_alive(self):
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/testrunner.py b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/testrunner.py
index 0bde5ba..961c40fc 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/testrunner.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/testrunner.py
@@ -98,7 +98,12 @@
     def run(self):
         """Main loop accepting commands over the pipe and triggering
         the associated methods"""
-        self.setup()
+        try:
+            self.setup()
+        except Exception:
+            self.logger.warning("An error occured during executor setup:\n%s" %
+                                traceback.format_exc())
+            raise
         commands = {"run_test": self.run_test,
                     "reset": self.reset,
                     "stop": self.stop,
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wptcommandline.py b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wptcommandline.py
index 9d1d4048..15d2322 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wptcommandline.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wptcommandline.py
@@ -284,6 +284,8 @@
                              help="Disable fission in Gecko.")
     gecko_group.add_argument("--stackfix-dir", dest="stackfix_dir", action="store",
                              help="Path to directory containing assertion stack fixing scripts")
+    gecko_group.add_argument("--specialpowers-path", action="store",
+                             help="Path to specialPowers extension xpi file")
     gecko_group.add_argument("--setpref", dest="extra_prefs", action='append',
                              default=[], metavar="PREF=VALUE",
                              help="Defines an extra user preference (overrides those in prefs_root)")
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wpttest.py b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wpttest.py
index aafe3fa..e21041b6 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wpttest.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wpttest.py
@@ -157,9 +157,10 @@
     default_timeout = 10  # seconds
     long_timeout = 60  # seconds
 
-    def __init__(self, tests_root, url, inherit_metadata, test_metadata,
+    def __init__(self, url_base, tests_root, url, inherit_metadata, test_metadata,
                  timeout=None, path=None, protocol="http", subdomain=False,
                  quic=False):
+        self.url_base = url_base
         self.tests_root = tests_root
         self.url = url
         self._inherit_metadata = inherit_metadata
@@ -167,7 +168,10 @@
         self.timeout = timeout if timeout is not None else self.default_timeout
         self.path = path
         self.subdomain = subdomain
-        self.environment = {"protocol": protocol, "prefs": self.prefs, "quic": quic}
+        self.environment = {"url_base": url_base,
+                            "protocol": protocol,
+                            "prefs": self.prefs,
+                            "quic": quic}
 
     def __eq__(self, other):
         if not isinstance(other, Test):
@@ -186,7 +190,8 @@
     @classmethod
     def from_manifest(cls, manifest_file, manifest_item, inherit_metadata, test_metadata):
         timeout = cls.long_timeout if manifest_item.timeout == "long" else cls.default_timeout
-        return cls(manifest_file.tests_root,
+        return cls(manifest_file.url_base,
+                   manifest_file.tests_root,
                    manifest_item.url,
                    inherit_metadata,
                    test_metadata,
@@ -396,10 +401,10 @@
     subtest_result_cls = TestharnessSubtestResult
     test_type = "testharness"
 
-    def __init__(self, tests_root, url, inherit_metadata, test_metadata,
+    def __init__(self, url_base, tests_root, url, inherit_metadata, test_metadata,
                  timeout=None, path=None, protocol="http", testdriver=False,
                  jsshell=False, scripts=None, subdomain=False, quic=False):
-        Test.__init__(self, tests_root, url, inherit_metadata, test_metadata, timeout,
+        Test.__init__(self, url_base, tests_root, url, inherit_metadata, test_metadata, timeout,
                       path, protocol, subdomain, quic)
 
         self.testdriver = testdriver
@@ -415,7 +420,8 @@
         script_metadata = manifest_item.script_metadata or []
         scripts = [v for (k, v) in script_metadata
                    if k == "script"]
-        return cls(manifest_file.tests_root,
+        return cls(manifest_file.url_base,
+                   manifest_file.tests_root,
                    manifest_item.url,
                    inherit_metadata,
                    test_metadata,
@@ -456,10 +462,10 @@
     result_cls = ReftestResult
     test_type = "reftest"
 
-    def __init__(self, tests_root, url, inherit_metadata, test_metadata, references,
+    def __init__(self, url_base, tests_root, url, inherit_metadata, test_metadata, references,
                  timeout=None, path=None, viewport_size=None, dpi=None, fuzzy=None,
                  protocol="http", quic=False):
-        Test.__init__(self, tests_root, url, inherit_metadata, test_metadata, timeout,
+        Test.__init__(self, url_base, tests_root, url, inherit_metadata, test_metadata, timeout,
                       path, protocol, quic)
 
         for _, ref_type in references:
@@ -490,7 +496,8 @@
 
         url = manifest_test.url
 
-        node = cls(manifest_file.tests_root,
+        node = cls(manifest_file.url_base,
+                   manifest_file.tests_root,
                    manifest_test.url,
                    inherit_metadata,
                    test_metadata,
@@ -511,20 +518,23 @@
         # Per the logic documented above, this means that none of the mismatches provided match,
         mismatch_walk = None
         if refs_by_type["!="]:
-            mismatch_walk = ReftestTest(manifest_file.tests_root,
+            mismatch_walk = ReftestTest(manifest_file.url_base,
+                                        manifest_file.tests_root,
                                         refs_by_type["!="][0],
                                         [],
                                         None,
                                         [])
             cmp_ref = mismatch_walk
             for ref_url in refs_by_type["!="][1:]:
-                cmp_self = ReftestTest(manifest_file.tests_root,
+                cmp_self = ReftestTest(manifest_file.url_base,
+                                       manifest_file.tests_root,
                                        url,
                                        [],
                                        None,
                                        [])
                 cmp_ref.references.append((cmp_self, "!="))
-                cmp_ref = ReftestTest(manifest_file.tests_root,
+                cmp_ref = ReftestTest(manifest_file.url_base,
+                                      manifest_file.tests_root,
                                       ref_url,
                                       [],
                                       None,
@@ -540,7 +550,8 @@
             # For each == ref, add a reference to this node whose tail is the mismatch list.
             # Per the logic documented above, this means any one of the matches must pass plus all the mismatches.
             for ref_url in refs_by_type["=="]:
-                ref = ReftestTest(manifest_file.tests_root,
+                ref = ReftestTest(manifest_file.url_base,
+                                  manifest_file.tests_root,
                                   ref_url,
                                   [],
                                   None,
@@ -609,10 +620,10 @@
 class PrintReftestTest(ReftestTest):
     test_type = "print-reftest"
 
-    def __init__(self, tests_root, url, inherit_metadata, test_metadata, references,
+    def __init__(self, url_base, tests_root, url, inherit_metadata, test_metadata, references,
                  timeout=None, path=None, viewport_size=None, dpi=None, fuzzy=None,
                  page_ranges=None, protocol="http", quic=False):
-        super(PrintReftestTest, self).__init__(tests_root, url, inherit_metadata, test_metadata,
+        super(PrintReftestTest, self).__init__(url_base, tests_root, url, inherit_metadata, test_metadata,
                                                references, timeout, path, viewport_size, dpi,
                                                fuzzy, protocol, quic=quic)
         self._page_ranges = page_ranges
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/overflow/overflow-changed-on-child-of-composited-layer-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/overflow/overflow-changed-on-child-of-composited-layer-expected.txt
index 47fa534..4e811c4 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/overflow/overflow-changed-on-child-of-composited-layer-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/overflow/overflow-changed-on-child-of-composited-layer-expected.txt
@@ -8,10 +8,10 @@
     },
     {
       "name": "LayoutNGBlockFlow DIV id='parent'",
-      "position": [-1, -1],
-      "bounds": [745, 102],
+      "position": [-10, -10],
+      "bounds": [754, 210],
       "invalidations": [
-        [0, 0, 102, 102]
+        [0, 0, 310, 210]
       ],
       "transform": 1
     }
diff --git a/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/compositing/pointer-events-composited-scrolling-expected.txt b/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/compositing/pointer-events-composited-scrolling-expected.txt
index ff4a4eef9..65716de 100644
--- a/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/compositing/pointer-events-composited-scrolling-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/compositing/pointer-events-composited-scrolling-expected.txt
@@ -10,7 +10,7 @@
       "name": "LayoutBlockFlow DIV id='target'",
       "bounds": [100, 100],
       "invalidations": [
-        [0, 0, 100, 100]
+        [0, 30, 50, 50]
       ],
       "transform": 1
     },
diff --git a/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/overflow/overflow-changed-on-child-of-composited-layer-expected.txt b/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/overflow/overflow-changed-on-child-of-composited-layer-expected.txt
index 0127dfcb0..652b942 100644
--- a/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/overflow/overflow-changed-on-child-of-composited-layer-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/overflow/overflow-changed-on-child-of-composited-layer-expected.txt
@@ -8,10 +8,10 @@
     },
     {
       "name": "LayoutBlockFlow DIV id='parent'",
-      "position": [-1, -1],
-      "bounds": [745, 102],
+      "position": [-10, -10],
+      "bounds": [754, 210],
       "invalidations": [
-        [0, 0, 745, 102]
+        [0, 0, 310, 210]
       ],
       "transform": 1
     }
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/paint/invalidation/compositing/pointer-events-composited-scrolling-expected.txt b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/paint/invalidation/compositing/pointer-events-composited-scrolling-expected.txt
index a30b4038..232a9aa 100644
--- a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/paint/invalidation/compositing/pointer-events-composited-scrolling-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/paint/invalidation/compositing/pointer-events-composited-scrolling-expected.txt
@@ -10,7 +10,7 @@
       "name": "LayoutNGBlockFlow DIV id='target'",
       "bounds": [150, 150],
       "invalidations": [
-        [0, 0, 150, 150]
+        [0, 45, 75, 75]
       ],
       "transform": 1
     },
diff --git a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/paint/invalidation/overflow/overflow-changed-on-child-of-composited-layer-expected.txt b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/paint/invalidation/overflow/overflow-changed-on-child-of-composited-layer-expected.txt
index 88167a8..0f7a807 100644
--- a/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/paint/invalidation/overflow/overflow-changed-on-child-of-composited-layer-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/force-device-scale-factor=1.5/paint/invalidation/overflow/overflow-changed-on-child-of-composited-layer-expected.txt
@@ -8,10 +8,10 @@
     },
     {
       "name": "LayoutNGBlockFlow DIV id='parent'",
-      "position": [-1, -1],
-      "bounds": [1117, 152],
+      "position": [-15, -15],
+      "bounds": [1131, 315],
       "invalidations": [
-        [0, 0, 1117, 152]
+        [0, 0, 465, 315]
       ],
       "transform": 1
     }
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/styles-1/add-new-rule-keyboard.js b/third_party/blink/web_tests/http/tests/devtools/elements/styles-1/add-new-rule-keyboard.js
index fa06867..ead3c42 100644
--- a/third_party/blink/web_tests/http/tests/devtools/elements/styles-1/add-new-rule-keyboard.js
+++ b/third_party/blink/web_tests/http/tests/devtools/elements/styles-1/add-new-rule-keyboard.js
@@ -13,7 +13,7 @@
   ElementsTestRunner.selectNodeAndWaitForStyles('inspected', next);
 
   async function next() {
-    await Elements.StylesSidebarPane._instance._createNewRuleInViaInspectorStyleSheet();
+    await Elements.StylesSidebarPane.instance()._createNewRuleInViaInspectorStyleSheet();
     eventSender.keyDown('Tab');
     await TestRunner.addSnifferPromise(Elements.StylePropertiesSection.prototype, '_editingSelectorCommittedForTest');
 
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/resources/inspector-protocol-test.js b/third_party/blink/web_tests/http/tests/inspector-protocol/resources/inspector-protocol-test.js
index 06f292c..5327102 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/resources/inspector-protocol-test.js
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/resources/inspector-protocol-test.js
@@ -3,13 +3,14 @@
 // found in the LICENSE file.
 
 var TestRunner = class {
-  constructor(testBaseURL, targetBaseURL, log, completeTest, fetch) {
+  constructor(testBaseURL, targetBaseURL, log, completeTest, fetch, params) {
     this._dumpInspectorProtocolMessages = false;
     this._testBaseURL = testBaseURL;
     this._targetBaseURL = targetBaseURL;
     this._log = log;
     this._completeTest = completeTest;
     this._fetch = fetch;
+    this._params = params;
     this._browserSession = new TestRunner.Session(this, '');
   }
 
@@ -35,6 +36,10 @@
     this._log.call(null, item);
   }
 
+  params(name) {
+    return name ? this._params.get(name) : this._params;
+  }
+
   _logObject(object, title, stabilizeNames = TestRunner.stabilizeNames) {
     var lines = [];
 
@@ -544,7 +549,7 @@
   var targetBaseURL = targetPageURL.substring(0, targetPageURL.lastIndexOf('/') + 1);
 
   DevToolsAPI._fetch(testScriptURL).then(testScript => {
-    var testRunner = new TestRunner(testBaseURL, targetBaseURL, DevToolsAPI._log, DevToolsAPI._completeTest, DevToolsAPI._fetch);
+    var testRunner = new TestRunner(testBaseURL, targetBaseURL, DevToolsAPI._log, DevToolsAPI._completeTest, DevToolsAPI._fetch, params);
     var testFunction = eval(`${testScript}\n//# sourceURL=${testScriptURL}`);
     if (params.get('debug')) {
       var dispatch = DevToolsAPI.dispatchMessage;
diff --git a/third_party/blink/web_tests/paint/invalidation/compositing/pointer-events-composited-scrolling-expected.txt b/third_party/blink/web_tests/paint/invalidation/compositing/pointer-events-composited-scrolling-expected.txt
index be2d2647..1e970a5 100644
--- a/third_party/blink/web_tests/paint/invalidation/compositing/pointer-events-composited-scrolling-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/compositing/pointer-events-composited-scrolling-expected.txt
@@ -10,7 +10,7 @@
       "name": "LayoutNGBlockFlow DIV id='target'",
       "bounds": [100, 100],
       "invalidations": [
-        [0, 0, 100, 100]
+        [0, 30, 50, 50]
       ],
       "transform": 1
     },
diff --git a/third_party/blink/web_tests/paint/invalidation/overflow/overflow-changed-on-child-of-composited-layer-expected.html b/third_party/blink/web_tests/paint/invalidation/overflow/overflow-changed-on-child-of-composited-layer-expected.html
index 887d9ca7..6954b75 100644
--- a/third_party/blink/web_tests/paint/invalidation/overflow/overflow-changed-on-child-of-composited-layer-expected.html
+++ b/third_party/blink/web_tests/paint/invalidation/overflow/overflow-changed-on-child-of-composited-layer-expected.html
@@ -1,5 +1,7 @@
 <!DOCTYPE html>
-<div id="parent" style="margin: 20px; transform: scale3d(1, 1, 1);">
-    <div id="test" style="height: 100px; width: 100px; outline: 1px solid black;"></div>
+<div id="parent" style="margin: 20px; position: relative">
+  <div id="test" style="height: 100px; width: 100px; outline: 10px solid black;"></div>
+  <div style="height: 100px; width: 100px; background: blue"></div>
+  <div style="position: absolute; top: 10px; left: 200px; height: 100px; width: 100px; background: yellow"></div>
 </div>
 <p>crbug.com/621612: Change overflow on the child of a composited layer should allow the layer to resize and contain the overflow, just like it would in a layout. There should be a white box with black outline below.</p>
diff --git a/third_party/blink/web_tests/paint/invalidation/overflow/overflow-changed-on-child-of-composited-layer-expected.txt b/third_party/blink/web_tests/paint/invalidation/overflow/overflow-changed-on-child-of-composited-layer-expected.txt
index bce679a5..df1bc49 100644
--- a/third_party/blink/web_tests/paint/invalidation/overflow/overflow-changed-on-child-of-composited-layer-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/overflow/overflow-changed-on-child-of-composited-layer-expected.txt
@@ -8,10 +8,10 @@
     },
     {
       "name": "LayoutNGBlockFlow DIV id='parent'",
-      "position": [-1, -1],
-      "bounds": [745, 102],
+      "position": [-10, -10],
+      "bounds": [754, 210],
       "invalidations": [
-        [0, 0, 745, 102]
+        [0, 0, 310, 210]
       ],
       "transform": 1
     }
diff --git a/third_party/blink/web_tests/paint/invalidation/overflow/overflow-changed-on-child-of-composited-layer.html b/third_party/blink/web_tests/paint/invalidation/overflow/overflow-changed-on-child-of-composited-layer.html
index 7bebd63..8f1a261 100644
--- a/third_party/blink/web_tests/paint/invalidation/overflow/overflow-changed-on-child-of-composited-layer.html
+++ b/third_party/blink/web_tests/paint/invalidation/overflow/overflow-changed-on-child-of-composited-layer.html
@@ -1,12 +1,14 @@
 <!DOCTYPE html>
 <div id="parent" style="margin: 20px; will-change: transform;">
-    <div id="test" style="height: 100px; width: 100px;"></div>
+  <div id="test" style="height: 100px; width: 100px;"></div>
+  <div style="height: 100px; width: 100px; background: blue"></div>
+  <div style="position: absolute; top: 10px; left: 200px; height: 100px; width: 100px; background: yellow"></div>
 </div>
 <script src="../resources/text-based-repaint.js"></script>
 <p>crbug.com/621612: Change overflow on the child of a composited layer should allow the layer to resize and contain the overflow, just like it would in a layout. There should be a white box with black outline below.</p>
 <script>
 function repaintTest() {
-  test.style.outline = "1px solid black";
+  test.style.outline = "10px solid black";
 }
 onload = function() {
   runRepaintAndPixelTest();
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index 3dbb67a..d493d63 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -11329,6 +11329,7 @@
     method getDepthInformation
     method getHitTestResults
     method getHitTestResultsForTransientInput
+    method getImageTrackingResults
     method getLightEstimate
     method getPose
     method getViewerPose
@@ -11341,6 +11342,13 @@
     attribute @@toStringTag
     method cancel
     method constructor
+interface XRImageTrackingResult
+    attribute @@toStringTag
+    getter imageSpace
+    getter index
+    getter measuredWidthInMeters
+    getter trackingState
+    method constructor
 interface XRInputSource
     attribute @@toStringTag
     getter gamepad
@@ -11464,6 +11472,7 @@
     method cancelAnimationFrame
     method constructor
     method end
+    method getTrackedImageScores
     method requestAnimationFrame
     method requestHitTestSource
     method requestHitTestSourceForTransientInput
diff --git a/third_party/closure_compiler/externs/input_method_private.js b/third_party/closure_compiler/externs/input_method_private.js
index e387d12..62a2c8e 100644
--- a/third_party/closure_compiler/externs/input_method_private.js
+++ b/third_party/closure_compiler/externs/input_method_private.js
@@ -400,6 +400,17 @@
 chrome.inputMethodPrivate.reset = function() {};
 
 /**
+ * Called after a word has been autocorrected to show some UI for autocorrect.
+ * @param{{
+ *  contextID: number,
+ *  typedWord: string,
+ *  correctedWord: string,
+ *  startIndex: number
+ * }} parameters
+ */
+chrome.inputMethodPrivate.onAutocorrect = function(parameters) {};
+
+/**
  * Fired when the input method is changed.
  * @type {!ChromeEvent}
  */
diff --git a/third_party/crashpad/README.chromium b/third_party/crashpad/README.chromium
index 48e225dd..7b15956 100644
--- a/third_party/crashpad/README.chromium
+++ b/third_party/crashpad/README.chromium
@@ -2,7 +2,7 @@
 Short Name: crashpad
 URL: https://crashpad.chromium.org/
 Version: unknown
-Revision: 640b13f3cb8000d361547f4cfa59275df44d1dc8
+Revision: ca944aeb88ff299346ecd6eadbf9c0eb9761fe7a
 License: Apache 2.0
 License File: crashpad/LICENSE
 Security Critical: yes
diff --git a/third_party/crashpad/crashpad/DEPS b/third_party/crashpad/crashpad/DEPS
index 7e83327..fbff155 100644
--- a/third_party/crashpad/crashpad/DEPS
+++ b/third_party/crashpad/crashpad/DEPS
@@ -42,7 +42,7 @@
       '7bde79cc274d06451bf65ae82c012a5d3e476b5a',
   'crashpad/third_party/mini_chromium/mini_chromium':
       Var('chromium_git') + '/chromium/mini_chromium@' +
-      '5fc64bfbf1c000161445c586de45e40464ff2314',
+      'cb82d71291f19590d3ee138ba64fcf1e9e0edd84',
   'crashpad/third_party/libfuzzer/src':
       Var('chromium_git') + '/chromium/llvm-project/compiler-rt/lib/fuzzer.git@' +
       'fda403cf93ecb8792cb1d061564d89a6553ca020',
diff --git a/third_party/crashpad/crashpad/handler/BUILD.gn b/third_party/crashpad/crashpad/handler/BUILD.gn
index 468303b..623c833 100644
--- a/third_party/crashpad/crashpad/handler/BUILD.gn
+++ b/third_party/crashpad/crashpad/handler/BUILD.gn
@@ -181,7 +181,7 @@
 
     deps = [ "../util:no_cfi_icall" ]
 
-    ldflags = [ "-llog" ]
+    libs = [ "log" ]
 
     if (crashpad_is_in_chromium) {
       no_default_deps = true
diff --git a/third_party/crashpad/crashpad/handler/linux/crash_report_exception_handler.cc b/third_party/crashpad/crashpad/handler/linux/crash_report_exception_handler.cc
index 557b171..9dc313c 100644
--- a/third_party/crashpad/crashpad/handler/linux/crash_report_exception_handler.cc
+++ b/third_party/crashpad/crashpad/handler/linux/crash_report_exception_handler.cc
@@ -43,10 +43,10 @@
 namespace crashpad {
 namespace {
 
-class Logger : public LogOutputStream::Delegate {
+class Logger final : public LogOutputStream::Delegate {
  public:
   Logger() = default;
-  ~Logger() = default;
+  ~Logger() override = default;
 
 #if defined(OS_ANDROID)
   int Log(const char* buf) override {
diff --git a/third_party/crashpad/crashpad/util/ios/exception_processor_test.mm b/third_party/crashpad/crashpad/util/ios/exception_processor_test.mm
index c9aa716..37d37392 100644
--- a/third_party/crashpad/crashpad/util/ios/exception_processor_test.mm
+++ b/third_party/crashpad/crashpad/util/ios/exception_processor_test.mm
@@ -25,7 +25,9 @@
 
 using IOSExceptionProcessor = PlatformTest;
 
-TEST_F(IOSExceptionProcessor, SelectorExists) {
+// TODO(crbug.com/crashpad/358): Re-enable once iOS14 redacted symbol issue is
+// fixed.
+TEST_F(IOSExceptionProcessor, DISABLED_SelectorExists) {
   IMP uigesture_deliver_event_imp = class_getMethodImplementation(
       NSClassFromString(@"UIGestureEnvironment"),
       NSSelectorFromString(@"_deliverEvent:toGestureRecognizers:usingBlock:"));
diff --git a/third_party/crashpad/crashpad/util/stream/log_output_stream.h b/third_party/crashpad/crashpad/util/stream/log_output_stream.h
index 5eff9a15..a1b6df3a 100644
--- a/third_party/crashpad/crashpad/util/stream/log_output_stream.h
+++ b/third_party/crashpad/crashpad/util/stream/log_output_stream.h
@@ -50,7 +50,7 @@
     virtual size_t LineWidth() = 0;
   };
 
-  LogOutputStream(std::unique_ptr<Delegate> delegate);
+  explicit LogOutputStream(std::unique_ptr<Delegate> delegate);
   ~LogOutputStream() override;
 
   // OutputStreamInterface:
diff --git a/third_party/crashpad/crashpad/util/stream/log_output_stream_test.cc b/third_party/crashpad/crashpad/util/stream/log_output_stream_test.cc
index f12aa76..9faf34b 100644
--- a/third_party/crashpad/crashpad/util/stream/log_output_stream_test.cc
+++ b/third_party/crashpad/crashpad/util/stream/log_output_stream_test.cc
@@ -31,11 +31,11 @@
 const char* kEndGuard = "-----END CRASHPAD MINIDUMP-----";
 const char* kAbortGuard = "-----ABORT CRASHPAD MINIDUMP-----";
 
-class LogOutputStreamTestDelegate : public LogOutputStream::Delegate {
+class LogOutputStreamTestDelegate final : public LogOutputStream::Delegate {
  public:
-  LogOutputStreamTestDelegate(std::string* logging_destination)
+  explicit LogOutputStreamTestDelegate(std::string* logging_destination)
       : logging_destination_(logging_destination) {}
-  ~LogOutputStreamTestDelegate() = default;
+  ~LogOutputStreamTestDelegate() override = default;
 
   int Log(const char* buf) override {
     size_t len = strnlen(buf, kLineBufferSize + 1);
@@ -53,7 +53,7 @@
 
 class LogOutputStreamTest : public testing::Test {
  public:
-  LogOutputStreamTest() : test_log_output_() {}
+  LogOutputStreamTest() {}
 
  protected:
   void SetUp() override {
diff --git a/third_party/crashpad/crashpad/util/thread/thread_log_messages_test.cc b/third_party/crashpad/crashpad/util/thread/thread_log_messages_test.cc
index b25dc60e..0400e8d 100644
--- a/third_party/crashpad/crashpad/util/thread/thread_log_messages_test.cc
+++ b/third_party/crashpad/crashpad/util/thread/thread_log_messages_test.cc
@@ -36,42 +36,13 @@
   EXPECT_TRUE(log_messages.empty());
 }
 
-// For a message formatted like "[preamble] message\n", returns just "message".
-// If the message is not formatted as expected, a Google Test expectation
-// failure will be recorded and this function will return an empty string.
-std::string MessageString(const std::string& log_message) {
-  if (log_message.size() < 1) {
-    EXPECT_GE(log_message.size(), 1u);
-    return std::string();
-  }
-
-  constexpr char kStartChar = '[';
-  if (log_message[0] != kStartChar) {
-    EXPECT_EQ(log_message[0], kStartChar);
-    return std::string();
-  }
-
-  static constexpr char kFindString[] = "] ";
-  size_t pos = log_message.find(kFindString);
-  if (pos == std::string::npos) {
-    EXPECT_NE(pos, std::string::npos);
-    return std::string();
-  }
-
-  std::string message_string = log_message.substr(pos + strlen(kFindString));
-  if (message_string.size() < 1) {
-    EXPECT_GE(message_string.size(), 1u);
-    return std::string();
-  }
-
-  constexpr char kEndChar = '\n';
-  if (message_string[message_string.size() - 1] != kEndChar) {
-    EXPECT_NE(message_string[message_string.size() - 1], kEndChar);
-    return std::string();
-  }
-
-  message_string.resize(message_string.size() - 1);
-  return message_string;
+void ExpectLogMessage(const std::string& message, const std::string& expected) {
+  ASSERT_GT(message.size(), expected.size());
+  EXPECT_EQ(message.back(), '\n');
+  EXPECT_STREQ(
+      message.substr(message.size() - expected.size() - 1, expected.size())
+          .c_str(),
+      expected.c_str());
 }
 
 TEST(ThreadLogMessages, Basic) {
@@ -96,7 +67,8 @@
 
     EXPECT_EQ(log_messages.size(), base::size(kMessages));
     for (size_t index = 0; index < base::size(kMessages); ++index) {
-      EXPECT_EQ(MessageString(log_messages[index]), kMessages[index])
+      ASSERT_NO_FATAL_FAILURE(
+          ExpectLogMessage(log_messages[index], kMessages[index]))
           << "index " << index;
     }
   }
@@ -112,7 +84,7 @@
         thread_log_messages.log_messages();
 
     EXPECT_EQ(log_messages.size(), 1u);
-    EXPECT_EQ(MessageString(log_messages[0]), kMessage);
+    ExpectLogMessage(log_messages[0], kMessage);
   }
 
   {
@@ -124,8 +96,8 @@
         thread_log_messages.log_messages();
 
     EXPECT_EQ(log_messages.size(), 1u);
-    EXPECT_EQ(MessageString(log_messages[0]),
-              "I can't believe I streamed the whole thing.");
+    ExpectLogMessage(log_messages[0],
+                     "I can't believe I streamed the whole thing.");
   }
 }
 
@@ -156,7 +128,8 @@
 
     ASSERT_EQ(log_messages.size(), static_cast<size_t>(count_));
     for (size_t index = 0; index < log_messages.size(); ++index) {
-      EXPECT_EQ(MessageString(log_messages[index]), expected_messages[index])
+      ASSERT_NO_FATAL_FAILURE(
+          ExpectLogMessage(log_messages[index], expected_messages[index]))
           << "thread_number_ " << thread_number_ << ", index " << index;
     }
   }
diff --git a/tools/auto-nav.py b/tools/auto-nav.py
index 46674a7..7aa4bac 100644
--- a/tools/auto-nav.py
+++ b/tools/auto-nav.py
@@ -15,6 +15,12 @@
 * --idlewakeups_dir: Windows only; specify the directory containing
                      idlewakeups.exe to print measurements taken by IdleWakeups,
                      e.g., --idlewakeups_dir=tools/win/IdleWakeups/x64/Debug
+
+Optional flags to chrome.exe, example:
+-- --user-data-dir=temp --disable-features=SomeFeature
+Note: must be at end of command, following options terminator "--". The options
+terminator stops command-line options from being interpreted as options for this
+script, which would cause an unrecognized-argument error.
 """
 
 # [VPYTHON:BEGIN]
@@ -49,10 +55,34 @@
 DEFAULT_INTERVAL = 1
 
 
+# Splits list |positional_args| into two lists: |urls| and |chrome_args|, where
+# arguments starting with '-' are treated as chrome args, and the rest as URLs.
+def ParsePositionalArgs(positional_args):
+  urls, chrome_args = [], []
+  for arg in positional_args:
+    if arg.startswith('-'):
+      chrome_args.append(arg)
+    else:
+      urls.append(arg)
+  return [urls, chrome_args]
+
+
 # Returns an object containing the arguments parsed from this script's command
 # line.
 def ParseArgs():
-  parser = argparse.ArgumentParser()
+  # Customize usage and help to include options to be passed to chrome.exe.
+  usage_text = '''%(prog)s [-h] [--interval INTERVAL] [--wait]
+                   [--idlewakeups_dir IDLEWAKEUPS_DIR]
+                   chrome_dir num_navigations url [url ...]
+                   [-- --chrome_option ...]'''
+  additional_help_text = '''optional arguments to chrome.exe, example:
+  -- --enable-features=MyFeature --browser-startup-dialog
+                        Must be at end of command, following the options
+                        terminator "--"'''
+  parser = argparse.ArgumentParser(
+      epilog=additional_help_text,
+      usage=usage_text,
+      formatter_class=argparse.RawDescriptionHelpFormatter)
   parser.add_argument(
       'chrome_dir', help='Directory containing chrome.exe and chromedriver.exe')
   parser.add_argument('num_navigations',
@@ -68,14 +98,19 @@
                       help='Wait for confirmation before beginning navigation')
   parser.add_argument(
       '--idlewakeups_dir',
-      type=str,
       help='Windows only; directory containing idlewakeups.exe, if using')
   parser.add_argument(
       'url',
       nargs='+',
       help='URL(s) to navigate, separated by spaces; must include scheme, '
       'e.g., "https://"')
-  return parser.parse_args()
+  args = parser.parse_args()
+  args.url, chrome_args = ParsePositionalArgs(args.url)
+  if not args.url:
+    parser.print_usage()
+    print(os.path.basename(__file__) + ': error: missing URL argument')
+    exit(1)
+  return [args, chrome_args]
 
 
 # If |path| does not exist, prints a generic error plus optional |error_message|
@@ -90,7 +125,7 @@
 
 def main():
   # Parse arguments and check that file paths received are valid.
-  args = ParseArgs()
+  args, chrome_args = ParseArgs()
   ExitIfNotFound(os.path.join(args.chrome_dir, 'chrome.exe'),
                  'Build target "chrome" to generate it first.')
   chromedriver_exe = os.path.join(args.chrome_dir, 'chromedriver.exe')
@@ -104,6 +139,8 @@
   # this script's output easier.
   chrome_options = webdriver.ChromeOptions()
   chrome_options.add_experimental_option('excludeSwitches', ['enable-logging'])
+  for arg in chrome_args:
+    chrome_options.add_argument(arg)
   driver = webdriver.Chrome(os.path.abspath(chromedriver_exe),
                             options=chrome_options)
 
diff --git a/tools/gritsettings/resource_ids.spec b/tools/gritsettings/resource_ids.spec
index e543e08..79b1093 100644
--- a/tools/gritsettings/resource_ids.spec
+++ b/tools/gritsettings/resource_ids.spec
@@ -180,7 +180,7 @@
     "META": {"sizes": {"includes": [50],}},
     "includes": [1850],
   },
-  "chrome/browser/resources/tab_search_merge/tab_search_resources.grd": {
+  "chrome/browser/resources/tab_search/tab_search_resources.grd": {
     "includes": [1880],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/tab_strip/tab_strip_resources.grd": {
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index 0cb5939..966eaa6 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -694,7 +694,7 @@
       'chromeos-amd64-generic-lacros-internal-rel': 'chromeos_amd64-generic_lacros_rel',
       'linux-autofill-captured-sites-rel': 'release_bot',
       'linux-password-manager-captured-sites-rel': 'release_bot',
-      'lorenz-graph-dbg': 'android_debug_static_bot',
+      'lorenz-graph-dbg': 'android_debug_static_external_bot',
       'mac-autofill-captured-sites-rel': 'release_bot',
       'win-autofill-captured-sites-rel': 'release_bot',
       'win-celab-rel': 'official_celab_release_bot',
@@ -1258,6 +1258,10 @@
       'android', 'debug_static_bot',
     ],
 
+    'android_debug_static_external_bot': [
+      'android', 'debug_static_bot', 'android_external'
+    ],
+
     'android_debug_static_bot_arm64': [
       'android', 'debug_static_bot', 'arm64',
     ],
@@ -2504,6 +2508,10 @@
       'mixins': ['android_without_codecs', 'chrome_with_codecs'],
     },
 
+    'android_external': {
+      'gn_args': 'enable_chrome_android_internal=false',
+    },
+
     # It's significantly faster to build without lint and errorprone.
     'android_fastbuild': {
       'gn_args': ('use_errorprone_java_compiler=false '
diff --git a/tools/mb/mb_config_expectations/internal.chrome.fyi.json b/tools/mb/mb_config_expectations/internal.chrome.fyi.json
index 70127d9..5e73395 100644
--- a/tools/mb/mb_config_expectations/internal.chrome.fyi.json
+++ b/tools/mb/mb_config_expectations/internal.chrome.fyi.json
@@ -43,6 +43,7 @@
   },
   "lorenz-graph-dbg": {
     "gn_args": {
+      "enable_chrome_android_internal": false,
       "ffmpeg_branding": "Chrome",
       "is_component_build": false,
       "is_debug": true,
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 3114bd52..b5d4d222 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -2981,6 +2981,7 @@
   <int value="34" label="LaCrOS"/>
   <int value="35" label="Remote App"/>
   <int value="36" label="Borealis App"/>
+  <int value="37" label="Help App"/>
 </enum>
 
 <enum name="AppListSearchResultDisplayType">
@@ -3147,6 +3148,21 @@
       label="Shortcut install, menu showed 'Install' (failure mode)"/>
 </enum>
 
+<enum name="AppWillTerminateReceived">
+  <int value="0" label="WasNotReceivedForXte">
+    ApplicationWillTerminate notification was not received for this XTE.
+  </int>
+  <int value="1" label="WasNotReceivedForUte">
+    ApplicationWillTerminate notification was not received for this UTE.
+  </int>
+  <int value="2" label="WasReceivedForXte">
+    ApplicationWillTerminate notification was received for this XTE.
+  </int>
+  <int value="3" label="WasReceivedForUte">
+    ApplicationWillTerminate notification was received for this UTE.
+  </int>
+</enum>
+
 <enum name="AppWindowDragEndWindowState">
   <int value="0" label="Maximized or Fullscreen"/>
   <int value="1" label="In Overview"/>
@@ -25191,6 +25207,7 @@
   <int value="1524"
       label="FILEMANAGERPRIVATEINTERNAL_GETARCDOCUMENTSPROVIDERTHUMBNAIL"/>
   <int value="1525" label="ACCESSIBILITY_PRIVATE_ISFEATUREENABLED"/>
+  <int value="1526" label="INPUTMETHODPRIVATE_ONAUTOCORRECT"/>
 </enum>
 
 <enum name="ExtensionIconState">
@@ -42324,7 +42341,6 @@
       label="AutofillEnforceMinRequiredFieldsForQuery:disabled"/>
   <int value="-1408370474" label="AssistantRoutines:enabled"/>
   <int value="-1408288176" label="enable-account-consistency"/>
-  <int value="-1407769602" label="UsageStats:disabled"/>
   <int value="-1405349891" label="PictureInPictureAPI:enabled"/>
   <int value="-1405048637" label="OfflinePagesResourceBasedSnapshot:enabled"/>
   <int value="-1404469375" label="RemoteCopyProgressNotification:enabled"/>
@@ -45042,7 +45058,6 @@
   <int value="1300753556" label="ManualPasswordGenerationAndroid:disabled"/>
   <int value="1301902557" label="AutofillCreditCardAuthentication:disabled"/>
   <int value="1302421166" label="NativeNotifications:disabled"/>
-  <int value="1303260487" label="UsageStats:enabled"/>
   <int value="1304636193" label="ArcEnableUnifiedAudioFocus:enabled"/>
   <int value="1305792241" label="SafeBrowsingEnhancedProtection:disabled"/>
   <int value="1307003774" label="AutofillEnableCompanyName:disabled"/>
diff --git a/tools/metrics/histograms/histograms_xml/ash/histograms.xml b/tools/metrics/histograms/histograms_xml/ash/histograms.xml
index 54fd96db..6ea68943 100644
--- a/tools/metrics/histograms/histograms_xml/ash/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/ash/histograms.xml
@@ -272,6 +272,41 @@
   </summary>
 </histogram>
 
+<histogram name="Ash.ClipboardHistory.ContextualNudge.NudgeToFeatureOpenTime"
+    units="seconds" expires_after="2021-09-01">
+  <owner>yulunwu@chromium.org</owner>
+  <owner>newcomer@chromium.org</owner>
+  <summary>
+    The number of seconds between the user being shown the clipboard history
+    contextual nudge and the opening the clipboard history menu. The sum over
+    this histogram will also be used to measure the conversion rate between
+    showing the nudge and the clipboard history feature being opened.
+  </summary>
+</histogram>
+
+<histogram name="Ash.ClipboardHistory.ContextualNudge.NudgeToFeatureUseTime"
+    units="seconds" expires_after="2021-09-01">
+  <owner>yulunwu@chromium.org</owner>
+  <owner>newcomer@chromium.org</owner>
+  <summary>
+    The number of seconds between the user being shown the clipboard history
+    contextual nudge and the user pasting with the clipboard history feature.
+    The sum over this histogram will also be used to measure the conversion rate
+    between showing the nudge and the clipboard history feature being used.
+  </summary>
+</histogram>
+
+<histogram name="Ash.ClipboardHistory.ContextualNudge.ShownCount"
+    enum="BooleanHit" expires_after="2021-09-01">
+  <owner>yulunwu@chromium.org</owner>
+  <owner>newcomer@chromium.org</owner>
+  <summary>
+    The number of times the clipboard history contextual nudge has been shown.
+    This number will be used as the baseline against the sum of the
+    NudgeToFeatureUseTime and NudgeToFeatureOpenTime.
+  </summary>
+</histogram>
+
 <histogram base="true" name="Ash.ContextualNudgeDismissContext"
     enum="ContextualNudgeDismissContext" expires_after="M86">
 <!-- Name completed by histogram_suffixes name="ContextualNudgesNames" -->
diff --git a/tools/metrics/histograms/histograms_xml/stability/histograms.xml b/tools/metrics/histograms/histograms_xml/stability/histograms.xml
index b5abdab..a1ab2e14 100644
--- a/tools/metrics/histograms/histograms_xml/stability/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/stability/histograms.xml
@@ -400,6 +400,19 @@
   </summary>
 </histogram>
 
+<histogram name="Stability.iOS.UTE.MobileSessionAppWillTerminateReceived"
+    enum="AppWillTerminateReceived" expires_after="2021-04-29">
+  <owner>eugenebut@chromium.org</owner>
+  <owner>olivierrobin@chromium.org</owner>
+  <summary>
+    Recorded when app starts after Unexplained Termination Event (UTE) or
+    Explained Termination Event (XTE). UTEs/XTEs can happen when app did not
+    finish writing prefs. This histogram should help to understand if prefs did
+    not get written during normal app termination sequence when
+    ApplicationWillTerminate notification was called.
+  </summary>
+</histogram>
+
 <histogram name="Stability.iOS.UTE.MobileSessionOOMShutdownHint"
     enum="OOMShutdownHint" expires_after="2021-06-22">
   <owner>eugenebut@chromium.org</owner>
diff --git a/ui/file_manager/BUILD.gn b/ui/file_manager/BUILD.gn
index 188066d..9c6b2bc 100644
--- a/ui/file_manager/BUILD.gn
+++ b/ui/file_manager/BUILD.gn
@@ -95,6 +95,7 @@
   in_files = [
     "base/js/app_util.m.js",
     "base/js/volume_manager_types.m.js",
+    "base/js/mediasession_types.m.js",
   ]
 
   deps = [ "//ui/file_manager/base/js:modulize" ]
diff --git a/ui/file_manager/base/js/BUILD.gn b/ui/file_manager/base/js/BUILD.gn
index 45354341..5eac0ac 100644
--- a/ui/file_manager/base/js/BUILD.gn
+++ b/ui/file_manager/base/js/BUILD.gn
@@ -16,6 +16,7 @@
     ":closure_compile_module",
     ":js_test_gen_html_type_check_auto",
     ":test_support_type_check",
+    ":test_support_type_check_jsmodules",
   ]
 }
 
@@ -43,6 +44,15 @@
   ]
 }
 
+js_type_check("test_support_type_check_jsmodules") {
+  uses_js_modules = true
+  testonly = true
+  deps = [
+    ":mock_chrome.m",
+    ":test_error_reporting.m",
+  ]
+}
+
 js_library("app_util") {
   externs_list = [ "//ui/file_manager/externs/app_window_common.js" ]
 }
@@ -76,6 +86,13 @@
   testonly = true
 }
 
+js_library("mock_chrome.m") {
+  testonly = true
+  sources = [ "$root_gen_dir/ui/file_manager/base/js/mock_chrome.m.js" ]
+
+  extra_deps = [ ":modulize" ]
+}
+
 js_library("test_error_reporting") {
   testonly = true
   deps = [
@@ -85,6 +102,14 @@
   ]
 }
 
+js_library("test_error_reporting.m") {
+  testonly = true
+  sources =
+      [ "$root_gen_dir/ui/file_manager/base/js/test_error_reporting.m.js" ]
+
+  extra_deps = [ ":modulize" ]
+}
+
 js_library("volume_manager_types") {
   deps = [
     "//ui/file_manager/externs:file_manager_private",
@@ -123,9 +148,18 @@
 js_library("mediasession_types") {
 }
 
+js_library("mediasession_types.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/base/js/mediasession_types.m.js" ]
+
+  extra_deps = [ ":modulize" ]
+}
+
 js_modulizer("modulize") {
   input_files = [
     "app_util.js",
     "volume_manager_types.js",
+    "mock_chrome.js",
+    "mediasession_types.js",
+    "test_error_reporting.js",
   ]
 }
diff --git a/ui/file_manager/base/js/mediasession_types.js b/ui/file_manager/base/js/mediasession_types.js
index 48d36dd..ab497a21 100644
--- a/ui/file_manager/base/js/mediasession_types.js
+++ b/ui/file_manager/base/js/mediasession_types.js
@@ -3,6 +3,11 @@
 // found in the LICENSE file.
 
 /**
+ * @fileoverview
+ * @suppress {uselessCode} Temporary suppress because of the line exporting.
+ */
+
+/**
  * @see https://wicg.github.io/mediasession/#enumdef-mediasessionplaybackstate
  * @enum {string}
  */
@@ -11,3 +16,6 @@
   PAUSED: 'paused',
   PLAYING: 'playing'
 };
+
+// eslint-disable-next-line semi,no-extra-semi
+/* #export */ {MediaSessionPlaybackState};
diff --git a/ui/file_manager/base/js/mock_chrome.js b/ui/file_manager/base/js/mock_chrome.js
index 8ab2f4a4..1fc91cbb 100644
--- a/ui/file_manager/base/js/mock_chrome.js
+++ b/ui/file_manager/base/js/mock_chrome.js
@@ -6,7 +6,7 @@
  * Installs a mock object to replace window.chrome in a unit test.
  * @param {Object} mockChrome
  */
-function installMockChrome(mockChrome) {
+/* #export */ function installMockChrome(mockChrome) {
   /** @suppress {const|checkTypes} */
   chrome = mockChrome;
 }
@@ -14,7 +14,7 @@
 /**
  * Mocks chrome.commandLinePrivate.
  */
-class MockCommandLinePrivate {
+/* #export */ class MockCommandLinePrivate {
   constructor() {
     this.flags_ = {};
     if (!chrome) {
@@ -44,7 +44,7 @@
 /**
  * Stubs the chrome.storage API.
  */
-class MockChromeStorageAPI {
+/* #export */ class MockChromeStorageAPI {
   constructor() {
     /** @type {Object<?>} */
     this.state = {};
@@ -68,8 +68,8 @@
    * @private
    */
   get_(keys, callback) {
-    var keys = keys instanceof Array ? keys : [keys];
-    var result = {};
+    keys = keys instanceof Array ? keys : [keys];
+    const result = {};
     keys.forEach((key) => {
       if (key in this.state) {
         result[key] = this.state[key];
@@ -84,7 +84,7 @@
    * @private
    */
   set_(values, opt_callback) {
-    for (var key in values) {
+    for (const key in values) {
       this.state[key] = values[key];
     }
     if (opt_callback) {
diff --git a/ui/file_manager/base/js/test_error_reporting.js b/ui/file_manager/base/js/test_error_reporting.js
index f76216ff..696f0e7d 100644
--- a/ui/file_manager/base/js/test_error_reporting.js
+++ b/ui/file_manager/base/js/test_error_reporting.js
@@ -6,7 +6,7 @@
  * Asserts that promise gets rejected.
  * @param {Promise} promise
  */
-async function assertRejected(promise) {
+/* #export */ async function assertRejected(promise) {
   let triggeredError = false;
   try {
     await promise;
@@ -26,7 +26,7 @@
  * @param {function(boolean)} callback Callback function. True is passed if the
  *     test failed.
  */
-function reportPromise(promise, callback) {
+/* #export */ function reportPromise(promise, callback) {
   promise.then(
       () => {
         callback(/* error */ false);
@@ -43,11 +43,11 @@
  * @return {!Promise} A promise which is fulfilled when the testFunction
  *     becomes true.
  */
-function waitUntil(testFunction) {
+/* #export */ function waitUntil(testFunction) {
   const INTERVAL_FOR_WAIT_UNTIL = 100;  // ms
 
   return new Promise((resolve) => {
-    let tryTestFunction = () => {
+    const tryTestFunction = () => {
       if (testFunction()) {
         resolve();
       } else {
diff --git a/ui/file_manager/file_manager/foreground/elements/files_toast_unittest.js b/ui/file_manager/file_manager/foreground/elements/files_toast_unittest.js
index d814398a..ed80868 100644
--- a/ui/file_manager/file_manager/foreground/elements/files_toast_unittest.js
+++ b/ui/file_manager/file_manager/foreground/elements/files_toast_unittest.js
@@ -17,7 +17,7 @@
     }
   };
   const getToastOpacity = () => {
-    return Number(
+    return parseFloat(
         window.getComputedStyle(toast.shadowRoot.querySelector('cr-toast'))
             .opacity);
   };
@@ -80,7 +80,7 @@
   // Call hide(), toast should no longer be visible, no more toasts shown.
   toast.hide();
   await waitFor(() => getToastOpacity() === 0);
-  assertFalse(toast.visible);
+  await waitFor(() => !toast.visible);
 
   done();
 }
diff --git a/ui/file_manager/file_manager/foreground/js/metadata/BUILD.gn b/ui/file_manager/file_manager/foreground/js/metadata/BUILD.gn
index 2d9a6c53..3d4c0a6 100644
--- a/ui/file_manager/file_manager/foreground/js/metadata/BUILD.gn
+++ b/ui/file_manager/file_manager/foreground/js/metadata/BUILD.gn
@@ -14,13 +14,13 @@
   testonly = true
   deps = [
     ":closure_compile_module",
-    ":closure_jsmodules",
+    ":closure_compile_jsmodules",
     ":js_test_gen_html_modules_type_check_auto",
     ":js_test_gen_html_type_check_auto",
   ]
 }
 
-js_type_check("closure_jsmodules") {
+js_type_check("closure_compile_jsmodules") {
   uses_js_modules = true
   deps = [
     ":image_orientation.m",
@@ -143,7 +143,6 @@
 
 js_library("image_orientation.m") {
   sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/metadata/image_orientation.m.js" ]
-  deps = []
 
   extra_deps = [ ":modulize" ]
 }
@@ -209,7 +208,6 @@
 
 js_library("metadata_item.m") {
   sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/metadata/metadata_item.m.js" ]
-  deps = []
 
   extra_deps = [ ":modulize" ]
 }
@@ -263,7 +261,6 @@
 
 js_library("metadata_request.m") {
   sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/metadata/metadata_request.m.js" ]
-  deps = []
 
   extra_deps = [ ":modulize" ]
 }
diff --git a/ui/views/animation/ink_drop_host_view.h b/ui/views/animation/ink_drop_host_view.h
index 7a6dda9..ce6581c 100644
--- a/ui/views/animation/ink_drop_host_view.h
+++ b/ui/views/animation/ink_drop_host_view.h
@@ -253,8 +253,10 @@
 VIEW_BUILDER_PROPERTY(InkDropHostView::InkDropMode, InkDropMode)
 VIEW_BUILDER_PROPERTY(int, InkDropSmallCornerRadius)
 VIEW_BUILDER_PROPERTY(float, InkDropVisibleOpacity)
-END_VIEW_BUILDER(VIEWS_EXPORT, InkDropHostView)
+END_VIEW_BUILDER
 
 }  // namespace views
 
+DEFINE_VIEW_BUILDER(VIEWS_EXPORT, InkDropHostView)
+
 #endif  // UI_VIEWS_ANIMATION_INK_DROP_HOST_VIEW_H_
diff --git a/ui/views/bubble/bubble_dialog_delegate_view.cc b/ui/views/bubble/bubble_dialog_delegate_view.cc
index 7aeee28..57d9ad2c 100644
--- a/ui/views/bubble/bubble_dialog_delegate_view.cc
+++ b/ui/views/bubble/bubble_dialog_delegate_view.cc
@@ -436,8 +436,6 @@
     GetWidget()->GetRootView()->NotifyAccessibilityEvent(
         ax::mojom::Event::kAlert, true);
   }
-  if (enable_arrow_key_traversal_)
-    GetFocusManager()->set_arrow_key_traversal_enabled_for_widget(true);
 }
 
 View* BubbleDialogDelegateView::GetContentsView() {
@@ -738,16 +736,6 @@
                     : nullptr);
 }
 
-void BubbleDialogDelegateView::EnableArrowKeyTraversal() {
-  if (enable_arrow_key_traversal_)
-    return;
-  enable_arrow_key_traversal_ = true;
-
-  // Can't get the focus manager for a view that hasn't been added to a widget.
-  if (GetFocusManager())
-    GetFocusManager()->set_arrow_key_traversal_enabled_for_widget(true);
-}
-
 void BubbleDialogDelegate::OnBubbleWidgetVisibilityChanged(bool visible) {
   UpdateHighlightedButton(visible);
 
diff --git a/ui/views/bubble/bubble_dialog_delegate_view.h b/ui/views/bubble/bubble_dialog_delegate_view.h
index aae7f6d..3a7401eb 100644
--- a/ui/views/bubble/bubble_dialog_delegate_view.h
+++ b/ui/views/bubble/bubble_dialog_delegate_view.h
@@ -408,11 +408,6 @@
   // Perform view initialization on the contents for bubble sizing.
   void Init() override;
 
-  // Allows the arrow keys to tab between items. Equivalent to calling
-  // FocusManager::set_arrow_key_traversal_enabled_for_widget(), but works even
-  // if the widget for this bubble view has not yet been created.
-  void EnableArrowKeyTraversal();
-
  private:
   FRIEND_TEST_ALL_PREFIXES(BubbleDelegateTest, CreateDelegate);
   FRIEND_TEST_ALL_PREFIXES(BubbleDelegateTest, NonClientHitTest);
@@ -420,8 +415,6 @@
   // Update the bubble color from the NativeTheme unless it was explicitly set.
   void UpdateColorsFromTheme();
 
-  bool enable_arrow_key_traversal_ = false;
-
   DISALLOW_COPY_AND_ASSIGN(BubbleDialogDelegateView);
 };
 
diff --git a/ui/views/controls/button/button.h b/ui/views/controls/button/button.h
index e667ec8..1e55100d 100644
--- a/ui/views/controls/button/button.h
+++ b/ui/views/controls/button/button.h
@@ -391,8 +391,10 @@
 VIEW_BUILDER_PROPERTY(Button::ButtonState, State)
 VIEW_BUILDER_PROPERTY(base::string16, TooltipText)
 VIEW_BUILDER_PROPERTY(int, TriggerableEventFlags)
-END_VIEW_BUILDER(VIEWS_EXPORT, Button)
+END_VIEW_BUILDER
 
 }  // namespace views
 
+DEFINE_VIEW_BUILDER(VIEWS_EXPORT, Button)
+
 #endif  // UI_VIEWS_CONTROLS_BUTTON_BUTTON_H_
diff --git a/ui/views/controls/button/checkbox.h b/ui/views/controls/button/checkbox.h
index 4d7ea9b..86cc2f1 100644
--- a/ui/views/controls/button/checkbox.h
+++ b/ui/views/controls/button/checkbox.h
@@ -93,8 +93,10 @@
 BEGIN_VIEW_BUILDER(VIEWS_EXPORT, Checkbox, LabelButton)
 VIEW_BUILDER_PROPERTY(bool, Checked)
 VIEW_BUILDER_PROPERTY(bool, MultiLine)
-END_VIEW_BUILDER(VIEWS_EXPORT, Checkbox)
+END_VIEW_BUILDER
 
 }  // namespace views
 
+DEFINE_VIEW_BUILDER(VIEWS_EXPORT, Checkbox)
+
 #endif  // UI_VIEWS_CONTROLS_BUTTON_CHECKBOX_H_
diff --git a/ui/views/controls/button/image_button.h b/ui/views/controls/button/image_button.h
index 3250f77f..93271f0d 100644
--- a/ui/views/controls/button/image_button.h
+++ b/ui/views/controls/button/image_button.h
@@ -114,7 +114,7 @@
                       ImageHorizontalAlignment)
 VIEW_BUILDER_PROPERTY(ImageButton::VerticalAlignment, ImageVerticalAlignment)
 VIEW_BUILDER_PROPERTY(gfx::Size, MinimumImageSize)
-END_VIEW_BUILDER(VIEWS_EXPORT, ImageButton)
+END_VIEW_BUILDER
 
 ////////////////////////////////////////////////////////////////////////////////
 //
@@ -179,8 +179,11 @@
 VIEW_BUILDER_PROPERTY(bool, Toggled)
 VIEW_BUILDER_PROPERTY(base::string16, ToggledTooltipText)
 VIEW_BUILDER_PROPERTY(base::string16, ToggledAccessibleName)
-END_VIEW_BUILDER(VIEWS_EXPORT, ToggleImageButton)
+END_VIEW_BUILDER
 
 }  // namespace views
 
+DEFINE_VIEW_BUILDER(VIEWS_EXPORT, ImageButton)
+DEFINE_VIEW_BUILDER(VIEWS_EXPORT, ToggleImageButton)
+
 #endif  // UI_VIEWS_CONTROLS_BUTTON_IMAGE_BUTTON_H_
diff --git a/ui/views/controls/button/label_button.h b/ui/views/controls/button/label_button.h
index 94c4686..1e18c21 100644
--- a/ui/views/controls/button/label_button.h
+++ b/ui/views/controls/button/label_button.h
@@ -283,8 +283,10 @@
 VIEW_BUILDER_PROPERTY(bool, IsDefault)
 VIEW_BUILDER_PROPERTY(int, ImageLabelSpacing)
 VIEW_BUILDER_PROPERTY(bool, ImageCentered)
-END_VIEW_BUILDER(VIEWS_EXPORT, LabelButton)
+END_VIEW_BUILDER
 
 }  // namespace views
 
+DEFINE_VIEW_BUILDER(VIEWS_EXPORT, LabelButton)
+
 #endif  // UI_VIEWS_CONTROLS_BUTTON_LABEL_BUTTON_H_
diff --git a/ui/views/controls/button/md_text_button.h b/ui/views/controls/button/md_text_button.h
index f1be12a..995a00a 100644
--- a/ui/views/controls/button/md_text_button.h
+++ b/ui/views/controls/button/md_text_button.h
@@ -86,8 +86,10 @@
 VIEW_BUILDER_PROPERTY(base::Optional<SkColor>, BgColorOverride)
 VIEW_BUILDER_PROPERTY(float, CornerRadius)
 VIEW_BUILDER_PROPERTY(base::Optional<gfx::Insets>, CustomPadding)
-END_VIEW_BUILDER(VIEWS_EXPORT, MdTextButton)
+END_VIEW_BUILDER
 
 }  // namespace views
 
+DEFINE_VIEW_BUILDER(VIEWS_EXPORT, MdTextButton)
+
 #endif  // UI_VIEWS_CONTROLS_BUTTON_MD_TEXT_BUTTON_H_
diff --git a/ui/views/controls/combobox/combobox.h b/ui/views/controls/combobox/combobox.h
index 515f7e9..812810c 100644
--- a/ui/views/controls/combobox/combobox.h
+++ b/ui/views/controls/combobox/combobox.h
@@ -238,8 +238,10 @@
 VIEW_BUILDER_PROPERTY(bool, SizeToLargestLabel)
 VIEW_BUILDER_PROPERTY(base::string16, AccessibleName)
 VIEW_BUILDER_PROPERTY(base::string16, TooltipTextAndAccessibleName)
-END_VIEW_BUILDER(VIEWS_EXPORT, Combobox)
+END_VIEW_BUILDER
 
 }  // namespace views
 
+DEFINE_VIEW_BUILDER(VIEWS_EXPORT, Combobox)
+
 #endif  // UI_VIEWS_CONTROLS_COMBOBOX_COMBOBOX_H_
diff --git a/ui/views/controls/label.h b/ui/views/controls/label.h
index ac651de..5fcf375 100644
--- a/ui/views/controls/label.h
+++ b/ui/views/controls/label.h
@@ -474,8 +474,10 @@
 VIEW_BUILDER_PROPERTY(int, MaximumWidth)
 VIEW_BUILDER_PROPERTY(bool, CollapseWhenHidden)
 VIEW_BUILDER_PROPERTY(bool, Selectable)
-END_VIEW_BUILDER(VIEWS_EXPORT, Label)
+END_VIEW_BUILDER
 
 }  // namespace views
 
+DEFINE_VIEW_BUILDER(VIEWS_EXPORT, Label)
+
 #endif  // UI_VIEWS_CONTROLS_LABEL_H_
diff --git a/ui/views/controls/link.h b/ui/views/controls/link.h
index 82a422b..6db2cd4 100644
--- a/ui/views/controls/link.h
+++ b/ui/views/controls/link.h
@@ -106,8 +106,10 @@
 };
 
 BEGIN_VIEW_BUILDER(VIEWS_EXPORT, Link, Label)
-END_VIEW_BUILDER(VIEWS_EXPORT, Link)
+END_VIEW_BUILDER
 
 }  // namespace views
 
+DEFINE_VIEW_BUILDER(VIEWS_EXPORT, Link)
+
 #endif  // UI_VIEWS_CONTROLS_LINK_H_
diff --git a/ui/views/controls/scroll_view.h b/ui/views/controls/scroll_view.h
index 690b2a4..ec20b21 100644
--- a/ui/views/controls/scroll_view.h
+++ b/ui/views/controls/scroll_view.h
@@ -338,7 +338,7 @@
 VIEW_BUILDER_VIEW_PROPERTY(ScrollBar, HorizontalScrollBar)
 VIEW_BUILDER_VIEW_PROPERTY(ScrollBar, VerticalScrollBar)
 VIEW_BUILDER_PROPERTY(bool, HasFocusIndicator)
-END_VIEW_BUILDER(VIEWS_EXPORT, ScrollView)
+END_VIEW_BUILDER
 
 // VariableRowHeightScrollHelper is intended for views that contain rows of
 // varying height. To use a VariableRowHeightScrollHelper create one supplying
@@ -414,4 +414,6 @@
 
 }  // namespace views
 
+DEFINE_VIEW_BUILDER(VIEWS_EXPORT, ScrollView)
+
 #endif  // UI_VIEWS_CONTROLS_SCROLL_VIEW_H_
diff --git a/ui/views/controls/separator.h b/ui/views/controls/separator.h
index eeb92d1..6e1a20c 100644
--- a/ui/views/controls/separator.h
+++ b/ui/views/controls/separator.h
@@ -46,8 +46,10 @@
 BEGIN_VIEW_BUILDER(VIEWS_EXPORT, Separator, View)
 VIEW_BUILDER_PROPERTY(SkColor, Color)
 VIEW_BUILDER_PROPERTY(int, PreferredHeight)
-END_VIEW_BUILDER(VIEWS_EXPORT, Separator)
+END_VIEW_BUILDER
 
 }  // namespace views
 
+DEFINE_VIEW_BUILDER(VIEWS_EXPORT, Separator)
+
 #endif  // UI_VIEWS_CONTROLS_SEPARATOR_H_
diff --git a/ui/views/controls/textfield/textfield.h b/ui/views/controls/textfield/textfield.h
index c13f86f3..3e3b731 100644
--- a/ui/views/controls/textfield/textfield.h
+++ b/ui/views/controls/textfield/textfield.h
@@ -762,8 +762,10 @@
 VIEW_BUILDER_PROPERTY(SkColor, TextColor)
 VIEW_BUILDER_PROPERTY(int, TextInputFlags)
 VIEW_BUILDER_PROPERTY(ui::TextInputType, TextInputType)
-END_VIEW_BUILDER(VIEWS_EXPORT, Textfield)
+END_VIEW_BUILDER
 
 }  // namespace views
 
+DEFINE_VIEW_BUILDER(VIEWS_EXPORT, Textfield)
+
 #endif  // UI_VIEWS_CONTROLS_TEXTFIELD_TEXTFIELD_H_
diff --git a/ui/views/focus/focus_manager.h b/ui/views/focus/focus_manager.h
index 31c1390..134a701 100644
--- a/ui/views/focus/focus_manager.h
+++ b/ui/views/focus/focus_manager.h
@@ -296,6 +296,9 @@
   void set_arrow_key_traversal_enabled_for_widget(bool enabled) {
     arrow_key_traversal_enabled_for_widget_ = enabled;
   }
+  bool arrow_key_traversal_enabled_for_widget() const {
+    return arrow_key_traversal_enabled_for_widget_;
+  }
 
   // Returns the next focusable view. Traversal starts at |starting_view|. If
   // |starting_view| is null, |starting_widget| is consulted to determine which
diff --git a/ui/views/layout/box_layout_view.h b/ui/views/layout/box_layout_view.h
index 111f322..9b0dcad 100644
--- a/ui/views/layout/box_layout_view.h
+++ b/ui/views/layout/box_layout_view.h
@@ -74,8 +74,10 @@
 VIEW_BUILDER_PROPERTY(int, BetweenChildSpacing)
 VIEW_BUILDER_PROPERTY(int, CollapseMarginsSpacing)
 VIEW_BUILDER_PROPERTY(int, DefaultFlex)
-END_VIEW_BUILDER(VIEWS_EXPORT, BoxLayoutView)
+END_VIEW_BUILDER
 
 }  // namespace views
 
+DEFINE_VIEW_BUILDER(VIEWS_EXPORT, BoxLayoutView)
+
 #endif  // UI_VIEWS_LAYOUT_BOX_LAYOUT_VIEW_H_
diff --git a/ui/views/layout/flex_layout_view.h b/ui/views/layout/flex_layout_view.h
index cfd504d0..1353244 100644
--- a/ui/views/layout/flex_layout_view.h
+++ b/ui/views/layout/flex_layout_view.h
@@ -88,8 +88,10 @@
 VIEW_BUILDER_PROPERTY(bool, IncludeHostInsetsInLayout)
 VIEW_BUILDER_PROPERTY(bool, IgnoreDefaultMainAxisMargins)
 VIEW_BUILDER_PROPERTY(FlexAllocationOrder, FlexAllocationOrder)
-END_VIEW_BUILDER(VIEWS_EXPORT, FlexLayoutView)
+END_VIEW_BUILDER
 
 }  // namespace views
 
+DEFINE_VIEW_BUILDER(VIEWS_EXPORT, FlexLayoutView)
+
 #endif  // UI_VIEWS_LAYOUT_FLEX_LAYOUT_VIEW_H_
diff --git a/ui/views/metadata/view_factory.h b/ui/views/metadata/view_factory.h
index e335e10..a9d3563 100644
--- a/ui/views/metadata/view_factory.h
+++ b/ui/views/metadata/view_factory.h
@@ -102,6 +102,8 @@
   ViewClass_* root_view_ = nullptr;
 };
 
+}  // namespace views
+
 // Example of builder class generated by the following macros.
 //
 // template <typename Builder, typename ViewClass>
@@ -151,6 +153,9 @@
 // class VIEWS_EXPORT LabelButtonBuilder
 //     : public LabelButtonBuilderT<LabelButtonBuilder, LabelButton> {};
 
+// BEGIN_VIEW_BUILDER, END_VIEW_BUILDER and VIEW_BUILDER_XXXX macros should
+// be placed into the same namespace as the 'view_class' parameter.
+
 #define BEGIN_VIEW_BUILDER(export, view_class, ancestor)                    \
   template <typename BuilderT>                                              \
   class export view_class##BuilderT : public ancestor##BuilderT<BuilderT> { \
@@ -220,23 +225,27 @@
 // semi-colon on a separate line.
 // clang-format off
 
-#define END_VIEW_BUILDER(export, view_class)                             \
-  };                                                                     \
-                                                                         \
-  template <>                                                            \
-  class export views::Builder<view_class>                                \
-      : public view_class##BuilderT<views::Builder<view_class>> {        \
-   public:                                                               \
-    Builder<view_class>() = default;                                     \
-    explicit Builder<view_class>(ViewClass_* root_view)                  \
-        : view_class##BuilderT<views::Builder<view_class>>(root_view) {} \
-    Builder<view_class>(Builder&&) = default;                            \
-    Builder<view_class>& operator=(Builder<view_class>&&) = default;     \
-    ~Builder<view_class>() = default;                                    \
-  };
+#define END_VIEW_BUILDER };
+
+// Unlike the above macros, DEFINE_VIEW_BUILDER must be placed in the global
+// namespace. Unless 'view_class' is already in the 'views' namespace, it should
+// be fully qualified with the namespace in which it lives.
+
+#define DEFINE_VIEW_BUILDER(export, view_class)                      \
+namespace views {                                                    \
+  template <>                                                        \
+  class export Builder<view_class>                                   \
+      : public view_class##BuilderT<Builder<view_class>> {           \
+   public:                                                           \
+    Builder<view_class>() = default;                                 \
+    explicit Builder<view_class>(ViewClass_* root_view)              \
+        : view_class##BuilderT<Builder<view_class>>(root_view) {}    \
+    Builder<view_class>(Builder&&) = default;                        \
+    Builder<view_class>& operator=(Builder<view_class>&&) = default; \
+    ~Builder<view_class>() = default;                                \
+  };                                                                 \
+}  // namespace views
 
 // clang-format on
 
-}  // namespace views
-
 #endif  // UI_VIEWS_METADATA_VIEW_FACTORY_H_
diff --git a/ui/views/view.h b/ui/views/view.h
index d30a5ef..6fb298b1 100644
--- a/ui/views/view.h
+++ b/ui/views/view.h
@@ -2085,8 +2085,10 @@
 VIEW_BUILDER_PROPERTY(gfx::Transform, Transform)
 VIEW_BUILDER_PROPERTY(bool, Visible)
 VIEW_BUILDER_PROPERTY(bool, CanProcessEventsWithinSubtree)
-END_VIEW_BUILDER(VIEWS_EXPORT, View)
+END_VIEW_BUILDER
 
 }  // namespace views
 
+DEFINE_VIEW_BUILDER(VIEWS_EXPORT, View)
+
 #endif  // UI_VIEWS_VIEW_H_
diff --git a/ui/views/widget/widget.cc b/ui/views/widget/widget.cc
index c27777b..0cca9d4 100644
--- a/ui/views/widget/widget.cc
+++ b/ui/views/widget/widget.cc
@@ -394,6 +394,11 @@
     SetInitialBoundsForFramelessWindow(bounds);
   }
 
+  if (widget_delegate_->enable_arrow_key_traversal()) {
+    DCHECK(is_top_level());
+    focus_manager_->set_arrow_key_traversal_enabled_for_widget(true);
+  }
+
   observer_manager_.Add(GetNativeTheme());
   native_widget_initialized_ = true;
   native_widget_->OnWidgetInitDone();
diff --git a/ui/views/widget/widget_delegate.cc b/ui/views/widget/widget_delegate.cc
index 7878662..7b503b6 100644
--- a/ui/views/widget/widget_delegate.cc
+++ b/ui/views/widget/widget_delegate.cc
@@ -22,13 +22,37 @@
 
 namespace views {
 
+namespace {
+
+std::unique_ptr<ClientView> CreateDefaultClientView(WidgetDelegate* delegate,
+                                                    Widget* widget) {
+  return std::make_unique<ClientView>(
+      widget, delegate->TransferOwnershipOfContentsView());
+}
+
+std::unique_ptr<NonClientFrameView> CreateDefaultNonClientFrameView(
+    Widget* widget) {
+  return nullptr;
+}
+
+std::unique_ptr<View> CreateDefaultOverlayView() {
+  return nullptr;
+}
+
+}  // namespace
+
 ////////////////////////////////////////////////////////////////////////////////
 // WidgetDelegate:
 
 WidgetDelegate::Params::Params() = default;
 WidgetDelegate::Params::~Params() = default;
 
-WidgetDelegate::WidgetDelegate() = default;
+WidgetDelegate::WidgetDelegate()
+    : client_view_factory_(
+          base::BindOnce(&CreateDefaultClientView, base::Unretained(this))),
+      non_client_frame_view_factory_(
+          base::BindOnce(&CreateDefaultNonClientFrameView)),
+      overlay_view_factory_(base::BindOnce(&CreateDefaultOverlayView)) {}
 WidgetDelegate::~WidgetDelegate() {
   CHECK(can_delete_this_) << "A WidgetDelegate must outlive its Widget";
 }
@@ -216,16 +240,19 @@
 }
 
 ClientView* WidgetDelegate::CreateClientView(Widget* widget) {
-  return new ClientView(widget, TransferOwnershipOfContentsView());
+  DCHECK(client_view_factory_);
+  return std::move(client_view_factory_).Run(widget).release();
 }
 
 std::unique_ptr<NonClientFrameView> WidgetDelegate::CreateNonClientFrameView(
     Widget* widget) {
-  return nullptr;
+  DCHECK(non_client_frame_view_factory_);
+  return std::move(non_client_frame_view_factory_).Run(widget);
 }
 
 View* WidgetDelegate::CreateOverlayView() {
-  return nullptr;
+  DCHECK(overlay_view_factory_);
+  return std::move(overlay_view_factory_).Run().release();
 }
 
 bool WidgetDelegate::WidgetHasHitTestMask() const {
@@ -276,6 +303,11 @@
   params_.focus_traverses_out = focus_traverses_out;
 }
 
+void WidgetDelegate::SetEnableArrowKeyTraversal(
+    bool enable_arrow_key_traversal) {
+  params_.enable_arrow_key_traversal = enable_arrow_key_traversal;
+}
+
 void WidgetDelegate::SetIcon(const gfx::ImageSkia& icon) {
   params_.icon = icon;
   if (GetWidget())
@@ -344,6 +376,22 @@
   delete_delegate_callbacks_.emplace_back(std::move(callback));
 }
 
+void WidgetDelegate::SetClientViewFactory(ClientViewFactory factory) {
+  DCHECK(!GetWidget());
+  client_view_factory_ = std::move(factory);
+}
+
+void WidgetDelegate::SetNonClientFrameViewFactory(
+    NonClientFrameViewFactory factory) {
+  DCHECK(!GetWidget());
+  non_client_frame_view_factory_ = std::move(factory);
+}
+
+void WidgetDelegate::SetOverlayViewFactory(OverlayViewFactory factory) {
+  DCHECK(!GetWidget());
+  overlay_view_factory_ = std::move(factory);
+}
+
 void WidgetDelegate::SetContentsViewImpl(View* contents) {
   // Note: DCHECKing the ownership of contents is done in the public setters,
   // which are inlined in the header.
diff --git a/ui/views/widget/widget_delegate.h b/ui/views/widget/widget_delegate.h
index f30479f..8d0453a 100644
--- a/ui/views/widget/widget_delegate.h
+++ b/ui/views/widget/widget_delegate.h
@@ -30,6 +30,12 @@
 // Handles events on Widgets in context-specific ways.
 class VIEWS_EXPORT WidgetDelegate {
  public:
+  using ClientViewFactory =
+      base::OnceCallback<std::unique_ptr<ClientView>(Widget*)>;
+  using NonClientFrameViewFactory =
+      base::OnceCallback<std::unique_ptr<NonClientFrameView>(Widget*)>;
+  using OverlayViewFactory = base::OnceCallback<std::unique_ptr<View>()>;
+
   struct Params {
     Params();
     ~Params();
@@ -61,6 +67,14 @@
     // Widget; if false, focus cycles within this Widget.
     bool focus_traverses_out = false;
 
+    // Controls whether the user can traverse widget views using up/down and
+    // left/right arrow keys in addition to TAB.
+    // TODO(dfried): Can only be set on top-level widgets. If we need to enable
+    // this for child widgets, we'll need to move away from checking
+    // FocusManager::arrow_key_traversal_enabled_for_widget_ on arrow key press
+    // to checking the current view's widget's enable_arrow_key_traversal().
+    bool enable_arrow_key_traversal = false;
+
     // The widget's icon, if any.
     gfx::ImageSkia icon;
 
@@ -306,6 +320,7 @@
   void SetCanMinimize(bool can_minimize);
   void SetCanResize(bool can_resize);
   void SetFocusTraversesOut(bool focus_traverses_out);
+  void SetEnableArrowKeyTraversal(bool enable_arrow_key_traversal);
   void SetIcon(const gfx::ImageSkia& icon);
   void SetInitiallyFocusedView(View* initially_focused_view);
   void SetModalType(ui::ModalType modal_type);
@@ -342,6 +357,10 @@
   void RegisterWindowClosingCallback(base::OnceClosure callback);
   void RegisterDeleteDelegateCallback(base::OnceClosure callback);
 
+  void SetClientViewFactory(ClientViewFactory factory);
+  void SetNonClientFrameViewFactory(NonClientFrameViewFactory factory);
+  void SetOverlayViewFactory(OverlayViewFactory factory);
+
   // Called to notify the WidgetDelegate of changes to the state of its Widget.
   // It is not usually necessary to call these from client code.
   void WidgetInitializing(Widget* widget);
@@ -353,6 +372,9 @@
   bool ShouldCenterWindowTitleText() const;
 
   bool focus_traverses_out() const { return params_.focus_traverses_out; }
+  bool enable_arrow_key_traversal() const {
+    return params_.enable_arrow_key_traversal;
+  }
   bool owned_by_widget() const { return params_.owned_by_widget; }
 
   void set_internal_name(std::string name) { params_.internal_name = name; }
@@ -382,6 +404,10 @@
   std::vector<base::OnceClosure> window_closing_callbacks_;
   std::vector<base::OnceClosure> delete_delegate_callbacks_;
 
+  ClientViewFactory client_view_factory_;
+  NonClientFrameViewFactory non_client_frame_view_factory_;
+  OverlayViewFactory overlay_view_factory_;
+
   DISALLOW_COPY_AND_ASSIGN(WidgetDelegate);
 };
 
diff --git a/ui/views/widget/widget_delegate_unittest.cc b/ui/views/widget/widget_delegate_unittest.cc
index 2127462..6129e44 100644
--- a/ui/views/widget/widget_delegate_unittest.cc
+++ b/ui/views/widget/widget_delegate_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "base/test/bind_test_util.h"
 #include "ui/views/test/views_test_base.h"
 #include "ui/views/view.h"
 #include "ui/views/view_tracker.h"
@@ -66,5 +67,51 @@
   EXPECT_FALSE(tracker.view());
 }
 
+TEST_F(WidgetDelegateTest, ClientViewFactoryCanReplaceClientView) {
+  ViewTracker tracker;
+
+  auto delegate = std::make_unique<WidgetDelegate>();
+  delegate->SetClientViewFactory(
+      base::BindLambdaForTesting([&tracker](Widget* widget) {
+        auto view = std::make_unique<ClientView>(widget, nullptr);
+        tracker.SetView(view.get());
+        return view;
+      }));
+
+  auto client =
+      base::WrapUnique<ClientView>(delegate->CreateClientView(nullptr));
+  EXPECT_EQ(tracker.view(), client.get());
+}
+
+TEST_F(WidgetDelegateTest,
+       NonClientFrameViewFactoryCanReplaceNonClientFrameView) {
+  ViewTracker tracker;
+
+  auto delegate = std::make_unique<WidgetDelegate>();
+  delegate->SetNonClientFrameViewFactory(
+      base::BindLambdaForTesting([&tracker](Widget* widget) {
+        auto view = std::make_unique<NonClientFrameView>();
+        tracker.SetView(view.get());
+        return view;
+      }));
+
+  auto nonclient = delegate->CreateNonClientFrameView(nullptr);
+  EXPECT_EQ(tracker.view(), nonclient.get());
+}
+
+TEST_F(WidgetDelegateTest, OverlayViewFactoryCanReplaceOverlayView) {
+  ViewTracker tracker;
+
+  auto delegate = std::make_unique<WidgetDelegate>();
+  delegate->SetOverlayViewFactory(base::BindLambdaForTesting([&tracker]() {
+    auto view = std::make_unique<View>();
+    tracker.SetView(view.get());
+    return view;
+  }));
+
+  auto overlay = base::WrapUnique<View>(delegate->CreateOverlayView());
+  EXPECT_EQ(tracker.view(), overlay.get());
+}
+
 }  // namespace
 }  // namespace views
diff --git a/ui/views/widget/widget_unittest.cc b/ui/views/widget/widget_unittest.cc
index 137d086..9d8a1745 100644
--- a/ui/views/widget/widget_unittest.cc
+++ b/ui/views/widget/widget_unittest.cc
@@ -256,6 +256,32 @@
   // children should be automatically destroyed with |toplevel|.
 }
 
+TEST_F(WidgetWithCustomParamsTest,
+       EnableArrowKeyTraversalFalsePropagatesFromDelegate) {
+  WidgetDelegate delegate;
+  SetInitFunction(base::BindLambdaForTesting(
+      [&](Widget::InitParams* params) { params->delegate = &delegate; }));
+
+  // Setting should default to off.
+  DCHECK(!delegate.enable_arrow_key_traversal());
+  std::unique_ptr<Widget> widget = CreateTestWidget();
+  EXPECT_FALSE(
+      widget->GetFocusManager()->arrow_key_traversal_enabled_for_widget());
+}
+
+TEST_F(WidgetWithCustomParamsTest,
+       EnableArrowKeyTraversalTruePropagatesFromDelegate) {
+  WidgetDelegate delegate;
+  SetInitFunction(base::BindLambdaForTesting(
+      [&](Widget::InitParams* params) { params->delegate = &delegate; }));
+
+  // Now set to true and create a new widget.
+  delegate.SetEnableArrowKeyTraversal(true);
+  std::unique_ptr<Widget> widget = CreateTestWidget();
+  EXPECT_TRUE(
+      widget->GetFocusManager()->arrow_key_traversal_enabled_for_widget());
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Widget::GetTopLevelWidget tests.
 
diff --git a/ui/views/window/non_client_view.cc b/ui/views/window/non_client_view.cc
index 06c6cc4..11183922 100644
--- a/ui/views/window/non_client_view.cc
+++ b/ui/views/window/non_client_view.cc
@@ -91,6 +91,15 @@
   return can_resize ? component : HTBORDER;
 }
 
+gfx::Rect NonClientFrameView::GetBoundsForClientView() const {
+  return gfx::Rect();
+}
+
+gfx::Rect NonClientFrameView::GetWindowBoundsForClientBounds(
+    const gfx::Rect& client_bounds) const {
+  return client_bounds;
+}
+
 bool NonClientFrameView::GetClientMask(const gfx::Size& size,
                                        SkPath* mask) const {
   return false;
@@ -107,6 +116,10 @@
 }
 #endif
 
+int NonClientFrameView::NonClientHitTest(const gfx::Point& point) {
+  return HTNOWHERE;
+}
+
 void NonClientFrameView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
   node_data->role = ax::mojom::Role::kClient;
 }
diff --git a/ui/views/window/non_client_view.h b/ui/views/window/non_client_view.h
index 6ddc334..b8df6d4 100644
--- a/ui/views/window/non_client_view.h
+++ b/ui/views/window/non_client_view.h
@@ -39,6 +39,7 @@
     kClientEdgeThickness = 1,
   };
 
+  NonClientFrameView();
   ~NonClientFrameView() override;
 
   // Used to determine if the frame should be painted as active. Keyed off the
@@ -59,10 +60,10 @@
 
   // Returns the bounds (in this View's parent's coordinates) that the client
   // view should be laid out within.
-  virtual gfx::Rect GetBoundsForClientView() const = 0;
+  virtual gfx::Rect GetBoundsForClientView() const;
 
   virtual gfx::Rect GetWindowBoundsForClientBounds(
-      const gfx::Rect& client_bounds) const = 0;
+      const gfx::Rect& client_bounds) const;
 
   // Gets the clip mask (in this View's parent's coordinates) that should be
   // applied to the client view. Returns false if no special clip should be
@@ -80,26 +81,24 @@
   // hittests for regions that are partially obscured by the ClientView, e.g.
   // HTSYSMENU.
   // Return value is one of the windows HT constants (see ui/base/hit_test.h).
-  virtual int NonClientHitTest(const gfx::Point& point) = 0;
+  virtual int NonClientHitTest(const gfx::Point& point);
 
   // Used to make the hosting widget shaped (non-rectangular). For a
   // rectangular window do nothing. For a shaped window update |window_mask|
   // accordingly. |size| is the size of the widget.
-  virtual void GetWindowMask(const gfx::Size& size, SkPath* window_mask) = 0;
-  virtual void ResetWindowControls() = 0;
-  virtual void UpdateWindowIcon() = 0;
-  virtual void UpdateWindowTitle() = 0;
+  virtual void GetWindowMask(const gfx::Size& size, SkPath* window_mask) {}
+  virtual void ResetWindowControls() {}
+  virtual void UpdateWindowIcon() {}
+  virtual void UpdateWindowTitle() {}
 
   // Whether the widget can be resized or maximized has changed.
-  virtual void SizeConstraintsChanged() = 0;
+  virtual void SizeConstraintsChanged() {}
 
   // View:
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
   void OnThemeChanged() override;
 
  protected:
-  NonClientFrameView();
-
   // ViewTargeterDelegate:
   bool DoesIntersectRect(const View* target,
                          const gfx::Rect& rect) const override;
diff --git a/ui/webui/resources/cr_elements/cr_toast/BUILD.gn b/ui/webui/resources/cr_elements/cr_toast/BUILD.gn
index 527ae4d..a29159d1 100644
--- a/ui/webui/resources/cr_elements/cr_toast/BUILD.gn
+++ b/ui/webui/resources/cr_elements/cr_toast/BUILD.gn
@@ -18,7 +18,6 @@
 js_library("cr_toast_manager") {
   deps = [
     ":cr_toast",
-    "//third_party/polymer/v1_0/components-chromium/iron-a11y-announcer:iron-a11y-announcer-extracted",
     "//ui/webui/resources/js:assert",
     "//ui/webui/resources/js:cr",
   ]
@@ -57,7 +56,6 @@
   sources =
       [ "$root_gen_dir/ui/webui/resources/cr_elements/cr_toast/cr_toast.m.js" ]
   deps = [
-    "//third_party/polymer/v3_0/components-chromium/iron-a11y-announcer:iron-a11y-announcer",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
   extra_deps = [ ":cr_toast_module" ]
@@ -67,7 +65,6 @@
   sources = [ "$root_gen_dir/ui/webui/resources/cr_elements/cr_toast/cr_toast_manager.m.js" ]
   deps = [
     ":cr_toast.m",
-    "//third_party/polymer/v3_0/components-chromium/iron-a11y-announcer:iron-a11y-announcer",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:assert.m",
   ]
diff --git a/ui/webui/resources/cr_elements/cr_toast/cr_toast.js b/ui/webui/resources/cr_elements/cr_toast/cr_toast.js
index f6a5f1b..97b4d55 100644
--- a/ui/webui/resources/cr_elements/cr_toast/cr_toast.js
+++ b/ui/webui/resources/cr_elements/cr_toast/cr_toast.js
@@ -14,6 +14,7 @@
     },
 
     open: {
+      readOnly: true,
       type: Boolean,
       value: false,
       reflectToAttribute: true,
@@ -37,7 +38,7 @@
 
     if (this.open && this.duration !== 0) {
       this.hideTimeoutId_ = window.setTimeout(() => {
-        this.open = false;
+        this._setOpen(false);
       }, this.duration);
     }
   },
@@ -58,7 +59,7 @@
     // the same as a previous toast.
     this.removeAttribute('role');
 
-    this.open = true;
+    this._setOpen(true);
     this.setAttribute('role', 'alert');
 
     if (shouldResetAutohide) {
@@ -68,6 +69,6 @@
 
   /** Hides the toast. */
   hide() {
-    this.open = false;
+    this._setOpen(false);
   },
 });
diff --git a/ui/webui/resources/cr_elements/cr_toast/cr_toast_manager.html b/ui/webui/resources/cr_elements/cr_toast/cr_toast_manager.html
index 07ecf9f..5c0ce95 100644
--- a/ui/webui/resources/cr_elements/cr_toast/cr_toast_manager.html
+++ b/ui/webui/resources/cr_elements/cr_toast/cr_toast_manager.html
@@ -1,6 +1,5 @@
 <link rel="import" href="../../html/polymer.html">
 
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-announcer/iron-a11y-announcer.html">
 <link rel="import" href="../../html/assert.html">
 <link rel="import" href="../../html/cr.html">
 <link rel="import" href="../../html/event_tracker.html">
diff --git a/ui/webui/resources/cr_elements/cr_toast/cr_toast_manager.js b/ui/webui/resources/cr_elements/cr_toast/cr_toast_manager.js
index b4df997..53009cd 100644
--- a/ui/webui/resources/cr_elements/cr_toast/cr_toast_manager.js
+++ b/ui/webui/resources/cr_elements/cr_toast/cr_toast_manager.js
@@ -93,10 +93,6 @@
      */
     showInternal_(hideSlotted) {
       this.$.slotted.hidden = hideSlotted;
-      Polymer.IronA11yAnnouncer.requestAvailability();
-      this.fire('iron-announce', {
-        text: this.$.content.textContent,
-      });
       this.$.toast.show();
     },
 
diff --git a/weblayer/shell/android/shell_apk/src/org/chromium/weblayer/shell/WebLayerShellActivity.java b/weblayer/shell/android/shell_apk/src/org/chromium/weblayer/shell/WebLayerShellActivity.java
index 7c5a26e..6b0d0c1 100644
--- a/weblayer/shell/android/shell_apk/src/org/chromium/weblayer/shell/WebLayerShellActivity.java
+++ b/weblayer/shell/android/shell_apk/src/org/chromium/weblayer/shell/WebLayerShellActivity.java
@@ -60,7 +60,6 @@
 import org.chromium.weblayer.NewTabCallback;
 import org.chromium.weblayer.NewTabType;
 import org.chromium.weblayer.Profile;
-import org.chromium.weblayer.SettingType;
 import org.chromium.weblayer.SiteSettingsActivity;
 import org.chromium.weblayer.Tab;
 import org.chromium.weblayer.TabCallback;
@@ -424,7 +423,6 @@
         fragment.setRetainInstance(true);
         mBrowser = Browser.fromFragment(fragment);
         mProfile = mBrowser.getProfile();
-        mProfile.setBooleanSetting(SettingType.UKM_ENABLED, true);
         mProfile.setUserIdentityCallback(new UserIdentityCallback() {
             @Override
             public String getEmail() {