diff --git a/.gn b/.gn
index 3533e142..9dc1fd1 100644
--- a/.gn
+++ b/.gn
@@ -67,7 +67,6 @@
   "//headless:*",  # 107 errors
   "//ppapi/proxy:ipc_sources",  # 13 errors
   "//remoting/host/security_key:*",  # 10 errors
-  "//sandbox/win:*",  # 7 errors
 
   "//third_party/icu/*",
   "//third_party/libwebp:*",  # 7 errors, https://crbug.com/800762
diff --git a/DEPS b/DEPS
index ab868a8..02ad4b5e 100644
--- a/DEPS
+++ b/DEPS
@@ -228,11 +228,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '53c06de5478f1754b90a16df3f4bccd97423cc68',
+  'skia_revision': 'f884b1254d2029302074a51cb1f32502daac90d3',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': 'ae254f30808154c3d27df1256a045ecf0e8cd34f',
+  'v8_revision': '5d7e05756b3bc736c97300f8346a45a51ba4b051',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -240,7 +240,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': '06b19ed81e8a6aaf19d95a7487ab9ac418da0d07',
+  'angle_revision': '23a50a7cade6ef51441ebf7afb13b4493b09e012',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -279,7 +279,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
-  'freetype_revision': '47cf8ebf4a78ed42da455a98d77a92ce6a180d78',
+  'freetype_revision': 'fed5521016227bf8cc4475f66450a9963568d162',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
@@ -299,7 +299,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': 'cf2488ab010520b862072e29362356e10a94a029',
+  'catapult_revision': '7d8f51997c84ced53eb1c595f5dcb207f8699299',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -307,7 +307,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': 'c4170ac05312211492911dd08a6b140120d4cde6',
+  'devtools_frontend_revision': '6f2632929afd7f74a2f1bf6fd83bb1d8818c3234',
   # 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.
@@ -375,7 +375,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling nearby
   # and whatever else without interference from each other.
-  'nearby_revision': '7c1f423014003e2bf4ed2feb7520b3355c834fed',
+  'nearby_revision': '7a02eaad37a4e0d3948750c987e82fcb94225547',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling securemessage
   # and whatever else without interference from each other.
@@ -600,7 +600,7 @@
   },
 
   'src/ios/third_party/gtx/src': {
-      'url': Var('chromium_git') + '/external/github.com/google/GTXiLib.git' + '@' + '3e09baa61b2c13fe98029d53b1783f4ca9edaabf',
+      'url': Var('chromium_git') + '/external/github.com/google/GTXiLib.git' + '@' + '8245048a7023a37055d8d6c7a421bce3fcf79e6b',
       'condition': 'checkout_ios',
   },
 
@@ -767,7 +767,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'gvnazuGtVHlQbjbAcs_bl3bMU68xAIWAIldSi82tON4C',
+          'version': 'vI-WBSoi_71Eq3PznEPhcmsxoxpzRHQQZd5hxQRtesIC',
       },
     ],
     'condition': 'checkout_android',
@@ -932,7 +932,7 @@
   },
 
   'src/third_party/breakpad/breakpad':
-    Var('chromium_git') + '/breakpad/breakpad.git' + '@' + '32096a2dc8f8a7d5aac4097e34912bb7e06a5277',
+    Var('chromium_git') + '/breakpad/breakpad.git' + '@' + 'b95c4868b10f69e642666742233aede1eb653012',
 
   'src/third_party/byte_buddy': {
       'packages': [
@@ -1003,7 +1003,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'e989bf92dbd9b03e0a2a0d38bdb3614245dfa562',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '7ecdc98e22588930c2b6e01fc1571d257dbb27e4',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1682,7 +1682,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@e730ae9c22953870f7bd782c8cdfb61915ce5408',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@53244b121d5f336a0b24e5a8e76e46dc1f6d2f2e',
     'condition': 'checkout_src_internal',
   },
 
@@ -1701,7 +1701,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/help_app/app',
-        'version': 'qWR-9In4e-M9rErPDJaHhWDNrHZ7og0qpLGgRJnxaYkC',
+        'version': 'JlJpuj6Ro65BFkAoQOPvIsrhMJEugNDa5Ysl8NvdHZMC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java b/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java
index 9ea6f88c..69322b2 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java
@@ -501,7 +501,8 @@
         EmbeddedComponentLoader loader =
                 new EmbeddedComponentLoader(Arrays.asList(componentPolicies));
         final Intent intent = new Intent();
-        intent.setClassName(getWebViewPackageName(), ServiceNames.COMPONENTS_PROVIDER_SERVICE);
+        intent.setClassName(
+                getWebViewPackageName(), EmbeddedComponentLoader.AW_COMPONENTS_PROVIDER_SERVICE);
         loader.connect(intent);
     }
 
diff --git a/android_webview/java/src/org/chromium/android_webview/AwWebContentsObserver.java b/android_webview/java/src/org/chromium/android_webview/AwWebContentsObserver.java
index 4a54e291..62b13ed 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwWebContentsObserver.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwWebContentsObserver.java
@@ -22,6 +22,7 @@
 import org.chromium.url.GURL;
 
 import java.lang.ref.WeakReference;
+import java.util.Locale;
 
 /**
  * Routes notifications from WebContents to AwContentsClient and other listeners.
@@ -210,7 +211,8 @@
         if (navigation.isInPrimaryMainFrame() && !navigation.isSameDocument()) {
             if (navigation.hasCommitted()) {
                 mStartTimeSpentMillis = SystemClock.uptimeMillis();
-                mCurrentSchemeForTimeSpent = navigation.getUrl().getScheme();
+                mCurrentSchemeForTimeSpent =
+                        navigation.getUrl().getScheme().toLowerCase(Locale.ROOT);
             } else {
                 mStartTimeSpentMillis = -1;
                 mCurrentSchemeForTimeSpent = null;
diff --git a/android_webview/java/src/org/chromium/android_webview/common/services/ServiceNames.java b/android_webview/java/src/org/chromium/android_webview/common/services/ServiceNames.java
index 2910af3..28f7ce4 100644
--- a/android_webview/java/src/org/chromium/android_webview/common/services/ServiceNames.java
+++ b/android_webview/java/src/org/chromium/android_webview/common/services/ServiceNames.java
@@ -23,8 +23,6 @@
             "org.chromium.android_webview.services.MetricsBridgeService";
     public static final String VARIATIONS_SEED_SERVER =
             "org.chromium.android_webview.services.VariationsSeedServer";
-    public static final String COMPONENTS_PROVIDER_SERVICE =
-            "org.chromium.android_webview.services.ComponentsProviderService";
     public static final String AW_COMPONENT_UPDATE_SERVICE =
             "org.chromium.android_webview.nonembedded.AwComponentUpdateService";
 
diff --git a/android_webview/junit/src/org/chromium/android_webview/robolectric/common/services/DEPS b/android_webview/junit/src/org/chromium/android_webview/robolectric/common/services/DEPS
new file mode 100644
index 0000000..f236ef95
--- /dev/null
+++ b/android_webview/junit/src/org/chromium/android_webview/robolectric/common/services/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+components/component_updater/android",
+]
\ No newline at end of file
diff --git a/android_webview/junit/src/org/chromium/android_webview/robolectric/common/services/ServiceNamesTest.java b/android_webview/junit/src/org/chromium/android_webview/robolectric/common/services/ServiceNamesTest.java
index 6979dd8d..324272e 100644
--- a/android_webview/junit/src/org/chromium/android_webview/robolectric/common/services/ServiceNamesTest.java
+++ b/android_webview/junit/src/org/chromium/android_webview/robolectric/common/services/ServiceNamesTest.java
@@ -20,6 +20,7 @@
 import org.chromium.android_webview.services.DeveloperUiService;
 import org.chromium.android_webview.services.MetricsBridgeService;
 import org.chromium.android_webview.services.VariationsSeedServer;
+import org.chromium.components.component_updater.EmbeddedComponentLoader;
 import org.chromium.testing.local.LocalRobolectricTestRunner;
 
 /** Tests the constants in ServiceNames. */
@@ -44,9 +45,9 @@
         Assert.assertEquals("Incorrect class name constant", VariationsSeedServer.class.getName(),
                 ServiceNames.VARIATIONS_SEED_SERVER);
         Assert.assertEquals("Incorrect class name constant",
-                ComponentsProviderService.class.getName(),
-                ServiceNames.COMPONENTS_PROVIDER_SERVICE);
-        Assert.assertEquals("Incorrect class name constant",
                 AwComponentUpdateService.class.getName(), ServiceNames.AW_COMPONENT_UPDATE_SERVICE);
+        Assert.assertEquals("Incorrect class name constant",
+                ComponentsProviderService.class.getName(),
+                EmbeddedComponentLoader.AW_COMPONENTS_PROVIDER_SERVICE);
     }
 }
diff --git a/android_webview/test/BUILD.gn b/android_webview/test/BUILD.gn
index 9cc5642..9bca92a 100644
--- a/android_webview/test/BUILD.gn
+++ b/android_webview/test/BUILD.gn
@@ -614,6 +614,7 @@
     "//base:base_java",
     "//base:base_java_test_support",
     "//base:base_junit_test_support",
+    "//components/component_updater/android:embedded_component_loader_java",
     "//content/public/test/android:content_java_test_support",
     "//third_party/android_deps:protobuf_lite_runtime_java",
     "//third_party/android_support_test_runner:runner_java",
diff --git a/ash/accelerators/accelerator_controller_impl.cc b/ash/accelerators/accelerator_controller_impl.cc
index db6573d..b6f555e 100644
--- a/ash/accelerators/accelerator_controller_impl.cc
+++ b/ash/accelerators/accelerator_controller_impl.cc
@@ -1583,6 +1583,13 @@
   return accelerator_history_.get();
 }
 
+bool AcceleratorControllerImpl::DoesAcceleratorMatchAction(
+    const ui::Accelerator& accelerator,
+    AcceleratorAction action) {
+  AcceleratorAction* action_ptr = accelerators_.Find(accelerator);
+  return action_ptr && *action_ptr == action;
+}
+
 bool AcceleratorControllerImpl::IsPreferred(
     const ui::Accelerator& accelerator) const {
   const AcceleratorAction* action_ptr = accelerators_.Find(accelerator);
diff --git a/ash/accelerators/accelerator_controller_impl.h b/ash/accelerators/accelerator_controller_impl.h
index d68bd0ff..24dbbe1 100644
--- a/ash/accelerators/accelerator_controller_impl.h
+++ b/ash/accelerators/accelerator_controller_impl.h
@@ -207,6 +207,8 @@
   bool OnMenuAccelerator(const ui::Accelerator& accelerator) override;
   bool IsRegistered(const ui::Accelerator& accelerator) const override;
   AcceleratorHistoryImpl* GetAcceleratorHistory() override;
+  bool DoesAcceleratorMatchAction(const ui::Accelerator& accelerator,
+                                  AcceleratorAction action) override;
 
   // Returns true if the |accelerator| is preferred. A preferred accelerator
   // is handled before being passed to an window/web contents, unless
diff --git a/ash/accelerators/accelerator_controller_unittest.cc b/ash/accelerators/accelerator_controller_unittest.cc
index 8ce6beb..b7a7a4a 100644
--- a/ash/accelerators/accelerator_controller_unittest.cc
+++ b/ash/accelerators/accelerator_controller_unittest.cc
@@ -39,6 +39,8 @@
 #include "ash/system/brightness_control_delegate.h"
 #include "ash/system/keyboard_brightness_control_delegate.h"
 #include "ash/system/power/power_button_controller_test_api.h"
+#include "ash/system/status_area_widget_test_helper.h"
+#include "ash/system/unified/unified_system_tray.h"
 #include "ash/test/ash_test_base.h"
 #include "ash/test_media_client.h"
 #include "ash/wm/lock_state_controller.h"
@@ -1308,6 +1310,23 @@
   GetAppListTestHelper()->CheckVisibility(true);
 }
 
+TEST_F(AcceleratorControllerTest, GlobalAcceleratorsToggleQuickSettings) {
+  UnifiedSystemTray* tray =
+      StatusAreaWidgetTestHelper::GetStatusAreaWidget()->unified_system_tray();
+
+  auto* generator = GetEventGenerator();
+
+  // Pressing accelerator once should show the quick settings bubble.
+  generator->PressKey(ui::VKEY_S, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(tray->IsBubbleShown());
+
+  // Pressing accelerator a second time should dismiss the bubble.
+  generator->PressKey(ui::VKEY_S, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(tray->IsBubbleShown());
+}
+
 TEST_F(AcceleratorControllerTest, GlobalAcceleratorsToggleAppListFullscreen) {
   base::HistogramTester histogram_tester;
 
diff --git a/ash/app_list/app_list_color_provider_impl.cc b/ash/app_list/app_list_color_provider_impl.cc
index 5d0f734..0527cc9 100644
--- a/ash/app_list/app_list_color_provider_impl.cc
+++ b/ash/app_list/app_list_color_provider_impl.cc
@@ -119,10 +119,9 @@
       AshColorProvider::ContentLayerType::kButtonIconColor, default_color);
 }
 
-SkColor AppListColorProviderImpl::GetFolderBackgroundColor(
-    SkColor default_color) const {
+SkColor AppListColorProviderImpl::GetFolderBackgroundColor() const {
   return DeprecatedGetBaseLayerColor(
-      AshColorProvider::BaseLayerType::kTransparent80, default_color);
+      AshColorProvider::BaseLayerType::kTransparent80, SK_ColorWHITE);
 }
 
 SkColor AppListColorProviderImpl::GetFolderBubbleColor() const {
@@ -162,6 +161,19 @@
       /*default_color*/ SkColorSetRGB(0xF2, 0xF2, 0xF2));
 }
 
+SkColor AppListColorProviderImpl::GetGridBackgroundCardActiveColor() const {
+  SkColor base_color = GetGridBackgroundCardInactiveColor();
+  AshColorProvider::RippleAttributes ripple =
+      AshColorProvider::Get()->GetRippleAttributes(base_color);
+  return SkColorSetA(base_color, SkColorGetA(base_color) +
+                                     255 * (1.0f + ripple.highlight_opacity));
+}
+
+SkColor AppListColorProviderImpl::GetGridBackgroundCardInactiveColor() const {
+  return AshColorProvider::Get()->GetControlsLayerColor(
+      AshColorProvider::ControlsLayerType::kControlBackgroundColorInactive);
+}
+
 SkColor AppListColorProviderImpl::GetSeparatorColor() const {
   return DeprecatedGetContentLayerColor(
       AshColorProvider::ContentLayerType::kSeparatorColor,
diff --git a/ash/app_list/app_list_color_provider_impl.h b/ash/app_list/app_list_color_provider_impl.h
index 2d95442..402dd35a 100644
--- a/ash/app_list/app_list_color_provider_impl.h
+++ b/ash/app_list/app_list_color_provider_impl.h
@@ -30,13 +30,15 @@
       bool is_root_app_grid_page_switcher) const override;
   SkColor GetSearchBoxIconColor(SkColor default_color) const override;
   SkColor GetSearchBoxCardBackgroundColor() const override;
-  SkColor GetFolderBackgroundColor(SkColor default_color) const override;
+  SkColor GetFolderBackgroundColor() const override;
   SkColor GetFolderBubbleColor() const override;
   SkColor GetFolderTitleTextColor(SkColor default_color) const override;
   SkColor GetFolderHintTextColor() const override;
   SkColor GetFolderNameBorderColor(bool active) const override;
   SkColor GetFolderNameSelectionColor() const override;
   SkColor GetContentsBackgroundColor() const override;
+  SkColor GetGridBackgroundCardActiveColor() const override;
+  SkColor GetGridBackgroundCardInactiveColor() const override;
   SkColor GetSeparatorColor() const override;
   SkColor GetFocusRingColor() const override;
   float GetFolderBackgrounBlurSigma() const override;
diff --git a/ash/app_list/app_list_controller_impl.cc b/ash/app_list/app_list_controller_impl.cc
index 4864c88f..ecfa264f 100644
--- a/ash/app_list/app_list_controller_impl.cc
+++ b/ash/app_list/app_list_controller_impl.cc
@@ -1289,7 +1289,6 @@
 }
 
 void AppListControllerImpl::Back() {
-  // TODO(https://crbug.com/1220808): Handle back action for AppListBubble
   fullscreen_presenter_->GetView()->Back();
 }
 
diff --git a/ash/app_list/app_list_presenter_unittest.cc b/ash/app_list/app_list_presenter_unittest.cc
index c3458c016..e531a72 100644
--- a/ash/app_list/app_list_presenter_unittest.cc
+++ b/ash/app_list/app_list_presenter_unittest.cc
@@ -1363,7 +1363,6 @@
   gfx::Point target =
       apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen().CenterPoint();
   event_generator->MoveTouch(target);
-  EXPECT_TRUE(apps_grid_view_->FireFolderDroppingTimerForTest());
   event_generator->ReleaseTouch();
 
   // Verify the new item location within the apps grid.
@@ -1398,7 +1397,6 @@
   event_generator->MoveMouseBy(10, 10);
   event_generator->MoveMouseTo(
       apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen().CenterPoint());
-  EXPECT_TRUE(apps_grid_view_->FireFolderDroppingTimerForTest());
   event_generator->ReleaseLeftButton();
   EXPECT_FALSE(apps_grid_view_->IsDragging());
 
diff --git a/ash/app_list/model/folder_image.cc b/ash/app_list/model/folder_image.cc
index 538bf0c..f1643d4 100644
--- a/ash/app_list/model/folder_image.cc
+++ b/ash/app_list/model/folder_image.cc
@@ -115,9 +115,6 @@
 }
 
 void FolderImageSource::Draw(gfx::Canvas* canvas) {
-  gfx::PointF bubble_center(size().width() / 2, size().height() / 2);
-  bubble_center.Offset(0, -app_list_config_.folder_bubble_y_offset());
-
   // Draw circle for folder bubble.
   cc::PaintFlags flags;
   flags.setStyle(cc::PaintFlags::kFill_Style);
@@ -125,6 +122,7 @@
   flags.setColor(AppListColorProvider::Get()
                      ? AppListColorProvider::Get()->GetFolderBubbleColor()
                      : kDefaultBubbleColor);
+  const gfx::PointF bubble_center(size().width() / 2, size().height() / 2);
   canvas->DrawCircle(bubble_center, app_list_config_.folder_bubble_radius(),
                      flags);
 
diff --git a/ash/app_list/views/app_list_bubble_view.cc b/ash/app_list/views/app_list_bubble_view.cc
index 2795d078..fd61d091 100644
--- a/ash/app_list/views/app_list_bubble_view.cc
+++ b/ash/app_list/views/app_list_bubble_view.cc
@@ -12,6 +12,7 @@
 #include "ash/app_list/views/app_list_bubble_search_page.h"
 #include "ash/app_list/views/assistant/app_list_bubble_assistant_page.h"
 #include "ash/app_list/views/search_box_view.h"
+#include "ash/keyboard/ui/keyboard_ui_controller.h"
 #include "ash/public/cpp/shelf_config.h"
 #include "ash/public/cpp/shelf_types.h"
 #include "ash/public/cpp/shell_window_ids.h"
@@ -140,6 +141,9 @@
   params.create_background = false;
   search_box_view_->Init(params);
 
+  AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE));
+  AddAccelerator(ui::Accelerator(ui::VKEY_BROWSER_BACK, ui::EF_NONE));
+
   // NOTE: Passing drag and drop host from a specific shelf instance assumes
   // that the `apps_page_` will not get reused for showing the app list in
   // another root window.
@@ -159,6 +163,17 @@
 
 AppListBubbleView::~AppListBubbleView() = default;
 
+bool AppListBubbleView::Back() {
+  if (search_box_view_->HasSearch()) {
+    search_box_view_->ClearSearchAndDeactivateSearchBox();
+    return true;
+  }
+  // TODO(https://crbug.com/1220808): Handle back action for open folders in
+  // AppListBubble
+
+  return false;
+}
+
 void AppListBubbleView::FocusSearchBox() {
   DCHECK(GetWidget());
   search_box_view_->SetSearchBoxActive(true, /*event_type=*/ui::ET_UNKNOWN);
@@ -178,6 +193,24 @@
   assistant_page_->RequestFocus();
 }
 
+bool AppListBubbleView::AcceleratorPressed(const ui::Accelerator& accelerator) {
+  switch (accelerator.key_code()) {
+    case ui::VKEY_ESCAPE:
+    case ui::VKEY_BROWSER_BACK:
+      // If the ContentsView does not handle the back action, then this is the
+      // top level, so we close the app list.
+      if (!Back())
+        view_delegate_->DismissAppList();
+      break;
+    default:
+      NOTREACHED();
+      return false;
+  }
+
+  // Don't let the accelerator propagate any further.
+  return true;
+}
+
 gfx::Size AppListBubbleView::CalculatePreferredSize() const {
   int height = kDefaultHeight - margins().height();
   int width = kDefaultWidth - margins().width();
diff --git a/ash/app_list/views/app_list_bubble_view.h b/ash/app_list/views/app_list_bubble_view.h
index a0b89e3..b3e23da 100644
--- a/ash/app_list/views/app_list_bubble_view.h
+++ b/ash/app_list/views/app_list_bubble_view.h
@@ -35,6 +35,9 @@
   AppListBubbleView& operator=(const AppListBubbleView&) = delete;
   ~AppListBubbleView() override;
 
+  // Handles back action if it we have a use for it besides dismissing.
+  bool Back();
+
   // Focuses the search box text input field.
   void FocusSearchBox();
 
@@ -45,6 +48,7 @@
   void ShowEmbeddedAssistantUI();
 
   // views::View:
+  bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
   gfx::Size CalculatePreferredSize() const override;
   void OnPaint(gfx::Canvas* canvas) override;
 
diff --git a/ash/app_list/views/app_list_bubble_view_unittest.cc b/ash/app_list/views/app_list_bubble_view_unittest.cc
index 9094ef5..a9550abd 100644
--- a/ash/app_list/views/app_list_bubble_view_unittest.cc
+++ b/ash/app_list/views/app_list_bubble_view_unittest.cc
@@ -278,6 +278,37 @@
   EXPECT_EQ(client->last_search_query(), u"ab");
 }
 
+TEST_F(AppListBubbleViewTest, BackActionsClearSearch) {
+  ShowAppList();
+  SearchBoxView* search_box_view = GetSearchBoxView();
+
+  PressAndReleaseKey(ui::VKEY_A);
+  EXPECT_FALSE(search_box_view->search_box()->GetText().empty());
+
+  PressAndReleaseKey(ui::VKEY_BROWSER_BACK);
+  EXPECT_TRUE(search_box_view->search_box()->GetText().empty());
+
+  PressAndReleaseKey(ui::VKEY_A);
+  EXPECT_FALSE(search_box_view->search_box()->GetText().empty());
+
+  PressAndReleaseKey(ui::VKEY_ESCAPE);
+  EXPECT_TRUE(search_box_view->search_box()->GetText().empty());
+}
+
+TEST_F(AppListBubbleViewTest, BackActionsCloseAppList) {
+  ShowAppList();
+  GetAppListTestHelper()->CheckVisibility(true);
+
+  PressAndReleaseKey(ui::VKEY_BROWSER_BACK);
+  GetAppListTestHelper()->CheckVisibility(false);
+
+  ShowAppList();
+  GetAppListTestHelper()->CheckVisibility(true);
+
+  PressAndReleaseKey(ui::VKEY_ESCAPE);
+  GetAppListTestHelper()->CheckVisibility(false);
+}
+
 TEST_F(AppListBubbleViewTest, CanSelectSearchResults) {
   ShowAppList();
 
diff --git a/ash/app_list/views/app_list_folder_view.cc b/ash/app_list/views/app_list_folder_view.cc
index 1f2d0b7..b971ad0 100644
--- a/ash/app_list/views/app_list_folder_view.cc
+++ b/ash/app_list/views/app_list_folder_view.cc
@@ -64,6 +64,12 @@
 constexpr int kIndexFolderHeader = 3;
 constexpr int kIndexPageSwitcher = 4;
 
+// Duration for fading in the target page when opening
+// or closing a folder, and the duration for the top folder icon animation
+// for flying in or out the folder.
+constexpr base::TimeDelta kFolderTransitionDuration =
+    base::TimeDelta::FromMilliseconds(250);
+
 // Transit from the background of the folder item's icon to the opened
 // folder's background when opening the folder. Transit the other way when
 // closing the folder.
@@ -96,8 +102,7 @@
                               : folder_view_->folder_item_icon_bounds();
     to_rect -= background_view_->bounds().OffsetFromOrigin();
     const SkColor background_color =
-        AppListColorProvider::Get()->GetFolderBackgroundColor(
-            folder_view_->GetAppListConfig().folder_background_color());
+        AppListColorProvider::Get()->GetFolderBackgroundColor();
     const SkColor from_color =
         show_ ? AppListColorProvider::Get()->GetFolderBubbleColor()
               : background_color;
@@ -114,8 +119,7 @@
 
     ui::ScopedLayerAnimationSettings settings(
         background_view_->layer()->GetAnimator());
-    settings.SetTransitionDuration(
-        folder_view_->GetAppListConfig().folder_transition_in_duration());
+    settings.SetTransitionDuration(kFolderTransitionDuration);
     settings.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
     settings.AddObserver(this);
     background_view_->layer()->SetColor(to_color);
@@ -165,8 +169,7 @@
                           folder_view_->GetAppListConfig().grid_title_color());
 
     animation_.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
-    animation_.SetSlideDuration(
-        folder_view_->GetAppListConfig().folder_transition_in_duration());
+    animation_.SetSlideDuration(kFolderTransitionDuration);
   }
 
   ~FolderItemTitleAnimation() override = default;
@@ -269,7 +272,7 @@
       top_icon_views_.push_back(
           folder_view_->background_view()->AddChildView(std::move(icon_view)));
       icon_view_ptr->SetBoundsRect(first_page_item_views_bounds[i]);
-      icon_view_ptr->TransformView();
+      icon_view_ptr->TransformView(kFolderTransitionDuration);
     }
   }
 
@@ -402,8 +405,7 @@
     ui::ScopedLayerAnimationSettings animation(layer->GetAnimator());
     animation.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
     animation.AddObserver(this);
-    animation.SetTransitionDuration(
-        folder_view_->GetAppListConfig().folder_transition_in_duration());
+    animation.SetTransitionDuration(kFolderTransitionDuration);
     layer->SetTransform(show_ ? gfx::Transform() : transform);
     layer->SetOpacity(show_ ? 1.0f : 0.0f);
 
@@ -488,8 +490,7 @@
       contents_container_->AddChildView(std::make_unique<PageSwitcher>(
           items_grid_view_->pagination_model(), false /* vertical */,
           view_delegate->IsInTabletMode(),
-          AppListColorProvider::Get()->GetFolderBackgroundColor(
-              items_grid_view_->GetAppListConfig().folder_background_color())));
+          AppListColorProvider::Get()->GetFolderBackgroundColor()));
   view_model_->Add(page_switcher_, kIndexPageSwitcher);
 
   model_->AddObserver(this);
diff --git a/ash/app_list/views/app_list_item_view.cc b/ash/app_list/views/app_list_item_view.cc
index be159123..264977e 100644
--- a/ash/app_list/views/app_list_item_view.cc
+++ b/ash/app_list/views/app_list_item_view.cc
@@ -690,11 +690,9 @@
       flags.setStrokeWidth(kFocusRingWidth);
     } else {
       const AppListColorProvider* color_provider = AppListColorProvider::Get();
-      const SkColor bg_color =
-          grid_delegate_->IsInFolder()
-              ? color_provider->GetFolderBackgroundColor(
-                    GetAppListConfig().folder_background_color())
-              : gfx::kPlaceholderColor;
+      const SkColor bg_color = grid_delegate_->IsInFolder()
+                                   ? color_provider->GetFolderBackgroundColor()
+                                   : gfx::kPlaceholderColor;
       flags.setColor(SkColorSetA(
           color_provider->GetRippleAttributesBaseColor(bg_color),
           color_provider->GetRippleAttributesHighlightOpacity(bg_color) * 255));
diff --git a/ash/app_list/views/apps_container_view.cc b/ash/app_list/views/apps_container_view.cc
index 197fc59..84a55183 100644
--- a/ash/app_list/views/apps_container_view.cc
+++ b/ash/app_list/views/apps_container_view.cc
@@ -56,6 +56,12 @@
 constexpr float kSuggestionChipOpacityStartProgress = 0.66;
 constexpr float kSuggestionChipOpacityEndProgress = 1;
 
+// Range of the height of centerline above screen bottom that all apps should
+// change opacity. NOTE: this is used to change page switcher's opacity as
+// well.
+constexpr float kAppsOpacityChangeStart = 8.0f;
+constexpr float kAppsOpacityChangeEnd = 144.0f;
+
 // The app list transition progress value for fullscreen state.
 constexpr float kAppListFullscreenProgressValue = 2.0;
 
@@ -318,7 +324,9 @@
 
   if (!apps_grid_view_->layer()->GetAnimator()->IsAnimatingProperty(
           ui::LayerAnimationElement::OPACITY)) {
-    apps_grid_view_->UpdateOpacity(true /*restore_opacity*/);
+    apps_grid_view_->UpdateOpacity(true /*restore_opacity*/,
+                                   kAppsOpacityChangeStart,
+                                   kAppsOpacityChangeEnd);
     apps_grid_view_->layer()->SetOpacity(current_progress > 1.0f ? 1.0f : 0.0f);
   }
 
@@ -774,7 +782,8 @@
 
 void AppsContainerView::UpdateContentsOpacity(float progress,
                                               bool restore_opacity) {
-  apps_grid_view_->UpdateOpacity(restore_opacity);
+  apps_grid_view_->UpdateOpacity(restore_opacity, kAppsOpacityChangeStart,
+                                 kAppsOpacityChangeEnd);
 
   // Updates the opacity of page switcher buttons. The same rule as all apps in
   // AppsGridView.
@@ -783,12 +792,11 @@
   gfx::Rect switcher_bounds = page_switcher_->GetBoundsInScreen();
   float centerline_above_work_area =
       std::max<float>(screen_bottom - switcher_bounds.CenterPoint().y(), 0.f);
-  const float start_px = GetAppListConfig().all_apps_opacity_start_px();
-  float opacity = std::min(
-      std::max((centerline_above_work_area - start_px) /
-                   (GetAppListConfig().all_apps_opacity_end_px() - start_px),
-               0.f),
-      1.0f);
+  float opacity =
+      std::min(std::max((centerline_above_work_area - kAppsOpacityChangeStart) /
+                            (kAppsOpacityChangeEnd - kAppsOpacityChangeStart),
+                        0.f),
+               1.0f);
   page_switcher_->layer()->SetOpacity(restore_opacity ? 1.0f : opacity);
 
   if (suggestion_chip_container_view_) {
diff --git a/ash/app_list/views/apps_grid_view.cc b/ash/app_list/views/apps_grid_view.cc
index 06dbb1e..27c066a 100644
--- a/ash/app_list/views/apps_grid_view.cc
+++ b/ash/app_list/views/apps_grid_view.cc
@@ -93,6 +93,14 @@
 // Maximum vertical and horizontal spacing between tiles.
 constexpr int kMaximumTileSpacing = 96;
 
+// Duration for page transition.
+constexpr base::TimeDelta kPageTransitionDuration =
+    base::TimeDelta::FromMilliseconds(250);
+
+// Duration for overscroll page transition.
+constexpr base::TimeDelta kOverscrollPageTransitionDuration =
+    base::TimeDelta::FromMilliseconds(50);
+
 // RowMoveAnimationDelegate is used when moving an item into a different row.
 // Before running the animation, the item's layer is re-created and kept in
 // the original position, then the item is moved to just before its target
@@ -295,9 +303,8 @@
 
   UpdateBorder();
 
-  pagination_model_.SetTransitionDurations(
-      GetAppListConfig().page_transition_duration(),
-      GetAppListConfig().overscroll_page_transition_duration());
+  pagination_model_.SetTransitionDurations(kPageTransitionDuration,
+                                           kOverscrollPageTransitionDuration);
 }
 
 AppsGridView::~AppsGridView() {
@@ -549,25 +556,25 @@
     // Don't do reordering while auto-scrolling, otherwise there is too much
     // motion during the drag.
     reorder_timer_.Stop();
-    folder_dropping_timer_.Stop();
+    // Reset the previous drop target.
+    if (last_drop_target_region == ON_ITEM)
+      SetAsFolderDroppingTarget(last_drop_target, false);
     return;
   }
 
   if (last_drop_target != drop_target_ ||
       last_drop_target_region != drop_target_region_) {
+    if (last_drop_target_region == ON_ITEM)
+      SetAsFolderDroppingTarget(last_drop_target, false);
     if (drop_target_region_ == ON_ITEM && DraggedItemCanEnterFolder() &&
         DropTargetIsValidFolder()) {
       reorder_timer_.Stop();
-      folder_dropping_timer_.Start(
-          FROM_HERE,
-          base::TimeDelta::FromMilliseconds(
-              GetAppListConfig().folder_dropping_delay()),
-          this, &AppsGridView::OnFolderDroppingTimer);
+      MaybeCreateFolderDroppingAccessibilityEvent();
+      SetAsFolderDroppingTarget(drop_target_, true);
+      BeginHideCurrentGhostImageView();
     } else if ((drop_target_region_ == ON_ITEM ||
                 drop_target_region_ == NEAR_ITEM) &&
                !folder_delegate_) {
-      folder_dropping_timer_.Stop();
-
       // If the drag changes regions from |BETWEEN_ITEMS| to |NEAR_ITEM| the
       // timer should reset, so that we gain the extra time from hovering near
       // the item
@@ -579,15 +586,10 @@
     } else if (drop_target_region_ != NO_TARGET) {
       // If none of the above cases evaluated true, then all of the possible
       // drop regions should result in a fast reorder.
-      folder_dropping_timer_.Stop();
       reorder_timer_.Start(FROM_HERE,
                            base::TimeDelta::FromMilliseconds(kReorderDelay),
                            this, &AppsGridView::OnReorderTimer);
     }
-
-    // Reset the previous drop target.
-    if (last_drop_target_region == ON_ITEM)
-      SetAsFolderDroppingTarget(last_drop_target, false);
   }
 }
 
@@ -1468,12 +1470,6 @@
   }
 }
 
-void AppsGridView::OnFolderDroppingTimer() {
-  MaybeCreateFolderDroppingAccessibilityEvent();
-  SetAsFolderDroppingTarget(drop_target_, true);
-  BeginHideCurrentGhostImageView();
-}
-
 void AppsGridView::UpdateDragStateInsideFolder(Pointer pointer,
                                                const gfx::Point& drag_point) {
   if (IsUnderOEMFolder())
@@ -1883,13 +1879,6 @@
   return true;
 }
 
-bool AppsGridView::FireFolderDroppingTimerForTest() {
-  if (!folder_dropping_timer_.IsRunning())
-    return false;
-  folder_dropping_timer_.FireNow();
-  return true;
-}
-
 bool AppsGridView::FireDragToShelfTimerForTest() {
   if (!host_drag_start_timer_.IsRunning())
     return false;
diff --git a/ash/app_list/views/apps_grid_view.h b/ash/app_list/views/apps_grid_view.h
index e055f91..ad705301 100644
--- a/ash/app_list/views/apps_grid_view.h
+++ b/ash/app_list/views/apps_grid_view.h
@@ -300,7 +300,6 @@
   const AppListModel* model() const { return model_; }
 
   bool FireFolderItemReparentTimerForTest();
-  bool FireFolderDroppingTimerForTest();
   bool FireDragToShelfTimerForTest();
 
   // For test: Return if the drag and drop handler was set.
@@ -667,10 +666,6 @@
   // Invoked when |folder_item_reparent_timer_| fires.
   void OnFolderItemReparentTimer();
 
-  // Invoked when |folder_dropping_timer_| fires to show folder dropping
-  // preview UI.
-  void OnFolderDroppingTimer();
-
   // Updates drag state for dragging inside a folder's grid view.
   void UpdateDragStateInsideFolder(Pointer pointer,
                                    const gfx::Point& drag_point);
@@ -821,10 +816,6 @@
   // Timer for re-ordering the |drop_target_region_| and |drag_view_|.
   base::OneShotTimer reorder_timer_;
 
-  // Timer for dropping |drag_view_| into the folder represented by
-  // the |drop_target_|.
-  base::OneShotTimer folder_dropping_timer_;
-
   // Timer for dragging a folder item out of folder container ink bubble.
   base::OneShotTimer folder_item_reparent_timer_;
 
diff --git a/ash/app_list/views/apps_grid_view_test_api.cc b/ash/app_list/views/apps_grid_view_test_api.cc
index ada15ce..2e34e05 100644
--- a/ash/app_list/views/apps_grid_view_test_api.cc
+++ b/ash/app_list/views/apps_grid_view_test_api.cc
@@ -68,10 +68,6 @@
     view_->reorder_timer_.Stop();
     view_->OnReorderTimer();
   }
-  if (view_->folder_dropping_timer_.IsRunning()) {
-    view_->folder_dropping_timer_.Stop();
-    view_->OnFolderDroppingTimer();
-  }
   view_->bounds_animator_->Cancel();
   view_->Layout();
 }
diff --git a/ash/app_list/views/contents_view.cc b/ash/app_list/views/contents_view.cc
index 8102c763..109b586 100644
--- a/ash/app_list/views/contents_view.cc
+++ b/ash/app_list/views/contents_view.cc
@@ -61,6 +61,14 @@
 constexpr float kSearchBoxOpacityStartProgress = 0.11f;
 constexpr float kSearchBoxOpacityEndProgress = 1.0f;
 
+// Duration for page transition.
+constexpr base::TimeDelta kPageTransitionDuration =
+    base::TimeDelta::FromMilliseconds(250);
+
+// Duration for overscroll page transition.
+constexpr base::TimeDelta kOverscrollPageTransitionDuration =
+    base::TimeDelta::FromMilliseconds(50);
+
 // Calculates opacity value for the current app list progress.
 // |progress| - The target app list view progress - a value in [0.0, 2.0]
 //              interval that describes the app list view position relative to
@@ -79,9 +87,8 @@
 
 ContentsView::ContentsView(AppListView* app_list_view)
     : app_list_view_(app_list_view) {
-  pagination_model_.SetTransitionDurations(
-      GetAppListConfig().page_transition_duration(),
-      GetAppListConfig().overscroll_page_transition_duration());
+  pagination_model_.SetTransitionDurations(kPageTransitionDuration,
+                                           kOverscrollPageTransitionDuration);
   pagination_model_.AddObserver(this);
 }
 
@@ -199,7 +206,7 @@
   apps_container_view_->OnTabletModeChanged(started);
 
   UpdateExpandArrowOpacity(GetActiveState(), target_view_state(),
-                           GetAppListConfig().page_transition_duration());
+                           kPageTransitionDuration);
 }
 
 void ContentsView::SetActiveState(AppListState state) {
@@ -294,10 +301,9 @@
   }
   pagination_model_.SelectPage(page_index, should_animate);
   ActivePageChanged();
-  UpdateExpandArrowOpacity(GetActiveState(), target_view_state(),
-                           should_animate
-                               ? GetAppListConfig().page_transition_duration()
-                               : base::TimeDelta());
+  UpdateExpandArrowOpacity(
+      GetActiveState(), target_view_state(),
+      should_animate ? kPageTransitionDuration : base::TimeDelta());
 
   if (!should_animate)
     Layout();
diff --git a/ash/app_list/views/paged_apps_grid_view.cc b/ash/app_list/views/paged_apps_grid_view.cc
index c0e76f2..6c665ff 100644
--- a/ash/app_list/views/paged_apps_grid_view.cc
+++ b/ash/app_list/views/paged_apps_grid_view.cc
@@ -17,6 +17,7 @@
 #include "ash/app_list/views/apps_container_view.h"
 #include "ash/app_list/views/contents_view.h"
 #include "ash/constants/ash_features.h"
+#include "ash/public/cpp/app_list/app_list_color_provider.h"
 #include "ash/public/cpp/app_list/app_list_config.h"
 #include "ash/public/cpp/app_list/app_list_features.h"
 #include "ash/public/cpp/pagination/pagination_controller.h"
@@ -231,7 +232,9 @@
   pagination_controller_->OnScroll(offset, type);
 }
 
-void PagedAppsGridView::UpdateOpacity(bool restore_opacity) {
+void PagedAppsGridView::UpdateOpacity(bool restore_opacity,
+                                      float apps_opacity_change_start,
+                                      float apps_opacity_change_end) {
   if (view_structure_.pages().empty())
     return;
 
@@ -294,11 +297,10 @@
     views::View::ConvertRectToScreen(item_view, &view_bounds);
     centerline_above_work_area = std::max<float>(
         app_list_view->GetScreenBottom() - view_bounds.CenterPoint().y(), 0.f);
-    const float start_px = GetAppListConfig().all_apps_opacity_start_px();
-    opacity = base::clamp(
-        (centerline_above_work_area - start_px) /
-            (GetAppListConfig().all_apps_opacity_end_px() - start_px),
-        0.f, 1.0f);
+    opacity =
+        base::clamp((centerline_above_work_area - apps_opacity_change_start) /
+                        (apps_opacity_change_end - apps_opacity_change_start),
+                    0.f, 1.0f);
 
     if (opacity == item_view->layer()->opacity())
       continue;
@@ -1053,8 +1055,11 @@
       const bool is_active_page =
           background_cards_[pagination_model_.selected_page()] ==
           background_card;
+      auto* color_provider = AppListColorProvider::Get();
       background_card->SetColor(
-          GetAppListConfig().GetCardifiedBackgroundColor(is_active_page));
+          is_active_page
+              ? color_provider->GetGridBackgroundCardActiveColor()
+              : color_provider->GetGridBackgroundCardInactiveColor());
     } else {
       background_card->SetOpacity(kBackgroundCardOpacityHide);
     }
@@ -1141,13 +1146,13 @@
     return;
 
   if (new_highlighted_page != highlighted_page_) {
+    auto* color_provider = AppListColorProvider::Get();
     background_cards_[highlighted_page_]->SetColor(
-        GetAppListConfig().GetCardifiedBackgroundColor(
-            /*is_active=*/false));
+        color_provider->GetGridBackgroundCardInactiveColor());
     if (static_cast<int>(background_cards_.size()) == new_highlighted_page)
       AppendBackgroundCard();
     background_cards_[new_highlighted_page]->SetColor(
-        GetAppListConfig().GetCardifiedBackgroundColor(/*is_active=*/true));
+        color_provider->GetGridBackgroundCardActiveColor());
 
     highlighted_page_ = new_highlighted_page;
   }
diff --git a/ash/app_list/views/paged_apps_grid_view.h b/ash/app_list/views/paged_apps_grid_view.h
index 2a47d6e..379df64 100644
--- a/ash/app_list/views/paged_apps_grid_view.h
+++ b/ash/app_list/views/paged_apps_grid_view.h
@@ -59,7 +59,12 @@
   // Updates the opacity of all the items in the grid when the grid itself is
   // being dragged. The app icons fade out as the launcher slides off the bottom
   // of the screen.
-  void UpdateOpacity(bool restore_opacity);
+  // `apps_opacity_change_start` and `apps_opacity_change_end` define the range
+  // of height of centerline above screen bottom in which apps should change
+  // opacity (from 0 to 1).
+  void UpdateOpacity(bool restore_opacity,
+                     float apps_opacity_change_start,
+                     float apps_opacity_change_end);
 
   // ui::EventHandler:
   void OnGestureEvent(ui::GestureEvent* event) override;
diff --git a/ash/app_list/views/top_icon_animation_view.cc b/ash/app_list/views/top_icon_animation_view.cc
index 0b062d39..fac7e24 100644
--- a/ash/app_list/views/top_icon_animation_view.cc
+++ b/ash/app_list/views/top_icon_animation_view.cc
@@ -77,7 +77,7 @@
   observers_.RemoveObserver(observer);
 }
 
-void TopIconAnimationView::TransformView() {
+void TopIconAnimationView::TransformView(base::TimeDelta duration) {
   // Transform used for scaling down the icon and move it back inside to the
   // original folder icon. The transform's origin is this view's origin.
   gfx::Transform transform;
@@ -99,8 +99,7 @@
   ui::ScopedLayerAnimationSettings settings(layer()->GetAnimator());
   settings.AddObserver(this);
   settings.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
-  settings.SetTransitionDuration(
-      grid_->GetAppListConfig().folder_transition_in_duration());
+  settings.SetTransitionDuration(duration);
   layer()->SetTransform(open_folder_ ? gfx::Transform() : transform);
   if (!item_in_folder_icon_)
     layer()->SetOpacity(open_folder_ ? 1.0f : 0.0f);
@@ -111,8 +110,7 @@
     ui::ScopedLayerAnimationSettings title_settings(
         title_->layer()->GetAnimator());
     title_settings.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
-    title_settings.SetTransitionDuration(
-        grid_->GetAppListConfig().folder_transition_in_duration());
+    title_settings.SetTransitionDuration(duration);
     title_->layer()->SetOpacity(open_folder_ ? 1.0f : 0.0f);
   }
 }
diff --git a/ash/app_list/views/top_icon_animation_view.h b/ash/app_list/views/top_icon_animation_view.h
index 139ba43..f605176 100644
--- a/ash/app_list/views/top_icon_animation_view.h
+++ b/ash/app_list/views/top_icon_animation_view.h
@@ -58,7 +58,7 @@
   // inside folder icon to the full scale icon at the target location.
   // When closing a folder, transform the full scale item icon from its
   // location to the small icon inside the folder icon.
-  void TransformView();
+  void TransformView(base::TimeDelta duration);
 
   // views::View:
   const char* GetClassName() const override;
diff --git a/ash/drag_drop/drag_drop_controller_unittest.cc b/ash/drag_drop/drag_drop_controller_unittest.cc
index c8ee5297..1055f17 100644
--- a/ash/drag_drop/drag_drop_controller_unittest.cc
+++ b/ash/drag_drop/drag_drop_controller_unittest.cc
@@ -1581,7 +1581,7 @@
                void((const ui::DataTransferEndpoint* const data_src,
                      const ui::DataTransferEndpoint* const data_dst,
                      const absl::optional<size_t> size,
-                     content::WebContents* web_contents,
+                     content::RenderFrameHost* rfh,
                      base::OnceCallback<void(bool)> callback)));
   MOCK_METHOD3(IsDragDropAllowed,
                bool(const ui::DataTransferEndpoint* const data_src,
diff --git a/ash/public/cpp/accelerators.h b/ash/public/cpp/accelerators.h
index 9a6b2c4..bd57004 100644
--- a/ash/public/cpp/accelerators.h
+++ b/ash/public/cpp/accelerators.h
@@ -223,6 +223,11 @@
   // Returns the accelerator histotry.
   virtual AcceleratorHistory* GetAcceleratorHistory() = 0;
 
+  // Returns true if the provided accelerator matches the provided accelerator
+  // action.
+  virtual bool DoesAcceleratorMatchAction(const ui::Accelerator& accelerator,
+                                          const AcceleratorAction action) = 0;
+
  protected:
   AcceleratorController();
   virtual ~AcceleratorController();
diff --git a/ash/public/cpp/app_list/app_list_color_provider.h b/ash/public/cpp/app_list/app_list_color_provider.h
index b97973b..6c2f9024 100644
--- a/ash/public/cpp/app_list/app_list_color_provider.h
+++ b/ash/public/cpp/app_list/app_list_color_provider.h
@@ -38,13 +38,15 @@
       bool is_root_app_grid_page_switcher) const = 0;
   virtual SkColor GetSearchBoxIconColor(SkColor default_color) const = 0;
   virtual SkColor GetSearchBoxCardBackgroundColor() const = 0;
-  virtual SkColor GetFolderBackgroundColor(SkColor default_color) const = 0;
+  virtual SkColor GetFolderBackgroundColor() const = 0;
   virtual SkColor GetFolderBubbleColor() const = 0;
   virtual SkColor GetFolderTitleTextColor(SkColor default_color) const = 0;
   virtual SkColor GetFolderHintTextColor() const = 0;
   virtual SkColor GetFolderNameBorderColor(bool active) const = 0;
   virtual SkColor GetFolderNameSelectionColor() const = 0;
   virtual SkColor GetContentsBackgroundColor() const = 0;
+  virtual SkColor GetGridBackgroundCardActiveColor() const = 0;
+  virtual SkColor GetGridBackgroundCardInactiveColor() const = 0;
   virtual SkColor GetSeparatorColor() const = 0;
   virtual SkColor GetFocusRingColor() const = 0;
   virtual float GetFolderBackgrounBlurSigma() const = 0;
diff --git a/ash/public/cpp/app_list/app_list_config.cc b/ash/public/cpp/app_list/app_list_config.cc
index 46fc92e..c071e1a 100644
--- a/ash/public/cpp/app_list/app_list_config.cc
+++ b/ash/public/cpp/app_list/app_list_config.cc
@@ -294,7 +294,6 @@
       page_spacing_(48),
       expand_arrow_tile_height_(72),
       folder_bubble_radius_(FolderUnclippedIconDimensionForType(type) / 2),
-      folder_bubble_y_offset_(0),
       folder_icon_dimension_(FolderClippedIconDimensionForType(type)),
       folder_unclipped_icon_dimension_(
           FolderUnclippedIconDimensionForType(type)),
@@ -304,24 +303,12 @@
           ItemIconInFolderIconDimensionForType(type)),
       item_icon_in_folder_icon_margin_(ItemIconInFolderIconMarginForType(type)),
       folder_dropping_circle_radius_(folder_bubble_radius_),
-      folder_dropping_delay_(0),
-      folder_background_color_(SK_ColorWHITE),
       page_flip_zone_size_(20),
       grid_tile_spacing_in_folder_(8),
       blur_radius_(30),
-      page_transition_duration_(base::TimeDelta::FromMilliseconds(250)),
-      overscroll_page_transition_duration_(
-          base::TimeDelta::FromMilliseconds(50)),
-      folder_transition_in_duration_(base::TimeDelta::FromMilliseconds(250)),
-      folder_transition_out_duration_(base::TimeDelta::FromMilliseconds(30)),
       max_folder_pages_(3),
       max_folder_items_per_page_(16),
-      max_folder_name_chars_(28),
-      all_apps_opacity_start_px_(8.0f),
-      all_apps_opacity_end_px_(144.0f),
-      cardified_background_color_(SkColorSetA(SK_ColorWHITE, 26 /* 10% */)),
-      cardified_background_color_active_(
-          SkColorSetA(SK_ColorWHITE, 41 /* 16% */)) {
+      max_folder_name_chars_(28) {
   DCHECK_EQ(SharedAppListConfig::instance().GetMaxNumOfItemsPerPage(),
             preferred_cols_ * preferred_rows_);
 }
@@ -402,7 +389,6 @@
       folder_bubble_radius_(MinScale(base_config.folder_bubble_radius_,
                                      scale_x,
                                      inner_tile_scale_y)),
-      folder_bubble_y_offset_(base_config.folder_bubble_y_offset_),
       folder_icon_dimension_(MinScale(base_config.folder_icon_dimension_,
                                       scale_x,
                                       inner_tile_scale_y)),
@@ -427,29 +413,15 @@
           MinScale(base_config.folder_dropping_circle_radius_,
                    scale_x,
                    inner_tile_scale_y)),
-      folder_dropping_delay_(base_config.folder_dropping_delay_),
-      folder_background_color_(base_config.folder_background_color_),
       page_flip_zone_size_(base_config.page_flip_zone_size_),
       grid_tile_spacing_in_folder_(
           MinScale(base_config.grid_tile_spacing_in_folder_,
                    scale_x,
                    inner_tile_scale_y)),
       blur_radius_(base_config.blur_radius_),
-      page_transition_duration_(base_config.page_transition_duration_),
-      overscroll_page_transition_duration_(
-          base_config.overscroll_page_transition_duration_),
-      folder_transition_in_duration_(
-          base_config.folder_transition_in_duration_),
-      folder_transition_out_duration_(
-          base_config.folder_transition_out_duration_),
       max_folder_pages_(base_config.max_folder_pages_),
       max_folder_items_per_page_(base_config.max_folder_items_per_page_),
-      max_folder_name_chars_(base_config.max_folder_name_chars_),
-      all_apps_opacity_start_px_(base_config.all_apps_opacity_start_px_),
-      all_apps_opacity_end_px_(base_config.all_apps_opacity_end_px_),
-      cardified_background_color_(base_config.cardified_background_color_),
-      cardified_background_color_active_(
-          base_config.cardified_background_color_active_) {
+      max_folder_name_chars_(base_config.max_folder_name_chars_) {
   DCHECK_EQ(SharedAppListConfig::instance().GetMaxNumOfItemsPerPage(),
             preferred_cols_ * preferred_rows_);
 }
@@ -474,9 +446,4 @@
   return available_bounds.height() / kAppsGridMarginRatio;
 }
 
-SkColor AppListConfig::GetCardifiedBackgroundColor(bool is_active) const {
-  return is_active ? cardified_background_color_active_
-                   : cardified_background_color_;
-}
-
 }  // namespace ash
diff --git a/ash/public/cpp/app_list/app_list_config.h b/ash/public/cpp/app_list/app_list_config.h
index 4ff6cd7..27117709 100644
--- a/ash/public/cpp/app_list/app_list_config.h
+++ b/ash/public/cpp/app_list/app_list_config.h
@@ -289,7 +289,6 @@
   int page_spacing() const { return page_spacing_; }
   int expand_arrow_tile_height() const { return expand_arrow_tile_height_; }
   int folder_bubble_radius() const { return folder_bubble_radius_; }
-  int folder_bubble_y_offset() const { return folder_bubble_y_offset_; }
   int folder_icon_dimension() const { return folder_icon_dimension_; }
   int folder_unclipped_icon_dimension() const {
     return folder_unclipped_icon_dimension_;
@@ -305,32 +304,16 @@
   int folder_dropping_circle_radius() const {
     return folder_dropping_circle_radius_;
   }
-  int folder_dropping_delay() const { return folder_dropping_delay_; }
-  SkColor folder_background_color() const { return folder_background_color_; }
   int page_flip_zone_size() const { return page_flip_zone_size_; }
   int grid_tile_spacing_in_folder() const {
     return grid_tile_spacing_in_folder_;
   }
   int blur_radius() const { return blur_radius_; }
-  base::TimeDelta page_transition_duration() const {
-    return page_transition_duration_;
-  }
-  base::TimeDelta overscroll_page_transition_duration() const {
-    return overscroll_page_transition_duration_;
-  }
-  base::TimeDelta folder_transition_in_duration() const {
-    return folder_transition_in_duration_;
-  }
-  base::TimeDelta folder_transition_out_duration() const {
-    return folder_transition_out_duration_;
-  }
   size_t max_folder_pages() const { return max_folder_pages_; }
   size_t max_folder_items_per_page() const {
     return max_folder_items_per_page_;
   }
   size_t max_folder_name_chars() const { return max_folder_name_chars_; }
-  float all_apps_opacity_start_px() const { return all_apps_opacity_start_px_; }
-  float all_apps_opacity_end_px() const { return all_apps_opacity_end_px_; }
 
   gfx::Size grid_icon_size() const {
     return gfx::Size(grid_icon_dimension_, grid_icon_dimension_);
@@ -371,9 +354,6 @@
   int GetIdealHorizontalMargin(const gfx::Rect& abailable_bounds) const;
   int GetIdealVerticalMargin(const gfx::Rect& abailable_bounds) const;
 
-  // Returns the color and opacity for the page background.
-  SkColor GetCardifiedBackgroundColor(bool is_active) const;
-
  private:
   const AppListConfigType type_;
 
@@ -467,9 +447,6 @@
   // The folder image bubble radius.
   const int folder_bubble_radius_;
 
-  // The y offset of folder image bubble center.
-  const int folder_bubble_y_offset_;
-
   // The icon dimension of folder.
   const int folder_icon_dimension_;
 
@@ -492,12 +469,6 @@
   // UI.
   const int folder_dropping_circle_radius_;
 
-  // Delays in milliseconds to show folder dropping preview circle.
-  const int folder_dropping_delay_;
-
-  // The background color of folder.
-  const SkColor folder_background_color_;
-
   // Width in pixels of the area on the sides that triggers a page flip.
   const int page_flip_zone_size_;
 
@@ -507,21 +478,6 @@
   // The blur radius used in the app list.
   const int blur_radius_;
 
-  // Duration for page transition.
-  const base::TimeDelta page_transition_duration_;
-
-  // Duration for over scroll page transition.
-  const base::TimeDelta overscroll_page_transition_duration_;
-
-  // Duration for fading in the target page when opening
-  // or closing a folder, and the duration for the top folder icon animation
-  // for flying in or out the folder.
-  const base::TimeDelta folder_transition_in_duration_;
-
-  // Duration for fading out the old page when opening or
-  // closing a folder.
-  const base::TimeDelta folder_transition_out_duration_;
-
   // Max pages allowed in a folder.
   const size_t max_folder_pages_;
 
@@ -531,16 +487,6 @@
   // Maximum length of the folder name in chars.
   const size_t max_folder_name_chars_;
 
-  // Range of the height of centerline above screen bottom that all apps should
-  // change opacity. NOTE: this is used to change page switcher's opacity as
-  // well.
-  const float all_apps_opacity_start_px_ = 8.0f;
-  const float all_apps_opacity_end_px_ = 144.0f;
-
-  // Cardified app list background properties
-  const SkColor cardified_background_color_;
-  const SkColor cardified_background_color_active_;
-
   DISALLOW_COPY_AND_ASSIGN(AppListConfig);
 };
 
diff --git a/ash/public/cpp/message_center_ash.h b/ash/public/cpp/message_center_ash.h
index 20bad8f..9c7cc75 100644
--- a/ash/public/cpp/message_center_ash.h
+++ b/ash/public/cpp/message_center_ash.h
@@ -35,6 +35,9 @@
   // observer on change for OnQuietModeChanged.
   virtual void SetQuietMode(bool in_quiet_mode) = 0;
 
+  // Queries current notification Quiet Mode status.
+  virtual bool IsQuietMode() const = 0;
+
   void AddObserver(Observer* observer);
   void RemoveObserver(Observer* observer);
 
diff --git a/ash/public/cpp/test/test_app_list_color_provider.cc b/ash/public/cpp/test/test_app_list_color_provider.cc
index 3b7009c3..7f431db 100644
--- a/ash/public/cpp/test/test_app_list_color_provider.cc
+++ b/ash/public/cpp/test/test_app_list_color_provider.cc
@@ -54,8 +54,7 @@
   return gfx::kGoogleGrey200;
 }
 
-SkColor TestAppListColorProvider::GetFolderBackgroundColor(
-    SkColor default_color) const {
+SkColor TestAppListColorProvider::GetFolderBackgroundColor() const {
   return gfx::kGoogleGrey900;
 }
 
@@ -97,6 +96,14 @@
   return gfx::kGoogleGrey200;
 }
 
+SkColor TestAppListColorProvider::GetGridBackgroundCardActiveColor() const {
+  return SkColorSetA(SK_ColorWHITE, 26 /* 10% */);
+}
+
+SkColor TestAppListColorProvider::GetGridBackgroundCardInactiveColor() const {
+  return SkColorSetA(SK_ColorWHITE, 41 /* 16% */);
+}
+
 SkColor TestAppListColorProvider::GetSeparatorColor() const {
   return SkColorSetA(SK_ColorWHITE, 0x24);
 }
diff --git a/ash/public/cpp/test/test_app_list_color_provider.h b/ash/public/cpp/test/test_app_list_color_provider.h
index 0322aca0..a1123b6 100644
--- a/ash/public/cpp/test/test_app_list_color_provider.h
+++ b/ash/public/cpp/test/test_app_list_color_provider.h
@@ -30,13 +30,15 @@
       bool is_root_app_grid_page_switcher) const override;
   SkColor GetSearchBoxIconColor(SkColor default_color) const override;
   SkColor GetSearchBoxCardBackgroundColor() const override;
-  SkColor GetFolderBackgroundColor(SkColor default_color) const override;
+  SkColor GetFolderBackgroundColor() const override;
   SkColor GetFolderBubbleColor() const override;
   SkColor GetFolderTitleTextColor(SkColor default_color) const override;
   SkColor GetFolderHintTextColor() const override;
   SkColor GetFolderNameBorderColor(bool active) const override;
   SkColor GetFolderNameSelectionColor() const override;
   SkColor GetContentsBackgroundColor() const override;
+  SkColor GetGridBackgroundCardActiveColor() const override;
+  SkColor GetGridBackgroundCardInactiveColor() const override;
   SkColor GetSeparatorColor() const override;
   SkColor GetFocusRingColor() const override;
   float GetFolderBackgrounBlurSigma() const override;
diff --git a/ash/system/message_center/message_center_ash_impl.cc b/ash/system/message_center/message_center_ash_impl.cc
index 5dac23f3..b00eca8 100644
--- a/ash/system/message_center/message_center_ash_impl.cc
+++ b/ash/system/message_center/message_center_ash_impl.cc
@@ -24,4 +24,8 @@
   message_center::MessageCenter::Get()->SetQuietMode(in_quiet_mode);
 }
 
+bool MessageCenterAshImpl ::IsQuietMode() const {
+  return message_center::MessageCenter::Get()->IsQuietMode();
+}
+
 }  // namespace ash
diff --git a/ash/system/message_center/message_center_ash_impl.h b/ash/system/message_center/message_center_ash_impl.h
index 0c1873f..1b56936 100644
--- a/ash/system/message_center/message_center_ash_impl.h
+++ b/ash/system/message_center/message_center_ash_impl.h
@@ -19,6 +19,7 @@
  private:
   // MessageCenterAsh override:
   void SetQuietMode(bool in_quiet_mode) override;
+  bool IsQuietMode() const override;
 
   // MessageCenterObserver override:
   void OnQuietModeChanged(bool in_quiet_mode) override;
diff --git a/ash/system/tray/tray_bubble_view.cc b/ash/system/tray/tray_bubble_view.cc
index 56cbaeb..fc4f049 100644
--- a/ash/system/tray/tray_bubble_view.cc
+++ b/ash/system/tray/tray_bubble_view.cc
@@ -7,7 +7,9 @@
 #include <algorithm>
 #include <numeric>
 
+#include "ash/accelerators/accelerator_controller_impl.h"
 #include "ash/accessibility/accessibility_controller_impl.h"
+#include "ash/public/cpp/accelerators.h"
 #include "ash/shell.h"
 #include "ash/style/ash_color_provider.h"
 #include "ash/system/tray/tray_constants.h"
@@ -20,6 +22,7 @@
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/aura/env.h"
 #include "ui/aura/window.h"
+#include "ui/base/accelerators/accelerator.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/layer_type.h"
@@ -141,6 +144,13 @@
 
 void TrayBubbleView::Delegate::HideBubble(const TrayBubbleView* bubble_view) {}
 
+absl::optional<AcceleratorAction>
+TrayBubbleView::Delegate::GetAcceleratorAction() const {
+  // TODO(crbug/1234891) Make this a pure virtual function so all
+  // bubble delegates need to specify accelerator actions.
+  return absl::nullopt;
+}
+
 TrayBubbleView::InitParams::InitParams() = default;
 
 TrayBubbleView::InitParams::InitParams(const InitParams& other) = default;
@@ -200,11 +210,21 @@
   // To provide consistent behavior with a menu, process accelerator as a menu
   // is open if the event is not handled by the widget.
   ui::Accelerator accelerator(*event);
-  ViewsDelegate::ProcessMenuAcceleratorResult result =
-      ViewsDelegate::GetInstance()->ProcessAcceleratorWhileMenuShowing(
-          accelerator);
-  if (result == ViewsDelegate::ProcessMenuAcceleratorResult::CLOSE_MENU)
+
+  // crbug/1212857 Immediately close the bubble if the accelerator action
+  // is going to do it and do not process the accelerator. If the accelerator
+  // action is executed asynchronously it will execute after the bubble has
+  // already been closed and it will result in the accelerator action reopening
+  // the bubble.
+  if (tray_bubble_view_->GetAcceleratorAction().has_value() &&
+      AcceleratorControllerImpl::Get()->DoesAcceleratorMatchAction(
+          ui::Accelerator(*event),
+          tray_bubble_view_->GetAcceleratorAction().value())) {
     tray_bubble_view_->CloseBubbleView();
+  } else {
+    ViewsDelegate::GetInstance()->ProcessAcceleratorWhileMenuShowing(
+        accelerator);
+  }
 }
 
 TrayBubbleView::TrayBubbleView(const InitParams& init_params)
@@ -324,6 +344,10 @@
   return bubble_border_ ? bubble_border_->GetInsets() : gfx::Insets();
 }
 
+absl::optional<AcceleratorAction> TrayBubbleView::GetAcceleratorAction() const {
+  return delegate_->GetAcceleratorAction();
+}
+
 void TrayBubbleView::ResetDelegate() {
   reroute_event_handler_.reset();
 
diff --git a/ash/system/tray/tray_bubble_view.h b/ash/system/tray/tray_bubble_view.h
index da80d38..5690ee8b 100644
--- a/ash/system/tray/tray_bubble_view.h
+++ b/ash/system/tray/tray_bubble_view.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "ash/ash_export.h"
+#include "ash/public/cpp/accelerators.h"
 #include "ash/public/cpp/shelf_types.h"
 #include "ash/system/status_area_widget.h"
 #include "base/macros.h"
@@ -67,6 +68,10 @@
     // child view was closed).
     virtual void HideBubble(const TrayBubbleView* bubble_view);
 
+    // Returns the accelerator action associated with the delegate's bubble
+    // view.
+    virtual absl::optional<AcceleratorAction> GetAcceleratorAction() const;
+
    private:
     DISALLOW_COPY_AND_ASSIGN(Delegate);
   };
@@ -129,6 +134,9 @@
   // Returns the border insets. Called by TrayEventFilter.
   gfx::Insets GetBorderInsets() const;
 
+  // Returns the accelerator action associated with this bubble view.
+  absl::optional<AcceleratorAction> GetAcceleratorAction() const;
+
   // Called when the delegate is destroyed. This must be called before the
   // delegate is actually destroyed. TrayBubbleView will do clean up in
   // ResetDelegate.
diff --git a/ash/system/unified/unified_system_tray.cc b/ash/system/unified/unified_system_tray.cc
index c8dd06a..207a7be 100644
--- a/ash/system/unified/unified_system_tray.cc
+++ b/ash/system/unified/unified_system_tray.cc
@@ -349,6 +349,11 @@
   return "UnifiedSystemTray";
 }
 
+absl::optional<AcceleratorAction> UnifiedSystemTray::GetAcceleratorAction()
+    const {
+  return absl::make_optional(TOGGLE_SYSTEM_TRAY_BUBBLE);
+}
+
 void UnifiedSystemTray::OnShelfConfigUpdated() {
   // Ensure the margin is updated correctly depending on whether dense shelf
   // is currently shown or not.
diff --git a/ash/system/unified/unified_system_tray.h b/ash/system/unified/unified_system_tray.h
index 18b3a10..6cab5ae 100644
--- a/ash/system/unified/unified_system_tray.h
+++ b/ash/system/unified/unified_system_tray.h
@@ -9,6 +9,7 @@
 #include <memory>
 
 #include "ash/ash_export.h"
+#include "ash/public/cpp/accelerators.h"
 #include "ash/public/cpp/shelf_config.h"
 #include "ash/system/tray/tray_background_view.h"
 #include "base/time/time.h"
@@ -152,6 +153,7 @@
   bool ShouldEnableExtraKeyboardAccessibility() override;
   views::Widget* GetBubbleWidget() const override;
   const char* GetClassName() const override;
+  absl::optional<AcceleratorAction> GetAcceleratorAction() const override;
 
   // ShelfConfig::Observer:
   void OnShelfConfigUpdated() override;
diff --git a/ash/test/ash_test_views_delegate.cc b/ash/test/ash_test_views_delegate.cc
index 6ae77b5..8c1b798a 100644
--- a/ash/test/ash_test_views_delegate.cc
+++ b/ash/test/ash_test_views_delegate.cc
@@ -4,11 +4,20 @@
 
 #include "ash/test/ash_test_views_delegate.h"
 
+#include "ash/accelerators/accelerator_controller_impl.h"
 #include "ash/shell.h"
 #include "chromeos/ui/frame/frame_utils.h"
 
 namespace ash {
 
+namespace {
+
+void ProcessAcceleratorNow(const ui::Accelerator& accelerator) {
+  ash::AcceleratorController::Get()->Process(accelerator);
+}
+
+}  // namespace
+
 AshTestViewsDelegate::AshTestViewsDelegate() = default;
 
 AshTestViewsDelegate::~AshTestViewsDelegate() = default;
@@ -28,10 +37,14 @@
 views::TestViewsDelegate::ProcessMenuAcceleratorResult
 AshTestViewsDelegate::ProcessAcceleratorWhileMenuShowing(
     const ui::Accelerator& accelerator) {
-  if (accelerator == close_menu_accelerator_)
-    return ProcessMenuAcceleratorResult::CLOSE_MENU;
+  if (ash::AcceleratorController::Get()->OnMenuAccelerator(accelerator)) {
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(ProcessAcceleratorNow, accelerator));
+    return views::ViewsDelegate::ProcessMenuAcceleratorResult::CLOSE_MENU;
+  }
 
-  return ProcessMenuAcceleratorResult::LEAVE_MENU_OPEN;
+  ProcessAcceleratorNow(accelerator);
+  return views::ViewsDelegate::ProcessMenuAcceleratorResult::LEAVE_MENU_OPEN;
 }
 
 }  // namespace ash
diff --git a/ash/webui/shortcut_customization_ui/resources/BUILD.gn b/ash/webui/shortcut_customization_ui/resources/BUILD.gn
index b5611a4..2091ad4 100644
--- a/ash/webui/shortcut_customization_ui/resources/BUILD.gn
+++ b/ash/webui/shortcut_customization_ui/resources/BUILD.gn
@@ -92,6 +92,8 @@
 js_library("accelerator_row") {
   deps = [
     ":accelerator_view",
+    ":icons",
+    ":mojo_interface_provider",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
 }
diff --git a/ash/webui/shortcut_customization_ui/resources/accelerator_row.html b/ash/webui/shortcut_customization_ui/resources/accelerator_row.html
index f25d23c..c33046b 100644
--- a/ash/webui/shortcut_customization_ui/resources/accelerator_row.html
+++ b/ash/webui/shortcut_customization_ui/resources/accelerator_row.html
@@ -26,4 +26,7 @@
       </accelerator-view>
     </template>
   </div>
+  <div id="lockIconContainer" hidden="[[!isLocked_]]">
+    <iron-icon icon="shortcut-customization:lock"></iron-icon>
+  </div>
 </div>
\ No newline at end of file
diff --git a/ash/webui/shortcut_customization_ui/resources/accelerator_row.js b/ash/webui/shortcut_customization_ui/resources/accelerator_row.js
index 29aeb0e7..38225fea 100644
--- a/ash/webui/shortcut_customization_ui/resources/accelerator_row.js
+++ b/ash/webui/shortcut_customization_ui/resources/accelerator_row.js
@@ -3,13 +3,17 @@
 // found in the LICENSE file.
 
 import './accelerator_view.js'
+import './icons.js';
 import './shortcut_customization_shared_css.js';
 
 import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
+import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 
 import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {AcceleratorInfo} from './shortcut_types.js';
+import {getShortcutProvider} from './mojo_interface_provider.js';
+import {AcceleratorInfo, AcceleratorSource, ShortcutProviderInterface} from './shortcut_types.js';
+
 
 /**
  * @fileoverview
@@ -37,22 +41,50 @@
       acceleratorInfos: {
         type: Array,
         value: () => {},
-      }
+      },
+
+      /** @private */
+      isLocked_: {
+        type: Boolean,
+        value: false,
+      },
+
+      /** @type {!AcceleratorSource} */
+      source: {
+        type: Number,
+        value: 0,
+        observer: 'onSourceChanged_',
+      },
     }
   }
 
+  constructor() {
+    super();
+    /** @private {!ShortcutProviderInterface} */
+    this.shortcutInterfaceProvider_ = getShortcutProvider();
+  }
+
   /** @override */
   connectedCallback() {
     super.connectedCallback();
-    // TODO(jimmyxgong): Only add the click event listener if the accelerator is
-    // not locked.
-    this.addEventListener('click', () => this.showDialog_());
   }
 
   /** @override */
   disconnectedCallback() {
     super.disconnectedCallback();
-    this.removeEventListener('click', () => this.showDialog_());
+    if (!this.isLocked_) {
+      this.removeEventListener('click', () => this.showDialog_());
+    }
+  }
+
+  /** @protected */
+  onSourceChanged_() {
+    this.shortcutInterfaceProvider_.isMutable(this.source).then((result) => {
+      this.isLocked_ = !result;
+      if (!this.isLocked_) {
+        this.addEventListener('click', () => this.showDialog_());
+      }
+    });
   }
 
   /** @private */
diff --git a/ash/wm/desks/persistent_desks_bar_button.cc b/ash/wm/desks/persistent_desks_bar_button.cc
index 788880e..6cc7003a 100644
--- a/ash/wm/desks/persistent_desks_bar_button.cc
+++ b/ash/wm/desks/persistent_desks_bar_button.cc
@@ -31,6 +31,10 @@
   SetShouldPaintBackground(desk_ == DesksController::Get()->active_desk());
 }
 
+void PersistentDesksBarDeskButton::UpdateText(std::u16string name) {
+  SetText(std::move(name));
+}
+
 void PersistentDesksBarDeskButton::OnButtonPressed() {
   DesksController::Get()->ActivateDesk(desk_,
                                        DesksSwitchSource::kPersistentDesksBar);
diff --git a/ash/wm/desks/persistent_desks_bar_button.h b/ash/wm/desks/persistent_desks_bar_button.h
index 7f18a98..fb76e00 100644
--- a/ash/wm/desks/persistent_desks_bar_button.h
+++ b/ash/wm/desks/persistent_desks_bar_button.h
@@ -5,6 +5,8 @@
 #ifndef ASH_WM_DESKS_PERSISTENT_DESKS_BAR_BUTTON_H_
 #define ASH_WM_DESKS_PERSISTENT_DESKS_BAR_BUTTON_H_
 
+#include <string>
+
 #include "ash/wm/desks/zero_state_button.h"
 #include "ui/gfx/vector_icon_types.h"
 #include "ui/views/controls/button/image_button.h"
@@ -24,6 +26,7 @@
   ~PersistentDesksBarDeskButton() override = default;
 
   const Desk* desk() const { return desk_; }
+  void UpdateText(std::u16string name);
 
  private:
   // DeskButtonBase:
diff --git a/ash/wm/desks/persistent_desks_bar_controller.cc b/ash/wm/desks/persistent_desks_bar_controller.cc
index fd10e79..406921fc 100644
--- a/ash/wm/desks/persistent_desks_bar_controller.cc
+++ b/ash/wm/desks/persistent_desks_bar_controller.cc
@@ -134,7 +134,10 @@
 
 void PersistentDesksBarController::OnDeskNameChanged(
     const Desk* desk,
-    const std::u16string& new_name) {}
+    const std::u16string& new_name) {
+  if (persistent_desks_bar_view_)
+    persistent_desks_bar_view_->RefreshDeskButtons();
+}
 
 void PersistentDesksBarController::OnTabletModeStarted() {
   DestroyBarWidget();
diff --git a/ash/wm/desks/persistent_desks_bar_view.cc b/ash/wm/desks/persistent_desks_bar_view.cc
index 36ce1c7a..768c4a5 100644
--- a/ash/wm/desks/persistent_desks_bar_view.cc
+++ b/ash/wm/desks/persistent_desks_bar_view.cc
@@ -50,6 +50,7 @@
                      });
     if (iter != to_be_removed.end()) {
       (*iter)->SetShouldPaintBackground(desk->is_active());
+      (*iter)->UpdateText(desk->name());
       to_be_removed.erase(iter);
       continue;
     }
diff --git a/ash/wm/desks/persistent_desks_bar_view.h b/ash/wm/desks/persistent_desks_bar_view.h
index b64a91f..430a6c739 100644
--- a/ash/wm/desks/persistent_desks_bar_view.h
+++ b/ash/wm/desks/persistent_desks_bar_view.h
@@ -26,9 +26,10 @@
   PersistentDesksBarView& operator=(const PersistentDesksBarView&) = delete;
   ~PersistentDesksBarView() override;
 
-  // Updates `desk_buttons_` on desk addition, removal and activation changes.
-  // It should just include desk buttons for all of the current desks and keep
-  // the background of the desk button for current active desk be painted.
+  // Updates `desk_buttons_` on desk addition, removal, activation changes and
+  // desk name changes. It should just include desk buttons for all of the
+  // current desks with current names and keep the background of the desk button
+  // for current active desk be painted.
   void RefreshDeskButtons();
 
  private:
diff --git a/ash/wm/lock_state_controller.cc b/ash/wm/lock_state_controller.cc
index f3733ec..d156d10 100644
--- a/ash/wm/lock_state_controller.cc
+++ b/ash/wm/lock_state_controller.cc
@@ -18,7 +18,6 @@
 #include "ash/shell.h"
 #include "ash/shell_delegate.h"
 #include "ash/shutdown_reason.h"
-#include "ash/utility/occlusion_tracker_pauser.h"
 #include "ash/wallpaper/wallpaper_controller_impl.h"
 #include "ash/wallpaper/wallpaper_widget_controller.h"
 #include "ash/wm/session_state_animator.h"
@@ -248,8 +247,6 @@
 
   system_is_locked_ = locked;
 
-  Shell::Get()->occlusion_tracker_pauser()->PauseUntilAnimationsEnd();
-
   if (locked) {
     StartPostLockAnimation();
 
diff --git a/build/BUILD.gn b/build/BUILD.gn
index 51ef9b0..b1724eb6 100644
--- a/build/BUILD.gn
+++ b/build/BUILD.gn
@@ -44,3 +44,19 @@
     "IS_CHROMEOS_ASH=$is_chromeos_ash",
   ]
 }
+
+buildflag_header("os_buildflags") {
+  header = "os_buildflags.h"
+  flags = [
+    "IS_ANDROID=$is_android",
+    "IS_CHROMEOS=$is_chromeos",
+    "IS_FUCHSIA=$is_fuchsia",
+    "IS_IOS=$is_ios",
+    "IS_LINUX=$is_linux",
+    "IS_MAC=$is_mac",
+    "IS_NACL=$is_nacl",
+    "IS_WIN=$is_win",
+    "IS_APPLE=$is_apple",
+    "IS_POSIX=$is_posix",
+  ]
+}
diff --git a/build/config/fuchsia/generate_runner_scripts.gni b/build/config/fuchsia/generate_runner_scripts.gni
index f6180755..e5e0bf8 100644
--- a/build/config/fuchsia/generate_runner_scripts.gni
+++ b/build/config/fuchsia/generate_runner_scripts.gni
@@ -156,6 +156,7 @@
         "${boot_image_root}/qemu/qemu-kernel.kernel",
         "${boot_image_root}/qemu/storage-full.blk",
         "${boot_image_root}/qemu/zircon-a.zbi",
+        "//third_party/fuchsia-sdk/sdk/tools/${test_host_cpu}/fvdl",
         "//third_party/qemu-${host_os}-${test_host_cpu}/",
         "${aemu_root}/",
       ]
diff --git a/build/config/ios/ios_sdk_overrides.gni b/build/config/ios/ios_sdk_overrides.gni
index 4333a2e4..12d52521d 100644
--- a/build/config/ios/ios_sdk_overrides.gni
+++ b/build/config/ios/ios_sdk_overrides.gni
@@ -7,11 +7,11 @@
 
 declare_args() {
   # Version of iOS that we're targeting.
-  ios_deployment_target = "13.0"
+  ios_deployment_target = "14.0"
 }
 
 # Always assert that ios_deployment_target is used on non-iOS platforms to
 # prevent unused args warnings.
 if (!is_ios) {
-  assert(ios_deployment_target == "13.0" || true)
+  assert(ios_deployment_target == "14.0" || true)
 }
diff --git a/build/config/ui.gni b/build/config/ui.gni
index 280ecc8..f267598 100644
--- a/build/config/ui.gni
+++ b/build/config/ui.gni
@@ -46,7 +46,7 @@
 
 assert(!use_glib || (is_linux && !is_chromecast))
 
-use_atk = is_linux && !is_chromecast && use_glib
+use_atk = is_linux && !is_chromecast && use_glib && current_toolchain == default_toolchain
 
 # Whether using Xvfb to provide a display server for a test might be
 # necessary.
diff --git a/chrome/android/features/cablev2_authenticator/java/src/org/chromium/chrome/browser/webauth/authenticator/CableAuthenticator.java b/chrome/android/features/cablev2_authenticator/java/src/org/chromium/chrome/browser/webauth/authenticator/CableAuthenticator.java
index 71021c2..8e9802a 100644
--- a/chrome/android/features/cablev2_authenticator/java/src/org/chromium/chrome/browser/webauth/authenticator/CableAuthenticator.java
+++ b/chrome/android/features/cablev2_authenticator/java/src/org/chromium/chrome/browser/webauth/authenticator/CableAuthenticator.java
@@ -14,6 +14,7 @@
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
+import android.provider.Settings;
 
 import com.google.android.gms.fido.Fido;
 import com.google.android.gms.fido.common.Transport;
@@ -481,8 +482,9 @@
         }
     }
 
-    static String getName() {
-        final String name = BluetoothAdapter.getDefaultAdapter().getName();
+    String getName() {
+        final String name = Settings.Global.getString(
+                mContext.getContentResolver(), Settings.Global.DEVICE_NAME);
         if (name != null && name.length() > 0) {
             return name;
         }
diff --git a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceCoordinator.java b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceCoordinator.java
index 628ec96..eda8d38 100644
--- a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceCoordinator.java
+++ b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceCoordinator.java
@@ -7,7 +7,6 @@
 import android.app.Activity;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
-import android.view.View;
 import android.view.ViewGroup;
 
 import androidx.annotation.NonNull;
@@ -167,9 +166,8 @@
                 this, mExploreSurfaceNavigationDelegate, profile, isPlaceholderShown,
                 bottomSheetController, mShareDelegateSupplier, scrollableContainerDelegate,
                 launchOrigin, PrivacyPreferencesManagerImpl.getInstance(), toolbarSupplier,
-                feedLaunchReliabilityLoggingState, swipeRefreshLayout);
+                feedLaunchReliabilityLoggingState, swipeRefreshLayout, /*overScrollDisabled=*/true);
         feedSurfaceCoordinator.getView().setId(R.id.start_surface_explore_view);
-        feedSurfaceCoordinator.getRecyclerView().setOverScrollMode(View.OVER_SCROLL_NEVER);
         return feedSurfaceCoordinator;
         // TODO(crbug.com/982018): Customize surface background for incognito and dark mode.
         // TODO(crbug.com/982018): Hide signin promo UI in incognito mode.
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
index cd7db2c..fce065c6 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
@@ -106,6 +106,7 @@
     private final WindowAndroid mWindowAndroid;
     private final Supplier<ShareDelegate> mShareSupplier;
     private final Handler mHandler;
+    private final boolean mOverScrollDisabled;
 
     private UiConfig mUiConfig;
     private FrameLayout mRootView;
@@ -158,8 +159,9 @@
 
     private FeedSwipeRefreshLayout mSwipeRefreshLayout;
 
-    @IntDef({StreamTabId.FOR_YOU, StreamTabId.FOLLOWING})
+    @IntDef({StreamTabId.DEFAULT, StreamTabId.FOR_YOU, StreamTabId.FOLLOWING})
     public @interface StreamTabId {
+        int DEFAULT = -1;
         int FOR_YOU = 0;
         int FOLLOWING = 1;
     };
@@ -259,6 +261,7 @@
      * @param toolbarSupplier Supplies the {@link Toolbar}.
      * @param FeedLaunchReliabilityLoggingState Holds the state for feed surface creation.
      * @param swipeRefreshLayout The layout to support pull-to-refresh.
+     * @param overScrollDisabled Whether the overscroll effect is disabled.
      */
     public FeedSurfaceCoordinator(Activity activity, SnackbarManager snackbarManager,
             WindowAndroid windowAndroid, @Nullable SnapScrollHelper snapScrollHelper,
@@ -272,7 +275,7 @@
             PrivacyPreferencesManagerImpl privacyPreferencesManager,
             @NonNull Supplier<Toolbar> toolbarSupplier,
             FeedLaunchReliabilityLoggingState launchReliabilityLoggingState,
-            @Nullable FeedSwipeRefreshLayout swipeRefreshLayout) {
+            @Nullable FeedSwipeRefreshLayout swipeRefreshLayout, boolean overScrollDisabled) {
         FeedSurfaceTracker.getInstance().initServiceBridge();
         mActivity = activity;
         mSnackbarManager = snackbarManager;
@@ -290,6 +293,7 @@
         mPrivacyPreferencesManager = privacyPreferencesManager;
         mToolbarSupplier = toolbarSupplier;
         mSwipeRefreshLayout = swipeRefreshLayout;
+        mOverScrollDisabled = overScrollDisabled;
 
         Resources resources = mActivity.getResources();
         mDefaultMarginPixels = mActivity.getResources().getDimensionPixelSize(
@@ -488,7 +492,7 @@
     @StreamTabId
     int getTabIdFromLaunchOrigin(@NewTabPageLaunchOrigin int launchOrigin) {
         return launchOrigin == NewTabPageLaunchOrigin.WEB_FEED ? StreamTabId.FOLLOWING
-                                                               : StreamTabId.FOR_YOU;
+                                                               : StreamTabId.DEFAULT;
     }
 
     private RecyclerView setUpView() {
@@ -620,6 +624,10 @@
         // Explicitly request focus on the scroll container to avoid UrlBar being focused after
         // the scroll container for policy is removed.
         mRecyclerView.requestFocus();
+
+        if (mOverScrollDisabled) {
+            mRecyclerView.setOverScrollMode(View.OVER_SCROLL_NEVER);
+        }
     }
 
     /**
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java
index 489b9fe..875fe2a 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java
@@ -161,6 +161,7 @@
             }
 
             maybeLogLaunchFinished(DiscoverLaunchResult.SWITCHED_FEED_TABS);
+            getPrefService().setInteger(Pref.LAST_SEEN_FEED_TYPE, index);
             bindStream(mTabToStreamMap.get(index));
         }
     }
@@ -277,7 +278,6 @@
         mSigninManager = IdentityServicesProvider.get().getSigninManager(
                 Profile.getLastUsedRegularProfile());
         mPageNavigationDelegate = pageNavigationDelegate;
-        mRestoreTabId = openingTabId;
 
         if (sTestPrefChangeRegistar != null) {
             mPrefChangeRegistrar = sTestPrefChangeRegistar;
@@ -287,6 +287,12 @@
         mHasHeader = headerModel != null;
         mPrefChangeRegistrar.addObserver(Pref.ENABLE_SNIPPETS, this::updateContent);
 
+        if (openingTabId == FeedSurfaceCoordinator.StreamTabId.DEFAULT) {
+            mRestoreTabId = getPrefService().getInteger(Pref.LAST_SEEN_FEED_TYPE);
+        } else {
+            mRestoreTabId = openingTabId;
+        }
+
         // Check that there is a navigation delegate when using the feed header menu.
         if (mPageNavigationDelegate == null) {
             assert false : "Need navigation delegate for header menu";
@@ -396,6 +402,9 @@
      * being shown again with a different {@link NewTabPageLaunchOrigin}.
      */
     void setTabId(@FeedSurfaceCoordinator.StreamTabId int tabId) {
+        if (tabId == FeedSurfaceCoordinator.StreamTabId.DEFAULT) {
+            tabId = getPrefService().getInteger(Pref.LAST_SEEN_FEED_TYPE);
+        }
         if (mTabToStreamMap.size() <= tabId) tabId = 0;
         mSectionHeaderModel.set(SectionHeaderListProperties.CURRENT_TAB_INDEX_KEY, tabId);
     }
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStream.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStream.java
index d985c3bd..fc4bd52 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStream.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStream.java
@@ -33,6 +33,7 @@
 import org.chromium.chrome.browser.feed.FeedReliabilityLoggingBridge;
 import org.chromium.chrome.browser.feed.FeedServiceBridge;
 import org.chromium.chrome.browser.feed.FeedSurfaceMediator;
+import org.chromium.chrome.browser.feed.FeedUma;
 import org.chromium.chrome.browser.feed.NtpListContentManager;
 import org.chromium.chrome.browser.feed.shared.ScrollTracker;
 import org.chromium.chrome.browser.feed.shared.stream.Stream;
@@ -711,9 +712,23 @@
 
     /**
      * Attempts to load more content if it can be triggered.
+     *
+     * <p>This method uses the default or Finch configured load more lookahead trigger.
+     *
      * @return true if loading more content can be triggered.
      */
     boolean maybeLoadMore() {
+        return maybeLoadMore(mLoadMoreTriggerLookahead);
+    }
+
+    /**
+     * Attempts to load more content if it can be triggered.
+     * @param lookaheadTrigger The threshold of off-screen cards below which the feed should attempt
+     *         to load more content. I.e., if there are less than or equal to |lookaheadTrigger|
+     *         cards left to show the user, then the feed should load more cards.
+     * @return true if loading more content can be triggered.
+     */
+    private boolean maybeLoadMore(int lookaheadTrigger) {
         // Checks if we've been unbinded.
         if (mRecyclerView == null) {
             return false;
@@ -723,15 +738,23 @@
         if (layoutManager == null) {
             return false;
         }
+
+        // Check if the layout manager is initialized.
         int totalItemCount = layoutManager.getItemCount();
+        if (totalItemCount < 0) {
+            return false;
+        }
+
         int lastVisibleItem = layoutManager.findLastVisibleItemPosition();
-        if (totalItemCount - lastVisibleItem > mLoadMoreTriggerLookahead) {
+        int numItemsRemaining = totalItemCount - lastVisibleItem;
+        if (numItemsRemaining > lookaheadTrigger) {
             return false;
         }
 
         // Starts to load more content if not yet.
         if (!mIsLoadingMoreContent) {
             mIsLoadingMoreContent = true;
+            FeedUma.recordFeedLoadMoreTrigger(getSectionType(), totalItemCount, numItemsRemaining);
             // The native loadMore() call may immediately result in onStreamUpdated(), which can
             // result in a crash if maybeLoadMore() is being called in response to certain events.
             // Use postTask to avoid this.
@@ -786,11 +809,15 @@
                 }
             }
         }
-
         updateContentsInPlace(newContentList);
 
         // TODO(iwells): Look into alternatives to View.post() that specifically wait for rendering.
         mRecyclerView.post(mReliabilityLoggingBridge::onStreamUpdateFinished);
+
+        // If all of the cards fit on the screen, load more content. The view
+        // may not be scrollable, preventing the user from otherwise triggering
+        // load more.
+        maybeLoadMore(/*lookaheadTrigger=*/0);
     }
 
     private NtpListContentManager.FeedContent createContentFromSlice(FeedUiProto.Slice slice) {
diff --git a/chrome/android/java/res/layout/share_sheet_content.xml b/chrome/android/java/res/layout/share_sheet_content.xml
index 0686281..0b6b829 100644
--- a/chrome/android/java/res/layout/share_sheet_content.xml
+++ b/chrome/android/java/res/layout/share_sheet_content.xml
@@ -44,7 +44,7 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_toEndOf="@id/image_preview"
-        android:layout_toStartOf="@id/image_preview_link"
+        android:layout_toStartOf="@id/link_toggle_view"
         android:maxLines="1"
         android:minHeight="18dp"
         android:paddingEnd="16dp"
@@ -60,7 +60,7 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_toEndOf="@id/image_preview"
-        android:layout_toStartOf="@id/image_preview_link"
+        android:layout_toStartOf="@id/link_toggle_view"
         android:layout_centerVertical="true"
         android:maxLines="1"
         android:minHeight="18dp"
@@ -71,7 +71,7 @@
         android:textAppearance="@style/TextAppearance.TextMedium.Primary"/>
 
       <org.chromium.ui.widget.ChromeImageView
-          android:id="@+id/image_preview_link"
+          android:id="@+id/link_toggle_view"
           android:layout_height="@dimen/sharing_hub_preview_icon_size"
           android:layout_width="@dimen/sharing_hub_preview_icon_size"
           android:layout_alignParentEnd="true"
diff --git a/chrome/android/java/res/xml/privacy_preferences.xml b/chrome/android/java/res/xml/privacy_preferences.xml
index cb7165279..5d2c6cc 100644
--- a/chrome/android/java/res/xml/privacy_preferences.xml
+++ b/chrome/android/java/res/xml/privacy_preferences.xml
@@ -17,6 +17,10 @@
         android:summary="@string/prefs_safe_browsing_summary"
         android:fragment="org.chromium.chrome.browser.safe_browsing.settings.SafeBrowsingSettingsFragment"/>
     <org.chromium.components.browser_ui.settings.ChromeSwitchPreference
+        android:key="https_first_mode"
+        android:title="@string/settings_https_first_mode_title"
+        android:summary="@string/settings_https_first_mode_summary"/>
+    <org.chromium.components.browser_ui.settings.ChromeSwitchPreference
         android:key="can_make_payment"
         android:title="@string/can_make_payment_title"
         android:summary="@string/settings_can_make_payment_toggle_label"/>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorSurfaceManagerImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorSurfaceManagerImpl.java
index 953c97c..45d540a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorSurfaceManagerImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorSurfaceManagerImpl.java
@@ -195,6 +195,9 @@
         // when it is safe to do that.
         disownClientSurface(mOwnedByClient, false);
 
+        // `disownClientSurface` may recursively shutdown.
+        if (mRequestedByClient == null) return;
+
         // The client now owns |mRequestedByClient|.  Notify it that it's ready.
         mOwnedByClient = mRequestedByClient;
         mClient.surfaceCreated(mOwnedByClient.surfaceHolder().getSurface());
@@ -302,6 +305,9 @@
         assert mOwnedByClient != state;
         disownClientSurface(mOwnedByClient, false);
 
+        // `disownClientSurface` may recursively shutdown.
+        if (mRequestedByClient == null) return;
+
         // The client now owns this surface, so notify it.
         mOwnedByClient = mRequestedByClient;
         mClient.surfaceCreated(mOwnedByClient.surfaceHolder().getSurface());
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
index 62ea85b..3f1a5d1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
@@ -467,7 +467,8 @@
                 NewTabPageUtils.decodeOriginFromNtpUrl(url),
                 PrivacyPreferencesManagerImpl.getInstance(), mToolbarSupplier,
                 new FeedLaunchReliabilityLoggingState(SurfaceType.NEW_TAB_PAGE, mConstructedTimeNs),
-                FeedSwipeRefreshLayout.create(activity));
+                FeedSwipeRefreshLayout.create(activity),
+                /* overScrollDisabled= */ false);
 
         // Record the timestamp at which the new tab page's construction started.
         uma.trackTimeToFirstDraw(mFeedSurfaceProvider.getView(), mConstructedTimeNs);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/privacy/settings/PrivacySettings.java b/chrome/android/java/src/org/chromium/chrome/browser/privacy/settings/PrivacySettings.java
index 8f178e7b..69bcd7e5e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/privacy/settings/PrivacySettings.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/privacy/settings/PrivacySettings.java
@@ -17,6 +17,7 @@
 
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.feedback.HelpAndFeedbackLauncherImpl;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.preferences.Pref;
 import org.chromium.chrome.browser.privacy.secure_dns.SecureDnsSettings;
 import org.chromium.chrome.browser.privacy_sandbox.PrivacySandboxBridge;
@@ -48,6 +49,7 @@
         extends PreferenceFragmentCompat implements Preference.OnPreferenceChangeListener {
     private static final String PREF_CAN_MAKE_PAYMENT = "can_make_payment";
     private static final String PREF_NETWORK_PREDICTIONS = "preload_pages";
+    private static final String PREF_HTTPS_FIRST_MODE = "https_first_mode";
     private static final String PREF_SECURE_DNS = "secure_dns";
     private static final String PREF_USAGE_STATS = "usage_stats_reporting";
     private static final String PREF_DO_NOT_TRACK = "do_not_track";
@@ -106,6 +108,15 @@
         networkPredictionPref.setOnPreferenceChangeListener(this);
         networkPredictionPref.setManagedPreferenceDelegate(mManagedPreferenceDelegate);
 
+        ChromeSwitchPreference httpsFirstModePref =
+                (ChromeSwitchPreference) findPreference(PREF_HTTPS_FIRST_MODE);
+        httpsFirstModePref.setVisible(
+                ChromeFeatureList.isEnabled(ChromeFeatureList.HTTPS_FIRST_MODE));
+        httpsFirstModePref.setOnPreferenceChangeListener(this);
+        httpsFirstModePref.setManagedPreferenceDelegate(mManagedPreferenceDelegate);
+        httpsFirstModePref.setChecked(UserPrefs.get(Profile.getLastUsedRegularProfile())
+                                              .getBoolean(Pref.HTTPS_ONLY_MODE_ENABLED));
+
         Preference secureDnsPref = findPreference(PREF_SECURE_DNS);
         secureDnsPref.setVisible(SecureDnsSettings.isUiEnabled());
 
@@ -148,6 +159,9 @@
         } else if (PREF_NETWORK_PREDICTIONS.equals(key)) {
             PrivacyPreferencesManagerImpl.getInstance().setNetworkPredictionEnabled(
                     (boolean) newValue);
+        } else if (PREF_HTTPS_FIRST_MODE.equals(key)) {
+            UserPrefs.get(Profile.getLastUsedRegularProfile())
+                    .setBoolean(Pref.HTTPS_ONLY_MODE_ENABLED, (boolean) newValue);
         }
 
         return true;
@@ -221,6 +235,9 @@
             String key = preference.getKey();
             if (PREF_NETWORK_PREDICTIONS.equals(key)) {
                 return PrivacyPreferencesManagerImpl.getInstance().isNetworkPredictionManaged();
+            } else if (PREF_HTTPS_FIRST_MODE.equals(key)) {
+                return UserPrefs.get(Profile.getLastUsedRegularProfile())
+                        .isManagedPreference(Pref.HTTPS_ONLY_MODE_ENABLED);
             }
             return false;
         };
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinatorTest.java
index 41d7095..b8fad6d 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinatorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinatorTest.java
@@ -313,7 +313,7 @@
 
     @Test
     public void testGetTabIdFromLaunchOrigin_unknown() {
-        assertEquals(FeedSurfaceCoordinator.StreamTabId.FOR_YOU,
+        assertEquals(FeedSurfaceCoordinator.StreamTabId.DEFAULT,
                 mCoordinator.getTabIdFromLaunchOrigin(NewTabPageLaunchOrigin.UNKNOWN));
     }
 
@@ -355,7 +355,7 @@
                 NewTabPageLaunchOrigin.UNKNOWN, mPrivacyPreferencesManager,
                 ()
                         -> { return null; },
-                new FeedLaunchReliabilityLoggingState(SURFACE_TYPE, SURFACE_CREATION_TIME_NS),
-                null);
+                new FeedLaunchReliabilityLoggingState(SURFACE_TYPE, SURFACE_CREATION_TIME_NS), null,
+                false);
     }
 }
diff --git a/chrome/app/chrome_command_ids.h b/chrome/app/chrome_command_ids.h
index ef12212..49b9821 100644
--- a/chrome/app/chrome_command_ids.h
+++ b/chrome/app/chrome_command_ids.h
@@ -385,8 +385,9 @@
 #define IDC_CONTENT_CONTEXT_SHARING_SHARED_CLIPBOARD_SINGLE_DEVICE 51032
 #define IDC_CONTENT_CONTEXT_SHARING_SHARED_CLIPBOARD_MULTIPLE_DEVICES 51033
 #define IDC_CONTENT_CONTEXT_GENERATE_QR_CODE 51034
+#define IDC_CONTENT_CONTEXT_SHARING_SUBMENU 51035
 // Context menu item to show the clipboard history menu
-#define IDC_CONTENT_CLIPBOARD_HISTORY_MENU 51035
+#define IDC_CONTENT_CLIPBOARD_HISTORY_MENU 51036
 
 // Context menu items in the status tray
 #define IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND 51100
diff --git a/chrome/app/chrome_main_delegate.cc b/chrome/app/chrome_main_delegate.cc
index 36dae08..f4bd1377 100644
--- a/chrome/app/chrome_main_delegate.cc
+++ b/chrome/app/chrome_main_delegate.cc
@@ -637,25 +637,30 @@
   base::PlatformThread::InitThreadPostFieldTrial();
 #endif
 
-#if BUILDFLAG(ENABLE_GWP_ASAN_MALLOC)
-  {
-    version_info::Channel channel = chrome::GetChannel();
-    bool is_canary_dev = (channel == version_info::Channel::CANARY ||
-                          channel == version_info::Channel::DEV);
-    gwp_asan::EnableForMalloc(is_canary_dev || is_browser_process,
-                              process_type.c_str());
-  }
+  version_info::Channel channel = chrome::GetChannel();
+  bool is_canary_dev = (channel == version_info::Channel::CANARY ||
+                        channel == version_info::Channel::DEV);
+  // GWP-ASAN requires crashpad to gather alloc/dealloc stack traces, which is
+  // not always enabled on Linux/ChromeOS.
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
+  bool enable_gwp_asan = crash_reporter::IsCrashpadEnabled();
+#else
+  bool enable_gwp_asan = true;
 #endif
 
+  if (enable_gwp_asan) {
+#if BUILDFLAG(ENABLE_GWP_ASAN_MALLOC)
+    gwp_asan::EnableForMalloc(is_canary_dev || is_browser_process,
+                              process_type.c_str());
+#endif
 #if BUILDFLAG(ENABLE_GWP_ASAN_PARTITIONALLOC)
-  {
-    version_info::Channel channel = chrome::GetChannel();
-    bool is_canary_dev = (channel == version_info::Channel::CANARY ||
-                          channel == version_info::Channel::DEV);
     gwp_asan::EnableForPartitionAlloc(is_canary_dev || is_browser_process,
                                       process_type.c_str());
-  }
 #endif
+  }
+
+  ALLOW_UNUSED_LOCAL(channel);
+  ALLOW_UNUSED_LOCAL(is_canary_dev);
 
   // Start heap profiling as early as possible so it can start recording
   // memory allocations.
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 475239ee..0caaf49 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -1021,6 +1021,10 @@
             Suggest Password...
           </message>
         </if>
+
+        <message name="IDS_SHARE_MENU_TITLE" desc="The label of the menu item used to share content (links, images, etc) with others.">
+          Share
+        </message>
       </if>
 
       <if expr="not use_titlecase">
diff --git a/chrome/app/generated_resources_grd/IDS_SHARE_MENU_TITLE.png.sha1 b/chrome/app/generated_resources_grd/IDS_SHARE_MENU_TITLE.png.sha1
new file mode 100644
index 0000000..5dfb2d62
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_SHARE_MENU_TITLE.png.sha1
@@ -0,0 +1 @@
+e4be6df92a96248c4108670a98d468c8d41d1f65
\ No newline at end of file
diff --git a/chrome/app/os_settings_search_tag_strings.grdp b/chrome/app/os_settings_search_tag_strings.grdp
index 7a8f4c0..934dbefe 100644
--- a/chrome/app/os_settings_search_tag_strings.grdp
+++ b/chrome/app/os_settings_search_tag_strings.grdp
@@ -859,6 +859,21 @@
   <message name="IDS_OS_SETTINGS_TAG_APPS_MANAGEMENT_ALT1" desc="Text for search result item which, when clicked, navigates the user to app mangement settings. Alternate phrase for: 'Manage apps'">
     View installed apps
   </message>
+  <message name="IDS_OS_SETTINGS_TAG_APP_NOTIFICATIONS" desc="Text for search result item which, when clicked, navigates the user to app notification settings.">
+    Notifications
+  </message>
+  <message name="IDS_OS_SETTINGS_TAG_DO_NOT_DISTURB_TURN_ON" desc="Text for search result item which, when clicked, navigates the user to app notification settings, with toggle to silence notifications. Alternate phrase for: 'Mute Notificatons'">
+    Turn on Do Not Disturb
+  </message>
+  <message name="IDS_OS_SETTINGS_TAG_DO_NOT_DISTURB_TURN_ON_ALT1" desc="Text for search result item which, when clicked, navigates the user to app notification settings with toggle to silence notifications. Alternate phrase for: 'Turn on Do Not Disturb'">
+    Mute Notifications
+  </message>
+  <message name="IDS_OS_SETTINGS_TAG_DO_NOT_DISTURB_TURN_OFF" desc="Text for search result item which, when clicked, navigates the user to app notification settings, with toggle to silence notifications. Alternate phrase for: 'Unmute Notificatons'">
+    Turn off Do Not Disturb
+  </message>
+  <message name="IDS_OS_SETTINGS_TAG_DO_NOT_DISTURB_TURN_OFF_ALT1" desc="Text for search result item which, when clicked, navigates the user to app notification settings with toggle to silence notifications. Alternate phrase for: 'Turn off Do Not Disturb'">
+    Unmute Notifications
+  </message>
   <message name="IDS_OS_SETTINGS_TAG_REMOVE_PLAY_STORE" desc="Text for search result item which, when clicked, navigates the user to Android app settings, with a button to remove the Play Store (Android app store). Alternate phrase for: 'Uninstall Google Play Store'">
     Remove Google Play Store
   </message>
diff --git a/chrome/app/os_settings_search_tag_strings_grdp/IDS_OS_SETTINGS_TAG_APP_NOTIFICATIONS.png.sha1 b/chrome/app/os_settings_search_tag_strings_grdp/IDS_OS_SETTINGS_TAG_APP_NOTIFICATIONS.png.sha1
new file mode 100644
index 0000000..d13f27e
--- /dev/null
+++ b/chrome/app/os_settings_search_tag_strings_grdp/IDS_OS_SETTINGS_TAG_APP_NOTIFICATIONS.png.sha1
@@ -0,0 +1 @@
+39aed1652f5b8f998262a28a600f4a15e13ef03b
\ No newline at end of file
diff --git a/chrome/app/os_settings_search_tag_strings_grdp/IDS_OS_SETTINGS_TAG_DO_NOT_DISTURB_TURN_OFF.png.sha1 b/chrome/app/os_settings_search_tag_strings_grdp/IDS_OS_SETTINGS_TAG_DO_NOT_DISTURB_TURN_OFF.png.sha1
new file mode 100644
index 0000000..32369d70
--- /dev/null
+++ b/chrome/app/os_settings_search_tag_strings_grdp/IDS_OS_SETTINGS_TAG_DO_NOT_DISTURB_TURN_OFF.png.sha1
@@ -0,0 +1 @@
+f2209176fe42dcedfa01a10b461f69c3517410f8
\ No newline at end of file
diff --git a/chrome/app/os_settings_search_tag_strings_grdp/IDS_OS_SETTINGS_TAG_DO_NOT_DISTURB_TURN_OFF_ALT1.png.sha1 b/chrome/app/os_settings_search_tag_strings_grdp/IDS_OS_SETTINGS_TAG_DO_NOT_DISTURB_TURN_OFF_ALT1.png.sha1
new file mode 100644
index 0000000..f19e383
--- /dev/null
+++ b/chrome/app/os_settings_search_tag_strings_grdp/IDS_OS_SETTINGS_TAG_DO_NOT_DISTURB_TURN_OFF_ALT1.png.sha1
@@ -0,0 +1 @@
+640e295a45d401a9e5b08cf01e3ae71428b4b4c9
\ No newline at end of file
diff --git a/chrome/app/os_settings_search_tag_strings_grdp/IDS_OS_SETTINGS_TAG_DO_NOT_DISTURB_TURN_ON.png.sha1 b/chrome/app/os_settings_search_tag_strings_grdp/IDS_OS_SETTINGS_TAG_DO_NOT_DISTURB_TURN_ON.png.sha1
new file mode 100644
index 0000000..17e3965
--- /dev/null
+++ b/chrome/app/os_settings_search_tag_strings_grdp/IDS_OS_SETTINGS_TAG_DO_NOT_DISTURB_TURN_ON.png.sha1
@@ -0,0 +1 @@
+4dbe8fd1515a04e647c74bc7f57d802047cde35f
\ No newline at end of file
diff --git a/chrome/app/os_settings_search_tag_strings_grdp/IDS_OS_SETTINGS_TAG_DO_NOT_DISTURB_TURN_ON_ALT1.png.sha1 b/chrome/app/os_settings_search_tag_strings_grdp/IDS_OS_SETTINGS_TAG_DO_NOT_DISTURB_TURN_ON_ALT1.png.sha1
new file mode 100644
index 0000000..b0937422
--- /dev/null
+++ b/chrome/app/os_settings_search_tag_strings_grdp/IDS_OS_SETTINGS_TAG_DO_NOT_DISTURB_TURN_ON_ALT1.png.sha1
@@ -0,0 +1 @@
+a7464222e52ebb07b83e8254b02d552e846670da
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index aaf78e1..030e4c1 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -451,6 +451,8 @@
     "download/drag_download_item.h",
     "download/mixed_content_download_blocking.cc",
     "download/mixed_content_download_blocking.h",
+    "download/notification/multi_profile_download_notifier.cc",
+    "download/notification/multi_profile_download_notifier.h",
     "download/offline_item_utils.cc",
     "download/offline_item_utils.h",
     "download/save_package_file_picker.cc",
@@ -4089,6 +4091,8 @@
       "sharing/click_to_call/click_to_call_metrics.h",
       "sharing/click_to_call/click_to_call_ui_controller.cc",
       "sharing/click_to_call/click_to_call_ui_controller.h",
+      "sharing/share_submenu_model.cc",
+      "sharing/share_submenu_model.h",
       "sharing/shared_clipboard/remote_copy_message_handler.cc",
       "sharing/shared_clipboard/remote_copy_message_handler.h",
       "sharing/shared_clipboard/shared_clipboard_context_menu_observer.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index de1e07b..d863955 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -257,7 +257,6 @@
 #endif  // OS_WIN
 
 #if defined(TOOLKIT_VIEWS)
-#include "ui/views/animation/installable_ink_drop.h"
 #include "ui/views/views_features.h"
 #include "ui/views/views_switches.h"
 #endif  // defined(TOOLKIT_VIEWS)
@@ -2991,9 +2990,6 @@
      flag_descriptions::kEnableWasmLazyCompilationName,
      flag_descriptions::kEnableWasmLazyCompilationDescription, kOsAll,
      FEATURE_VALUE_TYPE(features::kWebAssemblyLazyCompilation)},
-    {"enable-webassembly-simd", flag_descriptions::kEnableWasmSimdName,
-     flag_descriptions::kEnableWasmSimdDescription, kOsAll,
-     FEATURE_VALUE_TYPE(features::kWebAssemblySimd)},
     {"enable-webassembly-tiering", flag_descriptions::kEnableWasmTieringName,
      flag_descriptions::kEnableWasmTieringDescription, kOsAll,
      FEATURE_VALUE_TYPE(features::kWebAssemblyTiering)},
@@ -5613,12 +5609,6 @@
      flag_descriptions::kFileHandlingIconsDescription, kOsDesktop,
      FEATURE_VALUE_TYPE(blink::features::kFileHandlingIcons)},
 
-#if defined(TOOLKIT_VIEWS)
-    {"installable-ink-drop", flag_descriptions::kInstallableInkDropName,
-     flag_descriptions::kInstallableInkDropDescription, kOsDesktop,
-     FEATURE_VALUE_TYPE(views::kInstallableInkDropFeature)},
-#endif  // defined(TOOLKIT_VIEWS)
-
 #if BUILDFLAG(IS_CHROMEOS_ASH)
     {"enable-assistant-launcher-integration",
      flag_descriptions::kEnableAssistantLauncherIntegrationName,
diff --git a/chrome/browser/apps/app_service/app_icon_factory.cc b/chrome/browser/apps/app_service/app_icon_factory.cc
index 1334463..e6fdca6 100644
--- a/chrome/browser/apps/app_service/app_icon_factory.cc
+++ b/chrome/browser/apps/app_service/app_icon_factory.cc
@@ -1449,7 +1449,8 @@
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   DCHECK(context);
   web_app::WebAppProvider* web_app_provider =
-      web_app::WebAppProvider::Get(Profile::FromBrowserContext(context));
+      web_app::WebAppProvider::GetForLocalApps(
+          Profile::FromBrowserContext(context));
 
   DCHECK(web_app_provider);
   constexpr bool is_placeholder_icon = false;
diff --git a/chrome/browser/ash/policy/dlp/data_transfer_dlp_controller.cc b/chrome/browser/ash/policy/dlp/data_transfer_dlp_controller.cc
index b72143f..c51d4a2 100644
--- a/chrome/browser/ash/policy/dlp/data_transfer_dlp_controller.cc
+++ b/chrome/browser/ash/policy/dlp/data_transfer_dlp_controller.cc
@@ -219,11 +219,12 @@
     const ui::DataTransferEndpoint* const data_src,
     const ui::DataTransferEndpoint* const data_dst,
     const absl::optional<size_t> size,
-    content::WebContents* web_contents,
+    content::RenderFrameHost* rfh,
     base::OnceCallback<void(bool)> callback) {
   DCHECK(data_dst);
   DCHECK(data_dst->IsUrlType());
 
+  auto* web_contents = content::WebContents::FromRenderFrameHost(rfh);
   if (!web_contents) {
     std::move(callback).Run(false);
     return;
diff --git a/chrome/browser/ash/policy/dlp/data_transfer_dlp_controller.h b/chrome/browser/ash/policy/dlp/data_transfer_dlp_controller.h
index 2ca8081..96b309d6 100644
--- a/chrome/browser/ash/policy/dlp/data_transfer_dlp_controller.h
+++ b/chrome/browser/ash/policy/dlp/data_transfer_dlp_controller.h
@@ -41,7 +41,7 @@
   void PasteIfAllowed(const ui::DataTransferEndpoint* const data_src,
                       const ui::DataTransferEndpoint* const data_dst,
                       const absl::optional<size_t> size,
-                      content::WebContents* web_contents,
+                      content::RenderFrameHost* rfh,
                       base::OnceCallback<void(bool)> callback) override;
   bool IsDragDropAllowed(const ui::DataTransferEndpoint* const data_src,
                          const ui::DataTransferEndpoint* const data_dst,
diff --git a/chrome/browser/ash/policy/dlp/data_transfer_dlp_controller_unittest.cc b/chrome/browser/ash/policy/dlp/data_transfer_dlp_controller_unittest.cc
index 691d9d1..a61a2c6 100644
--- a/chrome/browser/ash/policy/dlp/data_transfer_dlp_controller_unittest.cc
+++ b/chrome/browser/ash/policy/dlp/data_transfer_dlp_controller_unittest.cc
@@ -21,6 +21,7 @@
 #include "components/account_id/account_id.h"
 #include "components/reporting/client/mock_report_queue.h"
 #include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_renderer_host.h"
 #include "content/public/test/web_contents_tester.h"
 #include "testing/gmock/include/gmock/gmock-matchers.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -119,6 +120,7 @@
   ~DataTransferDlpControllerTest() override = default;
 
   content::BrowserTaskEnvironment task_environment_;
+  content::RenderViewHostTestEnabler rvh_test_enabler_;
   ::testing::StrictMock<MockDlpRulesManager> rules_manager_;
   ::testing::StrictMock<MockDlpController> dlp_controller_;
   base::HistogramTester histogram_tester_;
@@ -159,7 +161,7 @@
       TestingProfile::Builder().Build();
   auto web_contents = CreateTestWebContents(testing_profile.get());
   dlp_controller_.PasteIfAllowed(&data_src, &data_dst, absl::nullopt,
-                                 web_contents.get(), callback.Get());
+                                 web_contents->GetMainFrame(), callback.Get());
 }
 
 TEST_F(DataTransferDlpControllerTest, PasteIfAllowed_NullWebContents) {
@@ -192,7 +194,7 @@
   EXPECT_CALL(dlp_controller_, WarnOnBlinkPaste);
 
   dlp_controller_.PasteIfAllowed(&data_src, &data_dst, absl::nullopt,
-                                 web_contents.get(), callback.Get());
+                                 web_contents->GetMainFrame(), callback.Get());
 }
 
 TEST_F(DataTransferDlpControllerTest, PasteIfAllowed_ProceedDst) {
@@ -215,7 +217,7 @@
 
   EXPECT_CALL(callback, Run(true));
   dlp_controller_.PasteIfAllowed(&data_src, &data_dst, absl::nullopt,
-                                 web_contents.get(), callback.Get());
+                                 web_contents->GetMainFrame(), callback.Get());
 }
 
 TEST_F(DataTransferDlpControllerTest, PasteIfAllowed_CancelDst) {
@@ -238,7 +240,7 @@
 
   EXPECT_CALL(callback, Run(false));
   dlp_controller_.PasteIfAllowed(&data_src, &data_dst, absl::nullopt,
-                                 web_contents.get(), callback.Get());
+                                 web_contents->GetMainFrame(), callback.Get());
 }
 
 // Create a version of the test class for parameterized testing.
diff --git a/chrome/browser/badging/badge_manager.cc b/chrome/browser/badging/badge_manager.cc
index 06b7c6ab..5fc860d 100644
--- a/chrome/browser/badging/badge_manager.cc
+++ b/chrome/browser/badging/badge_manager.cc
@@ -36,15 +36,17 @@
 #include "chrome/browser/badging/badge_manager_delegate_win.h"
 #endif
 
+using web_app::WebAppProvider;
+
 namespace {
 
 bool IsLastBadgingTimeWithin(base::TimeDelta time_frame,
                              const web_app::AppId& app_id,
                              const base::Clock* clock,
                              Profile* profile) {
-  const base::Time last_badging_time =
-      web_app::WebAppProvider::Get(profile)->registrar().GetAppLastBadgingTime(
-          app_id);
+  const base::Time last_badging_time = WebAppProvider::GetForLocalApps(profile)
+                                           ->registrar()
+                                           .GetAppLastBadgingTime(app_id);
   return clock->Now() < last_badging_time + time_frame;
 }
 
@@ -56,7 +58,7 @@
     return;
   }
 
-  web_app::WebAppProvider::Get(profile)
+  WebAppProvider::GetForLocalApps(profile)
       ->registry_controller()
       .SetAppLastBadgingTime(app_id, clock->Now());
 }
@@ -249,7 +251,7 @@
   if (!contents)
     return std::vector<std::tuple<web_app::AppId, GURL>>{};
 
-  auto* provider = web_app::WebAppProvider::Get(
+  auto* provider = WebAppProvider::GetForLocalApps(
       Profile::FromBrowserContext(contents->GetBrowserContext()));
   if (!provider)
     return std::vector<std::tuple<web_app::AppId, GURL>>{};
@@ -272,7 +274,7 @@
   if (!render_process_host)
     return std::vector<std::tuple<web_app::AppId, GURL>>{};
 
-  auto* provider = web_app::WebAppProvider::Get(
+  auto* provider = WebAppProvider::GetForLocalApps(
       Profile::FromBrowserContext(render_process_host->GetBrowserContext()));
   if (!provider)
     return std::vector<std::tuple<web_app::AppId, GURL>>{};
diff --git a/chrome/browser/browsing_data/browsing_data_remover_browsertest_base.cc b/chrome/browser/browsing_data/browsing_data_remover_browsertest_base.cc
index 9d44a90..859a894 100644
--- a/chrome/browser/browsing_data/browsing_data_remover_browsertest_base.cc
+++ b/chrome/browser/browsing_data/browsing_data_remover_browsertest_base.cc
@@ -372,7 +372,7 @@
       new browsing_data::AppCacheHelper(
           storage_partition->GetAppCacheService()),
       new browsing_data::IndexedDBHelper(storage_partition),
-      browsing_data::FileSystemHelper::Create(
+      base::MakeRefCounted<browsing_data::FileSystemHelper>(
           file_system_context,
           browsing_data_file_system_util::GetAdditionalFileSystemTypes(),
           native_io_context),
diff --git a/chrome/browser/browsing_data/cookies_tree_model.cc b/chrome/browser/browsing_data/cookies_tree_model.cc
index a8ca674..a5a824d 100644
--- a/chrome/browser/browsing_data/cookies_tree_model.cc
+++ b/chrome/browser/browsing_data/cookies_tree_model.cc
@@ -16,6 +16,7 @@
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/memory/ptr_util.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/run_loop.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
@@ -1953,7 +1954,7 @@
       new browsing_data::AppCacheHelper(
           storage_partition->GetAppCacheService()),
       new browsing_data::IndexedDBHelper(storage_partition),
-      browsing_data::FileSystemHelper::Create(
+      base::MakeRefCounted<browsing_data::FileSystemHelper>(
           file_system_context,
           browsing_data_file_system_util::GetAdditionalFileSystemTypes(),
           native_io_context),
diff --git a/chrome/browser/browsing_data/site_data_size_collector.cc b/chrome/browser/browsing_data/site_data_size_collector.cc
index 74142a7..41c2dfa 100644
--- a/chrome/browser/browsing_data/site_data_size_collector.cc
+++ b/chrome/browser/browsing_data/site_data_size_collector.cc
@@ -28,23 +28,23 @@
 
 SiteDataSizeCollector::SiteDataSizeCollector(
     const base::FilePath& default_storage_partition_path,
-    browsing_data::CookieHelper* cookie_helper,
-    browsing_data::DatabaseHelper* database_helper,
-    browsing_data::LocalStorageHelper* local_storage_helper,
-    browsing_data::AppCacheHelper* appcache_helper,
-    browsing_data::IndexedDBHelper* indexed_db_helper,
-    browsing_data::FileSystemHelper* file_system_helper,
-    browsing_data::ServiceWorkerHelper* service_worker_helper,
-    browsing_data::CacheStorageHelper* cache_storage_helper)
+    scoped_refptr<browsing_data::CookieHelper> cookie_helper,
+    scoped_refptr<browsing_data::DatabaseHelper> database_helper,
+    scoped_refptr<browsing_data::LocalStorageHelper> local_storage_helper,
+    scoped_refptr<browsing_data::AppCacheHelper> appcache_helper,
+    scoped_refptr<browsing_data::IndexedDBHelper> indexed_db_helper,
+    scoped_refptr<browsing_data::FileSystemHelper> file_system_helper,
+    scoped_refptr<browsing_data::ServiceWorkerHelper> service_worker_helper,
+    scoped_refptr<browsing_data::CacheStorageHelper> cache_storage_helper)
     : default_storage_partition_path_(default_storage_partition_path),
-      appcache_helper_(appcache_helper),
-      cookie_helper_(cookie_helper),
-      database_helper_(database_helper),
-      local_storage_helper_(local_storage_helper),
-      indexed_db_helper_(indexed_db_helper),
-      file_system_helper_(file_system_helper),
-      service_worker_helper_(service_worker_helper),
-      cache_storage_helper_(cache_storage_helper),
+      appcache_helper_(std::move(appcache_helper)),
+      cookie_helper_(std::move(cookie_helper)),
+      database_helper_(std::move(database_helper)),
+      local_storage_helper_(std::move(local_storage_helper)),
+      indexed_db_helper_(std::move(indexed_db_helper)),
+      file_system_helper_(std::move(file_system_helper)),
+      service_worker_helper_(std::move(service_worker_helper)),
+      cache_storage_helper_(std::move(cache_storage_helper)),
       in_flight_operations_(0),
       total_bytes_(0) {}
 
diff --git a/chrome/browser/browsing_data/site_data_size_collector.h b/chrome/browser/browsing_data/site_data_size_collector.h
index d7fd6c7..a948da0 100644
--- a/chrome/browser/browsing_data/site_data_size_collector.h
+++ b/chrome/browser/browsing_data/site_data_size_collector.h
@@ -8,6 +8,7 @@
 #include <list>
 #include <vector>
 
+#include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/browsing_data/content/appcache_helper.h"
@@ -33,14 +34,14 @@
 
   SiteDataSizeCollector(
       const base::FilePath& default_storage_partition_path,
-      browsing_data::CookieHelper* cookie_helper,
-      browsing_data::DatabaseHelper* database_helper,
-      browsing_data::LocalStorageHelper* local_storage_helper,
-      browsing_data::AppCacheHelper* appcache_helper,
-      browsing_data::IndexedDBHelper* indexed_db_helper,
-      browsing_data::FileSystemHelper* file_system_helper,
-      browsing_data::ServiceWorkerHelper* service_worker_helper,
-      browsing_data::CacheStorageHelper* cache_storage_helper);
+      scoped_refptr<browsing_data::CookieHelper> cookie_helper,
+      scoped_refptr<browsing_data::DatabaseHelper> database_helper,
+      scoped_refptr<browsing_data::LocalStorageHelper> local_storage_helper,
+      scoped_refptr<browsing_data::AppCacheHelper> appcache_helper,
+      scoped_refptr<browsing_data::IndexedDBHelper> indexed_db_helper,
+      scoped_refptr<browsing_data::FileSystemHelper> file_system_helper,
+      scoped_refptr<browsing_data::ServiceWorkerHelper> service_worker_helper,
+      scoped_refptr<browsing_data::CacheStorageHelper> cache_storage_helper);
   virtual ~SiteDataSizeCollector();
 
   using FetchCallback = base::OnceCallback<void(int64_t)>;
diff --git a/chrome/browser/download/notification/multi_profile_download_notifier.cc b/chrome/browser/download/notification/multi_profile_download_notifier.cc
new file mode 100644
index 0000000..470936f
--- /dev/null
+++ b/chrome/browser/download/notification/multi_profile_download_notifier.cc
@@ -0,0 +1,128 @@
+// Copyright 2021 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/download/notification/multi_profile_download_notifier.h"
+
+#include "base/sequenced_task_runner.h"
+#include "components/download/public/common/simple_download_manager.h"
+#include "content/public/browser/download_manager.h"
+
+bool MultiProfileDownloadNotifier::Client::ShouldObserveProfile(
+    Profile* profile) {
+  return true;
+}
+
+MultiProfileDownloadNotifier::MultiProfileDownloadNotifier(
+    MultiProfileDownloadNotifier::Client* client,
+    bool wait_for_manager_initialization)
+    : client_(client),
+      wait_for_manager_initialization_(wait_for_manager_initialization) {
+  DCHECK(client_);
+}
+
+MultiProfileDownloadNotifier::~MultiProfileDownloadNotifier() = default;
+
+void MultiProfileDownloadNotifier::AddProfile(Profile* profile) {
+  if (!client_->ShouldObserveProfile(profile))
+    return;
+
+  content::DownloadManager* manager = profile->GetDownloadManager();
+  auto it = std::find_if(download_notifiers_.begin(), download_notifiers_.end(),
+                         [manager](const auto& notifier) {
+                           return notifier->GetManager() == manager;
+                         });
+  if (it != download_notifiers_.end())
+    return;
+
+  profile_observer_.AddObservation(profile);
+  download_notifiers_.emplace(
+      std::make_unique<download::AllDownloadItemNotifier>(manager, this));
+
+  for (Profile* off_the_record : profile->GetAllOffTheRecordProfiles()) {
+    // An OTR profile lists itself among its own OTR profiles.
+    if (off_the_record != profile)
+      OnOffTheRecordProfileCreated(off_the_record);
+  }
+}
+
+std::vector<download::DownloadItem*>
+MultiProfileDownloadNotifier::GetAllDownloads() {
+  download::SimpleDownloadManager::DownloadVector downloads;
+  for (const auto& download_notifier : download_notifiers_) {
+    content::DownloadManager* manager = download_notifier->GetManager();
+    if (manager && IsManagerReady(manager)) {
+      manager->GetAllDownloads(&downloads);
+    }
+  }
+  return downloads;
+}
+
+download::DownloadItem* MultiProfileDownloadNotifier::GetDownloadByGuid(
+    const std::string& guid) {
+  for (const auto& notifier : download_notifiers_) {
+    content::DownloadManager* manager = notifier->GetManager();
+    if (!manager)
+      continue;
+
+    download::DownloadItem* item = manager->GetDownloadByGuid(guid);
+    if (item)
+      return item;
+  }
+
+  return nullptr;
+}
+
+void MultiProfileDownloadNotifier::OnOffTheRecordProfileCreated(
+    Profile* off_the_record) {
+  AddProfile(off_the_record);
+}
+
+void MultiProfileDownloadNotifier::OnProfileWillBeDestroyed(Profile* profile) {
+  profile_observer_.RemoveObservation(profile);
+  // This profile's download notifier is destroyed in `OnManagerGoingDown()`.
+}
+
+void MultiProfileDownloadNotifier::OnManagerInitialized(
+    content::DownloadManager* manager) {
+  client_->OnManagerInitialized(manager);
+}
+
+void MultiProfileDownloadNotifier::OnManagerGoingDown(
+    content::DownloadManager* manager) {
+  client_->OnManagerGoingDown(manager);
+
+  auto it = std::find_if(download_notifiers_.begin(), download_notifiers_.end(),
+                         [manager](const auto& notifier) {
+                           return notifier->GetManager() == manager;
+                         });
+  DCHECK(it != download_notifiers_.end());
+  download_notifiers_.erase(it);
+}
+
+void MultiProfileDownloadNotifier::OnDownloadCreated(
+    content::DownloadManager* manager,
+    download::DownloadItem* item) {
+  DCHECK(manager);
+  if (IsManagerReady(manager))
+    client_->OnDownloadCreated(manager, item);
+}
+
+void MultiProfileDownloadNotifier::OnDownloadUpdated(
+    content::DownloadManager* manager,
+    download::DownloadItem* item) {
+  if (!manager || IsManagerReady(manager))
+    client_->OnDownloadUpdated(manager, item);
+}
+
+void MultiProfileDownloadNotifier::OnDownloadDestroyed(
+    content::DownloadManager* manager,
+    download::DownloadItem* item) {
+  if (!manager || IsManagerReady(manager))
+    client_->OnDownloadDestroyed(manager, item);
+}
+
+bool MultiProfileDownloadNotifier::IsManagerReady(
+    content::DownloadManager* manager) {
+  return manager->IsManagerInitialized() || !wait_for_manager_initialization_;
+}
diff --git a/chrome/browser/download/notification/multi_profile_download_notifier.h b/chrome/browser/download/notification/multi_profile_download_notifier.h
new file mode 100644
index 0000000..64d20396
--- /dev/null
+++ b/chrome/browser/download/notification/multi_profile_download_notifier.h
@@ -0,0 +1,141 @@
+// Copyright 2021 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_DOWNLOAD_NOTIFICATION_MULTI_PROFILE_DOWNLOAD_NOTIFIER_H_
+#define CHROME_BROWSER_DOWNLOAD_NOTIFICATION_MULTI_PROFILE_DOWNLOAD_NOTIFIER_H_
+
+#include <memory>
+#include <set>
+#include <string>
+
+#include "base/containers/unique_ptr_adapters.h"
+#include "base/scoped_multi_source_observation.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_observer.h"
+#include "components/download/content/public/all_download_item_notifier.h"
+
+// `MultiProfileDownloadNotifier` observes the `DownloadItem`s created on an
+// arbitrary number of `Profile`s. No `Profile`s are observed by default; a
+// client must specify `Profile`s to be observed using `AddProfile()`. Once a
+// `Profile` is being observed, any off-the-record profile it has spawned or
+// spawns later will also be observed unless it is filtered out by
+// `ShouldObserveProfile()`.
+
+// Example Usage:
+// class DownloadsDelegate : public MultiProfileDownloadNotifier::Client {
+//  public:
+//   DownloadsDelegate(Profile* profile) {
+//     downloads_notifier_.AddProfile(profile);
+//   }
+//
+// void OnManagerInitialized(content::DownloadManager* manager) override { ... }
+// void OnManagerGoingDown(content::DownloadManager* manager) override { ... }
+// void OnDownloadCreated(content::DownloadManager* manager,
+//                        download::DownloadItem* item) override { ... }
+// void OnDownloadUpdated(content::DownloadManager* manager,
+//                        download::DownloadItem* item) override { ... }
+// void OnDownloadDestroyed(content::DownloadManager* manager,
+//                          download::DownloadItem* item) override { ... }
+// bool ShouldObserveProfile(Profile* profile) override { ... }
+//
+//  private:
+//   MultiProfileDownloadNotifier downloads_notifier_{this};
+// };
+
+namespace content {
+class DownloadManager;
+}  // namespace content
+
+namespace download {
+class DownloadItem;
+}  // namespace download
+
+class MultiProfileDownloadNotifier
+    : public ProfileObserver,
+      public download::AllDownloadItemNotifier::Observer {
+ public:
+  class Client {
+   public:
+    Client() = default;
+    Client(const Client&) = delete;
+    Client& operator=(const Client&) = delete;
+    virtual ~Client() = default;
+
+    // Each method is called with the relevant download manager for use in a
+    // client's auxiliary data structures, if applicable.
+    virtual void OnManagerInitialized(content::DownloadManager* manager) {}
+    virtual void OnManagerGoingDown(content::DownloadManager* manager) {}
+    virtual void OnDownloadCreated(content::DownloadManager* manager,
+                                   download::DownloadItem* item) {}
+    // For `OnDownloadUpdated() and OnDownloadDestroyed(), `manager` is nullptr
+    // if the function is called while `item`'s original download manager is
+    // shutting down.
+    virtual void OnDownloadUpdated(content::DownloadManager* manager,
+                                   download::DownloadItem* item) {}
+    virtual void OnDownloadDestroyed(content::DownloadManager* manager,
+                                     download::DownloadItem* item) {}
+
+    // Specifies whether the client wants to be notified for downloads created
+    // on `profile`. This function is called before observing any profile,
+    // regardless of whether `profile` was added automatically as the
+    // off-the-record child of an observed profile or the client added it
+    // explicitly using `AddProfile()`.
+    virtual bool ShouldObserveProfile(Profile* profile);
+  };
+
+  // `wait_for_manager_initialization` controls whether `client` will be
+  // notified about downloads belonging to `Profile`s with uninitialized
+  // `DownloadManager`s.
+  MultiProfileDownloadNotifier(Client* client,
+                               bool wait_for_manager_initialization);
+  MultiProfileDownloadNotifier(const MultiProfileDownloadNotifier&) = delete;
+  MultiProfileDownloadNotifier& operator=(const MultiProfileDownloadNotifier&) =
+      delete;
+  ~MultiProfileDownloadNotifier() override;
+
+  // Creates a download notifier for the download manager associated with
+  // `profile` if one does not already exist.
+  void AddProfile(Profile* profile);
+
+  // Returns all downloads for all observed profiles.
+  std::vector<download::DownloadItem*> GetAllDownloads();
+
+  // Searches all download notifiers for an observed `DownloadItem` matching
+  // `guid`. Returns the item if found or nullptr if none exists. Note that this
+  // function will return a matching download item even if it belongs to an
+  // uninitialized manager and `wait_for_manager_initialization_` is true.
+  download::DownloadItem* GetDownloadByGuid(const std::string& guid);
+
+ private:
+  // ProfileObserver:
+  void OnOffTheRecordProfileCreated(Profile* off_the_record) override;
+  void OnProfileWillBeDestroyed(Profile* profile) override;
+
+  // download::AllDownloadItemNotifier::Observer:
+  void OnManagerInitialized(content::DownloadManager* manager) override;
+  void OnManagerGoingDown(content::DownloadManager* manager) override;
+  void OnDownloadCreated(content::DownloadManager* manager,
+                         download::DownloadItem* item) override;
+  void OnDownloadUpdated(content::DownloadManager* manager,
+                         download::DownloadItem* item) override;
+  void OnDownloadDestroyed(content::DownloadManager* manager,
+                           download::DownloadItem* item) override;
+
+  // Helper function that makes sure a `DownloadManager` is initialized if
+  // `client_` requires it to be.
+  bool IsManagerReady(content::DownloadManager* manager);
+
+  MultiProfileDownloadNotifier::Client* const client_;
+
+  const bool wait_for_manager_initialization_;
+
+  std::set<std::unique_ptr<download::AllDownloadItemNotifier>,
+           base::UniquePtrComparator>
+      download_notifiers_;
+
+  base::ScopedMultiSourceObservation<Profile, ProfileObserver>
+      profile_observer_{this};
+};
+
+#endif  // CHROME_BROWSER_DOWNLOAD_NOTIFICATION_MULTI_PROFILE_DOWNLOAD_NOTIFIER_H_
diff --git a/chrome/browser/enterprise/signals/context_info_fetcher.cc b/chrome/browser/enterprise/signals/context_info_fetcher.cc
index 25f3ca9..4f13a76 100644
--- a/chrome/browser/enterprise/signals/context_info_fetcher.cc
+++ b/chrome/browser/enterprise/signals/context_info_fetcher.cc
@@ -23,6 +23,12 @@
 #include "content/public/browser/site_isolation_policy.h"
 #include "device_management_backend.pb.h"
 
+#if defined(OS_WIN)
+#include <netfw.h>
+#include <windows.h>
+#include <wrl/client.h>
+#endif
+
 namespace enterprise_signals {
 
 namespace {
@@ -71,6 +77,42 @@
 }
 #endif  // defined(OS_LINUX)
 
+#if defined(OS_WIN)
+SettingValue GetWinOSFirewall() {
+  Microsoft::WRL::ComPtr<INetFwPolicy2> firewall_policy;
+  HRESULT hr = CoCreateInstance(CLSID_NetFwPolicy2, nullptr, CLSCTX_ALL,
+                                IID_PPV_ARGS(&firewall_policy));
+  if (FAILED(hr)) {
+    DLOG(ERROR) << logging::SystemErrorCodeToString(hr);
+    return SettingValue::UNKNOWN;
+  }
+
+  long profile_types = 0;
+  hr = firewall_policy->get_CurrentProfileTypes(&profile_types);
+  if (FAILED(hr))
+    return SettingValue::UNKNOWN;
+
+  // The most restrictive active profile takes precedence.
+  constexpr NET_FW_PROFILE_TYPE2 kProfileTypes[] = {
+      NET_FW_PROFILE2_PUBLIC, NET_FW_PROFILE2_PRIVATE, NET_FW_PROFILE2_DOMAIN};
+  for (size_t i = 0; i < base::size(kProfileTypes); ++i) {
+    if ((profile_types & kProfileTypes[i]) != 0) {
+      VARIANT_BOOL enabled = VARIANT_TRUE;
+      hr = firewall_policy->get_FirewallEnabled(kProfileTypes[i], &enabled);
+      if (FAILED(hr))
+        return SettingValue::UNKNOWN;
+      if (enabled == VARIANT_TRUE)
+        return SettingValue::ENABLED;
+      else if (enabled == VARIANT_FALSE)
+        return SettingValue::DISABLED;
+      else
+        return SettingValue::UNKNOWN;
+    }
+  }
+  return SettingValue::UNKNOWN;
+}
+#endif
+
 }  // namespace
 
 ContextInfo::ContextInfo() = default;
@@ -236,6 +278,8 @@
 SettingValue ContextInfoFetcher::GetOSFirewall() {
 #if defined(OS_LINUX)
   return GetUfwStatus();
+#elif defined(OS_WIN)
+  return GetWinOSFirewall();
 #else
   return SettingValue::UNKNOWN;
 #endif
diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn
index 26d14bbd..3c3358a 100644
--- a/chrome/browser/extensions/BUILD.gn
+++ b/chrome/browser/extensions/BUILD.gn
@@ -949,28 +949,10 @@
   ]
 
   if (is_chromeos_ash) {
-    sources += [
-      "api/printing/print_job_controller.cc",
-      "api/printing/print_job_controller.h",
-      "api/printing/printer_capabilities_provider.cc",
-      "api/printing/printer_capabilities_provider.h",
-    ]
     deps += [
       "//chromeos/resources:media_app_bundle_resources_grit",
       "//ui/display/manager",
     ]
-    if (use_cups) {
-      sources += [
-        "api/printing/print_job_submitter.cc",
-        "api/printing/print_job_submitter.h",
-        "api/printing/printing_api.cc",
-        "api/printing/printing_api.h",
-        "api/printing/printing_api_handler.cc",
-        "api/printing/printing_api_handler.h",
-        "api/printing/printing_api_utils.cc",
-        "api/printing/printing_api_utils.h",
-      ]
-    }
   }
 
   if (is_chromeos) {
@@ -988,11 +970,25 @@
       "api/platform_keys/platform_keys_api.h",
       "api/platform_keys/verify_trust_api.cc",
       "api/platform_keys/verify_trust_api.h",
+      "api/printing/print_job_controller.cc",
+      "api/printing/print_job_controller.h",
       "clipboard_extension_helper_chromeos.cc",
       "clipboard_extension_helper_chromeos.h",
       "system_display/system_display_serialization.cc",
       "system_display/system_display_serialization.h",
     ]
+    if (use_cups) {
+      sources += [
+        "api/printing/print_job_submitter.cc",
+        "api/printing/print_job_submitter.h",
+        "api/printing/printing_api.cc",
+        "api/printing/printing_api.h",
+        "api/printing/printing_api_handler.cc",
+        "api/printing/printing_api_handler.h",
+        "api/printing/printing_api_utils.cc",
+        "api/printing/printing_api_utils.h",
+      ]
+    }
     deps += [
       "//chromeos/crosapi/cpp",
       "//chromeos/crosapi/mojom",
diff --git a/chrome/browser/extensions/api/automation/automation_apitest.cc b/chrome/browser/extensions/api/automation/automation_apitest.cc
index c1e76c03..07e39eb 100644
--- a/chrome/browser/extensions/api/automation/automation_apitest.cc
+++ b/chrome/browser/extensions/api/automation/automation_apitest.cc
@@ -445,26 +445,14 @@
       << message_;
 }
 
-// TODO(http://crbug.com/1229213): flaky on ChromeOS.
-#if defined(OS_CHROMEOS)
-#define MAYBE_DesktopFocusIframe DISABLED_DesktopFocusIframe
-#else
-#define MAYBE_DesktopFocusIframe DesktopFocusIframe
-#endif
-IN_PROC_BROWSER_TEST_F(AutomationApiTest, MAYBE_DesktopFocusIframe) {
+IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopFocusIframe) {
   StartEmbeddedTestServer();
   ASSERT_TRUE(RunExtensionTest("automation/tests/desktop",
                                {.page_url = "focus_iframe.html"}))
       << message_;
 }
 
-// TODO(http://crbug.com/1234340): flaky on ChromeOS.
-#if defined(OS_CHROMEOS)
-#define MAYBE_DesktopHitTestIframe DISABLED_DesktopHitTestIframe
-#else
-#define MAYBE_DesktopHitTestIframe DesktopHitTestIframe
-#endif
-IN_PROC_BROWSER_TEST_F(AutomationApiTest, MAYBE_DesktopHitTestIframe) {
+IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopHitTestIframe) {
   StartEmbeddedTestServer();
   ASSERT_TRUE(RunExtensionTest("automation/tests/desktop",
                                {.page_url = "hit_test_iframe.html"}))
diff --git a/chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc b/chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc
index 2d87773..688cf90 100644
--- a/chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc
+++ b/chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc
@@ -1997,86 +1997,6 @@
       download_url.c_str())).c_str());
 }
 
-class DownloadExtensionTestWithFtp : public DownloadExtensionTest {
- public:
-  DownloadExtensionTestWithFtp() {
-    // DownloadExtensionTest_Download_InvalidURLs2 requires FTP support.
-    // TODO(https://crbug.com/333943): Remove FTP tests and FTP feature flags.
-    scoped_feature_list_.InitAndEnableFeature(network::features::kFtpProtocol);
-  }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
-// Test that downloading invalid URLs immediately returns kInvalidURLError.
-IN_PROC_BROWSER_TEST_F(DownloadExtensionTestWithFtp,
-                       DownloadExtensionTest_Download_InvalidURLs1) {
-  static constexpr const char* kInvalidURLs[] = {
-      "foo bar", "../hello",          "/hello",      "http://",
-      "#frag",   "foo/bar.html#frag", "google.com/",
-  };
-
-  for (const char* url : kInvalidURLs) {
-    EXPECT_STREQ(errors::kInvalidURL,
-                 RunFunctionAndReturnError(
-                     new DownloadsDownloadFunction(),
-                     base::StringPrintf("[{\"url\": \"%s\"}]", url))
-                     .c_str())
-        << url;
-  }
-}
-
-// Test various failure modes for downloading invalid URLs.
-IN_PROC_BROWSER_TEST_F(DownloadExtensionTestWithFtp,
-                       DownloadExtensionTest_Download_InvalidURLs2) {
-  LoadExtension("downloads_split");
-  GoOnTheRecord();
-
-  int result_id = -1;
-  std::unique_ptr<base::Value> result(RunFunctionAndReturnResult(
-      new DownloadsDownloadFunction(),
-      "[{\"url\": \"javascript:document.write(\\\"hello\\\");\"}]"));
-  ASSERT_TRUE(result.get());
-  ASSERT_TRUE(result->is_int());
-  result_id = result->GetInt();
-  DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
-  ASSERT_TRUE(item);
-  ASSERT_TRUE(WaitForInterruption(
-      item, download::DOWNLOAD_INTERRUPT_REASON_NETWORK_INVALID_REQUEST,
-      "[{\"state\": \"in_progress\","
-      "  \"url\": \"javascript:document.write(\\\"hello\\\");\"}]"));
-
-  result.reset(
-      RunFunctionAndReturnResult(new DownloadsDownloadFunction(),
-                                 "[{\"url\": \"javascript:return false;\"}]"));
-  ASSERT_TRUE(result.get());
-  ASSERT_TRUE(result->is_int());
-  result_id = result->GetInt();
-  item = GetCurrentManager()->GetDownload(result_id);
-  ASSERT_TRUE(item);
-  ASSERT_TRUE(WaitForInterruption(
-      item, download::DOWNLOAD_INTERRUPT_REASON_NETWORK_INVALID_REQUEST,
-      "[{\"state\": \"in_progress\","
-      "  \"url\": \"javascript:return false;\"}]"));
-
-  result.reset(RunFunctionAndReturnResult(
-      new DownloadsDownloadFunction(),
-      "[{\"url\": \"ftp://example.com/example.txt\"}]"));
-  ASSERT_TRUE(result.get());
-  ASSERT_TRUE(result->is_int());
-  result_id = result->GetInt();
-  item = GetCurrentManager()->GetDownload(result_id);
-  ASSERT_TRUE(item);
-  ASSERT_TRUE(WaitForInterruption(
-      item, download::DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED,
-      "[{\"state\": \"in_progress\","
-      "  \"url\": \"ftp://example.com/example.txt\"}]"));
-}
-
-// TODO(benjhayden): Set up a test ftp server, add ftp://localhost* to
-// permissions, test downloading from ftp.
-
 // Valid URLs plus fragments are still valid URLs.
 IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
                        DownloadExtensionTest_Download_URLFragment) {
diff --git a/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_apitest.cc b/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_apitest.cc
index ef309e5..3fb2480 100644
--- a/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_apitest.cc
+++ b/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_apitest.cc
@@ -265,6 +265,7 @@
   constexpr char kThirdPartyBlockingEnabledType[] = "undefined";
   constexpr char kCount[] = "14";
 #endif  // defined(OS_WIN)
+
   constexpr char kTest[] = R"(
     chrome.test.assertEq(
       'function',
@@ -290,7 +291,7 @@
       chrome.test.assertEq
         (typeof info.chromeRemoteDesktopAppBlocked, 'boolean');
       chrome.test.assertEq(typeof info.thirdPartyBlockingEnabled,'%s');
-      chrome.test.assertEq(typeof info.osFirewall,'string');
+      chrome.test.assertEq(typeof info.osFirewall, 'string');
 
       chrome.test.notifyPass();
     });)";
diff --git a/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_unittest.cc b/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_unittest.cc
index b331d117..3702a59 100644
--- a/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_unittest.cc
+++ b/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/files/scoped_temp_dir.h"
 #include "base/macros.h"
 #include "build/build_config.h"
+#include "chrome/browser/enterprise/signals/signals_common.h"
 #include "chrome/browser/extensions/api/enterprise_reporting_private/chrome_desktop_report_request_helper.h"
 #include "chrome/browser/extensions/extension_api_unittest.h"
 #include "chrome/browser/extensions/extension_function_test_utils.h"
@@ -28,6 +29,10 @@
 #include "testing/gtest/include/gtest/gtest.h"
 
 #if defined(OS_WIN)
+#include <netfw.h>
+#include <windows.h>
+#include <wrl/client.h>
+
 #include "base/test/test_reg_util_win.h"
 #endif
 
@@ -38,6 +43,8 @@
 namespace enterprise_reporting_private =
     ::extensions::api::enterprise_reporting_private;
 
+using SettingValue = enterprise_signals::SettingValue;
+
 namespace extensions {
 
 #if !defined(OS_CHROMEOS)
@@ -732,10 +739,10 @@
       public testing::WithParamInterface<bool> {};
 
 TEST_P(EnterpriseReportingPrivateGetContextInfoChromeCleanupTest, Test) {
-  bool policyValue = GetParam();
+  bool policy_value = GetParam();
 
   g_browser_process->local_state()->SetBoolean(prefs::kSwReporterEnabled,
-                                               policyValue);
+                                               policy_value);
 
   enterprise_reporting_private::ContextInfo info = GetContextInfo();
 
@@ -756,7 +763,7 @@
       enterprise_reporting_private::PASSWORD_PROTECTION_TRIGGER_POLICY_UNSET,
       info.password_protection_warning_trigger);
   ExpectDefaultThirdPartyBlockingEnabled(info);
-  EXPECT_EQ(policyValue, *info.chrome_cleanup_enabled);
+  EXPECT_EQ(policy_value, *info.chrome_cleanup_enabled);
 }
 
 INSTANTIATE_TEST_SUITE_P(
@@ -849,6 +856,104 @@
                     "google.com",
                     "https://*"));
 
+#if defined(OS_WIN)
+class EnterpriseReportingPrivateGetContextInfoOSFirewallTest
+    : public EnterpriseReportingPrivateGetContextInfoTest,
+      public testing::WithParamInterface<SettingValue> {
+ public:
+  EnterpriseReportingPrivateGetContextInfoOSFirewallTest()
+      : enabled_(VARIANT_TRUE) {}
+
+ protected:
+  void SetUp() override {
+    ExtensionApiUnittest::SetUp();
+    HRESULT hr = CoCreateInstance(CLSID_NetFwPolicy2, nullptr, CLSCTX_ALL,
+                                  IID_PPV_ARGS(&firewall_policy_));
+    EXPECT_FALSE(FAILED(hr));
+
+    long profile_types = 0;
+    hr = firewall_policy_->get_CurrentProfileTypes(&profile_types);
+    EXPECT_FALSE(FAILED(hr));
+
+    // Setting the firewall for each active profile
+    const NET_FW_PROFILE_TYPE2 kProfileTypes[] = {NET_FW_PROFILE2_PUBLIC,
+                                                  NET_FW_PROFILE2_PRIVATE,
+                                                  NET_FW_PROFILE2_DOMAIN};
+    for (size_t i = 0; i < base::size(kProfileTypes); ++i) {
+      if ((profile_types & kProfileTypes[i]) != 0) {
+        hr = firewall_policy_->get_FirewallEnabled(kProfileTypes[i], &enabled_);
+        EXPECT_FALSE(FAILED(hr));
+        active_profile_ = kProfileTypes[i];
+        hr = firewall_policy_->put_FirewallEnabled(
+            kProfileTypes[i], firewall_value_ == SettingValue::ENABLED
+                                  ? VARIANT_TRUE
+                                  : VARIANT_FALSE);
+        EXPECT_FALSE(FAILED(hr));
+        break;
+      }
+    }
+  }
+
+  void TearDown() override {
+    // Resetting the firewall to its initial state
+    HRESULT hr =
+        firewall_policy_->put_FirewallEnabled(active_profile_, enabled_);
+    EXPECT_FALSE(FAILED(hr));
+  }
+
+  extensions::api::enterprise_reporting_private::SettingValue
+  ToInfoSettingValue(enterprise_signals::SettingValue value) {
+    switch (value) {
+      case SettingValue::DISABLED:
+        return extensions::api::enterprise_reporting_private::
+            SETTING_VALUE_DISABLED;
+      case SettingValue::ENABLED:
+        return extensions::api::enterprise_reporting_private::
+            SETTING_VALUE_ENABLED;
+      default:
+        NOTREACHED();
+        return extensions::api::enterprise_reporting_private::
+            SETTING_VALUE_UNKNOWN;
+    }
+  }
+  Microsoft::WRL::ComPtr<INetFwPolicy2> firewall_policy_;
+  SettingValue firewall_value_ = GetParam();
+  VARIANT_BOOL enabled_;
+  NET_FW_PROFILE_TYPE2 active_profile_;
+};
+
+TEST_P(EnterpriseReportingPrivateGetContextInfoOSFirewallTest, Test) {
+  enterprise_reporting_private::ContextInfo info = GetContextInfo();
+
+  EXPECT_TRUE(info.browser_affiliation_ids.empty());
+  EXPECT_TRUE(info.profile_affiliation_ids.empty());
+  EXPECT_TRUE(info.on_file_attached_providers.empty());
+  EXPECT_TRUE(info.on_file_downloaded_providers.empty());
+  EXPECT_TRUE(info.on_bulk_data_entry_providers.empty());
+  EXPECT_EQ(enterprise_reporting_private::REALTIME_URL_CHECK_MODE_DISABLED,
+            info.realtime_url_check_mode);
+  EXPECT_TRUE(info.on_security_event_providers.empty());
+  EXPECT_EQ(version_info::GetVersionNumber(), info.browser_version);
+  EXPECT_EQ(enterprise_reporting_private::SAFE_BROWSING_LEVEL_STANDARD,
+            info.safe_browsing_protection_level);
+  EXPECT_EQ(BuiltInDnsClientPlatformDefault(),
+            info.built_in_dns_client_enabled);
+  EXPECT_EQ(
+      enterprise_reporting_private::PASSWORD_PROTECTION_TRIGGER_POLICY_UNSET,
+      info.password_protection_warning_trigger);
+  ExpectDefaultChromeCleanupEnabled(info);
+  EXPECT_FALSE(info.chrome_remote_desktop_app_blocked);
+  ExpectDefaultThirdPartyBlockingEnabled(info);
+  EXPECT_EQ(ToInfoSettingValue(firewall_value_), info.os_firewall);
+}
+
+INSTANTIATE_TEST_SUITE_P(,
+                         EnterpriseReportingPrivateGetContextInfoOSFirewallTest,
+                         testing::Values(SettingValue::DISABLED,
+                                         SettingValue::ENABLED));
+
+#endif  // defined(OS_WIN)
+
 class EnterpriseReportingPrivateGetContextInfoRealTimeURLCheckTest
     : public EnterpriseReportingPrivateGetContextInfoTest,
       public testing::WithParamInterface<bool> {
diff --git a/chrome/browser/extensions/api/printing/fake_print_job_controller.cc b/chrome/browser/extensions/api/printing/fake_print_job_controller.cc
index 9c76b6b3..7d5a5b93 100644
--- a/chrome/browser/extensions/api/printing/fake_print_job_controller.cc
+++ b/chrome/browser/extensions/api/printing/fake_print_job_controller.cc
@@ -8,75 +8,66 @@
 #include <utility>
 
 #include "base/callback.h"
+#include "base/containers/flat_set.h"
 #include "base/strings/utf_string_conversions.h"
-#include "chrome/browser/chromeos/printing/cups_print_job.h"
-#include "chrome/browser/chromeos/printing/cups_print_job_manager.h"
-#include "chrome/browser/chromeos/printing/cups_printers_manager.h"
-#include "chrome/browser/chromeos/printing/history/print_job_info_proto_conversions.h"
-#include "chrome/browser/chromeos/printing/test_cups_print_job_manager.h"
+#include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/extensions/api/printing/printing_api_handler.h"
+#include "chrome/browser/printing/print_job.h"
 #include "chromeos/printing/printer_configuration.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_source.h"
 #include "printing/metafile_skia.h"
 #include "printing/print_settings.h"
+#include "printing/printed_document.h"
+#include "testing/gtest/include/gtest/gtest.h"
 
 namespace extensions {
 
-FakePrintJobController::FakePrintJobController(
-    chromeos::TestCupsPrintJobManager* print_job_manager,
-    chromeos::CupsPrintersManager* printers_manager)
-    : print_job_manager_(print_job_manager),
-      printers_manager_(printers_manager) {}
+FakePrintJobController::FakePrintJobController() = default;
 
 FakePrintJobController::~FakePrintJobController() = default;
 
+void FakePrintJobController::CreatePrintJob(
+    const std::string& printer_id,
+    int job_id,
+    const std::string& extension_id,
+    crosapi::mojom::PrintJob::Source source) const {
+  auto job = base::MakeRefCounted<printing::PrintJob>();
+  job->SetSource(source, extension_id);
+  auto settings = std::make_unique<printing::PrintSettings>();
+  settings->set_device_name(base::UTF8ToUTF16(printer_id));
+  auto document = base::MakeRefCounted<printing::PrintedDocument>(
+      std::move(settings), std::u16string(), 0);
+  auto details = base::MakeRefCounted<printing::JobEventDetails>(
+      printing::JobEventDetails::DOC_DONE, job_id, document.get());
+  // Normally, PrintingAPIHandler observes the DOC_DONE notification.
+  handler_->Observe(chrome::NOTIFICATION_PRINT_JOB_EVENT,
+                    content::Source<printing::PrintJob>(job.get()),
+                    content::Details<printing::JobEventDetails>(details.get()));
+}
+
 void FakePrintJobController::StartPrintJob(
     const std::string& extension_id,
     std::unique_ptr<printing::MetafileSkia> metafile,
     std::unique_ptr<printing::PrintSettings> settings,
     StartPrintJobCallback callback) {
-  absl::optional<chromeos::Printer> printer =
-      printers_manager_->GetPrinter(base::UTF16ToUTF8(settings->device_name()));
-  if (!printer) {
-    std::move(callback).Run(nullptr);
-    return;
-  }
-
-  // Create and start new CupsPrintJob.
-  job_id_++;
-  auto print_job = std::make_unique<chromeos::CupsPrintJob>(
-      *printer, job_id_, base::UTF16ToUTF8(settings->title()),
-      /*total_page_number=*/1, printing::PrintJob::Source::EXTENSION,
-      extension_id, chromeos::PrintSettingsToProto(*settings));
-  print_job_manager_->CreatePrintJob(print_job.get());
-  print_job_manager_->StartPrintJob(print_job.get());
-  std::string print_job_unique_id = print_job->GetUniqueId();
-  jobs_[print_job_unique_id] = std::move(print_job);
-
-  std::move(callback).Run(std::make_unique<std::string>(print_job_unique_id));
+  std::string printer_id = base::UTF16ToUTF8(settings->device_name());
+  CreatePrintJob(printer_id, ++job_id_, extension_id,
+                 crosapi::mojom::PrintJob::Source::EXTENSION);
+  std::string cups_id = PrintingAPIHandler::CreateUniqueId(printer_id, job_id_);
+  EXPECT_TRUE(print_jobs_.contains(cups_id));
+  std::move(callback).Run(std::make_unique<std::string>(std::move(cups_id)));
 }
 
-bool FakePrintJobController::CancelPrintJob(const std::string& job_id) {
-  auto it = jobs_.find(job_id);
-  if (it == jobs_.end())
-    return false;
-  print_job_manager_->CancelPrintJob(it->second.get());
-  return true;
+void FakePrintJobController::OnPrintJobCreated(const std::string& extension_id,
+                                               const std::string& job_id) {
+  EXPECT_FALSE(print_jobs_.contains(job_id));
+  print_jobs_.insert(job_id);
 }
 
-void FakePrintJobController::OnPrintJobCreated(
-    const std::string& extension_id,
-    const std::string& job_id,
-    base::WeakPtr<chromeos::CupsPrintJob> cups_job) {}
-
 void FakePrintJobController::OnPrintJobFinished(const std::string& job_id) {
-  jobs_.erase(job_id);
-}
-
-chromeos::CupsPrintJob* FakePrintJobController::GetCupsPrintJob(
-    const std::string& job_id) {
-  auto it = jobs_.find(job_id);
-  if (it == jobs_.end())
-    return nullptr;
-  return it->second.get();
+  EXPECT_TRUE(print_jobs_.contains(job_id));
+  print_jobs_.erase(job_id);
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/printing/fake_print_job_controller.h b/chrome/browser/extensions/api/printing/fake_print_job_controller.h
index 537a4ea..97ff03e 100644
--- a/chrome/browser/extensions/api/printing/fake_print_job_controller.h
+++ b/chrome/browser/extensions/api/printing/fake_print_job_controller.h
@@ -5,47 +5,45 @@
 #ifndef CHROME_BROWSER_EXTENSIONS_API_PRINTING_FAKE_PRINT_JOB_CONTROLLER_H_
 #define CHROME_BROWSER_EXTENSIONS_API_PRINTING_FAKE_PRINT_JOB_CONTROLLER_H_
 
-#include "base/containers/flat_map.h"
+#include "base/containers/flat_set.h"
 #include "chrome/browser/extensions/api/printing/print_job_controller.h"
-
-namespace chromeos {
-class CupsPrintersManager;
-class TestCupsPrintJobManager;
-}  // namespace chromeos
+#include "chromeos/crosapi/mojom/local_printer.mojom.h"
 
 namespace extensions {
 
+class PrintingAPIHandler;
+
 // Fake print job controller which doesn't send print jobs to actual printing
 // pipeline.
 // It's used in unit and API integration tests.
 class FakePrintJobController : public PrintJobController {
  public:
-  FakePrintJobController(chromeos::TestCupsPrintJobManager* print_job_manager,
-                         chromeos::CupsPrintersManager* printers_manager);
+  FakePrintJobController();
   ~FakePrintJobController() override;
 
+  void set_handler(PrintingAPIHandler* handler) { handler_ = handler; }
+
+  const base::flat_set<std::string>& print_jobs() const { return print_jobs_; }
+
+  void CreatePrintJob(const std::string& printer_id,
+                      int job_id,
+                      const std::string& extension_id,
+                      crosapi::mojom::PrintJob::Source source) const;
+
   // PrintJobController:
   void StartPrintJob(const std::string& extension_id,
                      std::unique_ptr<printing::MetafileSkia> metafile,
                      std::unique_ptr<printing::PrintSettings> settings,
                      StartPrintJobCallback callback) override;
-  bool CancelPrintJob(const std::string& job_id) override;
-  void OnPrintJobCreated(
-      const std::string& extension_id,
-      const std::string& job_id,
-      base::WeakPtr<chromeos::CupsPrintJob> cups_job) override;
+  void OnPrintJobCreated(const std::string& extension_id,
+                         const std::string& job_id) override;
   void OnPrintJobFinished(const std::string& job_id) override;
 
-  // Helper method to be used in tests to access ongoing print jobs.
-  chromeos::CupsPrintJob* GetCupsPrintJob(const std::string& job_id);
-
  private:
-  // Not owned by FakePrintJobController.
-  chromeos::TestCupsPrintJobManager* print_job_manager_;
-  chromeos::CupsPrintersManager* printers_manager_;
+  PrintingAPIHandler* handler_ = nullptr;
 
-  // Stores ongoing print jobs as a mapping from job id to CupsPrintJob.
-  base::flat_map<std::string, std::unique_ptr<chromeos::CupsPrintJob>> jobs_;
+  // Stores ids for ongoing print jobs.
+  base::flat_set<std::string> print_jobs_;
 
   // Current job id.
   int job_id_ = 0;
diff --git a/chrome/browser/extensions/api/printing/print_job_controller.cc b/chrome/browser/extensions/api/printing/print_job_controller.cc
index f2aada00..7590aa2 100644
--- a/chrome/browser/extensions/api/printing/print_job_controller.cc
+++ b/chrome/browser/extensions/api/printing/print_job_controller.cc
@@ -8,11 +8,12 @@
 
 #include "base/bind.h"
 #include "base/callback.h"
+#include "base/check.h"
 #include "base/containers/flat_map.h"
+#include "base/containers/flat_set.h"
 #include "base/containers/queue.h"
 #include "base/memory/scoped_refptr.h"
-#include "chrome/browser/chromeos/printing/cups_print_job.h"
-#include "chrome/browser/chromeos/printing/cups_print_job_manager.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/printing/print_job.h"
 #include "chrome/browser/printing/printer_query.h"
 #include "content/public/browser/browser_task_traits.h"
@@ -55,8 +56,7 @@
 // This class lives on UI thread.
 class PrintJobControllerImpl : public PrintJobController {
  public:
-  explicit PrintJobControllerImpl(
-      chromeos::CupsPrintJobManager* print_job_manager);
+  PrintJobControllerImpl();
   ~PrintJobControllerImpl() override;
 
   // PrintJobController:
@@ -65,13 +65,9 @@
                      std::unique_ptr<printing::PrintSettings> settings,
                      StartPrintJobCallback callback) override;
 
-  bool CancelPrintJob(const std::string& job_id) override;
-
   // Moves print job pointer to |print_jobs_map_| and resolves corresponding
-  void OnPrintJobCreated(
-      const std::string& extension_id,
-      const std::string& job_id,
-      base::WeakPtr<chromeos::CupsPrintJob> cups_job) override;
+  void OnPrintJobCreated(const std::string& extension_id,
+                         const std::string& job_id) override;
 
   // Removes print job pointer from |print_jobs_map_| as the job is finished.
   void OnPrintJobFinished(const std::string& job_id) override;
@@ -102,20 +98,10 @@
   base::flat_map<std::string, base::queue<JobState>> extension_pending_jobs_;
 
   // Stores mapping from job id to printing::PrintJob.
-  // This is needed to hold PrintJob pointer and correct handle CancelJob()
-  // requests.
+  // This is needed to hold the PrintJob pointer.
   base::flat_map<std::string, scoped_refptr<printing::PrintJob>>
       print_jobs_map_;
 
-  // Stores mapping from job id to chromeos::CupsPrintJob.
-  base::flat_map<std::string, base::WeakPtr<chromeos::CupsPrintJob>>
-      cups_print_jobs_map_;
-
-  // PrintingAPIHandler (which owns PrintJobController) depends on
-  // CupsPrintJobManagerFactory, so |print_job_manager_| outlives
-  // PrintJobController.
-  chromeos::CupsPrintJobManager* const print_job_manager_;
-
   base::WeakPtrFactory<PrintJobControllerImpl> weak_ptr_factory_{this};
 };
 
@@ -131,9 +117,7 @@
 
 PrintJobControllerImpl::JobState::~JobState() = default;
 
-PrintJobControllerImpl::PrintJobControllerImpl(
-    chromeos::CupsPrintJobManager* print_job_manager)
-    : print_job_manager_(print_job_manager) {}
+PrintJobControllerImpl::PrintJobControllerImpl() = default;
 
 PrintJobControllerImpl::~PrintJobControllerImpl() = default;
 
@@ -173,55 +157,40 @@
   job->StartPrinting();
 }
 
-bool PrintJobControllerImpl::CancelPrintJob(const std::string& job_id) {
+void PrintJobControllerImpl::OnPrintJobCreated(const std::string& extension_id,
+                                               const std::string& job_id) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  auto it = cups_print_jobs_map_.find(job_id);
-  if (it == cups_print_jobs_map_.end() || !it->second)
-    return false;
-  print_job_manager_->CancelPrintJob(it->second.get());
-  return true;
-}
-
-void PrintJobControllerImpl::OnPrintJobCreated(
-    const std::string& extension_id,
-    const std::string& job_id,
-    base::WeakPtr<chromeos::CupsPrintJob> cups_job) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  DCHECK(!cups_print_jobs_map_.contains(job_id));
-  cups_print_jobs_map_[job_id] = cups_job;
+  DCHECK(!print_jobs_map_.contains(job_id));
 
   auto it = extension_pending_jobs_.find(extension_id);
-  if (it == extension_pending_jobs_.end())
+  if (it == extension_pending_jobs_.end() || it->second.empty()) {
+    LOG(ERROR) << "Could not find print job for extension " << extension_id;
     return;
-  auto& pending_jobs = it->second;
-  if (!pending_jobs.empty()) {
-    // We need to move corresponding scoped refptr of PrintJob from queue
-    // of pending pointers to global map. We can't drop it as the print job is
-    // not completed yet so we should not destruct it.
-    print_jobs_map_[job_id] = pending_jobs.front().job;
-    // The job is submitted to CUPS so we have to resolve the first callback
-    // in the corresponding queue.
-    auto callback = std::move(pending_jobs.front().callback);
-    pending_jobs.pop();
-    if (pending_jobs.empty())
-      extension_pending_jobs_.erase(it);
-    std::move(callback).Run(std::make_unique<std::string>(job_id));
   }
+  auto& pending_jobs = it->second;
+  // We need to move corresponding scoped refptr of PrintJob from queue
+  // of pending pointers to global map. We can't drop it as the print job is
+  // not completed yet so we should not destruct it.
+  print_jobs_map_[job_id] = pending_jobs.front().job;
+  // The job is submitted to CUPS so we have to resolve the first callback
+  // in the corresponding queue.
+  auto callback = std::move(pending_jobs.front().callback);
+  pending_jobs.pop();
+  if (pending_jobs.empty())
+    extension_pending_jobs_.erase(it);
+  std::move(callback).Run(std::make_unique<std::string>(job_id));
 }
 
 void PrintJobControllerImpl::OnPrintJobFinished(const std::string& job_id) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   print_jobs_map_.erase(job_id);
-  cups_print_jobs_map_.erase(job_id);
 }
 
 // static
-std::unique_ptr<PrintJobController> PrintJobController::Create(
-    chromeos::CupsPrintJobManager* print_job_manager) {
-  return std::make_unique<PrintJobControllerImpl>(print_job_manager);
+std::unique_ptr<PrintJobController> PrintJobController::Create() {
+  return std::make_unique<PrintJobControllerImpl>();
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/printing/print_job_controller.h b/chrome/browser/extensions/api/printing/print_job_controller.h
index bd6d26a..d5173d8 100644
--- a/chrome/browser/extensions/api/printing/print_job_controller.h
+++ b/chrome/browser/extensions/api/printing/print_job_controller.h
@@ -11,11 +11,6 @@
 #include "base/callback_forward.h"
 #include "base/memory/weak_ptr.h"
 
-namespace chromeos {
-class CupsPrintJob;
-class CupsPrintJobManager;
-}  // namespace chromeos
-
 namespace printing {
 class MetafileSkia;
 class PrintSettings;
@@ -23,16 +18,15 @@
 
 namespace extensions {
 
-// This class is responsible for sending print jobs in the printing pipeline and
-// cancelling them. It should be used by API handler as the entry point of
-// actual printing pipeline.
+// This class is responsible for sending print jobs in the printing pipeline.
+// It should be used by API handler as the entry point of actual printing
+// pipeline.
 class PrintJobController {
  public:
   using StartPrintJobCallback =
       base::OnceCallback<void(std::unique_ptr<std::string> job_id)>;
 
-  static std::unique_ptr<PrintJobController> Create(
-      chromeos::CupsPrintJobManager* print_job_manager);
+  static std::unique_ptr<PrintJobController> Create();
 
   PrintJobController() = default;
   virtual ~PrintJobController() = default;
@@ -45,15 +39,9 @@
                              std::unique_ptr<printing::PrintSettings> settings,
                              StartPrintJobCallback callback) = 0;
 
-  // Returns false if there is no active print job with specified id.
-  // Returns true otherwise.
-  virtual bool CancelPrintJob(const std::string& job_id) = 0;
-
   // This should be called when CupsPrintJobManager created CupsPrintJob.
-  virtual void OnPrintJobCreated(
-      const std::string& extension_id,
-      const std::string& job_id,
-      base::WeakPtr<chromeos::CupsPrintJob> cups_job) = 0;
+  virtual void OnPrintJobCreated(const std::string& extension_id,
+                                 const std::string& job_id) = 0;
 
   // This should be called when CupsPrintJob is finished (it could be either
   // completed, failed or cancelled).
diff --git a/chrome/browser/extensions/api/printing/print_job_submitter.cc b/chrome/browser/extensions/api/printing/print_job_submitter.cc
index d5f6f69..f266659 100644
--- a/chrome/browser/extensions/api/printing/print_job_submitter.cc
+++ b/chrome/browser/extensions/api/printing/print_job_submitter.cc
@@ -12,9 +12,9 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "base/values.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/chromeos/printing/cups_printers_manager.h"
 #include "chrome/browser/extensions/api/printing/print_job_controller.h"
-#include "chrome/browser/extensions/api/printing/printer_capabilities_provider.h"
 #include "chrome/browser/extensions/api/printing/printing_api_utils.h"
 #include "chrome/browser/printing/printing_service.h"
 #include "chrome/browser/profiles/profile.h"
@@ -22,6 +22,7 @@
 #include "chrome/browser/ui/native_window_tracker.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/services/printing/public/mojom/pdf_flattener.mojom.h"
+#include "chromeos/crosapi/mojom/local_printer.mojom.h"
 #include "chromeos/printing/printer_configuration.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/browser_context.h"
@@ -35,6 +36,12 @@
 #include "ui/gfx/image/image.h"
 #include "ui/gfx/image/image_skia.h"
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "chrome/browser/ash/crosapi/local_printer_ash.h"
+#elif BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "chromeos/lacros/lacros_service.h"
+#endif
+
 namespace extensions {
 
 namespace {
@@ -80,20 +87,24 @@
 PrintJobSubmitter::PrintJobSubmitter(
     gfx::NativeWindow native_window,
     content::BrowserContext* browser_context,
-    chromeos::CupsPrintersManager* printers_manager,
-    PrinterCapabilitiesProvider* printer_capabilities_provider,
     PrintJobController* print_job_controller,
     mojo::Remote<printing::mojom::PdfFlattener>* pdf_flattener,
     scoped_refptr<const extensions::Extension> extension,
-    api::printing::SubmitJobRequest request)
+    api::printing::SubmitJobRequest request,
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+    int local_printer_version,
+#endif
+    crosapi::mojom::LocalPrinter* local_printer)
     : native_window_(native_window),
       browser_context_(browser_context),
-      printers_manager_(printers_manager),
-      printer_capabilities_provider_(printer_capabilities_provider),
       print_job_controller_(print_job_controller),
       pdf_flattener_(pdf_flattener),
       extension_(extension),
-      request_(std::move(request)) {
+      request_(std::move(request)),
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+      local_printer_version_(local_printer_version),
+#endif
+      local_printer_(local_printer) {
   DCHECK(extension);
   if (native_window)
     native_window_tracker_ = NativeWindowTracker::Create(native_window);
@@ -130,26 +141,31 @@
 }
 
 void PrintJobSubmitter::CheckPrinter() {
-  absl::optional<chromeos::Printer> printer =
-      printers_manager_->GetPrinter(request_.job.printer_id);
-  if (!printer) {
-    FireErrorCallback(kInvalidPrinterId);
+  if (!local_printer_) {
+    LOG(ERROR)
+        << "Local printer not available (PrintJobSubmitter::CheckPrinter()";
+    CheckCapabilitiesCompatibility(nullptr);
     return;
   }
-  printer_name_ = base::UTF8ToUTF16(printer->display_name());
-  printer_capabilities_provider_->GetPrinterCapabilities(
+  local_printer_->GetCapability(
       request_.job.printer_id,
       base::BindOnce(&PrintJobSubmitter::CheckCapabilitiesCompatibility,
                      weak_ptr_factory_.GetWeakPtr()));
 }
 
 void PrintJobSubmitter::CheckCapabilitiesCompatibility(
-    absl::optional<printing::PrinterSemanticCapsAndDefaults> capabilities) {
-  if (!capabilities) {
+    crosapi::mojom::CapabilitiesResponsePtr caps) {
+  if (!caps) {
+    FireErrorCallback(kInvalidPrinterId);
+    return;
+  }
+  printer_name_ = base::UTF8ToUTF16(caps->basic_info->name);
+  if (!caps->capabilities) {
     FireErrorCallback(kPrinterUnavailable);
     return;
   }
-  if (!CheckSettingsAndCapabilitiesCompatibility(*settings_, *capabilities)) {
+  if (!CheckSettingsAndCapabilitiesCompatibility(*settings_,
+                                                 *caps->capabilities)) {
     FireErrorCallback(kUnsupportedTicket);
     return;
   }
diff --git a/chrome/browser/extensions/api/printing/print_job_submitter.h b/chrome/browser/extensions/api/printing/print_job_submitter.h
index 7d05234..7bc3216 100644
--- a/chrome/browser/extensions/api/printing/print_job_submitter.h
+++ b/chrome/browser/extensions/api/printing/print_job_submitter.h
@@ -13,6 +13,7 @@
 #include "base/memory/read_only_shared_memory_region.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/common/extensions/api/printing.h"
+#include "chromeos/crosapi/mojom/local_printer.mojom-forward.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/gfx/native_widget_types.h"
@@ -21,10 +22,6 @@
 class ReadOnlySharedMemoryRegion;
 }  // namespace base
 
-namespace chromeos {
-class CupsPrintersManager;
-}  // namespace chromeos
-
 namespace content {
 class BrowserContext;
 }  // namespace content
@@ -37,7 +34,6 @@
 namespace mojom {
 class PdfFlattener;
 }  // namespace mojom
-struct PrinterSemanticCapsAndDefaults;
 class PrintSettings;
 }  // namespace printing
 
@@ -46,7 +42,6 @@
 namespace extensions {
 
 class Extension;
-class PrinterCapabilitiesProvider;
 class PrintJobController;
 
 // Handles chrome.printing.submitJob() API request including parsing job
@@ -64,12 +59,14 @@
 
   PrintJobSubmitter(gfx::NativeWindow native_window,
                     content::BrowserContext* browser_context,
-                    chromeos::CupsPrintersManager* printers_manager,
-                    PrinterCapabilitiesProvider* printer_capabilities_provider,
                     PrintJobController* print_job_controller,
                     mojo::Remote<printing::mojom::PdfFlattener>* pdf_flattener,
                     scoped_refptr<const extensions::Extension> extension,
-                    api::printing::SubmitJobRequest request);
+                    api::printing::SubmitJobRequest request,
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+                    int local_printer_version,
+#endif
+                    crosapi::mojom::LocalPrinter* local_printer);
   ~PrintJobSubmitter();
 
   // Only one call to Start() should happen at a time.
@@ -82,6 +79,8 @@
   static base::AutoReset<bool> SkipConfirmationDialogForTesting();
 
  private:
+  friend class PrintingAPIHandler;
+
   bool CheckContentType() const;
 
   bool CheckPrintTicket();
@@ -89,7 +88,7 @@
   void CheckPrinter();
 
   void CheckCapabilitiesCompatibility(
-      absl::optional<printing::PrinterSemanticCapsAndDefaults> capabilities);
+      crosapi::mojom::CapabilitiesResponsePtr capabilities);
 
   void ReadDocumentData();
 
@@ -119,8 +118,6 @@
   std::unique_ptr<NativeWindowTracker> native_window_tracker_;
 
   // These objects are owned by PrintingAPIHandler.
-  chromeos::CupsPrintersManager* const printers_manager_;
-  PrinterCapabilitiesProvider* const printer_capabilities_provider_;
   PrintJobController* const print_job_controller_;
   mojo::Remote<printing::mojom::PdfFlattener>* const pdf_flattener_;
 
@@ -134,6 +131,10 @@
   // This is cleared after the request is handled (successfully or not).
   SubmitJobCallback callback_;
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  const int local_printer_version_;
+#endif
+  crosapi::mojom::LocalPrinter* const local_printer_;
   base::WeakPtrFactory<PrintJobSubmitter> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/extensions/api/printing/printer_capabilities_provider.cc b/chrome/browser/extensions/api/printing/printer_capabilities_provider.cc
deleted file mode 100644
index 8d8bffda..0000000
--- a/chrome/browser/extensions/api/printing/printer_capabilities_provider.cc
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/extensions/api/printing/printer_capabilities_provider.h"
-
-#include <utility>
-
-#include "base/bind.h"
-#include "base/callback.h"
-#include "base/task/post_task.h"
-#include "base/task/task_traits.h"
-#include "base/task/thread_pool.h"
-#include "base/threading/sequenced_task_runner_handle.h"
-#include "chrome/browser/browser_process.h"
-#include "chrome/browser/chromeos/printing/cups_printers_manager.h"
-#include "chromeos/printing/printer_configuration.h"
-#include "printing/backend/print_backend.h"
-
-namespace extensions {
-
-namespace {
-
-constexpr int kMaxPrintersCount = 20;
-
-absl::optional<printing::PrinterSemanticCapsAndDefaults>
-FetchCapabilitiesOnBlockingTaskRunner(const std::string& printer_id) {
-  scoped_refptr<printing::PrintBackend> backend(
-      printing::PrintBackend::CreateInstance(
-          g_browser_process->GetApplicationLocale()));
-  printing::PrinterSemanticCapsAndDefaults capabilities;
-  if (backend->GetPrinterSemanticCapsAndDefaults(printer_id, &capabilities) !=
-      printing::mojom::ResultCode::kSuccess) {
-    LOG(WARNING) << "Failed to get capabilities for " << printer_id;
-    return absl::nullopt;
-  }
-  return capabilities;
-}
-
-}  // namespace
-
-PrinterCapabilitiesProvider::PrinterCapabilitiesProvider(
-    chromeos::CupsPrintersManager* printers_manager,
-    std::unique_ptr<chromeos::PrinterConfigurer> printer_configurer)
-    : printers_manager_(printers_manager),
-      printer_configurer_(std::move(printer_configurer)),
-      printer_capabilities_cache_(kMaxPrintersCount) {}
-
-PrinterCapabilitiesProvider::~PrinterCapabilitiesProvider() = default;
-
-void PrinterCapabilitiesProvider::GetPrinterCapabilities(
-    const std::string& printer_id,
-    GetPrinterCapabilitiesCallback callback) {
-  absl::optional<chromeos::Printer> printer =
-      printers_manager_->GetPrinter(printer_id);
-  if (!printer) {
-    base::SequencedTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, base::BindOnce(std::move(callback), absl::nullopt));
-    return;
-  }
-
-  if (!printers_manager_->IsPrinterInstalled(*printer)) {
-    printer_configurer_->SetUpPrinter(
-        *printer,
-        base::BindOnce(&PrinterCapabilitiesProvider::OnPrinterInstalled,
-                       weak_ptr_factory_.GetWeakPtr(), *printer,
-                       std::move(callback)));
-    return;
-  }
-
-  auto capabilities = printer_capabilities_cache_.Get(printer->id());
-  if (capabilities == printer_capabilities_cache_.end()) {
-    FetchCapabilities(printer->id(), std::move(callback));
-    return;
-  }
-  base::SequencedTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(std::move(callback), capabilities->second));
-}
-
-void PrinterCapabilitiesProvider::OnPrinterInstalled(
-    const chromeos::Printer& printer,
-    GetPrinterCapabilitiesCallback callback,
-    chromeos::PrinterSetupResult result) {
-  if (result != chromeos::kSuccess) {
-    base::SequencedTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, base::BindOnce(std::move(callback), absl::nullopt));
-    return;
-  }
-  printers_manager_->PrinterInstalled(printer, /*is_automatic=*/true);
-  FetchCapabilities(printer.id(), std::move(callback));
-}
-
-void PrinterCapabilitiesProvider::FetchCapabilities(
-    const std::string& printer_id,
-    GetPrinterCapabilitiesCallback callback) {
-  base::ThreadPool::PostTaskAndReplyWithResult(
-      FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
-      base::BindOnce(&FetchCapabilitiesOnBlockingTaskRunner, printer_id),
-      base::BindOnce(&PrinterCapabilitiesProvider::OnCapabilitiesFetched,
-                     weak_ptr_factory_.GetWeakPtr(), printer_id,
-                     std::move(callback)));
-}
-
-void PrinterCapabilitiesProvider::OnCapabilitiesFetched(
-    const std::string& printer_id,
-    GetPrinterCapabilitiesCallback callback,
-    absl::optional<printing::PrinterSemanticCapsAndDefaults> capabilities) {
-  if (capabilities.has_value())
-    printer_capabilities_cache_.Put(printer_id, capabilities.value());
-  base::SequencedTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(std::move(callback), std::move(capabilities)));
-}
-
-}  // namespace extensions
diff --git a/chrome/browser/extensions/api/printing/printer_capabilities_provider.h b/chrome/browser/extensions/api/printing/printer_capabilities_provider.h
deleted file mode 100644
index bc6ad94..0000000
--- a/chrome/browser/extensions/api/printing/printer_capabilities_provider.h
+++ /dev/null
@@ -1,70 +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 CHROME_BROWSER_EXTENSIONS_API_PRINTING_PRINTER_CAPABILITIES_PROVIDER_H_
-#define CHROME_BROWSER_EXTENSIONS_API_PRINTING_PRINTER_CAPABILITIES_PROVIDER_H_
-
-#include <memory>
-#include <string>
-
-#include "base/callback_forward.h"
-#include "base/containers/mru_cache.h"
-#include "base/memory/weak_ptr.h"
-#include "chrome/browser/chromeos/printing/printer_configurer.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
-
-namespace chromeos {
-class CupsPrintersManager;
-class Printer;
-}  // namespace chromeos
-
-namespace printing {
-struct PrinterSemanticCapsAndDefaults;
-}  // namespace printing
-
-namespace extensions {
-
-// Provides the capabilities for printers installed in Chrome OS.
-// Is used for chrome.printing API handling.
-class PrinterCapabilitiesProvider {
- public:
-  using GetPrinterCapabilitiesCallback = base::OnceCallback<void(
-      absl::optional<printing::PrinterSemanticCapsAndDefaults> capabilities)>;
-
-  PrinterCapabilitiesProvider(
-      chromeos::CupsPrintersManager* printers_manager,
-      std::unique_ptr<chromeos::PrinterConfigurer> printer_configurer);
-  ~PrinterCapabilitiesProvider();
-
-  void GetPrinterCapabilities(const std::string& printer_id,
-                              GetPrinterCapabilitiesCallback callback);
-
- private:
-  using PrinterCapabilitiesCache =
-      base::MRUCache<std::string, printing::PrinterSemanticCapsAndDefaults>;
-
-  void OnPrinterInstalled(const chromeos::Printer& printer,
-                          GetPrinterCapabilitiesCallback callback,
-                          chromeos::PrinterSetupResult result);
-
-  void FetchCapabilities(const std::string& printer_id,
-                         GetPrinterCapabilitiesCallback callback);
-
-  void OnCapabilitiesFetched(
-      const std::string& printer_id,
-      GetPrinterCapabilitiesCallback callback,
-      absl::optional<printing::PrinterSemanticCapsAndDefaults> capabilities);
-
-  chromeos::CupsPrintersManager* const printers_manager_;
-  std::unique_ptr<chromeos::PrinterConfigurer> printer_configurer_;
-
-  // Stores mapping from printer id to cached printer capabilities.
-  PrinterCapabilitiesCache printer_capabilities_cache_;
-
-  base::WeakPtrFactory<PrinterCapabilitiesProvider> weak_ptr_factory_{this};
-};
-
-}  // namespace extensions
-
-#endif  // CHROME_BROWSER_EXTENSIONS_API_PRINTING_PRINTER_CAPABILITIES_PROVIDER_H_
diff --git a/chrome/browser/extensions/api/printing/printing_api.cc b/chrome/browser/extensions/api/printing/printing_api.cc
index b5fb2f4..8dd951f 100644
--- a/chrome/browser/extensions/api/printing/printing_api.cc
+++ b/chrome/browser/extensions/api/printing/printing_api.cc
@@ -10,6 +10,7 @@
 #include "base/values.h"
 #include "chrome/browser/extensions/api/printing/printing_api_handler.h"
 #include "chrome/browser/extensions/chrome_extension_function_details.h"
+#include "extensions/browser/extension_function.h"
 #include "extensions/browser/quota_service.h"
 
 namespace extensions {
@@ -69,11 +70,19 @@
   return RespondNow(NoArguments());
 }
 
+PrintingGetPrintersFunction::PrintingGetPrintersFunction() = default;
 PrintingGetPrintersFunction::~PrintingGetPrintersFunction() = default;
 
 ExtensionFunction::ResponseAction PrintingGetPrintersFunction::Run() {
-  return RespondNow(ArgumentList(api::printing::GetPrinters::Results::Create(
-      PrintingAPIHandler::Get(browser_context())->GetPrinters())));
+  PrintingAPIHandler::Get(browser_context())
+      ->GetPrinters(
+          base::BindOnce(&PrintingGetPrintersFunction::OnPrintersReady, this));
+  return RespondLater();
+}
+
+void PrintingGetPrintersFunction::OnPrintersReady(
+    std::vector<api::printing::Printer> printers) {
+  Respond(ArgumentList(api::printing::GetPrinters::Results::Create(printers)));
 }
 
 PrintingGetPrinterInfoFunction::~PrintingGetPrinterInfoFunction() = default;
diff --git a/chrome/browser/extensions/api/printing/printing_api.h b/chrome/browser/extensions/api/printing/printing_api.h
index f07eacf2..160dfbd 100644
--- a/chrome/browser/extensions/api/printing/printing_api.h
+++ b/chrome/browser/extensions/api/printing/printing_api.h
@@ -47,6 +47,9 @@
 };
 
 class PrintingGetPrintersFunction : public ExtensionFunction {
+ public:
+  PrintingGetPrintersFunction();
+
  protected:
   ~PrintingGetPrintersFunction() override;
 
@@ -54,6 +57,7 @@
   ResponseAction Run() override;
 
  private:
+  void OnPrintersReady(std::vector<api::printing::Printer> printers);
   DECLARE_EXTENSION_FUNCTION("printing.getPrinters", PRINTING_GETPRINTERS)
 };
 
diff --git a/chrome/browser/extensions/api/printing/printing_api_handler.cc b/chrome/browser/extensions/api/printing/printing_api_handler.cc
index 6282ee3..bd60d3e 100644
--- a/chrome/browser/extensions/api/printing/printing_api_handler.cc
+++ b/chrome/browser/extensions/api/printing/printing_api_handler.cc
@@ -7,30 +7,52 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/callback.h"
+#include "base/callback_helpers.h"
+#include "base/check.h"
+#include "base/check_op.h"
+#include "base/containers/contains.h"
 #include "base/location.h"
-#include "base/memory/ptr_util.h"
 #include "base/no_destructor.h"
+#include "base/sequenced_task_runner.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
 #include "base/task/post_task.h"
+#include "base/task_runner.h"
 #include "base/threading/sequenced_task_runner_handle.h"
-#include "chrome/browser/ash/profiles/profile_helper.h"
-#include "chrome/browser/chromeos/printing/cups_print_job.h"
-#include "chrome/browser/chromeos/printing/cups_printers_manager.h"
+#include "build/chromeos_buildflags.h"
+#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/chromeos/printing/cups_wrapper.h"
-#include "chrome/browser/chromeos/printing/printer_configurer.h"
 #include "chrome/browser/chromeos/printing/printer_error_codes.h"
 #include "chrome/browser/extensions/api/printing/printing_api_utils.h"
+#include "chrome/browser/printing/print_job.h"
 #include "chrome/browser/printing/print_preview_sticky_settings.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/pref_names.h"
-#include "chromeos/printing/printer_configuration.h"
+#include "chromeos/crosapi/mojom/local_printer.mojom.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 #include "components/printing/common/cloud_print_cdd_conversion.h"
 #include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_source.h"
 #include "extensions/browser/event_router.h"
 #include "extensions/browser/extension_registry.h"
-#include "printing/backend/cups_jobs.h"
 #include "printing/backend/print_backend.h"
+#include "printing/print_settings.h"
+#include "printing/printed_document.h"
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "chrome/browser/ash/crosapi/crosapi_ash.h"
+#include "chrome/browser/ash/crosapi/crosapi_manager.h"
+#include "chrome/browser/ash/crosapi/local_printer_ash.h"
+#elif BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "chromeos/lacros/lacros_service.h"
+#endif
 
 namespace extensions {
 
@@ -47,57 +69,75 @@
     content::BrowserContext* browser_context,
     EventRouter* event_router,
     ExtensionRegistry* extension_registry,
-    chromeos::CupsPrintJobManager* print_job_manager,
-    chromeos::CupsPrintersManager* printers_manager,
     std::unique_ptr<PrintJobController> print_job_controller,
-    std::unique_ptr<chromeos::PrinterConfigurer> printer_configurer,
-    std::unique_ptr<chromeos::CupsWrapper> cups_wrapper) {
-  return base::WrapUnique(new PrintingAPIHandler(
-      browser_context, event_router, extension_registry, print_job_manager,
-      printers_manager, std::move(print_job_controller),
-      std::move(printer_configurer), std::move(cups_wrapper)));
+    std::unique_ptr<chromeos::CupsWrapper> cups_wrapper,
+    crosapi::mojom::LocalPrinter* local_printer) {
+  auto handler = std::make_unique<PrintingAPIHandler>(
+      browser_context, event_router, extension_registry,
+      std::move(print_job_controller), std::move(cups_wrapper), local_printer);
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  handler->local_printer_version_ = crosapi::mojom::LocalPrinter::Version_;
+#endif
+  return handler;
 }
 
 PrintingAPIHandler::PrintingAPIHandler(content::BrowserContext* browser_context)
-    : PrintingAPIHandler(
-          browser_context,
-          EventRouter::Get(browser_context),
-          ExtensionRegistry::Get(browser_context),
-          chromeos::CupsPrintJobManagerFactory::GetForBrowserContext(
-              browser_context),
-          chromeos::CupsPrintersManagerFactory::GetForBrowserContext(
-              browser_context),
-          PrintJobController::Create(
-              chromeos::CupsPrintJobManagerFactory::GetForBrowserContext(
-                  browser_context)),
-          chromeos::PrinterConfigurer::Create(
-              Profile::FromBrowserContext(browser_context)),
-          chromeos::CupsWrapper::Create()) {}
+    : PrintingAPIHandler(browser_context,
+                         EventRouter::Get(browser_context),
+                         ExtensionRegistry::Get(browser_context),
+                         PrintJobController::Create(),
+                         chromeos::CupsWrapper::Create()) {
+  registrar_.Add(this, chrome::NOTIFICATION_PRINT_JOB_EVENT,
+                 content::NotificationService::AllSources());
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  DCHECK(crosapi::CrosapiManager::IsInitialized());
+  local_printer_ =
+      crosapi::CrosapiManager::Get()->crosapi_ash()->local_printer_ash();
+#elif BUILDFLAG(IS_CHROMEOS_LACROS)
+  chromeos::LacrosService* service = chromeos::LacrosService::Get();
+  if (!service->IsAvailable<crosapi::mojom::LocalPrinter>()) {
+    LOG(ERROR) << "Local printer not available (PrintJobControllerImpl)";
+    return;
+  }
+  local_printer_ = service->GetRemote<crosapi::mojom::LocalPrinter>().get();
+  local_printer_version_ =
+      service->GetInterfaceVersion(crosapi::mojom::LocalPrinter::Uuid_);
+  if (local_printer_version_ <
+      int{crosapi::mojom::LocalPrinter::MethodMinVersions::
+              kAddPrintJobObserverMinVersion}) {
+    LOG(WARNING) << "Ash LocalPrinter version " << local_printer_version_
+                 << " does not support AddExtensionPrintJobObserver().";
+    return;
+  }
+#endif
+  local_printer_->AddPrintJobObserver(
+      receiver_.BindNewPipeAndPassRemoteWithVersion(),
+      crosapi::mojom::PrintJobSource::kExtension, base::DoNothing());
+}
 
 PrintingAPIHandler::PrintingAPIHandler(
     content::BrowserContext* browser_context,
     EventRouter* event_router,
     ExtensionRegistry* extension_registry,
-    chromeos::CupsPrintJobManager* print_job_manager,
-    chromeos::CupsPrintersManager* printers_manager,
     std::unique_ptr<PrintJobController> print_job_controller,
-    std::unique_ptr<chromeos::PrinterConfigurer> printer_configurer,
-    std::unique_ptr<chromeos::CupsWrapper> cups_wrapper)
+    std::unique_ptr<chromeos::CupsWrapper> cups_wrapper,
+    crosapi::mojom::LocalPrinter* local_printer)
     : browser_context_(browser_context),
       event_router_(event_router),
       extension_registry_(extension_registry),
-      print_job_manager_(print_job_manager),
-      printers_manager_(printers_manager),
       print_job_controller_(std::move(print_job_controller)),
-      printer_capabilities_provider_(printers_manager,
-                                     std::move(printer_configurer)),
-      cups_wrapper_(std::move(cups_wrapper)) {
-  print_job_manager_observation_.Observe(print_job_manager_);
-}
+      cups_wrapper_(std::move(cups_wrapper)),
+      local_printer_(local_printer) {}
 
 PrintingAPIHandler::~PrintingAPIHandler() = default;
 
 // static
+std::string PrintingAPIHandler::CreateUniqueId(const std::string& printer_id,
+                                               int job_id) {
+  return base::StringPrintf("%s%d", printer_id.c_str(), job_id);
+}
+
+// static
 BrowserContextKeyedAPIFactory<PrintingAPIHandler>*
 PrintingAPIHandler::GetFactoryInstance() {
   static base::NoDestructor<BrowserContextKeyedAPIFactory<PrintingAPIHandler>>
@@ -123,9 +163,12 @@
     std::unique_ptr<api::printing::SubmitJob::Params> params,
     PrintJobSubmitter::SubmitJobCallback callback) {
   auto print_job_submitter = std::make_unique<PrintJobSubmitter>(
-      native_window, browser_context_, printers_manager_,
-      &printer_capabilities_provider_, print_job_controller_.get(),
-      &pdf_flattener_, std::move(extension), std::move(params->request));
+      native_window, browser_context_, print_job_controller_.get(),
+      &pdf_flattener_, std::move(extension), std::move(params->request),
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+      local_printer_version_,
+#endif
+      local_printer_);
   PrintJobSubmitter* print_job_submitter_ptr = print_job_submitter.get();
   print_job_submitter_ptr->Start(base::BindOnce(
       &PrintingAPIHandler::OnPrintJobSubmitted, weak_ptr_factory_.GetWeakPtr(),
@@ -138,6 +181,8 @@
     absl::optional<api::printing::SubmitJobStatus> status,
     std::unique_ptr<std::string> job_id,
     absl::optional<std::string> error) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
   base::SequencedTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
       base::BindOnce(std::move(callback), status, std::move(job_id), error));
@@ -146,22 +191,56 @@
 absl::optional<std::string> PrintingAPIHandler::CancelJob(
     const std::string& extension_id,
     const std::string& job_id) {
-  auto it = print_jobs_extension_ids_.find(job_id);
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  auto it = print_jobs_.find(job_id);
+
   // If there was no print job with specified id sent by the extension return
   // an error.
-  if (it == print_jobs_extension_ids_.end() || it->second != extension_id)
+  if (it == print_jobs_.end() || it->second.extension_id != extension_id) {
     return kNoActivePrintJobWithIdError;
+  }
 
-  // If we can't cancel the print job (e.g. it's in terminated state) return an
-  // error.
-  if (!print_job_controller_->CancelPrintJob(job_id))
-    return kNoActivePrintJobWithIdError;
+  if (!local_printer_)
+    return "Local printer not available";
 
-  // Return no error otherwise.
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  if (local_printer_version_ <
+      int{crosapi::mojom::LocalPrinter::MethodMinVersions::
+              kCancelPrintJobMinVersion}) {
+    LOG(WARNING) << "Ash LocalPrinter version " << local_printer_version_
+                 << " does not support CancelPrintJob().";
+    return "Ash local printer interface does not support CancelPrintJob()";
+  }
+#endif
+
+  local_printer_->CancelPrintJob(it->second.printer_id, it->second.job_id,
+                                 base::DoNothing());
   return absl::nullopt;
 }
 
-std::vector<api::printing::Printer> PrintingAPIHandler::GetPrinters() {
+void PrintingAPIHandler::GetPrinters(GetPrintersCallback callback) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  if (!local_printer_) {
+    LOG(ERROR) << "Local printer interface not available "
+                  "(PrintingAPIHandler::GetPrinters()";
+    base::SequencedTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback),
+                                  std::vector<api::printing::Printer>{}));
+    return;
+  }
+
+  local_printer_->GetPrinters(
+      base::BindOnce(&PrintingAPIHandler::OnPrintersRetrieved,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void PrintingAPIHandler::OnPrintersRetrieved(
+    GetPrintersCallback callback,
+    std::vector<crosapi::mojom::LocalDestinationInfoPtr> data) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
   PrefService* prefs =
       Profile::FromBrowserContext(browser_context_)->GetPrefs();
 
@@ -174,63 +253,68 @@
   base::flat_map<std::string, int> recently_used_ranks =
       sticky_settings->GetPrinterRecentlyUsedRanks();
 
-  static constexpr chromeos::PrinterClass kPrinterClasses[] = {
-      chromeos::PrinterClass::kEnterprise,
-      chromeos::PrinterClass::kSaved,
-      chromeos::PrinterClass::kAutomatic,
-  };
-  std::vector<api::printing::Printer> all_printers;
-  for (auto printer_class : kPrinterClasses) {
-    const std::vector<chromeos::Printer>& printers =
-        printers_manager_->GetPrinters(printer_class);
-    for (const auto& printer : printers) {
-      all_printers.emplace_back(
-          PrinterToIdl(printer, default_printer_rules, recently_used_ranks));
-    }
+  std::vector<api::printing::Printer> printers;
+  printers.reserve(data.size());
+  for (const crosapi::mojom::LocalDestinationInfoPtr& ptr : data) {
+    printers.push_back(
+        PrinterToIdl(*ptr, default_printer_rules, recently_used_ranks));
   }
-  return all_printers;
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback), std::move(printers)));
 }
 
 void PrintingAPIHandler::GetPrinterInfo(const std::string& printer_id,
                                         GetPrinterInfoCallback callback) {
-  if (!printers_manager_->GetPrinter(printer_id)) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  if (!local_printer_) {
+    LOG(ERROR)
+        << "Local printer not available (PrintingAPIHandler::GetPrinterInfo)";
+    OnPrinterCapabilitiesRetrieved(std::string(), std::move(callback), nullptr);
+    return;
+  }
+  local_printer_->GetCapability(
+      printer_id,
+      base::BindOnce(&PrintingAPIHandler::OnPrinterCapabilitiesRetrieved,
+                     weak_ptr_factory_.GetWeakPtr(), printer_id,
+                     std::move(callback)));
+}
+
+void PrintingAPIHandler::OnPrinterCapabilitiesRetrieved(
+    const std::string& printer_id,
+    GetPrinterInfoCallback callback,
+    crosapi::mojom::CapabilitiesResponsePtr caps) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  if (!caps) {
     base::SequencedTaskRunnerHandle::Get()->PostTask(
         FROM_HERE,
         base::BindOnce(std::move(callback), /*capabilities=*/absl::nullopt,
                        /*status=*/absl::nullopt, kInvalidPrinterIdError));
     return;
   }
-  printer_capabilities_provider_.GetPrinterCapabilities(
-      printer_id, base::BindOnce(&PrintingAPIHandler::GetPrinterStatus,
-                                 weak_ptr_factory_.GetWeakPtr(), printer_id,
-                                 std::move(callback)));
-}
-
-void PrintingAPIHandler::GetPrinterStatus(
-    const std::string& printer_id,
-    GetPrinterInfoCallback callback,
-    absl::optional<printing::PrinterSemanticCapsAndDefaults> capabilities) {
-  if (!capabilities.has_value()) {
+  if (!caps->capabilities) {
     base::SequencedTaskRunnerHandle::Get()->PostTask(
         FROM_HERE,
         base::BindOnce(std::move(callback), /*capabilities=*/absl::nullopt,
-                       api::printing::PRINTER_STATUS_UNREACHABLE,
+                       /*status=*/api::printing::PRINTER_STATUS_UNREACHABLE,
                        /*error=*/absl::nullopt));
     return;
   }
-  base::Value capabilities_value =
-      cloud_print::PrinterSemanticCapsAndDefaultsToCdd(capabilities.value());
   cups_wrapper_->QueryCupsPrinterStatus(
       printer_id,
       base::BindOnce(&PrintingAPIHandler::OnPrinterStatusRetrieved,
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback),
-                     std::move(capabilities_value)));
+                     cloud_print::PrinterSemanticCapsAndDefaultsToCdd(
+                         *caps->capabilities)));
 }
 
 void PrintingAPIHandler::OnPrinterStatusRetrieved(
     GetPrinterInfoCallback callback,
     base::Value capabilities,
     std::unique_ptr<::printing::PrinterStatus> printer_status) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
   if (!printer_status) {
     base::SequencedTaskRunnerHandle::Get()->PostTask(
         FROM_HERE, base::BindOnce(std::move(callback), std::move(capabilities),
@@ -252,72 +336,91 @@
   print_job_controller_ = std::move(print_job_controller);
 }
 
-void PrintingAPIHandler::OnPrintJobCreated(
-    base::WeakPtr<chromeos::CupsPrintJob> job) {
-  DispatchJobStatusChangedEvent(api::printing::JOB_STATUS_PENDING, job);
-  if (!job || job->source() != printing::PrintJob::Source::EXTENSION)
+void PrintingAPIHandler::Observe(int type,
+                                 const content::NotificationSource& source,
+                                 const content::NotificationDetails& details) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK_EQ(chrome::NOTIFICATION_PRINT_JOB_EVENT, type);
+
+  content::Source<printing::PrintJob> job(source);
+  content::Details<printing::JobEventDetails> job_details(details);
+  if (job->source() != crosapi::mojom::PrintJob::Source::EXTENSION ||
+      job_details->type() != printing::JobEventDetails::DOC_DONE ||
+      !extension_registry_->enabled_extensions().Contains(job->source_id())) {
     return;
-  const std::string& extension_id = job->source_id();
-  const std::string& job_id = job->GetUniqueId();
-  print_jobs_extension_ids_[job_id] = extension_id;
-  print_job_controller_->OnPrintJobCreated(extension_id, job_id, job);
-}
+  }
 
-void PrintingAPIHandler::OnPrintJobStarted(
-    base::WeakPtr<chromeos::CupsPrintJob> job) {
-  DispatchJobStatusChangedEvent(api::printing::JOB_STATUS_IN_PROGRESS, job);
-}
-
-void PrintingAPIHandler::OnPrintJobDone(
-    base::WeakPtr<chromeos::CupsPrintJob> job) {
-  DispatchJobStatusChangedEvent(api::printing::JOB_STATUS_PRINTED, job);
-  FinishJob(job);
-}
-
-void PrintingAPIHandler::OnPrintJobError(
-    base::WeakPtr<chromeos::CupsPrintJob> job) {
-  DispatchJobStatusChangedEvent(api::printing::JOB_STATUS_FAILED, job);
-  FinishJob(job);
-}
-
-void PrintingAPIHandler::OnPrintJobCancelled(
-    base::WeakPtr<chromeos::CupsPrintJob> job) {
-  DispatchJobStatusChangedEvent(api::printing::JOB_STATUS_CANCELED, job);
-  FinishJob(job);
-}
-
-void PrintingAPIHandler::DispatchJobStatusChangedEvent(
-    api::printing::JobStatus job_status,
-    base::WeakPtr<chromeos::CupsPrintJob> job) {
-  if (!job || job->source() != printing::PrintJob::Source::EXTENSION)
-    return;
-
+  std::string printer_id =
+      base::UTF16ToUTF8(job_details->document()->settings().device_name());
+  std::string cups_id = CreateUniqueId(printer_id, job_details->job_id());
+  DCHECK(!base::Contains(print_jobs_, cups_id));
+  print_jobs_[cups_id] =
+      PrintJobInfo{printer_id, job_details->job_id(), job->source_id()};
+  print_job_controller_->OnPrintJobCreated(job->source_id(), cups_id);
   auto event =
       std::make_unique<Event>(events::PRINTING_ON_JOB_STATUS_CHANGED,
                               api::printing::OnJobStatusChanged::kEventName,
                               api::printing::OnJobStatusChanged::Create(
-                                  job->GetUniqueId(), job_status));
-
-  if (extension_registry_->enabled_extensions().Contains(job->source_id()))
-    event_router_->DispatchEventToExtension(job->source_id(), std::move(event));
+                                  cups_id, api::printing::JOB_STATUS_PENDING));
+  event_router_->DispatchEventToExtension(job->source_id(), std::move(event));
 }
 
-void PrintingAPIHandler::FinishJob(base::WeakPtr<chromeos::CupsPrintJob> job) {
-  if (!job || job->source() != printing::PrintJob::Source::EXTENSION)
+void PrintingAPIHandler::OnPrintJobUpdate(
+    const std::string& printer_id,
+    unsigned int job_id,
+    crosapi::mojom::PrintJobStatus status) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  bool done = true;
+  api::printing::JobStatus job_status;
+  switch (status) {
+    case crosapi::mojom::PrintJobStatus::kStarted:
+      job_status = api::printing::JOB_STATUS_IN_PROGRESS;
+      done = false;
+      break;
+    case crosapi::mojom::PrintJobStatus::kDone:
+      job_status = api::printing::JOB_STATUS_PRINTED;
+      break;
+    case crosapi::mojom::PrintJobStatus::kError:
+      job_status = api::printing::JOB_STATUS_FAILED;
+      break;
+    case crosapi::mojom::PrintJobStatus::kCancelled:
+      job_status = api::printing::JOB_STATUS_CANCELED;
+      break;
+    default:  // crosapi::mojom::PrintJobStatus::kCreated
+      return;
+  }
+
+  std::string cups_id = CreateUniqueId(printer_id, job_id);
+  auto it = print_jobs_.find(cups_id);
+  if (it == print_jobs_.end())
     return;
-  const std::string& job_id = job->GetUniqueId();
-  print_jobs_extension_ids_.erase(job_id);
-  print_job_controller_->OnPrintJobFinished(job_id);
+  const std::string& extension_id = it->second.extension_id;
+
+  if (extension_registry_->enabled_extensions().Contains(extension_id)) {
+    auto event = std::make_unique<Event>(
+        events::PRINTING_ON_JOB_STATUS_CHANGED,
+        api::printing::OnJobStatusChanged::kEventName,
+        api::printing::OnJobStatusChanged::Create(cups_id, job_status));
+    event_router_->DispatchEventToExtension(extension_id, std::move(event));
+  }
+
+  if (done) {
+    print_jobs_.erase(it);
+    print_job_controller_->OnPrintJobFinished(cups_id);
+  }
 }
 
 template <>
 KeyedService*
 BrowserContextKeyedAPIFactory<PrintingAPIHandler>::BuildServiceInstanceFor(
     content::BrowserContext* context) const {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
   Profile* profile = Profile::FromBrowserContext(context);
   // We do not want an instance of PrintingAPIHandler on the lock screen.
   // This will lead to multiple printing notifications.
-  if (!chromeos::ProfileHelper::IsRegularProfile(profile)) {
+  if (!profile->IsRegularProfile()) {
     return nullptr;
   }
   return new PrintingAPIHandler(context);
diff --git a/chrome/browser/extensions/api/printing/printing_api_handler.h b/chrome/browser/extensions/api/printing/printing_api_handler.h
index fdf0ce6..6877ddc 100644
--- a/chrome/browser/extensions/api/printing/printing_api_handler.h
+++ b/chrome/browser/extensions/api/printing/printing_api_handler.h
@@ -9,19 +9,21 @@
 #include <string>
 #include <vector>
 
+#include "base/callback.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/scoped_observation.h"
-#include "chrome/browser/chromeos/printing/cups_print_job_manager.h"
-#include "chrome/browser/chromeos/printing/cups_print_job_manager_factory.h"
-#include "chrome/browser/chromeos/printing/cups_printers_manager_factory.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/extensions/api/printing/print_job_controller.h"
 #include "chrome/browser/extensions/api/printing/print_job_submitter.h"
-#include "chrome/browser/extensions/api/printing/printer_capabilities_provider.h"
 #include "chrome/common/extensions/api/printing.h"
 #include "chrome/services/printing/public/mojom/pdf_flattener.mojom.h"
+#include "chromeos/crosapi/mojom/local_printer.mojom.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
 #include "extensions/browser/browser_context_keyed_api_factory.h"
 #include "extensions/browser/event_router_factory.h"
+#include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/gfx/native_widget_types.h"
@@ -31,7 +33,6 @@
 namespace chromeos {
 class CupsWrapper;
 class Printer;
-class PrinterConfigurer;
 }  // namespace chromeos
 
 namespace content {
@@ -39,7 +40,6 @@
 }  // namespace content
 
 namespace printing {
-struct PrinterSemanticCapsAndDefaults;
 struct PrinterStatus;
 }  // namespace printing
 
@@ -48,15 +48,20 @@
 class PrintJobSubmitter;
 class ExtensionRegistry;
 
-// Handles chrome.printing API functions calls, observes CupsPrintJobManager and
-// generates OnJobStatusChanged() events of chrome.printing API.
+// Handles chrome.printing API functions calls, observes NotificationService,
+// and generates OnJobStatusChanged() events of chrome.printing API.
+// The callback function is never run directly - it is posted to
+// base::SequencedTaskRunnerHandle::Get().
 class PrintingAPIHandler : public BrowserContextKeyedAPI,
-                           public chromeos::CupsPrintJobManager::Observer {
+                           public crosapi::mojom::PrintJobObserver,
+                           public content::NotificationObserver {
  public:
   using SubmitJobCallback = base::OnceCallback<void(
       absl::optional<api::printing::SubmitJobStatus> status,
       std::unique_ptr<std::string> job_id,
       absl::optional<std::string> error)>;
+  using GetPrintersCallback =
+      base::OnceCallback<void(std::vector<api::printing::Printer>)>;
   using GetPrinterInfoCallback = base::OnceCallback<void(
       absl::optional<base::Value> capabilities,
       absl::optional<api::printing::PrinterStatus> status,
@@ -66,15 +71,27 @@
       content::BrowserContext* browser_context,
       EventRouter* event_router,
       ExtensionRegistry* extension_registry,
-      chromeos::CupsPrintJobManager* print_job_manager,
-      chromeos::CupsPrintersManager* printers_manager,
       std::unique_ptr<PrintJobController> print_job_controller,
-      std::unique_ptr<chromeos::PrinterConfigurer> printer_configurer,
-      std::unique_ptr<chromeos::CupsWrapper> cups_wrapper);
+      std::unique_ptr<chromeos::CupsWrapper> cups_wrapper,
+      crosapi::mojom::LocalPrinter* local_printer);
 
   explicit PrintingAPIHandler(content::BrowserContext* browser_context);
+  PrintingAPIHandler(content::BrowserContext* browser_context,
+                     EventRouter* event_router,
+                     ExtensionRegistry* extension_registry,
+                     std::unique_ptr<PrintJobController> print_job_controller,
+                     std::unique_ptr<chromeos::CupsWrapper> cups_wrapper,
+                     crosapi::mojom::LocalPrinter* local_printer = nullptr);
+  PrintingAPIHandler(const PrintingAPIHandler&) = delete;
+  PrintingAPIHandler& operator=(const PrintingAPIHandler&) = delete;
   ~PrintingAPIHandler() override;
 
+  static std::string CreateUniqueId(const std::string& printer_id, int job_id);
+
+  // NotificationObserver:
+  void Observe(int type,
+               const content::NotificationSource& source,
+               const content::NotificationDetails& details) override;
   // BrowserContextKeyedAPI:
   static BrowserContextKeyedAPIFactory<PrintingAPIHandler>*
   GetFactoryInstance();
@@ -82,6 +99,16 @@
   // Returns the current instance for |browser_context|.
   static PrintingAPIHandler* Get(content::BrowserContext* browser_context);
 
+  // Print jobs should be registered before OnPrintJobUpdate() is called.
+  void RegisterPrintJob(const std::string& printer_id,
+                        int job_id,
+                        const std::string& extension_id);
+
+  // crosapi::mojom::PrintJobObserver:
+  void OnPrintJobUpdate(const std::string& printer_id,
+                        unsigned int job_id,
+                        crosapi::mojom::PrintJobStatus status) override;
+
   // Register the printing API preference with the |registry|.
   static void RegisterProfilePrefs(PrefRegistrySimple* registry);
 
@@ -99,7 +126,7 @@
   absl::optional<std::string> CancelJob(const std::string& extension_id,
                                         const std::string& job_id);
 
-  std::vector<api::printing::Printer> GetPrinters();
+  void GetPrinters(GetPrintersCallback callback);
 
   void GetPrinterInfo(const std::string& printer_id,
                       GetPrinterInfoCallback callback);
@@ -111,15 +138,11 @@
   // Needed for BrowserContextKeyedAPI implementation.
   friend class BrowserContextKeyedAPIFactory<PrintingAPIHandler>;
 
-  PrintingAPIHandler(
-      content::BrowserContext* browser_context,
-      EventRouter* event_router,
-      ExtensionRegistry* extension_registry,
-      chromeos::CupsPrintJobManager* print_job_manager,
-      chromeos::CupsPrintersManager* printers_manager,
-      std::unique_ptr<PrintJobController> print_job_controller,
-      std::unique_ptr<chromeos::PrinterConfigurer> printer_configurer,
-      std::unique_ptr<chromeos::CupsWrapper> cups_wrapper);
+  struct PrintJobInfo {
+    std::string printer_id;
+    int job_id;
+    std::string extension_id;
+  };
 
   // This is needed to save ownership of |print_job_submitter| object which
   // could be destructed because of asynchronous work otherwise.
@@ -130,28 +153,22 @@
       std::unique_ptr<std::string> job_id,
       absl::optional<std::string> error);
 
-  void GetPrinterStatus(
+  void OnPrintersRetrieved(
+      GetPrintersCallback callback,
+      std::vector<crosapi::mojom::LocalDestinationInfoPtr> data);
+
+  // GetPrinterInfo() calls this function.
+  void OnPrinterCapabilitiesRetrieved(
       const std::string& printer_id,
       GetPrinterInfoCallback callback,
-      absl::optional<printing::PrinterSemanticCapsAndDefaults> capabilities);
+      crosapi::mojom::CapabilitiesResponsePtr caps);
 
+  // OnPrinterCapabilitiesRetrieved() calls this function.
   void OnPrinterStatusRetrieved(
       GetPrinterInfoCallback callback,
       base::Value capabilities,
       std::unique_ptr<::printing::PrinterStatus> printer_status);
 
-  // CupsPrintJobManager::Observer:
-  void OnPrintJobCreated(base::WeakPtr<chromeos::CupsPrintJob> job) override;
-  void OnPrintJobStarted(base::WeakPtr<chromeos::CupsPrintJob> job) override;
-  void OnPrintJobDone(base::WeakPtr<chromeos::CupsPrintJob> job) override;
-  void OnPrintJobError(base::WeakPtr<chromeos::CupsPrintJob> job) override;
-  void OnPrintJobCancelled(base::WeakPtr<chromeos::CupsPrintJob> job) override;
-
-  void DispatchJobStatusChangedEvent(api::printing::JobStatus job_status,
-                                     base::WeakPtr<chromeos::CupsPrintJob> job);
-
-  void FinishJob(base::WeakPtr<chromeos::CupsPrintJob> job);
-
   // BrowserContextKeyedAPI:
   static const bool kServiceIsNULLWhileTesting = true;
   static const char* service_name() { return "PrintingAPIHandler"; }
@@ -159,28 +176,25 @@
   content::BrowserContext* const browser_context_;
   EventRouter* const event_router_;
   ExtensionRegistry* const extension_registry_;
-
-  chromeos::CupsPrintJobManager* print_job_manager_;
-  chromeos::CupsPrintersManager* const printers_manager_;
   std::unique_ptr<PrintJobController> print_job_controller_;
-  PrinterCapabilitiesProvider printer_capabilities_provider_;
   std::unique_ptr<chromeos::CupsWrapper> cups_wrapper_;
+  content::NotificationRegistrar registrar_;
 
   // Remote interface used to flatten a PDF.
   mojo::Remote<printing::mojom::PdfFlattener> pdf_flattener_;
 
-  // Stores mapping from job id to the extension id.
-  // This is needed to disallow extensions to cancel jobs initiated by other
-  // extensions.
-  base::flat_map<std::string, std::string> print_jobs_extension_ids_;
+  // Stores mapping from job id to PrintJobInfo object.
+  // This is needed to cancel print jobs.
+  base::flat_map<std::string, PrintJobInfo> print_jobs_;
 
-  base::ScopedObservation<chromeos::CupsPrintJobManager,
-                          chromeos::CupsPrintJobManager::Observer>
-      print_job_manager_observation_{this};
+  crosapi::mojom::LocalPrinter* local_printer_;
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  int local_printer_version_ = 0;
+#endif
+
+  mojo::Receiver<crosapi::mojom::PrintJobObserver> receiver_{this};
 
   base::WeakPtrFactory<PrintingAPIHandler> weak_ptr_factory_{this};
-
-  DISALLOW_COPY_AND_ASSIGN(PrintingAPIHandler);
 };
 
 template <>
@@ -188,8 +202,6 @@
   static void DeclareFactoryDependencies(
       BrowserContextKeyedAPIFactory<PrintingAPIHandler>* factory) {
     factory->DependsOn(EventRouterFactory::GetInstance());
-    factory->DependsOn(chromeos::CupsPrintJobManagerFactory::GetInstance());
-    factory->DependsOn(chromeos::CupsPrintersManagerFactory::GetInstance());
   }
 };
 
diff --git a/chrome/browser/extensions/api/printing/printing_api_utils.cc b/chrome/browser/extensions/api/printing/printing_api_utils.cc
index 454b1e0e..b126713 100644
--- a/chrome/browser/extensions/api/printing/printing_api_utils.cc
+++ b/chrome/browser/extensions/api/printing/printing_api_utils.cc
@@ -7,10 +7,12 @@
 #include <algorithm>
 #include <memory>
 #include <utility>
+#include <vector>
 
 #include "base/containers/contains.h"
 #include "base/json/json_reader.h"
 #include "base/values.h"
+#include "chromeos/crosapi/mojom/local_printer.mojom.h"
 #include "chromeos/printing/printer_configuration.h"
 #include "components/cloud_devices/common/cloud_device_description.h"
 #include "components/cloud_devices/common/printer_description.h"
@@ -30,27 +32,16 @@
 constexpr char kIdPattern[] = "idPattern";
 constexpr char kNamePattern[] = "namePattern";
 
-idl::PrinterSource PrinterSourceToIdl(chromeos::Printer::Source source) {
-  switch (source) {
-    case chromeos::Printer::Source::SRC_USER_PREFS:
-      return idl::PRINTER_SOURCE_USER;
-    case chromeos::Printer::Source::SRC_POLICY:
-      return idl::PRINTER_SOURCE_POLICY;
-  }
-  NOTREACHED();
-  return idl::PRINTER_SOURCE_USER;
-}
-
 bool DoesPrinterMatchDefaultPrinterRules(
-    const chromeos::Printer& printer,
+    const crosapi::mojom::LocalDestinationInfo& printer,
     const absl::optional<DefaultPrinterRules>& rules) {
   if (!rules.has_value())
     return false;
   return (rules->kind.empty() || rules->kind == kLocal) &&
          (rules->id_pattern.empty() ||
-          RE2::FullMatch(printer.id(), rules->id_pattern)) &&
+          RE2::FullMatch(printer.id, rules->id_pattern)) &&
          (rules->name_pattern.empty() ||
-          RE2::FullMatch(printer.display_name(), rules->name_pattern));
+          RE2::FullMatch(printer.name, rules->name_pattern));
 }
 
 }  // namespace
@@ -82,18 +73,21 @@
 }
 
 idl::Printer PrinterToIdl(
-    const chromeos::Printer& printer,
+    const crosapi::mojom::LocalDestinationInfo& printer,
     const absl::optional<DefaultPrinterRules>& default_printer_rules,
     const base::flat_map<std::string, int>& recently_used_ranks) {
   idl::Printer idl_printer;
-  idl_printer.id = printer.id();
-  idl_printer.name = printer.display_name();
-  idl_printer.description = printer.description();
-  idl_printer.uri = printer.uri().GetNormalized(true /*always_print_port*/);
-  idl_printer.source = PrinterSourceToIdl(printer.source());
+  idl_printer.id = printer.id;
+  idl_printer.name = printer.name;
+  idl_printer.description = printer.description;
+  if (printer.uri)
+    idl_printer.uri = *printer.uri;
+  idl_printer.source = printer.configured_via_policy
+                           ? idl::PRINTER_SOURCE_POLICY
+                           : idl::PRINTER_SOURCE_USER;
   idl_printer.is_default =
       DoesPrinterMatchDefaultPrinterRules(printer, default_printer_rules);
-  auto it = recently_used_ranks.find(printer.id());
+  auto it = recently_used_ranks.find(printer.id);
   if (it != recently_used_ranks.end())
     idl_printer.recently_used_rank = std::make_unique<int>(it->second);
   else
@@ -122,7 +116,7 @@
     case chromeos::PrinterErrorCode::STOPPED:
       return idl::PRINTER_STATUS_STOPPED;
     default:
-      return idl::PRINTER_STATUS_GENERIC_ISSUE;
+      break;
   }
   return idl::PRINTER_STATUS_GENERIC_ISSUE;
 }
diff --git a/chrome/browser/extensions/api/printing/printing_api_utils.h b/chrome/browser/extensions/api/printing/printing_api_utils.h
index ee072b9..7a31439 100644
--- a/chrome/browser/extensions/api/printing/printing_api_utils.h
+++ b/chrome/browser/extensions/api/printing/printing_api_utils.h
@@ -7,10 +7,12 @@
 
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "base/containers/flat_map.h"
 #include "chrome/browser/chromeos/printing/printer_error_codes.h"
 #include "chrome/common/extensions/api/printing.h"
+#include "chromeos/crosapi/mojom/local_printer.mojom.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace base {
@@ -41,7 +43,7 @@
     const std::string& default_destination_selection_rules);
 
 api::printing::Printer PrinterToIdl(
-    const chromeos::Printer& printer,
+    const crosapi::mojom::LocalDestinationInfo& printer,
     const absl::optional<DefaultPrinterRules>& default_printer_rules,
     const base::flat_map<std::string, int>& recently_used_ranks);
 
diff --git a/chrome/browser/extensions/api/printing/printing_apitest.cc b/chrome/browser/extensions/api/printing/printing_apitest.cc
index b1250f0..e535ca02 100644
--- a/chrome/browser/extensions/api/printing/printing_apitest.cc
+++ b/chrome/browser/extensions/api/printing/printing_apitest.cc
@@ -3,13 +3,14 @@
 // found in the LICENSE file.
 
 #include "base/bind.h"
+#include "chrome/browser/ash/crosapi/crosapi_manager.h"
 #include "chrome/browser/chromeos/printing/cups_print_job_manager_factory.h"
 #include "chrome/browser/chromeos/printing/cups_printers_manager_factory.h"
 #include "chrome/browser/chromeos/printing/printer_configurer.h"
 #include "chrome/browser/chromeos/printing/test_cups_print_job_manager.h"
 #include "chrome/browser/chromeos/printing/test_cups_printers_manager.h"
 #include "chrome/browser/chromeos/printing/test_printer_configurer.h"
-#include "chrome/browser/extensions/api/printing/fake_print_job_controller.h"
+#include "chrome/browser/extensions/api/printing/fake_print_job_controller_ash.h"
 #include "chrome/browser/extensions/api/printing/printing_api.h"
 #include "chrome/browser/extensions/api/printing/printing_api_handler.h"
 #include "chrome/browser/extensions/extension_apitest.h"
@@ -104,9 +105,14 @@
   void AddAvailablePrinter(
       const std::string& printer_id,
       std::unique_ptr<printing::PrinterSemanticCapsAndDefaults> capabilities) {
-    chromeos::Printer printer = chromeos::Printer(printer_id);
-    GetPrintersManager()->AddPrinter(printer,
+    GetPrintersManager()->AddPrinter(chromeos::Printer(printer_id),
                                      chromeos::PrinterClass::kEnterprise);
+    chromeos::CupsPrinterStatus status(printer_id);
+    status.AddStatusReason(
+        chromeos::CupsPrinterStatus::CupsPrinterStatusReason::Reason::
+            kPrinterUnreachable,
+        chromeos::CupsPrinterStatus::CupsPrinterStatusReason::Severity::kError);
+    GetPrintersManager()->SetPrinterStatus(status);
     test_print_backend_->AddValidPrinter(printer_id, std::move(capabilities),
                                          nullptr);
   }
@@ -153,10 +159,10 @@
   ASSERT_TRUE(StartEmbeddedTestServer());
 
   AddAvailablePrinter(kId, ConstructPrinterCapabilities());
-  PrintingAPIHandler::Get(browser()->profile())
-      ->SetPrintJobControllerForTesting(
-          std::make_unique<FakePrintJobController>(GetPrintJobManager(),
-                                                   GetPrintersManager()));
+  PrintingAPIHandler* handler = PrintingAPIHandler::Get(browser()->profile());
+  handler->SetPrintJobControllerForTesting(
+      std::make_unique<FakePrintJobControllerAsh>(GetPrintJobManager(),
+                                                  GetPrintersManager()));
   base::AutoReset<bool> skip_confirmation_dialog_reset(
       PrintJobSubmitter::SkipConfirmationDialogForTesting());
 
@@ -170,10 +176,10 @@
   ASSERT_TRUE(StartEmbeddedTestServer());
 
   AddAvailablePrinter(kId, ConstructPrinterCapabilities());
-  PrintingAPIHandler::Get(browser()->profile())
-      ->SetPrintJobControllerForTesting(
-          std::make_unique<FakePrintJobController>(GetPrintJobManager(),
-                                                   GetPrintersManager()));
+  PrintingAPIHandler* handler = PrintingAPIHandler::Get(browser()->profile());
+  handler->SetPrintJobControllerForTesting(
+      std::make_unique<FakePrintJobControllerAsh>(GetPrintJobManager(),
+                                                  GetPrintersManager()));
   base::AutoReset<bool> skip_confirmation_dialog_reset(
       PrintJobSubmitter::SkipConfirmationDialogForTesting());
 
diff --git a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedUma.java b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedUma.java
index fb7d9ff5..aceccaa 100644
--- a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedUma.java
+++ b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedUma.java
@@ -27,4 +27,35 @@
         RecordHistogram.recordEnumeratedHistogram(
                 "ContentSuggestions.Feed.Controls.Actions", action, NUM_CONTROLS_ACTIONS);
     }
+
+    static final String[] TOTAL_CARDS_HISTOGRAM_NAMES = {
+            "ContentSuggestions.Feed.LoadMoreTrigger.TotalCards",
+            "ContentSuggestions.Feed.WebFeed.LoadMoreTrigger.TotalCards",
+    };
+
+    static final String[] OFFSET_FROM_END_OF_STREAM_HISTOGRAM_NAMES = {
+            "ContentSuggestions.Feed.LoadMoreTrigger.OffsetFromEndOfStream",
+            "ContentSuggestions.Feed.WebFeed.LoadMoreTrigger.OffsetFromEndOfStream",
+    };
+
+    /**
+     * Records the number of remaining cards (for the user to scroll through) at which
+     * the feed is triggered to load more content.
+     *
+     * @param numCardsRemaining the number of cards the user has yet to scroll through.
+     */
+    public static void recordFeedLoadMoreTrigger(
+            int sectionType, int totalCards, int numCardsRemaining) {
+        // TODO(crbug/1238047): annotate sectionType param with
+        // @org.chromium.chrome.browser.ntp.snippets.SectionType
+        assert totalCards >= 0;
+        assert numCardsRemaining >= 0;
+        assert OFFSET_FROM_END_OF_STREAM_HISTOGRAM_NAMES.length
+                == TOTAL_CARDS_HISTOGRAM_NAMES.length;
+        assert sectionType >= 0 || sectionType <= TOTAL_CARDS_HISTOGRAM_NAMES.length;
+        RecordHistogram.recordCount1000Histogram(
+                TOTAL_CARDS_HISTOGRAM_NAMES[sectionType], totalCards);
+        RecordHistogram.recordCount100Histogram(
+                OFFSET_FROM_END_OF_STREAM_HISTOGRAM_NAMES[sectionType], numCardsRemaining);
+    }
 }
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index bdb264c1..03066f35b 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -838,7 +838,7 @@
   {
     "name": "crosh-swa",
     "owners": [ "joelhockey", "benwells" ],
-    "expiry_milestone": 93
+    "expiry_milestone": 100
   },
   {
       "name": "cross-origin-embedder-policy-credentialless",
@@ -2779,11 +2779,6 @@
     "expiry_milestone": 104
   },
   {
-    "name": "enable-webassembly-simd",
-    "owners": [ "gdeepti", "wasm-team@google.com" ],
-    "expiry_milestone": 92
-  },
-  {
     "name": "enable-webassembly-threads",
     "owners": [ "binji", "hablich", "wasm-team@google.com" ],
     "expiry_milestone": 92
@@ -3445,11 +3440,6 @@
     "expiry_milestone": 95
   },
   {
-    "name": "installable-ink-drop",
-    "owners": [ "collinbaker", "pbos" ],
-    "expiry_milestone": 90
-  },
-  {
     "name": "installed-apps-in-cbd",
     "owners": [ "jarrydg", "pwnall" ],
     "expiry_milestone": 97
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index cea47ad..f3ed772 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1233,10 +1233,6 @@
 const char kEnableWasmLazyCompilationDescription[] =
     "Enables lazy (JIT on first call) compilation of WebAssembly modules.";
 
-const char kEnableWasmSimdName[] = "WebAssembly SIMD support.";
-const char kEnableWasmSimdDescription[] =
-    "Enables support for the WebAssembly SIMD proposal.";
-
 const char kEnableWasmTieringName[] = "WebAssembly tiering";
 const char kEnableWasmTieringDescription[] =
     "Enables tiered compilation of WebAssembly (will tier up to TurboFan if "
@@ -5333,11 +5329,6 @@
 const char kEnableMDRoundedCornersOnDialogsDescription[] =
     "Increases corner radius on secondary UI.";
 
-const char kInstallableInkDropName[] = "Use InstallableInkDrop where supported";
-const char kInstallableInkDropDescription[] =
-    "InstallableInkDrop is part of an InkDrop refactoring effort. This enables "
-    "the pilot implementation where available.";
-
 #endif  // defined(TOOLKIT_VIEWS)
 
 // Random platform combinations -----------------------------------------------
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index e80bdfe..76e5451 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -712,9 +712,6 @@
 extern const char kEnableWasmLazyCompilationName[];
 extern const char kEnableWasmLazyCompilationDescription[];
 
-extern const char kEnableWasmSimdName[];
-extern const char kEnableWasmSimdDescription[];
-
 extern const char kEnableWasmTieringName[];
 extern const char kEnableWasmTieringDescription[];
 
@@ -3090,8 +3087,6 @@
 extern const char kEnableMDRoundedCornersOnDialogsName[];
 extern const char kEnableMDRoundedCornersOnDialogsDescription[];
 
-extern const char kInstallableInkDropName[];
-extern const char kInstallableInkDropDescription[];
 #endif  // defined(TOOLKIT_VIEWS)
 
 // Random platform combinations -----------------------------------------------
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index 2496185..4c29693 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -107,6 +107,7 @@
     &features::kContinuousSearch,
     &features::kEarlyLibraryLoad,
     &features::kGenericSensorExtraClasses,
+    &features::kHttpsOnlyMode,
     &features::kLinkDoctorDeprecationAndroid,
     &features::kMetricsSettingsAndroid,
     &features::kNetworkServiceInProcess,
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 e4cc95f..e8a5d50 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
@@ -364,6 +364,7 @@
     public static final String FORCE_STARTUP_SIGNIN_PROMO = "ForceStartupSigninPromo";
     public static final String GRANT_NOTIFICATIONS_TO_DSE = "GrantNotificationsToDSE";
     public static final String HANDLE_MEDIA_INTENTS = "HandleMediaIntents";
+    public static final String HTTPS_FIRST_MODE = "HttpsOnlyMode";
     public static final String CHROME_SURVEY_NEXT_ANDROID = "ChromeSurveyNextAndroid";
     public static final String IMMERSIVE_UI_MODE = "ImmersiveUiMode";
     public static final String INCOGNITO_NTP_REAL_BOX = "IncognitoNtpRealBox";
diff --git a/chrome/browser/history/history_tab_helper_unittest.cc b/chrome/browser/history/history_tab_helper_unittest.cc
index 512d29d..575631d 100644
--- a/chrome/browser/history/history_tab_helper_unittest.cc
+++ b/chrome/browser/history/history_tab_helper_unittest.cc
@@ -4,6 +4,11 @@
 
 #include "chrome/browser/history/history_tab_helper.h"
 
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+
 #include "base/run_loop.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
@@ -32,6 +37,8 @@
 #include "components/feed/core/v2/public/test/stub_feed_api.h"
 #endif
 
+using testing::NiceMock;
+
 namespace {
 
 #if defined(OS_ANDROID)
@@ -161,7 +168,7 @@
 }
 
 TEST_F(HistoryTabHelperTest, CreateAddPageArgsReferringURLMainFrameNoReferrer) {
-  content::MockNavigationHandle navigation_handle(web_contents());
+  NiceMock<content::MockNavigationHandle> navigation_handle(web_contents());
   navigation_handle.set_redirect_chain({GURL("https://someurl.com")});
   navigation_handle.set_previous_main_frame_url(GURL("http://previousurl.com"));
   history::HistoryAddPageArgs args =
@@ -173,7 +180,7 @@
 
 TEST_F(HistoryTabHelperTest,
        CreateAddPageArgsReferringURLMainFrameSameOriginReferrer) {
-  content::MockNavigationHandle navigation_handle(web_contents());
+  NiceMock<content::MockNavigationHandle> navigation_handle(web_contents());
   navigation_handle.set_redirect_chain({GURL("https://someurl.com")});
   navigation_handle.set_previous_main_frame_url(
       GURL("http://previousurl.com/abc"));
@@ -190,7 +197,7 @@
 
 TEST_F(HistoryTabHelperTest,
        CreateAddPageArgsReferringURLMainFrameSameOriginReferrerDifferentPath) {
-  content::MockNavigationHandle navigation_handle(web_contents());
+  NiceMock<content::MockNavigationHandle> navigation_handle(web_contents());
   navigation_handle.set_redirect_chain({GURL("https://someurl.com")});
   navigation_handle.set_previous_main_frame_url(
       GURL("http://previousurl.com/def"));
@@ -207,7 +214,7 @@
 
 TEST_F(HistoryTabHelperTest,
        CreateAddPageArgsReferringURLMainFrameCrossOriginReferrer) {
-  content::MockNavigationHandle navigation_handle(web_contents());
+  NiceMock<content::MockNavigationHandle> navigation_handle(web_contents());
   auto referrer = blink::mojom::Referrer::New();
   referrer->url = GURL("http://crossorigin.com");
   referrer->policy = network::mojom::ReferrerPolicy::kDefault;
@@ -226,8 +233,8 @@
       content::RenderFrameHostTester::For(main_rfh());
   main_rfh_tester->InitializeRenderFrameIfNeeded();
   content::RenderFrameHost* subframe = main_rfh_tester->AppendChild("subframe");
-  content::MockNavigationHandle navigation_handle(GURL("http://someurl.com"),
-                                                  subframe);
+  NiceMock<content::MockNavigationHandle> navigation_handle(
+      GURL("http://someurl.com"), subframe);
   navigation_handle.set_redirect_chain({GURL("https://someurl.com")});
   navigation_handle.set_previous_main_frame_url(GURL("http://previousurl.com"));
   history::HistoryAddPageArgs args =
diff --git a/chrome/browser/lookalikes/lookalike_url_navigation_throttle_browsertest.cc b/chrome/browser/lookalikes/lookalike_url_navigation_throttle_browsertest.cc
index 651cdb3..54efc10 100644
--- a/chrome/browser/lookalikes/lookalike_url_navigation_throttle_browsertest.cc
+++ b/chrome/browser/lookalikes/lookalike_url_navigation_throttle_browsertest.cc
@@ -814,6 +814,27 @@
   CheckUkm({kNavigatedUrl}, "MatchType", LookalikeUrlMatchType::kEditDistance);
 }
 
+// Navigate to a domain within a character swap of 1 to a top domain.
+// This should record metrics, but should not show a lookalike warning
+// interstitial yet.
+IN_PROC_BROWSER_TEST_P(LookalikeUrlNavigationThrottleBrowserTest,
+                       CharacterSwap_TopDomain_Match) {
+  base::HistogramTester histograms;
+  const GURL kNavigatedUrl = GetURL("goolge.com");
+  // Even if the navigated site has a low engagement score, it should be
+  // considered for lookalike suggestions.
+  SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
+
+  TestInterstitialNotShown(browser(), kNavigatedUrl);
+  histograms.ExpectTotalCount(lookalikes::kHistogramName, 1);
+  histograms.ExpectBucketCount(
+      lookalikes::kHistogramName,
+      NavigationSuggestionEvent::kMatchCharacterSwapTop500, 1);
+
+  CheckUkm({kNavigatedUrl}, "MatchType",
+           LookalikeUrlMatchType::kCharacterSwapTop500);
+}
+
 // Navigate to a domain within an edit distance of 1 to a top domain, but that
 // matches the allowlist. This should neither record metrics nor show an
 // interstitial.
diff --git a/chrome/browser/nearby_sharing/nearby_share_metrics_logger.cc b/chrome/browser/nearby_sharing/nearby_share_metrics_logger.cc
index a2d5c0c..bf2a785b 100644
--- a/chrome/browser/nearby_sharing/nearby_share_metrics_logger.cc
+++ b/chrome/browser/nearby_sharing/nearby_share_metrics_logger.cc
@@ -102,7 +102,8 @@
   kWifiDirect = 8,
   kWebRtc = 9,
   kNoUpgrade = 10,
-  kMaxValue = kNoUpgrade
+  kBleL2Cap = 11,
+  kMaxValue = kBleL2Cap
 };
 
 AttachmentType FileMetadataTypeToAttachmentType(
@@ -299,6 +300,7 @@
     case location::nearby::connections::mojom::Medium::kWifiAware:
     case location::nearby::connections::mojom::Medium::kNfc:
     case location::nearby::connections::mojom::Medium::kWifiDirect:
+    case location::nearby::connections::mojom::Medium::kBleL2Cap:
       return ".UnknownMediumUpgrade";
   }
 }
@@ -331,6 +333,8 @@
       return UpgradedMedium::kWifiDirect;
     case location::nearby::connections::mojom::Medium::kWebRtc:
       return UpgradedMedium::kWebRtc;
+    case location::nearby::connections::mojom::Medium::kBleL2Cap:
+      return UpgradedMedium::kBleL2Cap;
   }
 }
 
diff --git a/chrome/browser/net/external_protocol_browsertest.cc b/chrome/browser/net/external_protocol_browsertest.cc
new file mode 100644
index 0000000..3d89f15b
--- /dev/null
+++ b/chrome/browser/net/external_protocol_browsertest.cc
@@ -0,0 +1,143 @@
+// 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 <vector>
+
+#include <utility>
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/run_loop.h"
+#include "base/sequence_checker.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/task/post_task.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "chrome/browser/external_protocol/external_protocol_handler.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/shell_integration.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "net/test/spawned_test_server/spawned_test_server.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace {
+
+// DefaultProtocolClientWorker checks whether the browser is set as the default
+// handler for some scheme, and optionally sets the browser as the default
+// handler for some scheme. Our fake implementation pretends that the browser is
+// not the default handler.
+class FakeDefaultProtocolClientWorker
+    : public shell_integration::DefaultProtocolClientWorker {
+ public:
+  explicit FakeDefaultProtocolClientWorker(const std::string& protocol)
+      : DefaultProtocolClientWorker(protocol) {}
+  FakeDefaultProtocolClientWorker(const FakeDefaultProtocolClientWorker&) =
+      delete;
+  FakeDefaultProtocolClientWorker& operator=(
+      const FakeDefaultProtocolClientWorker&) = delete;
+
+ private:
+  ~FakeDefaultProtocolClientWorker() override = default;
+  shell_integration::DefaultWebClientState CheckIsDefaultImpl() override {
+    return shell_integration::DefaultWebClientState::NOT_DEFAULT;
+  }
+
+  void SetAsDefaultImpl(base::OnceClosure on_finished_callback) override {
+    base::SequencedTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, std::move(on_finished_callback));
+  }
+};
+
+// Used during testing to intercept invocations of external protocol handlers.
+class FakeProtocolHandlerDelegate : public ExternalProtocolHandler::Delegate {
+ public:
+  FakeProtocolHandlerDelegate() = default;
+  FakeProtocolHandlerDelegate(const FakeProtocolHandlerDelegate&) = delete;
+  FakeProtocolHandlerDelegate& operator=(const FakeProtocolHandlerDelegate&) =
+      delete;
+
+  const GURL& WaitForUrl() {
+    run_loop_.Run();
+    return url_invoked_;
+  }
+
+ private:
+  scoped_refptr<shell_integration::DefaultProtocolClientWorker>
+  CreateShellWorker(const std::string& protocol) override {
+    return base::MakeRefCounted<FakeDefaultProtocolClientWorker>(protocol);
+  }
+
+  ExternalProtocolHandler::BlockState GetBlockState(const std::string& scheme,
+                                                    Profile* profile) override {
+    return ExternalProtocolHandler::BlockState::DONT_BLOCK;
+  }
+
+  void BlockRequest() override { NOTREACHED(); }
+
+  void RunExternalProtocolDialog(
+      const GURL& url,
+      content::WebContents* web_contents,
+      ui::PageTransition page_transition,
+      bool has_user_gesture,
+      const absl::optional<url::Origin>& initiating_origin) override {
+    NOTREACHED();
+  }
+
+  void LaunchUrlWithoutSecurityCheck(
+      const GURL& url,
+      content::WebContents* web_contents) override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+    EXPECT_TRUE(url_invoked_.is_empty());
+    url_invoked_ = url;
+    run_loop_.Quit();
+  }
+
+  void FinishedProcessingCheck() override {}
+
+  GURL url_invoked_;
+  base::RunLoop run_loop_;
+  SEQUENCE_CHECKER(sequence_checker_);
+};
+
+// Navigates to the |target_url| and waits until that same URL is observed at
+// the ExternalProtocolHandler.
+void InvokeUrlAndWaitForExternalHandler(Browser* browser, GURL target_url) {
+  FakeProtocolHandlerDelegate external_handler_delegate;
+  ExternalProtocolHandler::SetDelegateForTesting(&external_handler_delegate);
+
+  ui_test_utils::NavigateToURL(browser, target_url);
+  auto actual_url = external_handler_delegate.WaitForUrl();
+  EXPECT_EQ(target_url, actual_url);
+
+  ExternalProtocolHandler::SetDelegateForTesting(nullptr);
+}
+
+}  // namespace
+
+// TODO(mmenke): Should these be merged into
+// chrome/browser/external_porotocol_handler_browsertest.cc?
+using ExternalProtocolBrowserTest = InProcessBrowserTest;
+
+// Ensure that ftp:// URLs are passed through to the external protocol handler.
+IN_PROC_BROWSER_TEST_F(ExternalProtocolBrowserTest, ExternalProtocolHandler) {
+  // If this test fails, then the issue is with the external protocol handler
+  // mechanism as configured by //chrome. This test must pass for the test below
+  // it to be valid.
+  InvokeUrlAndWaitForExternalHandler(browser(), GURL("example-not-real:foo"));
+  InvokeUrlAndWaitForExternalHandler(browser(), GURL("gopher://foo.example"));
+
+  // And now with an ftp:// URL.
+  InvokeUrlAndWaitForExternalHandler(browser(),
+                                     GURL("ftp://example.com/foo/bar"));
+}
diff --git a/chrome/browser/net/ftp_browsertest.cc b/chrome/browser/net/ftp_browsertest.cc
deleted file mode 100644
index 9459367..0000000
--- a/chrome/browser/net/ftp_browsertest.cc
+++ /dev/null
@@ -1,255 +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 <vector>
-
-#include <utility>
-
-#include "base/command_line.h"
-#include "base/files/file_path.h"
-#include "base/memory/scoped_refptr.h"
-#include "base/run_loop.h"
-#include "base/sequence_checker.h"
-#include "base/strings/utf_string_conversions.h"
-#include "base/task/post_task.h"
-#include "base/test/scoped_feature_list.h"
-#include "base/threading/sequenced_task_runner_handle.h"
-#include "build/build_config.h"
-#include "chrome/browser/external_protocol/external_protocol_handler.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/shell_integration.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/tabs/tab_strip_model.h"
-#include "chrome/common/chrome_features.h"
-#include "chrome/test/base/in_process_browser_test.h"
-#include "chrome/test/base/ui_test_utils.h"
-#include "content/public/browser/browser_context.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/common/content_switches.h"
-#include "content/public/test/browser_test.h"
-#include "content/public/test/browser_test_utils.h"
-#include "content/public/test/download_test_observer.h"
-#include "net/test/spawned_test_server/spawned_test_server.h"
-#include "services/network/public/cpp/features.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "url/gurl.h"
-
-namespace {
-
-// Whether FTP is enabled or not.
-enum class FtpState { ENABLED, DISABLED };
-
-class FtpBrowserTest : public InProcessBrowserTest {
- public:
-  explicit FtpBrowserTest(FtpState ftp_state = FtpState::ENABLED)
-      : ftp_server_(net::SpawnedTestServer::TYPE_FTP,
-                    base::FilePath(FILE_PATH_LITERAL("chrome/test/data/ftp"))) {
-    scoped_feature_list_.InitWithFeatureState(network::features::kFtpProtocol,
-                                              ftp_state == FtpState::ENABLED);
-  }
-
- protected:
-  net::SpawnedTestServer ftp_server_;
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
-void WaitForTitle(content::WebContents* contents, const char* expected_title) {
-  content::TitleWatcher title_watcher(contents,
-      base::ASCIIToUTF16(expected_title));
-
-  EXPECT_EQ(base::ASCIIToUTF16(expected_title),
-            title_watcher.WaitAndGetTitle());
-}
-
-// DefaultProtocolClientWorker checks whether the browser is set as the default
-// handler for some scheme, and optionally sets the browser as the default
-// handler for some scheme. Our fake implementation pretends that the browser is
-// not the default handler.
-class FakeDefaultProtocolClientWorker
-    : public shell_integration::DefaultProtocolClientWorker {
- public:
-  explicit FakeDefaultProtocolClientWorker(const std::string& protocol)
-      : DefaultProtocolClientWorker(protocol) {}
-
- private:
-  ~FakeDefaultProtocolClientWorker() override = default;
-  shell_integration::DefaultWebClientState CheckIsDefaultImpl() override {
-    return shell_integration::DefaultWebClientState::NOT_DEFAULT;
-  }
-
-  void SetAsDefaultImpl(base::OnceClosure on_finished_callback) override {
-    base::SequencedTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, std::move(on_finished_callback));
-  }
-
-  DISALLOW_COPY_AND_ASSIGN(FakeDefaultProtocolClientWorker);
-};
-
-// Used during testing to intercept invocations of external protocol handlers.
-class FakeProtocolHandlerDelegate : public ExternalProtocolHandler::Delegate {
- public:
-  FakeProtocolHandlerDelegate() = default;
-
-  const GURL& WaitForUrl() {
-    run_loop_.Run();
-    return url_invoked_;
-  }
-
- private:
-  scoped_refptr<shell_integration::DefaultProtocolClientWorker>
-  CreateShellWorker(const std::string& protocol) override {
-    return base::MakeRefCounted<FakeDefaultProtocolClientWorker>(protocol);
-  }
-
-  ExternalProtocolHandler::BlockState GetBlockState(const std::string& scheme,
-                                                    Profile* profile) override {
-    return ExternalProtocolHandler::BlockState::DONT_BLOCK;
-  }
-
-  void BlockRequest() override { NOTREACHED(); }
-
-  void RunExternalProtocolDialog(
-      const GURL& url,
-      content::WebContents* web_contents,
-      ui::PageTransition page_transition,
-      bool has_user_gesture,
-      const absl::optional<url::Origin>& initiating_origin) override {
-    NOTREACHED();
-  }
-
-  void LaunchUrlWithoutSecurityCheck(
-      const GURL& url,
-      content::WebContents* web_contents) override {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
-    EXPECT_TRUE(url_invoked_.is_empty());
-    url_invoked_ = url;
-    run_loop_.Quit();
-  }
-
-  void FinishedProcessingCheck() override {}
-
-  GURL url_invoked_;
-  base::RunLoop run_loop_;
-  SEQUENCE_CHECKER(sequence_checker_);
-
-  DISALLOW_COPY_AND_ASSIGN(FakeProtocolHandlerDelegate);
-};
-
-// Navigates to the |target_url| and waits until that same URL is observed at
-// the ExternalProtocolHandler.
-void InvokeUrlAndWaitForExternalHandler(Browser* browser, GURL target_url) {
-  FakeProtocolHandlerDelegate external_handler_delegate;
-  ExternalProtocolHandler::SetDelegateForTesting(&external_handler_delegate);
-
-  ui_test_utils::NavigateToURL(browser, target_url);
-  auto actual_url = external_handler_delegate.WaitForUrl();
-  EXPECT_EQ(target_url, actual_url);
-
-  ExternalProtocolHandler::SetDelegateForTesting(nullptr);
-}
-
-// Test fixture where FTP is disabled.
-class FtpDisabledFeatureBrowserTest : public FtpBrowserTest {
- public:
-  FtpDisabledFeatureBrowserTest() : FtpBrowserTest(FtpState::DISABLED) {}
-};
-
-class FtpEnabledBySwitchBrowserTest : public FtpBrowserTest {
- public:
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    command_line->AppendSwitch(switches::kEnableFtp);
-  }
-};
-
-}  // namespace
-
-IN_PROC_BROWSER_TEST_F(FtpBrowserTest, BasicFtpUrlAuthentication) {
-  ASSERT_TRUE(ftp_server_.Start());
-  ui_test_utils::NavigateToURL(
-      browser(),
-      ftp_server_.GetURLWithUserAndPassword("", "chrome", "chrome"));
-
-  WaitForTitle(browser()->tab_strip_model()->GetActiveWebContents(),
-               "Index of /");
-}
-
-IN_PROC_BROWSER_TEST_F(FtpBrowserTest, DirectoryListingNavigation) {
-  ftp_server_.set_no_anonymous_ftp_user(true);
-  ASSERT_TRUE(ftp_server_.Start());
-
-  ui_test_utils::NavigateToURL(
-      browser(),
-      ftp_server_.GetURLWithUserAndPassword("", "chrome", "chrome"));
-
-  // Navigate to directory dir1/ without needing to re-authenticate
-  EXPECT_TRUE(content::ExecJs(
-      browser()->tab_strip_model()->GetActiveWebContents(),
-      "(function() {"
-      "  function navigate() {"
-      "    for (const element of document.getElementsByTagName('a')) {"
-      "      if (element.innerHTML == 'dir1/') {"
-      "        element.click();"
-      "      }"
-      "    }"
-      "  }"
-      "  if (document.readyState === 'loading') {"
-      "    document.addEventListener('DOMContentLoaded', navigate);"
-      "  } else {"
-      "    navigate();"
-      "  }"
-      "})()"));
-
-  WaitForTitle(browser()->tab_strip_model()->GetActiveWebContents(),
-               "Index of /dir1/");
-
-  // Navigate to file `test.html`, verify that it's downloaded.
-  content::DownloadTestObserverTerminal download_test_observer_terminal(
-      browser()->profile()->GetDownloadManager(), 1,
-      content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_IGNORE);
-
-  EXPECT_TRUE(content::ExecJs(
-      browser()->tab_strip_model()->GetActiveWebContents(),
-      "(function() {"
-      "  function navigate() {"
-      "    for (const element of document.getElementsByTagName('a')) {"
-      "      if (element.innerHTML == 'test.html') {"
-      "        element.click();"
-      "      }"
-      "    }"
-      "  }"
-      "  if (document.readyState === 'loading') {"
-      "    document.addEventListener('DOMContentLoaded', navigate);"
-      "  } else {"
-      "    navigate();"
-      "  }"
-      "})()"));
-
-  download_test_observer_terminal.WaitForFinished();
-  EXPECT_EQ(download_test_observer_terminal.NumDownloadsSeenInState(
-                download::DownloadItem::COMPLETE),
-            1u);
-}
-
-// Ensure that ftp:// URLs are passed through to the external protocol handler
-// when the FTP feature is disabled.
-IN_PROC_BROWSER_TEST_F(FtpDisabledFeatureBrowserTest, ExternalProtocolHandler) {
-  // If this test fails, then the issue is with the external protocol handler
-  // mechanism as configured by //chrome. This test must pass for the test below
-  // it to be valid.
-  InvokeUrlAndWaitForExternalHandler(browser(), GURL("example-not-real:foo"));
-  InvokeUrlAndWaitForExternalHandler(browser(), GURL("gopher://foo.example"));
-
-  // And now with an ftp:// URL.
-  InvokeUrlAndWaitForExternalHandler(browser(),
-                                     GURL("ftp://example.com/foo/bar"));
-}
-
-// Did I wire the switch correctly?
-IN_PROC_BROWSER_TEST_F(FtpEnabledBySwitchBrowserTest, SwitchWorks) {
-  ASSERT_TRUE(
-      base::FeatureList::GetInstance()->IsFeatureOverriddenFromCommandLine(
-          network::features::kFtpProtocol.name,
-          base::FeatureList::OVERRIDE_ENABLE_FEATURE));
-}
diff --git a/chrome/browser/net/load_timing_browsertest.cc b/chrome/browser/net/load_timing_browsertest.cc
index de35ba3..2c128743 100644
--- a/chrome/browser/net/load_timing_browsertest.cc
+++ b/chrome/browser/net/load_timing_browsertest.cc
@@ -176,36 +176,4 @@
   EXPECT_EQ(navigation_deltas.ssl_start, -1);
 }
 
-// Test fixture for tests that depend on FTP support. Moved out to a separate
-// fixture since remaining functionality should be tested without FTP support.
-//
-// TODO(https://crbug.com/333943): Remove FTP specific tests and test fixtures.
-class LoadTimingBrowserTestWithFtp : public LoadTimingBrowserTest {
- public:
-  LoadTimingBrowserTestWithFtp() {
-    scoped_feature_list_.InitAndEnableFeature(network::features::kFtpProtocol);
-  }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
-IN_PROC_BROWSER_TEST_F(LoadTimingBrowserTestWithFtp, FTP) {
-  net::SpawnedTestServer ftp_server(net::SpawnedTestServer::TYPE_FTP,
-                                    base::FilePath());
-  ASSERT_TRUE(ftp_server.Start());
-  GURL url = ftp_server.GetURL("/");
-  ui_test_utils::NavigateToURL(browser(), url);
-
-  TimingDeltas navigation_deltas;
-  GetResultDeltas(&navigation_deltas);
-
-  EXPECT_EQ(navigation_deltas.dns_start, 0);
-  EXPECT_EQ(navigation_deltas.dns_end, 0);
-  EXPECT_EQ(navigation_deltas.connect_start, 0);
-  EXPECT_EQ(navigation_deltas.connect_end, 0);
-  EXPECT_EQ(navigation_deltas.receive_headers_end, 0);
-  EXPECT_EQ(navigation_deltas.ssl_start, -1);
-}
-
 }  // namespace
diff --git a/chrome/browser/net/network_context_configuration_browsertest.cc b/chrome/browser/net/network_context_configuration_browsertest.cc
index be06185ef..7326264 100644
--- a/chrome/browser/net/network_context_configuration_browsertest.cc
+++ b/chrome/browser/net/network_context_configuration_browsertest.cc
@@ -1783,57 +1783,6 @@
   TestProxyConfigured(/*expect_success=*/true);
 }
 
-// Make sure the system URLRequestContext can handle fetching PAC scripts from
-// ftp URLs. Unlike the other PAC tests, this test uses a PAC script that
-// results in an error, since the spawned test server is designed so that it can
-// run remotely (So can't just write a script to a local file and have the
-// server serve it).
-//
-// TODO(https://crbug.com/333943): Remove these tests when FTP support is
-// removed.
-class NetworkContextConfigurationFtpPacBrowserTest
-    : public NetworkContextConfigurationBrowserTest {
- public:
-  NetworkContextConfigurationFtpPacBrowserTest()
-      : ftp_server_(net::SpawnedTestServer::TYPE_FTP, GetChromeTestDataDir()) {
-    scoped_feature_list_.InitAndEnableFeature(network::features::kFtpProtocol);
-    EXPECT_TRUE(ftp_server_.Start());
-  }
-  ~NetworkContextConfigurationFtpPacBrowserTest() override {}
-
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    command_line->AppendSwitchASCII(
-        switches::kProxyPacUrl,
-        ftp_server_.GetURL("bad_server.pac").spec().c_str());
-  }
-
- private:
-  net::SpawnedTestServer ftp_server_;
-  base::test::ScopedFeatureList scoped_feature_list_;
-
-  DISALLOW_COPY_AND_ASSIGN(NetworkContextConfigurationFtpPacBrowserTest);
-};
-
-IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationFtpPacBrowserTest, FtpPac) {
-  if (IsRestartStateWithInProcessNetworkService())
-    return;
-  std::unique_ptr<network::ResourceRequest> request =
-      std::make_unique<network::ResourceRequest>();
-  // This URL should be directed to the test server because of the proxy.
-  request->url = GURL("http://does.not.resolve.test:1872/echo");
-
-  content::SimpleURLLoaderTestHelper simple_loader_helper;
-  std::unique_ptr<network::SimpleURLLoader> simple_loader =
-      network::SimpleURLLoader::Create(std::move(request),
-                                       TRAFFIC_ANNOTATION_FOR_TESTS);
-
-  simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
-      loader_factory(), simple_loader_helper.GetCallback());
-  simple_loader_helper.WaitForCallback();
-
-  EXPECT_EQ(net::ERR_PROXY_CONNECTION_FAILED, simple_loader->NetError());
-}
-
 class NetworkContextConfigurationProxySettingsBrowserTest
     : public NetworkContextConfigurationHttpPacBrowserTest {
  public:
@@ -2236,8 +2185,6 @@
 INSTANTIATE_TEST_CASES_FOR_TEST_FIXTURE(
     NetworkContextConfigurationDataPacBrowserTest);
 INSTANTIATE_TEST_CASES_FOR_TEST_FIXTURE(
-    NetworkContextConfigurationFtpPacBrowserTest);
-INSTANTIATE_TEST_CASES_FOR_TEST_FIXTURE(
     NetworkContextConfigurationProxySettingsBrowserTest);
 INSTANTIATE_TEST_CASES_FOR_TEST_FIXTURE(
     NetworkContextConfigurationManagedProxySettingsBrowserTest);
diff --git a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc b/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc
index 654df754..11b65ca 100644
--- a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc
+++ b/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc
@@ -713,7 +713,7 @@
 }
 
 // Disabled due to flakiness on Linux https://crbug.com/1229601
-#if defined(OS_LINUX) || (defined(OS_CHROMEOS) && defined(MEMORY_SANITIZER))
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
 #define MAYBE_CreativeOriginStatusWithThrottlingNestedThrottled \
   DISABLED_CreativeOriginStatusWithThrottlingNestedThrottled
 #else
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index 7366a0e1..9795a5d 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -1731,6 +1731,11 @@
   handlers->AddHandler(std::make_unique<extensions::ExtensionListPolicyHandler>(
       key::kAttestationExtensionAllowlist,
       prefs::kAttestationExtensionAllowlist, false));
+#if defined(USE_CUPS)
+  handlers->AddHandler(std::make_unique<extensions::ExtensionListPolicyHandler>(
+      key::kPrintingAPIExtensionsAllowlist,
+      prefs::kPrintingAPIExtensionsAllowlist, /*allow_wildcards=*/false));
+#endif  // defined(USE_CUPS)
 #else  // defined(OS_CHROMEOS)
   std::vector<std::unique_ptr<ConfigurationPolicyHandler>>
       signin_legacy_policies;
@@ -1997,11 +2002,6 @@
       SimpleSchemaValidatingPolicyHandler::RECOMMENDED_PROHIBITED,
       SimpleSchemaValidatingPolicyHandler::MANDATORY_ALLOWED));
   handlers->AddHandler(std::make_unique<LacrosAvailabilityPolicyHandler>());
-#if defined(USE_CUPS)
-  handlers->AddHandler(std::make_unique<extensions::ExtensionListPolicyHandler>(
-      key::kPrintingAPIExtensionsAllowlist,
-      prefs::kPrintingAPIExtensionsAllowlist, /*allow_wildcards=*/false));
-#endif  // defined(USE_CUPS)
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 // On most platforms, there is a legacy policy
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index bd7243805..8fadd3a 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -259,7 +259,10 @@
 
 #if defined(OS_CHROMEOS)
 #include "chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.h"
-#endif
+#if defined(USE_CUPS)
+#include "chrome/browser/extensions/api/printing/printing_api_handler.h"
+#endif  // defined(USE_CUPS)
+#endif  // defined(OS_CHROMEOS)
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/components/audio/audio_devices_pref_handler_impl.h"
@@ -375,10 +378,6 @@
 #include "components/quirks/quirks_manager.h"
 #include "extensions/browser/api/lock_screen_data/lock_screen_item_storage.h"
 
-#if defined(USE_CUPS)
-#include "chrome/browser/extensions/api/printing/printing_api_handler.h"
-#endif
-
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if defined(OS_MAC)
@@ -1245,6 +1244,9 @@
 
 #if defined(OS_CHROMEOS)
   extensions::platform_keys::RegisterProfilePrefs(registry);
+#if defined(USE_CUPS)
+  extensions::PrintingAPIHandler::RegisterProfilePrefs(registry);
+#endif  // defined(USE_CUPS)
 #endif  // defined(OS_CHROMEOS)
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -1299,9 +1301,6 @@
   ash::UserImageSyncObserver::RegisterProfilePrefs(registry);
   crostini::prefs::RegisterProfilePrefs(registry);
   ash::attestation::TpmChallengeKey::RegisterProfilePrefs(registry);
-#if defined(USE_CUPS)
-  extensions::PrintingAPIHandler::RegisterProfilePrefs(registry);
-#endif
   flags_ui::PrefServiceFlagsStorage::RegisterProfilePrefs(registry);
   guest_os::prefs::RegisterProfilePrefs(registry);
   lock_screen_apps::StateController::RegisterProfilePrefs(registry);
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu.cc b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
index 00305629..6da0c74 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu.cc
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
@@ -64,6 +64,8 @@
 #include "chrome/browser/sharing/click_to_call/click_to_call_context_menu_observer.h"
 #include "chrome/browser/sharing/click_to_call/click_to_call_metrics.h"
 #include "chrome/browser/sharing/click_to_call/click_to_call_utils.h"
+#include "chrome/browser/sharing/features.h"
+#include "chrome/browser/sharing/share_submenu_model.h"
 #include "chrome/browser/sharing/shared_clipboard/shared_clipboard_context_menu_observer.h"
 #include "chrome/browser/sharing/shared_clipboard/shared_clipboard_utils.h"
 #include "chrome/browser/spellchecker/spellcheck_service.h"
@@ -615,6 +617,10 @@
   return web_app::GetSystemWebAppTypeForAppId(profile, *link_app_id);
 }
 
+bool ShouldUseShareMenu() {
+  return base::FeatureList::IsEnabled(sharing::kShareMenu);
+}
+
 }  // namespace
 
 // static
@@ -1363,12 +1369,27 @@
 
     menu_model_.AddSeparator(ui::NORMAL_SEPARATOR);
 
+    if (ShouldUseShareMenu()) {
+      sharing::ShareSubmenuModel::Context context =
+          params_.has_image_contents
+              ? sharing::ShareSubmenuModel::Context::IMAGE
+              : sharing::ShareSubmenuModel::Context::LINK;
+      share_submenu_model_ = std::make_unique<sharing::ShareSubmenuModel>(
+          GetBrowser(), context, params_.page_url);
+      if (share_submenu_model_->GetItemCount() > 0) {
+        menu_model_.AddSubMenuWithStringId(IDC_CONTENT_CONTEXT_SHARING_SUBMENU,
+                                           IDS_SHARE_MENU_TITLE,
+                                           share_submenu_model_.get());
+      }
+    }
+
     // Place QR Generator close to send-tab-to-self feature for link images.
-    if (params_.has_image_contents)
+    if (!ShouldUseShareMenu() && params_.has_image_contents)
       AppendQRCodeGeneratorItem(/*for_image=*/true, /*draw_icon=*/true);
 
-    if (browser && send_tab_to_self::ShouldOfferFeatureForLink(
-                       active_web_contents, params_.link_url)) {
+    if (browser && !ShouldUseShareMenu() &&
+        send_tab_to_self::ShouldOfferFeatureForLink(active_web_contents,
+                                                    params_.link_url)) {
       if (send_tab_to_self::GetValidDeviceCount(GetBrowser()->profile()) == 1) {
 #if defined(OS_MAC)
         menu_model_.AddItem(IDC_CONTENT_LINK_SEND_TAB_TO_SELF_SINGLE_TARGET,
@@ -1517,8 +1538,19 @@
   menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_COPYIMAGELOCATION,
                                   IDS_CONTENT_CONTEXT_COPYIMAGELOCATION);
 
+  if (ShouldUseShareMenu() && !share_submenu_model_) {
+    share_submenu_model_ = std::make_unique<sharing::ShareSubmenuModel>(
+        GetBrowser(), sharing::ShareSubmenuModel::Context::IMAGE,
+        params_.src_url);
+    if (share_submenu_model_->GetItemCount() > 0) {
+      menu_model_.AddSubMenuWithStringId(IDC_CONTENT_CONTEXT_SHARING_SUBMENU,
+                                         IDS_SHARE_MENU_TITLE,
+                                         share_submenu_model_.get());
+    }
+  }
+
   // Don't double-add for linked images, which also add the item.
-  if (params_.link_url.is_empty())
+  if (!ShouldUseShareMenu() && params_.link_url.is_empty())
     AppendQRCodeGeneratorItem(/*for_image=*/true, /*draw_icon=*/false);
 }
 
@@ -1631,9 +1663,21 @@
   menu_model_.AddItemWithStringId(IDC_PRINT, IDS_CONTENT_CONTEXT_PRINT);
   AppendMediaRouterItem();
 
+  if (ShouldUseShareMenu()) {
+    menu_model_.AddSeparator(ui::NORMAL_SEPARATOR);
+    share_submenu_model_ = std::make_unique<sharing::ShareSubmenuModel>(
+        GetBrowser(), sharing::ShareSubmenuModel::Context::PAGE,
+        params_.page_url);
+    if (share_submenu_model_->GetItemCount() > 0) {
+      menu_model_.AddSubMenuWithStringId(IDC_CONTENT_CONTEXT_SHARING_SUBMENU,
+                                         IDS_SHARE_MENU_TITLE,
+                                         share_submenu_model_.get());
+    }
+  }
+
   // Send-Tab-To-Self (user's other devices), page level.
   bool send_tab_to_self_menu_present = false;
-  if (GetBrowser() &&
+  if (GetBrowser() && !ShouldUseShareMenu() &&
       send_tab_to_self::ShouldOfferFeature(
           GetBrowser()->tab_strip_model()->GetActiveWebContents())) {
     menu_model_.AddSeparator(ui::NORMAL_SEPARATOR);
@@ -1673,7 +1717,7 @@
   }
 
   // Context menu item for QR Code Generator.
-  if (IsQRCodeGeneratorEnabled()) {
+  if (!ShouldUseShareMenu() && IsQRCodeGeneratorEnabled()) {
     // This is presented alongside the send-tab-to-self items, though each may
     // be present without the other due to feature experimentation. Therefore we
     // may or may not need to create a new separator.
@@ -1683,7 +1727,7 @@
     AppendQRCodeGeneratorItem(/*for_image=*/false, /*draw_icon=*/true);
 
     menu_model_.AddSeparator(ui::NORMAL_SEPARATOR);
-  } else if (send_tab_to_self_menu_present) {
+  } else if (!ShouldUseShareMenu() && send_tab_to_self_menu_present) {
     // Close out sharing section if send-tab-to-self was present but QR
     // generator was not.
     menu_model_.AddSeparator(ui::NORMAL_SEPARATOR);
@@ -2286,6 +2330,9 @@
     case IDC_CONTENT_CONTEXT_GENERATE_QR_CODE:
       return IsQRCodeGeneratorEnabled();
 
+    case IDC_CONTENT_CONTEXT_SHARING_SUBMENU:
+      return true;
+
     case IDC_CONTENT_LINK_SEND_TAB_TO_SELF:
     case IDC_CONTENT_LINK_SEND_TAB_TO_SELF_SINGLE_TARGET:
       return send_tab_to_self::AreContentRequirementsMet(
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu.h b/chrome/browser/renderer_context_menu/render_view_context_menu.h
index b70efa2..fbcde56 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu.h
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu.h
@@ -15,6 +15,7 @@
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/custom_handlers/protocol_handler_registry.h"
+#include "chrome/browser/sharing/share_submenu_model.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/send_tab_to_self/send_tab_to_self_sub_menu_model.h"
 #include "chrome/browser/web_applications/system_web_apps/system_web_app_types.h"
@@ -344,6 +345,9 @@
   std::unique_ptr<send_tab_to_self::SendTabToSelfSubMenuModel>
       send_tab_to_self_sub_menu_model_;
 
+  // Sharing submenu, if present.
+  std::unique_ptr<sharing::ShareSubmenuModel> share_submenu_model_;
+
   // Click to call menu observer.
   std::unique_ptr<ClickToCallContextMenuObserver>
       click_to_call_context_menu_observer_;
diff --git a/chrome/browser/reputation/local_heuristics.cc b/chrome/browser/reputation/local_heuristics.cc
index aaa34cd..fca1ed86 100644
--- a/chrome/browser/reputation/local_heuristics.cc
+++ b/chrome/browser/reputation/local_heuristics.cc
@@ -126,6 +126,7 @@
       NOTREACHED();
       return false;
     case LookalikeUrlMatchType::kCharacterSwapSiteEngagement:
+    case LookalikeUrlMatchType::kCharacterSwapTop500:
       // For now, no UI is shown for character swap matches.
       return false;
     case LookalikeUrlMatchType::kNone:
diff --git a/chrome/browser/resources/new_tab_page/modules/BUILD.gn b/chrome/browser/resources/new_tab_page/modules/BUILD.gn
index c80af44..9cc99ba 100644
--- a/chrome/browser/resources/new_tab_page/modules/BUILD.gn
+++ b/chrome/browser/resources/new_tab_page/modules/BUILD.gn
@@ -42,6 +42,7 @@
     "drive:module",
     "drive_v2:module",
     "photos:module",
+    "recipes_v2:module",
     "task_module:module",
     "//ui/webui/resources/js:load_time_data.m",
   ]
@@ -103,6 +104,7 @@
     "drive_v2:web_components",
     "dummy:web_components",
     "photos:web_components",
+    "recipes_v2:web_components",
     "task_module:web_components",
   ]
 }
@@ -159,6 +161,7 @@
     "modules.js",
     "info_dialog.js",
     "task_module/module.js",
+    "recipes_v2/module.js",
     "cart/module.js",
     "cart_v2/module.js",
     "drive/module.js",
diff --git a/chrome/browser/resources/new_tab_page/modules/module_descriptors.js b/chrome/browser/resources/new_tab_page/modules/module_descriptors.js
index b1bbb37..369e1ef 100644
--- a/chrome/browser/resources/new_tab_page/modules/module_descriptors.js
+++ b/chrome/browser/resources/new_tab_page/modules/module_descriptors.js
@@ -20,6 +20,7 @@
 import {photosDescriptor} from './photos/module.js';
 // </if>
 import {recipeTasksDescriptor, shoppingTasksDescriptor} from './task_module/module.js';
+import {recipeTasksDescriptor as recipeTasksV2Descriptor} from './recipes_v2/module.js';
 
 /** @type {!Array<!ModuleDescriptor>} */
 export const descriptors = [];
@@ -29,7 +30,11 @@
 }
 
 if (loadTimeData.getBoolean('recipeTasksModuleEnabled')) {
-  descriptors.push(recipeTasksDescriptor);
+  if (loadTimeData.getBoolean('modulesRedesignedEnabled')) {
+    descriptors.push(recipeTasksV2Descriptor);
+  } else {
+    descriptors.push(recipeTasksDescriptor);
+  }
 }
 
 if (loadTimeData.getBoolean('chromeCartModuleEnabled')) {
diff --git a/chrome/browser/resources/new_tab_page/modules/recipes_v2/BUILD.gn b/chrome/browser/resources/new_tab_page/modules/recipes_v2/BUILD.gn
new file mode 100644
index 0000000..b297268a
--- /dev/null
+++ b/chrome/browser/resources/new_tab_page/modules/recipes_v2/BUILD.gn
@@ -0,0 +1,17 @@
+# Copyright 2021 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("//third_party/closure_compiler/compile_js.gni")
+import("//tools/polymer/html_to_js.gni")
+
+js_library("module") {
+  deps = [
+    "..:module_descriptor",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+  ]
+}
+
+html_to_js("web_components") {
+  js_files = [ "module.js" ]
+}
diff --git a/chrome/browser/resources/new_tab_page/modules/recipes_v2/module.html b/chrome/browser/resources/new_tab_page/modules/recipes_v2/module.html
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/chrome/browser/resources/new_tab_page/modules/recipes_v2/module.html
diff --git a/chrome/browser/resources/new_tab_page/modules/recipes_v2/module.js b/chrome/browser/resources/new_tab_page/modules/recipes_v2/module.js
new file mode 100644
index 0000000..84f4697
--- /dev/null
+++ b/chrome/browser/resources/new_tab_page/modules/recipes_v2/module.js
@@ -0,0 +1,32 @@
+// Copyright 2021 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 {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {loadTimeData} from '../../i18n_setup.js';
+import {ModuleDescriptor} from '../module_descriptor.js';
+
+class RecipeModuleElement extends PolymerElement {
+  static get is() {
+    return 'ntp-recipes-module-redesigned';
+  }
+
+  static get template() {
+    return html`{__html_template__}`;
+  }
+}
+
+customElements.define(RecipeModuleElement.is, RecipeModuleElement);
+
+/** @return {!Promise<?HTMLElement>} */
+async function createModule(taskModuleType) {
+  const element = new RecipeModuleElement();
+  return element;
+}
+
+/** @type {!ModuleDescriptor} */
+export const recipeTasksDescriptor = new ModuleDescriptor(
+    /*id=*/ 'recipe_tasks',
+    /*name=*/ loadTimeData.getString('modulesRecipeTasksSentence'),
+    createModule.bind(null, taskModule.mojom.TaskModuleType.kRecipe));
diff --git a/chrome/browser/resources/new_tab_page/new_tab_page.js b/chrome/browser/resources/new_tab_page/new_tab_page.js
index 5ef6ce77..0bd3fea 100644
--- a/chrome/browser/resources/new_tab_page/new_tab_page.js
+++ b/chrome/browser/resources/new_tab_page/new_tab_page.js
@@ -34,6 +34,7 @@
 export {PhotosProxy} from './modules/photos/photos_module_proxy.js';
 // </if>
 export {recipeTasksDescriptor, shoppingTasksDescriptor} from './modules/task_module/module.js';
+export {recipeTasksDescriptor as recipeTasksV2Descriptor} from './modules/recipes_v2/module.js';
 export {TaskModuleHandlerProxy} from './modules/task_module/task_module_handler_proxy.js';
 export {NewTabPageProxy} from './new_tab_page_proxy.js';
 export {PromoBrowserCommandProxy} from './promo_browser_command_proxy.js';
diff --git a/chrome/browser/resources/print_preview/print_preview.js b/chrome/browser/resources/print_preview/print_preview.js
index bfcd231..e2d859b 100644
--- a/chrome/browser/resources/print_preview/print_preview.js
+++ b/chrome/browser/resources/print_preview/print_preview.js
@@ -44,6 +44,14 @@
 export {PrintPreviewDestinationDialogCrosElement} from './ui/destination_dialog_cros.js';
 export {PrintPreviewDestinationDropdownCrosElement} from './ui/destination_dropdown_cros.js';
 // </if>
+export {PrintPreviewDestinationListElement} from './ui/destination_list.js';
+export {PrintPreviewDestinationListItemElement} from './ui/destination_list_item.js';
+// <if expr="not chromeos and not lacros">
+export {PrintPreviewDestinationSelectElement} from './ui/destination_select.js';
+// </if>
+// <if expr="chromeos or lacros">
+export {PrintPreviewDestinationSelectCrosElement} from './ui/destination_select_cros.js';
+// </if>
 export {DestinationState, NUM_PERSISTED_DESTINATIONS} from './ui/destination_settings.js';
 export {PDFPlugin, PluginProxy, PluginProxyImpl} from './ui/plugin_proxy.js';
 export {PreviewAreaState} from './ui/preview_area.js';
diff --git a/chrome/browser/resources/print_preview/ui/destination_dialog.js b/chrome/browser/resources/print_preview/ui/destination_dialog.js
index ccc0449..cfd967e 100644
--- a/chrome/browser/resources/print_preview/ui/destination_dialog.js
+++ b/chrome/browser/resources/print_preview/ui/destination_dialog.js
@@ -31,6 +31,8 @@
 import {Metrics, MetricsContext} from '../metrics.js';
 import {NativeLayerImpl} from '../native_layer.js';
 
+import {PrintPreviewDestinationListItemElement} from './destination_list_item.js';
+
 
 /**
  * @constructor
diff --git a/chrome/browser/resources/print_preview/ui/destination_dialog_cros.js b/chrome/browser/resources/print_preview/ui/destination_dialog_cros.js
index 0ddadda..8ef9bee 100644
--- a/chrome/browser/resources/print_preview/ui/destination_dialog_cros.js
+++ b/chrome/browser/resources/print_preview/ui/destination_dialog_cros.js
@@ -36,6 +36,8 @@
 import {NativeLayerImpl} from '../native_layer.js';
 import {PrintServer, PrintServersConfig} from '../native_layer_cros.js';
 
+import {PrintPreviewDestinationListItemElement} from './destination_list_item.js';
+
 
 /**
  * @constructor
diff --git a/chrome/browser/resources/print_preview/ui/destination_list.js b/chrome/browser/resources/print_preview/ui/destination_list.js
index 9d12a9a..868b5e3 100644
--- a/chrome/browser/resources/print_preview/ui/destination_list.js
+++ b/chrome/browser/resources/print_preview/ui/destination_list.js
@@ -10,77 +10,102 @@
 import '../strings.m.js';
 import './throbber_css.js';
 
-import {ListPropertyUpdateBehavior} from 'chrome://resources/js/list_property_update_behavior.m.js';
-import {afterNextRender, html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {ListPropertyUpdateBehavior, ListPropertyUpdateBehaviorInterface} from 'chrome://resources/js/list_property_update_behavior.m.js';
+import {afterNextRender, html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Destination} from '../data/destination.js';
 
 const DESTINATION_ITEM_HEIGHT = 32;
 
-Polymer({
-  is: 'print-preview-destination-list',
+/**
+ * @constructor
+ * @extends {PolymerElement}
+ * @implements {ListPropertyUpdateBehaviorInterface}
+ */
+const PrintPreviewDestinationListElementBase =
+    mixinBehaviors([ListPropertyUpdateBehavior], PolymerElement);
 
-  _template: html`{__html_template__}`,
+/** @polymer */
+export class PrintPreviewDestinationListElement extends
+    PrintPreviewDestinationListElementBase {
+  static get is() {
+    return 'print-preview-destination-list';
+  }
 
-  behaviors: [ListPropertyUpdateBehavior],
+  static get template() {
+    return html`{__html_template__}`;
+  }
 
-  properties: {
-    /** @type {Array<!Destination>} */
-    destinations: Array,
+  static get properties() {
+    return {
+      /** @type {Array<!Destination>} */
+      destinations: Array,
 
-    /** @type {?RegExp} */
-    searchQuery: Object,
+      /** @type {?RegExp} */
+      searchQuery: Object,
 
-    /** @type {boolean} */
-    loadingDestinations: {
-      type: Boolean,
-      value: false,
-    },
+      /** @type {boolean} */
+      loadingDestinations: {
+        type: Boolean,
+        value: false,
+      },
 
-    /** @private {!Array<!Destination>} */
-    matchingDestinations_: {
-      type: Array,
-      value: () => [],
-    },
+      /** @private {!Array<!Destination>} */
+      matchingDestinations_: {
+        type: Array,
+        value: () => [],
+      },
 
-    /** @private {boolean} */
-    hasDestinations_: {
-      type: Boolean,
-      value: true,
-    },
+      /** @private {boolean} */
+      hasDestinations_: {
+        type: Boolean,
+        value: true,
+      },
 
-    /** @private {boolean} */
-    throbberHidden_: {
-      type: Boolean,
-      value: false,
-    },
+      /** @private {boolean} */
+      throbberHidden_: {
+        type: Boolean,
+        value: false,
+      },
 
-    /** @private */
-    hideList_: {
-      type: Boolean,
-      value: false,
-    },
-  },
+      /** @private */
+      hideList_: {
+        type: Boolean,
+        value: false,
+      },
+    };
+  }
 
-  observers: [
-    'updateMatchingDestinations_(' +
-        'destinations.*, searchQuery, loadingDestinations)',
-  ],
+  static get observers() {
+    return [
+      'updateMatchingDestinations_(' +
+          'destinations.*, searchQuery, loadingDestinations)',
 
-  /** @private {?function(Event)} */
-  boundUpdateHeight_: null,
+    ];
+  }
+
+  constructor() {
+    super();
+
+    /** @private {?function(Event)} */
+    this.boundUpdateHeight_ = null;
+  }
 
   /** @override */
-  attached() {
+  connectedCallback() {
+    super.connectedCallback();
+
     this.boundUpdateHeight_ = () => this.updateHeight_();
     window.addEventListener('resize', this.boundUpdateHeight_);
-  },
+  }
 
   /** @override */
-  detached() {
+  disconnectedCallback() {
+    super.disconnectedCallback();
+
     window.removeEventListener('resize', this.boundUpdateHeight_);
     this.boundUpdateHeight_ = null;
-  },
+  }
 
   /**
    * This is a workaround to ensure that the iron-list correctly updates the
@@ -93,7 +118,7 @@
    */
   forceIronResize_() {
     this.$.list.fire('iron-resize');
-  },
+  }
 
   /**
    * @param {number=} numDestinations
@@ -122,7 +147,7 @@
     this.$.list.style.height = listHeight > DESTINATION_ITEM_HEIGHT ?
         `${listHeight}px` :
         `${DESTINATION_ITEM_HEIGHT}px`;
-  },
+  }
 
   /** @private */
   updateMatchingDestinations_() {
@@ -142,7 +167,7 @@
         matchingDestinations);
 
     this.forceIronResize_();
-  },
+  }
 
   /**
    * @param {!KeyboardEvent} e Event containing the destination and key.
@@ -153,7 +178,7 @@
       this.onDestinationSelected_(e);
       e.stopPropagation();
     }
-  },
+  }
 
   /**
    * @param {!Event} e Event containing the destination that was selected.
@@ -164,8 +189,10 @@
       return;
     }
 
-    this.fire('destination-selected', e.target);
-  },
+    this.dispatchEvent(new CustomEvent(
+        'destination-selected',
+        {bubbles: true, composed: true, detail: e.target}));
+  }
 
   /**
    * Returns a 1-based index for aria-rowindex.
@@ -175,5 +202,8 @@
    */
   getAriaRowindex_(index) {
     return index + 1;
-  },
-});
+  }
+}
+
+customElements.define(
+    PrintPreviewDestinationListElement.is, PrintPreviewDestinationListElement);
diff --git a/chrome/browser/resources/print_preview/ui/destination_list_item.js b/chrome/browser/resources/print_preview/ui/destination_list_item.js
index 8b5e7df..f0cc8cf 100644
--- a/chrome/browser/resources/print_preview/ui/destination_list_item.js
+++ b/chrome/browser/resources/print_preview/ui/destination_list_item.js
@@ -11,10 +11,10 @@
 import '../strings.m.js';
 
 import {assert} from 'chrome://resources/js/assert.m.js';
-import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
+import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/js/i18n_behavior.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {removeHighlights} from 'chrome://resources/js/search_highlight_utils.js';
-import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Destination, DestinationOrigin} from '../data/destination.js';
 // <if expr="chromeos or lacros">
@@ -23,7 +23,6 @@
 
 import {updateHighlights} from './highlight_utils.js';
 
-
 // <if expr="chromeos or lacros">
 /** @enum {number} */
 const DestinationConfigStatus = {
@@ -33,98 +32,115 @@
 };
 // </if>
 
-Polymer({
-  is: 'print-preview-destination-list-item',
+/**
+ * @constructor
+ * @extends {PolymerElement}
+ * @implements {I18nBehaviorInterface}
+ */
+const PrintPreviewDestinationListItemElementBase =
+    mixinBehaviors([I18nBehavior], PolymerElement);
 
-  _template: html`{__html_template__}`,
+/** @polymer */
+export class PrintPreviewDestinationListItemElement extends
+    PrintPreviewDestinationListItemElementBase {
+  static get is() {
+    return 'print-preview-destination-list-item';
+  }
 
-  // <if expr="chromeos or lacros">
-  behaviors: [I18nBehavior],
-  // </if>
+  static get template() {
+    return html`{__html_template__}`;
+  }
 
-  properties: {
-    /** @type {!Destination} */
-    destination: Object,
+  static get properties() {
+    return {
+      /** @type {!Destination} */
+      destination: Object,
 
-    /** @type {?RegExp} */
-    searchQuery: Object,
+      /** @type {?RegExp} */
+      searchQuery: Object,
 
-    /** @private */
-    destinationIcon_: {
-      type: String,
-      computed: 'computeDestinationIcon_(destination, ' +
-          'destination.printerStatusReason)',
-    },
+      /** @private */
+      destinationIcon_: {
+        type: String,
+        computed: 'computeDestinationIcon_(destination, ' +
+            'destination.printerStatusReason)',
+      },
 
-    /** @private */
-    stale_: {
-      type: Boolean,
-      reflectToAttribute: true,
-    },
+      /** @private */
+      stale_: {
+        type: Boolean,
+        reflectToAttribute: true,
+      },
 
-    /** @private {string} */
-    searchHint_: String,
+      /** @private {string} */
+      searchHint_: String,
 
-    /** @private */
-    statusText_: {
-      type: String,
-      computed:
-          'computeStatusText_(destination, destination.printerStatusReason,' +
-          'configurationStatus_)',
-    },
+      /** @private */
+      statusText_: {
+        type: String,
+        computed:
+            'computeStatusText_(destination, destination.printerStatusReason,' +
+            'configurationStatus_)',
+      },
 
-    // <if expr="chromeos or lacros">
-    /** @private */
-    isDestinationCrosLocal_: {
-      type: Boolean,
-      computed: 'computeIsDestinationCrosLocal_(destination)',
-      reflectToAttribute: true,
-    },
+      // <if expr="chromeos or lacros">
+      /** @private */
+      isDestinationCrosLocal_: {
+        type: Boolean,
+        computed: 'computeIsDestinationCrosLocal_(destination)',
+        reflectToAttribute: true,
+      },
 
+      /** @private {!DestinationConfigStatus} */
+      configurationStatus_: {
+        type: Number,
+        value: DestinationConfigStatus.IDLE,
+      },
 
-    /** @private {!DestinationConfigStatus} */
-    configurationStatus_: {
-      type: Number,
-      value: DestinationConfigStatus.IDLE,
-    },
+      /**
+       * Mirroring the enum so that it can be used from HTML bindings.
+       * @private
+       */
+      statusEnum_: {
+        type: Object,
+        value: DestinationConfigStatus,
+      },
+      // </if>
+    };
+  }
 
-    /**
-     * Mirroring the enum so that it can be used from HTML bindings.
-     * @private
-     */
-    statusEnum_: {
-      type: Object,
-      value: DestinationConfigStatus,
-    },
-    // </if>
-  },
+  static get observers() {
+    return [
+      'onDestinationPropertiesChange_(' +
+          'destination.displayName, destination.isOfflineOrInvalid, ' +
+          'destination.isExtension)',
+      'updateHighlightsAndHint_(destination, searchQuery)',
+      // <if expr="chromeos or lacros">
+      'requestPrinterStatus_(destination.key)',
+      // </if>
+    ];
+  }
 
-  observers: [
-    'onDestinationPropertiesChange_(' +
-        'destination.displayName, destination.isOfflineOrInvalid, ' +
-        'destination.isExtension)',
-    'updateHighlightsAndHint_(destination, searchQuery)',
-    // <if expr="chromeos or lacros">
-    'requestPrinterStatus_(destination.key)',
-    // </if>
-  ],
+  constructor() {
+    super();
 
-  /** @private {!Array<!Node>} */
-  highlights_: [],
+    /** @private {!Array<!Node>} */
+    this.highlights_ = [];
+  }
 
   /** @private */
   onDestinationPropertiesChange_() {
     this.title = this.destination.displayName;
     this.stale_ = this.destination.isOfflineOrInvalid;
     if (this.destination.isExtension) {
-      const icon = this.$$('.extension-icon');
+      const icon = this.shadowRoot.querySelector('.extension-icon');
       icon.style.backgroundImage = '-webkit-image-set(' +
           'url(chrome://extension-icon/' + this.destination.extensionId +
           '/24/1) 1x,' +
           'url(chrome://extension-icon/' + this.destination.extensionId +
           '/48/1) 2x)';
     }
-  },
+  }
 
   // <if expr="chromeos or lacros">
   /**
@@ -137,7 +153,7 @@
         this.destination.origin === DestinationOrigin.CROS &&
         !this.destination.capabilities);
     this.configurationStatus_ = DestinationConfigStatus.IN_PROGRESS;
-  },
+  }
 
   /**
    * Called when the printer configuration request completes.
@@ -146,7 +162,7 @@
   onConfigureComplete(success) {
     this.configurationStatus_ =
         success ? DestinationConfigStatus.IDLE : DestinationConfigStatus.FAILED;
-  },
+  }
 
   /**
    * @param {!DestinationConfigStatus} status
@@ -155,7 +171,7 @@
    */
   checkConfigurationStatus_(status) {
     return this.configurationStatus_ === status;
-  },
+  }
   // </if>
 
   /** @private */
@@ -163,7 +179,7 @@
     this.updateSearchHint_();
     removeHighlights(this.highlights_);
     this.highlights_ = updateHighlights(this, this.searchQuery, new Map);
-  },
+  }
 
   /** @private */
   updateSearchHint_() {
@@ -174,7 +190,7 @@
     this.searchHint_ = matches.length === 0 ?
         (this.destination.extraPropertiesToMatch.find(p => !!p) || '') :
         matches.join(' ');
-  },
+  }
 
   /**
    * @return {string} A tooltip for the extension printer icon.
@@ -186,7 +202,7 @@
     }
     return loadTimeData.getStringF(
         'extensionDestinationIconTooltip', this.destination.extensionName);
-  },
+  }
 
   /**
    * @return {string} If the destination is a local CrOS printer, this returns
@@ -194,7 +210,7 @@
    *    printers this returns the connection status text.
    * @private
    */
-  computeStatusText_: function() {
+  computeStatusText_() {
     if (!this.destination) {
       return '';
     }
@@ -221,13 +237,13 @@
     return this.destination.isOfflineOrInvalid ?
         this.destination.connectionStatusText :
         '';
-  },
+  }
 
   /**
    * @return {string}
    * @private
    */
-  computeDestinationIcon_: function() {
+  computeDestinationIcon_() {
     if (!this.destination) {
       return '';
     }
@@ -241,7 +257,7 @@
     // </if>
 
     return this.destination.icon;
-  },
+  }
 
   // <if expr="chromeos or lacros">
   /**
@@ -249,10 +265,10 @@
    * @return {boolean}
    * @private
    */
-  computeIsDestinationCrosLocal_: function() {
+  computeIsDestinationCrosLocal_() {
     return this.destination &&
         this.destination.origin === DestinationOrigin.CROS;
-  },
+  }
 
   /** @private */
   requestPrinterStatus_() {
@@ -263,7 +279,7 @@
 
     this.destination.requestPrinterStatus().then(
         destinationKey => this.onPrinterStatusReceived_(destinationKey));
-  },
+  }
 
   /**
    * @param {string} destinationKey
@@ -274,6 +290,10 @@
       // Notify printerStatusReason to trigger icon and status text update.
       this.notifyPath(`destination.printerStatusReason`);
     }
-  },
+  }
   // </if>
-});
+}
+
+customElements.define(
+    PrintPreviewDestinationListItemElement.is,
+    PrintPreviewDestinationListItemElement);
diff --git a/chrome/browser/resources/print_preview/ui/destination_select.js b/chrome/browser/resources/print_preview/ui/destination_select.js
index ebd291ef..bd45f627 100644
--- a/chrome/browser/resources/print_preview/ui/destination_select.js
+++ b/chrome/browser/resources/print_preview/ui/destination_select.js
@@ -20,67 +20,86 @@
 import './throbber_css.js';
 import '../strings.m.js';
 
-import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
+import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/js/i18n_behavior.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
-import {Base, html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {Base, html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Destination, DestinationOrigin, PDF_DESTINATION_KEY, RecentDestination} from '../data/destination.js';
 import {getSelectDropdownBackground} from '../print_preview_utils.js';
 
-import {SelectBehavior} from './select_behavior.js';
+import {SelectBehavior, SelectBehaviorInterface} from './select_behavior.js';
 
-Polymer({
-  is: 'print-preview-destination-select',
+/**
+ * @constructor
+ * @extends {PolymerElement}
+ * @implements {I18nBehaviorInterface}
+ * @implements {SelectBehaviorInterface}
+ */
+const PrintPreviewDestinationSelectElementBase =
+    mixinBehaviors([I18nBehavior, SelectBehavior], PolymerElement);
 
-  _template: html`{__html_template__}`,
+/** @polymer */
+export class PrintPreviewDestinationSelectElement extends
+    PrintPreviewDestinationSelectElementBase {
+  static get is() {
+    return 'print-preview-destination-select';
+  }
 
-  behaviors: [I18nBehavior, SelectBehavior],
+  static get template() {
+    return html`{__html_template__}`;
+  }
 
-  properties: {
-    activeUser: String,
+  static get properties() {
+    return {
+      activeUser: String,
 
-    dark: Boolean,
+      dark: Boolean,
 
-    /** @type {!Destination} */
-    destination: Object,
+      /** @type {!Destination} */
+      destination: Object,
 
-    disabled: Boolean,
+      disabled: Boolean,
 
-    loaded: Boolean,
+      loaded: Boolean,
 
-    noDestinations: Boolean,
+      noDestinations: Boolean,
 
-    pdfPrinterDisabled: Boolean,
+      pdfPrinterDisabled: Boolean,
 
-    /** @type {!Array<!Destination>} */
-    recentDestinationList: Array,
+      /** @type {!Array<!Destination>} */
+      recentDestinationList: Array,
 
-    /** @private {string} */
-    pdfDestinationKey_: {
-      type: String,
-      value: PDF_DESTINATION_KEY,
-    },
+      /** @private {string} */
+      pdfDestinationKey_: {
+        type: String,
+        value: PDF_DESTINATION_KEY,
+      },
 
-    /** @private {string} */
-    statusText_: {
-      type: String,
-      computed: 'computeStatusText_(destination)',
-      observer: 'onStatusTextSet_'
-    },
-  },
+      /** @private {string} */
+      statusText_: {
+        type: String,
+        computed: 'computeStatusText_(destination)',
+        observer: 'onStatusTextSet_'
+      },
+    };
+  }
 
-  /** @private {!IronMetaElement} */
-  meta_: /** @type {!IronMetaElement} */ (
-      Base.create('iron-meta', {type: 'iconset'})),
+  constructor() {
+    super();
+
+    /** @private {!IronMetaElement} */
+    this.meta_ = /** @type {!IronMetaElement} */ (
+        Base.create('iron-meta', {type: 'iconset'}));
+  }
 
   focus() {
-    this.$$('.md-select').focus();
-  },
+    this.shadowRoot.querySelector('.md-select').focus();
+  }
 
   /** Sets the select to the current value of |destination|. */
   updateDestination() {
     this.selectedValue = this.destination.key;
-  },
+  }
 
   /**
    * Returns the iconset and icon for the selected printer. If printer details
@@ -121,7 +140,7 @@
     // use, so just return the generic print icon for now. It will be updated
     // when the destination is set.
     return 'print-preview:print';
-  },
+  }
 
   /**
    * @return {string} An inline svg corresponding to the icon for the current
@@ -142,11 +161,13 @@
     const iconset = /** @type {!IronIconsetSvgElement} */ (
         this.meta_.byKey(iconSetAndIcon[0]));
     return getSelectDropdownBackground(iconset, iconSetAndIcon[1], this);
-  },
+  }
 
   onProcessSelectChange(value) {
-    this.fire('selected-option-change', value);
-  },
+    this.dispatchEvent(new CustomEvent(
+        'selected-option-change',
+        {bubbles: true, composed: true, detail: value}));
+  }
 
   /**
    * @return {string} The connection status text to display.
@@ -168,18 +189,23 @@
     }
 
     return '';
-  },
+  }
 
   /** @private */
   onStatusTextSet_() {
-    this.$$('.destination-status').innerHTML = this.statusText_;
-  },
+    this.shadowRoot.querySelector('.destination-status').innerHTML =
+        this.statusText_;
+  }
 
   /**
    * Return the options currently visible to the user for testing purposes.
    * @return {!NodeList<!Element>}
    */
-  getVisibleItemsForTest: function() {
+  getVisibleItemsForTest() {
     return this.shadowRoot.querySelectorAll('option:not([hidden])');
   }
-});
+}
+
+customElements.define(
+    PrintPreviewDestinationSelectElement.is,
+    PrintPreviewDestinationSelectElement);
diff --git a/chrome/browser/resources/print_preview/ui/destination_select_cros.js b/chrome/browser/resources/print_preview/ui/destination_select_cros.js
index b8112c2a..b9adcba0 100644
--- a/chrome/browser/resources/print_preview/ui/destination_select_cros.js
+++ b/chrome/browser/resources/print_preview/ui/destination_select_cros.js
@@ -16,82 +16,100 @@
 import '../strings.m.js';
 
 import {assert} from 'chrome://resources/js/assert.m.js';
-import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
-import {Base, html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/js/i18n_behavior.m.js';
+import {Base, html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {CloudOrigins, Destination, DestinationOrigin, PDF_DESTINATION_KEY, RecentDestination, SAVE_TO_DRIVE_CROS_DESTINATION_KEY} from '../data/destination.js';
 import {ERROR_STRING_KEY_MAP, getPrinterStatusIcon, PrinterStatusReason} from '../data/printer_status_cros.js';
 
-import {SelectBehavior} from './select_behavior.js';
+import {SelectBehavior, SelectBehaviorInterface} from './select_behavior.js';
 
-Polymer({
-  is: 'print-preview-destination-select-cros',
+/**
+ * @constructor
+ * @extends {PolymerElement}
+ * @implements {I18nBehaviorInterface}
+ * @implements {SelectBehaviorInterface}
+ */
+const PrintPreviewDestinationSelectCrosElementBase =
+    mixinBehaviors([I18nBehavior, SelectBehavior], PolymerElement);
 
-  _template: html`{__html_template__}`,
+/** @polymer */
+export class PrintPreviewDestinationSelectCrosElement extends
+    PrintPreviewDestinationSelectCrosElementBase {
+  static get is() {
+    return 'print-preview-destination-select-cros';
+  }
 
-  behaviors: [I18nBehavior, SelectBehavior],
+  static get template() {
+    return html`{__html_template__}`;
+  }
 
-  properties: {
-    activeUser: String,
+  static get properties() {
+    return {
+      activeUser: String,
 
-    dark: Boolean,
+      dark: Boolean,
 
-    /** @type {!Destination} */
-    destination: Object,
+      /** @type {!Destination} */
+      destination: Object,
 
-    disabled: Boolean,
+      disabled: Boolean,
 
-    driveDestinationKey: String,
+      driveDestinationKey: String,
 
-    loaded: Boolean,
+      loaded: Boolean,
 
-    noDestinations: Boolean,
+      noDestinations: Boolean,
 
-    pdfPrinterDisabled: Boolean,
+      pdfPrinterDisabled: Boolean,
 
-    /** @type {!Array<!Destination>} */
-    recentDestinationList: {
-      type: Array,
-      observer: 'onRecentDestinationListChanged_',
-    },
+      /** @type {!Array<!Destination>} */
+      recentDestinationList: {
+        type: Array,
+        observer: 'onRecentDestinationListChanged_',
+      },
 
-    /** @private {string} */
-    pdfDestinationKey_: {
-      type: String,
-      value: PDF_DESTINATION_KEY,
-    },
+      /** @private {string} */
+      pdfDestinationKey_: {
+        type: String,
+        value: PDF_DESTINATION_KEY,
+      },
 
-    /** @private {string} */
-    statusText_: {
-      type: String,
-      computed:
-          'computeStatusText_(destination, destination.printerStatusReason)',
-      observer: 'onStatusTextSet_',
-    },
+      /** @private {string} */
+      statusText_: {
+        type: String,
+        computed:
+            'computeStatusText_(destination, destination.printerStatusReason)',
+        observer: 'onStatusTextSet_',
+      },
 
-    /** @private {string} */
-    destinationIcon_: {
-      type: String,
-      computed:
-          'computeDestinationIcon_(selectedValue, destination, destination.printerStatusReason)',
-    },
+      /** @private {string} */
+      destinationIcon_: {
+        type: String,
+        computed:
+            'computeDestinationIcon_('+
+                'selectedValue, destination, destination.printerStatusReason)',
+      },
 
-    /** @private */
-    isCurrentDestinationCrosLocal_: {
-      type: Boolean,
-      computed: 'computeIsCurrentDestinationCrosLocal_(destination)',
-      reflectToAttribute: true,
-    },
-  },
+      /** @private */
+      isCurrentDestinationCrosLocal_: {
+        type: Boolean,
+        computed: 'computeIsCurrentDestinationCrosLocal_(destination)',
+        reflectToAttribute: true,
+      },
+    };
+  }
 
   focus() {
-    this.$$('#dropdown').$$('#destination-dropdown').focus();
-  },
+    this.shadowRoot.querySelector('#dropdown')
+        .$$('#destination-dropdown')
+        .focus();
+  }
 
   /** Sets the select to the current value of |destination|. */
   updateDestination() {
     this.selectedValue = this.destination.key;
-  },
+  }
 
   /**
    * Returns the iconset and icon for the selected printer. If printer details
@@ -143,11 +161,21 @@
     // use, so just return the generic print icon for now. It will be updated
     // when the destination is set.
     return 'print-preview:print';
-  },
+  }
+
+  /**
+   * @param {string} value
+   * @private
+   */
+  fireSelectedOptionChange_(value) {
+    this.dispatchEvent(new CustomEvent(
+        'selected-option-change',
+        {bubbles: true, composed: true, detail: value}));
+  }
 
   onProcessSelectChange(value) {
-    this.fire('selected-option-change', value);
-  },
+    this.fireSelectedOptionChange_(value);
+  }
 
   /**
    * @param {!Event} e
@@ -159,8 +187,8 @@
       return;
     }
 
-    this.fire('selected-option-change', selectedItem.value);
-  },
+    this.fireSelectedOptionChange_(selectedItem.value);
+  }
 
   /**
    * Send a printer status request for any new destination in the dropdown.
@@ -175,7 +203,7 @@
       destination.requestPrinterStatus().then(
           destinationKey => this.onPrinterStatusReceived_(destinationKey));
     }
-  },
+  }
 
   /**
    * Check if the printer is currently in the dropdown then update its status
@@ -201,14 +229,14 @@
     if (this.destination && this.destination.key === destinationKey) {
       this.notifyPath(`destination.printerStatusReason`);
     }
-  },
+  }
 
   /**
    * @return {string}  An error status for the current destination. If no error
    *     status exists, an empty string.
    * @private
    */
-  computeStatusText_: function() {
+  computeStatusText_() {
     // |destination| can be either undefined, or null here.
     if (!this.destination) {
       return '';
@@ -237,39 +265,43 @@
     }
 
     return this.getErrorString_(printerStatusReason);
-  },
+  }
 
   /** @private */
   onStatusTextSet_() {
-    this.$$('#statusText').innerHTML = this.statusText_;
-  },
+    this.shadowRoot.querySelector('#statusText').innerHTML = this.statusText_;
+  }
 
   /**
    * @param {!PrinterStatusReason} printerStatusReason
    * @return {!string}
    * @private
    */
-  getErrorString_: function(printerStatusReason) {
+  getErrorString_(printerStatusReason) {
     const errorStringKey = ERROR_STRING_KEY_MAP.get(printerStatusReason);
     return errorStringKey ? this.i18n(errorStringKey) : '';
-  },
+  }
 
   /**
    * True when the currently selected destination is a CrOS local printer.
    * @return {boolean}
    * @private
    */
-  computeIsCurrentDestinationCrosLocal_: function() {
+  computeIsCurrentDestinationCrosLocal_() {
     return this.destination &&
         this.destination.origin === DestinationOrigin.CROS;
-  },
+  }
 
   /**
    * Return the options currently visible to the user for testing purposes.
    * @return {!Array<!Element>}
    */
-  getVisibleItemsForTest: function() {
-    return this.$$('#dropdown')
+  getVisibleItemsForTest() {
+    return this.shadowRoot.querySelector('#dropdown')
         .shadowRoot.querySelectorAll('.list-item:not([hidden])');
-  },
-});
+  }
+}
+
+customElements.define(
+    PrintPreviewDestinationSelectCrosElement.is,
+    PrintPreviewDestinationSelectCrosElement);
diff --git a/chrome/browser/resources/settings/chromeos/BUILD.gn b/chrome/browser/resources/settings/chromeos/BUILD.gn
index ab76ce9..6dd088b3 100644
--- a/chrome/browser/resources/settings/chromeos/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/BUILD.gn
@@ -506,10 +506,10 @@
     "chromeos/os_reset_page/os_powerwash_dialog_esim_item.js",
     "chromeos/os_reset_page/os_reset_page.js",
     "chromeos/os_route.m.js",
-    "chromeos/os_search_page/os_search_page.m.js",
-    "chromeos/os_search_page/os_search_selection_dialog.m.js",
-    "chromeos/os_search_page/search_subpage.m.js",
-    "chromeos/os_search_page/search_engine.m.js",
+    "chromeos/os_search_page/os_search_page.js",
+    "chromeos/os_search_page/os_search_selection_dialog.js",
+    "chromeos/os_search_page/search_subpage.js",
+    "chromeos/os_search_page/search_engine.js",
     "chromeos/os_settings_icons_css.m.js",
     "chromeos/os_settings_menu/os_settings_menu.m.js",
     "chromeos/os_settings_main/os_settings_main.m.js",
@@ -518,8 +518,8 @@
     "chromeos/os_settings_page/os_settings_page.m.js",
     "chromeos/os_settings_ui/os_settings_ui.m.js",
     "chromeos/os_settings_routes.m.js",
-    "chromeos/os_settings_search_box/os_search_result_row.m.js",
-    "chromeos/os_settings_search_box/os_settings_search_box.m.js",
+    "chromeos/os_settings_search_box/os_search_result_row.js",
+    "chromeos/os_settings_search_box/os_settings_search_box.js",
     "chromeos/os_toolbar/os_toolbar.m.js",
     "chromeos/parental_controls_page/parental_controls_browser_proxy.m.js",
     "chromeos/parental_controls_page/parental_controls_page.m.js",
@@ -744,11 +744,11 @@
     "os_printing_page:web_components",
     "os_privacy_page:web_components",
     "os_reset_page:web_components",
-    "os_search_page:polymer3_elements",
+    "os_search_page:web_components",
     "os_settings_main:polymer3_elements",
     "os_settings_menu:polymer3_elements",
     "os_settings_page:polymer3_elements",
-    "os_settings_search_box:polymer3_elements",
+    "os_settings_search_box:web_components",
     "os_settings_ui:polymer3_elements",
     "os_toolbar:polymer3_elements",
     "parental_controls_page:polymer3_elements",
diff --git a/chrome/browser/resources/settings/chromeos/deep_linking_behavior.js b/chrome/browser/resources/settings/chromeos/deep_linking_behavior.js
index 7575fd6..d375ec98 100644
--- a/chrome/browser/resources/settings/chromeos/deep_linking_behavior.js
+++ b/chrome/browser/resources/settings/chromeos/deep_linking_behavior.js
@@ -142,3 +142,20 @@
     return this.showDeepLink(settingId);
   },
 };
+
+/** @interface */
+/* #export */ class DeepLinkingBehaviorInterface {
+  constructor() {
+    /** @type {!Object} */
+    this.Setting;
+
+    /** @type {!Set<!chromeos.settings.mojom.Setting>}} */
+    this.supportedSettingIds;
+  }
+
+  /**
+   * @return {!Promise<!{deepLinkShown: boolean, pendingSettingId:
+   *     ?chromeos.settings.mojom.Setting}>}
+   */
+  attemptDeepLink() {}
+}
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/BUILD.gn
index b0ff37e..336c4db 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/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("//chrome/browser/resources/settings/chromeos/os_settings.gni")
 import("//third_party/closure_compiler/compile_js.gni")
 import("//tools/grit/grit_rule.gni")
 import("//tools/grit/preprocess_if_expr.gni")
@@ -14,8 +15,8 @@
 ]
 
 js_type_check("closure_compile") {
+  closure_flags = os_settings_closure_flags
   is_polymer3 = true
-  closure_flags = default_closure_args
   deps = [
     ":app_notifications_subpage",
     ":mojo_interface_provider",
@@ -26,6 +27,9 @@
   deps = [
     ":app_notification_row",
     ":mojo_interface_provider",
+    "../..:deep_linking_behavior.m",
+    "../..:os_route.m",
+    "../../..:router",
     "//chrome/browser/ui/webui/settings/chromeos/os_apps_page/mojom:mojom_js_library_for_compile",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.html b/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.html
index 62d682c..44ac6cf 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.html
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.html
@@ -27,7 +27,8 @@
   <cr-toggle id="enableDndToggle"
       checked="{{isDndEnabled_}}"
       on-change = "setQuietMode_"
-      aria-label$="$i18n{doNotDisturbToggleTitle}">
+      aria-label$="$i18n{doNotDisturbToggleTitle}"
+      deep-link-focus-id$="[[Setting.kDoNotDisturbOnOff]]">
   </cr-toggle>
 </div>
 <div class="cr-row first" id="browserSettings">
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.js b/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.js
index 7d8e28a..2a87aaba07 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.js
@@ -12,7 +12,11 @@
 import '/app-management/types.mojom-lite.js';
 import '/os_apps_page/app_notification_handler.mojom-lite.js';
 
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {Route, RouteObserverBehavior, RouteObserverBehaviorInterface, Router} from '../../../router.js';
+import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../../deep_linking_behavior.m.js';
+import {routes} from '../../os_route.m.js';
 
 import {getAppNotificationProvider} from './mojo_interface_provider.js';
 
@@ -22,7 +26,18 @@
  * notifications of all apps.
  * TODO(ethanimooney): Implement this skeleton element.
  */
-export class AppNotificationsSubpage extends PolymerElement {
+
+/**
+ * @constructor
+ * @extends {PolymerElement}
+ * @implements {DeepLinkingBehaviorInterface}
+ * @implements {RouteObserverBehaviorInterface}
+ */
+const AppNotificationsSubpageBase = mixinBehaviors(
+    [DeepLinkingBehavior, RouteObserverBehavior], PolymerElement);
+
+/** @polymer */
+export class AppNotificationsSubpage extends AppNotificationsSubpageBase {
   static get is() {
     return 'settings-app-notifications-subpage';
   }
@@ -57,6 +72,17 @@
           {title: 'Files', id: 'hhaomjibdihmijegdhdafkllkbggdgoj'}
         ],
       },
+
+      /**
+       * Used by DeepLinkingBehavior to focus this page's deep links.
+       * @type {!Set<!chromeos.settings.mojom.Setting>}
+       */
+      supportedSettingIds: {
+        type: Object,
+        value: () => new Set([
+          chromeos.settings.mojom.Setting.kDoNotDisturbOnOff,
+        ]),
+      },
     };
   }
 
@@ -88,6 +114,18 @@
     this.appNotificationsObserverReceiver_.$.close();
   }
 
+  /**
+   * RouteObserverBehavior
+   * @param {!Route} route
+   */
+  currentRouteChanged(route) {
+    // Does not apply to this page.
+    if (route !== routes.APP_NOTIFICATIONS) {
+      return;
+    }
+    this.attemptDeepLink();
+  }
+
   /** @private */
   startObservingAppNotifications_() {
     this.appNotificationsObserverReceiver_ =
diff --git a/chrome/browser/resources/settings/chromeos/os_search_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_search_page/BUILD.gn
index 61dd0cec..2bd9618 100644
--- a/chrome/browser/resources/settings/chromeos/os_search_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_search_page/BUILD.gn
@@ -2,24 +2,22 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//chrome/browser/resources/settings/chromeos/os_settings.gni")
 import("//third_party/closure_compiler/compile_js.gni")
-import("//tools/polymer/polymer.gni")
+import("//tools/polymer/html_to_js.gni")
 import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
   closure_flags = os_settings_closure_flags
   is_polymer3 = true
   deps = [
-    ":os_search_page.m",
-    ":os_search_selection_dialog.m",
-    ":search_engine.m",
-    ":search_subpage.m",
+    ":os_search_page",
+    ":os_search_selection_dialog",
+    ":search_engine",
+    ":search_subpage",
   ]
 }
 
-js_library("os_search_page.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_search_page/os_search_page.m.js" ]
+js_library("os_search_page") {
   deps = [
     "//chrome/browser/resources/settings:router",
     "//chrome/browser/resources/settings/chromeos:deep_linking_behavior.m",
@@ -38,11 +36,9 @@
     "//ui/webui/resources/js:load_time_data.m",
     "//ui/webui/resources/js/cr/ui:focus_without_ink.m",
   ]
-  extra_deps = [ ":os_search_page_module" ]
 }
 
-js_library("os_search_selection_dialog.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_search_page/os_search_selection_dialog.m.js" ]
+js_library("os_search_selection_dialog") {
   deps = [
     "//chrome/browser/resources/settings/chromeos/os_search_page:search_engines_browser_proxy",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
@@ -52,11 +48,9 @@
     "//ui/webui/resources/js:i18n_behavior.m",
     "//ui/webui/resources/js:load_time_data.m",
   ]
-  extra_deps = [ ":os_search_selection_dialog_module" ]
 }
 
-js_library("search_subpage.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_search_page/search_subpage.m.js" ]
+js_library("search_subpage") {
   deps = [
     "//chrome/browser/resources/settings:router",
     "//chrome/browser/resources/settings/chromeos:deep_linking_behavior.m",
@@ -69,11 +63,9 @@
     "//ui/webui/resources/js:load_time_data.m",
     "//ui/webui/resources/js/cr/ui:focus_without_ink.m",
   ]
-  extra_deps = [ ":search_subpage_module" ]
 }
 
-js_library("search_engine.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_search_page/search_engine.m.js" ]
+js_library("search_engine") {
   deps = [
     "//chrome/browser/resources/settings:router",
     "//chrome/browser/resources/settings/chromeos:deep_linking_behavior.m",
@@ -86,54 +78,17 @@
     "//ui/webui/resources/js:load_time_data.m",
     "//ui/webui/resources/js/cr/ui:focus_without_ink.m",
   ]
-  extra_deps = [ ":search_engine_module" ]
 }
 
 js_library("search_engines_browser_proxy") {
   deps = [ "//ui/webui/resources/js:cr.m" ]
 }
 
-group("polymer3_elements") {
-  public_deps = [
-    ":os_search_page_module",
-    ":os_search_selection_dialog_module",
-    ":search_engine_module",
-    ":search_subpage_module",
+html_to_js("web_components") {
+  js_files = [
+    "os_search_page.js",
+    "os_search_selection_dialog.js",
+    "search_engine.js",
+    "search_subpage.js",
   ]
 }
-
-polymer_modulizer("os_search_page") {
-  js_file = "os_search_page.js"
-  html_file = "os_search_page.html"
-  html_type = "dom-module"
-  auto_imports = os_settings_auto_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  migrated_imports = os_settings_migrated_imports
-}
-
-polymer_modulizer("os_search_selection_dialog") {
-  js_file = "os_search_selection_dialog.js"
-  html_file = "os_search_selection_dialog.html"
-  html_type = "dom-module"
-  auto_imports = os_settings_auto_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  migrated_imports = os_settings_migrated_imports
-}
-
-polymer_modulizer("search_subpage") {
-  js_file = "search_subpage.js"
-  html_file = "search_subpage.html"
-  html_type = "dom-module"
-  auto_imports = os_settings_auto_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  migrated_imports = os_settings_migrated_imports
-}
-
-polymer_modulizer("search_engine") {
-  js_file = "search_engine.js"
-  html_file = "search_engine.html"
-  html_type = "dom-module"
-  auto_imports = os_settings_auto_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  migrated_imports = os_settings_migrated_imports
-}
diff --git a/chrome/browser/resources/settings/chromeos/os_search_page/os_search_page.html b/chrome/browser/resources/settings/chromeos/os_search_page/os_search_page.html
index 9b59396..2b3e2d1 100644
--- a/chrome/browser/resources/settings/chromeos/os_search_page/os_search_page.html
+++ b/chrome/browser/resources/settings/chromeos/os_search_page/os_search_page.html
@@ -1,95 +1,64 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="cr-shared-style settings-shared md-select">
+  #search-wrapper {
+    align-items: center;
+    display: flex;
+    min-height: var(--settings-row-min-height);
+  }
 
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_indicator.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/load_time_data.html">
-<link rel="import" href="os_search_selection_dialog.html">
-<link rel="import" href="../../controls/extension_controlled_indicator.html">
-<link rel="import" href="../../router.html">
-<link rel="import" href="search_engines_browser_proxy.html">
-<link rel="import" href="../../settings_page/settings_animated_pages.html">
-<link rel="import" href="../../settings_page/settings_subpage.html">
-<link rel="import" href="../../settings_shared_css.html">
-<link rel="import" href="../../settings_vars_css.html">
-<link rel="import" href="../deep_linking_behavior.html">
-<link rel="import" href="../google_assistant_page/google_assistant_page.html">
-<link rel="import" href="search_subpage.html">
-<link rel="import" href="search_engine.html">
-<link rel="import" href="../os_route.html">
-
-<dom-module id="os-settings-search-page">
-  <template>
-    <style include="cr-shared-style settings-shared md-select">
-      #search-wrapper {
-        align-items: center;
-        display: flex;
-        min-height: var(--settings-row-min-height);
-      }
-
-      cr-policy-pref-indicator {
-        padding-inline-end: 8px;
-      }
-    </style>
-    <settings-animated-pages id="pages" section="osSearch"
-        focus-config="[[focusConfig_]]">
-      <div route-path="default">
-        <template is="dom-if" if="[[!shouldShowQuickAnswersSettings_]]">
-          <settings-search-engine
-              deep-link-focus-id$="[[Setting.kPreferredSearchEngine]]">
-          </settings-search-engine>
-        </template>
-        <template is="dom-if" if="[[shouldShowQuickAnswersSettings_]]">
-          <cr-link-row id="search"
-              id="searchSubpageTrigger"
-              label="$i18n{searchSubpageTitle}"
-              on-click="onSearchTap_"
-              role-description="$i18n{subpageArrowRoleDescription}">
-          </cr-link-row>
-        </template>
-        <!-- Google Assistant -->
-        <template is="dom-if" if="[[isAssistantAllowed_]]">
-          <cr-link-row
-              class="hr"
-              id="assistantSubpageTrigger"
-              label="$i18n{searchGoogleAssistant}"
-              sub-label="[[getAssistantEnabledDisabledLabel_(
-                  prefs.settings.voice_interaction.enabled.value)]]"
-              on-click="onGoogleAssistantTap_"
-              role-description="$i18n{subpageArrowRoleDescription}">
-          </cr-link-row>
-        </template>
-      </div>
-      <template is="dom-if" if="[[shouldShowQuickAnswersSettings_]]">
-        <template is="dom-if" route-path="/osSearch/search">
-          <settings-subpage page-title="$i18n{searchSubpageTitle}">
-            <settings-search-subpage prefs="{{prefs}}">
-            </settings-search-subpage>
-          </settings-subpage>
-        </template>
-      </template>
-      <template is="dom-if" if="[[isAssistantAllowed_]]">
-        <template is="dom-if" route-path="/googleAssistant">
-          <settings-subpage page-title="$i18n{googleAssistantPageTitle}">
-            <settings-google-assistant-page prefs="{{prefs}}">
-            </settings-google-assistant-page>
-          </settings-subpage>
-        </template>
-      </template>
-    </settings-animated-pages>
-
-    <!-- Default Search Engine Selection Dialog -->
-    <template is="dom-if" if="[[showSearchSelectionDialog_]]" restamp>
-      <os-settings-search-selection-dialog
-          on-close="onSearchSelectionDialogClose_">
-      </os-settings-search-selection-dialog>
+  cr-policy-pref-indicator {
+    padding-inline-end: 8px;
+  }
+</style>
+<settings-animated-pages id="pages" section="osSearch"
+    focus-config="[[focusConfig_]]">
+  <div route-path="default">
+    <template is="dom-if" if="[[!shouldShowQuickAnswersSettings_]]">
+      <settings-search-engine
+          deep-link-focus-id$="[[Setting.kPreferredSearchEngine]]">
+      </settings-search-engine>
+    </template>
+    <template is="dom-if" if="[[shouldShowQuickAnswersSettings_]]">
+      <cr-link-row id="search"
+          id="searchSubpageTrigger"
+          label="$i18n{searchSubpageTitle}"
+          on-click="onSearchTap_"
+          role-description="$i18n{subpageArrowRoleDescription}">
+      </cr-link-row>
+    </template>
+    <!-- Google Assistant -->
+    <template is="dom-if" if="[[isAssistantAllowed_]]">
+      <cr-link-row
+          class="hr"
+          id="assistantSubpageTrigger"
+          label="$i18n{searchGoogleAssistant}"
+          sub-label="[[getAssistantEnabledDisabledLabel_(
+              prefs.settings.voice_interaction.enabled.value)]]"
+          on-click="onGoogleAssistantTap_"
+          role-description="$i18n{subpageArrowRoleDescription}">
+      </cr-link-row>
+    </template>
+  </div>
+  <template is="dom-if" if="[[shouldShowQuickAnswersSettings_]]">
+    <template is="dom-if" route-path="/osSearch/search">
+      <settings-subpage page-title="$i18n{searchSubpageTitle}">
+        <settings-search-subpage prefs="{{prefs}}">
+        </settings-search-subpage>
+      </settings-subpage>
     </template>
   </template>
-  <script src="os_search_page.js"></script>
-</dom-module>
+  <template is="dom-if" if="[[isAssistantAllowed_]]">
+    <template is="dom-if" route-path="/googleAssistant">
+      <settings-subpage page-title="$i18n{googleAssistantPageTitle}">
+        <settings-google-assistant-page prefs="{{prefs}}">
+        </settings-google-assistant-page>
+      </settings-subpage>
+    </template>
+  </template>
+</settings-animated-pages>
+
+<!-- Default Search Engine Selection Dialog -->
+<template is="dom-if" if="[[showSearchSelectionDialog_]]" restamp>
+  <os-settings-search-selection-dialog
+      on-close="onSearchSelectionDialogClose_">
+  </os-settings-search-selection-dialog>
+</template>
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 8a515191..534ff75 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
@@ -6,13 +6,42 @@
  * @fileoverview
  * 'os-settings-search-page' contains search and assistant settings.
  */
+import '//resources/cr_elements/cr_link_row/cr_link_row.js';
+import '//resources/cr_elements/icons.m.js';
+import '//resources/cr_elements/policy/cr_policy_pref_indicator.m.js';
+import '//resources/cr_elements/shared_style_css.m.js';
+import '//resources/cr_elements/shared_vars_css.m.js';
+import './os_search_selection_dialog.js';
+import '../../controls/extension_controlled_indicator.js';
+import '../../settings_page/settings_animated_pages.js';
+import '../../settings_page/settings_subpage.js';
+import '../../settings_shared_css.js';
+import '../../settings_vars_css.js';
+import '../google_assistant_page/google_assistant_page.js';
+import './search_subpage.js';
+import './search_engine.js';
+
+import {assert, assertNotReached} from '//resources/js/assert.m.js';
+import {addWebUIListener, removeWebUIListener, sendWithPromise, WebUIListener} from '//resources/js/cr.m.js';
+import {focusWithoutInk} from '//resources/js/cr/ui/focus_without_ink.m.js';
+import {I18nBehavior} from '//resources/js/i18n_behavior.m.js';
+import {loadTimeData} from '//resources/js/load_time_data.m.js';
+import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {Route, RouteObserverBehavior, Router} from '../../router.js';
+import {DeepLinkingBehavior} from '../deep_linking_behavior.m.js';
+import {routes} from '../os_route.m.js';
+
+import {SearchEngine, SearchEnginesBrowserProxy, SearchEnginesBrowserProxyImpl} from './search_engines_browser_proxy.js';
+
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'os-settings-search-page',
 
   behaviors: [
     DeepLinkingBehavior,
     I18nBehavior,
-    settings.RouteObserverBehavior,
+    RouteObserverBehavior,
   ],
 
   properties: {
@@ -49,19 +78,18 @@
   /** @override */
   ready() {
     this.focusConfig_ = new Map();
+    this.focusConfig_.set(routes.SEARCH_SUBPAGE.path, '#searchSubpageTrigger');
     this.focusConfig_.set(
-        settings.routes.SEARCH_SUBPAGE.path, '#searchSubpageTrigger');
-    this.focusConfig_.set(
-        settings.routes.GOOGLE_ASSISTANT.path, '#assistantSubpageTrigger');
+        routes.GOOGLE_ASSISTANT.path, '#assistantSubpageTrigger');
   },
 
   /**
-   * @param {!settings.Route} route
-   * @param {!settings.Route} oldRoute
+   * @param {!Route} route
+   * @param {!Route} oldRoute
    */
   currentRouteChanged(route, oldRoute) {
     // Does not apply to this page.
-    if (route !== settings.routes.OS_SEARCH) {
+    if (route !== routes.OS_SEARCH) {
       return;
     }
 
@@ -70,13 +98,13 @@
 
   /** @private */
   onSearchTap_() {
-    settings.Router.getInstance().navigateTo(settings.routes.SEARCH_SUBPAGE);
+    Router.getInstance().navigateTo(routes.SEARCH_SUBPAGE);
   },
 
   /** @private */
   onGoogleAssistantTap_() {
     assert(this.isAssistantAllowed_);
-    settings.Router.getInstance().navigateTo(settings.routes.GOOGLE_ASSISTANT);
+    Router.getInstance().navigateTo(routes.GOOGLE_ASSISTANT);
   },
 
   /**
diff --git a/chrome/browser/resources/settings/chromeos/os_search_page/os_search_selection_dialog.html b/chrome/browser/resources/settings/chromeos/os_search_page/os_search_selection_dialog.html
index 6f61f23..ab36cc6 100644
--- a/chrome/browser/resources/settings/chromeos/os_search_page/os_search_selection_dialog.html
+++ b/chrome/browser/resources/settings/chromeos/os_search_page/os_search_selection_dialog.html
@@ -1,56 +1,40 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="settings-shared shared-style md-select">
+  :host {
+      --cr-dialog-width: 320px;
+      --md-select-width: 280px;
+  }
 
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/md_select_css.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="chrome://resources/html/load_time_data.html">>
-<link rel="import" href="search_engines_browser_proxy.html">
-<link rel="import" href="../../settings_shared_css.html">
+  #dialogBody {
+    display: flex;
+    flex-direction: column;
+    height: 95px;
+    overflow: auto;
+  }
 
-<dom-module id="os-settings-search-selection-dialog">
-  <template>
-    <style include="settings-shared shared-style md-select">
-      :host {
-          --cr-dialog-width: 320px;
-          --md-select-width: 280px;
-      }
-
-      #dialogBody {
-        display: flex;
-        flex-direction: column;
-        height: 95px;
-        overflow: auto;
-      }
-
-      .md-select {
-          margin-top: 20px;
-      }
-    </style>
-    <cr-dialog id="dialog" show-on-attach>
-      <div id="title" slot="title">$i18n{osSearchEngineLabel}</div>
-      <div id="dialogBody" slot="body">
-        <div id="description">$i18n{osSearchEngineDescription}</div>
-        <select class="md-select"
-            aria-labelledby="title"
-            aria-describedby="description"
-            autofocus>
-          <template is="dom-repeat" items="[[searchEngines_]]">
-            <option selected="[[item.default]]">[[item.name]]</option>
-          </template>
-        </select>
-      </div>
-      <div slot="button-container">
-        <cr-button class="cancel-button" on-click="onCancelButtonClick_">
-          $i18n{cancel}
-        </cr-button>
-        <cr-button class="action-button" on-click="onActionButtonClick_"
-            disabled="[[disableActionButton_]]">
-          $i18n{done}
-        </cr-button>
-      </div>
-    </cr-dialog>
-  </template>
-  <script src="os_search_selection_dialog.js"></script>
-</dom-module>
+  .md-select {
+      margin-top: 20px;
+  }
+</style>
+<cr-dialog id="dialog" show-on-attach>
+  <div id="title" slot="title">$i18n{osSearchEngineLabel}</div>
+  <div id="dialogBody" slot="body">
+    <div id="description">$i18n{osSearchEngineDescription}</div>
+    <select class="md-select"
+        aria-labelledby="title"
+        aria-describedby="description"
+        autofocus>
+      <template is="dom-repeat" items="[[searchEngines_]]">
+        <option selected="[[item.default]]">[[item.name]]</option>
+      </template>
+    </select>
+  </div>
+  <div slot="button-container">
+    <cr-button class="cancel-button" on-click="onCancelButtonClick_">
+      $i18n{cancel}
+    </cr-button>
+    <cr-button class="action-button" on-click="onActionButtonClick_"
+        disabled="[[disableActionButton_]]">
+      $i18n{done}
+    </cr-button>
+  </div>
+</cr-dialog>
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 52652c8..d655bcb 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
@@ -6,7 +6,20 @@
  * @fileoverview 'os-settings-search-selection-dialog' is a dialog for setting
  * the preferred search engine.
  */
+import '//resources/cr_elements/cr_button/cr_button.m.js';
+import '//resources/cr_elements/cr_dialog/cr_dialog.m.js';
+import '//resources/cr_elements/md_select_css.m.js';
+import '//resources/cr_elements/shared_vars_css.m.js';
+import '../../settings_shared_css.js';
+
+import {addWebUIListener, removeWebUIListener, sendWithPromise, WebUIListener} from '//resources/js/cr.m.js';
+import {loadTimeData} from '//resources/js/load_time_data.m.js';
+import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {SearchEngine, SearchEnginesBrowserProxy, SearchEnginesBrowserProxyImpl} from './search_engines_browser_proxy.js';
+
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'os-settings-search-selection-dialog',
 
   behaviors: [],
@@ -24,12 +37,12 @@
     },
   },
 
-  /** @private {?settings.SearchEnginesBrowserProxy} */
+  /** @private {?SearchEnginesBrowserProxy} */
   browserProxy_: null,
 
   /** @override */
   created() {
-    this.browserProxy_ = settings.SearchEnginesBrowserProxyImpl.getInstance();
+    this.browserProxy_ = SearchEnginesBrowserProxyImpl.getInstance();
   },
 
   /** @override */
@@ -38,7 +51,7 @@
       this.set('searchEngines_', searchEngines.defaults);
     };
     this.browserProxy_.getSearchEnginesList().then(updateSearchEngines);
-    cr.addWebUIListener('search-engines-changed', updateSearchEngines);
+    addWebUIListener('search-engines-changed', updateSearchEngines);
   },
 
   /**
diff --git a/chrome/browser/resources/settings/chromeos/os_search_page/search_engine.html b/chrome/browser/resources/settings/chromeos/os_search_page/search_engine.html
index 920ff8f..c80ce83 100644
--- a/chrome/browser/resources/settings/chromeos/os_search_page/search_engine.html
+++ b/chrome/browser/resources/settings/chromeos/os_search_page/search_engine.html
@@ -1,87 +1,57 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="cr-shared-style settings-shared md-select">
+  #search-wrapper {
+    align-items: center;
+    display: flex;
+  }
 
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_indicator.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/load_time_data.html">
-<link rel="import" href="os_search_selection_dialog.html">
-<link rel="import" href="../../controls/extension_controlled_indicator.html">
-<link rel="import" href="../../controls/controlled_button.html">
-<link rel="import" href="../../controls/settings_toggle_button.html">
-<link rel="import" href="../../prefs/prefs.html">
-<link rel="import" href="../../prefs/prefs_behavior.html">
-<link rel="import" href="../../prefs/pref_util.html">
-<link rel="import" href="../../router.html">
-<link rel="import" href="search_engines_browser_proxy.html">
-<link rel="import" href="../../settings_shared_css.html">
-<link rel="import" href="../../settings_vars_css.html">
-<link rel="import" href="../os_route.html">
+  cr-policy-pref-indicator {
+    padding-inline-end: 8px;
+  }
+</style>
 
-<dom-module id="settings-search-engine">
-  <template>
-    <style include="cr-shared-style settings-shared md-select">
-      #search-wrapper {
-        align-items: center;
-        display: flex;
-      }
-
-      cr-policy-pref-indicator {
-        padding-inline-end: 8px;
-      }
-    </style>
-
-    <!-- Omnibox and launcher search engine. This shares the same pref with
-        browser search engine because users probably don't want one engine
-        in the omnibox and a different one in the launcher. -->
-    <div class="settings-box two-line first block">
-      <div id="search-wrapper">
-        <div class="start settings-box-text" aria-hidden="true">
-          <div id="searchExplanation">$i18n{osSearchEngineLabel}</div>
-          <div class="secondary" id="currentSearchEngine">
-            [[currentSearchEngine_.name]]
-          </div>
-        </div>
-        <template is="dom-if" if="[[isDefaultSearchControlledByPolicy_(
-            prefs.default_search_provider_data.template_url_data)]]">
-          <cr-policy-pref-indicator pref="[[
-              prefs.default_search_provider_data.template_url_data]]">
-          </cr-policy-pref-indicator>
-        </template>
-        <div class="separator"></div>
-        <cr-button id="searchSelectionDialogButton"
-            aria-labelledby="searchExplanation"
-            aria-describedby="currentSearchEngine"
-            disabled$="[[isDefaultSearchEngineEnforced_(
-                prefs.default_search_provider_data.template_url_data)]]"
-            on-click="onShowSearchSelectionDialogClick_">
-          $i18n{osSearchEngineButtonLabel}
-        </cr-button>
+<!-- Omnibox and launcher search engine. This shares the same pref with
+    browser search engine because users probably don't want one engine
+    in the omnibox and a different one in the launcher. -->
+<div class="settings-box two-line first block">
+  <div id="search-wrapper">
+    <div class="start settings-box-text" aria-hidden="true">
+      <div id="searchExplanation">$i18n{osSearchEngineLabel}</div>
+      <div class="secondary" id="currentSearchEngine">
+        [[currentSearchEngine_.name]]
       </div>
-      <template is="dom-if"
-          if="[[prefs.default_search_provider_data.template_url_data.extensionId]]">
-        <extension-controlled-indicator
-            extension-id="[[
-                prefs.default_search_provider_data.template_url_data.extensionId]]"
-            extension-name="[[
-                prefs.default_search_provider_data.template_url_data.controlledByName]]"
-            extension-can-be-disabled="[[
-                prefs.default_search_provider_data.template_url_data.extensionCanBeDisabled]]"
-            on-disable-extension="onDisableExtension_">
-        </extension-controlled-indicator>
-      </template>
     </div>
-
-    <template is="dom-if" if="[[showSearchSelectionDialog_]]" restamp>
-      <os-settings-search-selection-dialog
-          on-close="onSearchSelectionDialogClose_">
-      </os-settings-search-selection-dialog>
+    <template is="dom-if" if="[[isDefaultSearchControlledByPolicy_(
+        prefs.default_search_provider_data.template_url_data)]]">
+      <cr-policy-pref-indicator pref="[[
+          prefs.default_search_provider_data.template_url_data]]">
+      </cr-policy-pref-indicator>
     </template>
+    <div class="separator"></div>
+    <cr-button id="searchSelectionDialogButton"
+        aria-labelledby="searchExplanation"
+        aria-describedby="currentSearchEngine"
+        disabled$="[[isDefaultSearchEngineEnforced_(
+            prefs.default_search_provider_data.template_url_data)]]"
+        on-click="onShowSearchSelectionDialogClick_">
+      $i18n{osSearchEngineButtonLabel}
+    </cr-button>
+  </div>
+  <template is="dom-if"
+      if="[[prefs.default_search_provider_data.template_url_data.extensionId]]">
+    <extension-controlled-indicator
+        extension-id="[[
+            prefs.default_search_provider_data.template_url_data.extensionId]]"
+        extension-name="[[
+            prefs.default_search_provider_data.template_url_data.controlledByName]]"
+        extension-can-be-disabled="[[
+            prefs.default_search_provider_data.template_url_data.extensionCanBeDisabled]]"
+        on-disable-extension="onDisableExtension_">
+    </extension-controlled-indicator>
   </template>
-  <script src="search_engine.js"></script>
-</dom-module>
+</div>
+
+<template is="dom-if" if="[[showSearchSelectionDialog_]]" restamp>
+  <os-settings-search-selection-dialog
+      on-close="onSearchSelectionDialogClose_">
+  </os-settings-search-selection-dialog>
+</template>
diff --git a/chrome/browser/resources/settings/chromeos/os_search_page/search_engine.js b/chrome/browser/resources/settings/chromeos/os_search_page/search_engine.js
index f2363d5..c70d7a06 100644
--- a/chrome/browser/resources/settings/chromeos/os_search_page/search_engine.js
+++ b/chrome/browser/resources/settings/chromeos/os_search_page/search_engine.js
@@ -6,7 +6,35 @@
  * @fileoverview 'settings-search-engine' is the settings module for setting
  * the preferred search engine.
  */
+import '//resources/cr_elements/cr_link_row/cr_link_row.js';
+import '//resources/cr_elements/icons.m.js';
+import '//resources/cr_elements/policy/cr_policy_pref_indicator.m.js';
+import '//resources/cr_elements/shared_style_css.m.js';
+import '//resources/cr_elements/shared_vars_css.m.js';
+import './os_search_selection_dialog.js';
+import '../../controls/extension_controlled_indicator.js';
+import '../../controls/controlled_button.js';
+import '../../controls/settings_toggle_button.js';
+import '../../prefs/prefs.js';
+import '../../prefs/pref_util.js';
+import '../../settings_shared_css.js';
+import '../../settings_vars_css.js';
+
+import {assert, assertNotReached} from '//resources/js/assert.m.js';
+import {addWebUIListener, removeWebUIListener, sendWithPromise, WebUIListener} from '//resources/js/cr.m.js';
+import {focusWithoutInk} from '//resources/js/cr/ui/focus_without_ink.m.js';
+import {I18nBehavior} from '//resources/js/i18n_behavior.m.js';
+import {loadTimeData} from '//resources/js/load_time_data.m.js';
+import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {PrefsBehavior} from '../../prefs/prefs_behavior.js';
+import {Route, RouteObserverBehavior, Router} from '../../router.js';
+import {routes} from '../os_route.m.js';
+
+import {SearchEngine, SearchEnginesBrowserProxy, SearchEnginesBrowserProxyImpl} from './search_engines_browser_proxy.js';
+
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'settings-search-engine',
 
   behaviors: [
@@ -23,12 +51,12 @@
     showSearchSelectionDialog_: Boolean,
   },
 
-  /** @private {?settings.SearchEnginesBrowserProxy} */
+  /** @private {?SearchEnginesBrowserProxy} */
   browserProxy_: null,
 
   /** @override */
   created() {
-    this.browserProxy_ = settings.SearchEnginesBrowserProxyImpl.getInstance();
+    this.browserProxy_ = SearchEnginesBrowserProxyImpl.getInstance();
   },
 
   /** @override */
@@ -38,7 +66,7 @@
           searchEngines.defaults.find(searchEngine => searchEngine.default);
     };
     this.browserProxy_.getSearchEnginesList().then(updateCurrentSearchEngine);
-    cr.addWebUIListener('search-engines-changed', updateCurrentSearchEngine);
+    addWebUIListener('search-engines-changed', updateCurrentSearchEngine);
   },
 
   /** @override */
@@ -59,7 +87,7 @@
   /** @private */
   onSearchSelectionDialogClose_() {
     this.showSearchSelectionDialog_ = false;
-    cr.ui.focusWithoutInk(assert(this.$$('#searchSelectionDialogButton')));
+    focusWithoutInk(assert(this.$$('#searchSelectionDialogButton')));
   },
 
   /**
diff --git a/chrome/browser/resources/settings/chromeos/os_search_page/search_subpage.html b/chrome/browser/resources/settings/chromeos/os_search_page/search_subpage.html
index 7957d35..72d6919 100644
--- a/chrome/browser/resources/settings/chromeos/os_search_page/search_subpage.html
+++ b/chrome/browser/resources/settings/chromeos/os_search_page/search_subpage.html
@@ -1,78 +1,48 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="cr-shared-style settings-shared">
+  .subsection {
+    padding-inline-end: var(--cr-section-padding);
+    padding-inline-start: var(--cr-section-indent-padding);
+  }
 
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_indicator.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/load_time_data.html">
-<link rel="import" href="../../controls/controlled_button.html">
-<link rel="import" href="../../controls/settings_toggle_button.html">
-<link rel="import" href="../../prefs/prefs.html">
-<link rel="import" href="../../prefs/prefs_behavior.html">
-<link rel="import" href="../../prefs/pref_util.html">
-<link rel="import" href="../../router.html">
-<link rel="import" href="../../settings_shared_css.html">
-<link rel="import" href="../../settings_vars_css.html">
-<link rel="import" href="../localized_link/localized_link.js">
-<link rel="import" href="../deep_linking_behavior.html">
-<link rel="import" href="../os_route.html">
-<link rel="import" href="search_engine.html">
+  .subsection > settings-toggle-button {
+    padding-inline-end: 0;
+    padding-inline-start: 0;
+  }
 
-<dom-module id="settings-search-subpage">
-  <template>
-    <style include="cr-shared-style settings-shared">
-      .subsection {
-        padding-inline-end: var(--cr-section-padding);
-        padding-inline-start: var(--cr-section-indent-padding);
-      }
-
-      .subsection > settings-toggle-button {
-        padding-inline-end: 0;
-        padding-inline-start: 0;
-      }
-
-      #quick-answers-definition-enable {
-        border-top: none;
-      }
-    </style>
-    <settings-search-engine
-        deep-link-focus-id$="[[Setting.kPreferredSearchEngine]]">
-    </settings-search-engine>
-    <settings-toggle-button id="quick-answers-enable"
-        class="hr"
-        pref="{{prefs.settings.quick_answers.enabled}}"
-        label="$i18n{quickAnswersEnable}"
-        sub-label="$i18n{quickAnswersEnableDescription}"
-        deep-link-focus-id$="[[Setting.kQuickAnswersOnOff]]">
-    </settings-toggle-button>
-    <template is="dom-if"
-        if="[[prefs.settings.quick_answers.enabled.value]]">
-      <settings-toggle-button id="quick-answers-definition-enable"
-          class="hr subsection no-border-top"
-          pref="{{prefs.settings.quick_answers.definition.enabled}}"
-          label="$i18n{quickAnswersDefinitionEnable}"
-          deep-link-focus-id$="[[Setting.kQuickAnswersDefinition]]">
-      </settings-toggle-button>
-      <settings-toggle-button id="quick-answers-translation-enable"
-          class="hr subsection"
-          pref="{{prefs.settings.quick_answers.translation.enabled}}"
-          label="$i18n{quickAnswersTranslationEnable}"
-          sub-label-with-link="[[getAriaLabelledTranslationSubLabel_()]]"
-          on-sub-label-link-clicked="onSettingsLinkClick_"
-          deep-link-focus-id$="[[Setting.kQuickAnswersTranslation]]">
-      </settings-toggle-button>
-      <settings-toggle-button id="quick-answers-unit-conversion-enable"
-          class="hr subsection"
-          pref="{{prefs.settings.quick_answers.unit_conversion.enabled}}"
-          label="$i18n{quickAnswersUnitConversionEnable}"
-          deep-link-focus-id$="[[Setting.kQuickAnswersUnitConversion]]">
-      </settings-toggle-button>
-    </template>
-  </template>
-  <script src="search_subpage.js"></script>
-</dom-module>
+  #quick-answers-definition-enable {
+    border-top: none;
+  }
+</style>
+<settings-search-engine
+    deep-link-focus-id$="[[Setting.kPreferredSearchEngine]]">
+</settings-search-engine>
+<settings-toggle-button id="quick-answers-enable"
+    class="hr"
+    pref="{{prefs.settings.quick_answers.enabled}}"
+    label="$i18n{quickAnswersEnable}"
+    sub-label="$i18n{quickAnswersEnableDescription}"
+    deep-link-focus-id$="[[Setting.kQuickAnswersOnOff]]">
+</settings-toggle-button>
+<template is="dom-if"
+    if="[[prefs.settings.quick_answers.enabled.value]]">
+  <settings-toggle-button id="quick-answers-definition-enable"
+      class="hr subsection no-border-top"
+      pref="{{prefs.settings.quick_answers.definition.enabled}}"
+      label="$i18n{quickAnswersDefinitionEnable}"
+      deep-link-focus-id$="[[Setting.kQuickAnswersDefinition]]">
+  </settings-toggle-button>
+  <settings-toggle-button id="quick-answers-translation-enable"
+      class="hr subsection"
+      pref="{{prefs.settings.quick_answers.translation.enabled}}"
+      label="$i18n{quickAnswersTranslationEnable}"
+      sub-label-with-link="[[getAriaLabelledTranslationSubLabel_()]]"
+      on-sub-label-link-clicked="onSettingsLinkClick_"
+      deep-link-focus-id$="[[Setting.kQuickAnswersTranslation]]">
+  </settings-toggle-button>
+  <settings-toggle-button id="quick-answers-unit-conversion-enable"
+      class="hr subsection"
+      pref="{{prefs.settings.quick_answers.unit_conversion.enabled}}"
+      label="$i18n{quickAnswersUnitConversionEnable}"
+      deep-link-focus-id$="[[Setting.kQuickAnswersUnitConversion]]">
+  </settings-toggle-button>
+</template>
diff --git a/chrome/browser/resources/settings/chromeos/os_search_page/search_subpage.js b/chrome/browser/resources/settings/chromeos/os_search_page/search_subpage.js
index abe95a4..14bb846 100644
--- a/chrome/browser/resources/settings/chromeos/os_search_page/search_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/os_search_page/search_subpage.js
@@ -6,14 +6,41 @@
  * @fileoverview 'settings-search-subpage' is the settings sub-page containing
  * search engine and quick answers settings.
  */
+import '//resources/cr_elements/cr_link_row/cr_link_row.js';
+import '//resources/cr_elements/icons.m.js';
+import '//resources/cr_elements/policy/cr_policy_pref_indicator.m.js';
+import '//resources/cr_elements/shared_style_css.m.js';
+import '//resources/cr_elements/shared_vars_css.m.js';
+import '../../controls/controlled_button.js';
+import '../../controls/settings_toggle_button.js';
+import '../../prefs/prefs.js';
+import '../../prefs/pref_util.js';
+import '../../settings_shared_css.js';
+import '../../settings_vars_css.js';
+import '../localized_link/localized_link.js';
+import './search_engine.js';
+
+import {assert, assertNotReached} from '//resources/js/assert.m.js';
+import {addWebUIListener, removeWebUIListener, sendWithPromise, WebUIListener} from '//resources/js/cr.m.js';
+import {focusWithoutInk} from '//resources/js/cr/ui/focus_without_ink.m.js';
+import {I18nBehavior} from '//resources/js/i18n_behavior.m.js';
+import {loadTimeData} from '//resources/js/load_time_data.m.js';
+import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {PrefsBehavior} from '../../prefs/prefs_behavior.js';
+import {Route, RouteObserverBehavior, Router} from '../../router.js';
+import {DeepLinkingBehavior} from '../deep_linking_behavior.m.js';
+import {routes} from '../os_route.m.js';
+
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'settings-search-subpage',
 
   behaviors: [
     DeepLinkingBehavior,
     I18nBehavior,
     PrefsBehavior,
-    settings.RouteObserverBehavior,
+    RouteObserverBehavior,
   ],
 
   properties: {
@@ -34,12 +61,12 @@
   },
 
   /**
-   * @param {!settings.Route} route
-   * @param {!settings.Route} oldRoute
+   * @param {!Route} route
+   * @param {!Route} oldRoute
    */
   currentRouteChanged(route, oldRoute) {
     // Does not apply to this page.
-    if (route !== settings.routes.SEARCH_SUBPAGE) {
+    if (route !== routes.SEARCH_SUBPAGE) {
       return;
     }
 
@@ -50,7 +77,7 @@
    * @private
    */
   onSettingsLinkClick_() {
-    Router.getInstance().navigateTo(settings.routes.OS_LANGUAGES_LANGUAGES);
+    Router.getInstance().navigateTo(routes.OS_LANGUAGES_LANGUAGES);
   },
 
   /**
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.gni b/chrome/browser/resources/settings/chromeos/os_settings.gni
index 35200c3..a88d87e 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.gni
+++ b/chrome/browser/resources/settings/chromeos/os_settings.gni
@@ -378,7 +378,13 @@
                                  "chrome/browser/resources/settings/chromeos/os_reset_page/os_powerwash_dialog_esim_item.html",
                                  "chrome/browser/resources/settings/chromeos/os_reset_page/os_reset_browser_proxy.html",
                                  "chrome/browser/resources/settings/chromeos/os_reset_page/os_reset_page.html",
+                                 "chrome/browser/resources/settings/chromeos/os_search_page/os_search_page.html",
+                                 "chrome/browser/resources/settings/chromeos/os_search_page/os_search_selection_dialog.html",
+                                 "chrome/browser/resources/settings/chromeos/os_search_page/search_engine.html",
                                  "chrome/browser/resources/settings/chromeos/os_search_page/search_engines_browser_proxy.html",
+                                 "chrome/browser/resources/settings/chromeos/os_search_page/search_subpage.html",
+                                 "chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.html",
+                                 "chrome/browser/resources/settings/chromeos/os_settings_search_box/os_search_result_row.html",
                                  "chrome/browser/resources/settings/chromeos/personalization_page/change_picture_browser_proxy.html",
                                  "chrome/browser/resources/settings/chromeos/personalization_page/wallpaper_browser_proxy.html",
                                  "chrome/browser/resources/settings/chromeos/keyboard_shortcut_banner/keyboard_shortcut_banner.html",
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.js b/chrome/browser/resources/settings/chromeos/os_settings.js
index 5a2d181..c169b2d 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings.js
@@ -79,15 +79,15 @@
 import './os_people_page/account_manager.m.js';
 import './os_people_page/os_people_page.m.js';
 import './os_people_page/os_sync_controls.m.js';
-import './os_search_page/os_search_page.m.js';
+import './os_search_page/os_search_page.js';
 import './os_settings_main/os_settings_main.m.js';
 import './os_settings_page/os_settings_page.m.js';
 import './os_settings_page/settings_idle_load.m.js';
 import './os_settings_menu/os_settings_menu.m.js';
 import './os_settings_ui/os_settings_ui.m.js';
 import './os_settings_icons_css.m.js';
-import './os_settings_search_box/os_search_result_row.m.js';
-import './os_settings_search_box/os_settings_search_box.m.js';
+import './os_settings_search_box/os_search_result_row.js';
+import './os_settings_search_box/os_settings_search_box.js';
 import './os_toolbar/os_toolbar.m.js';
 import './parental_controls_page/parental_controls_page.m.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_settings_page/BUILD.gn
index 76a2f591..8f90c77 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_settings_page/BUILD.gn
@@ -40,7 +40,7 @@
     "../os_people_page:os_people_page.m",
     "../os_printing_page:os_printing_page",
     "../os_privacy_page:os_privacy_page",
-    "../os_search_page:os_search_page.m",
+    "../os_search_page:os_search_page",
     "../personalization_page:personalization_page.m",
     "//ui/webui/resources/cr_elements/cr_button:cr_button.m",
     "//ui/webui/resources/js:assert.m",
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_search_box/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_settings_search_box/BUILD.gn
index 826d6d1..a2dd079 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_search_box/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_settings_search_box/BUILD.gn
@@ -3,20 +3,19 @@
 # found in the LICENSE file.
 
 import("//third_party/closure_compiler/compile_js.gni")
-import("//tools/polymer/polymer.gni")
+import("//tools/polymer/html_to_js.gni")
 import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
   closure_flags = os_settings_closure_flags
   is_polymer3 = true
   deps = [
-    ":os_search_result_row.m",
-    ":os_settings_search_box.m",
+    ":os_search_result_row",
+    ":os_settings_search_box",
   ]
 }
 
-js_library("os_search_result_row.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_search_result_row.m.js" ]
+js_library("os_search_result_row") {
   deps = [
     "..:os_icons.m",
     "..:os_route.m",
@@ -28,14 +27,12 @@
     "//ui/webui/resources/js:i18n_behavior.m",
     "//ui/webui/resources/js/cr/ui:focus_row_behavior.m",
   ]
-  extra_deps = [ ":os_search_result_row_module" ]
   externs_list = [ "$externs_path/metrics_private.js" ]
 }
 
-js_library("os_settings_search_box.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.m.js" ]
+js_library("os_settings_search_box") {
   deps = [
-    ":os_search_result_row.m",
+    ":os_search_result_row",
     "..:metrics_recorder.m",
     "..:os_route.m",
     "..:search_handler.m",
@@ -48,31 +45,12 @@
     "//ui/webui/resources/js:assert.m",
     "//ui/webui/resources/js:i18n_behavior.m",
   ]
-  extra_deps = [ ":os_settings_search_box_module" ]
   externs_list = [ "$externs_path/metrics_private.js" ]
 }
 
-group("polymer3_elements") {
-  public_deps = [
-    ":os_search_result_row_module",
-    ":os_settings_search_box_module",
+html_to_js("web_components") {
+  js_files = [
+    "os_search_result_row.js",
+    "os_settings_search_box.js",
   ]
 }
-
-polymer_modulizer("os_search_result_row") {
-  js_file = "os_search_result_row.js"
-  html_file = "os_search_result_row.html"
-  html_type = "dom-module"
-  auto_imports = os_settings_auto_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  migrated_imports = os_settings_migrated_imports
-}
-
-polymer_modulizer("os_settings_search_box") {
-  js_file = "os_settings_search_box.js"
-  html_file = "os_settings_search_box.html"
-  html_type = "dom-module"
-  auto_imports = os_settings_auto_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  migrated_imports = os_settings_migrated_imports
-}
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_search_result_row.html b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_search_result_row.html
index a246efd3..8679db9c 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_search_result_row.html
+++ b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_search_result_row.html
@@ -1,78 +1,60 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="settings-shared">
+  :host {
+    width: 100%;
+  }
 
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_row_behavior.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-announcer/iron-a11y-announcer.html">
-<link rel="import" href="../os_icons.html">
-<link rel="import" href="../os_route.html">
-<link rel="import" href="../search_handler.html">
-<link rel="import" href="../../router.html">
-<link rel="import" href="../../settings_shared_css.html">
+  :host([selected]) [focus-row-container],
+  :host(:not([selected])) [focus-row-container]:hover {
+    background-color: var(--cros-highlight-color);
+  }
 
-<dom-module id="os-search-result-row">
-  <template>
-    <style include="settings-shared">
-      :host {
-        width: 100%;
-      }
+  :host-context([dir=rtl]) #actionTypeIcon {
+    transform: scaleX(-1);  /* Invert X: flip on the Y axis (aka mirror). */
+  }
 
-      :host([selected]) [focus-row-container],
-      :host(:not([selected])) [focus-row-container]:hover {
-        background-color: var(--cros-highlight-color);
-      }
+  [focus-row-container] {
+    width: inherit;
+  }
 
-      :host-context([dir=rtl]) #actionTypeIcon {
-        transform: scaleX(-1);  /* Invert X: flip on the Y axis (aka mirror). */
-      }
+  #searchResultContainer {
+    align-items: center;
+    display: flex;
+    height: 40px;
+    justify-content: center;
+  }
 
-      [focus-row-container] {
-        width: inherit;
-      }
+  #resultText {
+    flex-grow: 1;
+    margin: var(--cr-toolbar-search-field-term-margin);
+  }
 
-      #searchResultContainer {
-        align-items: center;
-        display: flex;
-        height: 40px;
-        justify-content: center;
-      }
+  iron-icon {
+    margin: var(--cr-toolbar-icon-margin);
+    width: var(--cr-toolbar-icon-container-size);
+  }
 
-      #resultText {
-        flex-grow: 1;
-        margin: var(--cr-toolbar-search-field-term-margin);
-      }
-
-      iron-icon {
-        margin: var(--cr-toolbar-icon-margin);
-        width: var(--cr-toolbar-icon-container-size);
-      }
-
-      /* Bolding occurs in the JS. */
-      b {
-        color: var(--cros-text-color-primary);
-      }
-    </style>
-    <div focus-row-container>
-      <!-- The focus-row-control is aria-disabled because the aria-label of
-           this element (:host) will include instructions on how to navigate
-           to a selection.-->
-      <div focus-row-control
-          focus-type="rowWrapper"
-          id="searchResultContainer"
-          on-click="navigateToSearchResultRoute"
-          on-keypress="onKeyPress_"
-          aria-disabled="true"
-          selectable>
-        <iron-icon id="resultIcon" icon="[[getResultIcon_(searchResult)]]">
-        </iron-icon>
-        <div id="resultText" aria-hidden="true"
-            inner-h-t-m-l="[[getResultInnerHtml_(searchResult)]]">
-        </div>
-        <iron-icon id="actionTypeIcon" icon="cr:arrow-forward">
-        </iron-icon>
-      </div>
+  /* Bolding occurs in the JS. */
+  b {
+    color: var(--cros-text-color-primary);
+  }
+</style>
+<div focus-row-container>
+  <!-- The focus-row-control is aria-disabled because the aria-label of
+        this element (:host) will include instructions on how to navigate
+        to a selection.-->
+  <div focus-row-control
+      focus-type="rowWrapper"
+      id="searchResultContainer"
+      on-click="navigateToSearchResultRoute"
+      on-keypress="onKeyPress_"
+      aria-disabled="true"
+      selectable>
+    <iron-icon id="resultIcon" icon="[[getResultIcon_(searchResult)]]">
+    </iron-icon>
+    <div id="resultText" aria-hidden="true"
+        inner-h-t-m-l="[[getResultInnerHtml_(searchResult)]]">
     </div>
-  </template>
-  <script src="os_search_result_row.js"></script>
-</dom-module>
+    <iron-icon id="actionTypeIcon" icon="cr:arrow-forward">
+    </iron-icon>
+  </div>
+</div>
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 0ff857ba..79dd824 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
@@ -5,45 +5,58 @@
 /**
  * @fileoverview 'os-search-result-row' is the container for one search result.
  */
-cr.define('settings', function() {
-  /**
-   * This solution uses DP and has the complexity of O(M*N), where M and N are
-   * the lengths of |string1| and |string2| respectively.
-   *
-   * @param {string} string1 The first case sensitive string to be compared.
-   * @param {string} string2 The second case sensitive string to be compared.
-   * @return {!Array<string>} An array of the longest common substrings starting
-   *     from the earliest to latest match, all of which have the same length.
-   *     Returns empty array if there are none.
-   */
-  function longestCommonSubstrings(string1, string2) {
-    let maxLength = 0;
-    let string1StartingIndices = [];
-    const dp = Array(string1.length + 1)
-                   .fill([])
-                   .map(() => Array(string2.length + 1).fill(0));
+import '//resources/cr_elements/icons.m.js';
+import '../os_icons.m.js';
+import '../../settings_shared_css.js';
 
-    for (let i = string1.length - 1; i >= 0; i--) {
-      for (let j = string2.length - 1; j >= 0; j--) {
-        if (string1[i] !== string2[j]) {
-          continue;
-        }
-        dp[i][j] = dp[i + 1][j + 1] + 1;
-        if (maxLength === dp[i][j]) {
-          string1StartingIndices.unshift(i);
-        }
-        if (maxLength < dp[i][j]) {
-          maxLength = dp[i][j];
-          string1StartingIndices = [i];
-        }
+import {assert, assertNotReached} from '//resources/js/assert.m.js';
+import {FocusRowBehavior} from '//resources/js/cr/ui/focus_row_behavior.m.js';
+import {I18nBehavior} from '//resources/js/i18n_behavior.m.js';
+import {IronA11yAnnouncer} from '//resources/polymer/v3_0/iron-a11y-announcer/iron-a11y-announcer.js';
+import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {Route, RouteObserverBehavior, Router} from '../../router.js';
+import {routes} from '../os_route.m.js';
+import {getSearchHandler, setSearchHandlerForTesting} from '../search_handler.m.js';
+
+/**
+ * This solution uses DP and has the complexity of O(M*N), where M and N are
+ * the lengths of |string1| and |string2| respectively.
+ *
+ * @param {string} string1 The first case sensitive string to be compared.
+ * @param {string} string2 The second case sensitive string to be compared.
+ * @return {!Array<string>} An array of the longest common substrings starting
+ *     from the earliest to latest match, all of which have the same length.
+ *     Returns empty array if there are none.
+ */
+function longestCommonSubstrings(string1, string2) {
+  let maxLength = 0;
+  let string1StartingIndices = [];
+  const dp = Array(string1.length + 1)
+                 .fill([])
+                 .map(() => Array(string2.length + 1).fill(0));
+
+  for (let i = string1.length - 1; i >= 0; i--) {
+    for (let j = string2.length - 1; j >= 0; j--) {
+      if (string1[i] !== string2[j]) {
+        continue;
+      }
+      dp[i][j] = dp[i + 1][j + 1] + 1;
+      if (maxLength === dp[i][j]) {
+        string1StartingIndices.unshift(i);
+      }
+      if (maxLength < dp[i][j]) {
+        maxLength = dp[i][j];
+        string1StartingIndices = [i];
       }
     }
-
-    return string1StartingIndices.map(idx => {
-      return string1.substr(idx, maxLength);
-    });
   }
 
+  return string1StartingIndices.map(idx => {
+    return string1.substr(idx, maxLength);
+  });
+}
+
   /**
    * Used to locate matches such that the query text omits a hyphen when the
    * matching result text contains a hyphen.
@@ -123,9 +136,10 @@
   }
 
   Polymer({
+    _template: html`{__html_template__}`,
     is: 'os-search-result-row',
 
-    behaviors: [I18nBehavior, cr.ui.FocusRowBehavior],
+    behaviors: [I18nBehavior, FocusRowBehavior],
 
     properties: {
       /** Whether the search result row is selected. */
@@ -161,7 +175,7 @@
     /** @override */
     attached() {
       // Initialize the announcer once.
-      Polymer.IronA11yAnnouncer.requestAvailability();
+      IronA11yAnnouncer.requestAvailability();
     },
 
     /** @private */
@@ -585,14 +599,13 @@
       assert(pathAndOptParams.length <= 2, 'Path and params format error.');
 
       const route = assert(
-          settings.Router.getInstance().getRouteForPath(
-              '/' + pathAndOptParams[0]),
+          Router.getInstance().getRouteForPath('/' + pathAndOptParams[0]),
           'Supplied path does not map to an existing route.');
 
       const paramsString = `search=${encodeURIComponent(this.searchQuery)}` +
           (pathAndOptParams.length === 2 ? `&${pathAndOptParams[1]}` : ``);
       const params = new URLSearchParams(paramsString);
-      settings.Router.getInstance().navigateTo(route, params);
+      Router.getInstance().navigateTo(route, params);
       this.fire('navigated-to-result-route');
     },
 
@@ -690,7 +703,3 @@
       }
     },
   });
-
-  // #cr_define_end
-  return {};
-});
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.html b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.html
index 73222cf..897682f 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.html
+++ b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.html
@@ -1,174 +1,153 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="settings-shared">
+  :host {
+    --cr-toolbar-search-field-background:
+        var(--cros-toolbar-search-bg-color);
+    --cr-toolbar-focused-min-height: 40px;
+    --cr-toolbar-icon-container-size: 32px;
+    --cr-toolbar-icon-margin: 8px 16px;
+    --cr-toolbar-search-field-icon-opacity: 1;
+    --cr-toolbar-search-field-narrow-mode-prompt-opacity: 1;
+    --cr-toolbar-search-field-prompt-opacity: 1;
+    --cr-toolbar-search-icon-margin-inline-start: 16px;
+    --cr-toolbar-query-exists-min-height:
+        var(--cr-toolbar-focused-min-height);
+    --separator-height: 8px;
+    -webkit-tap-highlight-color: transparent;
+    display: flex;
+    flex-basis: var(--cr-toolbar-field-width);
+    transition: width 150ms cubic-bezier(0.4, 0, 0.2, 1);
+    width: var(--cr-toolbar-field-width);
+  }
 
-<link rel="import" href="chrome://resources/cr_elements/cr_toolbar/cr_toolbar_search_field.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_row.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-announcer/iron-a11y-announcer.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-dropdown/iron-dropdown.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
-<link rel="import" href="os_search_result_row.html">
-<link rel="import" href="../metrics_recorder.html">
-<link rel="import" href="../search_handler.html">
-<link rel="import" href="../../settings_shared_css.html">
-<link rel="import" href="../../router.html">
-<link rel="import" href="../os_route.html">
+  @media (prefers-color-scheme: dark) {
+    :host {
+      --cr-toolbar-search-field-narrow-mode-prompt-opacity: 1;
+    }
+  }
 
-<dom-module id="os-settings-search-box">
-  <template>
-    <style include="settings-shared">
-      :host {
-        --cr-toolbar-search-field-background:
-            var(--cros-toolbar-search-bg-color);
-        --cr-toolbar-focused-min-height: 40px;
-        --cr-toolbar-icon-container-size: 32px;
-        --cr-toolbar-icon-margin: 8px 16px;
-        --cr-toolbar-search-field-icon-opacity: 1;
-        --cr-toolbar-search-field-narrow-mode-prompt-opacity: 1;
-        --cr-toolbar-search-field-prompt-opacity: 1;
-        --cr-toolbar-search-icon-margin-inline-start: 16px;
-        --cr-toolbar-query-exists-min-height:
-            var(--cr-toolbar-focused-min-height);
-        --separator-height: 8px;
-        -webkit-tap-highlight-color: transparent;
-        display: flex;
-        flex-basis: var(--cr-toolbar-field-width);
-        transition: width 150ms cubic-bezier(0.4, 0, 0.2, 1);
-        width: var(--cr-toolbar-field-width);
-      }
+  /* Only search icon is visible in this mode */
+  :host([narrow]:not([showing-search])) {
+    /* Row flex keeps magnifying glass on the rightmost edge. */
+    flex-direction: row;
+    justify-content: flex-end;
+  }
 
-      @media (prefers-color-scheme: dark) {
-        :host {
-          --cr-toolbar-search-field-narrow-mode-prompt-opacity: 1;
-        }
-      }
+  :host([narrow][showing-search]) {
+    justify-content: center;
+  }
 
-      /* Only search icon is visible in this mode */
-      :host([narrow]:not([showing-search])) {
-        /* Row flex keeps magnifying glass on the rightmost edge. */
-        flex-direction: row;
-        justify-content: flex-end;
-      }
+  cr-toolbar-search-field {
+    --cr-toolbar-search-field-term-margin: 0;
+    --cr-toolbar-search-field-border-radius: 20px;
+    --cr-toolbar-search-field-paper-spinner-margin: 0 12px;
+    --cr-toolbar-search-field-input-icon-color:
+        var(--cros-icon-color-primary);
+    --cr-toolbar-search-field-input-text-color:
+        var(--cros-text-color-primary);
+    --cr-toolbar-search-field-prompt-color:
+        var(--cros-text-color-secondary);
+    --cr-toolbar-icon-button-focus-outline-color:
+        var(--cros-focus-ring-color);
+    font-size: 13px;
+  }
 
-      :host([narrow][showing-search]) {
-        justify-content: center;
-      }
+  :host([narrow]:not([showing-search])) cr-toolbar-search-field {
+    padding-inline-end: 10px;
+  }
 
-      cr-toolbar-search-field {
-        --cr-toolbar-search-field-term-margin: 0;
-        --cr-toolbar-search-field-border-radius: 20px;
-        --cr-toolbar-search-field-paper-spinner-margin: 0 12px;
-        --cr-toolbar-search-field-input-icon-color:
-            var(--cros-icon-color-primary);
-        --cr-toolbar-search-field-input-text-color:
-            var(--cros-text-color-primary);
-        --cr-toolbar-search-field-prompt-color:
-            var(--cros-text-color-secondary);
-        --cr-toolbar-icon-button-focus-outline-color:
-            var(--cros-focus-ring-color);
-        font-size: 13px;
-      }
+  :host(:focus-within[showing-search]) cr-toolbar-search-field {
+    --cr-toolbar-search-field-background: var(--cros-bg-color-elevation-3);
+    box-shadow: var(--cr-elevation-1);
+    min-height: var(--cr-toolbar-focused-min-height);
+  }
 
-      :host([narrow]:not([showing-search])) cr-toolbar-search-field {
-        padding-inline-end: 10px;
-      }
+  :host([has-search-query]) cr-toolbar-search-field {
+    min-height: var(--cr-toolbar-query-exists-min-height);
+  }
 
-      :host(:focus-within[showing-search]) cr-toolbar-search-field {
-        --cr-toolbar-search-field-background: var(--cros-bg-color-elevation-3);
-        box-shadow: var(--cr-elevation-1);
-        min-height: var(--cr-toolbar-focused-min-height);
-      }
+  :host(:not(:focus-within)) cr-toolbar-search-field {
+    --cr-toolbar-search-field-cursor: pointer;
+  }
 
-      :host([has-search-query]) cr-toolbar-search-field {
-        min-height: var(--cr-toolbar-query-exists-min-height);
-      }
+  :host(:focus-within[should-show-dropdown_]) cr-toolbar-search-field {
+    --cr-toolbar-search-field-border-radius: 20px 20px 0 0;
+    box-shadow: var(--cr-elevation-3);
+    height: 56px;
+    margin-top: var(--separator-height);
+    padding-bottom: var(--separator-height);
+  }
 
-      :host(:not(:focus-within)) cr-toolbar-search-field {
-        --cr-toolbar-search-field-cursor: pointer;
-      }
+  iron-dropdown {
+    margin-top: 72px;
+  }
 
-      :host(:focus-within[should-show-dropdown_]) cr-toolbar-search-field {
-        --cr-toolbar-search-field-border-radius: 20px 20px 0 0;
-        box-shadow: var(--cr-elevation-3);
-        height: 56px;
-        margin-top: var(--separator-height);
-        padding-bottom: var(--separator-height);
-      }
+  iron-dropdown [slot='dropdown-content'] {
+    background-color: var(--cros-bg-color-elevation-3);
+    border-radius: 0 0 20px 20px;
+    box-shadow: var(--cr-elevation-3);
+    display: table;
+    padding-bottom: 16px;
+    width: var(--cr-toolbar-field-width);
+  }
 
-      iron-dropdown {
-        margin-top: 72px;
-      }
+  iron-list {
+    max-height: 50vh;
+  }
 
-      iron-dropdown [slot='dropdown-content'] {
-        background-color: var(--cros-bg-color-elevation-3);
-        border-radius: 0 0 20px 20px;
-        box-shadow: var(--cr-elevation-3);
-        display: table;
-        padding-bottom: 16px;
-        width: var(--cr-toolbar-field-width);
-      }
+  #noSearchResultsContainer {
+    height: 24px;
+    margin-inline-start: 64px;
+    margin-top: var(--separator-height);
+  }
 
-      iron-list {
-        max-height: 50vh;
-      }
-
-      #noSearchResultsContainer {
-        height: 24px;
-        margin-inline-start: 64px;
-        margin-top: var(--separator-height);
-      }
-
-      /* The separator covers the top box shadow of the dropdown so that
-       * var(--cr-elevation-3) can be used instead of custom values. */
-      .separator {
-        background-color: var(--cros-bg-color-elevation-3);
-        border-bottom: none;
-        border-inline-end: none;
-        border-inline-start: none;
-        border-top: 1px solid var(--cros-separator-color);
-        height: var(--separator-height);
-        margin-inline-end: 0;
-        margin-inline-start: 0;
-        margin-top: -10px;
-      }
-    </style>
-    <cr-toolbar-search-field id="search" narrow="[[narrow]]"
-        on-search-icon-clicked="onSearchIconClicked_"
-        label="$i18n{searchPrompt}" clear-label="$i18n{clearSearch}"
-        showing-search="{{showingSearch}}" spinner-active="[[spinnerActive]]">
-    </cr-toolbar-search-field>
-    <iron-dropdown id="searchResults" opened="[[shouldShowDropdown_]]"
-        allow-outside-scroll no-cancel-on-outside-click>
-      <!--  As part of iron-dropdown's behavior, the slot 'dropdown-content' is
-            hidden until iron-dropdown's opened attribute is set true, or when
-            iron-dropdown's open() is called on the JS side. -->
-      <div slot="dropdown-content">
-        <div class="separator"></div>
-        <iron-list id="searchResultList" selection-enabled risk-selection
-            items="[[searchResults_]]" selected-item="{{selectedItem_}}"
-            on-selected-item-changed="onSelectedItemChanged_">
-          <template>
-            <os-search-result-row actionable search-result="[[item]]"
-                search-query="[[getCurrentQuery_(searchResults_)]]"
-                selected="[[isItemSelected_(item, selectedItem_)]]"
-                tabindex$="[[getRowTabIndex_(item, selectedItem_,
-                    shouldShowDropdown_)]]"
-                iron-list-tab-index$="[[getRowTabIndex_(item, selectedItem_,
-                    shouldShowDropdown_)]]"
-                on-navigated-to-result-route="onNavigatedToResultRowRoute_"
-                last-focused="{{lastFocused_}}"
-                list-blurred="{{listBlurred_}}"
-                list-length="[[getListLength_(searchResults_)]]"
-                focus-row-index="[[index]]"
-                first$="[[!index]]">
-            </os-search-result-row>
-          </template>
-        </iron-list>
-        <div id="noSearchResultsContainer" hidden="[[searchResultsExist_]]">
-          $i18n{searchNoResults}
-        </div>
-      </div>
-    </iron-dropdown>
-  </template>
-  <script src="os_settings_search_box.js"></script>
-</dom-module>
+  /* The separator covers the top box shadow of the dropdown so that
+    * var(--cr-elevation-3) can be used instead of custom values. */
+  .separator {
+    background-color: var(--cros-bg-color-elevation-3);
+    border-bottom: none;
+    border-inline-end: none;
+    border-inline-start: none;
+    border-top: 1px solid var(--cros-separator-color);
+    height: var(--separator-height);
+    margin-inline-end: 0;
+    margin-inline-start: 0;
+    margin-top: -10px;
+  }
+</style>
+<cr-toolbar-search-field id="search" narrow="[[narrow]]"
+    on-search-icon-clicked="onSearchIconClicked_"
+    label="$i18n{searchPrompt}" clear-label="$i18n{clearSearch}"
+    showing-search="{{showingSearch}}" spinner-active="[[spinnerActive]]">
+</cr-toolbar-search-field>
+<iron-dropdown id="searchResults" opened="[[shouldShowDropdown_]]"
+    allow-outside-scroll no-cancel-on-outside-click>
+  <!--  As part of iron-dropdown's behavior, the slot 'dropdown-content' is
+        hidden until iron-dropdown's opened attribute is set true, or when
+        iron-dropdown's open() is called on the JS side. -->
+  <div slot="dropdown-content">
+    <div class="separator"></div>
+    <iron-list id="searchResultList" selection-enabled risk-selection
+        items="[[searchResults_]]" selected-item="{{selectedItem_}}"
+        on-selected-item-changed="onSelectedItemChanged_">
+      <template>
+        <os-search-result-row actionable search-result="[[item]]"
+            search-query="[[getCurrentQuery_(searchResults_)]]"
+            selected="[[isItemSelected_(item, selectedItem_)]]"
+            tabindex$="[[getRowTabIndex_(item, selectedItem_,
+                shouldShowDropdown_)]]"
+            iron-list-tab-index$="[[getRowTabIndex_(item, selectedItem_,
+                shouldShowDropdown_)]]"
+            on-navigated-to-result-route="onNavigatedToResultRowRoute_"
+            last-focused="{{lastFocused_}}"
+            list-blurred="{{listBlurred_}}"
+            list-length="[[getListLength_(searchResults_)]]"
+            focus-row-index="[[index]]"
+            first$="[[!index]]">
+        </os-search-result-row>
+      </template>
+    </iron-list>
+    <div id="noSearchResultsContainer" hidden="[[searchResultsExist_]]">
+      $i18n{searchNoResults}
+    </div>
+  </div>
+</iron-dropdown>
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.js b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.js
index 7ad4b27..58b150b0 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.js
@@ -34,7 +34,24 @@
  * @fileoverview 'os-settings-search-box' is the container for the search input
  * and settings search results.
  */
+import {afterNextRender, Polymer, html, flush, Templatizer, TemplateInstanceBase} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {CrToolbarSearchFieldElement} from '//resources/cr_elements/cr_toolbar/cr_toolbar_search_field.js';
+import {assert, assertNotReached} from '//resources/js/assert.m.js';
+import '//resources/js/cr/ui/focus_row.m.js';
+import {I18nBehavior} from '//resources/js/i18n_behavior.m.js';
+import {IronA11yAnnouncer} from '//resources/polymer/v3_0/iron-a11y-announcer/iron-a11y-announcer.js';
+import '//resources/polymer/v3_0/iron-dropdown/iron-dropdown.js';
+import '//resources/polymer/v3_0/iron-list/iron-list.js';
+import './os_search_result_row.js';
+import {recordSettingChange, recordSearch, setUserActionRecorderForTesting, recordPageFocus, recordPageBlur, recordClick, recordNavigation} from '../metrics_recorder.m.js';
+import {getSearchHandler, setSearchHandlerForTesting} from '../search_handler.m.js';
+import '../../settings_shared_css.js';
+import {Router, Route, RouteObserverBehavior} from '../../router.js';
+import {routes} from '../os_route.m.js';
+
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'os-settings-search-box',
 
   behaviors: [I18nBehavior],
@@ -150,7 +167,7 @@
   attached() {
     const toolbarSearchField = this.$.search;
     const searchInput = toolbarSearchField.getSearchInput();
-    if (settings.Router.getInstance().currentRoute === settings.routes.BASIC) {
+    if (Router.getInstance().currentRoute === routes.BASIC) {
       // The search field should only focus initially if settings is opened
       // directly to the base page, with no path. Opening to a section or a
       // subpage should not focus the search field.
@@ -164,14 +181,14 @@
     // If the search was initiated by directly entering a search URL, need to
     // sync the URL parameter to the textbox.
     const urlSearchQuery =
-        settings.Router.getInstance().getQueryParameters().get('search') || '';
+        Router.getInstance().getQueryParameters().get('search') || '';
 
     // Setting the search box value without triggering a 'search-changed'
     // event, to prevent an unnecessary duplicate entry in |window.history|.
     toolbarSearchField.setValue(urlSearchQuery, /*noEvent=*/true);
 
     // Initialize the announcer once.
-    Polymer.IronA11yAnnouncer.requestAvailability();
+    IronA11yAnnouncer.requestAvailability();
 
     // Log number of search requests made each time settings window closes.
     window.addEventListener('beforeunload', () => {
@@ -187,7 +204,7 @@
              * @type {!chromeos.settings.mojom.SearchResultsObserverInterface}
              */
             (this));
-    settings.getSearchHandler().observe(
+    getSearchHandler().observe(
         this.searchResultObserverReceiver_.$.bindNewPipeAndPassRemote());
   },
 
@@ -253,7 +270,7 @@
     // an array of 16 bit character codes that match std::u16string.
     const queryMojoString16 = {data: Array.from(query, c => c.charCodeAt())};
     const timeOfSearchRequest = Date.now();
-    settings.getSearchHandler()
+    getSearchHandler()
         .search(
             queryMojoString16, MAX_NUM_SEARCH_RESULTS,
             chromeos.settings.mojom.ParentResultBehavior.kAllowParentResults)
@@ -302,7 +319,7 @@
     this.spinnerActive = false;
     this.lastFocused_ = null;
     this.searchResults_ = results;
-    settings.recordSearch();
+    recordSearch();
   },
 
   /** @private */
@@ -371,7 +388,7 @@
     if (!this.shouldShowDropdown_) {
       // Select all search input text once the initial state is set.
       const searchInput = this.$.search.getSearchInput();
-      Polymer.RenderStatus.afterNextRender(this, () => searchInput.select());
+      afterNextRender(this, () => searchInput.select());
     }
   },
 
@@ -480,7 +497,7 @@
   /**
    * Keydown handler to specify how enter-key, arrow-up key, and arrow-down-key
    * interacts with search results in the dropdown. Note that 'Enter' on keyDown
-   * when a row is focused is blocked by cr.ui.FocusRowBehavior behavior.
+   * when a row is focused is blocked by FocusRowBehavior behavior.
    * @param {!KeyboardEvent} e
    * @private
    */
diff --git a/chrome/browser/resources/settings/chromeos/os_toolbar/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_toolbar/BUILD.gn
index 3969ca6..33eab43 100644
--- a/chrome/browser/resources/settings/chromeos/os_toolbar/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_toolbar/BUILD.gn
@@ -15,7 +15,7 @@
 js_library("os_toolbar.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_toolbar/os_toolbar.m.js" ]
   deps = [
-    "../os_settings_search_box:os_settings_search_box.m",
+    "../os_settings_search_box:os_settings_search_box",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_elements/cr_icon_button:cr_icon_button.m",
     "//ui/webui/resources/cr_elements/cr_toolbar:cr_toolbar_search_field",
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetBottomSheetContent.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetBottomSheetContent.java
index c294c35..1ea34245 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetBottomSheetContent.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetBottomSheetContent.java
@@ -66,9 +66,9 @@
     private final ShareSheetCoordinator mShareSheetCoordinator;
     private ViewGroup mContentView;
     private ShareParams mParams;
-    private String mUrl;
     private ScrollView mContentScrollableView;
     private @LinkGeneration int mLinkGenerationState;
+    private @LinkToggleState int mLinkToggleState;
     private Toast mToast;
 
     /**
@@ -95,6 +95,8 @@
             mLinkGenerationState = mParams.getLinkToTextSuccessful() ? LinkGeneration.LINK
                                                                      : LinkGeneration.FAILURE;
         }
+        // TODO(crbug.com/1227203): change default enabled/disabled state
+        mLinkToggleState = LinkToggleState.LINK;
         createContentView();
     }
 
@@ -234,9 +236,8 @@
             fetchFavicon(mParams.getUrl());
         }
 
-        if (ChromeFeatureList.isEnabled(ChromeFeatureList.PREEMPTIVE_LINK_TO_TEXT_GENERATION)
-                && contentTypes.contains(ContentType.HIGHLIGHTED_TEXT)) {
-            setLinkImageViewForPreview();
+        if (shouldShowLinkToggle(contentTypes)) {
+            setLinkToggleForPreview(contentTypes.contains(ContentType.HIGHLIGHTED_TEXT));
         }
 
         if ((contentTypes.contains(ContentType.TEXT)
@@ -310,76 +311,95 @@
         centerIcon(imageView);
     }
 
-    public void updateLinkGenerationState() {
-        switch (mLinkGenerationState) {
-            case LinkGeneration.FAILURE:
-                return;
-            case LinkGeneration.LINK:
-                mLinkGenerationState = LinkGeneration.TEXT;
-                break;
-            case LinkGeneration.TEXT:
-                mLinkGenerationState = LinkGeneration.LINK;
-                break;
+    private void updateLinkToggleState() {
+        int toastMessage;
+        if (mLinkToggleState == LinkToggleState.NO_LINK) {
+            mLinkToggleState = LinkToggleState.LINK;
+            toastMessage = R.string.link_to_text_success_link_toast_message;
+        } else {
+            mLinkToggleState = LinkToggleState.NO_LINK;
+            toastMessage = R.string.link_to_text_success_text_toast_message;
         }
+        showToast(toastMessage);
+        mShareSheetCoordinator.updateShareSheetForLinkToggle(mLinkToggleState);
     }
 
-    private void setLinkImageViewForPreview() {
-        int drawable = 0;
-        int contentDescription = 0;
-        int skillColor = 0;
-
-        switch (mLinkGenerationState) {
-            case LinkGeneration.FAILURE:
-                drawable = R.drawable.link_off;
-                contentDescription = R.string.link_to_text_failure_toast_message_v2;
-                skillColor = R.color.default_icon_color;
-                break;
-            case LinkGeneration.LINK:
-                drawable = R.drawable.link;
-                contentDescription = R.string.link_to_text_success_link_toast_message;
-                skillColor = R.color.default_icon_color_blue;
-                break;
-            case LinkGeneration.TEXT:
-                drawable = R.drawable.link_off;
-                contentDescription = R.string.link_to_text_success_text_toast_message;
-                skillColor = R.color.default_icon_color;
-                break;
+    private void updateLinkGenerationState() {
+        int toastMessage = 0;
+        String userAction = "";
+        if (mLinkGenerationState == LinkGeneration.LINK) {
+            mLinkGenerationState = LinkGeneration.TEXT;
+            mLinkToggleState = LinkToggleState.NO_LINK;
+            toastMessage = R.string.link_to_text_success_text_toast_message;
+            userAction = "SharingHubAndroid.LinkGeneration.Text";
+        } else if (mLinkGenerationState == LinkGeneration.TEXT) {
+            mLinkGenerationState = LinkGeneration.LINK;
+            mLinkToggleState = LinkToggleState.LINK;
+            toastMessage = R.string.link_to_text_success_link_toast_message;
+            userAction = "SharingHubAndroid.LinkGeneration.Link";
+        } else if (mLinkGenerationState == LinkGeneration.FAILURE) {
+            mLinkToggleState = LinkToggleState.NO_LINK;
+            toastMessage = R.string.link_to_text_failure_toast_message_v2;
+            userAction = "SharingHubAndroid.LinkGeneration.Failure";
         }
 
-        ImageView linkImageView = this.getContentView().findViewById(R.id.image_preview_link);
+        showToast(toastMessage);
+        RecordUserAction.record(userAction);
+        mShareSheetCoordinator.updateShareSheetForLinkToggle(mLinkToggleState);
+    }
+
+    private boolean shouldShowLinkToggle(Set<Integer> contentTypes) {
+        if (ChromeFeatureList.isEnabled(ChromeFeatureList.PREEMPTIVE_LINK_TO_TEXT_GENERATION)
+                && contentTypes.contains(ContentType.HIGHLIGHTED_TEXT)) {
+            return true;
+        } else if (ChromeFeatureList.isEnabled(ChromeFeatureList.SHARING_HUB_LINK_TOGGLE)) {
+            // TODO(crbug.com/1227203): don't show toggle for link shares
+            return true;
+        }
+        return false;
+    }
+
+    private void setLinkToggleForPreview(boolean isHighlightedText) {
+        int drawable;
+        int contentDescription;
+        int skillColor;
+
+        if (mLinkToggleState == LinkToggleState.LINK) {
+            drawable = R.drawable.link;
+            skillColor = R.color.default_icon_color_blue;
+            // TODO(sophey): update contentDescription with correct text
+            contentDescription = R.string.link_to_text_success_link_toast_message;
+        } else {
+            drawable = R.drawable.link_off;
+            skillColor = R.color.default_icon_color;
+            contentDescription = R.string.link_to_text_success_text_toast_message;
+        }
+
+        if (isHighlightedText) {
+            if (mLinkGenerationState == LinkGeneration.LINK) {
+                contentDescription = R.string.link_to_text_success_link_toast_message;
+            } else if (mLinkGenerationState == LinkGeneration.TEXT) {
+                contentDescription = R.string.link_to_text_success_text_toast_message;
+            } else if (mLinkGenerationState == LinkGeneration.FAILURE) {
+                contentDescription = R.string.link_to_text_failure_toast_message_v2;
+            }
+        }
+
+        ImageView linkImageView = this.getContentView().findViewById(R.id.link_toggle_view);
         linkImageView.setColorFilter(ContextCompat.getColor(mContext, skillColor));
         linkImageView.setVisibility(View.VISIBLE);
         linkImageView.setImageDrawable(AppCompatResources.getDrawable(mContext, drawable));
+        // This is necessary in order to prevent voice over announcing the content description
+        // change. See https://crbug.com/1192666.
+        linkImageView.setContentDescription(null);
         linkImageView.setContentDescription(mContext.getResources().getString(contentDescription));
         centerIcon(linkImageView);
 
         linkImageView.setOnClickListener(v -> {
-            updateLinkGenerationState();
-            switch (mLinkGenerationState) {
-                case LinkGeneration.FAILURE:
-                    showToast(R.string.link_to_text_failure_toast_message_v2);
-                    linkImageView.setContentDescription(mContext.getResources().getString(
-                            R.string.link_to_text_failure_toast_message_v2));
-                    RecordUserAction.record("SharingHubAndroid.LinkGeneration.Failure");
-                    break;
-                case LinkGeneration.LINK:
-                    showToast(R.string.link_to_text_success_link_toast_message);
-                    linkImageView.setImageDrawable(
-                            AppCompatResources.getDrawable(mContext, R.drawable.link));
-                    linkImageView.setContentDescription(mContext.getResources().getString(
-                            R.string.link_to_text_success_link_toast_message));
-                    mShareSheetCoordinator.updateShareSheetForLinkToggle(LinkToggleState.LINK);
-                    RecordUserAction.record("SharingHubAndroid.LinkGeneration.Link");
-                    break;
-                case LinkGeneration.TEXT:
-                    showToast(R.string.link_to_text_success_text_toast_message);
-                    linkImageView.setImageDrawable(
-                            AppCompatResources.getDrawable(mContext, R.drawable.link_off));
-                    linkImageView.setContentDescription(mContext.getResources().getString(
-                            R.string.link_to_text_success_text_toast_message));
-                    mShareSheetCoordinator.updateShareSheetForLinkToggle(LinkToggleState.NO_LINK);
-                    RecordUserAction.record("SharingHubAndroid.LinkGeneration.Text");
-                    break;
+            if (isHighlightedText) {
+                updateLinkGenerationState();
+            } else {
+                updateLinkToggleState();
             }
         });
     }
@@ -407,7 +427,6 @@
      **/
     private void fetchFavicon(String url) {
         if (!url.isEmpty()) {
-            mUrl = url;
             mIconBridge.getLargeIconForUrl(new GURL(url),
                     mContext.getResources().getDimensionPixelSize(R.dimen.default_favicon_min_size),
                     this::onFaviconAvailable);
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetCoordinator.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetCoordinator.java
index 9224782..b4e3628 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetCoordinator.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetCoordinator.java
@@ -5,7 +5,6 @@
 package org.chromium.chrome.browser.share.share_sheet;
 
 import android.app.Activity;
-import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
@@ -167,8 +166,16 @@
         mShareParams = params;
         mChromeShareExtras = chromeShareExtras;
         mActivity = params.getWindow().getActivity().get();
+        if (mShareSheetLinkToggleCoordinator != null) {
+            mShareSheetLinkToggleCoordinator.setShareParamsAndExtras(params, chromeShareExtras);
+            // TODO(crbug.com/1227203): set default enabled/disabled status depending on share type
+            mShareParams = mShareSheetLinkToggleCoordinator.getShareParams(LinkToggleState.LINK);
+        }
         if (mActivity == null) return;
 
+        // Current tab information is necessary to create the first party options.
+        if (!mExcludeFirstParty && (mTabProvider == null || mTabProvider.get() == null)) return;
+
         if (mWindowAndroid == null) {
             mWindowAndroid = params.getWindow();
             if (mWindowAndroid != null) {
@@ -380,7 +387,6 @@
 
         PackageManager pm = ContextUtils.getApplicationContext().getPackageManager();
 
-        Intent shareIntent = ShareHelper.getShareLinkAppCompatibilityIntent();
         List<ResolveInfo> availableResolveInfos =
                 pm.queryIntentActivities(ShareHelper.getShareLinkAppCompatibilityIntent(), 0);
         availableResolveInfos.addAll(pm.queryIntentActivities(
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetLinkToggleCoordinator.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetLinkToggleCoordinator.java
index 9c61d56..27536af 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetLinkToggleCoordinator.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetLinkToggleCoordinator.java
@@ -25,14 +25,15 @@
         int MAX = 2;
     }
 
-    private final ShareParams mShareParams;
-    private final ChromeShareExtras mChromeShareExtras;
-    private final GURL mUrl;
     private final long mShareStartTime;
     private final LinkToTextCoordinator mLinkToTextCoordinator;
     private final ChromeOptionShareCallback mChromeOptionShareCallback;
     private final boolean mShouldEnableLinkToTextToggle;
-    private final boolean mShouldEnableGenericToggle;
+
+    private ShareParams mShareParams;
+    private ChromeShareExtras mChromeShareExtras;
+    private GURL mUrl;
+    private boolean mShouldEnableGenericToggle;
 
     /**
      * Constructs a new ShareSheetLinkToggleCoordinator.
@@ -47,16 +48,11 @@
     ShareSheetLinkToggleCoordinator(ShareParams shareParams, ChromeShareExtras chromeShareExtras,
             long shareStartTime, LinkToTextCoordinator linkToTextCoordinator,
             ChromeOptionShareCallback chromeOptionShareCallback) {
-        mShareParams = shareParams;
-        mChromeShareExtras = chromeShareExtras;
-        mUrl = chromeShareExtras.getContentUrl();
+        setShareParamsAndExtras(shareParams, chromeShareExtras);
         mShareStartTime = shareStartTime;
         mLinkToTextCoordinator = linkToTextCoordinator;
         mChromeOptionShareCallback = chromeOptionShareCallback;
 
-        mShouldEnableGenericToggle =
-                ChromeFeatureList.isEnabled(ChromeFeatureList.SHARING_HUB_LINK_TOGGLE)
-                && (TextUtils.isEmpty(shareParams.getUrl()) && mUrl != null && !mUrl.isEmpty());
         mShouldEnableLinkToTextToggle =
                 (ChromeFeatureList.isEnabled(ChromeFeatureList.PREEMPTIVE_LINK_TO_TEXT_GENERATION)
                         || ChromeFeatureList.isEnabled(ChromeFeatureList.SHARING_HUB_LINK_TOGGLE))
@@ -65,6 +61,18 @@
     }
 
     /**
+     * Sets the {@link ShareParams} and {@link ChromeShareExtras}.
+     */
+    void setShareParamsAndExtras(ShareParams shareParams, ChromeShareExtras chromeShareExtras) {
+        mShareParams = shareParams;
+        mChromeShareExtras = chromeShareExtras;
+        mUrl = chromeShareExtras.getContentUrl();
+        mShouldEnableGenericToggle =
+                ChromeFeatureList.isEnabled(ChromeFeatureList.SHARING_HUB_LINK_TOGGLE)
+                && TextUtils.isEmpty(shareParams.getUrl()) && mUrl != null && !mUrl.isEmpty();
+    }
+
+    /**
      * Returns the {@link ShareParams} associated with the {@link LinkToggleState}.
      */
     ShareParams getShareParams(@LinkToggleState int linkToggleState) {
diff --git a/chrome/browser/sharing/features.cc b/chrome/browser/sharing/features.cc
index 3017a8e..7a4faa412 100644
--- a/chrome/browser/sharing/features.cc
+++ b/chrome/browser/sharing/features.cc
@@ -32,3 +32,12 @@
       base::FEATURE_DISABLED_BY_DEFAULT
 #endif  // defined(OS_ANDROID)
 };
+
+namespace sharing {
+
+const base::Feature kShareMenu{
+    "ShareMenu",
+    base::FEATURE_DISABLED_BY_DEFAULT,
+};
+
+}  // namespace sharing
diff --git a/chrome/browser/sharing/features.h b/chrome/browser/sharing/features.h
index 08e6a82e..eef28642 100644
--- a/chrome/browser/sharing/features.h
+++ b/chrome/browser/sharing/features.h
@@ -34,4 +34,10 @@
 // Feature flag for prefer sending sharing message using VAPID.
 extern const base::Feature kSharingPreferVapid;
 
+namespace sharing {
+
+extern const base::Feature kShareMenu;
+
+}  // namespace sharing
+
 #endif  // CHROME_BROWSER_SHARING_FEATURES_H_
diff --git a/chrome/browser/sharing/share_submenu_model.cc b/chrome/browser/sharing/share_submenu_model.cc
new file mode 100644
index 0000000..2cc83c9
--- /dev/null
+++ b/chrome/browser/sharing/share_submenu_model.cc
@@ -0,0 +1,161 @@
+// Copyright 2021 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/sharing/share_submenu_model.h"
+
+#include "base/metrics/user_metrics.h"
+#include "build/build_config.h"
+#include "chrome/app/chrome_command_ids.h"
+#include "chrome/app/vector_icons/vector_icons.h"
+#include "chrome/browser/send_tab_to_self/send_tab_to_self_desktop_util.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/qrcode_generator/qrcode_generator_bubble_controller.h"
+#include "chrome/browser/ui/send_tab_to_self/send_tab_to_self_sub_menu_model.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/grit/chromium_strings.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/send_tab_to_self/metrics_util.h"
+#include "components/strings/grit/components_strings.h"
+#include "components/url_formatter/url_formatter.h"
+#include "ui/base/clipboard/scoped_clipboard_writer.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/image_model.h"
+
+namespace sharing {
+
+namespace {
+
+bool ShouldUseSendTabToSelfIcons() {
+#if defined(OS_MAC)
+  return false;
+#else
+  return true;
+#endif
+}
+
+}  // namespace
+
+ShareSubmenuModel::ShareSubmenuModel(Browser* browser,
+                                     Context context,
+                                     GURL url)
+    : ui::SimpleMenuModel(this),
+      browser_(browser),
+      context_(context),
+      url_(url) {
+  AddGenerateQRCodeItem();
+  AddSendTabToSelfItem();
+}
+
+ShareSubmenuModel::~ShareSubmenuModel() = default;
+
+void ShareSubmenuModel::ExecuteCommand(int id, int event_flags) {
+  switch (id) {
+    case IDC_CONTENT_CONTEXT_GENERATE_QR_CODE:
+      GenerateQRCode();
+      break;
+    case IDC_SEND_TAB_TO_SELF_SINGLE_TARGET:
+      SendTabToSelfSingleTarget();
+      break;
+  }
+}
+
+void ShareSubmenuModel::AddGenerateQRCodeItem() {
+  switch (context_) {
+    case Context::IMAGE:
+      AddItemWithStringId(IDC_CONTENT_CONTEXT_GENERATE_QR_CODE,
+                          IDS_CONTEXT_MENU_GENERATE_QR_CODE_IMAGE);
+      break;
+    case Context::PAGE:
+      AddItemWithStringId(IDC_CONTENT_CONTEXT_GENERATE_QR_CODE,
+                          IDS_CONTEXT_MENU_GENERATE_QR_CODE_PAGE);
+      break;
+    case Context::LINK:
+      NOTIMPLEMENTED();
+      break;
+    default:
+      break;
+  }
+}
+
+void ShareSubmenuModel::AddSendTabToSelfItem() {
+  size_t devices = send_tab_to_self::GetValidDeviceCount(browser_->profile());
+
+  if (devices == 0)
+    return;
+
+  if (devices == 1) {
+    AddSendTabToSelfSingleTargetItem();
+    return;
+  }
+
+  int label_id, command_id;
+  send_tab_to_self::SendTabToSelfMenuType menu_type;
+  if (context_ == Context::LINK) {
+    label_id = IDS_LINK_MENU_SEND_TAB_TO_SELF;
+    command_id = IDC_CONTENT_LINK_SEND_TAB_TO_SELF;
+    menu_type = send_tab_to_self::SendTabToSelfMenuType::kLink;
+  } else {
+    label_id = IDS_CONTEXT_MENU_SEND_TAB_TO_SELF;
+    command_id = IDC_SEND_TAB_TO_SELF;
+    menu_type = send_tab_to_self::SendTabToSelfMenuType::kContent;
+  }
+
+  stts_submenu_model_ =
+      std::make_unique<send_tab_to_self::SendTabToSelfSubMenuModel>(
+          browser_->tab_strip_model()->GetActiveWebContents(), menu_type);
+  if (ShouldUseSendTabToSelfIcons()) {
+    AddSubMenuWithStringIdAndIcon(
+        command_id, label_id, stts_submenu_model_.get(),
+        ui::ImageModel::FromVectorIcon(kSendTabToSelfIcon));
+  } else {
+    AddSubMenuWithStringId(command_id, label_id, stts_submenu_model_.get());
+  }
+}
+
+void ShareSubmenuModel::AddSendTabToSelfSingleTargetItem() {
+  std::u16string label = l10n_util::GetStringFUTF16(
+      IDS_LINK_MENU_SEND_TAB_TO_SELF_SINGLE_TARGET,
+      send_tab_to_self::GetSingleTargetDeviceName(browser_->profile()));
+  int command_id = context_ == Context::LINK
+                       ? IDC_CONTENT_LINK_SEND_TAB_TO_SELF_SINGLE_TARGET
+                       : IDC_SEND_TAB_TO_SELF_SINGLE_TARGET;
+  if (ShouldUseSendTabToSelfIcons()) {
+    AddItemWithIcon(command_id, label,
+                    ui::ImageModel::FromVectorIcon(kSendTabToSelfIcon));
+  } else {
+    AddItem(command_id, label);
+  }
+}
+
+void ShareSubmenuModel::GenerateQRCode() {
+  auto* web_contents = browser_->tab_strip_model()->GetActiveWebContents();
+  auto* bubble_controller =
+      qrcode_generator::QRCodeGeneratorBubbleController::Get(web_contents);
+
+  if (context_ == Context::IMAGE) {
+    base::RecordAction(base::UserMetricsAction(
+        "SharingQRCode.DialogLaunched.ContextMenuImage"));
+  } else {
+    base::RecordAction(base::UserMetricsAction(
+        "SharingQRCode.DialogLaunched.ContextMenuPage"));
+  }
+
+  bubble_controller->ShowBubble(url_);
+}
+
+void ShareSubmenuModel::SendTabToSelfSingleTarget() {
+  if (context_ == Context::LINK) {
+    send_tab_to_self::ShareToSingleTarget(
+        browser_->tab_strip_model()->GetActiveWebContents(), url_);
+    send_tab_to_self::RecordDeviceClicked(
+        send_tab_to_self::ShareEntryPoint::kLinkMenu);
+  } else {
+    send_tab_to_self::ShareToSingleTarget(
+        browser_->tab_strip_model()->GetActiveWebContents());
+    send_tab_to_self::RecordDeviceClicked(
+        send_tab_to_self::ShareEntryPoint::kContentMenu);
+  }
+}
+
+}  // namespace sharing
diff --git a/chrome/browser/sharing/share_submenu_model.h b/chrome/browser/sharing/share_submenu_model.h
new file mode 100644
index 0000000..e3172d54
--- /dev/null
+++ b/chrome/browser/sharing/share_submenu_model.h
@@ -0,0 +1,70 @@
+// Copyright 2021 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_SHARING_SHARE_SUBMENU_MODEL_H_
+#define CHROME_BROWSER_SHARING_SHARE_SUBMENU_MODEL_H_
+
+#include "ui/base/models/simple_menu_model.h"
+#include "url/gurl.h"
+
+class Browser;
+
+namespace send_tab_to_self {
+class SendTabToSelfSubMenuModel;
+}
+
+namespace sharing {
+
+// ShareSubmenuModel is a MenuModel intended to be slotted into another menu,
+// usually a context menu, to offer a set of sharing options. Currently, it
+// contains these items:
+//
+//   (Optionally) "Generate a QR code for this"
+//   (Optionally) "Send this to my device"
+//
+// It is possible for there to be zero items in this submenu, in which case
+// callers should take care not to actually add it to the containing menu.
+class ShareSubmenuModel : public ui::SimpleMenuModel,
+                          public ui::SimpleMenuModel::Delegate {
+ public:
+  enum class Context {
+    // We're offering to share an entire page
+    PAGE,
+
+    // We're offering to share a specified link
+    LINK,
+
+    // We're offering to share a specified image
+    IMAGE,
+  };
+
+  // The |url| parameter is a bit tricky: it is the "target URL" of the
+  // containing menu, whatever that happens to be. The exact meaning of that
+  // depends on |context|.
+  ShareSubmenuModel(Browser* browser, Context context, GURL url);
+  ~ShareSubmenuModel() override;
+
+  // ui::SimpleMenuModel::Delegate:
+  void ExecuteCommand(int id, int event_flags) override;
+
+ private:
+  void AddGenerateQRCodeItem();
+  void AddSendTabToSelfItem();
+
+  void AddSendTabToSelfSingleTargetItem();
+
+  void GenerateQRCode();
+  void SendTabToSelfSingleTarget();
+
+  Browser* browser_;
+  Context context_;
+  GURL url_;
+
+  std::unique_ptr<send_tab_to_self::SendTabToSelfSubMenuModel>
+      stts_submenu_model_;
+};
+
+}  // namespace sharing
+
+#endif  // CHROME_BROWSER_SHARING_SHARE_SUBMENU_MODEL_H_
diff --git a/chrome/browser/speech/speech_recognition_service_browsertest.cc b/chrome/browser/speech/speech_recognition_service_browsertest.cc
index 4895c50..fb912c0 100644
--- a/chrome/browser/speech/speech_recognition_service_browsertest.cc
+++ b/chrome/browser/speech/speech_recognition_service_browsertest.cc
@@ -217,10 +217,6 @@
       prefs::kSodaEnUsConfigPath,
       test_data_dir_.Append(base::FilePath(soda::kSodaResourcePath))
           .Append(soda::kSodaLanguagePackRelativePath));
-
-  PrefService* profile_prefs = browser()->profile()->GetPrefs();
-  // TODO(crbug.com/1173135): Disconnect from kLiveCaptionEnabled.
-  profile_prefs->SetBoolean(prefs::kLiveCaptionEnabled, true);
 }
 
 void SpeechRecognitionServiceTest::LaunchService() {
diff --git a/chrome/browser/ssl/security_state_tab_helper_browsertest.cc b/chrome/browser/ssl/security_state_tab_helper_browsertest.cc
index 3ce656c..186ae04a 100644
--- a/chrome/browser/ssl/security_state_tab_helper_browsertest.cc
+++ b/chrome/browser/ssl/security_state_tab_helper_browsertest.cc
@@ -1176,47 +1176,6 @@
   // have any impact here.
 }
 
-// TODO(https://crbug.com/333943): Remove these tests when FTP support is
-// removed.
-class SecurityStateTabHelperTestWithFtpEnabled
-    : public SecurityStateTabHelperTest {
- public:
-  SecurityStateTabHelperTestWithFtpEnabled() {
-    scoped_feature_list_.InitAndEnableFeature(network::features::kFtpProtocol);
-  }
-  ~SecurityStateTabHelperTestWithFtpEnabled() override = default;
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
-// Tests that the security level of ftp: URLs is always downgraded to
-// WARNING.
-IN_PROC_BROWSER_TEST_F(SecurityStateTabHelperTestWithFtpEnabled,
-                       SecurityLevelDowngradedOnFtpUrl) {
-  content::WebContents* contents =
-      browser()->tab_strip_model()->GetActiveWebContents();
-  ASSERT_TRUE(contents);
-
-  SecurityStyleTestObserver observer(contents);
-
-  SecurityStateTabHelper* helper =
-      SecurityStateTabHelper::FromWebContents(contents);
-  ASSERT_TRUE(helper);
-
-  ui_test_utils::NavigateToURL(browser(), GURL("ftp://example.test/"));
-  EXPECT_EQ(security_state::WARNING, helper->GetSecurityLevel());
-
-  // Ensure that WebContentsObservers don't show an incorrect Form Not Secure
-  // explanation. Regression test for https://crbug.com/691412.
-  EXPECT_EQ(0u, observer.latest_explanations().neutral_explanations.size());
-  EXPECT_EQ(blink::SecurityStyle::kInsecure, observer.latest_security_style());
-
-  content::NavigationEntry* entry = contents->GetController().GetVisibleEntry();
-  ASSERT_TRUE(entry);
-  EXPECT_EQ(content::SSLStatus::NORMAL_CONTENT, entry->GetSSL().content_status);
-}
-
 class PKPModelClientTest : public SecurityStateTabHelperTest {
  public:
   static constexpr const char* kPKPHost = "example.test";
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index 8606891..3f4d445 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -4961,6 +4961,14 @@
       <message name="IDS_QUOTATION_MARK_SUFFIX" desc="String to put after other text to represent it as a quote end.">

       </message>
+
+      <!-- HTTPS-First Mode settings strings -->
+      <message name="IDS_SETTINGS_HTTPS_FIRST_MODE_TITLE" desc="Text for the HTTPS-First Mode toggle in settings.">
+        Always use secure connections
+      </message>
+      <message name="IDS_SETTINGS_HTTPS_FIRST_MODE_SUMMARY" desc="Secondary, continued explanation of HTTPS-First Mode in settings.">
+        Upgrade navigations to HTTPS and warn you before loading sites that don’t support it
+      </message>
     </messages>
   </release>
 </grit>
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SETTINGS_HTTPS_FIRST_MODE_SUMMARY.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SETTINGS_HTTPS_FIRST_MODE_SUMMARY.png.sha1
new file mode 100644
index 0000000..0bef6cc
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SETTINGS_HTTPS_FIRST_MODE_SUMMARY.png.sha1
@@ -0,0 +1 @@
+ba645664990094c6f2c62b0089bd29a1212de019
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SETTINGS_HTTPS_FIRST_MODE_TITLE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SETTINGS_HTTPS_FIRST_MODE_TITLE.png.sha1
new file mode 100644
index 0000000..0bef6cc
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SETTINGS_HTTPS_FIRST_MODE_TITLE.png.sha1
@@ -0,0 +1 @@
+ba645664990094c6f2c62b0089bd29a1212de019
\ No newline at end of file
diff --git a/chrome/browser/ui/ash/clipboard_history_browsertest.cc b/chrome/browser/ui/ash/clipboard_history_browsertest.cc
index b512cfe..76bae5e 100644
--- a/chrome/browser/ui/ash/clipboard_history_browsertest.cc
+++ b/chrome/browser/ui/ash/clipboard_history_browsertest.cc
@@ -1175,7 +1175,7 @@
   void PasteIfAllowed(const ui::DataTransferEndpoint* const data_src,
                       const ui::DataTransferEndpoint* const data_dst,
                       const absl::optional<size_t> size,
-                      content::WebContents* web_contents,
+                      content::RenderFrameHost* rfh,
                       base::OnceCallback<void(bool)> callback) override {}
 
   bool IsDragDropAllowed(const ui::DataTransferEndpoint* const data_src,
diff --git a/chrome/browser/ui/autofill/payments/virtual_card_manual_fallback_bubble_controller.h b/chrome/browser/ui/autofill/payments/virtual_card_manual_fallback_bubble_controller.h
index 1e53d82..c2644461 100644
--- a/chrome/browser/ui/autofill/payments/virtual_card_manual_fallback_bubble_controller.h
+++ b/chrome/browser/ui/autofill/payments/virtual_card_manual_fallback_bubble_controller.h
@@ -77,6 +77,10 @@
   virtual std::u16string GetValueForField(
       VirtualCardManualFallbackBubbleField field) const = 0;
 
+  // Return the tooltip of the button.
+  virtual std::u16string GetFieldButtonTooltip(
+      VirtualCardManualFallbackBubbleField field) const = 0;
+
   // Returns the related virtual card.
   virtual const CreditCard* GetVirtualCard() const = 0;
 
@@ -88,12 +92,7 @@
   virtual void OnBubbleClosed(PaymentsBubbleClosedReason closed_reason) = 0;
 
   // Handles the event of clicking the |field|'s button.
-  virtual void OnFieldClicked(
-      VirtualCardManualFallbackBubbleField field) const = 0;
-
-  // Returns a WeakPtr to this instance.
-  virtual base::WeakPtr<VirtualCardManualFallbackBubbleController>
-  GetWeakPtr() = 0;
+  virtual void OnFieldClicked(VirtualCardManualFallbackBubbleField field) = 0;
 };
 
 }  // namespace autofill
diff --git a/chrome/browser/ui/autofill/payments/virtual_card_manual_fallback_bubble_controller_impl.cc b/chrome/browser/ui/autofill/payments/virtual_card_manual_fallback_bubble_controller_impl.cc
index 08c1a18..61989c4 100644
--- a/chrome/browser/ui/autofill/payments/virtual_card_manual_fallback_bubble_controller_impl.cc
+++ b/chrome/browser/ui/autofill/payments/virtual_card_manual_fallback_bubble_controller_impl.cc
@@ -141,6 +141,15 @@
   }
 }
 
+std::u16string
+VirtualCardManualFallbackBubbleControllerImpl::GetFieldButtonTooltip(
+    VirtualCardManualFallbackBubbleField field) const {
+  return l10n_util::GetStringUTF16(
+      clicked_field_ == field
+          ? IDS_AUTOFILL_VIRTUAL_CARD_MANUAL_FALLBACK_BUBBLE_BUTTON_TOOLTIP_CLICKED
+          : IDS_AUTOFILL_VIRTUAL_CARD_MANUAL_FALLBACK_BUBBLE_BUTTON_TOOLTIP_NORMAL);
+}
+
 const CreditCard*
 VirtualCardManualFallbackBubbleControllerImpl::GetVirtualCard() const {
   return &virtual_card_;
@@ -182,7 +191,8 @@
 }
 
 void VirtualCardManualFallbackBubbleControllerImpl::OnFieldClicked(
-    VirtualCardManualFallbackBubbleField field) const {
+    VirtualCardManualFallbackBubbleField field) {
+  clicked_field_ = field;
   LogVirtualCardManualFallbackBubbleFieldClicked(field);
   // Strip the whitespaces that were added to the card number for legibility.
   UpdateClipboard(field == VirtualCardManualFallbackBubbleField::kCardNumber
@@ -309,11 +319,6 @@
   observer_for_test_ = observer_for_test;
 }
 
-base::WeakPtr<VirtualCardManualFallbackBubbleController>
-VirtualCardManualFallbackBubbleControllerImpl::GetWeakPtr() {
-  return weak_ptr_factory_.GetWeakPtr();
-}
-
 WEB_CONTENTS_USER_DATA_KEY_IMPL(VirtualCardManualFallbackBubbleControllerImpl)
 
 }  // namespace autofill
diff --git a/chrome/browser/ui/autofill/payments/virtual_card_manual_fallback_bubble_controller_impl.h b/chrome/browser/ui/autofill/payments/virtual_card_manual_fallback_bubble_controller_impl.h
index b545722..652802a 100644
--- a/chrome/browser/ui/autofill/payments/virtual_card_manual_fallback_bubble_controller_impl.h
+++ b/chrome/browser/ui/autofill/payments/virtual_card_manual_fallback_bubble_controller_impl.h
@@ -51,13 +51,12 @@
   std::u16string GetCvcFieldLabel() const override;
   std::u16string GetValueForField(
       VirtualCardManualFallbackBubbleField field) const override;
+  std::u16string GetFieldButtonTooltip(
+      VirtualCardManualFallbackBubbleField field) const override;
   const CreditCard* GetVirtualCard() const override;
   bool ShouldIconBeVisible() const override;
   void OnBubbleClosed(PaymentsBubbleClosedReason closed_reason) override;
-  void OnFieldClicked(
-      VirtualCardManualFallbackBubbleField field) const override;
-  base::WeakPtr<VirtualCardManualFallbackBubbleController> GetWeakPtr()
-      override;
+  void OnFieldClicked(VirtualCardManualFallbackBubbleField field) override;
 
  protected:
   explicit VirtualCardManualFallbackBubbleControllerImpl(
@@ -109,6 +108,10 @@
   // applicable.
   bool bubble_has_been_shown_ = false;
 
+  // The field of the most-recently-clicked button, whose value
+  // has been copied to the clipboard.
+  absl::optional<VirtualCardManualFallbackBubbleField> clicked_field_;
+
   ObserverForTest* observer_for_test_ = nullptr;
 
   base::WeakPtrFactory<VirtualCardManualFallbackBubbleControllerImpl>
diff --git a/chrome/browser/ui/commander/bookmark_command_source.cc b/chrome/browser/ui/commander/bookmark_command_source.cc
index fbb076f..b3b8013 100644
--- a/chrome/browser/ui/commander/bookmark_command_source.cc
+++ b/chrome/browser/ui/commander/bookmark_command_source.cc
@@ -20,11 +20,6 @@
 
 namespace {
 
-// The minimum size the input should have before the source returns commands to
-// open specific bookmarks without the user choosing "Open Bookmark..." first.
-// TODO(lgrey): Centralize this constant when more composite commands are added.
-size_t constexpr kNounFirstMinimum = 4;
-
 std::unique_ptr<CommandItem> CreateOpenBookmarkItem(
     const bookmarks::UrlAndTitle& bookmark,
     Browser* browser) {
@@ -78,10 +73,6 @@
   if (!model || !model->loaded() || !model->HasBookmarks())
     return results;
 
-  if (input.size() >= kNounFirstMinimum) {
-    results = GetMatchingBookmarks(browser, input);
-  }
-
   FuzzyFinder finder(input);
   std::vector<gfx::Range> ranges;
   // TODO(lgrey): Temporarily using an untranslated string since it's not
diff --git a/chrome/browser/ui/commander/window_command_source.cc b/chrome/browser/ui/commander/window_command_source.cc
index 35a4a70..29a406d 100644
--- a/chrome/browser/ui/commander/window_command_source.cc
+++ b/chrome/browser/ui/commander/window_command_source.cc
@@ -17,12 +17,6 @@
 
 namespace {
 
-// TODO(lgrey): Specifically not deduping this with BookmarkCommandSource right
-// now since I'm not actually sure if we want the same threshold for different
-// nouns.
-size_t constexpr kNounFirstMinimum = 2;
-
-
 // Activates `browser` if it's still present.
 void SwitchToBrowser(base::WeakPtr<Browser> browser) {
   if (browser.get())
@@ -89,9 +83,6 @@
   BrowserList* browser_list = BrowserList::GetInstance();
   if (browser_list->size() < 2)
     return results;
-  if (input.size() >= kNounFirstMinimum) {
-    results = SwitchCommandsForWindowsMatching(browser, input);
-  }
   FuzzyFinder finder(input);
   std::vector<gfx::Range> ranges;
   // TODO(lgrey): Temporarily using untranslated strings since it's not
diff --git a/chrome/browser/ui/global_media_controls/media_session_notification_item_unittest.cc b/chrome/browser/ui/global_media_controls/media_session_notification_item_unittest.cc
index 6983636..920cbb35 100644
--- a/chrome/browser/ui/global_media_controls/media_session_notification_item_unittest.cc
+++ b/chrome/browser/ui/global_media_controls/media_session_notification_item_unittest.cc
@@ -4,6 +4,9 @@
 
 #include "chrome/browser/ui/global_media_controls/media_session_notification_item.h"
 
+#include <memory>
+#include <utility>
+
 #include "base/test/mock_callback.h"
 #include "base/test/task_environment.h"
 #include "components/media_message_center/mock_media_notification_view.h"
@@ -14,6 +17,7 @@
 
 using media_session::mojom::MediaSessionAction;
 using testing::_;
+using testing::NiceMock;
 
 namespace {
 
@@ -73,8 +77,8 @@
   }
 
  private:
-  media_message_center::test::MockMediaNotificationView view_;
-  MockMediaSessionNotificationItemDelegate delegate_;
+  NiceMock<media_message_center::test::MockMediaNotificationView> view_;
+  NiceMock<MockMediaSessionNotificationItemDelegate> delegate_;
   media_session::test::TestMediaController controller_;
   std::unique_ptr<MediaSessionNotificationItem> item_;
 
diff --git a/chrome/browser/ui/login/login_handler_browsertest.cc b/chrome/browser/ui/login/login_handler_browsertest.cc
index b924eff..8f6b083 100644
--- a/chrome/browser/ui/login/login_handler_browsertest.cc
+++ b/chrome/browser/ui/login/login_handler_browsertest.cc
@@ -237,18 +237,11 @@
     // TODO(https://crbug.com/333943): Remove kFtpProtocol feature and FTP
     // credential tests when FTP support is removed.
     if (GetParam() == SplitAuthCacheByNetworkIsolationKey::kFalse) {
-      scoped_feature_list_.InitWithFeatures(
-          // enabled_features
-          {network::features::kFtpProtocol},
-          // disabled_features
-          {network::features::kSplitAuthCacheByNetworkIsolationKey});
+      scoped_feature_list_.InitAndDisableFeature(
+          network::features::kSplitAuthCacheByNetworkIsolationKey);
     } else {
-      scoped_feature_list_.InitWithFeatures(
-          // enabled_features
-          {network::features::kSplitAuthCacheByNetworkIsolationKey,
-           network::features::kFtpProtocol},
-          // disabled_features
-          {});
+      scoped_feature_list_.InitAndEnableFeature(
+          network::features::kSplitAuthCacheByNetworkIsolationKey);
     }
   }
 
@@ -2050,77 +2043,6 @@
   EXPECT_FALSE(notification_fired);
 }
 
-// Tests that FTP auth challenges appear over a blank committed interstitial.
-IN_PROC_BROWSER_TEST_P(LoginPromptBrowserTest, FtpAuth) {
-  net::SpawnedTestServer ftp_server(
-      net::SpawnedTestServer::TYPE_FTP,
-      base::FilePath(FILE_PATH_LITERAL("chrome/test/data/ftp")));
-  ftp_server.set_no_anonymous_ftp_user(true);
-  ASSERT_TRUE(ftp_server.Start());
-
-  content::WebContents* contents =
-      browser()->tab_strip_model()->GetActiveWebContents();
-  NavigationController* controller = &contents->GetController();
-  LoginPromptBrowserTestObserver observer;
-  observer.Register(content::Source<NavigationController>(controller));
-
-  // Navigate to an FTP server and wait for the auth prompt to appear.
-  WindowedAuthNeededObserver auth_needed_waiter(controller);
-  ui_test_utils::NavigateToURL(browser(), ftp_server.GetURL(""));
-  auth_needed_waiter.Wait();
-  ASSERT_EQ(1u, observer.handlers().size());
-  EXPECT_EQ("<head></head><body></body>",
-            content::EvalJs(contents, "document.documentElement.innerHTML"));
-
-  // Supply credentials and wait for the page to successfully load.
-  LoginHandler* handler = *observer.handlers().begin();
-  WindowedAuthSuppliedObserver auth_supplied_waiter(controller);
-  handler->SetAuth(u"chrome", u"chrome");
-  auth_supplied_waiter.Wait();
-  const std::u16string kExpectedTitle = u"Index of /";
-  content::TitleWatcher title_watcher(contents, kExpectedTitle);
-  EXPECT_EQ(kExpectedTitle, title_watcher.WaitAndGetTitle());
-}
-
-// Tests that FTP auth prompts do not appear when credentials have been
-// previously entered and cached.
-IN_PROC_BROWSER_TEST_P(LoginPromptBrowserTest, FtpAuthWithCache) {
-  net::SpawnedTestServer ftp_server(
-      net::SpawnedTestServer::TYPE_FTP,
-      base::FilePath(FILE_PATH_LITERAL("chrome/test/data/ftp")));
-  ftp_server.set_no_anonymous_ftp_user(true);
-  ASSERT_TRUE(ftp_server.Start());
-
-  content::WebContents* contents =
-      browser()->tab_strip_model()->GetActiveWebContents();
-  NavigationController* controller = &contents->GetController();
-  LoginPromptBrowserTestObserver observer;
-  observer.Register(content::Source<NavigationController>(controller));
-
-  // Navigate to an FTP server and wait for the auth prompt to appear.
-  WindowedAuthNeededObserver auth_needed_waiter(controller);
-  ui_test_utils::NavigateToURL(browser(), ftp_server.GetURL(""));
-  auth_needed_waiter.Wait();
-  ASSERT_EQ(1u, observer.handlers().size());
-
-  // Supply credentials and wait for the page to successfully load.
-  LoginHandler* handler = *observer.handlers().begin();
-  WindowedAuthSuppliedObserver auth_supplied_waiter(controller);
-  handler->SetAuth(u"chrome", u"chrome");
-  auth_supplied_waiter.Wait();
-  const std::u16string kExpectedTitle = u"Index of /";
-  content::TitleWatcher title_watcher(contents, kExpectedTitle);
-  EXPECT_EQ(kExpectedTitle, title_watcher.WaitAndGetTitle());
-
-  // Navigate away and then back to the FTP server. There should be no auth
-  // prompt because the credentials are cached.
-  ui_test_utils::NavigateToURL(browser(), GURL("about:blank"));
-  content::TitleWatcher revisit_title_watcher(contents, kExpectedTitle);
-  ui_test_utils::NavigateToURL(browser(), ftp_server.GetURL(""));
-  EXPECT_EQ(kExpectedTitle, revisit_title_watcher.WaitAndGetTitle());
-  EXPECT_EQ(0u, observer.handlers().size());
-}
-
 namespace {
 
 // A request handler that returns a 401 Unauthorized response on the
diff --git a/chrome/browser/ui/views/autofill/payments/virtual_card_manual_fallback_bubble_views.cc b/chrome/browser/ui/views/autofill/payments/virtual_card_manual_fallback_bubble_views.cc
index efd1790..23fb5e1 100644
--- a/chrome/browser/ui/views/autofill/payments/virtual_card_manual_fallback_bubble_views.cc
+++ b/chrome/browser/ui/views/autofill/payments/virtual_card_manual_fallback_bubble_views.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/views/autofill/payments/virtual_card_manual_fallback_bubble_views.h"
 
 #include "base/bind.h"
+#include "base/strings/strcat.h"
 #include "chrome/browser/ui/views/autofill/payments/payments_view_util.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "components/autofill/core/browser/data_model/credit_card.h"
@@ -125,6 +126,7 @@
   layout->AddView(CreateRowItemLabel(controller_->GetCvcFieldLabel()));
   layout->AddView(
       CreateRowItemButtonForField(VirtualCardManualFallbackBubbleField::kCvc));
+  UpdateButtonTooltipsAndAccessibleNames();
 }
 
 ui::ImageModel VirtualCardManualFallbackBubbleViews::GetWindowIcon() {
@@ -168,13 +170,29 @@
     VirtualCardManualFallbackBubbleField field) {
   std::u16string text = controller_->GetValueForField(field);
   auto button = std::make_unique<views::MdTextButton>(
-      base::BindRepeating(
-          &VirtualCardManualFallbackBubbleController::OnFieldClicked,
-          controller_->GetWeakPtr(), field),
+      base::BindRepeating(&VirtualCardManualFallbackBubbleViews::OnFieldClicked,
+                          weak_ptr_factory_.GetWeakPtr(), field),
       text, views::style::CONTEXT_BUTTON);
   button->SetCornerRadius(ChromeLayoutProvider::Get()->GetCornerRadiusMetric(
       views::Emphasis::kMaximum, button->GetPreferredSize()));
+  fields_to_buttons_map_[field] = button.get();
   return button;
 }
 
+void VirtualCardManualFallbackBubbleViews::OnFieldClicked(
+    VirtualCardManualFallbackBubbleField field) {
+  controller_->OnFieldClicked(field);
+  UpdateButtonTooltipsAndAccessibleNames();
+}
+
+void VirtualCardManualFallbackBubbleViews::
+    UpdateButtonTooltipsAndAccessibleNames() {
+  for (auto& pair : fields_to_buttons_map_) {
+    std::u16string tooltip = controller_->GetFieldButtonTooltip(pair.first);
+    pair.second->SetTooltipText(tooltip);
+    pair.second->SetAccessibleName(
+        base::StrCat({pair.second->GetText(), u" ", tooltip}));
+  }
+}
+
 }  // namespace autofill
diff --git a/chrome/browser/ui/views/autofill/payments/virtual_card_manual_fallback_bubble_views.h b/chrome/browser/ui/views/autofill/payments/virtual_card_manual_fallback_bubble_views.h
index 5d6087e6..e7c8172e 100644
--- a/chrome/browser/ui/views/autofill/payments/virtual_card_manual_fallback_bubble_views.h
+++ b/chrome/browser/ui/views/autofill/payments/virtual_card_manual_fallback_bubble_views.h
@@ -37,6 +37,10 @@
       const VirtualCardManualFallbackBubbleViews&) = delete;
 
  private:
+  FRIEND_TEST_ALL_PREFIXES(
+      VirtualCardManualFallbackBubbleViewsInteractiveUiTest,
+      TooltipAndAccessibleName);
+
   // AutofillBubbleBase:
   void Hide() override;
 
@@ -52,10 +56,24 @@
   std::unique_ptr<views::MdTextButton> CreateRowItemButtonForField(
       VirtualCardManualFallbackBubbleField field);
 
+  // Invoked when a button with card information is clicked.
+  void OnFieldClicked(VirtualCardManualFallbackBubbleField field);
+
+  // Update the tooltips and the accessible names of the buttons.
+  void UpdateButtonTooltipsAndAccessibleNames();
+
   VirtualCardManualFallbackBubbleController* controller_;
 
   PaymentsBubbleClosedReason closed_reason_ =
       PaymentsBubbleClosedReason::kUnknown;
+
+  // The map keeping the references to each button with card information text in
+  // the bubble.
+  std::map<VirtualCardManualFallbackBubbleField, views::MdTextButton*>
+      fields_to_buttons_map_;
+
+  base::WeakPtrFactory<VirtualCardManualFallbackBubbleViews> weak_ptr_factory_{
+      this};
 };
 
 }  // namespace autofill
diff --git a/chrome/browser/ui/views/autofill/payments/virtual_card_manual_fallback_bubble_views_interactive_uitest.cc b/chrome/browser/ui/views/autofill/payments/virtual_card_manual_fallback_bubble_views_interactive_uitest.cc
index c75b807a..6b2bd59 100644
--- a/chrome/browser/ui/views/autofill/payments/virtual_card_manual_fallback_bubble_views_interactive_uitest.cc
+++ b/chrome/browser/ui/views/autofill/payments/virtual_card_manual_fallback_bubble_views_interactive_uitest.cc
@@ -20,9 +20,12 @@
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/autofill/core/browser/autofill_test_utils.h"
 #include "components/autofill/core/browser/test_event_waiter.h"
+#include "components/strings/grit/components_strings.h"
 #include "content/public/test/browser_test.h"
 #include "ui/base/clipboard/clipboard.h"
+#include "ui/base/l10n/l10n_util.h"
 #include "ui/base/test/ui_controls.h"
+#include "ui/views/controls/button/md_text_button.h"
 #include "ui/views/test/widget_test.h"
 
 namespace autofill {
@@ -355,4 +358,47 @@
       1);
 }
 
+IN_PROC_BROWSER_TEST_F(VirtualCardManualFallbackBubbleViewsInteractiveUiTest,
+                       TooltipAndAccessibleName) {
+  ShowBubble();
+  ASSERT_TRUE(GetBubbleViews());
+  ASSERT_TRUE(IsIconVisible());
+
+  EXPECT_EQ(5U, GetBubbleViews()->fields_to_buttons_map_.size());
+  std::u16string normal_button_tooltip = l10n_util::GetStringUTF16(
+      IDS_AUTOFILL_VIRTUAL_CARD_MANUAL_FALLBACK_BUBBLE_BUTTON_TOOLTIP_NORMAL);
+  std::u16string clicked_button_tooltip = l10n_util::GetStringUTF16(
+      IDS_AUTOFILL_VIRTUAL_CARD_MANUAL_FALLBACK_BUBBLE_BUTTON_TOOLTIP_CLICKED);
+  for (auto& pair : GetBubbleViews()->fields_to_buttons_map_) {
+    EXPECT_EQ(normal_button_tooltip, pair.second->GetTooltipText());
+    EXPECT_EQ(pair.second->GetText() + u" " + normal_button_tooltip,
+              pair.second->GetAccessibleName());
+  }
+
+  auto* card_number_button =
+      GetBubbleViews()->fields_to_buttons_map_
+          [VirtualCardManualFallbackBubbleField::kCardNumber];
+  auto* cardholder_name_button =
+      GetBubbleViews()->fields_to_buttons_map_
+          [VirtualCardManualFallbackBubbleField::kCardholderName];
+
+  GetBubbleViews()->OnFieldClicked(
+      VirtualCardManualFallbackBubbleField::kCardNumber);
+  EXPECT_EQ(clicked_button_tooltip, card_number_button->GetTooltipText());
+  EXPECT_EQ(u"4111 1111 1111 1111 " + clicked_button_tooltip,
+            card_number_button->GetAccessibleName());
+  EXPECT_EQ(normal_button_tooltip, cardholder_name_button->GetTooltipText());
+  EXPECT_EQ(u"Full Carter " + normal_button_tooltip,
+            cardholder_name_button->GetAccessibleName());
+
+  GetBubbleViews()->OnFieldClicked(
+      VirtualCardManualFallbackBubbleField::kCardholderName);
+  EXPECT_EQ(normal_button_tooltip, card_number_button->GetTooltipText());
+  EXPECT_EQ(u"4111 1111 1111 1111 " + normal_button_tooltip,
+            card_number_button->GetAccessibleName());
+  EXPECT_EQ(clicked_button_tooltip, cardholder_name_button->GetTooltipText());
+  EXPECT_EQ(u"Full Carter " + clicked_button_tooltip,
+            cardholder_name_button->GetAccessibleName());
+}
+
 }  // namespace autofill
diff --git a/chrome/browser/ui/views/download/download_item_view.cc b/chrome/browser/ui/views/download/download_item_view.cc
index 9124da41..6dec3a0 100644
--- a/chrome/browser/ui/views/download/download_item_view.cc
+++ b/chrome/browser/ui/views/download/download_item_view.cc
@@ -810,6 +810,9 @@
 }
 
 void DownloadItemView::UpdateLabels() {
+  if (GetEnabled()) {
+    file_name_label_->SetText(ElidedFilename(*file_name_label_));
+  }
   file_name_label_->SetVisible(mode_ == download::DownloadItemMode::kNormal);
 
   status_label_->SetVisible(mode_ == download::DownloadItemMode::kNormal);
diff --git a/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc b/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc
index 3793908..ae36169 100644
--- a/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc
+++ b/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc
@@ -945,7 +945,7 @@
 // Tests that Safety Tips don't trigger on lookalike domains that are one
 // character swap away from an engaged site.
 IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
-                       NoTriggerOnCharacterSwap) {
+                       NoTriggerOnCharacterSwap_SiteEngagement) {
   const GURL kNavigatedUrl = GetURL("character-wsap.com");
   const GURL kTargetUrl = GetURL("character-swap.com");
   SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
@@ -954,6 +954,18 @@
   EXPECT_FALSE(IsUIShowing());
 }
 
+// Tests that Safety Tips don't trigger on lookalike domains that are one
+// character swap away from a top site.
+IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
+                       NoTriggerOnCharacterSwap_TopSite) {
+  const GURL kNavigatedUrl = GetURL("goolge.com");
+  const GURL kTargetUrl = GetURL("google.com");
+  SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
+  SetEngagementScore(browser(), kTargetUrl, kHighEngagement);
+  NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
+  EXPECT_FALSE(IsUIShowing());
+}
+
 // Tests that Safety Tips trigger on lookalike domains with tail embedding when
 // enabled, and not otherwise.
 IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
diff --git a/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_view_impl.cc b/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_view_impl.cc
index a5deaf9..d561e0e 100644
--- a/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_view_impl.cc
+++ b/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_view_impl.cc
@@ -36,8 +36,7 @@
   separator->SetColor(gfx::kGoogleGrey300);
   const int kIndent = 16;
   const int kPadding = 8;
-  constexpr auto kSeperatorBorder =
-      gfx::Insets(kPadding, kIndent, kPadding, kIndent);
+  constexpr auto kSeperatorBorder = gfx::Insets(kPadding, kIndent, 0, kIndent);
   separator->SetBorder(views::CreateEmptyBorder(kSeperatorBorder));
   return separator;
 }
@@ -106,14 +105,8 @@
 }
 
 void SharingHubBubbleViewImpl::Init() {
-  auto* provider = ChromeLayoutProvider::Get();
-  set_margins(
-      gfx::Insets(provider->GetDistanceMetric(
-                      views::DISTANCE_DIALOG_CONTENT_MARGIN_TOP_CONTROL),
-                  0,
-                  provider->GetDistanceMetric(
-                      views::DISTANCE_DIALOG_CONTENT_MARGIN_BOTTOM_CONTROL),
-                  0));
+  const int kPadding = 8;
+  set_margins(gfx::Insets(kPadding, 0, kPadding, 0));
   SetLayoutManager(std::make_unique<views::FillLayout>());
 
   scroll_view_ = AddChildView(std::make_unique<views::ScrollView>());
@@ -140,8 +133,9 @@
   action_list_view->AddChildView(GetSeparator());
 
   const int kLabelLineHeight = 22;
-  const int kLabelLinePadding = 9;
-  const int kIndent = 22;
+  const int kLabelLinePaddingTop = 8;
+  const int kLabelLinePaddingBottom = 4;
+  const int kIndent = 16;
 
   auto* share_link_label =
       new views::Label(l10n_util::GetStringUTF16(IDS_SHARING_HUB_SHARE_LABEL));
@@ -151,9 +145,9 @@
   share_link_label->SetHorizontalAlignment(gfx::ALIGN_TO_HEAD);
   share_link_label->SizeToFit(views::DISTANCE_BUBBLE_PREFERRED_WIDTH);
   constexpr auto kPrimaryIconBorder = gfx::Insets(
-      /*top*/ kLabelLinePadding,
+      /*top*/ kLabelLinePaddingTop,
       /*left*/ kIndent,
-      /*bottom*/ 0,
+      /*bottom*/ kLabelLinePaddingBottom,
       /*right*/ kIndent);
   share_link_label->SetBorder(views::CreateEmptyBorder(kPrimaryIconBorder));
 
diff --git a/chrome/browser/ui/views/toolbar/toolbar_button.cc b/chrome/browser/ui/views/toolbar/toolbar_button.cc
index 500c481..8747d20 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_button.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_button.cc
@@ -41,7 +41,6 @@
 #include "ui/views/animation/ink_drop.h"
 #include "ui/views/animation/ink_drop_highlight.h"
 #include "ui/views/animation/ink_drop_mask.h"
-#include "ui/views/animation/installable_ink_drop.h"
 #include "ui/views/background.h"
 #include "ui/views/border.h"
 #include "ui/views/controls/button/label_button_border.h"
@@ -163,21 +162,6 @@
 
   set_context_menu_controller(this);
 
-  if (base::FeatureList::IsEnabled(views::kInstallableInkDropFeature)) {
-    installable_ink_drop_ = std::make_unique<views::InstallableInkDrop>(this);
-    installable_ink_drop_->SetConfig(GetToolbarInstallableInkDropConfig(this));
-    views::InkDrop::Get(this)->SetCreateInkDropCallback(base::BindRepeating(
-        [](Button* host) -> std::unique_ptr<views::InkDrop> {
-          // Ensure this doesn't get called when InstallableInkDrops are
-          // enabled.
-          DCHECK(
-              !base::FeatureList::IsEnabled(views::kInstallableInkDropFeature));
-          return views::InkDrop::CreateInkDropForFloodFillRipple(
-              views::InkDrop::Get(host));
-        },
-        this));
-  }
-
   views::InkDrop::Get(this)->SetCreateMaskCallback(base::BindRepeating(
       [](ToolbarButton* host) -> std::unique_ptr<views::InkDropMask> {
         if (host->has_in_product_help_promo_) {
@@ -199,9 +183,6 @@
       this));
   views::InkDrop::Get(this)->SetBaseColorCallback(base::BindRepeating(
       [](ToolbarButton* host) {
-        // Ensure this doesn't get called when InstallableInkDrops are enabled.
-        DCHECK(
-            !base::FeatureList::IsEnabled(views::kInstallableInkDropFeature));
         if (host->has_in_product_help_promo_)
           return GetFeaturePromoHighlightColorForToolbar(
               host->GetThemeProvider());
@@ -464,9 +445,6 @@
 
 void ToolbarButton::OnThemeChanged() {
   UpdateColorsAndInsets();
-
-  if (installable_ink_drop_)
-    installable_ink_drop_->SetConfig(GetToolbarInstallableInkDropConfig(this));
   UpdateIcon();
 
   // Call this after UpdateIcon() to properly reset images.
diff --git a/chrome/browser/ui/views/toolbar/toolbar_button.h b/chrome/browser/ui/views/toolbar/toolbar_button.h
index 1247c52..a61be00 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_button.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_button.h
@@ -32,7 +32,6 @@
 }
 
 namespace views {
-class InstallableInkDrop;
 class MenuModelAdapter;
 class MenuRunner;
 }
@@ -296,14 +295,6 @@
   // larger-than-16dp avatar avatar icon outside of touchable mode.
   gfx::Insets layout_inset_delta_;
 
-  // Used instead of the standard InkDrop implementation when
-  // |views::kInstallableInkDropFeature| is enabled.
-  // TODO(crbug.com/931964): When InkDrops can be externally installed, connect
-  // this InkDrop when the experiment is enabled. This is currently not working
-  // as a virtual GetInkDrop() override was removed to finish
-  // InkDropHostView migration from the View hierarchy.
-  std::unique_ptr<views::InstallableInkDrop> installable_ink_drop_;
-
   // Class responsible for animating highlight color (calling a callback on
   // |this| to refresh UI).
   HighlightColorAnimation highlight_color_animation_;
diff --git a/chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.cc b/chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.cc
index 6026bcc8..72145856 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.cc
@@ -18,7 +18,6 @@
 #include "ui/views/animation/ink_drop.h"
 #include "ui/views/animation/ink_drop_host_view.h"
 #include "ui/views/animation/ink_drop_impl.h"
-#include "ui/views/animation/installable_ink_drop_config.h"
 #include "ui/views/controls/button/button.h"
 #include "ui/views/controls/highlight_path_generator.h"
 #include "ui/views/style/platform_style.h"
@@ -71,15 +70,6 @@
              : gfx::kPlaceholderColor;
 }
 
-views::InstallableInkDropConfig GetToolbarInstallableInkDropConfig(
-    const views::View* host_view) {
-  views::InstallableInkDropConfig config;
-  config.base_color = GetToolbarInkDropBaseColor(host_view);
-  config.ripple_opacity = kToolbarInkDropVisibleOpacity;
-  config.highlight_opacity = kToolbarInkDropHighlightVisibleOpacity;
-  return config;
-}
-
 void ConfigureInkDropForToolbar(views::Button* host) {
   host->SetHasInkDropActionOnClick(true);
   views::HighlightPathGenerator::Install(
diff --git a/chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h b/chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h
index b7614f6..b96eb6e 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h
@@ -12,7 +12,6 @@
 namespace views {
 class Button;
 class View;
-struct InstallableInkDropConfig;
 }  // namespace views
 
 constexpr float kToolbarInkDropVisibleOpacity = 0.06f;
@@ -27,9 +26,6 @@
 // This is only needed if you can't use ConfigureInkDropForToolbar().
 SkColor GetToolbarInkDropBaseColor(const views::View* host_view);
 
-views::InstallableInkDropConfig GetToolbarInstallableInkDropConfig(
-    const views::View* host_view);
-
 void ConfigureInkDropForToolbar(views::Button* host);
 
 #endif  // CHROME_BROWSER_UI_VIEWS_TOOLBAR_TOOLBAR_INK_DROP_UTIL_H_
diff --git a/chrome/browser/ui/web_applications/web_app_metrics.cc b/chrome/browser/ui/web_applications/web_app_metrics.cc
index 8aa4341..c210de4e 100644
--- a/chrome/browser/ui/web_applications/web_app_metrics.cc
+++ b/chrome/browser/ui/web_applications/web_app_metrics.cc
@@ -89,7 +89,7 @@
   base::PowerMonitor::AddPowerSuspendObserver(this);
   BrowserList::AddObserver(this);
 
-  WebAppProvider* provider = WebAppProvider::Get(profile_);
+  WebAppProvider* provider = WebAppProvider::GetForLocalApps(profile_);
   DCHECK(provider);
   provider->on_registry_ready().Post(
       FROM_HERE, base::BindOnce(&WebAppMetrics::CountUserInstalledApps,
diff --git a/chrome/browser/ui/webui/settings/chromeos/apps_section.cc b/chrome/browser/ui/webui/settings/chromeos/apps_section.cc
index a819001..20524cb 100644
--- a/chrome/browser/ui/webui/settings/chromeos/apps_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/apps_section.cc
@@ -53,6 +53,44 @@
   return *tags;
 }
 
+const std::vector<SearchConcept>& GetAppNotificationsSearchConcepts() {
+  static const base::NoDestructor<std::vector<SearchConcept>> tags({
+      {IDS_OS_SETTINGS_TAG_APP_NOTIFICATIONS,
+       mojom::kAppNotificationsSubpagePath,
+       mojom::SearchResultIcon::kAppsGrid,
+       mojom::SearchResultDefaultRank::kMedium,
+       mojom::SearchResultType::kSubpage,
+       {.subpage = mojom::Subpage::kAppNotifications}},
+  });
+  return *tags;
+}
+
+const std::vector<SearchConcept>& GetTurnOffAppNotificationSearchConcepts() {
+  static const base::NoDestructor<std::vector<SearchConcept>> tags(
+      {{IDS_OS_SETTINGS_TAG_DO_NOT_DISTURB_TURN_OFF,
+        mojom::kAppNotificationsSubpagePath,
+        mojom::SearchResultIcon::kAppsGrid,
+        mojom::SearchResultDefaultRank::kMedium,
+        mojom::SearchResultType::kSetting,
+        {.setting = mojom::Setting::kDoNotDisturbOnOff},
+        {IDS_OS_SETTINGS_TAG_DO_NOT_DISTURB_TURN_OFF_ALT1,
+         SearchConcept::kAltTagEnd}}});
+  return *tags;
+}
+
+const std::vector<SearchConcept>& GetTurnOnAppNotificationSearchConcepts() {
+  static const base::NoDestructor<std::vector<SearchConcept>> tags(
+      {{IDS_OS_SETTINGS_TAG_DO_NOT_DISTURB_TURN_ON,
+        mojom::kAppNotificationsSubpagePath,
+        mojom::SearchResultIcon::kAppsGrid,
+        mojom::SearchResultDefaultRank::kMedium,
+        mojom::SearchResultType::kSetting,
+        {.setting = mojom::Setting::kDoNotDisturbOnOff},
+        {IDS_OS_SETTINGS_TAG_DO_NOT_DISTURB_TURN_ON_ALT1,
+         SearchConcept::kAltTagEnd}}});
+  return *tags;
+}
+
 const std::vector<SearchConcept>& GetAndroidPlayStoreSearchConcepts() {
   static const base::NoDestructor<std::vector<SearchConcept>> tags({
       {IDS_OS_SETTINGS_TAG_PLAY_STORE,
@@ -228,6 +266,13 @@
   SearchTagRegistry::ScopedTagUpdater updater = registry()->StartUpdate();
   updater.AddSearchTags(GetAppsSearchConcepts());
 
+  // Note: The MessageCenterAsh check here is added for unit testing purposes
+  // otherwise check statements are not needed in production.
+  if (ash::MessageCenterAsh::Get()) {
+    ash::MessageCenterAsh::Get()->AddObserver(this);
+    OnQuietModeChanged(ash::MessageCenterAsh::Get()->IsQuietMode());
+  }
+
   if (arc::IsArcAllowedForProfile(profile)) {
     pref_change_registrar_.Init(pref_service_);
     pref_change_registrar_.Add(
@@ -246,6 +291,13 @@
 }
 
 AppsSection::~AppsSection() {
+  // TODO(crbug.com/1237465): observer is never removed because ash::Shell is
+  // destroyed first.
+  // Note: The MessageCenterAsh check is also added for unit testing purposes.
+  if (ash::MessageCenterAsh::Get()) {
+    ash::MessageCenterAsh::Get()->RemoveObserver(this);
+  }
+
   if (arc::IsArcAllowedForProfile(profile())) {
     if (arc_app_list_prefs_)
       arc_app_list_prefs_->RemoveObserver(this);
@@ -341,6 +393,9 @@
                                      mojom::SearchResultIcon::kAppsGrid,
                                      mojom::SearchResultDefaultRank::kMedium,
                                      mojom::kAppNotificationsSubpagePath);
+
+  generator->RegisterNestedSetting(mojom::Setting::kDoNotDisturbOnOff,
+                                   mojom::Subpage::kAppNotifications);
   // Note: The subpage name in the UI is updated dynamically based on the app
   // being shown, but we use a generic "App details" string here.
   generator->RegisterNestedSubpage(
@@ -472,5 +527,25 @@
   }
 }
 
+void AppsSection::OnQuietModeChanged(bool in_quiet_mode) {
+  if (!features::IsAppNotificationsPageEnabled()) {
+    return;
+  }
+  SearchTagRegistry::ScopedTagUpdater updater = registry()->StartUpdate();
+
+  updater.RemoveSearchTags(GetTurnOnAppNotificationSearchConcepts());
+  updater.RemoveSearchTags(GetTurnOffAppNotificationSearchConcepts());
+  updater.RemoveSearchTags(GetAppNotificationsSearchConcepts());
+
+  updater.AddSearchTags(GetAppNotificationsSearchConcepts());
+
+  if (!ash::MessageCenterAsh::Get()->IsQuietMode()) {
+    updater.AddSearchTags(GetTurnOnAppNotificationSearchConcepts());
+    return;
+  }
+
+  updater.AddSearchTags(GetTurnOffAppNotificationSearchConcepts());
+}
+
 }  // namespace settings
 }  // namespace chromeos
diff --git a/chrome/browser/ui/webui/settings/chromeos/apps_section.h b/chrome/browser/ui/webui/settings/chromeos/apps_section.h
index 272c923..b2f2911d 100644
--- a/chrome/browser/ui/webui/settings/chromeos/apps_section.h
+++ b/chrome/browser/ui/webui/settings/chromeos/apps_section.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_UI_WEBUI_SETTINGS_CHROMEOS_APPS_SECTION_H_
 #define CHROME_BROWSER_UI_WEBUI_SETTINGS_CHROMEOS_APPS_SECTION_H_
 
+#include "ash/public/cpp/message_center_ash.h"
 #include "base/values.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h"
@@ -23,7 +24,9 @@
 class SearchTagRegistry;
 
 // Provides UI strings and search tags for Apps settings.
-class AppsSection : public OsSettingsSection, public ArcAppListPrefs::Observer {
+class AppsSection : public OsSettingsSection,
+                    public ArcAppListPrefs::Observer,
+                    public ash::MessageCenterAsh::Observer {
  public:
   AppsSection(Profile* profile,
               SearchTagRegistry* search_tag_registry,
@@ -47,6 +50,9 @@
   void OnAppRegistered(const std::string& app_id,
                        const ArcAppListPrefs::AppInfo& app_info) override;
 
+  // MessageCenterAsh::Observer override:
+  void OnQuietModeChanged(bool in_quiet_mode) override;
+
   void AddAndroidAppStrings(content::WebUIDataSource* html_source);
   void AddPluginVmLoadTimeData(content::WebUIDataSource* html_source);
   void AddOnStartupTimeData(content::WebUIDataSource* html_source);
diff --git a/chrome/browser/ui/webui/settings/chromeos/calculator/size_calculator.cc b/chrome/browser/ui/webui/settings/chromeos/calculator/size_calculator.cc
index 2655828..8cf0a9ec 100644
--- a/chrome/browser/ui/webui/settings/chromeos/calculator/size_calculator.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/calculator/size_calculator.cc
@@ -7,6 +7,7 @@
 #include <numeric>
 
 #include "base/callback_helpers.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/system/sys_info.h"
 #include "base/task/post_task.h"
 #include "base/task/thread_pool.h"
@@ -178,7 +179,7 @@
         new browsing_data::AppCacheHelper(
             storage_partition->GetAppCacheService()),
         new browsing_data::IndexedDBHelper(storage_partition),
-        browsing_data::FileSystemHelper::Create(
+        base::MakeRefCounted<browsing_data::FileSystemHelper>(
             storage_partition->GetFileSystemContext(),
             browsing_data_file_system_util::GetAdditionalFileSystemTypes(),
             storage_partition->GetNativeIOContext()),
diff --git a/chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom b/chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom
index 5057c56..47cbadd6 100644
--- a/chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom
+++ b/chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom
@@ -162,6 +162,7 @@
   kRemovePlayStore = 701,
   kTurnOnPlayStore = 702,
   kRestoreAppsAndPages = 703,
+  kDoNotDisturbOnOff = 704,
 
   // Crostini section.
   kSetUpCrostini= 800,
diff --git a/chrome/browser/ui/webui/settings/chromeos/os_apps_page/app_notification_handler.cc b/chrome/browser/ui/webui/settings/chromeos/os_apps_page/app_notification_handler.cc
index d4aa2fd6..7fc34c1 100644
--- a/chrome/browser/ui/webui/settings/chromeos/os_apps_page/app_notification_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/os_apps_page/app_notification_handler.cc
@@ -67,17 +67,19 @@
 void AppNotificationHandler::BindInterface(
     mojo::PendingReceiver<app_notification::mojom::AppNotificationsHandler>
         receiver) {
+  if (receiver_.is_bound())
+    receiver_.reset();
   receiver_.Bind(std::move(receiver));
 }
 
-void AppNotificationHandler ::OnQuietModeChanged(bool in_quiet_mode) {
+void AppNotificationHandler::OnQuietModeChanged(bool in_quiet_mode) {
   in_quiet_mode_ = in_quiet_mode;
   for (const auto& observer : observer_list_) {
     observer->OnQuietModeChanged(in_quiet_mode);
   }
 }
 
-void AppNotificationHandler ::SetQuietMode(bool in_quiet_mode) {
+void AppNotificationHandler::SetQuietMode(bool in_quiet_mode) {
   ash::MessageCenterAsh::Get()->SetQuietMode(in_quiet_mode);
 }
 
diff --git a/chrome/browser/ui/webui/settings/chromeos/os_apps_page/app_notification_handler_unittest.cc b/chrome/browser/ui/webui/settings/chromeos/os_apps_page/app_notification_handler_unittest.cc
index e23a697..366cb91 100644
--- a/chrome/browser/ui/webui/settings/chromeos/os_apps_page/app_notification_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/os_apps_page/app_notification_handler_unittest.cc
@@ -31,8 +31,16 @@
 
   // MessageCenterAsh override:
   void SetQuietMode(bool in_quiet_mode) override {
-    NotifyOnQuietModeChanged(in_quiet_mode);
+    if (in_quiet_mode != in_quiet_mode_) {
+      in_quiet_mode_ = in_quiet_mode;
+      NotifyOnQuietModeChanged(in_quiet_mode);
+    }
   }
+
+  bool IsQuietMode() const override { return in_quiet_mode_; }
+
+ private:
+  bool in_quiet_mode_ = false;
 };
 
 }  // namespace
diff --git a/chrome/browser/web_applications/web_app_tab_helper.cc b/chrome/browser/web_applications/web_app_tab_helper.cc
index 30103da..2021884 100644
--- a/chrome/browser/web_applications/web_app_tab_helper.cc
+++ b/chrome/browser/web_applications/web_app_tab_helper.cc
@@ -32,7 +32,7 @@
 
 WebAppTabHelper::WebAppTabHelper(content::WebContents* web_contents)
     : content::WebContentsObserver(web_contents),
-      provider_(WebAppProvider::Get(
+      provider_(WebAppProvider::GetForLocalApps(
           Profile::FromBrowserContext(web_contents->GetBrowserContext()))) {
   DCHECK(provider_);
   observation_.Observe(&provider_->registrar());
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 5dce4a0..09450c5 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -511,7 +511,7 @@
 
 // Sets whether the HTTPS-Only Mode setting is displayed in the settings UI.
 const base::Feature kHttpsOnlyMode{"HttpsOnlyMode",
-                                   base::FEATURE_DISABLED_BY_DEFAULT};
+                                   base::FEATURE_ENABLED_BY_DEFAULT};
 
 #if defined(OS_MAC)
 const base::Feature kImmersiveFullscreen{"ImmersiveFullscreen",
diff --git a/chrome/common/extensions/api/_permission_features.json b/chrome/common/extensions/api/_permission_features.json
index 58f4f12..b1c2e216 100644
--- a/chrome/common/extensions/api/_permission_features.json
+++ b/chrome/common/extensions/api/_permission_features.json
@@ -672,7 +672,7 @@
   },
   "printing": {
     "channel": "stable",
-    "platforms": ["chromeos"],
+    "platforms": ["chromeos", "lacros"],
     "extension_types": ["extension"]
   },
   "printingMetrics": {
diff --git a/chrome/common/extensions/api/api_sources.gni b/chrome/common/extensions/api/api_sources.gni
index 542629d..f1c3093 100644
--- a/chrome/common/extensions/api/api_sources.gni
+++ b/chrome/common/extensions/api/api_sources.gni
@@ -74,7 +74,7 @@
   schema_sources_ += [ "processes.idl" ]
 }
 
-if (is_chromeos_ash || is_chromeos_lacros) {
+if (is_chromeos) {
   schema_sources_ += [
     "enterprise_device_attributes.idl",
     "enterprise_networking_attributes.idl",
@@ -83,6 +83,10 @@
     "platform_keys_internal.idl",
     "platform_keys.idl",
   ]
+
+  if (use_cups) {
+    schema_sources_ += [ "printing.idl" ]
+  }
 }
 
 if (is_chromeos_ash) {
@@ -112,10 +116,7 @@
     "wm_desks_private.idl",
   ]
   if (use_cups) {
-    schema_sources_ += [
-      "printing.idl",
-      "printing_metrics.idl",
-    ]
+    schema_sources_ += [ "printing_metrics.idl" ]
   }
 } else if (is_linux || is_chromeos || is_win) {
   schema_sources_ += [ "input_ime.json" ]
diff --git a/chrome/common/extensions/api/printing.idl b/chrome/common/extensions/api/printing.idl
index 310408d5..f301c8b 100644
--- a/chrome/common/extensions/api/printing.idl
+++ b/chrome/common/extensions/api/printing.idl
@@ -4,7 +4,7 @@
 
 // Use the <code>chrome.printing</code> API to send print jobs to printers
 // installed on Chromebook.
-[platforms=("chromeos"),
+[platforms=("chromeos", "lacros"),
 implemented_in="chrome/browser/extensions/api/printing/printing_api.h"]
 namespace printing {
   dictionary SubmitJobRequest {
diff --git a/chrome/installer/mac/signing/pipeline.py b/chrome/installer/mac/signing/pipeline.py
index 112945e76..b1adeb97 100644
--- a/chrome/installer/mac/signing/pipeline.py
+++ b/chrome/installer/mac/signing/pipeline.py
@@ -42,7 +42,7 @@
 
     command = ['lipo', '-archs', binary_path]
     output = commands.run_command_output(command)
-    output = output.strip()
+    output = output.decode('utf-8').strip()
     output = output.replace(' ', ',')
 
     return output
diff --git a/chrome/installer/mac/signing/pipeline_test.py b/chrome/installer/mac/signing/pipeline_test.py
index 5a4acab..97e80095 100644
--- a/chrome/installer/mac/signing/pipeline_test.py
+++ b/chrome/installer/mac/signing/pipeline_test.py
@@ -47,7 +47,7 @@
 
 
 def _run_command_output_lipo(b):
-    return 'x86_64,arm64'
+    return b'x86_64,arm64'
 
 
 def _read_file(p):
diff --git a/chrome/services/sharing/nearby/nearby_connections_conversions.cc b/chrome/services/sharing/nearby/nearby_connections_conversions.cc
index 9676145b..862b2db 100644
--- a/chrome/services/sharing/nearby/nearby_connections_conversions.cc
+++ b/chrome/services/sharing/nearby/nearby_connections_conversions.cc
@@ -122,6 +122,8 @@
       return mojom::Medium::kWifiDirect;
     case Medium::WEB_RTC:
       return mojom::Medium::kWebRtc;
+    case Medium::BLE_L2CAP:
+      return mojom::Medium::kBleL2Cap;
   }
 }
 
diff --git a/chrome/services/speech/speech_recognition_service_impl.cc b/chrome/services/speech/speech_recognition_service_impl.cc
index b620794..04852c3af 100644
--- a/chrome/services/speech/speech_recognition_service_impl.cc
+++ b/chrome/services/speech/speech_recognition_service_impl.cc
@@ -81,8 +81,6 @@
     BindRecognizerCallback callback) {
   // Destroy the speech recognition service if the SODA files haven't been
   // downloaded yet.
-  // TODO(crbug.com/1173135): Pass enable_soda as an options parameter instead
-  // of using media::kUseSodaForLiveCaption.
   if (base::FeatureList::IsEnabled(media::kUseSodaForLiveCaption) &&
       (!base::PathExists(binary_path_) || !base::PathExists(config_path_))) {
     speech_recognition_contexts_.Clear();
diff --git a/chrome/services/speech/speech_recognition_service_impl.h b/chrome/services/speech/speech_recognition_service_impl.h
index d8306786..d07229a 100644
--- a/chrome/services/speech/speech_recognition_service_impl.h
+++ b/chrome/services/speech/speech_recognition_service_impl.h
@@ -15,6 +15,9 @@
 
 namespace speech {
 
+// Implements the SpeechRecognitionService with SODA on-device speech
+// recognition. For debugging only, English speech recognition with web speech
+// be used as a fallback using the flag media::kUseSodaForLiveCaption.
 class SpeechRecognitionServiceImpl
     : public media::mojom::SpeechRecognitionService,
       public media::mojom::SpeechRecognitionContext {
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index b8853cf..8a545ff 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1609,7 +1609,7 @@
       "../browser/net/errorpage_browsertest.cc",
       "../browser/net/explicitly_allowed_network_ports_browsertest.cc",
       "../browser/net/explicitly_allowed_ports_switch_browsertest.cc",
-      "../browser/net/ftp_browsertest.cc",
+      "../browser/net/external_protocol_browsertest.cc",
       "../browser/net/load_timing_browsertest.cc",
       "../browser/net/log_net_log_browsertest.cc",
       "../browser/net/net_error_tab_helper_browsertest.cc",
@@ -3414,8 +3414,8 @@
         sources += [
           "../browser/chromeos/extensions/printing_metrics/print_job_finished_event_dispatcher_apitest.cc",
           "../browser/chromeos/extensions/printing_metrics/printing_metrics_apitest.cc",
-          "../browser/extensions/api/printing/fake_print_job_controller.cc",
-          "../browser/extensions/api/printing/fake_print_job_controller.h",
+          "../browser/extensions/api/printing/fake_print_job_controller_ash.cc",
+          "../browser/extensions/api/printing/fake_print_job_controller_ash.h",
           "../browser/extensions/api/printing/printing_apitest.cc",
         ]
         deps += [ "//printing:test_support" ]
@@ -4790,6 +4790,15 @@
       "chromeos/printing/fake_local_printer_chromeos.cc",
       "chromeos/printing/fake_local_printer_chromeos.h",
     ]
+
+    if (use_cups) {
+      sources += [
+        "../browser/chromeos/printing/test_cups_wrapper.cc",
+        "../browser/chromeos/printing/test_cups_wrapper.h",
+        "../browser/extensions/api/printing/fake_print_job_controller.cc",
+        "../browser/extensions/api/printing/fake_print_job_controller.h",
+      ]
+    }
   }
 
   if (is_chromeos_ash) {
@@ -6781,6 +6790,13 @@
       ]
     }
 
+    if (is_chromeos && use_cups) {
+      sources += [
+        "../browser/extensions/api/printing/printing_api_handler_unittest.cc",
+        "../browser/extensions/api/printing/printing_api_utils_unittest.cc",
+      ]
+    }
+
     if (is_chromeos_ash) {
       sources += [
         "../browser/ash/login/easy_unlock/easy_unlock_auth_attempt_unittest.cc",
diff --git a/chrome/test/base/chromeos/README.md b/chrome/test/base/chromeos/README.md
index a25bbf5..89b46eee 100644
--- a/chrome/test/base/chromeos/README.md
+++ b/chrome/test/base/chromeos/README.md
@@ -11,3 +11,7 @@
 need a fake ml service in ash. With that, they need to install the fake
 component in
 //chrome/test/base/chromeos/fake_ash_test_chrome_browser_main_extra_parts.cc.
+
+For Ash browser test, please see DemoAshRequiresLacrosTest. It will first
+start ash as wayland server, with no ash browser window opened. Then start
+Lacros browser. At the end, start Ash browser window.
diff --git a/chrome/test/base/chromeos/ash_browser_test_starter.cc b/chrome/test/base/chromeos/ash_browser_test_starter.cc
index c65a9a9c..c75be61 100644
--- a/chrome/test/base/chromeos/ash_browser_test_starter.cc
+++ b/chrome/test/base/chromeos/ash_browser_test_starter.cc
@@ -14,6 +14,9 @@
 #include "chrome/browser/ash/crosapi/browser_manager.h"
 #include "chrome/browser/ash/crosapi/browser_manager_observer.h"
 #include "chrome/browser/ash/crosapi/browser_util.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/browser_commands.h"
+#include "chrome/test/base/in_process_browser_test.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace test {
@@ -76,7 +79,7 @@
         base::PathExists(xdg_path.Append("wayland-0.lock")));
 }
 
-void AshBrowserTestStarter::StartLacros() {
+void AshBrowserTestStarter::StartLacros(InProcessBrowserTest* test_class_obj) {
   DCHECK(HasLacrosArgument());
 
   WaitForExoStarted(scoped_temp_dir_xdg_.GetPath());
@@ -90,6 +93,11 @@
   run_loop.Run();
   crosapi::BrowserManager::Get()->RemoveObserver(&observer);
   CHECK(crosapi::BrowserManager::Get()->IsRunning());
+
+  // Create a new ash browser window so browser() can work.
+  Profile* profile = ProfileManager::GetActiveUserProfile();
+  chrome::NewEmptyWindow(profile);
+  test_class_obj->SelectFirstBrowser();
 }
 
 }  // namespace test
diff --git a/chrome/test/base/chromeos/ash_browser_test_starter.h b/chrome/test/base/chromeos/ash_browser_test_starter.h
index 24865bf..ee0e343 100644
--- a/chrome/test/base/chromeos/ash_browser_test_starter.h
+++ b/chrome/test/base/chromeos/ash_browser_test_starter.h
@@ -8,6 +8,8 @@
 #include "base/files/scoped_temp_dir.h"
 #include "base/test/scoped_feature_list.h"
 
+class InProcessBrowserTest;
+
 namespace test {
 
 class AshBrowserTestStarter {
@@ -29,7 +31,7 @@
 
   // Starts Lacros and waits for it's fully started. You should call
   // this no earlier than SetUpOnMainThread().
-  void StartLacros();
+  void StartLacros(InProcessBrowserTest* test_class_obj);
 
  private:
   // This is XDG_RUNTIME_DIR.
diff --git a/chrome/test/base/chromeos/demo_ash_requires_lacros_browsertest.cc b/chrome/test/base/chromeos/demo_ash_requires_lacros_browsertest.cc
index 90c8071..4321690 100644
--- a/chrome/test/base/chromeos/demo_ash_requires_lacros_browsertest.cc
+++ b/chrome/test/base/chromeos/demo_ash_requires_lacros_browsertest.cc
@@ -3,6 +3,8 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/ash/crosapi/browser_manager.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
 #include "chrome/test/base/chromeos/ash_browser_test_starter.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "content/public/test/browser_test.h"
@@ -27,7 +29,7 @@
 
   void SetUpOnMainThread() override {
     if (ash_starter_.HasLacrosArgument()) {
-      ash_starter_.StartLacros();
+      ash_starter_.StartLacros(this);
     }
   }
 
@@ -38,6 +40,9 @@
 IN_PROC_BROWSER_TEST_F(DemoAshRequiresLacrosTest, NewTab) {
   if (ash_starter_.HasLacrosArgument()) {
     crosapi::BrowserManager::Get()->NewTab();
+    // Assert Lacros is running.
     ASSERT_TRUE(crosapi::BrowserManager::Get()->IsRunning());
+    // browser() returns an Ash browser instance.
+    ASSERT_FALSE(browser()->profile()->IsOffTheRecord());
   }
 }
diff --git a/chrome/test/data/extensions/api_test/automation/tests/desktop/focus_iframe.js b/chrome/test/data/extensions/api_test/automation/tests/desktop/focus_iframe.js
index ee69b37..f8267f0 100644
--- a/chrome/test/data/extensions/api_test/automation/tests/desktop/focus_iframe.js
+++ b/chrome/test/data/extensions/api_test/automation/tests/desktop/focus_iframe.js
@@ -11,24 +11,20 @@
       chrome.automation.getDesktop(function(rootNode) {
         // Succeed when the button inside the iframe gets focus.
         rootNode.addEventListener('focus', function(event) {
-          if (event.target.name == 'Inner')
+          if (event.target.name == 'Inner') {
             chrome.test.succeed();
-        });
-
-        // Wait for the inner frame to load, then find the button inside it
-        // and focus it.
-        rootNode.addEventListener('loadComplete', function(event) {
-          if (event.target.url.indexOf('iframe_inner.html') >= 0) {
-            chrome.automation.getFocus(function(focus) {
-              // Assert that the outer frame has focus.
-              assertTrue(focus.url.indexOf('iframe_outer') >= 0);
-
-              // Find the inner button and focus it.
-              var innerButton = focus.find({ attributes: { name: 'Inner' } });
-              innerButton.focus();
-            });
           }
         });
+
+        // Poll until we get the inner button, which is in the inner frame.
+        const id = setInterval(() => {
+          var innerButton =
+              rootNode.find({attributes: {name: 'Inner'}, role: 'button'});
+          if (innerButton) {
+            innerButton.focus();
+            clearInterval(id);
+          }
+        }, 100);
       });
     });
   },
diff --git a/chrome/test/data/extensions/api_test/automation/tests/desktop/hit_test_iframe.js b/chrome/test/data/extensions/api_test/automation/tests/desktop/hit_test_iframe.js
index bb2ec89b..0432e6e3 100644
--- a/chrome/test/data/extensions/api_test/automation/tests/desktop/hit_test_iframe.js
+++ b/chrome/test/data/extensions/api_test/automation/tests/desktop/hit_test_iframe.js
@@ -15,19 +15,19 @@
             chrome.test.succeed();
         });
 
-        // Wait for the inner frame to load, then find the button inside it
-        // and do a hit test on it.
-        rootNode.addEventListener(EventType.LOAD_COMPLETE, function(event) {
-          if (event.target.url.indexOf('iframe_inner.html') >= 0) {
-            // Find the inner button.
-            var innerButton = event.target.find(
-                { attributes: { name: 'Inner' } });
-            var bounds = innerButton.location;
-            var x = Math.floor(bounds.left + bounds.width / 2);
-            var y = Math.floor(bounds.top + bounds.height / 2);
-            rootNode.hitTest(x, y, EventType.HOVER);
+        const id = setInterval(() => {
+          var innerButton =
+              rootNode.find({attributes: {name: 'Inner'}, role: 'button'});
+          if (!innerButton) {
+            return;
           }
-        });
+
+          var bounds = innerButton.location;
+          var x = Math.floor(bounds.left + bounds.width / 2);
+          var y = Math.floor(bounds.top + bounds.height / 2);
+          rootNode.hitTest(x, y, EventType.HOVER);
+          clearInterval(id);
+        }, 100);
       });
     });
   },
diff --git a/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_row_test.js b/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_row_test.js
index 19e8433..f09f444 100644
--- a/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_row_test.js
+++ b/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_row_test.js
@@ -4,9 +4,10 @@
 
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {AcceleratorRowElement} from 'chrome://shortcut-customization/accelerator_row.js';
-import {AcceleratorInfo, AcceleratorKeys, AcceleratorState, AcceleratorType, Modifier} from 'chrome://shortcut-customization/shortcut_types.js';
+import {AcceleratorInfo, AcceleratorKeys, AcceleratorSource, AcceleratorState, AcceleratorType, Modifier} from 'chrome://shortcut-customization/shortcut_types.js';
 
-import {assertEquals} from '../../chai_assert.js';
+import {assertEquals, assertFalse, assertTrue} from '../../chai_assert.js';
+import {flushTasks} from '../../test_util.m.js';
 
 import {CreateDefaultAccelerator} from './shortcut_customization_test_util.js';
 
@@ -71,4 +72,29 @@
     assertEquals(
         'c', keys2[1].shadowRoot.querySelector('#key').textContent.trim());
   });
+
+  test('LockIcon', async () => {
+    const acceleratorInfo1 = CreateDefaultAccelerator(
+        Modifier.CONTROL | Modifier.SHIFT,
+        /*key=*/ 71,
+        /*key_display=*/ 'g');
+
+    const accelerators = [acceleratorInfo1];
+    const description = 'test shortcut';
+
+    rowElement.acceleratorInfos = accelerators;
+    rowElement.description = description;
+    rowElement.source = AcceleratorSource.kBrowser;
+    await flushTasks();
+
+    // Expected the lock icon to appear if the source is kBrowser.
+    assertFalse(
+        rowElement.shadowRoot.querySelector('#lockIconContainer').hidden);
+
+    // Update source to be kAsh, lock icon should no longer appear.
+    rowElement.source = AcceleratorSource.kAsh;
+    await flushTasks();
+    assertTrue(
+        rowElement.shadowRoot.querySelector('#lockIconContainer').hidden);
+  });
 }
\ No newline at end of file
diff --git a/chrome/test/data/webui/new_tab_page/modules/recipes_v2/module_test.js b/chrome/test/data/webui/new_tab_page/modules/recipes_v2/module_test.js
new file mode 100644
index 0000000..a474fd2
--- /dev/null
+++ b/chrome/test/data/webui/new_tab_page/modules/recipes_v2/module_test.js
@@ -0,0 +1,18 @@
+// Copyright 2021 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 {recipeTasksV2Descriptor} from 'chrome://new-tab-page/new_tab_page.js';
+
+suite('NewTabPageModulesRecipesV2ModuleTest', () => {
+  setup(() => {
+    PolymerTest.clearBody();
+  });
+
+  test('module appears on render', async () => {
+    const module = await recipeTasksV2Descriptor.initialize();
+    document.body.append(module);
+
+    assertTrue(!!module);
+  });
+});
diff --git a/chrome/test/data/webui/new_tab_page/new_tab_page_browsertest.js b/chrome/test/data/webui/new_tab_page/new_tab_page_browsertest.js
index 3d0d67379..6e30ade 100644
--- a/chrome/test/data/webui/new_tab_page/new_tab_page_browsertest.js
+++ b/chrome/test/data/webui/new_tab_page/new_tab_page_browsertest.js
@@ -298,6 +298,18 @@
 });
 
 // eslint-disable-next-line no-var
+var NewTabPageModulesRecipesV2ModuleTest = class extends NewTabPageBrowserTest {
+  /** @override */
+  get browsePreload() {
+    return 'chrome://new-tab-page/test_loader.html?module=new_tab_page/modules/recipes_v2/module_test.js';
+  }
+};
+
+TEST_F('NewTabPageModulesRecipesV2ModuleTest', 'All', function() {
+  mocha.run();
+});
+
+// eslint-disable-next-line no-var
 var NewTabPageModulesChromeCartModuleTest =
     class extends NewTabPageBrowserTest {
   /** @override */
diff --git a/chrome/test/data/webui/print_preview/destination_item_test.js b/chrome/test/data/webui/print_preview/destination_item_test.js
index 623777e..6017a19e 100644
--- a/chrome/test/data/webui/print_preview/destination_item_test.js
+++ b/chrome/test/data/webui/print_preview/destination_item_test.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {Destination, DestinationConnectionStatus, DestinationOrigin, DestinationType} from 'chrome://print/print_preview.js';
+import {Destination, DestinationConnectionStatus, DestinationOrigin, DestinationType, PrintPreviewDestinationListItemElement} from 'chrome://print/print_preview.js';
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 
@@ -49,13 +49,17 @@
   // Test that the destination is displayed correctly for the basic case of an
   // online destination with no search query.
   test(assert(destination_item_test.TestNames.Online), function() {
-    const name = item.$$('.name');
+    const name = item.shadowRoot.querySelector('.name');
     assertEquals(printerName, name.textContent);
     assertEquals('1', window.getComputedStyle(name).opacity);
-    assertEquals('', item.$$('.search-hint').textContent.trim());
-    assertEquals('', item.$$('.connection-status').textContent.trim());
-    assertTrue(item.$$('.learn-more-link').hidden);
-    assertTrue(item.$$('.extension-controlled-indicator').hidden);
+    assertEquals(
+        '', item.shadowRoot.querySelector('.search-hint').textContent.trim());
+    assertEquals(
+        '',
+        item.shadowRoot.querySelector('.connection-status').textContent.trim());
+    assertTrue(item.shadowRoot.querySelector('.learn-more-link').hidden);
+    assertTrue(item.shadowRoot.querySelector('.extension-controlled-indicator')
+                   .hidden);
   });
 
   // Test that the destination is opaque and the correct status shows up if
@@ -74,15 +78,17 @@
         printerName, DestinationConnectionStatus.OFFLINE,
         {lastAccessTime: twoMonthsAgo.getTime()});
 
-    const name = item.$$('.name');
+    const name = item.shadowRoot.querySelector('.name');
     assertEquals(printerName, name.textContent);
     assertEquals('0.4', window.getComputedStyle(name).opacity);
-    assertEquals('', item.$$('.search-hint').textContent.trim());
+    assertEquals(
+        '', item.shadowRoot.querySelector('.search-hint').textContent.trim());
     assertEquals(
         loadTimeData.getString('offlineForMonth'),
-        item.$$('.connection-status').textContent.trim());
-    assertTrue(item.$$('.learn-more-link').hidden);
-    assertTrue(item.$$('.extension-controlled-indicator').hidden);
+        item.shadowRoot.querySelector('.connection-status').textContent.trim());
+    assertTrue(item.shadowRoot.querySelector('.learn-more-link').hidden);
+    assertTrue(item.shadowRoot.querySelector('.extension-controlled-indicator')
+                   .hidden);
   });
 
   // Test that the destination is opaque and the correct status shows up if
@@ -92,15 +98,17 @@
     item.destination =
         createDestinationWithCertificateStatus(printerId, printerName, true);
 
-    const name = item.$$('.name');
+    const name = item.shadowRoot.querySelector('.name');
     assertEquals(printerName, name.textContent);
     assertEquals('0.4', window.getComputedStyle(name).opacity);
-    assertEquals('', item.$$('.search-hint').textContent.trim());
+    assertEquals(
+        '', item.shadowRoot.querySelector('.search-hint').textContent.trim());
     assertEquals(
         loadTimeData.getString('noLongerSupported'),
-        item.$$('.connection-status').textContent.trim());
-    assertFalse(item.$$('.learn-more-link').hidden);
-    assertTrue(item.$$('.extension-controlled-indicator').hidden);
+        item.shadowRoot.querySelector('.connection-status').textContent.trim());
+    assertFalse(item.shadowRoot.querySelector('.learn-more-link').hidden);
+    assertTrue(item.shadowRoot.querySelector('.extension-controlled-indicator')
+                   .hidden);
   });
 
   // Test that the destination is displayed correctly when the search query
@@ -108,7 +116,7 @@
   test(assert(destination_item_test.TestNames.QueryName), function() {
     item.searchQuery = /(Foo)/ig;
 
-    const name = item.$$('.name');
+    const name = item.shadowRoot.querySelector('.name');
     assertEquals(printerName + printerName, name.textContent);
 
     // Name should be highlighted.
@@ -117,7 +125,8 @@
     assertEquals('Foo', searchHits[0].textContent);
 
     // No hints.
-    assertEquals('', item.$$('.search-hint').textContent.trim());
+    assertEquals(
+        '', item.shadowRoot.querySelector('.search-hint').textContent.trim());
   });
 
   // Test that the destination is displayed correctly when the search query
@@ -133,12 +142,12 @@
     item.searchQuery = /(ABC)/ig;
 
     // No highlighting on name.
-    const name = item.$$('.name');
+    const name = item.shadowRoot.querySelector('.name');
     assertEquals(printerName, name.textContent);
     assertEquals(0, name.querySelectorAll('.search-highlight-hit').length);
 
     // Search hint should be have the description and be highlighted.
-    const hint = item.$$('.search-hint');
+    const hint = item.shadowRoot.querySelector('.search-hint');
     assertTrue(hint.textContent.includes(params.description));
     assertFalse(hint.textContent.includes(params.location));
     const searchHits = hint.querySelectorAll('.search-highlight-hit');
diff --git a/chrome/test/data/webui/print_preview/destination_item_test_cros.js b/chrome/test/data/webui/print_preview/destination_item_test_cros.js
index b76917ea..40ea625 100644
--- a/chrome/test/data/webui/print_preview/destination_item_test_cros.js
+++ b/chrome/test/data/webui/print_preview/destination_item_test_cros.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {Destination, DestinationConnectionStatus, DestinationOrigin, DestinationType, NativeLayerCros, NativeLayerCrosImpl, PrinterState, PrinterStatusReason, PrinterStatusSeverity} from 'chrome://print/print_preview.js';
+import {Destination, DestinationConnectionStatus, DestinationOrigin, DestinationType, NativeLayerCros, NativeLayerCrosImpl, PrinterState, PrinterStatusReason, PrinterStatusSeverity, PrintPreviewDestinationListItemElement} from 'chrome://print/print_preview.js';
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
@@ -70,7 +70,7 @@
   test(
       assert(destination_item_test_cros.TestNames.NewStatusUpdatesIcon),
       function() {
-        const icon = listItem.$$('iron-icon');
+        const icon = listItem.shadowRoot.querySelector('iron-icon');
         assertEquals('print-preview:printer-status-grey', icon.icon);
 
         return listItem.destination.requestPrinterStatus().then(() => {
@@ -82,7 +82,7 @@
       assert(
           destination_item_test_cros.TestNames.ChangingDestinationUpdatesIcon),
       function() {
-        const icon = listItem.$$('iron-icon');
+        const icon = listItem.shadowRoot.querySelector('iron-icon');
         assertEquals('print-preview:printer-status-grey', icon.icon);
 
         listItem.destination = new Destination(
@@ -102,7 +102,7 @@
       assert(
           destination_item_test_cros.TestNames.OnlyUpdateMatchingDestination),
       function() {
-        const icon = listItem.$$('iron-icon');
+        const icon = listItem.shadowRoot.querySelector('iron-icon');
         assertEquals('print-preview:printer-status-grey', icon.icon);
         const firstDestinationStatusRequestPromise =
             listItem.destination.requestPrinterStatus();
diff --git a/chrome/test/data/webui/print_preview/destination_list_test.js b/chrome/test/data/webui/print_preview/destination_list_test.js
index 7eb2031..ed937be 100644
--- a/chrome/test/data/webui/print_preview/destination_list_test.js
+++ b/chrome/test/data/webui/print_preview/destination_list_test.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {Destination, DestinationConnectionStatus, DestinationOrigin, DestinationType} from 'chrome://print/print_preview.js';
+import {Destination, DestinationConnectionStatus, DestinationOrigin, DestinationType, PrintPreviewDestinationListElement} from 'chrome://print/print_preview.js';
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {keyEventOn} from 'chrome://resources/polymer/v3_0/iron-test-helpers/mock-interactions.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
@@ -65,7 +65,8 @@
   test(assert(destination_list_test.TestNames.FilterDestinations), function() {
     const items =
         list.shadowRoot.querySelectorAll('print-preview-destination-list-item');
-    const noMatchHint = list.$$('.no-destinations-message');
+    const noMatchHint =
+        list.shadowRoot.querySelector('.no-destinations-message');
     const ironList = list.$.list;
 
     // Query is initialized to null. All items are shown and the hint is
diff --git a/chrome/test/data/webui/print_preview/destination_select_test.js b/chrome/test/data/webui/print_preview/destination_select_test.js
index 814674a..1233c86 100644
--- a/chrome/test/data/webui/print_preview/destination_select_test.js
+++ b/chrome/test/data/webui/print_preview/destination_select_test.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {Destination, DestinationConnectionStatus, DestinationOrigin, DestinationType, getSelectDropdownBackground} from 'chrome://print/print_preview.js';
+import {Destination, DestinationConnectionStatus, DestinationOrigin, DestinationType, getSelectDropdownBackground, PrintPreviewDestinationSelectElement} from 'chrome://print/print_preview.js';
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {Base} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
@@ -44,7 +44,6 @@
         /** @type {!PrintPreviewDestinationSelectElement} */ (
             document.createElement('print-preview-destination-select'));
     destinationSelect.activeUser = account;
-    destinationSelect.appKioskMode = false;
     destinationSelect.disabled = false;
     destinationSelect.loaded = false;
     destinationSelect.noDestinations = false;
@@ -97,7 +96,7 @@
     destinationSelect.destination = destination;
     destinationSelect.updateDestination();
     destinationSelect.loaded = true;
-    const selectEl = destinationSelect.$$('.md-select');
+    const selectEl = destinationSelect.shadowRoot.querySelector('.md-select');
     compareIcon(selectEl, 'print');
 
     return selectOption(destinationSelect, recentDestinationList[1].key)
@@ -169,16 +168,21 @@
       offline: 'offline',
     });
 
-    assertFalse(destinationSelect.$$('.throbber-container').hidden);
-    assertTrue(destinationSelect.$$('.md-select').hidden);
+    assertFalse(
+        destinationSelect.shadowRoot.querySelector('.throbber-container')
+            .hidden);
+    assertTrue(destinationSelect.shadowRoot.querySelector('.md-select').hidden);
 
     destinationSelect.loaded = true;
-    assertTrue(destinationSelect.$$('.throbber-container').hidden);
-    assertFalse(destinationSelect.$$('.md-select').hidden);
+    assertTrue(destinationSelect.shadowRoot.querySelector('.throbber-container')
+                   .hidden);
+    assertFalse(
+        destinationSelect.shadowRoot.querySelector('.md-select').hidden);
 
-    const additionalInfoEl =
-        destinationSelect.$$('.destination-additional-info');
-    const statusEl = destinationSelect.$$('.destination-status');
+    const additionalInfoEl = destinationSelect.shadowRoot.querySelector(
+        '.destination-additional-info');
+    const statusEl =
+        destinationSelect.shadowRoot.querySelector('.destination-status');
 
     destinationSelect.destination = recentDestinationList[1];
     destinationSelect.updateDestination();
diff --git a/chrome/test/data/webui/print_preview/destination_select_test_cros.js b/chrome/test/data/webui/print_preview/destination_select_test_cros.js
index 3561c32..b998db3 100644
--- a/chrome/test/data/webui/print_preview/destination_select_test_cros.js
+++ b/chrome/test/data/webui/print_preview/destination_select_test_cros.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {Destination, DestinationConnectionStatus, DestinationOrigin, DestinationType, NativeLayer, NativeLayerCros, NativeLayerCrosImpl, NativeLayerImpl, PrinterStatus, PrinterStatusReason, PrinterStatusSeverity, PrintPreviewDestinationDropdownCrosElement, SAVE_TO_DRIVE_CROS_DESTINATION_KEY} from 'chrome://print/print_preview.js';
+import {Destination, DestinationConnectionStatus, DestinationOrigin, DestinationType, NativeLayer, NativeLayerCros, NativeLayerCrosImpl, NativeLayerImpl, PrinterStatus, PrinterStatusReason, PrinterStatusSeverity, PrintPreviewDestinationDropdownCrosElement, PrintPreviewDestinationSelectCrosElement, SAVE_TO_DRIVE_CROS_DESTINATION_KEY} from 'chrome://print/print_preview.js';
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {Base, flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
@@ -162,7 +162,6 @@
     destinationSelect =
         /** @type {!PrintPreviewDestinationSelectCrosElement} */
         (document.createElement('print-preview-destination-select-cros'));
-    destinationSelect.statusRequestedMap = new Map();
     document.body.appendChild(destinationSelect);
   });
 
@@ -213,7 +212,7 @@
             .then(() => {
               const dropdown =
                   /** @type {!PrintPreviewDestinationDropdownCrosElement} */ (
-                      destinationSelect.$$('#dropdown'));
+                      destinationSelect.shadowRoot.querySelector('#dropdown'));
 
               // Empty printer status.
               assertEquals(
@@ -296,8 +295,8 @@
       });
 
   test(assert(printer_status_test_cros.TestNames.HiddenStatusText), function() {
-    const destinationStatus =
-        destinationSelect.$$('.destination-additional-info');
+    const destinationStatus = destinationSelect.shadowRoot.querySelector(
+        '.destination-additional-info');
     return waitBeforeNextRender(destinationSelect)
         .then(() => {
           const destinationWithoutErrorStatus =
@@ -317,7 +316,8 @@
           ];
 
           const destinationEulaWrapper =
-              destinationSelect.$$('#destinationEulaWrapper');
+              destinationSelect.shadowRoot.querySelector(
+                  '#destinationEulaWrapper');
 
           destinationSelect.destination = cloudPrintDestination;
           assertFalse(destinationStatus.hidden);
@@ -369,7 +369,7 @@
         saveToDrive,
         saveAsPdf,
       ];
-      const dropdown = destinationSelect.$$('#dropdown');
+      const dropdown = destinationSelect.shadowRoot.querySelector('#dropdown');
 
       destinationSelect.destination = localCrosPrinter;
       destinationSelect.updateDestination();
@@ -425,7 +425,7 @@
 
         const dropdown =
             /** @type {!PrintPreviewDestinationDropdownCrosElement} */ (
-                destinationSelect.$$('#dropdown'));
+                destinationSelect.shadowRoot.querySelector('#dropdown'));
         return whenStatusRequestsDonePromise
             .then(() => {
               assertEquals(
diff --git a/chromecast/browser/cast_content_browser_client.cc b/chromecast/browser/cast_content_browser_client.cc
index 2dc1db7..c23e2e3 100644
--- a/chromecast/browser/cast_content_browser_client.cc
+++ b/chromecast/browser/cast_content_browser_client.cc
@@ -160,9 +160,6 @@
       cast_feature_list_creator_(cast_feature_list_creator) {
   cast_feature_list_creator_->SetExtraEnableFeatures({
     ::media::kInternalMediaSession, features::kNetworkServiceInProcess,
-        // TODO(b/161486194): Can be removed when it's enabled by default in
-        // Chrome.
-        features::kWebAssemblySimd,
 #if defined(OS_ANDROID) && BUILDFLAG(ENABLE_VIDEO_CAPTURE_SERVICE)
         features::kMojoVideoCapture,
 #endif
diff --git a/chromeos/network/onc/onc_merger.cc b/chromeos/network/onc/onc_merger.cc
index b39ee903..375a28c 100644
--- a/chromeos/network/onc/onc_merger.cc
+++ b/chromeos/network/onc/onc_merger.cc
@@ -21,8 +21,6 @@
 namespace onc {
 namespace {
 
-typedef std::unique_ptr<base::DictionaryValue> DictionaryPtr;
-
 // Returns true if the field is the identifier of a configuration, i.e. the GUID
 // of a network or a certificate.
 bool IsIdentifierField(const OncValueSignature& value_signature,
@@ -48,97 +46,76 @@
 
 // Inserts |true| at every field name in |result| that is recommended in
 // |policy|.
-void MarkRecommendedFieldnames(const base::DictionaryValue& policy,
-                               base::Value* result) {
+void MarkRecommendedFieldnames(const base::Value& policy, base::Value* result) {
   const base::Value* recommended_value =
       policy.FindListKey(::onc::kRecommended);
   if (!recommended_value)
     return;
   for (const auto& value : recommended_value->GetList()) {
     if (value.is_string())
-      result->SetKey(value.GetString(), base::Value(true));
+      result->SetBoolKey(value.GetString(), true);
   }
 }
 
 // Returns a dictionary which contains |true| at each path that is editable by
 // the user. No other fields are set.
-DictionaryPtr GetEditableFlags(const base::DictionaryValue& policy) {
-  DictionaryPtr result_editable(new base::DictionaryValue);
-  MarkRecommendedFieldnames(policy, result_editable.get());
+base::Value GetEditableFlags(const base::Value& policy) {
+  base::Value result(base::Value::Type::DICTIONARY);
+  MarkRecommendedFieldnames(policy, &result);
 
   // Recurse into nested dictionaries.
-  for (base::DictionaryValue::Iterator it(policy); !it.IsAtEnd();
-       it.Advance()) {
-    const base::DictionaryValue* child_policy = nullptr;
-    if (it.key() == ::onc::kRecommended ||
-        !it.value().GetAsDictionary(&child_policy)) {
+  for (auto iter : policy.DictItems()) {
+    if (iter.first == ::onc::kRecommended || !iter.second.is_dict())
       continue;
-    }
-
-    result_editable->SetKey(it.key(), base::Value::FromUniquePtrValue(
-                                          GetEditableFlags(*child_policy)));
+    result.SetKey(iter.first, GetEditableFlags(iter.second));
   }
-  return result_editable;
+  return result;
 }
 
-// This is the base class for merging a list of DictionaryValues in
-// parallel. See MergeDictionaries function.
+// This is the base class for merging a list of Values in parallel. See
+// MergeDictionaries function.
 class MergeListOfDictionaries {
  public:
-  typedef std::vector<const base::DictionaryValue*> DictPtrs;
+  using ValuePtrs = std::vector<const base::Value*>;
 
   MergeListOfDictionaries() = default;
 
   virtual ~MergeListOfDictionaries() = default;
 
-  // For each path in any of the dictionaries |dicts|, the function
-  // MergeListOfValues is called with the list of values that are located at
-  // that path in each of the dictionaries. This function returns a new
-  // dictionary containing all results of MergeListOfValues at the respective
+  // For each path in any of the dictionaries |dicts|, either MergeListOfValues
+  // or MergeNestedDictionaries is called with the list of values that are
+  // located at that path in each of the dictionaries. This function returns a
+  // new dictionary containing all results of those calls at the respective
   // paths. The resulting dictionary doesn't contain empty dictionaries.
-  std::unique_ptr<base::Value> MergeDictionaries(const DictPtrs& dicts) {
-    auto result = std::make_unique<base::Value>(base::Value::Type::DICTIONARY);
+  base::Value MergeDictionaries(const ValuePtrs& dicts) {
+    base::Value result(base::Value::Type::DICTIONARY);
     std::set<std::string> visited;
-    for (DictPtrs::const_iterator it_outer = dicts.begin();
-         it_outer != dicts.end(); ++it_outer) {
-      if (!*it_outer)
+    for (const base::Value* dict_outer : dicts) {
+      if (!dict_outer || !dict_outer->is_dict())
         continue;
 
-      for (base::DictionaryValue::Iterator field(**it_outer); !field.IsAtEnd();
-           field.Advance()) {
-        const std::string& key = field.key();
+      for (auto field : dict_outer->DictItems()) {
+        const std::string& key = field.first;
         if (key == ::onc::kRecommended || !visited.insert(key).second)
           continue;
 
-        std::unique_ptr<base::Value> merged_value;
-        if (field.value().is_dict()) {
-          DictPtrs nested_dicts;
-          for (DictPtrs::const_iterator it_inner = dicts.begin();
-               it_inner != dicts.end(); ++it_inner) {
-            const base::DictionaryValue* nested_dict = nullptr;
-            if (*it_inner)
-              (*it_inner)->GetDictionaryWithoutPathExpansion(key, &nested_dict);
-            nested_dicts.push_back(nested_dict);
-          }
-          std::unique_ptr<base::Value> merged_dict =
-              MergeNestedDictionaries(key, nested_dicts);
-          if (!merged_dict->DictEmpty())
-            merged_value = std::move(merged_dict);
-        } else {
-          std::vector<const base::Value*> values;
-          for (DictPtrs::const_iterator it_inner = dicts.begin();
-               it_inner != dicts.end(); ++it_inner) {
-            const base::Value* value = nullptr;
-            if (*it_inner)
-              value = (*it_inner)->FindKey(key);
-            values.push_back(value);
-          }
-          merged_value = MergeListOfValues(key, values);
+        ValuePtrs values_for_key;
+        for (const base::Value* dict_inner : dicts) {
+          const base::Value* value = nullptr;
+          if (dict_inner && dict_inner->is_dict())
+            value = dict_inner->FindKey(key);
+          values_for_key.push_back(value);
         }
-
-        if (merged_value)
-          result->SetKey(
-              key, base::Value::FromUniquePtrValue(std::move(merged_value)));
+        if (field.second.is_dict()) {
+          base::Value merged_dict =
+              MergeNestedDictionaries(key, values_for_key);
+          if (!merged_dict.DictEmpty())
+            result.SetKey(key, std::move(merged_dict));
+        } else {
+          base::Value merged_value = MergeListOfValues(key, values_for_key);
+          if (!merged_value.is_none())
+            result.SetKey(key, std::move(merged_value));
+        }
       }
     }
     return result;
@@ -148,14 +125,12 @@
   // This function is called by MergeDictionaries for each list of values that
   // are located at the same path in each of the dictionaries. The order of the
   // values is the same as of the given dictionaries |dicts|. If a dictionary
-  // doesn't contain a path then it's value is null.
-  virtual std::unique_ptr<base::Value> MergeListOfValues(
-      const std::string& key,
-      const std::vector<const base::Value*>& values) = 0;
+  // doesn't contain a path then it's value is nullptr.
+  virtual base::Value MergeListOfValues(const std::string& key,
+                                        const ValuePtrs& values) = 0;
 
-  virtual std::unique_ptr<base::Value> MergeNestedDictionaries(
-      const std::string& key,
-      const DictPtrs& dicts) {
+  virtual base::Value MergeNestedDictionaries(const std::string& key,
+                                              const ValuePtrs& dicts) {
     return MergeDictionaries(dicts);
   }
 
@@ -182,31 +157,32 @@
   // MergeValues is called. Its results are collected in a new dictionary which
   // is then returned. The resulting dictionary never contains empty
   // dictionaries.
-  std::unique_ptr<base::Value> MergeDictionaries(
-      const base::DictionaryValue* user_policy,
-      const base::DictionaryValue* device_policy,
-      const base::DictionaryValue* user_settings,
-      const base::DictionaryValue* shared_settings,
-      const base::DictionaryValue* active_settings) {
+  base::Value MergeDictionaries(const base::Value* user_policy,
+                                const base::Value* device_policy,
+                                const base::Value* user_settings,
+                                const base::Value* shared_settings,
+                                const base::Value* active_settings) {
     hasUserPolicy_ = (user_policy != nullptr);
     hasDevicePolicy_ = (device_policy != nullptr);
 
-    DictionaryPtr user_editable;
+    // Note: The call to MergeListOfDictionaries::MergeDictionaries below will
+    // ignore Value entries that are not Type::DICTIONARY.
+    base::Value user_editable;
     if (user_policy)
       user_editable = GetEditableFlags(*user_policy);
 
-    DictionaryPtr device_editable;
+    base::Value device_editable;
     if (device_policy)
       device_editable = GetEditableFlags(*device_policy);
 
-    std::vector<const base::DictionaryValue*> dicts(kLastIndex, nullptr);
+    ValuePtrs dicts(kLastIndex, nullptr);
     dicts[kUserPolicyIndex] = user_policy;
     dicts[kDevicePolicyIndex] = device_policy;
     dicts[kUserSettingsIndex] = user_settings;
     dicts[kSharedSettingsIndex] = shared_settings;
     dicts[kActiveSettingsIndex] = active_settings;
-    dicts[kUserEditableIndex] = user_editable.get();
-    dicts[kDeviceEditableIndex] = device_editable.get();
+    dicts[kUserEditableIndex] = &user_editable;
+    dicts[kDeviceEditableIndex] = &device_editable;
     return MergeListOfDictionaries::MergeDictionaries(dicts);
   }
 
@@ -214,9 +190,8 @@
   // This function is called by MergeDictionaries for each list of values that
   // are located at the same path in each of the dictionaries. Implementations
   // can use the Has*Policy functions.
-  virtual std::unique_ptr<base::Value> MergeValues(
-      const std::string& key,
-      const ValueParams& values) = 0;
+  virtual base::Value MergeValues(const std::string& key,
+                                  const ValueParams& values) = 0;
 
   // Whether a user policy was provided.
   bool HasUserPolicy() { return hasUserPolicy_; }
@@ -225,9 +200,8 @@
   bool HasDevicePolicy() { return hasDevicePolicy_; }
 
   // MergeListOfDictionaries override.
-  std::unique_ptr<base::Value> MergeListOfValues(
-      const std::string& key,
-      const std::vector<const base::Value*>& values) override {
+  base::Value MergeListOfValues(const std::string& key,
+                                const ValuePtrs& values) override {
     bool user_editable = !HasUserPolicy();
     if (values[kUserEditableIndex] && values[kUserEditableIndex]->is_bool())
       user_editable = values[kUserEditableIndex]->GetBool();
@@ -279,9 +253,9 @@
   // and set |which| to ::onc::kAugmentationUserPolicy, which means that the
   // user policy didn't set a value but also didn't recommend it, thus enforcing
   // the empty value.
-  std::unique_ptr<base::Value> MergeValues(const std::string& key,
-                                           const ValueParams& values,
-                                           std::string* which) {
+  base::Value MergeValues(const std::string& key,
+                          const ValueParams& values,
+                          std::string* which) {
     const base::Value* result = nullptr;
     which->clear();
     if (!values.user_editable) {
@@ -306,14 +280,12 @@
       // Can be reached if the current field is recommended, but none of the
       // dictionaries contained a value for it.
     }
-    if (result)
-      return base::Value::ToUniquePtrValue(result->Clone());
-    return nullptr;
+    return result ? result->Clone() : base::Value();
   }
 
   // MergeSettingsAndPolicies override.
-  std::unique_ptr<base::Value> MergeValues(const std::string& key,
-                                           const ValueParams& values) override {
+  base::Value MergeValues(const std::string& key,
+                          const ValueParams& values) override {
     std::string which;
     return MergeValues(key, values, &which);
   }
@@ -349,13 +321,12 @@
  public:
   MergeToAugmented() = default;
 
-  std::unique_ptr<base::Value> MergeDictionaries(
-      const OncValueSignature& signature,
-      const base::DictionaryValue* user_policy,
-      const base::DictionaryValue* device_policy,
-      const base::DictionaryValue* user_settings,
-      const base::DictionaryValue* shared_settings,
-      const base::DictionaryValue* active_settings) {
+  base::Value MergeDictionaries(const OncValueSignature& signature,
+                                const base::Value* user_policy,
+                                const base::Value* device_policy,
+                                const base::Value* user_settings,
+                                const base::Value* shared_settings,
+                                const base::Value* active_settings) {
     signature_ = &signature;
     return MergeToEffective::MergeDictionaries(user_policy, device_policy,
                                                user_settings, shared_settings,
@@ -364,8 +335,8 @@
 
  protected:
   // MergeSettingsAndPolicies override.
-  std::unique_ptr<base::Value> MergeValues(const std::string& key,
-                                           const ValueParams& values) override {
+  base::Value MergeValues(const std::string& key,
+                          const ValueParams& values) override {
     const OncFieldSignature* field = nullptr;
     if (signature_)
       field = GetFieldSignature(*signature_, key);
@@ -375,45 +346,44 @@
       // controlled by policy. Return the plain active value instead of an
       // augmented dictionary.
       if (values.active_setting)
-        return base::Value::ToUniquePtrValue(values.active_setting->Clone());
-      return nullptr;
+        return values.active_setting->Clone();
+      return base::Value();
     }
 
     // This field is part of the provided ONCSignature, thus it can be
     // controlled by policy.
     std::string which_effective;
-    std::unique_ptr<base::Value> effective_value =
+    base::Value effective_value =
         MergeToEffective::MergeValues(key, values, &which_effective);
 
     if (IsReadOnlyField(*signature_, key)) {
       // Don't augment read-only fields (GUID and Type).
-      if (effective_value) {
+      if (!effective_value.is_none()) {
         // DCHECK that all provided fields are identical.
-        DCHECK(AllPresentValuesEqual(values, *effective_value))
+        DCHECK(AllPresentValuesEqual(values, effective_value))
             << "Values do not match: " << key
-            << " Effective: " << *effective_value;
+            << " Effective: " << effective_value;
         // Return the un-augmented field.
         return effective_value;
       }
       if (values.active_setting) {
         // Unmanaged networks have assigned (active) values.
-        return base::Value::ToUniquePtrValue(values.active_setting->Clone());
+        return values.active_setting->Clone();
       }
       LOG(ERROR) << "Field has no effective value: " << key;
-      return nullptr;
+      return base::Value();
     }
 
-    std::unique_ptr<base::DictionaryValue> augmented_value(
-        new base::DictionaryValue);
+    base::Value augmented_value(base::Value::Type::DICTIONARY);
 
     if (values.active_setting) {
-      augmented_value->SetKey(::onc::kAugmentationActiveSetting,
-                              values.active_setting->Clone());
+      augmented_value.SetKey(::onc::kAugmentationActiveSetting,
+                             values.active_setting->Clone());
     }
 
     if (!which_effective.empty()) {
-      augmented_value->SetKey(::onc::kAugmentationEffectiveSetting,
-                              base::Value(which_effective));
+      augmented_value.SetKey(::onc::kAugmentationEffectiveSetting,
+                             base::Value(which_effective));
     }
 
     // Prevent credentials from being forwarded in cleartext to UI.
@@ -424,56 +394,55 @@
     if (is_credential) {
       // Set |kFakeCredential| to notify UI that credential is saved.
       if (values.user_policy) {
-        augmented_value->SetKey(
+        augmented_value.SetKey(
             ::onc::kAugmentationUserPolicy,
             base::Value(chromeos::policy_util::kFakeCredential));
       }
       if (values.device_policy) {
-        augmented_value->SetKey(
+        augmented_value.SetKey(
             ::onc::kAugmentationDevicePolicy,
             base::Value(chromeos::policy_util::kFakeCredential));
       }
       if (values.active_setting) {
-        augmented_value->SetKey(
+        augmented_value.SetKey(
             ::onc::kAugmentationActiveSetting,
             base::Value(chromeos::policy_util::kFakeCredential));
       }
     } else {
       if (values.user_policy) {
-        augmented_value->SetKey(::onc::kAugmentationUserPolicy,
-                                values.user_policy->Clone());
+        augmented_value.SetKey(::onc::kAugmentationUserPolicy,
+                               values.user_policy->Clone());
       }
       if (values.device_policy) {
-        augmented_value->SetKey(::onc::kAugmentationDevicePolicy,
-                                values.device_policy->Clone());
+        augmented_value.SetKey(::onc::kAugmentationDevicePolicy,
+                               values.device_policy->Clone());
       }
     }
     if (values.user_setting) {
-      augmented_value->SetKey(::onc::kAugmentationUserSetting,
-                              values.user_setting->Clone());
+      augmented_value.SetKey(::onc::kAugmentationUserSetting,
+                             values.user_setting->Clone());
     }
     if (values.shared_setting) {
-      augmented_value->SetKey(::onc::kAugmentationSharedSetting,
-                              values.shared_setting->Clone());
+      augmented_value.SetKey(::onc::kAugmentationSharedSetting,
+                             values.shared_setting->Clone());
     }
     if (HasUserPolicy() && values.user_editable) {
-      augmented_value->SetKey(::onc::kAugmentationUserEditable,
-                              base::Value(true));
+      augmented_value.SetKey(::onc::kAugmentationUserEditable,
+                             base::Value(true));
     }
     if (HasDevicePolicy() && values.device_editable) {
-      augmented_value->SetKey(::onc::kAugmentationDeviceEditable,
-                              base::Value(true));
+      augmented_value.SetKey(::onc::kAugmentationDeviceEditable,
+                             base::Value(true));
     }
-    if (augmented_value->DictEmpty())
-      augmented_value.reset();
-    return std::move(augmented_value);
+    if (!augmented_value.DictEmpty())
+      return augmented_value;
+
+    return base::Value();
   }
 
   // MergeListOfDictionaries override.
-  std::unique_ptr<base::Value> MergeNestedDictionaries(
-      const std::string& key,
-      const DictPtrs& dicts) override {
-    std::unique_ptr<base::Value> result;
+  base::Value MergeNestedDictionaries(const std::string& key,
+                                      const ValuePtrs& dicts) override {
     if (signature_) {
       const OncValueSignature* enclosing_signature = signature_;
       signature_ = nullptr;
@@ -482,13 +451,12 @@
           GetFieldSignature(*enclosing_signature, key);
       if (field)
         signature_ = field->value_signature;
-      result = MergeToEffective::MergeNestedDictionaries(key, dicts);
-
+      base::Value result =
+          MergeToEffective::MergeNestedDictionaries(key, dicts);
       signature_ = enclosing_signature;
-    } else {
-      result = MergeToEffective::MergeNestedDictionaries(key, dicts);
+      return result;
     }
-    return result;
+    return MergeToEffective::MergeNestedDictionaries(key, dicts);
   }
 
  private:
@@ -498,23 +466,23 @@
 
 }  // namespace
 
-std::unique_ptr<base::Value> MergeSettingsAndPoliciesToEffective(
-    const base::DictionaryValue* user_policy,
-    const base::DictionaryValue* device_policy,
-    const base::DictionaryValue* user_settings,
-    const base::DictionaryValue* shared_settings) {
+base::Value MergeSettingsAndPoliciesToEffective(
+    const base::Value* user_policy,
+    const base::Value* device_policy,
+    const base::Value* user_settings,
+    const base::Value* shared_settings) {
   MergeToEffective merger;
   return merger.MergeDictionaries(user_policy, device_policy, user_settings,
                                   shared_settings, nullptr);
 }
 
-std::unique_ptr<base::Value> MergeSettingsAndPoliciesToAugmented(
+base::Value MergeSettingsAndPoliciesToAugmented(
     const OncValueSignature& signature,
-    const base::DictionaryValue* user_policy,
-    const base::DictionaryValue* device_policy,
-    const base::DictionaryValue* user_settings,
-    const base::DictionaryValue* shared_settings,
-    const base::DictionaryValue* active_settings) {
+    const base::Value* user_policy,
+    const base::Value* device_policy,
+    const base::Value* user_settings,
+    const base::Value* shared_settings,
+    const base::Value* active_settings) {
   MergeToAugmented merger;
   return merger.MergeDictionaries(signature, user_policy, device_policy,
                                   user_settings, shared_settings,
diff --git a/chromeos/network/onc/onc_merger.h b/chromeos/network/onc/onc_merger.h
index ac16f0a..7dfedadb 100644
--- a/chromeos/network/onc/onc_merger.h
+++ b/chromeos/network/onc/onc_merger.h
@@ -5,12 +5,9 @@
 #ifndef CHROMEOS_NETWORK_ONC_ONC_MERGER_H_
 #define CHROMEOS_NETWORK_ONC_ONC_MERGER_H_
 
-#include <memory>
-
 #include "base/component_export.h"
 
 namespace base {
-class DictionaryValue;
 class Value;
 }
 
@@ -21,19 +18,19 @@
 
 // Merges the given |user_settings| and |shared_settings| settings with the
 // given |user_policy| and |device_policy| settings. Each can be omitted by
-// providing a NULL pointer. Each dictionary has to be part of a valid ONC
-// dictionary. They don't have to describe top-level ONC but should refer to the
-// same section in ONC. |user_settings| and |shared_settings| should not contain
+// providing nullptr. Each dictionary has to be part of a valid ONC dictionary.
+// They don't have to describe top-level ONC but should refer to the same
+// section in ONC. |user_settings| and |shared_settings| should not contain
 // kRecommended fields. The resulting dictionary is valid ONC but may contain
 // dispensable fields (e.g. in a network with type: "WiFi", the field "VPN" is
 // dispensable) that can be removed by the caller using the ONC normalizer. ONC
 // conformance of the arguments is not checked. Use ONC validator for that.
 COMPONENT_EXPORT(CHROMEOS_NETWORK)
-std::unique_ptr<base::Value> MergeSettingsAndPoliciesToEffective(
-    const base::DictionaryValue* user_policy,
-    const base::DictionaryValue* device_policy,
-    const base::DictionaryValue* user_settings,
-    const base::DictionaryValue* shared_settings);
+base::Value MergeSettingsAndPoliciesToEffective(
+    const base::Value* user_policy,
+    const base::Value* device_policy,
+    const base::Value* user_settings,
+    const base::Value* shared_settings);
 
 // Like MergeSettingsWithPoliciesToEffective but creates one dictionary in place
 // of each field that exists in any of the argument dictionaries. Each of these
@@ -43,13 +40,13 @@
 // overrides all other values. Credentials from policies are not written to the
 // result.
 COMPONENT_EXPORT(CHROMEOS_NETWORK)
-std::unique_ptr<base::Value> MergeSettingsAndPoliciesToAugmented(
+base::Value MergeSettingsAndPoliciesToAugmented(
     const OncValueSignature& signature,
-    const base::DictionaryValue* user_policy,
-    const base::DictionaryValue* device_policy,
-    const base::DictionaryValue* user_settings,
-    const base::DictionaryValue* shared_settings,
-    const base::DictionaryValue* active_settings);
+    const base::Value* user_policy,
+    const base::Value* device_policy,
+    const base::Value* user_settings,
+    const base::Value* shared_settings,
+    const base::Value* active_settings);
 
 }  // namespace onc
 }  // namespace chromeos
diff --git a/chromeos/network/onc/onc_merger_unittest.cc b/chromeos/network/onc/onc_merger_unittest.cc
index e39aee6..ce5885a 100644
--- a/chromeos/network/onc/onc_merger_unittest.cc
+++ b/chromeos/network/onc/onc_merger_unittest.cc
@@ -65,92 +65,89 @@
 };
 
 TEST_F(ONCMergerTest, MandatoryValueOverwritesUserValue) {
-  std::unique_ptr<base::Value> merged(MergeSettingsAndPoliciesToEffective(
-      policy_.get(), nullptr, user_.get(), nullptr));
-  EXPECT_TRUE(HaveSameValueAt(*merged, *policy_, "Type"));
-  EXPECT_TRUE(HaveSameValueAt(*merged, *policy_, "StaticIPConfig"));
+  base::Value merged(MergeSettingsAndPoliciesToEffective(policy_.get(), nullptr,
+                                                         user_.get(), nullptr));
+  EXPECT_TRUE(HaveSameValueAt(merged, *policy_, "Type"));
+  EXPECT_TRUE(HaveSameValueAt(merged, *policy_, "StaticIPConfig"));
 }
 
 TEST_F(ONCMergerTest, MandatoryValueAndNoUserValue) {
-  std::unique_ptr<base::Value> merged(MergeSettingsAndPoliciesToEffective(
-      policy_.get(), nullptr, user_.get(), nullptr));
-  EXPECT_TRUE(HaveSameValueAt(*merged, *policy_, "GUID"));
-  EXPECT_TRUE(HaveSameValueAt(*merged, *policy_, "VPN.OpenVPN.Username"));
+  base::Value merged(MergeSettingsAndPoliciesToEffective(policy_.get(), nullptr,
+                                                         user_.get(), nullptr));
+  EXPECT_TRUE(HaveSameValueAt(merged, *policy_, "GUID"));
+  EXPECT_TRUE(HaveSameValueAt(merged, *policy_, "VPN.OpenVPN.Username"));
 }
 
 TEST_F(ONCMergerTest, MandatoryDictionaryAndNoUserValue) {
-  std::unique_ptr<base::Value> merged(MergeSettingsAndPoliciesToEffective(
-      policy_.get(), nullptr, user_.get(), nullptr));
-  EXPECT_TRUE(HaveSameValueAt(*merged, *policy_without_recommended_,
+  base::Value merged(MergeSettingsAndPoliciesToEffective(policy_.get(), nullptr,
+                                                         user_.get(), nullptr));
+  EXPECT_TRUE(HaveSameValueAt(merged, *policy_without_recommended_,
                               "VPN.OpenVPN.ClientCertPattern"));
 }
 
 TEST_F(ONCMergerTest, UserValueOverwritesRecommendedValue) {
-  std::unique_ptr<base::Value> merged(MergeSettingsAndPoliciesToEffective(
-      policy_.get(), nullptr, user_.get(), nullptr));
-  EXPECT_TRUE(HaveSameValueAt(*merged, *user_, "VPN.Host"));
+  base::Value merged(MergeSettingsAndPoliciesToEffective(policy_.get(), nullptr,
+                                                         user_.get(), nullptr));
+  EXPECT_TRUE(HaveSameValueAt(merged, *user_, "VPN.Host"));
 }
 
 TEST_F(ONCMergerTest, UserValueAndRecommendedUnset) {
-  std::unique_ptr<base::Value> merged(MergeSettingsAndPoliciesToEffective(
-      policy_.get(), nullptr, user_.get(), nullptr));
-  EXPECT_TRUE(HaveSameValueAt(*merged, *user_, "VPN.OpenVPN.Password"));
+  base::Value merged(MergeSettingsAndPoliciesToEffective(policy_.get(), nullptr,
+                                                         user_.get(), nullptr));
+  EXPECT_TRUE(HaveSameValueAt(merged, *user_, "VPN.OpenVPN.Password"));
 }
 
 TEST_F(ONCMergerTest, UserDictionaryAndNoPolicyValue) {
-  std::unique_ptr<base::Value> merged(MergeSettingsAndPoliciesToEffective(
-      policy_.get(), nullptr, user_.get(), nullptr));
-  EXPECT_FALSE(merged->FindKey("ProxySettings"));
+  base::Value merged(MergeSettingsAndPoliciesToEffective(policy_.get(), nullptr,
+                                                         user_.get(), nullptr));
+  EXPECT_FALSE(merged.FindKey("ProxySettings"));
 }
 
 TEST_F(ONCMergerTest, MergeWithEmptyPolicyProhibitsEverything) {
   base::DictionaryValue emptyDict;
-  std::unique_ptr<base::Value> merged(MergeSettingsAndPoliciesToEffective(
-      &emptyDict, nullptr, user_.get(), nullptr));
-  EXPECT_TRUE(merged->DictEmpty());
+  base::Value merged(MergeSettingsAndPoliciesToEffective(&emptyDict, nullptr,
+                                                         user_.get(), nullptr));
+  EXPECT_TRUE(merged.DictEmpty());
 }
 
 TEST_F(ONCMergerTest, MergeWithoutPolicyAllowsAnything) {
-  std::unique_ptr<base::Value> merged(MergeSettingsAndPoliciesToEffective(
-      nullptr, nullptr, user_.get(), nullptr));
-  EXPECT_TRUE(test_utils::Equals(user_.get(), merged.get()));
+  base::Value merged(MergeSettingsAndPoliciesToEffective(nullptr, nullptr,
+                                                         user_.get(), nullptr));
+  EXPECT_TRUE(test_utils::Equals(user_.get(), &merged));
 }
 
 TEST_F(ONCMergerTest, MergeWithoutUserSettings) {
   base::DictionaryValue emptyDict;
-  std::unique_ptr<base::Value> merged;
+  base::Value merged;
 
   merged = MergeSettingsAndPoliciesToEffective(policy_.get(), nullptr,
                                                &emptyDict, nullptr);
-  EXPECT_TRUE(
-      test_utils::Equals(policy_without_recommended_.get(), merged.get()));
+  EXPECT_TRUE(test_utils::Equals(policy_without_recommended_.get(), &merged));
 
   merged = MergeSettingsAndPoliciesToEffective(policy_.get(), nullptr, nullptr,
                                                nullptr);
-  EXPECT_TRUE(
-      test_utils::Equals(policy_without_recommended_.get(), merged.get()));
+  EXPECT_TRUE(test_utils::Equals(policy_without_recommended_.get(), &merged));
 }
 
 TEST_F(ONCMergerTest, MandatoryUserPolicyOverwritesDevicePolicy) {
-  std::unique_ptr<base::Value> merged(MergeSettingsAndPoliciesToEffective(
+  base::Value merged(MergeSettingsAndPoliciesToEffective(
       policy_.get(), device_policy_.get(), user_.get(), nullptr));
-  EXPECT_TRUE(HaveSameValueAt(*merged, *policy_, "VPN.OpenVPN.Port"));
+  EXPECT_TRUE(HaveSameValueAt(merged, *policy_, "VPN.OpenVPN.Port"));
 }
 
 TEST_F(ONCMergerTest, MandatoryDevicePolicyOverwritesRecommendedUserPolicy) {
-  std::unique_ptr<base::Value> merged(MergeSettingsAndPoliciesToEffective(
+  base::Value merged(MergeSettingsAndPoliciesToEffective(
       policy_.get(), device_policy_.get(), user_.get(), nullptr));
-  EXPECT_TRUE(
-      HaveSameValueAt(*merged, *device_policy_, "VPN.OpenVPN.Username"));
+  EXPECT_TRUE(HaveSameValueAt(merged, *device_policy_, "VPN.OpenVPN.Username"));
 }
 
 TEST_F(ONCMergerTest, MergeToAugmented) {
   std::unique_ptr<base::Value> expected_augmented =
       test_utils::ReadTestDictionary("augmented_merge.json");
-  std::unique_ptr<base::Value> merged(MergeSettingsAndPoliciesToAugmented(
+  base::Value merged(MergeSettingsAndPoliciesToAugmented(
       kNetworkConfigurationSignature, policy_.get(), device_policy_.get(),
       user_.get(), nullptr, active_.get()));
-  EXPECT_TRUE(test_utils::Equals(expected_augmented.get(), merged.get()));
+  EXPECT_TRUE(test_utils::Equals(expected_augmented.get(), &merged));
 }
 
 }  // namespace merger
diff --git a/chromeos/network/policy_util.cc b/chromeos/network/policy_util.cc
index 27c386c..75bc6a9c 100644
--- a/chromeos/network/policy_util.cc
+++ b/chromeos/network/policy_util.cc
@@ -223,10 +223,9 @@
   }
 
   // This call also removes credentials from policies.
-  std::unique_ptr<base::Value> augmented_onc_network =
-      onc::MergeSettingsAndPoliciesToAugmented(
-          onc::kNetworkConfigurationSignature, user_policy, device_policy,
-          nonshared_user_settings, shared_user_settings, active_settings);
+  base::Value augmented_onc_network = onc::MergeSettingsAndPoliciesToAugmented(
+      onc::kNetworkConfigurationSignature, user_policy, device_policy,
+      nonshared_user_settings, shared_user_settings, active_settings);
 
   // If present, apply the Autoconnect policy only to networks that are not
   // managed by policy.
@@ -237,12 +236,11 @@
                               kAllowOnlyPolicyNetworksToAutoconnect)
             .value_or(false);
     if (allow_only_policy_autoconnect) {
-      ApplyGlobalAutoconnectPolicy(profile->type(),
-                                   augmented_onc_network.get());
+      ApplyGlobalAutoconnectPolicy(profile->type(), &augmented_onc_network);
     }
   }
 
-  return augmented_onc_network;
+  return base::Value::ToUniquePtrValue(std::move(augmented_onc_network));
 }
 
 void SetShillPropertiesForGlobalPolicy(
@@ -283,7 +281,7 @@
     const base::DictionaryValue* global_policy,
     const base::DictionaryValue* network_policy,
     const base::DictionaryValue* user_settings) {
-  std::unique_ptr<base::Value> effective;
+  base::Value effective;
   ::onc::ONCSource onc_source = ::onc::ONC_SOURCE_NONE;
   if (network_policy) {
     switch (profile.type()) {
@@ -306,25 +304,27 @@
     }
     DCHECK(onc_source != ::onc::ONC_SOURCE_NONE);
   } else if (user_settings) {
-    effective.reset(user_settings->DeepCopy());
+    effective = user_settings->Clone();
     // TODO(pneubeck): change to source ONC_SOURCE_USER
     onc_source = ::onc::ONC_SOURCE_NONE;
   } else {
     NOTREACHED();
   }
 
-  RemoveFakeCredentials(onc::kNetworkConfigurationSignature, effective.get());
+  RemoveFakeCredentials(onc::kNetworkConfigurationSignature, &effective);
 
-  effective->SetKey(::onc::network_config::kGUID, base::Value(guid));
+  effective.SetKey(::onc::network_config::kGUID, base::Value(guid));
 
   // Remove irrelevant fields.
   onc::Normalizer normalizer(true /* remove recommended fields */);
-  effective = normalizer.NormalizeObject(&onc::kNetworkConfigurationSignature,
-                                         *effective);
+  std::unique_ptr<base::DictionaryValue> normalized_network =
+      normalizer.NormalizeObject(&onc::kNetworkConfigurationSignature,
+                                 effective);
+  effective = std::move(*normalized_network);
 
   std::unique_ptr<base::DictionaryValue> shill_dictionary(
       onc::TranslateONCObjectToShill(&onc::kNetworkConfigurationSignature,
-                                     *effective));
+                                     effective));
 
   shill_dictionary->SetKey(shill::kProfileProperty, base::Value(profile.path));
 
diff --git a/chromeos/services/nearby/public/mojom/nearby_connections_types.mojom b/chromeos/services/nearby/public/mojom/nearby_connections_types.mojom
index 8111dec..96e90c5b 100644
--- a/chromeos/services/nearby/public/mojom/nearby_connections_types.mojom
+++ b/chromeos/services/nearby/public/mojom/nearby_connections_types.mojom
@@ -271,6 +271,7 @@
   kNfc = 7,
   kWifiDirect = 8,
   kWebRtc = 9,
+  kBleL2Cap = 10,
 };
 
 // Log severity levels. This is passed as a member of
diff --git a/components/autofill_payments_strings.grdp b/components/autofill_payments_strings.grdp
index 27c52e1c..c183679 100644
--- a/components/autofill_payments_strings.grdp
+++ b/components/autofill_payments_strings.grdp
@@ -498,6 +498,12 @@
     <message name="IDS_AUTOFILL_VIRTUAL_CARD_MANUAL_FALLBACK_ICON_TOOLTIP" desc="The tooltip message for the omnibox icon for the virtual card manual fallback bubble on Desktop. This bubble shows the complete information for the virtual card selected to fill the form.">
       View your virtual card number
     </message>
+    <message name="IDS_AUTOFILL_VIRTUAL_CARD_MANUAL_FALLBACK_BUBBLE_BUTTON_TOOLTIP_NORMAL" desc="The tooltip message for the button that contains the card information in the virtual card manual fallback bubble on Desktop. This message is shown when the button is hovered over and if the button has not been clicked.">
+      Click to copy
+    </message>
+    <message name="IDS_AUTOFILL_VIRTUAL_CARD_MANUAL_FALLBACK_BUBBLE_BUTTON_TOOLTIP_CLICKED" desc="The tooltip message for the button that contains the card information in the virtual card manual fallback bubble on Desktop. This message is shown when the button is hovered over and if the button has been recently clicked.">
+      Copied
+    </message>
   </if>
   <message name="IDS_AUTOFILL_VIRTUAL_CARD_SUGGESTION_OPTION_VALUE" desc="The text shown in the virtual card option in the credit card suggestion list. It is shown as the value of the suggestion.">
     Virtual card
diff --git a/components/autofill_payments_strings_grdp/IDS_AUTOFILL_VIRTUAL_CARD_MANUAL_FALLBACK_BUBBLE_BUTTON_TOOLTIP_CLICKED.png.sha1 b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_VIRTUAL_CARD_MANUAL_FALLBACK_BUBBLE_BUTTON_TOOLTIP_CLICKED.png.sha1
new file mode 100644
index 0000000..81f62fa
--- /dev/null
+++ b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_VIRTUAL_CARD_MANUAL_FALLBACK_BUBBLE_BUTTON_TOOLTIP_CLICKED.png.sha1
@@ -0,0 +1 @@
+036066495ddb5125f78344567ddf63d2740e7ea8
\ No newline at end of file
diff --git a/components/autofill_payments_strings_grdp/IDS_AUTOFILL_VIRTUAL_CARD_MANUAL_FALLBACK_BUBBLE_BUTTON_TOOLTIP_NORMAL.png.sha1 b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_VIRTUAL_CARD_MANUAL_FALLBACK_BUBBLE_BUTTON_TOOLTIP_NORMAL.png.sha1
new file mode 100644
index 0000000..3dd80508
--- /dev/null
+++ b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_VIRTUAL_CARD_MANUAL_FALLBACK_BUBBLE_BUTTON_TOOLTIP_NORMAL.png.sha1
@@ -0,0 +1 @@
+2d1c4e820ba59677b57cb4050d03a01c318dd100
\ No newline at end of file
diff --git a/components/browser_ui/styles/android/java/src/org/chromium/components/browser_ui/styles/ChromeColors.java b/components/browser_ui/styles/android/java/src/org/chromium/components/browser_ui/styles/ChromeColors.java
index 004bdf5..0a9ac49 100644
--- a/components/browser_ui/styles/android/java/src/org/chromium/components/browser_ui/styles/ChromeColors.java
+++ b/components/browser_ui/styles/android/java/src/org/chromium/components/browser_ui/styles/ChromeColors.java
@@ -10,6 +10,7 @@
 import androidx.annotation.ColorInt;
 import androidx.annotation.ColorRes;
 import androidx.annotation.DimenRes;
+import androidx.annotation.Px;
 import androidx.appcompat.content.res.AppCompatResources;
 
 import com.google.android.material.color.MaterialColors;
@@ -131,14 +132,24 @@
     }
 
     /**
-     * Calculates the surface color using theme colors. Only the elevation is needed.
+     * Calculates the surface color using theme colors.
      * @param context The {@link Context} used to retrieve attrs, colors, and dimens.
      * @param elevationDimen The dimen to look up the elevation level with.
      * @return the {@link ColorInt} for the background of a surface view.
      */
     public static @ColorInt int getSurfaceColor(Context context, @DimenRes int elevationDimen) {
-        ElevationOverlayProvider elevationOverlayProvider = new ElevationOverlayProvider(context);
         float elevation = context.getResources().getDimension(elevationDimen);
+        return getSurfaceColor(context, elevation);
+    }
+
+    /**
+     * Calculates the surface color using theme colors.
+     * @param context The {@link Context} used to retrieve attrs and colors.
+     * @param elevation The elevation in px.
+     * @return the {@link ColorInt} for the background of a surface view.
+     */
+    public static @ColorInt int getSurfaceColor(Context context, @Px float elevation) {
+        ElevationOverlayProvider elevationOverlayProvider = new ElevationOverlayProvider(context);
         return elevationOverlayProvider.compositeOverlayWithThemeSurfaceColorIfNeeded(elevation);
     }
 }
diff --git a/components/browser_ui/widget/android/BUILD.gn b/components/browser_ui/widget/android/BUILD.gn
index 7585c56..be9ccb9 100644
--- a/components/browser_ui/widget/android/BUILD.gn
+++ b/components/browser_ui/widget/android/BUILD.gn
@@ -33,6 +33,7 @@
     "java/src/org/chromium/components/browser_ui/widget/RadioButtonWithEditText.java",
     "java/src/org/chromium/components/browser_ui/widget/RoundedCornerImageView.java",
     "java/src/org/chromium/components/browser_ui/widget/RoundedIconGenerator.java",
+    "java/src/org/chromium/components/browser_ui/widget/SurfaceColorOvalView.java",
     "java/src/org/chromium/components/browser_ui/widget/TintedDrawable.java",
     "java/src/org/chromium/components/browser_ui/widget/TouchEventObserver.java",
     "java/src/org/chromium/components/browser_ui/widget/ViewResourceFrameLayout.java",
@@ -115,6 +116,7 @@
   deps = [
     ":java_resources",
     "//base:base_java",
+    "//components/browser_ui/styles/android:java",
     "//components/browser_ui/util/android:java",
     "//components/embedder_support/android:util_java",
     "//third_party/android_deps:android_support_v4_java",
diff --git a/components/browser_ui/widget/android/java/res/layout/tile_no_text_view.xml b/components/browser_ui/widget/android/java/res/layout/tile_no_text_view.xml
index df843fe..966dfaf 100644
--- a/components/browser_ui/widget/android/java/res/layout/tile_no_text_view.xml
+++ b/components/browser_ui/widget/android/java/res/layout/tile_no_text_view.xml
@@ -10,13 +10,14 @@
     xmlns:tools="http://schemas.android.com/tools" >
 
     <!-- The icon background. -->
-    <View
+    <org.chromium.components.browser_ui.widget.SurfaceColorOvalView
         android:id="@+id/tile_view_icon_background"
         android:layout_width="@dimen/tile_view_icon_size"
         android:layout_height="@dimen/tile_view_icon_size"
         android:layout_gravity="center_horizontal"
         android:layout_marginTop="@dimen/tile_view_icon_background_margin_top_modern"
-        android:background="@drawable/tile_view_icon_background_modern" />
+        android:background="@drawable/tile_view_icon_background_modern"
+        app:surfaceElevation="@dimen/default_elevation_1"/>
 
     <!-- The main icon. -->
     <ImageView
@@ -47,4 +48,4 @@
         android:visibility="gone"
         android:contentDescription="@string/accessibility_ntp_offline_badge"
         app:srcCompat="@drawable/ic_offline_pin_blue_white" />
-</merge>
\ No newline at end of file
+</merge>
diff --git a/components/browser_ui/widget/android/java/res/layout/tile_no_text_view_condensed.xml b/components/browser_ui/widget/android/java/res/layout/tile_no_text_view_condensed.xml
index 392c8fd..f295498 100644
--- a/components/browser_ui/widget/android/java/res/layout/tile_no_text_view_condensed.xml
+++ b/components/browser_ui/widget/android/java/res/layout/tile_no_text_view_condensed.xml
@@ -10,13 +10,14 @@
     xmlns:tools="http://schemas.android.com/tools" >
 
     <!-- The icon background. -->
-    <View
+    <org.chromium.components.browser_ui.widget.SurfaceColorOvalView
         android:id="@+id/tile_view_icon_background"
         android:layout_width="@dimen/tile_view_icon_size"
         android:layout_height="@dimen/tile_view_icon_size"
         android:layout_gravity="center_horizontal"
         android:layout_marginTop="@dimen/tile_view_icon_background_margin_top_modern"
-        android:background="@drawable/tile_view_icon_background_modern" />
+        android:background="@drawable/tile_view_icon_background_modern"
+        app:surfaceElevation="@dimen/default_elevation_1"/>
 
     <!-- The main icon. -->
     <ImageView
@@ -47,4 +48,4 @@
         android:visibility="gone"
         android:contentDescription="@string/accessibility_ntp_offline_badge"
         app:srcCompat="@drawable/ic_offline_pin_blue_white" />
-</merge>
\ No newline at end of file
+</merge>
diff --git a/components/browser_ui/widget/android/java/res/values/attrs.xml b/components/browser_ui/widget/android/java/res/values/attrs.xml
index 5a0cd87..b5c2fac 100644
--- a/components/browser_ui/widget/android/java/res/values/attrs.xml
+++ b/components/browser_ui/widget/android/java/res/values/attrs.xml
@@ -80,4 +80,11 @@
         <attr name="horizontalSpacing" format="dimension" />
         <attr name="verticalSpacing" format="dimension" />
     </declare-styleable>
+
+    <declare-styleable name="SurfaceColorOvalView">
+      <!-- Used to specify the elevation level in dp. Similar to
+           android:elevation, but only affects color. Does not cause shadows to
+           be added or z-order to be adjusted. -->
+      <attr name="surfaceElevation" format="reference|dimension"/>
+    </declare-styleable>
 </resources>
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/SurfaceColorOvalView.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/SurfaceColorOvalView.java
new file mode 100644
index 0000000..b55db93
--- /dev/null
+++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/SurfaceColorOvalView.java
@@ -0,0 +1,45 @@
+// Copyright 2021 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.components.browser_ui.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.GradientDrawable;
+import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.Px;
+
+import org.chromium.components.browser_ui.styles.ChromeColors;
+
+/** Simple view class with a Surface-N colored oval/circle background. */
+public class SurfaceColorOvalView extends View {
+    /**
+     * Constructor that is called when inflating a view from XML.
+     * @param context The Context the view is running in, through which it can access the current
+     *        theme, resources, etc.
+     * @param attrs The attributes of the XML tag that is inflating the view.
+     */
+    public SurfaceColorOvalView(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray typedArray =
+                context.obtainStyledAttributes(attrs, R.styleable.SurfaceColorOvalView, 0, 0);
+        @Px
+        float elevation =
+                typedArray.getDimension(R.styleable.SurfaceColorOvalView_surfaceElevation, 0);
+        @ColorInt
+        int color = ChromeColors.getSurfaceColor(context, elevation);
+        typedArray.recycle();
+
+        GradientDrawable gradientDrawable = new GradientDrawable();
+        gradientDrawable.setColor(color);
+        gradientDrawable.setShape(GradientDrawable.OVAL);
+        setBackground(gradientDrawable);
+    }
+}
diff --git a/components/browsing_data/content/appcache_helper.cc b/components/browsing_data/content/appcache_helper.cc
index 8a679836..18e41b3a 100644
--- a/components/browsing_data/content/appcache_helper.cc
+++ b/components/browsing_data/content/appcache_helper.cc
@@ -10,6 +10,7 @@
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/location.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/time/time.h"
 #include "components/browsing_data/content/browsing_data_helper.h"
 #include "content/public/browser/browser_context.h"
@@ -68,8 +69,8 @@
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(!callback.is_null());
 
-  scoped_refptr<content::AppCacheInfoCollection> info_collection =
-      new content::AppCacheInfoCollection();
+  auto info_collection =
+      base::MakeRefCounted<content::AppCacheInfoCollection>();
 
   auto complete_callback = base::BindOnce(&OnAppCacheInfoFetchComplete,
                                           std::move(callback), info_collection);
diff --git a/components/browsing_data/content/cookie_helper_unittest.cc b/components/browsing_data/content/cookie_helper_unittest.cc
index 87dba4e7..58d8a73 100644
--- a/components/browsing_data/content/cookie_helper_unittest.cc
+++ b/components/browsing_data/content/cookie_helper_unittest.cc
@@ -6,6 +6,7 @@
 
 #include "base/bind.h"
 #include "base/callback_helpers.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/run_loop.h"
 #include "base/test/bind.h"
 #include "base/time/time.h"
@@ -66,7 +67,8 @@
 class CookieHelperTest : public testing::Test {
  public:
   CookieHelperTest()
-      : testing_browser_context_(new content::TestBrowserContext()) {}
+      : testing_browser_context_(
+            std::make_unique<content::TestBrowserContext>()) {}
 
   void SetUp() override { cookie_expectations_.clear(); }
 
@@ -234,8 +236,8 @@
 
 TEST_F(CookieHelperTest, FetchData) {
   CreateCookiesForTest();
-  scoped_refptr<CookieHelper> cookie_helper(
-      new CookieHelper(storage_partition(), base::NullCallback()));
+  auto cookie_helper = base::MakeRefCounted<CookieHelper>(storage_partition(),
+                                                          base::NullCallback());
 
   cookie_helper->StartFetching(
       base::BindOnce(&CookieHelperTest::FetchCallback, base::Unretained(this)));
@@ -244,8 +246,8 @@
 
 TEST_F(CookieHelperTest, DomainCookie) {
   CreateCookiesForDomainCookieTest();
-  scoped_refptr<CookieHelper> cookie_helper(
-      new CookieHelper(storage_partition(), base::NullCallback()));
+  auto cookie_helper = base::MakeRefCounted<CookieHelper>(storage_partition(),
+                                                          base::NullCallback());
 
   cookie_helper->StartFetching(base::BindOnce(
       &CookieHelperTest::DomainCookieCallback, base::Unretained(this)));
@@ -254,8 +256,8 @@
 
 TEST_F(CookieHelperTest, DeleteCookie) {
   CreateCookiesForTest();
-  scoped_refptr<CookieHelper> cookie_helper(
-      new CookieHelper(storage_partition(), base::NullCallback()));
+  auto cookie_helper = base::MakeRefCounted<CookieHelper>(storage_partition(),
+                                                          base::NullCallback());
 
   cookie_helper->StartFetching(
       base::BindOnce(&CookieHelperTest::FetchCallback, base::Unretained(this)));
@@ -272,10 +274,10 @@
 TEST_F(CookieHelperTest, DeleteCookieWithCallback) {
   CreateCookiesForTest();
   bool disable_delete = true;
-  scoped_refptr<CookieHelper> cookie_helper(new CookieHelper(
+  auto cookie_helper = base::MakeRefCounted<CookieHelper>(
       storage_partition(), base::BindLambdaForTesting([&](const GURL& url) {
         return disable_delete;
-      })));
+      }));
 
   cookie_helper->StartFetching(
       base::BindOnce(&CookieHelperTest::FetchCallback, base::Unretained(this)));
@@ -297,8 +299,8 @@
 
 TEST_F(CookieHelperTest, CannedDeleteCookie) {
   CreateCookiesForTest();
-  scoped_refptr<CannedCookieHelper> helper(
-      new CannedCookieHelper(storage_partition(), base::NullCallback()));
+  auto helper = base::MakeRefCounted<CannedCookieHelper>(storage_partition(),
+                                                         base::NullCallback());
 
   ASSERT_TRUE(helper->empty());
 
@@ -339,8 +341,8 @@
   const GURL origin("http://www.google.com");
   net::CookieList cookie;
 
-  scoped_refptr<CannedCookieHelper> helper(
-      new CannedCookieHelper(storage_partition(), base::NullCallback()));
+  auto helper = base::MakeRefCounted<CannedCookieHelper>(storage_partition(),
+                                                         base::NullCallback());
 
   ASSERT_TRUE(helper->empty());
   std::unique_ptr<net::CanonicalCookie> cookie1(net::CanonicalCookie::Create(
@@ -377,8 +379,8 @@
 TEST_F(CookieHelperTest, CannedUnique) {
   const GURL origin("http://www.google.com");
 
-  scoped_refptr<CannedCookieHelper> helper(
-      new CannedCookieHelper(storage_partition(), base::NullCallback()));
+  auto helper = base::MakeRefCounted<CannedCookieHelper>(storage_partition(),
+                                                         base::NullCallback());
 
   ASSERT_TRUE(helper->empty());
   std::unique_ptr<net::CanonicalCookie> cookie(net::CanonicalCookie::Create(
@@ -407,8 +409,8 @@
 TEST_F(CookieHelperTest, CannedReplaceCookie) {
   const GURL origin("http://www.google.com");
 
-  scoped_refptr<CannedCookieHelper> helper(
-      new CannedCookieHelper(storage_partition(), base::NullCallback()));
+  auto helper = base::MakeRefCounted<CannedCookieHelper>(storage_partition(),
+                                                         base::NullCallback());
 
   ASSERT_TRUE(helper->empty());
   std::unique_ptr<net::CanonicalCookie> cookie1(net::CanonicalCookie::Create(
@@ -519,8 +521,8 @@
 TEST_F(CookieHelperTest, CannedEmpty) {
   const GURL url_google("http://www.google.com");
 
-  scoped_refptr<CannedCookieHelper> helper(
-      new CannedCookieHelper(storage_partition(), base::NullCallback()));
+  auto helper = base::MakeRefCounted<CannedCookieHelper>(storage_partition(),
+                                                         base::NullCallback());
 
   ASSERT_TRUE(helper->empty());
   std::unique_ptr<net::CanonicalCookie> changed_cookie(
@@ -555,8 +557,8 @@
   GURL frame2_url("http://www.google.de");
   GURL request_url("http://www.google.com");
 
-  scoped_refptr<CannedCookieHelper> helper(
-      new CannedCookieHelper(storage_partition(), base::NullCallback()));
+  auto helper = base::MakeRefCounted<CannedCookieHelper>(storage_partition(),
+                                                         base::NullCallback());
 
   ASSERT_TRUE(helper->empty());
   std::unique_ptr<net::CanonicalCookie> cookie1(net::CanonicalCookie::Create(
@@ -599,8 +601,8 @@
   GURL request2_url("http://static.google.com/bar/res2.html");
   std::string cookie_domain(".www.google.com");
 
-  scoped_refptr<CannedCookieHelper> helper(
-      new CannedCookieHelper(storage_partition(), base::NullCallback()));
+  auto helper = base::MakeRefCounted<CannedCookieHelper>(storage_partition(),
+                                                         base::NullCallback());
 
   // Add two different cookies (distinguished by the tuple [cookie-name,
   // domain-value, path-value]) for a HTTP request to |frame1_url| and verify
diff --git a/components/browsing_data/content/database_helper_unittest.cc b/components/browsing_data/content/database_helper_unittest.cc
index d158c87..587cda2 100644
--- a/components/browsing_data/content/database_helper_unittest.cc
+++ b/components/browsing_data/content/database_helper_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "components/browsing_data/content/database_helper.h"
 
+#include "base/memory/scoped_refptr.h"
 #include "content/public/test/browser_task_environment.h"
 #include "content/public/test/test_browser_context.h"
 #include "storage/common/database/database_identifier.h"
@@ -23,8 +24,7 @@
 
   const GURL origin("http://host1:1/");
 
-  scoped_refptr<CannedDatabaseHelper> helper(
-      new CannedDatabaseHelper(&browser_context));
+  auto helper = base::MakeRefCounted<CannedDatabaseHelper>(&browser_context);
 
   ASSERT_TRUE(helper->empty());
   helper->Add(url::Origin::Create(origin));
@@ -40,8 +40,7 @@
   const GURL origin2("http://example.com");
   const GURL origin3("http://foo.example.com");
 
-  scoped_refptr<CannedDatabaseHelper> helper(
-      new CannedDatabaseHelper(&browser_context));
+  auto helper = base::MakeRefCounted<CannedDatabaseHelper>(&browser_context);
 
   EXPECT_TRUE(helper->empty());
   helper->Add(url::Origin::Create(origin1));
@@ -60,8 +59,7 @@
   const GURL origin1("chrome-extension://abcdefghijklmnopqrstuvwxyz/");
   const GURL origin2("devtools://abcdefghijklmnopqrstuvwxyz/");
 
-  scoped_refptr<CannedDatabaseHelper> helper(
-      new CannedDatabaseHelper(&browser_context));
+  auto helper = base::MakeRefCounted<CannedDatabaseHelper>(&browser_context);
 
   ASSERT_TRUE(helper->empty());
   helper->Add(url::Origin::Create(origin1));
diff --git a/components/browsing_data/content/file_system_helper.cc b/components/browsing_data/content/file_system_helper.cc
index 12ef3316b..3aa4e6c 100644
--- a/components/browsing_data/content/file_system_helper.cc
+++ b/components/browsing_data/content/file_system_helper.cc
@@ -10,6 +10,7 @@
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/location.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/sequenced_task_runner.h"
 #include "components/browsing_data/content/browsing_data_helper.h"
 #include "content/public/browser/browser_task_traits.h"
@@ -146,15 +147,6 @@
 
 FileSystemHelper::FileSystemInfo::~FileSystemInfo() {}
 
-// static
-FileSystemHelper* FileSystemHelper::Create(
-    storage::FileSystemContext* filesystem_context,
-    const std::vector<storage::FileSystemType>& additional_types,
-    content::NativeIOContext* native_io_context) {
-  return new FileSystemHelper(filesystem_context, additional_types,
-                              native_io_context);
-}
-
 CannedFileSystemHelper::CannedFileSystemHelper(
     storage::FileSystemContext* filesystem_context,
     const std::vector<storage::FileSystemType>& additional_types,
diff --git a/components/browsing_data/content/file_system_helper.h b/components/browsing_data/content/file_system_helper.h
index 539dc31..461a1306 100644
--- a/components/browsing_data/content/file_system_helper.h
+++ b/components/browsing_data/content/file_system_helper.h
@@ -67,16 +67,15 @@
       base::OnceCallback<void(const std::list<FileSystemInfo>&)>;
 
   // Creates a FileSystemHelper instance for the file systems stored
-  // in |profile|'s user data directory. The FileSystemHelper object
+  // in `profile`'s user data directory. The FileSystemHelper object
   // will hold a reference to the FileSystemContext that's passed in, but is not
   // responsible for destroying it.
   //
   // The FileSystemHelper will not change the profile itself, but
   // can modify data it contains (by removing file systems).
-  static FileSystemHelper* Create(
-      storage::FileSystemContext* file_system_context,
-      const std::vector<storage::FileSystemType>& additional_types,
-      content::NativeIOContext* native_io_context);
+  FileSystemHelper(storage::FileSystemContext* filesystem_context,
+                   const std::vector<storage::FileSystemType>& additional_types,
+                   content::NativeIOContext* native_io_context);
 
   // Starts the process of fetching file system data, which will call |callback|
   // upon completion, passing it a constant list of FileSystemInfo objects.
@@ -95,10 +94,6 @@
  protected:
   friend class base::RefCountedThreadSafe<FileSystemHelper>;
 
-  FileSystemHelper(storage::FileSystemContext* filesystem_context,
-                   const std::vector<storage::FileSystemType>& additional_types,
-                   content::NativeIOContext* native_io_context);
-
   virtual ~FileSystemHelper();
 
  private:
diff --git a/components/browsing_data/content/file_system_helper_unittest.cc b/components/browsing_data/content/file_system_helper_unittest.cc
index cd4b8415..c7078deb 100644
--- a/components/browsing_data/content/file_system_helper_unittest.cc
+++ b/components/browsing_data/content/file_system_helper_unittest.cc
@@ -8,11 +8,13 @@
 
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/containers/contains.h"
 #include "base/files/file_util.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/run_loop.h"
 #include "base/strings/utf_string_conversions.h"
 #include "content/public/browser/native_io_context.h"
@@ -53,11 +55,13 @@
         browser_context_.GetDefaultStoragePartition()->GetFileSystemContext();
     auto* native_io_context =
         browser_context_.GetDefaultStoragePartition()->GetNativeIOContext();
-    helper_ =
-        FileSystemHelper::Create(file_system_context, {}, native_io_context);
+    helper_ = base::MakeRefCounted<FileSystemHelper>(
+        file_system_context, std::vector<storage::FileSystemType>(),
+        native_io_context);
     content::RunAllTasksUntilIdle();
-    canned_helper_ =
-        new CannedFileSystemHelper(file_system_context, {}, native_io_context);
+    canned_helper_ = base::MakeRefCounted<CannedFileSystemHelper>(
+        file_system_context, std::vector<storage::FileSystemType>(),
+        native_io_context);
   }
 
   // Blocks on the run_loop quits.
diff --git a/components/browsing_data/content/indexed_db_helper_browsertest.cc b/components/browsing_data/content/indexed_db_helper_browsertest.cc
index 2cb07a22..61c5837 100644
--- a/components/browsing_data/content/indexed_db_helper_browsertest.cc
+++ b/components/browsing_data/content/indexed_db_helper_browsertest.cc
@@ -8,6 +8,7 @@
 #include "base/callback_helpers.h"
 #include "base/files/file_path.h"
 #include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/strings/utf_string_conversions.h"
 #include "components/browsing_data/content/browsing_data_helper_browsertest.h"
 #include "components/browsing_data/content/indexed_db_helper.h"
@@ -42,8 +43,7 @@
   const blink::StorageKey storage_key2 =
       blink::StorageKey::CreateFromStringForTesting("http://host2:1/");
 
-  scoped_refptr<CannedIndexedDBHelper> helper(
-      new CannedIndexedDBHelper(StoragePartition()));
+  auto helper = base::MakeRefCounted<CannedIndexedDBHelper>(StoragePartition());
   helper->Add(storage_key1);
   helper->Add(storage_key2);
 
@@ -64,8 +64,7 @@
   const blink::StorageKey storage_key =
       blink::StorageKey::CreateFromStringForTesting("http://host1:1/");
 
-  scoped_refptr<CannedIndexedDBHelper> helper(
-      new CannedIndexedDBHelper(StoragePartition()));
+  auto helper = base::MakeRefCounted<CannedIndexedDBHelper>(StoragePartition());
   helper->Add(storage_key);
   helper->Add(storage_key);
 
diff --git a/components/browsing_data/content/indexed_db_helper_unittest.cc b/components/browsing_data/content/indexed_db_helper_unittest.cc
index 5c4a61c6e..447f3929 100644
--- a/components/browsing_data/content/indexed_db_helper_unittest.cc
+++ b/components/browsing_data/content/indexed_db_helper_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "components/browsing_data/content/indexed_db_helper.h"
 
+#include "base/memory/scoped_refptr.h"
 #include "base/run_loop.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/bind.h"
@@ -33,8 +34,7 @@
 TEST_F(CannedIndexedDBHelperTest, Empty) {
   const blink::StorageKey storage_key =
       blink::StorageKey::CreateFromStringForTesting("http://host1:1/");
-  scoped_refptr<CannedIndexedDBHelper> helper(
-      new CannedIndexedDBHelper(StoragePartition()));
+  auto helper = base::MakeRefCounted<CannedIndexedDBHelper>(StoragePartition());
 
   ASSERT_TRUE(helper->empty());
   helper->Add(storage_key);
@@ -49,8 +49,7 @@
   const blink::StorageKey storage_key2 =
       blink::StorageKey::CreateFromStringForTesting("http://example.com");
 
-  scoped_refptr<CannedIndexedDBHelper> helper(
-      new CannedIndexedDBHelper(StoragePartition()));
+  auto helper = base::MakeRefCounted<CannedIndexedDBHelper>(StoragePartition());
 
   EXPECT_TRUE(helper->empty());
   helper->Add(storage_key1);
@@ -74,8 +73,7 @@
       blink::StorageKey::CreateFromStringForTesting(
           "devtools://abcdefghijklmnopqrstuvwxyz/");
 
-  scoped_refptr<CannedIndexedDBHelper> helper(
-      new CannedIndexedDBHelper(StoragePartition()));
+  auto helper = base::MakeRefCounted<CannedIndexedDBHelper>(StoragePartition());
 
   ASSERT_TRUE(helper->empty());
   helper->Add(storage_key1);
diff --git a/components/browsing_data/content/local_shared_objects_container.cc b/components/browsing_data/content/local_shared_objects_container.cc
index e1f259a2..0935c0a7 100644
--- a/components/browsing_data/content/local_shared_objects_container.cc
+++ b/components/browsing_data/content/local_shared_objects_container.cc
@@ -9,6 +9,7 @@
 #include <utility>
 #include <vector>
 
+#include "base/memory/scoped_refptr.h"
 #include "base/strings/string_piece.h"
 #include "components/browsing_data/content/appcache_helper.h"
 #include "components/browsing_data/content/cache_storage_helper.h"
@@ -45,27 +46,29 @@
     content::BrowserContext* browser_context,
     const std::vector<storage::FileSystemType>& additional_file_system_types,
     browsing_data::CookieHelper::IsDeletionDisabledCallback callback)
-    : appcaches_(new CannedAppCacheHelper(
+    : appcaches_(base::MakeRefCounted<CannedAppCacheHelper>(
           browser_context->GetDefaultStoragePartition()->GetAppCacheService())),
-      cookies_(
-          new CannedCookieHelper(browser_context->GetDefaultStoragePartition(),
-                                 std::move(callback))),
-      databases_(new CannedDatabaseHelper(browser_context)),
-      file_systems_(new CannedFileSystemHelper(
+      cookies_(base::MakeRefCounted<CannedCookieHelper>(
+          browser_context->GetDefaultStoragePartition(),
+          std::move(callback))),
+      databases_(base::MakeRefCounted<CannedDatabaseHelper>(browser_context)),
+      file_systems_(base::MakeRefCounted<CannedFileSystemHelper>(
           browser_context->GetDefaultStoragePartition()->GetFileSystemContext(),
           additional_file_system_types,
           browser_context->GetDefaultStoragePartition()->GetNativeIOContext())),
-      indexed_dbs_(new CannedIndexedDBHelper(
+      indexed_dbs_(base::MakeRefCounted<CannedIndexedDBHelper>(
           browser_context->GetDefaultStoragePartition())),
-      local_storages_(new CannedLocalStorageHelper(browser_context)),
-      service_workers_(new CannedServiceWorkerHelper(
+      local_storages_(
+          base::MakeRefCounted<CannedLocalStorageHelper>(browser_context)),
+      service_workers_(base::MakeRefCounted<CannedServiceWorkerHelper>(
           browser_context->GetDefaultStoragePartition()
               ->GetServiceWorkerContext())),
-      shared_workers_(new CannedSharedWorkerHelper(
+      shared_workers_(base::MakeRefCounted<CannedSharedWorkerHelper>(
           browser_context->GetDefaultStoragePartition())),
-      cache_storages_(new CannedCacheStorageHelper(
+      cache_storages_(base::MakeRefCounted<CannedCacheStorageHelper>(
           browser_context->GetDefaultStoragePartition())),
-      session_storages_(new CannedLocalStorageHelper(browser_context)) {}
+      session_storages_(
+          base::MakeRefCounted<CannedLocalStorageHelper>(browser_context)) {}
 
 LocalSharedObjectsContainer::~LocalSharedObjectsContainer() = default;
 
diff --git a/components/browsing_data/content/local_storage_helper_browsertest.cc b/components/browsing_data/content/local_storage_helper_browsertest.cc
index e117453c7..a60e489 100644
--- a/components/browsing_data/content/local_storage_helper_browsertest.cc
+++ b/components/browsing_data/content/local_storage_helper_browsertest.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/memory/scoped_refptr.h"
 #include "components/browsing_data/content/local_storage_helper.h"
 
 #include <stddef.h>
@@ -123,8 +124,8 @@
 };
 
 IN_PROC_BROWSER_TEST_F(LocalStorageHelperTest, CallbackCompletes) {
-  scoped_refptr<LocalStorageHelper> local_storage_helper(
-      new LocalStorageHelper(shell()->web_contents()->GetBrowserContext()));
+  auto local_storage_helper = base::MakeRefCounted<LocalStorageHelper>(
+      shell()->web_contents()->GetBrowserContext());
   CreateLocalStorageDataForTest();
   StopTestOnCallback stop_test_on_callback(local_storage_helper.get());
   local_storage_helper->StartFetching(base::BindOnce(
@@ -134,8 +135,8 @@
 }
 
 IN_PROC_BROWSER_TEST_F(LocalStorageHelperTest, DeleteSingleOrigin) {
-  scoped_refptr<LocalStorageHelper> local_storage_helper(
-      new LocalStorageHelper(shell()->web_contents()->GetBrowserContext()));
+  auto local_storage_helper = base::MakeRefCounted<LocalStorageHelper>(
+      shell()->web_contents()->GetBrowserContext());
   CreateLocalStorageDataForTest();
   base::RunLoop delete_run_loop;
   local_storage_helper->DeleteStorageKey(
@@ -177,8 +178,8 @@
   const blink::StorageKey storage_key2 =
       blink::StorageKey::CreateFromStringForTesting("http://host2:1/");
 
-  scoped_refptr<CannedLocalStorageHelper> helper(new CannedLocalStorageHelper(
-      shell()->web_contents()->GetBrowserContext()));
+  auto helper = base::MakeRefCounted<CannedLocalStorageHelper>(
+      shell()->web_contents()->GetBrowserContext());
   helper->Add(storage_key1);
   helper->Add(storage_key2);
 
@@ -199,8 +200,8 @@
   const blink::StorageKey storage_key =
       blink::StorageKey::CreateFromStringForTesting("http://host1:1/");
 
-  scoped_refptr<CannedLocalStorageHelper> helper(new CannedLocalStorageHelper(
-      shell()->web_contents()->GetBrowserContext()));
+  auto helper = base::MakeRefCounted<CannedLocalStorageHelper>(
+      shell()->web_contents()->GetBrowserContext());
   helper->Add(storage_key);
   helper->Add(storage_key);
 
diff --git a/components/browsing_data/content/local_storage_helper_unittest.cc b/components/browsing_data/content/local_storage_helper_unittest.cc
index 8919e5504..3f9de1a 100644
--- a/components/browsing_data/content/local_storage_helper_unittest.cc
+++ b/components/browsing_data/content/local_storage_helper_unittest.cc
@@ -5,6 +5,7 @@
 #include "components/browsing_data/content/local_storage_helper.h"
 
 #include "base/callback_helpers.h"
+#include "base/memory/scoped_refptr.h"
 #include "content/public/test/browser_task_environment.h"
 #include "content/public/test/test_browser_context.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -24,8 +25,7 @@
   const blink::StorageKey storage_key =
       blink::StorageKey::CreateFromStringForTesting("http://host1:1/");
 
-  scoped_refptr<CannedLocalStorageHelper> helper(
-      new CannedLocalStorageHelper(&context));
+  auto helper = base::MakeRefCounted<CannedLocalStorageHelper>(&context);
 
   ASSERT_TRUE(helper->empty());
   helper->Add(storage_key);
@@ -44,8 +44,7 @@
   const blink::StorageKey storage_key3 =
       blink::StorageKey::CreateFromStringForTesting("http://foo.example.com");
 
-  scoped_refptr<CannedLocalStorageHelper> helper(
-      new CannedLocalStorageHelper(&context));
+  auto helper = base::MakeRefCounted<CannedLocalStorageHelper>(&context);
 
   EXPECT_TRUE(helper->empty());
   helper->Add(storage_key1);
@@ -68,8 +67,7 @@
       blink::StorageKey::CreateFromStringForTesting(
           "devtools://abcdefghijklmnopqrstuvwxyz/");
 
-  scoped_refptr<CannedLocalStorageHelper> helper(
-      new CannedLocalStorageHelper(&context));
+  auto helper = base::MakeRefCounted<CannedLocalStorageHelper>(&context);
 
   ASSERT_TRUE(helper->empty());
   helper->Add(storage_key1);
diff --git a/components/browsing_data/content/mock_file_system_helper.cc b/components/browsing_data/content/mock_file_system_helper.cc
index cb5600ab..8fd8c8fa 100644
--- a/components/browsing_data/content/mock_file_system_helper.cc
+++ b/components/browsing_data/content/mock_file_system_helper.cc
@@ -6,6 +6,7 @@
 
 #include "base/callback.h"
 #include "base/containers/contains.h"
+#include "components/browsing_data/content/file_system_helper.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/storage_partition.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/components/browsing_data/content/service_worker_helper_unittest.cc b/components/browsing_data/content/service_worker_helper_unittest.cc
index de4877d3..c325b5b 100644
--- a/components/browsing_data/content/service_worker_helper_unittest.cc
+++ b/components/browsing_data/content/service_worker_helper_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <vector>
 
+#include "base/memory/scoped_refptr.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "content/public/browser/browser_context.h"
@@ -34,8 +35,8 @@
   std::vector<GURL> scopes;
   scopes.push_back(GURL("https://host1:1/*"));
 
-  scoped_refptr<CannedServiceWorkerHelper> helper(
-      new CannedServiceWorkerHelper(ServiceWorkerContext()));
+  auto helper =
+      base::MakeRefCounted<CannedServiceWorkerHelper>(ServiceWorkerContext());
 
   ASSERT_TRUE(helper->empty());
   helper->Add(url::Origin::Create(origin));
@@ -54,8 +55,8 @@
   scopes2.push_back(GURL("https://example.com/app1/*"));
   scopes2.push_back(GURL("https://example.com/app2/*"));
 
-  scoped_refptr<CannedServiceWorkerHelper> helper(
-      new CannedServiceWorkerHelper(ServiceWorkerContext()));
+  auto helper =
+      base::MakeRefCounted<CannedServiceWorkerHelper>(ServiceWorkerContext());
 
   EXPECT_TRUE(helper->empty());
   helper->Add(url::Origin::Create(origin1));
@@ -70,8 +71,8 @@
   const GURL origin2("devtools://abcdefghijklmnopqrstuvwxyz/");
   const std::vector<GURL> scopes;
 
-  scoped_refptr<CannedServiceWorkerHelper> helper(
-      new CannedServiceWorkerHelper(ServiceWorkerContext()));
+  auto helper =
+      base::MakeRefCounted<CannedServiceWorkerHelper>(ServiceWorkerContext());
 
   ASSERT_TRUE(helper->empty());
   helper->Add(url::Origin::Create(origin1));
diff --git a/components/component_updater/android/java/src/org/chromium/components/component_updater/EmbeddedComponentLoader.java b/components/component_updater/android/java/src/org/chromium/components/component_updater/EmbeddedComponentLoader.java
index d4e4e81..19c9eb4 100644
--- a/components/component_updater/android/java/src/org/chromium/components/component_updater/EmbeddedComponentLoader.java
+++ b/components/component_updater/android/java/src/org/chromium/components/component_updater/EmbeddedComponentLoader.java
@@ -32,6 +32,14 @@
 public class EmbeddedComponentLoader implements ServiceConnection {
     private static final String TAG = "EmbedComponentLoader";
 
+    /**
+     * WebView's ComponentsProviderService name that implements IComponentsProviderService.aidl
+     * interface. Use this String in an intent to connect to the service to avoid dependency on the
+     * service class itself.
+     */
+    public static final String AW_COMPONENTS_PROVIDER_SERVICE =
+            "org.chromium.android_webview.services.ComponentsProviderService";
+
     private static final String KEY_RESULT = "RESULT";
 
     // Maintain a set of ComponentResultReceivers, remove a receiver once it gets a result back.
diff --git a/components/content_creation/OWNERS b/components/content_creation/OWNERS
index 12c130a..e6d30b08 100644
--- a/components/content_creation/OWNERS
+++ b/components/content_creation/OWNERS
@@ -1,2 +1,3 @@
+gayane@chromium.org
 seblalancette@chromium.org
-sebsg@chromium.org
+sebsg@chromium.org
\ No newline at end of file
diff --git a/components/download/content/public/all_download_item_notifier.cc b/components/download/content/public/all_download_item_notifier.cc
index 38145a8..deeee15e 100644
--- a/components/download/content/public/all_download_item_notifier.cc
+++ b/components/download/content/public/all_download_item_notifier.cc
@@ -47,7 +47,14 @@
 void AllDownloadItemNotifier::ManagerGoingDown(
     content::DownloadManager* manager) {
   DCHECK_EQ(manager_, manager);
+
+  // We might get deleted while calling `observer_->OnManagerGoingDown()`.
+  // If so, the destructor will remove `this` as an observer of `manager_`.
+  auto weak_ptr = weak_factory_.GetWeakPtr();
   observer_->OnManagerGoingDown(manager);
+  if (!weak_ptr)
+    return;
+
   manager_->RemoveObserver(this);
   manager_ = nullptr;
 }
@@ -75,6 +82,7 @@
 void AllDownloadItemNotifier::OnDownloadDestroyed(DownloadItem* item) {
   item->RemoveObserver(this);
   observing_.erase(item);
+  observer_->OnDownloadDestroyed(manager_, item);
 }
 
 }  // namespace download
diff --git a/components/download/content/public/all_download_item_notifier.h b/components/download/content/public/all_download_item_notifier.h
index ddfebf8b..ff547ff 100644
--- a/components/download/content/public/all_download_item_notifier.h
+++ b/components/download/content/public/all_download_item_notifier.h
@@ -56,6 +56,8 @@
                                   download::DownloadItem* item) {}
     virtual void OnDownloadRemoved(content::DownloadManager* manager,
                                    download::DownloadItem* item) {}
+    virtual void OnDownloadDestroyed(content::DownloadManager* manager,
+                                     download::DownloadItem* item) {}
 
    private:
     DISALLOW_COPY_AND_ASSIGN(Observer);
@@ -66,7 +68,7 @@
 
   ~AllDownloadItemNotifier() override;
 
-  // Returns NULL if the manager has gone down.
+  // Returns nullptr if the manager has gone down.
   content::DownloadManager* GetManager() const { return manager_; }
 
   // Returns the estimate of dynamically allocated memory in bytes.
@@ -88,6 +90,7 @@
   content::DownloadManager* manager_;
   AllDownloadItemNotifier::Observer* observer_;
   std::set<DownloadItem*> observing_;
+  base::WeakPtrFactory<AllDownloadItemNotifier> weak_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(AllDownloadItemNotifier);
 };
diff --git a/components/download/content/public/all_download_item_notifier_unittest.cc b/components/download/content/public/all_download_item_notifier_unittest.cc
index a7bae16..43b9814 100644
--- a/components/download/content/public/all_download_item_notifier_unittest.cc
+++ b/components/download/content/public/all_download_item_notifier_unittest.cc
@@ -24,14 +24,26 @@
   MockNotifierObserver() {}
   ~MockNotifierObserver() override {}
 
-  MOCK_METHOD2(OnDownloadCreated,
-               void(content::DownloadManager* manager, DownloadItem* item));
-  MOCK_METHOD2(OnDownloadUpdated,
-               void(content::DownloadManager* manager, DownloadItem* item));
-  MOCK_METHOD2(OnDownloadOpened,
-               void(content::DownloadManager* manager, DownloadItem* item));
-  MOCK_METHOD2(OnDownloadRemoved,
-               void(content::DownloadManager* manager, DownloadItem* item));
+  MOCK_METHOD(void,
+              OnDownloadCreated,
+              (content::DownloadManager * manager, DownloadItem* item),
+              (override));
+  MOCK_METHOD(void,
+              OnDownloadUpdated,
+              (content::DownloadManager * manager, DownloadItem* item),
+              (override));
+  MOCK_METHOD(void,
+              OnDownloadOpened,
+              (content::DownloadManager * manager, DownloadItem* item),
+              (override));
+  MOCK_METHOD(void,
+              OnDownloadRemoved,
+              (content::DownloadManager * manager, DownloadItem* item),
+              (override));
+  MOCK_METHOD(void,
+              OnDownloadDestroyed,
+              (content::DownloadManager * manager, DownloadItem* item),
+              (override));
 
  private:
   DISALLOW_COPY_AND_ASSIGN(MockNotifierObserver);
@@ -92,6 +104,9 @@
   EXPECT_CALL(observer(), OnDownloadRemoved(&manager(), &item()));
   NotifierAsItemObserver()->OnDownloadRemoved(&item());
 
+  EXPECT_CALL(observer(), OnDownloadDestroyed(&manager(), &item()));
+  NotifierAsItemObserver()->OnDownloadDestroyed(&item());
+
   EXPECT_CALL(manager(), RemoveObserver(NotifierAsManagerObserver()));
   ClearNotifier();
 }
diff --git a/components/download/internal/background_service/ios/background_download_service_impl.cc b/components/download/internal/background_service/ios/background_download_service_impl.cc
index 2d25694..a2b56a2d 100644
--- a/components/download/internal/background_service/ios/background_download_service_impl.cc
+++ b/components/download/internal/background_service/ios/background_download_service_impl.cc
@@ -298,6 +298,7 @@
     bool success,
     const base::FilePath& file_path,
     int64_t file_size) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   download::Client* client = clients_->GetClient(download_client);
   if (!client)
     return;
@@ -335,6 +336,7 @@
     DownloadClient download_client,
     const std::string& guid,
     int64_t bytes_downloaded) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   uint64_t bytes_count = base::saturated_cast<uint64_t>(bytes_downloaded);
   MaybeUpdateProgress(guid, bytes_count);
 
diff --git a/components/download/internal/background_service/ios/background_download_service_impl.h b/components/download/internal/background_service/ios/background_download_service_impl.h
index 615ce6a..96477ec 100644
--- a/components/download/internal/background_service/ios/background_download_service_impl.h
+++ b/components/download/internal/background_service/ios/background_download_service_impl.h
@@ -10,6 +10,7 @@
 #include <string>
 
 #include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
 #include "base/time/clock.h"
 #include "components/download/internal/background_service/log_source.h"
 #include "components/download/internal/background_service/model_impl.h"
@@ -117,6 +118,7 @@
   // pruned frequently.
   const base::FilePath download_dir_;
 
+  SEQUENCE_CHECKER(sequence_checker_);
   base::WeakPtrFactory<BackgroundDownloadServiceImpl> weak_ptr_factory_{this};
 };
 
diff --git a/components/download/internal/background_service/ios/background_download_task_helper.mm b/components/download/internal/background_service/ios/background_download_task_helper.mm
index e0c0f7e..fff43631 100644
--- a/components/download/internal/background_service/ios/background_download_task_helper.mm
+++ b/components/download/internal/background_service/ios/background_download_task_helper.mm
@@ -11,8 +11,10 @@
 #include "base/files/file_util.h"
 #include "base/logging.h"
 #include "base/mac/foundation_util.h"
+#include "base/single_thread_task_runner.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/sys_string_conversions.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "components/download/public/background_service/download_params.h"
 #include "net/base/mac/url_conversions.h"
 
@@ -27,29 +29,40 @@
 @interface BackgroundDownloadDelegate : NSObject <NSURLSessionDownloadDelegate>
 - (instancetype)initWithDownloadPath:(base::FilePath)downloadPath
                    completionHandler:(CompletionCallback)completionHandler
-                       updateHandler:(UpdateCallback)updateHandler;
+                       updateHandler:(UpdateCallback)updateHandler
+                          taskRunner:
+                              (scoped_refptr<base::SingleThreadTaskRunner>)
+                                  taskRunner;
 @end
 
 @implementation BackgroundDownloadDelegate {
   base::FilePath _downloadPath;
   CompletionCallback _completionCallback;
   UpdateCallback _updateCallback;
+  scoped_refptr<base::SingleThreadTaskRunner> _taskRunner;
 }
 
 - (instancetype)initWithDownloadPath:(base::FilePath)downloadPath
                    completionHandler:(CompletionCallback)completionHandler
-                       updateHandler:(UpdateCallback)updateHandler {
+                       updateHandler:(UpdateCallback)updateHandler
+                          taskRunner:
+                              (scoped_refptr<base::SingleThreadTaskRunner>)
+                                  taskRunner {
   _downloadPath = downloadPath;
   _completionCallback = std::move(completionHandler);
   _updateCallback = updateHandler;
+  _taskRunner = taskRunner;
   return self;
 }
 
 - (void)invokeCompletionHandler:(bool)success
                        filePath:(base::FilePath)filePath
                        fileSize:(int64_t)fileSize {
-  if (_completionCallback)
-    std::move(_completionCallback).Run(success, filePath, fileSize);
+  if (_completionCallback) {
+    _taskRunner->PostTask(
+        FROM_HERE, base::BindOnce(std::move(_completionCallback), success,
+                                  filePath, fileSize));
+  }
 }
 
 #pragma mark - NSURLSessionDownloadDelegate
@@ -71,8 +84,10 @@
   DVLOG(1) << __func__ << ",byte written: " << bytesWritten
            << ", totalBytesWritten:" << totalBytesWritten
            << ", totalBytesExpectedToWrite:" << totalBytesExpectedToWrite;
-  if (_updateCallback)
-    _updateCallback.Run(totalBytesWritten);
+  if (_updateCallback) {
+    _taskRunner->PostTask(
+        FROM_HERE, base::BindRepeating(_updateCallback, totalBytesWritten));
+  }
 }
 
 - (void)URLSession:(NSURLSession*)session
@@ -129,6 +144,9 @@
 
 // Implementation of BackgroundDownloadTaskHelper based on
 // NSURLSessionDownloadTask api.
+// This class lives on main thread and all the callbacks will be invoked on main
+// thread. The NSURLSessionDownloadDelegate it uses will broadcast download
+// events on a background thread.
 class BackgroundDownloadTaskHelperImpl : public BackgroundDownloadTaskHelper {
  public:
   BackgroundDownloadTaskHelperImpl() = default;
@@ -157,7 +175,8 @@
     BackgroundDownloadDelegate* delegate = [[BackgroundDownloadDelegate alloc]
         initWithDownloadPath:target_path
            completionHandler:std::move(completion_callback)
-               updateHandler:update_callback];
+               updateHandler:update_callback
+                  taskRunner:base::ThreadTaskRunnerHandle::Get()];
     NSURLSession* session = [NSURLSession sessionWithConfiguration:configuration
                                                           delegate:delegate
                                                      delegateQueue:nil];
diff --git a/components/download/internal/background_service/ios/background_download_task_helper_unittest.mm b/components/download/internal/background_service/ios/background_download_task_helper_unittest.mm
index c13b6975..5a37ebf2 100644
--- a/components/download/internal/background_service/ios/background_download_task_helper_unittest.mm
+++ b/components/download/internal/background_service/ios/background_download_task_helper_unittest.mm
@@ -10,6 +10,7 @@
 #import "base/files/file_util.h"
 #import "base/files/scoped_temp_dir.h"
 #import "base/run_loop.h"
+#import "base/sequence_checker.h"
 #import "base/test/bind.h"
 #import "base/test/gmock_callback_support.h"
 #import "base/test/task_environment.h"
@@ -63,6 +64,7 @@
               ASSERT_TRUE(base::ReadFileToString(file_path, &content));
               EXPECT_EQ(kDefaultResponseContent, content);
               EXPECT_EQ(file_size, static_cast<int64_t>(content.size()));
+              DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
               loop.Quit();
             }),
         base::DoNothing());
@@ -92,6 +94,7 @@
   std::unique_ptr<HttpRequest> request_sent_;
   base::ScopedTempDir dir_;
   std::unique_ptr<BackgroundDownloadTaskHelper> helper_;
+  SEQUENCE_CHECKER(sequence_checker_);
 };
 
 // Verifies download can be finished.
diff --git a/components/download/internal/common/base_file_unittest.cc b/components/download/internal/common/base_file_unittest.cc
index 9e384e1..dbfa9c5 100644
--- a/components/download/internal/common/base_file_unittest.cc
+++ b/components/download/internal/common/base_file_unittest.cc
@@ -23,6 +23,10 @@
 #include "crypto/sha2.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if defined(OS_WIN)
+#include "base/win/scoped_com_initializer.h"
+#endif
+
 namespace download {
 namespace {
 
@@ -61,6 +65,9 @@
         expected_error_(DOWNLOAD_INTERRUPT_REASON_NONE) {}
 
   void SetUp() override {
+#if defined(OS_WIN)
+    ASSERT_TRUE(com_initializer_.Succeeded());
+#endif
     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
     base_file_ = std::make_unique<BaseFile>(DownloadItem::kInvalidId);
   }
@@ -185,6 +192,13 @@
     EXPECT_EQ(0, memcmp(expected_hash, &hash_value.front(), hash_value.size()));
   }
 
+ private:
+#if defined(OS_WIN)
+  // This must occur early in the member list to ensure COM is initialized first
+  // and uninitialized last.
+  base::win::ScopedCOMInitializer com_initializer_;
+#endif
+
  protected:
   // BaseClass instance we are testing.
   std::unique_ptr<BaseFile> base_file_;
diff --git a/components/download/internal/common/download_file_unittest.cc b/components/download/internal/common/download_file_unittest.cc
index 5dcac40..0d1618a 100644
--- a/components/download/internal/common/download_file_unittest.cc
+++ b/components/download/internal/common/download_file_unittest.cc
@@ -30,6 +30,10 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if defined(OS_WIN)
+#include "base/win/scoped_com_initializer.h"
+#endif
+
 using ::testing::_;
 using ::testing::AnyNumber;
 using ::testing::DoAll;
@@ -170,6 +174,9 @@
   }
 
   void SetUp() override {
+#if defined(OS_WIN)
+    ASSERT_TRUE(com_initializer_.Succeeded());
+#endif
     EXPECT_CALL(*(observer_.get()), DestinationUpdate(_, _, _))
         .Times(AnyNumber())
         .WillRepeatedly(Invoke(this, &DownloadFileTest::SetUpdateDownloadInfo));
@@ -475,6 +482,14 @@
     return download_file_->TotalBytesReceived();
   }
 
+ private:
+#if defined(OS_WIN)
+  // This must occur early in the member list to ensure COM is initialized first
+  // and uninitialized last.
+  base::win::ScopedCOMInitializer com_initializer_;
+#endif
+
+ protected:
   std::unique_ptr<StrictMock<MockDownloadDestinationObserver>> observer_;
   base::WeakPtrFactory<DownloadDestinationObserver> observer_factory_;
 
diff --git a/components/exo/data_offer_unittest.cc b/components/exo/data_offer_unittest.cc
index ac6acdf..1be2faf 100644
--- a/components/exo/data_offer_unittest.cc
+++ b/components/exo/data_offer_unittest.cc
@@ -96,7 +96,7 @@
   void PasteIfAllowed(const ui::DataTransferEndpoint* const data_src,
                       const ui::DataTransferEndpoint* const data_dst,
                       const absl::optional<size_t> size,
-                      content::WebContents* web_contents,
+                      content::RenderFrameHost* web_contents,
                       base::OnceCallback<void(bool)> callback) override {}
 
   bool IsDragDropAllowed(const ui::DataTransferEndpoint* const data_src,
diff --git a/components/exo/wayland/zaura_shell_unittest.cc b/components/exo/wayland/zaura_shell_unittest.cc
index ad99a07..a1bfaf9 100644
--- a/components/exo/wayland/zaura_shell_unittest.cc
+++ b/components/exo/wayland/zaura_shell_unittest.cc
@@ -319,8 +319,6 @@
             ash::window_util::GetActiveWindow());
   EXPECT_EQ(0.0f, occlusion_fraction_on_activation_loss());
   EXPECT_EQ(0.0f, aura_surface().last_sent_occlusion_fraction());
-  EXPECT_EQ(aura::Window::OcclusionState::VISIBLE,
-            aura_surface().last_sent_occlusion_state());
 }
 
 TEST_F(ZAuraSurfaceTest, OcclusionIncludesOffScreenArea) {
diff --git a/components/feed/core/common/pref_names.cc b/components/feed/core/common/pref_names.cc
index 0144f765..7b029c0f 100644
--- a/components/feed/core/common/pref_names.cc
+++ b/components/feed/core/common/pref_names.cc
@@ -43,6 +43,7 @@
 const char kReliabilityLoggingIdSalt[] = "feedv2.reliability_logging_id_salt";
 const char kHasStoredData[] = "feedv2.has_stored_data";
 const char kWebFeedContentOrder[] = "webfeed.content_order";
+const char kLastSeenFeedType[] = "feedv2.last_seen_feed_type";
 
 }  // namespace prefs
 
@@ -112,6 +113,7 @@
   registry->RegisterUint64Pref(feed::prefs::kReliabilityLoggingIdSalt, 0);
   registry->RegisterBooleanPref(feed::prefs::kHasStoredData, false);
   registry->RegisterIntegerPref(feed::prefs::kWebFeedContentOrder, 0);
+  registry->RegisterIntegerPref(feed::prefs::kLastSeenFeedType, 0);
 
 #if defined(OS_IOS)
   registry->RegisterBooleanPref(feed::prefs::kLastFetchHadLoggingEnabled,
diff --git a/components/feed/core/common/pref_names.h b/components/feed/core/common/pref_names.h
index b673e06..b6dd78c5 100644
--- a/components/feed/core/common/pref_names.h
+++ b/components/feed/core/common/pref_names.h
@@ -74,6 +74,8 @@
 extern const char kHasStoredData[];
 // `feed::ContentOrder` of the Web feed.
 extern const char kWebFeedContentOrder[];
+// The last feed type that the user was viewing.
+extern const char kLastSeenFeedType[];
 
 }  // namespace prefs
 
diff --git a/components/gwp_asan/buildflags/buildflags.gni b/components/gwp_asan/buildflags/buildflags.gni
index 06769f9..92a5158 100644
--- a/components/gwp_asan/buildflags/buildflags.gni
+++ b/components/gwp_asan/buildflags/buildflags.gni
@@ -10,7 +10,7 @@
 # Android requires frame pointers for unwinding, unwind tables aren't shipped in
 # official builds.
 supported_platform =
-    is_mac || (is_win && current_cpu == "x64") ||
+    is_linux || is_chromeos || is_mac || (is_win && current_cpu == "x64") ||
     (is_android && !is_component_build && enable_frame_pointers)
 
 declare_args() {
diff --git a/components/lookalikes/core/lookalike_url_util.cc b/components/lookalikes/core/lookalike_url_util.cc
index fa252390..99ab331b 100644
--- a/components/lookalikes/core/lookalike_url_util.cc
+++ b/components/lookalikes/core/lookalike_url_util.cc
@@ -114,14 +114,16 @@
   return std::string();
 }
 
-// Returns the first matching top domain with an edit distance of at most one
-// to |domain_and_registry|. This search is done in lexicographic order on the
-// top 500 suitable domains, instead of in order by popularity. This means that
-// the resulting "similar" domain may not be the most popular domain that
-// matches.
-std::string GetSimilarDomainFromTop500(
+// Scans the top sites list and returns true if it finds a domain with an edit
+// distance or character swap of one to |domain_and_registry|. This search is
+// done in lexicographic order on the top 500 suitable domains, instead of in
+// order by popularity. This means that the resulting "similar" domain may not
+// be the most popular domain that matches.
+bool GetSimilarDomainFromTop500(
     const DomainInfo& navigated_domain,
-    const LookalikeTargetAllowlistChecker& target_allowlisted) {
+    const LookalikeTargetAllowlistChecker& target_allowlisted,
+    std::string* matched_domain,
+    LookalikeUrlMatchType* match_type) {
   for (const std::string& navigated_skeleton : navigated_domain.skeletons) {
     for (const char* const top_domain_skeleton :
          top500_domains::kTop500EditDistanceSkeletons) {
@@ -129,32 +131,45 @@
       if (strlen(top_domain_skeleton) == 0) {
         continue;
       }
+      // Check edit distance on skeletons.
+      if (IsEditDistanceAtMostOne(base::UTF8ToUTF16(navigated_skeleton),
+                                  base::UTF8ToUTF16(top_domain_skeleton))) {
+        const std::string top_domain =
+            url_formatter::LookupSkeletonInTopDomains(
+                top_domain_skeleton, url_formatter::SkeletonType::kFull)
+                .domain;
+        DCHECK(!top_domain.empty());
 
-      if (!IsEditDistanceAtMostOne(base::UTF8ToUTF16(navigated_skeleton),
-                                   base::UTF8ToUTF16(top_domain_skeleton))) {
-        continue;
+        if (!IsLikelyEditDistanceFalsePositive(navigated_domain,
+                                               GetDomainInfo(top_domain)) &&
+            !target_allowlisted.Run(top_domain)) {
+          *matched_domain = top_domain;
+          *match_type = LookalikeUrlMatchType::kEditDistance;
+          return true;
+        }
       }
 
-      const std::string top_domain =
-          url_formatter::LookupSkeletonInTopDomains(
-              top_domain_skeleton, url_formatter::SkeletonType::kFull)
-              .domain;
-      DCHECK(!top_domain.empty());
-
-      if (IsLikelyEditDistanceFalsePositive(navigated_domain,
-                                            GetDomainInfo(top_domain))) {
-        continue;
+      // Check character swap on skeletons.
+      // TODO(crbug/1109056): Also check character swap on actual hostnames
+      // with diacritics etc removed. This is because some characters have two
+      // character skeletons such as m -> rn, and this prevents us from
+      // detecting character swaps between example.com and exapmle.com.
+      if (HasOneCharacterSwap(base::UTF8ToUTF16(navigated_skeleton),
+                              base::UTF8ToUTF16(top_domain_skeleton))) {
+        const std::string top_domain =
+            url_formatter::LookupSkeletonInTopDomains(
+                top_domain_skeleton, url_formatter::SkeletonType::kFull)
+                .domain;
+        DCHECK(!top_domain.empty());
+        if (!target_allowlisted.Run(top_domain)) {
+          *matched_domain = top_domain;
+          *match_type = LookalikeUrlMatchType::kCharacterSwapTop500;
+          return true;
+        }
       }
-
-      // Skip past domains that are allowed to be spoofed.
-      if (target_allowlisted.Run(top_domain)) {
-        continue;
-      }
-
-      return top_domain;
     }
   }
-  return std::string();
+  return false;
 }
 
 // Scans the engaged site list for edit distance and character swap matches.
@@ -724,13 +739,12 @@
       return true;
     }
 
-    // Finally, try to find a top domain within an edit distance of one.
-    const std::string similar_top_domain =
-        GetSimilarDomainFromTop500(navigated_domain, in_target_allowlist);
-    if (!similar_top_domain.empty() &&
-        navigated_domain.domain_and_registry != similar_top_domain) {
-      *matched_domain = similar_top_domain;
-      *match_type = LookalikeUrlMatchType::kEditDistance;
+    // Finally, try to find a top domain within an edit distance or character
+    // swap of one.
+    if (GetSimilarDomainFromTop500(navigated_domain, in_target_allowlist,
+                                   matched_domain, match_type)) {
+      DCHECK_NE(navigated_domain.domain_and_registry, *matched_domain);
+      DCHECK(!matched_domain->empty());
       return true;
     }
   }
@@ -780,6 +794,9 @@
     case LookalikeUrlMatchType::kCharacterSwapSiteEngagement:
       RecordEvent(NavigationSuggestionEvent::kMatchCharacterSwapSiteEngagement);
       break;
+    case LookalikeUrlMatchType::kCharacterSwapTop500:
+      RecordEvent(NavigationSuggestionEvent::kMatchCharacterSwapTop500);
+      break;
     case LookalikeUrlMatchType::kNone:
       break;
   }
diff --git a/components/lookalikes/core/lookalike_url_util.h b/components/lookalikes/core/lookalike_url_util.h
index 6ebc9e3..c640b145 100644
--- a/components/lookalikes/core/lookalike_url_util.h
+++ b/components/lookalikes/core/lookalike_url_util.h
@@ -55,10 +55,11 @@
   kFailedSpoofChecks = 9,
 
   kCharacterSwapSiteEngagement = 10,
+  kCharacterSwapTop500 = 11,
 
   // Append new items to the end of the list above; do not modify or replace
   // existing values. Comment out obsolete items.
-  kMaxValue = kCharacterSwapSiteEngagement,
+  kMaxValue = kCharacterSwapTop500,
 };
 
 // Used for UKM. There is only a single LookalikeUrlBlockingPageUserAction per
@@ -91,10 +92,11 @@
   kMatchTargetEmbeddingForSafetyTips = 10,
   kFailedSpoofChecks = 11,
   kMatchCharacterSwapSiteEngagement = 12,
+  kMatchCharacterSwapTop500 = 13,
 
   // Append new items to the end of the list above; do not modify or
   // replace existing values. Comment out obsolete items.
-  kMaxValue = kMatchCharacterSwapSiteEngagement,
+  kMaxValue = kMatchCharacterSwapTop500,
 };
 
 struct DomainInfo {
diff --git a/components/omnibox/browser/autocomplete_match.cc b/components/omnibox/browser/autocomplete_match.cc
index 0566af4..0aee5b9f 100644
--- a/components/omnibox/browser/autocomplete_match.cc
+++ b/components/omnibox/browser/autocomplete_match.cc
@@ -9,7 +9,6 @@
 #include "base/check_op.h"
 #include "base/cxx17_backports.h"
 #include "base/debug/crash_logging.h"
-#include "base/debug/dump_without_crashing.h"
 #include "base/feature_list.h"
 #include "base/i18n/case_conversion.h"
 #include "base/logging.h"
@@ -43,6 +42,7 @@
 #endif
 
 namespace {
+
 bool IsTrivialClassification(const ACMatchClassifications& classifications) {
   return classifications.empty() ||
       ((classifications.size() == 1) &&
@@ -225,11 +225,7 @@
       additional_info(match.additional_info),
       duplicate_matches(match.duplicate_matches),
       query_tiles(match.query_tiles),
-      navsuggest_tiles(match.navsuggest_tiles) {
-#if defined(OS_ANDROID)
-  magic_signature_ = match.magic_signature_;
-#endif
-}
+      navsuggest_tiles(match.navsuggest_tiles) {}
 
 AutocompleteMatch::AutocompleteMatch(AutocompleteMatch&& match) noexcept {
   *this = std::move(match);
@@ -290,8 +286,6 @@
   DestroyJavaObject();
   std::swap(java_match_, match.java_match_);
   UpdateJavaObjectNativeRef();
-  magic_signature_ = match.magic_signature_;
-  match.magic_signature_ = 0;
 #endif
   return *this;
 }
@@ -299,7 +293,6 @@
 AutocompleteMatch::~AutocompleteMatch() {
 #if defined(OS_ANDROID)
   DestroyJavaObject();
-  magic_signature_ = 0;
 #endif
 }
 
@@ -993,16 +986,6 @@
 
 std::string AutocompleteMatch::GetAdditionalInfo(
     const std::string& property) const {
-#if defined(OS_ANDROID)
-  if (!CheckMatchAlive()) {
-    SCOPED_CRASH_KEY_STRING32("ACMatch", "bad-magic", "true");
-    DCHECK(false)
-        << "Please report this on crbug.com/1217575, and include both C++ and "
-        << "Java stack traces where possible.";
-    base::debug::DumpWithoutCrashing();
-    return "";
-  }
-#endif
   auto i(additional_info.find(property));
   return (i == additional_info.end()) ? std::string() : i->second;
 }
diff --git a/components/omnibox/browser/autocomplete_match.h b/components/omnibox/browser/autocomplete_match.h
index 23252733d..9b47847 100644
--- a/components/omnibox/browser/autocomplete_match.h
+++ b/components/omnibox/browser/autocomplete_match.h
@@ -233,9 +233,6 @@
   void UpdateJavaAnswer();
   // Update the Java object description.
   void UpdateJavaDescription();
-  // Returns an information associated with the match, indicating that the match
-  // has neither been destroyed nor has it been moved-from.
-  bool CheckMatchAlive() const { return magic_signature_ == kMagicSignature; }
 #endif
 
 #if (!defined(OS_ANDROID) || BUILDFLAG(ENABLE_VR)) && !defined(OS_IOS)
@@ -772,11 +769,6 @@
   mutable std::unique_ptr<base::android::ScopedJavaGlobalRef<jobject>>
       java_match_;
 
-  // TODO(1217575): Needed for troubleshooting possible UAF. Delete when bug is
-  // fixed and this logic is no longer needed.
-  static constexpr const int kMagicSignature = 0xfce2fe43;
-  int magic_signature_{kMagicSignature};
-
   base::WeakPtrFactory<AutocompleteMatch> weak_ptr_factory_{this};
 #endif
 };
diff --git a/components/omnibox/browser/autocomplete_result_android.cc b/components/omnibox/browser/autocomplete_result_android.cc
index ebf592d..f20bddd 100644
--- a/components/omnibox/browser/autocomplete_result_android.cc
+++ b/components/omnibox/browser/autocomplete_result_android.cc
@@ -12,11 +12,7 @@
 #include "base/android/jni_array.h"
 #include "base/android/jni_string.h"
 #include "base/containers/contains.h"
-#include "base/debug/crash_logging.h"
-#include "base/debug/dump_without_crashing.h"
 #include "base/metrics/histogram_macros.h"
-#include "base/strings/string_number_conversions.h"
-#include "base/strings/utf_string_conversions.h"
 #include "components/omnibox/browser/jni_headers/AutocompleteResult_jni.h"
 #include "components/omnibox/browser/search_suggestion_parser.h"
 #include "components/query_tiles/android/tile_conversion_bridge.h"
@@ -38,7 +34,7 @@
   VALID_MATCH = 0,
   WRONG_MATCH = 1,
   BAD_RESULT_SIZE = 2,
-  NATIVE_MATCH_DEAD = 3,
+  OBSOLETE_NATIVE_MATCH_DEAD = 3,
   INVALID_MATCH_POSITION = 4,
   // Keep as the last entry:
   COUNT
@@ -166,7 +162,7 @@
         auto* other_match = reinterpret_cast<AutocompleteMatch*>(j_matches[i]);
         DLOG(WARNING) << "Suggestion at index " << i << ": "
                       << "(Native): " << this_match->fill_into_edit
-                      << "; (Java): "
+                      << "(Java): "
                       << (other_match ? other_match->fill_into_edit
                                       : u"<null>");
       }
@@ -176,44 +172,6 @@
           << index;
       return false;
     }
-
-    if (!match_at(index)->CheckMatchAlive()) {
-      SCOPED_CRASH_KEY_STRING32("ACMatch", "bad-magic", "true");
-      const auto* match = match_at(index);
-
-      // Construct the debug info string. Report fallback crash key in case
-      // things are broken beyond repair.
-      const std::string debug_info =
-          "I:" + base::NumberToString(index) + "/" +
-          base::NumberToString(size()) + "; P:" +
-          ((match->provider != nullptr) ? match->provider->GetName()
-                                        : "<null>");
-
-      SCOPED_CRASH_KEY_STRING32("ACMatch", "info", debug_info);
-
-      UMA_HISTOGRAM_ENUMERATION("Android.Omnibox.InvalidMatch",
-                                MatchVerificationResult::NATIVE_MATCH_DEAD,
-                                MatchVerificationResult::COUNT);
-      // Note: the NDEBUG is defined for release / debug-disabled builds.
-#ifndef NDEBUG
-      // Print the list of matches at every position on each side.
-      // Used for debugging purposes.
-      for (auto i = 0u; i < size(); i++) {
-        auto* this_match = match_at(i);
-        auto* other_match = reinterpret_cast<AutocompleteMatch*>(j_matches[i]);
-        DLOG(WARNING) << "Suggestion at index " << i << ": "
-                      << "(Native): " << this_match->fill_into_edit
-                      << ", alive:" << this_match->CheckMatchAlive()
-                      << "; (Java): "
-                      << (other_match ? other_match->fill_into_edit
-                                      : u"<null>");
-      }
-#endif
-      NOTREACHED() << "Native AutocompleteMatch dead at " << index
-                   << " - please report";
-      base::debug::DumpWithoutCrashing();
-      return false;
-    }
   }
 
   UMA_HISTOGRAM_ENUMERATION("Android.Omnibox.InvalidMatch",
diff --git a/components/paint_preview/browser/BUILD.gn b/components/paint_preview/browser/BUILD.gn
index 7dc28043..86d2cdc7 100644
--- a/components/paint_preview/browser/BUILD.gn
+++ b/components/paint_preview/browser/BUILD.gn
@@ -29,7 +29,6 @@
     "paint_preview_file_mixin.cc",
     "paint_preview_file_mixin.h",
     "paint_preview_policy.h",
-    "service_sandbox_type.h",
     "warm_compositor.cc",
     "warm_compositor.h",
   ]
diff --git a/components/paint_preview/browser/DEPS b/components/paint_preview/browser/DEPS
index e6e5c8e7..72880bb 100644
--- a/components/paint_preview/browser/DEPS
+++ b/components/paint_preview/browser/DEPS
@@ -10,7 +10,6 @@
   "+content/public/browser",
   "+content/public/test",
   "+services/metrics/public/cpp",
-  "+sandbox/policy/sandbox_type.h",
   "+third_party/blink/public/common",
   "+third_party/zlib/google",
   "+ui/accessibility",
diff --git a/components/paint_preview/browser/compositor_utils.cc b/components/paint_preview/browser/compositor_utils.cc
index f8ce150a..2df00aeb 100644
--- a/components/paint_preview/browser/compositor_utils.cc
+++ b/components/paint_preview/browser/compositor_utils.cc
@@ -11,7 +11,6 @@
 #include "build/build_config.h"
 #include "components/discardable_memory/service/discardable_shared_memory_manager.h"
 #include "components/paint_preview/browser/paint_preview_compositor_service_impl.h"
-#include "components/paint_preview/browser/service_sandbox_type.h"
 #include "components/services/paint_preview_compositor/public/mojom/paint_preview_compositor.mojom.h"
 #include "components/strings/grit/components_strings.h"
 #include "content/public/browser/browser_task_traits.h"
diff --git a/components/paint_preview/browser/compositor_utils.h b/components/paint_preview/browser/compositor_utils.h
index 2bb23d8a..5776ba6 100644
--- a/components/paint_preview/browser/compositor_utils.h
+++ b/components/paint_preview/browser/compositor_utils.h
@@ -6,7 +6,7 @@
 #define COMPONENTS_PAINT_PREVIEW_BROWSER_COMPOSITOR_UTILS_H_
 
 #include "components/paint_preview/public/paint_preview_compositor_service.h"
-#include "components/services/paint_preview_compositor/public/mojom/paint_preview_compositor.mojom.h"
+#include "components/services/paint_preview_compositor/public/mojom/paint_preview_compositor.mojom-forward.h"
 #include "mojo/public/cpp/bindings/remote.h"
 
 namespace paint_preview {
diff --git a/components/paint_preview/browser/service_sandbox_type.h b/components/paint_preview/browser/service_sandbox_type.h
deleted file mode 100644
index e15d15fa..0000000
--- a/components/paint_preview/browser/service_sandbox_type.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_PAINT_PREVIEW_BROWSER_SERVICE_SANDBOX_TYPE_H_
-#define COMPONENTS_PAINT_PREVIEW_BROWSER_SERVICE_SANDBOX_TYPE_H_
-
-#include "content/public/browser/service_process_host.h"
-#include "sandbox/policy/sandbox_type.h"
-
-// This file maps service classes to sandbox types.  Services which
-// require a non-utility sandbox can be added here.  See
-// ServiceProcessHost::Launch() for how these templates are consumed.
-
-// paint_preview::mojom::PaintPreviewCompositorCollection
-namespace paint_preview {
-namespace mojom {
-class PaintPreviewCompositorCollection;
-}
-}  // namespace paint_preview
-
-template <>
-inline sandbox::policy::SandboxType content::GetServiceSandboxType<
-    paint_preview::mojom::PaintPreviewCompositorCollection>() {
-  // TODO(crbug/1074323): Investigate using a different SandboxType.
-  return sandbox::policy::SandboxType::kPrintCompositor;
-}
-
-#endif  // COMPONENTS_PAINT_PREVIEW_BROWSER_SERVICE_SANDBOX_TYPE_H_
diff --git a/components/password_manager/core/browser/form_fetcher_impl.cc b/components/password_manager/core/browser/form_fetcher_impl.cc
index aa548f3..2d755900 100644
--- a/components/password_manager/core/browser/form_fetcher_impl.cc
+++ b/components/password_manager/core/browser/form_fetcher_impl.cc
@@ -16,7 +16,7 @@
 #include "components/password_manager/core/browser/password_form.h"
 #include "components/password_manager/core/browser/password_manager_client.h"
 #include "components/password_manager/core/browser/password_manager_util.h"
-#include "components/password_manager/core/browser/password_store.h"
+#include "components/password_manager/core/browser/password_store_interface.h"
 #include "components/password_manager/core/browser/psl_matching_helper.h"
 #include "components/password_manager/core/browser/statistics_table.h"
 #include "components/password_manager/core/common/password_manager_features.h"
@@ -106,7 +106,8 @@
     return;
   }
 
-  PasswordStore* password_store = client_->GetProfilePasswordStore();
+  PasswordStoreInterface* password_store =
+      client_->GetProfilePasswordStoreInterface();
   if (!password_store) {
     if (logger)
       logger->LogMessage(Logger::STRING_NO_STORE);
@@ -269,7 +270,8 @@
       form_digest_.url.SchemeIs(url::kHttpsScheme)) {
     http_migrator_ = std::make_unique<HttpPasswordStoreMigrator>(
         url::Origin::Create(form_digest_.url),
-        client_->GetProfilePasswordStore(), client_->GetNetworkContext(), this);
+        client_->GetProfilePasswordStoreInterface(),
+        client_->GetNetworkContext(), this);
     return;
   }
 
diff --git a/components/password_manager/core/browser/form_fetcher_impl_unittest.cc b/components/password_manager/core/browser/form_fetcher_impl_unittest.cc
index db290c38..fa1c5b6 100644
--- a/components/password_manager/core/browser/form_fetcher_impl_unittest.cc
+++ b/components/password_manager/core/browser/form_fetcher_impl_unittest.cc
@@ -18,12 +18,12 @@
 #include "build/build_config.h"
 #include "components/password_manager/core/browser/android_affiliation/affiliated_match_helper.h"
 #include "components/password_manager/core/browser/android_affiliation/mock_affiliated_match_helper.h"
-#include "components/password_manager/core/browser/mock_password_store.h"
+#include "components/password_manager/core/browser/mock_password_store_interface.h"
 #include "components/password_manager/core/browser/mock_smart_bubble_stats_store.h"
 #include "components/password_manager/core/browser/multi_store_form_fetcher.h"
 #include "components/password_manager/core/browser/password_form.h"
 #include "components/password_manager/core/browser/password_manager_test_utils.h"
-#include "components/password_manager/core/browser/password_store.h"
+#include "components/password_manager/core/browser/password_store_interface.h"
 #include "components/password_manager/core/browser/statistics_table.h"
 #include "components/password_manager/core/browser/stub_credentials_filter.h"
 #include "components/password_manager/core/browser/stub_password_manager_client.h"
@@ -116,7 +116,7 @@
     filter_ = std::move(filter);
   }
 
-  void set_store(PasswordStore* store) { store_ = store; }
+  void set_store(PasswordStoreInterface* store) { store_ = store; }
 
  private:
   const CredentialsFilter* GetStoreResultFilter() const override {
@@ -124,11 +124,15 @@
                    : StubPasswordManagerClient::GetStoreResultFilter();
   }
 
-  PasswordStore* GetProfilePasswordStore() const override { return store_; }
-  PasswordStore* GetAccountPasswordStore() const override { return nullptr; }
+  PasswordStoreInterface* GetProfilePasswordStoreInterface() const override {
+    return store_;
+  }
+  PasswordStoreInterface* GetAccountPasswordStoreInterface() const override {
+    return nullptr;
+  }
 
   std::unique_ptr<CredentialsFilter> filter_;
-  PasswordStore* store_ = nullptr;
+  PasswordStoreInterface* store_ = nullptr;
   mutable FakeNetworkContext network_context_;
 
   DISALLOW_COPY_AND_ASSIGN(FakePasswordManagerClient);
@@ -214,8 +218,7 @@
       : form_digest_(PasswordForm::Scheme::kHtml,
                      kTestHttpURL,
                      GURL(kTestHttpURL)) {
-    mock_store_ = new testing::NiceMock<MockPasswordStore>;
-    mock_store_->Init(nullptr);
+    mock_store_ = new testing::NiceMock<MockPasswordStoreInterface>;
     client_.set_store(mock_store_.get());
 
     if (!GetParam()) {
@@ -236,7 +239,7 @@
         .WillByDefault(Return(&mock_smart_bubble_stats_store_));
   }
 
-  ~FormFetcherImplTest() override { mock_store_->ShutdownOnUIThread(); }
+  ~FormFetcherImplTest() override = default;
 
  protected:
   // A wrapper around form_fetcher_.Fetch(), adding the call expectations.
@@ -254,7 +257,7 @@
   PasswordFormDigest form_digest_;
   std::unique_ptr<FormFetcherImpl> form_fetcher_;
   MockConsumer consumer_;
-  scoped_refptr<MockPasswordStore> mock_store_;
+  scoped_refptr<MockPasswordStoreInterface> mock_store_;
   testing::NiceMock<MockSmartBubbleStatsStore> mock_smart_bubble_stats_store_;
   FakePasswordManagerClient client_;
 
diff --git a/components/password_manager/core/browser/multi_store_form_fetcher.cc b/components/password_manager/core/browser/multi_store_form_fetcher.cc
index 8fc212ca..3228713 100644
--- a/components/password_manager/core/browser/multi_store_form_fetcher.cc
+++ b/components/password_manager/core/browser/multi_store_form_fetcher.cc
@@ -39,7 +39,8 @@
     return;
   }
 
-  PasswordStore* account_password_store = client_->GetAccountPasswordStore();
+  PasswordStoreInterface* account_password_store =
+      client_->GetAccountPasswordStoreInterface();
 
   // Issue a fetch from the profile store and, if it exists, also from the
   // account store.
diff --git a/components/pdf/renderer/DEPS b/components/pdf/renderer/DEPS
index f31a6ef..31597bd 100644
--- a/components/pdf/renderer/DEPS
+++ b/components/pdf/renderer/DEPS
@@ -8,6 +8,7 @@
   "+pdf/buildflags.h",
   "+pdf/mojom/pdf.mojom.h",
   "+pdf/pdf_accessibility_action_handler.h",
+  "+pdf/pdf_accessibility_data_handler.h",
   "+pdf/pdf_features.h",
   "+pdf/pdf_view_web_plugin.h",
   "+ppapi",
diff --git a/components/pdf/renderer/pdf_accessibility_tree.h b/components/pdf/renderer/pdf_accessibility_tree.h
index e14a933..b5f69b87 100644
--- a/components/pdf/renderer/pdf_accessibility_tree.h
+++ b/components/pdf/renderer/pdf_accessibility_tree.h
@@ -11,6 +11,7 @@
 
 #include "content/public/renderer/plugin_ax_tree_source.h"
 #include "content/public/renderer/render_frame_observer.h"
+#include "pdf/pdf_accessibility_data_handler.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/accessibility/ax_node.h"
 #include "ui/accessibility/ax_tree.h"
@@ -42,7 +43,8 @@
 namespace pdf {
 
 class PdfAccessibilityTree : public content::PluginAXTreeSource,
-                             public content::RenderFrameObserver {
+                             public content::RenderFrameObserver,
+                             public chrome_pdf::PdfAccessibilityDataHandler {
  public:
   PdfAccessibilityTree(
       content::RenderFrame* render_frame,
@@ -64,15 +66,17 @@
     uint32_t annotation_index;
   };
 
+  // chrome_pdf::PdfAccessibilityDataHandler:
   void SetAccessibilityViewportInfo(
-      const chrome_pdf::AccessibilityViewportInfo& viewport_info);
+      const chrome_pdf::AccessibilityViewportInfo& viewport_info) override;
   void SetAccessibilityDocInfo(
-      const chrome_pdf::AccessibilityDocInfo& doc_info);
+      const chrome_pdf::AccessibilityDocInfo& doc_info) override;
   void SetAccessibilityPageInfo(
       const chrome_pdf::AccessibilityPageInfo& page_info,
       const std::vector<chrome_pdf::AccessibilityTextRunInfo>& text_runs,
       const std::vector<chrome_pdf::AccessibilityCharInfo>& chars,
-      const chrome_pdf::AccessibilityPageObjects& page_objects);
+      const chrome_pdf::AccessibilityPageObjects& page_objects) override;
+
   void HandleAction(const chrome_pdf::AccessibilityActionData& action_data);
   absl::optional<AnnotationInfo> GetPdfAnnotationInfoFromAXNode(
       int32_t ax_node_id) const;
diff --git a/components/pdf/renderer/pdf_view_web_plugin_client.cc b/components/pdf/renderer/pdf_view_web_plugin_client.cc
index 7a81a3c6..5614983 100644
--- a/components/pdf/renderer/pdf_view_web_plugin_client.cc
+++ b/components/pdf/renderer/pdf_view_web_plugin_client.cc
@@ -5,6 +5,7 @@
 #include "components/pdf/renderer/pdf_view_web_plugin_client.h"
 
 #include "base/check.h"
+#include "components/pdf/renderer/pdf_accessibility_tree.h"
 #include "content/public/renderer/render_thread.h"
 #include "printing/buildflags/buildflags.h"
 #include "third_party/blink/public/web/web_element.h"
@@ -34,4 +35,10 @@
   content::RenderThread::Get()->RecordComputedAction(action);
 }
 
+std::unique_ptr<chrome_pdf::PdfAccessibilityDataHandler>
+PdfViewWebPluginClient::CreateAccessibilityDataHandler(
+    chrome_pdf::PdfAccessibilityActionHandler* action_handler) {
+  return std::make_unique<PdfAccessibilityTree>(render_frame_, action_handler);
+}
+
 }  // namespace pdf
diff --git a/components/pdf/renderer/pdf_view_web_plugin_client.h b/components/pdf/renderer/pdf_view_web_plugin_client.h
index ec8cd57..082713c 100644
--- a/components/pdf/renderer/pdf_view_web_plugin_client.h
+++ b/components/pdf/renderer/pdf_view_web_plugin_client.h
@@ -23,6 +23,9 @@
   // chrome_pdf::PdfViewWebPlugin::Client:
   void Print(const blink::WebElement& element) override;
   void RecordComputedAction(const std::string& action) override;
+  std::unique_ptr<chrome_pdf::PdfAccessibilityDataHandler>
+  CreateAccessibilityDataHandler(
+      chrome_pdf::PdfAccessibilityActionHandler* action_handler) override;
 
  private:
   content::RenderFrame* const render_frame_;
diff --git a/components/printing/browser/BUILD.gn b/components/printing/browser/BUILD.gn
index 8ec33507..35f2de2 100644
--- a/components/printing/browser/BUILD.gn
+++ b/components/printing/browser/BUILD.gn
@@ -12,7 +12,6 @@
     "print_manager.h",
     "print_manager_utils.cc",
     "print_manager_utils.h",
-    "service_sandbox_type.h",
   ]
 
   public_deps = [ "//content/public/browser" ]
diff --git a/components/printing/browser/DEPS b/components/printing/browser/DEPS
index f1c82e26..9e2eaa9 100644
--- a/components/printing/browser/DEPS
+++ b/components/printing/browser/DEPS
@@ -7,7 +7,6 @@
   "+components/strings/grit",
   "+content/public/browser",
   "+mojo/public",
-  "+sandbox/policy/sandbox_type.h",
   "+ui/base/l10n",
 ]
 
diff --git a/components/printing/browser/OWNERS b/components/printing/browser/OWNERS
deleted file mode 100644
index bc908bd..0000000
--- a/components/printing/browser/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-
-# Service sandbox specialization must be reviewed by SECURITY_OWNERS
-per-file service_sandbox_type.h=set noparent
-per-file service_sandbox_type.h=file://ipc/SECURITY_OWNERS
diff --git a/components/printing/browser/print_composite_client.cc b/components/printing/browser/print_composite_client.cc
index df55bb2b..22063503 100644
--- a/components/printing/browser/print_composite_client.cc
+++ b/components/printing/browser/print_composite_client.cc
@@ -11,8 +11,8 @@
 #include "base/memory/read_only_shared_memory_region.h"
 #include "build/build_config.h"
 #include "components/discardable_memory/service/discardable_shared_memory_manager.h"
-#include "components/printing/browser/service_sandbox_type.h"
 #include "components/services/print_compositor/public/cpp/print_service_mojo_types.h"
+#include "components/services/print_compositor/public/mojom/print_compositor.mojom.h"
 #include "components/strings/grit/components_strings.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
diff --git a/components/printing/browser/service_sandbox_type.h b/components/printing/browser/service_sandbox_type.h
deleted file mode 100644
index 0281ce7..0000000
--- a/components/printing/browser/service_sandbox_type.h
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_PRINTING_BROWSER_SERVICE_SANDBOX_TYPE_H_
-#define COMPONENTS_PRINTING_BROWSER_SERVICE_SANDBOX_TYPE_H_
-
-#include "content/public/browser/service_process_host.h"
-#include "sandbox/policy/sandbox_type.h"
-
-// This file maps service classes to sandbox types.  Services which
-// require a non-utility sandbox can be added here.  See
-// ServiceProcessHost::Launch() for how these templates are consumed.
-
-// printing::mojom::PrintCompositor
-namespace printing {
-namespace mojom {
-class PrintCompositor;
-}
-}  // namespace printing
-
-template <>
-inline sandbox::policy::SandboxType
-content::GetServiceSandboxType<printing::mojom::PrintCompositor>() {
-  return sandbox::policy::SandboxType::kPrintCompositor;
-}
-
-#endif  // COMPONENTS_PRINTING_BROWSER_SERVICE_SANDBOX_TYPE_H_
diff --git a/components/services/paint_preview_compositor/public/mojom/BUILD.gn b/components/services/paint_preview_compositor/public/mojom/BUILD.gn
index 23c1f49..027a20a 100644
--- a/components/services/paint_preview_compositor/public/mojom/BUILD.gn
+++ b/components/services/paint_preview_compositor/public/mojom/BUILD.gn
@@ -15,6 +15,7 @@
     "//components/discardable_memory/public/mojom",
     "//components/paint_preview/common/mojom",
     "//mojo/public/mojom/base",
+    "//sandbox/policy/mojom",
     "//skia/public/mojom",
     "//ui/gfx/geometry/mojom",
     "//url/mojom:url_mojom_gurl",
diff --git a/components/services/paint_preview_compositor/public/mojom/paint_preview_compositor.mojom b/components/services/paint_preview_compositor/public/mojom/paint_preview_compositor.mojom
index 90120a0..9f3bfb7 100644
--- a/components/services/paint_preview_compositor/public/mojom/paint_preview_compositor.mojom
+++ b/components/services/paint_preview_compositor/public/mojom/paint_preview_compositor.mojom
@@ -8,6 +8,7 @@
 import "components/paint_preview/common/mojom/paint_preview_types.mojom";
 import "mojo/public/mojom/base/shared_memory.mojom";
 import "mojo/public/mojom/base/unguessable_token.mojom";
+import "sandbox/policy/mojom/sandbox.mojom";
 import "skia/public/mojom/bitmap.mojom";
 import "ui/gfx/geometry/mojom/geometry.mojom";
 import "url/mojom/url.mojom";
@@ -134,6 +135,8 @@
 
 // Holds a collection of PaintPreviewCompositor instances running in the same
 // process.
+// TODO(crbug/1074323): Investigate using a different Sandbox.
+[ServiceSandbox=sandbox.mojom.Sandbox.kPrintCompositor]
 interface PaintPreviewCompositorCollection {
   // Provides an interface for managing discardable shared memory regions. Must
   // be called before calling any methods on managed PaintPreviewCompositors.
diff --git a/components/services/print_compositor/public/mojom/BUILD.gn b/components/services/print_compositor/public/mojom/BUILD.gn
index 176291aa..b148536 100644
--- a/components/services/print_compositor/public/mojom/BUILD.gn
+++ b/components/services/print_compositor/public/mojom/BUILD.gn
@@ -10,6 +10,7 @@
   public_deps = [
     "//components/discardable_memory/public/mojom",
     "//mojo/public/mojom/base",
+    "//sandbox/policy/mojom",
     "//url/mojom:url_mojom_gurl",
   ]
 
diff --git a/components/services/print_compositor/public/mojom/print_compositor.mojom b/components/services/print_compositor/public/mojom/print_compositor.mojom
index 60d9667..dfaabe0 100644
--- a/components/services/print_compositor/public/mojom/print_compositor.mojom
+++ b/components/services/print_compositor/public/mojom/print_compositor.mojom
@@ -6,11 +6,13 @@
 
 import "components/discardable_memory/public/mojom/discardable_shared_memory_manager.mojom";
 import "mojo/public/mojom/base/shared_memory.mojom";
+import "sandbox/policy/mojom/sandbox.mojom";
 import "url/mojom/url.mojom";
 
 [EnableIf=enable_tagged_pdf]
 import "ui/accessibility/mojom/ax_tree_update.mojom";
 
+[ServiceSandbox=sandbox.mojom.Sandbox.kPrintCompositor]
 interface PrintCompositor {
   // The status of composition and conversion execution.
   // These values are persisted to logs. Entries should not be renumbered and
diff --git a/components/services/quarantine/quarantine_unittest.cc b/components/services/quarantine/quarantine_unittest.cc
index 4a5b4072..052430a 100644
--- a/components/services/quarantine/quarantine_unittest.cc
+++ b/components/services/quarantine/quarantine_unittest.cc
@@ -14,9 +14,14 @@
 #include "base/files/scoped_temp_dir.h"
 #include "base/run_loop.h"
 #include "base/test/task_environment.h"
+#include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
+#if defined(OS_WIN)
+#include "base/win/scoped_com_initializer.h"
+#endif
+
 namespace quarantine {
 
 namespace {
@@ -31,17 +36,35 @@
   EXPECT_EQ(expected_result, result);
 }
 
+class QuarantineTest : public testing::Test {
+ public:
+  void SetUp() override {
+#if defined(OS_WIN)
+    ASSERT_TRUE(com_initializer_.Succeeded());
+#endif
+    ASSERT_TRUE(test_dir_.CreateUniqueTempDir());
+    ASSERT_EQ(
+        static_cast<int>(base::size(kTestData)),
+        base::WriteFile(GetTestFilePath(), kTestData, base::size(kTestData)));
+  }
+
+ protected:
+  base::FilePath GetTestFilePath() {
+    return test_dir_.GetPath().AppendASCII("foo.class");
+  }
+
+ private:
+#if defined(OS_WIN)
+  base::win::ScopedCOMInitializer com_initializer_;
+#endif
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  base::ScopedTempDir test_dir_;
+};
+
 }  // namespace
 
-TEST(QuarantineTest, FileCanBeOpenedForReadAfterAnnotation) {
-  base::test::SingleThreadTaskEnvironment task_environment;
-  base::ScopedTempDir test_dir;
-  ASSERT_TRUE(test_dir.CreateUniqueTempDir());
-
-  base::FilePath test_file = test_dir.GetPath().AppendASCII("foo.class");
-  ASSERT_EQ(static_cast<int>(base::size(kTestData)),
-            base::WriteFile(test_file, kTestData, base::size(kTestData)));
-
+TEST_F(QuarantineTest, FileCanBeOpenedForReadAfterAnnotation) {
+  base::FilePath test_file = GetTestFilePath();
   QuarantineFile(
       test_file, GURL(kInternetURL), GURL(kInternetReferrerURL), kTestGUID,
       base::BindOnce(&CheckQuarantineResult, QuarantineFileResult::OK));
@@ -52,17 +75,10 @@
   EXPECT_EQ(std::string(std::begin(kTestData), std::end(kTestData)), contents);
 }
 
-TEST(QuarantineTest, FileCanBeAnnotatedWithNoGUID) {
-  base::test::SingleThreadTaskEnvironment task_environment;
-  base::ScopedTempDir test_dir;
-  ASSERT_TRUE(test_dir.CreateUniqueTempDir());
-
-  base::FilePath test_file = test_dir.GetPath().AppendASCII("foo.class");
-  ASSERT_EQ(static_cast<int>(base::size(kTestData)),
-            base::WriteFile(test_file, kTestData, base::size(kTestData)));
-
+TEST_F(QuarantineTest, FileCanBeAnnotatedWithNoGUID) {
   QuarantineFile(
-      test_file, GURL(kInternetURL), GURL(kInternetReferrerURL), std::string(),
+      GetTestFilePath(), GURL(kInternetURL), GURL(kInternetReferrerURL),
+      std::string(),
       base::BindOnce(&CheckQuarantineResult, QuarantineFileResult::OK));
   base::RunLoop().RunUntilIdle();
 }
diff --git a/components/variations/metrics.cc b/components/variations/metrics.cc
index fe9d125..b8764606 100644
--- a/components/variations/metrics.cc
+++ b/components/variations/metrics.cc
@@ -34,4 +34,21 @@
   RecordStoreSeedResult(StoreSeedResult::FAILED_UNSUPPORTED_SEED_FORMAT);
 }
 
+void RecordStoreSafeSeedResult(StoreSeedResult result) {
+  UMA_HISTOGRAM_ENUMERATION("Variations.SafeMode.StoreSafeSeed.Result", result,
+                            StoreSeedResult::ENUM_SIZE);
+}
+
+void RecordSeedInstanceManipulations(const InstanceManipulations& im) {
+  if (im.delta_compressed && im.gzip_compressed) {
+    RecordStoreSeedResult(StoreSeedResult::GZIP_DELTA_COUNT);
+  } else if (im.delta_compressed) {
+    RecordStoreSeedResult(StoreSeedResult::NON_GZIP_DELTA_COUNT);
+  } else if (im.gzip_compressed) {
+    RecordStoreSeedResult(StoreSeedResult::GZIP_FULL_COUNT);
+  } else {
+    RecordStoreSeedResult(StoreSeedResult::NON_GZIP_FULL_COUNT);
+  }
+}
+
 }  // namespace variations
diff --git a/components/variations/metrics.h b/components/variations/metrics.h
index 069cde3..ebf5be9 100644
--- a/components/variations/metrics.h
+++ b/components/variations/metrics.h
@@ -91,6 +91,12 @@
   ENUM_SIZE
 };
 
+// Describes instance manipulations applied to data.
+struct InstanceManipulations {
+  const bool gzip_compressed;
+  const bool delta_compressed;
+};
+
 #if defined(OS_ANDROID)
 // Records the result of importing a seed during Android first run.
 COMPONENT_EXPORT(VARIATIONS)
@@ -109,10 +115,17 @@
 // server.
 COMPONENT_EXPORT(VARIATIONS) void RecordStoreSeedResult(StoreSeedResult result);
 
+// Records the result of attempting to store a seed as the safe seed.
+COMPONENT_EXPORT(VARIATIONS)
+void RecordStoreSafeSeedResult(StoreSeedResult result);
 
 // Reports to UMA that the seed format specified by the server is unsupported.
 COMPONENT_EXPORT(VARIATIONS) void ReportUnsupportedSeedFormatError();
 
+// Records the instance manipulations a seed was received with.
+COMPONENT_EXPORT(VARIATIONS)
+void RecordSeedInstanceManipulations(const InstanceManipulations& im);
+
 }  // namespace variations
 
 #endif  // COMPONENTS_VARIATIONS_METRICS_H_
diff --git a/components/variations/variations_seed_store.cc b/components/variations/variations_seed_store.cc
index 5356ec9..1829cc89 100644
--- a/components/variations/variations_seed_store.cc
+++ b/components/variations/variations_seed_store.cc
@@ -35,13 +35,14 @@
 // The ECDSA public key of the variations server for verifying variations seed
 // signatures.
 const uint8_t kPublicKey[] = {
-  0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01,
-  0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00,
-  0x04, 0x51, 0x7c, 0x31, 0x4b, 0x50, 0x42, 0xdd, 0x59, 0xda, 0x0b, 0xfa, 0x43,
-  0x44, 0x33, 0x7c, 0x5f, 0xa1, 0x0b, 0xd5, 0x82, 0xf6, 0xac, 0x04, 0x19, 0x72,
-  0x6c, 0x40, 0xd4, 0x3e, 0x56, 0xe2, 0xa0, 0x80, 0xa0, 0x41, 0xb3, 0x23, 0x7b,
-  0x71, 0xc9, 0x80, 0x87, 0xde, 0x35, 0x0d, 0x25, 0x71, 0x09, 0x7f, 0xb4, 0x15,
-  0x2b, 0xff, 0x82, 0x4d, 0xd3, 0xfe, 0xc5, 0xef, 0x20, 0xc6, 0xa3, 0x10, 0xbf,
+    0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02,
+    0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03,
+    0x42, 0x00, 0x04, 0x51, 0x7c, 0x31, 0x4b, 0x50, 0x42, 0xdd, 0x59, 0xda,
+    0x0b, 0xfa, 0x43, 0x44, 0x33, 0x7c, 0x5f, 0xa1, 0x0b, 0xd5, 0x82, 0xf6,
+    0xac, 0x04, 0x19, 0x72, 0x6c, 0x40, 0xd4, 0x3e, 0x56, 0xe2, 0xa0, 0x80,
+    0xa0, 0x41, 0xb3, 0x23, 0x7b, 0x71, 0xc9, 0x80, 0x87, 0xde, 0x35, 0x0d,
+    0x25, 0x71, 0x09, 0x7f, 0xb4, 0x15, 0x2b, 0xff, 0x82, 0x4d, 0xd3, 0xfe,
+    0xc5, 0xef, 0x20, 0xc6, 0xa3, 0x10, 0xbf,
 };
 
 // A sentinel value that may be stored as the latest variations seed value in
@@ -110,6 +111,17 @@
   return UpdateSeedDateResult::SAME_DAY;
 }
 
+// Remove gzip compression from |data|.
+// Returns success or error, populating result on success.
+StoreSeedResult Uncompress(const std::string& compressed, std::string* result) {
+  DCHECK(result);
+  if (!compression::GzipUncompress(compressed, result))
+    return StoreSeedResult::FAILED_UNGZIP;
+  if (result->empty())
+    return StoreSeedResult::FAILED_EMPTY_GZIP_CONTENTS;
+  return StoreSeedResult::SUCCESS;
+}
+
 }  // namespace
 
 VariationsSeedStore::VariationsSeedStore(PrefService* local_state)
@@ -144,6 +156,43 @@
   return true;
 }
 
+StoreSeedResult VariationsSeedStore::ResolveDelta(
+    const std::string& delta_bytes,
+    std::string* seed_bytes) {
+  DCHECK(seed_bytes);
+  std::string existing_seed_bytes;
+  LoadSeedResult read_result =
+      ReadSeedData(SeedType::LATEST, &existing_seed_bytes);
+  if (read_result != LoadSeedResult::kSuccess)
+    return StoreSeedResult::FAILED_DELTA_READ_SEED;
+  if (!ApplyDeltaPatch(existing_seed_bytes, delta_bytes, seed_bytes))
+    return StoreSeedResult::FAILED_DELTA_APPLY;
+  return StoreSeedResult::SUCCESS;
+}
+
+StoreSeedResult VariationsSeedStore::ResolveInstanceManipulations(
+    const std::string& data,
+    const InstanceManipulations& im,
+    std::string* seed_bytes) {
+  DCHECK(seed_bytes);
+  // If the data is gzip compressed, first uncompress it.
+  std::string ungzipped_data;
+  if (im.gzip_compressed) {
+    StoreSeedResult result = Uncompress(data, &ungzipped_data);
+    if (result != StoreSeedResult::SUCCESS)
+      return result;
+  } else {
+    ungzipped_data = data;
+  }
+
+  if (!im.delta_compressed) {
+    seed_bytes->swap(ungzipped_data);
+    return StoreSeedResult::SUCCESS;
+  }
+
+  return ResolveDelta(ungzipped_data, seed_bytes);
+}
+
 bool VariationsSeedStore::StoreSeedData(
     const std::string& data,
     const std::string& base64_seed_signature,
@@ -154,58 +203,40 @@
     VariationsSeed* parsed_seed) {
   UMA_HISTOGRAM_COUNTS_1000("Variations.StoreSeed.DataSize",
                             data.length() / 1024);
-
-  if (is_delta_compressed && is_gzip_compressed) {
-    RecordStoreSeedResult(StoreSeedResult::GZIP_DELTA_COUNT);
-  } else if (is_delta_compressed) {
-    RecordStoreSeedResult(StoreSeedResult::NON_GZIP_DELTA_COUNT);
-  } else if (is_gzip_compressed) {
-    RecordStoreSeedResult(StoreSeedResult::GZIP_FULL_COUNT);
-  } else {
-    RecordStoreSeedResult(StoreSeedResult::NON_GZIP_FULL_COUNT);
-  }
-  // If the data is gzip compressed, first uncompress it.
-  std::string ungzipped_data;
-  if (is_gzip_compressed) {
-    if (compression::GzipUncompress(data, &ungzipped_data)) {
-      if (ungzipped_data.empty()) {
-        RecordStoreSeedResult(StoreSeedResult::FAILED_EMPTY_GZIP_CONTENTS);
-        return false;
-      }
-    } else {
-      RecordStoreSeedResult(StoreSeedResult::FAILED_UNGZIP);
-      return false;
-    }
-  } else {
-    ungzipped_data = data;
-  }
-
-  if (!is_delta_compressed) {
-    return StoreSeedDataNoDelta(ungzipped_data, base64_seed_signature,
-                                country_code, date_fetched, parsed_seed);
-  }
-
-  // If the data is delta compressed, first decode it.
-  std::string existing_seed_data;
-  std::string updated_seed_data;
-  LoadSeedResult read_result =
-      ReadSeedData(SeedType::LATEST, &existing_seed_data);
-  if (read_result != LoadSeedResult::kSuccess) {
-    RecordStoreSeedResult(StoreSeedResult::FAILED_DELTA_READ_SEED);
+  InstanceManipulations im = {
+      .gzip_compressed = is_gzip_compressed,
+      .delta_compressed = is_delta_compressed,
+  };
+  RecordSeedInstanceManipulations(im);
+  std::string seed_bytes;
+  StoreSeedResult im_result =
+      ResolveInstanceManipulations(data, im, &seed_bytes);
+  if (im_result != StoreSeedResult::SUCCESS) {
+    RecordStoreSeedResult(im_result);
     return false;
-  }
-  if (!ApplyDeltaPatch(existing_seed_data, ungzipped_data,
-                       &updated_seed_data)) {
-    RecordStoreSeedResult(StoreSeedResult::FAILED_DELTA_APPLY);
+  };
+
+  ValidatedSeed validated;
+  StoreSeedResult validate_result = ValidateSeedBytes(
+      seed_bytes, base64_seed_signature, SeedType::LATEST, &validated);
+  if (validate_result != StoreSeedResult::SUCCESS) {
+    RecordStoreSeedResult(validate_result);
+    if (im.delta_compressed)
+      RecordStoreSeedResult(StoreSeedResult::FAILED_DELTA_STORE);
     return false;
   }
 
-  const bool result =
-      StoreSeedDataNoDelta(updated_seed_data, base64_seed_signature,
-                           country_code, date_fetched, parsed_seed);
-  if (!result)
-    RecordStoreSeedResult(StoreSeedResult::FAILED_DELTA_STORE);
-  return result;
+  StoreSeedResult result =
+      StoreValidatedSeed(validated, country_code, date_fetched);
+  RecordStoreSeedResult(result);
+  if (result != StoreSeedResult::SUCCESS) {
+    if (im.delta_compressed)
+      RecordStoreSeedResult(StoreSeedResult::FAILED_DELTA_STORE);
+    return false;
+  }
+  if (parsed_seed)
+    parsed_seed->Swap(&validated.parsed);
+  return true;
 }
 
 LoadSeedResult VariationsSeedStore::LoadSafeSeed(
@@ -236,20 +267,30 @@
     const ClientFilterableState& client_state,
     base::Time seed_fetch_time) {
   std::string base64_seed_data;
-  StoreSeedResult result =
-      VerifyAndCompressSeedData(seed_data, base64_seed_signature,
-                                SeedType::SAFE, &base64_seed_data, nullptr);
-  UMA_HISTOGRAM_ENUMERATION("Variations.SafeMode.StoreSafeSeed.Result", result,
-                            StoreSeedResult::ENUM_SIZE);
-  if (result != StoreSeedResult::SUCCESS)
+  ValidatedSeed validated;
+  StoreSeedResult validation_result = ValidateSeedBytes(
+      seed_data, base64_seed_signature, SeedType::SAFE, &validated);
+  if (validation_result != StoreSeedResult::SUCCESS) {
+    RecordStoreSafeSeedResult(validation_result);
     return false;
+  }
 
-  // The sentinel value should only ever be saved in place of the latest seed --
-  // it should never be saved in place of the safe seed.
-  DCHECK_NE(base64_seed_data, kIdenticalToSafeSeedSentinel);
+  StoreSeedResult result =
+      StoreValidatedSafeSeed(validated, client_state, seed_fetch_time);
+  RecordStoreSafeSeedResult(result);
+  return result == StoreSeedResult::SUCCESS;
+}
 
-  // As a performance optimization, avoid an expensive no-op of overwriting the
-  // previous safe seed with an identical copy.
+StoreSeedResult VariationsSeedStore::StoreValidatedSafeSeed(
+    const ValidatedSeed& validated,
+    const ClientFilterableState& client_state,
+    base::Time seed_fetch_time) {
+  std::string base64_seed_data;
+  StoreSeedResult result = CompressSeedBytes(validated, &base64_seed_data);
+  if (result != StoreSeedResult::SUCCESS)
+    return result;
+  // As a performance optimization, avoid an expensive no-op of overwriting
+  // the previous safe seed with an identical copy.
   std::string previous_safe_seed =
       local_state_->GetString(prefs::kVariationsSafeCompressedSeed);
   if (base64_seed_data != previous_safe_seed) {
@@ -264,11 +305,12 @@
     //       configuration from seed B.
     //   (4) Seed A is received again from the server, perhaps due to a
     //       rollback.
-    // In this situation, seed A should be saved as the latest seed, while seed
-    // B should be saved as the safe seed, i.e. the previously saved values
-    // should be swapped. Indeed, it is guaranteed that the latest seed value
-    // should be overwritten in this case, as a seed should not be considered
-    // safe unless a new seed can be both received *and saved* from the server.
+    // In this situation, seed A should be saved as the latest seed, while
+    // seed B should be saved as the safe seed, i.e. the previously saved
+    // values should be swapped. Indeed, it is guaranteed that the latest seed
+    // value should be overwritten in this case, as a seed should not be
+    // considered safe unless a new seed can be both received *and saved* from
+    // the server.
     std::string latest_seed =
         local_state_->GetString(prefs::kVariationsCompressedSeed);
     if (latest_seed == kIdenticalToSafeSeedSentinel) {
@@ -280,7 +322,7 @@
   }
 
   local_state_->SetString(prefs::kVariationsSafeSeedSignature,
-                          base64_seed_signature);
+                          validated.base64_seed_signature);
   local_state_->SetTime(prefs::kVariationsSafeSeedDate,
                         client_state.reference_date);
   local_state_->SetString(prefs::kVariationsSafeSeedLocale,
@@ -303,7 +345,7 @@
   }
   local_state_->SetTime(prefs::kVariationsSafeSeedFetchTime, seed_fetch_time);
 
-  return true;
+  return StoreSeedResult::SUCCESS;
 }
 
 base::Time VariationsSeedStore::GetLastFetchTime() const {
@@ -507,22 +549,14 @@
   return LoadSeedResult::kSuccess;
 }
 
-bool VariationsSeedStore::StoreSeedDataNoDelta(
-    const std::string& seed_data,
-    const std::string& base64_seed_signature,
+StoreSeedResult VariationsSeedStore::StoreValidatedSeed(
+    const ValidatedSeed& validated,
     const std::string& country_code,
-    const base::Time& date_fetched,
-    VariationsSeed* parsed_seed) {
+    const base::Time& date_fetched) {
   std::string base64_seed_data;
-  VariationsSeed seed;
-  StoreSeedResult result =
-      VerifyAndCompressSeedData(seed_data, base64_seed_signature,
-                                SeedType::LATEST, &base64_seed_data, &seed);
-  if (result != StoreSeedResult::SUCCESS) {
-    RecordStoreSeedResult(result);
-    return false;
-  }
-
+  StoreSeedResult result = CompressSeedBytes(validated, &base64_seed_data);
+  if (result != StoreSeedResult::SUCCESS)
+    return result;
 #if defined(OS_ANDROID)
   // If currently we do not have any stored pref then we mark seed storing as
   // successful on the Java side to avoid repeated seed fetches.
@@ -538,40 +572,37 @@
 
   // As a space optimization, store an alias to the safe seed if the contents
   // are identical.
-  if (base64_seed_data ==
-      local_state_->GetString(prefs::kVariationsSafeCompressedSeed)) {
-    base64_seed_data = kIdenticalToSafeSeedSentinel;
-  }
+  bool matches_safe_seed =
+      (base64_seed_data ==
+       local_state_->GetString(prefs::kVariationsSafeCompressedSeed));
+  local_state_->SetString(
+      prefs::kVariationsCompressedSeed,
+      matches_safe_seed ? kIdenticalToSafeSeedSentinel : base64_seed_data);
 
-  local_state_->SetString(prefs::kVariationsCompressedSeed, base64_seed_data);
   UpdateSeedDateAndLogDayChange(date_fetched);
   local_state_->SetString(prefs::kVariationsSeedSignature,
-                          base64_seed_signature);
-  latest_serial_number_ = seed.serial_number();
-  if (parsed_seed)
-    seed.Swap(parsed_seed);
-
-  RecordStoreSeedResult(StoreSeedResult::SUCCESS);
-  return true;
+                          validated.base64_seed_signature);
+  latest_serial_number_ = validated.parsed.serial_number();
+  return StoreSeedResult::SUCCESS;
 }
 
-StoreSeedResult VariationsSeedStore::VerifyAndCompressSeedData(
-    const std::string& seed_data,
+StoreSeedResult VariationsSeedStore::ValidateSeedBytes(
+    const std::string& seed_bytes,
     const std::string& base64_seed_signature,
     SeedType seed_type,
-    std::string* base64_seed_data,
-    VariationsSeed* parsed_seed) {
-  if (seed_data.empty())
+    ValidatedSeed* result) {
+  DCHECK(result);
+  if (seed_bytes.empty())
     return StoreSeedResult::FAILED_EMPTY_GZIP_CONTENTS;
 
   // Only store the seed data if it parses correctly.
   VariationsSeed seed;
-  if (!seed.ParseFromString(seed_data))
+  if (!seed.ParseFromString(seed_bytes))
     return StoreSeedResult::FAILED_PARSE;
 
   if (signature_verification_enabled_) {
     const VerifySignatureResult result =
-        VerifySeedSignature(seed_data, base64_seed_signature);
+        VerifySeedSignature(seed_bytes, base64_seed_signature);
     switch (seed_type) {
       case SeedType::LATEST:
         UMA_HISTOGRAM_ENUMERATION("Variations.StoreSeedSignature", result,
@@ -587,15 +618,21 @@
     if (result != VerifySignatureResult::VALID_SIGNATURE)
       return StoreSeedResult::FAILED_SIGNATURE;
   }
+  result->bytes = seed_bytes;
+  result->base64_seed_signature = base64_seed_signature;
+  result->parsed.Swap(&seed);
+  return StoreSeedResult::SUCCESS;
+}
 
+StoreSeedResult VariationsSeedStore::CompressSeedBytes(
+    const ValidatedSeed& seed,
+    std::string* base64_seed_data) {
   // Compress the seed before base64-encoding and storing.
   std::string compressed_seed_data;
-  if (!compression::GzipCompress(seed_data, &compressed_seed_data))
+  if (!compression::GzipCompress(seed.bytes, &compressed_seed_data))
     return StoreSeedResult::FAILED_GZIP;
 
   base::Base64Encode(compressed_seed_data, base64_seed_data);
-  if (parsed_seed)
-    seed.Swap(parsed_seed);
   return StoreSeedResult::SUCCESS;
 }
 
diff --git a/components/variations/variations_seed_store.h b/components/variations/variations_seed_store.h
index aceb11b..84d4b957 100644
--- a/components/variations/variations_seed_store.h
+++ b/components/variations/variations_seed_store.h
@@ -16,6 +16,7 @@
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "components/variations/metrics.h"
+#include "components/variations/proto/variations_seed.pb.h"
 #include "components/variations/seed_response.h"
 
 class PrefService;
@@ -26,6 +27,16 @@
 struct ClientFilterableState;
 class VariationsSeed;
 
+// A seed that has passed validation.
+struct ValidatedSeed {
+  // The serialized VariationsSeed bytes.
+  std::string bytes;
+  // A cryptographic signature on the seed_data.
+  std::string base64_seed_signature;
+  // The seed data parsed as a proto.
+  VariationsSeed parsed;
+};
+
 // VariationsSeedStore is a helper class for reading and writing the variations
 // seed from Local State.
 class COMPONENT_EXPORT(VARIATIONS) VariationsSeedStore {
@@ -177,26 +188,45 @@
   LoadSeedResult ReadSeedData(SeedType seed_type,
                               std::string* seed_data) WARN_UNUSED_RESULT;
 
-  // Internal version of |StoreSeedData()| that assumes |seed_data| is not delta
-  // compressed.
-  bool StoreSeedDataNoDelta(const std::string& seed_data,
-                            const std::string& base64_seed_signature,
-                            const std::string& country_code,
-                            const base::Time& date_fetched,
-                            VariationsSeed* parsed_seed) WARN_UNUSED_RESULT;
+  // Resolves a |delta_bytes| against the latest seed.
+  // Returns success or an error, populating |seed_bytes| on success.
+  StoreSeedResult ResolveDelta(const std::string& delta_bytes,
+                               std::string* seed_bytes) WARN_UNUSED_RESULT;
 
-  // Validates the |seed_data|, comparing it (if enabled) against the provided
-  // cryptographic signature. Returns the result of the operation. On success,
-  // fills |base64_seed_data| with the compressed and base64-encoded seed data;
-  // and if |parsed_seed| is non-null, fills it with the parsed seed data.
-  // |seed_type| specifies whether |seed_data| is for the safe seed (vs. the
-  // regular/normal seed).
-  StoreSeedResult VerifyAndCompressSeedData(
-      const std::string& seed_data,
-      const std::string& base64_seed_signature,
-      SeedType seed_type,
-      std::string* base64_seed_data,
-      VariationsSeed* parsed_seed) WARN_UNUSED_RESULT;
+  // Resolves instance manipulations applied to received data.
+  // Returns success or an error, populating |seed_bytes| on success.
+  StoreSeedResult ResolveInstanceManipulations(const std::string& data,
+                                               const InstanceManipulations& im,
+                                               std::string* seed_bytes)
+      WARN_UNUSED_RESULT;
+
+  // Validates that |seed_bytes| parses and matches |base64_seed_signature|.
+  // Signature checking may be disabled via |signature_verification_enabled_|.
+  // |seed_type| indicates the source of the seed for logging purposes.
+  // |result| must be non-null, and will be populated on success.
+  // Returns success or some error value.
+  StoreSeedResult ValidateSeedBytes(const std::string& seed_bytes,
+                                    const std::string& base64_seed_signature,
+                                    SeedType seed_type,
+                                    ValidatedSeed* result) WARN_UNUSED_RESULT;
+
+  // Gzip compresses and base64 encodes a validated seed.
+  // Returns success or error and populates base64_seed_data on success.
+  StoreSeedResult CompressSeedBytes(const ValidatedSeed& validated,
+                                    std::string* base64_seed_data)
+      WARN_UNUSED_RESULT;
+
+  // Updates the latest seed with validated data.
+  StoreSeedResult StoreValidatedSeed(const ValidatedSeed& seed,
+                                     const std::string& country_code,
+                                     const base::Time& date_fetched)
+      WARN_UNUSED_RESULT;
+
+  // Updates the safe seed with validated data.
+  StoreSeedResult StoreValidatedSafeSeed(
+      const ValidatedSeed& validated,
+      const ClientFilterableState& client_state,
+      base::Time seed_fetch_time) WARN_UNUSED_RESULT;
 
   // Applies a delta-compressed |patch| to |existing_data|, producing the result
   // in |output|. Returns whether the operation was successful.
diff --git a/components/viz/common/BUILD.gn b/components/viz/common/BUILD.gn
index 6869f9d..ce433280 100644
--- a/components/viz/common/BUILD.gn
+++ b/components/viz/common/BUILD.gn
@@ -263,6 +263,7 @@
     "skia_helper.h",
     "surfaces/child_local_surface_id_allocator.cc",
     "surfaces/child_local_surface_id_allocator.h",
+    "surfaces/frame_sink_bundle_id.h",
     "surfaces/frame_sink_id.cc",
     "surfaces/frame_sink_id.h",
     "surfaces/frame_sink_id_allocator.cc",
diff --git a/components/viz/common/surfaces/frame_sink_bundle_id.h b/components/viz/common/surfaces/frame_sink_bundle_id.h
new file mode 100644
index 0000000..27389fd
--- /dev/null
+++ b/components/viz/common/surfaces/frame_sink_bundle_id.h
@@ -0,0 +1,61 @@
+// Copyright 2021 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_VIZ_COMMON_SURFACES_FRAME_SINK_BUNDLE_ID_H_
+#define COMPONENTS_VIZ_COMMON_SURFACES_FRAME_SINK_BUNDLE_ID_H_
+
+#include <stdint.h>
+
+#include <iosfwd>
+#include <string>
+#include <tuple>
+
+#include "base/hash/hash.h"
+#include "base/strings/string_piece.h"
+#include "components/viz/common/viz_common_export.h"
+
+namespace viz {
+
+// A FrameSinkBundleId uniquely identifies a FrameSinkBundle and the client that
+// uses it within the Viz compositing system.
+class VIZ_COMMON_EXPORT FrameSinkBundleId {
+ public:
+  constexpr FrameSinkBundleId() = default;
+  constexpr FrameSinkBundleId(const FrameSinkBundleId& other) = default;
+  constexpr FrameSinkBundleId& operator=(const FrameSinkBundleId& other) =
+      default;
+  constexpr FrameSinkBundleId(uint32_t client_id, uint32_t bundle_id)
+      : client_id_(client_id), bundle_id_(bundle_id) {}
+
+  constexpr bool is_valid() const { return client_id_ != 0 || bundle_id_ != 0; }
+  constexpr uint32_t client_id() const { return client_id_; }
+  constexpr uint32_t bundle_id() const { return bundle_id_; }
+
+  bool operator==(const FrameSinkBundleId& other) const {
+    return client_id_ == other.client_id_ && bundle_id_ == other.bundle_id_;
+  }
+
+  bool operator!=(const FrameSinkBundleId& other) const {
+    return !(*this == other);
+  }
+
+  bool operator<(const FrameSinkBundleId& other) const {
+    return std::tie(client_id_, bundle_id_) <
+           std::tie(other.client_id_, other.bundle_id_);
+  }
+
+  size_t hash() const { return base::HashInts(client_id_, bundle_id_); }
+
+ private:
+  uint32_t client_id_{0};
+  uint32_t bundle_id_{0};
+};
+
+struct FrameSinkBundleIdHash {
+  size_t operator()(const FrameSinkBundleId& key) const { return key.hash(); }
+};
+
+}  // namespace viz
+
+#endif  // COMPONENTS_VIZ_COMMON_SURFACES_FRAME_SINK_BUNDLE_ID_H_
diff --git a/components/viz/host/host_frame_sink_manager.cc b/components/viz/host/host_frame_sink_manager.cc
index 391d912a..42015b9 100644
--- a/components/viz/host/host_frame_sink_manager.cc
+++ b/components/viz/host/host_frame_sink_manager.cc
@@ -172,7 +172,8 @@
   data.has_created_compositor_frame_sink = true;
 
   frame_sink_manager_->CreateCompositorFrameSink(
-      frame_sink_id, std::move(receiver), std::move(client));
+      frame_sink_id, /*bundle_id=*/absl::nullopt, std::move(receiver),
+      std::move(client));
 }
 
 void HostFrameSinkManager::OnFrameTokenChanged(
diff --git a/components/viz/host/host_frame_sink_manager_unittest.cc b/components/viz/host/host_frame_sink_manager_unittest.cc
index f9cad838..f3411d5 100644
--- a/components/viz/host/host_frame_sink_manager_unittest.cc
+++ b/components/viz/host/host_frame_sink_manager_unittest.cc
@@ -73,8 +73,9 @@
   MOCK_METHOD2(SetFrameSinkDebugLabel,
                void(const FrameSinkId& frame_sink_id,
                     const std::string& debug_label));
-  MOCK_METHOD3(CreateCompositorFrameSink,
+  MOCK_METHOD4(CreateCompositorFrameSink,
                void(const FrameSinkId&,
+                    const absl::optional<FrameSinkBundleId>&,
                     mojo::PendingReceiver<mojom::CompositorFrameSink>,
                     mojo::PendingRemote<mojom::CompositorFrameSinkClient>));
   void CreateRootCompositorFrameSink(
@@ -182,7 +183,7 @@
 
   MockCompositorFrameSinkClient compositor_frame_sink_client;
   mojo::Remote<mojom::CompositorFrameSink> compositor_frame_sink;
-  EXPECT_CALL(impl(), CreateCompositorFrameSink(kFrameSinkChild1, _, _));
+  EXPECT_CALL(impl(), CreateCompositorFrameSink(kFrameSinkChild1, _, _, _));
   host().CreateCompositorFrameSink(
       kFrameSinkChild1, compositor_frame_sink.BindNewPipeAndPassReceiver(),
       compositor_frame_sink_client.BindInterfaceRemote());
@@ -328,7 +329,7 @@
 
   MockCompositorFrameSinkClient compositor_frame_sink_client;
   mojo::Remote<mojom::CompositorFrameSink> compositor_frame_sink;
-  EXPECT_CALL(impl(), CreateCompositorFrameSink(kFrameSinkChild1, _, _));
+  EXPECT_CALL(impl(), CreateCompositorFrameSink(kFrameSinkChild1, _, _, _));
   host().CreateCompositorFrameSink(
       kFrameSinkChild1, compositor_frame_sink.BindNewPipeAndPassReceiver(),
       compositor_frame_sink_client.BindInterfaceRemote());
@@ -436,7 +437,7 @@
       compositor_frame_sink_client1.BindInterfaceRemote());
 
   // Verify CompositorFrameSink was created on other end of message pipe.
-  EXPECT_CALL(impl(), CreateCompositorFrameSink(kFrameSinkChild1, _, _));
+  EXPECT_CALL(impl(), CreateCompositorFrameSink(kFrameSinkChild1, _, _, _));
   FlushHostAndVerifyExpectations();
 
   // Create a new CompositorFrameSink and try to connect it with the same
@@ -449,7 +450,7 @@
 
   // Verify CompositorFrameSink is destroyed and then recreated.
   EXPECT_CALL(impl(), MockDestroyCompositorFrameSink(kFrameSinkChild1));
-  EXPECT_CALL(impl(), CreateCompositorFrameSink(kFrameSinkChild1, _, _));
+  EXPECT_CALL(impl(), CreateCompositorFrameSink(kFrameSinkChild1, _, _, _));
   FlushHostAndVerifyExpectations();
 }
 
diff --git a/components/viz/service/BUILD.gn b/components/viz/service/BUILD.gn
index 1cd15f24..f0eb025 100644
--- a/components/viz/service/BUILD.gn
+++ b/components/viz/service/BUILD.gn
@@ -151,6 +151,8 @@
     "frame_sinks/compositor_frame_sink_support.h",
     "frame_sinks/external_begin_frame_source_mojo.cc",
     "frame_sinks/external_begin_frame_source_mojo.h",
+    "frame_sinks/frame_sink_bundle_impl.cc",
+    "frame_sinks/frame_sink_bundle_impl.h",
     "frame_sinks/frame_sink_manager_impl.cc",
     "frame_sinks/frame_sink_manager_impl.h",
     "frame_sinks/frame_sink_observer.h",
@@ -561,6 +563,7 @@
     "display_embedder/vsync_parameter_listener_unittest.cc",
     "frame_sinks/begin_frame_tracker_unittest.cc",
     "frame_sinks/compositor_frame_sink_support_unittest.cc",
+    "frame_sinks/frame_sink_bundle_impl_unittest.cc",
     "frame_sinks/frame_sink_manager_unittest.cc",
     "frame_sinks/surface_references_unittest.cc",
     "frame_sinks/surface_synchronization_unittest.cc",
@@ -590,9 +593,7 @@
     sources -= [ "display_embedder/buffer_queue_unittest.cc" ]
   }
 
-  configs = [
-    "//third_party/khronos:khronos_headers",
-  ]
+  configs = [ "//third_party/khronos:khronos_headers" ]
 
   deps = [
     ":service",
diff --git a/components/viz/service/compositor_frame_fuzzer/fuzzer_browser_process.cc b/components/viz/service/compositor_frame_fuzzer/fuzzer_browser_process.cc
index a3d75bd..443ae07 100644
--- a/components/viz/service/compositor_frame_fuzzer/fuzzer_browser_process.cc
+++ b/components/viz/service/compositor_frame_fuzzer/fuzzer_browser_process.cc
@@ -14,6 +14,7 @@
 #include "components/viz/common/resources/bitmap_allocation.h"
 #include "components/viz/common/surfaces/surface_range.h"
 #include "mojo/public/cpp/bindings/remote.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace viz {
 
@@ -56,7 +57,8 @@
   mojo::Remote<mojom::CompositorFrameSink> sink_remote;
   FakeCompositorFrameSinkClient sink_client;
   frame_sink_manager_.CreateCompositorFrameSink(
-      kEmbeddedFrameSinkId, sink_remote.BindNewPipeAndPassReceiver(),
+      kEmbeddedFrameSinkId, /*bundle_id=*/absl::nullopt,
+      sink_remote.BindNewPipeAndPassReceiver(),
       sink_client.BindInterfaceRemote());
 
   for (auto& fuzzed_bitmap : allocated_bitmaps) {
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_impl.cc b/components/viz/service/frame_sinks/compositor_frame_sink_impl.cc
index 3fc9538..d3c138d 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_impl.cc
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_impl.cc
@@ -4,33 +4,126 @@
 
 #include "components/viz/service/frame_sinks/compositor_frame_sink_impl.h"
 
+#include <memory>
 #include <utility>
+#include <vector>
 
 #include "base/bind.h"
 #include "base/callback_helpers.h"
+#include "components/viz/service/frame_sinks/frame_sink_bundle_impl.h"
 #include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
 #include "ui/gfx/overlay_transform.h"
 
 namespace viz {
 
+namespace {
+
+// Helper class which implements the CompositorFrameSinkClient interface so it
+// can route CompositorFrameSinkSupport client messages to a local
+// FrameSinkBundleImpl for batching, rather than having them go directly to the
+// remote client.
+class BundleClientProxy : public mojom::CompositorFrameSinkClient {
+ public:
+  BundleClientProxy(FrameSinkManagerImpl& manager,
+                    FrameSinkId frame_sink_id,
+                    FrameSinkBundleId bundle_id)
+      : manager_(manager),
+        frame_sink_id_(frame_sink_id),
+        bundle_id_(bundle_id) {}
+
+  BundleClientProxy(const BundleClientProxy&) = delete;
+  BundleClientProxy& operator=(const BundleClientProxy&) = delete;
+  ~BundleClientProxy() override = default;
+
+  // mojom::CompositorFrameSinkClient implementation:
+  void DidReceiveCompositorFrameAck(
+      std::vector<ReturnedResource> resources) override {
+    if (auto* bundle = GetBundle()) {
+      bundle->EnqueueDidReceiveCompositorFrameAck(frame_sink_id_.sink_id(),
+                                                  std::move(resources));
+    }
+  }
+
+  void OnBeginFrame(const BeginFrameArgs& args,
+                    const FrameTimingDetailsMap& timing_details) override {
+    if (auto* bundle = GetBundle()) {
+      bundle->EnqueueOnBeginFrame(frame_sink_id_.sink_id(), args,
+                                  timing_details);
+    }
+  }
+
+  void ReclaimResources(std::vector<ReturnedResource> resources) override {
+    if (auto* bundle = GetBundle()) {
+      bundle->EnqueueReclaimResources(frame_sink_id_.sink_id(),
+                                      std::move(resources));
+    }
+  }
+
+  void OnBeginFramePausedChanged(bool paused) override {
+    if (auto* bundle = GetBundle()) {
+      bundle->SendOnBeginFramePausedChanged(frame_sink_id_.sink_id(), paused);
+    }
+  }
+
+  void OnCompositorFrameTransitionDirectiveProcessed(
+      uint32_t sequence_id) override {
+    if (auto* bundle = GetBundle()) {
+      bundle->SendOnCompositorFrameTransitionDirectiveProcessed(
+          frame_sink_id_.sink_id(), sequence_id);
+    }
+  }
+
+ private:
+  FrameSinkBundleImpl* GetBundle() {
+    return manager_.GetFrameSinkBundle(bundle_id_);
+  }
+
+  FrameSinkManagerImpl& manager_;
+  const FrameSinkId frame_sink_id_;
+  const FrameSinkBundleId bundle_id_;
+};
+
+}  // namespace
+
 CompositorFrameSinkImpl::CompositorFrameSinkImpl(
     FrameSinkManagerImpl* frame_sink_manager,
     const FrameSinkId& frame_sink_id,
+    absl::optional<FrameSinkBundleId> bundle_id,
     mojo::PendingReceiver<mojom::CompositorFrameSink> receiver,
     mojo::PendingRemote<mojom::CompositorFrameSinkClient> client)
-    : compositor_frame_sink_client_(std::move(client)),
+    : manager_(*frame_sink_manager),
+      bundle_id_(bundle_id),
+      compositor_frame_sink_client_(std::move(client)),
+      proxying_client_(bundle_id.has_value()
+                           ? std::make_unique<BundleClientProxy>(manager_,
+                                                                 frame_sink_id,
+                                                                 *bundle_id)
+                           : nullptr),
       compositor_frame_sink_receiver_(this, std::move(receiver)),
       support_(std::make_unique<CompositorFrameSinkSupport>(
-          compositor_frame_sink_client_.get(),
+          proxying_client_ ? proxying_client_.get()
+                           : compositor_frame_sink_client_.get(),
           frame_sink_manager,
           frame_sink_id,
           false /* is_root */)) {
   compositor_frame_sink_receiver_.set_disconnect_handler(
       base::BindOnce(&CompositorFrameSinkImpl::OnClientConnectionLost,
                      base::Unretained(this)));
+  if (bundle_id.has_value()) {
+    support_->SetBundle(*bundle_id);
+    manager_.GetFrameSinkBundle(*bundle_id)->AddFrameSink(frame_sink_id);
+  }
 }
 
-CompositorFrameSinkImpl::~CompositorFrameSinkImpl() = default;
+CompositorFrameSinkImpl::~CompositorFrameSinkImpl() {
+  if (!bundle_id_.has_value()) {
+    return;
+  }
+
+  if (FrameSinkBundleImpl* bundle = manager_.GetFrameSinkBundle(*bundle_id_)) {
+    bundle->RemoveFrameSink(support_->frame_sink_id());
+  }
+}
 
 void CompositorFrameSinkImpl::SetNeedsBeginFrame(bool needs_begin_frame) {
   support_->SetNeedsBeginFrame(needs_begin_frame);
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_impl.h b/components/viz/service/frame_sinks/compositor_frame_sink_impl.h
index 37c72b4c..51cc851 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_impl.h
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_impl.h
@@ -5,8 +5,11 @@
 #ifndef COMPONENTS_VIZ_SERVICE_FRAME_SINKS_COMPOSITOR_FRAME_SINK_IMPL_H_
 #define COMPONENTS_VIZ_SERVICE_FRAME_SINKS_COMPOSITOR_FRAME_SINK_IMPL_H_
 
+#include <memory>
+
 #include "base/macros.h"
 #include "base/memory/read_only_shared_memory_region.h"
+#include "components/viz/common/surfaces/frame_sink_bundle_id.h"
 #include "components/viz/common/surfaces/frame_sink_id.h"
 #include "components/viz/common/surfaces/local_surface_id.h"
 #include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
@@ -15,6 +18,7 @@
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/viz/public/mojom/compositing/compositor_frame_sink.mojom.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace viz {
 
@@ -27,6 +31,7 @@
   CompositorFrameSinkImpl(
       FrameSinkManagerImpl* frame_sink_manager,
       const FrameSinkId& frame_sink_id,
+      absl::optional<FrameSinkBundleId> bundle_id,
       mojo::PendingReceiver<mojom::CompositorFrameSink> receiver,
       mojo::PendingRemote<mojom::CompositorFrameSinkClient> client);
 
@@ -63,7 +68,12 @@
 
   void OnClientConnectionLost();
 
+  FrameSinkManagerImpl& manager_;
+  const absl::optional<FrameSinkBundleId> bundle_id_;
+
   mojo::Remote<mojom::CompositorFrameSinkClient> compositor_frame_sink_client_;
+  std::unique_ptr<mojom::CompositorFrameSinkClient> proxying_client_;
+
   mojo::Receiver<mojom::CompositorFrameSink> compositor_frame_sink_receiver_;
 
   // Must be destroyed before |compositor_frame_sink_client_|. This must never
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support.cc b/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
index 8c46a63..ffca9d9a 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
@@ -12,6 +12,7 @@
 #include "base/callback_helpers.h"
 #include "base/containers/contains.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/threading/sequenced_task_runner_handle.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "cc/base/features.h"
@@ -25,6 +26,7 @@
 #include "components/viz/common/surfaces/surface_info.h"
 #include "components/viz/service/display/display.h"
 #include "components/viz/service/display/shared_bitmap_manager.h"
+#include "components/viz/service/frame_sinks/frame_sink_bundle_impl.h"
 #include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
 #include "components/viz/service/surfaces/surface.h"
 #include "components/viz/service/surfaces/surface_reference.h"
@@ -143,10 +145,31 @@
     begin_frame_source_->RemoveObserver(this);
     added_frame_observer_ = false;
   }
+  if (bundle_id_ && begin_frame_source_ != nullptr &&
+      begin_frame_source_ != begin_frame_source) {
+    // If we are in a bundle, our previous BeginFrameSource must have been
+    // identical to the bundle's. If that's changing, we're no longer able to
+    // participate in the bundle. Force the client to re-establish a
+    // CompositorFrameSink in this case.
+    ScheduleSelfDestruction();
+    return;
+  }
   begin_frame_source_ = begin_frame_source;
   UpdateNeedsBeginFramesInternal();
 }
 
+void CompositorFrameSinkSupport::SetBundle(const FrameSinkBundleId& bundle_id) {
+  auto* bundle = frame_sink_manager_->GetFrameSinkBundle(bundle_id);
+  if (!bundle || (begin_frame_source_ &&
+                  begin_frame_source_ != bundle->begin_frame_source())) {
+    ScheduleSelfDestruction();
+    return;
+  }
+
+  bundle_id_ = bundle_id;
+  UpdateNeedsBeginFramesInternal();
+}
+
 void CompositorFrameSinkSupport::ThrottleBeginFrame(base::TimeDelta interval) {
   begin_frame_interval_ = interval;
 }
@@ -834,10 +857,11 @@
   // We require a begin frame if there's a callback pending, or if the client
   // requested it, or if the client needs to get some frame timing details.
   bool needs_begin_frame =
-      client_needs_begin_frame_ || !frame_timing_details_.empty() ||
-      !pending_surfaces_.empty() ||
-      (compositor_frame_callback_ && !callback_received_begin_frame_) ||
-      surface_animation_manager_.NeedsBeginFrame();
+      (client_needs_begin_frame_ || !frame_timing_details_.empty() ||
+       !pending_surfaces_.empty() ||
+       (compositor_frame_callback_ && !callback_received_begin_frame_) ||
+       surface_animation_manager_.NeedsBeginFrame()) &&
+      !bundle_id_.has_value();
 
   if (needs_begin_frame == added_frame_observer_)
     return;
@@ -1097,4 +1121,15 @@
     client_->OnCompositorFrameTransitionDirectiveProcessed(sequence_id);
 }
 
+void CompositorFrameSinkSupport::DestroySelf() {
+  frame_sink_manager_->DestroyCompositorFrameSink(frame_sink_id_,
+                                                  base::DoNothing());
+}
+
+void CompositorFrameSinkSupport::ScheduleSelfDestruction() {
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&CompositorFrameSinkSupport::DestroySelf,
+                                weak_factory_.GetWeakPtr()));
+}
+
 }  // namespace viz
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support.h b/components/viz/service/frame_sinks/compositor_frame_sink_support.h
index 00cefee..f7846cc3 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support.h
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support.h
@@ -18,6 +18,7 @@
 #include "components/viz/common/frame_sinks/begin_frame_source.h"
 #include "components/viz/common/frame_timing_details_map.h"
 #include "components/viz/common/quads/compositor_frame.h"
+#include "components/viz/common/surfaces/frame_sink_bundle_id.h"
 #include "components/viz/common/surfaces/surface_info.h"
 #include "components/viz/common/surfaces/surface_range.h"
 #include "components/viz/service/frame_sinks/begin_frame_tracker.h"
@@ -111,6 +112,13 @@
   // This allows the FrameSinkManagerImpl to pass a BeginFrameSource to use.
   void SetBeginFrameSource(BeginFrameSource* begin_frame_source);
 
+  // Sets the ID of the FrameSinkBundle to which this sink should belong. If the
+  // sink is incompatible with the bundle (i.e. uses a different
+  // BeginFrameSource than this CompositorFrameSinkSupport) then the sink will
+  // be removed from the bundle and destroyed asynchronously, disconnecting its
+  // client.
+  void SetBundle(const FrameSinkBundleId& bundle_id);
+
   base::TimeDelta GetPreferredFrameInterval(
       mojom::CompositorFrameSinkType* type) const;
   void InitializeCompositorFrameSinkType(mojom::CompositorFrameSinkType type);
@@ -264,6 +272,13 @@
   // passed and a BeginFrame should be sent.
   bool ShouldThrottleBeginFrameAsRequested(base::TimeTicks frame_time);
 
+  // Instructs the FrameSinkManager to destroy our CompositorFrameSinkImpl.
+  // To avoid reentrancy issues, this should be called from its own task.
+  void DestroySelf();
+
+  // Posts a task to invoke DestroySelf() ASAP.
+  void ScheduleSelfDestruction();
+
   mojom::CompositorFrameSinkClient* const client_;
 
   FrameSinkManagerImpl* const frame_sink_manager_;
@@ -302,6 +317,9 @@
 
   bool wants_animate_only_begin_frames_ = false;
 
+  // Indicates the FrameSinkBundle to which this sink belongs, if any.
+  absl::optional<FrameSinkBundleId> bundle_id_;
+
   const bool is_root_;
 
   // By default, this is equivalent to |is_root_|, but may be overridden for
diff --git a/components/viz/service/frame_sinks/frame_sink_bundle_impl.cc b/components/viz/service/frame_sinks/frame_sink_bundle_impl.cc
new file mode 100644
index 0000000..21311811
--- /dev/null
+++ b/components/viz/service/frame_sinks/frame_sink_bundle_impl.cc
@@ -0,0 +1,240 @@
+// Copyright 2021 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/viz/service/frame_sinks/frame_sink_bundle_impl.h"
+
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/check.h"
+#include "components/viz/service/frame_sinks/compositor_frame_sink_impl.h"
+#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "services/viz/public/mojom/compositing/compositor_frame_sink.mojom.h"
+#include "services/viz/public/mojom/compositing/frame_sink_bundle.mojom.h"
+
+namespace viz {
+
+FrameSinkBundleImpl::FrameSinkBundleImpl(
+    FrameSinkManagerImpl& manager,
+    const FrameSinkBundleId& id,
+    BeginFrameSource* begin_frame_source,
+    mojo::PendingReceiver<mojom::FrameSinkBundle> receiver,
+    mojo::PendingRemote<mojom::FrameSinkBundleClient> client)
+    : manager_(manager),
+      id_(id),
+      begin_frame_source_(begin_frame_source),
+      receiver_(this, std::move(receiver)),
+      client_(std::move(client)) {
+  DCHECK(begin_frame_source_);
+  begin_frame_source_->AddObserver(this);
+
+  receiver_.set_disconnect_handler(base::BindOnce(
+      &FrameSinkBundleImpl::OnDisconnect, base::Unretained(this)));
+}
+
+FrameSinkBundleImpl::~FrameSinkBundleImpl() {
+  if (begin_frame_source_) {
+    begin_frame_source_->RemoveObserver(this);
+  }
+}
+
+void FrameSinkBundleImpl::AddFrameSink(const FrameSinkId& id) {
+  frame_sinks_.insert(id.sink_id());
+}
+
+void FrameSinkBundleImpl::RemoveFrameSink(const FrameSinkId& id) {
+  frame_sinks_.erase(id.sink_id());
+}
+
+void FrameSinkBundleImpl::InitializeCompositorFrameSinkType(
+    uint32_t sink_id,
+    mojom::CompositorFrameSinkType type) {
+  if (!sink_type_.has_value()) {
+    sink_type_ = type;
+  } else if (type != *sink_type_) {
+    receiver_.ReportBadMessage("Bundled frame sinks must be of the same type");
+    return;
+  }
+
+  if (auto* sink = GetFrameSink(sink_id)) {
+    sink->InitializeCompositorFrameSinkType(type);
+  }
+}
+
+void FrameSinkBundleImpl::SetNeedsBeginFrame(uint32_t sink_id,
+                                             bool needs_begin_frame) {
+  if (auto* sink = GetFrameSink(sink_id)) {
+    sink->SetNeedsBeginFrame(needs_begin_frame);
+  }
+}
+
+void FrameSinkBundleImpl::Submit(
+    std::vector<mojom::BundledFrameSubmissionPtr> submissions) {
+  // Count the frame submissions before processing anything. This ensures that
+  // any frames submitted here will be acked together in a batch, and not acked
+  // individually in case they happen to ack synchronously within
+  // SubmitCompositorFrame below.
+  for (auto& submission : submissions) {
+    if (GetFrameSink(submission->sink_id) && submission->data->is_frame()) {
+      ++num_unacked_submissions_;
+    }
+  }
+
+  for (auto& submission : submissions) {
+    if (auto* sink = GetFrameSink(submission->sink_id)) {
+      switch (submission->data->which()) {
+        case mojom::BundledFrameSubmissionData::Tag::kFrame: {
+          mojom::BundledCompositorFramePtr& frame =
+              submission->data->get_frame();
+          sink->SubmitCompositorFrame(
+              frame->local_surface_id, std::move(frame->frame),
+              std::move(frame->hit_test_region_list), frame->submit_time);
+          break;
+        }
+
+        case mojom::BundledFrameSubmissionData::Tag::kDidNotProduceFrame:
+          sink->DidNotProduceFrame(
+              submission->data->get_did_not_produce_frame());
+          break;
+
+        case mojom::BundledFrameSubmissionData::Tag::kDidDeleteSharedBitmap:
+          sink->DidDeleteSharedBitmap(
+              submission->data->get_did_delete_shared_bitmap());
+          break;
+      }
+    }
+  }
+  FlushMessages();
+}
+
+void FrameSinkBundleImpl::DidAllocateSharedBitmap(
+    uint32_t sink_id,
+    base::ReadOnlySharedMemoryRegion region,
+    const gpu::Mailbox& id) {
+  if (auto* sink = GetFrameSink(sink_id)) {
+    sink->DidAllocateSharedBitmap(std::move(region), id);
+  }
+}
+
+void FrameSinkBundleImpl::EnqueueDidReceiveCompositorFrameAck(
+    uint32_t sink_id,
+    std::vector<ReturnedResource> resources) {
+  pending_received_frame_acks_.push_back(
+      mojom::BundledReturnedResources::New(sink_id, std::move(resources)));
+
+  // We expect to be notified about the consumption of all previously submitted
+  // frames at approximately the same time. This condition allows us to batch
+  // most ack messages without any significant delays. Note that
+  // `num_unacked_submissions_` is incremented in Submit() exactly once for
+  // every actual frame submitted by clients within the bundle, and is
+  // decremented only when acks are flushed by FlushMessages().
+  if (pending_received_frame_acks_.size() >= num_unacked_submissions_) {
+    FlushMessages();
+  }
+}
+
+void FrameSinkBundleImpl::EnqueueOnBeginFrame(
+    uint32_t sink_id,
+    const BeginFrameArgs& args,
+    const base::flat_map<uint32_t, FrameTimingDetails>& details) {
+  pending_on_begin_frames_.push_back(
+      mojom::BeginFrameInfo::New(sink_id, args, details));
+  if (!defer_on_begin_frames_) {
+    FlushMessages();
+  }
+}
+
+void FrameSinkBundleImpl::EnqueueReclaimResources(
+    uint32_t sink_id,
+    std::vector<ReturnedResource> resources) {
+  // We always defer ReclaimResources until the next flush, whether it's done
+  // for frame acks or OnBeginFrames.
+  pending_reclaimed_resources_.push_back(
+      mojom::BundledReturnedResources::New(sink_id, std::move(resources)));
+}
+
+void FrameSinkBundleImpl::SendOnBeginFramePausedChanged(uint32_t sink_id,
+                                                        bool paused) {
+  client_->OnBeginFramePausedChanged(sink_id, paused);
+}
+
+void FrameSinkBundleImpl::SendOnCompositorFrameTransitionDirectiveProcessed(
+    uint32_t sink_id,
+    uint32_t sequence_id) {
+  client_->OnCompositorFrameTransitionDirectiveProcessed(sink_id, sequence_id);
+}
+
+void FrameSinkBundleImpl::UnregisterBeginFrameSource(BeginFrameSource* source) {
+  if (begin_frame_source_ == source) {
+    begin_frame_source_->RemoveObserver(this);
+    begin_frame_source_ = nullptr;
+  }
+}
+
+CompositorFrameSinkImpl* FrameSinkBundleImpl::GetFrameSink(
+    uint32_t sink_id) const {
+  return manager_.GetFrameSinkImpl(FrameSinkId(id_.client_id(), sink_id));
+}
+
+void FrameSinkBundleImpl::FlushMessages() {
+  std::vector<mojom::BundledReturnedResourcesPtr> pending_received_frame_acks;
+  std::swap(pending_received_frame_acks_, pending_received_frame_acks);
+
+  DCHECK_GE(num_unacked_submissions_, pending_received_frame_acks.size());
+  num_unacked_submissions_ -= pending_received_frame_acks.size();
+
+  std::vector<mojom::BeginFrameInfoPtr> pending_on_begin_frames;
+  std::swap(pending_on_begin_frames_, pending_on_begin_frames);
+
+  std::vector<mojom::BundledReturnedResourcesPtr> pending_reclaimed_resources;
+  std::swap(pending_reclaimed_resources_, pending_reclaimed_resources);
+
+  if (!pending_received_frame_acks.empty() ||
+      !pending_on_begin_frames.empty() ||
+      !pending_reclaimed_resources.empty()) {
+    client_->FlushNotifications(std::move(pending_received_frame_acks),
+                                std::move(pending_on_begin_frames),
+                                std::move(pending_reclaimed_resources));
+  }
+}
+
+void FrameSinkBundleImpl::OnDisconnect() {
+  manager_.DestroyFrameSinkBundle(id_);
+}
+
+void FrameSinkBundleImpl::OnBeginFrame(const BeginFrameArgs& args) {
+  last_used_begin_frame_args_ = args;
+
+  // We expect the calls below to result in reentrant calls to our own
+  // EnqueueOnBeginFrame(), via the sink's BundleClientProxy. In a sense this
+  // means we act both as the sink's BeginFrameSource and its client. The
+  // indirection is useful since the sink performs some non-trivial logic in
+  // OnBeginFrame() and passes computed data to the client, which we want to
+  // be able to forward in our batched OnBeginFrame notifications back to the
+  // real remote client.
+  defer_on_begin_frames_ = true;
+  for (const auto sink_id : frame_sinks_) {
+    FrameSinkId id(id_.client_id(), sink_id);
+    if (BeginFrameObserver* observer = manager_.GetFrameSinkForId(id)) {
+      observer->OnBeginFrame(args);
+    }
+  }
+  defer_on_begin_frames_ = false;
+  FlushMessages();
+}
+
+const BeginFrameArgs& FrameSinkBundleImpl::LastUsedBeginFrameArgs() const {
+  return last_used_begin_frame_args_;
+}
+
+void FrameSinkBundleImpl::OnBeginFrameSourcePausedChanged(bool paused) {}
+
+bool FrameSinkBundleImpl::WantsAnimateOnlyBeginFrames() const {
+  return false;
+}
+
+}  // namespace viz
diff --git a/components/viz/service/frame_sinks/frame_sink_bundle_impl.h b/components/viz/service/frame_sinks/frame_sink_bundle_impl.h
new file mode 100644
index 0000000..e77f380
--- /dev/null
+++ b/components/viz/service/frame_sinks/frame_sink_bundle_impl.h
@@ -0,0 +1,118 @@
+// Copyright 2021 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_VIZ_SERVICE_FRAME_SINKS_FRAME_SINK_BUNDLE_IMPL_H_
+#define COMPONENTS_VIZ_SERVICE_FRAME_SINKS_FRAME_SINK_BUNDLE_IMPL_H_
+
+#include <cstdint>
+#include <map>
+#include <set>
+#include <vector>
+
+#include "components/viz/common/frame_sinks/begin_frame_source.h"
+#include "components/viz/common/surfaces/frame_sink_bundle_id.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "services/viz/public/mojom/compositing/compositor_frame_sink.mojom.h"
+#include "services/viz/public/mojom/compositing/frame_sink_bundle.mojom.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace viz {
+
+class CompositorFrameSinkImpl;
+class FrameSinkManagerImpl;
+
+// This object receives aggregate SubmitCompositorFrame and DidNotProduceFrame
+// messages from remote CompositorFrameSink clients, with the `client_id`, who
+// who were created as part of this bundle.
+//
+// Outgoing client messages from the corresponding CompositorFrameSinkImpls are
+// also aggregated here and sent in batch back to the client.
+//
+// Every sink added to a bundle must be of the same CompositorFrameSinkType and
+// use the same BeginFrameSource.
+class FrameSinkBundleImpl : public mojom::FrameSinkBundle,
+                            public BeginFrameObserver {
+ public:
+  // Constructs a new FrameSinkBundleImpl for a subset of sinks belonging to
+  // `client_id`. This object observes OnBeginFrame() events from
+  // `begin_frame_source` and pushes them synchronously to each frame sink in
+  // the bundle at the same time.
+  FrameSinkBundleImpl(FrameSinkManagerImpl& manager,
+                      const FrameSinkBundleId& id,
+                      BeginFrameSource* begin_frame_source,
+                      mojo::PendingReceiver<mojom::FrameSinkBundle> receiver,
+                      mojo::PendingRemote<mojom::FrameSinkBundleClient> client);
+  FrameSinkBundleImpl(const FrameSinkBundleImpl&) = delete;
+  FrameSinkBundleImpl& operator=(const FrameSinkBundleImpl&) = delete;
+  ~FrameSinkBundleImpl() override;
+
+  BeginFrameSource* begin_frame_source() { return begin_frame_source_; }
+
+  void AddFrameSink(const FrameSinkId& id);
+  void RemoveFrameSink(const FrameSinkId& id);
+
+  // mojom::FrameSinkBundle implementation:
+  void InitializeCompositorFrameSinkType(
+      uint32_t sink_id,
+      mojom::CompositorFrameSinkType type) override;
+  void SetNeedsBeginFrame(uint32_t sink_id, bool needs_begin_frame) override;
+  void Submit(
+      std::vector<mojom::BundledFrameSubmissionPtr> submissions) override;
+  void DidAllocateSharedBitmap(uint32_t sink_id,
+                               base::ReadOnlySharedMemoryRegion region,
+                               const gpu::Mailbox& id) override;
+
+  // Helpers used by each CompositorFrameSinkImpl to proxy their client messages
+  // to this object for potentially batched communication.
+  void EnqueueDidReceiveCompositorFrameAck(
+      uint32_t sink_id,
+      std::vector<ReturnedResource> resources);
+  void EnqueueOnBeginFrame(
+      uint32_t sink_id,
+      const BeginFrameArgs& args,
+      const base::flat_map<uint32_t, FrameTimingDetails>& details);
+  void EnqueueReclaimResources(uint32_t sink_id,
+                               std::vector<ReturnedResource> resources);
+  void SendOnBeginFramePausedChanged(uint32_t sink_id, bool paused);
+  void SendOnCompositorFrameTransitionDirectiveProcessed(uint32_t sink_id,
+                                                         uint32_t sequence_id);
+
+  void UnregisterBeginFrameSource(BeginFrameSource* source);
+
+ private:
+  CompositorFrameSinkImpl* GetFrameSink(uint32_t sink_id) const;
+
+  void FlushMessages();
+  void OnDisconnect();
+
+  // BeginFrameObserver implementation:
+  void OnBeginFrame(const BeginFrameArgs& args) override;
+  const BeginFrameArgs& LastUsedBeginFrameArgs() const override;
+  void OnBeginFrameSourcePausedChanged(bool paused) override;
+  bool WantsAnimateOnlyBeginFrames() const override;
+
+  FrameSinkManagerImpl& manager_;
+  const FrameSinkBundleId id_;
+  BeginFrameSource* begin_frame_source_;
+  mojo::Receiver<mojom::FrameSinkBundle> receiver_;
+  mojo::Remote<mojom::FrameSinkBundleClient> client_;
+
+  BeginFrameArgs last_used_begin_frame_args_;
+
+  size_t num_unacked_submissions_ = 0;
+
+  absl::optional<mojom::CompositorFrameSinkType> sink_type_;
+  bool defer_on_begin_frames_ = false;
+  std::vector<mojom::BundledReturnedResourcesPtr> pending_received_frame_acks_;
+  std::vector<mojom::BundledReturnedResourcesPtr> pending_reclaimed_resources_;
+  std::vector<mojom::BeginFrameInfoPtr> pending_on_begin_frames_;
+
+  std::set<uint32_t> frame_sinks_;
+};
+
+}  // namespace viz
+
+#endif  // COMPONENTS_VIZ_SERVICE_FRAME_SINKS_FRAME_SINK_BUNDLE_IMPL_H_
diff --git a/components/viz/service/frame_sinks/frame_sink_bundle_impl_unittest.cc b/components/viz/service/frame_sinks/frame_sink_bundle_impl_unittest.cc
new file mode 100644
index 0000000..7b3c168
--- /dev/null
+++ b/components/viz/service/frame_sinks/frame_sink_bundle_impl_unittest.cc
@@ -0,0 +1,397 @@
+// Copyright 2021 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/viz/service/frame_sinks/frame_sink_bundle_impl.h"
+
+#include <cstdint>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/json/values_util.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/test/bind.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "base/test/task_environment.h"
+#include "base/unguessable_token.h"
+#include "components/viz/common/frame_sinks/begin_frame_source.h"
+#include "components/viz/common/quads/compositor_frame.h"
+#include "components/viz/common/resources/resource_id.h"
+#include "components/viz/common/resources/transferable_resource.h"
+#include "components/viz/common/surfaces/frame_sink_bundle_id.h"
+#include "components/viz/common/surfaces/frame_sink_id.h"
+#include "components/viz/common/surfaces/local_surface_id.h"
+#include "components/viz/service/display_embedder/server_shared_bitmap_manager.h"
+#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
+#include "components/viz/test/compositor_frame_helpers.h"
+#include "components/viz/test/fake_external_begin_frame_source.h"
+#include "components/viz/test/mock_compositor_frame_sink_client.h"
+#include "components/viz/test/mock_display_client.h"
+#include "components/viz/test/test_output_surface_provider.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "services/viz/public/mojom/compositing/compositor_frame_sink.mojom-params-data.h"
+#include "services/viz/public/mojom/compositing/compositor_frame_sink.mojom.h"
+#include "services/viz/public/mojom/compositing/frame_sink_bundle.mojom-forward.h"
+#include "services/viz/public/mojom/compositing/frame_sink_bundle.mojom.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/khronos/GLES2/gl2.h"
+
+namespace viz {
+namespace {
+
+using ::testing::ElementsAre;
+using ::testing::UnorderedElementsAre;
+
+constexpr FrameSinkId kRootFrame(1, 1);
+constexpr FrameSinkBundleId kBundleId(2, 1);
+constexpr FrameSinkId kMainFrame(2, 1);
+constexpr FrameSinkId kSubFrameA(2, 2);
+constexpr FrameSinkId kSubFrameB(2, 3);
+constexpr FrameSinkId kSubFrameC(2, 4);
+
+const base::UnguessableToken kSurfaceTokenA =
+    base::UnguessableToken::Deserialize(3, 42);
+const base::UnguessableToken kSurfaceTokenB =
+    base::UnguessableToken::Deserialize(5, 42);
+const base::UnguessableToken kSurfaceTokenC =
+    base::UnguessableToken::Deserialize(7, 42);
+
+const LocalSurfaceId kSurfaceA{2, kSurfaceTokenA};
+const LocalSurfaceId kSurfaceB{3, kSurfaceTokenB};
+const LocalSurfaceId kSurfaceC{4, kSurfaceTokenC};
+
+gpu::SyncToken MakeVerifiedSyncToken(int id) {
+  gpu::SyncToken token;
+  token.Set(gpu::CommandBufferNamespace::GPU_IO,
+            gpu::CommandBufferId::FromUnsafeValue(id), 1);
+  token.SetVerifyFlush();
+  return token;
+}
+
+// Matches a pointer to a structure with a sink_id field against a given
+// FrameSinkId.
+MATCHER_P(ForSink, id, "is for " + id.ToString()) {
+  return arg->sink_id == id.sink_id();
+}
+
+// Matches a ReturnedResource with an ID matching a given ResourceId.
+MATCHER_P(ForResource,
+          id,
+          "is for resource " + base::NumberToString(id.GetUnsafeValue())) {
+  return arg.id == id;
+}
+
+// Holds the four interface objects needed to create a RootCompositorFrameSink.
+struct TestRootFrameSink {
+  TestRootFrameSink(FrameSinkManagerImpl& manager,
+                    BeginFrameSource& begin_frame_source)
+      : manager_(manager) {
+    auto params = mojom::RootCompositorFrameSinkParams::New();
+    params->frame_sink_id = kRootFrame;
+    params->widget = gpu::kNullSurfaceHandle;
+    params->compositor_frame_sink =
+        compositor_frame_sink.BindNewEndpointAndPassReceiver();
+    params->compositor_frame_sink_client =
+        compositor_frame_sink_client.BindInterfaceRemote();
+    params->display_private = display_private.BindNewEndpointAndPassReceiver();
+    params->display_client = display_client.BindRemote();
+
+    manager_.RegisterFrameSinkId(kRootFrame, /*report_activation=*/true);
+    manager_.RegisterBeginFrameSource(&begin_frame_source, kRootFrame);
+    manager_.CreateRootCompositorFrameSink(std::move(params));
+  }
+
+  ~TestRootFrameSink() { manager_.InvalidateFrameSinkId(kRootFrame); }
+
+  FrameSinkManagerImpl& manager_;
+  mojo::AssociatedRemote<mojom::CompositorFrameSink> compositor_frame_sink;
+  MockCompositorFrameSinkClient compositor_frame_sink_client;
+  mojo::AssociatedRemote<mojom::DisplayPrivate> display_private;
+  MockDisplayClient display_client;
+};
+
+// Holds objects needed to create other kinds of CompositorFrameSinks.
+struct TestFrameSink {
+  TestFrameSink(
+      FrameSinkManagerImpl& manager,
+      const FrameSinkId& id,
+      const absl::optional<FrameSinkId>& parent_id,
+      const absl::optional<FrameSinkBundleId>& bundle_id = absl::nullopt)
+      : manager_(manager), id_(id) {
+    manager_.RegisterFrameSinkId(id, /*report_activation=*/true);
+    if (parent_id) {
+      manager_.RegisterFrameSinkHierarchy(*parent_id, id);
+    }
+    manager_.CreateCompositorFrameSink(
+        id, bundle_id, frame_sink.BindNewPipeAndPassReceiver(),
+        client_receiver_.BindNewPipeAndPassRemote());
+    manager_.GetFrameSinkForId(id)->SetNeedsBeginFrame(true);
+  }
+
+  ~TestFrameSink() {
+    base::RunLoop loop;
+    manager_.DestroyCompositorFrameSink(id_, loop.QuitClosure());
+    loop.Run();
+  }
+
+  FrameSinkManagerImpl& manager_;
+  const FrameSinkId id_;
+  MockCompositorFrameSinkClient mock_client_;
+  mojo::Receiver<mojom::CompositorFrameSinkClient> client_receiver_{
+      &mock_client_};
+  mojo::Remote<mojom::CompositorFrameSink> frame_sink;
+};
+
+// Helper to receive FrameSinkBundleClient notifications and aggregate their
+// contents for inspection by tests.
+class TestBundleClient : public mojom::FrameSinkBundleClient {
+ public:
+  TestBundleClient() = default;
+  ~TestBundleClient() override = default;
+
+  void WaitForNextMessage() {
+    DCHECK(!wait_loop_);
+    wait_loop_.emplace();
+    wait_loop_->Run();
+    wait_loop_.reset();
+  }
+
+  void WaitForNextFlush(
+      std::vector<mojom::BundledReturnedResourcesPtr>* acks,
+      std::vector<mojom::BeginFrameInfoPtr>* begin_frames,
+      std::vector<mojom::BundledReturnedResourcesPtr>* reclaimed_resources) {
+    acks_ = acks;
+    begin_frames_ = begin_frames;
+    reclaimed_resources_ = reclaimed_resources;
+    WaitForNextMessage();
+  }
+
+  // mojom::FrameSinkBundleClient implementation:
+  void FlushNotifications(std::vector<mojom::BundledReturnedResourcesPtr> acks,
+                          std::vector<mojom::BeginFrameInfoPtr> begin_frames,
+                          std::vector<mojom::BundledReturnedResourcesPtr>
+                              reclaimed_resources) override {
+    if (acks_) {
+      *acks_ = std::move(acks);
+    } else {
+      EXPECT_TRUE(acks.empty()) << "Got unexpected acks";
+    }
+
+    if (begin_frames_) {
+      *begin_frames_ = std::move(begin_frames);
+    } else {
+      EXPECT_TRUE(begin_frames.empty()) << "Got unexpected OnBeginFrames";
+    }
+
+    if (reclaimed_resources_) {
+      *reclaimed_resources_ = std::move(reclaimed_resources);
+    } else {
+      EXPECT_TRUE(reclaimed_resources.empty())
+          << "Got unexpected ReclaimResources";
+    }
+
+    NotifyOnMessage();
+  }
+
+  void OnBeginFramePausedChanged(uint32_t, bool) override {}
+  void OnCompositorFrameTransitionDirectiveProcessed(
+      uint32_t sink_id,
+      uint32_t sequence_id) override {}
+
+ private:
+  void NotifyOnMessage() {
+    if (wait_loop_) {
+      wait_loop_->Quit();
+    }
+  }
+
+  absl::optional<base::RunLoop> wait_loop_;
+  std::vector<mojom::BundledReturnedResourcesPtr>* acks_;
+  std::vector<mojom::BeginFrameInfoPtr>* begin_frames_;
+  std::vector<mojom::BundledReturnedResourcesPtr>* reclaimed_resources_;
+};
+
+class FrameSinkBundleImplTest : public testing::Test {
+ public:
+  FrameSinkBundleImplTest() {
+    manager_.surface_manager()->SetTickClockForTesting(&test_clock_);
+    manager_.CreateFrameSinkBundle(kMainFrame, kBundleId,
+                                   bundle_.BindNewPipeAndPassReceiver(),
+                                   client_receiver_.BindNewPipeAndPassRemote());
+  }
+
+  ~FrameSinkBundleImplTest() override {
+    manager_.UnregisterBeginFrameSource(&begin_frame_source_);
+  }
+
+  void IssueOnBeginFrame() {
+    begin_frame_source_.TestOnBeginFrame(
+        begin_frame_source_.CreateBeginFrameArgs(BEGINFRAME_FROM_HERE));
+  }
+
+  mojom::BundledFrameSubmissionPtr CreateFrameSubmission(
+      const FrameSinkId& frame_sink_id,
+      const LocalSurfaceId& surface_id,
+      std::vector<ResourceId> resource_ids = {}) {
+    auto frame = MakeDefaultCompositorFrame();
+    for (const auto& id : resource_ids) {
+      TransferableResource resource;
+      resource.id = id;
+      resource.mailbox_holder.texture_target = GL_TEXTURE_2D;
+      resource.mailbox_holder.sync_token = frame_sync_token_;
+      frame.resource_list.push_back(resource);
+    }
+
+    auto data = mojom::BundledCompositorFrame::New(surface_id, std::move(frame),
+                                                   absl::nullopt, 0);
+    return mojom::BundledFrameSubmission::New(
+        frame_sink_id.sink_id(),
+        mojom::BundledFrameSubmissionData::NewFrame(std::move(data)));
+  }
+
+  mojom::BundledFrameSubmissionPtr CreateDidNotSubmitFrame(
+      const FrameSinkId& frame_sink_id) {
+    return mojom::BundledFrameSubmission::New(
+        frame_sink_id.sink_id(),
+        mojom::BundledFrameSubmissionData::NewDidNotProduceFrame(
+            BeginFrameAck::CreateManualAckWithDamage()));
+  }
+
+  FrameSinkManagerImpl& manager() { return manager_; }
+  TestBundleClient& test_client() { return test_client_; }
+  mojo::Remote<mojom::FrameSinkBundle>& bundle() { return bundle_; }
+
+ private:
+  const gpu::SyncToken frame_sync_token_{MakeVerifiedSyncToken(42)};
+
+  base::SimpleTestTickClock test_clock_;
+  DebugRendererSettings debug_settings_;
+  ServerSharedBitmapManager shared_bitmap_manager_;
+  TestOutputSurfaceProvider output_surface_provider_;
+  FrameSinkManagerImpl manager_{
+      FrameSinkManagerImpl::InitParams(&shared_bitmap_manager_,
+                                       &output_surface_provider_)};
+  FakeExternalBeginFrameSource begin_frame_source_{0.0f, false};
+
+  TestBundleClient test_client_;
+  mojo::Receiver<mojom::FrameSinkBundleClient> client_receiver_{&test_client_};
+  mojo::Remote<mojom::FrameSinkBundle> bundle_;
+
+  TestRootFrameSink root_sink_{manager_, begin_frame_source_};
+  TestFrameSink main_frame_{manager_, kMainFrame, kRootFrame};
+};
+
+TEST_F(FrameSinkBundleImplTest, DestroyOnDisconnect) {
+  EXPECT_NE(nullptr, manager().GetFrameSinkBundle(kBundleId));
+
+  bundle().reset();
+  base::RunLoop loop;
+  base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+                                                   loop.QuitClosure());
+  loop.Run();
+
+  EXPECT_EQ(nullptr, manager().GetFrameSinkBundle(kBundleId));
+}
+
+TEST_F(FrameSinkBundleImplTest, OnBeginFrame) {
+  TestFrameSink frame_a(manager(), kSubFrameA, kMainFrame, kBundleId);
+  TestFrameSink frame_b(manager(), kSubFrameB, kMainFrame, kBundleId);
+  TestFrameSink frame_c(manager(), kSubFrameC, kMainFrame, kBundleId);
+
+  // OnBeginFrame() should elicit a single batch of notifications to the bundle
+  // client, with a notification for each frame in the bundle.
+  std::vector<mojom::BeginFrameInfoPtr> begin_frames;
+  IssueOnBeginFrame();
+  test_client().WaitForNextFlush(nullptr, &begin_frames, nullptr);
+  EXPECT_THAT(begin_frames,
+              UnorderedElementsAre(ForSink(kSubFrameA), ForSink(kSubFrameB),
+                                   ForSink(kSubFrameC)));
+
+  // Also verify that if a sink does not want OnBeginFrame, we don't include one
+  // for it in subsequent batches.
+  manager().GetFrameSinkForId(kSubFrameB)->SetNeedsBeginFrame(false);
+  IssueOnBeginFrame();
+  test_client().WaitForNextFlush(nullptr, &begin_frames, nullptr);
+  EXPECT_THAT(begin_frames,
+              UnorderedElementsAre(ForSink(kSubFrameA), ForSink(kSubFrameC)));
+}
+
+TEST_F(FrameSinkBundleImplTest, SubmitAndAck) {
+  TestFrameSink frame_a(manager(), kSubFrameA, kMainFrame, kBundleId);
+  TestFrameSink frame_b(manager(), kSubFrameB, kMainFrame, kBundleId);
+  TestFrameSink frame_c(manager(), kSubFrameC, kMainFrame, kBundleId);
+
+  // Verify that submitting a batch of frames results in the client eventually
+  // receiving a corresponding batch of acks.
+  std::vector<mojom::BundledFrameSubmissionPtr> submissions;
+  submissions.push_back(CreateFrameSubmission(kSubFrameA, kSurfaceA));
+  submissions.push_back(CreateFrameSubmission(kSubFrameB, kSurfaceB));
+  submissions.push_back(CreateFrameSubmission(kSubFrameC, kSurfaceC));
+  bundle()->Submit(std::move(submissions));
+
+  std::vector<mojom::BundledReturnedResourcesPtr> acks;
+  test_client().WaitForNextFlush(&acks, nullptr, nullptr);
+
+  EXPECT_THAT(acks, ElementsAre(ForSink(kSubFrameA), ForSink(kSubFrameB),
+                                ForSink(kSubFrameC)));
+}
+
+TEST_F(FrameSinkBundleImplTest, NoAckIfDidNotProduceFrame) {
+  TestFrameSink frame_a(manager(), kSubFrameA, kMainFrame, kBundleId);
+  TestFrameSink frame_b(manager(), kSubFrameB, kMainFrame, kBundleId);
+  TestFrameSink frame_c(manager(), kSubFrameC, kMainFrame, kBundleId);
+
+  // Verify batched DidNotProduceFrame requests do not elicit a batched ack.
+  std::vector<mojom::BundledFrameSubmissionPtr> submissions;
+  submissions.push_back(CreateFrameSubmission(kSubFrameA, kSurfaceA));
+  submissions.push_back(CreateDidNotSubmitFrame(kSubFrameB));
+  submissions.push_back(CreateFrameSubmission(kSubFrameC, kSurfaceC));
+  bundle()->Submit(std::move(submissions));
+
+  std::vector<mojom::BundledReturnedResourcesPtr> acks;
+  test_client().WaitForNextFlush(&acks, nullptr, nullptr);
+  EXPECT_THAT(acks, ElementsAre(ForSink(kSubFrameA), ForSink(kSubFrameC)));
+}
+
+TEST_F(FrameSinkBundleImplTest, ReclaimResourcesOnAck) {
+  TestFrameSink frame_a(manager(), kSubFrameA, kMainFrame, kBundleId);
+  TestFrameSink frame_b(manager(), kSubFrameB, kMainFrame, kBundleId);
+  TestFrameSink frame_c(manager(), kSubFrameC, kMainFrame, kBundleId);
+
+  // First submit frames through all the sinks, to each surface.
+  std::vector<mojom::BundledFrameSubmissionPtr> submissions;
+  submissions.push_back(CreateFrameSubmission(kSubFrameA, kSurfaceA));
+  submissions.push_back(CreateFrameSubmission(kSubFrameB, kSurfaceB));
+  submissions.push_back(CreateFrameSubmission(kSubFrameC, kSurfaceC));
+  bundle()->Submit(std::move(submissions));
+
+  std::vector<mojom::BundledReturnedResourcesPtr> acks;
+  test_client().WaitForNextFlush(&acks, nullptr, nullptr);
+  EXPECT_THAT(acks, ElementsAre(ForSink(kSubFrameA), ForSink(kSubFrameB),
+                                ForSink(kSubFrameC)));
+
+  // Now frame C will submit with resources to a dead surface and be rejected
+  // immediately. This should result in an ack which immediately returns the
+  // attached resource.
+  ResourceId resource(1337);
+  manager().GetFrameSinkForId(kSubFrameC)->EvictSurface(kSurfaceC);
+  submissions.clear();
+  submissions.push_back(
+      CreateFrameSubmission(kSubFrameC, kSurfaceC, {resource}));
+  bundle()->Submit(std::move(submissions));
+
+  acks.clear();
+  test_client().WaitForNextFlush(&acks, nullptr, nullptr);
+  EXPECT_THAT(acks, ElementsAre(ForSink(kSubFrameC)));
+
+  EXPECT_EQ(kSubFrameC.sink_id(), acks[0]->sink_id);
+  EXPECT_THAT(acks[0]->resources, ElementsAre(ForResource(resource)));
+}
+
+}  // namespace
+}  // namespace viz
diff --git a/components/viz/service/frame_sinks/frame_sink_manager_impl.cc b/components/viz/service/frame_sinks/frame_sink_manager_impl.cc
index 9538ccc..ecef9320 100644
--- a/components/viz/service/frame_sinks/frame_sink_manager_impl.cc
+++ b/components/viz/service/frame_sinks/frame_sink_manager_impl.cc
@@ -20,6 +20,7 @@
 #include "components/viz/service/display/shared_bitmap_manager.h"
 #include "components/viz/service/display_embedder/output_surface_provider.h"
 #include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
+#include "components/viz/service/frame_sinks/frame_sink_bundle_impl.h"
 #include "components/viz/service/frame_sinks/video_capture/capturable_frame_sink.h"
 #include "components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.h"
 #include "components/viz/service/surfaces/pending_copy_output_request.h"
@@ -95,6 +96,24 @@
   surface_manager_.RemoveObserver(&hit_test_manager_);
 }
 
+CompositorFrameSinkImpl* FrameSinkManagerImpl::GetFrameSinkImpl(
+    const FrameSinkId& id) {
+  auto it = sink_map_.find(id);
+  if (it == sink_map_.end()) {
+    return nullptr;
+  }
+  return it->second.get();
+}
+
+FrameSinkBundleImpl* FrameSinkManagerImpl::GetFrameSinkBundle(
+    const FrameSinkBundleId& id) {
+  auto it = bundle_map_.find(id);
+  if (it == bundle_map_.end()) {
+    return nullptr;
+  }
+  return it->second.get();
+}
+
 void FrameSinkManagerImpl::BindAndSetClient(
     mojo::PendingReceiver<mojom::FrameSinkManager> receiver,
     scoped_refptr<base::SingleThreadTaskRunner> task_runner,
@@ -173,15 +192,44 @@
     root_sink_map_[frame_sink_id] = std::move(root_compositor_frame_sink);
 }
 
+void FrameSinkManagerImpl::CreateFrameSinkBundle(
+    const FrameSinkId& parent_frame_sink_id,
+    const FrameSinkBundleId& bundle_id,
+    mojo::PendingReceiver<mojom::FrameSinkBundle> receiver,
+    mojo::PendingRemote<mojom::FrameSinkBundleClient> client) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  if (base::Contains(bundle_map_, bundle_id)) {
+    receiver_.ReportBadMessage("Duplicate FrameSinkBundle ID");
+    return;
+  }
+
+  BeginFrameSource* parent_source =
+      frame_sink_source_map_[parent_frame_sink_id].source;
+  if (!parent_source) {
+    receiver_.ReportBadMessage("Bundle parent sink must have BeginFrameSource");
+    return;
+  }
+
+  bundle_map_[bundle_id] = std::make_unique<FrameSinkBundleImpl>(
+      *this, bundle_id, parent_source, std::move(receiver), std::move(client));
+}
+
 void FrameSinkManagerImpl::CreateCompositorFrameSink(
     const FrameSinkId& frame_sink_id,
+    const absl::optional<FrameSinkBundleId>& bundle_id,
     mojo::PendingReceiver<mojom::CompositorFrameSink> receiver,
     mojo::PendingRemote<mojom::CompositorFrameSinkClient> client) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  DCHECK(!base::Contains(sink_map_, frame_sink_id));
-
+  if (base::Contains(sink_map_, frame_sink_id)) {
+    receiver_.ReportBadMessage("Duplicate FrameSinkId");
+    return;
+  }
+  if (bundle_id && !GetFrameSinkBundle(*bundle_id)) {
+    receiver_.ReportBadMessage("Invalid FrameSinkBundleId");
+    return;
+  }
   sink_map_[frame_sink_id] = std::make_unique<CompositorFrameSinkImpl>(
-      this, frame_sink_id, std::move(receiver), std::move(client));
+      this, frame_sink_id, bundle_id, std::move(receiver), std::move(client));
 }
 
 void FrameSinkManagerImpl::DestroyCompositorFrameSink(
@@ -315,6 +363,10 @@
   root_sink_map_[root_frame_sink_id]->ForceImmediateDrawAndSwapIfPossible();
 }
 
+void FrameSinkManagerImpl::DestroyFrameSinkBundle(const FrameSinkBundleId& id) {
+  bundle_map_.erase(id);
+}
+
 void FrameSinkManagerImpl::OnFirstSurfaceActivation(
     const SurfaceInfo& surface_info) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
@@ -405,6 +457,10 @@
   FrameSinkId frame_sink_id = registered_sources_[source];
   registered_sources_.erase(source);
 
+  for (const auto& entry : bundle_map_) {
+    entry.second->UnregisterBeginFrameSource(source);
+  }
+
   if (frame_sink_source_map_.count(frame_sink_id) == 0u)
     return;
 
@@ -570,7 +626,7 @@
   return {};
 }
 
-const CompositorFrameSinkSupport* FrameSinkManagerImpl::GetFrameSinkForId(
+CompositorFrameSinkSupport* FrameSinkManagerImpl::GetFrameSinkForId(
     const FrameSinkId& frame_sink_id) const {
   auto it = support_map_.find(frame_sink_id);
   if (it != support_map_.end())
diff --git a/components/viz/service/frame_sinks/frame_sink_manager_impl.h b/components/viz/service/frame_sinks/frame_sink_manager_impl.h
index 65f4d19f..6224953 100644
--- a/components/viz/service/frame_sinks/frame_sink_manager_impl.h
+++ b/components/viz/service/frame_sinks/frame_sink_manager_impl.h
@@ -22,6 +22,7 @@
 #include "base/threading/thread_checker.h"
 #include "base/time/time.h"
 #include "components/viz/common/constants.h"
+#include "components/viz/common/surfaces/frame_sink_bundle_id.h"
 #include "components/viz/common/surfaces/frame_sink_id.h"
 #include "components/viz/service/frame_sinks/compositor_frame_sink_impl.h"
 #include "components/viz/service/frame_sinks/frame_sink_observer.h"
@@ -48,6 +49,7 @@
 
 class CapturableFrameSink;
 class CompositorFrameSinkSupport;
+class FrameSinkBundleImpl;
 class OutputSurfaceProvider;
 class SharedBitmapManager;
 
@@ -85,6 +87,9 @@
       OutputSurfaceProvider* output_surface_provider = nullptr);
   ~FrameSinkManagerImpl() override;
 
+  CompositorFrameSinkImpl* GetFrameSinkImpl(const FrameSinkId& id);
+  FrameSinkBundleImpl* GetFrameSinkBundle(const FrameSinkBundleId& id);
+
   // Binds |this| as a FrameSinkManagerImpl for |receiver| on |task_runner|. On
   // Mac |task_runner| will be the resize helper task runner. May only be called
   // once.
@@ -106,8 +111,14 @@
                               const std::string& debug_label) override;
   void CreateRootCompositorFrameSink(
       mojom::RootCompositorFrameSinkParamsPtr params) override;
+  void CreateFrameSinkBundle(
+      const FrameSinkId& parent_frame_sink_id,
+      const FrameSinkBundleId& bundle_id,
+      mojo::PendingReceiver<mojom::FrameSinkBundle> receiver,
+      mojo::PendingRemote<mojom::FrameSinkBundleClient> client) override;
   void CreateCompositorFrameSink(
       const FrameSinkId& frame_sink_id,
+      const absl::optional<FrameSinkBundleId>& bundle_id,
       mojo::PendingReceiver<mojom::CompositorFrameSink> receiver,
       mojo::PendingRemote<mojom::CompositorFrameSinkClient> client) override;
   void DestroyCompositorFrameSink(
@@ -139,6 +150,8 @@
   void Throttle(const std::vector<FrameSinkId>& ids,
                 base::TimeDelta interval) override;
 
+  void DestroyFrameSinkBundle(const FrameSinkBundleId& id);
+
   // SurfaceObserver implementation.
   void OnFirstSurfaceActivation(const SurfaceInfo& surface_info) override;
 
@@ -213,7 +226,7 @@
   // Returns an empty set if a parent doesn't have any children.
   base::flat_set<FrameSinkId> GetChildrenByParent(
       const FrameSinkId& parent_frame_sink_id) const;
-  const CompositorFrameSinkSupport* GetFrameSinkForId(
+  CompositorFrameSinkSupport* GetFrameSinkForId(
       const FrameSinkId& frame_sink_id) const;
 
   base::TimeDelta GetPreferredFrameIntervalForFrameSinkId(
@@ -347,6 +360,9 @@
   base::flat_map<FrameSinkId, std::unique_ptr<CompositorFrameSinkImpl>>
       sink_map_;
 
+  base::flat_map<FrameSinkBundleId, std::unique_ptr<FrameSinkBundleImpl>>
+      bundle_map_;
+
   base::flat_set<std::unique_ptr<FrameSinkVideoCapturerImpl>,
                  base::UniquePtrComparator>
       video_capturers_;
diff --git a/components/viz/service/frame_sinks/frame_sink_manager_unittest.cc b/components/viz/service/frame_sinks/frame_sink_manager_unittest.cc
index 895ecb0..749d676 100644
--- a/components/viz/service/frame_sinks/frame_sink_manager_unittest.cc
+++ b/components/viz/service/frame_sinks/frame_sink_manager_unittest.cc
@@ -140,7 +140,8 @@
   MockCompositorFrameSinkClient compositor_frame_sink_client;
   mojo::Remote<mojom::CompositorFrameSink> compositor_frame_sink;
   manager_.CreateCompositorFrameSink(
-      kFrameSinkIdA, compositor_frame_sink.BindNewPipeAndPassReceiver(),
+      kFrameSinkIdA, /*bundle_id=*/absl::nullopt,
+      compositor_frame_sink.BindNewPipeAndPassReceiver(),
       compositor_frame_sink_client.BindInterfaceRemote());
   EXPECT_TRUE(CompositorFrameSinkExists(kFrameSinkIdA));
 
@@ -156,7 +157,8 @@
   MockCompositorFrameSinkClient compositor_frame_sink_client;
   mojo::Remote<mojom::CompositorFrameSink> compositor_frame_sink;
   manager_.CreateCompositorFrameSink(
-      kFrameSinkIdA, compositor_frame_sink.BindNewPipeAndPassReceiver(),
+      kFrameSinkIdA, /*bundle_id=*/absl::nullopt,
+      compositor_frame_sink.BindNewPipeAndPassReceiver(),
       compositor_frame_sink_client.BindInterfaceRemote());
   EXPECT_TRUE(CompositorFrameSinkExists(kFrameSinkIdA));
 
diff --git a/components/viz/test/test_frame_sink_manager.h b/components/viz/test/test_frame_sink_manager.h
index 07efca6..419b74c 100644
--- a/components/viz/test/test_frame_sink_manager.h
+++ b/components/viz/test/test_frame_sink_manager.h
@@ -10,6 +10,7 @@
 #include <vector>
 
 #include "base/macros.h"
+#include "components/viz/common/surfaces/frame_sink_bundle_id.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
@@ -35,8 +36,14 @@
                               const std::string& debug_label) override {}
   void CreateRootCompositorFrameSink(
       mojom::RootCompositorFrameSinkParamsPtr params) override {}
+  void CreateFrameSinkBundle(
+      const FrameSinkId& parent_frame_sink_id,
+      const FrameSinkBundleId& bundle_id,
+      mojo::PendingReceiver<mojom::FrameSinkBundle> receiver,
+      mojo::PendingRemote<mojom::FrameSinkBundleClient> client) override {}
   void CreateCompositorFrameSink(
       const FrameSinkId& frame_sink_id,
+      const absl::optional<FrameSinkBundleId>& bundle_id,
       mojo::PendingReceiver<mojom::CompositorFrameSink> receiver,
       mojo::PendingRemote<mojom::CompositorFrameSinkClient> client) override {}
   void DestroyCompositorFrameSink(
diff --git a/content/browser/file_system_access/file_system_access_file_delegate_host_impl.cc b/content/browser/file_system_access/file_system_access_file_delegate_host_impl.cc
index bf8df4f..22e602a 100644
--- a/content/browser/file_system_access/file_system_access_file_delegate_host_impl.cc
+++ b/content/browser/file_system_access/file_system_access_file_delegate_host_impl.cc
@@ -8,7 +8,6 @@
 
 #include "base/allocator/partition_allocator/partition_alloc_constants.h"
 #include "base/bind.h"
-#include "base/bind_post_task.h"
 #include "base/location.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/numerics/safe_math.h"
@@ -155,6 +154,25 @@
   }
 }
 
+void FileSystemAccessFileDelegateHostImpl::GetLength(
+    GetLengthCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  DoFileSystemOperation(
+      FROM_HERE, &storage::FileSystemOperationRunner::GetMetadata,
+      base::BindOnce(
+          [](GetLengthCallback callback, base::File::Error file_error,
+             const base::File::Info& file_info) {
+            if (file_error == base::File::Error::FILE_OK) {
+              std::move(callback).Run(file_error, file_info.size);
+              return;
+            }
+            std::move(callback).Run(file_error, 0);
+          },
+          std::move(callback)),
+      url(), storage::FileSystemOperation::GET_METADATA_FIELD_SIZE);
+}
+
 void FileSystemAccessFileDelegateHostImpl::OnDisconnect() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   receiver_.reset();
diff --git a/content/browser/file_system_access/file_system_access_file_delegate_host_impl.h b/content/browser/file_system_access/file_system_access_file_delegate_host_impl.h
index 7db6905..34e0325 100644
--- a/content/browser/file_system_access/file_system_access_file_delegate_host_impl.h
+++ b/content/browser/file_system_access/file_system_access_file_delegate_host_impl.h
@@ -5,6 +5,7 @@
 #ifndef CONTENT_BROWSER_FILE_SYSTEM_ACCESS_FILE_SYSTEM_ACCESS_FILE_DELEGATE_HOST_IMPL_H_
 #define CONTENT_BROWSER_FILE_SYSTEM_ACCESS_FILE_SYSTEM_ACCESS_FILE_DELEGATE_HOST_IMPL_H_
 
+#include "base/bind_post_task.h"
 #include "components/services/storage/public/cpp/big_io_buffer.h"
 #include "content/browser/file_system_access/file_system_access_manager_impl.h"
 #include "storage/browser/file_system/file_stream_reader.h"
@@ -36,13 +37,45 @@
   void Write(uint64_t offset,
              mojo::ScopedDataPipeConsumerHandle data,
              WriteCallback callback) override;
+  void GetLength(GetLengthCallback callback) override;
 
  private:
   // State that is kept for the duration of a write operation, to keep track of
   // progress until the write completes.
   struct WriteState;
 
-  // BindRepeating DoFileSystemOperation copied from FileSystemAccessHandleBase.
+  // Copied from FileSystemAccessHandleBase.
+  template <typename... MethodArgs,
+            typename... ArgsMinusCallback,
+            typename... CallbackArgs>
+  void DoFileSystemOperation(
+      const base::Location& from_here,
+      storage::FileSystemOperationRunner::OperationID (
+          storage::FileSystemOperationRunner::*method)(MethodArgs...),
+      base::OnceCallback<void(CallbackArgs...)> callback,
+      ArgsMinusCallback&&... args) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    // Wrap the passed in callback in one that posts a task back to the current
+    // sequence.
+    auto wrapped_callback = base::BindPostTask(
+        base::SequencedTaskRunnerHandle::Get(), std::move(callback));
+
+    // And then post a task to the sequence bound operation runner to run the
+    // provided method with the provided arguments (and the wrapped callback).
+    //
+    // FileSystemOperationRunner assumes file_system_context() is kept alive, to
+    // make sure this happens it is bound to a DoNothing callback.
+    manager()
+        ->operation_runner()
+        .AsyncCall(base::IgnoreResult(method))
+        .WithArgs(std::forward<ArgsMinusCallback>(args)...,
+                  std::move(wrapped_callback))
+        .Then(base::BindOnce(
+            base::DoNothing::Once<scoped_refptr<storage::FileSystemContext>>(),
+            base::WrapRefCounted(file_system_context())));
+  }
+  // Same as the previous overload, but using RepeatingCallback and
+  // BindRepeating instead.
   template <typename... MethodArgs,
             typename... ArgsMinusCallback,
             typename... CallbackArgs>
diff --git a/content/browser/renderer_host/clipboard_host_impl.cc b/content/browser/renderer_host/clipboard_host_impl.cc
index 2e38607..513d177 100644
--- a/content/browser/renderer_host/clipboard_host_impl.cc
+++ b/content/browser/renderer_host/clipboard_host_impl.cc
@@ -508,14 +508,9 @@
                      data_type, std::move(data), std::move(callback));
 
   if (ui::DataTransferPolicyController::HasInstance()) {
-    WebContents* web_contents = nullptr;
-    auto* delegate =
-        static_cast<RenderFrameHostImpl*>(render_frame_host())->delegate();
-    web_contents = delegate ? delegate->GetAsWebContents() : nullptr;
-
     ui::DataTransferPolicyController::Get()->PasteIfAllowed(
         ui::Clipboard::GetForCurrentThread()->GetSource(clipboard_buffer),
-        CreateDataEndpoint().get(), data_size, web_contents,
+        CreateDataEndpoint().get(), data_size, render_frame_host(),
         std::move(policy_cb));
     return;
   }
diff --git a/content/browser/renderer_host/clipboard_host_impl_unittest.cc b/content/browser/renderer_host/clipboard_host_impl_unittest.cc
index d654b806..284482aa 100644
--- a/content/browser/renderer_host/clipboard_host_impl_unittest.cc
+++ b/content/browser/renderer_host/clipboard_host_impl_unittest.cc
@@ -78,7 +78,7 @@
                void(const ui::DataTransferEndpoint* const data_src,
                     const ui::DataTransferEndpoint* const data_dst,
                     const absl::optional<size_t> size,
-                    content::WebContents* web_contents,
+                    content::RenderFrameHost* rfh,
                     base::OnceCallback<void(bool)> callback));
 
   MOCK_METHOD3(IsDragDropAllowed,
@@ -389,12 +389,11 @@
   // Policy controller cancels the paste request.
   PolicyControllerTest policy_controller;
   EXPECT_CALL(policy_controller, PasteIfAllowed)
-      .WillOnce(
-          testing::Invoke([](const ui::DataTransferEndpoint* const data_src,
-                             const ui::DataTransferEndpoint* const data_dst,
-                             const absl::optional<size_t> size,
-                             content::WebContents* web_contents,
-                             base::OnceCallback<void(bool)> callback) {
+      .WillOnce(testing::Invoke(
+          [](const ui::DataTransferEndpoint* const data_src,
+             const ui::DataTransferEndpoint* const data_dst,
+             const absl::optional<size_t> size, content::RenderFrameHost* rfh,
+             base::OnceCallback<void(bool)> callback) {
             std::move(callback).Run(false);
           }));
 
@@ -422,12 +421,11 @@
   // Policy controller accepts the paste request.
   PolicyControllerTest policy_controller;
   EXPECT_CALL(policy_controller, PasteIfAllowed)
-      .WillOnce(
-          testing::Invoke([](const ui::DataTransferEndpoint* const data_src,
-                             const ui::DataTransferEndpoint* const data_dst,
-                             const absl::optional<size_t> size,
-                             content::WebContents* web_contents,
-                             base::OnceCallback<void(bool)> callback) {
+      .WillOnce(testing::Invoke(
+          [](const ui::DataTransferEndpoint* const data_src,
+             const ui::DataTransferEndpoint* const data_dst,
+             const absl::optional<size_t> size, content::RenderFrameHost* rfh,
+             base::OnceCallback<void(bool)> callback) {
             std::move(callback).Run(true);
           }));
 
diff --git a/content/renderer/BUILD.gn b/content/renderer/BUILD.gn
index 6ba6cac..c2c5507a 100644
--- a/content/renderer/BUILD.gn
+++ b/content/renderer/BUILD.gn
@@ -241,6 +241,7 @@
     "//base:i18n",
     "//build:chromecast_buildflags",
     "//build:chromeos_buildflags",
+    "//build:os_buildflags",
     "//cc",
     "//cc/animation",
     "//cc/mojo_embedder",
diff --git a/content/renderer/media/media_factory.cc b/content/renderer/media/media_factory.cc
index fa220d34..ebcc788 100644
--- a/content/renderer/media/media_factory.cc
+++ b/content/renderer/media/media_factory.cc
@@ -16,7 +16,10 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/task_runner_util.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
 #include "build/buildflag.h"
+#include "build/chromecast_buildflags.h"
+#include "build/os_buildflags.h"
 #include "cc/trees/layer_tree_settings.h"
 #include "content/public/common/content_client.h"
 #include "content/public/common/content_switches.h"
@@ -116,11 +119,11 @@
 #include "media/remoting/remoting_renderer_factory.h"  // nogncheck
 #endif
 
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
 #include "content/renderer/media/win/dcomp_texture_wrapper_impl.h"
 #include "media/cdm/win/media_foundation_cdm.h"
 #include "media/mojo/clients/win/media_foundation_renderer_client_factory.h"
-#endif  // defined(OS_WIN)
+#endif  // BUILDFLAG(IS_WIN)
 
 namespace {
 
@@ -714,7 +717,7 @@
       RendererType::kCourier, std::move(courier_factory), is_remoting_cb);
 #endif
 
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
   // Only use MediaFoundationRenderer when MediaFoundationCdm is available.
   if (media::MediaFoundationCdm::IsAvailable()) {
     auto dcomp_texture_creation_cb =
@@ -729,7 +732,7 @@
             std::move(dcomp_texture_creation_cb),
             CreateMojoRendererFactory()));
   }
-#endif  // defined(OS_WIN)
+#endif  // BUILDFLAG(IS_WIN)
 
 #if BUILDFLAG(IS_CHROMECAST)
   if (renderer_media_playback_options.is_remoting_renderer_enabled()) {
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
index cd3243a..af836b2 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
@@ -313,6 +313,9 @@
 # Fails when dcheck_always_on set to true
 [ fuchsia ] deqp/data/gles2/shaders/preprocessor.html [ Skip ]
 
+crbug.com/1238056 [ fuchsia angle-swiftshader ] conformance/ogles/GL/build/build_017_to_024.html [ Failure ]
+
+
 ####################
 # Win failures     #
 ####################
@@ -413,6 +416,7 @@
 crbug.com/angleproject/4334 [ win angle-vulkan passthrough ] conformance/textures/misc/canvas-teximage-after-multiple-drawimages.html [ Skip ]
 crbug.com/1216686 [ win angle-vulkan passthrough ] conformance/glsl/bugs/complex-glsl-does-not-crash.html [ Failure ]
 crbug.com/1216686 [ win angle-vulkan passthrough ] conformance/glsl/misc/shader-uniform-packing-restrictions.html [ Failure ]
+crbug.com/1238056 [ win angle-vulkan passthrough ] conformance/ogles/GL/build/build_017_to_024.html [ Failure ]
 
 # Vulkan / Win / NVIDIA / Passthrough command decoder
 crbug.com/963205 [ win nvidia angle-vulkan passthrough ] conformance/extensions/webgl-compressed-texture-s3tc-srgb.html [ Failure ]
@@ -492,6 +496,7 @@
 crbug.com/angleproject/5505 [ mac angle-metal passthrough ] conformance/ogles/GL/acos/acos_001_to_006.html [ Failure ]
 crbug.com/angleproject/5505 [ mac angle-metal passthrough ] conformance/ogles/GL/asin/asin_001_to_006.html [ Failure ]
 crbug.com/1227129 [ mac angle-metal passthrough ] conformance/glsl/misc/ternary-operators-in-initializers.html [ Failure ]
+crbug.com/1238056 [ mac angle-metal passthrough ] conformance/ogles/GL/build/build_017_to_024.html [ Failure ]
 
 # Mac / Passthrough command decoder / Metal / AMD
 crbug.com/1227767 [ bigsur amd-0x6821 angle-metal passthrough ] conformance/ogles/GL/discard/discard_001_to_002.html [ Failure ]
diff --git a/gpu/command_buffer/service/shared_image_representation_gl_ozone.cc b/gpu/command_buffer/service/shared_image_representation_gl_ozone.cc
index 8cd7bb9..cebf7b5 100644
--- a/gpu/command_buffer/service/shared_image_representation_gl_ozone.cc
+++ b/gpu/command_buffer/service/shared_image_representation_gl_ozone.cc
@@ -127,6 +127,7 @@
   bool readonly =
       current_access_mode_ != GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM;
   ozone_backing()->EndAccess(readonly, std::move(fence));
+  current_access_mode_ = 0;
 }
 
 }  // namespace gpu
diff --git a/gpu/command_buffer/service/shared_image_representation_gl_ozone.h b/gpu/command_buffer/service/shared_image_representation_gl_ozone.h
index 6c090f7..78cc7f888b 100644
--- a/gpu/command_buffer/service/shared_image_representation_gl_ozone.h
+++ b/gpu/command_buffer/service/shared_image_representation_gl_ozone.h
@@ -50,7 +50,7 @@
   }
 
   gles2::Texture* texture_;
-  GLenum current_access_mode_;
+  GLenum current_access_mode_ = 0;
 
   DISALLOW_COPY_AND_ASSIGN(SharedImageRepresentationGLOzone);
 };
diff --git a/infra/config/generated/commit-queue.cfg b/infra/config/generated/commit-queue.cfg
index 55759fb..605c3f6 100644
--- a/infra/config/generated/commit-queue.cfg
+++ b/infra/config/generated/commit-queue.cfg
@@ -1249,10 +1249,7 @@
       }
       builders {
         name: "chromium/try/linux-rel-orchestrator"
-        experiment_percentage: 100
-        location_regexp: ".*"
-        location_regexp_exclude: ".+/[+]/docs/.+"
-        location_regexp_exclude: ".+/[+]/infra/config/.+"
+        includable_only: true
       }
       builders {
         name: "chromium/try/linux-rel-reclient"
diff --git a/infra/config/generated/cq-builders.md b/infra/config/generated/cq-builders.md
index 81222190..c72799f 100644
--- a/infra/config/generated/cq-builders.md
+++ b/infra/config/generated/cq-builders.md
@@ -459,9 +459,6 @@
   * [`//content/browser/tracing/.+`](https://cs.chromium.org/chromium/src/content/browser/tracing/)
   * [`//services/tracing/.+`](https://cs.chromium.org/chromium/src/services/tracing/)
 
-* [linux-rel-orchestrator](https://ci.chromium.org/p/chromium/builders/try/linux-rel-orchestrator) ([definition](https://cs.chromium.org/search?q=package:%5Echromium$+file:/cq.star$+-file:/beta/+-file:/stable/+linux-rel-orchestrator)) ([matching builders](https://cs.chromium.org/search?q=+file:trybots.py+linux-rel-orchestrator))
-  * Experiment percentage: 100.0
-
 * [linux-rel-reclient](https://ci.chromium.org/p/chromium/builders/try/linux-rel-reclient) ([definition](https://cs.chromium.org/search?q=package:%5Echromium$+file:/cq.star$+-file:/beta/+-file:/stable/+linux-rel-reclient)) ([matching builders](https://cs.chromium.org/search?q=+file:trybots.py+linux-rel-reclient))
   * Experiment percentage: 10.0
 
diff --git a/infra/config/subprojects/chromium/try.star b/infra/config/subprojects/chromium/try.star
index d618594..5d72581d 100644
--- a/infra/config/subprojects/chromium/try.star
+++ b/infra/config/subprojects/chromium/try.star
@@ -1284,9 +1284,10 @@
         "compilator": "linux-rel-compilator",
     },
     service_account = "chromium-orchestrator@chops-service-accounts.iam.gserviceaccount.com",
-    tryjob = try_.job(
-        experiment_percentage = 100,
-    ),
+    # TODO (kimstephanie): Turn back on when Aug 9 pending tasks go back down
+    #tryjob = try_.job(
+    #    experiment_percentage = 100,
+    #),
 )
 
 try_.chromium_linux_builder(
diff --git a/ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_mediator.mm b/ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_mediator.mm
index 948d817..bbef297 100644
--- a/ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_mediator.mm
+++ b/ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_mediator.mm
@@ -206,6 +206,10 @@
     }
     _reauthenticationModule = reauthenticationModule;
     _securityAlertHandler = securityAlertHandler;
+
+    // Prevent a flicker from happening by starting with valid activity. This
+    // will get updated as soon as a form is interacted.
+    _validActivityForAccessoryView = YES;
   }
   return self;
 }
@@ -377,6 +381,23 @@
 }
 
 - (BOOL)isInputAccessoryViewActive {
+  // Return early if there is no WebState.
+  if (!_webState) {
+    return NO;
+  }
+
+  // Return early if the URL can't be verified.
+  web::URLVerificationTrustLevel trustLevel;
+  const GURL pageURL(_webState->GetCurrentURL(&trustLevel));
+  if (trustLevel != web::URLVerificationTrustLevel::kAbsolute) {
+    return NO;
+  }
+
+  // Return early if the url is not HTML.
+  if (!web::UrlHasWebScheme(pageURL) || !_webState->ContentIsHTML()) {
+    return NO;
+  }
+
   return self.validActivityForAccessoryView;
 }
 
diff --git a/ios/chrome/browser/ui/reading_list/BUILD.gn b/ios/chrome/browser/ui/reading_list/BUILD.gn
index 840c0e4..1a2d411 100644
--- a/ios/chrome/browser/ui/reading_list/BUILD.gn
+++ b/ios/chrome/browser/ui/reading_list/BUILD.gn
@@ -92,6 +92,7 @@
     "//components/infobars/core",
     "//components/prefs",
     "//components/reading_list/core",
+    "//components/ukm/ios:ukm_url_recorder",
     "//ios/chrome/browser",
     "//ios/chrome/browser/browser_state",
     "//ios/chrome/browser/infobars",
@@ -100,6 +101,7 @@
     "//ios/chrome/browser/ui/reading_list:reading_list_constants",
     "//ios/web/public",
     "//ios/web/public/js_messaging:js_messaging",
+    "//services/metrics/public/cpp:ukm_builders",
   ]
   configs += [ "//build/config/compiler:enable_arc" ]
 }
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_javascript_feature.mm b/ios/chrome/browser/ui/reading_list/reading_list_javascript_feature.mm
index 11c8787..c1737435 100644
--- a/ios/chrome/browser/ui/reading_list/reading_list_javascript_feature.mm
+++ b/ios/chrome/browser/ui/reading_list/reading_list_javascript_feature.mm
@@ -13,6 +13,7 @@
 #include "components/infobars/core/infobar_manager.h"
 #include "components/prefs/pref_service.h"
 #include "components/reading_list/core/reading_list_model.h"
+#include "components/ukm/ios/ukm_url_recorder.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #import "ios/chrome/browser/chrome_url_util.h"
 #include "ios/chrome/browser/infobars/infobar_ios.h"
@@ -23,6 +24,7 @@
 #import "ios/web/public/js_messaging/java_script_feature_util.h"
 #import "ios/web/public/js_messaging/script_message.h"
 #import "ios/web/public/web_state.h"
+#include "services/metrics/public/cpp/ukm_builders.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -146,6 +148,25 @@
       "IOS.ReadingList.Javascript.LongPageDistillabilityScore",
       (long_score + 1) * 100, 0, 400, 50);
 
+  ukm::SourceId sourceID = ukm::GetSourceIdForWebStateDocument(web_state);
+  if (sourceID != ukm::kInvalidSourceId) {
+    // Round to the nearest tenth, and additionally round to a .5 level of
+    // granularity if <0.5 or > 1.5. Get accuracy to the tenth digit in UKM by
+    // multiplying by 10.
+    int score_minimization = (int)(round(score * 10));
+    int long_score_minimization = (int)(round(long_score * 10));
+    if (score_minimization > 15 || score_minimization < 5) {
+      score_minimization = ((score_minimization + 2.5) / 5) * 5;
+    }
+    if (long_score_minimization > 15 || long_score_minimization < 5) {
+      long_score_minimization = ((long_score_minimization + 2.5) / 5) * 5;
+    }
+    ukm::builders::IOS_PageReadability(sourceID)
+        .SetDistilibilityScore(score_minimization)
+        .SetDistilibilityLongScore(long_score_minimization)
+        .Record(ukm::UkmRecorder::Get());
+  }
+
   // Calculate Time to Read
   absl::optional<double> opt_word_count =
       message.body()->FindDoubleKey("wordCount");
diff --git a/ios/chrome/test/earl_grey/accessibility_util.mm b/ios/chrome/test/earl_grey/accessibility_util.mm
index e7179c0..4c6db161 100644
--- a/ios/chrome/test/earl_grey/accessibility_util.mm
+++ b/ios/chrome/test/earl_grey/accessibility_util.mm
@@ -19,7 +19,7 @@
   // run, but may not be the correct long-term solution.
   [GTXAnalytics setEnabled:NO];
 
-  GTXToolKit* toolkit = [[GTXToolKit alloc] init];
+  GTXToolKit* toolkit = [GTXToolKit defaultToolkit];
   for (UIWindow* window in [[UIApplication sharedApplication] windows]) {
     // Run the checks on all elements on the screen.
     BOOL success = [toolkit checkAllElementsFromRootElements:@[ window ]
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1
index 127bded..3d967bc 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-c4bd6b12daa9a46d74a1e1fd58e2575241498332
\ No newline at end of file
+f7740b329a1554c83d8cc4ffb551b23f893001e6
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1
index eac60c8..09c8716 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-99c170a5fdd9ef43214c251dcf333e51241a0aa8
\ No newline at end of file
+b38cdf41a3f93e1e2844095e7ca9e9d6ccdf246f
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.arm64.zip.sha1
index 25375f3..7133a93 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-638cf0f4b0c9be77107e75c59630fa195b82302c
\ No newline at end of file
+a9d96b3e936aa648d92653578e2567fb853acf33
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.x64.zip.sha1
index 0896051..6ba03a8 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-3cbcae34d85519df4a3e864d9670076f8fa576b2
\ No newline at end of file
+8240edab429489165e3388add0bb77940cd7582b
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1
index ec6b1a22..3ee109d 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-e9940962d6e31270c829a1928707d4df82b046c7
\ No newline at end of file
+40e645a83b7e689009b67836d72919389bad1b16
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1
index e7e8722..db61856 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-7930ea5e8d5e80cc3bae92040a482133d62ed020
\ No newline at end of file
+09edb2624ed8cce11bdee17f72b09b82086c3f9d
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1
index d6d04d9..4dfbbaf 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-7985217c3d4f93dbb5029698d187aec623de86e3
\ No newline at end of file
+307e3dc66125067d044deb0431e37e467c5ab52e
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1
index 950900e..9beb0dd 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-cfe853bf0a26566bbd90ee4bcd1efdb18c555513
\ No newline at end of file
+ae5ffc4bacad995363d8e4447a8af7d477ceb673
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1
index 691474c..0838d72 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-164ea08eabdc778a4ae9d0b50a50ad7293ec78a5
\ No newline at end of file
+fd2473e852d7a594f2bc14b5692c683a2ed545b6
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1
index 8075c32..e0e53d43 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-5587bffbc01fa9be5ba40dfae76ed1a121dc1cb7
\ No newline at end of file
+c75f890f7753d19d247437334e691123aaf01997
\ No newline at end of file
diff --git a/ios/third_party/gtx/BUILD.gn b/ios/third_party/gtx/BUILD.gn
index b8101a0..a5e2e140 100644
--- a/ios/third_party/gtx/BUILD.gn
+++ b/ios/third_party/gtx/BUILD.gn
@@ -4,11 +4,68 @@
 
 import("//build/config/ios/ios_sdk.gni")
 import("//build/config/ios/rules.gni")
+import("//third_party/protobuf/proto_library.gni")
 
 config("config") {
   include_dirs = [ "src/Classes" ]
 }
 
+proto_library("proto") {
+  sources = [ "src/OOPClasses/Protos/gtx.proto" ]
+  cc_generator_options = "lite"
+  allow_optional = true
+}
+
+source_set("oop_classes") {
+  testonly = true
+  sources = [
+    "src/OOPClasses/accessibility_label_not_punctuated_check.cc",
+    "src/OOPClasses/accessibility_label_not_punctuated_check.h",
+    "src/OOPClasses/check.cc",
+    "src/OOPClasses/check.h",
+    "src/OOPClasses/check_result.cc",
+    "src/OOPClasses/check_result.h",
+    "src/OOPClasses/contrast_check.cc",
+    "src/OOPClasses/contrast_check.h",
+    "src/OOPClasses/contrast_swatch.cc",
+    "src/OOPClasses/contrast_swatch.h",
+    "src/OOPClasses/element_type.h",
+    "src/OOPClasses/error_message.cc",
+    "src/OOPClasses/error_message.h",
+    "src/OOPClasses/gtx_types.cc",
+    "src/OOPClasses/gtx_types.h",
+    "src/OOPClasses/image_color_utils.cc",
+    "src/OOPClasses/image_color_utils.h",
+    "src/OOPClasses/localized_strings_manager.cc",
+    "src/OOPClasses/localized_strings_manager.h",
+    "src/OOPClasses/minimum_tappable_area_check.cc",
+    "src/OOPClasses/minimum_tappable_area_check.h",
+    "src/OOPClasses/no_label_check.h",
+    "src/OOPClasses/parameters.cc",
+    "src/OOPClasses/parameters.h",
+    "src/OOPClasses/string_utils.cc",
+    "src/OOPClasses/string_utils.h",
+    "src/OOPClasses/toolkit.cc",
+    "src/OOPClasses/toolkit.h",
+    "src/OOPClasses/ui_element.cc",
+    "src/OOPClasses/ui_element.h",
+  ]
+
+  deps = [ ":proto" ]
+
+  # Don't use the gtx provided compiled protobuf header. Use an -include here to
+  # force that.
+  cflags_cc = [
+    "-include",
+    rebase_path(
+        "$root_out_dir/gen/ios/third_party/gtx/src/OOPClasses/Protos/gtx.pb.h",
+        root_build_dir),
+  ]
+
+  configs -= [ "//build/config/compiler:chromium_code" ]
+  configs += [ "//build/config/compiler:enable_arc" ]
+}
+
 ios_framework_bundle("gtx") {
   output_name = "GTXiLib"
   info_plist = "Info.plist"
@@ -22,55 +79,85 @@
     "src/Classes/GTXAnalyticsUtils.h",
     "src/Classes/GTXAnalyticsUtils.m",
     "src/Classes/GTXAssertions.h",
-    "src/Classes/GTXBlacklistBlock.h",
-    "src/Classes/GTXBlacklistBlock.m",
-    "src/Classes/GTXBlacklistFactory.h",
-    "src/Classes/GTXBlacklistFactory.m",
-    "src/Classes/GTXBlacklisting.h",
     "src/Classes/GTXCheckBlock.h",
     "src/Classes/GTXCheckBlock.m",
+    "src/Classes/GTXCheckResult.h",
+    "src/Classes/GTXCheckResult.m",
     "src/Classes/GTXChecking.h",
     "src/Classes/GTXChecksCollection.h",
     "src/Classes/GTXChecksCollection.m",
     "src/Classes/GTXCommon.h",
-    "src/Classes/GTXElementBlacklist.h",
-    "src/Classes/GTXElementBlacklist.m",
+    "src/Classes/GTXElementReference.h",
+    "src/Classes/GTXElementReference.m",
+    "src/Classes/GTXElementResultCollection.h",
+    "src/Classes/GTXElementResultCollection.m",
+    "src/Classes/GTXError.h",
+    "src/Classes/GTXError.m",
     "src/Classes/GTXErrorReporter.h",
     "src/Classes/GTXErrorReporter.m",
+    "src/Classes/GTXExcludeListBlock.h",
+    "src/Classes/GTXExcludeListBlock.m",
+    "src/Classes/GTXExcludeListFactory.h",
+    "src/Classes/GTXExcludeListFactory.m",
+    "src/Classes/GTXExcludeListing.h",
+    "src/Classes/GTXHierarchyResultCollection.h",
+    "src/Classes/GTXHierarchyResultCollection.m",
     "src/Classes/GTXImageAndColorUtils.h",
-    "src/Classes/GTXImageAndColorUtils.m",
+    "src/Classes/GTXImageAndColorUtils.mm",
+    "src/Classes/GTXImageRGBAData+GTXOOPAdditions.h",
+    "src/Classes/GTXImageRGBAData+GTXOOPAdditions.mm",
     "src/Classes/GTXImageRGBAData.h",
     "src/Classes/GTXImageRGBAData.m",
     "src/Classes/GTXLogging.h",
     "src/Classes/GTXPluginXCTestCase.h",
     "src/Classes/GTXPluginXCTestCase.m",
+    "src/Classes/GTXReport.h",
+    "src/Classes/GTXReport.m",
+    "src/Classes/GTXResult.h",
+    "src/Classes/GTXResult.m",
+    "src/Classes/GTXSwizzler.h",
+    "src/Classes/GTXSwizzler.m",
     "src/Classes/GTXTestCase.h",
     "src/Classes/GTXTestCase.m",
     "src/Classes/GTXTestEnvironment.h",
     "src/Classes/GTXTestEnvironment.m",
     "src/Classes/GTXTestSuite.h",
     "src/Classes/GTXTestSuite.m",
+    "src/Classes/GTXToolKit+GTXOOPAdditions.h",
     "src/Classes/GTXToolKit.h",
-    "src/Classes/GTXToolKit.m",
+    "src/Classes/GTXToolKit.mm",
+    "src/Classes/GTXXCUIApplicationProxy.h",
+    "src/Classes/GTXXCUIApplicationProxy.m",
+    "src/Classes/GTXXCUIElementProxy.h",
+    "src/Classes/GTXXCUIElementProxy.m",
+    "src/Classes/GTXXCUIElementQueryProxy.h",
+    "src/Classes/GTXXCUIElementQueryProxy.m",
     "src/Classes/GTXiLib.h",
     "src/Classes/GTXiLibCore.h",
     "src/Classes/GTXiLibCore.m",
     "src/Classes/NSError+GTXAdditions.h",
     "src/Classes/NSError+GTXAdditions.m",
+    "src/Classes/NSObject+GTXAdditions.h",
+    "src/Classes/NSObject+GTXAdditions.mm",
+    "src/Classes/NSString+GTXAdditions.h",
+    "src/Classes/NSString+GTXAdditions.mm",
+    "src/Classes/UIColor+GTXAdditions.h",
+    "src/Classes/UIColor+GTXAdditions.m",
+    "src/Classes/UIColor+GTXOOPAdditions.h",
+    "src/Classes/UIColor+GTXOOPAdditions.mm",
   ]
   public_headers = [
     "src/Classes/GTXAccessibilityTree.h",
     "src/Classes/GTXAnalytics.h",
     "src/Classes/GTXAnalyticsUtils.h",
     "src/Classes/GTXAssertions.h",
-    "src/Classes/GTXBlacklistBlock.h",
-    "src/Classes/GTXBlacklistFactory.h",
-    "src/Classes/GTXBlacklisting.h",
+    "src/Classes/GTXExcludeListBlock.h",
+    "src/Classes/GTXExcludeListFactory.h",
+    "src/Classes/GTXExcludeListing.h",
     "src/Classes/GTXCheckBlock.h",
     "src/Classes/GTXChecking.h",
     "src/Classes/GTXChecksCollection.h",
     "src/Classes/GTXCommon.h",
-    "src/Classes/GTXElementBlacklist.h",
     "src/Classes/GTXErrorReporter.h",
     "src/Classes/GTXImageAndColorUtils.h",
     "src/Classes/GTXImageRGBAData.h",
@@ -83,8 +170,42 @@
     "src/Classes/GTXiLib.h",
     "src/Classes/GTXiLibCore.h",
     "src/Classes/NSError+GTXAdditions.h",
+    "src/Classes/GTXImageRGBAData+GTXOOPAdditions.h",
+    "src/Classes/GTXError.h",
+    "src/Classes/GTXReport.h",
+    "src/Classes/UIColor+GTXAdditions.h",
+    "src/Classes/GTXCheckResult.h",
+    "src/Classes/GTXElementReference.h",
+    "src/Classes/GTXElementResultCollection.h",
+    "src/Classes/GTXHierarchyResultCollection.h",
+    "src/Classes/GTXResult.h",
+    "src/Classes/GTXSwizzler.h",
+    "src/Classes/GTXXCUIApplicationProxy.h",
+    "src/Classes/GTXXCUIElementProxy.h",
+    "src/Classes/GTXXCUIElementQueryProxy.h",
+    "src/Classes/NSObject+GTXAdditions.h",
+    "src/Classes/NSString+GTXAdditions.h",
+    "src/Classes/UIColor+GTXOOPAdditions.h",
+    "src/Classes/GTXToolKit+GTXOOPAdditions.h",
   ]
-  deps = [ "//build/config/ios:xctest" ]
+  deps = [
+    ":oop_classes",
+    "//build/config/ios:xctest",
+  ]
+
+  # Don't use the gtx provided compiled protobuf header. Use an -include here to
+  # force that.
+  cflags_objcc = [
+    "-include",
+    rebase_path(
+        "$root_out_dir/gen/ios/third_party/gtx/src/OOPClasses/Protos/gtx.pb.h",
+        root_build_dir),
+  ]
+
+  include_dirs = [
+    "src/OOPClasses",
+    "../../../third_party/protobuf/src",
+  ]
 
   frameworks = [
     "CoreGraphics.framework",
diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc
index 605001d..a76be9a0 100644
--- a/media/filters/ffmpeg_demuxer.cc
+++ b/media/filters/ffmpeg_demuxer.cc
@@ -119,28 +119,6 @@
   return start_time;
 }
 
-// Some videos just want to watch the world burn, with a height of 0; cap the
-// "infinite" aspect ratio resulting.
-const int kInfiniteRatio = 99999;
-
-// Common aspect ratios (multiplied by 100 and truncated) used for histogramming
-// video sizes.  These were taken on 20111103 from
-// http://wikipedia.org/wiki/Aspect_ratio_(image)#Previous_and_currently_used_aspect_ratios
-const int kCommonAspectRatios100[] = {
-    100, 115, 133, 137, 143, 150, 155, 160,  166,
-    175, 177, 185, 200, 210, 220, 221, 235,  237,
-    240, 255, 259, 266, 276, 293, 400, 1200, kInfiniteRatio,
-};
-
-template <class T>  // T has int width() & height() methods.
-static void UmaHistogramAspectRatio(const char* name, const T& size) {
-  UMA_HISTOGRAM_CUSTOM_ENUMERATION(
-      name,
-      // Intentionally use integer division to truncate the result.
-      size.height() ? (size.width() * 100) / size.height() : kInfiniteRatio,
-      base::CustomHistogram::ArrayToCustomEnumRanges(kCommonAspectRatios100));
-}
-
 // Record audio decoder config UMA stats corresponding to a src= playback.
 static void RecordAudioCodecStats(const AudioDecoderConfig& audio_config) {
   UMA_HISTOGRAM_ENUMERATION("Media.AudioCodec", audio_config.codec(),
diff --git a/net/third_party/nss/README.chromium b/net/third_party/nss/README.chromium
index 87108826..65586d8 100644
--- a/net/third_party/nss/README.chromium
+++ b/net/third_party/nss/README.chromium
@@ -4,14 +4,14 @@
 Security Critical: Yes
 License: MPL 2
 License File: LICENSE
-CPEPrefix: cpe:/a:mozilla:nss:3.23
 
-This directory includes a file derived from NSS's libssl, from the hg repo at:
+This directory includes a single function that was historically derived from
+NSS's libssl, from the hg repo at hg tag NSS_3_23_RTM:
   https://hg.mozilla.org/projects/nss
 
-The snapshot was updated to the hg tag: NSS_3_23_RTM
+The CPEPrefix field is intentionally omitted because this code no longer shares
+fate with NSS. This is derived code, not than a vendored dependency.
 
 Local Modifications:
-Files are forked from Mozilla's because of the heavy adaptations necessary.
-Differences are using Chromium libraries instead of Mozilla's, matching the
-Chromium style, and returning the certificate chain built.
+The function has been rewritten using Chromium's libraries and Chromium's
+certificate verifier.
diff --git a/pdf/BUILD.gn b/pdf/BUILD.gn
index 10453cab..3b7c6e2 100644
--- a/pdf/BUILD.gn
+++ b/pdf/BUILD.gn
@@ -254,6 +254,7 @@
       "accessibility_structs.cc",
       "accessibility_structs.h",
       "pdf_accessibility_action_handler.h",
+      "pdf_accessibility_data_handler.h",
     ]
 
     configs += [ ":strict" ]
diff --git a/pdf/pdf_accessibility_data_handler.h b/pdf/pdf_accessibility_data_handler.h
new file mode 100644
index 0000000..29bda10
--- /dev/null
+++ b/pdf/pdf_accessibility_data_handler.h
@@ -0,0 +1,36 @@
+// Copyright 2021 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 PDF_PDF_ACCESSIBILITY_DATA_HANDLER_H_
+#define PDF_PDF_ACCESSIBILITY_DATA_HANDLER_H_
+
+#include <vector>
+
+namespace chrome_pdf {
+
+struct AccessibilityCharInfo;
+struct AccessibilityDocInfo;
+struct AccessibilityPageInfo;
+struct AccessibilityPageObjects;
+struct AccessibilityTextRunInfo;
+struct AccessibilityViewportInfo;
+
+class PdfAccessibilityDataHandler {
+ public:
+  virtual ~PdfAccessibilityDataHandler() = default;
+
+  virtual void SetAccessibilityViewportInfo(
+      const AccessibilityViewportInfo& viewport_info) = 0;
+  virtual void SetAccessibilityDocInfo(
+      const AccessibilityDocInfo& doc_info) = 0;
+  virtual void SetAccessibilityPageInfo(
+      const AccessibilityPageInfo& page_info,
+      const std::vector<AccessibilityTextRunInfo>& text_runs,
+      const std::vector<AccessibilityCharInfo>& chars,
+      const AccessibilityPageObjects& page_objects) = 0;
+};
+
+}  // namespace chrome_pdf
+
+#endif  // PDF_PDF_ACCESSIBILITY_DATA_HANDLER_H_
diff --git a/pdf/pdf_view_plugin_base.cc b/pdf/pdf_view_plugin_base.cc
index c5814fbe..e795ae3 100644
--- a/pdf/pdf_view_plugin_base.cc
+++ b/pdf/pdf_view_plugin_base.cc
@@ -765,9 +765,8 @@
   const gfx::Rect new_plugin_rect =
       gfx::ScaleToEnclosingRectSafe(new_view_rect, new_device_scale);
 
-  if (new_device_scale == device_scale_ && new_plugin_rect == plugin_rect_) {
+  if (new_device_scale == device_scale_ && new_plugin_rect == plugin_rect_)
     return;
-  }
 
   const float old_device_scale = device_scale_;
   device_scale_ = new_device_scale;
@@ -786,10 +785,8 @@
   }
 
   // Skip updating the geometry if the new image data buffer is empty.
-  if (image_data_.drawsNothing()) {
-    DCHECK(plugin_rect_.IsEmpty());
+  if (image_data_.drawsNothing())
     return;
-  }
 
   OnGeometryChanged(zoom_, old_device_scale);
 }
diff --git a/pdf/pdf_view_web_plugin.cc b/pdf/pdf_view_web_plugin.cc
index 6417861..dcf9e1b 100644
--- a/pdf/pdf_view_web_plugin.cc
+++ b/pdf/pdf_view_web_plugin.cc
@@ -29,6 +29,7 @@
 #include "pdf/accessibility_structs.h"
 #include "pdf/mojom/pdf.mojom.h"
 #include "pdf/parsed_params.h"
+#include "pdf/pdf_accessibility_data_handler.h"
 #include "pdf/pdf_engine.h"
 #include "pdf/pdf_init.h"
 #include "pdf/pdfium/pdfium_engine.h"
@@ -225,15 +226,21 @@
 
 }  // namespace
 
+std::unique_ptr<PdfAccessibilityDataHandler>
+PdfViewWebPlugin::Client::CreateAccessibilityDataHandler(
+    PdfAccessibilityActionHandler* action_handler) {
+  return nullptr;
+}
+
 PdfViewWebPlugin::PdfViewWebPlugin(
     std::unique_ptr<Client> client,
     mojo::AssociatedRemote<pdf::mojom::PdfService> pdf_service_remote,
     const blink::WebPluginParams& params)
     : client_(std::move(client)),
       pdf_service_remote_(std::move(pdf_service_remote)),
-      initial_params_(params) {
-  DCHECK(client_);
-}
+      initial_params_(params),
+      pdf_accessibility_data_handler_(
+          client_->CreateAccessibilityDataHandler(this)) {}
 
 PdfViewWebPlugin::~PdfViewWebPlugin() = default;
 
@@ -707,6 +714,11 @@
     InvalidatePluginContainer();
 }
 
+void PdfViewWebPlugin::HandleAccessibilityAction(
+    const AccessibilityActionData& action_data) {
+  NOTIMPLEMENTED_LOG_ONCE();
+}
+
 base::WeakPtr<PdfViewPluginBase> PdfViewWebPlugin::GetWeakPtr() {
   return weak_factory_.GetWeakPtr();
 }
diff --git a/pdf/pdf_view_web_plugin.h b/pdf/pdf_view_web_plugin.h
index 9bcea06..41fb726 100644
--- a/pdf/pdf_view_web_plugin.h
+++ b/pdf/pdf_view_web_plugin.h
@@ -14,6 +14,7 @@
 #include "cc/paint/paint_image.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
 #include "pdf/mojom/pdf.mojom.h"
+#include "pdf/pdf_accessibility_action_handler.h"
 #include "pdf/pdf_view_plugin_base.h"
 #include "pdf/post_message_receiver.h"
 #include "pdf/post_message_sender.h"
@@ -46,12 +47,15 @@
 
 namespace chrome_pdf {
 
+class PdfAccessibilityDataHandler;
+
 // Skeleton for a `blink::WebPlugin` to replace `OutOfProcessInstance`.
 class PdfViewWebPlugin final : public PdfViewPluginBase,
                                public blink::WebPlugin,
                                public BlinkUrlLoader::Client,
                                public PostMessageReceiver::Client,
-                               public SkiaGraphics::Client {
+                               public SkiaGraphics::Client,
+                               public PdfAccessibilityActionHandler {
  public:
   class ContainerWrapper {
    public:
@@ -127,6 +131,12 @@
     // When you use this, you need to also update the rules for extracting known
     // actions in tools/metrics/actions/extract_actions.py.
     virtual void RecordComputedAction(const std::string& action) {}
+
+    // Creates an implementation of `PdfAccessibilityDataHandler` catered to the
+    // client.
+    virtual std::unique_ptr<PdfAccessibilityDataHandler>
+    CreateAccessibilityDataHandler(
+        PdfAccessibilityActionHandler* action_handler);
   };
 
   PdfViewWebPlugin(
@@ -221,6 +231,10 @@
   // SkiaGraphics::Client:
   void UpdateSnapshot(sk_sp<SkImage> snapshot) override;
 
+  // PdfAccessibilityActionHandler:
+  void HandleAccessibilityAction(
+      const AccessibilityActionData& action_data) override;
+
   // Initializes the plugin using the `container_wrapper` provided by tests.
   bool InitializeForTesting(
       std::unique_ptr<ContainerWrapper> container_wrapper);
@@ -310,6 +324,10 @@
 
   cc::PaintImage snapshot_;
 
+  // May be null in unit tests.
+  std::unique_ptr<PdfAccessibilityDataHandler> const
+      pdf_accessibility_data_handler_;
+
   // The metafile in which to save the printed output. Assigned a value only
   // between `PrintBegin()` and `PrintEnd()` calls.
   printing::MetafileSkia* printing_metafile_ = nullptr;
diff --git a/printing/mojom/BUILD.gn b/printing/mojom/BUILD.gn
index 1f09262..7a9c8c1 100644
--- a/printing/mojom/BUILD.gn
+++ b/printing/mojom/BUILD.gn
@@ -28,6 +28,10 @@
           mojom = "printing.mojom.PageRange"
           cpp = "::printing::PageRange"
         },
+        {
+          mojom = "printing.mojom.RequestedMedia"
+          cpp = "::printing::PrintSettings::RequestedMedia"
+        },
       ]
       traits_sources = [ "printing_context_mojom_traits.cc" ]
       traits_headers = [ "printing_context_mojom_traits.h" ]
diff --git a/printing/mojom/printing_context.mojom b/printing/mojom/printing_context.mojom
index 00f8fce..5e85173 100644
--- a/printing/mojom/printing_context.mojom
+++ b/printing/mojom/printing_context.mojom
@@ -33,3 +33,10 @@
   uint32 from;
   uint32 to;
 };
+
+// Corresponds to `printing::PrintSettings::RequestedMedia` in
+// printing/print_settings.h.
+struct RequestedMedia {
+  gfx.mojom.Size size_microns;
+  string vendor_id;
+};
diff --git a/printing/mojom/printing_context_mojom_traits.cc b/printing/mojom/printing_context_mojom_traits.cc
index 1040628..e6ba492 100644
--- a/printing/mojom/printing_context_mojom_traits.cc
+++ b/printing/mojom/printing_context_mojom_traits.cc
@@ -70,4 +70,13 @@
   return true;
 }
 
+// static
+bool StructTraits<printing::mojom::RequestedMediaDataView,
+                  printing::PrintSettings::RequestedMedia>::
+    Read(printing::mojom::RequestedMediaDataView data,
+         printing::PrintSettings::RequestedMedia* out) {
+  return data.ReadSizeMicrons(&out->size_microns) &&
+         data.ReadVendorId(&out->vendor_id);
+}
+
 }  // namespace mojo
diff --git a/printing/mojom/printing_context_mojom_traits.h b/printing/mojom/printing_context_mojom_traits.h
index 7a3cb51..8bcc06f 100644
--- a/printing/mojom/printing_context_mojom_traits.h
+++ b/printing/mojom/printing_context_mojom_traits.h
@@ -8,6 +8,7 @@
 #include "printing/mojom/printing_context.mojom-shared.h"
 #include "printing/page_range.h"
 #include "printing/page_setup.h"
+#include "printing/print_settings.h"
 #include "ui/gfx/geometry/size.h"
 
 namespace mojo {
@@ -68,6 +69,22 @@
                    printing::PageRange* out);
 };
 
+template <>
+struct StructTraits<printing::mojom::RequestedMediaDataView,
+                    printing::PrintSettings::RequestedMedia> {
+  static const gfx::Size& size_microns(
+      const printing::PrintSettings::RequestedMedia& r) {
+    return r.size_microns;
+  }
+  static const std::string& vendor_id(
+      const printing::PrintSettings::RequestedMedia& r) {
+    return r.vendor_id;
+  }
+
+  static bool Read(printing::mojom::RequestedMediaDataView data,
+                   printing::PrintSettings::RequestedMedia* out);
+};
+
 }  // namespace mojo
 
 #endif  // PRINTING_MOJOM_PRINTING_CONTEXT_MOJOM_TRAITS_H_
diff --git a/printing/mojom/printing_context_mojom_traits_unittest.cc b/printing/mojom/printing_context_mojom_traits_unittest.cc
index a187017..481ca7a 100644
--- a/printing/mojom/printing_context_mojom_traits_unittest.cc
+++ b/printing/mojom/printing_context_mojom_traits_unittest.cc
@@ -6,6 +6,7 @@
 #include "printing/mojom/printing_context.mojom.h"
 #include "printing/page_range.h"
 #include "printing/page_setup.h"
+#include "printing/print_settings.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/gfx/geometry/size.h"
 
@@ -45,6 +46,17 @@
                                         /*forced_margins=*/true,
                                         kPageSetupTextHeight);
 
+constexpr gfx::Size kRequestedMediaSize =
+    gfx::Size(/*width=*/25, /*height=*/75);
+const char kRequestedMediaVendorId[] = "iso-foo";
+
+PrintSettings::RequestedMedia GenerateSampleRequestedMedia() {
+  PrintSettings::RequestedMedia media;
+  media.size_microns = kRequestedMediaSize;
+  media.vendor_id = kRequestedMediaVendorId;
+  return media;
+}
+
 }  // namespace
 
 TEST(PrintingContextMojomTraitsTest, TestSerializeAndDeserializePageMargins) {
@@ -208,4 +220,30 @@
       mojo::test::SerializeAndDeserialize<mojom::PageRange>(input, output));
 }
 
+TEST(PrintingContextMojomTraitsTest,
+     TestSerializeAndDeserializeRequestedMedia) {
+  PrintSettings::RequestedMedia input = GenerateSampleRequestedMedia();
+  PrintSettings::RequestedMedia output;
+
+  EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::RequestedMedia>(
+      input, output));
+
+  EXPECT_EQ(kRequestedMediaSize, output.size_microns);
+  EXPECT_EQ(kRequestedMediaVendorId, output.vendor_id);
+}
+
+TEST(PrintingContextMojomTraitsTest,
+     TestSerializeAndDeserializeRequestedMediaEmpty) {
+  PrintSettings::RequestedMedia input;
+  PrintSettings::RequestedMedia output;
+
+  // The default is empty.
+  EXPECT_TRUE(input.IsDefault());
+
+  EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::RequestedMedia>(
+      input, output));
+
+  EXPECT_TRUE(output.IsDefault());
+}
+
 }  // namespace printing
diff --git a/sandbox/policy/mojom/sandbox.mojom b/sandbox/policy/mojom/sandbox.mojom
index 6dbd704..41986b0a 100644
--- a/sandbox/policy/mojom/sandbox.mojom
+++ b/sandbox/policy/mojom/sandbox.mojom
@@ -21,6 +21,9 @@
   // For instance, it allows dynamic code and wider access to APIs on Windows.
   kUtility,
 
+  // Composits PDF and XPS documents.
+  kPrintCompositor,
+
   // |kXrCompositing| hosts XR Device Service on Windows.
   [EnableIf=is_win]
   kXrCompositing,
diff --git a/sandbox/policy/sandbox_type.h b/sandbox/policy/sandbox_type.h
index 21584dba..6d4cfd1 100644
--- a/sandbox/policy/sandbox_type.h
+++ b/sandbox/policy/sandbox_type.h
@@ -123,6 +123,8 @@
 inline constexpr sandbox::policy::SandboxType MapToSandboxType(
     sandbox::mojom::Sandbox mojo_sandbox) {
   switch (mojo_sandbox) {
+    case sandbox::mojom::Sandbox::kPrintCompositor:
+      return sandbox::policy::SandboxType::kPrintCompositor;
     case sandbox::mojom::Sandbox::kService:
       return sandbox::policy::SandboxType::kService;
     case sandbox::mojom::Sandbox::kUtility:
diff --git a/sandbox/win/BUILD.gn b/sandbox/win/BUILD.gn
index a9edf86..8fb6c13 100644
--- a/sandbox/win/BUILD.gn
+++ b/sandbox/win/BUILD.gn
@@ -94,25 +94,21 @@
     "src/restricted_token_utils.cc",
     "src/restricted_token_utils.h",
     "src/sandbox.cc",
-    "src/sandbox.h",
     "src/sandbox_factory.h",
     "src/sandbox_globals.cc",
     "src/sandbox_nt_types.h",
     "src/sandbox_nt_util.cc",
     "src/sandbox_nt_util.h",
-    "src/sandbox_policy.h",
     "src/sandbox_policy_base.cc",
     "src/sandbox_policy_base.h",
     "src/sandbox_policy_diagnostic.cc",
     "src/sandbox_policy_diagnostic.h",
     "src/sandbox_rand.cc",
     "src/sandbox_rand.h",
-    "src/sandbox_types.h",
     "src/sandbox_utils.cc",
     "src/sandbox_utils.h",
     "src/security_capabilities.cc",
     "src/security_capabilities.h",
-    "src/security_level.h",
     "src/service_resolver.cc",
     "src/service_resolver.h",
     "src/sharedmem_ipc_client.cc",
@@ -184,7 +180,10 @@
 
   configs += [ "//build/config:precompiled_headers" ]
 
-  public_deps = [ "//base" ]
+  public_deps = [
+    ":common",
+    "//base",
+  ]
 
   deps = [
     ":maybe_set_appcontainer_acls",
@@ -218,7 +217,6 @@
     "src/sync_policy_test.h",
     "src/unload_dll_test.cc",
     "tests/common/controller.cc",
-    "tests/common/controller.h",
     "tests/common/test_utils.cc",
     "tests/common/test_utils.h",
     "tests/integration_tests/cfi_unittest.cc",
@@ -279,41 +277,38 @@
   }
 }
 
-loadable_module("sbox_integration_test_hijack_shim_dll") {
+shared_library("sbox_integration_test_hijack_shim_dll") {
   sources = [
     "tests/integration_tests/hijack_shim_dll.cc",
     "tests/integration_tests/hijack_shim_dll.def",
-    "tests/integration_tests/hijack_shim_dll.h",
   ]
 
   # Implicitly linking hijack_dll as loader import resolution required.
   deps = [
+    ":common",
     ":sbox_integration_test_hijack_dll",
     "//base",
   ]
 }
 
-loadable_module("sbox_integration_test_hooking_dll") {
-  sources = [
-    "tests/integration_tests/hooking_dll.cc",
-    "tests/integration_tests/hooking_dll.h",
-  ]
+shared_library("sbox_integration_test_hooking_dll") {
+  sources = [ "tests/integration_tests/hooking_dll.cc" ]
+
+  deps = [ ":common_test" ]
 }
 
 executable("sbox_integration_test_win_proc") {
-  sources = [
-    "tests/integration_tests/hooking_win_proc.cc",
-    "tests/integration_tests/hooking_win_proc.h",
-  ]
+  sources = [ "tests/integration_tests/hooking_win_proc.cc" ]
 
   configs -= [ "//build/config/win:console" ]
   configs += [ "//build/config/win:windowed" ]
+
+  deps = [ ":common_test" ]
 }
 
 test("sbox_validation_tests") {
   sources = [
     "tests/common/controller.cc",
-    "tests/common/controller.h",
     "tests/validation_tests/commands.cc",
     "tests/validation_tests/commands.h",
     "tests/validation_tests/suite.cc",
@@ -393,18 +388,39 @@
   ]
 
   defines = [ "POCDLL_EXPORTS" ]
+
+  deps = [ "//base" ]
 }
 
 # This fuzzer will only work on Windows, add fuzz targets which could run on
 # Linux to //sandbox/ directly.
 fuzzer_test("sandbox_policy_rule_fuzzer") {
-  sources = [
-    "fuzzer/fuzzer_types.h",
-    "fuzzer/sandbox_policy_rule_fuzzer.cc",
-  ]
+  sources = [ "fuzzer/sandbox_policy_rule_fuzzer.cc" ]
   dict = "fuzzer/sandbox_policy_rule.dict"
   deps = [
     "//base",
     "//sandbox",
   ]
 }
+
+source_set("common") {
+  sources = [
+    "fuzzer/fuzzer_types.h",
+    "src/sandbox.h",
+    "src/sandbox_policy.h",
+    "src/sandbox_types.h",
+    "src/security_level.h",
+    "tests/common/controller.h",
+    "tests/integration_tests/hijack_shim_dll.h",
+  ]
+
+  deps = [ "//base" ]
+  public_deps = [ ":common_test" ]
+}
+
+source_set("common_test") {
+  sources = [
+    "tests/integration_tests/hooking_dll.h",
+    "tests/integration_tests/hooking_win_proc.h",
+  ]
+}
diff --git a/sandbox/win/src/app_container_test.cc b/sandbox/win/src/app_container_test.cc
index d95bfe3..29d83ed 100644
--- a/sandbox/win/src/app_container_test.cc
+++ b/sandbox/win/src/app_container_test.cc
@@ -45,6 +45,17 @@
     L"S-1-15-2-3251537155-1984446955-2931258699-841473695-1938553385-"
     L"924012148-2839372144";
 
+// Some tests depend on a timeout happening (e.g. to detect if firewall blocks a
+// TCP/UDP connection from App Container). However, if process startup time is
+// too slow (which can happen on slower bots with a high degree of test
+// concurrency) then tiny_timeout is not long enough, so a slightly longer
+// timeout is used here to avoid having to retry flaky tests.
+DWORD test_timeout() {
+  const static DWORD kMillisTimeout =
+      TestTimeouts::tiny_timeout().InMilliseconds() * 2;
+  return kMillisTimeout;
+}
+
 std::wstring GenerateRandomPackageName() {
   return base::StringPrintf(L"%016lX%016lX", base::RandUint64(),
                             base::RandUint64());
@@ -398,8 +409,7 @@
   ASSERT_EQ(WSA_IO_PENDING, ::WSAGetLastError());
 
   // Wait to receive data from the child process. Only wait 1 second.
-  DWORD wait = WaitForSingleObject(
-      recv_event.Get(), TestTimeouts::tiny_timeout().InMilliseconds());
+  DWORD wait = WaitForSingleObject(recv_event.Get(), test_timeout());
 
   if (wait != WAIT_OBJECT_0)
     return;  // No connections. Expected for certain types of tests.
@@ -428,8 +438,7 @@
   // If not, the operation should be pending.
   ASSERT_EQ(WSA_IO_PENDING, ::WSAGetLastError());
   // Wait for send. Only wait 1 second.
-  wait = WaitForSingleObject(send_event.Get(),
-                             TestTimeouts::tiny_timeout().InMilliseconds());
+  wait = WaitForSingleObject(send_event.Get(), test_timeout());
   // Send should always succeed in a timely manner.
   EXPECT_EQ(wait, WAIT_OBJECT_0);
 }
@@ -673,8 +682,7 @@
   // Non-blocking socket, always returns SOCKET_ERROR and sets WSAlastError to
   // WSAEWOULDBLOCK.
   // Wait for the connect to succeed.
-  DWORD wait = WaitForSingleObject(
-      connect_event.Get(), TestTimeouts::tiny_timeout().InMilliseconds());
+  DWORD wait = WaitForSingleObject(connect_event.Get(), test_timeout());
 
   if (wait != WAIT_OBJECT_0)
     return SBOX_TEST_TIMED_OUT;
@@ -753,8 +761,7 @@
     // Winsock should return WSA_IO_PENDING and we wait on the event.
     if (WSAGetLastError() != WSA_IO_PENDING)
       return SBOX_TEST_THIRD_ERROR;
-    DWORD wait = WaitForSingleObject(
-        send_event.Get(), TestTimeouts::tiny_timeout().InMilliseconds());
+    DWORD wait = WaitForSingleObject(send_event.Get(), test_timeout());
 
     if (wait != WAIT_OBJECT_0)
       return SBOX_TEST_TIMED_OUT;
@@ -781,8 +788,7 @@
     if (WSAGetLastError() != WSA_IO_PENDING) {
       return SBOX_TEST_FOURTH_ERROR;
     }
-    DWORD wait = WaitForSingleObject(
-        read_event.Get(), TestTimeouts::tiny_timeout().InMilliseconds());
+    DWORD wait = WaitForSingleObject(read_event.Get(), test_timeout());
 
     if (wait != WAIT_OBJECT_0)
       return SBOX_TEST_TIMED_OUT;
diff --git a/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom b/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom
index 8d7cfe5..e33e5de 100644
--- a/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom
+++ b/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom
@@ -12,6 +12,8 @@
 import "services/viz/privileged/mojom/compositing/renderer_settings.mojom";
 import "services/viz/public/mojom/compositing/compositor_frame_sink.mojom";
 import "services/viz/public/mojom/compositing/copy_output_request.mojom";
+import "services/viz/public/mojom/compositing/frame_sink_bundle.mojom";
+import "services/viz/public/mojom/compositing/frame_sink_bundle_id.mojom";
 import "services/viz/public/mojom/compositing/frame_sink_id.mojom";
 import "services/viz/public/mojom/compositing/local_surface_id.mojom";
 import "services/viz/public/mojom/compositing/surface_id.mojom";
@@ -89,11 +91,28 @@
   // ExternalBeginFrameController.
   CreateRootCompositorFrameSink(RootCompositorFrameSinkParams params);
 
+  // Used by unprivileged clients to create a new FrameSinkBundle in the
+  // service. Each bundle is associated with a parent frame sink and can be
+  // used to communicate aggregate notifications and requests to and from any
+  // of the other frame sinks belonging to the same client.
+  CreateFrameSinkBundle(FrameSinkId parent_frame_sink_id,
+                        FrameSinkBundleId bundle_id,
+                        pending_receiver<FrameSinkBundle> receiver,
+                        pending_remote<FrameSinkBundleClient> client);
+
   // CreateCompositorFrameSink is used by unprivileged clients. This
   // CompositorFrameSink is not a root, and has to be parented by another
   // CompositorFrameSink in order to appear on screen.
+  //
+  // If `bundle_id` is provided, the new frame sink is added to the identified
+  // bundle. This means its client will receive OnBeginFrame() and various
+  // other notifications exclusively through the corresponding FrameSinkBundle,
+  // in batch with notifications for other bundled frame sinks; it also means
+  // the client can submit frames for this sink in batch with other bundled
+  // sinks using the FrameSinkBundle interface.
   CreateCompositorFrameSink(
       FrameSinkId frame_sink_id,
+      FrameSinkBundleId? bundle_id,
       pending_receiver<CompositorFrameSink> compositor_frame_sink,
       pending_remote<CompositorFrameSinkClient> compositor_frame_sink_client);
 
diff --git a/services/viz/public/cpp/compositing/frame_sink_bundle_id_mojom_traits.h b/services/viz/public/cpp/compositing/frame_sink_bundle_id_mojom_traits.h
new file mode 100644
index 0000000..fdc3b00
--- /dev/null
+++ b/services/viz/public/cpp/compositing/frame_sink_bundle_id_mojom_traits.h
@@ -0,0 +1,33 @@
+// Copyright 2021 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_VIZ_PUBLIC_CPP_COMPOSITING_FRAME_SINK_BUNDLE_ID_MOJOM_TRAITS_H_
+#define SERVICES_VIZ_PUBLIC_CPP_COMPOSITING_FRAME_SINK_BUNDLE_ID_MOJOM_TRAITS_H_
+
+#include "components/viz/common/surfaces/frame_sink_bundle_id.h"
+#include "services/viz/public/mojom/compositing/frame_sink_bundle_id.mojom-shared.h"
+
+namespace mojo {
+
+template <>
+struct StructTraits<viz::mojom::FrameSinkBundleIdDataView,
+                    viz::FrameSinkBundleId> {
+  static uint32_t client_id(const viz::FrameSinkBundleId& frame_sink_id) {
+    return frame_sink_id.client_id();
+  }
+
+  static uint32_t bundle_id(const viz::FrameSinkBundleId& frame_sink_id) {
+    return frame_sink_id.bundle_id();
+  }
+
+  static bool Read(viz::mojom::FrameSinkBundleIdDataView data,
+                   viz::FrameSinkBundleId* out) {
+    *out = viz::FrameSinkBundleId(data.client_id(), data.bundle_id());
+    return out->is_valid();
+  }
+};
+
+}  // namespace mojo
+
+#endif  // SERVICES_VIZ_PUBLIC_CPP_COMPOSITING_FRAME_SINK_BUNDLE_ID_MOJOM_TRAITS_H_
diff --git a/services/viz/public/mojom/BUILD.gn b/services/viz/public/mojom/BUILD.gn
index c16788a..355cd7e 100644
--- a/services/viz/public/mojom/BUILD.gn
+++ b/services/viz/public/mojom/BUILD.gn
@@ -22,6 +22,8 @@
     "compositing/filter_operation.mojom",
     "compositing/filter_operations.mojom",
     "compositing/frame_deadline.mojom",
+    "compositing/frame_sink_bundle.mojom",
+    "compositing/frame_sink_bundle_id.mojom",
     "compositing/frame_sink_id.mojom",
     "compositing/frame_timing_details.mojom",
     "compositing/local_surface_id.mojom",
@@ -70,6 +72,16 @@
     {
       types = [
         {
+          mojom = "viz.mojom.FrameSinkBundleId"
+          cpp = "::viz::FrameSinkBundleId"
+        },
+      ]
+      traits_headers = [ "//services/viz/public/cpp/compositing/frame_sink_bundle_id_mojom_traits.h" ]
+      traits_public_deps = [ "//components/viz/common" ]
+    },
+    {
+      types = [
+        {
           mojom = "viz.mojom.FrameSinkId"
           cpp = "::viz::FrameSinkId"
         },
diff --git a/services/viz/public/mojom/compositing/frame_sink_bundle.mojom b/services/viz/public/mojom/compositing/frame_sink_bundle.mojom
new file mode 100644
index 0000000..b39118b
--- /dev/null
+++ b/services/viz/public/mojom/compositing/frame_sink_bundle.mojom
@@ -0,0 +1,111 @@
+// Copyright 2021 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 viz.mojom;
+
+import "gpu/ipc/common/mailbox.mojom";
+import "mojo/public/mojom/base/shared_memory.mojom";
+import "services/viz/public/mojom/compositing/begin_frame_args.mojom";
+import "services/viz/public/mojom/compositing/compositor_frame.mojom";
+import "services/viz/public/mojom/compositing/compositor_frame_sink.mojom";
+import "services/viz/public/mojom/compositing/local_surface_id.mojom";
+import "services/viz/public/mojom/compositing/frame_timing_details.mojom";
+import "services/viz/public/mojom/compositing/returned_resource.mojom";
+import "services/viz/public/mojom/hit_test/hit_test_region_list.mojom";
+
+// Encapsulates either a single SubmitCompositorFrame call or a single
+// DidNotProduceFrameCall on behalf of a specific frame sink.
+struct BundledFrameSubmission {
+  // The ID of the frame sink which submitted this data. Scoped to the
+  // corresponding bundle's client ID.
+  uint32 sink_id;
+
+  // Data detailing the submission.
+  BundledFrameSubmissionData data;
+};
+
+// Represents data for a batched request equivalent to some CompositorFrameSink
+// message.
+union BundledFrameSubmissionData {
+  // Data for a SubmitCompositorFrame call.
+  BundledCompositorFrame frame;
+
+  // Data for a DidNotProduceFrame call.
+  BeginFrameAck did_not_produce_frame;
+
+  // Data for a DidDeleteSharedBitmap call.
+  gpu.mojom.Mailbox did_delete_shared_bitmap;
+};
+
+// SubmitCompositorFrame parameters, as bundled by BundledFrameSubmissionData
+// above. Each field here corresponds to a SubmitCompositorFrame argument.
+struct BundledCompositorFrame {
+  LocalSurfaceId local_surface_id;
+  CompositorFrame frame;
+  HitTestRegionList? hit_test_region_list;
+  uint64 submit_time;
+};
+
+// Controls an endpoint for aggregate communication regarding a collection of
+// related frame sinks.
+interface FrameSinkBundle {
+  // Corresponds to a single message of the same name on CompositorFrameSink
+  // for the identified sink.
+  InitializeCompositorFrameSinkType(
+      uint32 sink_id, CompositorFrameSinkType type);
+
+  // Corresponds to a single message of the same name on CompositorFrameSink
+  // for the identified sink.
+  SetNeedsBeginFrame(uint32 sink_id, bool needs_begin_frame);
+
+  // Corresponds to a series of batched requests equivalent to calls across
+  // arbitrary CompositorFrameSinks for frame sinks belonging to this bundle.
+  // Can be used to substantially reduce IPC traffic when many related sinks are
+  // submitting frames at the same time.
+  [UnlimitedSize]
+  Submit(array<BundledFrameSubmission> submissions);
+
+  // Corresponds to a single message of the same name on CompositorFrameSink
+  // for the identified sink.
+  DidAllocateSharedBitmap(
+      uint32 sink_id, mojo_base.mojom.ReadOnlySharedMemoryRegion region,
+      gpu.mojom.Mailbox id);
+};
+
+// A set of ReturnedResources belonging to a specific frame sink.
+struct BundledReturnedResources {
+  uint32 sink_id;
+  array<ReturnedResource> resources;
+};
+
+// Encapsulates details of an OnBeginFrame() notification for a specific frame
+// sink. A batchable equivalent to CompositorFrameSinkClient.OnBeginFrame().
+struct BeginFrameInfo {
+  uint32 sink_id;
+  BeginFrameArgs args;
+  map<uint32, FrameTimingDetails> details;
+};
+
+// Client interface for FrameSinkBundle. This behaves as an aggregated
+// CompositorFrameSinkClient interface.
+interface FrameSinkBundleClient {
+  // Flushes a series of batched notifications corresponding to any number of
+  // DidReceiveCompositorFrameAck, OnBeginFrame, and/or ReclaimResources
+  // messages for individual CompositorFrameSinkClients. Note that although
+  // both DidReceiveCompositorFrameAck and ReclaimResources send the same kind
+  // of data, we keep them separated since they have different meaning to the
+  // client.
+  FlushNotifications(array<BundledReturnedResources> acks,
+                     array<BeginFrameInfo> begin_frames,
+                     array<BundledReturnedResources> reclaimed_resources);
+
+  // Corresponds to a single message of the same name on
+  // CompositorFrameSinkClient for the identified sink.
+  OnBeginFramePausedChanged(uint32 sink_id, bool paused);
+
+  // Corresponds to a single message of the same name on
+  // CompositorFrameSinkClient for the identified sink.
+  OnCompositorFrameTransitionDirectiveProcessed(
+      uint32 sink_id, uint32 sequence_id);
+};
diff --git a/services/viz/public/mojom/compositing/frame_sink_bundle_id.mojom b/services/viz/public/mojom/compositing/frame_sink_bundle_id.mojom
new file mode 100644
index 0000000..27a27e6
--- /dev/null
+++ b/services/viz/public/mojom/compositing/frame_sink_bundle_id.mojom
@@ -0,0 +1,10 @@
+// Copyright 2021 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 viz.mojom;
+
+struct FrameSinkBundleId {
+  uint32 client_id;
+  uint32 bundle_id;
+};
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index 239233ae..b8a37a4 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -5964,21 +5964,21 @@
       },
       {
         "args": [
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v94.0.4599.3/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v94.0.4602.0/test_ash_chrome",
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_Lacros version skew testing ash 94.0.4599.3",
+        "name": "lacros_chrome_browsertests_Lacros version skew testing ash 94.0.4602.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v94.0.4599.3",
-              "revision": "version:94.0.4599.3"
+              "location": "lacros_version_skew_tests_v94.0.4602.0",
+              "revision": "version:94.0.4602.0"
             }
           ],
           "dimension_sets": [
@@ -6022,21 +6022,21 @@
       },
       {
         "args": [
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v94.0.4599.3/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v94.0.4602.0/test_ash_chrome",
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_run_in_series_Lacros version skew testing ash 94.0.4599.3",
+        "name": "lacros_chrome_browsertests_run_in_series_Lacros version skew testing ash 94.0.4602.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v94.0.4599.3",
-              "revision": "version:94.0.4599.3"
+              "location": "lacros_version_skew_tests_v94.0.4602.0",
+              "revision": "version:94.0.4602.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index ff500a25..5bf9725 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -83340,7 +83340,7 @@
       },
       {
         "args": [
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v94.0.4599.3/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v94.0.4602.0/test_ash_chrome",
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter"
         ],
         "isolate_profile_data": true,
@@ -83348,14 +83348,14 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_Lacros version skew testing ash 94.0.4599.3",
+        "name": "lacros_chrome_browsertests_Lacros version skew testing ash 94.0.4602.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v94.0.4599.3",
-              "revision": "version:94.0.4599.3"
+              "location": "lacros_version_skew_tests_v94.0.4602.0",
+              "revision": "version:94.0.4602.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -83390,7 +83390,7 @@
       },
       {
         "args": [
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v94.0.4599.3/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v94.0.4602.0/test_ash_chrome",
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter"
         ],
         "isolate_profile_data": true,
@@ -83398,14 +83398,14 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_run_in_series_Lacros version skew testing ash 94.0.4599.3",
+        "name": "lacros_chrome_browsertests_run_in_series_Lacros version skew testing ash 94.0.4602.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v94.0.4599.3",
-              "revision": "version:94.0.4599.3"
+              "location": "lacros_version_skew_tests_v94.0.4602.0",
+              "revision": "version:94.0.4602.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -84753,21 +84753,21 @@
       },
       {
         "args": [
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v94.0.4599.3/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v94.0.4602.0/test_ash_chrome",
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_Lacros version skew testing ash 94.0.4599.3",
+        "name": "lacros_chrome_browsertests_Lacros version skew testing ash 94.0.4602.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v94.0.4599.3",
-              "revision": "version:94.0.4599.3"
+              "location": "lacros_version_skew_tests_v94.0.4602.0",
+              "revision": "version:94.0.4602.0"
             }
           ],
           "dimension_sets": [
@@ -84813,21 +84813,21 @@
       },
       {
         "args": [
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v94.0.4599.3/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v94.0.4602.0/test_ash_chrome",
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_run_in_series_Lacros version skew testing ash 94.0.4599.3",
+        "name": "lacros_chrome_browsertests_run_in_series_Lacros version skew testing ash 94.0.4602.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v94.0.4599.3",
-              "revision": "version:94.0.4599.3"
+              "location": "lacros_version_skew_tests_v94.0.4602.0",
+              "revision": "version:94.0.4602.0"
             }
           ],
           "dimension_sets": [
@@ -86352,21 +86352,21 @@
       },
       {
         "args": [
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v94.0.4599.3/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v94.0.4602.0/test_ash_chrome",
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_Lacros version skew testing ash 94.0.4599.3",
+        "name": "lacros_chrome_browsertests_Lacros version skew testing ash 94.0.4602.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v94.0.4599.3",
-              "revision": "version:94.0.4599.3"
+              "location": "lacros_version_skew_tests_v94.0.4602.0",
+              "revision": "version:94.0.4602.0"
             }
           ],
           "dimension_sets": [
@@ -86412,21 +86412,21 @@
       },
       {
         "args": [
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v94.0.4599.3/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v94.0.4602.0/test_ash_chrome",
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_run_in_series_Lacros version skew testing ash 94.0.4599.3",
+        "name": "lacros_chrome_browsertests_run_in_series_Lacros version skew testing ash 94.0.4602.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v94.0.4599.3",
-              "revision": "version:94.0.4599.3"
+              "location": "lacros_version_skew_tests_v94.0.4602.0",
+              "revision": "version:94.0.4602.0"
             }
           ],
           "dimension_sets": [
@@ -87139,21 +87139,21 @@
       },
       {
         "args": [
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v94.0.4599.3/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v94.0.4602.0/test_ash_chrome",
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_Lacros version skew testing ash 94.0.4599.3",
+        "name": "lacros_chrome_browsertests_Lacros version skew testing ash 94.0.4602.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v94.0.4599.3",
-              "revision": "version:94.0.4599.3"
+              "location": "lacros_version_skew_tests_v94.0.4602.0",
+              "revision": "version:94.0.4602.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -87187,21 +87187,21 @@
       },
       {
         "args": [
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v94.0.4599.3/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v94.0.4602.0/test_ash_chrome",
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_run_in_series_Lacros version skew testing ash 94.0.4599.3",
+        "name": "lacros_chrome_browsertests_run_in_series_Lacros version skew testing ash 94.0.4602.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v94.0.4599.3",
-              "revision": "version:94.0.4599.3"
+              "location": "lacros_version_skew_tests_v94.0.4602.0",
+              "revision": "version:94.0.4602.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 5aab0a1..111e0b4 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -46,16 +46,16 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v94.0.4599.3/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v94.0.4602.0/test_ash_chrome',
       '--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter',
     ],
-    'identifier': 'Lacros version skew testing ash 94.0.4599.3',
+    'identifier': 'Lacros version skew testing ash 94.0.4602.0',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v94.0.4599.3',
-          'revision': 'version:94.0.4599.3',
+          'location': 'lacros_version_skew_tests_v94.0.4602.0',
+          'revision': 'version:94.0.4602.0',
         },
       ],
     },
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 794b7b13..611ff45 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -5950,6 +5950,22 @@
             ]
         }
     ],
+    "PageInfoHistory": [
+        {
+            "platforms": [
+                "android",
+                "android_weblayer"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "PageInfoHistory"
+                    ]
+                }
+            ]
+        }
+    ],
     "PageInfoIPH": [
         {
             "platforms": [
diff --git a/third_party/blink/public/mojom/file_system_access/file_system_access_file_delegate_host.mojom b/third_party/blink/public/mojom/file_system_access/file_system_access_file_delegate_host.mojom
index 10d4526..7f8a5b9 100644
--- a/third_party/blink/public/mojom/file_system_access/file_system_access_file_delegate_host.mojom
+++ b/third_party/blink/public/mojom/file_system_access/file_system_access_file_delegate_host.mojom
@@ -29,4 +29,9 @@
   [Sync]
   Write(uint64 offset, handle<data_pipe_consumer> data) =>
       (mojo_base.mojom.FileError error, int32 bytes_written);
+
+  // Returns the `length` of the associated file and a file error, which is
+  // `base::File::Error::FILE_OK` if the operation completed successfully.
+  // The returned length of the file will never be more than the max int64.
+  GetLength() => (mojo_base.mojom.FileError error, uint64 length);
 };
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc b/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc
index bcc26d52..95edbfc 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc
+++ b/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc
@@ -485,14 +485,6 @@
       execution_context);
 }
 
-static bool WasmSimdEnabledCallback(v8::Local<v8::Context> context) {
-  ExecutionContext* execution_context = ToExecutionContext(context);
-  if (!execution_context)
-    return false;
-
-  return RuntimeEnabledFeatures::WebAssemblySimdEnabled(execution_context);
-}
-
 v8::Local<v8::Value> NewRangeException(v8::Isolate* isolate,
                                        const char* message) {
   return v8::Exception::RangeError(
@@ -654,7 +646,6 @@
   isolate->SetSharedArrayBufferConstructorEnabledCallback(
       SharedArrayBufferConstructorEnabledCallback);
   isolate->SetWasmExceptionsEnabledCallback(WasmExceptionsEnabledCallback);
-  isolate->SetWasmSimdEnabledCallback(WasmSimdEnabledCallback);
   isolate->SetHostImportModuleDynamicallyCallback(HostImportModuleDynamically);
   isolate->SetHostInitializeImportMetaObjectCallback(
       HostGetImportMetaProperties);
diff --git a/third_party/blink/renderer/bindings/generated_in_modules.gni b/third_party/blink/renderer/bindings/generated_in_modules.gni
index bd379407..f39c34e 100644
--- a/third_party/blink/renderer/bindings/generated_in_modules.gni
+++ b/third_party/blink/renderer/bindings/generated_in_modules.gni
@@ -1094,8 +1094,8 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_gpu_front_face.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_gpu_index_format.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_gpu_index_format.h",
-  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_gpu_input_step_mode.cc",
-  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_gpu_input_step_mode.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_gpu_vertex_step_mode.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_gpu_vertex_step_mode.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_gpu_load_op.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_gpu_load_op.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_gpu_pipeline_statistic_name.cc",
diff --git a/third_party/blink/renderer/core/app_history/app_history.cc b/third_party/blink/renderer/core/app_history/app_history.cc
index d7c7e58..e0a91a42 100644
--- a/third_party/blink/renderer/core/app_history/app_history.cc
+++ b/third_party/blink/renderer/core/app_history/app_history.cc
@@ -395,7 +395,7 @@
     return ScriptPromise::CastUndefined(script_state);
 
   if (AppHistoryApiNavigation* previous_navigation =
-          ongoing_traversals_.at(key)) {
+          ongoing_traversals_.DeprecatedAtOrEmptyValue(key)) {
     return previous_navigation->returned_promise->Promise();
   }
 
@@ -403,7 +403,8 @@
       MakeGarbageCollected<AppHistoryApiNavigation>(script_state, options, key);
   ongoing_traversals_.insert(key, ongoing_navigation);
 
-  AppHistoryEntry* destination = entries_[keys_to_indices_.at(key)];
+  AppHistoryEntry* destination =
+      entries_[keys_to_indices_.DeprecatedAtOrEmptyValue(key)];
 
   // TODO(japhet): We will fire the navigate event for same-document navigations
   // at commit time, but not cross-document. This should probably move to a more
@@ -513,10 +514,12 @@
   const KURL& current_url = GetSupplementable()->Url();
 
   AppHistoryApiNavigation* navigation = nullptr;
-  if (destination_item && !destination_item->GetAppHistoryKey().IsNull())
-    navigation = ongoing_traversals_.at(destination_item->GetAppHistoryKey());
-  else
+  if (destination_item && !destination_item->GetAppHistoryKey().IsNull()) {
+    navigation = ongoing_traversals_.DeprecatedAtOrEmptyValue(
+        destination_item->GetAppHistoryKey());
+  } else {
     navigation = ongoing_non_traversal_navigation_;
+  }
 
   auto* init = AppHistoryNavigateEventInit::Create();
   init->setNavigationType(DetermineNavigationType(type));
@@ -532,8 +535,9 @@
           destination_state);
   if (type == WebFrameLoadType::kBackForward) {
     const String& key = destination_item->GetAppHistoryKey();
-    destination->SetTraverseProperties(key, destination_item->GetAppHistoryId(),
-                                       keys_to_indices_.at(key));
+    destination->SetTraverseProperties(
+        key, destination_item->GetAppHistoryId(),
+        keys_to_indices_.DeprecatedAtOrEmptyValue(key));
   }
   init->setDestination(destination);
 
diff --git a/third_party/blink/renderer/core/css/css_paint_value.cc b/third_party/blink/renderer/core/css/css_paint_value.cc
index 20d6997..42d810d7 100644
--- a/third_party/blink/renderer/core/css/css_paint_value.cc
+++ b/third_party/blink/renderer/core/css/css_paint_value.cc
@@ -245,7 +245,8 @@
 
 bool CSSPaintValue::KnownToBeOpaque(const Document& document,
                                     const ComputedStyle&) const {
-  const CSSPaintImageGenerator* generator = generators_.at(&document);
+  const CSSPaintImageGenerator* generator =
+      generators_.DeprecatedAtOrEmptyValue(&document);
   return generator && !generator->HasAlpha();
 }
 
diff --git a/third_party/blink/renderer/core/css/element_rule_collector.cc b/third_party/blink/renderer/core/css/element_rule_collector.cc
index 007039f2..f55d2c1 100644
--- a/third_party/blink/renderer/core/css/element_rule_collector.cc
+++ b/third_party/blink/renderer/core/css/element_rule_collector.cc
@@ -275,7 +275,7 @@
         match_request.rule_set->IdRules(element.IdForStyleResolution()),
         match_request, checker);
   }
-  if (element.IsStyledElement() && element.HasClass()) {
+  if (element.HasClass()) {
     for (wtf_size_t i = 0; i < element.ClassNames().size(); ++i) {
       CollectMatchingRulesForList(
           match_request.rule_set->ClassRules(element.ClassNames()[i]),
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index d49d48d..5f6bcae8 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -2432,10 +2432,7 @@
 
 void Element::UpdateClassList(const AtomicString& old_class_string,
                               const AtomicString& new_class_string) {
-  if (!HasRareData())
-    return;
-  if (DOMTokenList* class_list = GetElementRareData()->GetClassList())
-    class_list->DidUpdateAttributeValue(old_class_string, new_class_string);
+  classList().DidUpdateAttributeValue(old_class_string, new_class_string);
 }
 
 // Returns true if the given attribute is an event handler.
diff --git a/third_party/blink/renderer/core/frame/frame_overlay_test.cc b/third_party/blink/renderer/core/frame/frame_overlay_test.cc
index e8c4618..f32836f 100644
--- a/third_party/blink/renderer/core/frame/frame_overlay_test.cc
+++ b/third_party/blink/renderer/core/frame/frame_overlay_test.cc
@@ -115,9 +115,10 @@
     EXPECT_EQ(PropertyTreeState::Root(),
               graphics_layer->GetPropertyTreeState());
     Vector<PreCompositedLayerInfo> pre_composited_layers;
-    graphics_layer->PaintRecursively(builder.Context(), pre_composited_layers);
+    PaintController::CycleScope cycle_scope;
+    graphics_layer->PaintRecursively(builder.Context(), pre_composited_layers,
+                                     cycle_scope);
     ASSERT_EQ(1u, pre_composited_layers.size());
-    graphics_layer->GetPaintController().FinishCycle();
     SkiaPaintCanvas(&canvas).drawPicture(
         graphics_layer->GetPaintController().GetPaintArtifact().GetPaintRecord(
             PropertyTreeState::Root()));
@@ -171,9 +172,10 @@
     EXPECT_FALSE(graphics_layer->IsHitTestable());
     EXPECT_EQ(state, graphics_layer->GetPropertyTreeState());
     Vector<PreCompositedLayerInfo> pre_composited_layers;
-    graphics_layer->PaintRecursively(context, pre_composited_layers);
+    PaintController::CycleScope cycle_scope;
+    graphics_layer->PaintRecursively(context, pre_composited_layers,
+                                     cycle_scope);
     check_paint_results(graphics_layer->GetPaintController());
-    graphics_layer->GetPaintController().FinishCycle();
   }
 }
 
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 2d234ab..f307f8dc 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -2739,17 +2739,22 @@
   if (AnyFrameIsPrintingOrPaintingPreview())
     return;
 
-  bool repainted = PaintTree(benchmark_mode);
+  bool needed_update;
+  {
+    PaintController::CycleScope cycle_scope;
+    bool repainted = PaintTree(benchmark_mode, cycle_scope);
 
-  if (paint_artifact_compositor_ &&
-      benchmark_mode ==
-          PaintBenchmarkMode::kForcePaintArtifactCompositorUpdate) {
-    paint_artifact_compositor_->SetNeedsUpdate();
+    if (paint_artifact_compositor_ &&
+        benchmark_mode ==
+            PaintBenchmarkMode::kForcePaintArtifactCompositorUpdate) {
+      paint_artifact_compositor_->SetNeedsUpdate();
+    }
+
+    needed_update = !paint_artifact_compositor_ ||
+                    paint_artifact_compositor_->NeedsUpdate();
+    PushPaintArtifactToCompositor(repainted);
   }
 
-  bool needed_update =
-      !paint_artifact_compositor_ || paint_artifact_compositor_->NeedsUpdate();
-  PushPaintArtifactToCompositor(repainted);
   size_t total_animations_count = 0;
   ForAllNonThrottledLocalFrameViews(
       [this, &needed_update,
@@ -2792,22 +2797,6 @@
     }
   }
 
-  // Notify the controller that the artifact has been pushed and some
-  // lifecycle state can be freed (such as raster invalidations).
-  if (paint_controller_)
-    paint_controller_->FinishCycle();
-
-  if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
-    auto* root = GetLayoutView()->Compositor()->PaintRootGraphicsLayer();
-    if (root) {
-      ForAllPaintingGraphicsLayers(*root, [](GraphicsLayer& layer) {
-        // Notify the paint controller that the artifact has been pushed and
-        // some lifecycle state can be freed (such as raster invalidations).
-        layer.GetPaintController().FinishCycle();
-      });
-    }
-  }
-
   if (paint_artifact_compositor_)
     paint_artifact_compositor_->ClearPropertyTreeChangedState();
 
@@ -2873,7 +2862,8 @@
   });
 }
 
-bool LocalFrameView::PaintTree(PaintBenchmarkMode benchmark_mode) {
+bool LocalFrameView::PaintTree(PaintBenchmarkMode benchmark_mode,
+                               PaintController::CycleScope& cycle_scope) {
   SCOPED_UMA_AND_UKM_TIMER(EnsureUkmAggregator(),
                            LocalFrameUkmAggregator::kPaint);
 
@@ -2921,7 +2911,11 @@
   bool needs_clear_repaint_flags = false;
 
   if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
+    // TODO(paint-dev): We should be able to get rid of AddController entirely
+    // after non-CAP code is removed. The call to EnsurePaintController() will
+    // need to be moved up the call stack.
     EnsurePaintController();
+    cycle_scope.AddController(*paint_controller_);
 
     PaintChunkSubset previous_chunks(
         paint_controller_->GetPaintArtifactShared());
@@ -2932,7 +2926,6 @@
     if (paint_controller_->ShouldForcePaintForBenchmark() ||
         GetLayoutView()->Layer()->SelfOrDescendantNeedsRepaint() ||
         visual_viewport_or_overlay_needs_repaint_) {
-      paint_controller_->ReserveCapacity();
       GraphicsContext graphics_context(*paint_controller_);
 
       if (Settings* settings = frame_->GetSettings()) {
@@ -2993,6 +2986,8 @@
     // parented into the main frame tree, or when the LocalFrameView is the main
     // frame view of a page overlay. The page overlay is in the layer tree of
     // the host page and will be painted during painting of the host page.
+    // Note that paint_controller_ is not added to cycle_scope, because it is
+    // transient and may be deleted before cycle_scope.
     paint_controller_ =
         std::make_unique<PaintController>(PaintController::kTransient);
     pre_composited_layers_.clear();
@@ -3000,8 +2995,9 @@
 
     if (GraphicsLayer* root =
             layout_view->Compositor()->PaintRootGraphicsLayer()) {
-      repainted = root->PaintRecursively(
-          graphics_context, pre_composited_layers_, benchmark_mode);
+      repainted =
+          root->PaintRecursively(graphics_context, pre_composited_layers_,
+                                 cycle_scope, benchmark_mode);
       if (visual_viewport_or_overlay_needs_repaint_ &&
           paint_artifact_compositor_)
         paint_artifact_compositor_->SetNeedsUpdate();
@@ -4126,16 +4122,15 @@
   if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
     PaintController& paint_controller = EnsurePaintController();
     if (GetLayoutView()->Layer()->SelfOrDescendantNeedsRepaint()) {
+      PaintController::CycleScope cycle_scope(paint_controller);
       GraphicsContext graphics_context(paint_controller);
       Paint(graphics_context, kGlobalPaintNormalPhase, cull_rect);
       paint_controller.CommitNewDisplayItems();
     }
-    paint_controller.FinishCycle();
   } else {
     GraphicsLayer* graphics_layer =
         GetLayoutView()->Layer()->GraphicsLayerBacking();
     graphics_layer->PaintForTesting(cull_rect.Rect());
-    graphics_layer->GetPaintController().FinishCycle();
   }
   Lifecycle().AdvanceTo(DocumentLifecycle::kPaintClean);
 }
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.h b/third_party/blink/renderer/core/frame/local_frame_view.h
index cdba46e..92b108c6 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.h
+++ b/third_party/blink/renderer/core/frame/local_frame_view.h
@@ -50,6 +50,7 @@
 #include "third_party/blink/renderer/platform/graphics/color.h"
 #include "third_party/blink/renderer/platform/graphics/compositor_element_id.h"
 #include "third_party/blink/renderer/platform/graphics/paint/cull_rect.h"
+#include "third_party/blink/renderer/platform/graphics/paint/paint_controller.h"
 #include "third_party/blink/renderer/platform/graphics/paint_invalidation_reason.h"
 #include "third_party/blink/renderer/platform/graphics/subtree_paint_property_update_reason.h"
 #include "third_party/blink/renderer/platform/timer.h"
@@ -862,7 +863,7 @@
   void PerformLayout();
   void PerformPostLayoutTasks(bool view_size_changed);
 
-  bool PaintTree(PaintBenchmarkMode);
+  bool PaintTree(PaintBenchmarkMode, PaintController::CycleScope&);
   void PushPaintArtifactToCompositor(bool repainted);
 
   void ClearLayoutSubtreeRootsAndMarkContainingBlocks();
diff --git a/third_party/blink/renderer/core/html/custom/custom_element_reaction_stack.cc b/third_party/blink/renderer/core/html/custom/custom_element_reaction_stack.cc
index a9a90a1..aa05b3e 100644
--- a/third_party/blink/renderer/core/html/custom/custom_element_reaction_stack.cc
+++ b/third_party/blink/renderer/core/html/custom/custom_element_reaction_stack.cc
@@ -100,8 +100,10 @@
 }
 
 void CustomElementReactionStack::ClearQueue(Element& element) {
-  if (CustomElementReactionQueue* reactions = map_.at(&element))
+  if (CustomElementReactionQueue* reactions =
+          map_.DeprecatedAtOrEmptyValue(&element)) {
     reactions->Clear();
+  }
 }
 
 void CustomElementReactionStack::InvokeBackupQueue() {
diff --git a/third_party/blink/renderer/core/html/custom/custom_element_registry.cc b/third_party/blink/renderer/core/html/custom/custom_element_registry.cc
index a5cfbd6..68a19e9 100644
--- a/third_party/blink/renderer/core/html/custom/custom_element_registry.cc
+++ b/third_party/blink/renderer/core/html/custom/custom_element_registry.cc
@@ -298,7 +298,8 @@
   CustomElementDefinition* definition = DefinitionForName(name);
   if (definition)
     return ScriptPromise::CastUndefined(script_state);
-  ScriptPromiseResolver* resolver = when_defined_promise_map_.at(name);
+  ScriptPromiseResolver* resolver =
+      when_defined_promise_map_.DeprecatedAtOrEmptyValue(name);
   if (resolver)
     return resolver->Promise();
   auto* new_resolver =
diff --git a/third_party/blink/renderer/core/html/custom/element_internals.cc b/third_party/blink/renderer/core/html/custom/element_internals.cc
index 3e940b3..30f2cf3 100644
--- a/third_party/blink/renderer/core/html/custom/element_internals.cc
+++ b/third_party/blink/renderer/core/html/custom/element_internals.cc
@@ -234,7 +234,7 @@
 
 const AtomicString& ElementInternals::FastGetAttribute(
     const QualifiedName& attribute) const {
-  return accessibility_semantics_map_.at(attribute);
+  return accessibility_semantics_map_.DeprecatedAtOrEmptyValue(attribute);
 }
 
 const HashMap<QualifiedName, AtomicString>& ElementInternals::GetAttributes()
diff --git a/third_party/blink/renderer/core/html/html_element.cc b/third_party/blink/renderer/core/html/html_element.cc
index 18ba8c64..46185c3 100644
--- a/third_party/blink/renderer/core/html/html_element.cc
+++ b/third_party/blink/renderer/core/html/html_element.cc
@@ -909,14 +909,6 @@
   else
     new_child = Text::Create(GetDocument(), text);
 
-  // textToFragment might cause mutation events.
-  if (!parentNode()) {
-    // TODO(crbug.com/1206014) We can likely remove this entire if() block.
-    NOTREACHED();
-    exception_state.ThrowDOMException(DOMExceptionCode::kHierarchyRequestError,
-                                      "The element has no parent.");
-  }
-
   if (exception_state.HadException())
     return;
 
diff --git a/third_party/blink/renderer/core/html/track/vtt/vtt_cue.cc b/third_party/blink/renderer/core/html/track/vtt/vtt_cue.cc
index 40c7bf3a..b08e214 100644
--- a/third_party/blink/renderer/core/html/track/vtt/vtt_cue.cc
+++ b/third_party/blink/renderer/core/html/track/vtt/vtt_cue.cc
@@ -1124,8 +1124,10 @@
         break;
       }
       case kRegionId:
-        if (region_map)
-          region_ = region_map->at(input.ExtractString(value_run));
+        if (region_map) {
+          region_ = region_map->DeprecatedAtOrEmptyValue(
+              input.ExtractString(value_run));
+        }
         break;
       case kNone:
         break;
diff --git a/third_party/blink/renderer/core/input/pointer_event_manager.cc b/third_party/blink/renderer/core/input/pointer_event_manager.cc
index 9541a87..af3453d 100644
--- a/third_party/blink/renderer/core/input/pointer_event_manager.cc
+++ b/third_party/blink/renderer/core/input/pointer_event_manager.cc
@@ -267,7 +267,8 @@
                                                  Element* target) {
   if (element_under_pointer_.Contains(pointer_event->pointerId())) {
     EventTargetAttributes* node =
-        element_under_pointer_.at(pointer_event->pointerId());
+        element_under_pointer_.DeprecatedAtOrEmptyValue(
+            pointer_event->pointerId());
     if (!target) {
       element_under_pointer_.erase(pointer_event->pointerId());
     } else if (target != node->target) {
@@ -320,7 +321,9 @@
     // target before.
     Element* target = nullptr;
     if (element_under_pointer_.Contains(pointer_event->pointerId())) {
-      target = element_under_pointer_.at(pointer_event->pointerId())->target;
+      target = element_under_pointer_
+                   .DeprecatedAtOrEmptyValue(pointer_event->pointerId())
+                   ->target;
     }
 
     DispatchPointerEvent(
@@ -450,7 +453,7 @@
     // pointer is captured otherwise it would have gone to the |if| block
     // and perform a hit-test.
     pointer_event_target.target_element =
-        pending_pointer_capture_target_.at(pointer_id);
+        pending_pointer_capture_target_.DeprecatedAtOrEmptyValue(pointer_id);
     pointer_event_target.target_frame =
         pointer_event_target.target_element->GetDocument().GetFrame();
   }
@@ -1003,7 +1006,7 @@
 
 Element* PointerEventManager::GetCapturingElement(PointerId pointer_id) {
   if (pointer_capture_target_.Contains(pointer_id))
-    return pointer_capture_target_.at(pointer_id);
+    return pointer_capture_target_.DeprecatedAtOrEmptyValue(pointer_id);
   return nullptr;
 }
 
@@ -1053,7 +1056,8 @@
   // but |m_pendingPointerCaptureTarget| indicated the element that gets the
   // very next pointer event. They will be the same if there was no change in
   // capturing of a particular |pointerId|. See crbug.com/614481.
-  if (pending_pointer_capture_target_.at(pointer_id) == target) {
+  if (pending_pointer_capture_target_.DeprecatedAtOrEmptyValue(pointer_id) ==
+      target) {
     ReleasePointerCapture(pointer_id);
     return true;
   }
@@ -1075,8 +1079,10 @@
 }
 
 Element* PointerEventManager::GetMouseCaptureTarget() {
-  if (pending_pointer_capture_target_.Contains(PointerEventFactory::kMouseId))
-    return pending_pointer_capture_target_.at(PointerEventFactory::kMouseId);
+  if (pending_pointer_capture_target_.Contains(PointerEventFactory::kMouseId)) {
+    return pending_pointer_capture_target_.DeprecatedAtOrEmptyValue(
+        PointerEventFactory::kMouseId);
+  }
   return nullptr;
 }
 
@@ -1092,7 +1098,7 @@
                                                    LocalFrame* frame) const {
   Element* last_element_receiving_event =
       element_under_pointer_.Contains(pointer_id)
-          ? element_under_pointer_.at(pointer_id)->target
+          ? element_under_pointer_.DeprecatedAtOrEmptyValue(pointer_id)->target
           : nullptr;
   return last_element_receiving_event &&
          last_element_receiving_event->GetDocument().GetFrame() == frame;
@@ -1123,9 +1129,10 @@
     Element* new_target) {
   PointerId pointer_id =
       pointer_event_factory_.GetPointerEventId(web_pointer_event);
-  Element* last_target = element_under_pointer_.Contains(pointer_id)
-                             ? element_under_pointer_.at(pointer_id)->target
-                             : nullptr;
+  Element* last_target =
+      element_under_pointer_.Contains(pointer_id)
+          ? element_under_pointer_.DeprecatedAtOrEmptyValue(pointer_id)->target
+          : nullptr;
   if (!new_target) {
     pointer_event_factory_.RemoveLastPosition(pointer_id);
   } else if (!last_target || new_target->GetDocument().GetFrame() !=
diff --git a/third_party/blink/renderer/core/scroll/scrollbar_theme_mac.mm b/third_party/blink/renderer/core/scroll/scrollbar_theme_mac.mm
index 42caecb..9c48398 100644
--- a/third_party/blink/renderer/core/scroll/scrollbar_theme_mac.mm
+++ b/third_party/blink/renderer/core/scroll/scrollbar_theme_mac.mm
@@ -224,8 +224,8 @@
 
 ScrollbarPainter ScrollbarThemeMac::PainterForScrollbar(
     const Scrollbar& scrollbar) const {
-  return
-      [GetScrollbarPainterMap().at(const_cast<Scrollbar*>(&scrollbar)) painter];
+  return [GetScrollbarPainterMap().DeprecatedAtOrEmptyValue(
+      const_cast<Scrollbar*>(&scrollbar)) painter];
 }
 
 WebThemeEngine::ExtraParams GetPaintParams(const Scrollbar& scrollbar,
@@ -356,7 +356,8 @@
   // and because the ScrollAnimator doesn't animate correctly without them.
   {
     base::scoped_nsobject<BlinkScrollbarObserver> observer(
-        GetScrollbarPainterMap().at(const_cast<Scrollbar*>(&scrollbar)),
+        GetScrollbarPainterMap().DeprecatedAtOrEmptyValue(
+            const_cast<Scrollbar*>(&scrollbar)),
         base::scoped_policy::RETAIN);
     ScrollbarPainter scrollbar_painter = [observer painter];
     [scrollbar_painter setEnabled:scrollbar.Enabled()];
diff --git a/third_party/blink/renderer/core/style/computed_style.cc b/third_party/blink/renderer/core/style/computed_style.cc
index d07b987..29ca0c6 100644
--- a/third_party/blink/renderer/core/style/computed_style.cc
+++ b/third_party/blink/renderer/core/style/computed_style.cc
@@ -1442,7 +1442,7 @@
 const CounterDirectives ComputedStyle::GetCounterDirectives(
     const AtomicString& identifier) const {
   if (const CounterDirectiveMap* directives = GetCounterDirectives())
-    return directives->at(identifier);
+    return directives->DeprecatedAtOrEmptyValue(identifier);
   return CounterDirectives();
 }
 
diff --git a/third_party/blink/renderer/core/svg/graphics/filters/svg_filter_builder.cc b/third_party/blink/renderer/core/svg/graphics/filters/svg_filter_builder.cc
index bea7d73..c84d33e 100644
--- a/third_party/blink/renderer/core/svg/graphics/filters/svg_filter_builder.cc
+++ b/third_party/blink/renderer/core/svg/graphics/filters/svg_filter_builder.cc
@@ -210,17 +210,20 @@
 
 FilterEffect* SVGFilterBuilder::GetEffectById(const AtomicString& id) const {
   if (!id.IsEmpty()) {
-    if (FilterEffect* builtin_effect = builtin_effects_.at(id))
+    if (FilterEffect* builtin_effect =
+            builtin_effects_.DeprecatedAtOrEmptyValue(id))
       return builtin_effect;
 
-    if (FilterEffect* named_effect = named_effects_.at(id))
+    if (FilterEffect* named_effect =
+            named_effects_.DeprecatedAtOrEmptyValue(id))
       return named_effect;
   }
 
   if (last_effect_)
     return last_effect_;
 
-  return builtin_effects_.at(FilterInputKeywords::GetSourceGraphic());
+  return builtin_effects_.DeprecatedAtOrEmptyValue(
+      FilterInputKeywords::GetSourceGraphic());
 }
 
 }  // namespace blink
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 5f3f9f31..93d12ad8 100644
--- a/third_party/blink/renderer/core/svg/graphics/svg_image.cc
+++ b/third_party/blink/renderer/core/svg/graphics/svg_image.cc
@@ -557,6 +557,7 @@
     return nullptr;
 
   view->UpdateAllLifecyclePhasesExceptPaint(DocumentUpdateReason::kSVGImage);
+  PaintController::CycleScope cycle_scope(*paint_controller_);
   PaintRecordBuilder builder(*paint_controller_);
   builder.Context().SetDarkModeEnabled(draw_info.IsDarkModeEnabled());
   view->PaintOutsideOfLifecycle(builder.Context(), kGlobalPaintNormalPhase);
diff --git a/third_party/blink/renderer/core/svg/svg_element.cc b/third_party/blink/renderer/core/svg/svg_element.cc
index 5a2e41e..532d911 100644
--- a/third_party/blink/renderer/core/svg/svg_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_element.cc
@@ -460,8 +460,10 @@
     }
   }
 
-  return property_name_to_id_map->DeprecatedAtOrEmptyValue(
-      attr_name.LocalName().Impl());
+  auto it = property_name_to_id_map->find(attr_name.LocalName().Impl());
+  if (it == property_name_to_id_map->end())
+    return CSSPropertyID::kInvalid;
+  return it->value;
 }
 
 void SVGElement::UpdateRelativeLengthsInformation(
@@ -747,7 +749,10 @@
     for (size_t i = 0; i < base::size(attr_to_types); i++)
       css_property_map.Set(attr_to_types[i].attr, attr_to_types[i].prop_type);
   }
-  return css_property_map.DeprecatedAtOrEmptyValue(attribute_name);
+  auto it = css_property_map.find(attribute_name);
+  if (it == css_property_map.end())
+    return kAnimatedUnknown;
+  return it->value;
 }
 
 void SVGElement::AddToPropertyMap(SVGAnimatedPropertyBase* property) {
diff --git a/third_party/blink/renderer/core/svg/svg_tree_scope_resources.cc b/third_party/blink/renderer/core/svg/svg_tree_scope_resources.cc
index a7bc3cb..547131c 100644
--- a/third_party/blink/renderer/core/svg/svg_tree_scope_resources.cc
+++ b/third_party/blink/renderer/core/svg/svg_tree_scope_resources.cc
@@ -27,7 +27,10 @@
     const AtomicString& id) const {
   if (id.IsEmpty())
     return nullptr;
-  return resources_.DeprecatedAtOrEmptyValue(id);
+  auto it = resources_.find(id);
+  if (it == resources_.end())
+    return nullptr;
+  return it->value;
 }
 
 void SVGTreeScopeResources::ProcessCustomWeakness(const LivenessBroker& info) {
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
index abacc99..8888b86f 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
@@ -665,7 +665,7 @@
     }
   }
 
-  AXObject* result = objects_.at(ax_id);
+  AXObject* result = objects_.DeprecatedAtOrEmptyValue(ax_id);
 #if DCHECK_IS_ON()
   DCHECK(result) << "Had AXID for Node but no entry in objects_";
   DCHECK(result->IsAXNodeObject());
@@ -1358,7 +1358,7 @@
     return;
 
   // First, fetch object to operate some cleanup functions on it.
-  AXObject* obj = objects_.at(ax_id);
+  AXObject* obj = objects_.DeprecatedAtOrEmptyValue(ax_id);
   if (!obj)
     return;
 
@@ -2052,10 +2052,13 @@
     return;
 
   LayoutObject* layout_object = node->GetLayoutObject();
-  AXID layout_id = layout_object ? layout_object_mapping_.at(layout_object) : 0;
+  AXID layout_id =
+      layout_object
+          ? layout_object_mapping_.DeprecatedAtOrEmptyValue(layout_object)
+          : 0;
   DCHECK(!HashTraits<AXID>::IsDeletedValue(layout_id));
 
-  AXID node_id = node_object_mapping_.at(node);
+  AXID node_id = node_object_mapping_.DeprecatedAtOrEmptyValue(node);
   DCHECK(!HashTraits<AXID>::IsDeletedValue(node_id));
   DCHECK(!node->GetDocument().NeedsLayoutTreeUpdateForNode(*node));
 
@@ -2233,7 +2236,7 @@
     if (current->GetLayoutObject()) {
       layout_object_mapping_.erase(current->GetLayoutObject());
     } else if (node->GetLayoutObject()) {
-      DCHECK(!layout_object_mapping_.at(node->GetLayoutObject()))
+      DCHECK(!layout_object_mapping_.Contains(node->GetLayoutObject()))
           << node << " " << node->GetLayoutObject();
     }
 
@@ -3129,7 +3132,7 @@
   if (!InlineTextBoxAccessibilityEnabled())
     return;
 
-  AXID ax_id = layout_object_mapping_.at(layout_object);
+  AXID ax_id = layout_object_mapping_.DeprecatedAtOrEmptyValue(layout_object);
   DCHECK(!HashTraits<AXID>::IsDeletedValue(ax_id));
 
   // Only update if the accessibility object already exists and it's
diff --git a/third_party/blink/renderer/modules/file_system_access/file_system_access_file_delegate.h b/third_party/blink/renderer/modules/file_system_access/file_system_access_file_delegate.h
index 9066bf67..d4cd08a 100644
--- a/third_party/blink/renderer/modules/file_system_access/file_system_access_file_delegate.h
+++ b/third_party/blink/renderer/modules/file_system_access/file_system_access_file_delegate.h
@@ -26,7 +26,8 @@
  public:
   virtual ~FileSystemAccessFileDelegate() = default;
 
-  static FileSystemAccessFileDelegate* Create(base::File backing_file);
+  static FileSystemAccessFileDelegate* Create(ExecutionContext* context,
+                                              base::File backing_file);
   static FileSystemAccessFileDelegate* CreateForIncognito(
       ExecutionContext* context,
       mojo::PendingRemote<mojom::blink::FileSystemAccessFileDelegateHost>
@@ -43,8 +44,10 @@
   virtual FileErrorOr<int> Write(int64_t offset,
                                  const base::span<uint8_t> data) = 0;
 
-  // Returns the current size of this file, or a file error on failure.
-  virtual FileErrorOr<int64_t> GetLength() = 0;
+  // Asynchronously get the size of the file. Returns the current size of this
+  // file, or a file error on failure.
+  virtual void GetLength(
+      base::OnceCallback<void(FileErrorOr<int64_t>)> callback) = 0;
 
   // Truncates the file to the given length. If |length| is greater than the
   // current size of the file, the file is extended with zeros. If the file
diff --git a/third_party/blink/renderer/modules/file_system_access/file_system_access_incognito_file_delegate.cc b/third_party/blink/renderer/modules/file_system_access/file_system_access_incognito_file_delegate.cc
index b2024f0..1ab0d45 100644
--- a/third_party/blink/renderer/modules/file_system_access/file_system_access_incognito_file_delegate.cc
+++ b/third_party/blink/renderer/modules/file_system_access/file_system_access_incognito_file_delegate.cc
@@ -152,10 +152,15 @@
   return file_error == base::File::Error::FILE_OK ? bytes_written : file_error;
 }
 
-FileErrorOr<int64_t> FileSystemAccessIncognitoFileDelegate::GetLength() {
-  // TODO(crbug.com/1225653): Implement this method.
-  NOTIMPLEMENTED();
-  return 0;
+void FileSystemAccessIncognitoFileDelegate::GetLength(
+    base::OnceCallback<void(FileErrorOr<int64_t>)> callback) {
+  mojo_ptr_->GetLength(WTF::Bind(
+      [](base::OnceCallback<void(FileErrorOr<int64_t>)> callback,
+         base::File::Error file_error, uint64_t length) {
+        std::move(callback).Run(
+            file_error == base::File::Error::FILE_OK ? length : file_error);
+      },
+      std::move(callback)));
 }
 
 bool FileSystemAccessIncognitoFileDelegate::SetLength(int64_t length) {
diff --git a/third_party/blink/renderer/modules/file_system_access/file_system_access_incognito_file_delegate.h b/third_party/blink/renderer/modules/file_system_access/file_system_access_incognito_file_delegate.h
index bbc8ea5..7cf2eabc 100644
--- a/third_party/blink/renderer/modules/file_system_access/file_system_access_incognito_file_delegate.h
+++ b/third_party/blink/renderer/modules/file_system_access/file_system_access_incognito_file_delegate.h
@@ -38,7 +38,8 @@
   FileErrorOr<int> Write(int64_t offset,
                          const base::span<uint8_t> data) override;
 
-  FileErrorOr<int64_t> GetLength() override;
+  void GetLength(
+      base::OnceCallback<void(FileErrorOr<int64_t>)> callback) override;
   bool SetLength(int64_t length) override;
 
   bool Flush() override;
diff --git a/third_party/blink/renderer/modules/file_system_access/file_system_access_regular_file_delegate.cc b/third_party/blink/renderer/modules/file_system_access/file_system_access_regular_file_delegate.cc
index c7027e8..88a7d85 100644
--- a/third_party/blink/renderer/modules/file_system_access/file_system_access_regular_file_delegate.cc
+++ b/third_party/blink/renderer/modules/file_system_access/file_system_access_regular_file_delegate.cc
@@ -4,22 +4,33 @@
 
 #include "third_party/blink/renderer/modules/file_system_access/file_system_access_regular_file_delegate.h"
 
+#include "base/memory/scoped_refptr.h"
 #include "base/notreached.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
+#include "third_party/blink/renderer/core/execution_context/execution_context.h"
+#include "third_party/blink/renderer/modules/file_system_access/file_error_or.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
+#include "third_party/blink/renderer/platform/scheduler/public/worker_pool.h"
+#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
+#include "third_party/blink/renderer/platform/wtf/functional.h"
 
 namespace blink {
 
 FileSystemAccessFileDelegate* FileSystemAccessFileDelegate::Create(
+    ExecutionContext* context,
     base::File backing_file) {
   return MakeGarbageCollected<FileSystemAccessRegularFileDelegate>(
-      std::move(backing_file), base::PassKey<FileSystemAccessFileDelegate>());
+      context, std::move(backing_file),
+      base::PassKey<FileSystemAccessFileDelegate>());
 }
 
 FileSystemAccessRegularFileDelegate::FileSystemAccessRegularFileDelegate(
+    ExecutionContext* context,
     base::File backing_file,
     base::PassKey<FileSystemAccessFileDelegate>)
-    : backing_file_(std::move(backing_file)) {}
+    : backing_file_(std::move(backing_file)),
+      task_runner_(context->GetTaskRunner(TaskType::kMiscPlatformAPI)) {}
 
 FileErrorOr<int> FileSystemAccessRegularFileDelegate::Read(
     int64_t offset,
@@ -45,12 +56,34 @@
   return base::File::GetLastFileError();
 }
 
-FileErrorOr<int64_t> FileSystemAccessRegularFileDelegate::GetLength() {
-  int64_t result = backing_file_.GetLength();
-  if (result >= 0) {
-    return result;
-  }
-  return base::File::GetLastFileError();
+void FileSystemAccessRegularFileDelegate::GetLength(
+    base::OnceCallback<void(FileErrorOr<int64_t>)> callback) {
+  auto wrapped_callback =
+      CrossThreadOnceFunction<void(FileErrorOr<int64_t>)>(std::move(callback));
+
+  // Get file length on a worker thread and reply back to this sequence.
+  worker_pool::PostTask(
+      FROM_HERE, {base::MayBlock()},
+      CrossThreadBindOnce(&FileSystemAccessRegularFileDelegate::DoGetLength,
+                          WrapCrossThreadPersistent(this),
+                          std::move(wrapped_callback), task_runner_));
+}
+
+// static
+void FileSystemAccessRegularFileDelegate::DoGetLength(
+    CrossThreadPersistent<FileSystemAccessRegularFileDelegate> delegate,
+    WTF::CrossThreadOnceFunction<void(FileErrorOr<int64_t>)> wrapped_callback,
+    scoped_refptr<base::SequencedTaskRunner> task_runner) {
+  int64_t length = delegate->backing_file_.GetLength();
+
+  // If the length is negative, the file operation failed. Get the last error
+  // now before another file operation might run.
+  FileErrorOr<int64_t> result =
+      length >= 0 ? length : base::File::GetLastFileError();
+
+  PostCrossThreadTask(
+      *task_runner, FROM_HERE,
+      CrossThreadBindOnce(std::move(wrapped_callback), std::move(result)));
 }
 
 bool FileSystemAccessRegularFileDelegate::SetLength(int64_t length) {
diff --git a/third_party/blink/renderer/modules/file_system_access/file_system_access_regular_file_delegate.h b/third_party/blink/renderer/modules/file_system_access/file_system_access_regular_file_delegate.h
index 3c1f88d9..cd072c0 100644
--- a/third_party/blink/renderer/modules/file_system_access/file_system_access_regular_file_delegate.h
+++ b/third_party/blink/renderer/modules/file_system_access/file_system_access_regular_file_delegate.h
@@ -9,6 +9,7 @@
 #include "base/types/pass_key.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "third_party/blink/public/mojom/file_system_access/file_system_access_file_handle.mojom-blink.h"
+#include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/modules/file_system_access/file_system_access_file_delegate.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 
@@ -22,6 +23,7 @@
   // Instances should only be constructed via
   // `FileSystemAccessFileDelegate::Create()`
   explicit FileSystemAccessRegularFileDelegate(
+      ExecutionContext* context,
       base::File backing_file,
       base::PassKey<FileSystemAccessFileDelegate>);
 
@@ -34,7 +36,8 @@
   FileErrorOr<int> Write(int64_t offset,
                          const base::span<uint8_t> data) override;
 
-  FileErrorOr<int64_t> GetLength() override;
+  void GetLength(
+      base::OnceCallback<void(FileErrorOr<int64_t>)> callback) override;
   bool SetLength(int64_t length) override;
 
   bool Flush() override;
@@ -43,8 +46,15 @@
   bool IsValid() const override { return backing_file_.IsValid(); }
 
  private:
+  static void DoGetLength(
+      CrossThreadPersistent<FileSystemAccessRegularFileDelegate> delegate,
+      CrossThreadOnceFunction<void(FileErrorOr<int64_t>)> wrapped_callback,
+      scoped_refptr<base::SequencedTaskRunner> file_task_runner);
+
   // The file on disk backing the parent FileSystemFileHandle.
   base::File backing_file_;
+
+  const scoped_refptr<base::SequencedTaskRunner> task_runner_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/file_system_access/file_system_file_handle.cc b/third_party/blink/renderer/modules/file_system_access/file_system_file_handle.cc
index 21668f9a..81fffc2 100644
--- a/third_party/blink/renderer/modules/file_system_access/file_system_file_handle.cc
+++ b/third_party/blink/renderer/modules/file_system_access/file_system_file_handle.cc
@@ -128,7 +128,7 @@
         FileSystemAccessFileDelegate* file_delegate = nullptr;
         if (file->is_regular_file()) {
           file_delegate = FileSystemAccessFileDelegate::Create(
-              std::move(file->get_regular_file()));
+              context, std::move(file->get_regular_file()));
         } else if (file->is_incognito_file_delegate()) {
           file_delegate = FileSystemAccessFileDelegate::CreateForIncognito(
               context, std::move(file->get_incognito_file_delegate()));
diff --git a/third_party/blink/renderer/modules/file_system_access/file_system_sync_access_handle.cc b/third_party/blink/renderer/modules/file_system_access/file_system_sync_access_handle.cc
index f1e96a6..dffcdab 100644
--- a/third_party/blink/renderer/modules/file_system_access/file_system_sync_access_handle.cc
+++ b/third_party/blink/renderer/modules/file_system_access/file_system_sync_access_handle.cc
@@ -7,6 +7,7 @@
 #include "build/build_config.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_throw_dom_exception.h"
+#include "third_party/blink/renderer/modules/file_system_access/file_system_access_file_delegate.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
 #include "third_party/blink/renderer/platform/scheduler/public/worker_pool.h"
@@ -187,46 +188,32 @@
   }
 
   auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
-  worker_pool::PostTask(
-      FROM_HERE, {base::MayBlock()},
-      CrossThreadBindOnce(&DoGetSize, WrapCrossThreadPersistent(this),
-                          WrapCrossThreadPersistent(resolver),
-                          resolver_task_runner_));
-  return resolver->Promise();
-}
+  ScriptPromise result = resolver->Promise();
 
-// static
-void FileSystemSyncAccessHandle::DoGetSize(
-    CrossThreadPersistent<FileSystemSyncAccessHandle> access_handle,
-    CrossThreadPersistent<ScriptPromiseResolver> resolver,
-    scoped_refptr<base::SequencedTaskRunner> resolver_task_runner) {
-  DCHECK(access_handle->file_delegate()->IsValid())
+  DCHECK(file_delegate()->IsValid())
       << "file I/O operation queued after file closed";
-  FileErrorOr<int64_t> result = access_handle->file_delegate()->GetLength();
 
-  PostCrossThreadTask(
-      *resolver_task_runner, FROM_HERE,
-      CrossThreadBindOnce(&FileSystemSyncAccessHandle::DidGetSize,
-                          std::move(access_handle), std::move(resolver),
-                          result));
-}
+  file_delegate()->GetLength(WTF::Bind(
+      [](ScriptPromiseResolver* resolver,
+         FileSystemSyncAccessHandle* access_handle,
+         FileErrorOr<int64_t> error_or_length) {
+        ScriptState* script_state = resolver->GetScriptState();
+        if (!script_state->ContextIsValid())
+          return;
+        ScriptState::Scope scope(script_state);
 
-void FileSystemSyncAccessHandle::DidGetSize(
-    CrossThreadPersistent<ScriptPromiseResolver> resolver,
-    FileErrorOr<int64_t> result) {
-  ScriptState* script_state = resolver->GetScriptState();
-  if (!script_state->ContextIsValid())
-    return;
-  ScriptState::Scope scope(script_state);
+        access_handle->ExitOperation();
+        if (error_or_length.is_error()) {
+          resolver->Reject(V8ThrowDOMException::CreateOrEmpty(
+              script_state->GetIsolate(), DOMExceptionCode::kInvalidStateError,
+              "getSize failed"));
+          return;
+        }
+        resolver->Resolve(error_or_length.value());
+      },
+      WrapPersistent(resolver), WrapPersistent(this)));
 
-  ExitOperation();
-  if (result.is_error()) {
-    resolver->Reject(V8ThrowDOMException::CreateOrEmpty(
-        script_state->GetIsolate(), DOMExceptionCode::kInvalidStateError,
-        "getSize failed"));
-    return;
-  }
-  resolver->Resolve(result.value());
+  return result;
 }
 
 ScriptPromise FileSystemSyncAccessHandle::truncate(
diff --git a/third_party/blink/renderer/modules/file_system_access/file_system_sync_access_handle.h b/third_party/blink/renderer/modules/file_system_access/file_system_sync_access_handle.h
index ae18c82..fc83aee 100644
--- a/third_party/blink/renderer/modules/file_system_access/file_system_sync_access_handle.h
+++ b/third_party/blink/renderer/modules/file_system_access/file_system_sync_access_handle.h
@@ -76,16 +76,6 @@
   void DidFlush(CrossThreadPersistent<ScriptPromiseResolver> resolver,
                 bool success);
 
-  // Performs the file I/O part of getSize().
-  static void DoGetSize(
-      CrossThreadPersistent<FileSystemSyncAccessHandle> access_handle,
-      CrossThreadPersistent<ScriptPromiseResolver> resolver,
-      scoped_refptr<base::SequencedTaskRunner> file_task_runner);
-
-  // Performs the post file-I/O part of getSize(), on the foreground thread.
-  void DidGetSize(CrossThreadPersistent<ScriptPromiseResolver> resolver,
-                  FileErrorOr<int64_t> size);
-
   // Performs the file I/O part of truncate().
   static void DoTruncate(
       CrossThreadPersistent<FileSystemSyncAccessHandle> access_handle,
diff --git a/third_party/blink/renderer/modules/sanitizer_api/sanitizer.cc b/third_party/blink/renderer/modules/sanitizer_api/sanitizer.cc
index 773bdb18..1ba4bd0 100644
--- a/third_party/blink/renderer/modules/sanitizer_api/sanitizer.cc
+++ b/third_party/blink/renderer/modules/sanitizer_api/sanitizer.cc
@@ -327,8 +327,10 @@
                              String& node_name,
                              LocalDOMWindow* window) {
   Element* element = To<Element>(node);
-  if (config_.allow_attributes_.at("*").Contains(node_name)) {
-  } else if (config_.drop_attributes_.at("*").Contains(node_name)) {
+  if (config_.allow_attributes_.DeprecatedAtOrEmptyValue("*").Contains(
+          node_name)) {
+  } else if (config_.drop_attributes_.DeprecatedAtOrEmptyValue("*").Contains(
+                 node_name)) {
     for (const auto& name : element->getAttributeNames()) {
       element->removeAttribute(name);
       UseCounter::Count(window->GetExecutionContext(),
@@ -338,15 +340,22 @@
     for (const auto& name : element->getAttributeNames()) {
       // Attributes in drop list or not in allow list while allow list
       // exists will be dropped.
-      bool drop = (baseline_drop_attributes_.Contains(name) &&
-                   (baseline_drop_attributes_.at(name) == kVectorStar ||
-                    baseline_drop_attributes_.at(name).Contains(node_name))) ||
-                  (config_.drop_attributes_.Contains(name) &&
-                   (config_.drop_attributes_.at(name) == kVectorStar ||
-                    config_.drop_attributes_.at(name).Contains(node_name))) ||
-                  !(config_.allow_attributes_.Contains(name) &&
-                    (config_.allow_attributes_.at(name) == kVectorStar ||
-                     config_.allow_attributes_.at(name).Contains(node_name)));
+      bool drop =
+          (baseline_drop_attributes_.Contains(name) &&
+           (baseline_drop_attributes_.DeprecatedAtOrEmptyValue(name) ==
+                kVectorStar ||
+            baseline_drop_attributes_.DeprecatedAtOrEmptyValue(name).Contains(
+                node_name))) ||
+          (config_.drop_attributes_.Contains(name) &&
+           (config_.drop_attributes_.DeprecatedAtOrEmptyValue(name) ==
+                kVectorStar ||
+            config_.drop_attributes_.DeprecatedAtOrEmptyValue(name).Contains(
+                node_name))) ||
+          !(config_.allow_attributes_.Contains(name) &&
+            (config_.allow_attributes_.DeprecatedAtOrEmptyValue(name) ==
+                 kVectorStar ||
+             config_.allow_attributes_.DeprecatedAtOrEmptyValue(name).Contains(
+                 node_name)));
       // 9. If |element|'s [=element interface=] is {{HTMLAnchorElement}} or
       // {{HTMLAreaElement}} and |element|'s `protocol` property is
       // "javascript:", then remove the `href` attribute from |element|.
diff --git a/third_party/blink/renderer/modules/webgpu/dawn_enum_conversions.cc b/third_party/blink/renderer/modules/webgpu/dawn_enum_conversions.cc
index 6d0ad02..e37382f 100644
--- a/third_party/blink/renderer/modules/webgpu/dawn_enum_conversions.cc
+++ b/third_party/blink/renderer/modules/webgpu/dawn_enum_conversions.cc
@@ -583,16 +583,16 @@
 }
 
 template <>
-WGPUInputStepMode AsDawnEnum<WGPUInputStepMode>(
+WGPUVertexStepMode AsDawnEnum<WGPUVertexStepMode>(
     const WTF::String& webgpu_enum) {
   if (webgpu_enum == "vertex") {
-    return WGPUInputStepMode_Vertex;
+    return WGPUVertexStepMode_Vertex;
   }
   if (webgpu_enum == "instance") {
-    return WGPUInputStepMode_Instance;
+    return WGPUVertexStepMode_Instance;
   }
   NOTREACHED();
-  return WGPUInputStepMode_Force32;
+  return WGPUVertexStepMode_Force32;
 }
 
 template <>
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_render_pipeline.cc b/third_party/blink/renderer/modules/webgpu/gpu_render_pipeline.cc
index 62001327..e185a1a 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_render_pipeline.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_render_pipeline.cc
@@ -205,7 +205,7 @@
         value->IsNullOrUndefined()) {
       WGPUVertexBufferLayout dawn_vertex_buffer = {};
       dawn_vertex_buffer.arrayStride = 0;
-      dawn_vertex_buffer.stepMode = WGPUInputStepMode_Vertex;
+      dawn_vertex_buffer.stepMode = WGPUVertexStepMode_Vertex;
       dawn_vertex_buffer.attributeCount = 0;
       dawn_vertex_buffer.attributes = nullptr;
       dawn_vertex_buffers->push_back(dawn_vertex_buffer);
@@ -222,7 +222,7 @@
     WGPUVertexBufferLayout dawn_vertex_buffer = {};
     dawn_vertex_buffer.arrayStride = vertex_buffer->arrayStride();
     dawn_vertex_buffer.stepMode =
-        AsDawnEnum<WGPUInputStepMode>(vertex_buffer->stepMode());
+        AsDawnEnum<WGPUVertexStepMode>(vertex_buffer->stepMode());
     dawn_vertex_buffer.attributeCount =
         static_cast<uint32_t>(vertex_buffer->attributes().size());
     dawn_vertex_buffer.attributes = nullptr;
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_vertex_buffer_layout.idl b/third_party/blink/renderer/modules/webgpu/gpu_vertex_buffer_layout.idl
index 42c754d..c6dfc05 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_vertex_buffer_layout.idl
+++ b/third_party/blink/renderer/modules/webgpu/gpu_vertex_buffer_layout.idl
@@ -6,11 +6,11 @@
 
 dictionary GPUVertexBufferLayout {
     required GPUSize64 arrayStride;
-    GPUInputStepMode stepMode = "vertex";
+    GPUVertexStepMode stepMode = "vertex";
     required sequence<GPUVertexAttribute> attributes;
 };
 
-enum GPUInputStepMode {
+enum GPUVertexStepMode {
     "vertex",
     "instance"
 };
diff --git a/third_party/blink/renderer/platform/fonts/font_fallback_iterator.cc b/third_party/blink/renderer/platform/fonts/font_fallback_iterator.cc
index d922e07e..470df1c 100644
--- a/third_party/blink/renderer/platform/fonts/font_fallback_iterator.cc
+++ b/third_party/blink/renderer/platform/fonts/font_fallback_iterator.cc
@@ -12,12 +12,6 @@
 
 namespace blink {
 
-namespace {
-
-const unsigned kMaxRecursionDepth = 128;
-
-}
-
 FontFallbackIterator::FontFallbackIterator(
     const FontDescription& description,
     scoped_refptr<FontFallbackList> fallback_list,
@@ -112,12 +106,9 @@
 
 scoped_refptr<FontDataForRangeSet> FontFallbackIterator::Next(
     const Vector<UChar32>& hint_list) {
-  if (fallback_stage_ == kOutOfLuck || recursion_depth_ > kMaxRecursionDepth)
+  if (fallback_stage_ == kOutOfLuck)
     return base::AdoptRef(new FontDataForRangeSet());
 
-  base::AutoReset<unsigned> recursion_scope(&recursion_depth_,
-                                            recursion_depth_ + 1);
-
   if (fallback_stage_ == kFallbackPriorityFonts) {
     // Only try one fallback priority font,
     // then proceed to regular system fallback.
diff --git a/third_party/blink/renderer/platform/fonts/font_fallback_iterator.h b/third_party/blink/renderer/platform/fonts/font_fallback_iterator.h
index b98b21c..b1b3e80 100644
--- a/third_party/blink/renderer/platform/fonts/font_fallback_iterator.h
+++ b/third_party/blink/renderer/platform/fonts/font_fallback_iterator.h
@@ -87,9 +87,6 @@
   scoped_refptr<FontDataForRangeSet> first_candidate_;
   Vector<scoped_refptr<FontDataForRangeSet>> tracked_loading_range_sets_;
   FontFallbackPriority font_fallback_priority_;
-
-  // Limits the recursion depth on Next().
-  unsigned recursion_depth_ = 0;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
index 9d1eddc8..98a596e0 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
@@ -342,6 +342,16 @@
     DCHECK_EQ(previous.text_known_to_be_on_opaque_background,
               repainted.text_known_to_be_on_opaque_background);
     DCHECK_EQ(previous.has_text, repainted.has_text);
+
+    // Debugging for https://crbug.com/1237389 and https://crbug.com/1230104.
+    // Before returning that a full update is not needed, check that the
+    // properties are changed, which would indicate a missing call to
+    // SetNeedsUpdate.
+    if (previous.properties != repainted.properties) {
+      NOTREACHED();
+      return true;
+    }
+
     // Not checking ForeignLayer() here because the old ForeignDisplayItem
     // was set to 0 when we moved the cached subsequence. This is also the
     // reason why we check is_moved_from_cached_subsequence before checking
@@ -377,6 +387,15 @@
   if (previous.has_text != repainted.has_text)
     return true;
 
+  // Debugging for https://crbug.com/1237389 and https://crbug.com/1230104.
+  // Before returning that a full update is not needed, check that the
+  // properties are changed, which would indicate a missing call to
+  // SetNeedsUpdate.
+  if (previous.properties != repainted.properties) {
+    NOTREACHED();
+    return true;
+  }
+
   return false;
 }
 
diff --git a/third_party/blink/renderer/platform/graphics/graphics_layer.cc b/third_party/blink/renderer/platform/graphics/graphics_layer.cc
index 5314fc6..7091f84 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_layer.cc
+++ b/third_party/blink/renderer/platform/graphics/graphics_layer.cc
@@ -277,6 +277,7 @@
 bool GraphicsLayer::PaintRecursively(
     GraphicsContext& context,
     Vector<PreCompositedLayerInfo>& pre_composited_layers,
+    PaintController::CycleScope& cycle_scope,
     PaintBenchmarkMode benchmark_mode) {
   bool repainted = false;
   ForAllGraphicsLayers(
@@ -286,7 +287,7 @@
           layer.ClearPaintStateRecursively();
           return false;
         }
-        layer.Paint(pre_composited_layers, benchmark_mode);
+        layer.Paint(pre_composited_layers, benchmark_mode, &cycle_scope);
         repainted |= layer.repainted_;
         return true;
       },
@@ -313,11 +314,14 @@
 
 void GraphicsLayer::PaintForTesting(const IntRect& interest_rect) {
   Vector<PreCompositedLayerInfo> pre_composited_layers;
-  Paint(pre_composited_layers, PaintBenchmarkMode::kNormal, &interest_rect);
+  PaintController::CycleScope cycle_scope;
+  Paint(pre_composited_layers, PaintBenchmarkMode::kNormal, &cycle_scope,
+        &interest_rect);
 }
 
 void GraphicsLayer::Paint(Vector<PreCompositedLayerInfo>& pre_composited_layers,
                           PaintBenchmarkMode benchmark_mode,
+                          PaintController::CycleScope* cycle_scope,
                           const IntRect* interest_rect) {
   repainted_ = false;
 
@@ -351,6 +355,8 @@
   }
 
   auto& paint_controller = GetPaintController();
+  if (cycle_scope)
+    cycle_scope->AddController(paint_controller);
 
   absl::optional<PaintChunkSubset> previous_chunks;
   if (ShouldCreateLayersAfterPaint())
@@ -365,7 +371,7 @@
                 paint_controller.ClientCacheIsValid(*this) &&
                 previous_interest_rect_ == new_interest_rect;
   if (!cached) {
-    paint_controller.ReserveCapacity();
+    paint_controller.MarkClientForValidation(*this);
     GraphicsContext context(paint_controller);
     DCHECK(layer_state_) << "No layer state for GraphicsLayer: " << DebugName();
     paint_controller.UpdateCurrentPaintChunkProperties(
@@ -377,9 +383,6 @@
     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()
              << " paintable region: " << PaintableRegion().ToString();
   }
diff --git a/third_party/blink/renderer/platform/graphics/graphics_layer.h b/third_party/blink/renderer/platform/graphics/graphics_layer.h
index 694ae97..5ddf1add 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_layer.h
+++ b/third_party/blink/renderer/platform/graphics/graphics_layer.h
@@ -173,6 +173,7 @@
   // Returns true if any layer is repainted.
   bool PaintRecursively(GraphicsContext&,
                         Vector<PreCompositedLayerInfo>&,
+                        PaintController::CycleScope& cycle_scope,
                         PaintBenchmarkMode = PaintBenchmarkMode::kNormal);
 
   PaintController& GetPaintController() const;
@@ -239,6 +240,7 @@
   void ClearPaintStateRecursively();
   void Paint(Vector<PreCompositedLayerInfo>&,
              PaintBenchmarkMode,
+             PaintController::CycleScope*,
              const IntRect* interest_rect = nullptr);
 
   // Adds a child without calling NotifyChildListChange(), so that adding
diff --git a/third_party/blink/renderer/platform/graphics/graphics_layer_test.cc b/third_party/blink/renderer/platform/graphics/graphics_layer_test.cc
index 3856e9a..af4b9ca7 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_layer_test.cc
+++ b/third_party/blink/renderer/platform/graphics/graphics_layer_test.cc
@@ -100,12 +100,14 @@
   GraphicsContext context(GetPaintController());
   client.SetNeedsRepaint(true);
   Vector<PreCompositedLayerInfo> pre_composited_layers;
-  EXPECT_TRUE(root.PaintRecursively(context, pre_composited_layers));
+  {
+    PaintController::CycleScope cycle_scope;
+    EXPECT_TRUE(
+        root.PaintRecursively(context, pre_composited_layers, cycle_scope));
+  }
   EXPECT_TRUE(root.Repainted());
-  root.GetPaintController().FinishCycle();
   EXPECT_FALSE(layer1.Repainted());
   EXPECT_TRUE(layer2.Repainted());
-  layer2.GetPaintController().FinishCycle();
 
   HitTestData hit_test_data;
   hit_test_data.touch_action_rects = {{IntRect(1, 2, 3, 4)}};
@@ -129,7 +131,11 @@
   // Paint again with nothing changed.
   client.SetNeedsRepaint(false);
   pre_composited_layers.clear();
-  EXPECT_FALSE(root.PaintRecursively(context, pre_composited_layers));
+  {
+    PaintController::CycleScope cycle_scope;
+    EXPECT_FALSE(
+        root.PaintRecursively(context, pre_composited_layers, cycle_scope));
+  }
   EXPECT_FALSE(root.Repainted());
   EXPECT_FALSE(layer1.Repainted());
   EXPECT_FALSE(layer2.Repainted());
@@ -138,10 +144,13 @@
   // Paint again with layer1 drawing content.
   layer1.SetDrawsContent(true);
   pre_composited_layers.clear();
-  EXPECT_TRUE(root.PaintRecursively(context, pre_composited_layers));
+  {
+    PaintController::CycleScope cycle_scope;
+    EXPECT_TRUE(
+        root.PaintRecursively(context, pre_composited_layers, cycle_scope));
+  }
   EXPECT_FALSE(root.Repainted());
   EXPECT_TRUE(layer1.Repainted());
-  layer1.GetPaintController().FinishCycle();
   EXPECT_FALSE(layer2.Repainted());
 
   EXPECT_EQ(3u, pre_composited_layers.size());
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 24fe02f..9023fe2 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
@@ -27,7 +27,10 @@
 // dereferenced unless we can make sure the client is still alive.
 class PLATFORM_EXPORT DisplayItemClient {
  public:
-  DisplayItemClient() {
+  DisplayItemClient()
+      : paint_invalidation_reason_(
+            static_cast<uint8_t>(PaintInvalidationReason::kJustCreated)),
+        marked_for_validation_(0) {
 #if DCHECK_IS_ON()
     OnCreate();
 #endif
@@ -73,30 +76,32 @@
         // However, kUncacheable overwrites any other reason.
         reason != PaintInvalidationReason::kUncacheable)
       return;
-    paint_invalidation_reason_ = reason;
+    paint_invalidation_reason_ = static_cast<uint8_t>(reason);
   }
 
   PaintInvalidationReason GetPaintInvalidationReason() const {
-    return paint_invalidation_reason_;
+    return static_cast<PaintInvalidationReason>(paint_invalidation_reason_);
   }
 
   // A client is considered "just created" if its display items have never been
   // validated by any PaintController since it's created.
   bool IsJustCreated() const {
-    return paint_invalidation_reason_ == PaintInvalidationReason::kJustCreated;
+    return GetPaintInvalidationReason() ==
+           PaintInvalidationReason::kJustCreated;
   }
 
   // Whether the client is cacheable. The uncacheable status is set when the
   // client produces any display items that skipped caching of any
   // PaintController.
   bool IsCacheable() const {
-    return paint_invalidation_reason_ != PaintInvalidationReason::kUncacheable;
+    return GetPaintInvalidationReason() !=
+           PaintInvalidationReason::kUncacheable;
   }
 
   // True if the client's display items are cached in PaintControllers without
   // needing to update.
   bool IsValid() const {
-    return paint_invalidation_reason_ == PaintInvalidationReason::kNone;
+    return GetPaintInvalidationReason() == PaintInvalidationReason::kNone;
   }
 
   String ToString() const;
@@ -104,11 +109,15 @@
  private:
   friend class FakeDisplayItemClient;
   friend class ObjectPaintInvalidatorTest;
+  friend class PaintChunker;
   friend class PaintController;
-  friend class GraphicsLayer;  // Temporary for Validate().
 
+  void MarkForValidation() const { marked_for_validation_ = 1; }
+  bool IsMarkedForValidation() const { return marked_for_validation_; }
   void Validate() const {
-    paint_invalidation_reason_ = PaintInvalidationReason::kNone;
+    paint_invalidation_reason_ =
+        static_cast<uint8_t>(PaintInvalidationReason::kNone);
+    marked_for_validation_ = 0;
   }
 
 #if DCHECK_IS_ON()
@@ -116,8 +125,8 @@
   void OnDestroy();
 #endif
 
-  mutable PaintInvalidationReason paint_invalidation_reason_ =
-      PaintInvalidationReason::kJustCreated;
+  mutable uint8_t paint_invalidation_reason_ : 7;
+  mutable uint8_t marked_for_validation_ : 1;
 };
 
 inline bool operator==(const DisplayItemClient& client1,
diff --git a/third_party/blink/renderer/platform/graphics/paint/display_item_raster_invalidator_test.cc b/third_party/blink/renderer/platform/graphics/paint/display_item_raster_invalidator_test.cc
index 0a9925b..f07e50d 100644
--- a/third_party/blink/renderer/platform/graphics/paint/display_item_raster_invalidator_test.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/display_item_raster_invalidator_test.cc
@@ -22,18 +22,7 @@
  protected:
   DisplayItemRasterInvalidatorTest() = default;
 
-  Vector<RasterInvalidationInfo> GenerateRasterInvalidations() {
-    GetPaintController().CommitNewDisplayItems();
-    invalidator_.Generate(
-        base::DoNothing(),
-        PaintChunkSubset(GetPaintController().GetPaintArtifactShared()),
-        // The layer bounds are big enough not to clip display item raster
-        // invalidation rects in the tests.
-        FloatPoint(), IntSize(20000, 20000), PropertyTreeState::Root());
-    GetPaintController().FinishCycle();
-    for (auto& chunk : GetPaintController().PaintChunks())
-      chunk.properties.ClearChangedTo(PropertyTreeState::Root());
-
+  Vector<RasterInvalidationInfo> GetRasterInvalidations() {
     if (invalidator_.GetTracking())
       return invalidator_.GetTracking()->Invalidations();
     return Vector<RasterInvalidationInfo>();
@@ -44,6 +33,29 @@
   RasterInvalidator invalidator_;
 };
 
+class RasterInvalidationCycleScope : public PaintController::CycleScope {
+ public:
+  RasterInvalidationCycleScope(PaintController& controller,
+                               RasterInvalidator& invalidator)
+      : PaintController::CycleScope(controller), invalidator_(invalidator) {}
+  ~RasterInvalidationCycleScope() {
+    for (auto* controller : controllers_) {
+      controller->CommitNewDisplayItems();
+      invalidator_.Generate(
+          base::DoNothing(),
+          PaintChunkSubset(controller->GetPaintArtifactShared()),
+          // The layer bounds are big enough not to clip display item raster
+          // invalidation rects in the tests.
+          FloatPoint(), IntSize(20000, 20000), PropertyTreeState::Root());
+      for (auto& chunk : controller->PaintChunks())
+        chunk.properties.ClearChangedTo(PropertyTreeState::Root());
+    }
+  }
+
+ private:
+  RasterInvalidator& invalidator_;
+};
+
 INSTANTIATE_PAINT_TEST_SUITE_P(DisplayItemRasterInvalidatorTest);
 
 TEST_P(DisplayItemRasterInvalidatorTest,
@@ -52,20 +64,27 @@
   FakeDisplayItemClient second("second");
   GraphicsContext context(GetPaintController());
 
-  InitRootChunk();
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 300, 300));
-  DrawRect(context, second, kBackgroundType, IntRect(100, 100, 200, 200));
-  DrawRect(context, first, kForegroundType, IntRect(100, 150, 300, 300));
-  GenerateRasterInvalidations();
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 300, 300));
+    DrawRect(context, second, kBackgroundType, IntRect(100, 100, 200, 200));
+    DrawRect(context, first, kForegroundType, IntRect(100, 150, 300, 300));
+  }
 
   first.Invalidate(PaintInvalidationReason::kStyle);
   invalidator_.SetTracksRasterInvalidations(true);
-  InitRootChunk();
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 300, 300));
-  DrawRect(context, second, kBackgroundType, IntRect(100, 100, 200, 200));
-  DrawRect(context, first, kForegroundType, IntRect(100, 150, 300, 300));
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 300, 300));
+    DrawRect(context, second, kBackgroundType, IntRect(100, 100, 200, 200));
+    DrawRect(context, first, kForegroundType, IntRect(100, 150, 300, 300));
+  }
 
-  EXPECT_THAT(GenerateRasterInvalidations(),
+  EXPECT_THAT(GetRasterInvalidations(),
               UnorderedElementsAre(RasterInvalidationInfo{
                   &first, "first", IntRect(100, 100, 300, 350),
                   PaintInvalidationReason::kStyle}));
@@ -77,21 +96,28 @@
   FakeDisplayItemClient second("second");
   GraphicsContext context(GetPaintController());
 
-  InitRootChunk();
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 300, 300));
-  DrawRect(context, second, kBackgroundType, IntRect(100, 100, 200, 200));
-  DrawRect(context, first, kForegroundType, IntRect(100, 150, 300, 300));
-  GenerateRasterInvalidations();
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 300, 300));
+    DrawRect(context, second, kBackgroundType, IntRect(100, 100, 200, 200));
+    DrawRect(context, first, kForegroundType, IntRect(100, 150, 300, 300));
+  }
 
   first.Invalidate(PaintInvalidationReason::kStyle);
   invalidator_.SetTracksRasterInvalidations(true);
-  InitRootChunk();
-  DrawRect(context, first, kBackgroundType, IntRect(200, 100, 300, 300));
-  DrawRect(context, second, kBackgroundType, IntRect(100, 100, 200, 200));
-  DrawRect(context, first, kForegroundType, IntRect(200, 150, 300, 300));
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    DrawRect(context, first, kBackgroundType, IntRect(200, 100, 300, 300));
+    DrawRect(context, second, kBackgroundType, IntRect(100, 100, 200, 200));
+    DrawRect(context, first, kForegroundType, IntRect(200, 150, 300, 300));
+  }
 
   EXPECT_THAT(
-      GenerateRasterInvalidations(),
+      GetRasterInvalidations(),
       UnorderedElementsAre(
           RasterInvalidationInfo{&first, "first", IntRect(100, 100, 300, 350),
                                  PaintInvalidationReason::kStyle},
@@ -105,18 +131,25 @@
   FakeDisplayItemClient second("second");
   GraphicsContext context(GetPaintController());
 
-  InitRootChunk();
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 300, 300));
-  DrawRect(context, second, kBackgroundType, IntRect(100, 100, 200, 200));
-  DrawRect(context, first, kForegroundType, IntRect(100, 100, 300, 300));
-  GenerateRasterInvalidations();
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 300, 300));
+    DrawRect(context, second, kBackgroundType, IntRect(100, 100, 200, 200));
+    DrawRect(context, first, kForegroundType, IntRect(100, 100, 300, 300));
+  }
 
   invalidator_.SetTracksRasterInvalidations(true);
-  InitRootChunk();
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 300, 300));
-  DrawRect(context, first, kForegroundType, IntRect(100, 100, 300, 300));
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 300, 300));
+    DrawRect(context, first, kForegroundType, IntRect(100, 100, 300, 300));
+  }
 
-  EXPECT_THAT(GenerateRasterInvalidations(),
+  EXPECT_THAT(GetRasterInvalidations(),
               UnorderedElementsAre(RasterInvalidationInfo{
                   &second, "second", IntRect(100, 100, 200, 200),
                   PaintInvalidationReason::kDisappeared}));
@@ -129,25 +162,32 @@
   FakeDisplayItemClient unaffected("unaffected");
   GraphicsContext context(GetPaintController());
 
-  InitRootChunk();
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, first, kForegroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, second, kForegroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, unaffected, kBackgroundType, IntRect(300, 300, 10, 10));
-  DrawRect(context, unaffected, kForegroundType, IntRect(300, 300, 10, 10));
-  GenerateRasterInvalidations();
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, first, kForegroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, second, kForegroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, unaffected, kBackgroundType, IntRect(300, 300, 10, 10));
+    DrawRect(context, unaffected, kForegroundType, IntRect(300, 300, 10, 10));
+  }
 
   invalidator_.SetTracksRasterInvalidations(true);
-  InitRootChunk();
-  DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, second, kForegroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, first, kForegroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, unaffected, kBackgroundType, IntRect(300, 300, 10, 10));
-  DrawRect(context, unaffected, kForegroundType, IntRect(300, 300, 10, 10));
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, second, kForegroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, first, kForegroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, unaffected, kBackgroundType, IntRect(300, 300, 10, 10));
+    DrawRect(context, unaffected, kForegroundType, IntRect(300, 300, 10, 10));
+  }
 
-  EXPECT_THAT(GenerateRasterInvalidations(),
+  EXPECT_THAT(GetRasterInvalidations(),
               UnorderedElementsAre(RasterInvalidationInfo{
                   &first, "first", IntRect(100, 100, 100, 100),
                   PaintInvalidationReason::kReordered}));
@@ -160,20 +200,27 @@
   FakeDisplayItemClient unaffected("unaffected");
   GraphicsContext context(GetPaintController());
 
-  InitRootChunk();
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, unaffected, kBackgroundType, IntRect(300, 300, 10, 10));
-  GenerateRasterInvalidations();
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, unaffected, kBackgroundType, IntRect(300, 300, 10, 10));
+  }
 
   invalidator_.SetTracksRasterInvalidations(true);
-  InitRootChunk();
-  first.Invalidate(PaintInvalidationReason::kOutline);
-  DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, unaffected, kBackgroundType, IntRect(300, 300, 10, 10));
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    first.Invalidate(PaintInvalidationReason::kOutline);
+    DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, unaffected, kBackgroundType, IntRect(300, 300, 10, 10));
+  }
 
-  EXPECT_THAT(GenerateRasterInvalidations(),
+  EXPECT_THAT(GetRasterInvalidations(),
               UnorderedElementsAre(RasterInvalidationInfo{
                   &first, "first", IntRect(100, 100, 100, 100),
                   PaintInvalidationReason::kOutline}));
@@ -186,20 +233,27 @@
   FakeDisplayItemClient unaffected("unaffected");
   GraphicsContext context(GetPaintController());
 
-  InitRootChunk();
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, unaffected, kBackgroundType, IntRect(300, 300, 10, 10));
-  GenerateRasterInvalidations();
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, unaffected, kBackgroundType, IntRect(300, 300, 10, 10));
+  }
 
   invalidator_.SetTracksRasterInvalidations(true);
-  InitRootChunk();
-  second.Invalidate(PaintInvalidationReason::kOutline);
-  DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, unaffected, kBackgroundType, IntRect(300, 300, 10, 10));
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    second.Invalidate(PaintInvalidationReason::kOutline);
+    DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, unaffected, kBackgroundType, IntRect(300, 300, 10, 10));
+  }
 
-  EXPECT_THAT(GenerateRasterInvalidations(),
+  EXPECT_THAT(GetRasterInvalidations(),
               UnorderedElementsAre(RasterInvalidationInfo{
                   &second, "second", IntRect(100, 100, 50, 200),
                   PaintInvalidationReason::kOutline}));
@@ -212,21 +266,28 @@
   FakeDisplayItemClient unaffected("unaffected");
   GraphicsContext context(GetPaintController());
 
-  InitRootChunk();
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, unaffected, kBackgroundType, IntRect(300, 300, 10, 10));
-  GenerateRasterInvalidations();
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, unaffected, kBackgroundType, IntRect(300, 300, 10, 10));
+  }
 
   invalidator_.SetTracksRasterInvalidations(true);
-  InitRootChunk();
-  first.Invalidate(PaintInvalidationReason::kIncremental);
-  DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, unaffected, kBackgroundType, IntRect(300, 300, 10, 10));
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    first.Invalidate(PaintInvalidationReason::kIncremental);
+    DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, unaffected, kBackgroundType, IntRect(300, 300, 10, 10));
+  }
 
   // Incremental invalidation is not applicable when the item is reordered.
-  EXPECT_THAT(GenerateRasterInvalidations(),
+  EXPECT_THAT(GetRasterInvalidations(),
               UnorderedElementsAre(RasterInvalidationInfo{
                   &first, "first", IntRect(100, 100, 100, 100),
                   PaintInvalidationReason::kReordered}));
@@ -239,18 +300,25 @@
   FakeDisplayItemClient third("third");
   GraphicsContext context(GetPaintController());
 
-  InitRootChunk();
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
-  GenerateRasterInvalidations();
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
+  }
 
   invalidator_.SetTracksRasterInvalidations(true);
-  InitRootChunk();
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, third, kBackgroundType, IntRect(125, 100, 200, 50));
-  DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, third, kBackgroundType, IntRect(125, 100, 200, 50));
+    DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
+  }
 
-  EXPECT_THAT(GenerateRasterInvalidations(),
+  EXPECT_THAT(GetRasterInvalidations(),
               UnorderedElementsAre(RasterInvalidationInfo{
                   &third, "third", IntRect(125, 100, 200, 50),
                   PaintInvalidationReason::kAppeared}));
@@ -266,24 +334,31 @@
   }
   GraphicsContext context(GetPaintController());
 
-  InitRootChunk();
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
 
-  for (auto& client : clients)
-    DrawRect(context, *client, kBackgroundType, IntRect(initial_rect));
-  GenerateRasterInvalidations();
-
-  invalidator_.SetTracksRasterInvalidations(true);
-  InitRootChunk();
-  IntRect visual_rects[] = {
-      IntRect(100, 100, 150, 100), IntRect(100, 100, 100, 150),
-      IntRect(100, 100, 150, 80),  IntRect(100, 100, 80, 150),
-      IntRect(100, 100, 150, 150), IntRect(100, 100, 80, 80)};
-  for (size_t i = 0; i < base::size(clients); i++) {
-    clients[i]->Invalidate(PaintInvalidationReason::kIncremental);
-    DrawRect(context, *clients[i], kBackgroundType, IntRect(visual_rects[i]));
+    for (auto& client : clients)
+      DrawRect(context, *client, kBackgroundType, IntRect(initial_rect));
   }
 
-  EXPECT_THAT(GenerateRasterInvalidations(),
+  invalidator_.SetTracksRasterInvalidations(true);
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    IntRect visual_rects[] = {
+        IntRect(100, 100, 150, 100), IntRect(100, 100, 100, 150),
+        IntRect(100, 100, 150, 80),  IntRect(100, 100, 80, 150),
+        IntRect(100, 100, 150, 150), IntRect(100, 100, 80, 80)};
+    for (size_t i = 0; i < base::size(clients); i++) {
+      clients[i]->Invalidate(PaintInvalidationReason::kIncremental);
+      DrawRect(context, *clients[i], kBackgroundType, IntRect(visual_rects[i]));
+    }
+  }
+
+  EXPECT_THAT(GetRasterInvalidations(),
               UnorderedElementsAre(
                   RasterInvalidationInfo{clients[0].get(), "0",
                                          IntRect(200, 100, 50, 100),
@@ -324,23 +399,30 @@
   FakeDisplayItemClient second("second");
   GraphicsContext context(GetPaintController());
 
-  InitRootChunk();
-  DrawRect(context, second, kBackgroundType, IntRect(200, 200, 50, 50));
-  DrawRect(context, second, kForegroundType, IntRect(200, 200, 50, 50));
-  GenerateRasterInvalidations();
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    DrawRect(context, second, kBackgroundType, IntRect(200, 200, 50, 50));
+    DrawRect(context, second, kForegroundType, IntRect(200, 200, 50, 50));
+  }
 
   invalidator_.SetTracksRasterInvalidations(true);
-  InitRootChunk();
-  first.Invalidate();
-  second.Invalidate();
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 150, 150));
-  DrawRect(context, first, kForegroundType, IntRect(100, 100, 150, 150));
-  DrawRect(context, second, kBackgroundType, IntRect(150, 250, 100, 100));
-  DrawRect(context, second, kForegroundType, IntRect(150, 250, 100, 100));
-  EXPECT_EQ(0u, NumCachedNewItems());
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    first.Invalidate();
+    second.Invalidate();
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 150, 150));
+    DrawRect(context, first, kForegroundType, IntRect(100, 100, 150, 150));
+    DrawRect(context, second, kBackgroundType, IntRect(150, 250, 100, 100));
+    DrawRect(context, second, kForegroundType, IntRect(150, 250, 100, 100));
+    EXPECT_EQ(0u, NumCachedNewItems());
+  }
 
   EXPECT_THAT(
-      GenerateRasterInvalidations(),
+      GetRasterInvalidations(),
       UnorderedElementsAre(
           RasterInvalidationInfo{&first, "first", IntRect(100, 100, 150, 150),
                                  PaintInvalidationReason::kAppeared},
@@ -351,11 +433,15 @@
   invalidator_.SetTracksRasterInvalidations(false);
 
   invalidator_.SetTracksRasterInvalidations(true);
-  InitRootChunk();
-  DrawRect(context, second, kBackgroundType, IntRect(150, 250, 100, 100));
-  DrawRect(context, second, kForegroundType, IntRect(150, 250, 100, 100));
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    DrawRect(context, second, kBackgroundType, IntRect(150, 250, 100, 100));
+    DrawRect(context, second, kForegroundType, IntRect(150, 250, 100, 100));
+  }
 
-  EXPECT_THAT(GenerateRasterInvalidations(),
+  EXPECT_THAT(GetRasterInvalidations(),
               UnorderedElementsAre(RasterInvalidationInfo{
                   &first, "first", IntRect(100, 100, 150, 150),
                   PaintInvalidationReason::kDisappeared}));
@@ -367,22 +453,29 @@
   FakeDisplayItemClient second("second");
   GraphicsContext context(GetPaintController());
 
-  InitRootChunk();
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 150, 150));
-  DrawRect(context, first, kForegroundType, IntRect(100, 100, 150, 150));
-  GenerateRasterInvalidations();
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 150, 150));
+    DrawRect(context, first, kForegroundType, IntRect(100, 100, 150, 150));
+  }
 
   invalidator_.SetTracksRasterInvalidations(true);
-  InitRootChunk();
-  first.Invalidate();
-  second.Invalidate();
-  DrawRect(context, first, kBackgroundType, IntRect(150, 150, 100, 100));
-  DrawRect(context, first, kForegroundType, IntRect(150, 150, 100, 100));
-  DrawRect(context, second, kBackgroundType, IntRect(200, 200, 50, 50));
-  DrawRect(context, second, kForegroundType, IntRect(200, 200, 50, 50));
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    first.Invalidate();
+    second.Invalidate();
+    DrawRect(context, first, kBackgroundType, IntRect(150, 150, 100, 100));
+    DrawRect(context, first, kForegroundType, IntRect(150, 150, 100, 100));
+    DrawRect(context, second, kBackgroundType, IntRect(200, 200, 50, 50));
+    DrawRect(context, second, kForegroundType, IntRect(200, 200, 50, 50));
+  }
 
   EXPECT_THAT(
-      GenerateRasterInvalidations(),
+      GetRasterInvalidations(),
       UnorderedElementsAre(
           RasterInvalidationInfo{&first, "first", IntRect(100, 100, 150, 150),
                                  PaintInvalidationReason::kFull},
@@ -391,14 +484,18 @@
   invalidator_.SetTracksRasterInvalidations(false);
 
   invalidator_.SetTracksRasterInvalidations(true);
-  InitRootChunk();
-  first.Invalidate();
-  second.Invalidate();
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 150, 150));
-  DrawRect(context, first, kForegroundType, IntRect(100, 100, 150, 150));
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    first.Invalidate();
+    second.Invalidate();
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 150, 150));
+    DrawRect(context, first, kForegroundType, IntRect(100, 100, 150, 150));
+  }
 
   EXPECT_THAT(
-      GenerateRasterInvalidations(),
+      GetRasterInvalidations(),
       UnorderedElementsAre(
           RasterInvalidationInfo{&first, "first", IntRect(100, 100, 150, 150),
                                  PaintInvalidationReason::kFull},
@@ -414,31 +511,38 @@
   FakeDisplayItemClient content2("content2");
   GraphicsContext context(GetPaintController());
 
-  InitRootChunk();
-  DrawRect(context, container1, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, content1, kForegroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, container1, kForegroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, container2, kBackgroundType, IntRect(100, 200, 100, 100));
-  DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
-  DrawRect(context, content2, kForegroundType, IntRect(100, 200, 50, 200));
-  DrawRect(context, container2, kForegroundType, IntRect(100, 200, 100, 100));
-  GenerateRasterInvalidations();
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    DrawRect(context, container1, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, content1, kForegroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, container1, kForegroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, container2, kBackgroundType, IntRect(100, 200, 100, 100));
+    DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
+    DrawRect(context, content2, kForegroundType, IntRect(100, 200, 50, 200));
+    DrawRect(context, container2, kForegroundType, IntRect(100, 200, 100, 100));
+  }
 
   // Simulate the situation when |container1| gets a z-index that is greater
   // than that of |container2|.
   invalidator_.SetTracksRasterInvalidations(true);
-  InitRootChunk();
-  DrawRect(context, container2, kBackgroundType, IntRect(100, 200, 100, 100));
-  DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
-  DrawRect(context, content2, kForegroundType, IntRect(100, 200, 50, 200));
-  DrawRect(context, container2, kForegroundType, IntRect(100, 200, 100, 100));
-  DrawRect(context, container1, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, content1, kForegroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, container1, kForegroundType, IntRect(100, 100, 100, 100));
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    DrawRect(context, container2, kBackgroundType, IntRect(100, 200, 100, 100));
+    DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
+    DrawRect(context, content2, kForegroundType, IntRect(100, 200, 50, 200));
+    DrawRect(context, container2, kForegroundType, IntRect(100, 200, 100, 100));
+    DrawRect(context, container1, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, content1, kForegroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, container1, kForegroundType, IntRect(100, 100, 100, 100));
+  }
 
-  EXPECT_THAT(GenerateRasterInvalidations(),
+  EXPECT_THAT(GetRasterInvalidations(),
               UnorderedElementsAre(
                   RasterInvalidationInfo{&container1, "container1",
                                          IntRect(100, 100, 100, 100),
@@ -456,32 +560,39 @@
   FakeDisplayItemClient content2("content2");
   GraphicsContext context(GetPaintController());
 
-  InitRootChunk();
-  DrawRect(context, container1, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, content1, kForegroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, container1, kForegroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, container2, kBackgroundType, IntRect(100, 200, 100, 100));
-  DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
-  DrawRect(context, content2, kForegroundType, IntRect(100, 200, 50, 200));
-  DrawRect(context, container2, kForegroundType, IntRect(100, 200, 100, 100));
-  GenerateRasterInvalidations();
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    DrawRect(context, container1, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, content1, kForegroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, container1, kForegroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, container2, kBackgroundType, IntRect(100, 200, 100, 100));
+    DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
+    DrawRect(context, content2, kForegroundType, IntRect(100, 200, 50, 200));
+    DrawRect(context, container2, kForegroundType, IntRect(100, 200, 100, 100));
+  }
 
   invalidator_.SetTracksRasterInvalidations(true);
-  InitRootChunk();
-  // Simulate the situation when |container1| gets a z-index that is greater
-  // than that of |container2|, and |container1| is invalidated.
-  container2.Invalidate();
-  DrawRect(context, container2, kBackgroundType, IntRect(100, 200, 100, 100));
-  DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
-  DrawRect(context, content2, kForegroundType, IntRect(100, 200, 50, 200));
-  DrawRect(context, container2, kForegroundType, IntRect(100, 200, 100, 100));
-  DrawRect(context, container1, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, content1, kForegroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, container1, kForegroundType, IntRect(100, 100, 100, 100));
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    // Simulate the situation when |container1| gets a z-index that is greater
+    // than that of |container2|, and |container1| is invalidated.
+    container2.Invalidate();
+    DrawRect(context, container2, kBackgroundType, IntRect(100, 200, 100, 100));
+    DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
+    DrawRect(context, content2, kForegroundType, IntRect(100, 200, 50, 200));
+    DrawRect(context, container2, kForegroundType, IntRect(100, 200, 100, 100));
+    DrawRect(context, container1, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, content1, kForegroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, container1, kForegroundType, IntRect(100, 100, 100, 100));
+  }
 
-  EXPECT_THAT(GenerateRasterInvalidations(),
+  EXPECT_THAT(GetRasterInvalidations(),
               UnorderedElementsAre(
                   RasterInvalidationInfo{&container1, "container1",
                                          IntRect(100, 100, 100, 100),
@@ -512,28 +623,35 @@
 
   PaintChunk::Id container1_id(container1, kBackgroundType);
   PaintChunk::Id container2_id(container2, kBackgroundType);
-  GetPaintController().UpdateCurrentPaintChunkProperties(&container1_id,
-                                                         container1_properties);
-  DrawRect(context, container1, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
-  GetPaintController().UpdateCurrentPaintChunkProperties(&container2_id,
-                                                         container2_properties);
-  DrawRect(context, container2, kBackgroundType, IntRect(100, 200, 100, 100));
-  DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
-  GenerateRasterInvalidations();
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    GetPaintController().UpdateCurrentPaintChunkProperties(
+        &container1_id, container1_properties);
+    DrawRect(context, container1, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
+    GetPaintController().UpdateCurrentPaintChunkProperties(
+        &container2_id, container2_properties);
+    DrawRect(context, container2, kBackgroundType, IntRect(100, 200, 100, 100));
+    DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
+  }
 
   // Move content2 into container1, without invalidation.
   invalidator_.SetTracksRasterInvalidations(true);
-  GetPaintController().UpdateCurrentPaintChunkProperties(&container1_id,
-                                                         container1_properties);
-  DrawRect(context, container1, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
-  GetPaintController().UpdateCurrentPaintChunkProperties(&container2_id,
-                                                         container2_properties);
-  DrawRect(context, container2, kBackgroundType, IntRect(100, 200, 100, 100));
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    GetPaintController().UpdateCurrentPaintChunkProperties(
+        &container1_id, container1_properties);
+    DrawRect(context, container1, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
+    GetPaintController().UpdateCurrentPaintChunkProperties(
+        &container2_id, container2_properties);
+    DrawRect(context, container2, kBackgroundType, IntRect(100, 200, 100, 100));
+  }
 
-  EXPECT_THAT(GenerateRasterInvalidations(),
+  EXPECT_THAT(GetRasterInvalidations(),
               UnorderedElementsAre(
                   RasterInvalidationInfo{&content2, "content2",
                                          IntRect(100, 200, 50, 200),
@@ -548,50 +666,60 @@
   FakeDisplayItemClient multicol("multicol");
   FakeDisplayItemClient content("content");
   GraphicsContext context(GetPaintController());
-
-  InitRootChunk();
   IntRect rect1(100, 100, 50, 50);
   IntRect rect2(150, 100, 50, 50);
   IntRect rect3(200, 100, 50, 50);
 
-  DrawRect(context, multicol, kBackgroundType, IntRect(100, 200, 100, 100));
-  GetPaintController().BeginSkippingCache();
-  DrawRect(context, content, kForegroundType, rect1);
-  DrawRect(context, content, kForegroundType, rect2);
-  GetPaintController().EndSkippingCache();
-  GenerateRasterInvalidations();
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    DrawRect(context, multicol, kBackgroundType, IntRect(100, 200, 100, 100));
+    GetPaintController().BeginSkippingCache();
+    DrawRect(context, content, kForegroundType, rect1);
+    DrawRect(context, content, kForegroundType, rect2);
+    GetPaintController().EndSkippingCache();
+  }
 
   invalidator_.SetTracksRasterInvalidations(true);
-  InitRootChunk();
-  // Draw again with nothing invalidated.
-  EXPECT_TRUE(ClientCacheIsValid(multicol));
-  DrawRect(context, multicol, kBackgroundType, IntRect(100, 200, 100, 100));
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    // Draw again with nothing invalidated.
+    EXPECT_TRUE(ClientCacheIsValid(multicol));
+    DrawRect(context, multicol, kBackgroundType, IntRect(100, 200, 100, 100));
 
-  GetPaintController().BeginSkippingCache();
-  DrawRect(context, content, kForegroundType, rect1);
-  DrawRect(context, content, kForegroundType, rect2);
-  GetPaintController().EndSkippingCache();
+    GetPaintController().BeginSkippingCache();
+    DrawRect(context, content, kForegroundType, rect1);
+    DrawRect(context, content, kForegroundType, rect2);
+    GetPaintController().EndSkippingCache();
+  }
 
-  EXPECT_THAT(GenerateRasterInvalidations(),
+  EXPECT_THAT(GetRasterInvalidations(),
               UnorderedElementsAre(RasterInvalidationInfo{
                   &content, "content", UnionRect(rect1, rect2),
                   PaintInvalidationReason::kUncacheable}));
   invalidator_.SetTracksRasterInvalidations(false);
 
   invalidator_.SetTracksRasterInvalidations(true);
-  InitRootChunk();
-  // Now the multicol becomes 3 columns and repaints.
-  multicol.Invalidate();
-  DrawRect(context, multicol, kBackgroundType, IntRect(100, 100, 100, 100));
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    // Now the multicol becomes 3 columns and repaints.
+    multicol.Invalidate();
+    DrawRect(context, multicol, kBackgroundType, IntRect(100, 100, 100, 100));
 
-  GetPaintController().BeginSkippingCache();
-  DrawRect(context, content, kForegroundType, rect1);
-  DrawRect(context, content, kForegroundType, rect2);
-  DrawRect(context, content, kForegroundType, rect3);
-  GetPaintController().EndSkippingCache();
+    GetPaintController().BeginSkippingCache();
+    DrawRect(context, content, kForegroundType, rect1);
+    DrawRect(context, content, kForegroundType, rect2);
+    DrawRect(context, content, kForegroundType, rect3);
+    GetPaintController().EndSkippingCache();
+  }
 
   EXPECT_THAT(
-      GenerateRasterInvalidations(),
+      GetRasterInvalidations(),
       UnorderedElementsAre(
           RasterInvalidationInfo{&multicol, "multicol",
                                  IntRect(100, 200, 100, 100),
@@ -613,25 +741,32 @@
   IntRect rect2(150, 100, 50, 50);
   IntRect rect3(200, 100, 50, 50);
 
-  InitRootChunk();
-  DrawRect(context, content, kBackgroundType, rect1);
-  GetPaintController().BeginSkippingCache();
-  DrawRect(context, content, kForegroundType, rect2);
-  GetPaintController().EndSkippingCache();
-  DrawRect(context, content, kForegroundType, rect3);
-  GenerateRasterInvalidations();
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    DrawRect(context, content, kBackgroundType, rect1);
+    GetPaintController().BeginSkippingCache();
+    DrawRect(context, content, kForegroundType, rect2);
+    GetPaintController().EndSkippingCache();
+    DrawRect(context, content, kForegroundType, rect3);
+  }
 
   invalidator_.SetTracksRasterInvalidations(true);
-  InitRootChunk();
-  // Draw again with nothing invalidated.
-  DrawRect(context, content, kBackgroundType, rect1);
-  GetPaintController().BeginSkippingCache();
-  DrawRect(context, content, kForegroundType, rect2);
-  GetPaintController().EndSkippingCache();
-  DrawRect(context, content, kForegroundType, rect3);
+  {
+    RasterInvalidationCycleScope cycle_scope(GetPaintController(),
+                                             invalidator_);
+    InitRootChunk();
+    // Draw again with nothing invalidated.
+    DrawRect(context, content, kBackgroundType, rect1);
+    GetPaintController().BeginSkippingCache();
+    DrawRect(context, content, kForegroundType, rect2);
+    GetPaintController().EndSkippingCache();
+    DrawRect(context, content, kForegroundType, rect3);
+  }
 
   EXPECT_THAT(
-      GenerateRasterInvalidations(),
+      GetRasterInvalidations(),
       UnorderedElementsAre(RasterInvalidationInfo{
           &content, "content", UnionRect(rect1, UnionRect(rect2, rect3)),
           PaintInvalidationReason::kUncacheable}));
diff --git a/third_party/blink/renderer/platform/graphics/paint/drawing_recorder_test.cc b/third_party/blink/renderer/platform/graphics/paint/drawing_recorder_test.cc
index 42eb5e04..f5295542 100644
--- a/third_party/blink/renderer/platform/graphics/paint/drawing_recorder_test.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/drawing_recorder_test.cc
@@ -22,9 +22,12 @@
 TEST_F(DrawingRecorderTest, Nothing) {
   FakeDisplayItemClient client;
   GraphicsContext context(GetPaintController());
-  InitRootChunk();
-  DrawNothing(context, client, kForegroundType);
-  CommitAndFinishCycle();
+  {
+    PaintController::CycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    DrawNothing(context, client, kForegroundType);
+    GetPaintController().CommitNewDisplayItems();
+  }
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&client, kForegroundType)));
   EXPECT_FALSE(
@@ -35,9 +38,12 @@
 TEST_F(DrawingRecorderTest, Rect) {
   FakeDisplayItemClient client;
   GraphicsContext context(GetPaintController());
-  InitRootChunk();
-  DrawRect(context, client, kForegroundType, kBounds);
-  CommitAndFinishCycle();
+  {
+    PaintController::CycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    DrawRect(context, client, kForegroundType, kBounds);
+    GetPaintController().CommitNewDisplayItems();
+  }
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&client, kForegroundType)));
 }
@@ -45,22 +51,28 @@
 TEST_F(DrawingRecorderTest, Cached) {
   FakeDisplayItemClient client;
   GraphicsContext context(GetPaintController());
-  InitRootChunk();
-  DrawNothing(context, client, kBackgroundType);
-  DrawRect(context, client, kForegroundType, kBounds);
-  CommitAndFinishCycle();
+  {
+    PaintController::CycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    DrawNothing(context, client, kBackgroundType);
+    DrawRect(context, client, kForegroundType, kBounds);
+    GetPaintController().CommitNewDisplayItems();
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&client, kBackgroundType),
                           IsSameId(&client, kForegroundType)));
 
-  InitRootChunk();
-  DrawNothing(context, client, kBackgroundType);
-  DrawRect(context, client, kForegroundType, kBounds);
+  {
+    PaintController::CycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    DrawNothing(context, client, kBackgroundType);
+    DrawRect(context, client, kForegroundType, kBounds);
 
-  EXPECT_EQ(2u, NumCachedNewItems());
+    EXPECT_EQ(2u, NumCachedNewItems());
 
-  CommitAndFinishCycle();
+    GetPaintController().CommitNewDisplayItems();
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&client, kBackgroundType),
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_chunker.cc b/third_party/blink/renderer/platform/graphics/paint/paint_chunker.cc
index 7c40c44..1737e21cf 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_chunker.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_chunker.cc
@@ -34,6 +34,26 @@
 }
 #endif
 
+void PaintChunker::StartMarkingClientsForValidation(
+    Vector<const DisplayItemClient*>& clients_to_validate) {
+#if DCHECK_IS_ON()
+  DCHECK(IsInInitialState());
+#endif
+  DCHECK(!clients_to_validate_);
+  clients_to_validate_ = &clients_to_validate;
+}
+
+void PaintChunker::MarkClientForValidation(const DisplayItemClient& client) {
+  if (clients_to_validate_ && !client.IsMarkedForValidation()) {
+    clients_to_validate_->push_back(&client);
+    client.MarkForValidation();
+  }
+}
+
+void PaintChunker::StopMarkingClientsForValidation() {
+  clients_to_validate_ = nullptr;
+}
+
 void PaintChunker::UpdateCurrentPaintChunkProperties(
     const PaintChunk::Id* chunk_id,
     const PropertyTreeStateOrAlias& properties) {
@@ -52,6 +72,7 @@
 void PaintChunker::AppendByMoving(PaintChunk&& chunk) {
   DCHECK(chunks_);
   FinalizeLastChunkProperties();
+  MarkClientForValidation(chunk.id.client);
   wtf_size_t next_chunk_begin_index =
       chunks_->IsEmpty() ? 0 : chunks_->back().end_index;
   chunks_->emplace_back(next_chunk_begin_index, std::move(chunk));
@@ -73,6 +94,7 @@
       next_chunk_id_.emplace(id);
     FinalizeLastChunkProperties();
     wtf_size_t begin = chunks_->IsEmpty() ? 0 : chunks_->back().end_index;
+    MarkClientForValidation(next_chunk_id_->client);
     chunks_->emplace_back(begin, begin, *next_chunk_id_, current_properties_,
                           current_effectively_invisible_);
     next_chunk_id_ = absl::nullopt;
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_chunker.h b/third_party/blink/renderer/platform/graphics/paint/paint_chunker.h
index 7a18b74..9373392 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_chunker.h
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_chunker.h
@@ -37,6 +37,11 @@
   bool IsInInitialState() const;
 #endif
 
+  void StartMarkingClientsForValidation(
+      Vector<const DisplayItemClient*>& clients_to_validate);
+  void MarkClientForValidation(const DisplayItemClient& client);
+  void StopMarkingClientsForValidation();
+
   const PropertyTreeStateOrAlias& CurrentPaintChunkProperties() const {
     return current_properties_;
   }
@@ -101,6 +106,7 @@
   void FinalizeLastChunkProperties();
 
   Vector<PaintChunk>* chunks_ = nullptr;
+  Vector<const DisplayItemClient*>* clients_to_validate_ = nullptr;
 
   // The id specified by UpdateCurrentPaintChunkProperties(). If it is not
   // nullopt, we will use it as the id of the next new chunk. Otherwise we will
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 45ebb7c..8f0d2fb 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc
@@ -233,6 +233,7 @@
 
   AppendSubsequenceByMoving(client, subsequence_index,
                             markers.start_chunk_index, markers.end_chunk_index);
+  MarkClientForValidation(client);
   return true;
 }
 
@@ -327,6 +328,13 @@
     under_invalidation_checker_->CheckNewItem();
 }
 
+void PaintController::MarkClientForValidation(const DisplayItemClient& client) {
+  if (clients_to_validate_ && !client.IsMarkedForValidation()) {
+    clients_to_validate_->push_back(&client);
+    client.MarkForValidation();
+  }
+}
+
 void PaintController::ProcessNewItem(DisplayItem& display_item) {
   if (IsSkippingCache() && usage_ == kMultiplePaints) {
     display_item.Client().Invalidate(PaintInvalidationReason::kUncacheable);
@@ -530,6 +538,7 @@
                                         ? PaintInvalidationReason::kUncacheable
                                         : PaintInvalidationReason::kNone);
     DCHECK(!item.IsCacheable() || ClientCacheIsValid(item.Client()));
+    MarkClientForValidation(item.Client());
 #if DCHECK_IS_ON()
     CheckNewItem(item);
 #endif
@@ -615,49 +624,42 @@
 #endif
 }
 
+PaintController::CycleScope::~CycleScope() {
+  for (const auto* client : clients_to_validate_) {
+    if (client->IsCacheable())
+      client->Validate();
+  }
+  for (auto* controller : controllers_)
+    controller->FinishCycle();
+}
+
+void PaintController::StartCycle(
+    Vector<const DisplayItemClient*>& clients_to_validate) {
+  // StartCycle() can only be called before the controller has painted anything.
+  DCHECK(new_paint_artifact_);
+  DCHECK(new_paint_artifact_->IsEmpty());
+  DCHECK(!clients_to_validate_);
+  if (usage_ == kTransient)
+    return;
+  clients_to_validate_ = &clients_to_validate;
+  paint_chunker_.StartMarkingClientsForValidation(clients_to_validate);
+  ReserveCapacity();
+}
+
 void PaintController::FinishCycle() {
+  DCHECK(usage_ == kTransient || clients_to_validate_);
+  clients_to_validate_ = nullptr;
+  paint_chunker_.StopMarkingClientsForValidation();
   if (usage_ == kTransient || !committed_)
     return;
 
   CheckNoNewPaint();
   committed_ = false;
 
-  // Validate display item clients that have validly cached subsequence or
-  // display items in this PaintController.
-  for (auto& item : current_subsequences_.tree) {
-    if (item.is_moved_from_cached_subsequence) {
-      // We don't need to validate the client of a cached subsequence, because
-      // it should be already valid. See http://crbug.com/1050090 for more
-      // details.
-      DCHECK(!item.client->IsCacheable() || ClientCacheIsValid(*item.client));
-      continue;
-    }
-    if (item.client->IsCacheable())
-      item.client->Validate();
-  }
   for (wtf_size_t i = 0; i < current_paint_artifact_->PaintChunks().size();
        i++) {
     auto& chunk = current_paint_artifact_->PaintChunks()[i];
     chunk.client_is_just_created = false;
-    const auto& client = chunk.id.client;
-    if (chunk.is_moved_from_cached_subsequence) {
-      // We don't need to validate the clients of paint chunks and display
-      // items that are moved from a cached subsequence, because they should be
-      // already valid. See http://crbug.com/1050090 for more details.
-#if DCHECK_IS_ON()
-      DCHECK(!chunk.is_cacheable || ClientCacheIsValid(client));
-      for (const auto& item : current_paint_artifact_->DisplayItemsInChunk(i))
-        DCHECK(!item.IsCacheable() || ClientCacheIsValid(item.Client()));
-#endif
-      continue;
-    }
-    if (client.IsCacheable())
-      client.Validate();
-
-    for (const auto& item : current_paint_artifact_->DisplayItemsInChunk(i)) {
-      if (item.Client().IsCacheable())
-        item.Client().Validate();
-    }
   }
 
 #if DCHECK_IS_ON()
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 5adfdf91..8a2b98c 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_controller.h
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_controller.h
@@ -82,15 +82,32 @@
   PaintController& operator=(const PaintController&) = delete;
   ~PaintController();
 
-  // Called before painting to optimize memory allocation by reserving space in
-  // |new_paint_artifact_| and |new_subsequences_| based on the size of the
-  // previous ones (|current_paint_artifact_| and |current_subsequences_|).
-  void ReserveCapacity();
-
 #if DCHECK_IS_ON()
   Usage GetUsage() const { return usage_; }
 #endif
 
+  class PLATFORM_EXPORT CycleScope {
+    STACK_ALLOCATED();
+
+   public:
+    CycleScope() = default;
+    explicit CycleScope(PaintController& controller) {
+      AddController(controller);
+    }
+    void AddController(PaintController& controller) {
+      controller.StartCycle(clients_to_validate_);
+      controllers_.push_back(&controller);
+    }
+    ~CycleScope();
+
+   protected:
+    Vector<PaintController*> controllers_;
+
+   private:
+    Vector<const DisplayItemClient*> clients_to_validate_;
+  };
+  friend class CycleScope;
+
   // These methods are called during painting.
 
   // Provide a new set of paint chunk properties to apply to recorded display
@@ -144,12 +161,15 @@
     return new_paint_artifact_->PaintChunks().back().bounds;
   }
 
+  void MarkClientForValidation(const DisplayItemClient& client);
+
   template <typename DisplayItemClass, typename... Args>
-  void CreateAndAppend(Args&&... args) {
+  void CreateAndAppend(const DisplayItemClient& client, Args&&... args) {
+    MarkClientForValidation(client);
     DisplayItemClass& display_item =
         new_paint_artifact_->GetDisplayItemList()
             .AllocateAndConstruct<DisplayItemClass>(
-                std::forward<Args>(args)...);
+                client, std::forward<Args>(args)...);
     display_item.SetFragment(current_fragment_);
     ProcessNewItem(display_item);
   }
@@ -189,14 +209,6 @@
   // artifact with the new paintings.
   void CommitNewDisplayItems();
 
-  // Called when the caller finishes updating a full document life cycle.
-  // The PaintController will cleanup data that will no longer be used for the
-  // next cycle, and update status to be ready for the next cycle.
-  // It updates caching status of DisplayItemClients, so if there are
-  // DisplayItemClients painting on multiple PaintControllers, we should call
-  // there FinishCycle() at the same time to ensure consistent caching status.
-  void FinishCycle();
-
   // Returns the approximate memory usage owned by this PaintController.
   size_t ApproximateUnsharedMemoryUsage() const;
 
@@ -291,6 +303,22 @@
   friend class PaintUnderInvalidationChecker;
   friend class GraphicsLayer;  // Temporary for ClientCacheIsValid().
 
+  // Called before painting to optimize memory allocation by reserving space in
+  // |new_paint_artifact_| and |new_subsequences_| based on the size of the
+  // previous ones (|current_paint_artifact_| and |current_subsequences_|).
+  void ReserveCapacity();
+
+  // Called at the beginning of a paint cycle, as defined by CycleScope.
+  void StartCycle(Vector<const DisplayItemClient*>& clients_to_validate);
+
+  // Called at the end of a paint cycle, as defined by CycleScope.
+  // The PaintController will cleanup data that will no longer be used for the
+  // next cycle, and update status to be ready for the next cycle.
+  // It updates caching status of DisplayItemClients, so if there are
+  // DisplayItemClients painting on multiple PaintControllers, we should call
+  // there FinishCycle() at the same time to ensure consistent caching status.
+  void FinishCycle();
+
   // True if all display items associated with the client are validly cached.
   // However, the current algorithm allows the following situations even if
   // ClientCacheIsValid() is true for a client during painting:
@@ -375,6 +403,7 @@
   // CommitNewDisplayItems().
   scoped_refptr<PaintArtifact> new_paint_artifact_;
   PaintChunker paint_chunker_;
+  Vector<const DisplayItemClient*>* clients_to_validate_ = nullptr;
 
   bool cache_is_all_invalid_ = true;
   bool committed_ = false;
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 4049044..b64b32d 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
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/platform/graphics/paint/paint_controller_test.h"
 
+#include "build/build_config.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
 #include "third_party/blink/renderer/platform/graphics/paint/display_item_cache_skipper.h"
@@ -25,6 +26,16 @@
                             public PaintControllerTestBase {
 };
 
+class CommitCycleScope : public PaintController::CycleScope {
+ public:
+  explicit CommitCycleScope(PaintController& controller)
+      : PaintController::CycleScope(controller) {}
+  ~CommitCycleScope() {
+    for (auto* controller : controllers_)
+      controller->CommitNewDisplayItems();
+  }
+};
+
 #define EXPECT_DEFAULT_ROOT_CHUNK(size)                               \
   EXPECT_THAT(GetPaintController().PaintChunks(),                     \
               ElementsAre(IsPaintChunk(0, size, DefaultRootChunkId(), \
@@ -37,14 +48,15 @@
                     kCompositeAfterPaint,
                     kUnderInvalidationChecking,
                     kCompositeAfterPaint | kUnderInvalidationChecking));
-
 TEST_P(PaintControllerTest, NestedRecorders) {
   GraphicsContext context(GetPaintController());
   FakeDisplayItemClient client("client");
-  InitRootChunk();
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
 
-  DrawRect(context, client, kBackgroundType, IntRect(100, 100, 200, 200));
-  CommitAndFinishCycle();
+    DrawRect(context, client, kBackgroundType, IntRect(100, 100, 200, 200));
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&client, kBackgroundType)));
@@ -55,16 +67,17 @@
   FakeDisplayItemClient first("first");
   FakeDisplayItemClient second("second");
   GraphicsContext context(GetPaintController());
-  InitRootChunk();
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
 
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 300, 300));
-  DrawRect(context, second, kBackgroundType, IntRect(100, 100, 200, 200));
-  DrawRect(context, first, kForegroundType, IntRect(100, 100, 300, 300));
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 300, 300));
+    DrawRect(context, second, kBackgroundType, IntRect(100, 100, 200, 200));
+    DrawRect(context, first, kForegroundType, IntRect(100, 100, 300, 300));
 
-  EXPECT_EQ(0u, NumCachedNewItems());
-  EXPECT_EQ(0u, NumCachedNewSubsequences());
-
-  CommitAndFinishCycle();
+    EXPECT_EQ(0u, NumCachedNewItems());
+    EXPECT_EQ(0u, NumCachedNewSubsequences());
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&first, kBackgroundType),
@@ -72,19 +85,20 @@
                           IsSameId(&first, kForegroundType)));
   EXPECT_DEFAULT_ROOT_CHUNK(3);
 
-  InitRootChunk();
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 300, 300));
-  DrawRect(context, first, kForegroundType, IntRect(100, 100, 300, 300));
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 300, 300));
+    DrawRect(context, first, kForegroundType, IntRect(100, 100, 300, 300));
 
-  EXPECT_EQ(2u, NumCachedNewItems());
-  EXPECT_EQ(0u, NumCachedNewSubsequences());
+    EXPECT_EQ(2u, NumCachedNewItems());
+    EXPECT_EQ(0u, NumCachedNewSubsequences());
 #if DCHECK_IS_ON()
-  EXPECT_EQ(1u, NumIndexedItems());
-  EXPECT_EQ(2u, NumSequentialMatches());
-  EXPECT_EQ(0u, NumOutOfOrderMatches());
+    EXPECT_EQ(1u, NumIndexedItems());
+    EXPECT_EQ(2u, NumSequentialMatches());
+    EXPECT_EQ(0u, NumOutOfOrderMatches());
 #endif
-
-  CommitAndFinishCycle();
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&first, kBackgroundType),
@@ -97,15 +111,17 @@
   FakeDisplayItemClient second("second");
   FakeDisplayItemClient unaffected("unaffected");
   GraphicsContext context(GetPaintController());
-  InitRootChunk();
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
 
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, first, kForegroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, second, kForegroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, unaffected, kBackgroundType, IntRect(300, 300, 10, 10));
-  DrawRect(context, unaffected, kForegroundType, IntRect(300, 300, 10, 10));
-  CommitAndFinishCycle();
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, first, kForegroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, second, kForegroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, unaffected, kBackgroundType, IntRect(300, 300, 10, 10));
+    DrawRect(context, unaffected, kForegroundType, IntRect(300, 300, 10, 10));
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&first, kBackgroundType),
@@ -115,24 +131,25 @@
                           IsSameId(&unaffected, kBackgroundType),
                           IsSameId(&unaffected, kForegroundType)));
 
-  InitRootChunk();
-  DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, second, kForegroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, first, kForegroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, unaffected, kBackgroundType, IntRect(300, 300, 10, 10));
-  DrawRect(context, unaffected, kForegroundType, IntRect(300, 300, 10, 10));
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, second, kForegroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, first, kForegroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, unaffected, kBackgroundType, IntRect(300, 300, 10, 10));
+    DrawRect(context, unaffected, kForegroundType, IntRect(300, 300, 10, 10));
 
-  EXPECT_EQ(6u, NumCachedNewItems());
-  EXPECT_EQ(0u, NumCachedNewSubsequences());
+    EXPECT_EQ(6u, NumCachedNewItems());
+    EXPECT_EQ(0u, NumCachedNewSubsequences());
 #if DCHECK_IS_ON()
-  EXPECT_EQ(2u, NumIndexedItems());  // first
-  EXPECT_EQ(5u,
-            NumSequentialMatches());  // second, first foreground, unaffected
-  EXPECT_EQ(1u, NumOutOfOrderMatches());  // first
+    EXPECT_EQ(2u, NumIndexedItems());  // first
+    EXPECT_EQ(5u,
+              NumSequentialMatches());  // second, first foreground, unaffected
+    EXPECT_EQ(1u, NumOutOfOrderMatches());  // first
 #endif
-
-  CommitAndFinishCycle();
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&second, kBackgroundType),
@@ -149,15 +166,17 @@
   FakeDisplayItemClient second("second");
   FakeDisplayItemClient unaffected("unaffected");
   GraphicsContext context(GetPaintController());
-  InitRootChunk();
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
 
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, first, kForegroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, second, kForegroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, unaffected, kBackgroundType, IntRect(300, 300, 10, 10));
-  DrawRect(context, unaffected, kForegroundType, IntRect(300, 300, 10, 10));
-  CommitAndFinishCycle();
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, first, kForegroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, second, kForegroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, unaffected, kBackgroundType, IntRect(300, 300, 10, 10));
+    DrawRect(context, unaffected, kForegroundType, IntRect(300, 300, 10, 10));
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&first, kBackgroundType),
@@ -167,24 +186,25 @@
                           IsSameId(&unaffected, kBackgroundType),
                           IsSameId(&unaffected, kForegroundType)));
 
-  InitRootChunk();
-  first.Invalidate();
-  DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, second, kForegroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, first, kForegroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, unaffected, kBackgroundType, IntRect(300, 300, 10, 10));
-  DrawRect(context, unaffected, kForegroundType, IntRect(300, 300, 10, 10));
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    first.Invalidate();
+    DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, second, kForegroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, first, kForegroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, unaffected, kBackgroundType, IntRect(300, 300, 10, 10));
+    DrawRect(context, unaffected, kForegroundType, IntRect(300, 300, 10, 10));
 
-  EXPECT_EQ(4u, NumCachedNewItems());
-  EXPECT_EQ(0u, NumCachedNewSubsequences());
+    EXPECT_EQ(4u, NumCachedNewItems());
+    EXPECT_EQ(0u, NumCachedNewSubsequences());
 #if DCHECK_IS_ON()
-  EXPECT_EQ(2u, NumIndexedItems());
-  EXPECT_EQ(4u, NumSequentialMatches());  // second, unaffected
-  EXPECT_EQ(0u, NumOutOfOrderMatches());
+    EXPECT_EQ(2u, NumIndexedItems());
+    EXPECT_EQ(4u, NumSequentialMatches());  // second, unaffected
+    EXPECT_EQ(0u, NumOutOfOrderMatches());
 #endif
-
-  CommitAndFinishCycle();
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&second, kBackgroundType),
@@ -201,31 +221,34 @@
   FakeDisplayItemClient second("second");
   FakeDisplayItemClient third("third");
   GraphicsContext context(GetPaintController());
-  InitRootChunk();
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
 
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
-  CommitAndFinishCycle();
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&first, kBackgroundType),
                           IsSameId(&second, kBackgroundType)));
 
-  InitRootChunk();
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
 
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, third, kBackgroundType, IntRect(125, 100, 200, 50));
-  DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, third, kBackgroundType, IntRect(125, 100, 200, 50));
+    DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
 
-  EXPECT_EQ(2u, NumCachedNewItems());
-  EXPECT_EQ(0u, NumCachedNewSubsequences());
+    EXPECT_EQ(2u, NumCachedNewItems());
+    EXPECT_EQ(0u, NumCachedNewSubsequences());
 #if DCHECK_IS_ON()
-  EXPECT_EQ(0u, NumIndexedItems());
-  EXPECT_EQ(2u, NumSequentialMatches());  // first, second
-  EXPECT_EQ(0u, NumOutOfOrderMatches());
+    EXPECT_EQ(0u, NumIndexedItems());
+    EXPECT_EQ(2u, NumSequentialMatches());  // first, second
+    EXPECT_EQ(0u, NumOutOfOrderMatches());
 #endif
-
-  CommitAndFinishCycle();
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&first, kBackgroundType),
@@ -239,15 +262,17 @@
   FakeDisplayItemClient second("second");
   FakeDisplayItemClient third("third");
   GraphicsContext context(GetPaintController());
-  InitRootChunk();
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
 
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, third, kBackgroundType, IntRect(300, 100, 50, 50));
-  DrawRect(context, first, kForegroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, second, kForegroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, third, kForegroundType, IntRect(300, 100, 50, 50));
-  CommitAndFinishCycle();
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, third, kBackgroundType, IntRect(300, 100, 50, 50));
+    DrawRect(context, first, kForegroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, second, kForegroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, third, kForegroundType, IntRect(300, 100, 50, 50));
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&first, kBackgroundType),
@@ -257,25 +282,26 @@
                           IsSameId(&second, kForegroundType),
                           IsSameId(&third, kForegroundType)));
 
-  InitRootChunk();
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
 
-  second.Invalidate();
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, third, kBackgroundType, IntRect(300, 100, 50, 50));
-  DrawRect(context, first, kForegroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, second, kForegroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, third, kForegroundType, IntRect(300, 100, 50, 50));
+    second.Invalidate();
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, second, kBackgroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, third, kBackgroundType, IntRect(300, 100, 50, 50));
+    DrawRect(context, first, kForegroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, second, kForegroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, third, kForegroundType, IntRect(300, 100, 50, 50));
 
-  EXPECT_EQ(4u, NumCachedNewItems());
-  EXPECT_EQ(0u, NumCachedNewSubsequences());
+    EXPECT_EQ(4u, NumCachedNewItems());
+    EXPECT_EQ(0u, NumCachedNewSubsequences());
 #if DCHECK_IS_ON()
-  EXPECT_EQ(2u, NumIndexedItems());
-  EXPECT_EQ(4u, NumSequentialMatches());
-  EXPECT_EQ(0u, NumOutOfOrderMatches());
+    EXPECT_EQ(2u, NumIndexedItems());
+    EXPECT_EQ(4u, NumSequentialMatches());
+    EXPECT_EQ(0u, NumOutOfOrderMatches());
 #endif
-
-  CommitAndFinishCycle();
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&first, kBackgroundType),
@@ -291,27 +317,31 @@
   FakeDisplayItemClient first("first");
   FakeDisplayItemClient second("second");
   GraphicsContext context(GetPaintController());
-  InitRootChunk();
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
 
-  DrawRect(context, second, kBackgroundType, IntRect(200, 200, 50, 50));
-  DrawRect(context, second, kForegroundType, IntRect(200, 200, 50, 50));
-  CommitAndFinishCycle();
+    DrawRect(context, second, kBackgroundType, IntRect(200, 200, 50, 50));
+    DrawRect(context, second, kForegroundType, IntRect(200, 200, 50, 50));
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&second, kBackgroundType),
                           IsSameId(&second, kForegroundType)));
 
-  InitRootChunk();
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
 
-  first.Invalidate();
-  second.Invalidate();
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 150, 150));
-  DrawRect(context, first, kForegroundType, IntRect(100, 100, 150, 150));
-  DrawRect(context, second, kBackgroundType, IntRect(150, 250, 100, 100));
-  DrawRect(context, second, kForegroundType, IntRect(150, 250, 100, 100));
-  EXPECT_EQ(0u, NumCachedNewItems());
-  EXPECT_EQ(0u, NumCachedNewSubsequences());
-  CommitAndFinishCycle();
+    first.Invalidate();
+    second.Invalidate();
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 150, 150));
+    DrawRect(context, first, kForegroundType, IntRect(100, 100, 150, 150));
+    DrawRect(context, second, kBackgroundType, IntRect(150, 250, 100, 100));
+    DrawRect(context, second, kForegroundType, IntRect(150, 250, 100, 100));
+    EXPECT_EQ(0u, NumCachedNewItems());
+    EXPECT_EQ(0u, NumCachedNewSubsequences());
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&first, kBackgroundType),
@@ -320,19 +350,20 @@
                           IsSameId(&second, kForegroundType)));
   EXPECT_DEFAULT_ROOT_CHUNK(4);
 
-  InitRootChunk();
-  DrawRect(context, second, kBackgroundType, IntRect(150, 250, 100, 100));
-  DrawRect(context, second, kForegroundType, IntRect(150, 250, 100, 100));
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    DrawRect(context, second, kBackgroundType, IntRect(150, 250, 100, 100));
+    DrawRect(context, second, kForegroundType, IntRect(150, 250, 100, 100));
 
-  EXPECT_EQ(2u, NumCachedNewItems());
-  EXPECT_EQ(0u, NumCachedNewSubsequences());
+    EXPECT_EQ(2u, NumCachedNewItems());
+    EXPECT_EQ(0u, NumCachedNewSubsequences());
 #if DCHECK_IS_ON()
-  EXPECT_EQ(2u, NumIndexedItems());
-  EXPECT_EQ(2u, NumSequentialMatches());
-  EXPECT_EQ(0u, NumOutOfOrderMatches());
+    EXPECT_EQ(2u, NumIndexedItems());
+    EXPECT_EQ(2u, NumSequentialMatches());
+    EXPECT_EQ(0u, NumOutOfOrderMatches());
 #endif
-
-  CommitAndFinishCycle();
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&second, kBackgroundType),
@@ -344,27 +375,31 @@
   FakeDisplayItemClient first("first");
   FakeDisplayItemClient second("second");
   GraphicsContext context(GetPaintController());
-  InitRootChunk();
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
 
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 150, 150));
-  DrawRect(context, first, kForegroundType, IntRect(100, 100, 150, 150));
-  CommitAndFinishCycle();
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 150, 150));
+    DrawRect(context, first, kForegroundType, IntRect(100, 100, 150, 150));
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&first, kBackgroundType),
                           IsSameId(&first, kForegroundType)));
 
-  InitRootChunk();
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
 
-  first.Invalidate();
-  second.Invalidate();
-  DrawRect(context, first, kBackgroundType, IntRect(150, 150, 100, 100));
-  DrawRect(context, first, kForegroundType, IntRect(150, 150, 100, 100));
-  DrawRect(context, second, kBackgroundType, IntRect(200, 200, 50, 50));
-  DrawRect(context, second, kForegroundType, IntRect(200, 200, 50, 50));
-  EXPECT_EQ(0u, NumCachedNewItems());
-  EXPECT_EQ(0u, NumCachedNewSubsequences());
-  CommitAndFinishCycle();
+    first.Invalidate();
+    second.Invalidate();
+    DrawRect(context, first, kBackgroundType, IntRect(150, 150, 100, 100));
+    DrawRect(context, first, kForegroundType, IntRect(150, 150, 100, 100));
+    DrawRect(context, second, kBackgroundType, IntRect(200, 200, 50, 50));
+    DrawRect(context, second, kForegroundType, IntRect(200, 200, 50, 50));
+    EXPECT_EQ(0u, NumCachedNewItems());
+    EXPECT_EQ(0u, NumCachedNewSubsequences());
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&first, kBackgroundType),
@@ -373,14 +408,16 @@
                           IsSameId(&second, kForegroundType)));
   EXPECT_DEFAULT_ROOT_CHUNK(4);
 
-  InitRootChunk();
-  first.Invalidate();
-  second.Invalidate();
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 150, 150));
-  DrawRect(context, first, kForegroundType, IntRect(100, 100, 150, 150));
-  EXPECT_EQ(0u, NumCachedNewItems());
-  EXPECT_EQ(0u, NumCachedNewSubsequences());
-  CommitAndFinishCycle();
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    first.Invalidate();
+    second.Invalidate();
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 150, 150));
+    DrawRect(context, first, kForegroundType, IntRect(100, 100, 150, 150));
+    EXPECT_EQ(0u, NumCachedNewItems());
+    EXPECT_EQ(0u, NumCachedNewSubsequences());
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&first, kBackgroundType),
@@ -392,11 +429,13 @@
   FakeDisplayItemClient first("first");
   FakeDisplayItemClient second("second");
   GraphicsContext context(GetPaintController());
-  InitRootChunk();
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
 
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 150, 150));
-  DrawRect(context, second, kBackgroundType, IntRect(100, 100, 150, 150));
-  CommitAndFinishCycle();
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 150, 150));
+    DrawRect(context, second, kBackgroundType, IntRect(100, 100, 150, 150));
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&first, kBackgroundType),
@@ -414,10 +453,12 @@
   EXPECT_FALSE(ClientCacheIsValid(first));
   EXPECT_TRUE(ClientCacheIsValid(second));
 
-  InitRootChunk();
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 150, 150));
-  DrawRect(context, second, kBackgroundType, IntRect(100, 100, 150, 150));
-  CommitAndFinishCycle();
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 150, 150));
+    DrawRect(context, second, kBackgroundType, IntRect(100, 100, 150, 150));
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&first, kBackgroundType),
@@ -447,17 +488,19 @@
   FakeDisplayItemClient container2("container2");
   FakeDisplayItemClient content2("content2");
   GraphicsContext context(GetPaintController());
-  InitRootChunk();
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
 
-  DrawRect(context, container1, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, content1, kForegroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, container1, kForegroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, container2, kBackgroundType, IntRect(100, 200, 100, 100));
-  DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
-  DrawRect(context, content2, kForegroundType, IntRect(100, 200, 50, 200));
-  DrawRect(context, container2, kForegroundType, IntRect(100, 200, 100, 100));
-  CommitAndFinishCycle();
+    DrawRect(context, container1, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, content1, kForegroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, container1, kForegroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, container2, kBackgroundType, IntRect(100, 200, 100, 100));
+    DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
+    DrawRect(context, content2, kForegroundType, IntRect(100, 200, 50, 200));
+    DrawRect(context, container2, kForegroundType, IntRect(100, 200, 100, 100));
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&container1, kBackgroundType),
@@ -469,19 +512,21 @@
                           IsSameId(&content2, kForegroundType),
                           IsSameId(&container2, kForegroundType)));
 
-  InitRootChunk();
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
 
-  // Simulate the situation when |container1| gets a z-index that is greater
-  // than that of |container2|.
-  DrawRect(context, container2, kBackgroundType, IntRect(100, 200, 100, 100));
-  DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
-  DrawRect(context, content2, kForegroundType, IntRect(100, 200, 50, 200));
-  DrawRect(context, container2, kForegroundType, IntRect(100, 200, 100, 100));
-  DrawRect(context, container1, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, content1, kForegroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, container1, kForegroundType, IntRect(100, 100, 100, 100));
-  CommitAndFinishCycle();
+    // Simulate the situation when |container1| gets a z-index that is greater
+    // than that of |container2|.
+    DrawRect(context, container2, kBackgroundType, IntRect(100, 200, 100, 100));
+    DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
+    DrawRect(context, content2, kForegroundType, IntRect(100, 200, 50, 200));
+    DrawRect(context, container2, kForegroundType, IntRect(100, 200, 100, 100));
+    DrawRect(context, container1, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, content1, kForegroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, container1, kForegroundType, IntRect(100, 100, 100, 100));
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&container2, kBackgroundType),
@@ -501,17 +546,19 @@
   FakeDisplayItemClient container2("container2");
   FakeDisplayItemClient content2("content2");
   GraphicsContext context(GetPaintController());
-  InitRootChunk();
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
 
-  DrawRect(context, container1, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, content1, kForegroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, container1, kForegroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, container2, kBackgroundType, IntRect(100, 200, 100, 100));
-  DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
-  DrawRect(context, content2, kForegroundType, IntRect(100, 200, 50, 200));
-  DrawRect(context, container2, kForegroundType, IntRect(100, 200, 100, 100));
-  CommitAndFinishCycle();
+    DrawRect(context, container1, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, content1, kForegroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, container1, kForegroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, container2, kBackgroundType, IntRect(100, 200, 100, 100));
+    DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
+    DrawRect(context, content2, kForegroundType, IntRect(100, 200, 50, 200));
+    DrawRect(context, container2, kForegroundType, IntRect(100, 200, 100, 100));
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&container1, kBackgroundType),
@@ -523,20 +570,22 @@
                           IsSameId(&content2, kForegroundType),
                           IsSameId(&container2, kForegroundType)));
 
-  InitRootChunk();
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
 
-  // Simulate the situation when |container1| gets a z-index that is greater
-  // than that of |container2|, and |container1| is invalidated.
-  container1.Invalidate();
-  DrawRect(context, container2, kBackgroundType, IntRect(100, 200, 100, 100));
-  DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
-  DrawRect(context, content2, kForegroundType, IntRect(100, 200, 50, 200));
-  DrawRect(context, container2, kForegroundType, IntRect(100, 200, 100, 100));
-  DrawRect(context, container1, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, content1, kForegroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, container1, kForegroundType, IntRect(100, 100, 100, 100));
-  CommitAndFinishCycle();
+    // Simulate the situation when |container1| gets a z-index that is greater
+    // than that of |container2|, and |container1| is invalidated.
+    container1.Invalidate();
+    DrawRect(context, container2, kBackgroundType, IntRect(100, 200, 100, 100));
+    DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
+    DrawRect(context, content2, kForegroundType, IntRect(100, 200, 50, 200));
+    DrawRect(context, container2, kForegroundType, IntRect(100, 200, 100, 100));
+    DrawRect(context, container1, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, content1, kForegroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, container1, kForegroundType, IntRect(100, 100, 100, 100));
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&container2, kBackgroundType),
@@ -554,30 +603,34 @@
   if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled())
     return;
 
-  GraphicsContext context(GetPaintController());
-
   FakeDisplayItemClient root("root");
   auto root_properties = DefaultPaintChunkProperties();
   PaintChunk::Id root_id(root, DisplayItem::kCaret);
-  GetPaintController().UpdateCurrentPaintChunkProperties(&root_id,
-                                                         root_properties);
-  DrawRect(context, root, kBackgroundType, IntRect(100, 100, 100, 100));
-
   FakeDisplayItemClient container("container");
   auto container_properties = DefaultPaintChunkProperties();
   PaintChunk::Id container_id(container, DisplayItem::kCaret);
+  GraphicsContext context(GetPaintController());
+
   {
-    SubsequenceRecorder r(context, container);
-    GetPaintController().UpdateCurrentPaintChunkProperties(
-        &container_id, container_properties);
-    DrawRect(context, container, kBackgroundType, IntRect(100, 100, 100, 100));
-    DrawRect(context, container, kForegroundType, IntRect(100, 100, 100, 100));
+    CommitCycleScope cycle_scope(GetPaintController());
+
+    GetPaintController().UpdateCurrentPaintChunkProperties(&root_id,
+                                                           root_properties);
+    DrawRect(context, root, kBackgroundType, IntRect(100, 100, 100, 100));
+
+    {
+      SubsequenceRecorder r(context, container);
+      GetPaintController().UpdateCurrentPaintChunkProperties(
+          &container_id, container_properties);
+      DrawRect(context, container, kBackgroundType,
+               IntRect(100, 100, 100, 100));
+      DrawRect(context, container, kForegroundType,
+               IntRect(100, 100, 100, 100));
+    }
+
+    DrawRect(context, root, kForegroundType, IntRect(100, 100, 100, 100));
   }
 
-  DrawRect(context, root, kForegroundType, IntRect(100, 100, 100, 100));
-
-  CommitAndFinishCycle();
-
   // Even though the paint properties match, |container| should receive its
   // own PaintChunk because it created a subsequence.
   EXPECT_THAT(
@@ -587,12 +640,14 @@
                   IsPaintChunk(3, 4, PaintChunk::Id(root, kForegroundType),
                                root_properties)));
 
-  GetPaintController().UpdateCurrentPaintChunkProperties(&root_id,
-                                                         root_properties);
-  DrawRect(context, root, kBackgroundType, IntRect(100, 100, 100, 100));
-  EXPECT_TRUE(GetPaintController().UseCachedSubsequenceIfPossible(container));
-  DrawRect(context, root, kForegroundType, IntRect(100, 100, 100, 100));
-  CommitAndFinishCycle();
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    GetPaintController().UpdateCurrentPaintChunkProperties(&root_id,
+                                                           root_properties);
+    DrawRect(context, root, kBackgroundType, IntRect(100, 100, 100, 100));
+    EXPECT_TRUE(GetPaintController().UseCachedSubsequenceIfPossible(container));
+    DrawRect(context, root, kForegroundType, IntRect(100, 100, 100, 100));
+  }
 
   // |container| should still receive its own PaintChunk because it is a cached
   // subsequence.
@@ -622,26 +677,33 @@
   container2_properties.SetEffect(*container2_effect);
 
   {
-    GetPaintController().UpdateCurrentPaintChunkProperties(
-        &container1_id, container1_properties);
+    CommitCycleScope cycle_scope(GetPaintController());
 
-    SubsequenceRecorder r(context, container1);
-    DrawRect(context, container1, kBackgroundType, IntRect(100, 100, 100, 100));
-    DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
-    DrawRect(context, content1, kForegroundType, IntRect(100, 100, 50, 200));
-    DrawRect(context, container1, kForegroundType, IntRect(100, 100, 100, 100));
-  }
-  {
-    GetPaintController().UpdateCurrentPaintChunkProperties(
-        &container2_id, container2_properties);
+    {
+      GetPaintController().UpdateCurrentPaintChunkProperties(
+          &container1_id, container1_properties);
 
-    SubsequenceRecorder r(context, container2);
-    DrawRect(context, container2, kBackgroundType, IntRect(100, 200, 100, 100));
-    DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
-    DrawRect(context, content2, kForegroundType, IntRect(100, 200, 50, 200));
-    DrawRect(context, container2, kForegroundType, IntRect(100, 200, 100, 100));
+      SubsequenceRecorder r(context, container1);
+      DrawRect(context, container1, kBackgroundType,
+               IntRect(100, 100, 100, 100));
+      DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
+      DrawRect(context, content1, kForegroundType, IntRect(100, 100, 50, 200));
+      DrawRect(context, container1, kForegroundType,
+               IntRect(100, 100, 100, 100));
+    }
+    {
+      GetPaintController().UpdateCurrentPaintChunkProperties(
+          &container2_id, container2_properties);
+
+      SubsequenceRecorder r(context, container2);
+      DrawRect(context, container2, kBackgroundType,
+               IntRect(100, 200, 100, 100));
+      DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
+      DrawRect(context, content2, kForegroundType, IntRect(100, 200, 50, 200));
+      DrawRect(context, container2, kForegroundType,
+               IntRect(100, 200, 100, 100));
+    }
   }
-  CommitAndFinishCycle();
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&container1, kBackgroundType),
@@ -664,56 +726,61 @@
       ElementsAre(IsPaintChunk(0, 4, container1_id, container1_properties),
                   IsPaintChunk(4, 8, container2_id, container2_properties)));
 
-  // Simulate the situation when |container1| gets a z-index that is greater
-  // than that of |container2|.
-  if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled()) {
-    // When under-invalidation-checking is enabled,
-    // UseCachedSubsequenceIfPossible is forced off, and the client is expected
-    // to create the same painting as in the previous paint.
-    EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
-        context, container2));
-    {
-      GetPaintController().UpdateCurrentPaintChunkProperties(
-          &container2_id, container2_properties);
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    // Simulate the situation when |container1| gets a z-index that is greater
+    // than that of |container2|.
+    if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled()) {
+      // When under-invalidation-checking is enabled,
+      // UseCachedSubsequenceIfPossible is forced off, and the client is
+      // expected to create the same painting as in the previous paint.
+      EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
+          context, container2));
+      {
+        GetPaintController().UpdateCurrentPaintChunkProperties(
+            &container2_id, container2_properties);
 
-      SubsequenceRecorder r(context, container2);
-      DrawRect(context, container2, kBackgroundType,
-               IntRect(100, 200, 100, 100));
-      DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
-      DrawRect(context, content2, kForegroundType, IntRect(100, 200, 50, 200));
-      DrawRect(context, container2, kForegroundType,
-               IntRect(100, 200, 100, 100));
+        SubsequenceRecorder r(context, container2);
+        DrawRect(context, container2, kBackgroundType,
+                 IntRect(100, 200, 100, 100));
+        DrawRect(context, content2, kBackgroundType,
+                 IntRect(100, 200, 50, 200));
+        DrawRect(context, content2, kForegroundType,
+                 IntRect(100, 200, 50, 200));
+        DrawRect(context, container2, kForegroundType,
+                 IntRect(100, 200, 100, 100));
+      }
+      EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
+          context, container1));
+      {
+        GetPaintController().UpdateCurrentPaintChunkProperties(
+            &container1_id, container1_properties);
+
+        SubsequenceRecorder r(context, container1);
+        DrawRect(context, container1, kBackgroundType,
+                 IntRect(100, 100, 100, 100));
+        DrawRect(context, content1, kBackgroundType,
+                 IntRect(100, 100, 50, 200));
+        DrawRect(context, content1, kForegroundType,
+                 IntRect(100, 100, 50, 200));
+        DrawRect(context, container1, kForegroundType,
+                 IntRect(100, 100, 100, 100));
+      }
+    } else {
+      EXPECT_TRUE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
+          context, container2));
+      EXPECT_TRUE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
+          context, container1));
     }
-    EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
-        context, container1));
-    {
-      GetPaintController().UpdateCurrentPaintChunkProperties(
-          &container1_id, container1_properties);
 
-      SubsequenceRecorder r(context, container1);
-      DrawRect(context, container1, kBackgroundType,
-               IntRect(100, 100, 100, 100));
-      DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
-      DrawRect(context, content1, kForegroundType, IntRect(100, 100, 50, 200));
-      DrawRect(context, container1, kForegroundType,
-               IntRect(100, 100, 100, 100));
-    }
-  } else {
-    EXPECT_TRUE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
-        context, container2));
-    EXPECT_TRUE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
-        context, container1));
-  }
-
-  EXPECT_EQ(8u, NumCachedNewItems());
-  EXPECT_EQ(2u, NumCachedNewSubsequences());
+    EXPECT_EQ(8u, NumCachedNewItems());
+    EXPECT_EQ(2u, NumCachedNewSubsequences());
 #if DCHECK_IS_ON()
-  EXPECT_EQ(0u, NumIndexedItems());
-  EXPECT_EQ(0u, NumSequentialMatches());
-  EXPECT_EQ(0u, NumOutOfOrderMatches());
+    EXPECT_EQ(0u, NumIndexedItems());
+    EXPECT_EQ(0u, NumSequentialMatches());
+    EXPECT_EQ(0u, NumOutOfOrderMatches());
 #endif
-
-  CommitAndFinishCycle();
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&container2, kBackgroundType),
@@ -746,18 +813,22 @@
   PaintChunk::Id container2_id(container2, kBackgroundType);
   PaintChunk::Id content2_id(content2, kBackgroundType);
 
-  InitRootChunk();
-
-  DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
   {
-    SubsequenceRecorder r(context, container2);
-    DrawRect(context, container2, kBackgroundType, IntRect(100, 200, 100, 100));
-    DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
-    DrawRect(context, content2, kForegroundType, IntRect(100, 200, 50, 200));
-    DrawRect(context, container2, kForegroundType, IntRect(100, 200, 100, 100));
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+
+    DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
+    {
+      SubsequenceRecorder r(context, container2);
+      DrawRect(context, container2, kBackgroundType,
+               IntRect(100, 200, 100, 100));
+      DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
+      DrawRect(context, content2, kForegroundType, IntRect(100, 200, 50, 200));
+      DrawRect(context, container2, kForegroundType,
+               IntRect(100, 200, 100, 100));
+    }
+    DrawRect(context, content1, kForegroundType, IntRect(100, 100, 50, 200));
   }
-  DrawRect(context, content1, kForegroundType, IntRect(100, 100, 50, 200));
-  CommitAndFinishCycle();
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&content1, kBackgroundType),
@@ -783,42 +854,45 @@
 
   // Simulate the situation when |container2| gets a z-index that is smaller
   // than that of |content1|.
-  InitRootChunk();
-  if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled()) {
-    // When under-invalidation-checking is enabled,
-    // UseCachedSubsequenceIfPossible is forced off, and the client is expected
-    // to create the same painting as in the previous paint.
-    EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
-        context, container2));
-    {
-      SubsequenceRecorder r(context, container2);
-      DrawRect(context, container2, kBackgroundType,
-               IntRect(100, 200, 100, 100));
-      DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
-      DrawRect(context, content2, kForegroundType, IntRect(100, 200, 50, 200));
-      DrawRect(context, container2, kForegroundType,
-               IntRect(100, 200, 100, 100));
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled()) {
+      // When under-invalidation-checking is enabled,
+      // UseCachedSubsequenceIfPossible is forced off, and the client is
+      // expected to create the same painting as in the previous paint.
+      EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
+          context, container2));
+      {
+        SubsequenceRecorder r(context, container2);
+        DrawRect(context, container2, kBackgroundType,
+                 IntRect(100, 200, 100, 100));
+        DrawRect(context, content2, kBackgroundType,
+                 IntRect(100, 200, 50, 200));
+        DrawRect(context, content2, kForegroundType,
+                 IntRect(100, 200, 50, 200));
+        DrawRect(context, container2, kForegroundType,
+                 IntRect(100, 200, 100, 100));
+      }
+      DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
+      DrawRect(context, content1, kForegroundType, IntRect(100, 100, 50, 200));
+    } else {
+      EXPECT_TRUE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
+          context, container2));
+      EXPECT_TRUE(DrawingRecorder::UseCachedDrawingIfPossible(context, content1,
+                                                              kBackgroundType));
+      EXPECT_TRUE(DrawingRecorder::UseCachedDrawingIfPossible(context, content1,
+                                                              kForegroundType));
     }
-    DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
-    DrawRect(context, content1, kForegroundType, IntRect(100, 100, 50, 200));
-  } else {
-    EXPECT_TRUE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
-        context, container2));
-    EXPECT_TRUE(DrawingRecorder::UseCachedDrawingIfPossible(context, content1,
-                                                            kBackgroundType));
-    EXPECT_TRUE(DrawingRecorder::UseCachedDrawingIfPossible(context, content1,
-                                                            kForegroundType));
-  }
 
-  EXPECT_EQ(6u, NumCachedNewItems());
-  EXPECT_EQ(1u, NumCachedNewSubsequences());
+    EXPECT_EQ(6u, NumCachedNewItems());
+    EXPECT_EQ(1u, NumCachedNewSubsequences());
 #if DCHECK_IS_ON()
-  EXPECT_EQ(0u, NumIndexedItems());
-  EXPECT_EQ(2u, NumSequentialMatches());
-  EXPECT_EQ(0u, NumOutOfOrderMatches());
+    EXPECT_EQ(0u, NumIndexedItems());
+    EXPECT_EQ(2u, NumSequentialMatches());
+    EXPECT_EQ(0u, NumOutOfOrderMatches());
 #endif
-
-  CommitAndFinishCycle();
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&container2, kBackgroundType),
@@ -854,16 +928,18 @@
   PaintChunk::Id content3_id(content3, kBackgroundType);
   IntRect rect(100, 100, 50, 200);
 
-  InitRootChunk();
-
-  DrawRect(context, content1a, kBackgroundType, rect);
-  DrawRect(context, content1b, kBackgroundType, rect);
   {
-    SubsequenceRecorder r(context, container2);
-    DrawRect(context, container2, kBackgroundType, rect);
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+
+    DrawRect(context, content1a, kBackgroundType, rect);
+    DrawRect(context, content1b, kBackgroundType, rect);
+    {
+      SubsequenceRecorder r(context, container2);
+      DrawRect(context, container2, kBackgroundType, rect);
+    }
+    DrawRect(context, content3, kBackgroundType, rect);
   }
-  DrawRect(context, content3, kBackgroundType, rect);
-  CommitAndFinishCycle();
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&content1a, kBackgroundType),
@@ -875,43 +951,44 @@
   // Subsequence(container1): container1, content1b(cached), content1a(cached).
   // Subsequence(container2): cached
   // Subsequence(contaienr3): container3, content3
-  InitRootChunk();
-  if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled()) {
-    EXPECT_FALSE(DrawingRecorder::UseCachedDrawingIfPossible(context, content1b,
-                                                             kBackgroundType));
-    DrawRect(context, content1b, kBackgroundType, rect);
-    EXPECT_FALSE(DrawingRecorder::UseCachedDrawingIfPossible(context, content1a,
-                                                             kBackgroundType));
-    DrawRect(context, content1a, kBackgroundType, rect);
-    {
-      EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled()) {
+      EXPECT_FALSE(DrawingRecorder::UseCachedDrawingIfPossible(
+          context, content1b, kBackgroundType));
+      DrawRect(context, content1b, kBackgroundType, rect);
+      EXPECT_FALSE(DrawingRecorder::UseCachedDrawingIfPossible(
+          context, content1a, kBackgroundType));
+      DrawRect(context, content1a, kBackgroundType, rect);
+      {
+        EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
+            context, container2));
+        SubsequenceRecorder r(context, container2);
+        DrawRect(context, container2, kBackgroundType, rect);
+      }
+      EXPECT_FALSE(DrawingRecorder::UseCachedDrawingIfPossible(
+          context, content3, kBackgroundType));
+      DrawRect(context, content3, kBackgroundType, rect);
+    } else {
+      EXPECT_TRUE(DrawingRecorder::UseCachedDrawingIfPossible(
+          context, content1b, kBackgroundType));
+      EXPECT_TRUE(DrawingRecorder::UseCachedDrawingIfPossible(
+          context, content1a, kBackgroundType));
+      EXPECT_TRUE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
           context, container2));
-      SubsequenceRecorder r(context, container2);
-      DrawRect(context, container2, kBackgroundType, rect);
+      EXPECT_TRUE(DrawingRecorder::UseCachedDrawingIfPossible(context, content3,
+                                                              kBackgroundType));
     }
-    EXPECT_FALSE(DrawingRecorder::UseCachedDrawingIfPossible(context, content3,
-                                                             kBackgroundType));
-    DrawRect(context, content3, kBackgroundType, rect);
-  } else {
-    EXPECT_TRUE(DrawingRecorder::UseCachedDrawingIfPossible(context, content1b,
-                                                            kBackgroundType));
-    EXPECT_TRUE(DrawingRecorder::UseCachedDrawingIfPossible(context, content1a,
-                                                            kBackgroundType));
-    EXPECT_TRUE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
-        context, container2));
-    EXPECT_TRUE(DrawingRecorder::UseCachedDrawingIfPossible(context, content3,
-                                                            kBackgroundType));
-  }
 
-  EXPECT_EQ(4u, NumCachedNewItems());
-  EXPECT_EQ(1u, NumCachedNewSubsequences());
+    EXPECT_EQ(4u, NumCachedNewItems());
+    EXPECT_EQ(1u, NumCachedNewSubsequences());
 #if DCHECK_IS_ON()
-  EXPECT_EQ(1u, NumIndexedItems());
-  EXPECT_EQ(2u, NumSequentialMatches());
-  EXPECT_EQ(1u, NumOutOfOrderMatches());
+    EXPECT_EQ(1u, NumIndexedItems());
+    EXPECT_EQ(2u, NumSequentialMatches());
+    EXPECT_EQ(1u, NumOutOfOrderMatches());
 #endif
-
-  CommitAndFinishCycle();
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&content1b, kBackgroundType),
@@ -926,7 +1003,6 @@
   constexpr wtf_size_t kFragmentCount = 3;
   FakeDisplayItemClient container("container");
 
-  // The first paint.
   auto paint_container = [this, &context, &container]() {
     SubsequenceRecorder r(context, container);
     for (wtf_size_t i = 0; i < kFragmentCount; ++i) {
@@ -938,7 +1014,10 @@
                IntRect(100, 100, 100, 100));
     }
   };
+
+  // The first paint.
   {
+    CommitCycleScope cycle_scope(GetPaintController());
     ScopedPaintChunkProperties root_chunk_properties(
         GetPaintController(), DefaultPaintChunkProperties(), root,
         kBackgroundType);
@@ -946,7 +1025,6 @@
     paint_container();
     DrawRect(context, root, kForegroundType, IntRect(100, 100, 100, 100));
   }
-  CommitAndFinishCycle();
 
   auto check_paint_results = [this, &root, &container]() {
     EXPECT_THAT(
@@ -966,6 +1044,7 @@
 
   // The second paint.
   {
+    CommitCycleScope cycle_scope(GetPaintController());
     ScopedPaintChunkProperties root_chunk_properties(
         GetPaintController(), DefaultPaintChunkProperties(), root,
         kBackgroundType);
@@ -981,7 +1060,6 @@
     }
     DrawRect(context, root, kForegroundType, IntRect(100, 100, 100, 100));
   }
-  CommitAndFinishCycle();
 
   // The second paint should produce the exactly same results.
   check_paint_results();
@@ -1004,15 +1082,17 @@
   auto container2_properties = DefaultPaintChunkProperties();
   container2_properties.SetEffect(*container2_effect);
 
-  GetPaintController().UpdateCurrentPaintChunkProperties(&container1_id,
-                                                         container1_properties);
-  DrawRect(context, container1, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
-  GetPaintController().UpdateCurrentPaintChunkProperties(&container2_id,
-                                                         container2_properties);
-  DrawRect(context, container2, kBackgroundType, IntRect(100, 200, 100, 100));
-  DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
-  CommitAndFinishCycle();
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    GetPaintController().UpdateCurrentPaintChunkProperties(
+        &container1_id, container1_properties);
+    DrawRect(context, container1, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
+    GetPaintController().UpdateCurrentPaintChunkProperties(
+        &container2_id, container2_properties);
+    DrawRect(context, container2, kBackgroundType, IntRect(100, 200, 100, 100));
+    DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&container1, kBackgroundType),
@@ -1026,24 +1106,25 @@
                   IsPaintChunk(2, 4, container2_id, container2_properties)));
 
   // Move content2 into container1, without invalidation.
-  GetPaintController().UpdateCurrentPaintChunkProperties(&container1_id,
-                                                         container1_properties);
-  DrawRect(context, container1, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
-  DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
-  GetPaintController().UpdateCurrentPaintChunkProperties(&container2_id,
-                                                         container2_properties);
-  DrawRect(context, container2, kBackgroundType, IntRect(100, 200, 100, 100));
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    GetPaintController().UpdateCurrentPaintChunkProperties(
+        &container1_id, container1_properties);
+    DrawRect(context, container1, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
+    DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
+    GetPaintController().UpdateCurrentPaintChunkProperties(
+        &container2_id, container2_properties);
+    DrawRect(context, container2, kBackgroundType, IntRect(100, 200, 100, 100));
 
-  EXPECT_EQ(4u, NumCachedNewItems());
-  EXPECT_EQ(0u, NumCachedNewSubsequences());
+    EXPECT_EQ(4u, NumCachedNewItems());
+    EXPECT_EQ(0u, NumCachedNewSubsequences());
 #if DCHECK_IS_ON()
-  EXPECT_EQ(1u, NumIndexedItems());
-  EXPECT_EQ(3u, NumSequentialMatches());
-  EXPECT_EQ(1u, NumOutOfOrderMatches());
+    EXPECT_EQ(1u, NumIndexedItems());
+    EXPECT_EQ(3u, NumSequentialMatches());
+    EXPECT_EQ(1u, NumOutOfOrderMatches());
 #endif
-
-  CommitAndFinishCycle();
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&container1, kBackgroundType),
@@ -1069,21 +1150,23 @@
   const DisplayItem::Type kType4 =
       static_cast<DisplayItem::Type>(DisplayItem::kDrawingFirst + 3);
 
-  InitRootChunk();
-  DrawRect(context, client, kType1, IntRect(100, 100, 100, 100));
-  DrawRect(context, client, kType2, IntRect(100, 100, 50, 200));
-  DrawRect(context, client, kType3, IntRect(100, 100, 50, 200));
-  DrawRect(context, client, kType4, IntRect(100, 100, 100, 100));
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    DrawRect(context, client, kType1, IntRect(100, 100, 100, 100));
+    DrawRect(context, client, kType2, IntRect(100, 100, 50, 200));
+    DrawRect(context, client, kType3, IntRect(100, 100, 50, 200));
+    DrawRect(context, client, kType4, IntRect(100, 100, 100, 100));
+  }
 
-  CommitAndFinishCycle();
-
-  InitRootChunk();
-  DrawRect(context, client, kType2, IntRect(100, 100, 50, 200));
-  DrawRect(context, client, kType3, IntRect(100, 100, 50, 200));
-  DrawRect(context, client, kType1, IntRect(100, 100, 100, 100));
-  DrawRect(context, client, kType4, IntRect(100, 100, 100, 100));
-
-  CommitAndFinishCycle();
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    DrawRect(context, client, kType2, IntRect(100, 100, 50, 200));
+    DrawRect(context, client, kType3, IntRect(100, 100, 50, 200));
+    DrawRect(context, client, kType1, IntRect(100, 100, 100, 100));
+    DrawRect(context, client, kType4, IntRect(100, 100, 100, 100));
+  }
 }
 
 TEST_P(PaintControllerTest, CachedNestedSubsequenceUpdate) {
@@ -1117,35 +1200,43 @@
   content2_properties.SetEffect(*content2_effect);
 
   {
-    SubsequenceRecorder r(context, container1);
-    GetPaintController().UpdateCurrentPaintChunkProperties(
-        &container1_background_id, container1_background_properties);
-    DrawRect(context, container1, kBackgroundType, IntRect(100, 100, 100, 100));
+    CommitCycleScope cycle_scope(GetPaintController());
+    {
+      SubsequenceRecorder r(context, container1);
+      GetPaintController().UpdateCurrentPaintChunkProperties(
+          &container1_background_id, container1_background_properties);
+      DrawRect(context, container1, kBackgroundType,
+               IntRect(100, 100, 100, 100));
 
-    {
-      SubsequenceRecorder r(context, content1);
+      {
+        SubsequenceRecorder r(context, content1);
+        GetPaintController().UpdateCurrentPaintChunkProperties(
+            &content1_id, content1_properties);
+        DrawRect(context, content1, kBackgroundType,
+                 IntRect(100, 100, 50, 200));
+        DrawRect(context, content1, kForegroundType,
+                 IntRect(100, 100, 50, 200));
+      }
       GetPaintController().UpdateCurrentPaintChunkProperties(
-          &content1_id, content1_properties);
-      DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
-      DrawRect(context, content1, kForegroundType, IntRect(100, 100, 50, 200));
+          &container1_foreground_id, container1_foreground_properties);
+      DrawRect(context, container1, kForegroundType,
+               IntRect(100, 100, 100, 100));
     }
-    GetPaintController().UpdateCurrentPaintChunkProperties(
-        &container1_foreground_id, container1_foreground_properties);
-    DrawRect(context, container1, kForegroundType, IntRect(100, 100, 100, 100));
-  }
-  {
-    SubsequenceRecorder r(context, container2);
-    GetPaintController().UpdateCurrentPaintChunkProperties(
-        &container2_background_id, container2_background_properties);
-    DrawRect(context, container2, kBackgroundType, IntRect(100, 200, 100, 100));
     {
-      SubsequenceRecorder r(context, content2);
+      SubsequenceRecorder r(context, container2);
       GetPaintController().UpdateCurrentPaintChunkProperties(
-          &content2_id, content2_properties);
-      DrawRect(context, content2, kBackgroundType, IntRect(100, 200, 50, 200));
+          &container2_background_id, container2_background_properties);
+      DrawRect(context, container2, kBackgroundType,
+               IntRect(100, 200, 100, 100));
+      {
+        SubsequenceRecorder r(context, content2);
+        GetPaintController().UpdateCurrentPaintChunkProperties(
+            &content2_id, content2_properties);
+        DrawRect(context, content2, kBackgroundType,
+                 IntRect(100, 200, 50, 200));
+      }
     }
   }
-  CommitAndFinishCycle();
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&container1, kBackgroundType),
@@ -1171,58 +1262,62 @@
                                container2_background_properties),
                   IsPaintChunk(5, 6, content2_id, content2_properties)));
 
-  // Invalidate container1 but not content1.
-  container1.Invalidate();
-  // Container2 itself now becomes empty (but still has the 'content2' child),
-  // and chooses not to output subsequence info.
-  container2.Invalidate();
-  content2.Invalidate();
-  EXPECT_FALSE(
-      SubsequenceRecorder::UseCachedSubsequenceIfPossible(context, container2));
-  EXPECT_FALSE(
-      SubsequenceRecorder::UseCachedSubsequenceIfPossible(context, content2));
-  // Content2 now outputs foreground only.
   {
-    SubsequenceRecorder r(context, content2);
-    GetPaintController().UpdateCurrentPaintChunkProperties(&content2_id,
-                                                           content2_properties);
-    DrawRect(context, content2, kForegroundType, IntRect(100, 200, 50, 200));
-  }
-  // Repaint container1 with foreground only.
-  {
-    SubsequenceRecorder r(context, container1);
+    CommitCycleScope cycle_scope(GetPaintController());
+    // Invalidate container1 but not content1.
+    container1.Invalidate();
+    // Container2 itself now becomes empty (but still has the 'content2' child),
+    // and chooses not to output subsequence info.
+    container2.Invalidate();
+    content2.Invalidate();
     EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
-        context, container1));
-    // Use cached subsequence of content1.
-    if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled()) {
-      // When under-invalidation-checking is enabled,
-      // UseCachedSubsequenceIfPossible is forced off, and the client is
-      // expected to create the same painting as in the previous paint.
-      EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
-          context, content1));
-      SubsequenceRecorder r(context, content1);
+        context, container2));
+    EXPECT_FALSE(
+        SubsequenceRecorder::UseCachedSubsequenceIfPossible(context, content2));
+    // Content2 now outputs foreground only.
+    {
+      SubsequenceRecorder r(context, content2);
       GetPaintController().UpdateCurrentPaintChunkProperties(
-          &content1_id, content1_properties);
-      DrawRect(context, content1, kBackgroundType, IntRect(100, 100, 50, 200));
-      DrawRect(context, content1, kForegroundType, IntRect(100, 100, 50, 200));
-    } else {
-      EXPECT_TRUE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
-          context, content1));
+          &content2_id, content2_properties);
+      DrawRect(context, content2, kForegroundType, IntRect(100, 200, 50, 200));
     }
-    GetPaintController().UpdateCurrentPaintChunkProperties(
-        &container1_foreground_id, container1_foreground_properties);
-    DrawRect(context, container1, kForegroundType, IntRect(100, 100, 100, 100));
-  }
+    // Repaint container1 with foreground only.
+    {
+      SubsequenceRecorder r(context, container1);
+      EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
+          context, container1));
+      // Use cached subsequence of content1.
+      if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled()) {
+        // When under-invalidation-checking is enabled,
+        // UseCachedSubsequenceIfPossible is forced off, and the client is
+        // expected to create the same painting as in the previous paint.
+        EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
+            context, content1));
+        SubsequenceRecorder r(context, content1);
+        GetPaintController().UpdateCurrentPaintChunkProperties(
+            &content1_id, content1_properties);
+        DrawRect(context, content1, kBackgroundType,
+                 IntRect(100, 100, 50, 200));
+        DrawRect(context, content1, kForegroundType,
+                 IntRect(100, 100, 50, 200));
+      } else {
+        EXPECT_TRUE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
+            context, content1));
+      }
+      GetPaintController().UpdateCurrentPaintChunkProperties(
+          &container1_foreground_id, container1_foreground_properties);
+      DrawRect(context, container1, kForegroundType,
+               IntRect(100, 100, 100, 100));
+    }
 
-  EXPECT_EQ(2u, NumCachedNewItems());
-  EXPECT_EQ(1u, NumCachedNewSubsequences());
+    EXPECT_EQ(2u, NumCachedNewItems());
+    EXPECT_EQ(1u, NumCachedNewSubsequences());
 #if DCHECK_IS_ON()
-  EXPECT_EQ(0u, NumIndexedItems());
-  EXPECT_EQ(0u, NumSequentialMatches());
-  EXPECT_EQ(0u, NumOutOfOrderMatches());
+    EXPECT_EQ(0u, NumIndexedItems());
+    EXPECT_EQ(0u, NumSequentialMatches());
+    EXPECT_EQ(0u, NumOutOfOrderMatches());
 #endif
-
-  CommitAndFinishCycle();
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&content2, kForegroundType),
@@ -1248,10 +1343,8 @@
 
   FakeDisplayItemClient root("root");
   auto properties = DefaultPaintChunkProperties();
-  PaintChunk::Id root_id(root, DisplayItem::kLayerChunk);
   GraphicsContext context(GetPaintController());
-  GetPaintController().UpdateCurrentPaintChunkProperties(&root_id, properties);
-
+  PaintChunk::Id root_id(root, DisplayItem::kLayerChunk);
   FakeDisplayItemClient container1("container1");
   PaintChunk::Id container1_bg_id(container1, kBackgroundType);
   PaintChunk::Id container1_fg_id(container1, kForegroundType);
@@ -1265,36 +1358,47 @@
   PaintChunk::Id content2a_id(content2a, kBackgroundType);
   FakeDisplayItemClient content2b("content2b");
   PaintChunk::Id content2b_id(content2b, kForegroundType);
-
   {
-    SubsequenceRecorder r(context, container1);
-    DrawRect(context, container1, kBackgroundType, IntRect(100, 100, 100, 100));
-    {
-      SubsequenceRecorder r(context, content1a);
-      DrawRect(context, content1a, kBackgroundType, IntRect(100, 100, 50, 200));
-    }
-    {
-      SubsequenceRecorder r(context, content1b);
-      DrawRect(context, content1b, kForegroundType, IntRect(100, 100, 50, 200));
-    }
-    DrawRect(context, container1, kForegroundType, IntRect(100, 100, 100, 100));
-  }
-  {
-    SubsequenceRecorder r(context, container2);
-    DrawRect(context, container2, kBackgroundType, IntRect(100, 200, 100, 100));
-    {
-      SubsequenceRecorder r(context, content2a);
-      DrawRect(context, content2a, kBackgroundType, IntRect(100, 200, 50, 200));
-    }
-    {
-      SubsequenceRecorder r(context, content2b);
-      DrawRect(context, content2b, kForegroundType, IntRect(100, 200, 50, 200));
-    }
-  }
+    CommitCycleScope cycle_scope(GetPaintController());
+    GetPaintController().UpdateCurrentPaintChunkProperties(&root_id,
+                                                           properties);
 
-  EXPECT_EQ(0u, NumCachedNewItems());
-  EXPECT_EQ(0u, NumCachedNewSubsequences());
-  CommitAndFinishCycle();
+    {
+      SubsequenceRecorder r(context, container1);
+      DrawRect(context, container1, kBackgroundType,
+               IntRect(100, 100, 100, 100));
+      {
+        SubsequenceRecorder r(context, content1a);
+        DrawRect(context, content1a, kBackgroundType,
+                 IntRect(100, 100, 50, 200));
+      }
+      {
+        SubsequenceRecorder r(context, content1b);
+        DrawRect(context, content1b, kForegroundType,
+                 IntRect(100, 100, 50, 200));
+      }
+      DrawRect(context, container1, kForegroundType,
+               IntRect(100, 100, 100, 100));
+    }
+    {
+      SubsequenceRecorder r(context, container2);
+      DrawRect(context, container2, kBackgroundType,
+               IntRect(100, 200, 100, 100));
+      {
+        SubsequenceRecorder r(context, content2a);
+        DrawRect(context, content2a, kBackgroundType,
+                 IntRect(100, 200, 50, 200));
+      }
+      {
+        SubsequenceRecorder r(context, content2b);
+        DrawRect(context, content2b, kForegroundType,
+                 IntRect(100, 200, 50, 200));
+      }
+    }
+
+    EXPECT_EQ(0u, NumCachedNewItems());
+    EXPECT_EQ(0u, NumCachedNewSubsequences());
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&container1, kBackgroundType),
@@ -1322,14 +1426,16 @@
                           IsPaintChunk(6, 7, content2b_id, properties)));
 
   // Nothing invalidated. Should keep all subsequences.
-  EXPECT_TRUE(
-      SubsequenceRecorder::UseCachedSubsequenceIfPossible(context, container1));
-  EXPECT_TRUE(
-      SubsequenceRecorder::UseCachedSubsequenceIfPossible(context, container2));
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    EXPECT_TRUE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
+        context, container1));
+    EXPECT_TRUE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
+        context, container2));
 
-  EXPECT_EQ(7u, NumCachedNewItems());
-  EXPECT_EQ(6u, NumCachedNewSubsequences());
-  CommitAndFinishCycle();
+    EXPECT_EQ(7u, NumCachedNewItems());
+    EXPECT_EQ(6u, NumCachedNewSubsequences());
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&container1, kBackgroundType),
@@ -1358,14 +1464,16 @@
 
   // Swap order of the subsequences of container1 and container2.
   // Nothing invalidated. Should keep all subsequences.
-  EXPECT_TRUE(
-      SubsequenceRecorder::UseCachedSubsequenceIfPossible(context, container2));
-  EXPECT_TRUE(
-      SubsequenceRecorder::UseCachedSubsequenceIfPossible(context, container1));
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    EXPECT_TRUE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
+        context, container2));
+    EXPECT_TRUE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
+        context, container1));
 
-  EXPECT_EQ(7u, NumCachedNewItems());
-  EXPECT_EQ(6u, NumCachedNewSubsequences());
-  CommitAndFinishCycle();
+    EXPECT_EQ(7u, NumCachedNewItems());
+    EXPECT_EQ(6u, NumCachedNewSubsequences());
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&container2, kBackgroundType),
@@ -1397,20 +1505,21 @@
   FakeDisplayItemClient multicol("multicol");
   FakeDisplayItemClient content("content");
   GraphicsContext context(GetPaintController());
-  InitRootChunk();
-
   IntRect rect1(100, 100, 50, 50);
   IntRect rect2(150, 100, 50, 50);
   IntRect rect3(200, 100, 50, 50);
 
-  DrawRect(context, multicol, kBackgroundType, IntRect(100, 200, 100, 100));
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
 
-  GetPaintController().BeginSkippingCache();
-  DrawRect(context, content, kForegroundType, rect1);
-  DrawRect(context, content, kForegroundType, rect2);
-  GetPaintController().EndSkippingCache();
+    DrawRect(context, multicol, kBackgroundType, IntRect(100, 200, 100, 100));
 
-  CommitAndFinishCycle();
+    GetPaintController().BeginSkippingCache();
+    DrawRect(context, content, kForegroundType, rect1);
+    DrawRect(context, content, kForegroundType, rect2);
+    GetPaintController().EndSkippingCache();
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&multicol, kBackgroundType),
@@ -1425,25 +1534,26 @@
   EXPECT_NE(record1, record2);
   EXPECT_DEFAULT_ROOT_CHUNK(3);
 
-  InitRootChunk();
-  // Draw again with nothing invalidated.
-  EXPECT_TRUE(ClientCacheIsValid(multicol));
-  DrawRect(context, multicol, kBackgroundType, IntRect(100, 200, 100, 100));
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    // Draw again with nothing invalidated.
+    EXPECT_TRUE(ClientCacheIsValid(multicol));
+    DrawRect(context, multicol, kBackgroundType, IntRect(100, 200, 100, 100));
 
-  GetPaintController().BeginSkippingCache();
-  DrawRect(context, content, kForegroundType, rect1);
-  DrawRect(context, content, kForegroundType, rect2);
-  GetPaintController().EndSkippingCache();
+    GetPaintController().BeginSkippingCache();
+    DrawRect(context, content, kForegroundType, rect1);
+    DrawRect(context, content, kForegroundType, rect2);
+    GetPaintController().EndSkippingCache();
 
-  EXPECT_EQ(1u, NumCachedNewItems());
-  EXPECT_EQ(0u, NumCachedNewSubsequences());
+    EXPECT_EQ(1u, NumCachedNewItems());
+    EXPECT_EQ(0u, NumCachedNewSubsequences());
 #if DCHECK_IS_ON()
-  EXPECT_EQ(0u, NumIndexedItems());
-  EXPECT_EQ(1u, NumSequentialMatches());
-  EXPECT_EQ(0u, NumOutOfOrderMatches());
+    EXPECT_EQ(0u, NumIndexedItems());
+    EXPECT_EQ(1u, NumSequentialMatches());
+    EXPECT_EQ(0u, NumOutOfOrderMatches());
 #endif
-
-  CommitAndFinishCycle();
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&multicol, kBackgroundType),
@@ -1457,31 +1567,32 @@
                 .GetPaintRecord());
   EXPECT_DEFAULT_ROOT_CHUNK(3);
 
-  InitRootChunk();
-  // Now the multicol becomes 3 columns and repaints.
-  multicol.Invalidate();
-  DrawRect(context, multicol, kBackgroundType, IntRect(100, 100, 100, 100));
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    // Now the multicol becomes 3 columns and repaints.
+    multicol.Invalidate();
+    DrawRect(context, multicol, kBackgroundType, IntRect(100, 100, 100, 100));
 
-  GetPaintController().BeginSkippingCache();
-  DrawRect(context, content, kForegroundType, rect1);
-  DrawRect(context, content, kForegroundType, rect2);
-  DrawRect(context, content, kForegroundType, rect3);
-  GetPaintController().EndSkippingCache();
+    GetPaintController().BeginSkippingCache();
+    DrawRect(context, content, kForegroundType, rect1);
+    DrawRect(context, content, kForegroundType, rect2);
+    DrawRect(context, content, kForegroundType, rect3);
+    GetPaintController().EndSkippingCache();
 
-  // We should repaint everything on invalidation of the scope container.
-  const auto& display_item_list =
-      GetPaintController().GetNewPaintArtifactShared()->GetDisplayItemList();
-  EXPECT_THAT(display_item_list,
-              ElementsAre(IsSameId(&multicol, kBackgroundType),
-                          IsSameId(&content, kForegroundType),
-                          IsSameId(&content, kForegroundType),
-                          IsSameId(&content, kForegroundType)));
-  EXPECT_NE(record1,
-            To<DrawingDisplayItem>(display_item_list[1]).GetPaintRecord());
-  EXPECT_NE(record2,
-            To<DrawingDisplayItem>(display_item_list[2]).GetPaintRecord());
-
-  CommitAndFinishCycle();
+    // We should repaint everything on invalidation of the scope container.
+    const auto& display_item_list =
+        GetPaintController().GetNewPaintArtifactShared()->GetDisplayItemList();
+    EXPECT_THAT(display_item_list,
+                ElementsAre(IsSameId(&multicol, kBackgroundType),
+                            IsSameId(&content, kForegroundType),
+                            IsSameId(&content, kForegroundType),
+                            IsSameId(&content, kForegroundType)));
+    EXPECT_NE(record1,
+              To<DrawingDisplayItem>(display_item_list[1]).GetPaintRecord());
+    EXPECT_NE(record2,
+              To<DrawingDisplayItem>(display_item_list[2]).GetPaintRecord());
+  }
   EXPECT_DEFAULT_ROOT_CHUNK(4);
 }
 
@@ -1493,14 +1604,15 @@
   IntRect rect2(150, 100, 50, 50);
   IntRect rect3(200, 100, 50, 50);
 
-  InitRootChunk();
-  DrawRect(context, content, kBackgroundType, rect1);
-  GetPaintController().BeginSkippingCache();
-  DrawRect(context, content, kForegroundType, rect2);
-  GetPaintController().EndSkippingCache();
-  DrawRect(context, content, kForegroundType, rect3);
-
-  CommitAndFinishCycle();
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    DrawRect(context, content, kBackgroundType, rect1);
+    GetPaintController().BeginSkippingCache();
+    DrawRect(context, content, kForegroundType, rect2);
+    GetPaintController().EndSkippingCache();
+    DrawRect(context, content, kForegroundType, rect3);
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&content, kBackgroundType),
@@ -1522,23 +1634,24 @@
   EXPECT_EQ(PaintInvalidationReason::kUncacheable,
             content.GetPaintInvalidationReason());
 
-  InitRootChunk();
-  // Draw again with nothing invalidated.
-  DrawRect(context, content, kBackgroundType, rect1);
-  GetPaintController().BeginSkippingCache();
-  DrawRect(context, content, kForegroundType, rect2);
-  GetPaintController().EndSkippingCache();
-  DrawRect(context, content, kForegroundType, rect3);
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    // Draw again with nothing invalidated.
+    DrawRect(context, content, kBackgroundType, rect1);
+    GetPaintController().BeginSkippingCache();
+    DrawRect(context, content, kForegroundType, rect2);
+    GetPaintController().EndSkippingCache();
+    DrawRect(context, content, kForegroundType, rect3);
 
-  EXPECT_EQ(0u, NumCachedNewItems());
-  EXPECT_EQ(0u, NumCachedNewSubsequences());
+    EXPECT_EQ(0u, NumCachedNewItems());
+    EXPECT_EQ(0u, NumCachedNewSubsequences());
 #if DCHECK_IS_ON()
-  EXPECT_EQ(0u, NumIndexedItems());
-  EXPECT_EQ(0u, NumSequentialMatches());
-  EXPECT_EQ(0u, NumOutOfOrderMatches());
+    EXPECT_EQ(0u, NumIndexedItems());
+    EXPECT_EQ(0u, NumSequentialMatches());
+    EXPECT_EQ(0u, NumOutOfOrderMatches());
 #endif
-
-  CommitAndFinishCycle();
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&content, kBackgroundType),
@@ -1562,17 +1675,18 @@
   PaintChunk::Id chunk_id(chunk_client, DisplayItem::kLayerChunk);
   auto& paint_controller = GetPaintController();
 
-  GraphicsContext context(paint_controller);
-  paint_controller.BeginSkippingCache();
-  paint_controller.SetWillForceNewChunk(true);
-  paint_controller.UpdateCurrentPaintChunkProperties(&chunk_id, properties);
-  DrawRect(context, item_client, kBackgroundType, IntRect(0, 0, 100, 100));
-  paint_controller.SetWillForceNewChunk(true);
-  paint_controller.UpdateCurrentPaintChunkProperties(&chunk_id, properties);
-  DrawRect(context, item_client, kBackgroundType, IntRect(0, 0, 100, 100));
-  paint_controller.EndSkippingCache();
-
-  CommitAndFinishCycle();
+  {
+    CommitCycleScope cycle_scope(paint_controller);
+    GraphicsContext context(paint_controller);
+    paint_controller.BeginSkippingCache();
+    paint_controller.SetWillForceNewChunk(true);
+    paint_controller.UpdateCurrentPaintChunkProperties(&chunk_id, properties);
+    DrawRect(context, item_client, kBackgroundType, IntRect(0, 0, 100, 100));
+    paint_controller.SetWillForceNewChunk(true);
+    paint_controller.UpdateCurrentPaintChunkProperties(&chunk_id, properties);
+    DrawRect(context, item_client, kBackgroundType, IntRect(0, 0, 100, 100));
+    paint_controller.EndSkippingCache();
+  }
 
   EXPECT_THAT(paint_controller.GetDisplayItemList(),
               ElementsAre(IsSameId(&item_client, kBackgroundType),
@@ -1590,15 +1704,15 @@
 TEST_P(PaintControllerTest, SmallPaintControllerHasOnePaintChunk) {
   FakeDisplayItemClient client("test client");
 
-  InitRootChunk();
-  GraphicsContext context(GetPaintController());
-  DrawRect(context, client, kBackgroundType, IntRect(0, 0, 100, 100));
-
-  CommitAndFinishCycle();
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    GraphicsContext context(GetPaintController());
+    DrawRect(context, client, kBackgroundType, IntRect(0, 0, 100, 100));
+  }
   EXPECT_THAT(GetPaintController().PaintChunks(),
               ElementsAre(IsPaintChunk(0, 1)));
 }
-
 void DrawPath(GraphicsContext& context,
               DisplayItemClient& client,
               DisplayItem::Type type,
@@ -1671,15 +1785,17 @@
   FakeDisplayItemClient fourth("fourth");
   GraphicsContext context(GetPaintController());
 
-  InitRootChunk();
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 300, 300));
-  DrawRect(context, second, kBackgroundType, IntRect(100, 100, 200, 200));
-  DrawRect(context, third, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, fourth, kBackgroundType, IntRect(100, 100, 50, 50));
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 300, 300));
+    DrawRect(context, second, kBackgroundType, IntRect(100, 100, 200, 200));
+    DrawRect(context, third, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, fourth, kBackgroundType, IntRect(100, 100, 50, 50));
 
-  EXPECT_EQ(0u, NumCachedNewItems());
-  EXPECT_EQ(0u, NumCachedNewSubsequences());
-  CommitAndFinishCycle();
+    EXPECT_EQ(0u, NumCachedNewItems());
+    EXPECT_EQ(0u, NumCachedNewSubsequences());
+  }
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&first, kBackgroundType),
                           IsSameId(&second, kBackgroundType),
@@ -1692,20 +1808,21 @@
 
   // Simulate that a composited scrolling element is scrolled down, and "first"
   // and "second" are scrolled out of the interest rect.
-  InitRootChunk();
-  DrawRect(context, third, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, fourth, kBackgroundType, IntRect(100, 100, 50, 50));
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    DrawRect(context, third, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, fourth, kBackgroundType, IntRect(100, 100, 50, 50));
 
-  EXPECT_EQ(2u, NumCachedNewItems());
-  EXPECT_EQ(0u, NumCachedNewSubsequences());
+    EXPECT_EQ(2u, NumCachedNewItems());
+    EXPECT_EQ(0u, NumCachedNewSubsequences());
 #if DCHECK_IS_ON()
-  // We indexed "first" and "second" when finding the cached item for "third".
-  EXPECT_EQ(2u, NumIndexedItems());
-  EXPECT_EQ(2u, NumSequentialMatches());
-  EXPECT_EQ(0u, NumOutOfOrderMatches());
+    // We indexed "first" and "second" when finding the cached item for "third".
+    EXPECT_EQ(2u, NumIndexedItems());
+    EXPECT_EQ(2u, NumSequentialMatches());
+    EXPECT_EQ(0u, NumOutOfOrderMatches());
 #endif
-
-  CommitAndFinishCycle();
+  }
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&third, kBackgroundType),
                           IsSameId(&fourth, kBackgroundType)));
@@ -1715,22 +1832,23 @@
   EXPECT_TRUE(fourth.IsValid());
 
   // Simulate "first" and "second" are scrolled back into the interest rect.
-  InitRootChunk();
-  DrawRect(context, first, kBackgroundType, IntRect(100, 100, 300, 300));
-  DrawRect(context, second, kBackgroundType, IntRect(100, 100, 200, 200));
-  DrawRect(context, third, kBackgroundType, IntRect(100, 100, 100, 100));
-  DrawRect(context, fourth, kBackgroundType, IntRect(100, 100, 50, 50));
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 300, 300));
+    DrawRect(context, second, kBackgroundType, IntRect(100, 100, 200, 200));
+    DrawRect(context, third, kBackgroundType, IntRect(100, 100, 100, 100));
+    DrawRect(context, fourth, kBackgroundType, IntRect(100, 100, 50, 50));
 
-  EXPECT_EQ(2u, NumCachedNewItems());
-  EXPECT_EQ(0u, NumCachedNewSubsequences());
+    EXPECT_EQ(2u, NumCachedNewItems());
+    EXPECT_EQ(0u, NumCachedNewSubsequences());
 #if DCHECK_IS_ON()
-  // We indexed "third" and "fourth" when finding the cached item for "first".
-  EXPECT_EQ(2u, NumIndexedItems());
-  EXPECT_EQ(2u, NumSequentialMatches());
-  EXPECT_EQ(0u, NumOutOfOrderMatches());
+    // We indexed "third" and "fourth" when finding the cached item for "first".
+    EXPECT_EQ(2u, NumIndexedItems());
+    EXPECT_EQ(2u, NumSequentialMatches());
+    EXPECT_EQ(0u, NumOutOfOrderMatches());
 #endif
-
-  CommitAndFinishCycle();
+  }
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&first, kBackgroundType),
                           IsSameId(&second, kBackgroundType),
@@ -1767,25 +1885,28 @@
   EXPECT_TRUE(cacheable.IsCacheable());
   EXPECT_FALSE(uncacheable.IsCacheable());
 
-  InitRootChunk();
   {
-    SubsequenceRecorder recorder(context, cacheable);
-    DrawRect(context, cacheable, kBackgroundType, IntRect(r));
-    DrawRect(context, uncacheable, kBackgroundType, IntRect(r));
-    // This should not trigger the duplicated id assert.
-    DrawRect(context, uncacheable, kBackgroundType, IntRect(r));
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    {
+      SubsequenceRecorder recorder(context, cacheable);
+      DrawRect(context, cacheable, kBackgroundType, IntRect(r));
+      DrawRect(context, uncacheable, kBackgroundType, IntRect(r));
+      // This should not trigger the duplicated id assert.
+      DrawRect(context, uncacheable, kBackgroundType, IntRect(r));
+    }
   }
-
-  CommitAndFinishCycle();
   EXPECT_TRUE(GetPaintController().GetDisplayItemList()[0].IsCacheable());
   EXPECT_FALSE(GetPaintController().GetDisplayItemList()[1].IsCacheable());
   EXPECT_FALSE(GetPaintController().GetDisplayItemList()[2].IsCacheable());
   EXPECT_TRUE(cacheable.IsCacheable());
   EXPECT_FALSE(uncacheable.IsCacheable());
 
-  InitRootChunk();
-  EXPECT_TRUE(GetPaintController().UseCachedSubsequenceIfPossible(cacheable));
-  CommitAndFinishCycle();
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    EXPECT_TRUE(GetPaintController().UseCachedSubsequenceIfPossible(cacheable));
+  }
   EXPECT_TRUE(GetPaintController().GetDisplayItemList()[0].IsCacheable());
   EXPECT_FALSE(GetPaintController().GetDisplayItemList()[1].IsCacheable());
   EXPECT_FALSE(GetPaintController().GetDisplayItemList()[2].IsCacheable());
@@ -1798,19 +1919,22 @@
 
 TEST_P(PaintControllerTest, DuplicatedSubsequences) {
   FakeDisplayItemClient client("test");
-  GraphicsContext context(GetPaintController());
+  PaintController& controller = GetPaintController();
+  GraphicsContext context(controller);
 
   auto paint_duplicated_subsequences = [&]() {
-    InitRootChunk();
     {
-      SubsequenceRecorder r(context, client);
-      DrawRect(context, client, kBackgroundType, IntRect(100, 100, 100, 100));
+      CommitCycleScope cycle_scope(controller);
+      InitRootChunk();
+      {
+        SubsequenceRecorder r(context, client);
+        DrawRect(context, client, kBackgroundType, IntRect(100, 100, 100, 100));
+      }
+      {
+        SubsequenceRecorder r(context, client);
+        DrawRect(context, client, kForegroundType, IntRect(100, 100, 100, 100));
+      }
     }
-    {
-      SubsequenceRecorder r(context, client);
-      DrawRect(context, client, kForegroundType, IntRect(100, 100, 100, 100));
-    }
-    CommitAndFinishCycle();
   };
 
 #if DCHECK_IS_ON()
@@ -1818,27 +1942,29 @@
                "Multiple subsequences for client: \"test\"");
 #else
   // The following is for non-DCHECK path. No security CHECK should trigger.
-  paint_duplicated_subsequences();
-  // Paint again.
-  InitRootChunk();
-  if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled()) {
-    EXPECT_FALSE(GetPaintController().UseCachedSubsequenceIfPossible(client));
-    SubsequenceRecorder r(context, client);
-    DrawRect(context, client, kBackgroundType, IntRect(100, 100, 100, 100));
-  } else {
-    EXPECT_TRUE(GetPaintController().UseCachedSubsequenceIfPossible(client));
-  }
   {
-    // Should not use the cached duplicated subsequence.
-    EXPECT_FALSE(GetPaintController().UseCachedSubsequenceIfPossible(client));
-    SubsequenceRecorder r(context, client);
-    DrawRect(context, client, kForegroundType, IntRect(100, 100, 100, 100));
+    CommitCycleScope cycle_scope(GetPaintController());
+    paint_duplicated_subsequences();
+    // Paint again.
+    InitRootChunk();
+    if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled()) {
+      EXPECT_FALSE(GetPaintController().UseCachedSubsequenceIfPossible(client));
+      SubsequenceRecorder r(context, client);
+      DrawRect(context, client, kBackgroundType, IntRect(100, 100, 100, 100));
+    } else {
+      EXPECT_TRUE(GetPaintController().UseCachedSubsequenceIfPossible(client));
+    }
+    {
+      // Should not use the cached duplicated subsequence.
+      EXPECT_FALSE(GetPaintController().UseCachedSubsequenceIfPossible(client));
+      SubsequenceRecorder r(context, client);
+      DrawRect(context, client, kForegroundType, IntRect(100, 100, 100, 100));
+    }
   }
-  CommitAndFinishCycle();
 #endif
 }
 
-TEST_P(PaintControllerTest, DeletedClientInUnderInvaldiatedSubsequence) {
+TEST_P(PaintControllerTest, DeletedClientInUnderInvalidatedSubsequence) {
   if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled())
     return;
 
@@ -1846,26 +1972,30 @@
   auto content = std::make_unique<FakeDisplayItemClient>("content");
   GraphicsContext context(GetPaintController());
 
-  InitRootChunk();
   {
-    SubsequenceRecorder r(context, container);
-    DrawRect(context, *content, kBackgroundType, IntRect(100, 100, 300, 300));
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    {
+      SubsequenceRecorder r(context, container);
+      DrawRect(context, *content, kBackgroundType, IntRect(100, 100, 300, 300));
+    }
   }
-  CommitAndFinishCycle();
 
   content = nullptr;
-  InitRootChunk();
-  // Leave container not invalidated.
+  {
+    CommitCycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    // Leave container not invalidated.
 #if DCHECK_IS_ON()
-  ASSERT_DEATH(
-      SubsequenceRecorder::UseCachedSubsequenceIfPossible(context, container),
-      "");
+    ASSERT_DEATH(
+        SubsequenceRecorder::UseCachedSubsequenceIfPossible(context, container),
+        "");
 #else
-  // This should not crash.
-  EXPECT_TRUE(
-      SubsequenceRecorder::UseCachedSubsequenceIfPossible(context, container));
-  CommitAndFinishCycle();
+    // This should not crash.
+    EXPECT_TRUE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(context,
+                                                                    container));
 #endif
+  }
 }
 
 #endif  // defined(GTEST_HAS_DEATH_TEST) && !defined(OS_ANDROID)
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_controller_test.h b/third_party/blink/renderer/platform/graphics/paint/paint_controller_test.h
index ca7223d5..f8839d0 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_controller_test.h
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_controller_test.h
@@ -80,11 +80,6 @@
 
   void InvalidateAll() { paint_controller_->InvalidateAllForTesting(); }
 
-  void CommitAndFinishCycle() {
-    paint_controller_->CommitNewDisplayItems();
-    paint_controller_->FinishCycle();
-  }
-
   using SubsequenceMarkers = PaintController::SubsequenceMarkers;
   const SubsequenceMarkers* GetSubsequenceMarkers(
       const DisplayItemClient& client) {
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_record_builder.cc b/third_party/blink/renderer/platform/graphics/paint/paint_record_builder.cc
index bfb86a5..a161110e 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_record_builder.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_record_builder.cc
@@ -20,16 +20,13 @@
 }
 
 PaintRecordBuilder::PaintRecordBuilder(PaintController& paint_controller)
-    : paint_controller_(&paint_controller), context_(*paint_controller_) {
-  paint_controller.ReserveCapacity();
-}
+    : paint_controller_(&paint_controller), context_(*paint_controller_) {}
 
 PaintRecordBuilder::~PaintRecordBuilder() = default;
 
 sk_sp<PaintRecord> PaintRecordBuilder::EndRecording(
     const PropertyTreeState& replay_state) {
   paint_controller_->CommitNewDisplayItems();
-  paint_controller_->FinishCycle();
   return paint_controller_->GetPaintArtifact().GetPaintRecord(replay_state);
 }
 
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_record_builder_test.cc b/third_party/blink/renderer/platform/graphics/paint/paint_record_builder_test.cc
index efd5eb3cc..c7829348 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_record_builder_test.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_record_builder_test.cc
@@ -36,34 +36,40 @@
 }
 
 TEST_F(PaintRecordBuilderTest, LastingPaintController) {
-  InitRootChunk();
-
+  FakeDisplayItemClient client("client");
   PaintRecordBuilder builder(GetPaintController());
   auto& context = builder.Context();
-  EXPECT_EQ(&context.GetPaintController(), &GetPaintController());
-
-  FakeDisplayItemClient client("client");
-  DrawRect(context, client, kBackgroundType, IntRect(10, 10, 20, 20));
-  DrawRect(context, client, kForegroundType, IntRect(15, 15, 10, 10));
-  EXPECT_FALSE(ClientCacheIsValid(client));
-
   MockPaintCanvas canvas;
   PaintFlags flags;
-  EXPECT_CALL(canvas, drawPicture(_)).Times(1);
-  builder.EndRecording(canvas);
+  {
+    PaintController::CycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+
+    EXPECT_EQ(&context.GetPaintController(), &GetPaintController());
+
+    DrawRect(context, client, kBackgroundType, IntRect(10, 10, 20, 20));
+    DrawRect(context, client, kForegroundType, IntRect(15, 15, 10, 10));
+    EXPECT_FALSE(ClientCacheIsValid(client));
+
+    EXPECT_CALL(canvas, drawPicture(_)).Times(1);
+    builder.EndRecording(canvas);
+  }
   EXPECT_TRUE(ClientCacheIsValid(client));
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&client, kBackgroundType),
                           IsSameId(&client, kForegroundType)));
 
-  InitRootChunk();
-  EXPECT_TRUE(DrawingRecorder::UseCachedDrawingIfPossible(context, client,
-                                                          kBackgroundType));
-  EXPECT_TRUE(DrawingRecorder::UseCachedDrawingIfPossible(context, client,
-                                                          kForegroundType));
-  EXPECT_CALL(canvas, drawPicture(_)).Times(1);
-  builder.EndRecording(canvas);
+  {
+    PaintController::CycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    EXPECT_TRUE(DrawingRecorder::UseCachedDrawingIfPossible(context, client,
+                                                            kBackgroundType));
+    EXPECT_TRUE(DrawingRecorder::UseCachedDrawingIfPossible(context, client,
+                                                            kForegroundType));
+    EXPECT_CALL(canvas, drawPicture(_)).Times(1);
+    builder.EndRecording(canvas);
+  }
 
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&client, kBackgroundType),
@@ -73,21 +79,27 @@
 
 TEST_F(PaintRecordBuilderTest, TransientAndAnotherPaintController) {
   GraphicsContext context(GetPaintController());
-
-  InitRootChunk();
   FakeDisplayItemClient client("client");
-  DrawRect(context, client, kBackgroundType, IntRect(10, 10, 20, 20));
-  DrawRect(context, client, kForegroundType, IntRect(15, 15, 10, 10));
-  CommitAndFinishCycle();
+  PaintRecordBuilder builder;
+  {
+    PaintController::CycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    DrawRect(context, client, kBackgroundType, IntRect(10, 10, 20, 20));
+    DrawRect(context, client, kForegroundType, IntRect(15, 15, 10, 10));
+    GetPaintController().CommitNewDisplayItems();
+  }
   EXPECT_THAT(GetPaintController().GetDisplayItemList(),
               ElementsAre(IsSameId(&client, kBackgroundType),
                           IsSameId(&client, kForegroundType)));
   EXPECT_TRUE(ClientCacheIsValid(client));
 
-  PaintRecordBuilder builder;
-  EXPECT_NE(&builder.Context().GetPaintController(), &GetPaintController());
-  DrawRect(builder.Context(), client, kBackgroundType, IntRect(10, 10, 20, 20));
-  builder.EndRecording();
+  {
+    PaintController::CycleScope cycle_scope(GetPaintController());
+    EXPECT_NE(&builder.Context().GetPaintController(), &GetPaintController());
+    DrawRect(builder.Context(), client, kBackgroundType,
+             IntRect(10, 10, 20, 20));
+    builder.EndRecording();
+  }
 
   // The transient PaintController in PaintRecordBuilder doesn't affect the
   // client's cache status in another PaintController.
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_under_invalidation_checker_test.cc b/third_party/blink/renderer/platform/graphics/paint/paint_under_invalidation_checker_test.cc
index 1b22a95..f93aba6 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_under_invalidation_checker_test.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_under_invalidation_checker_test.cc
@@ -29,15 +29,21 @@
     FakeDisplayItemClient first("first");
     GraphicsContext context(GetPaintController());
 
-    InitRootChunk();
-    DrawRect(context, first, kBackgroundType, IntRect(1, 1, 1, 1));
-    DrawRect(context, first, kForegroundType, IntRect(1, 1, 3, 3));
-    CommitAndFinishCycle();
+    {
+      PaintController::CycleScope cycle_scope(GetPaintController());
+      InitRootChunk();
+      DrawRect(context, first, kBackgroundType, IntRect(1, 1, 1, 1));
+      DrawRect(context, first, kForegroundType, IntRect(1, 1, 3, 3));
+      GetPaintController().CommitNewDisplayItems();
+    }
 
-    InitRootChunk();
-    DrawRect(context, first, kBackgroundType, IntRect(2, 2, 3, 3));
-    DrawRect(context, first, kForegroundType, IntRect(1, 1, 3, 3));
-    CommitAndFinishCycle();
+    {
+      PaintController::CycleScope cycle_scope(GetPaintController());
+      InitRootChunk();
+      DrawRect(context, first, kBackgroundType, IntRect(2, 2, 3, 3));
+      DrawRect(context, first, kForegroundType, IntRect(1, 1, 3, 3));
+      GetPaintController().CommitNewDisplayItems();
+    }
   };
 
   EXPECT_DEATH(test(),
@@ -55,14 +61,20 @@
   FakeDisplayItemClient first("first");
   GraphicsContext context(GetPaintController());
 
-  InitRootChunk();
-  DrawRect(context, first, kBackgroundType, IntRect(1, 1, 1, 1));
-  CommitAndFinishCycle();
+  {
+    PaintController::CycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    DrawRect(context, first, kBackgroundType, IntRect(1, 1, 1, 1));
+    GetPaintController().CommitNewDisplayItems();
+  }
 
-  InitRootChunk();
-  DrawRect(context, first, kBackgroundType, IntRect(1, 1, 1, 1));
-  DrawRect(context, first, kForegroundType, IntRect(1, 1, 3, 3));
-  CommitAndFinishCycle();
+  {
+    PaintController::CycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    DrawRect(context, first, kBackgroundType, IntRect(1, 1, 1, 1));
+    DrawRect(context, first, kForegroundType, IntRect(1, 1, 3, 3));
+    GetPaintController().CommitNewDisplayItems();
+  }
 }
 
 TEST_F(PaintControllerUnderInvalidationTest, LessDrawing) {
@@ -71,37 +83,49 @@
   FakeDisplayItemClient first("first");
   GraphicsContext context(GetPaintController());
 
-  InitRootChunk();
-  DrawRect(context, first, kBackgroundType, IntRect(1, 1, 1, 1));
-  DrawRect(context, first, kForegroundType, IntRect(1, 1, 3, 3));
-  CommitAndFinishCycle();
+  {
+    PaintController::CycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    DrawRect(context, first, kBackgroundType, IntRect(1, 1, 1, 1));
+    DrawRect(context, first, kForegroundType, IntRect(1, 1, 3, 3));
+    GetPaintController().CommitNewDisplayItems();
+  }
 
-  InitRootChunk();
-  DrawRect(context, first, kBackgroundType, IntRect(1, 1, 1, 1));
-  CommitAndFinishCycle();
+  {
+    PaintController::CycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    DrawRect(context, first, kBackgroundType, IntRect(1, 1, 1, 1));
+    GetPaintController().CommitNewDisplayItems();
+  }
 }
 
 TEST_F(PaintControllerUnderInvalidationTest, ChangeDrawingInSubsequence) {
   auto test = [&]() {
     FakeDisplayItemClient first("first");
     GraphicsContext context(GetPaintController());
-    InitRootChunk();
     {
-      SubsequenceRecorder r(context, first);
-      DrawRect(context, first, kBackgroundType, IntRect(1, 1, 1, 1));
-      DrawRect(context, first, kForegroundType, IntRect(1, 1, 3, 3));
+      PaintController::CycleScope cycle_scope(GetPaintController());
+      InitRootChunk();
+      {
+        SubsequenceRecorder r(context, first);
+        DrawRect(context, first, kBackgroundType, IntRect(1, 1, 1, 1));
+        DrawRect(context, first, kForegroundType, IntRect(1, 1, 3, 3));
+      }
+      GetPaintController().CommitNewDisplayItems();
     }
-    CommitAndFinishCycle();
 
-    InitRootChunk();
     {
-      EXPECT_FALSE(
-          SubsequenceRecorder::UseCachedSubsequenceIfPossible(context, first));
-      SubsequenceRecorder r(context, first);
-      DrawRect(context, first, kBackgroundType, IntRect(2, 2, 1, 1));
-      DrawRect(context, first, kForegroundType, IntRect(1, 1, 3, 3));
+      PaintController::CycleScope cycle_scope(GetPaintController());
+      InitRootChunk();
+      {
+        EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
+            context, first));
+        SubsequenceRecorder r(context, first);
+        DrawRect(context, first, kBackgroundType, IntRect(2, 2, 1, 1));
+        DrawRect(context, first, kForegroundType, IntRect(1, 1, 3, 3));
+      }
+      GetPaintController().CommitNewDisplayItems();
     }
-    CommitAndFinishCycle();
   };
 
   EXPECT_DEATH(test(),
@@ -119,22 +143,28 @@
     FakeDisplayItemClient first("first");
     GraphicsContext context(GetPaintController());
 
-    InitRootChunk();
     {
-      SubsequenceRecorder r(context, first);
-      DrawRect(context, first, kBackgroundType, IntRect(1, 1, 1, 1));
+      PaintController::CycleScope cycle_scope(GetPaintController());
+      InitRootChunk();
+      {
+        SubsequenceRecorder r(context, first);
+        DrawRect(context, first, kBackgroundType, IntRect(1, 1, 1, 1));
+      }
+      GetPaintController().CommitNewDisplayItems();
     }
-    CommitAndFinishCycle();
 
-    InitRootChunk();
     {
-      EXPECT_FALSE(
-          SubsequenceRecorder::UseCachedSubsequenceIfPossible(context, first));
-      SubsequenceRecorder r(context, first);
-      DrawRect(context, first, kBackgroundType, IntRect(1, 1, 1, 1));
-      DrawRect(context, first, kForegroundType, IntRect(1, 1, 3, 3));
+      PaintController::CycleScope cycle_scope(GetPaintController());
+      InitRootChunk();
+      {
+        EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
+            context, first));
+        SubsequenceRecorder r(context, first);
+        DrawRect(context, first, kBackgroundType, IntRect(1, 1, 1, 1));
+        DrawRect(context, first, kForegroundType, IntRect(1, 1, 3, 3));
+      }
+      GetPaintController().CommitNewDisplayItems();
     }
-    CommitAndFinishCycle();
   };
 
   EXPECT_DEATH(test(),
@@ -151,22 +181,28 @@
     FakeDisplayItemClient first("first");
     GraphicsContext context(GetPaintController());
 
-    InitRootChunk();
     {
-      SubsequenceRecorder r(context, first);
-      DrawRect(context, first, kBackgroundType, IntRect(1, 1, 3, 3));
-      DrawRect(context, first, kForegroundType, IntRect(1, 1, 3, 3));
+      PaintController::CycleScope cycle_scope(GetPaintController());
+      InitRootChunk();
+      {
+        SubsequenceRecorder r(context, first);
+        DrawRect(context, first, kBackgroundType, IntRect(1, 1, 3, 3));
+        DrawRect(context, first, kForegroundType, IntRect(1, 1, 3, 3));
+      }
+      GetPaintController().CommitNewDisplayItems();
     }
-    CommitAndFinishCycle();
 
-    InitRootChunk();
     {
-      EXPECT_FALSE(
-          SubsequenceRecorder::UseCachedSubsequenceIfPossible(context, first));
-      SubsequenceRecorder r(context, first);
-      DrawRect(context, first, kBackgroundType, IntRect(1, 1, 3, 3));
+      PaintController::CycleScope cycle_scope(GetPaintController());
+      InitRootChunk();
+      {
+        EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
+            context, first));
+        SubsequenceRecorder r(context, first);
+        DrawRect(context, first, kBackgroundType, IntRect(1, 1, 3, 3));
+      }
+      GetPaintController().CommitNewDisplayItems();
     }
-    CommitAndFinishCycle();
   };
 
   EXPECT_DEATH(test(),
@@ -182,23 +218,29 @@
   FakeDisplayItemClient content("content");
   GraphicsContext context(GetPaintController());
 
-  InitRootChunk();
   {
-    SubsequenceRecorder r(context, container);
-    DrawRect(context, content, kBackgroundType, IntRect(1, 1, 3, 3));
+    PaintController::CycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    {
+      SubsequenceRecorder r(context, container);
+      DrawRect(context, content, kBackgroundType, IntRect(1, 1, 3, 3));
+    }
+    GetPaintController().CommitNewDisplayItems();
   }
-  CommitAndFinishCycle();
 
   content.Invalidate();
-  InitRootChunk();
-  // Leave container not invalidated.
   {
-    EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
-        context, container));
-    SubsequenceRecorder r(context, container);
-    DrawRect(context, content, kBackgroundType, IntRect(1, 1, 3, 3));
+    PaintController::CycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    // Leave container not invalidated.
+    {
+      EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
+          context, container));
+      SubsequenceRecorder r(context, container);
+      DrawRect(context, content, kBackgroundType, IntRect(1, 1, 3, 3));
+    }
+    GetPaintController().CommitNewDisplayItems();
   }
-  CommitAndFinishCycle();
 }
 
 TEST_F(PaintControllerUnderInvalidationTest, SubsequenceBecomesEmpty) {
@@ -206,20 +248,26 @@
     FakeDisplayItemClient target("target");
     GraphicsContext context(GetPaintController());
 
-    InitRootChunk();
     {
-      SubsequenceRecorder r(context, target);
-      DrawRect(context, target, kBackgroundType, IntRect(1, 1, 3, 3));
+      PaintController::CycleScope cycle_scope(GetPaintController());
+      InitRootChunk();
+      {
+        SubsequenceRecorder r(context, target);
+        DrawRect(context, target, kBackgroundType, IntRect(1, 1, 3, 3));
+      }
+      GetPaintController().CommitNewDisplayItems();
     }
-    CommitAndFinishCycle();
 
-    InitRootChunk();
     {
-      EXPECT_FALSE(
-          SubsequenceRecorder::UseCachedSubsequenceIfPossible(context, target));
-      SubsequenceRecorder r(context, target);
+      PaintController::CycleScope cycle_scope(GetPaintController());
+      InitRootChunk();
+      {
+        EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
+            context, target));
+        SubsequenceRecorder r(context, target);
+      }
+      GetPaintController().CommitNewDisplayItems();
     }
-    CommitAndFinishCycle();
   };
 
   EXPECT_DEATH(test(),
@@ -232,29 +280,35 @@
   FakeDisplayItemClient content("content");
   GraphicsContext context(GetPaintController());
 
-  InitRootChunk();
   {
-    SubsequenceRecorder r(context, container);
+    PaintController::CycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
     {
-      DisplayItemCacheSkipper cache_skipper(context);
-      DrawRect(context, content, kBackgroundType, IntRect(1, 1, 3, 3));
+      SubsequenceRecorder r(context, container);
+      {
+        DisplayItemCacheSkipper cache_skipper(context);
+        DrawRect(context, content, kBackgroundType, IntRect(1, 1, 3, 3));
+      }
+      DrawRect(context, content, kForegroundType, IntRect(2, 2, 4, 4));
     }
-    DrawRect(context, content, kForegroundType, IntRect(2, 2, 4, 4));
+    GetPaintController().CommitNewDisplayItems();
   }
-  CommitAndFinishCycle();
 
-  InitRootChunk();
   {
-    EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
-        context, container));
-    SubsequenceRecorder r(context, container);
+    PaintController::CycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
     {
-      DisplayItemCacheSkipper cache_skipper(context);
-      DrawRect(context, content, kBackgroundType, IntRect(2, 2, 4, 4));
+      EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
+          context, container));
+      SubsequenceRecorder r(context, container);
+      {
+        DisplayItemCacheSkipper cache_skipper(context);
+        DrawRect(context, content, kBackgroundType, IntRect(2, 2, 4, 4));
+      }
+      DrawRect(context, content, kForegroundType, IntRect(2, 2, 4, 4));
     }
-    DrawRect(context, content, kForegroundType, IntRect(2, 2, 4, 4));
+    GetPaintController().CommitNewDisplayItems();
   }
-  CommitAndFinishCycle();
 }
 
 TEST_F(PaintControllerUnderInvalidationTest,
@@ -263,27 +317,33 @@
   FakeDisplayItemClient content("content");
   GraphicsContext context(GetPaintController());
 
-  InitRootChunk();
   {
-    SubsequenceRecorder r(context, container);
-    DrawRect(context, container, kBackgroundType, IntRect(1, 1, 3, 3));
-    { SubsequenceRecorder r1(context, content); }
-    DrawRect(context, container, kForegroundType, IntRect(1, 1, 3, 3));
+    PaintController::CycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    {
+      SubsequenceRecorder r(context, container);
+      DrawRect(context, container, kBackgroundType, IntRect(1, 1, 3, 3));
+      { SubsequenceRecorder r1(context, content); }
+      DrawRect(context, container, kForegroundType, IntRect(1, 1, 3, 3));
+    }
+    GetPaintController().CommitNewDisplayItems();
   }
-  CommitAndFinishCycle();
 
-  InitRootChunk();
   {
-    EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
-        context, container));
-    SubsequenceRecorder r(context, container);
-    DrawRect(context, container, kBackgroundType, IntRect(1, 1, 3, 3));
-    EXPECT_FALSE(
-        SubsequenceRecorder::UseCachedSubsequenceIfPossible(context, content));
-    { SubsequenceRecorder r1(context, content); }
-    DrawRect(context, container, kForegroundType, IntRect(1, 1, 3, 3));
+    PaintController::CycleScope cycle_scope(GetPaintController());
+    InitRootChunk();
+    {
+      EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
+          context, container));
+      SubsequenceRecorder r(context, container);
+      DrawRect(context, container, kBackgroundType, IntRect(1, 1, 3, 3));
+      EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
+          context, content));
+      { SubsequenceRecorder r1(context, content); }
+      DrawRect(context, container, kForegroundType, IntRect(1, 1, 3, 3));
+    }
+    GetPaintController().CommitNewDisplayItems();
   }
-  CommitAndFinishCycle();
 }
 
 #endif  // defined(GTEST_HAS_DEATH_TEST) && !defined(OS_ANDROID)
diff --git a/third_party/blink/renderer/platform/graphics/paint/subsequence_recorder.h b/third_party/blink/renderer/platform/graphics/paint/subsequence_recorder.h
index b8a22435..9781d7e9 100644
--- a/third_party/blink/renderer/platform/graphics/paint/subsequence_recorder.h
+++ b/third_party/blink/renderer/platform/graphics/paint/subsequence_recorder.h
@@ -37,6 +37,7 @@
   SubsequenceRecorder(GraphicsContext& context, const DisplayItemClient& client)
       : paint_controller_(context.GetPaintController()) {
     subsequence_index_ = paint_controller_.BeginSubsequence(client);
+    paint_controller_.MarkClientForValidation(client);
   }
 
   SubsequenceRecorder(const SubsequenceRecorder&) = delete;
diff --git a/third_party/blink/renderer/platform/mhtml/mhtml_archive.cc b/third_party/blink/renderer/platform/mhtml/mhtml_archive.cc
index 7170f118..0dcc944 100644
--- a/third_party/blink/renderer/platform/mhtml/mhtml_archive.cc
+++ b/third_party/blink/renderer/platform/mhtml/mhtml_archive.cc
@@ -444,7 +444,7 @@
 }
 
 ArchiveResource* MHTMLArchive::SubresourceForURL(const KURL& url) const {
-  return subresources_.at(url.GetString());
+  return subresources_.DeprecatedAtOrEmptyValue(url.GetString());
 }
 
 String MHTMLArchive::GetCacheIdentifier() const {
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index fa6aba8..725540df 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -2332,11 +2332,6 @@
       origin_trial_feature_name: "WebAssemblyExceptions",
       status: "test",
     },
-    {
-      name: "WebAssemblySimd",
-      origin_trial_feature_name: "WebAssemblySimd",
-      status: "test",
-    },
     // WebAuth is disabled on Android versions prior to N (7.0) due to lack of
     // supporting APIs, see runtime_features.cc.
     {
diff --git a/third_party/blink/renderer/platform/wtf/text/text_encoding_registry.cc b/third_party/blink/renderer/platform/wtf/text/text_encoding_registry.cc
index 4ba14af..06772d31 100644
--- a/third_party/blink/renderer/platform/wtf/text/text_encoding_registry.cc
+++ b/third_party/blink/renderer/platform/wtf/text/text_encoding_registry.cc
@@ -124,7 +124,8 @@
 #else
 
 static void CheckExistingName(const char* alias, const char* atomic_name) {
-  const char* old_atomic_name = g_text_encoding_name_map->at(alias);
+  const char* old_atomic_name =
+      g_text_encoding_name_map->DeprecatedAtOrEmptyValue(alias);
   if (!old_atomic_name)
     return;
   if (old_atomic_name == atomic_name)
@@ -160,7 +161,8 @@
   DCHECK_LE(strlen(alias), kMaxEncodingNameLength);
   if (IsUndesiredAlias(alias))
     return;
-  const char* atomic_name = g_text_encoding_name_map->at(name);
+  const char* atomic_name =
+      g_text_encoding_name_map->DeprecatedAtOrEmptyValue(name);
   DCHECK(strcmp(alias, name) == 0 || atomic_name);
   if (!atomic_name)
     atomic_name = name;
@@ -224,13 +226,14 @@
   if (!g_text_encoding_name_map)
     BuildBaseTextCodecMaps();
 
-  if (const char* atomic_name = g_text_encoding_name_map->at(name))
+  if (const char* atomic_name =
+          g_text_encoding_name_map->DeprecatedAtOrEmptyValue(name))
     return atomic_name;
   if (AtomicDidExtendTextCodecMaps())
     return nullptr;
   ExtendTextCodecMaps();
   AtomicSetDidExtendTextCodecMaps();
-  return g_text_encoding_name_map->at(name);
+  return g_text_encoding_name_map->DeprecatedAtOrEmptyValue(name);
 }
 
 template <typename CharacterType>
diff --git a/third_party/blink/web_tests/LeakExpectations b/third_party/blink/web_tests/LeakExpectations
index fc235c0..d581e55 100644
--- a/third_party/blink/web_tests/LeakExpectations
+++ b/third_party/blink/web_tests/LeakExpectations
@@ -13,7 +13,7 @@
 # ask hajimehoshi@ or yuzus@.                                             #
 ###########################################################################
 
-crbug.com/786995 virtual/threaded/http/tests/devtools/tracing/timeline-style/timeline-style-recalc-all-invalidator-types.js [ Failure Pass ]
+crbug.com/786995 virtual/threaded/http/tests/devtools/tracing/timeline-style/timeline-style-recalc-all-invalidator-types.js [ Skip ]
 
 # Requests with keepalive specified will be kept alive even when the frame is
 # detached, which means leaks reported by the leak detector are by design.
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 48a98b5..25cb6c2 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -7155,7 +7155,7 @@
 crbug.com/1225653 virtual/file-system-access-access-handle-incognito/external/wpt/file-system-access/sandboxed_FileSystemSyncAccessHandle-read-write.https.tentative.worker.html [ Pass ]
 crbug.com/1225653 virtual/file-system-access-access-handle-incognito/external/wpt/file-system-access/sandboxed_FileSystemSyncAccessHandle-flush.https.tentative.worker.html [ Skip ]
 crbug.com/1225653 virtual/file-system-access-access-handle-incognito/external/wpt/file-system-access/sandboxed_FileSystemSyncAccessHandle-close.https.tentative.worker.html [ Skip ]
-crbug.com/1225653 virtual/file-system-access-access-handle-incognito/external/wpt/file-system-access/sandboxed_FileSystemSyncAccessHandle-getSize.https.tentative.worker.html [ Skip ]
+crbug.com/1225653 virtual/file-system-access-access-handle-incognito/external/wpt/file-system-access/sandboxed_FileSystemSyncAccessHandle-getSize.https.tentative.worker.html [ Pass ]
 crbug.com/1225653 virtual/file-system-access-access-handle-incognito/external/wpt/file-system-access/sandboxed_FileSystemSyncAccessHandle-truncate.https.tentative.worker.html [ Skip ]
 
 # Sheriff 2021-06-30
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/xml-class-selector-ref.xml b/third_party/blink/web_tests/external/wpt/css/selectors/xml-class-selector-ref.xml
new file mode 100644
index 0000000..6b44280
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/xml-class-selector-ref.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <title>Class selectors in an XML namespace</title>
+  <link rel="author" href="mailto:masonf@chromium.org" />
+  <body>
+    <p>The .class selector should work in any namespace. Both boxes should be green.</p>
+    <Boxes xmlns="http://foo">
+      <box>.classname selector</box>
+      <box>*[class~="classname"] selector</box>
+    </Boxes>
+
+    <style>
+      box {background:green;}
+    </style>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/xml-class-selector.xml b/third_party/blink/web_tests/external/wpt/css/selectors/xml-class-selector.xml
new file mode 100644
index 0000000..00fda11
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/xml-class-selector.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <title>Class selectors in an XML namespace</title>
+  <link rel="author" href="mailto:masonf@chromium.org" />
+  <link rel="help" href="https://dom.spec.whatwg.org/#dom-element-classname" />
+  <link rel="match" href="xml-class-selector-ref.xml" />
+  <body>
+    <p>The .class selector should work in any namespace. Both boxes should be green.</p>
+    <Boxes xmlns="http://foo">
+      <box class="green">.classname selector</box>
+      <box class="green2">*[class~="classname"] selector</box>
+    </Boxes>
+
+    <style>
+      box {background:red;}
+      .green {background: green;}
+      *[class~="green2"] {background: green;}
+    </style>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/hidden-elements-expected.txt b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/hidden-elements-expected.txt
index bba3d84..78ec33ef 100644
--- a/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/hidden-elements-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/hidden-elements-expected.txt
@@ -1,21 +1,21 @@
 This is a testharness.js-based test.
-PASS source should not be display:none
-PASS track should not be display:none
-FAIL area should be display:none assert_true: expected true got false
-PASS base should be display:none
-PASS basefont should be display:none
-PASS datalist should be display:none
-PASS head should be display:none
-PASS link should be display:none
-PASS meta should be display:none
-PASS noembed should be display:none
-PASS noframes should be display:none
-PASS param should be display:none
-PASS rp should be display:none
-PASS script should be display:none
-PASS style should be display:none
-PASS template should be display:none
-PASS title should be display:none
-PASS [hidden] element should be display:none
+PASS source should not be hidden
+PASS track should not be hidden
+FAIL area should be hidden assert_equals: expected "none" but got "inline"
+PASS base should be hidden
+PASS basefont should be hidden
+PASS datalist should be hidden
+PASS head should be hidden
+PASS link should be hidden
+PASS meta should be hidden
+PASS noembed should be hidden
+PASS noframes should be hidden
+PASS param should be hidden
+PASS rp should be hidden
+PASS script should be hidden
+PASS style should be hidden
+PASS template should be hidden
+PASS title should be hidden
+PASS [hidden] element should be hidden
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/hidden-elements.html b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/hidden-elements.html
index 577f32d..4286681 100644
--- a/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/hidden-elements.html
+++ b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/hidden-elements.html
@@ -13,27 +13,23 @@
   "noframes", "param", "rp", "script", "style", "template", "title",
 ];
 
-function isDisplayNone(element) {
-  return getComputedStyle(element).display === "none";
-}
-
 for (let name of kNotHiddenElementLocalNames) {
   test(function() {
     let element = document.createElement(name);
     document.body.appendChild(element);
-    assert_false(isDisplayNone(element));
-  }, `${name} should not be display:none`);
+    assert_equals(getComputedStyle(element).display, "inline");
+  }, `${name} should not be hidden`);
 }
 
 for (let name of kHiddenElementLocalNames) {
   test(function() {
     let element = document.createElement(name);
     document.body.appendChild(element);
-    assert_true(isDisplayNone(element));
-  }, `${name} should be display:none`);
+    assert_equals(getComputedStyle(element).display, "none");
+  }, `${name} should be hidden`);
 }
 
 test(function() {
-  assert_true(isDisplayNone(document.querySelector("[hidden]")));
-}, `[hidden] element should be display:none`);
+  assert_equals(getComputedStyle(document.querySelector("[hidden]")).display, "none");
+}, `[hidden] element should be hidden`);
 </script>
diff --git a/third_party/blink/web_tests/flag-specific/disable-layout-ng/fast/parser/xhtml-alternate-entities-expected.png b/third_party/blink/web_tests/flag-specific/disable-layout-ng/fast/parser/xhtml-alternate-entities-expected.png
index 79f74b5..25c33ef 100644
--- a/third_party/blink/web_tests/flag-specific/disable-layout-ng/fast/parser/xhtml-alternate-entities-expected.png
+++ b/third_party/blink/web_tests/flag-specific/disable-layout-ng/fast/parser/xhtml-alternate-entities-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/parser/xhtml-alternate-entities-expected.png b/third_party/blink/web_tests/platform/linux/fast/parser/xhtml-alternate-entities-expected.png
index 954d31b..f95e579 100644
--- a/third_party/blink/web_tests/platform/linux/fast/parser/xhtml-alternate-entities-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/parser/xhtml-alternate-entities-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/parser/xhtml-alternate-entities-expected.png b/third_party/blink/web_tests/platform/mac/fast/parser/xhtml-alternate-entities-expected.png
index 06638e2..6508f8e 100644
--- a/third_party/blink/web_tests/platform/mac/fast/parser/xhtml-alternate-entities-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/parser/xhtml-alternate-entities-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/parser/xhtml-alternate-entities-expected.png b/third_party/blink/web_tests/platform/win/fast/parser/xhtml-alternate-entities-expected.png
index 3a23e07..5b26601ed 100644
--- a/third_party/blink/web_tests/platform/win/fast/parser/xhtml-alternate-entities-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/parser/xhtml-alternate-entities-expected.png
Binary files differ
diff --git a/third_party/freetype/README.chromium b/third_party/freetype/README.chromium
index 049045c..c134b33 100644
--- a/third_party/freetype/README.chromium
+++ b/third_party/freetype/README.chromium
@@ -1,7 +1,7 @@
 Name: FreeType
 URL: http://www.freetype.org/
-Version: VER-2-11-0-18-g47cf8ebf4
-Revision: 47cf8ebf4a78ed42da455a98d77a92ce6a180d78
+Version: VER-2-11-0-22-gfed552101
+Revision: fed5521016227bf8cc4475f66450a9963568d162
 CPEPrefix: cpe:/a:freetype:freetype:2.10.4
 License: Custom license "inspired by the BSD, Artistic, and IJG (Independent
          JPEG Group) licenses"
diff --git a/third_party/nearby/BUILD.gn b/third_party/nearby/BUILD.gn
index ae0404a..53f7b68 100644
--- a/third_party/nearby/BUILD.gn
+++ b/third_party/nearby/BUILD.gn
@@ -13,7 +13,7 @@
 
 proto_library("web_rtc_signaling_frames_proto") {
   sources = [ "src/proto/mediums/web_rtc_signaling_frames.proto" ]
-  proto_out_dir = "location/nearby/mediums/proto"
+  proto_out_dir = "third_party/nearby/proto/mediums"
 }
 
 # src/proto
diff --git a/third_party/nearby/README.chromium b/third_party/nearby/README.chromium
index bacce12..e00fb1a 100644
--- a/third_party/nearby/README.chromium
+++ b/third_party/nearby/README.chromium
@@ -1,7 +1,7 @@
 Name: Nearby Connections Library
 Short Name: Nearby
 URL: https://github.com/google/nearby-connections
-Version: 7c1f423014003e2bf4ed2feb7520b3355c834fed
+Version: 7a02eaad37a4e0d3948750c987e82fcb94225547
 License: Apache 2.0
 License File: LICENSE
 Security Critical: yes
diff --git a/third_party/protobuf/proto_library.gni b/third_party/protobuf/proto_library.gni
index 1561a86..38b09fd 100644
--- a/third_party/protobuf/proto_library.gni
+++ b/third_party/protobuf/proto_library.gni
@@ -31,6 +31,10 @@
 #   generate_library (optional, default true)
 #       Generate a "static_library" target for linking with the generated code.
 #
+#   TODO(crbug.com/1237958): Remove allow_optional when proto rolls to 3.15.
+#   allow_optional (optional, default false)
+#       Enables experimental_allow_proto3_optional.
+#
 #   cc_generator_options (optional)
 #       List of extra flags passed to the protocol compiler.  If you need to
 #       add an EXPORT macro to a protobuf's C++ header, set the
@@ -358,6 +362,9 @@
           invoker.cc_generator_options,
         ]
       }
+      if (defined(invoker.allow_optional) && invoker.allow_optional == true) {
+        args += [ "--allow-optional" ]
+      }
       if (defined(invoker.cc_include)) {
         args += [
           "--include",
diff --git a/tools/android/avd/proto/generic_android28.textpb b/tools/android/avd/proto/generic_android28.textpb
index 96017414..22229c49 100644
--- a/tools/android/avd/proto/generic_android28.textpb
+++ b/tools/android/avd/proto/generic_android28.textpb
@@ -6,20 +6,20 @@
 
 emulator_package {
   package_name: "chromium/third_party/android_sdk/public/emulator"
-  version: "Dogc_gNCYNb3fIG-ovlMkV5EhjaYwdA_Jw9goUpl3A8C"  # 30.7.5
+  version: "xhyuoquVvBTcJelgRjMKZeoBVSQRjB7pLVJPt5C9saIC"
   dest_path: ".emulator_sdk"
 }
 
 system_image_package {
   package_name: "chromium/third_party/android_sdk/public/system-images/android-28/google_apis/x86"
-  version: "6Su449q7w5pa__QFk2yDsRT-xWiiDRLLJ3uPiQtJquoC"  # 12
+  version: "LDa0XkTjgGYx7Amzg5qjIRgCfc4F_pq7rKMJVdACYx8C"
   dest_path: ".emulator_sdk"
 }
 system_image_name: "system-images;android-28;google_apis;x86"
 
 avd_package {
   package_name: "chromium/third_party/android_sdk/public/avds/android-28/google_apis/x86"
-  version: "M-Xg_Vm48lSHYnKaeaOYdXyQNDND9jQs9br0I80Lpt8C"  # created in bb_id 8839810454095989792
+  version: "WTz-I74ingG51r_ChkqEoPrerAHf8vqjqcaEnvg8b54C"
   dest_path: ".android"
 }
 avd_name: "android_28_google_apis_x86"
diff --git a/tools/android/avd/proto/generic_playstore_android28.textpb b/tools/android/avd/proto/generic_playstore_android28.textpb
index 2581dac..ef5f53e 100644
--- a/tools/android/avd/proto/generic_playstore_android28.textpb
+++ b/tools/android/avd/proto/generic_playstore_android28.textpb
@@ -6,20 +6,20 @@
 
 emulator_package {
   package_name: "chromium/third_party/android_sdk/public/emulator"
-  version: "Dogc_gNCYNb3fIG-ovlMkV5EhjaYwdA_Jw9goUpl3A8C"  # 30.7.5
+  version: "lnt2Oz8NS73mAJL389R1QwgbM2qDSKNIRWTUOdmywukC"
   dest_path: ".emulator_sdk"
 }
 
 system_image_package {
   package_name: "chromium/third_party/android_sdk/public/system-images/android-28/google_apis_playstore/x86"
-  version: "ypyVIHZKfJqFfYlDJzi22Ty7iKT6zlzChE396PLyPF4C"  # 9
+  version: "aHLrqkrOa04ksjgo_LhSRx9V9P_FKxFqEJohj9cYsBUC"
   dest_path: ".emulator_sdk"
 }
 system_image_name: "system-images;android-28;google_apis_playstore;x86"
 
 avd_package {
   package_name: "chromium/third_party/android_sdk/public/avds/android-28/google_apis_playstore/x86"
-  version: "3ZmKHZKy6oyhwel5QKOExpao9ZW24N9YpKZ_W5MvFqMC"  # created in bb_id 8839810454095989792
+  version: "bmg8zBF8218BBIET9QlBTJvCtf1Pn1KN4jqJNkp2JlcC"
   dest_path: ".android"
 }
 avd_name: "android_28_google_apis_playstore_x86"
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index ed56ec5..2d828df 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -53683,7 +53683,7 @@
   <int value="0" label="Valid match"/>
   <int value="1" label="Suggestion mismatch"/>
   <int value="2" label="Result array size mismatch"/>
-  <int value="3" label="Native match dead"/>
+  <int value="3" label="Native match dead (obsolete)"/>
   <int value="4" label="Suggestion index out of bounds"/>
 </enum>
 
@@ -56366,6 +56366,9 @@
   <int value="12"
       label="A navigation suggestion is found using a character swap match
              against an engaged site."/>
+  <int value="13"
+      label="A navigation suggestion is found using a character swap match
+             against a top 500 domain."/>
 </enum>
 
 <enum name="NavigationURLScheme">
@@ -56604,6 +56607,7 @@
   <int value="8" label="WiFi Direct"/>
   <int value="9" label="WebRTC"/>
   <int value="10" label="No upgrade"/>
+  <int value="11" label="BLE L2CAP"/>
 </enum>
 
 <enum name="NearbyShareVisibility">
@@ -61582,6 +61586,7 @@
   <int value="701" label="Remove Play Store"/>
   <int value="702" label="Turn On Play Store"/>
   <int value="703" label="Restore Apps And Pages On Startup"/>
+  <int value="704" label="Do Not Disturb: On/Off"/>
   <int value="800" label="Set Up Crostini"/>
   <int value="801" label="Uninstall Crostini"/>
   <int value="802" label="Backup Linux Apps And Files"/>
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml
index dc1986d1..1ac44ba 100644
--- a/tools/metrics/histograms/metadata/ash/histograms.xml
+++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -2017,7 +2017,7 @@
 </histogram>
 
 <histogram name="Ash.PciePeripheral.ConnectivityResults"
-    enum="PciePeripheralConnectivityResult" expires_after="M94">
+    enum="PciePeripheralConnectivityResult" expires_after="2022-03-15">
   <owner>jimmyxgong@chromium.org</owner>
   <owner>cros-peripherals@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/browser/histograms.xml b/tools/metrics/histograms/metadata/browser/histograms.xml
index 256656c..9f604216 100644
--- a/tools/metrics/histograms/metadata/browser/histograms.xml
+++ b/tools/metrics/histograms/metadata/browser/histograms.xml
@@ -120,7 +120,7 @@
 
 <histogram
     name="Browser.ContinuousSearch.BackNavigationToSrp{ExperimentCategory}"
-    units="count" expires_after="2021-09-20">
+    units="count" expires_after="2021-12-20">
   <owner>ckitagawa@chromium.org</owner>
   <owner>fredmello@chromium.org</owner>
   <owner>yashard@chromium.org</owner>
@@ -168,7 +168,7 @@
 </histogram>
 
 <histogram name="Browser.ContinuousSearch.SearchResultExtractionStatus"
-    enum="SearchResultExtractorClientStatus" expires_after="2021-09-20">
+    enum="SearchResultExtractorClientStatus" expires_after="2021-12-20">
   <owner>ckitagawa@chromium.org</owner>
   <owner>fredmello@chromium.org</owner>
   <owner>yashard@chromium.org</owner>
@@ -201,7 +201,7 @@
 
 <histogram
     name="Browser.ContinuousSearch.UI.CarouselScrolled3{ExperimentCategory}"
-    enum="Boolean" expires_after="2021-09-20">
+    enum="Boolean" expires_after="2021-12-20">
   <owner>ckitagawa@chromium.org</owner>
   <owner>fredmello@chromium.org</owner>
   <owner>yashard@chromium.org</owner>
@@ -234,7 +234,7 @@
 
 <histogram
     name="Browser.ContinuousSearch.UI.ClickedItemPosition{ExperimentCategory}"
-    units="position" expires_after="2021-09-20">
+    units="position" expires_after="2021-12-20">
   <owner>ckitagawa@chromium.org</owner>
   <owner>fredmello@chromium.org</owner>
   <owner>yashard@chromium.org</owner>
@@ -247,7 +247,7 @@
 
 <histogram
     name="Browser.ContinuousSearch.UI.DismissButtonClicked{ExperimentCategory}"
-    enum="Boolean" expires_after="2021-09-20">
+    enum="Boolean" expires_after="2021-12-20">
   <owner>ckitagawa@chromium.org</owner>
   <owner>fredmello@chromium.org</owner>
   <owner>yashard@chromium.org</owner>
@@ -262,7 +262,7 @@
 
 <histogram
     name="Browser.ContinuousSearch.UI.ProviderButtonClicked{ExperimentCategory}"
-    enum="Boolean" expires_after="2021-09-20">
+    enum="Boolean" expires_after="2021-12-20">
   <owner>ckitagawa@chromium.org</owner>
   <owner>fredmello@chromium.org</owner>
   <owner>yashard@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/content/histograms.xml b/tools/metrics/histograms/metadata/content/histograms.xml
index 97a05d1..61d13a5 100644
--- a/tools/metrics/histograms/metadata/content/histograms.xml
+++ b/tools/metrics/histograms/metadata/content/histograms.xml
@@ -1758,6 +1758,37 @@
 </histogram>
 
 <histogram
+    name="ContentSuggestions.{FeedType}.LoadMoreTrigger.NumCardsRemaining"
+    units="cards" expires_after="2022-03-01">
+  <owner>rogerm@chromium.org</owner>
+  <owner>dewittj@chromium.org</owner>
+  <owner>feed@chromium.org</owner>
+  <summary>
+    The number of cards not yet scrolled into visibility at the moment the Feed
+    decides to load more content.
+  </summary>
+  <token key="FeedType">
+    <variant name="Feed" summary="For-You Feed"/>
+    <variant name="Feed.WebFeed" summary="Web Feed"/>
+  </token>
+</histogram>
+
+<histogram name="ContentSuggestions.{FeedType}.LoadMoreTrigger.TotalCards"
+    units="cards" expires_after="2022-03-01">
+  <owner>rogerm@chromium.org</owner>
+  <owner>dewittj@chromium.org</owner>
+  <owner>feed@chromium.org</owner>
+  <summary>
+    The total number of cards in the feed stream at the moment the Feed decides
+    to load more content.
+  </summary>
+  <token key="FeedType">
+    <variant name="Feed" summary="For-You Feed"/>
+    <variant name="Feed.WebFeed" summary="Web Feed"/>
+  </token>
+</histogram>
+
+<histogram
     name="ContentSuggestions.{FeedType}.LoadStreamStatus.BackgroundRefresh"
     enum="FeedLoadStreamStatus" expires_after="2022-03-01">
   <owner>harringtond@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/enterprise/histograms.xml b/tools/metrics/histograms/metadata/enterprise/histograms.xml
index 0853d96..55e31b8 100644
--- a/tools/metrics/histograms/metadata/enterprise/histograms.xml
+++ b/tools/metrics/histograms/metadata/enterprise/histograms.xml
@@ -353,7 +353,7 @@
 </histogram>
 
 <histogram name="Enterprise.CBCMPolicyInvalidations"
-    enum="EnterprisePolicyInvalidations" expires_after="2021-11-01">
+    enum="EnterprisePolicyInvalidations" expires_after="2022-01-01">
   <owner>anthonyvd@chromium.org</owner>
   <owner>cbe-eng@google.com</owner>
   <summary>
@@ -365,7 +365,7 @@
 </histogram>
 
 <histogram name="Enterprise.CBCMPolicyRefresh" enum="EnterprisePolicyRefresh"
-    expires_after="2021-11-01">
+    expires_after="2022-01-01">
   <owner>anthonyvd@chromium.org</owner>
   <owner>cbe-eng@google.com</owner>
   <summary>
@@ -376,7 +376,7 @@
 </histogram>
 
 <histogram name="Enterprise.CBCMRemoteCommand.Executed"
-    enum="RemoteCommandExecutionStatus" expires_after="2021-11-01">
+    enum="RemoteCommandExecutionStatus" expires_after="2022-01-01">
   <owner>anthonyvd@chromium.org</owner>
   <owner>cbe-eng@google.com</owner>
   <summary>
@@ -387,7 +387,7 @@
 </histogram>
 
 <histogram name="Enterprise.CBCMRemoteCommand.Executed.Unsigned"
-    enum="RemoteCommandExecutionStatus" expires_after="2021-11-01">
+    enum="RemoteCommandExecutionStatus" expires_after="2022-01-01">
   <owner>anthonyvd@chromium.org</owner>
   <owner>cbe-eng@google.com</owner>
   <summary>
@@ -398,7 +398,7 @@
 </histogram>
 
 <histogram name="Enterprise.CBCMRemoteCommand.Received"
-    enum="RemoteCommandReceivedStatus" expires_after="2021-11-01">
+    enum="RemoteCommandReceivedStatus" expires_after="2022-01-01">
   <owner>anthonyvd@chromium.org</owner>
   <owner>cbe-eng@google.com</owner>
   <summary>
@@ -410,7 +410,7 @@
 </histogram>
 
 <histogram name="Enterprise.CBCMRemoteCommand.Received.Unsigned"
-    enum="RemoteCommandReceivedStatus" expires_after="2021-11-01">
+    enum="RemoteCommandReceivedStatus" expires_after="2022-01-01">
   <owner>anthonyvd@chromium.org</owner>
   <owner>cbe-eng@google.com</owner>
   <summary>
@@ -422,7 +422,7 @@
 </histogram>
 
 <histogram name="Enterprise.CBCMRemoteCommandInvalidations"
-    enum="EnterprisePolicyInvalidations" expires_after="2021-11-01">
+    enum="EnterprisePolicyInvalidations" expires_after="2022-01-01">
   <owner>anthonyvd@chromium.org</owner>
   <owner>cbe-eng@google.com</owner>
   <summary>
@@ -1209,7 +1209,7 @@
 </histogram>
 
 <histogram name="Enterprise.FCMInvalidationService.CBCMPolicyInvalidations"
-    enum="EnterprisePolicyInvalidations" expires_after="2021-11-01">
+    enum="EnterprisePolicyInvalidations" expires_after="2022-01-01">
   <owner>anthonyvd@chromium.org</owner>
   <owner>chrome-enterprise-team-core@google.com</owner>
   <summary>
@@ -1223,7 +1223,7 @@
 </histogram>
 
 <histogram name="Enterprise.FCMInvalidationService.CBCMPolicyRefresh"
-    enum="EnterprisePolicyRefresh" expires_after="2021-11-01">
+    enum="EnterprisePolicyRefresh" expires_after="2022-01-01">
   <owner>anthonyvd@chromium.org</owner>
   <owner>chrome-enterprise-team-core@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/gpu/histograms.xml b/tools/metrics/histograms/metadata/gpu/histograms.xml
index aa255de..81dd184 100644
--- a/tools/metrics/histograms/metadata/gpu/histograms.xml
+++ b/tools/metrics/histograms/metadata/gpu/histograms.xml
@@ -1287,7 +1287,7 @@
 </histogram>
 
 <histogram name="GPU.SharedImage.ContentConsumed" enum="BooleanMatched"
-    expires_after="2021-08-09">
+    expires_after="2022-01-01">
   <owner>penghuang@chromium.org</owner>
   <owner>backer@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index 8ec49e9..562ca78 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -9226,7 +9226,7 @@
 </histogram>
 
 <histogram name="Linux.SandboxStatus" enum="LinuxSandboxStatus"
-    expires_after="2021-09-19">
+    expires_after="2022-09-19">
   <owner>mpdenton@google.com</owner>
   <owner>src/sandbox/linux/OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/v8/histograms.xml b/tools/metrics/histograms/metadata/v8/histograms.xml
index 1e96084..e422f55 100644
--- a/tools/metrics/histograms/metadata/v8/histograms.xml
+++ b/tools/metrics/histograms/metadata/v8/histograms.xml
@@ -913,14 +913,14 @@
   </summary>
 </histogram>
 
-<histogram name="V8.GCScavenger" units="ms" expires_after="2021-12-12">
+<histogram name="V8.GCScavenger" units="ms" expires_after="2022-04-01">
   <owner>hpayer@chromium.org</owner>
   <owner>v8-memory-sheriffs@google.com</owner>
   <summary>Time spent in scavenging phase of GC.</summary>
 </histogram>
 
 <histogram name="V8.GCScavenger.ScavengeMain" units="ms"
-    expires_after="2021-12-12">
+    expires_after="2022-04-01">
   <owner>mlippautz@chromium.org</owner>
   <owner>v8-memory-sheriffs@google.com</owner>
   <summary>
@@ -930,14 +930,14 @@
 </histogram>
 
 <histogram name="V8.GCScavenger.ScavengeRoots" units="ms"
-    expires_after="2021-12-05">
+    expires_after="2022-04-01">
   <owner>mlippautz@chromium.org</owner>
   <owner>v8-memory-sheriffs@google.com</owner>
   <summary>Time spent in scavenging the roots during a V8 scavenge.</summary>
 </histogram>
 
 <histogram name="V8.GCScavengerBackground" units="ms"
-    expires_after="2021-08-15">
+    expires_after="2022-04-01">
   <owner>hpayer@chromium.org</owner>
   <owner>v8-memory-sheriffs@google.com</owner>
   <summary>
@@ -953,7 +953,7 @@
 </histogram>
 
 <histogram name="V8.GCScavengerForeground" units="ms"
-    expires_after="2021-12-12">
+    expires_after="2022-04-01">
   <owner>hpayer@chromium.org</owner>
   <owner>v8-memory-sheriffs@google.com</owner>
   <summary>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index 172b31d..8ffbaba 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -7793,6 +7793,28 @@
   </metric>
 </event>
 
+<event name="IOS.PageReadability">
+  <owner>thegreenfrog@chromium.org</owner>
+  <summary>
+    Logged when the Distilibility score of the current page is returned.
+    Muliplied by 10X to get tenth digit granularity. 0.1 granularity will be
+    recorded for scores between 0.5 and 1.5. Otherwise, the granularity will be
+    0.5.
+  </summary>
+  <metric name="DistilibilityLongScore">
+    <summary>
+      Distilibility score value indicating the &quot;long readability&quot; of
+      the page.
+    </summary>
+  </metric>
+  <metric name="DistilibilityScore">
+    <summary>
+      Distilibility score value indicating the &quot;readability&quot; of the
+      page.
+    </summary>
+  </metric>
+</event>
+
 <event name="IOS.PageZoomChanged">
   <owner>rkgibson@google.com</owner>
   <summary>
diff --git a/tools/protoc_wrapper/protoc_wrapper.py b/tools/protoc_wrapper/protoc_wrapper.py
index dc8a3f1..01ff0d9 100755
--- a/tools/protoc_wrapper/protoc_wrapper.py
+++ b/tools/protoc_wrapper/protoc_wrapper.py
@@ -94,6 +94,10 @@
                       'codesearch.')
   parser.add_argument("--plugin",
                       help="Relative path to custom generator plugin.")
+  #   TODO(crbug.com/1237958): Remove allow_optional when proto rolls to 3.15.
+  parser.add_argument("--allow-optional",
+                      action='store_true',
+                      help="Enables experimental_allow_proto3_optional.")
   parser.add_argument("--plugin-options",
                       help="Custom generator plugin options.")
   parser.add_argument("--cc-options",
@@ -151,6 +155,9 @@
     if options.cc_options:
       cc_options_list.append(options.cc_options)
 
+    if options.allow_optional:
+      protoc_cmd += ["--experimental_allow_proto3_optional"]
+
     cc_options = FormatGeneratorOptions(','.join(cc_options_list))
     protoc_cmd += ["--cpp_out", cc_options + cc_out_dir]
     for filename in protos:
diff --git a/ui/base/clipboard/clipboard_test_template.h b/ui/base/clipboard/clipboard_test_template.h
index e75861f2..494b050 100644
--- a/ui/base/clipboard/clipboard_test_template.h
+++ b/ui/base/clipboard/clipboard_test_template.h
@@ -120,7 +120,7 @@
                void(const DataTransferEndpoint* const data_src,
                     const DataTransferEndpoint* const data_dst,
                     const absl::optional<size_t> size,
-                    content::WebContents* web_contents,
+                    content::RenderFrameHost* rfh,
                     base::OnceCallback<void(bool)> callback));
   MOCK_METHOD3(IsDragDropAllowed,
                bool(const DataTransferEndpoint* const data_src,
diff --git a/ui/base/data_transfer_policy/data_transfer_policy_controller.h b/ui/base/data_transfer_policy/data_transfer_policy_controller.h
index 5c4273f0..47fc9d2 100644
--- a/ui/base/data_transfer_policy/data_transfer_policy_controller.h
+++ b/ui/base/data_transfer_policy/data_transfer_policy_controller.h
@@ -11,7 +11,7 @@
 #include "ui/base/data_transfer_policy/data_transfer_endpoint.h"
 
 namespace content {
-class WebContents;
+class RenderFrameHost;
 }
 
 namespace ui {
@@ -43,12 +43,12 @@
   // data is set to be in warning mode, this function will show a notification
   // to the user. If clipboard read is allowed, `callback` will be invoked with
   // true. Otherwise `callback` will be invoked with false.
-  // If `web_contents` got destroyed before `callback` is invoked, the
+  // If the WebContents of `rfh` got destroyed before `callback` is invoked, the
   // notification will get closed.
   virtual void PasteIfAllowed(const DataTransferEndpoint* data_src,
                               const DataTransferEndpoint* data_dst,
                               absl::optional<size_t> size,
-                              content::WebContents* web_contents,
+                              content::RenderFrameHost* rfh,
                               base::OnceCallback<void(bool)> callback) = 0;
 
   // nullptr can be passed instead of `data_src` or `data_dst`. If dropping the
diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn
index f6050d1..d30fcd3 100644
--- a/ui/views/BUILD.gn
+++ b/ui/views/BUILD.gn
@@ -86,10 +86,6 @@
     "animation/ink_drop_state.h",
     "animation/ink_drop_stub.h",
     "animation/ink_drop_util.h",
-    "animation/installable_ink_drop.h",
-    "animation/installable_ink_drop_animator.h",
-    "animation/installable_ink_drop_config.h",
-    "animation/installable_ink_drop_painter.h",
     "animation/scroll_animator.h",
     "animation/slide_out_controller.h",
     "animation/slide_out_controller_delegate.h",
@@ -309,9 +305,6 @@
     "animation/ink_drop_state.cc",
     "animation/ink_drop_stub.cc",
     "animation/ink_drop_util.cc",
-    "animation/installable_ink_drop.cc",
-    "animation/installable_ink_drop_animator.cc",
-    "animation/installable_ink_drop_painter.cc",
     "animation/scroll_animator.cc",
     "animation/slide_out_controller.cc",
     "animation/square_ink_drop_ripple.cc",
@@ -1101,9 +1094,6 @@
     "animation/ink_drop_mask_unittest.cc",
     "animation/ink_drop_ripple_unittest.cc",
     "animation/ink_drop_unittest.cc",
-    "animation/installable_ink_drop_animator_unittest.cc",
-    "animation/installable_ink_drop_painter_unittest.cc",
-    "animation/installable_ink_drop_unittest.cc",
     "animation/slide_out_controller_unittest.cc",
     "animation/square_ink_drop_ripple_unittest.cc",
     "animation/widget_fade_animator_unittest.cc",
diff --git a/ui/views/animation/ink_drop_host_view.cc b/ui/views/animation/ink_drop_host_view.cc
index a8b1057..584f39c 100644
--- a/ui/views/animation/ink_drop_host_view.cc
+++ b/ui/views/animation/ink_drop_host_view.cc
@@ -259,8 +259,6 @@
 }
 
 const InkDropEventHandler* InkDropHost::GetEventHandler() const {
-  if (ink_drop_event_handler_override_)
-    return ink_drop_event_handler_override_;
   return &ink_drop_event_handler_;
 }
 
diff --git a/ui/views/animation/ink_drop_host_view.h b/ui/views/animation/ink_drop_host_view.h
index bd0975b..7ff4204 100644
--- a/ui/views/animation/ink_drop_host_view.h
+++ b/ui/views/animation/ink_drop_host_view.h
@@ -129,16 +129,6 @@
   void SetLargeCornerRadius(int large_radius);
   int GetLargeCornerRadius() const;
 
-  // Allows InstallableInkDrop to override our InkDropEventHandler
-  // instance.
-  //
-  // TODO(crbug.com/931964): Remove this, either by finishing refactor or by
-  // giving up.
-  void set_ink_drop_event_handler_override(
-      InkDropEventHandler* ink_drop_event_handler_override) {
-    ink_drop_event_handler_override_ = ink_drop_event_handler_override;
-  }
-
   // Animates |ink_drop_| to the desired |ink_drop_state|. Caches |event| as the
   // last_ripple_triggering_event().
   //
@@ -246,8 +236,6 @@
   InkDropHostEventHandlerDelegate ink_drop_event_handler_delegate_;
   InkDropEventHandler ink_drop_event_handler_;
 
-  InkDropEventHandler* ink_drop_event_handler_override_ = nullptr;
-
   float ink_drop_visible_opacity_ = 0.175f;
 
   // The color of the ripple and hover.
diff --git a/ui/views/animation/installable_ink_drop.cc b/ui/views/animation/installable_ink_drop.cc
deleted file mode 100644
index 6f38481..0000000
--- a/ui/views/animation/installable_ink_drop.cc
+++ /dev/null
@@ -1,226 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ui/views/animation/installable_ink_drop.h"
-
-#include <algorithm>
-#include <utility>
-
-#include "base/bind.h"
-#include "base/check_op.h"
-#include "base/memory/ptr_util.h"
-#include "base/notreached.h"
-#include "cc/paint/paint_flags.h"
-#include "third_party/skia/include/core/SkColor.h"
-#include "third_party/skia/include/core/SkPath.h"
-#include "ui/compositor/layer.h"
-#include "ui/compositor/paint_context.h"
-#include "ui/compositor/paint_recorder.h"
-#include "ui/gfx/animation/animation_container.h"
-#include "ui/gfx/canvas.h"
-#include "ui/gfx/color_palette.h"
-#include "ui/gfx/geometry/rect.h"
-#include "ui/gfx/geometry/rect_f.h"
-#include "ui/gfx/geometry/size.h"
-#include "ui/gfx/skia_util.h"
-#include "ui/views/animation/compositor_animation_runner.h"
-#include "ui/views/animation/ink_drop_host_view.h"
-#include "ui/views/controls/focus_ring.h"
-#include "ui/views/view.h"
-#include "ui/views/view_class_properties.h"
-#include "ui/views/widget/widget.h"
-
-namespace views {
-
-namespace {
-
-InstallableInkDropConfig GetPlaceholderInstallableInkDropConfig() {
-  InstallableInkDropConfig config{0};
-  config.base_color = gfx::kPlaceholderColor;
-  config.ripple_opacity = 1.0f;
-  config.highlight_opacity = 1.0f;
-  return config;
-}
-
-}  // namespace
-
-const base::Feature kInstallableInkDropFeature{
-    "InstallableInkDrop", base::FEATURE_DISABLED_BY_DEFAULT};
-
-InstallableInkDrop::InstallableInkDrop(View* view)
-    : view_(view),
-      config_(GetPlaceholderInstallableInkDropConfig()),
-      layer_(std::make_unique<ui::Layer>()),
-      event_handler_(view_, this),
-      painter_(&config_, &visual_state_),
-      animation_container_(base::MakeRefCounted<gfx::AnimationContainer>()),
-      animator_(layer_->size(),
-                &visual_state_,
-                animation_container_.get(),
-                base::BindRepeating(&InstallableInkDrop::SchedulePaint,
-                                    base::Unretained(this))) {
-  // Catch if |view_| is destroyed out from under us.
-  if (DCHECK_IS_ON())
-    view_->AddObserver(this);
-
-  layer_->set_delegate(this);
-  layer_->SetFillsBoundsOpaquely(false);
-  layer_->SetFillsBoundsCompletely(false);
-  view_->AddLayerBeneathView(layer_.get());
-
-  // AddLayerBeneathView() changes the location of |layer_| so this must be done
-  // after.
-  layer_->SetBounds(gfx::Rect(view_->size()) +
-                    layer_->bounds().OffsetFromOrigin());
-  layer_->SchedulePaint(gfx::Rect(layer_->size()));
-
-  if (view_->GetWidget()) {
-    // Using CompositorAnimationRunner keeps our animation updates in sync with
-    // compositor frames and avoids jank.
-    animation_container_->SetAnimationRunner(
-        std::make_unique<CompositorAnimationRunner>(view_->GetWidget()));
-  }
-}
-
-InstallableInkDrop::InstallableInkDrop(InkDropHost* ink_drop_host)
-    : InstallableInkDrop(ink_drop_host->host_view()) {
-  // To get all events, we must override InkDropHost's event handler.
-  ink_drop_host->set_ink_drop_event_handler_override(&event_handler_);
-  ink_drop_host_ = ink_drop_host;
-
-  // TODO(crbug.com/931964): When this is removed, classes relying on property
-  // changed notifications from InkDropHost for the highlighted state will
-  // need to register here instead.
-  RegisterHighlightedChangedCallback(
-      base::BindRepeating(&InkDropHost::OnInkDropHighlightedChanged,
-                          base::Unretained(ink_drop_host_)));
-}
-
-InstallableInkDrop::~InstallableInkDrop() {
-  view_->RemoveLayerBeneathView(layer_.get());
-  if (ink_drop_host_)
-    ink_drop_host_->set_ink_drop_event_handler_override(nullptr);
-  if (DCHECK_IS_ON())
-    view_->RemoveObserver(this);
-}
-
-void InstallableInkDrop::SetConfig(InstallableInkDropConfig config) {
-  config_ = config;
-  SchedulePaint();
-}
-
-base::CallbackListSubscription
-InstallableInkDrop::RegisterHighlightedChangedCallback(
-    base::RepeatingClosure callback) {
-  return highlighted_changed_list_.Add(std::move(callback));
-}
-
-void InstallableInkDrop::HostSizeChanged(const gfx::Size& new_size) {
-  layer_->SetBounds(gfx::Rect(new_size) + layer_->bounds().OffsetFromOrigin());
-  layer_->SchedulePaint(gfx::Rect(layer_->size()));
-  animator_.SetSize(layer_->size());
-}
-
-void InstallableInkDrop::HostTransformChanged(
-    const gfx::Transform& new_transform) {}
-
-InkDropState InstallableInkDrop::GetTargetInkDropState() const {
-  return animator_.target_state();
-}
-
-void InstallableInkDrop::AnimateToState(InkDropState ink_drop_state) {
-  const gfx::Point ripple_center =
-      event_handler_.GetLastRippleTriggeringEvent()
-          ? event_handler_.GetLastRippleTriggeringEvent()->location()
-          : view_->GetMirroredRect(view_->GetLocalBounds()).CenterPoint();
-  animator_.SetLastEventLocation(ripple_center);
-  animator_.AnimateToState(ink_drop_state);
-}
-
-void InstallableInkDrop::SetHoverHighlightFadeDuration(
-    base::TimeDelta duration) {
-  NOTREACHED();
-}
-
-void InstallableInkDrop::UseDefaultHoverHighlightFadeDuration() {
-  NOTREACHED();
-}
-
-void InstallableInkDrop::SnapToActivated() {
-  // TODO(crbug.com/933384): do this without animation.
-  animator_.AnimateToState(InkDropState::ACTIVATED);
-}
-
-void InstallableInkDrop::SnapToHidden() {
-  // TODO(crbug.com/933384): do this without animation.
-  animator_.AnimateToState(InkDropState::HIDDEN);
-}
-
-void InstallableInkDrop::SetHovered(bool is_hovered) {
-  if (is_hovered_ == is_hovered)
-    return;
-  is_hovered_ = is_hovered;
-  UpdateAnimatorHighlight();
-}
-
-void InstallableInkDrop::SetFocused(bool is_focused) {
-  if (is_focused_ == is_focused)
-    return;
-  is_focused_ = is_focused;
-  UpdateAnimatorHighlight();
-}
-
-bool InstallableInkDrop::IsHighlightFadingInOrVisible() const {
-  return is_hovered_ || is_focused_;
-}
-
-void InstallableInkDrop::SetShowHighlightOnHover(bool show_highlight_on_hover) {
-  NOTREACHED();
-}
-
-void InstallableInkDrop::SetShowHighlightOnFocus(bool show_highlight_on_focus) {
-  NOTREACHED();
-}
-
-InkDrop* InstallableInkDrop::GetInkDrop() {
-  return this;
-}
-
-bool InstallableInkDrop::HasInkDrop() const {
-  return true;
-}
-
-bool InstallableInkDrop::SupportsGestureEvents() const {
-  return true;
-}
-
-void InstallableInkDrop::OnViewIsDeleting(View* observed_view) {
-  DCHECK_EQ(view_, observed_view);
-  NOTREACHED() << "|this| needs to outlive the view it's installed on";
-}
-
-void InstallableInkDrop::OnPaintLayer(const ui::PaintContext& context) {
-  DCHECK_EQ(view_->size(), layer_->size());
-
-  ui::PaintRecorder paint_recorder(context, layer_->size());
-  gfx::Canvas* canvas = paint_recorder.canvas();
-  canvas->ClipPath(GetHighlightPath(view_), true);
-
-  painter_.Paint(canvas, view_->size());
-}
-
-void InstallableInkDrop::OnDeviceScaleFactorChanged(
-    float old_device_scale_factor,
-    float new_device_scale_factor) {}
-
-void InstallableInkDrop::SchedulePaint() {
-  layer_->SchedulePaint(gfx::Rect(layer_->size()));
-}
-
-void InstallableInkDrop::UpdateAnimatorHighlight() {
-  animator_.AnimateHighlight(is_hovered_ || is_focused_);
-  highlighted_changed_list_.Notify();
-}
-
-}  // namespace views
diff --git a/ui/views/animation/installable_ink_drop.h b/ui/views/animation/installable_ink_drop.h
deleted file mode 100644
index 512bb92..0000000
--- a/ui/views/animation/installable_ink_drop.h
+++ /dev/null
@@ -1,145 +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 UI_VIEWS_ANIMATION_INSTALLABLE_INK_DROP_H_
-#define UI_VIEWS_ANIMATION_INSTALLABLE_INK_DROP_H_
-
-#include <memory>
-
-#include "base/feature_list.h"
-#include "base/memory/scoped_refptr.h"
-#include "ui/compositor/layer_delegate.h"
-#include "ui/views/animation/ink_drop.h"
-#include "ui/views/animation/ink_drop_event_handler.h"
-#include "ui/views/animation/ink_drop_state.h"
-#include "ui/views/animation/installable_ink_drop_animator.h"
-#include "ui/views/animation/installable_ink_drop_config.h"
-#include "ui/views/animation/installable_ink_drop_painter.h"
-#include "ui/views/view_observer.h"
-#include "ui/views/views_export.h"
-
-namespace gfx {
-class AnimationContainer;
-class Size;
-}  // namespace gfx
-
-namespace ui {
-class Layer;
-class PaintContext;
-}  // namespace ui
-
-namespace views {
-
-class InkDropHost;
-class View;
-
-extern const VIEWS_EXPORT base::Feature kInstallableInkDropFeature;
-
-// Stub for future InkDrop implementation that will be installable on any View
-// without needing InkDropHostView. This is currently non-functional and fails
-// on some method calls. TODO(crbug.com/931964): implement the necessary parts
-// of the API and remove the rest from the InkDrop interface.
-class VIEWS_EXPORT InstallableInkDrop : public InkDrop,
-                                        public InkDropEventHandler::Delegate,
-                                        public ui::LayerDelegate,
-                                        public ViewObserver {
- public:
-  // Create ink drop for |view|. Note that |view| must live longer than us.
-  explicit InstallableInkDrop(View* view);
-
-  // Overload for working within an InkDropHost structure. Similar to above,
-  // |ink_drop_host| must outlive us.
-  //
-  // TODO(crbug.com/931964): Remove this.
-  explicit InstallableInkDrop(InkDropHost* ink_drop_host);
-
-  InstallableInkDrop(const InstallableInkDrop&) = delete;
-  InstallableInkDrop(InstallableInkDrop&&) = delete;
-
-  ~InstallableInkDrop() override;
-
-  void SetConfig(InstallableInkDropConfig config);
-  InstallableInkDropConfig config() const { return config_; }
-
-  // Registers |callback| to be called whenever the highlighted state changes.
-  base::CallbackListSubscription RegisterHighlightedChangedCallback(
-      base::RepeatingClosure callback);
-
-  // Should only be used for inspecting properties of the layer in tests.
-  const ui::Layer* layer_for_testing() const { return layer_.get(); }
-
-  // InkDrop:
-  void HostSizeChanged(const gfx::Size& new_size) override;
-  void HostTransformChanged(const gfx::Transform& new_transform) override;
-  InkDropState GetTargetInkDropState() const override;
-  void AnimateToState(InkDropState ink_drop_state) override;
-  void SetHoverHighlightFadeDuration(base::TimeDelta duration) override;
-  void UseDefaultHoverHighlightFadeDuration() override;
-  void SnapToActivated() override;
-  void SnapToHidden() override;
-  void SetHovered(bool is_hovered) override;
-  void SetFocused(bool is_focused) override;
-  bool IsHighlightFadingInOrVisible() const override;
-  void SetShowHighlightOnHover(bool show_highlight_on_hover) override;
-  void SetShowHighlightOnFocus(bool show_highlight_on_focus) override;
-
-  // InkDropEventHandler::Delegate:
-  InkDrop* GetInkDrop() override;
-  bool HasInkDrop() const override;
-  bool SupportsGestureEvents() const override;
-
-  // ViewObserver:
-  void OnViewIsDeleting(View* observed_view) override;
-
-  // ui::LayerDelegate:
-  void OnPaintLayer(const ui::PaintContext& context) override;
-  void OnDeviceScaleFactorChanged(float old_device_scale_factor,
-                                  float new_device_scale_factor) override;
-
- private:
-  void SchedulePaint();
-  void UpdateAnimatorHighlight();
-
-  // The view this ink drop is showing for. |layer_| is added to the layer
-  // hierarchy that |view_| belongs to. We track events on |view_| to update our
-  // visual state.
-  View* const view_;
-
-  // If we were installed on an InkDropHost, this will be non-null. We store
-  // this to to remove our InkDropEventHandler override.
-  InkDropHost* ink_drop_host_ = nullptr;
-
-  // Contains the colors and opacities used to paint.
-  InstallableInkDropConfig config_;
-
-  // The layer we paint to.
-  std::unique_ptr<ui::Layer> layer_;
-
-  // Observes |view_| and updates our visual state accordingly.
-  InkDropEventHandler event_handler_;
-
-  // Completely describes the current visual state of the ink drop, including
-  // progress of animations.
-  InstallableInkDropPainter::State visual_state_;
-
-  // Handles painting |visual_state_| on request.
-  InstallableInkDropPainter painter_;
-
-  // Used to synchronize the hover and activation animations within this ink
-  // drop. Since we use |views::CompositorAnimationRunner|, this also
-  // synchronizes them with compositor frames.
-  scoped_refptr<gfx::AnimationContainer> animation_container_;
-
-  // Manages our animations and maniuplates |visual_state_| for us.
-  InstallableInkDropAnimator animator_;
-
-  base::RepeatingClosureList highlighted_changed_list_;
-
-  bool is_hovered_ = false;
-  bool is_focused_ = false;
-};
-
-}  // namespace views
-
-#endif  // UI_VIEWS_ANIMATION_INSTALLABLE_INK_DROP_H_
diff --git a/ui/views/animation/installable_ink_drop_animator.cc b/ui/views/animation/installable_ink_drop_animator.cc
deleted file mode 100644
index 4acf8cf..0000000
--- a/ui/views/animation/installable_ink_drop_animator.cc
+++ /dev/null
@@ -1,298 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ui/views/animation/installable_ink_drop_animator.h"
-
-#include <algorithm>
-
-#include "base/bind.h"
-#include "base/check_op.h"
-#include "base/notreached.h"
-#include "base/trace_event/trace_event.h"
-#include "ui/gfx/animation/tween.h"
-#include "ui/gfx/geometry/point_f.h"
-#include "ui/gfx/geometry/rect_conversions.h"
-#include "ui/gfx/geometry/rect_f.h"
-#include "ui/gfx/geometry/size_f.h"
-#include "ui/gfx/geometry/vector2d_f.h"
-#include "ui/views/animation/installable_ink_drop_painter.h"
-
-namespace views {
-
-// static
-constexpr base::TimeDelta
-    InstallableInkDropAnimator::kHighlightAnimationDuration;
-
-InstallableInkDropAnimator::InstallableInkDropAnimator(
-    gfx::Size size,
-    InstallableInkDropPainter::State* visual_state,
-    gfx::AnimationContainer* animation_container,
-    base::RepeatingClosure repaint_callback)
-    : size_(size),
-      visual_state_(visual_state),
-      repaint_callback_(repaint_callback),
-      highlight_animation_(this),
-      flood_fill_animation_(this),
-      fade_out_animation_(this) {
-  highlight_animation_.SetContainer(animation_container);
-  flood_fill_animation_.SetContainer(animation_container);
-  fade_out_animation_.SetContainer(animation_container);
-
-  highlight_animation_.SetSlideDuration(kHighlightAnimationDuration);
-}
-
-InstallableInkDropAnimator::~InstallableInkDropAnimator() = default;
-
-void InstallableInkDropAnimator::SetSize(gfx::Size size) {
-  size_ = size;
-}
-
-void InstallableInkDropAnimator::SetLastEventLocation(
-    gfx::Point last_event_location) {
-  last_event_location_ = last_event_location;
-}
-
-void InstallableInkDropAnimator::AnimateToState(InkDropState target_state) {
-  VerifyAnimationState();
-
-  const InkDropState last_state = target_state_;
-
-  TRACE_EVENT2("views", "InstallableInkDropAnimator::AnimateToState",
-               "target_state", target_state, "last_state", last_state);
-
-  switch (target_state) {
-    case InkDropState::HIDDEN:
-    case InkDropState::DEACTIVATED:
-      // If we weren't animating to a visible state, we shouldn't do anything.
-      if (last_state == InkDropState::HIDDEN ||
-          last_state == InkDropState::DEACTIVATED) {
-        break;
-      }
-      // If we were flood-filling, jump to the end of that animation.
-      if (flood_fill_animation_.is_animating()) {
-        flood_fill_animation_.End();
-      }
-      // If we weren't already fading out, start fading out. Otherwise, just
-      // continue fading out.
-      if (!fade_out_animation_.is_animating()) {
-        const SubAnimation sub_animation =
-            target_state == InkDropState::HIDDEN
-                ? SubAnimation::kHiddenFadeOut
-                : SubAnimation::kDeactivatedFadeOut;
-        StartSubAnimation(sub_animation);
-      }
-      break;
-
-    case InkDropState::ACTION_PENDING:
-      if (last_state != InkDropState::HIDDEN) {
-        // Snap to hidden state by stopping all animations.
-        flood_fill_animation_.Stop();
-        fade_out_animation_.Stop();
-      }
-      StartSubAnimation(SubAnimation::kActionPendingFloodFill);
-      break;
-
-    case InkDropState::ACTION_TRIGGERED:
-      if (last_state == InkDropState::HIDDEN) {
-        // Start the flood fill. On this animation's end, we will start the fade
-        // out animation in AnimationEnded().
-        StartSubAnimation(SubAnimation::kActionPendingFloodFill);
-      } else if (last_state == InkDropState::ACTION_PENDING &&
-                 !flood_fill_animation_.is_animating()) {
-        // If we were done animating to ACTION_PENDING, we must start the fade
-        // out animation here.
-        StartSubAnimation(SubAnimation::kActionTriggeredFadeOut);
-        // If we were in ACTION_PENDING but weren't done animating, we will
-        // start the fade out animation in AnimationEnded().
-      } else if (last_state != InkDropState::ACTION_PENDING) {
-        // Any other state transitions are invalid.
-        NOTREACHED() << "Transition from " << ToString(last_state)
-                     << " to ACTION_TRIGGERED is invalid.";
-      }
-      break;
-
-    case InkDropState::ALTERNATE_ACTION_PENDING:
-    case InkDropState::ALTERNATE_ACTION_TRIGGERED:
-      // TODO(crbug.com/933384): handle these.
-      NOTREACHED() << "target_state = " << ToString(target_state);
-      break;
-
-    case InkDropState::ACTIVATED:
-      if (fade_out_animation_.is_animating()) {
-        // If we were fading out of ACTIVATED or ACTION_TRIGGERED, finish the
-        // animation to reset to HIDDEN state.
-        fade_out_animation_.End();
-      }
-
-      // Now simply start the flood fill animation again.
-      StartSubAnimation(SubAnimation::kActivatedFloodFill);
-      break;
-  }
-
-  target_state_ = target_state;
-  VerifyAnimationState();
-  repaint_callback_.Run();
-}
-
-void InstallableInkDropAnimator::AnimateHighlight(bool fade_in) {
-  if (fade_in) {
-    highlight_animation_.Show();
-  } else {
-    highlight_animation_.Hide();
-  }
-}
-
-base::TimeDelta InstallableInkDropAnimator::GetSubAnimationDuration(
-    SubAnimation sub_animation) {
-  switch (sub_animation) {
-    case SubAnimation::kHiddenFadeOut:
-      return base::TimeDelta::FromMilliseconds(200);
-    case SubAnimation::kActionPendingFloodFill:
-      return base::TimeDelta::FromMilliseconds(240);
-    case SubAnimation::kActionTriggeredFadeOut:
-      return base::TimeDelta::FromMilliseconds(300);
-    case SubAnimation::kActivatedFloodFill:
-      return base::TimeDelta::FromMilliseconds(200);
-    case SubAnimation::kDeactivatedFadeOut:
-      return base::TimeDelta::FromMilliseconds(300);
-  }
-}
-
-void InstallableInkDropAnimator::StartSubAnimation(SubAnimation sub_animation) {
-  const base::TimeDelta duration = GetSubAnimationDuration(sub_animation);
-  switch (sub_animation) {
-    case SubAnimation::kHiddenFadeOut:
-    case SubAnimation::kActionTriggeredFadeOut:
-    case SubAnimation::kDeactivatedFadeOut:
-      if (!fade_out_animation_.is_animating()) {
-        fade_out_animation_.SetDuration(duration);
-        fade_out_animation_.Start();
-      }
-      break;
-    case SubAnimation::kActionPendingFloodFill:
-    case SubAnimation::kActivatedFloodFill:
-      if (!flood_fill_animation_.is_animating()) {
-        flood_fill_animation_.SetDuration(duration);
-        flood_fill_animation_.Start();
-      }
-      break;
-  }
-}
-
-void InstallableInkDropAnimator::VerifyAnimationState() const {
-#if DCHECK_IS_ON()
-  switch (target_state_) {
-    case InkDropState::HIDDEN:
-    case InkDropState::DEACTIVATED:
-      // We can only be fading out or completely invisible. So, we cannot be
-      // flood-filling.
-      DCHECK(!flood_fill_animation_.is_animating());
-      break;
-    case InkDropState::ACTION_PENDING:
-    case InkDropState::ACTIVATED:
-      // These states flood-fill then stay visible.
-      DCHECK(!fade_out_animation_.is_animating());
-      if (!flood_fill_animation_.is_animating())
-        DCHECK_EQ(1.0f, visual_state_->flood_fill_progress);
-      break;
-    case InkDropState::ACTION_TRIGGERED:
-      // We should always be animating during this state. Once animations are
-      // done, we automatically transition to HIDDEN. Make sure exactly one of
-      // our animations are running.
-      DCHECK_NE(flood_fill_animation_.is_animating(),
-                fade_out_animation_.is_animating());
-      break;
-    case InkDropState::ALTERNATE_ACTION_PENDING:
-    case InkDropState::ALTERNATE_ACTION_TRIGGERED:
-      // TODO(crbug.com/933384): handle these.
-      NOTREACHED() << "target_state = " << ToString(target_state_);
-      break;
-  }
-#endif  // DCHECK_IS_ON()
-}
-
-void InstallableInkDropAnimator::AnimationEnded(
-    const gfx::Animation* animation) {
-  TRACE_EVENT0("views", "InstallableInkDropAnimator::AnimationEnded");
-
-  if (animation == &flood_fill_animation_) {
-    switch (target_state_) {
-      case InkDropState::ACTION_PENDING:
-      case InkDropState::ACTIVATED:
-        // The ink drop stays filled in these states so nothing needs to be
-        // done.
-        break;
-
-      case InkDropState::ACTION_TRIGGERED:
-        // After filling, the ink drop should fade out.
-        StartSubAnimation(SubAnimation::kActionTriggeredFadeOut);
-        break;
-
-      case InkDropState::ALTERNATE_ACTION_PENDING:
-      case InkDropState::ALTERNATE_ACTION_TRIGGERED:
-        // TODO(crbug.com/933384): handle these.
-        NOTREACHED() << "target_state_ = " << ToString(target_state_);
-        break;
-
-      case InkDropState::HIDDEN:
-      case InkDropState::DEACTIVATED:
-        // The flood fill animation should never run in these states.
-        NOTREACHED() << "target_state_ = " << ToString(target_state_);
-        break;
-    }
-
-    // The ink drop is now fully activated.
-    visual_state_->flood_fill_progress = 1.0f;
-    repaint_callback_.Run();
-  } else if (animation == &fade_out_animation_) {
-    switch (target_state_) {
-      case InkDropState::HIDDEN:
-        // Nothing to do.
-        break;
-
-      case InkDropState::ACTION_TRIGGERED:
-      case InkDropState::DEACTIVATED:
-        // These states transition to HIDDEN when they're done animating.
-        target_state_ = InkDropState::HIDDEN;
-        break;
-
-      case InkDropState::ACTION_PENDING:
-      case InkDropState::ACTIVATED:
-        // The fade out animation should never run in these states.
-        NOTREACHED() << "target_state_ = " << ToString(target_state_);
-        break;
-
-      case InkDropState::ALTERNATE_ACTION_PENDING:
-      case InkDropState::ALTERNATE_ACTION_TRIGGERED:
-        // TODO(crbug.com/933384): handle these.
-        NOTREACHED() << "target_state_ = " << ToString(target_state_);
-        break;
-    }
-
-    // The ink drop fill is fully invisible.
-    visual_state_->flood_fill_progress = 0.0f;
-    repaint_callback_.Run();
-  }
-}
-
-void InstallableInkDropAnimator::AnimationProgressed(
-    const gfx::Animation* animation) {
-  TRACE_EVENT0("views", "InstallableInkDropAnimator::AnimationProgressed");
-
-  if (animation == &highlight_animation_) {
-    visual_state_->highlighted_ratio = highlight_animation_.GetCurrentValue();
-  } else if (animation == &flood_fill_animation_) {
-    visual_state_->flood_fill_center = gfx::PointF(last_event_location_);
-    visual_state_->flood_fill_progress = gfx::Tween::CalculateValue(
-        gfx::Tween::FAST_OUT_SLOW_IN, flood_fill_animation_.GetCurrentValue());
-  } else if (animation == &fade_out_animation_) {
-    // Do nothing for now.
-  } else {
-    NOTREACHED();
-  }
-
-  repaint_callback_.Run();
-}
-
-}  // namespace views
diff --git a/ui/views/animation/installable_ink_drop_animator.h b/ui/views/animation/installable_ink_drop_animator.h
deleted file mode 100644
index fdeb3e4..0000000
--- a/ui/views/animation/installable_ink_drop_animator.h
+++ /dev/null
@@ -1,109 +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 UI_VIEWS_ANIMATION_INSTALLABLE_INK_DROP_ANIMATOR_H_
-#define UI_VIEWS_ANIMATION_INSTALLABLE_INK_DROP_ANIMATOR_H_
-
-#include "base/callback.h"
-#include "base/time/time.h"
-#include "base/timer/timer.h"
-#include "ui/gfx/animation/animation_delegate.h"
-#include "ui/gfx/animation/linear_animation.h"
-#include "ui/gfx/animation/slide_animation.h"
-#include "ui/gfx/geometry/point.h"
-#include "ui/gfx/geometry/size.h"
-#include "ui/views/animation/ink_drop_state.h"
-#include "ui/views/animation/installable_ink_drop_painter.h"
-
-namespace gfx {
-class AnimationContainer;
-}
-
-namespace views {
-
-// Manages animating the ink drop's visual state. This class is essentially a
-// state machine, using the current and target InkDropStates to affect the
-// InstallableInkDropPainter passed in. The animations are currently minimal.
-class VIEWS_EXPORT InstallableInkDropAnimator : public gfx::AnimationDelegate {
- public:
-  // Placeholder duration used for highlight animation. TODO(crbug.com/933384):
-  // remove this and make highlight animation duration controllable, like in
-  // InkDropHighlight.
-  static constexpr base::TimeDelta kHighlightAnimationDuration =
-      base::TimeDelta::FromMilliseconds(500);
-
-  // We use a shared gfx::AnimationContainer for our animations to allow them to
-  // update in sync. It is passed in at construction for two reasons: it allows
-  // |views::CompositorAnimationRunner| to be used for more efficient and less
-  // janky animations, and it enables easier unit testing.
-  explicit InstallableInkDropAnimator(
-      gfx::Size size,
-      InstallableInkDropPainter::State* visual_state,
-      gfx::AnimationContainer* animation_container,
-      base::RepeatingClosure repaint_callback);
-  ~InstallableInkDropAnimator() override;
-
-  void SetSize(gfx::Size size);
-  void SetLastEventLocation(gfx::Point last_event_location);
-
-  // Set the target state and animate to it.
-  void AnimateToState(InkDropState target_state);
-
-  // Animates the hover highlight in or out. Animates in if |fade_in| is true,
-  // and out otherwise.
-  void AnimateHighlight(bool fade_in);
-
-  InkDropState target_state() const { return target_state_; }
-
-  // The sub-animations used when animating to an |InkDropState|. These are used
-  // to look up the animation durations. This is mainly meant for internal use
-  // but is public for tests.
-  enum class SubAnimation {
-    kHiddenFadeOut,
-    kActionPendingFloodFill,
-    kActionTriggeredFadeOut,
-    kActivatedFloodFill,
-    kDeactivatedFadeOut,
-  };
-
-  static base::TimeDelta GetSubAnimationDurationForTesting(
-      SubAnimation sub_animation) {
-    return GetSubAnimationDuration(sub_animation);
-  }
-
- private:
-  static base::TimeDelta GetSubAnimationDuration(SubAnimation sub_animation);
-
-  void StartSubAnimation(SubAnimation sub_animation);
-
-  // Checks that the states of our animations make sense given
-  // |target_state_|. DCHECKs if something is wrong.
-  void VerifyAnimationState() const;
-
-  // gfx::AnimationDelegate:
-  void AnimationEnded(const gfx::Animation* animation) override;
-  void AnimationProgressed(const gfx::Animation* animation) override;
-
-  gfx::Size size_;
-
-  // The visual state we are controlling.
-  InstallableInkDropPainter::State* const visual_state_;
-
-  // Called when |visual_state_| changes so the user can repaint.
-  base::RepeatingClosure repaint_callback_;
-
-  InkDropState target_state_ = InkDropState::HIDDEN;
-
-  // Used to animate the painter's highlight value in and out.
-  gfx::SlideAnimation highlight_animation_;
-
-  gfx::LinearAnimation flood_fill_animation_;
-  gfx::LinearAnimation fade_out_animation_;
-
-  gfx::Point last_event_location_;
-};
-
-}  // namespace views
-
-#endif  // UI_VIEWS_ANIMATION_INSTALLABLE_INK_DROP_ANIMATOR_H_
diff --git a/ui/views/animation/installable_ink_drop_animator_unittest.cc b/ui/views/animation/installable_ink_drop_animator_unittest.cc
deleted file mode 100644
index 5a2f635..0000000
--- a/ui/views/animation/installable_ink_drop_animator_unittest.cc
+++ /dev/null
@@ -1,278 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ui/views/animation/installable_ink_drop_animator.h"
-
-#include "base/callback_helpers.h"
-#include "base/memory/scoped_refptr.h"
-#include "base/run_loop.h"
-#include "base/test/task_environment.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "ui/gfx/animation/animation_test_api.h"
-#include "ui/gfx/geometry/point.h"
-#include "ui/gfx/geometry/point_conversions.h"
-#include "ui/gfx/geometry/point_f.h"
-#include "ui/gfx/geometry/rect_f.h"
-#include "ui/gfx/geometry/size.h"
-#include "ui/views/animation/ink_drop_state.h"
-#include "ui/views/animation/installable_ink_drop_painter.h"
-
-namespace views {
-
-namespace {
-
-class InstallableInkDropAnimatorTest : public ::testing::Test {
- public:
-  InstallableInkDropAnimatorTest()
-      : animation_container_(base::MakeRefCounted<gfx::AnimationContainer>()),
-        animation_tester_(animation_container_.get()),
-        callback_(base::BindRepeating(
-            [](bool* callback_called) { *callback_called = true; },
-            &callback_called_)) {}
-
- protected:
-  base::test::TaskEnvironment task_environment_;
-
-  scoped_refptr<gfx::AnimationContainer> animation_container_;
-  gfx::AnimationContainerTestApi animation_tester_;
-
-  InstallableInkDropPainter::State visual_state_;
-
-  bool callback_called_ = false;
-  base::RepeatingClosure callback_;
-};
-
-}  // namespace
-
-TEST_F(InstallableInkDropAnimatorTest, UpdatesTargetState) {
-  InstallableInkDropAnimator animator(gfx::Size(2, 2), &visual_state_,
-                                      animation_container_.get(),
-                                      base::DoNothing());
-  EXPECT_EQ(InkDropState::HIDDEN, animator.target_state());
-
-  animator.AnimateToState(InkDropState::ACTIVATED);
-  EXPECT_EQ(InkDropState::ACTIVATED, animator.target_state());
-}
-
-TEST_F(InstallableInkDropAnimatorTest, AnimateToTriggeredFromHidden) {
-  InstallableInkDropAnimator animator(gfx::Size(10, 10), &visual_state_,
-                                      animation_container_.get(), callback_);
-  EXPECT_EQ(0.0f, visual_state_.flood_fill_progress);
-
-  animator.SetLastEventLocation(gfx::Point(5, 5));
-  animator.AnimateToState(InkDropState::ACTION_TRIGGERED);
-  EXPECT_EQ(InkDropState::ACTION_TRIGGERED, animator.target_state());
-  EXPECT_GT(1.0f, visual_state_.flood_fill_progress);
-
-  callback_called_ = false;
-  animation_tester_.IncrementTime(
-      InstallableInkDropAnimator::GetSubAnimationDurationForTesting(
-          InstallableInkDropAnimator::SubAnimation::kActionPendingFloodFill));
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(InkDropState::ACTION_TRIGGERED, animator.target_state());
-  EXPECT_EQ(1.0f, visual_state_.flood_fill_progress);
-  EXPECT_TRUE(callback_called_);
-
-  callback_called_ = false;
-  animation_tester_.IncrementTime(
-      InstallableInkDropAnimator::GetSubAnimationDurationForTesting(
-          InstallableInkDropAnimator::SubAnimation::kActionTriggeredFadeOut));
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(InkDropState::HIDDEN, animator.target_state());
-  EXPECT_EQ(0.0f, visual_state_.flood_fill_progress);
-  EXPECT_TRUE(callback_called_);
-}
-
-TEST_F(InstallableInkDropAnimatorTest,
-       AnimateToPendingThenTriggeredFromHidden) {
-  InstallableInkDropAnimator animator(gfx::Size(10, 10), &visual_state_,
-                                      animation_container_.get(), callback_);
-
-  animator.SetLastEventLocation(gfx::Point(5, 5));
-  animator.AnimateToState(InkDropState::ACTION_PENDING);
-  EXPECT_EQ(InkDropState::ACTION_PENDING, animator.target_state());
-  EXPECT_GT(1.0f, visual_state_.flood_fill_progress);
-
-  callback_called_ = false;
-  animation_tester_.IncrementTime(
-      InstallableInkDropAnimator::GetSubAnimationDurationForTesting(
-          InstallableInkDropAnimator::SubAnimation::kActionPendingFloodFill));
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(InkDropState::ACTION_PENDING, animator.target_state());
-  EXPECT_EQ(1.0f, visual_state_.flood_fill_progress);
-  EXPECT_TRUE(callback_called_);
-
-  // The animation should be finished now and the visual state should *not*
-  // change; ACTION_PENDING lasts indefinitely.
-  animation_tester_.IncrementTime(
-      InstallableInkDropAnimator::GetSubAnimationDurationForTesting(
-          InstallableInkDropAnimator::SubAnimation::kActionTriggeredFadeOut));
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(InkDropState::ACTION_PENDING, animator.target_state());
-  EXPECT_EQ(1.0f, visual_state_.flood_fill_progress);
-
-  // Animating to ACTION_TRIGGERED from ACTION_PENDING should not repeat the
-  // flood-fill animation. Instead, it should just fade out.
-  animator.AnimateToState(InkDropState::ACTION_TRIGGERED);
-  EXPECT_EQ(InkDropState::ACTION_TRIGGERED, animator.target_state());
-  EXPECT_EQ(1.0f, visual_state_.flood_fill_progress);
-
-  animation_tester_.IncrementTime(
-      InstallableInkDropAnimator::GetSubAnimationDurationForTesting(
-          InstallableInkDropAnimator::SubAnimation::kActionTriggeredFadeOut));
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(InkDropState::HIDDEN, animator.target_state());
-  EXPECT_EQ(0.0f, visual_state_.flood_fill_progress);
-}
-
-TEST_F(InstallableInkDropAnimatorTest,
-       AnimateToPendingWhileAnimatingToTriggered) {
-  const base::TimeDelta kPendingAnimationDuration =
-      InstallableInkDropAnimator::GetSubAnimationDurationForTesting(
-          InstallableInkDropAnimator::SubAnimation::kActionPendingFloodFill);
-  const base::TimeDelta kHalfAnimationDuration = kPendingAnimationDuration / 2;
-  const base::TimeDelta kRemainingAnimationDuration =
-      kPendingAnimationDuration - kHalfAnimationDuration;
-
-  InstallableInkDropAnimator animator(gfx::Size(10, 10), &visual_state_,
-                                      animation_container_.get(), callback_);
-
-  animator.SetLastEventLocation(gfx::Point(5, 5));
-  animator.AnimateToState(InkDropState::ACTION_PENDING);
-  EXPECT_EQ(InkDropState::ACTION_PENDING, animator.target_state());
-  EXPECT_GT(1.0f, visual_state_.flood_fill_progress);
-
-  callback_called_ = false;
-  animation_tester_.IncrementTime(kHalfAnimationDuration);
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(InkDropState::ACTION_PENDING, animator.target_state());
-  EXPECT_LT(0.0f, visual_state_.flood_fill_progress);
-  EXPECT_GT(1.0f, visual_state_.flood_fill_progress);
-  EXPECT_TRUE(callback_called_);
-
-  // Switching to ACTION_TRIGGERED should not restart the animation. Instead, it
-  // should just add a transition to HIDDEN after the flood-fill is done.
-  animator.AnimateToState(InkDropState::ACTION_TRIGGERED);
-  EXPECT_EQ(InkDropState::ACTION_TRIGGERED, animator.target_state());
-  EXPECT_LT(0.0f, visual_state_.flood_fill_progress);
-  EXPECT_GT(1.0f, visual_state_.flood_fill_progress);
-
-  callback_called_ = false;
-  animation_tester_.IncrementTime(kRemainingAnimationDuration);
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(InkDropState::ACTION_TRIGGERED, animator.target_state());
-  EXPECT_EQ(1.0f, visual_state_.flood_fill_progress);
-  EXPECT_TRUE(callback_called_);
-}
-
-TEST_F(InstallableInkDropAnimatorTest, AnimateToActivatedThenDeactivated) {
-  InstallableInkDropAnimator animator(gfx::Size(10, 10), &visual_state_,
-                                      animation_container_.get(), callback_);
-
-  animator.SetLastEventLocation(gfx::Point(5, 5));
-  animator.AnimateToState(InkDropState::ACTIVATED);
-  EXPECT_EQ(InkDropState::ACTIVATED, animator.target_state());
-  EXPECT_GT(1.0f, visual_state_.flood_fill_progress);
-
-  callback_called_ = false;
-  animation_tester_.IncrementTime(
-      InstallableInkDropAnimator::GetSubAnimationDurationForTesting(
-          InstallableInkDropAnimator::SubAnimation::kActivatedFloodFill));
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(InkDropState::ACTIVATED, animator.target_state());
-  EXPECT_EQ(1.0f, visual_state_.flood_fill_progress);
-  EXPECT_TRUE(callback_called_);
-
-  // The state should stay the same indefinitely.
-  animation_tester_.IncrementTime(
-      InstallableInkDropAnimator::GetSubAnimationDurationForTesting(
-          InstallableInkDropAnimator::SubAnimation::kDeactivatedFadeOut));
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(InkDropState::ACTIVATED, animator.target_state());
-  EXPECT_EQ(1.0f, visual_state_.flood_fill_progress);
-
-  // Animating to DEACTIVATED should fade out and transition to HIDDEN.
-  animator.AnimateToState(InkDropState::DEACTIVATED);
-  EXPECT_EQ(InkDropState::DEACTIVATED, animator.target_state());
-  EXPECT_EQ(1.0f, visual_state_.flood_fill_progress);
-
-  callback_called_ = false;
-  animation_tester_.IncrementTime(
-      InstallableInkDropAnimator::GetSubAnimationDurationForTesting(
-          InstallableInkDropAnimator::SubAnimation::kDeactivatedFadeOut));
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(InkDropState::HIDDEN, animator.target_state());
-  EXPECT_EQ(0.0f, visual_state_.flood_fill_progress);
-}
-
-TEST_F(InstallableInkDropAnimatorTest,
-       FloodFillAnimationExpandsFromEventLocation) {
-  constexpr gfx::Point kEventLocation(3, 7);
-
-  const base::TimeDelta kActivatedAnimationDuration =
-      InstallableInkDropAnimator::GetSubAnimationDurationForTesting(
-          InstallableInkDropAnimator::SubAnimation::kActivatedFloodFill);
-  // Split |kActivatedAnimationDuration| into three chunks.
-  const base::TimeDelta kFirstDuration = kActivatedAnimationDuration / 3;
-  const base::TimeDelta kSecondDuration = kFirstDuration;
-  const base::TimeDelta kLastDuration =
-      kActivatedAnimationDuration - kFirstDuration - kSecondDuration;
-
-  InstallableInkDropAnimator animator(gfx::Size(10, 10), &visual_state_,
-                                      animation_container_.get(), callback_);
-
-  animator.SetLastEventLocation(kEventLocation);
-  animator.AnimateToState(InkDropState::ACTIVATED);
-
-  callback_called_ = false;
-  animation_tester_.IncrementTime(kFirstDuration);
-  base::RunLoop().RunUntilIdle();
-  EXPECT_TRUE(callback_called_);
-  EXPECT_LT(0.0f, visual_state_.flood_fill_progress);
-  EXPECT_GT(1.0f, visual_state_.flood_fill_progress);
-
-  const float first_progress = visual_state_.flood_fill_progress;
-
-  callback_called_ = false;
-  animation_tester_.IncrementTime(kSecondDuration);
-  base::RunLoop().RunUntilIdle();
-  EXPECT_TRUE(callback_called_);
-  EXPECT_LT(first_progress, visual_state_.flood_fill_progress);
-  EXPECT_GT(1.0f, visual_state_.flood_fill_progress);
-
-  callback_called_ = false;
-  animation_tester_.IncrementTime(kLastDuration);
-  base::RunLoop().RunUntilIdle();
-  EXPECT_TRUE(callback_called_);
-  EXPECT_EQ(1.0f, visual_state_.flood_fill_progress);
-}
-
-TEST_F(InstallableInkDropAnimatorTest, HighlightAnimationFadesInAndOut) {
-  InstallableInkDropAnimator animator(gfx::Size(2, 2), &visual_state_,
-                                      animation_container_.get(), callback_);
-  EXPECT_EQ(0.0f, visual_state_.highlighted_ratio);
-  EXPECT_FALSE(callback_called_);
-
-  animator.AnimateHighlight(true);
-  EXPECT_EQ(0.0f, visual_state_.highlighted_ratio);
-
-  callback_called_ = false;
-  animation_tester_.IncrementTime(
-      InstallableInkDropAnimator::kHighlightAnimationDuration);
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(1.0f, visual_state_.highlighted_ratio);
-  EXPECT_TRUE(callback_called_);
-
-  animator.AnimateHighlight(false);
-  EXPECT_EQ(1.0f, visual_state_.highlighted_ratio);
-
-  callback_called_ = false;
-  animation_tester_.IncrementTime(
-      InstallableInkDropAnimator::kHighlightAnimationDuration);
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(0.0f, visual_state_.highlighted_ratio);
-  EXPECT_TRUE(callback_called_);
-}
-
-}  // namespace views
diff --git a/ui/views/animation/installable_ink_drop_config.h b/ui/views/animation/installable_ink_drop_config.h
deleted file mode 100644
index 8e19ae1..0000000
--- a/ui/views/animation/installable_ink_drop_config.h
+++ /dev/null
@@ -1,23 +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 UI_VIEWS_ANIMATION_INSTALLABLE_INK_DROP_CONFIG_H_
-#define UI_VIEWS_ANIMATION_INSTALLABLE_INK_DROP_CONFIG_H_
-
-#include "third_party/skia/include/core/SkColor.h"
-
-namespace views {
-
-struct InstallableInkDropConfig {
-  // The color of ink drop effects, modulated by opacity.
-  SkColor base_color;
-  // The opacity to paint |base_color| at for a fully-visible ripple.
-  float ripple_opacity;
-  // The opacity to paint |base_color| at for a fully-visible hover highlight.
-  float highlight_opacity;
-};
-
-}  // namespace views
-
-#endif  // UI_VIEWS_ANIMATION_INSTALLABLE_INK_DROP_CONFIG_H_
diff --git a/ui/views/animation/installable_ink_drop_painter.cc b/ui/views/animation/installable_ink_drop_painter.cc
deleted file mode 100644
index 8ae4abe..0000000
--- a/ui/views/animation/installable_ink_drop_painter.cc
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ui/views/animation/installable_ink_drop_painter.h"
-
-#include "base/trace_event/trace_event.h"
-#include "cc/paint/paint_flags.h"
-#include "ui/gfx/animation/tween.h"
-#include "ui/gfx/canvas.h"
-#include "ui/gfx/geometry/point.h"
-#include "ui/gfx/geometry/point_f.h"
-#include "ui/gfx/geometry/rect.h"
-#include "ui/gfx/geometry/rect_f.h"
-#include "ui/gfx/geometry/size.h"
-#include "ui/gfx/geometry/vector2d_f.h"
-#include "ui/views/animation/installable_ink_drop_config.h"
-
-namespace views {
-
-InstallableInkDropPainter::State::State() = default;
-InstallableInkDropPainter::State::~State() = default;
-
-gfx::Size InstallableInkDropPainter::GetMinimumSize() const {
-  return gfx::Size();
-}
-
-void InstallableInkDropPainter::Paint(gfx::Canvas* canvas,
-                                      const gfx::Size& size) {
-  TRACE_EVENT0("views", "InstallableInkDropPainter::Paint");
-
-  DCHECK_GE(state_->flood_fill_progress, 0.0f);
-  DCHECK_LE(state_->flood_fill_progress, 1.0f);
-  DCHECK_GE(state_->highlighted_ratio, 0.0f);
-  DCHECK_LE(state_->highlighted_ratio, 1.0f);
-
-  if (state_->highlighted_ratio > 0.0f) {
-    canvas->FillRect(
-        gfx::Rect(size),
-        SkColorSetA(config_->base_color, config_->highlight_opacity *
-                                             state_->highlighted_ratio *
-                                             SK_AlphaOPAQUE));
-  }
-
-  // If fully filled, we can draw the activated color more efficiently as a
-  // rectangle.
-  if (state_->flood_fill_progress == 1.0f) {
-    canvas->FillRect(gfx::Rect(size),
-                     SkColorSetA(config_->base_color,
-                                 config_->ripple_opacity * SK_AlphaOPAQUE));
-  } else if (state_->flood_fill_progress > 0.0f) {
-    // We interpolate between a circle of radius 2 and a circle whose radius is
-    // the diagonal of |size|.
-    const float min_radius = 2.0f;
-    const float max_radius =
-        gfx::Vector2dF(size.width(), size.height()).Length();
-    const float cur_radius = gfx::Tween::FloatValueBetween(
-        state_->flood_fill_progress, min_radius, max_radius);
-
-    cc::PaintFlags flags;
-    flags.setStyle(cc::PaintFlags::kFill_Style);
-    flags.setColor(SkColorSetA(config_->base_color,
-                               config_->ripple_opacity * SK_AlphaOPAQUE));
-    canvas->DrawCircle(state_->flood_fill_center, cur_radius, flags);
-  }
-}
-
-}  // namespace views
diff --git a/ui/views/animation/installable_ink_drop_painter.h b/ui/views/animation/installable_ink_drop_painter.h
deleted file mode 100644
index 4235f19..0000000
--- a/ui/views/animation/installable_ink_drop_painter.h
+++ /dev/null
@@ -1,52 +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 UI_VIEWS_ANIMATION_INSTALLABLE_INK_DROP_PAINTER_H_
-#define UI_VIEWS_ANIMATION_INSTALLABLE_INK_DROP_PAINTER_H_
-
-#include "ui/gfx/geometry/rect_f.h"
-#include "ui/views/painter.h"
-
-namespace views {
-
-struct InstallableInkDropConfig;
-
-// Holds the current visual state of the installable ink drop and handles
-// painting it. The |Painter::Paint()| implementation draws a rectangular ink
-// drop of the given size; the user should set a clip path via
-// |gfx::Canvas::ClipPath()| to control the shape.
-class VIEWS_EXPORT InstallableInkDropPainter : public Painter {
- public:
-  struct VIEWS_EXPORT State {
-    State();
-    ~State();
-
-    gfx::PointF flood_fill_center;
-    float flood_fill_progress = 0.0f;
-    float highlighted_ratio = 0.0f;
-  };
-
-  // Pointer arguments must outlive |this|.
-  InstallableInkDropPainter(const InstallableInkDropConfig* config,
-                            const State* state)
-      : config_(config), state_(state) {}
-  ~InstallableInkDropPainter() override = default;
-
-  // Painter:
-  gfx::Size GetMinimumSize() const override;
-  void Paint(gfx::Canvas* canvas, const gfx::Size& size) override;
-
- private:
-  // Contains the colors and opacities we use to paint, given the current state.
-  // This isn't modified inside this class, but it can be modified by our user.
-  const InstallableInkDropConfig* const config_;
-
-  // The current visual state. This isn't modified inside this class, but it can
-  // be modified by our user.
-  const State* const state_;
-};
-
-}  // namespace views
-
-#endif  // UI_VIEWS_ANIMATION_INSTALLABLE_INK_DROP_PAINTER_H_
diff --git a/ui/views/animation/installable_ink_drop_painter_unittest.cc b/ui/views/animation/installable_ink_drop_painter_unittest.cc
deleted file mode 100644
index ec14f77..0000000
--- a/ui/views/animation/installable_ink_drop_painter_unittest.cc
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ui/views/animation/installable_ink_drop_painter.h"
-
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/skia/include/core/SkBitmap.h"
-#include "third_party/skia/include/core/SkColor.h"
-#include "ui/gfx/canvas.h"
-#include "ui/gfx/geometry/point.h"
-#include "ui/views/animation/installable_ink_drop_config.h"
-
-namespace views {
-
-namespace {
-
-class InstallableInkDropPainterTest : public ::testing::Test {
- protected:
-  void SetUp() override {
-    config_.base_color = SK_ColorCYAN;
-    config_.ripple_opacity = 1.0f;
-    config_.highlight_opacity = 1.0f;
-
-    state_.flood_fill_center = gfx::PointF(5.0f, 5.0f);
-    state_.flood_fill_progress = 1.0f;
-    state_.highlighted_ratio = 0.0f;
-  }
-
-  InstallableInkDropConfig config_;
-  InstallableInkDropPainter::State state_;
-};
-
-}  // namespace
-
-TEST_F(InstallableInkDropPainterTest, MinSize) {
-  InstallableInkDropPainter painter(&config_, &state_);
-  EXPECT_EQ(gfx::Size(), painter.GetMinimumSize());
-}
-
-TEST_F(InstallableInkDropPainterTest, Paint) {
-  InstallableInkDropPainter painter(&config_, &state_);
-
-  {
-    // No highlight, half filled.
-    state_.flood_fill_progress = 0.5f;
-    gfx::Canvas canvas(gfx::Size(10, 10), 1.0f, true);
-    SkBitmap bitmap = canvas.GetBitmap();
-    painter.Paint(&canvas, gfx::Size(5.0f, 5.0f));
-    EXPECT_EQ(SkColorSetA(SK_ColorBLACK, 0), bitmap.getColor(0, 0));
-    EXPECT_EQ(SK_ColorCYAN, bitmap.getColor(4, 4));
-  }
-
-  {
-    // No highlight, fully filled.
-    state_.flood_fill_progress = 1.0f;
-    gfx::Canvas canvas(gfx::Size(10, 10), 1.0f, true);
-    SkBitmap bitmap = canvas.GetBitmap();
-    painter.Paint(&canvas, gfx::Size(5.0f, 5.0f));
-    EXPECT_EQ(SK_ColorCYAN, bitmap.getColor(0, 0));
-    EXPECT_EQ(SK_ColorCYAN, bitmap.getColor(4, 4));
-  }
-
-  {
-    // Use highlight, half filled.
-    state_.flood_fill_progress = 0.5f;
-    state_.highlighted_ratio = 0.5f;
-    gfx::Canvas canvas(gfx::Size(10, 10), 1.0f, true);
-    SkBitmap bitmap = canvas.GetBitmap();
-    painter.Paint(&canvas, gfx::Size(5.0f, 5.0f));
-    EXPECT_EQ(0x7F007F7Fu, bitmap.getColor(1, 1));
-    EXPECT_EQ(SK_ColorCYAN, bitmap.getColor(4, 4));
-  }
-
-  {
-    // Change highlight opacity.
-    state_.flood_fill_progress = 0.5f;
-    state_.highlighted_ratio = 0.1f;
-    gfx::Canvas canvas(gfx::Size(10, 10), 1.0f, true);
-    SkBitmap bitmap = canvas.GetBitmap();
-    painter.Paint(&canvas, gfx::Size(5.0f, 5.0f));
-    EXPECT_EQ(0x19001919u, bitmap.getColor(1, 1));
-    EXPECT_EQ(SK_ColorCYAN, bitmap.getColor(4, 4));
-  }
-}
-
-}  // namespace views
diff --git a/ui/views/animation/installable_ink_drop_unittest.cc b/ui/views/animation/installable_ink_drop_unittest.cc
deleted file mode 100644
index cf4c3a95..0000000
--- a/ui/views/animation/installable_ink_drop_unittest.cc
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ui/views/animation/installable_ink_drop.h"
-
-#include <memory>
-#include <utility>
-
-#include "base/test/task_environment.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/skia/include/core/SkPath.h"
-#include "ui/compositor/layer.h"
-#include "ui/gfx/geometry/rect.h"
-#include "ui/views/animation/ink_drop_host_view.h"
-#include "ui/views/animation/ink_drop_state.h"
-#include "ui/views/test/views_test_base.h"
-#include "ui/views/view.h"
-#include "ui/views/view_class_properties.h"
-
-namespace views {
-
-class InstallableInkDropTest : public ViewsTestBase {
- protected:
-  void SetUp() override {
-    ViewsTestBase::SetUp();
-
-    root_view_ = std::make_unique<View>();
-    // Ink drop layers get installed as siblings to their host view's
-    // layer. Hence, there needs to be a root view with a layer above them.
-    root_view_->SetPaintToLayer();
-  }
-
-  View* root_view() { return root_view_.get(); }
-  std::unique_ptr<View> own_root_view() { return std::move(root_view_); }
-
- private:
-  std::unique_ptr<View> root_view_;
-};
-
-TEST_F(InstallableInkDropTest, LayerIsAddedAndRemoved) {
-  View* view = root_view()->AddChildView(std::make_unique<View>());
-  view->SetPaintToLayer();
-  EXPECT_EQ(1, static_cast<int>(root_view()->layer()->children().size()));
-
-  {
-    InstallableInkDrop ink_drop(view);
-    EXPECT_EQ(2, static_cast<int>(root_view()->layer()->children().size()));
-  }
-
-  EXPECT_EQ(1, static_cast<int>(root_view()->layer()->children().size()));
-}
-
-TEST_F(InstallableInkDropTest, LayerSizeTracksViewSize) {
-  View* view = root_view()->AddChildView(std::make_unique<View>());
-  view->SetBoundsRect(gfx::Rect(0, 0, 10, 10));
-
-  InstallableInkDrop ink_drop(view);
-  EXPECT_EQ(view->size(), ink_drop.layer_for_testing()->size());
-
-  view->SetBoundsRect(gfx::Rect(0, 0, 20, 15));
-  EXPECT_EQ(view->size(), ink_drop.layer_for_testing()->size());
-
-  view->SetBoundsRect(gfx::Rect(10, 10, 30, 30));
-  EXPECT_EQ(view->size(), ink_drop.layer_for_testing()->size());
-}
-
-TEST_F(InstallableInkDropTest, UpdatesState) {
-  View* view = root_view()->AddChildView(std::make_unique<View>());
-  InstallableInkDrop ink_drop(view);
-
-  // Initial state should be HIDDEN.
-  EXPECT_EQ(ink_drop.GetTargetInkDropState(), InkDropState::HIDDEN);
-
-  ink_drop.AnimateToState(InkDropState::ACTIVATED);
-  EXPECT_EQ(ink_drop.GetTargetInkDropState(), InkDropState::ACTIVATED);
-
-  ink_drop.SnapToHidden();
-  EXPECT_EQ(ink_drop.GetTargetInkDropState(), InkDropState::HIDDEN);
-
-  ink_drop.SnapToActivated();
-  EXPECT_EQ(ink_drop.GetTargetInkDropState(), InkDropState::ACTIVATED);
-}
-
-TEST_F(InstallableInkDropTest, HighlightStates) {
-  View* view = root_view()->AddChildView(std::make_unique<View>());
-  InstallableInkDrop ink_drop(view);
-
-  // Initial state should be false.
-  EXPECT_FALSE(ink_drop.IsHighlightFadingInOrVisible());
-
-  ink_drop.SetFocused(true);
-  EXPECT_TRUE(ink_drop.IsHighlightFadingInOrVisible());
-
-  ink_drop.SetFocused(false);
-  EXPECT_FALSE(ink_drop.IsHighlightFadingInOrVisible());
-
-  ink_drop.SetHovered(true);
-  EXPECT_TRUE(ink_drop.IsHighlightFadingInOrVisible());
-
-  ink_drop.SetHovered(false);
-  EXPECT_FALSE(ink_drop.IsHighlightFadingInOrVisible());
-}
-
-TEST_F(InstallableInkDropTest, Paint) {
-  std::unique_ptr<Widget> widget = CreateTestWidget();
-  View* root_view = widget->SetContentsView(own_root_view());
-  View* view = root_view->AddChildView(std::make_unique<View>());
-  InstallableInkDrop ink_drop(view);
-
-  views::InstallableInkDropConfig config;
-  config.base_color = SK_ColorCYAN;
-  config.ripple_opacity = 0.05;
-  config.highlight_opacity = 0.07;
-  ink_drop.SetConfig(config);
-  ink_drop.AnimateToState(InkDropState::ACTIVATED);
-
-  auto list = base::MakeRefCounted<cc::DisplayItemList>();
-  ink_drop.OnPaintLayer(
-      ui::PaintContext(list.get(), 1.f, view->bounds(), false));
-  EXPECT_GT(2u, list->num_paint_ops());
-}
-
-}  // namespace views
diff --git a/weblayer/BUILD.gn b/weblayer/BUILD.gn
index 5c273746..b813423 100644
--- a/weblayer/BUILD.gn
+++ b/weblayer/BUILD.gn
@@ -380,9 +380,7 @@
     "utility/content_utility_client_impl.h",
   ]
 
-  configs += [
-    "//build/config:precompiled_headers",
-  ]
+  configs += [ "//build/config:precompiled_headers" ]
 
   public_deps = [ ":android_descriptors" ]
   deps = [
@@ -601,6 +599,8 @@
       "browser/browser_controls_navigation_state_handler_delegate.h",
       "browser/browser_list_proxy.cc",
       "browser/browser_list_proxy.h",
+      "browser/component_updater/registration.cc",
+      "browser/component_updater/registration.h",
       "browser/content_view_render_view.cc",
       "browser/content_view_render_view.h",
       "browser/devtools_manager_delegate_android.cc",
@@ -694,6 +694,7 @@
       "//components/browser_ui/site_settings/android",
       "//components/browser_ui/sms/android",
       "//components/cdm/browser",
+      "//components/component_updater/android:embedded_component_loader",
       "//components/content_capture/android",
       "//components/content_settings/android",
       "//components/crash/android:crash_android",
diff --git a/weblayer/browser/DEPS b/weblayer/browser/DEPS
index 598d8a6..8178a09 100644
--- a/weblayer/browser/DEPS
+++ b/weblayer/browser/DEPS
@@ -14,6 +14,7 @@
   "+components/captive_portal",
   "+components/cdm/browser",
   "+components/client_hints/browser",
+  "+components/component_updater/android",
   "+components/content_capture/browser",
   "+components/content_settings/browser",
   "+components/content_settings/common",
diff --git a/weblayer/browser/component_updater/registration.cc b/weblayer/browser/component_updater/registration.cc
new file mode 100644
index 0000000..321e1905
--- /dev/null
+++ b/weblayer/browser/component_updater/registration.cc
@@ -0,0 +1,14 @@
+// Copyright 2021 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 "weblayer/browser/component_updater/registration.h"
+
+namespace weblayer {
+
+component_updater::ComponentLoaderPolicyVector GetComponentLoaderPolicies() {
+  // TODO(crbug.com/1233490) register AutoFillRegex component loader policy.
+  return component_updater::ComponentLoaderPolicyVector();
+}
+
+}  // namespace weblayer
diff --git a/weblayer/browser/component_updater/registration.h b/weblayer/browser/component_updater/registration.h
new file mode 100644
index 0000000..bebebfd9
--- /dev/null
+++ b/weblayer/browser/component_updater/registration.h
@@ -0,0 +1,17 @@
+// Copyright 2021 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 WEBLAYER_BROWSER_COMPONENT_UPDATER_REGISTRATION_H_
+#define WEBLAYER_BROWSER_COMPONENT_UPDATER_REGISTRATION_H_
+
+#include "components/component_updater/android/component_loader_policy.h"
+
+namespace weblayer {
+
+// ComponentLoaderPolicies for component to load in WebLayer during startup.
+component_updater::ComponentLoaderPolicyVector GetComponentLoaderPolicies();
+
+}  // namespace weblayer
+
+#endif  // WEBLAYER_BROWSER_COMPONENT_UPDATER_REGISTRATION_H_
\ No newline at end of file
diff --git a/weblayer/browser/java/BUILD.gn b/weblayer/browser/java/BUILD.gn
index 29113b87..fbef01e 100644
--- a/weblayer/browser/java/BUILD.gn
+++ b/weblayer/browser/java/BUILD.gn
@@ -226,6 +226,7 @@
     "//components/browser_ui/util/android:java",
     "//components/browser_ui/webshare/android:java",
     "//components/browser_ui/widget/android:java",
+    "//components/component_updater/android:embedded_component_loader_java",
     "//components/content_capture/android:java",
     "//components/content_settings/android:content_settings_enums_java",
     "//components/content_settings/android:java",
diff --git a/weblayer/browser/java/DEPS b/weblayer/browser/java/DEPS
index b6aec97..5e4a0d7 100644
--- a/weblayer/browser/java/DEPS
+++ b/weblayer/browser/java/DEPS
@@ -2,6 +2,7 @@
   "+components/browser_ui/browser_ui/android",
   "+components/browser_ui/http_auth",
   "+components/browser_ui/util/android",
+  "+components/component_updater/android",
   "+components/content_capture/android",
   "+components/content_settings/android/java",
   "+components/crash/android/java",
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java
index 85dee64..000421e 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java
@@ -59,6 +59,8 @@
 import org.chromium.components.browser_ui.photo_picker.PhotoPickerDialog;
 import org.chromium.components.browser_ui.share.ClipboardImageFileProvider;
 import org.chromium.components.browser_ui.share.ShareImageFileUtils;
+import org.chromium.components.component_updater.ComponentLoaderPolicyBridge;
+import org.chromium.components.component_updater.EmbeddedComponentLoader;
 import org.chromium.components.embedder_support.application.ClassLoaderContextWrapperFactory;
 import org.chromium.components.embedder_support.application.FirebaseConfig;
 import org.chromium.components.embedder_support.util.Origin;
@@ -103,6 +105,7 @@
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -210,6 +213,9 @@
         NetworkChangeNotifier.init();
         NetworkChangeNotifier.registerToReceiveNotificationsAlways();
 
+        // Native and variations has to be loaded before this.
+        loadComponents();
+
         // This issues JNI calls which require native code to be loaded.
         MetricsServiceClient.init();
 
@@ -973,6 +979,23 @@
         });
     }
 
+    /**
+     * Load components files from {@link
+     * org.chromium.android_webview.services.ComponentsProviderService}.
+     */
+    private static void loadComponents() {
+        ComponentLoaderPolicyBridge[] componentPolicies =
+                WebLayerImplJni.get().getComponentLoaderPolicies();
+        // Don't connect to the service if there are no components to load.
+        if (componentPolicies.length == 0) {
+            return;
+        }
+        final Intent intent = new Intent();
+        intent.setClassName(WebViewFactory.getLoadedPackageInfo().packageName,
+                EmbeddedComponentLoader.AW_COMPONENTS_PROVIDER_SERVICE);
+        new EmbeddedComponentLoader(Arrays.asList(componentPolicies)).connect(intent);
+    }
+
     @NativeMethods
     interface Natives {
         void setRemoteDebuggingEnabled(boolean enabled);
@@ -981,5 +1004,6 @@
         String getUserAgentString();
         void registerExternalExperimentIDs(int[] experimentIDs);
         boolean isLocationPermissionManaged(String origin);
+        ComponentLoaderPolicyBridge[] getComponentLoaderPolicies();
     }
 }
diff --git a/weblayer/browser/weblayer_impl_android.cc b/weblayer/browser/weblayer_impl_android.cc
index c696d076..53e0107 100644
--- a/weblayer/browser/weblayer_impl_android.cc
+++ b/weblayer/browser/weblayer_impl_android.cc
@@ -7,10 +7,12 @@
 #include "base/android/jni_android.h"
 #include "base/android/jni_array.h"
 #include "base/android/jni_string.h"
+#include "components/component_updater/android/component_loader_policy.h"
 #include "components/crash/core/common/crash_key.h"
 #include "components/embedder_support/user_agent_utils.h"
 #include "components/page_info/android/page_info_client.h"
 #include "weblayer/browser/android/metrics/weblayer_metrics_service_client.h"
+#include "weblayer/browser/component_updater/registration.h"
 #include "weblayer/browser/default_search_engine.h"
 #include "weblayer/browser/devtools_server_android.h"
 #include "weblayer/browser/java/jni/WebLayerImpl_jni.h"
@@ -72,4 +74,11 @@
       url::Origin::Create(GURL(ConvertJavaStringToUTF8(origin))));
 }
 
+static base::android::ScopedJavaLocalRef<jobjectArray>
+JNI_WebLayerImpl_GetComponentLoaderPolicies(JNIEnv* env) {
+  return component_updater::AndroidComponentLoaderPolicy::
+      ToJavaArrayOfAndroidComponentLoaderPolicy(env,
+                                                GetComponentLoaderPolicies());
+}
+
 }  // namespace weblayer