diff --git a/.clang-tidy b/.clang-tidy
index 02540c4..3477d88 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -1,6 +1,8 @@
 ---
 ---
   Checks:          '-*,
+                    bugprone-string-integer-assignment,
+                    bugprone-unused-raii,
                     bugprone-use-after-move,
                     google-build-explicit-make-pair,
                     google-explicit-constructor,
diff --git a/BUILD.gn b/BUILD.gn
index 39b9075..c4f5057 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -545,7 +545,7 @@
 
     if (enable_extensions) {
       deps += [ "//extensions/shell:app_shell" ]
-      if (is_desktop_linux && is_official_build) {
+      if (is_linux && is_official_build) {
         deps += [ "//extensions/shell:app_shell_linux_symbols" ]
       }
     }
@@ -1146,25 +1146,38 @@
   }
 
   # https://chromium.googlesource.com/chromium/src/+/master/docs/testing/web_tests.md
-  generate_wrapper("blink_web_tests") {
-    testonly = true
-    wrapper_script = "${root_build_dir}/bin/run_blink_web_tests"
-    executable = "//third_party/blink/tools/run_web_tests.py"
-    executable_args = []
+  script_test("blink_web_tests") {
+    script = "//testing/scripts/run_isolated_script_test.py"
+
+    args = [ "@WrappedPath(" +
+             rebase_path("//third_party/blink/tools/run_web_tests.py",
+                         root_build_dir) + ")" ]
 
     if (is_debug) {
-      executable_args += [ "--debug" ]
+      args += [ "--debug" ]
     } else {
-      executable_args += [ "--release" ]
+      args += [ "--release" ]
     }
 
     if (is_android) {
-      executable_args += [
+      args += [
         "--platform",
         "android",
       ]
     }
 
+    args += [
+      "--seed",
+      "4",
+      "--no-show-results",
+      "--zero-tests-executed-ok",
+      "--clobber-old-results",
+      "--exit-after-n-failures",
+      "5000",
+      "--exit-after-n-crashes-or-timeouts",
+      "100",
+    ]
+
     data_deps = [ ":blink_web_tests_support_data" ]
     data = [
       "//third_party/blink/perf_tests/",
@@ -1227,14 +1240,15 @@
     deps = [ "//mojo/public/js:bindings_lite" ]
   }
 
-  group("blink_python_tests") {
+  script_test("blink_python_tests") {
+    script = "//testing/scripts/run_isolated_script_test.py"
+    args = [ "@WrappedPath(" +
+             rebase_path("//third_party/blink/tools/run_blinkpy_tests.py",
+                         root_build_dir) + ")" ]
+
     data = [
       "//build/android/",
       "//components/crash/content/tools/generate_breakpad_symbols.py",
-      "//testing/scripts/common.py",
-      "//testing/scripts/run_isolated_script_test.py",
-      "//testing/test_env.py",
-      "//testing/xvfb.py",
       "//third_party/blink/renderer/bindings/scripts/",
       "//third_party/blink/renderer/build/scripts/",
       "//third_party/blink/tools/",
diff --git a/DEPS b/DEPS
index daa57bf..428e994 100644
--- a/DEPS
+++ b/DEPS
@@ -207,15 +207,15 @@
   # 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': '3d5e8c4c466d5ec179f4d88e4ea566d6c4683ae6',
+  'angle_revision': '64eb64a034cb725baf3dea089f3badbbab77a0be',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': '5e947adaf26eee51de47e406a9f7943afd1a882f',
+  'swiftshader_revision': '02818d1fdd05410fa9d3ffa6f9ce313a63d03dcf',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': 'd6055cbe1f06a4f8acc172e53e4b2f14f2e057d6',
+  'pdfium_revision': 'dcffb3db73cd70771cc57cfdcca93a9b756467fb',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -881,7 +881,7 @@
   # Tools used when building Chrome for Chrome OS. This affects both the Simple
   # Chrome workflow, as well as the chromeos-chrome ebuild.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '11b196b0c0c6c3101d622ad2c45ea60013de86c2',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '124ea2073720aa86e57239b1483a50961166382c',
       'condition': 'checkout_chromeos',
   },
 
@@ -901,7 +901,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '68377adad5aecf06c5f7e189319a5199fde6992f',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '98218e9219b11e9681420b7a61e126bdb6fafec3',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1237,7 +1237,7 @@
     Var('chromium_git') + '/external/github.com/cisco/openh264' + '@' + '3dd5b80bc4f172dd82925bb259cb7c82348409c5',
 
   'src/third_party/openscreen/src':
-    Var('chromium_git') + '/openscreen' + '@' + '8b462fcdef400fe15823ec8c89b7037720aeeb37',
+    Var('chromium_git') + '/openscreen' + '@' + 'ca55b2704b0f97487b5271cc1aaedc90fed10686',
 
   'src/third_party/openxr/src': {
     'url': Var('chromium_git') + '/external/github.com/KhronosGroup/OpenXR-SDK' + '@' + '9e97b73e7dd2bfc07745489d728f6a36665c648f',
@@ -1254,7 +1254,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'f01bf538a4ff420c223043482aa455e6974d3de0',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'bce69fefbe63cae099a90ff890fe8f1daaa9cd70',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1332,7 +1332,7 @@
       'packages': [
           {
               'package': 'fuchsia/third_party/aemu/linux-amd64',
-              'version': 'ExOy6Vlzx-dwIqNTNzIfOKNavzc1k0qJL5M0J2LT_ckC'
+              'version': '1hf_Qc-S06rqD9f-iPsgfnzfAQrZOCzZzzcMI6_T3ooC'
           },
       ],
       'condition': 'host_os == "linux" and checkout_fuchsia',
@@ -1486,7 +1486,7 @@
   },
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '281fb1ed7ba9f43f69462b57205a1906d1702cbc',
+    Var('webrtc_git') + '/src.git' + '@' + 'dba4db5668d6dd95475dc4e8aa42de3011b4a15e',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1558,7 +1558,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@e2764fee10f668480f4cabe38766a56f9cfb1e7e',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@30ea8e724567e2cf48359b2b41b4bcefcb32e9ac',
     'condition': 'checkout_src_internal',
   },
 
@@ -1577,7 +1577,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'ilg0VTVmMGe2tnW36QoR7xaXNXp9wHu7HMww1knXLeQC',
+        'version': 'NmnsodASCDlnL1R0fAI_q3in9QdwqYpagivmAet3MhcC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsRenderTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsRenderTest.java
index 1e72106b..69e6c66 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsRenderTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsRenderTest.java
@@ -70,6 +70,12 @@
         setBackgroundColorOnUiThread(Color.YELLOW);
         GraphicsTestUtils.pollForBackgroundColor(mAwContents, Color.YELLOW);
 
+        final String html_meta = "<html><head><meta name=color-scheme content=dark></head></html>";
+        mActivityTestRule.loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
+                "data:text/html," + html_meta);
+        final int dark_scheme_color = 0xFF121212;
+        GraphicsTestUtils.pollForBackgroundColor(mAwContents, dark_scheme_color);
+
         final String html = "<html><head><style>body {background-color:#227788}</style></head>"
                 + "<body></body></html>";
         // Loading the html via a data URI requires us to encode '#' symbols as '%23'.
diff --git a/ash/ambient/ambient_constants.h b/ash/ambient/ambient_constants.h
index eb4f5e67..56b010c7 100644
--- a/ash/ambient/ambient_constants.h
+++ b/ash/ambient/ambient_constants.h
@@ -29,8 +29,7 @@
     base::TimeDelta::FromMinutes(5);
 
 // The batch size of topics to fetch in one request.
-// Magic number 2 is based on experiments that no curation on Google Photos.
-constexpr int kTopicsBatchSize = 2;
+constexpr int kTopicsBatchSize = 100;
 
 // Max cached images.
 constexpr int kMaxNumberOfCachedImages = 100;
diff --git a/ash/ambient/backdrop/ambient_backend_controller_impl.cc b/ash/ambient/backdrop/ambient_backend_controller_impl.cc
index 461bf860..81b199a 100644
--- a/ash/ambient/backdrop/ambient_backend_controller_impl.cc
+++ b/ash/ambient/backdrop/ambient_backend_controller_impl.cc
@@ -15,6 +15,7 @@
 #include "ash/public/cpp/ambient/ambient_metrics.h"
 #include "ash/public/cpp/ambient/ambient_prefs.h"
 #include "ash/public/cpp/ambient/common/ambient_settings.h"
+#include "ash/public/cpp/shell_window_ids.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "base/barrier_closure.h"
@@ -28,11 +29,14 @@
 #include "components/user_manager/user_manager.h"
 #include "net/base/load_flags.h"
 #include "net/base/net_errors.h"
+#include "net/base/url_util.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "services/data_decoder/public/cpp/data_decoder.h"
 #include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/public/cpp/simple_url_loader.h"
+#include "ui/display/display.h"
+#include "ui/display/screen.h"
 #include "url/gurl.h"
 
 namespace ash {
@@ -244,6 +248,14 @@
   return false;
 }
 
+gfx::Size GetDisplaySizeInPixel() {
+  auto* ambient_container = Shell::GetContainer(
+      Shell::GetPrimaryRootWindow(), kShellWindowId_AmbientModeContainer);
+  return display::Screen::GetScreen()
+      ->GetDisplayNearestView(ambient_container)
+      .GetSizeInPixel();
+}
+
 }  // namespace
 
 // Helper class for handling Backdrop service requests.
@@ -426,6 +438,15 @@
           num_topics, gaia_id, access_token, client_id);
   auto resource_request = CreateResourceRequest(request);
 
+  // Request photo with display size in pixel.
+  gfx::Size display_size_px = GetDisplaySizeInPixel();
+  resource_request->url =
+      net::AppendQueryParameter(resource_request->url, "device-screen-width",
+                                base::NumberToString(display_size_px.width()));
+  resource_request->url =
+      net::AppendQueryParameter(resource_request->url, "device-screen-height",
+                                base::NumberToString(display_size_px.height()));
+
   auto backdrop_url_loader = std::make_unique<BackdropURLLoader>();
   auto* loader_ptr = backdrop_url_loader.get();
   loader_ptr->Start(
diff --git a/ash/ambient/ui/media_string_view_unittest.cc b/ash/ambient/ui/media_string_view_unittest.cc
index bab60b8a..2a4ffaa 100644
--- a/ash/ambient/ui/media_string_view_unittest.cc
+++ b/ash/ambient/ui/media_string_view_unittest.cc
@@ -6,6 +6,7 @@
 
 #include "ash/ambient/ambient_constants.h"
 #include "ash/ambient/test/ambient_ash_test_base.h"
+#include "ash/ambient/ui/ambient_container_view.h"
 #include "ash/public/cpp/ash_pref_names.h"
 #include "ash/shell.h"
 #include "base/strings/string16.h"
@@ -260,8 +261,8 @@
   metadata.artist = base::ASCIIToUTF16("artist");
 
   SimulateMediaMetadataChanged(metadata);
-  // Wait for layout.
-  task_environment()->FastForwardBy(base::TimeDelta::FromMilliseconds(100));
+  // Force re-layout.
+  container_view()->Layout();
 
   EXPECT_LT(GetMediaStringViewTextLabel()->GetPreferredSize().width(),
             kMediaStringMaxWidthDip);
@@ -277,8 +278,8 @@
   metadata.artist = base::ASCIIToUTF16("A super duper long artist name");
 
   SimulateMediaMetadataChanged(metadata);
-  // Wait for layout.
-  task_environment()->FastForwardBy(base::TimeDelta::FromMilliseconds(100));
+  // Force re-layout.
+  container_view()->Layout();
 
   EXPECT_GT(GetMediaStringViewTextLabel()->GetPreferredSize().width(),
             kMediaStringMaxWidthDip);
@@ -294,8 +295,8 @@
   metadata.artist = base::ASCIIToUTF16("artist");
 
   SimulateMediaMetadataChanged(metadata);
-  // Wait for layout.
-  task_environment()->FastForwardBy(base::TimeDelta::FromMilliseconds(100));
+  // Force re-layout.
+  container_view()->Layout();
 
   EXPECT_LT(GetMediaStringViewTextLabel()->GetPreferredSize().width(),
             kMediaStringMaxWidthDip);
@@ -306,8 +307,8 @@
   metadata.artist = base::ASCIIToUTF16("A super duper long artist name");
 
   SimulateMediaMetadataChanged(metadata);
-  // Wait for layout.
-  task_environment()->FastForwardBy(base::TimeDelta::FromMilliseconds(100));
+  // Force re-layout.
+  container_view()->Layout();
 
   EXPECT_GT(GetMediaStringViewTextLabel()->GetPreferredSize().width(),
             kMediaStringMaxWidthDip);
@@ -318,8 +319,8 @@
   metadata.artist = base::ASCIIToUTF16("artist");
 
   SimulateMediaMetadataChanged(metadata);
-  // Wait for layout.
-  task_environment()->FastForwardBy(base::TimeDelta::FromMilliseconds(100));
+  // Force re-layout.
+  container_view()->Layout();
 
   EXPECT_LT(GetMediaStringViewTextLabel()->GetPreferredSize().width(),
             kMediaStringMaxWidthDip);
diff --git a/ash/app_list/app_list_color_provider_impl.cc b/ash/app_list/app_list_color_provider_impl.cc
index 59eb57c..d9f5877 100644
--- a/ash/app_list/app_list_color_provider_impl.cc
+++ b/ash/app_list/app_list_color_provider_impl.cc
@@ -135,6 +135,22 @@
       AshColorProvider::ContentLayerType::kSeparatorColor);
 }
 
+SkColor AppListColorProviderImpl::GetSearchResultViewInkDropColor() const {
+  AshColorProvider::RippleAttributes ripple_attributes =
+      ash_color_provider_->GetRippleAttributes(
+          GetSearchBoxCardBackgroundColor());
+  return SkColorSetA(ripple_attributes.base_color,
+                     ripple_attributes.inkdrop_opacity * 255);
+}
+
+SkColor AppListColorProviderImpl::GetSearchResultViewHighlightColor() const {
+  AshColorProvider::RippleAttributes ripple_attributes =
+      ash_color_provider_->GetRippleAttributes(
+          GetSearchBoxCardBackgroundColor());
+  return SkColorSetA(ripple_attributes.base_color,
+                     ripple_attributes.highlight_opacity * 255);
+}
+
 float AppListColorProviderImpl::GetFolderBackgrounBlurSigma() const {
   return static_cast<float>(AshColorProvider::LayerBlurSigma::kBlurDefault);
 }
diff --git a/ash/app_list/app_list_color_provider_impl.h b/ash/app_list/app_list_color_provider_impl.h
index 2f0a765..44163f3 100644
--- a/ash/app_list/app_list_color_provider_impl.h
+++ b/ash/app_list/app_list_color_provider_impl.h
@@ -38,6 +38,8 @@
   SkColor GetFolderNameBackgroundColor(bool active) const override;
   SkColor GetContentsBackgroundColor() const override;
   SkColor GetSeparatorColor() const override;
+  SkColor GetSearchResultViewHighlightColor() const override;
+  SkColor GetSearchResultViewInkDropColor() const override;
   float GetFolderBackgrounBlurSigma() const override;
 
  private:
diff --git a/ash/app_list/test/test_app_list_color_provider.cc b/ash/app_list/test/test_app_list_color_provider.cc
index 362251e..06c703c 100644
--- a/ash/app_list/test/test_app_list_color_provider.cc
+++ b/ash/app_list/test/test_app_list_color_provider.cc
@@ -100,6 +100,14 @@
   return SkColorSetA(SK_ColorWHITE, 0x24);
 }
 
+SkColor TestAppListColorProvider::GetSearchResultViewHighlightColor() const {
+  return SkColorSetA(SK_ColorWHITE, 0x12);
+}
+
+SkColor TestAppListColorProvider::GetSearchResultViewInkDropColor() const {
+  return SkColorSetA(SK_ColorWHITE, 0x17);
+}
+
 float TestAppListColorProvider::GetFolderBackgrounBlurSigma() const {
   return 30.0f;
 }
diff --git a/ash/app_list/test/test_app_list_color_provider.h b/ash/app_list/test/test_app_list_color_provider.h
index f8b3b10..cfdec65a 100644
--- a/ash/app_list/test/test_app_list_color_provider.h
+++ b/ash/app_list/test/test_app_list_color_provider.h
@@ -38,6 +38,8 @@
   SkColor GetFolderNameBackgroundColor(bool active) const override;
   SkColor GetContentsBackgroundColor() const override;
   SkColor GetSeparatorColor() const override;
+  SkColor GetSearchResultViewHighlightColor() const override;
+  SkColor GetSearchResultViewInkDropColor() const override;
   float GetFolderBackgrounBlurSigma() const override;
 };
 
diff --git a/ash/app_list/views/search_result_actions_view.cc b/ash/app_list/views/search_result_actions_view.cc
index 924057d..6ea5f74 100644
--- a/ash/app_list/views/search_result_actions_view.cc
+++ b/ash/app_list/views/search_result_actions_view.cc
@@ -11,12 +11,12 @@
 
 #include "ash/app_list/views/search_result_actions_view_delegate.h"
 #include "ash/app_list/views/search_result_view.h"
+#include "ash/public/cpp/app_list/app_list_color_provider.h"
 #include "ash/public/cpp/app_list/app_list_config.h"
 #include "base/numerics/ranges.h"
 #include "ui/accessibility/ax_enums.mojom.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/gfx/canvas.h"
-#include "ui/gfx/color_palette.h"
 #include "ui/gfx/geometry/insets.h"
 #include "ui/gfx/image/image_skia_operations.h"
 #include "ui/views/animation/flood_fill_ink_drop_ripple.h"
@@ -34,8 +34,6 @@
 // Image buttons.
 constexpr int kImageButtonSizeDip = 40;
 constexpr int kActionButtonBetweenSpacing = 8;
-// Button hover color, Google Grey 8%.
-constexpr SkColor kButtonHoverColor = SkColorSetA(gfx::kGoogleGrey900, 0x14);
 
 }  // namespace
 
@@ -128,8 +126,8 @@
   const int ripple_radius = GetInkDropRadius();
   gfx::Rect bounds(center.x() - ripple_radius, center.y() - ripple_radius,
                    2 * ripple_radius, 2 * ripple_radius);
-  constexpr SkColor ripple_color = SkColorSetA(gfx::kGoogleGrey900, 0x17);
-
+  SkColor ripple_color =
+      AppListColorProvider::Get()->GetSearchResultViewInkDropColor();
   return std::make_unique<views::FloodFillInkDropRipple>(
       size(), GetLocalBounds().InsetsFrom(bounds),
       GetInkDropCenterBasedOnLastEvent(), ripple_color, 1.0f);
@@ -137,9 +135,8 @@
 
 std::unique_ptr<views::InkDropHighlight>
 SearchResultImageButton::CreateInkDropHighlight() const {
-  // TODO(crbug.com/1051167): Grab ink drop related colors and opacities from a
-  // theme.
-  constexpr SkColor ripple_color = SkColorSetA(gfx::kGoogleGrey900, 0x12);
+  SkColor ripple_color =
+      AppListColorProvider::Get()->GetSearchResultViewHighlightColor();
   auto highlight = std::make_unique<views::InkDropHighlight>(gfx::SizeF(size()),
                                                              ripple_color);
   highlight->set_visible_opacity(1.f);
@@ -157,7 +154,8 @@
   if (HasFocus() || parent_->GetSelectedAction() == tag()) {
     cc::PaintFlags circle_flags;
     circle_flags.setAntiAlias(true);
-    circle_flags.setColor(kButtonHoverColor);
+    circle_flags.setColor(
+        AppListColorProvider::Get()->GetSearchResultViewHighlightColor());
     circle_flags.setStyle(cc::PaintFlags::kFill_Style);
     canvas->DrawCircle(GetLocalBounds().CenterPoint(), GetInkDropRadius(),
                        circle_flags);
diff --git a/ash/app_list/views/search_result_page_view.cc b/ash/app_list/views/search_result_page_view.cc
index fb2d06b2..03cc9a8 100644
--- a/ash/app_list/views/search_result_page_view.cc
+++ b/ash/app_list/views/search_result_page_view.cc
@@ -69,8 +69,6 @@
 // Minimum spacing between shelf and bottom of search box.
 constexpr int kSearchResultPageMinimumBottomMargin = 24;
 
-constexpr SkColor kSeparatorColor = SkColorSetA(gfx::kGoogleGrey900, 0x24);
-
 // The shadow elevation value for the shadow of the expanded search box.
 constexpr int kSearchBoxSearchResultShadowElevation = 12;
 
@@ -134,7 +132,7 @@
     // Draw a separator between SearchBoxView and SearchResultPageView.
     bounds.set_y(kSearchBoxHeight + kSearchBoxBottomSpacing);
     bounds.set_height(kSeparatorThickness);
-    canvas->FillRect(bounds, kSeparatorColor);
+    canvas->FillRect(bounds, AppListColorProvider::Get()->GetSeparatorColor());
   }
 };
 
@@ -159,7 +157,7 @@
 
   void OnPaint(gfx::Canvas* canvas) override {
     gfx::Rect rect = GetContentsBounds();
-    canvas->FillRect(rect, kSeparatorColor);
+    canvas->FillRect(rect, AppListColorProvider::Get()->GetSeparatorColor());
     View::OnPaint(canvas);
   }
 
diff --git a/ash/app_list/views/search_result_tile_item_view.cc b/ash/app_list/views/search_result_tile_item_view.cc
index be728030..5ff59977cb 100644
--- a/ash/app_list/views/search_result_tile_item_view.cc
+++ b/ash/app_list/views/search_result_tile_item_view.cc
@@ -57,8 +57,6 @@
 
 constexpr int kIconSelectedSize = 56;
 constexpr int kIconSelectedCornerRadius = 4;
-// Icon selected color, Google Grey 900 8%.
-constexpr int kIconSelectedColor = SkColorSetA(gfx::kGoogleGrey900, 0x14);
 
 // Offset for centering star rating when there is no price.
 constexpr int kSearchRatingCenteringOffset =
@@ -308,7 +306,8 @@
   flags.setStyle(cc::PaintFlags::kFill_Style);
   if (IsSuggestedAppTileShownInAppPage()) {
     rect.ClampToCenteredSize(AppListConfig::instance().grid_focus_size());
-    flags.setColor(AppListConfig::instance().grid_selected_color());
+    flags.setColor(
+        AppListColorProvider::Get()->GetSearchResultViewInkDropColor());
     canvas->DrawRoundRect(gfx::RectF(rect),
                           AppListConfig::instance().grid_focus_corner_radius(),
                           flags);
@@ -316,7 +315,8 @@
     const int kLeftRightPadding = (rect.width() - kIconSelectedSize) / 2;
     rect.Inset(kLeftRightPadding, 0);
     rect.set_height(kIconSelectedSize);
-    flags.setColor(kIconSelectedColor);
+    flags.setColor(
+        AppListColorProvider::Get()->GetSearchResultViewInkDropColor());
     canvas->DrawRoundRect(gfx::RectF(rect), kIconSelectedCornerRadius, flags);
   }
 }
diff --git a/ash/app_list/views/search_result_view.cc b/ash/app_list/views/search_result_view.cc
index 26e9311..7d8897c1 100644
--- a/ash/app_list/views/search_result_view.cc
+++ b/ash/app_list/views/search_result_view.cc
@@ -47,8 +47,6 @@
 
 // URL color.
 constexpr SkColor kUrlColor = gfx::kGoogleBlue600;
-// Row selected color, Google Grey 8%.
-constexpr SkColor kRowHighlightedColor = SkColorSetA(gfx::kGoogleGrey900, 0x14);
 // Search result border color.
 constexpr SkColor kResultBorderColor = SkColorSetARGB(0xFF, 0xE5, 0xE5, 0xE5);
 
@@ -272,7 +270,9 @@
   // Possibly call FillRect a second time (these colours are partially
   // transparent, so the previous FillRect is not redundant).
   if (selected() && !actions_view()->HasSelectedAction()) {
-    canvas->FillRect(content_rect, kRowHighlightedColor);
+    canvas->FillRect(
+        content_rect,
+        AppListColorProvider::Get()->GetSearchResultViewHighlightColor());
   }
 
   gfx::Rect border_bottom = gfx::SubtractRects(rect, content_rect);
diff --git a/ash/assistant/ui/main_stage/assistant_onboarding_suggestion_view.cc b/ash/assistant/ui/main_stage/assistant_onboarding_suggestion_view.cc
index 8808878..603fccec58 100644
--- a/ash/assistant/ui/main_stage/assistant_onboarding_suggestion_view.cc
+++ b/ash/assistant/ui/main_stage/assistant_onboarding_suggestion_view.cc
@@ -216,6 +216,10 @@
       views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
                                views::MaximumFlexSizeRule::kUnbounded));
   label_->SetText(base::UTF8ToUTF16(suggestion.text));
+
+  // Workaround issue where multiline label is not allocated enough height.
+  label_->SetPreferredSize(
+      gfx::Size(label_->GetPreferredSize().width(), 2 * kLabelLineHeight));
 }
 
 void AssistantOnboardingSuggestionView::UpdateIcon(const gfx::ImageSkia& icon) {
diff --git a/ash/capture_mode/capture_mode_close_button.cc b/ash/capture_mode/capture_mode_close_button.cc
index 0202381..2a106cf 100644
--- a/ash/capture_mode/capture_mode_close_button.cc
+++ b/ash/capture_mode/capture_mode_close_button.cc
@@ -18,7 +18,7 @@
   SetBorder(views::CreateEmptyBorder(capture_mode::kButtonPadding));
   auto* color_provider = AshColorProvider::Get();
   const SkColor normal_color = color_provider->GetContentLayerColor(
-      AshColorProvider::ContentLayerType::kSystemMenuIconColor);
+      AshColorProvider::ContentLayerType::kButtonIconColor);
   SetImage(views::Button::STATE_NORMAL,
            gfx::CreateVectorIcon(kCloseButtonIcon, normal_color));
   SetImageHorizontalAlignment(ALIGN_CENTER);
diff --git a/ash/capture_mode/capture_mode_toggle_button.cc b/ash/capture_mode/capture_mode_toggle_button.cc
index 9cfdfcdb..55cccf6 100644
--- a/ash/capture_mode/capture_mode_toggle_button.cc
+++ b/ash/capture_mode/capture_mode_toggle_button.cc
@@ -69,9 +69,9 @@
 void CaptureModeToggleButton::SetIcon(const gfx::VectorIcon& icon) {
   auto* color_provider = AshColorProvider::Get();
   const SkColor normal_color = color_provider->GetContentLayerColor(
-      AshColorProvider::ContentLayerType::kSystemMenuIconColor);
+      AshColorProvider::ContentLayerType::kButtonIconColor);
   const SkColor toggled_color = color_provider->GetContentLayerColor(
-      AshColorProvider::ContentLayerType::kSystemMenuIconColorToggled);
+      AshColorProvider::ContentLayerType::kButtonIconColorPrimary);
 
   SetImage(views::Button::STATE_NORMAL,
            gfx::CreateVectorIcon(icon, normal_color));
diff --git a/ash/host/ash_window_tree_host_platform_unittest.cc b/ash/host/ash_window_tree_host_platform_unittest.cc
index 2f9b7506..d8d7807 100644
--- a/ash/host/ash_window_tree_host_platform_unittest.cc
+++ b/ash/host/ash_window_tree_host_platform_unittest.cc
@@ -30,6 +30,7 @@
 
   // these are all stubs that are not used yet in these tests
   bool HasMouse() override { return false; }
+  bool HasPointingStick() override { return false; }
   bool HasTouchpad() override { return false; }
   bool IsCapsLockEnabled() override { return false; }
   void SetCapsLockEnabled(bool enabled) override {}
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 d089b41..d94db8a7 100644
--- a/ash/public/cpp/app_list/app_list_color_provider.h
+++ b/ash/public/cpp/app_list/app_list_color_provider.h
@@ -37,6 +37,8 @@
   virtual SkColor GetFolderNameBackgroundColor(bool active) const = 0;
   virtual SkColor GetContentsBackgroundColor() const = 0;
   virtual SkColor GetSeparatorColor() const = 0;
+  virtual SkColor GetSearchResultViewHighlightColor() const = 0;
+  virtual SkColor GetSearchResultViewInkDropColor() const = 0;
   virtual float GetFolderBackgrounBlurSigma() const = 0;
 
  protected:
diff --git a/ash/style/ash_color_provider.cc b/ash/style/ash_color_provider.cc
index bc3e701..04b952fb 100644
--- a/ash/style/ash_color_provider.cc
+++ b/ash/style/ash_color_provider.cc
@@ -210,13 +210,9 @@
       return is_dark_mode ? gfx::kGoogleBlue300 : gfx::kGoogleBlue600;
     case ContentLayerType::kButtonLabelColorPrimary:
     case ContentLayerType::kButtonIconColorPrimary:
-      return is_dark_mode ? gfx::kGoogleGrey200 : gfx::kGoogleGrey900;
+      return is_dark_mode ? gfx::kGoogleGrey900 : gfx::kGoogleGrey200;
     case ContentLayerType::kSliderThumbColorDisabled:
       return is_dark_mode ? gfx::kGoogleGrey600 : gfx::kGoogleGrey600;
-    case ContentLayerType::kSystemMenuIconColor:
-      return is_dark_mode ? gfx::kGoogleGrey200 : gfx::kGoogleGrey700;
-    case ContentLayerType::kSystemMenuIconColorToggled:
-      return is_dark_mode ? gfx::kGoogleGrey900 : gfx::kGoogleGrey200;
     case ContentLayerType::kAppStateIndicatorColor:
       return is_dark_mode ? gfx::kGoogleGrey200 : gfx::kGoogleGrey700;
     case ContentLayerType::kAppStateIndicatorColorInactive:
diff --git a/ash/style/ash_color_provider.h b/ash/style/ash_color_provider.h
index e9bb23c..d745f25 100644
--- a/ash/style/ash_color_provider.h
+++ b/ash/style/ash_color_provider.h
@@ -105,11 +105,6 @@
     kButtonIconColor,
     kButtonIconColorPrimary,
 
-    // Color for system menu icon buttons with inverted dark mode colors, e.g,
-    // FeaturePodIconButton
-    kSystemMenuIconColor,
-    kSystemMenuIconColorToggled,
-
     // Color for sliders (volume, brightness etc.)
     kSliderThumbColorEnabled,
     kSliderThumbColorDisabled,
diff --git a/ash/system/holding_space/holding_space_item_view.cc b/ash/system/holding_space/holding_space_item_view.cc
index 9702fa9..49d4c78 100644
--- a/ash/system/holding_space/holding_space_item_view.cc
+++ b/ash/system/holding_space/holding_space_item_view.cc
@@ -208,7 +208,7 @@
   pin_->SetVisible(false);
 
   const SkColor icon_color = AshColorProvider::Get()->GetContentLayerColor(
-      AshColorProvider::ContentLayerType::kSystemMenuIconColor);
+      AshColorProvider::ContentLayerType::kButtonIconColor);
 
   const gfx::ImageSkia unpinned_icon = gfx::CreateVectorIcon(
       views::kUnpinIcon, kHoldingSpacePinIconSize, icon_color);
diff --git a/ash/system/network/network_feature_pod_button.cc b/ash/system/network/network_feature_pod_button.cc
index b8902fef..f7517669 100644
--- a/ash/system/network/network_feature_pod_button.cc
+++ b/ash/system/network/network_feature_pod_button.cc
@@ -141,42 +141,32 @@
 }
 
 void NetworkFeaturePodButton::Update() {
-  bool image_animating = false;
-  bool toggled_image_animating = false;
+  TrayNetworkStateModel* model =
+      Shell::Get()->system_tray_model()->network_state_model();
+  const NetworkStateProperties* network = model->default_network();
 
+  const bool toggled = network || model->GetDeviceState(NetworkType::kWiFi) ==
+                                      DeviceStateType::kEnabled;
+  network_icon::IconType icon_type =
+      toggled ? network_icon::ICON_TYPE_FEATURE_POD_TOGGLED
+              : network_icon::ICON_TYPE_FEATURE_POD;
+  bool image_animating = false;
   gfx::ImageSkia image =
       Shell::Get()->system_tray_model()->active_network_icon()->GetImage(
-          ActiveNetworkIcon::Type::kSingle, network_icon::ICON_TYPE_FEATURE_POD,
-          &image_animating);
-
-  gfx::ImageSkia image_toggled =
-      Shell::Get()->system_tray_model()->active_network_icon()->GetImage(
-          ActiveNetworkIcon::Type::kSingle,
-          network_icon::ICON_TYPE_FEATURE_POD_TOGGLED,
-          &toggled_image_animating);
-
+          ActiveNetworkIcon::Type::kSingle, icon_type, &image_animating);
   gfx::ImageSkia image_disabled =
       Shell::Get()->system_tray_model()->active_network_icon()->GetImage(
           ActiveNetworkIcon::Type::kSingle,
           network_icon::ICON_TYPE_FEATURE_POD_DISABLED, &image_animating);
 
-  if (image_animating || toggled_image_animating)
+  if (image_animating)
     network_icon::NetworkIconAnimation::GetInstance()->AddObserver(this);
   else
     network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this);
 
-  TrayNetworkStateModel* model =
-      Shell::Get()->system_tray_model()->network_state_model();
-  const NetworkStateProperties* network = model->default_network();
-
-  bool toggled = network || model->GetDeviceState(NetworkType::kWiFi) ==
-                                DeviceStateType::kEnabled;
-  SetToggled(toggled);
   icon_button()->SetImage(views::Button::STATE_NORMAL, image);
   icon_button()->SetImage(views::Button::STATE_DISABLED, image_disabled);
-  icon_button()->SetToggledImage(views::Button::STATE_NORMAL, &image_toggled);
-  icon_button()->SetToggledImage(views::Button::STATE_DISABLED,
-                                 &image_disabled);
+  SetToggled(toggled);
 
   base::string16 network_name;
   if (network) {
diff --git a/ash/system/network/network_icon.cc b/ash/system/network/network_icon.cc
index bfd9dc2..9301f6a4 100644
--- a/ash/system/network/network_icon.cc
+++ b/ash/system/network/network_icon.cc
@@ -438,10 +438,10 @@
       return kIconColorInOobe;
     case ICON_TYPE_FEATURE_POD:
       return AshColorProvider::Get()->GetContentLayerColor(
-          AshColorProvider::ContentLayerType::kSystemMenuIconColor);
+          AshColorProvider::ContentLayerType::kButtonIconColor);
     case ICON_TYPE_FEATURE_POD_TOGGLED:
       return AshColorProvider::Get()->GetContentLayerColor(
-          AshColorProvider::ContentLayerType::kSystemMenuIconColorToggled);
+          AshColorProvider::ContentLayerType::kButtonIconColorPrimary);
     case ICON_TYPE_FEATURE_POD_DISABLED:
       return AshColorProvider::GetDisabledColor(
           GetDefaultColorForIconType(ICON_TYPE_FEATURE_POD));
diff --git a/ash/system/palette/common_palette_tool.cc b/ash/system/palette/common_palette_tool.cc
index 0999c19..bd55c72 100644
--- a/ash/system/palette/common_palette_tool.cc
+++ b/ash/system/palette/common_palette_tool.cc
@@ -75,7 +75,7 @@
 
 views::View* CommonPaletteTool::CreateDefaultView(const base::string16& name) {
   SkColor icon_color = AshColorProvider::Get()->GetContentLayerColor(
-      AshColorProvider::ContentLayerType::kSystemMenuIconColor);
+      AshColorProvider::ContentLayerType::kButtonIconColor);
   gfx::ImageSkia icon =
       CreateVectorIcon(GetPaletteIcon(), kMenuIconSize, icon_color);
   highlight_view_ = new HoverHighlightView(this, true /* use_unified_theme */);
diff --git a/ash/system/unified/feature_pod_button.cc b/ash/system/unified/feature_pod_button.cc
index 97fe7a0..e0c09d2 100644
--- a/ash/system/unified/feature_pod_button.cc
+++ b/ash/system/unified/feature_pod_button.cc
@@ -52,7 +52,7 @@
 
 FeaturePodIconButton::FeaturePodIconButton(views::ButtonListener* listener,
                                            bool is_togglable)
-    : views::ToggleImageButton(listener), is_togglable_(is_togglable) {
+    : views::ImageButton(listener), is_togglable_(is_togglable) {
   SetPreferredSize(kUnifiedFeaturePodIconSize);
   SetBorder(views::CreateEmptyBorder(kUnifiedFeaturePodIconPadding));
   SetImageHorizontalAlignment(ALIGN_CENTER);
@@ -76,43 +76,12 @@
     return;
 
   toggled_ = toggled;
-  views::ToggleImageButton::SetToggled(toggled);
+  UpdateVectorIcon();
 }
 
 void FeaturePodIconButton::SetVectorIcon(const gfx::VectorIcon& icon) {
-  const SkColor icon_color = AshColorProvider::Get()->GetContentLayerColor(
-      ContentLayerType::kSystemMenuIconColor);
-  const SkColor toggled_color = AshColorProvider::Get()->GetContentLayerColor(
-      ContentLayerType::kSystemMenuIconColorToggled);
-
-  // Skip repainting if the incoming icon is the same as the current icon. If
-  // the icon has been painted before, |gfx::CreateVectorIcon()| will simply
-  // grab the ImageSkia from a cache, so it will be cheap. Note that this
-  // assumes that toggled/disabled images changes at the same time as the normal
-  // image, which it currently does.
-  const gfx::ImageSkia new_normal_image =
-      gfx::CreateVectorIcon(icon, kUnifiedFeaturePodVectorIconSize, icon_color);
-  const gfx::ImageSkia& old_normal_image =
-      GetImage(views::Button::STATE_NORMAL);
-  if (!new_normal_image.isNull() && !old_normal_image.isNull() &&
-      new_normal_image.BackedBySameObjectAs(old_normal_image)) {
-    return;
-  }
-
-  SetImage(views::Button::STATE_NORMAL, new_normal_image);
-
-  SetToggledImage(views::Button::STATE_NORMAL,
-                  new gfx::ImageSkia(gfx::CreateVectorIcon(
-                      icon, kUnifiedFeaturePodVectorIconSize, toggled_color)));
-
-  SetImage(
-      views::Button::STATE_DISABLED,
-      gfx::CreateVectorIcon(icon, kUnifiedFeaturePodVectorIconSize,
-                            AshColorProvider::GetDisabledColor(icon_color)));
-  SetToggledImage(views::Button::STATE_DISABLED,
-                  new gfx::ImageSkia(gfx::CreateVectorIcon(
-                      icon, kUnifiedFeaturePodVectorIconSize,
-                      AshColorProvider::GetDisabledColor(icon_color))));
+  icon_ = &icon;
+  UpdateVectorIcon();
 }
 
 void FeaturePodIconButton::PaintButtonContents(gfx::Canvas* canvas) {
@@ -171,6 +140,38 @@
   return "FeaturePodIconButton";
 }
 
+void FeaturePodIconButton::UpdateVectorIcon() {
+  if (!icon_)
+    return;
+
+  const auto* color_provider = AshColorProvider::Get();
+  const SkColor normal_color =
+      color_provider->GetContentLayerColor(ContentLayerType::kButtonIconColor);
+  const SkColor toggled_icon_color = color_provider->GetContentLayerColor(
+      ContentLayerType::kButtonIconColorPrimary);
+  const SkColor icon_color = toggled_ ? toggled_icon_color : normal_color;
+
+  // Skip repainting if the incoming icon is the same as the current icon. If
+  // the icon has been painted before, |gfx::CreateVectorIcon()| will simply
+  // grab the ImageSkia from a cache, so it will be cheap. Note that this
+  // assumes that toggled/disabled images changes at the same time as the normal
+  // image, which it currently does.
+  const gfx::ImageSkia new_normal_image = gfx::CreateVectorIcon(
+      *icon_, kUnifiedFeaturePodVectorIconSize, icon_color);
+  const gfx::ImageSkia& old_normal_image =
+      GetImage(views::Button::STATE_NORMAL);
+  if (!new_normal_image.isNull() && !old_normal_image.isNull() &&
+      new_normal_image.BackedBySameObjectAs(old_normal_image)) {
+    return;
+  }
+
+  SetImage(views::Button::STATE_NORMAL, new_normal_image);
+  SetImage(
+      views::Button::STATE_DISABLED,
+      gfx::CreateVectorIcon(*icon_, kUnifiedFeaturePodVectorIconSize,
+                            AshColorProvider::GetDisabledColor(normal_color)));
+}
+
 FeaturePodLabelButton::FeaturePodLabelButton(views::ButtonListener* listener)
     : Button(listener),
       label_(new views::Label),
diff --git a/ash/system/unified/feature_pod_button.h b/ash/system/unified/feature_pod_button.h
index 07bf008e..4fe7d02 100644
--- a/ash/system/unified/feature_pod_button.h
+++ b/ash/system/unified/feature_pod_button.h
@@ -21,7 +21,7 @@
 class FeaturePodControllerBase;
 
 // A toggle button with an icon used by feature pods and in other places.
-class FeaturePodIconButton : public views::ToggleImageButton {
+class FeaturePodIconButton : public views::ImageButton {
  public:
   FeaturePodIconButton(views::ButtonListener* listener, bool is_togglable);
   ~FeaturePodIconButton() override;
@@ -44,12 +44,18 @@
   bool toggled() const { return toggled_; }
 
  private:
+  // Updates vector icon. Called by SetToggled to update the icon's color on
+  // toggle state.
+  void UpdateVectorIcon();
+
   // True if this button is a togglable.
   const bool is_togglable_;
 
   // True if the button is currently toggled.
   bool toggled_ = false;
 
+  const gfx::VectorIcon* icon_ = nullptr;
+
   DISALLOW_COPY_AND_ASSIGN(FeaturePodIconButton);
 };
 
diff --git a/ash/system/unified/unified_slider_view.cc b/ash/system/unified/unified_slider_view.cc
index ad67bfa..54b7a7c 100644
--- a/ash/system/unified/unified_slider_view.cc
+++ b/ash/system/unified/unified_slider_view.cc
@@ -106,9 +106,9 @@
 
 void UnifiedSliderButton::SetVectorIcon(const gfx::VectorIcon& icon) {
   const SkColor toggled_color = AshColorProvider::Get()->GetContentLayerColor(
-      ContentLayerType::kSystemMenuIconColorToggled);
+      ContentLayerType::kButtonIconColorPrimary);
   const SkColor icon_color = AshColorProvider::Get()->GetContentLayerColor(
-      ContentLayerType::kSystemMenuIconColor);
+      ContentLayerType::kButtonIconColor);
 
   SetImage(views::Button::STATE_NORMAL,
            gfx::CreateVectorIcon(icon, icon_color));
diff --git a/ash/wm/desks/root_window_desk_switch_animator.cc b/ash/wm/desks/root_window_desk_switch_animator.cc
index 47a80c0..39b858b5 100644
--- a/ash/wm/desks/root_window_desk_switch_animator.cc
+++ b/ash/wm/desks/root_window_desk_switch_animator.cc
@@ -43,10 +43,6 @@
 constexpr base::TimeDelta kAnimationDuration =
     base::TimeDelta::FromMilliseconds(300);
 
-// In touchpad units, a touchpad swipe of this length will correspond to a full
-// desk change.
-constexpr int kTouchpadSwipeLengthForDeskChange = 100;
-
 // The amount, by which the detached old layers of the removed desk's windows,
 // is translated vertically during the for-remove desk switch animation.
 constexpr int kRemovedDeskWindowYTranslation = 20;
@@ -115,7 +111,8 @@
 // units. Convert these units so that what is considered a full touchpad swipe
 // shifts the animation layer one entire desk length.
 float TouchpadToXTranslation(float touchpad_x, int desk_length) {
-  return desk_length * touchpad_x / kTouchpadSwipeLengthForDeskChange;
+  return desk_length * touchpad_x /
+         RootWindowDeskSwitchAnimator::kTouchpadSwipeLengthForDeskChange;
 }
 
 }  // namespace
@@ -274,21 +271,23 @@
   // the delegate to request a new screenshot if the animating layer is about to
   // slide past the bounds which are visible to the user (root window bounds).
   //
-  //            <--- moving left
-  //   +------------------------------+
-  //   |               +-----------+  |
-  //   |      b        |     a     |  |
-  //   |               +___________+  |
-  //   +______________________________+
+  //              moving right ---->
+  //   +---+------------------------------+---+
+  //   |   |               +-----------+  |   |
+  //   | c |      b        |     a     |  | c |
+  //   |   |               +___________+  |   |
+  //   +___+______________________________+___+
   //
   //  a - root window/visible bounds - (0,0-1000x500)
-  //  b - animating layer with two screenshots - (0,0-2050x500)
-  //    - with translation (-1000, 0)
-  //  We will notify the delegate to request a new screenshot once the right
-  //  of b is within |kMinDistanceBeforeScreenshotDp| of the right of a (i.e.
-  //  translation of (-1040, 0)).
+  //  b - animating layer with two screenshots and edge padding - (0,0-2350x500)
+  //    - current second screenshot is visible (translation (-1200, 0))
+  //  c - Edge padding, equal to |kEdgePaddingRatio| x 1000 - 150 dips wide
+  //  We will notify the delegate to request a new screenshot once the x of b is
+  //  within |kMinDistanceBeforeScreenshotDp| of the x of a, not including the
+  //  edge padding (i.e. translation of (-190, 0)).
   gfx::RectF transformed_animation_layer_bounds(animation_layer->bounds());
   transform.TransformRect(&transformed_animation_layer_bounds);
+  transformed_animation_layer_bounds.Inset(edge_padding_width_dp_, 0);
 
   const bool moving_left = scroll_delta_x < 0.f;
   const bool going_out_of_bounds =
diff --git a/ash/wm/desks/root_window_desk_switch_animator.h b/ash/wm/desks/root_window_desk_switch_animator.h
index ab42a90..009016f 100644
--- a/ash/wm/desks/root_window_desk_switch_animator.h
+++ b/ash/wm/desks/root_window_desk_switch_animator.h
@@ -190,6 +190,10 @@
   // continuously.
   static constexpr float kEdgePaddingRatio = 0.15f;
 
+  // In touchpad units, a touchpad swipe of this length will correspond to a
+  // full desk change.
+  static constexpr int kTouchpadSwipeLengthForDeskChange = 100;
+
   RootWindowDeskSwitchAnimator(aura::Window* root,
                                int starting_desk_index,
                                int ending_desk_index,
diff --git a/ash/wm/desks/root_window_desk_switch_animator_unittest.cc b/ash/wm/desks/root_window_desk_switch_animator_unittest.cc
index f4c91105..1662993 100644
--- a/ash/wm/desks/root_window_desk_switch_animator_unittest.cc
+++ b/ash/wm/desks/root_window_desk_switch_animator_unittest.cc
@@ -9,6 +9,8 @@
 #include "ash/public/cpp/ash_features.h"
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
+#include "ash/wm/desks/desks_controller.h"
+#include "ash/wm/desks/desks_histogram_enums.h"
 #include "ash/wm/desks/root_window_desk_switch_animator_test_api.h"
 #include "base/callback_forward.h"
 #include "base/test/scoped_feature_list.h"
@@ -341,4 +343,107 @@
                              animation_layer));
 }
 
+// Tests that the update swipe animation api requests a new screenshot when
+// needed.
+TEST_F(RootWindowDeskSwitchAnimatorTest, UpdateSwipeAnimationNewScreenshot) {
+  // Add two more desks as we need three desks for this test.
+  DesksController::Get()->NewDesk(DesksCreationRemovalSource::kButton);
+  DesksController::Get()->NewDesk(DesksCreationRemovalSource::kButton);
+
+  InitAnimator(0, 1);
+  TakeStartingDeskScreenshotAndWait();
+  TakeEndingDeskScreenshotAndWait();
+
+  const int touchpad_swipe_length_for_desk_change =
+      RootWindowDeskSwitchAnimator::kTouchpadSwipeLengthForDeskChange;
+  // Swipe so that half of desk indexed 0 and half of desk indexed 1 is shown.
+  // Verify that a new screenshot is not needed, as the screenshots of both desk
+  // 0 and desk 1 were taken when initializing.
+  EXPECT_FALSE(animator()->UpdateSwipeAnimation(
+      -touchpad_swipe_length_for_desk_change / 2));
+
+  // Swipe so that desk indexed 1 is fully shown. Verify that a new screenshot
+  // is needed as we expect the user to continue swiping to show desk indexed 2.
+  EXPECT_TRUE(animator()->UpdateSwipeAnimation(
+      -touchpad_swipe_length_for_desk_change / 2));
+}
+
+// Tests that additional swipes do not shift the animation layer if it has
+// reached its limit.
+TEST_F(RootWindowDeskSwitchAnimatorTest, UpdateSwipeAnimationLimit) {
+  // Add one more desk as we need two desks for this test.
+  DesksController::Get()->NewDesk(DesksCreationRemovalSource::kButton);
+
+  InitAnimator(0, 1);
+  TakeStartingDeskScreenshotAndWait();
+  TakeEndingDeskScreenshotAndWait();
+
+  // Do a large right swipe; this will ensure we reach the limit on the left of
+  // the animation layer.
+  const int touchpad_swipe_length_for_desk_change =
+      RootWindowDeskSwitchAnimator::kTouchpadSwipeLengthForDeskChange;
+  animator()->UpdateSwipeAnimation(5 * touchpad_swipe_length_for_desk_change);
+  auto* animation_layer = test_api()->GetAnimationLayer();
+  int x_translation = animation_layer->transform().To2dTranslation().x();
+
+  // Test that an additional small right swipe will not shift the animation
+  // layer.
+  animator()->UpdateSwipeAnimation(5);
+  EXPECT_EQ(x_translation, animation_layer->transform().To2dTranslation().x());
+
+  // Swipe back to desk indexed 1.
+  animator()->UpdateSwipeAnimation(-2 * touchpad_swipe_length_for_desk_change);
+
+  // Do a large right swipe; this will ensure we reach the limit on the left of
+  // the animation layer.
+  animator()->UpdateSwipeAnimation(-5 * touchpad_swipe_length_for_desk_change);
+  x_translation = animation_layer->transform().To2dTranslation().x();
+
+  // Test that an additional small left swipe will not shift the animation
+  // layer.
+  animator()->UpdateSwipeAnimation(-5);
+  EXPECT_EQ(x_translation, animation_layer->transform().To2dTranslation().x());
+}
+
+// Tests the when ending the swipe animation, we animate to the expected desk.
+TEST_F(RootWindowDeskSwitchAnimatorTest, EndSwipeAnimation) {
+  // Add one more desk as we need two desks for this test.
+  DesksController::Get()->NewDesk(DesksCreationRemovalSource::kButton);
+
+  InitAnimator(0, 1);
+  TakeStartingDeskScreenshotAndWait();
+  TakeEndingDeskScreenshotAndWait();
+  auto* animation_layer = test_api()->GetAnimationLayer();
+
+  const int touchpad_swipe_length_for_desk_change =
+      RootWindowDeskSwitchAnimator::kTouchpadSwipeLengthForDeskChange;
+  // Make a small left swipe headed towards desk indexed 1. Desk indexed 0
+  // should still be the most visible desk, so on ending the swipe animation,
+  // desk indexed 0 is the target desk.
+  animator()->UpdateSwipeAnimation(-touchpad_swipe_length_for_desk_change / 10);
+  animator()->EndSwipeAnimation();
+  EXPECT_EQ(
+      Shell::GetPrimaryRootWindow()->bounds(),
+      GetTargetVisibleBounds(test_api()->GetScreenshotLayerOfDeskWithIndex(0),
+                             animation_layer));
+
+  // Reinitialize the animator as each animator only supports one
+  // EndSwipeAnimation during its lifetime.
+  InitAnimator(0, 1);
+  TakeStartingDeskScreenshotAndWait();
+  TakeEndingDeskScreenshotAndWait();
+  animation_layer = test_api()->GetAnimationLayer();
+
+  // Make a big left swipe headed towards desk indexed 1. Desk indexed 1 should
+  // be the new most visible desk, so on ending the swipe animation, desk
+  // indexed 1 is the target desk.
+  animator()->UpdateSwipeAnimation(-9 * touchpad_swipe_length_for_desk_change /
+                                   10);
+  animator()->EndSwipeAnimation();
+  EXPECT_EQ(
+      Shell::GetPrimaryRootWindow()->bounds(),
+      GetTargetVisibleBounds(test_api()->GetScreenshotLayerOfDeskWithIndex(1),
+                             animation_layer));
+}
+
 }  // namespace ash
diff --git a/ash/wm/gestures/wm_gesture_handler.cc b/ash/wm/gestures/wm_gesture_handler.cc
index 12013e0..c5ba5fa 100644
--- a/ash/wm/gestures/wm_gesture_handler.cc
+++ b/ash/wm/gestures/wm_gesture_handler.cc
@@ -251,7 +251,6 @@
 bool WmGestureHandler::ProcessEventImpl(int finger_count,
                                         float delta_x,
                                         float delta_y) {
-  LOG(ERROR) << "Scroll: " << delta_x;
   if (!scroll_data_)
     return false;
 
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 1d449b3..e82420f 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -3271,7 +3271,7 @@
     }
   }
 
-  if (is_desktop_linux) {
+  if (is_linux) {
     sources += [
       "linux_util_unittest.cc",
       "nix/xdg_util_unittest.cc",
diff --git a/build/config/BUILDCONFIG.gn b/build/config/BUILDCONFIG.gn
index 3082046..2959d53 100644
--- a/build/config/BUILDCONFIG.gn
+++ b/build/config/BUILDCONFIG.gn
@@ -131,6 +131,7 @@
   is_official_build = false
 
   # Whether we're a traditional desktop unix.
+  # Deprecated: Please use `is_linux` instead. See https://crbug.com/1132395
   is_desktop_linux = current_os == "linux"
 
   # Set to true when compiling with the Clang compiler.
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index 4f6461b..a5d9efb9 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -1741,7 +1741,7 @@
 # (de)allocate memory on a different heap, which would spell trouble if pointers
 # to heap-allocated memory are passed over shared library boundaries.
 config("export_dynamic") {
-  if (is_desktop_linux || export_libcxxabi_from_executables) {
+  if (is_linux || export_libcxxabi_from_executables) {
     ldflags = [ "-rdynamic" ]
   }
 }
diff --git a/build/config/compiler/pgo/pgo.gni b/build/config/compiler/pgo/pgo.gni
index c8c053c..15d2f29 100644
--- a/build/config/compiler/pgo/pgo.gni
+++ b/build/config/compiler/pgo/pgo.gni
@@ -12,7 +12,7 @@
   #     2 : Used during the PGO (optimization) phase.
   chrome_pgo_phase = 0
   if (is_official_build &&
-      (is_win || is_mac || (is_desktop_linux && !chromeos_is_browser_only))) {
+      (is_win || is_mac || (is_linux && !chromeos_is_browser_only))) {
     chrome_pgo_phase = 2
   }
 
diff --git a/build/config/linux/gtk/gtk.gni b/build/config/linux/gtk/gtk.gni
index 0a3f35d..6e7d7327 100644
--- a/build/config/linux/gtk/gtk.gni
+++ b/build/config/linux/gtk/gtk.gni
@@ -6,7 +6,7 @@
 
 declare_args() {
   # Whether or not we should use libgtk.
-  use_gtk = is_desktop_linux && !is_chromecast
+  use_gtk = is_linux && !is_chromecast
 
   # The (major) version of GTK to build against.
   gtk_version = 3
diff --git a/build/config/linux/libdrm/BUILD.gn b/build/config/linux/libdrm/BUILD.gn
index 3029416..e9b40184 100644
--- a/build/config/linux/libdrm/BUILD.gn
+++ b/build/config/linux/libdrm/BUILD.gn
@@ -11,7 +11,7 @@
   # Controls whether the build should use the version of libdrm library shipped
   # with the system. In release builds of desktop Linux and Chrome OS we use the
   # system version. Some Chromecast devices use this as well.
-  use_system_libdrm = is_chromeos_device || (is_desktop_linux && !is_chromecast)
+  use_system_libdrm = is_chromeos_device || (is_linux && !is_chromecast)
 }
 
 if (use_system_libdrm) {
diff --git a/build/config/ui.gni b/build/config/ui.gni
index 90a237d..2d281ef 100644
--- a/build/config/ui.gni
+++ b/build/config/ui.gni
@@ -42,14 +42,14 @@
   toolkit_views = is_mac || is_win || is_linux || is_chromeos || is_fuchsia
 
   # Whether we should use glib, a low level C utility library.
-  use_glib = is_desktop_linux && !is_chromecast
+  use_glib = is_linux && !is_chromecast
 }
 
 # Make sure glib is not used if building for ChromeOS/Chromecast
 assert(!use_glib || (is_linux && !is_chromeos && !is_chromecast))
 
 # Whether to use atk, the Accessibility ToolKit library
-use_atk = is_desktop_linux && !is_chromecast && use_glib
+use_atk = is_linux && !is_chromecast && use_glib
 
 # Whether using Xvfb to provide a display server for a test might be
 # necessary.
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 6522dcb..3e92f4f 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-0.20200928.3.1
+0.20201001.2.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 6522dcb..3e92f4f 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-0.20200928.3.1
+0.20201001.2.1
diff --git a/build/util/generate_wrapper.gni b/build/util/generate_wrapper.gni
index 32e6d5b..157c09c 100644
--- a/build/util/generate_wrapper.gni
+++ b/build/util/generate_wrapper.gni
@@ -92,5 +92,9 @@
       "--",
     ]
     args += _wrapped_arguments
+
+    if (defined(invoker.write_runtime_deps)) {
+      write_runtime_deps = invoker.write_runtime_deps
+    }
   }
 }
diff --git a/cc/layers/picture_layer_impl.cc b/cc/layers/picture_layer_impl.cc
index bd6923c..928508b1 100644
--- a/cc/layers/picture_layer_impl.cc
+++ b/cc/layers/picture_layer_impl.cc
@@ -60,6 +60,10 @@
 // scales.
 const float kMaxIdealContentsScale = 10000.f;
 
+// We try to avoid raster scale adjustment for will-change:transform for
+// performance, unless the scale is too small compared to the ideal scale.
+const float kMinScaleRatioForWillChangeTransform = 0.1f;
+
 // Intersect rects which may have right() and bottom() that overflow integer
 // boundaries. This code is similar to gfx::Rect::Intersect with the exception
 // that the types are promoted to int64_t when there is a chance of overflow.
@@ -1343,9 +1347,10 @@
   }
 
   // Don't update will-change: transform layers if the raster contents scale is
-  // at least the native scale (otherwise, we'd need to clamp it).
+  // bigger than the minimum scale.
   if (HasWillChangeTransformHint() &&
-      raster_contents_scale_ >= raster_page_scale_ * raster_device_scale_) {
+      raster_contents_scale_ >=
+          MinimumRasterContentsScaleForWillChangeTransform()) {
     return false;
   }
 
@@ -1504,13 +1509,10 @@
     }
   }
 
-  // Clamp will-change: transform layers to be at least the native scale.
   if (HasWillChangeTransformHint()) {
-    float min_desired_scale = raster_device_scale_ * raster_page_scale_;
-    if (raster_contents_scale_ < min_desired_scale) {
-      raster_contents_scale_ = min_desired_scale;
-      raster_page_scale_ = 1.f;
-    }
+    raster_contents_scale_ =
+        std::max(raster_contents_scale_,
+                 MinimumRasterContentsScaleForWillChangeTransform());
   }
 
   raster_contents_scale_ =
@@ -1570,6 +1572,23 @@
   SanityCheckTilingState();
 }
 
+float PictureLayerImpl::MinimumRasterContentsScaleForWillChangeTransform()
+    const {
+  DCHECK(HasWillChangeTransformHint());
+  // Don't let the scale too small compared to the ideal scale.
+  float min_scale =
+      ideal_contents_scale_ * kMinScaleRatioForWillChangeTransform;
+  float native_scale = ideal_device_scale_ * ideal_page_scale_;
+  // Clamp will-change: transform layers to be at least the native scale,
+  // unless the scale is too small to avoid too many tiles using too much tile
+  // memory.
+  if (ideal_contents_scale_ <
+      native_scale * kMinScaleRatioForWillChangeTransform) {
+    return min_scale;
+  }
+  return std::max(native_scale, min_scale);
+}
+
 bool PictureLayerImpl::CalculateRasterTranslation(
     gfx::Vector2dF& raster_translation) const {
   // If this setting is set, the client (e.g. the Chromium UI) is sure that it
@@ -1631,24 +1650,13 @@
 }
 
 float PictureLayerImpl::MinimumContentsScale() const {
-  float setting_min = layer_tree_impl()->settings().minimum_contents_scale;
-
   // If the contents scale is less than 1 / width (also for height),
   // then it will end up having less than one pixel of content in that
   // dimension.  Bump the minimum contents scale up in this case to prevent
   // this from happening.
   int min_dimension = std::min(raster_source_->GetSize().width(),
                                raster_source_->GetSize().height());
-  if (!min_dimension)
-    return setting_min;
-
-  // Directly composited images may result in contents scales that are
-  // less than the configured setting. We allow this lower scale so that we
-  // can raster at the intrinsic image size.
-  const float inverse_min_dimension = 1.f / min_dimension;
-  return (directly_composited_image_size_.has_value())
-             ? inverse_min_dimension
-             : std::max(inverse_min_dimension, setting_min);
+  return min_dimension ? 1.f / min_dimension : 1.f;
 }
 
 float PictureLayerImpl::MaximumContentsScale() const {
diff --git a/cc/layers/picture_layer_impl.h b/cc/layers/picture_layer_impl.h
index ea8bcab9..be63a23 100644
--- a/cc/layers/picture_layer_impl.h
+++ b/cc/layers/picture_layer_impl.h
@@ -177,6 +177,7 @@
   void AddLowResolutionTilingIfNeeded();
   bool ShouldAdjustRasterScale() const;
   void RecalculateRasterScales();
+  float MinimumRasterContentsScaleForWillChangeTransform() const;
   // Returns false if raster translation is not applicable.
   bool CalculateRasterTranslation(gfx::Vector2dF& raster_translation) const;
   void CleanUpTilingsOnActiveLayer(
diff --git a/cc/layers/picture_layer_impl_unittest.cc b/cc/layers/picture_layer_impl_unittest.cc
index b41fb25a..627e929 100644
--- a/cc/layers/picture_layer_impl_unittest.cc
+++ b/cc/layers/picture_layer_impl_unittest.cc
@@ -846,7 +846,7 @@
   EXPECT_FLOAT_EQ(
       0.24f, active_layer()->tilings()->tiling_at(0)->contents_scale_key());
   EXPECT_FLOAT_EQ(
-      0.0625f, active_layer()->tilings()->tiling_at(1)->contents_scale_key());
+      0.06f, active_layer()->tilings()->tiling_at(1)->contents_scale_key());
 
   // Ensure UpdateTiles won't remove any tilings.
   active_layer()->MarkAllTilingsUsed();
@@ -868,7 +868,7 @@
   EXPECT_FLOAT_EQ(
       0.12f, active_layer()->tilings()->tiling_at(1)->contents_scale_key());
   EXPECT_FLOAT_EQ(
-      0.0625, active_layer()->tilings()->tiling_at(2)->contents_scale_key());
+      0.06f, active_layer()->tilings()->tiling_at(2)->contents_scale_key());
 
   // Ensure UpdateTiles won't remove any tilings.
   active_layer()->MarkAllTilingsUsed();
@@ -878,10 +878,12 @@
   SetContentsScaleOnBothLayers(0.1f, 1.0f, 0.1f, 1.0f, 0.f, false);
   ASSERT_EQ(3u, active_layer()->tilings()->num_tilings());
 
-  // Zoom in. 0.25(desired_scale) should be snapped to 0.24 during zoom-in
-  // because 0.25(desired_scale) is within the ratio(1.2).
-  SetContentsScaleOnBothLayers(0.25f, 1.0f, 0.25f, 1.0f, 0.f, false);
+  // Zoom in. 0.22(desired_scale) should be snapped to 0.24 during zoom-in
+  // because 0.22(desired_scale) is within the ratio(1.2).
+  SetContentsScaleOnBothLayers(0.22f, 1.0f, 0.22f, 1.0f, 0.f, false);
   ASSERT_EQ(3u, active_layer()->tilings()->num_tilings());
+  EXPECT_FLOAT_EQ(
+      0.24f, active_layer()->tilings()->tiling_at(0)->contents_scale_key());
 
   // Zoom in a lot. Since we move in factors of two, we should get a scale that
   // is a power of 2 times 0.24.
@@ -1320,8 +1322,7 @@
 
   // Resize even larger, so that the scale would be smaller than the minimum
   // contents scale. Then the layer should no longer have any tiling.
-  float min_contents_scale = host_impl()->settings().minimum_contents_scale;
-  gfx::Size extra_huge_bounds(max_texture_size / min_contents_scale + 1, 10);
+  gfx::Size extra_huge_bounds(max_texture_size * 10 + 1, 10);
   scoped_refptr<FakeRasterSource> extra_huge_raster_source =
       FakeRasterSource::CreateFilled(extra_huge_bounds);
 
@@ -3526,6 +3527,22 @@
                                starting_animation_scale, animating_transform);
   EXPECT_BOTH_EQ(HighResTiling()->contents_scale_key(), 1.5f);
 
+  // ... unless the difference is very big.
+  contents_scale = 20.f;
+
+  SetContentsScaleOnBothLayers(contents_scale, device_scale, page_scale,
+                               maximum_animation_scale,
+                               starting_animation_scale, animating_transform);
+  EXPECT_BOTH_EQ(HighResTiling()->contents_scale_key(), 20.f);
+
+  // And we don't downscale from a higher scale.
+  contents_scale = 2.f;
+
+  SetContentsScaleOnBothLayers(contents_scale, device_scale, page_scale,
+                               maximum_animation_scale,
+                               starting_animation_scale, animating_transform);
+  EXPECT_BOTH_EQ(HighResTiling()->contents_scale_key(), 20.f);
+
   // Disabling the will-change hint will once again make the raster scale update
   // with the ideal scale.
   GetTransformNode(active_layer())->will_change_transform = false;
@@ -3539,6 +3556,62 @@
   EXPECT_BOTH_EQ(HighResTiling()->contents_scale_key(), 3.f);
 }
 
+TEST_F(LegacySWPictureLayerImplTest, TinyRasterScale) {
+  gfx::Size tile_size(host_impl()->settings().default_tile_size);
+  SetupDefaultTrees(tile_size);
+
+  ResetTilingsAndRasterScales();
+
+  float contents_scale = 0.01f;
+  float device_scale = 1.5f;
+  float page_scale = 1.f;
+  float maximum_animation_scale = 1.f;
+  float starting_animation_scale = 0.f;
+  bool animating_transform = false;
+
+  SetContentsScaleOnBothLayers(contents_scale, device_scale, page_scale,
+                               maximum_animation_scale,
+                               starting_animation_scale, animating_transform);
+  EXPECT_BOTH_EQ(HighResTiling()->contents_scale_key(), 0.01f);
+
+  // If we change the layer contents scale after setting will change
+  // will, then it will be updated if it's below the minimum scale (page scale *
+  // device scale).
+  GetTransformNode(active_layer())->will_change_transform = true;
+  GetTransformNode(pending_layer())->will_change_transform = true;
+
+  SetContentsScaleOnBothLayers(contents_scale, device_scale, page_scale,
+                               maximum_animation_scale,
+                               starting_animation_scale, animating_transform);
+  // The scale is clamped to the native scale.
+  EXPECT_BOTH_EQ(HighResTiling()->contents_scale_key(), 0.01f);
+
+  // Further changes to the source scale will no longer be reflected in the
+  // contents scale.
+  contents_scale = 0.02f;
+
+  SetContentsScaleOnBothLayers(contents_scale, device_scale, page_scale,
+                               maximum_animation_scale,
+                               starting_animation_scale, animating_transform);
+  EXPECT_BOTH_EQ(HighResTiling()->contents_scale_key(), 0.01f);
+
+  // ... unless the difference is very big.
+  contents_scale = 0.12f;
+
+  SetContentsScaleOnBothLayers(contents_scale, device_scale, page_scale,
+                               maximum_animation_scale,
+                               starting_animation_scale, animating_transform);
+  EXPECT_BOTH_EQ(HighResTiling()->contents_scale_key(), 0.12f);
+
+  // Bigger scale will be clamped to the native scale.
+  contents_scale = 0.5f;
+
+  SetContentsScaleOnBothLayers(contents_scale, device_scale, page_scale,
+                               maximum_animation_scale,
+                               starting_animation_scale, animating_transform);
+  EXPECT_BOTH_EQ(HighResTiling()->contents_scale_key(), 1.5f);
+}
+
 TEST_F(LegacySWPictureLayerImplTest,
        AnimationChangeRespectsWillChangeTransformHint) {}
 
diff --git a/cc/trees/layer_tree_host_unittest.cc b/cc/trees/layer_tree_host_unittest.cc
index ddb268c..e09aea2 100644
--- a/cc/trees/layer_tree_host_unittest.cc
+++ b/cc/trees/layer_tree_host_unittest.cc
@@ -2897,13 +2897,6 @@
 // its damage is preserved until the next time it is drawn.
 class LayerTreeHostTestUndrawnLayersDamageLater : public LayerTreeHostTest {
  public:
-  void InitializeSettings(LayerTreeSettings* settings) override {
-    // If we don't set the minimum contents scale, it's harder to verify whether
-    // the damage we get is correct. For other scale amounts, please see
-    // LayerTreeHostTestDamageWithScale.
-    settings->minimum_contents_scale = 1.f;
-  }
-
   void SetupTree() override {
     root_layer_ = FakePictureLayer::Create(&client_);
     root_layer_->SetIsDrawable(true);
diff --git a/cc/trees/layer_tree_settings.h b/cc/trees/layer_tree_settings.h
index e5a5865..64fe3c1 100644
--- a/cc/trees/layer_tree_settings.h
+++ b/cc/trees/layer_tree_settings.h
@@ -64,7 +64,6 @@
   base::TimeDelta scroll_animation_duration_for_testing;
   bool timeout_and_draw_when_animation_checkerboards = true;
   bool layers_always_allowed_lcd_text = false;
-  float minimum_contents_scale = 0.0625f;
   float low_res_contents_scale_factor = 0.25f;
   float top_controls_show_threshold = 0.5f;
   float top_controls_hide_threshold = 0.5f;
diff --git a/chrome/BUILD.gn b/chrome/BUILD.gn
index bfaf61fe..f138a9e 100644
--- a/chrome/BUILD.gn
+++ b/chrome/BUILD.gn
@@ -263,7 +263,7 @@
         ldflags += [ "-Wl,--long-plt" ]
       }
 
-      if (is_desktop_linux && !is_component_build && !using_sanitizer) {
+      if (is_linux && !is_component_build && !using_sanitizer) {
         version_script = "//build/linux/chrome.map"
         inputs = [ version_script ]
         ldflags += [ "-Wl,--version-script=" +
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 003b192..606f75c5 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -5,7 +5,6 @@
 import("//build/android/resource_sizes.gni")
 import("//build/config/android/config.gni")
 import("//build/config/android/rules.gni")
-import("//build/config/chrome_build.gni")  # For branding_file_path.
 import("//build/config/python.gni")
 import("//build/util/process_version.gni")
 import("//build/util/version.gni")
@@ -77,7 +76,6 @@
     "$target_gen_dir/trichrome_library_32_apk/AndroidManifest.xml"
 
 app_hooks_impl = "java/src/org/chromium/chrome/browser/AppHooksImpl.java"
-_chrome_version_java_file = "$target_gen_dir/templates/org/chromium/chrome/browser/ChromeVersionConstants.java"
 
 # Exclude it from JNI registration if VR is not enabled.
 chrome_jni_sources_exclusions = []
@@ -355,6 +353,7 @@
     "//chrome/browser/ui/messages/android:java",
     "//chrome/browser/user_education:java",
     "//chrome/browser/util:java",
+    "//chrome/browser/version:java",
     "//chrome/browser/video_tutorials:factory_java",
     "//chrome/browser/video_tutorials:java",
     "//chrome/browser/webauthn/android:java",
@@ -921,13 +920,6 @@
   package_name = chrome_public_manifest_package
 }
 
-process_version("chrome_version_constants") {
-  process_only = true
-  template_file = "java/ChromeVersionConstants.java.version"
-  sources = [ branding_file_path ]
-  output = _chrome_version_java_file
-}
-
 # Files used for both chrome tests and VR/AR and autofill_assistant tests
 android_library("chrome_test_util_java") {
   testonly = true
@@ -2053,6 +2045,7 @@
     ":base_module_java",
     "//android_webview:android_webview_java",
     "//base:base_java",
+    "//chrome/browser/version:java",
     "//components/version_info/android:version_constants_java",
   ]
 }
@@ -2063,15 +2056,12 @@
   sources = [
     "java/src/org/chromium/chrome/browser/base/SplitChromeApplication.java",
     "java/src/org/chromium/chrome/browser/base/SplitCompatApplication.java",
-    _chrome_version_java_file,
   ]
   deps = [
     ":chrome_base_module_resources",
-    ":chrome_version_constants",
     "//base:base_java",
     "//components/embedder_support/android:application_java",
     "//components/module_installer/android:module_installer_java",
-    "//components/version_info/android:version_constants_java",
     "//third_party/android_deps:androidx_annotation_annotation_java",
     "//ui/android:ui_no_recycler_view_java",
 
diff --git a/chrome/android/DEPS b/chrome/android/DEPS
index 7d49efe..177aa58 100644
--- a/chrome/android/DEPS
+++ b/chrome/android/DEPS
@@ -24,6 +24,7 @@
   "+chrome/browser/ui/messages/android",
   "+chrome/browser/user_education",
   "+chrome/browser/util/android/java",
+  "+chrome/browser/version",
   "+chrome/browser/webauthn/android",
   "+components/browser_ui/android/bottomsheet",
   "+components/browser_ui/banners/android",
diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni
index f1d181af..6022e71 100644
--- a/chrome/android/chrome_java_resources.gni
+++ b/chrome/android/chrome_java_resources.gni
@@ -776,6 +776,7 @@
   "java/res/layout/accessibility_tab_switcher_list_item.xml",
   "java/res/layout/account_chooser_dialog_item.xml",
   "java/res/layout/account_chooser_dialog_title.xml",
+  "java/res/layout/account_divider_preference.xml",
   "java/res/layout/account_management_account_row.xml",
   "java/res/layout/account_picker_bottom_sheet_view.xml",
   "java/res/layout/account_picker_dialog_body.xml",
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 4751642..9caba09 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -1642,6 +1642,7 @@
   "java/src/org/chromium/chrome/browser/usage_stats/WebsiteEvent.java",
   "java/src/org/chromium/chrome/browser/video_tutorials/NewTabPageVideoIPHManager.java",
   "java/src/org/chromium/chrome/browser/video_tutorials/VideoPlayerActivity.java",
+  "java/src/org/chromium/chrome/browser/video_tutorials/VideoTutorialListActivity.java",
   "java/src/org/chromium/chrome/browser/vr/ArDelegate.java",
   "java/src/org/chromium/chrome/browser/vr/ArDelegateProvider.java",
   "java/src/org/chromium/chrome/browser/webapps/ActivateWebApkActivity.java",
diff --git a/chrome/android/expectations/monochrome_public_bundle.AndroidManifest.expected b/chrome/android/expectations/monochrome_public_bundle.AndroidManifest.expected
index 972ca62..6fcffa86 100644
--- a/chrome/android/expectations/monochrome_public_bundle.AndroidManifest.expected
+++ b/chrome/android/expectations/monochrome_public_bundle.AndroidManifest.expected
@@ -503,6 +503,11 @@
         android:name="org.chromium.chrome.browser.video_tutorials.VideoPlayerActivity"
         android:theme="@style/Theme.Chromium.Activity.Fullscreen">
     </activity>  # DIFF-ANCHOR: bf27023e
+    <activity  # DIFF-ANCHOR: f15792de
+        android:exported="false"
+        android:name="org.chromium.chrome.browser.video_tutorials.VideoTutorialListActivity"
+        android:theme="@style/Theme.Chromium.Activity.Fullscreen">
+    </activity>  # DIFF-ANCHOR: f15792de
     <activity  # DIFF-ANCHOR: b007dcaa
         android:enableVrMode="@string/gvr_vr_mode_component"
         android:excludeFromRecents="true"
diff --git a/chrome/android/expectations/trichrome_chrome_bundle.AndroidManifest.expected b/chrome/android/expectations/trichrome_chrome_bundle.AndroidManifest.expected
index b59d3338..1ee8405 100644
--- a/chrome/android/expectations/trichrome_chrome_bundle.AndroidManifest.expected
+++ b/chrome/android/expectations/trichrome_chrome_bundle.AndroidManifest.expected
@@ -476,6 +476,11 @@
         android:name="org.chromium.chrome.browser.video_tutorials.VideoPlayerActivity"
         android:theme="@style/Theme.Chromium.Activity.Fullscreen">
     </activity>  # DIFF-ANCHOR: bf27023e
+    <activity  # DIFF-ANCHOR: f15792de
+        android:exported="false"
+        android:name="org.chromium.chrome.browser.video_tutorials.VideoTutorialListActivity"
+        android:theme="@style/Theme.Chromium.Activity.Fullscreen">
+    </activity>  # DIFF-ANCHOR: f15792de
     <activity  # DIFF-ANCHOR: b007dcaa
         android:enableVrMode="@string/gvr_vr_mode_component"
         android:excludeFromRecents="true"
diff --git a/chrome/android/features/autofill_assistant/BUILD.gn b/chrome/android/features/autofill_assistant/BUILD.gn
index 0065c41..6972de7 100644
--- a/chrome/android/features/autofill_assistant/BUILD.gn
+++ b/chrome/android/features/autofill_assistant/BUILD.gn
@@ -44,6 +44,7 @@
     "//chrome/browser/ui/android/favicon:java",
     "//chrome/browser/ui/messages/android:java",
     "//chrome/browser/util:java",
+    "//chrome/browser/version:java",
     "//components/autofill/android:autofill_java",
     "//components/browser_ui/android/bottomsheet:java",
     "//components/browser_ui/modaldialog/android:java",
diff --git a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryIPHUtils.java b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryIPHUtils.java
index dfd27382..60348b9 100644
--- a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryIPHUtils.java
+++ b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryIPHUtils.java
@@ -16,7 +16,8 @@
 import org.chromium.components.feature_engagement.EventConstants;
 import org.chromium.components.feature_engagement.FeatureConstants;
 import org.chromium.components.feature_engagement.Tracker;
-import org.chromium.ui.widget.ChipView;
+import org.chromium.components.feature_engagement.TriggerState;
+import org.chromium.ui.widget.RectProvider;
 import org.chromium.ui.widget.ViewRectProvider;
 
 /**
@@ -30,12 +31,8 @@
      * @param feature The feature to emit a filling event for. Fails if no event to emit.
      */
     static void emitFillingEvent(String feature) {
-        // TODO(https://crbug.com/1048632): Use the current profile (i.e., regular profile or
-        // incognito profile) instead of always using regular profile. It works correctly now,
-        // but it is not safe.
-        final Tracker tracker =
-                TrackerFactory.getTrackerForProfile(Profile.getLastUsedRegularProfile());
-        if (!tracker.isInitialized()) return;
+        final Tracker tracker = getTrackerFromProfile();
+        if (tracker == null) return;
         switch (feature) {
             case FeatureConstants.KEYBOARD_ACCESSORY_ADDRESS_FILL_FEATURE:
                 tracker.notifyEvent(EventConstants.KEYBOARD_ACCESSORY_ADDRESS_AUTOFILLED);
@@ -47,7 +44,33 @@
                 tracker.notifyEvent(EventConstants.KEYBOARD_ACCESSORY_PAYMENT_AUTOFILLED);
                 return;
         }
-        assert false : "No event emitted for feature: " + feature;
+        assert false : "No filling event emitted for feature: " + feature;
+    }
+
+    /**
+     * Emits a scrolling event recording user's familiarity. Noop if no tracker is available yet.
+     */
+    static void emitScrollingEvent() {
+        final Tracker tracker = getTrackerFromProfile();
+        if (tracker != null) tracker.notifyEvent(EventConstants.KEYBOARD_ACCESSORY_BAR_SWIPED);
+    }
+
+    /**
+     * Used to check that filling IPH has priority over IPH that only supports filling, like the IPH
+     * promoting the swipeability of the suggestions.
+     * @return True iff any IPH prompting to use a chip was shown before.
+     */
+    static boolean hasShownAnyAutofillIphBefore() {
+        final Tracker tracker = getTrackerFromProfile();
+        if (tracker == null) return false;
+        return tracker.getTriggerState(FeatureConstants.KEYBOARD_ACCESSORY_ADDRESS_FILL_FEATURE)
+                == TriggerState.HAS_BEEN_DISPLAYED
+                || tracker.getTriggerState(
+                           FeatureConstants.KEYBOARD_ACCESSORY_PASSWORD_FILLING_FEATURE)
+                == TriggerState.HAS_BEEN_DISPLAYED
+                || tracker.getTriggerState(
+                           FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_FILLING_FEATURE)
+                == TriggerState.HAS_BEEN_DISPLAYED;
     }
 
     /**
@@ -59,27 +82,52 @@
      * @param view The {@link View} providing context and the Rect to which the bubble will point.
      * @param rootView The {@link View} used to determine the maximal dimensions for the bubble.
      */
-    static void showHelpBubble(String feature, ChipView view, View rootView) {
+    static void showHelpBubble(String feature, View view, View rootView) {
+        TextBubble helpBubble = createBubble(feature, new ViewRectProvider(view), rootView);
+        if (helpBubble == null) return;
+        // To emphasize which chip is pointed to, set selected to true for the built-in highlight.
+        // Prefer ViewHighlighter for views without a LayerDrawable background.
+        view.setSelected(true);
+        helpBubble.addOnDismissListener(() -> { view.setSelected(false); });
+        helpBubble.show();
+    }
+
+    /**
+     * Shows a help bubble pointing to the given rect. It contains an appropriate text for the given
+     * feature. The help bubble will not be shown if the {@link Tracker} doesn't allow it anymore.
+     * This may happen for example: if it was shown too often, too many IPH were triggered this
+     * session or other config restrictions apply.
+     * @param feature A String identifying the IPH feature and its appropriate help text.
+     * @param rectProvider The {@link RectProvider} providing bounds to which the bubble will point.
+     * @param rootView The {@link View} used to determine the maximal dimensions for the bubble.
+     */
+    static void showHelpBubble(String feature, RectProvider rectProvider, View rootView) {
+        TextBubble helpBubble = createBubble(feature, rectProvider, rootView);
+        if (helpBubble != null) helpBubble.show();
+    }
+
+    private static TextBubble createBubble(
+            String feature, RectProvider rectProvider, View rootView) {
+        final Tracker tracker = getTrackerFromProfile();
+        if (tracker == null) return null;
+        if (!tracker.shouldTriggerHelpUI(feature)) return null; // This call records the IPH intent.
+        @StringRes
+        int helpText = getHelpTextForFeature(feature);
+        TextBubble helpBubble = new TextBubble(rootView.getContext(), rootView, helpText, helpText,
+                rectProvider, ChromeAccessibilityUtil.get().isAccessibilityEnabled());
+        helpBubble.setDismissOnTouchInteraction(true);
+        helpBubble.addOnDismissListener(() -> { tracker.dismissed(feature); });
+        return helpBubble;
+    }
+
+    private static Tracker getTrackerFromProfile() {
         // TODO(https://crbug.com/1048632): Use the current profile (i.e., regular profile or
         // incognito profile) instead of always using regular profile. It works correctly now,
         // but it is not safe.
         final Tracker tracker =
                 TrackerFactory.getTrackerForProfile(Profile.getLastUsedRegularProfile());
-        if (!tracker.isInitialized()) return;
-        if (!tracker.shouldTriggerHelpUI(feature)) return; // This call records the IPH intent.
-        @StringRes
-        int helpText = getHelpTextForFeature(feature);
-        TextBubble helpBubble = new TextBubble(view.getContext(), rootView, helpText, helpText,
-                new ViewRectProvider(view), ChromeAccessibilityUtil.get().isAccessibilityEnabled());
-        helpBubble.setDismissOnTouchInteraction(true);
-        helpBubble.show();
-        // To emphasize which chip is pointed to, set selected to true for the built-in highlight.
-        // Prefer ViewHighlighter for views without a LayerDrawable background.
-        view.setSelected(true);
-        helpBubble.addOnDismissListener(() -> {
-            tracker.dismissed(feature);
-            view.setSelected(false);
-        });
+        if (!tracker.isInitialized()) return null;
+        return tracker;
     }
 
     /**
@@ -93,6 +141,8 @@
             case FeatureConstants.KEYBOARD_ACCESSORY_PASSWORD_FILLING_FEATURE:
             case FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_FILLING_FEATURE:
                 return R.string.iph_keyboard_accessory_fill_with_chrome;
+            case FeatureConstants.KEYBOARD_ACCESSORY_BAR_SWIPING_FEATURE:
+                return R.string.iph_keyboard_accessory_swipe_for_more;
         }
         assert false : "Unknown help text for feature: " + feature;
         return 0;
diff --git a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryMediator.java b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryMediator.java
index 5dc7f26..027021c2 100644
--- a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryMediator.java
+++ b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryMediator.java
@@ -8,8 +8,10 @@
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.BOTTOM_OFFSET_PX;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.DISABLE_ANIMATIONS_FOR_TESTING;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.KEYBOARD_TOGGLE_VISIBLE;
+import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.OBFUSCATED_CHILD_AT_CALLBACK;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.SHEET_TITLE;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.SHOW_KEYBOARD_CALLBACK;
+import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.SHOW_SWIPING_IPH;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.SKIP_CLOSING_ANIMATION;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.TAB_LAYOUT_ITEM;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.VISIBLE;
@@ -66,6 +68,7 @@
         mTabSwitcher = tabSwitcher;
 
         // Add mediator as observer so it can use model changes as signal for accessory visibility.
+        mModel.set(OBFUSCATED_CHILD_AT_CALLBACK, this::onSuggestionObfuscatedAt);
         mModel.set(SHOW_KEYBOARD_CALLBACK, this::closeSheet);
         mModel.set(TAB_LAYOUT_ITEM, new TabLayoutBarItem(tabLayoutCallbacks));
         if (ChromeFeatureList.isEnabled(ChromeFeatureList.AUTOFILL_KEYBOARD_ACCESSORY)) {
@@ -235,6 +238,7 @@
             PropertyObservable<PropertyKey> source, @Nullable PropertyKey propertyKey) {
         // Update the visibility only if we haven't set it just now.
         if (propertyKey == VISIBLE) {
+            mModel.set(SHOW_SWIPING_IPH, false); // Reset IPH if visibility changes.
             // When the accessory just (dis)appeared, there should be no active tab.
             mTabSwitcher.closeActiveTab();
             if (!mModel.get(VISIBLE)) {
@@ -251,7 +255,8 @@
         if (propertyKey == BOTTOM_OFFSET_PX || propertyKey == SHOW_KEYBOARD_CALLBACK
                 || propertyKey == TAB_LAYOUT_ITEM || propertyKey == SHEET_TITLE
                 || propertyKey == SKIP_CLOSING_ANIMATION
-                || propertyKey == DISABLE_ANIMATIONS_FOR_TESTING) {
+                || propertyKey == DISABLE_ANIMATIONS_FOR_TESTING
+                || propertyKey == OBFUSCATED_CHILD_AT_CALLBACK || propertyKey == SHOW_SWIPING_IPH) {
             return;
         }
         assert false : "Every property update needs to be handled explicitly!";
@@ -280,6 +285,11 @@
         mVisibilityDelegate.onCloseAccessorySheet();
     }
 
+    private void onSuggestionObfuscatedAt(Integer indexOfLast) {
+        // Show IPH if at least one entire item (suggestion or fallback) can be revealed by swiping.
+        mModel.set(SHOW_SWIPING_IPH, indexOfLast <= mModel.get(BAR_ITEMS).size() - 2);
+    }
+
     /**
      * @return True if neither suggestions nor tabs are available.
      */
diff --git a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryMetricsRecorder.java b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryMetricsRecorder.java
index cbeb9b9..8b1a63e 100644
--- a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryMetricsRecorder.java
+++ b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryMetricsRecorder.java
@@ -75,7 +75,9 @@
                     || propertyKey == KeyboardAccessoryProperties.SHEET_TITLE
                     || propertyKey == KeyboardAccessoryProperties.SHOW_KEYBOARD_CALLBACK
                     || propertyKey == KeyboardAccessoryProperties.SKIP_CLOSING_ANIMATION
-                    || propertyKey == KeyboardAccessoryProperties.DISABLE_ANIMATIONS_FOR_TESTING) {
+                    || propertyKey == KeyboardAccessoryProperties.DISABLE_ANIMATIONS_FOR_TESTING
+                    || propertyKey == KeyboardAccessoryProperties.SHOW_SWIPING_IPH
+                    || propertyKey == KeyboardAccessoryProperties.OBFUSCATED_CHILD_AT_CALLBACK) {
                 return;
             }
             assert false : "Every property update needs to be handled explicitly!";
diff --git a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryModernView.java b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryModernView.java
index fe9797c..9069952 100644
--- a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryModernView.java
+++ b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryModernView.java
@@ -13,12 +13,15 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Px;
 import androidx.appcompat.content.res.AppCompatResources;
 import androidx.core.view.ViewCompat;
 import androidx.recyclerview.widget.RecyclerView;
 
+import org.chromium.base.Callback;
 import org.chromium.chrome.browser.keyboard_accessory.R;
+import org.chromium.ui.widget.ViewRectProvider;
 
 /**
  * The Accessory sitting above the keyboard and below the content area. It is used for autofill
@@ -31,6 +34,20 @@
 
     private ImageView mKeyboardToggle;
     private TextView mSheetTitle;
+    private Callback<Integer> mObfuscatedLastChildAt;
+
+    // Records the first time a user scrolled to suppress an IPH explaining how scrolling works.
+    private final RecyclerView.OnScrollListener mScrollingIphCallback =
+            new RecyclerView.OnScrollListener() {
+                @Override
+                public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
+                    super.onScrollStateChanged(recyclerView, newState);
+                    if (newState != RecyclerView.SCROLL_STATE_IDLE) {
+                        mBarItemsView.removeOnScrollListener(mScrollingIphCallback);
+                        KeyboardAccessoryIPHUtils.emitScrollingEvent();
+                    }
+                }
+            };
 
     /**
      * This decoration ensures that the last item is right-aligned.
@@ -117,6 +134,7 @@
         int pad = getResources().getDimensionPixelSize(R.dimen.keyboard_accessory_bar_item_padding);
         // Ensure the last element (although scrollable) is always end-aligned.
         mBarItemsView.addItemDecoration(new StickyLastItemDecoration(pad));
+        mBarItemsView.addOnScrollListener(mScrollingIphCallback);
 
         // Remove any paddings that might be inherited since this messes up the fading edge.
         ViewCompat.setPaddingRelative(mBarItemsView, 0, 0, 0, 0);
@@ -139,6 +157,44 @@
         }
     }
 
+    @Override
+    protected void onItemsChanged() {
+        super.onItemsChanged();
+        if (isLastChildObfuscated()) {
+            mObfuscatedLastChildAt.onResult(mBarItemsView.indexOfChild(getLastChild()));
+        }
+    }
+
+    ViewRectProvider getSwipingIphRect() {
+        View lastChild = getLastChild();
+        if (lastChild == null) return null;
+        ViewRectProvider provider = new ViewRectProvider(getLastChild());
+        provider.setIncludePadding(true);
+        return provider;
+    }
+
+    private boolean isLastChildObfuscated() {
+        View lastChild = getLastChild();
+        RecyclerView.Adapter adapter = mBarItemsView.getAdapter();
+        // The recycler view isn't ready yet, so no children can be considered:
+        if (lastChild == null || adapter == null) return false;
+        // The last child wasn't even rendered, so it's definitely not visible:
+        if (mBarItemsView.indexOfChild(lastChild) < adapter.getItemCount()) return true;
+        // The last child is partly off screen:
+        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
+                ? lastChild.getX() < 0
+                : lastChild.getX() + lastChild.getWidth() > mBarItemsView.getWidth();
+    }
+
+    private View getLastChild() {
+        for (int i = mBarItemsView.getChildCount() - 1; i >= 0; --i) {
+            View lastChild = mBarItemsView.getChildAt(i);
+            if (lastChild == null) continue;
+            return lastChild;
+        }
+        return null;
+    }
+
     void setKeyboardToggleVisibility(boolean hasActiveTab) {
         mKeyboardToggle.setVisibility(hasActiveTab ? VISIBLE : GONE);
         mSheetTitle.setVisibility(hasActiveTab ? VISIBLE : GONE);
@@ -155,6 +211,10 @@
                 showKeyboardCallback == null ? null : view -> showKeyboardCallback.run());
     }
 
+    void setObfuscatedLastChildAt(Callback<Integer> obfuscatedLastChildAt) {
+        mObfuscatedLastChildAt = obfuscatedLastChildAt;
+    }
+
     private void animateSuggestionArrival() {
         if (areAnimationsDisabled()) return;
         int bounceDirection = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? 1 : -1;
diff --git a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryModernViewBinder.java b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryModernViewBinder.java
index 02fc725..40fd157 100644
--- a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryModernViewBinder.java
+++ b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryModernViewBinder.java
@@ -4,11 +4,13 @@
 
 package org.chromium.chrome.browser.keyboard_accessory.bar_component;
 
+import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryIPHUtils.hasShownAnyAutofillIphBefore;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryIPHUtils.showHelpBubble;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.KEYBOARD_TOGGLE_VISIBLE;
+import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.OBFUSCATED_CHILD_AT_CALLBACK;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.SHEET_TITLE;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.SHOW_KEYBOARD_CALLBACK;
-import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.TAB_LAYOUT_ITEM;
+import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.SHOW_SWIPING_IPH;
 
 import android.view.View;
 import android.view.ViewGroup;
@@ -21,9 +23,11 @@
 import org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.TabLayoutBarItem;
 import org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryViewBinder.BarItemViewHolder;
 import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData;
+import org.chromium.components.feature_engagement.FeatureConstants;
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.widget.ChipView;
+import org.chromium.ui.widget.RectProvider;
 
 /**
  * Observes {@link KeyboardAccessoryProperties} changes (like a newly available tab) and triggers
@@ -102,8 +106,15 @@
             modernView.setShowKeyboardCallback(model.get(SHOW_KEYBOARD_CALLBACK));
         } else if (propertyKey == SHEET_TITLE) {
             modernView.setSheetTitle(model.get(SHEET_TITLE));
-        } else if (propertyKey == TAB_LAYOUT_ITEM) {
-            // No binding required.
+        } else if (propertyKey == OBFUSCATED_CHILD_AT_CALLBACK) {
+            modernView.setObfuscatedLastChildAt(model.get(OBFUSCATED_CHILD_AT_CALLBACK));
+        } else if (propertyKey == SHOW_SWIPING_IPH) {
+            RectProvider swipingIphRectProvider = modernView.getSwipingIphRect();
+            if (model.get(SHOW_SWIPING_IPH) && swipingIphRectProvider != null
+                    && hasShownAnyAutofillIphBefore()) {
+                showHelpBubble(FeatureConstants.KEYBOARD_ACCESSORY_BAR_SWIPING_FEATURE,
+                        swipingIphRectProvider, modernView.mBarItemsView);
+            }
         } else {
             assert wasBound : "Every possible property update needs to be handled!";
         }
diff --git a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryProperties.java b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryProperties.java
index 74d267c9..57743a1 100644
--- a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryProperties.java
+++ b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryProperties.java
@@ -10,6 +10,7 @@
 
 import com.google.android.material.tabs.TabLayout;
 
+import org.chromium.base.Callback;
 import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.Action;
 import org.chromium.chrome.browser.keyboard_accessory.tab_layout_component.KeyboardAccessoryTabLayoutCoordinator.TabLayoutCallbacks;
 import org.chromium.components.autofill.AutofillSuggestion;
@@ -47,18 +48,23 @@
     static final WritableObjectPropertyKey<Runnable> SHOW_KEYBOARD_CALLBACK =
             new WritableObjectPropertyKey<>("keyboard_callback");
     static final ReadableBooleanPropertyKey DISABLE_ANIMATIONS_FOR_TESTING =
-            new WritableBooleanPropertyKey("skip_all_animations_for_testing");
+            new ReadableBooleanPropertyKey("skip_all_animations_for_testing");
+    static final WritableObjectPropertyKey<Callback<Integer>> OBFUSCATED_CHILD_AT_CALLBACK =
+            new WritableObjectPropertyKey<>("obfuscated_child_at_callback");
+    static final WritableBooleanPropertyKey SHOW_SWIPING_IPH =
+            new WritableBooleanPropertyKey("show_swiping_iph");
 
     static PropertyModel.Builder defaultModelBuilder() {
         return new PropertyModel
                 .Builder(DISABLE_ANIMATIONS_FOR_TESTING, BAR_ITEMS, VISIBLE, SKIP_CLOSING_ANIMATION,
                         BOTTOM_OFFSET_PX, TAB_LAYOUT_ITEM, KEYBOARD_TOGGLE_VISIBLE, SHEET_TITLE,
-                        SHOW_KEYBOARD_CALLBACK)
+                        SHOW_KEYBOARD_CALLBACK, OBFUSCATED_CHILD_AT_CALLBACK, SHOW_SWIPING_IPH)
                 .with(BAR_ITEMS, new ListModel<>())
                 .with(VISIBLE, false)
                 .with(SKIP_CLOSING_ANIMATION, false)
                 .with(KEYBOARD_TOGGLE_VISIBLE, false)
-                .with(DISABLE_ANIMATIONS_FOR_TESTING, false);
+                .with(DISABLE_ANIMATIONS_FOR_TESTING, false)
+                .with(SHOW_SWIPING_IPH, false);
     }
 
     /**
diff --git a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryView.java b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryView.java
index cdd977a..a66d43a 100644
--- a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryView.java
+++ b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryView.java
@@ -15,6 +15,7 @@
 import android.view.animation.AccelerateInterpolator;
 import android.widget.LinearLayout;
 
+import androidx.annotation.CallSuper;
 import androidx.core.view.ViewCompat;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
@@ -121,11 +122,16 @@
                 super.onItemRangeChanged(positionStart, itemCount);
                 mBarItemsView.scrollToPosition(0);
                 mBarItemsView.invalidateItemDecorations();
+                onItemsChanged();
             }
         });
         mBarItemsView.setAdapter(adapter);
     }
 
+    /** Template method. Override to be notified if the bar items change. */
+    @CallSuper
+    protected void onItemsChanged() {}
+
     private void show() {
         bringToFront(); // Needs to overlay every component and the bottom sheet - like a keyboard.
         if (mRunningAnimation != null) mRunningAnimation.cancel();
diff --git a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryViewBinder.java b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryViewBinder.java
index a926340..614273c 100644
--- a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryViewBinder.java
+++ b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryViewBinder.java
@@ -8,8 +8,10 @@
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.BOTTOM_OFFSET_PX;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.DISABLE_ANIMATIONS_FOR_TESTING;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.KEYBOARD_TOGGLE_VISIBLE;
+import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.OBFUSCATED_CHILD_AT_CALLBACK;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.SHEET_TITLE;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.SHOW_KEYBOARD_CALLBACK;
+import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.SHOW_SWIPING_IPH;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.SKIP_CLOSING_ANIMATION;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.TAB_LAYOUT_ITEM;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.VISIBLE;
@@ -111,7 +113,8 @@
         } else if (propertyKey == BOTTOM_OFFSET_PX) {
             view.setBottomOffset(model.get(BOTTOM_OFFSET_PX));
         } else if (propertyKey == SHOW_KEYBOARD_CALLBACK || propertyKey == KEYBOARD_TOGGLE_VISIBLE
-                || propertyKey == SHEET_TITLE || propertyKey == TAB_LAYOUT_ITEM) {
+                || propertyKey == SHEET_TITLE || propertyKey == TAB_LAYOUT_ITEM
+                || propertyKey == OBFUSCATED_CHILD_AT_CALLBACK || propertyKey == SHOW_SWIPING_IPH) {
             // No binding required.
         } else {
             return false;
diff --git a/chrome/android/features/keyboard_accessory/internal/java/strings/android_keyboard_accessory_strings.grd b/chrome/android/features/keyboard_accessory/internal/java/strings/android_keyboard_accessory_strings.grd
index 9c44aaa..7faeb8cce 100644
--- a/chrome/android/features/keyboard_accessory/internal/java/strings/android_keyboard_accessory_strings.grd
+++ b/chrome/android/features/keyboard_accessory/internal/java/strings/android_keyboard_accessory_strings.grd
@@ -216,6 +216,9 @@
       <message name="IDS_IPH_KEYBOARD_ACCESSORY_FILL_WITH_CHROME" desc="Text in In-Product-Help bubble suggesting to use an autofill suggestion provided by Chrome.">
           Use Chrome autofill suggestion
       </message>
+      <message name="IDS_IPH_KEYBOARD_ACCESSORY_SWIPE_FOR_MORE" desc="Text in In-Product-Help bubble suggesting to scroll the accessory to reveal more suggestions.">
+          Swipe to see more suggestions
+      </message>
       <message name="IDS_PASSWORD_GENERATION_ACCESSORY_BUTTON" desc="Text for the button used to generate a password.">
           Suggest strong password
       </message>
diff --git a/chrome/android/features/keyboard_accessory/internal/java/strings/android_keyboard_accessory_strings_grd/IDS_IPH_KEYBOARD_ACCESSORY_SWIPE_FOR_MORE.png.sha1 b/chrome/android/features/keyboard_accessory/internal/java/strings/android_keyboard_accessory_strings_grd/IDS_IPH_KEYBOARD_ACCESSORY_SWIPE_FOR_MORE.png.sha1
new file mode 100644
index 0000000..29c23f0c
--- /dev/null
+++ b/chrome/android/features/keyboard_accessory/internal/java/strings/android_keyboard_accessory_strings_grd/IDS_IPH_KEYBOARD_ACCESSORY_SWIPE_FOR_MORE.png.sha1
@@ -0,0 +1 @@
+af6ea41db50ee56da277563b7dc173b869e19089
\ No newline at end of file
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryModernViewTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryModernViewTest.java
index 002bd127..2aa29a70 100644
--- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryModernViewTest.java
+++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryModernViewTest.java
@@ -24,7 +24,9 @@
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.BAR_ITEMS;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.DISABLE_ANIMATIONS_FOR_TESTING;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.KEYBOARD_TOGGLE_VISIBLE;
+import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.OBFUSCATED_CHILD_AT_CALLBACK;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.SHEET_TITLE;
+import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.SHOW_SWIPING_IPH;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.TAB_LAYOUT_ITEM;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.VISIBLE;
 import static org.chromium.chrome.test.util.ViewUtils.onViewWaiting;
@@ -36,6 +38,7 @@
 import android.view.ViewStub;
 
 import androidx.annotation.Nullable;
+import androidx.test.espresso.ViewInteraction;
 import androidx.test.espresso.matcher.RootMatchers;
 import androidx.test.filters.MediumTest;
 
@@ -81,6 +84,7 @@
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 
 /**
@@ -173,6 +177,8 @@
                                                                              TabLayout tabs) {}
                                                                  }))
                             .with(DISABLE_ANIMATIONS_FOR_TESTING, true)
+                            .with(OBFUSCATED_CHILD_AT_CALLBACK, unused -> {})
+                            .with(SHOW_SWIPING_IPH, false)
                             .build();
             ViewStub viewStub =
                     mActivityTestRule.getActivity().findViewById(R.id.keyboard_accessory_stub);
@@ -324,9 +330,66 @@
                 is(EventConstants.KEYBOARD_ACCESSORY_PAYMENT_AUTOFILLED));
     }
 
-    private void waitForHelpBubble(Matcher<View> matcher) {
+    @Test
+    @MediumTest
+    public void testDismissesSwipingEducationBubbleOnTap() {
+        TestTracker tracker = new TestTracker() {
+            @Override
+            public int getTriggerState(String feature) {
+                // Pretend that an autofill IPH was shown already.
+                return feature.equals(FeatureConstants.KEYBOARD_ACCESSORY_PASSWORD_FILLING_FEATURE)
+                        ? TriggerState.HAS_BEEN_DISPLAYED
+                        : TriggerState.HAS_NOT_BEEN_DISPLAYED;
+            }
+        };
+        TrackerFactory.setTrackerForTests(tracker);
+
+        // Render a keyboard accessory bar and wait for completion.
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mModel.set(VISIBLE, true);
+            mModel.get(BAR_ITEMS).set(createAutofillChipAndTab("Johnathan", null));
+        });
+        onViewWaiting(withText("Johnathan"));
+
+        // Pretend an item is offscreen, so swiping is possible and an IPH could be shown.
+        TestThreadUtils.runOnUiThreadBlocking(() -> mModel.set(SHOW_SWIPING_IPH, true));
+
+        // Wait until the bubble appears, then dismiss is by tapping it.
+        waitForHelpBubble(withText(R.string.iph_keyboard_accessory_swipe_for_more))
+                .perform(click());
+        assertThat(tracker.wasDismissed(), is(true));
+    }
+
+    @Test
+    @MediumTest
+    public void testNotifiesAboutPartiallyVisibleSuggestions() throws InterruptedException {
+        // Ensure that the callback isn't triggered while all items are visible:
+        AtomicInteger obfuscatedChildAt = new AtomicInteger(-1);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mModel.set(OBFUSCATED_CHILD_AT_CALLBACK, obfuscatedChildAt::set);
+            mModel.set(VISIBLE, true);
+            mModel.get(BAR_ITEMS).set(createAutofillChipAndTab("John", null));
+        });
+        KeyboardAccessoryModernView view = mKeyboardAccessoryView.take();
+        CriteriaHelper.pollUiThread(() -> view.mBarItemsView.getChildCount() > 0);
+        assertThat(obfuscatedChildAt.get(), is(-1));
+
+        // As soon as at least one item can't be displayed in full, trigger the swiping callback.
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mModel.get(BAR_ITEMS).set(new BarItem[] {createAutofillBarItem("JohnathanSmith", null),
+                    createAutofillBarItem("TroyMcSpartanGregor", null),
+                    createAutofillBarItem("SomeOtherRandomLongName", null),
+                    createAutofillBarItem("ToddTester", null),
+                    createAutofillBarItem("MayaPark", null),
+                    createAutofillBarItem("ThisChipIsProbablyHiddenNow", null), createTabs()});
+        });
+        onViewWaiting(withText("JohnathanSmith"));
+        CriteriaHelper.pollUiThread(() -> obfuscatedChildAt.get() > -1);
+    }
+
+    private ViewInteraction waitForHelpBubble(Matcher<View> matcher) {
         View mainDecorView = mActivityTestRule.getActivity().getWindow().getDecorView();
-        onView(isRoot())
+        return onView(isRoot())
                 .inRoot(RootMatchers.withDecorView(not(is(mainDecorView))))
                 .check(waitForView(matcher));
     }
@@ -357,12 +420,13 @@
     }
 
     private BarItem[] createAutofillChipAndTab(String label, Callback<Action> chipCallback) {
-        return new BarItem[] {
-                new AutofillBarItem(new AutofillSuggestion(label, "Smith", /*itemTag=*/"",
-                                            DropdownItem.NO_ICON, false, 1, false, false, false),
-                        new KeyboardAccessoryData.Action(
-                                "Unused", AUTOFILL_SUGGESTION, chipCallback)),
-                createTabs()};
+        return new BarItem[] {createAutofillBarItem(label, chipCallback), createTabs()};
+    }
+
+    private AutofillBarItem createAutofillBarItem(String label, Callback<Action> chipCallback) {
+        return new AutofillBarItem(new AutofillSuggestion(label, "Smith", /*itemTag=*/"",
+                                           DropdownItem.NO_ICON, false, 1, false, false, false),
+                new KeyboardAccessoryData.Action("Unused", AUTOFILL_SUGGESTION, chipCallback));
     }
 
     private TabLayoutBarItem createTabs() {
diff --git a/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryControllerTest.java b/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryControllerTest.java
index 6c3f4dc..8dca966 100644
--- a/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryControllerTest.java
+++ b/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryControllerTest.java
@@ -19,7 +19,9 @@
 import static org.chromium.chrome.browser.keyboard_accessory.AccessoryAction.AUTOFILL_SUGGESTION;
 import static org.chromium.chrome.browser.keyboard_accessory.AccessoryAction.GENERATE_PASSWORD_AUTOMATIC;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.BAR_ITEMS;
+import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.OBFUSCATED_CHILD_AT_CALLBACK;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.SHEET_TITLE;
+import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.SHOW_SWIPING_IPH;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.SKIP_CLOSING_ANIMATION;
 import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.VISIBLE;
 
@@ -33,6 +35,7 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.annotation.Config;
 
+import org.chromium.base.Callback;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordHistogramJni;
 import org.chromium.base.metrics.test.ShadowRecordHistogram;
@@ -419,6 +422,26 @@
     }
 
     @Test
+    public void testShowSwipingIphUntilVisibilityIsReset() {
+        // By default, no IPH is shown but the model holds a callback to notify the mediator.
+        mCoordinator.show();
+        Callback<Integer> obfuscatedChildAt = mModel.get(OBFUSCATED_CHILD_AT_CALLBACK);
+        assertThat(obfuscatedChildAt, notNullValue());
+        assertThat(mModel.get(SHOW_SWIPING_IPH), is(false));
+
+        // Notify the mediator to show the IPH because at least one of three items is not visible.
+        mModel.get(BAR_ITEMS).add(mock(BarItem.class));
+        mModel.get(BAR_ITEMS).add(mock(BarItem.class));
+        mModel.get(BAR_ITEMS).add(mock(BarItem.class));
+        obfuscatedChildAt.onResult(1);
+        assertThat(mModel.get(SHOW_SWIPING_IPH), is(true));
+
+        // Any change that changes the visibility should reset the swiping IPH.
+        mModel.set(VISIBLE, false);
+        assertThat(mModel.get(SHOW_SWIPING_IPH), is(false));
+    }
+
+    @Test
     public void testRecordsOneImpressionForEveryInitialContentOnVisibilityChange() {
         assertThat(RecordHistogram.getHistogramTotalCountForTesting(
                            KeyboardAccessoryMetricsRecorder.UMA_KEYBOARD_ACCESSORY_BAR_SHOWN),
diff --git a/chrome/android/features/start_surface/internal/BUILD.gn b/chrome/android/features/start_surface/internal/BUILD.gn
index 723962c..25841d96 100644
--- a/chrome/android/features/start_surface/internal/BUILD.gn
+++ b/chrome/android/features/start_surface/internal/BUILD.gn
@@ -79,6 +79,7 @@
     "//chrome/browser/tabmodel:java",
     "//chrome/browser/ui/messages/android:java",
     "//chrome/browser/util:java",
+    "//chrome/browser/version:java",
     "//components/browser_ui/android/bottomsheet:java",
     "//components/browser_ui/widget/android:java",
     "//components/prefs/android:java",
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 7757615..b9207d6 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
@@ -86,7 +86,7 @@
             boolean isInNightMode, boolean isPlaceholderShown,
             BottomSheetController bottomSheetController) {
         if (mExploreSurfaceNavigationDelegate == null) {
-            mExploreSurfaceNavigationDelegate = new ExploreSurfaceNavigationDelegate(mActivity);
+            mExploreSurfaceNavigationDelegate = new ExploreSurfaceNavigationDelegate();
         }
         Profile profile = Profile.getLastUsedRegularProfile();
 
diff --git a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceNavigationDelegate.java b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceNavigationDelegate.java
index d71e9f0..05ff1b3 100644
--- a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceNavigationDelegate.java
+++ b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceNavigationDelegate.java
@@ -4,17 +4,11 @@
 
 package org.chromium.chrome.features.start_surface;
 
-import android.content.Context;
-import android.net.Uri;
-import android.provider.Browser;
-
 import androidx.annotation.Nullable;
-import androidx.browser.customtabs.CustomTabsIntent;
 
-import org.chromium.chrome.browser.IntentHandler;
 import org.chromium.chrome.browser.native_page.NativePageNavigationDelegate;
 import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.start_surface.R;
+import org.chromium.chrome.browser.tasks.ReturnToChromeExperimentsUtil;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.ui.base.PageTransition;
 import org.chromium.ui.mojom.WindowOpenDisposition;
@@ -22,34 +16,21 @@
 /** Implementation of the {@link NativePageNavigationDelegate} for the explore surface. */
 class ExploreSurfaceNavigationDelegate implements NativePageNavigationDelegate {
     private static final String NEW_TAB_URL_HELP = "https://support.google.com/chrome/?p=new_tab";
-    private final Context mContext;
 
-    ExploreSurfaceNavigationDelegate(Context context) {
-        mContext = context;
-    }
+    ExploreSurfaceNavigationDelegate() {}
 
     @Override
     public boolean isOpenInNewWindowEnabled() {
         return false;
     }
 
-    // TODO(crbug.com/982018): Experiment opening feeds in normal Tabs.
     @Override
     @Nullable
     public Tab openUrl(int windowOpenDisposition, LoadUrlParams loadUrlParams) {
-        CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
-        builder.setShowTitle(true);
-        builder.setStartAnimations(mContext, R.anim.abc_grow_fade_in_from_bottom, 0);
-        builder.setExitAnimations(mContext, 0, R.anim.abc_shrink_fade_out_from_bottom);
-        CustomTabsIntent customTabsIntent = builder.build();
-        customTabsIntent.intent.setPackage(mContext.getPackageName());
-        customTabsIntent.intent.putExtra(IntentHandler.EXTRA_OPEN_NEW_INCOGNITO_TAB,
-                (windowOpenDisposition == WindowOpenDisposition.OFF_THE_RECORD) ? true : false);
-        customTabsIntent.intent.putExtra(Browser.EXTRA_APPLICATION_ID, mContext.getPackageName());
-        customTabsIntent.launchUrl(mContext, Uri.parse(loadUrlParams.getUrl()));
-
-        // TODO(crbug.com/982018): Return the opened tab and make sure it is opened in incoginito
-        // mode accordingly (note that payment window supports incognito mode).
+        boolean result = ReturnToChromeExperimentsUtil.willHandleLoadUrlFromStartSurface(
+                loadUrlParams.getUrl(), PageTransition.AUTO_BOOKMARK,
+                windowOpenDisposition == WindowOpenDisposition.OFF_THE_RECORD);
+        assert result;
         return null;
     }
 
diff --git a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java
index f16d8cf..42a9988 100644
--- a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java
+++ b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java
@@ -54,6 +54,7 @@
 import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
 import org.chromium.chrome.browser.preferences.Pref;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabLaunchType;
 import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver;
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tabmodel.TabModelObserver;
@@ -641,7 +642,13 @@
             }
             mPropertyModel.set(IS_SHOWING_OVERVIEW, false);
 
-            destroyFeedSurfaceCoordinator();
+            if (mTabModelSelector.getCurrentTab() == null
+                    || mTabModelSelector.getCurrentTab().getLaunchType()
+                            != TabLaunchType.FROM_START_SURFACE) {
+                // TODO(https://crbug.com/1132852): Destroy FeedSurfaceCoordinator if users don't
+                // navigate back to Start after a while.
+                destroyFeedSurfaceCoordinator();
+            }
             if (mNormalTabModelObserver != null) {
                 mNormalTabModel.removeObserver(mNormalTabModelObserver);
             }
diff --git a/chrome/android/features/start_surface/internal/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java b/chrome/android/features/start_surface/internal/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java
index de9ed76..a04de99 100644
--- a/chrome/android/features/start_surface/internal/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java
+++ b/chrome/android/features/start_surface/internal/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java
@@ -7,7 +7,10 @@
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
@@ -59,11 +62,13 @@
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeState;
+import org.chromium.chrome.browser.feed.FeedSurfaceCoordinator;
 import org.chromium.chrome.browser.night_mode.NightModeStateProvider;
 import org.chromium.chrome.browser.ntp.FakeboxDelegate;
 import org.chromium.chrome.browser.omnibox.UrlFocusChangeListener;
 import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabLaunchType;
 import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver;
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tabmodel.TabModelObserver;
@@ -93,6 +98,8 @@
     @Mock
     private TabModelSelector mTabModelSelector;
     @Mock
+    private Tab mTab;
+    @Mock
     private TabModel mNormalTabModel;
     @Mock
     private TabModel mIncognitoTabModel;
@@ -101,6 +108,8 @@
     @Mock
     private ExploreSurfaceCoordinator.FeedSurfaceCreator mFeedSurfaceCreator;
     @Mock
+    private FeedSurfaceCoordinator mFeedSurfaceCoordinator;
+    @Mock
     private NightModeStateProvider mNightModeStateProvider;
     @Mock
     private BrowserControlsStateProvider mBrowserControlsStateProvider;
@@ -144,6 +153,7 @@
                 .when(mSecondaryTasksSurfaceInitializer)
                 .initialize();
         doReturn(false).when(mActivityStateChecker).isFinishingOrDestroyed();
+        doReturn(mTab).when(mTabModelSelector).getCurrentTab();
     }
 
     @After
@@ -1506,6 +1516,50 @@
     }
 
     @Test
+    public void singleShowingPreviousFromATabOfFeeds() {
+        doReturn(false).when(mTabModelSelector).isIncognitoSelected();
+        doReturn(mVoiceRecognitionHandler).when(mFakeBoxDelegate).getVoiceRecognitionHandler();
+        doReturn(true).when(mVoiceRecognitionHandler).isVoiceSearchEnabled();
+
+        StartSurfaceMediator mediator = createStartSurfaceMediator(SurfaceMode.SINGLE_PANE, false);
+        InOrder mainTabGridController = inOrder(mMainTabGridController);
+        mainTabGridController.verify(mMainTabGridController)
+                .addOverviewModeObserver(mOverviewModeObserverCaptor.capture());
+        assertThat(mediator.getOverviewState(), equalTo(OverviewModeState.NOT_SHOWN));
+
+        mediator.setOverviewState(OverviewModeState.SHOWING_HOMEPAGE);
+        mPropertyModel.set(IS_EXPLORE_SURFACE_VISIBLE, true);
+        when(mFeedSurfaceCreator.createFeedSurfaceCoordinator(anyBoolean(), anyBoolean()))
+                .thenReturn(mFeedSurfaceCoordinator);
+        mediator.showOverview(false);
+        mainTabGridController.verify(mMainTabGridController).showOverview(eq(false));
+        assertThat(mediator.getOverviewState(), equalTo(OverviewModeState.SHOWN_HOMEPAGE));
+        assertThat(mPropertyModel.get(FEED_SURFACE_COORDINATOR), equalTo(mFeedSurfaceCoordinator));
+
+        doReturn(TabLaunchType.FROM_START_SURFACE).when(mTab).getLaunchType();
+        mediator.hideOverview(true);
+        mOverviewModeObserverCaptor.getValue().startedHiding();
+        mOverviewModeObserverCaptor.getValue().finishedHiding();
+        assertThat(mPropertyModel.get(FEED_SURFACE_COORDINATOR), equalTo(mFeedSurfaceCoordinator));
+
+        FeedSurfaceCoordinator feedSurfaceCoordinator = mock(FeedSurfaceCoordinator.class);
+        assertNotEquals(mFeedSurfaceCoordinator, feedSurfaceCoordinator);
+        when(mFeedSurfaceCreator.createFeedSurfaceCoordinator(anyBoolean(), anyBoolean()))
+                .thenReturn(feedSurfaceCoordinator);
+        mediator.setOverviewState(OverviewModeState.SHOWING_PREVIOUS);
+        mediator.showOverview(false);
+        mainTabGridController.verify(mMainTabGridController).showOverview(eq(false));
+        assertThat(mediator.getOverviewState(), equalTo(OverviewModeState.SHOWN_HOMEPAGE));
+        assertThat(mPropertyModel.get(FEED_SURFACE_COORDINATOR), equalTo(mFeedSurfaceCoordinator));
+
+        doReturn(TabLaunchType.FROM_LINK).when(mTab).getLaunchType();
+        mediator.hideOverview(true);
+        mOverviewModeObserverCaptor.getValue().startedHiding();
+        mOverviewModeObserverCaptor.getValue().finishedHiding();
+        assertNull(mPropertyModel.get(FEED_SURFACE_COORDINATOR));
+    }
+
+    @Test
     public void changeTopContentOffset() {
         doReturn(false).when(mTabModelSelector).isIncognitoSelected();
         doReturn(mVoiceRecognitionHandler).when(mFakeBoxDelegate).getVoiceRecognitionHandler();
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/MostVisitedListCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/MostVisitedListCoordinator.java
index 280a744..e5409d78 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/MostVisitedListCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/MostVisitedListCoordinator.java
@@ -149,7 +149,7 @@
         @Override
         public void onClick(View v) {
             ReturnToChromeExperimentsUtil.willHandleLoadUrlFromStartSurface(
-                    mTile.getUrl().getSpec(), PageTransition.AUTO_BOOKMARK);
+                    mTile.getUrl().getSpec(), PageTransition.AUTO_BOOKMARK, null /*incognito*/);
         }
 
         @Override
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TrendyTermsCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TrendyTermsCoordinator.java
index 7985108..ea131609 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TrendyTermsCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TrendyTermsCoordinator.java
@@ -82,7 +82,7 @@
                 RecordUserAction.record("StartSurface.TrendyTerms.TapTerm");
                 String url = TemplateUrlServiceFactory.get().getUrlForSearchQuery(trendyTerm);
                 ReturnToChromeExperimentsUtil.willHandleLoadUrlFromStartSurface(
-                        url, PageTransition.AUTO_BOOKMARK);
+                        url, PageTransition.AUTO_BOOKMARK, null /*incognito*/);
             };
             PropertyModel trendInfo =
                     new PropertyModel.Builder(TrendyTermsProperties.ALL_KEYS)
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java
index 49cf675..4901a2f 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java
@@ -23,6 +23,7 @@
 import org.chromium.chrome.browser.metrics.UmaSessionStats;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.state.CriticalPersistedTabData;
+import org.chromium.chrome.browser.tabmodel.TabModelFilter;
 import org.chromium.chrome.browser.tabmodel.TabModelFilterProvider;
 import org.chromium.chrome.browser.tabmodel.TabModelObserver;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
@@ -126,7 +127,7 @@
         mActivityLifecycleDispatcher = activity.getLifecycleDispatcher();
         mActivityLifecycleDispatcher.register(this);
 
-
+        // TODO(meiliang): Potential leak if the observer is added after restoreCompleted. Fix it.
         // Record the group count after all tabs are being restored. This only happen once per life
         // cycle, therefore remove the observer after recording. We only focus on normal tab model
         // because we don't restore tabs in incognito tab model.
@@ -214,6 +215,22 @@
     private void recordTabGroupCount() {
         TabModelFilterProvider provider =
                 mActivity.getTabModelSelector().getTabModelFilterProvider();
+
+        if (TabUiFeatureUtilities.isLaunchPolishEnabled()) {
+            TabModelFilter normalTabModelFilter = provider.getTabModelFilter(false);
+
+            if (!(normalTabModelFilter instanceof TabGroupModelFilter)) {
+                String actualType = normalTabModelFilter == null
+                        ? "null"
+                        : normalTabModelFilter.getClass().getName();
+                assert false
+                    : "Please file bug, this is unexpected. Expected TabGroupModelFilter, but was "
+                      + actualType;
+
+                return;
+            }
+        }
+
         TabGroupModelFilter normalFilter = (TabGroupModelFilter) provider.getTabModelFilter(false);
         TabGroupModelFilter incognitoFilter =
                 (TabGroupModelFilter) provider.getTabModelFilter(true);
diff --git a/chrome/android/java/AndroidManifest.xml b/chrome/android/java/AndroidManifest.xml
index bd1f891..4d0a6e2 100644
--- a/chrome/android/java/AndroidManifest.xml
+++ b/chrome/android/java/AndroidManifest.xml
@@ -642,6 +642,12 @@
             android:configChanges="orientation|keyboardHidden|keyboard|screenSize|mcc|mnc|screenLayout|smallestScreenSize">
         </activity>
 
+        <!-- Activities for video tutorials. -->
+        <activity android:name="org.chromium.chrome.browser.video_tutorials.VideoTutorialListActivity"
+            android:theme="@style/Theme.Chromium.Activity.Fullscreen"
+            android:exported="false">
+        </activity>
+
         <!-- Activities for downloads. -->
         <activity android:name="org.chromium.chrome.browser.download.DownloadActivity"
             android:theme="@style/Theme.Chromium.Activity.Fullscreen"
diff --git a/chrome/android/java/res/layout/account_divider_preference.xml b/chrome/android/java/res/layout/account_divider_preference.xml
new file mode 100644
index 0000000..0f8c212d
--- /dev/null
+++ b/chrome/android/java/res/layout/account_divider_preference.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2020 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+
+<!-- Creates a partial divider that is used to attach Manage Google Account row to the signed in
+     account in AccountManagementFragment. -->
+<View
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    style="@style/HorizontalDivider"
+    android:layout_marginStart="72dp" />
diff --git a/chrome/android/java/res/layout/account_picker_incognito_row.xml b/chrome/android/java/res/layout/account_picker_incognito_row.xml
index 10f232e..334557fa 100644
--- a/chrome/android/java/res/layout/account_picker_incognito_row.xml
+++ b/chrome/android/java/res/layout/account_picker_incognito_row.xml
@@ -14,6 +14,6 @@
     android:paddingTop="12dp"
     android:paddingBottom="12dp"
     android:gravity="center_vertical"
-    android:text="@string/signin_go_incognito"
+    android:text="@string/signin_incognito_button"
     android:textAppearance="@style/TextAppearance.TextLarge.Primary"
     app:drawableStartCompat="@drawable/ic_incognito_24dp" />
\ No newline at end of file
diff --git a/chrome/android/java/res/layout/incognito_interstitial_bottom_sheet_view.xml b/chrome/android/java/res/layout/incognito_interstitial_bottom_sheet_view.xml
index 0ca7c56..96b634d7 100644
--- a/chrome/android/java/res/layout/incognito_interstitial_bottom_sheet_view.xml
+++ b/chrome/android/java/res/layout/incognito_interstitial_bottom_sheet_view.xml
@@ -13,6 +13,7 @@
     android:orientation="vertical">
 
     <org.chromium.ui.widget.TextViewWithLeading
+        android:id="@+id/incognito_interstitial_message"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginBottom="16dp"
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index f065dec..9de0d5d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -1487,7 +1487,8 @@
 
         mInactivityTracker = new ChromeInactivityTracker(
                 ChromePreferenceKeys.TABBED_ACTIVITY_LAST_BACKGROUNDED_TIME_MS_PREF);
-        PaintPreviewHelper.initialize(this, getTabModelSelector());
+        PaintPreviewHelper.initialize(
+                this, getTabModelSelector(), () -> getToolbarManager().getProgressBarCoordinator());
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeVersionInfo.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeVersionInfo.java
index 8960f25..bc796d8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeVersionInfo.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeVersionInfo.java
@@ -4,74 +4,9 @@
 
 package org.chromium.chrome.browser;
 
-import org.chromium.components.version_info.Channel;
-
 /**
- * A utility class for querying information about the current Chrome build.
- * Intentionally doesn't depend on native so that the data can be accessed before
- * libchrome.so is loaded.
+ * Temporary class left until references are moved to the new package and build target.
+ *
+ * TODO(crbug.com/1131982): Remove this.
  */
-public class ChromeVersionInfo {
-    /**
-     * @return Whether this build is a local build.
-     */
-    public static boolean isLocalBuild() {
-        return ChromeVersionConstants.CHANNEL == Channel.DEFAULT;
-    }
-
-    /**
-     * @return Whether this build is a canary build.
-     */
-    public static boolean isCanaryBuild() {
-        return ChromeVersionConstants.CHANNEL == Channel.CANARY;
-    }
-
-    /**
-     * @return Whether this build is a dev build.
-     */
-    public static boolean isDevBuild() {
-        return ChromeVersionConstants.CHANNEL == Channel.DEV;
-    }
-
-    /**
-     * @return Whether this build is a beta build.
-     */
-    public static boolean isBetaBuild() {
-        return ChromeVersionConstants.CHANNEL == Channel.BETA;
-    }
-
-    /**
-     * @return Whether this build is a stable build.
-     */
-    public static boolean isStableBuild() {
-        return ChromeVersionConstants.CHANNEL == Channel.STABLE;
-    }
-
-    /**
-     * @return Whether this is an official (i.e. Google Chrome) build.
-     */
-    public static boolean isOfficialBuild() {
-        return ChromeVersionConstants.IS_OFFICIAL_BUILD;
-    }
-
-    /**
-     * @return The version number.
-     */
-    public static String getProductVersion() {
-        return ChromeVersionConstants.PRODUCT_VERSION;
-    }
-
-    /**
-     * @return The major version number.
-     */
-    public static int getProductMajorVersion() {
-        return ChromeVersionConstants.PRODUCT_MAJOR_VERSION;
-    }
-
-    /**
-     * @return The build number.
-     */
-    public static int getBuildVersion() {
-        return ChromeVersionConstants.PRODUCT_BUILD_VERSION;
-    }
-}
+public class ChromeVersionInfo extends org.chromium.chrome.browser.version.ChromeVersionInfo {}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/DEPS b/chrome/android/java/src/org/chromium/chrome/browser/DEPS
index 4fe8bfb5..7656803a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/DEPS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/DEPS
@@ -128,9 +128,6 @@
   "CustomTabBottomBarDelegate\.java": [
     "+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
   ],
-  "CustomTabCompositorContentInitializer\.java": [
-    "+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
-  ],
   "CustomTabDelegateFactory\.java": [
     "+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
   ],
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
index 50ad49f..aefac36 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
@@ -220,7 +220,7 @@
         implements TabCreatorManager, PolicyChangeListener, ContextualSearchTabPromotionDelegate,
                    SnackbarManageable, SceneChangeObserver,
                    StatusBarColorController.StatusBarColorProvider, AppMenuDelegate, AppMenuBlocker,
-                   MenuOrKeyboardActionController {
+                   MenuOrKeyboardActionController, CompositorViewHolder.Initializer {
     /**
      * No control container to inflate during initialization.
      */
@@ -411,7 +411,8 @@
                         getWindowAndroid(), this::getCompositorViewHolder, this,
                         this::getCurrentTabCreator, this::isCustomTab,
                         getStatusBarColorController(), ScreenOrientationProvider.getInstance(),
-                        this::getNotificationManagerProxy)
+                        this::getNotificationManagerProxy, getTabContentManagerSupplier(),
+                        /* CompositorViewHolder.Initializer */ this)
                 : overridenCommonsFactory.create(this, mRootUiCoordinator::getBottomSheetController,
                         mTabModelSelectorSupplier, getBrowserControlsManager(),
                         getBrowserControlsManager(), getBrowserControlsManager(),
@@ -420,7 +421,8 @@
                         getTabContentManager(), getWindowAndroid(), this::getCompositorViewHolder,
                         this, this::getCurrentTabCreator, this::isCustomTab,
                         getStatusBarColorController(), ScreenOrientationProvider.getInstance(),
-                        this::getNotificationManagerProxy);
+                        this::getNotificationManagerProxy, getTabContentManagerSupplier(),
+                        /* CompositorViewHolder.Initializer */ this);
 
         return createComponent(commonsModule);
     }
@@ -1718,18 +1720,7 @@
         return false;
     }
 
-    /**
-     * Initializes the {@link CompositorViewHolder} with the relevant content it needs to properly
-     * show content on the screen.
-     * @param layoutManager             A {@link LayoutManager} instance.  This class is
-     *                                  responsible for driving all high level screen content and
-     *                                  determines which {@link Layout} is shown when.
-     * @param urlBar                    The {@link View} representing the URL bar (must be
-     *                                  focusable) or {@code null} if none exists.
-     * @param contentContainer          A {@link ViewGroup} that can have content attached by
-     *                                  {@link Layout}s.
-     * @param controlContainer          A {@link ControlContainer} instance to draw.
-     */
+    @Override
     public void initializeCompositorContent(LayoutManager layoutManager, View urlBar,
             ViewGroup contentContainer, ControlContainer controlContainer) {
         if (mContextualSearchManager != null) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/base/SplitMonochromeApplication.java b/chrome/android/java/src/org/chromium/chrome/browser/base/SplitMonochromeApplication.java
index aaa23d29..bf5fcf3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/base/SplitMonochromeApplication.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/base/SplitMonochromeApplication.java
@@ -10,8 +10,7 @@
 
 import org.chromium.android_webview.nonembedded.WebViewApkApplication;
 import org.chromium.base.library_loader.LibraryLoader;
-import org.chromium.chrome.browser.ChromeVersionConstants;
-import org.chromium.components.version_info.Channel;
+import org.chromium.chrome.browser.version.ChromeVersionInfo;
 
 /**
  * Application class to use for Monochrome when //chrome code is in an isolated split. See {@link
@@ -30,7 +29,7 @@
             super.onCreate();
             // TODO(crbug.com/1126301): This matches logic in MonochromeApplication.java.
             // Deduplicate if chrome split launches.
-            if (ChromeVersionConstants.CHANNEL != Channel.STABLE && isWebViewProcess()) {
+            if (!ChromeVersionInfo.isStableBuild() && isWebViewProcess()) {
                 WebViewApkApplication.postDeveloperUiLauncherIconTask();
             }
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
index 9bebd59d..7a9562f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
@@ -103,6 +103,27 @@
     private static final long SYSTEM_UI_VIEWPORT_UPDATE_DELAY_MS = 500;
 
     /**
+     * Initializer interface used to decouple initialization from the class that owns
+     * the CompositorViewHolder.
+     */
+    public interface Initializer {
+        /**
+         * Initializes the {@link CompositorViewHolder} with the relevant content it needs to
+         * properly show content on the screen.
+         * @param layoutManager             A {@link LayoutManager} instance.  This class is
+         *                                  responsible for driving all high level screen content
+         * and determines which {@link Layout} is shown when.
+         * @param urlBar                    The {@link View} representing the URL bar (must be
+         *                                  focusable) or {@code null} if none exists.
+         * @param contentContainer          A {@link ViewGroup} that can have content attached by
+         *                                  {@link Layout}s.
+         * @param controlContainer          A {@link ControlContainer} instance to draw.
+         */
+        void initializeCompositorContent(LayoutManager layoutManager, View urlBar,
+                ViewGroup contentContainer, ControlContainer controlContainer);
+    }
+
+    /**
      * Observer interface for any object that needs to process touch events.
      */
     public interface TouchEventObserver {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabCompositorContentInitializer.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabCompositorContentInitializer.java
index a363d73..8d8f1ba 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabCompositorContentInitializer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabCompositorContentInitializer.java
@@ -4,12 +4,14 @@
 
 package org.chromium.chrome.browser.customtabs;
 
+import android.app.Activity;
 import android.view.ViewGroup;
 
 import org.chromium.base.Callback;
-import org.chromium.chrome.browser.app.ChromeActivity;
+import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.chrome.browser.compositor.CompositorViewHolder;
 import org.chromium.chrome.browser.compositor.layouts.LayoutManager;
+import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
 import org.chromium.chrome.browser.dependency_injection.ActivityScope;
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
 import org.chromium.chrome.browser.lifecycle.NativeInitObserver;
@@ -26,20 +28,26 @@
  */
 @ActivityScope
 public class CustomTabCompositorContentInitializer implements NativeInitObserver {
-    private final ActivityLifecycleDispatcher mLifecycleDispatcher;
-
-    private final ChromeActivity<?> mActivity;
-    private final Lazy<CompositorViewHolder> mCompositorViewHolder;
-
     private final List<Callback<LayoutManager>> mListeners = new ArrayList<>();
+
+    private final ActivityLifecycleDispatcher mLifecycleDispatcher;
+    private final Activity mActivity;
+    private final Lazy<CompositorViewHolder> mCompositorViewHolder;
+    private final ObservableSupplier<TabContentManager> mTabContentManagerSupplier;
+    private final CompositorViewHolder.Initializer mCompositorViewHolderInitializer;
+
     private boolean mInitialized;
 
     @Inject
     public CustomTabCompositorContentInitializer(ActivityLifecycleDispatcher lifecycleDispatcher,
-            ChromeActivity<?> activity, Lazy<CompositorViewHolder> compositorViewHolder) {
+            Activity activity, Lazy<CompositorViewHolder> compositorViewHolder,
+            ObservableSupplier<TabContentManager> tabContentManagerSupplier,
+            CompositorViewHolder.Initializer compositorViewHolderInitializer) {
         mLifecycleDispatcher = lifecycleDispatcher;
         mActivity = activity;
         mCompositorViewHolder = compositorViewHolder;
+        mTabContentManagerSupplier = tabContentManagerSupplier;
+        mCompositorViewHolderInitializer = compositorViewHolderInitializer;
 
         lifecycleDispatcher.register(this);
     }
@@ -49,7 +57,6 @@
      * initialized, or immediately (synchronously) if it is already initialized.
      */
     public void addCallback(Callback<LayoutManager> callback) {
-
         if (mInitialized) {
             callback.onResult(mCompositorViewHolder.get().getLayoutManager());
         } else {
@@ -60,10 +67,10 @@
     @Override
     public void onFinishNativeInitialization() {
         ViewGroup contentContainer = mActivity.findViewById(android.R.id.content);
-        LayoutManager layoutDriver = new LayoutManager(mCompositorViewHolder.get(),
-                contentContainer, mActivity.getTabContentManagerSupplier());
+        LayoutManager layoutDriver = new LayoutManager(
+                mCompositorViewHolder.get(), contentContainer, mTabContentManagerSupplier);
 
-        mActivity.initializeCompositorContent(layoutDriver,
+        mCompositorViewHolderInitializer.initializeCompositorContent(layoutDriver,
                 mActivity.findViewById(org.chromium.chrome.R.id.url_bar), contentContainer,
                 mActivity.findViewById(org.chromium.chrome.R.id.control_container));
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/dependency_injection/ChromeActivityCommonsModule.java b/chrome/android/java/src/org/chromium/chrome/browser/dependency_injection/ChromeActivityCommonsModule.java
index e7eadd00..a17261a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/dependency_injection/ChromeActivityCommonsModule.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/dependency_injection/ChromeActivityCommonsModule.java
@@ -13,6 +13,7 @@
 import android.content.res.Resources;
 import android.view.View;
 
+import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.browser.ActivityTabProvider;
 import org.chromium.chrome.browser.app.ChromeActivity;
@@ -65,6 +66,8 @@
     private final StatusBarColorController mStatusBarColorController;
     private final ScreenOrientationProvider mScreenOrientationProvider;
     private final Supplier<NotificationManagerProxy> mNotificationManagerProxySupplier;
+    private final ObservableSupplier<TabContentManager> mTabContentManagerSupplier;
+    private final CompositorViewHolder.Initializer mCompositorViewHolderInitializer;
 
     /** See {@link ModuleFactoryOverrides} */
     public interface Factory {
@@ -84,7 +87,9 @@
                 Supplier<Boolean> isPromotableToTabSupplier,
                 StatusBarColorController statusBarColorController,
                 ScreenOrientationProvider screenOrientationProvider,
-                Supplier<NotificationManagerProxy> notificationManagerProxySupplier);
+                Supplier<NotificationManagerProxy> notificationManagerProxySupplier,
+                ObservableSupplier<TabContentManager> tabContentManagerSupplier,
+                CompositorViewHolder.Initializer compositorViewHolderInitializer);
     }
 
     public ChromeActivityCommonsModule(ChromeActivity activity,
@@ -103,7 +108,9 @@
             Supplier<Boolean> isPromotableToTabSupplier,
             StatusBarColorController statusBarColorController,
             ScreenOrientationProvider screenOrientationProvider,
-            Supplier<NotificationManagerProxy> notificationManagerProxySupplier) {
+            Supplier<NotificationManagerProxy> notificationManagerProxySupplier,
+            ObservableSupplier<TabContentManager> tabContentManagerSupplier,
+            CompositorViewHolder.Initializer compositorViewHolderInitializer) {
         mActivity = activity;
         mBottomSheetControllerSupplier = bottomSheetControllerSupplier;
         mTabModelSelectorSupplier = tabModelSelectorSupplier;
@@ -124,6 +131,8 @@
         mStatusBarColorController = statusBarColorController;
         mScreenOrientationProvider = screenOrientationProvider;
         mNotificationManagerProxySupplier = notificationManagerProxySupplier;
+        mTabContentManagerSupplier = tabContentManagerSupplier;
+        mCompositorViewHolderInitializer = compositorViewHolderInitializer;
     }
 
     @Provides
@@ -255,4 +264,14 @@
     public NotificationManagerProxy provideNotificationManagerProxy() {
         return mNotificationManagerProxySupplier.get();
     }
+
+    @Provides
+    public ObservableSupplier<TabContentManager> provideTabContentManagerSupplier() {
+        return mTabContentManagerSupplier;
+    }
+
+    @Provides
+    public CompositorViewHolder.Initializer provideCompositorViewHolderInitializer() {
+        return mCompositorViewHolderInitializer;
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/incognito/interstitial/IncognitoInterstitialCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/incognito/interstitial/IncognitoInterstitialCoordinator.java
index 8992c93..042ee8a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/incognito/interstitial/IncognitoInterstitialCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/incognito/interstitial/IncognitoInterstitialCoordinator.java
@@ -4,11 +4,15 @@
 
 package org.chromium.chrome.browser.incognito.interstitial;
 
+import android.text.style.StyleSpan;
 import android.view.View;
+import android.widget.TextView;
 
 import androidx.annotation.MainThread;
 
+import org.chromium.chrome.R;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
+import org.chromium.ui.text.SpanApplier;
 
 /**
  * The coordinator of the incognito interstitial along with IncognitoInterstitialDelegate are the
@@ -31,9 +35,25 @@
     @MainThread
     public IncognitoInterstitialCoordinator(
             View view, IncognitoInterstitialDelegate incognitoInterstitialDelegate) {
+        formatIncognitoInterstitialMessage(view);
         IncognitoInterstitialMediator mediator =
                 new IncognitoInterstitialMediator(incognitoInterstitialDelegate);
         PropertyModelChangeProcessor.create(
                 mediator.getModel(), view, IncognitoInterstitialViewBinder::bindView);
     }
+
+    private static void formatIncognitoInterstitialMessage(View incognitoInterstitialView) {
+        TextView incognitoInterstitialMessageView =
+                incognitoInterstitialView.findViewById(R.id.incognito_interstitial_message);
+
+        String incognitoInterstitialMessageText =
+                incognitoInterstitialMessageView.getText().toString();
+
+        incognitoInterstitialMessageView.setText(
+                SpanApplier.applySpans(incognitoInterstitialMessageText,
+                        new SpanApplier.SpanInfo(
+                                "<b1>", "</b1>", new StyleSpan(android.graphics.Typeface.BOLD)),
+                        new SpanApplier.SpanInfo(
+                                "<b2>", "</b2>", new StyleSpan(android.graphics.Typeface.BOLD))));
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
index f8c8947a..21f01d0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
@@ -991,7 +991,7 @@
 
         // TODO(crbug.com/1085812): Should be taking a fulll loaded LoadUrlParams.
         if (ReturnToChromeExperimentsUtil.willHandleLoadUrlWithPostDataFromStartSurface(
-                    url, transition, postDataType, postData)) {
+                    url, transition, postDataType, postData, mToolbarDataProvider.isIncognito())) {
             return;
         }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionProcessor.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionProcessor.java
index 6313a4d6fd..96f748f2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionProcessor.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionProcessor.java
@@ -83,7 +83,8 @@
         if (position != 0) return false;
 
         Tab activeTab = mTabSupplier.get();
-        if (activeTab == null || activeTab.isNativePage() || SadTab.isShowing(activeTab)) {
+        if (activeTab == null || !activeTab.isInitialized() || activeTab.isNativePage()
+                || SadTab.isShowing(activeTab)) {
             return false;
         }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/paint_preview/PaintPreviewHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/paint_preview/PaintPreviewHelper.java
index 04f63f4..2612be83 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/paint_preview/PaintPreviewHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/paint_preview/PaintPreviewHelper.java
@@ -9,6 +9,7 @@
 
 import org.chromium.base.ActivityState;
 import org.chromium.base.ApplicationStatus;
+import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.chrome.browser.flags.CachedFeatureFlags;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
@@ -20,6 +21,7 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.browser.toolbar.load_progress.LoadProgressCoordinator;
 import org.chromium.chrome.browser.util.ChromeAccessibilityUtil;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.base.WindowAndroid;
@@ -52,14 +54,15 @@
      * @param activity The ChromeActivity that corresponds to the tabModelSelector.
      * @param tabModelSelector The TabModelSelector to observe.
      */
-    public static void initialize(ChromeActivity<?> activity, TabModelSelector tabModelSelector) {
+    public static void initialize(ChromeActivity<?> activity, TabModelSelector tabModelSelector,
+            Supplier<LoadProgressCoordinator> progressBarCoordinatorSupplier) {
         if (!CachedFeatureFlags.isEnabled(ChromeFeatureList.PAINT_PREVIEW_SHOW_ON_STARTUP)) return;
 
         if (!MultiWindowUtils.getInstance().areMultipleChromeInstancesRunning(activity)) {
             sHasAttemptedToShowOnRestore = false;
         }
-        sWindowAndroidHelperMap.put(
-                activity.getWindowAndroid(), new PaintPreviewWindowAndroidHelper(activity));
+        sWindowAndroidHelperMap.put(activity.getWindowAndroid(),
+                new PaintPreviewWindowAndroidHelper(activity, progressBarCoordinatorSupplier));
 
         // TODO(crbug/1074428): verify this doesn't cause a memory leak if the user exits Chrome
         // prior to onTabStateInitialized being called.
@@ -95,6 +98,14 @@
         TabbedPaintPreviewPlayer player = TabbedPaintPreviewPlayer.get(tab);
         player.setBrowserVisibilityDelegate(
                 windowAndroidHelper.getBrowserControlsManager().getBrowserVisibilityDelegate());
+        player.setProgressSimulatorNeededCallback(
+                ()
+                        -> windowAndroidHelper.getLoadProgressCoordinator()
+                                   .simulateLoadProgressCompletion());
+        player.setProgressbarUpdatePreventionCallback(
+                (preventProgressbar)
+                        -> windowAndroidHelper.getLoadProgressCoordinator().setPreventUpdates(
+                                preventProgressbar));
         PageLoadMetrics.Observer observer = new PageLoadMetrics.Observer() {
             @Override
             public void onFirstMeaningfulPaint(WebContents webContents, long navigationId,
@@ -124,11 +135,14 @@
         private long mActivityCreationTime;
         private WindowAndroid mWindowAndroid;
         private BrowserControlsManager mBrowserControlsManager;
+        private Supplier<LoadProgressCoordinator> mProgressBarCoordinatorSupplier;
 
-        PaintPreviewWindowAndroidHelper(ChromeActivity<?> chromeActivity) {
+        PaintPreviewWindowAndroidHelper(ChromeActivity<?> chromeActivity,
+                Supplier<LoadProgressCoordinator> progressBarCoordinatorSupplier) {
             mWindowAndroid = chromeActivity.getWindowAndroid();
             mActivityCreationTime = chromeActivity.getOnCreateTimestampMs();
             mBrowserControlsManager = chromeActivity.getBrowserControlsManager();
+            mProgressBarCoordinatorSupplier = progressBarCoordinatorSupplier;
             ApplicationStatus.registerStateListenerForActivity(this, chromeActivity);
         }
 
@@ -136,6 +150,10 @@
             return mActivityCreationTime;
         }
 
+        LoadProgressCoordinator getLoadProgressCoordinator() {
+            return mProgressBarCoordinatorSupplier.get();
+        }
+
         BrowserControlsManager getBrowserControlsManager() {
             return mBrowserControlsManager;
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/account_picker/AccountPickerDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/account_picker/AccountPickerDelegate.java
index a91c396..5276543 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/account_picker/AccountPickerDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/account_picker/AccountPickerDelegate.java
@@ -173,7 +173,7 @@
      */
     public static void recordAccountConsistencyPromoAction(
             @AccountConsistencyPromoAction int promoAction) {
-        RecordHistogram.recordEnumeratedHistogram("Signin.AndroidAccountConsistencyPromoAction",
+        RecordHistogram.recordEnumeratedHistogram("Signin.AccountConsistencyPromoAction",
                 promoAction, AccountConsistencyPromoAction.MAX);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sync/settings/AccountManagementFragment.java b/chrome/android/java/src/org/chromium/chrome/browser/sync/settings/AccountManagementFragment.java
index 752c7f5..e1fc830 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/sync/settings/AccountManagementFragment.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/sync/settings/AccountManagementFragment.java
@@ -12,6 +12,7 @@
 import android.os.Bundle;
 import android.os.UserManager;
 
+import androidx.annotation.LayoutRes;
 import androidx.annotation.Nullable;
 import androidx.fragment.app.DialogFragment;
 import androidx.preference.Preference;
@@ -290,18 +291,20 @@
 
         accountsCategory.removeAll();
 
+        accountsCategory.addPreference(
+                createAccountPreference(AccountUtils.createAccountFromName(mSignedInAccountName)));
+        if (ChromeFeatureList.isEnabled(ChromeFeatureList.MOBILE_IDENTITY_CONSISTENCY)) {
+            accountsCategory.addPreference(
+                    createDividerPreference(R.layout.account_divider_preference));
+            accountsCategory.addPreference(createManageYourGoogleAccountPreference());
+            accountsCategory.addPreference(createDividerPreference(R.layout.divider_preference));
+        }
+
         List<Account> accounts = AccountManagerFacadeProvider.getInstance().tryGetGoogleAccounts();
-        for (int i = 0; i < accounts.size(); i++) {
-            Account account = accounts.get(i);
-            Preference pref = new Preference(getStyledContext());
-            pref.setLayoutResource(R.layout.account_management_account_row);
-            pref.setTitle(account.name);
-            pref.setIcon(mProfileDataCache.getProfileDataOrDefault(account.name).getImage());
-
-            pref.setOnPreferenceClickListener(
-                    preference -> SigninUtils.openSettingsForAccount(getActivity(), account));
-
-            accountsCategory.addPreference(pref);
+        for (Account account : accounts) {
+            if (!mSignedInAccountName.equals(account.name)) {
+                accountsCategory.addPreference(createAccountPreference(account));
+            }
         }
 
         if (!mProfile.isChild()) {
@@ -309,6 +312,39 @@
         }
     }
 
+    private Preference createAccountPreference(Account account) {
+        Preference accountPreference = new Preference(getStyledContext());
+        accountPreference.setLayoutResource(R.layout.account_management_account_row);
+        accountPreference.setTitle(account.name);
+        accountPreference.setIcon(
+                mProfileDataCache.getProfileDataOrDefault(account.name).getImage());
+
+        accountPreference.setOnPreferenceClickListener(SyncSettingsUtils.toOnClickListener(
+                this, () -> SigninUtils.openSettingsForAccount(getActivity(), account)));
+
+        return accountPreference;
+    }
+
+    private Preference createManageYourGoogleAccountPreference() {
+        Preference manageYourGoogleAccountPreference = new Preference(getStyledContext());
+        manageYourGoogleAccountPreference.setLayoutResource(
+                R.layout.account_management_account_row);
+        manageYourGoogleAccountPreference.setTitle(R.string.manage_your_google_account);
+        manageYourGoogleAccountPreference.setIcon(R.drawable.ic_google_services_48dp);
+        manageYourGoogleAccountPreference.setOnPreferenceClickListener(
+                SyncSettingsUtils.toOnClickListener(
+                        this, () -> SyncSettingsUtils.openGoogleMyAccount(getActivity())));
+
+        return manageYourGoogleAccountPreference;
+    }
+
+    private Preference createDividerPreference(@LayoutRes int layoutResId) {
+        Preference dividerPreference = new Preference(getStyledContext());
+        dividerPreference.setLayoutResource(layoutResId);
+
+        return dividerPreference;
+    }
+
     private ChromeBasePreference createAddAccountPreference() {
         ChromeBasePreference addAccountPreference = new ChromeBasePreference(getStyledContext());
         addAccountPreference.setLayoutResource(R.layout.account_management_account_row);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/ReturnToChromeExperimentsUtil.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/ReturnToChromeExperimentsUtil.java
index 16b7c49..c3a7447 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/ReturnToChromeExperimentsUtil.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/ReturnToChromeExperimentsUtil.java
@@ -23,7 +23,6 @@
 import org.chromium.chrome.browser.homepage.HomepageManager;
 import org.chromium.chrome.browser.locale.LocaleManager;
 import org.chromium.chrome.browser.tab.TabLaunchType;
-import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tasks.pseudotab.PseudoTab;
 import org.chromium.chrome.browser.util.ChromeAccessibilityUtil;
@@ -139,11 +138,13 @@
      *
      * @param url The URL to load.
      * @param transition The page transition type.
+     * @param incognito Whether to load URL in an incognito Tab.
      * @return true if we have handled the navigation, false otherwise.
      */
     public static boolean willHandleLoadUrlFromStartSurface(
-            String url, @PageTransition int transition) {
-        return willHandleLoadUrlWithPostDataFromStartSurface(url, transition, null, null);
+            String url, @PageTransition int transition, @Nullable Boolean incognito) {
+        return willHandleLoadUrlWithPostDataFromStartSurface(
+                url, transition, null, null, incognito);
     }
 
     /**
@@ -155,24 +156,33 @@
      * @param postDataType   postData type.
      * @param postData       POST data to include in the tab URL's request body, ex. bitmap when
      *         image search.
+     * @param incognito Whether to load URL in an incognito Tab. If null, the current tab model will
+     *         be used.
      * @return true if we have handled the navigation, false otherwise.
      */
     public static boolean willHandleLoadUrlWithPostDataFromStartSurface(String url,
             @PageTransition int transition, @Nullable String postDataType,
-            @Nullable byte[] postData) {
+            @Nullable byte[] postData, @Nullable Boolean incognito) {
         ChromeActivity chromeActivity = getActivityPresentingOverviewWithOmnibox();
         if (chromeActivity == null) return false;
 
         // Create a new unparented tab.
-        TabModel model = chromeActivity.getCurrentTabModel();
+        boolean incognitoParam;
+        if (incognito == null) {
+            incognitoParam = chromeActivity.getCurrentTabModel().isIncognito();
+        } else {
+            incognitoParam = incognito;
+        }
+
         LoadUrlParams params = new LoadUrlParams(url);
+        // TODO(https://crbug.com/1134187): This may no longer accurate.
         params.setTransitionType(transition | PageTransition.FROM_ADDRESS_BAR);
         if (!TextUtils.isEmpty(postDataType) && postData != null && postData.length != 0) {
             params.setVerbatimHeaders("Content-Type: " + postDataType);
             params.setPostData(ResourceRequestBody.createFromBytes(postData));
         }
 
-        chromeActivity.getTabCreator(model.isIncognito())
+        chromeActivity.getTabCreator(incognitoParam)
                 .createNewTab(params, TabLaunchType.FROM_START_SURFACE, null);
 
         if (transition == PageTransition.AUTO_BOOKMARK) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
index dbffdea2..f47d98236 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
@@ -1187,6 +1187,13 @@
     }
 
     /**
+     * @return The current {@link LoadProgressCoordinator}.
+     */
+    public LoadProgressCoordinator getProgressBarCoordinator() {
+        return mProgressBarCoordinator;
+    }
+
+    /**
      * Updates the current button states and calls appropriate abstract visibility methods, giving
      * inheriting classes the chance to update the button visuals as well.
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/load_progress/LoadProgressCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/load_progress/LoadProgressCoordinator.java
index c2c24e4..b5e5b02 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/load_progress/LoadProgressCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/load_progress/LoadProgressCoordinator.java
@@ -31,4 +31,20 @@
         mPropertyModelChangeProcessor = PropertyModelChangeProcessor.create(
                 mModel, mProgressBarView, mLoadProgressViewBinder::bind);
     }
+
+    /**
+     * Simulates progressbar being filled over a short time.
+     */
+    public void simulateLoadProgressCompletion() {
+        mMediator.simulateLoadProgressCompletion();
+    }
+
+    /**
+     * Whether progressbar should be updated on tab progress changes.
+     * @param preventUpdates If true, prevents updating progressbar when the tab it's observing
+     *                       is being loaded.
+     */
+    public void setPreventUpdates(boolean preventUpdates) {
+        mMediator.setPreventUpdates(preventUpdates);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/load_progress/LoadProgressMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/load_progress/LoadProgressMediator.java
index ca487cb..506789e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/load_progress/LoadProgressMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/load_progress/LoadProgressMediator.java
@@ -24,6 +24,7 @@
     private final PropertyModel mModel;
     private final EmptyTabObserver mTabObserver;
     private final LoadProgressSimulator mLoadProgressSimulator;
+    private boolean mPreventUpdates;
 
     public LoadProgressMediator(ActivityTabProvider activityTabProvider, PropertyModel model) {
         mModel = model;
@@ -75,7 +76,7 @@
                 // If loading both started and finished before we swapped in the WebContents, we
                 // won't get any load progress signals. Otherwise, we should receive at least one
                 // real signal so we don't need to simulate them.
-                if (didStartLoad && didFinishLoad) {
+                if (didStartLoad && didFinishLoad && !mPreventUpdates) {
                     mLoadProgressSimulator.start();
                 }
             }
@@ -93,8 +94,25 @@
         onNewTabObserved(activityTabProvider.get());
     }
 
+    /**
+     * Simulates progressbar being filled over a short time.
+     */
+    void simulateLoadProgressCompletion() {
+        mLoadProgressSimulator.start();
+    }
+
+    /**
+     * Whether progressbar should be updated on tab progress changes.
+     * @param preventUpdates If true, prevents updating progressbar when the tab it's observing
+     *                       is being loaded.
+     */
+    void setPreventUpdates(boolean preventUpdates) {
+        mPreventUpdates = preventUpdates;
+    }
+
     private void onNewTabObserved(Tab tab) {
         if (tab == null) return;
+
         if (tab.isLoading()) {
             if (NativePageFactory.isNativePageUrl(tab.getUrlString(), tab.isIncognito())) {
                 finishLoadProgress(false);
@@ -108,10 +126,14 @@
     }
 
     private void startLoadProgress() {
+        if (mPreventUpdates) return;
+
         mModel.set(LoadProgressProperties.COMPLETION_STATE, CompletionState.UNFINISHED);
     }
 
     private void updateLoadProgress(float progress) {
+        if (mPreventUpdates) return;
+
         progress = Math.max(progress, MINIMUM_LOAD_PROGRESS);
         mModel.set(LoadProgressProperties.PROGRESS, progress);
         if (MathUtils.areFloatsEqual(progress, 1)) finishLoadProgress(true);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/video_tutorials/NewTabPageVideoIPHManager.java b/chrome/android/java/src/org/chromium/chrome/browser/video_tutorials/NewTabPageVideoIPHManager.java
index 5fd8efd..7fe153c7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/video_tutorials/NewTabPageVideoIPHManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/video_tutorials/NewTabPageVideoIPHManager.java
@@ -4,6 +4,7 @@
 
 package org.chromium.chrome.browser.video_tutorials;
 
+import android.content.Context;
 import android.view.ViewStub;
 
 import org.chromium.base.Callback;
@@ -27,6 +28,7 @@
  * dismissed.
  */
 public class NewTabPageVideoIPHManager {
+    private Context mContext;
     private Tracker mTracker;
     private VideoIPHCoordinator mVideoIPHCoordinator;
     private VideoTutorialService mVideoTutorialService;
@@ -39,6 +41,7 @@
     public NewTabPageVideoIPHManager(ViewStub viewStub, Profile profile) {
         if (!ChromeFeatureList.isEnabled(ChromeFeatureList.VIDEO_TUTORIALS)) return;
 
+        mContext = viewStub.getContext();
         mTracker = TrackerFactory.getTrackerForProfile(profile);
         ImageFetcher imageFetcher =
                 ImageFetcherFactory.createImageFetcher(ImageFetcherConfig.IN_MEMORY_WITH_DISK_CACHE,
@@ -76,6 +79,7 @@
         mTracker.notifyEvent(VideoTutorialIPHUtils.getClickEvent(tutorial.featureType));
 
         // Bring up the player and start playing the video.
+        VideoPlayerActivity.playVideoTutorial(mContext, tutorial);
     }
 
     private void onDismissIPH(Tutorial tutorial) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/video_tutorials/VideoPlayerActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/video_tutorials/VideoPlayerActivity.java
index 42bf587..1683438 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/video_tutorials/VideoPlayerActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/video_tutorials/VideoPlayerActivity.java
@@ -5,6 +5,8 @@
 package org.chromium.chrome.browser.video_tutorials;
 
 import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
 import android.os.Bundle;
 import android.util.Pair;
 import android.view.WindowManager;
@@ -69,4 +71,12 @@
         mCoordinator.destroy();
         super.onDestroy();
     }
+
+    /** Called to launch this activity to play a given video tutorial. */
+    static void playVideoTutorial(Context context, Tutorial tutorial) {
+        Intent intent = new Intent();
+        intent.setClass(context, VideoPlayerActivity.class);
+        intent.putExtra(VideoPlayerActivity.EXTRA_VIDEO_TUTORIAL, tutorial.featureType);
+        context.startActivity(intent);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/video_tutorials/VideoTutorialListActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/video_tutorials/VideoTutorialListActivity.java
new file mode 100644
index 0000000..f089e830
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/video_tutorials/VideoTutorialListActivity.java
@@ -0,0 +1,43 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.video_tutorials;
+
+import android.os.Bundle;
+
+import org.chromium.chrome.browser.SynchronousInitializationActivity;
+import org.chromium.chrome.browser.image_fetcher.ImageFetcher;
+import org.chromium.chrome.browser.image_fetcher.ImageFetcherConfig;
+import org.chromium.chrome.browser.image_fetcher.ImageFetcherFactory;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.video_tutorials.list.TutorialListCoordinator;
+import org.chromium.components.browser_ui.util.GlobalDiscardableReferencePool;
+
+/**
+ * Activity for displaying a list of video tutorials available to watch.
+ */
+public class VideoTutorialListActivity extends SynchronousInitializationActivity {
+    private TutorialListCoordinator mCoordinator;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.video_tutorial_list);
+
+        Profile profile = Profile.getLastUsedRegularProfile();
+        VideoTutorialService videoTutorialService =
+                VideoTutorialServiceFactory.getForProfile(profile);
+        ImageFetcher imageFetcher =
+                ImageFetcherFactory.createImageFetcher(ImageFetcherConfig.IN_MEMORY_WITH_DISK_CACHE,
+                        profile, GlobalDiscardableReferencePool.getReferencePool());
+        mCoordinator = VideoTutorialServiceFactory.createTutorialListCoordinator(
+                findViewById(R.id.recycler_view), videoTutorialService, imageFetcher,
+                this::onTutorialSelected);
+        findViewById(R.id.close_button).setOnClickListener(v -> finish());
+    }
+
+    private void onTutorialSelected(Tutorial tutorial) {
+        VideoPlayerActivity.playVideoTutorial(this, tutorial);
+    }
+}
\ No newline at end of file
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/ManageTrustedWebActivityDataActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/ManageTrustedWebActivityDataActivityTest.java
index 352eb1e..3c1e42e9 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/ManageTrustedWebActivityDataActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/ManageTrustedWebActivityDataActivityTest.java
@@ -4,8 +4,6 @@
 
 package org.chromium.chrome.browser.browserservices;
 
-import static junit.framework.Assert.assertTrue;
-
 import static org.chromium.chrome.browser.browserservices.TrustedWebActivityTestUtil.createSession;
 import static org.chromium.chrome.browser.browserservices.TrustedWebActivityTestUtil.spoofVerification;
 
@@ -25,10 +23,12 @@
 
 import org.chromium.base.ApplicationStatus;
 import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.DisabledTest;
+import org.chromium.base.test.util.Matchers;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.components.webapk.lib.client.WebApkValidator;
+import org.chromium.content_public.browser.test.util.Criteria;
+import org.chromium.content_public.browser.test.util.CriteriaHelper;
 import org.chromium.webapk.lib.common.WebApkConstants;
 
 import java.util.concurrent.TimeoutException;
@@ -44,13 +44,12 @@
 public class ManageTrustedWebActivityDataActivityTest {
     private static final String SETTINGS_ACTIVITY_NAME =
             "org.chromium.chrome.browser.settings.SettingsActivity";
-    private static final String WEBAPK_TEST_URL = "https://padr31.github.io";
+    private static final String WEBAPK_TEST_URL = "https://www.example.com";
     private static final String TEST_PACKAGE_NAME =
             InstrumentationRegistry.getTargetContext().getPackageName();
 
     @Test
     @MediumTest
-    @DisabledTest(message = "https://crbug.com/1131836")
     public void launchesWebApkSiteSettings() {
         Intent siteSettingsIntent =
                 createWebApkSiteSettingsIntent(TEST_PACKAGE_NAME, Uri.parse(WEBAPK_TEST_URL));
@@ -60,20 +59,30 @@
             launchSiteSettingsIntent(siteSettingsIntent);
 
             // Check settings activity is running.
-            boolean settingsActivityRunning = false;
-            for (Activity a : ApplicationStatus.getRunningActivities()) {
-                String activityName =
-                        a.getPackageManager().getActivityInfo(a.getComponentName(), 0).name;
-                if (activityName.equals(SETTINGS_ACTIVITY_NAME)) {
-                    settingsActivityRunning = true;
+            CriteriaHelper.pollUiThread(() -> {
+                try {
+                    Criteria.checkThat("Site settings activity was not launched",
+                            siteSettingsActivityRunning(), Matchers.is(true));
+                } catch (PackageManager.NameNotFoundException e) {
+                    e.printStackTrace();
                 }
-            }
-            assertTrue(settingsActivityRunning);
-        } catch (TimeoutException | PackageManager.NameNotFoundException e) {
+            });
+        } catch (TimeoutException e) {
             e.printStackTrace();
         }
     }
 
+    private boolean siteSettingsActivityRunning() throws PackageManager.NameNotFoundException {
+        for (Activity a : ApplicationStatus.getRunningActivities()) {
+            String activityName =
+                    a.getPackageManager().getActivityInfo(a.getComponentName(), 0).name;
+            if (activityName.equals(SETTINGS_ACTIVITY_NAME)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private static Intent createWebApkSiteSettingsIntent(String packageName, Uri uri) {
         // CustomTabsIntent builder is used just to put in the session extras.
         CustomTabsIntent.Builder builder =
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/RunningInChromeTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/RunningInChromeTest.java
index 31c1a04..7a97bfc 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/RunningInChromeTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/RunningInChromeTest.java
@@ -79,21 +79,25 @@
 
     private final TestRule mModuleOverridesRule = new ModuleOverridesRule().setOverride(
             ChromeActivityCommonsModule.Factory.class,
-            (activity, bottomSheetControllerSupplier, tabModelSelectorSupplier,
-                    browserControlsManager, browserControlsVisibilityManager, browserControlsSizer,
-                    fullscreenManager, layoutManagerSupplier, lifecycleDispatcher,
-                    snackbarManagerSupplier, activityTabProvider, tabContentManager,
-                    activityWindowAndroid, compositorViewHolderSupplier, tabCreatorManager,
-                    tabCreatorSupplier, isPromotableToTabSupplier, statusBarColorController,
-                    screenOrientationProvider, notificationManagerProxySupplier) -> {
-                return new ChromeActivityCommonsModule(activity, bottomSheetControllerSupplier,
+            (activity, bottomSheetController, tabModelSelectorSupplier, browserControlsManager,
+                    browserControlsVisibilityManager, browserControlsSizer, fullscreenManager,
+                    layoutManagerSupplier, lifecycleDispatcher, snackbarManagerSupplier,
+                    activityTabProvider, tabContentManager, activityWindowAndroid,
+                    compositorViewHolderSupplier, tabCreatorManager, tabCreatorSupplier,
+                    isPromotableToTabSupplier, statusBarColorController, screenOrientationProvider,
+                    notificationManagerProxySupplier, tabContentManagerSupplier,
+                    compositorViewHolderInitializer) -> {
+                return new ChromeActivityCommonsModule(activity, bottomSheetController,
                         tabModelSelectorSupplier, browserControlsManager,
                         browserControlsVisibilityManager, browserControlsSizer, fullscreenManager,
                         layoutManagerSupplier, lifecycleDispatcher, snackbarManagerSupplier,
                         activityTabProvider, tabContentManager, activityWindowAndroid,
                         compositorViewHolderSupplier, tabCreatorManager, tabCreatorSupplier,
                         isPromotableToTabSupplier, statusBarColorController,
-                        screenOrientationProvider, () -> mMockNotificationManager);
+                        screenOrientationProvider,
+                        ()
+                                -> mMockNotificationManager,
+                        tabContentManagerSupplier, compositorViewHolderInitializer);
             });
 
     @Rule
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionUnitTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionUnitTest.java
index 0b1e045..8edacc5 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionUnitTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionUnitTest.java
@@ -27,6 +27,7 @@
 import org.mockito.MockitoAnnotations;
 
 import org.chromium.base.ContextUtils;
+import org.chromium.base.UserDataHost;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.UiThreadTest;
 import org.chromium.base.test.util.Batch;
@@ -40,6 +41,7 @@
 import org.chromium.chrome.browser.omnibox.suggestions.basic.SuggestionViewProperties;
 import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
 import org.chromium.chrome.browser.share.ShareDelegate;
+import org.chromium.chrome.browser.tab.SadTab;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.ui.favicon.LargeIconBridge;
 import org.chromium.chrome.test.util.browser.Features;
@@ -87,6 +89,9 @@
     private Tab mTab;
 
     @Mock
+    private SadTab mSadTab;
+
+    @Mock
     private OmniboxSuggestion mWhatYouTypedSuggestion;
 
     @Mock
@@ -118,6 +123,7 @@
 
     // The original (real) ClipboardManager to be restored after a test run.
     private ClipboardManager mOldClipboardManager;
+    private UserDataHost mUserDataHost;
 
     @Before
     public void setUp() {
@@ -126,21 +132,10 @@
 
         TemplateUrlServiceFactory.setInstanceForTesting(mTemplateUrlService);
 
-        doReturn(mTestUrl).when(mTab).getUrl();
-        doReturn(TEST_TITLE).when(mTab).getTitle();
-        doReturn(false).when(mTab).isNativePage();
-        doReturn(false).when(mTab).isInitialized();
-
-        doReturn(OmniboxSuggestionType.URL_WHAT_YOU_TYPED).when(mWhatYouTypedSuggestion).getType();
-        doReturn(mTestUrl).when(mWhatYouTypedSuggestion).getUrl();
-
-        doReturn(OmniboxSuggestionType.SEARCH_WHAT_YOU_TYPED).when(mSearchSuggestion).getType();
-        doReturn(mFoobarSearchUrl).when(mSearchSuggestion).getUrl();
-        doReturn(FOOBAR_SEARCH_TERMS).when(mSearchSuggestion).getFillIntoEdit();
-
-        doReturn(OmniboxSuggestionType.SEARCH_HISTORY).when(mOtherSuggestion).getType();
-
         TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mUserDataHost = new UserDataHost();
+            mUserDataHost.setUserData(SadTab.class, mSadTab);
+
             mOldClipboardManager =
                     Clipboard.getInstance().overrideClipboardManagerForTesting(mClipboardManager);
 
@@ -150,6 +145,23 @@
                     mSuggestionHost, mUrlBarDelegate,
                     () -> mIconBridge, () -> mTab, () -> mShareDelegate);
         });
+
+        doReturn(mTestUrl).when(mTab).getUrl();
+        doReturn(TEST_TITLE).when(mTab).getTitle();
+        doReturn(false).when(mTab).isNativePage();
+        doReturn(true).when(mTab).isInitialized();
+
+        // Simulate that all our test tabs are never 'sad'.
+        doReturn(mUserDataHost).when(mTab).getUserDataHost();
+        doReturn(false).when(mSadTab).isShowing();
+        doReturn(OmniboxSuggestionType.URL_WHAT_YOU_TYPED).when(mWhatYouTypedSuggestion).getType();
+        doReturn(mTestUrl).when(mWhatYouTypedSuggestion).getUrl();
+
+        doReturn(OmniboxSuggestionType.SEARCH_WHAT_YOU_TYPED).when(mSearchSuggestion).getType();
+        doReturn(mFoobarSearchUrl).when(mSearchSuggestion).getUrl();
+        doReturn(FOOBAR_SEARCH_TERMS).when(mSearchSuggestion).getFillIntoEdit();
+
+        doReturn(OmniboxSuggestionType.SEARCH_HISTORY).when(mOtherSuggestion).getType();
     }
 
     @After
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountPickerBottomSheetTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountPickerBottomSheetTest.java
index e8e744f..a75ee5e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountPickerBottomSheetTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountPickerBottomSheetTest.java
@@ -190,7 +190,7 @@
     @MediumTest
     public void testExpandedSheetShowsWhenBackpressingOnIncognitoInterstitial() {
         buildAndShowExpandedBottomSheet();
-        onView(withText(R.string.signin_go_incognito)).perform(click());
+        onView(withText(R.string.signin_incognito_button)).perform(click());
         onView(isRoot()).perform(pressBack());
 
         onView(withText(R.string.signin_account_picker_dialog_title)).check(matches(isDisplayed()));
@@ -433,7 +433,7 @@
     @MediumTest
     public void testIncognitoOptionShownOnExpandedSheet() {
         buildAndShowExpandedBottomSheet();
-        onView(withText(R.string.signin_go_incognito)).perform(click());
+        onView(withText(R.string.signin_incognito_button)).perform(click());
         checkIncognitoInterstitialSheet();
     }
 
@@ -441,7 +441,7 @@
     @MediumTest
     public void testLearnMoreButtonOnIncognitoInterstitial() {
         buildAndShowExpandedBottomSheet();
-        onView(withText(R.string.signin_go_incognito)).perform(click());
+        onView(withText(R.string.signin_incognito_button)).perform(click());
         onView(withId(R.id.incognito_interstitial_learn_more)).perform(click());
         verify(mIncognitoInterstitialDelegateMock).openLearnMorePage();
     }
@@ -450,7 +450,7 @@
     @MediumTest
     public void testContinueButtonOnIncognitoInterstitial() {
         buildAndShowExpandedBottomSheet();
-        onView(withText(R.string.signin_go_incognito)).perform(click());
+        onView(withText(R.string.signin_incognito_button)).perform(click());
         onView(withId(R.id.incognito_interstitial_continue_button)).perform(click());
         verify(mIncognitoInterstitialDelegateMock).openCurrentUrlInIncognitoTab();
     }
@@ -465,7 +465,7 @@
         onView(withId(R.id.account_picker_horizontal_divider)).check(matches(not(isDisplayed())));
         onView(withId(R.id.account_picker_account_list)).check(matches(not(isDisplayed())));
         onView(withId(R.id.incognito_interstitial_bottom_sheet_view)).check(matches(isDisplayed()));
-        onView(withText(R.string.incognito_interstitial_message)).check(matches(isDisplayed()));
+        onView(withId(R.id.incognito_interstitial_message)).check(matches(isDisplayed()));
         onView(withId(R.id.incognito_interstitial_learn_more)).check(matches(isDisplayed()));
         onView(withId(R.id.incognito_interstitial_continue_button)).check(matches(isDisplayed()));
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkInitializationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkInitializationTest.java
index 9ccfc7f..189c0774 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkInitializationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkInitializationTest.java
@@ -92,7 +92,8 @@
                     snackbarManagerSupplier, activityTabProvider, tabContentManager,
                     activityWindowAndroid, compositorViewHolderSupplier, tabCreatorManager,
                     tabCreatorSupplier, isPromotableToTabSupplier, statusBarColorController,
-                    screenOrientationProvider, notificationManagerProxySupplier) -> {
+                    screenOrientationProvider, notificationManagerProxySupplier,
+                    tabContentManagerSupplier, compositorViewHolderInitializer) -> {
                 mTrackingActivityLifecycleDispatcher.init(lifecycleDispatcher);
                 return new ChromeActivityCommonsModule(activity, bottomSheetControllerSupplier,
                         tabModelSelectorSupplier, browserControlsManager,
@@ -101,7 +102,8 @@
                         snackbarManagerSupplier, activityTabProvider, tabContentManager,
                         activityWindowAndroid, compositorViewHolderSupplier, tabCreatorManager,
                         tabCreatorSupplier, isPromotableToTabSupplier, statusBarColorController,
-                        screenOrientationProvider, notificationManagerProxySupplier);
+                        screenOrientationProvider, notificationManagerProxySupplier,
+                        tabContentManagerSupplier, compositorViewHolderInitializer);
             });
 
     private final WebApkActivityTestRule mActivityRule = new WebApkActivityTestRule();
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index e1240aa..7791275 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-87.0.4277.0_rc-r1-merged.afdo.bz2
+chromeos-chrome-amd64-87.0.4278.0_rc-r1-merged.afdo.bz2
diff --git a/chrome/android/webapk/strings/android_webapk_strings_grd/IDS_NO_SUPPORT_FOR_MANAGE_SPACE.png.sha1 b/chrome/android/webapk/strings/android_webapk_strings_grd/IDS_NO_SUPPORT_FOR_MANAGE_SPACE.png.sha1
deleted file mode 100644
index 3a924d2..0000000
--- a/chrome/android/webapk/strings/android_webapk_strings_grd/IDS_NO_SUPPORT_FOR_MANAGE_SPACE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-27bfe4a6c6d5159b982fb1246904e72f9149db09
\ No newline at end of file
diff --git a/chrome/android/webapk/strings/android_webapk_strings_grd/IDS_SITE_SETTINGS_LONG_LABEL.png.sha1 b/chrome/android/webapk/strings/android_webapk_strings_grd/IDS_SITE_SETTINGS_LONG_LABEL.png.sha1
deleted file mode 100644
index f721993..0000000
--- a/chrome/android/webapk/strings/android_webapk_strings_grd/IDS_SITE_SETTINGS_LONG_LABEL.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-ac7345c9eee8154458660df14df05782b2678ba8
\ No newline at end of file
diff --git a/chrome/android/webapk/strings/android_webapk_strings_grd/IDS_SITE_SETTINGS_SHORT_LABEL.png.sha1 b/chrome/android/webapk/strings/android_webapk_strings_grd/IDS_SITE_SETTINGS_SHORT_LABEL.png.sha1
deleted file mode 100644
index f721993..0000000
--- a/chrome/android/webapk/strings/android_webapk_strings_grd/IDS_SITE_SETTINGS_SHORT_LABEL.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-ac7345c9eee8154458660df14df05782b2678ba8
\ No newline at end of file
diff --git a/chrome/app/chromium_strings.grd b/chrome/app/chromium_strings.grd
index 95de57f..5f901f7 100644
--- a/chrome/app/chromium_strings.grd
+++ b/chrome/app/chromium_strings.grd
@@ -315,6 +315,16 @@
           Chromium is unresponsive. Relaunch now?
         </message>
       </if>
+      <!-- Sharing messages -->
+      <message name="IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES" desc="The label to be shown as a help text of the dialog when user click on a phone number, if there are no phones to choose from.">
+        To send a number from here to your Android phone, sign in to Chromium on both devices.
+      </message>
+      <message name="IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES_ORIGIN" desc="The label to be shown as a help text of the dialog when user click on a phone number, if there are no phones to choose from.">
+        To send a number from <ph name="ORIGIN">$1<ex>www.google.com</ex></ph> to your Android phone, sign in to Chromium on both devices.
+      </message>
+      <message name="IDS_BROWSER_SHARING_ERROR_DIALOG_TEXT_DEVICE_NOT_FOUND" desc="The text to be shown on the dialog when an error occurred because the device is not synced.">
+        Make sure you are signed in to Chromium on your <ph name="TARGET_DEVICE_NAME">$1<ex>Pixel XL</ex></ph> and then try sending again.
+      </message>
       <!-- Uninstall messages -->
       <if expr="is_win">
         <message name="IDS_UNINSTALL_CLOSE_APP" desc="Message to user when uninstall detects other app instance running">
diff --git a/chrome/app/chromium_strings_grd/IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES.png.sha1 b/chrome/app/chromium_strings_grd/IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES.png.sha1
new file mode 100644
index 0000000..adb4334
--- /dev/null
+++ b/chrome/app/chromium_strings_grd/IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES.png.sha1
@@ -0,0 +1 @@
+d528af52ab90ae596b6f923a54528c4d49faf902
\ No newline at end of file
diff --git a/chrome/app/chromium_strings_grd/IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES_ORIGIN.png.sha1 b/chrome/app/chromium_strings_grd/IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES_ORIGIN.png.sha1
new file mode 100644
index 0000000..bd27352
--- /dev/null
+++ b/chrome/app/chromium_strings_grd/IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES_ORIGIN.png.sha1
@@ -0,0 +1 @@
+7b7f07346b26f7e724b5c20a23e8f7e734f17fb6
\ No newline at end of file
diff --git a/chrome/app/chromium_strings_grd/IDS_BROWSER_SHARING_ERROR_DIALOG_TEXT_DEVICE_NOT_FOUND.png.sha1 b/chrome/app/chromium_strings_grd/IDS_BROWSER_SHARING_ERROR_DIALOG_TEXT_DEVICE_NOT_FOUND.png.sha1
new file mode 100644
index 0000000..f4b38c8d
--- /dev/null
+++ b/chrome/app/chromium_strings_grd/IDS_BROWSER_SHARING_ERROR_DIALOG_TEXT_DEVICE_NOT_FOUND.png.sha1
@@ -0,0 +1 @@
+6f6ccd9ea2db3423ce93c0d012571fa1a3baea40
\ No newline at end of file
diff --git a/chrome/app/google_chrome_strings.grd b/chrome/app/google_chrome_strings.grd
index d2f3845e..4e1916ad 100644
--- a/chrome/app/google_chrome_strings.grd
+++ b/chrome/app/google_chrome_strings.grd
@@ -321,6 +321,16 @@
           Google Chrome is unresponsive. Relaunch now?
         </message>
       </if>
+      <!-- Sharing messages -->
+      <message name="IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES" desc="The label to be shown as a help text of the dialog when user click on a phone number, if there are no phones to choose from.">
+        To send a number from here to your Android phone, sign in to Chrome on both devices.
+      </message>
+      <message name="IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES_ORIGIN" desc="The label to be shown as a help text of the dialog when user click on a phone number, if there are no phones to choose from.">
+        To send a number from <ph name="ORIGIN">$1<ex>www.google.com</ex></ph> to your Android phone, sign in to Chrome on both devices.
+      </message>
+      <message name="IDS_BROWSER_SHARING_ERROR_DIALOG_TEXT_DEVICE_NOT_FOUND" desc="The text to be shown on the dialog when an error occurred because the device is not synced.">
+        Make sure you are signed in to Chrome on your <ph name="TARGET_DEVICE_NAME">$1<ex>Pixel XL</ex></ph> and then try sending again.
+      </message>
       <!-- Uninstall messages -->
       <if expr="is_win">
         <message name="IDS_UNINSTALL_CLOSE_APP" desc="Message to user when uninstall detects other app instance running">
diff --git a/chrome/app/google_chrome_strings_grd/IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES.png.sha1 b/chrome/app/google_chrome_strings_grd/IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES.png.sha1
new file mode 100644
index 0000000..e5e725d4
--- /dev/null
+++ b/chrome/app/google_chrome_strings_grd/IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES.png.sha1
@@ -0,0 +1 @@
+7c00c91b09b2dafd8f53247e6c935f3d7970b548
\ No newline at end of file
diff --git a/chrome/app/google_chrome_strings_grd/IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES_ORIGIN.png.sha1 b/chrome/app/google_chrome_strings_grd/IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES_ORIGIN.png.sha1
new file mode 100644
index 0000000..4b5bf1d
--- /dev/null
+++ b/chrome/app/google_chrome_strings_grd/IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES_ORIGIN.png.sha1
@@ -0,0 +1 @@
+d13169c130e5cb1620a665c8a13df8e2a377c385
\ No newline at end of file
diff --git a/chrome/app/google_chrome_strings_grd/IDS_BROWSER_SHARING_ERROR_DIALOG_TEXT_DEVICE_NOT_FOUND.png.sha1 b/chrome/app/google_chrome_strings_grd/IDS_BROWSER_SHARING_ERROR_DIALOG_TEXT_DEVICE_NOT_FOUND.png.sha1
new file mode 100644
index 0000000..280984e
--- /dev/null
+++ b/chrome/app/google_chrome_strings_grd/IDS_BROWSER_SHARING_ERROR_DIALOG_TEXT_DEVICE_NOT_FOUND.png.sha1
@@ -0,0 +1 @@
+16b55cb1813b9cf8eb3864bddd0996be809abe69
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index d976981..2da8bfe 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -137,6 +137,8 @@
     "autofill/autocomplete_history_manager_factory.h",
     "autofill/autofill_gstatic_reader.cc",
     "autofill/autofill_gstatic_reader.h",
+    "autofill/autofill_offer_manager_factory.cc",
+    "autofill/autofill_offer_manager_factory.h",
     "autofill/autofill_profile_validator_factory.cc",
     "autofill/autofill_profile_validator_factory.h",
     "autofill/personal_data_manager_factory.cc",
@@ -292,8 +294,8 @@
     "component_updater/file_type_policies_component_installer.h",
     "component_updater/first_party_sets_component_installer.cc",
     "component_updater/first_party_sets_component_installer.h",
-    "component_updater/floc_blocklist_component_installer.cc",
-    "component_updater/floc_blocklist_component_installer.h",
+    "component_updater/floc_component_installer.cc",
+    "component_updater/floc_component_installer.h",
     "component_updater/games_component_installer.cc",
     "component_updater/games_component_installer.h",
     "component_updater/mei_preload_component_installer.cc",
@@ -4786,7 +4788,7 @@
     }
   }
 
-  if (is_desktop_linux) {
+  if (is_linux) {
     # Desktop linux, doesn't count ChromeOS.
     sources += [
       "download/download_status_updater_linux.cc",
@@ -4865,7 +4867,7 @@
     ]
   }
 
-  if (is_win || is_mac || is_desktop_linux) {
+  if (is_win || is_mac || is_linux) {
     sources += [
       "browser_switcher/alternative_browser_driver.h",
       "browser_switcher/browser_switcher_navigation_throttle.cc",
@@ -4884,7 +4886,7 @@
     if (is_win) {
       sources += [ "browser_switcher/alternative_browser_driver_win.cc" ]
     }
-    if (is_mac || is_desktop_linux) {
+    if (is_mac || is_linux) {
       sources += [ "browser_switcher/alternative_browser_driver_posix.cc" ]
     }
   }
@@ -6095,7 +6097,7 @@
     if (is_chromeos) {
       sources += [ "net/nss_context_chromeos.cc" ]
     }
-    if (is_desktop_linux) {
+    if (is_linux) {
       sources += [ "net/nss_context_linux.cc" ]
     }
   }
@@ -6241,7 +6243,7 @@
     }
   }
 
-  if (is_win || is_mac || is_desktop_linux || is_chromeos) {
+  if (is_win || is_mac || is_linux || is_chromeos) {
     deps += [
       "//chrome/browser/resources/discards:discards_resources_gen",
       "//chrome/browser/resources/gaia_auth_host:modulize",
diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS
index a0f3818..202397b9 100644
--- a/chrome/browser/DEPS
+++ b/chrome/browser/DEPS
@@ -20,6 +20,7 @@
   "+chrome/browser/preferences/android",
   "+chrome/browser/profiles/android/jni_headers",
   "+chrome/browser/util/android",
+  "+chrome/browser/version",
   "+chrome/browser/video_tutorials",
   "+chrome/credential_provider/common",
   "+chrome/grit",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 78a8218..b04778c 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -1194,6 +1194,43 @@
         },
 };
 
+const FeatureEntry::FeatureVariation kOmniboxBookmarkPathsVariations[] = {
+    {
+        "Default UI (Title - URL)",
+        (FeatureEntry::FeatureParam[]){},
+        0,
+        nullptr,
+    },
+    {
+        "Replace title (Path/Title - URL)",
+        (FeatureEntry::FeatureParam[]){
+            {"OmniboxBookmarkPathsUiReplaceTitle", "true"}},
+        1,
+        nullptr,
+    },
+    {
+        "Replace URL (Title - Path)",
+        (FeatureEntry::FeatureParam[]){
+            {"OmniboxBookmarkPathsUiReplaceUrl", "true"}},
+        1,
+        nullptr,
+    },
+    {
+        "Append after title (Title : Path - URL)",
+        (FeatureEntry::FeatureParam[]){
+            {"OmniboxBookmarkPathsUiAppendAfterTitle", "true"}},
+        1,
+        nullptr,
+    },
+    {
+        "Dynamic Replace URL (Title - Path|URL)",
+        (FeatureEntry::FeatureParam[]){
+            {"OmniboxBookmarkPathsUiDynamicReplaceUrl", "true"}},
+        1,
+        nullptr,
+    },
+};
+
 #endif  // defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_MAC) ||
         // defined(OS_WIN)
 
@@ -3985,6 +4022,11 @@
          omnibox::kRichAutocompletion,
          kOmniboxRichAutocompletionPromisingVariations,
          "OmniboxBundledExperimentV1")},
+    {"omnibox-bookmark-paths", flag_descriptions::kOmniboxBookmarkPathsName,
+     flag_descriptions::kOmniboxBookmarkPathsDescription, kOsDesktop,
+     FEATURE_WITH_PARAMS_VALUE_TYPE(omnibox::kBookmarkPaths,
+                                    kOmniboxBookmarkPathsVariations,
+                                    "OmniboxBundledExperimentV1")},
 #endif  // defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_MAC) ||
         // defined(OS_WIN)
 
diff --git a/chrome/browser/android/webapk/webapk_installer.cc b/chrome/browser/android/webapk/webapk_installer.cc
index 4aa5bca..c9eb1d1 100644
--- a/chrome/browser/android/webapk/webapk_installer.cc
+++ b/chrome/browser/android/webapk/webapk_installer.cc
@@ -478,7 +478,8 @@
       base::android::ConvertUTF8ToJavaString(env, token);
 
   if (task_type_ == WebApkInstaller::INSTALL) {
-    webapk::TrackRequestTokenDuration(install_duration_timer_->Elapsed());
+    webapk::TrackRequestTokenDuration(install_duration_timer_->Elapsed(),
+                                      package_name);
     base::android::ScopedJavaLocalRef<jobject> java_primary_icon =
         gfx::ConvertToJavaBitmap(&install_primary_icon_);
     Java_WebApkInstaller_installWebApkAsync(
diff --git a/chrome/browser/android/webapk/webapk_metrics.cc b/chrome/browser/android/webapk/webapk_metrics.cc
index d4b5b0f4..07ee9f0c 100644
--- a/chrome/browser/android/webapk/webapk_metrics.cc
+++ b/chrome/browser/android/webapk/webapk_metrics.cc
@@ -5,15 +5,19 @@
 #include "chrome/browser/android/webapk/webapk_metrics.h"
 
 #include "base/metrics/histogram_macros.h"
-#include "base/time/time.h"
+#include "base/strings/string_util.h"
 
 namespace webapk {
 
 const char kInstallDurationHistogram[] = "WebApk.Install.InstallDuration";
 const char kInstallEventHistogram[] = "WebApk.Install.InstallEvent";
 
-void TrackRequestTokenDuration(base::TimeDelta delta) {
-  UMA_HISTOGRAM_TIMES("WebApk.Install.RequestTokenDuration", delta);
+void TrackRequestTokenDuration(base::TimeDelta delta,
+                               const std::string& webapk_package) {
+  if (base::EndsWith(webapk_package, "_v2"))
+    UMA_HISTOGRAM_MEDIUM_TIMES("WebApk.Install.RequestTokenDurationV2", delta);
+  else
+    UMA_HISTOGRAM_TIMES("WebApk.Install.RequestTokenDuration", delta);
 }
 
 void TrackInstallDuration(base::TimeDelta delta) {
diff --git a/chrome/browser/android/webapk/webapk_metrics.h b/chrome/browser/android/webapk/webapk_metrics.h
index c6888420..67d5acd 100644
--- a/chrome/browser/android/webapk/webapk_metrics.h
+++ b/chrome/browser/android/webapk/webapk_metrics.h
@@ -5,9 +5,9 @@
 #ifndef CHROME_BROWSER_ANDROID_WEBAPK_WEBAPK_METRICS_H_
 #define CHROME_BROWSER_ANDROID_WEBAPK_WEBAPK_METRICS_H_
 
-namespace base {
-class TimeDelta;
-}
+#include <string>
+
+#include "base/time/time.h"
 
 namespace webapk {
 
@@ -25,7 +25,8 @@
   INSTALL_EVENT_MAX = 5,
 };
 
-void TrackRequestTokenDuration(base::TimeDelta delta);
+void TrackRequestTokenDuration(base::TimeDelta delta,
+                               const std::string& webapk_package);
 void TrackInstallDuration(base::TimeDelta delta);
 void TrackInstallEvent(InstallEvent event);
 
diff --git a/chrome/browser/autofill/autofill_offer_manager_factory.cc b/chrome/browser/autofill/autofill_offer_manager_factory.cc
new file mode 100644
index 0000000..957450db
--- /dev/null
+++ b/chrome/browser/autofill/autofill_offer_manager_factory.cc
@@ -0,0 +1,41 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/autofill/autofill_offer_manager_factory.h"
+
+#include "base/memory/singleton.h"
+#include "chrome/browser/autofill/personal_data_manager_factory.h"
+#include "components/autofill/core/browser/payments/autofill_offer_manager.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+
+namespace autofill {
+
+// static
+AutofillOfferManager* AutofillOfferManagerFactory::GetForBrowserContext(
+    content::BrowserContext* context) {
+  return static_cast<AutofillOfferManager*>(
+      GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
+AutofillOfferManagerFactory* AutofillOfferManagerFactory::GetInstance() {
+  return base::Singleton<AutofillOfferManagerFactory>::get();
+}
+
+AutofillOfferManagerFactory::AutofillOfferManagerFactory()
+    : BrowserContextKeyedServiceFactory(
+          "AutofillOfferManager",
+          BrowserContextDependencyManager::GetInstance()) {
+  DependsOn(PersonalDataManagerFactory::GetInstance());
+}
+
+AutofillOfferManagerFactory::~AutofillOfferManagerFactory() = default;
+
+KeyedService* AutofillOfferManagerFactory::BuildServiceInstanceFor(
+    content::BrowserContext* context) const {
+  return new AutofillOfferManager(
+      PersonalDataManagerFactory::GetForBrowserContext(context));
+}
+
+}  // namespace autofill
diff --git a/chrome/browser/autofill/autofill_offer_manager_factory.h b/chrome/browser/autofill/autofill_offer_manager_factory.h
new file mode 100644
index 0000000..31e9c034
--- /dev/null
+++ b/chrome/browser/autofill/autofill_offer_manager_factory.h
@@ -0,0 +1,47 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_AUTOFILL_AUTOFILL_OFFER_MANAGER_FACTORY_H_
+#define CHROME_BROWSER_AUTOFILL_AUTOFILL_OFFER_MANAGER_FACTORY_H_
+
+#include "base/compiler_specific.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "components/keyed_service/core/keyed_service.h"
+
+namespace base {
+template <typename T>
+struct DefaultSingletonTraits;
+}
+
+namespace autofill {
+
+class AutofillOfferManager;
+
+// Singleton that owns all AutofillOfferManager and associates them with
+// Profiles.
+class AutofillOfferManagerFactory : public BrowserContextKeyedServiceFactory {
+ public:
+  AutofillOfferManagerFactory(const AutofillOfferManagerFactory&) = delete;
+  AutofillOfferManagerFactory& operator=(const AutofillOfferManagerFactory&) =
+      delete;
+
+  static AutofillOfferManager* GetForBrowserContext(
+      content::BrowserContext* context);
+
+  static AutofillOfferManagerFactory* GetInstance();
+
+ private:
+  friend struct base::DefaultSingletonTraits<AutofillOfferManagerFactory>;
+
+  AutofillOfferManagerFactory();
+  ~AutofillOfferManagerFactory() override;
+
+  // BrowserContextKeyedServiceFactory:
+  KeyedService* BuildServiceInstanceFor(
+      content::BrowserContext* profile) const override;
+};
+
+}  // namespace autofill
+
+#endif  // CHROME_BROWSER_AUTOFILL_AUTOFILL_OFFER_MANAGER_FACTORY_H_
diff --git a/chrome/browser/autofill/personal_data_manager_factory.cc b/chrome/browser/autofill/personal_data_manager_factory.cc
index a7d220ac..a5da2aa 100644
--- a/chrome/browser/autofill/personal_data_manager_factory.cc
+++ b/chrome/browser/autofill/personal_data_manager_factory.cc
@@ -41,6 +41,13 @@
 }
 
 // static
+PersonalDataManager* PersonalDataManagerFactory::GetForBrowserContext(
+    content::BrowserContext* context) {
+  return static_cast<PersonalDataManager*>(
+      GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
 PersonalDataManagerFactory* PersonalDataManagerFactory::GetInstance() {
   return base::Singleton<PersonalDataManagerFactory>::get();
 }
diff --git a/chrome/browser/autofill/personal_data_manager_factory.h b/chrome/browser/autofill/personal_data_manager_factory.h
index e962f15c..4f9108e 100644
--- a/chrome/browser/autofill/personal_data_manager_factory.h
+++ b/chrome/browser/autofill/personal_data_manager_factory.h
@@ -30,6 +30,11 @@
   // yet created.
   static PersonalDataManager* GetForProfile(Profile* profile);
 
+  // Returns the PersonalDataManager for |context|, creating it if it is not
+  // yet created.
+  static PersonalDataManager* GetForBrowserContext(
+      content::BrowserContext* context);
+
   static PersonalDataManagerFactory* GetInstance();
 
   static KeyedService* BuildPersonalDataManager(
diff --git a/chrome/browser/browser_process.h b/chrome/browser/browser_process.h
index e52ef49..b6fe1fe 100644
--- a/chrome/browser/browser_process.h
+++ b/chrome/browser/browser_process.h
@@ -56,6 +56,7 @@
 
 namespace federated_learning {
 class FlocBlocklistService;
+class FlocSortingLshClustersService;
 }
 
 namespace variations {
@@ -223,6 +224,11 @@
   virtual federated_learning::FlocBlocklistService*
   floc_blocklist_service() = 0;
 
+  // Returns the service providing versioned storage for a list of limit values
+  // for calculating the floc based on SortingLSH.
+  virtual federated_learning::FlocSortingLshClustersService*
+  floc_sorting_lsh_clusters_service() = 0;
+
   // Returns the service used to provide hints for what optimizations can be
   // performed on slow page loads.
   virtual optimization_guide::OptimizationGuideService*
diff --git a/chrome/browser/browser_process_impl.cc b/chrome/browser/browser_process_impl.cc
index f60af22..443348c 100644
--- a/chrome/browser/browser_process_impl.cc
+++ b/chrome/browser/browser_process_impl.cc
@@ -92,6 +92,7 @@
 #include "components/crash/core/common/crash_key.h"
 #include "components/federated_learning/floc_blocklist_service.h"
 #include "components/federated_learning/floc_constants.h"
+#include "components/federated_learning/floc_sorting_lsh_clusters_service.h"
 #include "components/gcm_driver/gcm_driver.h"
 #include "components/language/core/browser/pref_names.h"
 #include "components/metrics/metrics_pref_names.h"
@@ -1000,6 +1001,14 @@
   return floc_blocklist_service_.get();
 }
 
+federated_learning::FlocSortingLshClustersService*
+BrowserProcessImpl::floc_sorting_lsh_clusters_service() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!floc_sorting_lsh_clusters_service_)
+    CreateFlocSortingLshClustersService();
+  return floc_sorting_lsh_clusters_service_.get();
+}
+
 optimization_guide::OptimizationGuideService*
 BrowserProcessImpl::optimization_guide_service() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -1279,6 +1288,12 @@
       std::make_unique<federated_learning::FlocBlocklistService>();
 }
 
+void BrowserProcessImpl::CreateFlocSortingLshClustersService() {
+  DCHECK(!floc_sorting_lsh_clusters_service_);
+  floc_sorting_lsh_clusters_service_ =
+      std::make_unique<federated_learning::FlocSortingLshClustersService>();
+}
+
 void BrowserProcessImpl::CreateOptimizationGuideService() {
   DCHECK(!created_optimization_guide_service_);
   DCHECK(!optimization_guide_service_);
diff --git a/chrome/browser/browser_process_impl.h b/chrome/browser/browser_process_impl.h
index f22581e..8a8d8a82 100644
--- a/chrome/browser/browser_process_impl.h
+++ b/chrome/browser/browser_process_impl.h
@@ -171,6 +171,8 @@
   subresource_filter::RulesetService* subresource_filter_ruleset_service()
       override;
   federated_learning::FlocBlocklistService* floc_blocklist_service() override;
+  federated_learning::FlocSortingLshClustersService*
+  floc_sorting_lsh_clusters_service() override;
   optimization_guide::OptimizationGuideService* optimization_guide_service()
       override;
 
@@ -221,6 +223,7 @@
   void CreateSafeBrowsingService();
   void CreateSubresourceFilterRulesetService();
   void CreateFlocBlocklistService();
+  void CreateFlocSortingLshClustersService();
   void CreateOptimizationGuideService();
   void CreateStatusTray();
   void CreateBackgroundModeManager();
@@ -324,6 +327,9 @@
   std::unique_ptr<federated_learning::FlocBlocklistService>
       floc_blocklist_service_;
 
+  std::unique_ptr<federated_learning::FlocSortingLshClustersService>
+      floc_sorting_lsh_clusters_service_;
+
   bool created_optimization_guide_service_ = false;
   std::unique_ptr<optimization_guide::OptimizationGuideService>
       optimization_guide_service_;
diff --git a/chrome/browser/chrome_browser_interface_binders.cc b/chrome/browser/chrome_browser_interface_binders.cc
index 424dd0e..5cd46f8 100644
--- a/chrome/browser/chrome_browser_interface_binders.cc
+++ b/chrome/browser/chrome_browser_interface_binders.cc
@@ -196,6 +196,7 @@
 #include "chromeos/components/file_manager/file_manager_ui.h"
 #include "chromeos/components/telemetry_extension_ui/mojom/diagnostics_service.mojom.h"  // nogncheck crbug.com/1125897
 #include "chromeos/components/telemetry_extension_ui/mojom/probe_service.mojom.h"  // nogncheck crbug.com/1125897
+#include "chromeos/components/telemetry_extension_ui/mojom/system_events_service.mojom.h" // nogncheck crbug.com/1125897
 #include "chromeos/components/telemetry_extension_ui/telemetry_extension_ui.h"
 #endif
 
@@ -726,6 +727,9 @@
     RegisterWebUIControllerInterfaceBinder<
         chromeos::health::mojom::ProbeService, chromeos::TelemetryExtensionUI>(
         map);
+    RegisterWebUIControllerInterfaceBinder<
+        chromeos::health::mojom::SystemEventsService,
+        chromeos::TelemetryExtensionUI>(map);
   }
 #endif
 
diff --git a/chrome/browser/chromeos/child_accounts/secondary_account_consent_logger.cc b/chrome/browser/chromeos/child_accounts/secondary_account_consent_logger.cc
index 2040dade..12bb68d 100644
--- a/chrome/browser/chromeos/child_accounts/secondary_account_consent_logger.cc
+++ b/chrome/browser/chromeos/child_accounts/secondary_account_consent_logger.cc
@@ -92,6 +92,13 @@
   registry->RegisterStringPref(
       chromeos::prefs::kEduCoexistenceSecondaryAccountsInvalidationVersion,
       "iv2153049" /* default_value, the first invalidation version */);
+
+  // |kEduCoexistenceToSVersion| is derived from Google3 cl that introduced new
+  // ToS version. We use string here for the ToS version to be more future
+  // proof. In the future we might add a prefix to indicate the flow where the
+  // ToS were accepted (OOBE or Settings flow).
+  registry->RegisterStringPref(chromeos::prefs::kEduCoexistenceToSVersion,
+                               std::string());
 }
 
 // static
diff --git a/chrome/browser/chromeos/external_protocol_dialog.cc b/chrome/browser/chromeos/external_protocol_dialog.cc
index 54e18354..2fa87a24 100644
--- a/chrome/browser/chromeos/external_protocol_dialog.cc
+++ b/chrome/browser/chromeos/external_protocol_dialog.cc
@@ -96,10 +96,8 @@
   // own dialog with more information in the future.
   if (scheme_ == url::kTelScheme &&
       base::FeatureList::IsEnabled(kClickToCallUI)) {
-    return l10n_util::GetStringFUTF16(
-        IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES,
-        l10n_util::GetStringUTF16(
-            IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_TROUBLESHOOT_LINK));
+    return l10n_util::GetStringUTF16(
+        IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES);
   }
   return l10n_util::GetStringUTF16(IDS_EXTERNAL_PROTOCOL_TITLE);
 }
diff --git a/chrome/browser/chromeos/login/chrome_restart_request.cc b/chrome/browser/chromeos/login/chrome_restart_request.cc
index 3e6ea98..7f4ac4d 100644
--- a/chrome/browser/chromeos/login/chrome_restart_request.cc
+++ b/chrome/browser/chromeos/login/chrome_restart_request.cc
@@ -161,6 +161,7 @@
     ash::switches::kEnableDimShelf,
     ash::switches::kShowTaps,
     blink::switches::kBlinkSettings,
+    blink::switches::kDarkModeSettings,
     blink::switches::kDisableLowResTiling,
     blink::switches::kDisablePartialRaster,
     blink::switches::kDisablePreferCompositingToLCDText,
diff --git a/chrome/browser/chromeos/system/fake_input_device_settings.cc b/chrome/browser/chromeos/system/fake_input_device_settings.cc
index 54861ef4..3a75f0b3e 100644
--- a/chrome/browser/chromeos/system/fake_input_device_settings.cc
+++ b/chrome/browser/chromeos/system/fake_input_device_settings.cc
@@ -106,6 +106,11 @@
   UpdateMouseSettings(settings);
 }
 
+void FakeInputDeviceSettings::PointingStickExists(
+    DeviceExistsCallback callback) {
+  std::move(callback).Run(pointing_stick_exists_);
+}
+
 void FakeInputDeviceSettings::SetTouchpadAcceleration(bool enabled) {
   TouchpadSettings settings;
   settings.SetAcceleration(enabled);
@@ -137,6 +142,10 @@
   mouse_exists_ = exists;
 }
 
+void FakeInputDeviceSettings::set_pointing_stick_exists(bool exists) {
+  pointing_stick_exists_ = exists;
+}
+
 const TouchpadSettings& FakeInputDeviceSettings::current_touchpad_settings()
     const {
   return current_touchpad_settings_;
diff --git a/chrome/browser/chromeos/system/fake_input_device_settings.h b/chrome/browser/chromeos/system/fake_input_device_settings.h
index 6da1c96..a036a3b6 100644
--- a/chrome/browser/chromeos/system/fake_input_device_settings.h
+++ b/chrome/browser/chromeos/system/fake_input_device_settings.h
@@ -35,6 +35,7 @@
   void SetMouseReverseScroll(bool enabled) override;
   void SetMouseAcceleration(bool enabled) override;
   void SetMouseScrollAcceleration(bool enabled) override;
+  void PointingStickExists(DeviceExistsCallback callback) override;
   void SetTouchpadAcceleration(bool enabled) override;
   void SetTouchpadScrollAcceleration(bool enabled) override;
   void SetNaturalScroll(bool enabled) override;
@@ -45,6 +46,7 @@
   // Overridden from InputDeviceSettings::FakeInterface.
   void set_touchpad_exists(bool exists) override;
   void set_mouse_exists(bool exists) override;
+  void set_pointing_stick_exists(bool exists) override;
   const TouchpadSettings& current_touchpad_settings() const override;
   const MouseSettings& current_mouse_settings() const override;
 
@@ -54,6 +56,7 @@
 
   bool touchpad_exists_ = true;
   bool mouse_exists_ = true;
+  bool pointing_stick_exists_ = true;
 
   DISALLOW_COPY_AND_ASSIGN(FakeInputDeviceSettings);
 };
diff --git a/chrome/browser/chromeos/system/input_device_settings.h b/chrome/browser/chromeos/system/input_device_settings.h
index dcb59ff..d84b91f4 100644
--- a/chrome/browser/chromeos/system/input_device_settings.h
+++ b/chrome/browser/chromeos/system/input_device_settings.h
@@ -151,6 +151,7 @@
    public:
     virtual void set_touchpad_exists(bool exists) = 0;
     virtual void set_mouse_exists(bool exists) = 0;
+    virtual void set_pointing_stick_exists(bool exists) = 0;
     virtual const TouchpadSettings& current_touchpad_settings() const = 0;
     virtual const MouseSettings& current_mouse_settings() const = 0;
   };
@@ -222,6 +223,10 @@
   // Turns mouse scroll acceleration on/off.
   virtual void SetMouseScrollAcceleration(bool enabled) = 0;
 
+  // Calls |callback|, possibly asynchronously, after determining if a pointing
+  // stick is connected.
+  virtual void PointingStickExists(DeviceExistsCallback callback) = 0;
+
   // Turns touchpad acceleration on/off.
   virtual void SetTouchpadAcceleration(bool enabled) = 0;
 
diff --git a/chrome/browser/chromeos/system/input_device_settings_impl_ozone.cc b/chrome/browser/chromeos/system/input_device_settings_impl_ozone.cc
index 9b6d3751..c5ca1ba 100644
--- a/chrome/browser/chromeos/system/input_device_settings_impl_ozone.cc
+++ b/chrome/browser/chromeos/system/input_device_settings_impl_ozone.cc
@@ -46,6 +46,7 @@
   void SetMouseReverseScroll(bool enabled) override;
   void SetMouseAcceleration(bool enabled) override;
   void SetMouseScrollAcceleration(bool enabled) override;
+  void PointingStickExists(DeviceExistsCallback callback) override;
   void SetTouchpadAcceleration(bool enabled) override;
   void SetTouchpadScrollAcceleration(bool enabled) override;
   void ReapplyTouchpadSettings() override;
@@ -159,6 +160,12 @@
   input_controller()->SetMouseScrollAcceleration(enabled);
 }
 
+void InputDeviceSettingsImplOzone::PointingStickExists(
+    DeviceExistsCallback callback) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  std::move(callback).Run(input_controller()->HasPointingStick());
+}
+
 void InputDeviceSettingsImplOzone::SetTouchpadAcceleration(bool enabled) {
   current_touchpad_settings_.SetAcceleration(enabled);
   input_controller()->SetTouchpadAcceleration(enabled);
diff --git a/chrome/browser/chromeos/system/pointer_device_observer.cc b/chrome/browser/chromeos/system/pointer_device_observer.cc
index e323a97..6483db6 100644
--- a/chrome/browser/chromeos/system/pointer_device_observer.cc
+++ b/chrome/browser/chromeos/system/pointer_device_observer.cc
@@ -27,6 +27,7 @@
 
 void PointerDeviceObserver::CheckDevices() {
   CheckMouseExists();
+  CheckPointingStickExists();
   CheckTouchpadExists();
 }
 
@@ -56,6 +57,12 @@
       &PointerDeviceObserver::OnMouseExists, weak_factory_.GetWeakPtr()));
 }
 
+void PointerDeviceObserver::CheckPointingStickExists() {
+  InputDeviceSettings::Get()->PointingStickExists(
+      base::BindOnce(&PointerDeviceObserver::OnPointingStickExists,
+                     weak_factory_.GetWeakPtr()));
+}
+
 void PointerDeviceObserver::OnTouchpadExists(bool exists) {
   for (auto& observer : observers_)
     observer.TouchpadExists(exists);
@@ -66,6 +73,11 @@
     observer.MouseExists(exists);
 }
 
+void PointerDeviceObserver::OnPointingStickExists(bool exists) {
+  for (auto& observer : observers_)
+    observer.PointingStickExists(exists);
+}
+
 PointerDeviceObserver::Observer::~Observer() {
 }
 
diff --git a/chrome/browser/chromeos/system/pointer_device_observer.h b/chrome/browser/chromeos/system/pointer_device_observer.h
index e9631beb..58dca6bc 100644
--- a/chrome/browser/chromeos/system/pointer_device_observer.h
+++ b/chrome/browser/chromeos/system/pointer_device_observer.h
@@ -28,6 +28,7 @@
    public:
     virtual void TouchpadExists(bool exists) = 0;
     virtual void MouseExists(bool exists) = 0;
+    virtual void PointingStickExists(bool exists) = 0;
 
    protected:
     Observer() {}
@@ -43,10 +44,12 @@
   // Check for pointer devices.
   void CheckTouchpadExists();
   void CheckMouseExists();
+  void CheckPointingStickExists();
 
   // Callback for pointer device checks.
   void OnTouchpadExists(bool exists);
   void OnMouseExists(bool exists);
+  void OnPointingStickExists(bool exists);
 
   base::ObserverList<Observer>::Unchecked observers_;
 
diff --git a/chrome/browser/component_updater/floc_blocklist_component_installer.cc b/chrome/browser/component_updater/floc_blocklist_component_installer.cc
deleted file mode 100644
index 3d0ea1c..0000000
--- a/chrome/browser/component_updater/floc_blocklist_component_installer.cc
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/component_updater/floc_blocklist_component_installer.h"
-
-#include "base/callback.h"
-#include "base/files/file_path.h"
-#include "base/files/file_util.h"
-#include "base/memory/scoped_refptr.h"
-#include "base/version.h"
-#include "components/component_updater/component_updater_paths.h"
-#include "components/federated_learning/floc_blocklist_service.h"
-#include "components/federated_learning/floc_constants.h"
-
-namespace component_updater {
-
-// The extension id is: cmahhnpholdijhjokonmfdjbfmklppij
-constexpr uint8_t kFlocBlocklistPublicKeySHA256[32] = {
-    0x2c, 0x07, 0x7d, 0xf7, 0xeb, 0x38, 0x97, 0x9e, 0xae, 0xdc, 0x53,
-    0x91, 0x5c, 0xab, 0xff, 0x89, 0xbc, 0xf0, 0xd9, 0x30, 0xd2, 0x2e,
-    0x8f, 0x68, 0x3a, 0xf9, 0x21, 0x91, 0x9f, 0xc1, 0x84, 0xa1};
-
-constexpr char kFlocBlocklistFetcherManifestName[] = "Floc Blocklist";
-
-FlocBlocklistComponentInstallerPolicy::FlocBlocklistComponentInstallerPolicy(
-    federated_learning::FlocBlocklistService* floc_blocklist_service)
-    : floc_blocklist_service_(floc_blocklist_service) {}
-
-FlocBlocklistComponentInstallerPolicy::
-    ~FlocBlocklistComponentInstallerPolicy() = default;
-
-bool FlocBlocklistComponentInstallerPolicy::
-    SupportsGroupPolicyEnabledComponentUpdates() const {
-  return false;
-}
-
-// Public data is delivered via this component, no need for encryption.
-bool FlocBlocklistComponentInstallerPolicy::RequiresNetworkEncryption() const {
-  return false;
-}
-
-update_client::CrxInstaller::Result
-FlocBlocklistComponentInstallerPolicy::OnCustomInstall(
-    const base::DictionaryValue& manifest,
-    const base::FilePath& install_dir) {
-  return update_client::CrxInstaller::Result(0);  // Nothing custom here.
-}
-
-void FlocBlocklistComponentInstallerPolicy::OnCustomUninstall() {}
-
-void FlocBlocklistComponentInstallerPolicy::ComponentReady(
-    const base::Version& version,
-    const base::FilePath& install_dir,
-    std::unique_ptr<base::DictionaryValue> manifest) {
-  DCHECK(!install_dir.empty());
-
-  floc_blocklist_service_->OnBlocklistFileReady(
-      install_dir.Append(federated_learning::kBlocklistFileName));
-}
-
-// Called during startup and installation before ComponentReady().
-bool FlocBlocklistComponentInstallerPolicy::VerifyInstallation(
-    const base::DictionaryValue& manifest,
-    const base::FilePath& install_dir) const {
-  if (!base::PathExists(install_dir))
-    return false;
-
-  int blocklist_format = 0;
-  if (!manifest.GetInteger(federated_learning::kManifestBlocklistFormatKey,
-                           &blocklist_format) ||
-      blocklist_format != federated_learning::kCurrentBlocklistFormatVersion) {
-    return false;
-  }
-
-  return true;
-}
-
-base::FilePath FlocBlocklistComponentInstallerPolicy::GetRelativeInstallDir()
-    const {
-  return base::FilePath(federated_learning::kTopLevelDirectoryName)
-      .Append(federated_learning::kBlocklistBaseDirectoryName);
-}
-
-void FlocBlocklistComponentInstallerPolicy::GetHash(
-    std::vector<uint8_t>* hash) const {
-  hash->assign(std::begin(kFlocBlocklistPublicKeySHA256),
-               std::end(kFlocBlocklistPublicKeySHA256));
-}
-
-std::string FlocBlocklistComponentInstallerPolicy::GetName() const {
-  return kFlocBlocklistFetcherManifestName;
-}
-
-update_client::InstallerAttributes
-FlocBlocklistComponentInstallerPolicy::GetInstallerAttributes() const {
-  return update_client::InstallerAttributes();
-}
-
-std::vector<std::string> FlocBlocklistComponentInstallerPolicy::GetMimeTypes()
-    const {
-  return std::vector<std::string>();
-}
-
-void RegisterFlocBlocklistComponent(
-    ComponentUpdateService* cus,
-    federated_learning::FlocBlocklistService* floc_blocklist_service) {
-  auto installer = base::MakeRefCounted<ComponentInstaller>(
-      std::make_unique<FlocBlocklistComponentInstallerPolicy>(
-          floc_blocklist_service));
-  installer->Register(cus, base::OnceClosure());
-}
-
-}  // namespace component_updater
diff --git a/chrome/browser/component_updater/floc_blocklist_component_installer_unittest.cc b/chrome/browser/component_updater/floc_blocklist_component_installer_unittest.cc
deleted file mode 100644
index 5715c50..0000000
--- a/chrome/browser/component_updater/floc_blocklist_component_installer_unittest.cc
+++ /dev/null
@@ -1,181 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/component_updater/floc_blocklist_component_installer.h"
-
-#include <memory>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "base/files/file_path.h"
-#include "base/files/file_util.h"
-#include "base/files/scoped_temp_dir.h"
-#include "base/run_loop.h"
-#include "base/sequenced_task_runner.h"
-#include "base/strings/string_util.h"
-#include "base/test/bind_test_util.h"
-#include "base/threading/sequenced_task_runner_handle.h"
-#include "base/version.h"
-#include "chrome/test/base/testing_browser_process.h"
-#include "components/component_updater/mock_component_updater_service.h"
-#include "components/federated_learning/floc_blocklist_service.h"
-#include "components/federated_learning/floc_constants.h"
-#include "content/public/test/browser_task_environment.h"
-#include "testing/gmock/include/gmock/gmock-actions.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "testing/platform_test.h"
-
-namespace component_updater {
-
-namespace {
-
-ACTION_P(QuitMessageLoop, loop) {
-  loop->Quit();
-  return true;
-}
-
-// This class monitors the OnBlocklistFileReady method calls.
-class MockFlocBlocklistService
-    : public federated_learning::FlocBlocklistService {
- public:
-  MockFlocBlocklistService() = default;
-
-  MockFlocBlocklistService(const MockFlocBlocklistService&) = delete;
-  MockFlocBlocklistService& operator=(const MockFlocBlocklistService&) = delete;
-
-  ~MockFlocBlocklistService() override = default;
-
-  void OnBlocklistFileReady(const base::FilePath& file_path) override {
-    file_paths_.push_back(file_path);
-  }
-
-  const std::vector<base::FilePath>& file_paths() const { return file_paths_; }
-
- private:
-  std::vector<base::FilePath> file_paths_;
-};
-
-}  //  namespace
-
-class FlocBlocklistComponentInstallerTest : public PlatformTest {
- public:
-  FlocBlocklistComponentInstallerTest() = default;
-
-  FlocBlocklistComponentInstallerTest(
-      const FlocBlocklistComponentInstallerTest&) = delete;
-  FlocBlocklistComponentInstallerTest& operator=(
-      const FlocBlocklistComponentInstallerTest&) = delete;
-
-  ~FlocBlocklistComponentInstallerTest() override = default;
-
-  void SetUp() override {
-    PlatformTest::SetUp();
-
-    ASSERT_TRUE(component_install_dir_.CreateUniqueTempDir());
-
-    auto test_floc_blocklist_service =
-        std::make_unique<MockFlocBlocklistService>();
-    test_floc_blocklist_service->SetBackgroundTaskRunnerForTesting(
-        base::SequencedTaskRunnerHandle::Get());
-
-    test_floc_blocklist_service_ = test_floc_blocklist_service.get();
-
-    TestingBrowserProcess::GetGlobal()->SetFlocBlocklistService(
-        std::move(test_floc_blocklist_service));
-    policy_ = std::make_unique<FlocBlocklistComponentInstallerPolicy>(
-        test_floc_blocklist_service_);
-  }
-
-  void TearDown() override {
-    TestingBrowserProcess::GetGlobal()->SetFlocBlocklistService(nullptr);
-    PlatformTest::TearDown();
-  }
-
-  MockFlocBlocklistService* service() { return test_floc_blocklist_service_; }
-
-  void WriteStringToFile(const std::string& data, const base::FilePath& path) {
-    ASSERT_EQ(base::WriteFile(path, data.data(), data.length()),
-              static_cast<int32_t>(data.length()));
-  }
-
-  base::FilePath component_install_dir() {
-    return component_install_dir_.GetPath();
-  }
-
-  void CreateTestFlocBlocklist(const std::string& contents) {
-    base::FilePath file_path =
-        component_install_dir().Append(federated_learning::kBlocklistFileName);
-    ASSERT_NO_FATAL_FAILURE(WriteStringToFile(contents, file_path));
-  }
-
-  void LoadFlocBlocklist(const std::string& content_version,
-                         int format_version) {
-    auto manifest = std::make_unique<base::DictionaryValue>();
-    manifest->SetInteger(federated_learning::kManifestBlocklistFormatKey,
-                         format_version);
-
-    if (!policy_->VerifyInstallation(*manifest, component_install_dir()))
-      return;
-
-    policy_->ComponentReady(base::Version(content_version),
-                            component_install_dir(), std::move(manifest));
-  }
-
- protected:
-  content::BrowserTaskEnvironment task_environment_;
-  base::ScopedTempDir component_install_dir_;
-  std::unique_ptr<FlocBlocklistComponentInstallerPolicy> policy_;
-  MockFlocBlocklistService* test_floc_blocklist_service_ = nullptr;
-};
-
-TEST_F(FlocBlocklistComponentInstallerTest, TestComponentRegistration) {
-  auto component_updater =
-      std::make_unique<component_updater::MockComponentUpdateService>();
-
-  base::RunLoop run_loop;
-  EXPECT_CALL(*component_updater, RegisterComponent(testing::_))
-      .Times(1)
-      .WillOnce(QuitMessageLoop(&run_loop));
-
-  RegisterFlocBlocklistComponent(component_updater.get(), service());
-  run_loop.Run();
-}
-
-TEST_F(FlocBlocklistComponentInstallerTest, LoadBlocklist) {
-  ASSERT_TRUE(service());
-
-  std::string contents = "abcd";
-  ASSERT_NO_FATAL_FAILURE(CreateTestFlocBlocklist(contents));
-  ASSERT_NO_FATAL_FAILURE(LoadFlocBlocklist(
-      "1.0.0", federated_learning::kCurrentBlocklistFormatVersion));
-
-  ASSERT_EQ(service()->file_paths().size(), 1u);
-
-  // Assert that the file path is the concatenation of |component_install_dir_|
-  // and |kBlocklistFileName|, which implies that the |version| argument has no
-  // impact. In reality, though, the |component_install_dir_| and the |version|
-  // should always match.
-  ASSERT_EQ(service()->file_paths()[0].AsUTF8Unsafe(),
-            component_install_dir()
-                .Append(federated_learning::kBlocklistFileName)
-                .AsUTF8Unsafe());
-
-  std::string actual_contents;
-  ASSERT_TRUE(
-      base::ReadFileToString(service()->file_paths()[0], &actual_contents));
-  EXPECT_EQ(actual_contents, contents);
-}
-
-TEST_F(FlocBlocklistComponentInstallerTest, UnsupportedFormatVersionIgnored) {
-  ASSERT_TRUE(service());
-  const std::string contents = "future stuff";
-  ASSERT_NO_FATAL_FAILURE(CreateTestFlocBlocklist(contents));
-  ASSERT_NO_FATAL_FAILURE(LoadFlocBlocklist(
-      "1.0.0", federated_learning::kCurrentBlocklistFormatVersion + 1));
-  EXPECT_EQ(service()->file_paths().size(), 0u);
-}
-
-}  // namespace component_updater
diff --git a/chrome/browser/component_updater/floc_component_installer.cc b/chrome/browser/component_updater/floc_component_installer.cc
new file mode 100644
index 0000000..f5e06a2d
--- /dev/null
+++ b/chrome/browser/component_updater/floc_component_installer.cc
@@ -0,0 +1,124 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/component_updater/floc_component_installer.h"
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/version.h"
+#include "components/component_updater/component_updater_paths.h"
+#include "components/federated_learning/floc_blocklist_service.h"
+#include "components/federated_learning/floc_constants.h"
+#include "components/federated_learning/floc_sorting_lsh_clusters_service.h"
+
+namespace component_updater {
+
+// The extension id is: cmahhnpholdijhjokonmfdjbfmklppij
+constexpr uint8_t kFlocComponentPublicKeySHA256[32] = {
+    0x2c, 0x07, 0x7d, 0xf7, 0xeb, 0x38, 0x97, 0x9e, 0xae, 0xdc, 0x53,
+    0x91, 0x5c, 0xab, 0xff, 0x89, 0xbc, 0xf0, 0xd9, 0x30, 0xd2, 0x2e,
+    0x8f, 0x68, 0x3a, 0xf9, 0x21, 0x91, 0x9f, 0xc1, 0x84, 0xa1};
+
+constexpr char kFlocComponentFetcherManifestName[] =
+    "Federated Learning of Cohorts";
+
+FlocComponentInstallerPolicy::FlocComponentInstallerPolicy(
+    federated_learning::FlocBlocklistService* floc_blocklist_service,
+    federated_learning::FlocSortingLshClustersService*
+        floc_sorting_lsh_clusters_service)
+    : floc_blocklist_service_(floc_blocklist_service),
+      floc_sorting_lsh_clusters_service_(floc_sorting_lsh_clusters_service) {}
+
+FlocComponentInstallerPolicy::~FlocComponentInstallerPolicy() = default;
+
+bool FlocComponentInstallerPolicy::SupportsGroupPolicyEnabledComponentUpdates()
+    const {
+  return false;
+}
+
+// Public data is delivered via this component, no need for encryption.
+bool FlocComponentInstallerPolicy::RequiresNetworkEncryption() const {
+  return false;
+}
+
+update_client::CrxInstaller::Result
+FlocComponentInstallerPolicy::OnCustomInstall(
+    const base::DictionaryValue& manifest,
+    const base::FilePath& install_dir) {
+  return update_client::CrxInstaller::Result(0);  // Nothing custom here.
+}
+
+void FlocComponentInstallerPolicy::OnCustomUninstall() {}
+
+void FlocComponentInstallerPolicy::ComponentReady(
+    const base::Version& version,
+    const base::FilePath& install_dir,
+    std::unique_ptr<base::DictionaryValue> manifest) {
+  DCHECK(!install_dir.empty());
+
+  // TODO(yaoxia): Pass along the |version| to each service. At the end of each
+  // floc computation cycle, it should verify the two versions match. This is
+  // not needed currently as the floc_sorting_lsh_clusters_service is not set up
+  // yet.
+  floc_blocklist_service_->OnBlocklistFileReady(
+      install_dir.Append(federated_learning::kBlocklistFileName));
+
+  floc_sorting_lsh_clusters_service_->OnSortingLshClustersFileReady(
+      install_dir.Append(federated_learning::kSortingLshClustersFileName));
+}
+
+// Called during startup and installation before ComponentReady().
+bool FlocComponentInstallerPolicy::VerifyInstallation(
+    const base::DictionaryValue& manifest,
+    const base::FilePath& install_dir) const {
+  if (!base::PathExists(install_dir))
+    return false;
+
+  int floc_component_format = 0;
+  if (!manifest.GetInteger(federated_learning::kManifestFlocComponentFormatKey,
+                           &floc_component_format) ||
+      floc_component_format !=
+          federated_learning::kCurrentFlocComponentFormatVersion) {
+    return false;
+  }
+
+  return true;
+}
+
+base::FilePath FlocComponentInstallerPolicy::GetRelativeInstallDir() const {
+  return base::FilePath(federated_learning::kTopLevelDirectoryName);
+}
+
+void FlocComponentInstallerPolicy::GetHash(std::vector<uint8_t>* hash) const {
+  hash->assign(std::begin(kFlocComponentPublicKeySHA256),
+               std::end(kFlocComponentPublicKeySHA256));
+}
+
+std::string FlocComponentInstallerPolicy::GetName() const {
+  return kFlocComponentFetcherManifestName;
+}
+
+update_client::InstallerAttributes
+FlocComponentInstallerPolicy::GetInstallerAttributes() const {
+  return update_client::InstallerAttributes();
+}
+
+std::vector<std::string> FlocComponentInstallerPolicy::GetMimeTypes() const {
+  return std::vector<std::string>();
+}
+
+void RegisterFlocComponent(
+    ComponentUpdateService* cus,
+    federated_learning::FlocBlocklistService* floc_blocklist_service,
+    federated_learning::FlocSortingLshClustersService*
+        floc_sorting_lsh_clusters_service) {
+  auto installer = base::MakeRefCounted<ComponentInstaller>(
+      std::make_unique<FlocComponentInstallerPolicy>(
+          floc_blocklist_service, floc_sorting_lsh_clusters_service));
+  installer->Register(cus, base::OnceClosure());
+}
+
+}  // namespace component_updater
diff --git a/chrome/browser/component_updater/floc_blocklist_component_installer.h b/chrome/browser/component_updater/floc_component_installer.h
similarity index 63%
rename from chrome/browser/component_updater/floc_blocklist_component_installer.h
rename to chrome/browser/component_updater/floc_component_installer.h
index 14f45df4..a7b2d7c 100644
--- a/chrome/browser/component_updater/floc_blocklist_component_installer.h
+++ b/chrome/browser/component_updater/floc_component_installer.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_COMPONENT_UPDATER_FLOC_BLOCKLIST_COMPONENT_INSTALLER_H_
-#define CHROME_BROWSER_COMPONENT_UPDATER_FLOC_BLOCKLIST_COMPONENT_INSTALLER_H_
+#ifndef CHROME_BROWSER_COMPONENT_UPDATER_FLOC_COMPONENT_INSTALLER_H_
+#define CHROME_BROWSER_COMPONENT_UPDATER_FLOC_COMPONENT_INSTALLER_H_
 
 #include <stdint.h>
 
@@ -19,26 +19,28 @@
 
 namespace federated_learning {
 class FlocBlocklistService;
+class FlocSortingLshClustersService;
 }  // namespace federated_learning
 
 namespace component_updater {
 
 class ComponentUpdateService;
 
-// Component for receiving a blocklist of floc ids.
-class FlocBlocklistComponentInstallerPolicy : public ComponentInstallerPolicy {
+// Component for receiving FLoC files, e.g. blocklist, sorting-lsh, etc.
+class FlocComponentInstallerPolicy : public ComponentInstallerPolicy {
  public:
-  explicit FlocBlocklistComponentInstallerPolicy(
-      federated_learning::FlocBlocklistService* floc_blocklist_service);
-  ~FlocBlocklistComponentInstallerPolicy() override;
+  explicit FlocComponentInstallerPolicy(
+      federated_learning::FlocBlocklistService* floc_blocklist_service,
+      federated_learning::FlocSortingLshClustersService*
+          floc_sorting_lsh_clusters_service);
+  ~FlocComponentInstallerPolicy() override;
 
-  FlocBlocklistComponentInstallerPolicy(
-      const FlocBlocklistComponentInstallerPolicy&) = delete;
-  FlocBlocklistComponentInstallerPolicy& operator=(
-      const FlocBlocklistComponentInstallerPolicy&) = delete;
+  FlocComponentInstallerPolicy(const FlocComponentInstallerPolicy&) = delete;
+  FlocComponentInstallerPolicy& operator=(const FlocComponentInstallerPolicy&) =
+      delete;
 
  private:
-  friend class FlocBlocklistComponentInstallerTest;
+  friend class FlocComponentInstallerTest;
 
   // ComponentInstallerPolicy implementation.
   bool SupportsGroupPolicyEnabledComponentUpdates() const override;
@@ -59,12 +61,16 @@
   std::vector<std::string> GetMimeTypes() const override;
 
   federated_learning::FlocBlocklistService* floc_blocklist_service_;
+  federated_learning::FlocSortingLshClustersService*
+      floc_sorting_lsh_clusters_service_;
 };
 
-void RegisterFlocBlocklistComponent(
+void RegisterFlocComponent(
     ComponentUpdateService* cus,
-    federated_learning::FlocBlocklistService* floc_blocklist_service);
+    federated_learning::FlocBlocklistService* floc_blocklist_service,
+    federated_learning::FlocSortingLshClustersService*
+        floc_sorting_lsh_clusters_service);
 
 }  // namespace component_updater
 
-#endif  // CHROME_BROWSER_COMPONENT_UPDATER_FLOC_BLOCKLIST_COMPONENT_INSTALLER_H_
+#endif  // CHROME_BROWSER_COMPONENT_UPDATER_FLOC_COMPONENT_INSTALLER_H_
diff --git a/chrome/browser/component_updater/floc_component_installer_unittest.cc b/chrome/browser/component_updater/floc_component_installer_unittest.cc
new file mode 100644
index 0000000..4e91736
--- /dev/null
+++ b/chrome/browser/component_updater/floc_component_installer_unittest.cc
@@ -0,0 +1,244 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/component_updater/floc_component_installer.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/run_loop.h"
+#include "base/sequenced_task_runner.h"
+#include "base/strings/string_util.h"
+#include "base/test/bind_test_util.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/version.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "components/component_updater/mock_component_updater_service.h"
+#include "components/federated_learning/floc_blocklist_service.h"
+#include "components/federated_learning/floc_constants.h"
+#include "components/federated_learning/floc_sorting_lsh_clusters_service.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gmock/include/gmock/gmock-actions.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace component_updater {
+
+namespace {
+
+ACTION_P(QuitMessageLoop, loop) {
+  loop->Quit();
+  return true;
+}
+
+// This class monitors the OnBlocklistFileReady method calls.
+class MockFlocBlocklistService
+    : public federated_learning::FlocBlocklistService {
+ public:
+  MockFlocBlocklistService() = default;
+
+  MockFlocBlocklistService(const MockFlocBlocklistService&) = delete;
+  MockFlocBlocklistService& operator=(const MockFlocBlocklistService&) = delete;
+
+  ~MockFlocBlocklistService() override = default;
+
+  void OnBlocklistFileReady(const base::FilePath& file_path) override {
+    file_paths_.push_back(file_path);
+  }
+
+  const std::vector<base::FilePath>& file_paths() const { return file_paths_; }
+
+ private:
+  std::vector<base::FilePath> file_paths_;
+};
+
+// This class monitors the OnSortingLshClustersFileReady method calls.
+class MockFlocSortingLshClustersService
+    : public federated_learning::FlocSortingLshClustersService {
+ public:
+  MockFlocSortingLshClustersService() = default;
+
+  MockFlocSortingLshClustersService(const MockFlocSortingLshClustersService&) =
+      delete;
+  MockFlocSortingLshClustersService& operator=(
+      const MockFlocSortingLshClustersService&) = delete;
+
+  ~MockFlocSortingLshClustersService() override = default;
+
+  void OnSortingLshClustersFileReady(const base::FilePath& file_path) override {
+    file_paths_.push_back(file_path);
+  }
+
+  const std::vector<base::FilePath>& file_paths() const { return file_paths_; }
+
+ private:
+  std::vector<base::FilePath> file_paths_;
+};
+
+}  //  namespace
+
+class FlocComponentInstallerTest : public PlatformTest {
+ public:
+  FlocComponentInstallerTest() = default;
+
+  FlocComponentInstallerTest(const FlocComponentInstallerTest&) = delete;
+  FlocComponentInstallerTest& operator=(const FlocComponentInstallerTest&) =
+      delete;
+
+  ~FlocComponentInstallerTest() override = default;
+
+  void SetUp() override {
+    PlatformTest::SetUp();
+
+    ASSERT_TRUE(component_install_dir_.CreateUniqueTempDir());
+
+    auto test_floc_blocklist_service =
+        std::make_unique<MockFlocBlocklistService>();
+    test_floc_blocklist_service->SetBackgroundTaskRunnerForTesting(
+        base::SequencedTaskRunnerHandle::Get());
+
+    test_floc_blocklist_service_ = test_floc_blocklist_service.get();
+
+    TestingBrowserProcess::GetGlobal()->SetFlocBlocklistService(
+        std::move(test_floc_blocklist_service));
+
+    auto test_floc_sorting_lsh_clusters_service =
+        std::make_unique<MockFlocSortingLshClustersService>();
+    test_floc_sorting_lsh_clusters_service->SetBackgroundTaskRunnerForTesting(
+        base::SequencedTaskRunnerHandle::Get());
+
+    test_floc_sorting_lsh_clusters_service_ =
+        test_floc_sorting_lsh_clusters_service.get();
+
+    TestingBrowserProcess::GetGlobal()->SetFlocSortingLshClustersService(
+        std::move(test_floc_sorting_lsh_clusters_service));
+
+    policy_ = std::make_unique<FlocComponentInstallerPolicy>(
+        test_floc_blocklist_service_, test_floc_sorting_lsh_clusters_service_);
+  }
+
+  void TearDown() override {
+    TestingBrowserProcess::GetGlobal()->SetFlocBlocklistService(nullptr);
+    TestingBrowserProcess::GetGlobal()->SetFlocSortingLshClustersService(
+        nullptr);
+    PlatformTest::TearDown();
+  }
+
+  MockFlocBlocklistService* blocklist_service() {
+    return test_floc_blocklist_service_;
+  }
+  MockFlocSortingLshClustersService* sorting_lsh_clusters_service() {
+    return test_floc_sorting_lsh_clusters_service_;
+  }
+
+  void WriteStringToFile(const std::string& data, const base::FilePath& path) {
+    ASSERT_EQ(base::WriteFile(path, data.data(), data.length()),
+              static_cast<int32_t>(data.length()));
+  }
+
+  base::FilePath component_install_dir() {
+    return component_install_dir_.GetPath();
+  }
+
+  void CreateTestFlocComponentFiles(const std::string& blocklist_content,
+                                    const std::string& sorting_lsh_content) {
+    base::FilePath blocklist_file_path =
+        component_install_dir().Append(federated_learning::kBlocklistFileName);
+    ASSERT_NO_FATAL_FAILURE(
+        WriteStringToFile(blocklist_content, blocklist_file_path));
+
+    base::FilePath sorting_lsh_file_path = component_install_dir().Append(
+        federated_learning::kSortingLshClustersFileName);
+    ASSERT_NO_FATAL_FAILURE(
+        WriteStringToFile(sorting_lsh_content, sorting_lsh_file_path));
+  }
+
+  void LoadFlocComponent(const std::string& content_version,
+                         int format_version) {
+    auto manifest = std::make_unique<base::DictionaryValue>();
+    manifest->SetInteger(federated_learning::kManifestFlocComponentFormatKey,
+                         format_version);
+
+    if (!policy_->VerifyInstallation(*manifest, component_install_dir()))
+      return;
+
+    policy_->ComponentReady(base::Version(content_version),
+                            component_install_dir(), std::move(manifest));
+  }
+
+ protected:
+  content::BrowserTaskEnvironment task_environment_;
+  base::ScopedTempDir component_install_dir_;
+  std::unique_ptr<FlocComponentInstallerPolicy> policy_;
+  MockFlocBlocklistService* test_floc_blocklist_service_ = nullptr;
+  MockFlocSortingLshClustersService* test_floc_sorting_lsh_clusters_service_ =
+      nullptr;
+};
+
+TEST_F(FlocComponentInstallerTest, TestComponentRegistration) {
+  auto component_updater =
+      std::make_unique<component_updater::MockComponentUpdateService>();
+
+  base::RunLoop run_loop;
+  EXPECT_CALL(*component_updater, RegisterComponent(testing::_))
+      .Times(1)
+      .WillOnce(QuitMessageLoop(&run_loop));
+
+  RegisterFlocComponent(component_updater.get(), blocklist_service(),
+                        sorting_lsh_clusters_service());
+  run_loop.Run();
+}
+
+TEST_F(FlocComponentInstallerTest, LoadFlocComponent) {
+  ASSERT_TRUE(blocklist_service());
+  ASSERT_TRUE(sorting_lsh_clusters_service());
+
+  std::string contents = "abcd";
+  ASSERT_NO_FATAL_FAILURE(CreateTestFlocComponentFiles(contents, contents));
+  ASSERT_NO_FATAL_FAILURE(LoadFlocComponent(
+      "1.0.0", federated_learning::kCurrentFlocComponentFormatVersion));
+
+  ASSERT_EQ(blocklist_service()->file_paths().size(), 1u);
+  ASSERT_EQ(sorting_lsh_clusters_service()->file_paths().size(), 1u);
+
+  // Assert that the file path is the concatenation of |component_install_dir_|
+  // and the corresponding file name, which implies that the |version| argument
+  // has no impact. In reality, though, the |component_install_dir_| and the
+  // |version| should always match.
+  ASSERT_EQ(blocklist_service()->file_paths()[0].AsUTF8Unsafe(),
+            component_install_dir()
+                .Append(federated_learning::kBlocklistFileName)
+                .AsUTF8Unsafe());
+
+  ASSERT_EQ(sorting_lsh_clusters_service()->file_paths()[0].AsUTF8Unsafe(),
+            component_install_dir()
+                .Append(federated_learning::kSortingLshClustersFileName)
+                .AsUTF8Unsafe());
+
+  std::string actual_contents;
+  ASSERT_TRUE(base::ReadFileToString(blocklist_service()->file_paths()[0],
+                                     &actual_contents));
+  ASSERT_TRUE(base::ReadFileToString(
+      sorting_lsh_clusters_service()->file_paths()[0], &actual_contents));
+  EXPECT_EQ(actual_contents, contents);
+}
+
+TEST_F(FlocComponentInstallerTest, UnsupportedFormatVersionIgnored) {
+  ASSERT_TRUE(blocklist_service());
+  ASSERT_TRUE(sorting_lsh_clusters_service());
+  const std::string contents = "future stuff";
+  ASSERT_NO_FATAL_FAILURE(CreateTestFlocComponentFiles(contents, contents));
+  ASSERT_NO_FATAL_FAILURE(LoadFlocComponent(
+      "1.0.0", federated_learning::kCurrentFlocComponentFormatVersion + 1));
+  EXPECT_EQ(blocklist_service()->file_paths().size(), 0u);
+  EXPECT_EQ(sorting_lsh_clusters_service()->file_paths().size(), 0u);
+}
+
+}  // namespace component_updater
diff --git a/chrome/browser/component_updater/registration.cc b/chrome/browser/component_updater/registration.cc
index 302cca1f3..53f5ee9 100644
--- a/chrome/browser/component_updater/registration.cc
+++ b/chrome/browser/component_updater/registration.cc
@@ -14,7 +14,7 @@
 #include "chrome/browser/component_updater/crowd_deny_component_installer.h"
 #include "chrome/browser/component_updater/file_type_policies_component_installer.h"
 #include "chrome/browser/component_updater/first_party_sets_component_installer.h"
-#include "chrome/browser/component_updater/floc_blocklist_component_installer.h"
+#include "chrome/browser/component_updater/floc_component_installer.h"
 #include "chrome/browser/component_updater/games_component_installer.h"
 #include "chrome/browser/component_updater/mei_preload_component_installer.h"
 #include "chrome/browser/component_updater/optimization_hints_component_installer.h"
@@ -121,8 +121,8 @@
 #endif
 
   RegisterSubresourceFilterComponent(cus);
-  RegisterFlocBlocklistComponent(cus,
-                                 g_browser_process->floc_blocklist_service());
+  RegisterFlocComponent(cus, g_browser_process->floc_blocklist_service(),
+                        g_browser_process->floc_sorting_lsh_clusters_service());
   RegisterOnDeviceHeadSuggestComponent(
       cus, g_browser_process->GetApplicationLocale());
   RegisterOptimizationHintsComponent(cus, is_off_the_record_profile);
diff --git a/chrome/browser/download/chrome_download_manager_delegate.cc b/chrome/browser/download/chrome_download_manager_delegate.cc
index 4c71f5308..a8fdddb 100644
--- a/chrome/browser/download/chrome_download_manager_delegate.cc
+++ b/chrome/browser/download/chrome_download_manager_delegate.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/download/chrome_download_manager_delegate.h"
 
+#include <algorithm>
 #include <string>
 #include <utility>
 
@@ -39,6 +40,7 @@
 #include "chrome/browser/download/download_target_determiner.h"
 #include "chrome/browser/download/mixed_content_download_blocking.h"
 #include "chrome/browser/download/save_package_file_picker.h"
+#include "chrome/browser/enterprise/connectors/common.h"
 #include "chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h"
 #include "chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_factory.h"
 #include "chrome/browser/platform_util.h"
@@ -342,6 +344,15 @@
   if (!profile)
     return;
 
+  // If |download| has a deep scanning malware verdict, then it means the
+  // dangerous file has already been reported.
+  auto* scan_result = static_cast<enterprise_connectors::ScanResult*>(
+      download->GetUserData(enterprise_connectors::ScanResult::kKey));
+  if (scan_result &&
+      enterprise_connectors::ContainsMalwareVerdict(scan_result->response)) {
+    return;
+  }
+
   std::string raw_digest_sha256 = download->GetHash();
   extensions::SafeBrowsingPrivateEventRouterFactory::GetForProfile(profile)
       ->OnDangerousDownloadEvent(
diff --git a/chrome/browser/extensions/api/developer_private/developer_private_api.cc b/chrome/browser/extensions/api/developer_private/developer_private_api.cc
index 5987000..f6aa62e 100644
--- a/chrome/browser/extensions/api/developer_private/developer_private_api.cc
+++ b/chrome/browser/extensions/api/developer_private/developer_private_api.cc
@@ -1775,8 +1775,8 @@
     if (!extension)
       return RespondNow(Error(kNoSuchExtensionError));
     if (properties.render_process_id == -1) {
-      // TODO(crbug.com/1107596): Implement inspecting view of an inactive
-      // service worker.
+      // Start the service worker and open the inspect window.
+      devtools_util::InspectInactiveServiceWorkerBackground(extension, profile);
       return RespondNow(NoArguments());
     }
     devtools_util::InspectServiceWorkerBackground(extension, profile);
diff --git a/chrome/browser/extensions/api/developer_private/developer_private_apitest.cc b/chrome/browser/extensions/api/developer_private/developer_private_apitest.cc
index f82ca2f..952e5bbc 100644
--- a/chrome/browser/extensions/api/developer_private/developer_private_apitest.cc
+++ b/chrome/browser/extensions/api/developer_private/developer_private_apitest.cc
@@ -5,15 +5,19 @@
 #include "base/path_service.h"
 #include "base/strings/stringprintf.h"
 #include "chrome/browser/devtools/devtools_window.h"
+#include "chrome/browser/devtools/devtools_window_testing.h"
 #include "chrome/browser/extensions/api/developer_private/developer_private_api.h"
 #include "chrome/browser/extensions/extension_apitest.h"
 #include "chrome/browser/extensions/extension_function_test_utils.h"
 #include "chrome/browser/extensions/extension_tab_util.h"
 #include "chrome/common/chrome_paths.h"
 #include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/storage_partition.h"
 #include "content/public/test/browser_test.h"
+#include "content/public/test/service_worker_test_helpers.h"
 #include "extensions/browser/app_window/app_window.h"
 #include "extensions/browser/app_window/app_window_registry.h"
+#include "extensions/browser/service_worker/service_worker_test_utils.h"
 #include "extensions/common/manifest_handlers/background_info.h"
 #include "extensions/test/result_catcher.h"
 
@@ -134,6 +138,88 @@
 }
 
 IN_PROC_BROWSER_TEST_F(DeveloperPrivateApiTest,
+                       InspectInactiveServiceWorkerBackground) {
+  ResultCatcher result_catcher;
+  // Load an extension that is service worker based.
+  const Extension* extension =
+      LoadExtension(test_data_dir_.AppendASCII("service_worker")
+                        .AppendASCII("worker_based_background")
+                        .AppendASCII("inspect"));
+  ASSERT_TRUE(extension);
+  ASSERT_TRUE(result_catcher.GetNextResult());
+
+  service_worker_test_utils::TestRegistrationObserver registration_observer(
+      browser()->profile());
+  registration_observer.WaitForRegistrationStored();
+
+  // Stop the service worker.
+  {
+    base::RunLoop run_loop;
+    content::StoragePartition* storage_partition =
+        content::BrowserContext::GetDefaultStoragePartition(profile());
+    content::ServiceWorkerContext* context =
+        storage_partition->GetServiceWorkerContext();
+    content::StopServiceWorkerForScope(context, extension->url(),
+                                       run_loop.QuitClosure());
+    run_loop.Run();
+  }
+
+  // Get the info about the extension, including the inspectable views.
+  auto get_info_function =
+      base::MakeRefCounted<api::DeveloperPrivateGetExtensionInfoFunction>();
+  std::unique_ptr<base::Value> result(
+      extension_function_test_utils::RunFunctionAndReturnSingleResult(
+          get_info_function.get(),
+          base::StringPrintf("[\"%s\"]", extension->id().c_str()), browser()));
+  ASSERT_TRUE(result);
+  std::unique_ptr<api::developer_private::ExtensionInfo> info =
+      api::developer_private::ExtensionInfo::FromValue(*result);
+  ASSERT_TRUE(info);
+
+  // There should be a worker based background for the extension.
+  ASSERT_EQ(1u, info->views.size());
+  const api::developer_private::ExtensionView& view = info->views[0];
+  EXPECT_EQ(
+      api::developer_private::VIEW_TYPE_EXTENSION_SERVICE_WORKER_BACKGROUND,
+      view.type);
+  // The service worker should be inactive (indicated by -1 for
+  // the process id).
+  EXPECT_EQ(-1, view.render_process_id);
+
+  // Inspect the inactive service worker background.
+  DevToolsWindowCreationObserver devtools_window_created_observer;
+  auto dev_tools_function =
+      base::MakeRefCounted<api::DeveloperPrivateOpenDevToolsFunction>();
+  extension_function_test_utils::RunFunction(dev_tools_function.get(),
+                                             base::StringPrintf(
+                                                 R"([{"renderViewId": -1,
+                                                      "renderProcessId": -1,
+                                                      "isServiceWorker": true,
+                                                      "extensionId": "%s"
+                                                   }])",
+                                                 extension->id().c_str()),
+                                             browser(), api_test_utils::NONE);
+  devtools_window_created_observer.WaitForLoad();
+
+  // Verify that dev tool window opened.
+  scoped_refptr<content::DevToolsAgentHost> service_worker_host;
+  content::DevToolsAgentHost::List targets =
+      content::DevToolsAgentHost::GetOrCreateAll();
+  for (const scoped_refptr<content::DevToolsAgentHost>& host : targets) {
+    if (host->GetType() == content::DevToolsAgentHost::kTypeServiceWorker &&
+        host->GetURL() ==
+            extension->GetResourceURL(
+                BackgroundInfo::GetBackgroundServiceWorkerScript(extension))) {
+      EXPECT_FALSE(service_worker_host);
+      service_worker_host = host;
+    }
+  }
+
+  ASSERT_TRUE(service_worker_host);
+  EXPECT_TRUE(DevToolsWindow::FindDevToolsWindow(service_worker_host.get()));
+}
+
+IN_PROC_BROWSER_TEST_F(DeveloperPrivateApiTest,
                        InspectActiveServiceWorkerBackground) {
   ResultCatcher result_catcher;
   // Load an extension that is service worker based.
diff --git a/chrome/browser/extensions/devtools_util.cc b/chrome/browser/extensions/devtools_util.cc
index 1f05884..4e383e4 100644
--- a/chrome/browser/extensions/devtools_util.cc
+++ b/chrome/browser/extensions/devtools_util.cc
@@ -8,9 +8,11 @@
 #include "chrome/browser/devtools/devtools_window.h"
 #include "chrome/browser/profiles/profile.h"
 #include "extensions/browser/extension_host.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/lazy_context_id.h"
 #include "extensions/browser/lazy_context_task_queue.h"
 #include "extensions/browser/process_manager.h"
+#include "extensions/browser/service_worker_task_queue.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/manifest_handlers/background_info.h"
 
@@ -26,6 +28,23 @@
     DevToolsWindow::OpenDevToolsWindow(context_info->web_contents);
 }
 
+void InspectServiceWorkerBackgroundHelper(
+    std::unique_ptr<LazyContextTaskQueue::ContextInfo> context_info) {
+  if (!context_info)
+    return;
+
+  Profile* profile = Profile::FromBrowserContext(context_info->browser_context);
+  const Extension* extension =
+      ExtensionRegistry::Get(context_info->browser_context)
+          ->enabled_extensions()
+          .GetByID(context_info->extension_id);
+
+  // A non-null context info does not guarantee that the extension is enabled,
+  // due to thread/process asynchrony.
+  if (extension)
+    InspectServiceWorkerBackground(extension, profile);
+}
+
 }  // namespace
 
 // Helper to inspect a service worker after it has been started.
@@ -45,6 +64,17 @@
   }
 }
 
+void InspectInactiveServiceWorkerBackground(const Extension* extension,
+                                            Profile* profile) {
+  DCHECK(extension);
+  DCHECK(BackgroundInfo::IsServiceWorkerBased(extension));
+  LazyContextId context_id(
+      profile, extension->id(),
+      Extension::GetBaseURLFromExtensionId(extension->id()));
+  context_id.GetTaskQueue()->AddPendingTask(
+      context_id, base::BindOnce(&InspectServiceWorkerBackgroundHelper));
+}
+
 void InspectBackgroundPage(const Extension* extension, Profile* profile) {
   DCHECK(extension);
   ExtensionHost* host = ProcessManager::Get(profile)
diff --git a/chrome/browser/extensions/devtools_util.h b/chrome/browser/extensions/devtools_util.h
index da72c210..ad248b3 100644
--- a/chrome/browser/extensions/devtools_util.h
+++ b/chrome/browser/extensions/devtools_util.h
@@ -17,6 +17,11 @@
 void InspectServiceWorkerBackground(const Extension* extension,
                                     Profile* profile);
 
+// Open a dev tools window for an inactive service worker background for the
+// given extension.
+void InspectInactiveServiceWorkerBackground(const Extension* extension,
+                                            Profile* profile);
+
 // Open a dev tools window for the background page for the given extension,
 // starting the background page first if necessary.
 void InspectBackgroundPage(const Extension* extension, Profile* profile);
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 3a455a3..b4383a79b 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -3332,6 +3332,11 @@
     "expiry_milestone": 88
   },
   {
+    "name": "omnibox-bookmark-paths",
+    "owners": [ "manukh", "chrome-omnibox-team@google.com" ],
+    "expiry_milestone": 95
+  },
+  {
     "name": "omnibox-bubble-url-suggestions",
     "owners": [ "manukh", "chrome-omnibox-team@google.com" ],
     "expiry_milestone": 95
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 17cf166..67879e0c 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1477,6 +1477,12 @@
     "When enabled, use Assistant for omnibox voice query recognition instead of"
     " Android's built-in voice recognition service. Only works on Android.";
 
+extern const char kOmniboxBookmarkPathsName[] = "Omnibox Bookmark Paths";
+extern const char kOmniboxBookmarkPathsDescription[] =
+    "Allows inputs to match with bookmark paths. E.g. 'planets jupiter' can "
+    "suggest a bookmark titled 'Jupiter' with URL "
+    "'en.wikipedia.org/wiki/Jupiter' located in a path containing 'planet.'";
+
 const char kOmniboxClobberTriggersContextualWebZeroSuggestName[] =
     "Omnibox Clobber Triggers Contextual Web ZeroSuggest";
 const char kOmniboxClobberTriggersContextualWebZeroSuggestDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index b3bc5b8..07181216 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -861,6 +861,9 @@
 extern const char kOmniboxAssistantVoiceSearchName[];
 extern const char kOmniboxAssistantVoiceSearchDescription[];
 
+extern const char kOmniboxBookmarkPathsName[];
+extern const char kOmniboxBookmarkPathsDescription[];
+
 extern const char kOmniboxClobberTriggersContextualWebZeroSuggestName[];
 extern const char kOmniboxClobberTriggersContextualWebZeroSuggestDescription[];
 
diff --git a/chrome/browser/image_fetcher/android/java/src/org/chromium/chrome/browser/image_fetcher/ImageFetcher.java b/chrome/browser/image_fetcher/android/java/src/org/chromium/chrome/browser/image_fetcher/ImageFetcher.java
index 40b074f5..3406aff 100644
--- a/chrome/browser/image_fetcher/android/java/src/org/chromium/chrome/browser/image_fetcher/ImageFetcher.java
+++ b/chrome/browser/image_fetcher/android/java/src/org/chromium/chrome/browser/image_fetcher/ImageFetcher.java
@@ -29,7 +29,8 @@
     public static final String FEED_UMA_CLIENT_NAME = "Feed";
     public static final String NTP_ANIMATED_LOGO_UMA_CLIENT_NAME = "NewTabPageAnimatedLogo";
     public static final String QUERY_TILE_UMA_CLIENT_NAME = "QueryTiles";
-    public static final String VIDEO_TUTORIALS_UMA_CLIENT_NAME = "VideoTutorials";
+    public static final String VIDEO_TUTORIALS_IPH_UMA_CLIENT_NAME = "VideoTutorialsIPH";
+    public static final String VIDEO_TUTORIALS_LIST_UMA_CLIENT_NAME = "VideoTutorialsList";
 
     /**
      * Encapsulates image fetching customization options. Supports a subset of the native
diff --git a/chrome/browser/media/kaleidoscope/kaleidoscope_tab_helper.cc b/chrome/browser/media/kaleidoscope/kaleidoscope_tab_helper.cc
index d45d28a..d7b853f 100644
--- a/chrome/browser/media/kaleidoscope/kaleidoscope_tab_helper.cc
+++ b/chrome/browser/media/kaleidoscope/kaleidoscope_tab_helper.cc
@@ -14,6 +14,12 @@
 
 const url::Origin& KaleidoscopeOrigin() {
   static base::NoDestructor<url::Origin> origin(
+      url::Origin::Create(GURL(kKaleidoscopeUIURL)));
+  return *origin;
+}
+
+const url::Origin& KaleidoscopeUntrustedOrigin() {
+  static base::NoDestructor<url::Origin> origin(
       url::Origin::Create(GURL(kKaleidoscopeUntrustedContentUIURL)));
   return *origin;
 }
@@ -28,6 +34,27 @@
   kMaxValue = kNormal,
 };
 
+bool IsOpenedFromKaleidoscope(content::NavigationHandle* handle) {
+  return (handle->GetInitiatorOrigin() &&
+          handle->GetInitiatorOrigin()->IsSameOriginWith(
+              KaleidoscopeUntrustedOrigin()));
+}
+
+bool ShouldAllowAutoplay(content::NavigationHandle* handle) {
+  // If the initiating origin is Kaleidoscope then we should allow autoplay.
+  if (IsOpenedFromKaleidoscope(handle))
+    return true;
+
+  // If the tab is Kaleidoscope then we should allow autoplay.
+  auto parent_origin =
+      url::Origin::Create(handle->GetWebContents()->GetLastCommittedURL());
+  if (parent_origin.IsSameOriginWith(KaleidoscopeOrigin())) {
+    return true;
+  }
+
+  return false;
+}
+
 }  // namespace
 
 KaleidoscopeTabHelper::KaleidoscopeTabHelper(content::WebContents* web_contents)
@@ -37,15 +64,11 @@
 
 void KaleidoscopeTabHelper::ReadyToCommitNavigation(
     content::NavigationHandle* handle) {
-  if (!handle->IsInMainFrame() || handle->IsSameDocument() ||
-      handle->IsErrorPage()) {
+  if (handle->IsSameDocument() || handle->IsErrorPage())
     return;
-  }
 
-  if (!handle->GetInitiatorOrigin() ||
-      !handle->GetInitiatorOrigin()->IsSameOriginWith(KaleidoscopeOrigin())) {
+  if (!ShouldAllowAutoplay(handle))
     return;
-  }
 
   mojo::AssociatedRemote<blink::mojom::AutoplayConfigurationClient> client;
   handle->GetRenderFrameHost()->GetRemoteAssociatedInterfaces()->GetInterface(
@@ -53,8 +76,11 @@
   client->AddAutoplayFlags(url::Origin::Create(handle->GetURL()),
                            blink::mojom::kAutoplayFlagUserException);
 
-  base::UmaHistogramEnumeration(kKaleidoscopeNavigationHistogramName,
-                                KaleidoscopeNavigation::kNormal);
+  // Only record metrics if this page was opened by Kaleidoscope.
+  if (IsOpenedFromKaleidoscope(handle)) {
+    base::UmaHistogramEnumeration(kKaleidoscopeNavigationHistogramName,
+                                  KaleidoscopeNavigation::kNormal);
+  }
 }
 
 WEB_CONTENTS_USER_DATA_KEY_IMPL(KaleidoscopeTabHelper)
diff --git a/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.cc
index ee3f0bb..c8b4beb 100644
--- a/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.cc
@@ -101,6 +101,25 @@
   return data;
 }
 
+int SiteInstanceRenderProcessAssignmentToInt(
+    content::SiteInstanceProcessAssignment assignment) {
+  // These values are logged in UKM and should not be reordered or changed. Add
+  // new values to the end and be sure to update the enum
+  // |SiteInstanceProcessAssignment| in
+  // //tools/metrics/histograms/enums.xml.
+  switch (assignment) {
+    case content::SiteInstanceProcessAssignment::UNKNOWN:
+      return 0;
+    case content::SiteInstanceProcessAssignment::REUSED_EXISTING_PROCESS:
+      return 1;
+    case content::SiteInstanceProcessAssignment::USED_SPARE_PROCESS:
+      return 2;
+    case content::SiteInstanceProcessAssignment::CREATED_NEW_PROCESS:
+      return 3;
+  }
+  return 0;
+}
+
 }  // namespace
 
 // static
@@ -211,6 +230,12 @@
                                        ->GetController()
                                        .GetLastCommittedEntry()
                                        ->GetMainFrameDocumentSequenceNumber();
+
+  render_process_assignment_ = navigation_handle->GetWebContents()
+                                   ->GetMainFrame()
+                                   ->GetSiteInstance()
+                                   ->GetLastProcessAssignmentOutcome();
+
   return CONTINUE_OBSERVING;
 }
 
@@ -224,6 +249,7 @@
   if (!was_hidden_) {
     RecordNavigationTimingMetrics();
     RecordPageLoadMetrics(current_time);
+    RecordRendererUsageMetrics();
     RecordTimingMetrics(timing);
     RecordInputTimingMetrics();
   }
@@ -247,6 +273,7 @@
   if (!was_hidden_) {
     RecordNavigationTimingMetrics();
     RecordPageLoadMetrics(base::TimeTicks() /* no app_background_time */);
+    RecordRendererUsageMetrics();
     RecordTimingMetrics(timing);
     RecordInputTimingMetrics();
     was_hidden_ = true;
@@ -275,6 +302,8 @@
 
   RecordPageLoadMetrics(base::TimeTicks() /* no app_background_time */);
 
+  RecordRendererUsageMetrics();
+
   // Error codes have negative values, however we log net error code enum values
   // for UMA histograms using the equivalent positive value. For consistency in
   // UKM, we convert to a positive value here.
@@ -296,6 +325,7 @@
   if (!was_hidden_) {
     RecordNavigationTimingMetrics();
     RecordPageLoadMetrics(current_time /* no app_background_time */);
+    RecordRendererUsageMetrics();
     RecordTimingMetrics(timing);
     RecordInputTimingMetrics();
   }
@@ -666,6 +696,9 @@
         foreground_duration.value().InMilliseconds());
   }
 
+  builder.SetSiteInstanceRenderProcessAssignment(
+      SiteInstanceRenderProcessAssignmentToInt(render_process_assignment_));
+
   // Convert to the EffectiveConnectionType as used in SystemProfileProto
   // before persisting the metric.
   metrics::SystemProfileProto::Network::EffectiveConnectionType
@@ -706,6 +739,15 @@
   builder.Record(ukm::UkmRecorder::Get());
 }
 
+void UkmPageLoadMetricsObserver::RecordRendererUsageMetrics() {
+  ukm::builders::PageLoad builder(GetDelegate().GetPageUkmSourceId());
+
+  builder.SetSiteInstanceRenderProcessAssignment(
+      SiteInstanceRenderProcessAssignmentToInt(render_process_assignment_));
+
+  builder.Record(ukm::UkmRecorder::Get());
+}
+
 void UkmPageLoadMetricsObserver::ReportMainResourceTimingMetrics(
     const page_load_metrics::mojom::PageLoadTiming& timing,
     ukm::builders::PageLoad* builder) {
diff --git a/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.h b/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.h
index fff07778..97a7d32 100644
--- a/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.h
+++ b/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.h
@@ -10,6 +10,7 @@
 #include "base/optional.h"
 #include "base/time/time.h"
 #include "components/page_load_metrics/browser/page_load_metrics_observer.h"
+#include "content/public/browser/site_instance_process_assignment.h"
 #include "net/http/http_response_info.h"
 #include "services/metrics/public/cpp/ukm_source.h"
 #include "ui/base/page_transition_types.h"
@@ -133,6 +134,10 @@
   // a null TimeTicks.
   void RecordPageLoadMetrics(base::TimeTicks app_background_time);
 
+  // Records metrics related to how the renderer process was used for the
+  // navigation.
+  void RecordRendererUsageMetrics();
+
   // Adds main resource timing metrics to |builder|.
   void ReportMainResourceTimingMetrics(
       const page_load_metrics::mojom::PageLoadTiming& timing,
@@ -210,6 +215,9 @@
   content::NavigationHandleTiming navigation_handle_timing_;
   base::Optional<net::LoadTimingInfo> main_frame_timing_;
 
+  // How the SiteInstance for the committed page was assigned a renderer.
+  content::SiteInstanceProcessAssignment render_process_assignment_;
+
   // PAGE_TRANSITION_LINK is the default PageTransition value.
   ui::PageTransition page_transition_ = ui::PAGE_TRANSITION_LINK;
 
diff --git a/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer_unittest.cc
index aa49a71..dfa5847c 100644
--- a/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer_unittest.cc
+++ b/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer_unittest.cc
@@ -1462,6 +1462,26 @@
               testing::ElementsAre(base::Bucket(25, 1)));
 }
 
+TEST_F(UkmPageLoadMetricsObserverTest, SiteInstanceRenderProcessAssignment) {
+  NavigateAndCommit(GURL(kTestUrl1));
+
+  // Simulate closing the tab.
+  DeleteContents();
+
+  const auto& ukm_recorder = tester()->test_ukm_recorder();
+  std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries =
+      ukm_recorder.GetMergedEntriesByName(PageLoad::kEntryName);
+  EXPECT_EQ(1ul, merged_entries.size());
+
+  for (const auto& kv : merged_entries) {
+    const int64_t* metric = ukm_recorder.GetEntryMetric(
+        kv.second.get(),
+        ukm::builders::PageLoad::kSiteInstanceRenderProcessAssignmentName);
+    EXPECT_TRUE(metric);
+    EXPECT_NE(0u, *metric);
+  }
+}
+
 TEST_F(UkmPageLoadMetricsObserverTest,
        PerfectHeuristicsDelayaAsyncScriptExecution) {
   NavigateAndCommit(GURL(kTestUrl1));
diff --git a/chrome/browser/paint_preview/android/java/src/org/chromium/chrome/browser/paint_preview/TabbedPaintPreviewPlayer.java b/chrome/browser/paint_preview/android/java/src/org/chromium/chrome/browser/paint_preview/TabbedPaintPreviewPlayer.java
index 73cc445..495d7ec0 100644
--- a/chrome/browser/paint_preview/android/java/src/org/chromium/chrome/browser/paint_preview/TabbedPaintPreviewPlayer.java
+++ b/chrome/browser/paint_preview/android/java/src/org/chromium/chrome/browser/paint_preview/TabbedPaintPreviewPlayer.java
@@ -14,6 +14,7 @@
 
 import androidx.annotation.Nullable;
 
+import org.chromium.base.Callback;
 import org.chromium.base.UserData;
 import org.chromium.chrome.browser.browser_controls.BrowserStateBrowserControlsVisibilityDelegate;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
@@ -33,6 +34,7 @@
 import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.util.TokenHolder;
+import org.chromium.ui.widget.Toast;
 import org.chromium.url.GURL;
 
 import java.util.concurrent.Callable;
@@ -55,7 +57,6 @@
     private PlayerManager mPlayerManager;
     private Runnable mOnDismissed;
     private Boolean mInitializing;
-    private boolean mHasUserInteraction;
     private boolean mFirstMeaningfulPaintHappened;
     private TabbedPaintPreviewObserver mObserver;
     private long mLastShownSnackBarTime;
@@ -65,6 +66,9 @@
     private TabbedPaintPreviewMetricsHelper mMetricsHelper;
     private BrowserStateBrowserControlsVisibilityDelegate mBrowserVisibilityDelegate;
     private int mPersistentToolbarToken = TokenHolder.INVALID_TOKEN;
+    private SnackbarManager.SnackbarController mSnackbarController;
+    private Runnable mProgressSimulatorNeededCallback;
+    private Callback<Boolean> mProgressPreventionCallback;
 
     public static TabbedPaintPreviewPlayer get(Tab tab) {
         if (tab.getUserDataHost().getUserData(USER_DATA_KEY) == null) {
@@ -89,12 +93,7 @@
             new Handler().postDelayed(() -> {
                 if (!isShowingAndNeedsBadge()) return;
 
-                if (!mHasUserInteraction) {
-                    removePaintPreview(ExitCause.TAB_FINISHED_LOADING);
-                    return;
-                }
-
-                showSnackbar();
+                removePaintPreview(ExitCause.TAB_FINISHED_LOADING);
             }, delayMs);
         }
 
@@ -121,6 +120,10 @@
         @Override
         public void onHidden(Tab tab, @TabHidingType int hidingType) {
             releasePersistentToolbar();
+            dismissSnackbar();
+            if (mProgressSimulatorNeededCallback != null) {
+                mProgressPreventionCallback.onResult(false);
+            }
 
             if (mPlayerManager == null || !isShowingAndNeedsBadge()) return;
 
@@ -132,7 +135,12 @@
 
         @Override
         public void onShown(Tab tab, int type) {
-            if (isShowingAndNeedsBadge()) showToolbarPersistent();
+            if (!isShowingAndNeedsBadge()) return;
+
+            showToolbarPersistent();
+            if (mProgressSimulatorNeededCallback != null) {
+                mProgressPreventionCallback.onResult(true);
+            }
         }
     }
 
@@ -149,6 +157,14 @@
         mBrowserVisibilityDelegate = browserVisibilityDelegate;
     }
 
+    public void setProgressSimulatorNeededCallback(Runnable callback) {
+        mProgressSimulatorNeededCallback = callback;
+    }
+
+    public void setProgressbarUpdatePreventionCallback(Callback<Boolean> callback) {
+        mProgressPreventionCallback = callback;
+    }
+
     /**
      * Triggered via {@link PageLoadMetrics.Observer} when First Meaningful Paint happens.
      * @param webContents the webContents that triggered the event.
@@ -204,7 +220,7 @@
 
                     mMetricsHelper.onFirstPaint(activityCreationTimestampMs, recordFirstPaint);
                 },
-                () -> mHasUserInteraction = true,
+                null,
                 ChromeColors.getPrimaryBackgroundColor(mTab.getContext().getResources(), false),
                 (status) -> {
                     mMetricsHelper.onCompositorFailure(status);
@@ -254,28 +270,40 @@
                         mFadingOut = false;
                     }
                 });
+        if (exitCause == ExitCause.TAB_FINISHED_LOADING) showUpgradeToast();
+        if (mProgressSimulatorNeededCallback != null) mProgressSimulatorNeededCallback.run();
         mMetricsHelper.recordExitMetrics(exitCause, mSnackbarShownCount);
     }
 
+    private void showUpgradeToast() {
+        if (mTab == null) return;
+
+        Toast.makeText(mTab.getContext(), R.string.paint_preview_startup_auto_upgrade_toast,
+                Toast.LENGTH_SHORT).show();
+    }
+
     private void showSnackbar() {
         if (mTab == null || mTab.getWindowAndroid() == null) return;
 
         // If the Snackbar is already being displayed, return.
         if (System.currentTimeMillis() - mLastShownSnackBarTime < SNACKBAR_DURATION_MS) return;
 
+        if (mSnackbarController == null) {
+            mSnackbarController = new SnackbarManager.SnackbarController() {
+                @Override
+                public void onAction(Object actionData) {
+                    removePaintPreview(ExitCause.SNACK_BAR_ACTION);
+                }
+
+                @Override
+                public void onDismissNoAction(Object actionData) {}
+            };
+        }
         Resources resources = mTab.getContext().getResources();
         Snackbar snackbar = Snackbar.make(
                 resources.getString(R.string.paint_preview_startup_upgrade_snackbar_message),
-                new SnackbarManager.SnackbarController() {
-                    @Override
-                    public void onAction(Object actionData) {
-                        removePaintPreview(ExitCause.SNACK_BAR_ACTION);
-                    }
-
-                    @Override
-                    public void onDismissNoAction(Object actionData) {}
-                },
-                Snackbar.TYPE_NOTIFICATION, Snackbar.UMA_PAINT_PREVIEW_UPGRADE_NOTIFICATION);
+                mSnackbarController, Snackbar.TYPE_NOTIFICATION,
+                Snackbar.UMA_PAINT_PREVIEW_UPGRADE_NOTIFICATION);
         snackbar.setAction(
                 resources.getString(R.string.paint_preview_startup_upgrade_snackbar_action), null);
         snackbar.setDuration(SNACKBAR_DURATION_MS);
@@ -284,6 +312,12 @@
         mSnackbarShownCount++;
     }
 
+    private void dismissSnackbar() {
+        if (mSnackbarController == null || mTab == null || mTab.getWindowAndroid() == null) return;
+
+        SnackbarManagerProvider.from(mTab.getWindowAndroid()).dismissSnackbars(mSnackbarController);
+    }
+
     public boolean isShowingAndNeedsBadge() {
         if (mTab == null) return false;
 
@@ -329,11 +363,14 @@
     @Override
     public void onShown() {
         showToolbarPersistent();
+        if (mProgressSimulatorNeededCallback != null) mProgressPreventionCallback.onResult(true);
     }
 
     @Override
     public void onHidden() {
         releasePersistentToolbar();
+        dismissSnackbar();
+        if (mProgressSimulatorNeededCallback != null) mProgressPreventionCallback.onResult(false);
     }
 
     @Override
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index 9fba9f14..34bf25d 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -1805,6 +1805,12 @@
           SCHEMA_ALLOW_UNKNOWN,
           SimpleSchemaValidatingPolicyHandler::RECOMMENDED_PROHIBITED,
           SimpleSchemaValidatingPolicyHandler::MANDATORY_ALLOWED)));
+  handlers->AddHandler(std::make_unique<SimpleSchemaValidatingPolicyHandler>(
+      key::kEduCoexistenceToSVersion,
+      chromeos::prefs::kEduCoexistenceToSVersion, chrome_schema,
+      SCHEMA_ALLOW_UNKNOWN,
+      SimpleSchemaValidatingPolicyHandler::RECOMMENDED_PROHIBITED,
+      SimpleSchemaValidatingPolicyHandler::MANDATORY_ALLOWED));
   handlers->AddHandler(
       std::make_unique<EcryptfsMigrationStrategyPolicyHandler>());
   handlers->AddHandler(std::make_unique<SimpleSchemaValidatingPolicyHandler>(
diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
index 1c28b702..f10172a 100644
--- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
+++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
@@ -11,6 +11,7 @@
 #include "chrome/browser/autocomplete/autocomplete_classifier_factory.h"
 #include "chrome/browser/autocomplete/in_memory_url_index_factory.h"
 #include "chrome/browser/autocomplete/shortcuts_backend_factory.h"
+#include "chrome/browser/autofill/autofill_offer_manager_factory.h"
 #include "chrome/browser/autofill/personal_data_manager_factory.h"
 #include "chrome/browser/background/background_contents_service_factory.h"
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
@@ -232,6 +233,7 @@
 #endif
   AutocompleteClassifierFactory::GetInstance();
   autofill::PersonalDataManagerFactory::GetInstance();
+  autofill::AutofillOfferManagerFactory::GetInstance();
 #if BUILDFLAG(ENABLE_BACKGROUND_CONTENTS)
   BackgroundContentsServiceFactory::GetInstance();
 #endif
diff --git a/chrome/browser/resources/BUILD.gn b/chrome/browser/resources/BUILD.gn
index 60ea26a..a2e7b17c 100644
--- a/chrome/browser/resources/BUILD.gn
+++ b/chrome/browser/resources/BUILD.gn
@@ -91,7 +91,7 @@
         "webapks:closure_compile",
       ]
     }
-    if (is_win || is_mac || is_desktop_linux) {
+    if (is_win || is_mac || is_linux) {
       deps += [ "browser_switch:closure_compile" ]
     }
 
diff --git a/chrome/browser/resources/new_tab_page/app.html b/chrome/browser/resources/new_tab_page/app.html
index d15f0dc..99fab88 100644
--- a/chrome/browser/resources/new_tab_page/app.html
+++ b/chrome/browser/resources/new_tab_page/app.html
@@ -108,6 +108,11 @@
     margin-top: 16px;
   }
 
+  :host(:not([promo-and-modules-loaded_])) ntp-middle-slot-promo,
+  :host(:not([promo-and-modules-loaded_])) ntp-module-wrapper {
+    visibility: hidden;
+  }
+
   #customizeButtonSpacer {
     flex-grow: 1;
   }
diff --git a/chrome/browser/resources/new_tab_page/app.js b/chrome/browser/resources/new_tab_page/app.js
index 71f4b04..c79a3dc 100644
--- a/chrome/browser/resources/new_tab_page/app.js
+++ b/chrome/browser/resources/new_tab_page/app.js
@@ -199,6 +199,25 @@
         reflectToAttribute: true,
       },
 
+      /** @private */
+      middleSlotPromoLoaded_: Boolean,
+
+      /** @private */
+      modulesLoaded_: Boolean,
+
+      /**
+       * In order to avoid flicker, the promo and modules are hidden until both
+       * are loaded. If modules are disabled, the promo is shown as soon as it
+       * is loaded.
+       * @private
+       */
+      promoAndModulesLoaded_: {
+        type: Boolean,
+        computed: `computePromoAndModulesLoaded_(middleSlotPromoLoaded_,
+            modulesLoaded_)`,
+        reflectToAttribute: true,
+      },
+
       /**
        * If true, renders additional elements that were not deemed crucial to
        * to show up immediately on load.
@@ -472,6 +491,15 @@
         !!this.theme_;
   }
 
+  /**
+   * @return {boolean}
+   * @private
+   */
+  computePromoAndModulesLoaded_() {
+    return this.middleSlotPromoLoaded_ &&
+        (!loadTimeData.getBoolean('modulesEnabled') || this.modulesLoaded_);
+  }
+
   /** @private */
   async onLazyRendered_() {
     if (!loadTimeData.getBoolean('modulesEnabled')) {
@@ -479,6 +507,7 @@
     }
     this.moduleDescriptors_ =
         await ModuleRegistry.getInstance().initializeModules();
+    this.modulesLoaded_ = true;
   }
 
   /** @private */
@@ -762,6 +791,7 @@
 
   /** @private */
   onMiddleSlotPromoLoaded_() {
+    this.middleSlotPromoLoaded_ = true;
     // The promo is always shown when modules are enabled since it will not
     // overlap with other elements.
     if (this.modulesEnabled_) {
diff --git a/chrome/browser/resources/new_tab_page/middle_slot_promo.js b/chrome/browser/resources/new_tab_page/middle_slot_promo.js
index 93b716b..407758f 100644
--- a/chrome/browser/resources/new_tab_page/middle_slot_promo.js
+++ b/chrome/browser/resources/new_tab_page/middle_slot_promo.js
@@ -84,9 +84,9 @@
       });
       this.pageHandler_.onPromoRendered(
           BrowserProxy.getInstance().now(), promo.logUrl || null);
+      this.hidden = false;
       this.dispatchEvent(new Event(
           'ntp-middle-slot-promo-loaded', {bubbles: true, composed: true}));
-      this.hidden = false;
     });
   }
 
diff --git a/chrome/browser/resources/new_tab_page/modules/shopping_tasks/module.html b/chrome/browser/resources/new_tab_page/modules/shopping_tasks/module.html
index 97ca0da2..3d32fa5 100644
--- a/chrome/browser/resources/new_tab_page/modules/shopping_tasks/module.html
+++ b/chrome/browser/resources/new_tab_page/modules/shopping_tasks/module.html
@@ -133,7 +133,7 @@
 </style>
 <div id="products">
   <template is="dom-repeat" id="productsRepeat"
-      items="[[shoppingTask.products]]" on-dom-change="onDomChange_">
+      items="[[shoppingTask.products]]">
     <a class="product" href="[[item.targetUrl.url]]" on-click="onClick_"
         on-auxclick="onClick_">
       <div class="image">
@@ -146,8 +146,8 @@
   </template>
 </div>
 <div id="relatedSearches">
-  <template is="dom-repeat" id="relatedSearchesRepeat"
-      items="[[shoppingTask.relatedSearches]]" on-dom-change="onDomChange_">
+  <template is="dom-repeat"  id="relatedSearchesRepeat"
+      items="[[shoppingTask.relatedSearches]]">
     <a class="pill" href="[[item.targetUrl.url]]" on-click="onClick_"
         on-auxclick="onClick_">
       <div class="loupe"></div>
diff --git a/chrome/browser/resources/new_tab_page/modules/shopping_tasks/module.js b/chrome/browser/resources/new_tab_page/modules/shopping_tasks/module.js
index 6b815055..1a29bd7a 100644
--- a/chrome/browser/resources/new_tab_page/modules/shopping_tasks/module.js
+++ b/chrome/browser/resources/new_tab_page/modules/shopping_tasks/module.js
@@ -34,13 +34,6 @@
     };
   }
 
-  /** @override */
-  ready() {
-    super.ready();
-    /** @type {IntersectionObserver} */
-    this.intersectionObserver_ = null;
-  }
-
   /** @private */
   onClick_() {
     this.dispatchEvent(new Event('usage', {bubbles: true, composed: true}));
@@ -50,22 +43,6 @@
   onCloseClick_() {
     this.showInfoDialog = false;
   }
-
-  /** @private */
-  onDomChange_() {
-    if (!this.intersectionObserver_) {
-      this.intersectionObserver_ = new IntersectionObserver(entries => {
-        entries.forEach(({intersectionRatio, target}) => {
-          target.style.visibility =
-              intersectionRatio < 1 ? 'hidden' : 'visible';
-        });
-      }, {root: this, threshold: 1});
-    } else {
-      this.intersectionObserver_.disconnect();
-    }
-    this.shadowRoot.querySelectorAll('.product, .pill')
-        .forEach(el => this.intersectionObserver_.observe(el));
-  }
 }
 
 customElements.define(
diff --git a/chrome/browser/resources/pdf/elements/viewer-thumbnail-bar.js b/chrome/browser/resources/pdf/elements/viewer-thumbnail-bar.js
index 5eaf4890..850169d 100644
--- a/chrome/browser/resources/pdf/elements/viewer-thumbnail-bar.js
+++ b/chrome/browser/resources/pdf/elements/viewer-thumbnail-bar.js
@@ -49,26 +49,29 @@
     /** @private {!IntersectionObserver} */
     this.intersectionObserver_ = new IntersectionObserver(entries => {
       entries.forEach(entry => {
-        if (!entry.isIntersecting) {
-          // TODO(crbug.com/652400): Unpaint thumbnails.
-          return;
-        }
-
         const thumbnail = /** @type {!ViewerThumbnailElement} */ (entry.target);
-        if (thumbnail.isPending()) {
+
+        if (!entry.isIntersecting) {
+          thumbnail.clearImage();
           return;
         }
 
-        thumbnail.setPending();
+        if (thumbnail.isPainted()) {
+          return;
+        }
+
+        thumbnail.setPainted();
         this.dispatchEvent(new CustomEvent(
             'paint-thumbnail',
             {detail: thumbnail, bubbles: true, composed: true}));
       });
     }, {
       root: thumbnailsDiv,
-      // The vertical root margin is set to 100% to also track thumbnails that
-      // are one standard finger swipe away.
-      rootMargin: '100% 0%',
+      // The root margin is set to 100% on the bottom to prepare thumbnails that
+      // are one standard scroll finger swipe away.
+      // The root margin is set to 500% on the top to discard thumbnails that
+      // far from view, but to avoid regenerating thumbnails that are close.
+      rootMargin: '500% 0% 100%',
     });
 
     FocusOutlineManager.forDocument(document);
diff --git a/chrome/browser/resources/pdf/elements/viewer-thumbnail.js b/chrome/browser/resources/pdf/elements/viewer-thumbnail.js
index b743e9d..03c8dce3 100644
--- a/chrome/browser/resources/pdf/elements/viewer-thumbnail.js
+++ b/chrome/browser/resources/pdf/elements/viewer-thumbnail.js
@@ -16,6 +16,9 @@
 /** @type {number} */
 const LANDSCAPE_WIDTH = 140;
 
+/** @type {string} */
+const PAINTED_ATTRIBUTE = 'painted';
+
 export class ViewerThumbnailElement extends PolymerElement {
   static get is() {
     return 'viewer-thumbnail';
@@ -66,7 +69,20 @@
 
     const ctx = canvas.getContext('2d');
     ctx.putImageData(imageData, 0, 0);
-    this.removeAttribute('pending');
+  }
+
+  clearImage() {
+    if (!this.isPainted()) {
+      return;
+    }
+
+    const canvas = this.getCanvas_();
+    const ctx = canvas.getContext('2d');
+    ctx.clearRect(0, 0, canvas.width, canvas.height);
+    this.removeAttribute(PAINTED_ATTRIBUTE);
+
+    // For tests
+    this.dispatchEvent(new CustomEvent('clear-thumbnail-for-testing'));
   }
 
   /** @return {!HTMLElement} */
@@ -122,12 +138,12 @@
   }
 
   /** @return {boolean} */
-  isPending() {
-    return this.hasAttribute('pending');
+  isPainted() {
+    return this.hasAttribute(PAINTED_ATTRIBUTE);
   }
 
-  setPending() {
-    this.toggleAttribute('pending', true);
+  setPainted() {
+    this.toggleAttribute(PAINTED_ATTRIBUTE, true);
   }
 
   /** @private */
diff --git a/chrome/browser/resources/settings/autofill_page/password_check.html b/chrome/browser/resources/settings/autofill_page/password_check.html
index eec0e9cb..46fdf9c 100644
--- a/chrome/browser/resources/settings/autofill_page/password_check.html
+++ b/chrome/browser/resources/settings/autofill_page/password_check.html
@@ -111,14 +111,6 @@
         <h2>$i18n{compromisedPasswords}</h2>
       </div>
       <div class="list-frame vertical-list">
-        <div class="list-item secondary" hidden$="[[!isSignedOut_]]"
-            id="signedOutUserLabel">
-          <div>
-            $i18nRaw{signedOutUserLabel}
-          </div>
-        </div>
-      </div>
-      <div class="list-frame vertical-list">
         <div class="list-item secondary" id="compromisedPasswordsDescription"
             hidden$="[[!hasLeakedCredentials_(leakedPasswords)]]">
           $i18n{compromisedPasswordsDescription}
@@ -140,6 +132,12 @@
           </template>
         </iron-list>
       </div>
+      <div class="list-frame vertical-list">
+        <div class="list-item secondary" hidden$="[[!isSignedOut_]]"
+            id="signedOutUserLabel">
+          <div inner-h-t-m-l="[[getSignedOutUserLabel_(leakedPasswords)]]"></div>
+        </div>
+      </div>
     </div>
     <!-- TODO(crbug.com/1119752): |weakCredentialsBody| is almost a copy-paste
     of |compromisedCredentialsBody|. Clean it up by creating a polymer element
diff --git a/chrome/browser/resources/settings/autofill_page/password_check.js b/chrome/browser/resources/settings/autofill_page/password_check.js
index 6f451e8..06bf7ca 100644
--- a/chrome/browser/resources/settings/autofill_page/password_check.js
+++ b/chrome/browser/resources/settings/autofill_page/password_check.js
@@ -668,6 +668,22 @@
   },
 
   /**
+   * Returns the label that should be shown in the compromised password section
+   * if a user is signed out. This label depends on whether the user already had
+   * compromised credentials that were found in the past.
+   * @return {string}
+   * @private
+   */
+  getSignedOutUserLabel_() {
+    // This label contains the link, thus we need to use i18nAdvanced() here as
+    // well as the `inner-h-t-m-l` attribute in the DOM.
+    return this.i18nAdvanced(
+        this.hasLeakedCredentials_() ?
+            'signedOutUserHasCompromisedCredentialsLabel' :
+            'signedOutUserLabel');
+  },
+
+  /**
    * Returns true iff the leak check was performed at least once before.
    * @return {boolean}
    * @private
diff --git a/chrome/browser/resources/settings/chromeos/device_page/device_page.html b/chrome/browser/resources/settings/chromeos/device_page/device_page.html
index 0e7ee32..2867f9c 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/device_page.html
+++ b/chrome/browser/resources/settings/chromeos/device_page/device_page.html
@@ -24,7 +24,8 @@
         focus-config="[[focusConfig_]]">
       <div id="main" route-path="default">
         <cr-link-row id="pointersRow"
-            label="[[getPointersTitle_(hasMouse_, hasTouchpad_)]]"
+            label="[[getPointersTitle_(hasMouse_, hasPointingStick_,
+                                       hasTouchpad_)]]"
             on-click="onPointersTap_"
             role-description="$i18n{subpageArrowRoleDescription}"></cr-link-row>
         <cr-link-row class="hr" id="keyboardRow" label="$i18n{keyboardTitle}"
@@ -50,9 +51,12 @@
       <template is="dom-if" route-path="/pointer-overlay">
         <settings-subpage
             associated-control="[[$$('#pointersRow')]]"
-            page-title="[[getPointersTitle_(hasMouse_, hasTouchpad_)]]">
+            page-title="[[getPointersTitle_(hasMouse_, hasPointingStick_,
+                                            hasTouchpad_)]]">
           <settings-pointers prefs="{{prefs}}"
-              has-mouse="[[hasMouse_]]" has-touchpad="[[hasTouchpad_]]">
+              has-mouse="[[hasMouse_]]"
+              has-pointing-stick="[[hasPointingStick_]]"
+              has-touchpad="[[hasTouchpad_]]">
           </settings-pointers>
         </settings-subpage>
       </template>
diff --git a/chrome/browser/resources/settings/chromeos/device_page/device_page.js b/chrome/browser/resources/settings/chromeos/device_page/device_page.js
index 525e8809..1146521 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/device_page.js
+++ b/chrome/browser/resources/settings/chromeos/device_page/device_page.js
@@ -24,12 +24,18 @@
     showCrostini: Boolean,
 
     /**
-     * |hasMouse_| and |hasTouchpad_| start undefined so observers don't trigger
-     * until they have been populated.
+     * |hasMouse_|, |hasPointingStick_|, and |hasTouchpad_| start undefined so
+     * observers don't trigger until they have been populated.
      * @private
      */
     hasMouse_: Boolean,
 
+    /**
+     * Whether a pointing stick (such as a TrackPoint) is connected.
+     * @private
+     */
+    hasPointingStick_: Boolean,
+
     /** @private */
     hasTouchpad_: Boolean,
 
@@ -98,7 +104,7 @@
   },
 
   observers: [
-    'pointersChanged_(hasMouse_, hasTouchpad_)',
+    'pointersChanged_(hasMouse_, hasPointingStick_, hasTouchpad_)',
   ],
 
   /** @override */
@@ -106,6 +112,8 @@
     this.addWebUIListener(
         'has-mouse-changed', this.set.bind(this, 'hasMouse_'));
     this.addWebUIListener(
+        'has-pointing-stick-changed', this.set.bind(this, 'hasPointingStick_'));
+    this.addWebUIListener(
         'has-touchpad-changed', this.set.bind(this, 'hasTouchpad_'));
     settings.DevicePageBrowserProxyImpl.getInstance().initializePointers();
 
@@ -124,10 +132,13 @@
    * @private
    */
   getPointersTitle_() {
-    if (this.hasMouse_ && this.hasTouchpad_) {
+    // For the purposes of the title, we call pointing sticks mice. The user
+    // will know what we mean, and otherwise we'd get too many possible titles.
+    const hasMouseOrPointingStick = this.hasMouse_ || this.hasPointingStick_;
+    if (hasMouseOrPointingStick && this.hasTouchpad_) {
       return this.i18n('mouseAndTouchpadTitle');
     }
-    if (this.hasMouse_) {
+    if (hasMouseOrPointingStick) {
       return this.i18n('mouseTitle');
     }
     if (this.hasTouchpad_) {
@@ -191,11 +202,12 @@
 
   /**
    * @param {boolean} hasMouse
+   * @param {boolean} hasPointingStick
    * @param {boolean} hasTouchpad
    * @private
    */
-  pointersChanged_(hasMouse, hasTouchpad) {
-    this.$.pointersRow.hidden = !hasMouse && !hasTouchpad;
+  pointersChanged_(hasMouse, hasPointingStick, hasTouchpad) {
+    this.$.pointersRow.hidden = !hasMouse && !hasPointingStick && !hasTouchpad;
     this.checkPointerSubpage_();
   },
 
@@ -205,7 +217,8 @@
    */
   checkPointerSubpage_() {
     // Check that the properties have explicitly been set to false.
-    if (this.hasMouse_ === false && this.hasTouchpad_ === false &&
+    if (this.hasMouse_ === false && this.hasPointingStick_ === false &&
+        this.hasTouchpad_ === false &&
         settings.Router.getInstance().getCurrentRoute() ==
             settings.routes.POINTERS) {
       settings.Router.getInstance().navigateTo(settings.routes.DEVICE);
diff --git a/chrome/browser/resources/settings/chromeos/device_page/pointers.html b/chrome/browser/resources/settings/chromeos/device_page/pointers.html
index 8bef25bb..20b7b39 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/pointers.html
+++ b/chrome/browser/resources/settings/chromeos/device_page/pointers.html
@@ -34,10 +34,10 @@
         padding-inline-end: 0;
       }
     </style>
-    <div id="mouse" hidden$="[[!hasMouse]]">
+    <div id="mouse" hidden$="[[!showMouseSection_]]">
       <!-- Subsection title only appears if both mouse and touchpad exist. -->
       <h2 hidden$="[[!hasTouchpad]]">$i18n{mouseTitle}</h2>
-      <div class$="[[getSubsectionClass_(hasMouse, hasTouchpad)]]">
+      <div class$="[[getSubsectionClass_(showMouseSection_, hasTouchpad)]]">
         <!-- Do not change the mouse button pref before the mouse is released.
              See crbug.com/686949 -->
         <settings-toggle-button id="mouseSwapButton"
@@ -107,8 +107,8 @@
     </div>
     <div id="touchpad" hidden$="[[!hasTouchpad]]">
       <!-- Subsection title only appears if both mouse and touchpad exist. -->
-      <h2 hidden$="[[!hasMouse]]">$i18n{touchpadTitle}</h2>
-      <div class$="[[getSubsectionClass_(hasMouse, hasTouchpad)]]">
+      <h2 hidden$="[[!showMouseSection_]]">$i18n{touchpadTitle}</h2>
+      <div class$="[[getSubsectionClass_(showMouseSection_, hasTouchpad)]]">
         <settings-toggle-button id="enableTapToClick"
             pref="{{prefs.settings.touchpad.enable_tap_to_click}}"
             label="$i18n{touchpadTapToClickEnabledLabel}"
diff --git a/chrome/browser/resources/settings/chromeos/device_page/pointers.js b/chrome/browser/resources/settings/chromeos/device_page/pointers.js
index bf73bb3..be6fbda0 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/pointers.js
+++ b/chrome/browser/resources/settings/chromeos/device_page/pointers.js
@@ -23,9 +23,21 @@
 
     hasMouse: Boolean,
 
+    hasPointingStick: Boolean,
+
     hasTouchpad: Boolean,
 
     /**
+     * Interim property for use until we have a separate subsection for pointing
+     * sticks. (See crbug.com/1114828)
+     * @private
+     */
+    showMouseSection_: {
+      type: Boolean,
+      computed: 'computeShowMouseSection_(hasMouse, hasPointingStick)',
+    },
+
+    /**
      * TODO(michaelpg): settings-slider should optionally take a min and max so
      * we don't have to generate a simple range of natural numbers ourselves.
      * @type {!Array<number>}
@@ -81,6 +93,14 @@
     },
   },
 
+  /**
+   * @param {boolean} hasMouse
+   * @param {boolean} hasPointingStick
+   */
+  computeShowMouseSection_(hasMouse, hasPointingStick) {
+    return hasMouse || hasPointingStick;
+  },
+
   // Used to correctly identify when the mouse button has been released.
   // crbug.com/686949.
   receivedMouseSwapButtonsDown_: false,
@@ -100,13 +120,13 @@
 
   /**
    * Mouse and touchpad sections are only subsections if they are both present.
-   * @param {boolean} hasMouse
+   * @param {boolean} showMouseSection
    * @param {boolean} hasTouchpad
    * @return {string}
    * @private
    */
-  getSubsectionClass_(hasMouse, hasTouchpad) {
-    return hasMouse && hasTouchpad ? 'subsection' : '';
+  getSubsectionClass_(showMouseSection, hasTouchpad) {
+    return showMouseSection && hasTouchpad ? 'subsection' : '';
   },
 
   /** @private */
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/manage_a11y_page.js b/chrome/browser/resources/settings/chromeos/os_a11y_page/manage_a11y_page.js
index 8b60af6..4d1c425 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/manage_a11y_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/manage_a11y_page.js
@@ -197,8 +197,9 @@
     },
 
     /**
-     * |hasKeyboard_|, |hasMouse_|, and |hasTouchpad_| start undefined so
-     * observers don't trigger until they have been populated.
+     * |hasKeyboard_|, |hasMouse_|, |hasPointingStick_|, and |hasTouchpad_|
+     * start undefined so observers don't trigger until they have been
+     * populated.
      * @private
      */
     hasKeyboard_: Boolean,
@@ -207,6 +208,9 @@
     hasMouse_: Boolean,
 
     /** @private */
+    hasPointingStick_: Boolean,
+
+    /** @private */
     hasTouchpad_: Boolean,
 
     /**
@@ -268,7 +272,8 @@
   },
 
   observers: [
-    'pointersChanged_(hasMouse_, hasTouchpad_, isKioskModeActive_)',
+    'pointersChanged_(hasMouse_, hasPointingStick_, hasTouchpad_, ' +
+        'isKioskModeActive_)',
   ],
 
   /** settings.RouteOriginBehavior override */
@@ -292,6 +297,8 @@
     this.addWebUIListener(
         'has-mouse-changed', this.set.bind(this, 'hasMouse_'));
     this.addWebUIListener(
+        'has-pointing-stick-changed', this.set.bind(this, 'hasPointingStick_'));
+    this.addWebUIListener(
         'has-touchpad-changed', this.set.bind(this, 'hasTouchpad_'));
     this.deviceBrowserProxy_.initializePointers();
 
@@ -331,12 +338,13 @@
 
   /**
    * @param {boolean} hasMouse
+   * @param {boolean} hasPointingStick
    * @param {boolean} hasTouchpad
    * @private
    */
-  pointersChanged_(hasMouse, hasTouchpad, isKioskModeActive) {
+  pointersChanged_(hasMouse, hasTouchpad, hasPointingStick, isKioskModeActive) {
     this.$.pointerSubpageButton.hidden =
-        (!hasMouse && !hasTouchpad) || isKioskModeActive;
+        (!hasMouse && !hasPointingStick && !hasTouchpad) || isKioskModeActive;
   },
 
   /**
diff --git a/chrome/browser/safe_browsing/download_protection/deep_scanning_request.cc b/chrome/browser/safe_browsing/download_protection/deep_scanning_request.cc
index 7821412b..ec72cb2 100644
--- a/chrome/browser/safe_browsing/download_protection/deep_scanning_request.cc
+++ b/chrome/browser/safe_browsing/download_protection/deep_scanning_request.cc
@@ -10,6 +10,7 @@
 #include "base/optional.h"
 #include "base/strings/string_number_conversions.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/download/download_prefs.h"
 #include "chrome/browser/enterprise/connectors/common.h"
 #include "chrome/browser/enterprise/connectors/connectors_manager.h"
 #include "chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h"
@@ -22,6 +23,7 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/views/safe_browsing/deep_scanning_failure_modal_dialog.h"
+#include "chrome/common/pref_names.h"
 #include "components/download/public/common/download_item.h"
 #include "components/enterprise/common/proto/connectors.pb.h"
 #include "components/policy/core/browser/url_util.h"
@@ -106,6 +108,49 @@
   *download_result = DownloadCheckResult::DEEP_SCANNED_SAFE;
 }
 
+EventResult GetEventResult(DownloadCheckResult download_result,
+                           Profile* profile) {
+  auto download_restriction = static_cast<DownloadPrefs::DownloadRestriction>(
+      profile->GetPrefs()->GetInteger(prefs::kDownloadRestrictions));
+  switch (download_result) {
+    case DownloadCheckResult::UNKNOWN:
+    case DownloadCheckResult::SAFE:
+    case DownloadCheckResult::WHITELISTED_BY_POLICY:
+    case DownloadCheckResult::DEEP_SCANNED_SAFE:
+      return EventResult::ALLOWED;
+
+    // The following results return WARNED or BLOCKED depending on
+    // |download_restriction|.
+    case DownloadCheckResult::DANGEROUS:
+    case DownloadCheckResult::DANGEROUS_HOST:
+      switch (download_restriction) {
+        case DownloadPrefs::DownloadRestriction::ALL_FILES:
+        case DownloadPrefs::DownloadRestriction::POTENTIALLY_DANGEROUS_FILES:
+        case DownloadPrefs::DownloadRestriction::DANGEROUS_FILES:
+        case DownloadPrefs::DownloadRestriction::MALICIOUS_FILES:
+          return EventResult::BLOCKED;
+        case DownloadPrefs::DownloadRestriction::NONE:
+          return EventResult::WARNED;
+      }
+
+    case DownloadCheckResult::UNCOMMON:
+    case DownloadCheckResult::POTENTIALLY_UNWANTED:
+    case DownloadCheckResult::SENSITIVE_CONTENT_WARNING:
+      return EventResult::WARNED;
+
+    case DownloadCheckResult::BLOCKED_PASSWORD_PROTECTED:
+    case DownloadCheckResult::BLOCKED_TOO_LARGE:
+    case DownloadCheckResult::SENSITIVE_CONTENT_BLOCK:
+    case DownloadCheckResult::BLOCKED_UNSUPPORTED_FILE_TYPE:
+      return EventResult::BLOCKED;
+
+    default:
+      NOTREACHED() << "Should never be final result";
+      break;
+  }
+  return EventResult::UNKNOWN;
+}
+
 }  // namespace
 
 /* static */
@@ -228,36 +273,6 @@
       download_result = DownloadCheckResult::BLOCKED_UNSUPPORTED_FILE_TYPE;
   }
 
-  // Determine if the user is allowed to access the downloaded file.
-  EventResult event_result = EventResult::UNKNOWN;
-  switch (download_result) {
-    case DownloadCheckResult::UNKNOWN:
-    case DownloadCheckResult::SAFE:
-    case DownloadCheckResult::WHITELISTED_BY_POLICY:
-    case DownloadCheckResult::DEEP_SCANNED_SAFE:
-      event_result = EventResult::ALLOWED;
-      break;
-
-    case DownloadCheckResult::UNCOMMON:
-    case DownloadCheckResult::POTENTIALLY_UNWANTED:
-    case DownloadCheckResult::SENSITIVE_CONTENT_WARNING:
-    case DownloadCheckResult::DANGEROUS:
-    case DownloadCheckResult::DANGEROUS_HOST:
-      event_result = EventResult::WARNED;
-      break;
-
-    case DownloadCheckResult::BLOCKED_PASSWORD_PROTECTED:
-    case DownloadCheckResult::BLOCKED_TOO_LARGE:
-    case DownloadCheckResult::SENSITIVE_CONTENT_BLOCK:
-    case DownloadCheckResult::BLOCKED_UNSUPPORTED_FILE_TYPE:
-      event_result = EventResult::BLOCKED;
-      break;
-
-    default:
-      NOTREACHED() << "Should never be final result";
-      break;
-  }
-
   Profile* profile = Profile::FromBrowserContext(
       content::DownloadItemUtils::GetBrowserContext(item_));
   if (profile && trigger_ == DeepScanTrigger::TRIGGER_POLICY) {
@@ -268,7 +283,7 @@
         item_->GetMimeType(),
         extensions::SafeBrowsingPrivateEventRouter::kTriggerFileDownload,
         DeepScanAccessPoint::DOWNLOAD, item_->GetTotalBytes(), result, response,
-        event_result);
+        GetEventResult(download_result, profile));
 
     item_->SetUserData(
         enterprise_connectors::ScanResult::kKey,
diff --git a/chrome/browser/safe_browsing/download_protection/deep_scanning_request_unittest.cc b/chrome/browser/safe_browsing/download_protection/deep_scanning_request_unittest.cc
index 4ed5c330..f4a190d 100644
--- a/chrome/browser/safe_browsing/download_protection/deep_scanning_request_unittest.cc
+++ b/chrome/browser/safe_browsing/download_protection/deep_scanning_request_unittest.cc
@@ -9,6 +9,7 @@
 #include "base/files/scoped_temp_dir.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/download/download_prefs.h"
 #include "chrome/browser/enterprise/connectors/common.h"
 #include "chrome/browser/enterprise/connectors/connectors_manager.h"
 #include "chrome/browser/enterprise/connectors/connectors_prefs.h"
@@ -22,6 +23,7 @@
 #include "chrome/browser/safe_browsing/download_protection/download_protection_service.h"
 #include "chrome/browser/safe_browsing/download_protection/download_protection_util.h"
 #include "chrome/browser/safe_browsing/test_extension_event_observer.h"
+#include "chrome/common/pref_names.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chrome/test/base/testing_profile_manager.h"
@@ -732,6 +734,124 @@
   }
 }
 
+class DeepScanningDownloadRestrictionsTest
+    : public DeepScanningReportingTest,
+      public testing::WithParamInterface<DownloadPrefs::DownloadRestriction> {
+ public:
+  void SetUp() override {
+    DeepScanningReportingTest::SetUp();
+    profile_->GetPrefs()->SetInteger(prefs::kDownloadRestrictions,
+                                     static_cast<int>(download_restriction()));
+  }
+
+  DownloadPrefs::DownloadRestriction download_restriction() const {
+    return GetParam();
+  }
+
+  EventResult expected_event_result_for_malware() const {
+    switch (download_restriction()) {
+      case DownloadPrefs::DownloadRestriction::NONE:
+        return EventResult::WARNED;
+      case DownloadPrefs::DownloadRestriction::DANGEROUS_FILES:
+      case DownloadPrefs::DownloadRestriction::MALICIOUS_FILES:
+      case DownloadPrefs::DownloadRestriction::POTENTIALLY_DANGEROUS_FILES:
+      case DownloadPrefs::DownloadRestriction::ALL_FILES:
+        return EventResult::BLOCKED;
+    }
+  }
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    ,
+    DeepScanningDownloadRestrictionsTest,
+    testing::Values(
+        DownloadPrefs::DownloadRestriction::NONE,
+        DownloadPrefs::DownloadRestriction::DANGEROUS_FILES,
+        DownloadPrefs::DownloadRestriction::POTENTIALLY_DANGEROUS_FILES,
+        DownloadPrefs::DownloadRestriction::ALL_FILES,
+        DownloadPrefs::DownloadRestriction::MALICIOUS_FILES));
+
+TEST_P(DeepScanningDownloadRestrictionsTest, GeneratesCorrectReport) {
+  {
+    DeepScanningRequest request(
+        &item_, DeepScanningRequest::DeepScanTrigger::TRIGGER_POLICY,
+        base::BindRepeating(&DeepScanningRequestTest::SetLastResult,
+                            base::Unretained(this)),
+        &download_protection_service_, settings().value());
+
+    enterprise_connectors::ContentAnalysisResponse response;
+
+    auto* malware_result = response.add_results();
+    malware_result->set_tag("malware");
+    malware_result->set_status(
+        enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
+    auto* malware_rule = malware_result->add_triggered_rules();
+    malware_rule->set_action(enterprise_connectors::TriggeredRule::BLOCK);
+    malware_rule->set_rule_name("malware");
+
+    download_protection_service_.GetFakeBinaryUploadService()->SetResponse(
+        BinaryUploadService::Result::SUCCESS, response);
+
+    EventReportValidator validator(client_.get());
+    validator.ExpectDangerousDownloadEvent(
+        /*url*/ "https://example.com/download.exe",
+        /*filename*/ download_path_.AsUTF8Unsafe(),
+        // printf "download contents" | sha256sum |  tr '[:lower:]'
+        // '[:upper:]'
+        /*sha256*/
+        "76E00EB33811F5778A5EE557512C30D9341D4FEB07646BCE3E4DB13F9428573C",
+        /*threat_type*/ "DANGEROUS",
+        /*trigger*/
+        extensions::SafeBrowsingPrivateEventRouter::kTriggerFileDownload,
+        /*mimetypes*/ ExeMimeTypes(),
+        /*size*/ std::string("download contents").size(),
+        /*result*/ EventResultToString(expected_event_result_for_malware()));
+
+    request.Start();
+
+    EXPECT_EQ(DownloadCheckResult::DANGEROUS, last_result_);
+  }
+  {
+    DeepScanningRequest request(
+        &item_, DeepScanningRequest::DeepScanTrigger::TRIGGER_POLICY,
+        base::BindRepeating(&DeepScanningRequestTest::SetLastResult,
+                            base::Unretained(this)),
+        &download_protection_service_, settings().value());
+
+    enterprise_connectors::ContentAnalysisResponse response;
+
+    auto* malware_result = response.add_results();
+    malware_result->set_tag("malware");
+    malware_result->set_status(
+        enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
+    auto* malware_rule = malware_result->add_triggered_rules();
+    malware_rule->set_action(enterprise_connectors::TriggeredRule::WARN);
+    malware_rule->set_rule_name("uws");
+
+    download_protection_service_.GetFakeBinaryUploadService()->SetResponse(
+        BinaryUploadService::Result::SUCCESS, response);
+
+    EventReportValidator validator(client_.get());
+    validator.ExpectDangerousDownloadEvent(
+        /*url*/ "https://example.com/download.exe",
+        /*filename*/ download_path_.AsUTF8Unsafe(),
+        // printf "download contents" | sha256sum |  tr '[:lower:]'
+        // '[:upper:]'
+        /*sha256*/
+        "76E00EB33811F5778A5EE557512C30D9341D4FEB07646BCE3E4DB13F9428573C",
+        /*threat_type*/ "POTENTIALLY_UNWANTED",
+        /*trigger*/
+        extensions::SafeBrowsingPrivateEventRouter::kTriggerFileDownload,
+        /*mimetypes*/ ExeMimeTypes(),
+        /*size*/ std::string("download contents").size(),
+        /*result*/ EventResultToString(EventResult::WARNED));
+
+    request.Start();
+
+    EXPECT_EQ(DownloadCheckResult::POTENTIALLY_UNWANTED, last_result_);
+  }
+}
+
 TEST_F(DeepScanningRequestTest, ShouldUploadBinary_MalwareListPolicy) {
   SetFeatures(/*enabled*/ {enterprise_connectors::kEnterpriseConnectorsEnabled},
               /*disabled*/ {});
diff --git a/chrome/browser/sharing/click_to_call/click_to_call_ui_controller.cc b/chrome/browser/sharing/click_to_call/click_to_call_ui_controller.cc
index e7f10f4..4ec6efb 100644
--- a/chrome/browser/sharing/click_to_call/click_to_call_ui_controller.cc
+++ b/chrome/browser/sharing/click_to_call/click_to_call_ui_controller.cc
@@ -16,6 +16,7 @@
 #include "chrome/browser/shell_integration.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
+#include "chrome/grit/chromium_strings.h"
 #include "components/sync_device_info/device_info.h"
 #include "components/vector_icons/vector_icons.h"
 #include "content/public/browser/web_contents.h"
@@ -183,8 +184,6 @@
       IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES;
   data.help_text_origin_id =
       IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES_ORIGIN;
-  data.help_link_text_id =
-      IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_TROUBLESHOOT_LINK;
   data.origin_text_id =
       IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_INITIATING_ORIGIN;
 
diff --git a/chrome/browser/sharing/sharing_dialog_data.h b/chrome/browser/sharing/sharing_dialog_data.h
index 778f460..3d2e81a 100644
--- a/chrome/browser/sharing/sharing_dialog_data.h
+++ b/chrome/browser/sharing/sharing_dialog_data.h
@@ -47,12 +47,10 @@
   base::string16 error_text;
   int help_text_id = 0;
   int help_text_origin_id = 0;
-  int help_link_text_id = 0;
   base::Optional<HeaderIcons> header_icons;
   int origin_text_id = 0;
   base::Optional<url::Origin> initiating_origin;
 
-  base::OnceCallback<void(SharingDialogType)> help_callback;
   base::OnceCallback<void(const syncer::DeviceInfo&)> device_callback;
   base::OnceCallback<void(const SharingApp&)> app_callback;
   base::OnceCallback<void(SharingDialog*)> close_callback;
diff --git a/chrome/browser/sharing/sharing_ui_controller.cc b/chrome/browser/sharing/sharing_ui_controller.cc
index 3dfe25d..89789e17 100644
--- a/chrome/browser/sharing/sharing_ui_controller.cc
+++ b/chrome/browser/sharing/sharing_ui_controller.cc
@@ -15,6 +15,7 @@
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/singleton_tabs.h"
 #include "chrome/common/url_constants.h"
+#include "chrome/grit/chromium_strings.h"
 #include "components/sync_device_info/device_info.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/vector_icon_types.h"
@@ -112,11 +113,6 @@
   UpdateIcon();
 }
 
-void SharingUiController::OnHelpTextClicked(SharingDialogType dialog_type) {
-  ShowSingletonTab(chrome::FindBrowserWithWebContents(web_contents()),
-                   GURL(chrome::kSyncLearnMoreURL));
-}
-
 void SharingUiController::OnDialogShown(bool has_devices, bool has_apps) {
   if (on_dialog_shown_closure_for_testing_)
     std::move(on_dialog_shown_closure_for_testing_).Run();
@@ -161,8 +157,6 @@
   data.error_text = GetErrorDialogText();
 
   auto weak_ptr = weak_ptr_factory_.GetWeakPtr();
-  data.help_callback =
-      base::BindOnce(&SharingUiController::OnHelpTextClicked, weak_ptr);
   data.device_callback =
       base::BindOnce(&SharingUiController::OnDeviceChosen, weak_ptr);
   data.app_callback =
diff --git a/chrome/browser/sharing/sharing_ui_controller.h b/chrome/browser/sharing/sharing_ui_controller.h
index 43b86775..a058e8f8 100644
--- a/chrome/browser/sharing/sharing_ui_controller.h
+++ b/chrome/browser/sharing/sharing_ui_controller.h
@@ -66,8 +66,6 @@
 
   // Called by the SharingDialog when it is being closed.
   virtual void OnDialogClosed(SharingDialog* dialog);
-  // Called by the SharingDialogView when the help text got clicked.
-  virtual void OnHelpTextClicked(SharingDialogType dialog_type);
   // Called when a new dialog is shown.
   virtual void OnDialogShown(bool has_devices, bool has_apps);
 
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index bf4ca6cd..c6a03c98 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -589,7 +589,7 @@
   allow_circular_includes_from +=
       [ "//chrome/browser/ui/webui/bluetooth_internals" ]
 
-  if (is_win || is_mac || is_desktop_linux || is_chromeos) {
+  if (is_win || is_mac || is_linux || is_chromeos) {
     deps += [ "//chrome/browser/ui/webui/discards:mojo_bindings" ]
   }
 
@@ -2702,7 +2702,7 @@
     ]
   }
 
-  if (is_win || is_mac || is_desktop_linux || is_chromeos) {
+  if (is_win || is_mac || is_linux || is_chromeos) {
     sources += [
       "autofill/payments/virtual_card_selection_dialog_controller.h",
       "autofill/payments/virtual_card_selection_dialog_controller_impl.cc",
@@ -2771,7 +2771,7 @@
     deps += [ "//ui/webui" ]
   }
 
-  if (is_win || is_mac || is_desktop_linux) {
+  if (is_win || is_mac || is_linux) {
     sources += [
       "bookmarks/bookmark_bubble_sign_in_delegate.cc",
       "bookmarks/bookmark_bubble_sign_in_delegate.h",
@@ -3180,7 +3180,7 @@
     }
   }
 
-  if (is_desktop_linux) {
+  if (is_linux) {
     sources += [
       "views/apps/chrome_app_window_client_views_linux.cc",
       "views/first_run_dialog.cc",
@@ -3237,7 +3237,7 @@
       ]
     }
     if (use_ozone) {
-      if (!is_desktop_linux) {
+      if (!is_linux) {
         sources += [
           "views/frame/browser_desktop_window_tree_host_platform.cc",
           "views/frame/browser_desktop_window_tree_host_platform.h",
@@ -3245,7 +3245,7 @@
       }
       sources += [ "views/frame/native_browser_frame_factory_ozone.cc" ]
     }
-    if (is_desktop_linux) {
+    if (is_linux) {
       sources += [
         "views/frame/browser_desktop_window_tree_host_linux.cc",
         "views/frame/browser_desktop_window_tree_host_linux.h",
@@ -4075,7 +4075,7 @@
 
     allow_circular_includes_from += [ "//chrome/browser/ui/views" ]
 
-    if (is_desktop_linux) {
+    if (is_linux) {
       sources += [
         "views/chrome_views_delegate_linux.cc",
         "views/frame/desktop_linux_browser_frame_view.cc",
@@ -4127,7 +4127,7 @@
       sources += [ "views/chrome_views_delegate_win.cc" ]
     }
 
-    if (is_win || is_desktop_linux) {
+    if (is_win || is_linux) {
       sources += [
         "views/native_widget_factory.cc",
         "views/native_widget_factory.h",
@@ -4136,7 +4136,7 @@
 
     if (use_aura) {
       # These files can do Gtk+-based theming for builds with gtk enabled.
-      if (is_desktop_linux) {
+      if (is_linux) {
         sources += [
           "views/chrome_browser_main_extra_parts_views_linux.cc",
           "views/chrome_browser_main_extra_parts_views_linux.h",
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index f92e7ec..68f9562 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -2475,8 +2475,8 @@
       <message name="IDS_SIGNIN_ADD_ACCOUNT_TO_DEVICE" desc="Text for adding another Google Account to device in the web sign-in flow and account picker dialog, users will be asked to add an account to his device when this text is tapped">
         Add account to device
       </message>
-      <message name="IDS_SIGNIN_GO_INCOGNITO" desc="Button text to open the incognito interstitial in the web sign-in account picker bottom sheet">
-        Go Incognito
+      <message name="IDS_SIGNIN_INCOGNITO_BUTTON" desc="Button text to open the incognito interstitial in the web sign-in account picker bottom sheet">
+        Open an Incognito tab
       </message>
       <message name="IDS_SIGNIN_GMS_UPDATING" desc="Message notifying user that Google Play Services is still updating.">
         Waiting for Google Play Services to finish updating
@@ -2484,14 +2484,13 @@
 
       <!-- Strings for Incognito Interstitial. -->
       <message name="IDS_INCOGNITO_INTERSTITIAL_TITLE" desc="The title of the incognito interstitial bottom sheet.">
-        Go Incognito
+        Use Incognito to sign in
       </message>
-      <message name="IDS_INCOGNITO_INTERSTITIAL_MESSAGE" desc="The content of incognito interstitial which is shown when a user is in the sign-in flow and they want to go incognito mode to sign in temporarily. Before they go incognito mode they would be presented with this incognito interstitial which would inform them about how they can clear sign-in information in incognito, how their activity may be visbile to others and the possibility of cookies and other incognito data being used across other already open incognito tabs.">
-        To clear your sign-in information and other data from Chrome, close all Incognito tabs.
+      <message name="IDS_INCOGNITO_INTERSTITIAL_MESSAGE" desc="The content of Incognito interstitial which is shown when a user is in the sign-in flow and they want to go Incognito mode to continue signing in. Before they go Incognito mode they would be presented with an Incognito interstitial and this message is part of that interstitial which would inform how activity in Incognito may be visbile to others and how data from the Incognito session can be cleared.">
+In Incognito, your activity <ph name="BEGIN_BOLD1">&lt;b1&gt;</ph>might still be visible <ph name="END_BOLD1">&lt;/b1&gt;</ph>to websites that you visit, your employer or school, and your internet service provider.
 
-In Incognito, your activity might still be visible to websites that you visit, your employer or school, and your internet service provider.
+Data from your Incognito session will only be cleared from Chrome when you <ph name="BEGIN_BOLD2">&lt;b2&gt;</ph>close all Incognito tabs<ph name="END_BOLD2">&lt;/b2&gt;</ph>.
       </message>
-
       <!-- Strings for Streamlined Signin and Unified Consent. -->
       <message name="IDS_SIGNIN_TITLE" desc="Title for the screen that asks users to sign-in and turn on Sync. [CHAR-LIMIT=27]">
         Turn on sync?
@@ -3074,7 +3073,7 @@
       <message name="IDS_LOCATION_BAR_PREVIEW_LITE_PAGE_STATUS" desc="An indication in the location bar that the page the user is viewing has been modified in order to speed up the page load or decrease the amount of data used to load the page. [CHAR-LIMIT=10]">
         Lite
       </message>
-      <message name="IDS_LOCATION_BAR_PAINT_PREVIEW_PAGE_STATUS" desc="An indication in the location bar that the page the user is viewing is a paint preview of the original page. [CHAR-LIMIT=10]" translateable="false">
+      <message name="IDS_LOCATION_BAR_PAINT_PREVIEW_PAGE_STATUS" desc="An indication in the location bar that the page the user is viewing is a paint preview of the original page. [CHAR-LIMIT=10]">
         Preview
       </message>
 
@@ -3192,7 +3191,7 @@
         Switch To Tab
       </message>
       <message name="IDS_ACCESSIBILITY_OMNIBOX_SHOWING_SUGGESTIONS_FOR_WEBSITE" desc="Whenever the User is visiting a Website, when they click the Omnibox, we will announce this message, if we have suggestions to show.">
-        Showing suggestion for <ph name="WEBSITE_TITLE">%1$s<ex>Crater Lake National Park (U.S. National Park Service)</ex></ph>
+        Showing suggestions for <ph name="WEBSITE_TITLE">%1$s<ex>Crater Lake National Park (U.S. National Park Service)</ex></ph>
       </message>
       <message name="IDS_ACCESSIBILITY_OMNIBOX_MOST_VISITED_TILE" desc="When the user focuses a Frequently Visited URL tile suggestion, the tile will be crome://announced according to the following pattern">
         <ph name="WEBSITE_TITLE">%1$s<ex>Crater Lake National Park (U.S. National Park Service)</ex></ph>: <ph name="WEBSITE_URL">%1$s<ex>https://www.nps.gov/crla</ex></ph>
@@ -4124,11 +4123,15 @@
       </message>
 
       <!-- Paint Preview Startup Experiment -->
-      <message name="IDS_PAINT_PREVIEW_STARTUP_UPGRADE_SNACKBAR_MESSAGE" desc="Message displayed on a snackbar when a paint preview is shown on startup, telling the user that this is a preview of the page" translateable="false">
-        This is a preview
+      <message name="IDS_PAINT_PREVIEW_STARTUP_UPGRADE_SNACKBAR_MESSAGE" desc="Message displayed on a snackbar when a paint preview is shown on startup, telling the user that this is a preview of the page">
+        Previewing this page
       </message>
-      <message name="IDS_PAINT_PREVIEW_STARTUP_UPGRADE_SNACKBAR_ACTION" desc="Text displayed on the action button of snackbar, promting user to switch to the live page and exist paint preivew." translateable="false">
-        View live page
+      <message name="IDS_PAINT_PREVIEW_STARTUP_UPGRADE_SNACKBAR_ACTION" desc="Text displayed on the action button of snackbar, promting user to switch to the live page and exit paint preivew. [CHAR-LIMIT=12]">
+        Reload
+      </message>
+      <message name="IDS_PAINT_PREVIEW_STARTUP_AUTO_UPGRADE_TOAST" desc="Text displayed as a toast message when paint preview
+        is automatically removed and live tab is shown.">
+        Viewing live page
       </message>
 
       <!-- Default Browser Promo Strings-->
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_INCOGNITO_INTERSTITIAL_MESSAGE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_INCOGNITO_INTERSTITIAL_MESSAGE.png.sha1
index 5a35b3b..7c97744 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_INCOGNITO_INTERSTITIAL_MESSAGE.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_INCOGNITO_INTERSTITIAL_MESSAGE.png.sha1
@@ -1 +1 @@
-93b07d0c04d34fc6715bee5eae9dc3a17cdff2fe
\ No newline at end of file
+e1fcdd2b8b872b3c5daa0255606bfff0f2b8c0ee
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_INCOGNITO_INTERSTITIAL_TITLE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_INCOGNITO_INTERSTITIAL_TITLE.png.sha1
index 5a35b3b..7c97744 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_INCOGNITO_INTERSTITIAL_TITLE.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_INCOGNITO_INTERSTITIAL_TITLE.png.sha1
@@ -1 +1 @@
-93b07d0c04d34fc6715bee5eae9dc3a17cdff2fe
\ No newline at end of file
+e1fcdd2b8b872b3c5daa0255606bfff0f2b8c0ee
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_LOCATION_BAR_PAINT_PREVIEW_PAGE_STATUS.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_LOCATION_BAR_PAINT_PREVIEW_PAGE_STATUS.png.sha1
new file mode 100644
index 0000000..20fc1e5
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_LOCATION_BAR_PAINT_PREVIEW_PAGE_STATUS.png.sha1
@@ -0,0 +1 @@
+570ebed5854314ba806b89f50786d3d5c693f206
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PAINT_PREVIEW_STARTUP_AUTO_UPGRADE_TOAST.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PAINT_PREVIEW_STARTUP_AUTO_UPGRADE_TOAST.png.sha1
new file mode 100644
index 0000000..8784930e
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PAINT_PREVIEW_STARTUP_AUTO_UPGRADE_TOAST.png.sha1
@@ -0,0 +1 @@
+f927e18a7bbeffdc21b46f767f54bb366febe9ca
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PAINT_PREVIEW_STARTUP_UPGRADE_SNACKBAR_ACTION.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PAINT_PREVIEW_STARTUP_UPGRADE_SNACKBAR_ACTION.png.sha1
new file mode 100644
index 0000000..d2158f4
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PAINT_PREVIEW_STARTUP_UPGRADE_SNACKBAR_ACTION.png.sha1
@@ -0,0 +1 @@
+29f1d1489ff14eaae398ad0a2e5afe860f4b6552
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PAINT_PREVIEW_STARTUP_UPGRADE_SNACKBAR_MESSAGE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PAINT_PREVIEW_STARTUP_UPGRADE_SNACKBAR_MESSAGE.png.sha1
new file mode 100644
index 0000000..d2158f4
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PAINT_PREVIEW_STARTUP_UPGRADE_SNACKBAR_MESSAGE.png.sha1
@@ -0,0 +1 @@
+29f1d1489ff14eaae398ad0a2e5afe860f4b6552
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SIGNIN_GO_INCOGNITO.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SIGNIN_GO_INCOGNITO.png.sha1
deleted file mode 100644
index d4868f7..0000000
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SIGNIN_GO_INCOGNITO.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-2e490122886e008f50df3aca1ccf84f0451b1acb
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SIGNIN_INCOGNITO_BUTTON.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SIGNIN_INCOGNITO_BUTTON.png.sha1
new file mode 100644
index 0000000..cdc139b
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SIGNIN_INCOGNITO_BUTTON.png.sha1
@@ -0,0 +1 @@
+6b46bdd9d93b289e6c9dae0c8b8c34753e8f5108
\ No newline at end of file
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.cc b/chrome/browser/ui/autofill/chrome_autofill_client.cc
index ff66c16..95bc882 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.cc
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.cc
@@ -15,6 +15,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/autofill/address_normalizer_factory.h"
 #include "chrome/browser/autofill/autocomplete_history_manager_factory.h"
+#include "chrome/browser/autofill/autofill_offer_manager_factory.h"
 #include "chrome/browser/autofill/personal_data_manager_factory.h"
 #include "chrome/browser/autofill/risk_util.h"
 #include "chrome/browser/autofill/strike_database_factory.h"
@@ -176,6 +177,11 @@
   return AddressNormalizerFactory::GetInstance();
 }
 
+AutofillOfferManager* ChromeAutofillClient::GetAutofillOfferManager() {
+  return AutofillOfferManagerFactory::GetForBrowserContext(
+      web_contents()->GetBrowserContext());
+}
+
 const GURL& ChromeAutofillClient::GetLastCommittedURL() {
   return web_contents()->GetLastCommittedURL();
 }
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.h b/chrome/browser/ui/autofill/chrome_autofill_client.h
index 144f8ae..d8d8f7ed 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.h
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.h
@@ -63,6 +63,7 @@
   ukm::UkmRecorder* GetUkmRecorder() override;
   ukm::SourceId GetUkmSourceId() override;
   AddressNormalizer* GetAddressNormalizer() override;
+  AutofillOfferManager* GetAutofillOfferManager() override;
   const GURL& GetLastCommittedURL() override;
   security_state::SecurityLevel GetSecurityLevelForUmaHistograms() override;
   const translate::LanguageState* GetLanguageState() override;
diff --git a/chrome/browser/ui/commander/simple_command_source.cc b/chrome/browser/ui/commander/simple_command_source.cc
index 93eb740..b385cea 100644
--- a/chrome/browser/ui/commander/simple_command_source.cc
+++ b/chrome/browser/ui/commander/simple_command_source.cc
@@ -42,8 +42,9 @@
   for (const auto& command_spec : command_map) {
     if (!chrome::IsCommandEnabled(browser, command_spec.command_id))
       continue;
-    const base::string16 title =
+    base::string16 title =
         l10n_util::GetStringUTF16(command_spec.string_constant);
+    base::Erase(title, '&');
     double score = FuzzyFind(folded_input, title, &ranges);
     if (score == 0)
       continue;
diff --git a/chrome/browser/ui/in_product_help/feature_promo_snooze_service.h b/chrome/browser/ui/in_product_help/feature_promo_snooze_service.h
index c72fbf4..b24556e 100644
--- a/chrome/browser/ui/in_product_help/feature_promo_snooze_service.h
+++ b/chrome/browser/ui/in_product_help/feature_promo_snooze_service.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_UI_IN_PRODUCT_HELP_FEATURE_PROMO_SNOOZE_SERVICE_H_
 
 #include <string>
+
 #include "base/optional.h"
 #include "base/time/time.h"
 
@@ -61,6 +62,10 @@
   static void RegisterProfilePrefs(PrefRegistrySimple* registry);
 
  private:
+  // TODO(crbug.com/1121399): refactor prefs code so friending tests
+  // isn't necessary.
+  friend class FeaturePromoSnoozeInteractiveTest;
+
   // Snooze information dictionary saved under path
   // in_product_help.snoozed_feature.[iph_name] in PerfService.
   struct SnoozeData {
diff --git a/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura.cc b/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura.cc
index 7d41a7c..20e5897f 100644
--- a/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura.cc
+++ b/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura.cc
@@ -19,9 +19,8 @@
 #include "ui/gfx/image/image_skia.h"
 #include "ui/views/widget/widget.h"
 
-#if defined(USE_X11)
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
 #include "chrome/browser/shell_integration_linux.h"
-#include "ui/base/ui_base_features.h"
 #endif
 
 using extensions::AppWindow;
@@ -57,20 +56,17 @@
     const AppWindow::CreateParams& create_params,
     views::Widget::InitParams* init_params,
     views::Widget* widget) {
-#if defined(USE_X11)
-  if (!features::IsUsingOzonePlatform()) {
-    std::string app_name =
-        web_app::GenerateApplicationNameFromAppId(app_window()->extension_id());
-    // Set up a custom WM_CLASS for app windows. This allows task switchers in
-    // X11 environments to distinguish them from main browser windows.
-    init_params->wm_class_name =
-        shell_integration_linux::GetWMClassFromAppName(app_name);
-    init_params->wm_class_class =
-        shell_integration_linux::GetProgramClassClass();
-    const char kX11WindowRoleApp[] = "app";
-    init_params->wm_role_name = std::string(kX11WindowRoleApp);
-  }
-#endif
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+  std::string app_name =
+      web_app::GenerateApplicationNameFromAppId(app_window()->extension_id());
+  // Set up a custom WM_CLASS for app windows. This allows task switchers in
+  // X11 environments to distinguish them from main browser windows.
+  init_params->wm_class_name =
+      shell_integration_linux::GetWMClassFromAppName(app_name);
+  init_params->wm_class_class = shell_integration_linux::GetProgramClassClass();
+  const char kX11WindowRoleApp[] = "app";
+  init_params->wm_role_name = std::string(kX11WindowRoleApp);
+#endif  // defined(OS_LINUX) && !defined(OS_CHROMEOS)
 
   ChromeNativeAppWindowViews::OnBeforeWidgetInit(create_params, init_params,
                                                  widget);
diff --git a/chrome/browser/ui/views/autofill/payments/save_card_bubble_views_browsertest.cc b/chrome/browser/ui/views/autofill/payments/save_card_bubble_views_browsertest.cc
index 201b039..bf291cc 100644
--- a/chrome/browser/ui/views/autofill/payments/save_card_bubble_views_browsertest.cc
+++ b/chrome/browser/ui/views/autofill/payments/save_card_bubble_views_browsertest.cc
@@ -1019,8 +1019,7 @@
   ClickOnDialogViewWithId(DialogViewId::OK_BUTTON);
   WaitForObservedEvent();
 
-  // TODO(crbug.com/1082217): This should only produce one event
-  EXPECT_LT(0, counter.GetCount(ax::mojom::Event::kAlert));
+  EXPECT_EQ(1, counter.GetCount(ax::mojom::Event::kAlert));
 }
 #endif
 
diff --git a/chrome/browser/ui/views/eye_dropper/eye_dropper_view.cc b/chrome/browser/ui/views/eye_dropper/eye_dropper_view.cc
index 7103b05..1adde90 100644
--- a/chrome/browser/ui/views/eye_dropper/eye_dropper_view.cc
+++ b/chrome/browser/ui/views/eye_dropper/eye_dropper_view.cc
@@ -121,6 +121,10 @@
   HideCursor();
   pre_dispatch_handler_ = std::make_unique<PreEventDispatchHandler>(this);
   widget->Show();
+  // The ignore selection time should be long enough to allow the user to see
+  // the UI.
+  ignore_selection_time_ =
+      base::TimeTicks::Now() + base::TimeDelta::FromMilliseconds(500);
 }
 
 EyeDropperView::~EyeDropperView() {
@@ -246,6 +250,10 @@
     return;
   }
 
+  // Prevent the user from selecting a color for a period of time.
+  if (base::TimeTicks::Now() <= ignore_selection_time_)
+    return;
+
   // Use the last selected color and notify listener.
   listener_->ColorSelected(selected_color_.value());
 }
diff --git a/chrome/browser/ui/views/eye_dropper/eye_dropper_view.h b/chrome/browser/ui/views/eye_dropper/eye_dropper_view.h
index 07ff1c7..34873f9a 100644
--- a/chrome/browser/ui/views/eye_dropper/eye_dropper_view.h
+++ b/chrome/browser/ui/views/eye_dropper/eye_dropper_view.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "base/optional.h"
+#include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "build/build_config.h"
 #include "content/public/browser/eye_dropper.h"
@@ -78,6 +79,7 @@
   std::unique_ptr<ViewPositionHandler> view_position_handler_;
   std::unique_ptr<ScreenCapturer> screen_capturer_;
   base::Optional<SkColor> selected_color_;
+  base::TimeTicks ignore_selection_time_;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_EYE_DROPPER_EYE_DROPPER_VIEW_H_
diff --git a/chrome/browser/ui/views/fullscreen_control/fullscreen_control_view.cc b/chrome/browser/ui/views/fullscreen_control/fullscreen_control_view.cc
index 549682b..7f405f3 100644
--- a/chrome/browser/ui/views/fullscreen_control/fullscreen_control_view.cc
+++ b/chrome/browser/ui/views/fullscreen_control/fullscreen_control_view.cc
@@ -32,8 +32,8 @@
 
 class CloseFullscreenButton : public views::Button {
  public:
-  explicit CloseFullscreenButton(views::ButtonListener* listener)
-      : views::Button(listener) {
+  explicit CloseFullscreenButton(PressedCallback callback)
+      : views::Button(std::move(callback)) {
     std::unique_ptr<views::ImageView> close_image_view =
         std::make_unique<views::ImageView>();
     close_image_view->SetImage(gfx::CreateVectorIcon(
@@ -60,19 +60,12 @@
 }  // namespace
 
 FullscreenControlView::FullscreenControlView(
-    const base::RepeatingClosure& on_button_pressed)
-    : on_button_pressed_(on_button_pressed),
-      exit_fullscreen_button_(new CloseFullscreenButton(this)) {
-  AddChildView(exit_fullscreen_button_);
+    views::Button::PressedCallback callback) {
+  exit_fullscreen_button_ = AddChildView(
+      std::make_unique<CloseFullscreenButton>(std::move(callback)));
   SetLayoutManager(std::make_unique<views::FillLayout>());
   exit_fullscreen_button_->SetPreferredSize(
       gfx::Size(kCircleButtonDiameter, kCircleButtonDiameter));
 }
 
 FullscreenControlView::~FullscreenControlView() = default;
-
-void FullscreenControlView::ButtonPressed(views::Button* sender,
-                                          const ui::Event& event) {
-  if (sender == exit_fullscreen_button_ && on_button_pressed_)
-    on_button_pressed_.Run();
-}
diff --git a/chrome/browser/ui/views/fullscreen_control/fullscreen_control_view.h b/chrome/browser/ui/views/fullscreen_control/fullscreen_control_view.h
index 6116bf9..6c2b0d5b 100644
--- a/chrome/browser/ui/views/fullscreen_control/fullscreen_control_view.h
+++ b/chrome/browser/ui/views/fullscreen_control/fullscreen_control_view.h
@@ -13,24 +13,20 @@
 // FullscreenControlView shows a FAB (floating action button from the material
 // design spec) with close icon (i.e. a partially-transparent black circular
 // button with a "X" icon in the middle).
-// |on_button_pressed| will be called when the user taps the button.
-class FullscreenControlView : public views::View,
-                              public views::ButtonListener {
+// |callback| will be called when the user taps the button.
+class FullscreenControlView : public views::View {
  public:
-  explicit FullscreenControlView(
-      const base::RepeatingClosure& on_button_pressed);
+  explicit FullscreenControlView(views::Button::PressedCallback callback);
   ~FullscreenControlView() override;
 
   static constexpr int kCircleButtonDiameter = 48;
 
-  // views::ButtonListener:
-  void ButtonPressed(views::Button* sender, const ui::Event& event) override;
-
-  views::Button* exit_fullscreen_button() { return exit_fullscreen_button_; }
+  views::Button* exit_fullscreen_button_for_testing() {
+    return exit_fullscreen_button_;
+  }
 
  private:
-  const base::RepeatingClosure on_button_pressed_;
-  views::Button* const exit_fullscreen_button_;
+  views::Button* exit_fullscreen_button_;
 
   DISALLOW_COPY_AND_ASSIGN(FullscreenControlView);
 };
diff --git a/chrome/browser/ui/views/fullscreen_control/fullscreen_control_view_interactive_uitest.cc b/chrome/browser/ui/views/fullscreen_control/fullscreen_control_view_interactive_uitest.cc
index 6ccf235..cb496886 100644
--- a/chrome/browser/ui/views/fullscreen_control/fullscreen_control_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/fullscreen_control/fullscreen_control_view_interactive_uitest.cc
@@ -34,6 +34,7 @@
 #include "ui/events/keycodes/dom/dom_code.h"
 #include "ui/gfx/animation/animation_test_api.h"
 #include "ui/views/controls/button/button.h"
+#include "ui/views/test/button_test_api.h"
 #include "ui/views/view.h"
 #include "url/gurl.h"
 
@@ -91,7 +92,7 @@
   }
 
   views::Button* GetFullscreenExitButton() {
-    return GetFullscreenControlView()->exit_fullscreen_button();
+    return GetFullscreenControlView()->exit_fullscreen_button_for_testing();
   }
 
   ExclusiveAccessManager* GetExclusiveAccessManager() {
@@ -207,8 +208,8 @@
   ui::MouseEvent mouse_click(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
                              base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON,
                              ui::EF_LEFT_MOUSE_BUTTON);
-  GetFullscreenControlView()->ButtonPressed(GetFullscreenExitButton(),
-                                            mouse_click);
+  views::test::ButtonTestApi(GetFullscreenExitButton())
+      .NotifyClick(mouse_click);
 
   ASSERT_FALSE(GetFullscreenControlHost());
   ASSERT_FALSE(browser_view->IsFullscreen());
@@ -385,8 +386,8 @@
   touch_event = ui::TouchEvent(
       ui::ET_TOUCH_PRESSED, gfx::Point(1, 1), ui::EventTimeForNow(),
       ui::PointerDetails(ui::EventPointerType::kTouch, 0));
-  GetFullscreenControlView()->ButtonPressed(GetFullscreenExitButton(),
-                                            touch_event);
+  views::test::ButtonTestApi(GetFullscreenExitButton())
+      .NotifyClick(touch_event);
 
   ASSERT_FALSE(GetFullscreenControlHost());
   ASSERT_FALSE(browser_view->IsFullscreen());
diff --git a/chrome/browser/ui/views/in_product_help/feature_promo_snooze_interactive_uitest.cc b/chrome/browser/ui/views/in_product_help/feature_promo_snooze_interactive_uitest.cc
new file mode 100644
index 0000000..ea8094c
--- /dev/null
+++ b/chrome/browser/ui/views/in_product_help/feature_promo_snooze_interactive_uitest.cc
@@ -0,0 +1,253 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+
+#include "base/optional.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/time/time.h"
+#include "chrome/browser/feature_engagement/tracker_factory.h"
+#include "chrome/browser/ui/browser_commands.h"
+#include "chrome/browser/ui/in_product_help/feature_promo_snooze_service.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/in_product_help/feature_promo_bubble_view.h"
+#include "chrome/browser/ui/views/in_product_help/feature_promo_controller_views.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/interactive_test_utils.h"
+#include "components/feature_engagement/public/feature_constants.h"
+#include "components/feature_engagement/test/mock_tracker.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "content/public/test/browser_test.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/test/ui_controls.h"
+#include "ui/views/controls/button/button.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+#include "url/gurl.h"
+
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::NiceMock;
+using ::testing::Ref;
+using ::testing::Return;
+
+class FeaturePromoSnoozeInteractiveTest : public InProcessBrowserTest {
+ public:
+  FeaturePromoSnoozeInteractiveTest() {
+    scoped_feature_list_.InitAndEnableFeature(
+        feature_engagement::kIPHDesktopSnoozeFeature);
+
+    subscription_ = BrowserContextDependencyManager::GetInstance()
+                        ->RegisterCreateServicesCallbackForTesting(
+                            base::BindRepeating(RegisterMockTracker));
+  }
+
+  void SetUpOnMainThread() override {
+    mock_tracker_ =
+        static_cast<NiceMock<feature_engagement::test::MockTracker>*>(
+            feature_engagement::TrackerFactory::GetForBrowserContext(
+                browser()->profile()));
+    ASSERT_TRUE(mock_tracker_);
+
+    promo_controller_ = BrowserView::GetBrowserViewForBrowser(browser())
+                            ->feature_promo_controller();
+    snooze_service_ = promo_controller_->snooze_service_for_testing();
+  }
+
+ protected:
+  void ClickButton(views::Button* button) {
+    base::RunLoop run_loop;
+
+    ui_test_utils::MoveMouseToCenterAndPress(
+        button, ui_controls::LEFT, ui_controls::DOWN | ui_controls::UP,
+        run_loop.QuitClosure());
+    run_loop.Run();
+  }
+
+  bool HasSnoozePrefs(const base::Feature& iph_feature) {
+    return snooze_service_->ReadSnoozeData(iph_feature).has_value();
+  }
+
+  void CheckSnoozePrefs(const base::Feature& iph_feature,
+                        bool is_dismissed,
+                        int snooze_count,
+                        base::Time last_snooze_time_min,
+                        base::Time last_snooze_time_max) {
+    auto data = snooze_service_->ReadSnoozeData(iph_feature);
+
+    // If false, adds a failure and returns early from this function.
+    ASSERT_TRUE(data.has_value());
+
+    EXPECT_EQ(data->is_dismissed, is_dismissed);
+    EXPECT_EQ(data->snooze_count, snooze_count);
+
+    // last_snooze_time is only meaningful if a snooze has occurred.
+    if (data->snooze_count > 0) {
+      EXPECT_GE(data->last_snooze_time, last_snooze_time_min);
+      EXPECT_LE(data->last_snooze_time, last_snooze_time_max);
+    }
+  }
+
+  void SetSnoozePrefs(const base::Feature& iph_feature,
+                      bool is_dismissed,
+                      int snooze_count,
+                      base::Time last_snooze_time,
+                      base::TimeDelta last_snooze_duration) {
+    FeaturePromoSnoozeService::SnoozeData data;
+    data.is_dismissed = is_dismissed;
+    data.snooze_count = snooze_count;
+    data.last_snooze_time = last_snooze_time;
+    data.last_snooze_duration = last_snooze_duration;
+    snooze_service_->SaveSnoozeData(iph_feature, data);
+  }
+
+  // Tries to show tab groups IPH by meeting the trigger conditions. If
+  // |should_show| is true it checks that it was shown. If false, it
+  // checks that it was not shown.
+  void AttemptTabGroupsIPH(bool should_show) {
+    if (should_show) {
+      EXPECT_CALL(*mock_tracker_,
+                  ShouldTriggerHelpUI(Ref(
+                      feature_engagement::kIPHDesktopTabGroupsNewGroupFeature)))
+          .WillOnce(Return(true));
+    } else {
+      EXPECT_CALL(*mock_tracker_,
+                  ShouldTriggerHelpUI(Ref(
+                      feature_engagement::kIPHDesktopTabGroupsNewGroupFeature)))
+          .Times(0);
+    }
+
+    // Opening 6 or more tabs is the triggering event for tab groups
+    // IPH.
+    for (int i = 0; i < 5; ++i)
+      AddTabAtIndex(0, GURL("about:blank"), ui::PAGE_TRANSITION_TYPED);
+
+    ASSERT_EQ(should_show,
+              promo_controller_->BubbleIsShowing(
+                  feature_engagement::kIPHDesktopTabGroupsNewGroupFeature));
+
+    // If shown, Tracker::Dismissed should be called eventually.
+    if (should_show) {
+      EXPECT_CALL(
+          *mock_tracker_,
+          Dismissed(
+              Ref(feature_engagement::kIPHDesktopTabGroupsNewGroupFeature)));
+    }
+  }
+
+  NiceMock<feature_engagement::test::MockTracker>* mock_tracker_;
+  FeaturePromoControllerViews* promo_controller_;
+  FeaturePromoSnoozeService* snooze_service_;
+
+ private:
+  static void RegisterMockTracker(content::BrowserContext* context) {
+    feature_engagement::TrackerFactory::GetInstance()->SetTestingFactory(
+        context, base::BindRepeating(CreateMockTracker));
+  }
+
+  static std::unique_ptr<KeyedService> CreateMockTracker(
+      content::BrowserContext* context) {
+    auto mock_tracker =
+        std::make_unique<NiceMock<feature_engagement::test::MockTracker>>();
+
+    // Allow any other IPH to call, but don't ever show them.
+    EXPECT_CALL(*mock_tracker, ShouldTriggerHelpUI(_))
+        .Times(AnyNumber())
+        .WillRepeatedly(Return(false));
+
+    return mock_tracker;
+  }
+
+  base::test::ScopedFeatureList scoped_feature_list_;
+
+  std::unique_ptr<
+      BrowserContextDependencyManager::CreateServicesCallbackList::Subscription>
+      subscription_;
+};
+
+IN_PROC_BROWSER_TEST_F(FeaturePromoSnoozeInteractiveTest,
+                       DismissDoesNotSnooze) {
+  ASSERT_NO_FATAL_FAILURE(AttemptTabGroupsIPH(true));
+
+  FeaturePromoBubbleView* promo = promo_controller_->promo_bubble_for_testing();
+  ClickButton(promo->GetDismissButtonForTesting());
+  CheckSnoozePrefs(feature_engagement::kIPHDesktopTabGroupsNewGroupFeature,
+                   true, 0, base::Time(), base::Time());
+}
+
+IN_PROC_BROWSER_TEST_F(FeaturePromoSnoozeInteractiveTest,
+                       SnoozeSetsCorrectTime) {
+  ASSERT_NO_FATAL_FAILURE(AttemptTabGroupsIPH(true));
+
+  FeaturePromoBubbleView* promo = promo_controller_->promo_bubble_for_testing();
+
+  base::Time snooze_time_min = base::Time::Now();
+  ClickButton(promo->GetSnoozeButtonForTesting());
+  base::Time snooze_time_max = base::Time::Now();
+
+  CheckSnoozePrefs(feature_engagement::kIPHDesktopTabGroupsNewGroupFeature,
+                   false, 1, snooze_time_min, snooze_time_max);
+}
+
+IN_PROC_BROWSER_TEST_F(FeaturePromoSnoozeInteractiveTest, CanReSnooze) {
+  // Simulate the user snoozing the IPH.
+  base::TimeDelta snooze_duration = base::TimeDelta::FromHours(26);
+  base::Time snooze_time = base::Time::Now() - snooze_duration;
+  SetSnoozePrefs(feature_engagement::kIPHDesktopTabGroupsNewGroupFeature, false,
+                 1, snooze_time, snooze_duration);
+
+  ASSERT_NO_FATAL_FAILURE(AttemptTabGroupsIPH(true));
+
+  FeaturePromoBubbleView* promo = promo_controller_->promo_bubble_for_testing();
+
+  base::Time snooze_time_min = base::Time::Now();
+  ClickButton(promo->GetSnoozeButtonForTesting());
+  base::Time snooze_time_max = base::Time::Now();
+
+  CheckSnoozePrefs(feature_engagement::kIPHDesktopTabGroupsNewGroupFeature,
+                   false, 2, snooze_time_min, snooze_time_max);
+}
+
+IN_PROC_BROWSER_TEST_F(FeaturePromoSnoozeInteractiveTest,
+                       DoesNotShowIfDismissed) {
+  // Simulate the user dismissing the IPH.
+  SetSnoozePrefs(feature_engagement::kIPHDesktopTabGroupsNewGroupFeature, true,
+                 0, base::Time(), base::TimeDelta());
+
+  AttemptTabGroupsIPH(false);
+}
+
+IN_PROC_BROWSER_TEST_F(FeaturePromoSnoozeInteractiveTest,
+                       DoesNotShowBeforeSnoozeDuration) {
+  // Simulate a very recent snooze.
+  base::TimeDelta snooze_duration = base::TimeDelta::FromHours(26);
+  base::Time snooze_time = base::Time::Now();
+  SetSnoozePrefs(feature_engagement::kIPHDesktopTabGroupsNewGroupFeature, false,
+                 1, snooze_time, snooze_duration);
+
+  AttemptTabGroupsIPH(false);
+}
+
+IN_PROC_BROWSER_TEST_F(FeaturePromoSnoozeInteractiveTest,
+                       CloseBubbleSetsNoPrefs) {
+  ASSERT_NO_FATAL_FAILURE(AttemptTabGroupsIPH(true));
+
+  promo_controller_->CloseBubble(
+      feature_engagement::kIPHDesktopTabGroupsNewGroupFeature);
+  EXPECT_FALSE(
+      HasSnoozePrefs(feature_engagement::kIPHDesktopTabGroupsNewGroupFeature));
+}
+
+IN_PROC_BROWSER_TEST_F(FeaturePromoSnoozeInteractiveTest,
+                       WidgetCloseSetsNoPrefs) {
+  ASSERT_NO_FATAL_FAILURE(AttemptTabGroupsIPH(true));
+
+  FeaturePromoBubbleView* promo = promo_controller_->promo_bubble_for_testing();
+  promo->GetWidget()->CloseWithReason(
+      views::Widget::ClosedReason::kEscKeyPressed);
+  EXPECT_FALSE(
+      HasSnoozePrefs(feature_engagement::kIPHDesktopTabGroupsNewGroupFeature));
+}
diff --git a/chrome/browser/ui/views/location_bar/location_bar_bubble_delegate_view.cc b/chrome/browser/ui/views/location_bar/location_bar_bubble_delegate_view.cc
index e4dee33..11c8a221 100644
--- a/chrome/browser/ui/views/location_bar/location_bar_bubble_delegate_view.cc
+++ b/chrome/browser/ui/views/location_bar/location_bar_bubble_delegate_view.cc
@@ -94,10 +94,6 @@
           l10n_util::GetStringUTF8(IDS_SHOW_BUBBLE_INACTIVE_DESCRIPTION));
     }
   }
-  if (ui::IsAlert(GetAccessibleWindowRole())) {
-    GetWidget()->GetRootView()->NotifyAccessibilityEvent(
-        ax::mojom::Event::kAlert, true);
-  }
 }
 
 void LocationBarBubbleDelegateView::OnFullscreenStateChanged() {
diff --git a/chrome/browser/ui/views/session_crashed_bubble_view_browsertest.cc b/chrome/browser/ui/views/session_crashed_bubble_view_browsertest.cc
index c679739..3592b2bf 100644
--- a/chrome/browser/ui/views/session_crashed_bubble_view_browsertest.cc
+++ b/chrome/browser/ui/views/session_crashed_bubble_view_browsertest.cc
@@ -87,8 +87,7 @@
   views::test::AXEventCounter counter(views::AXEventManager::Get());
   EXPECT_EQ(0, counter.GetCount(ax::mojom::Event::kAlert));
   ShowUi("SessionCrashedBubble");
-  // TODO(crbug.com/1082217): This should only produce one event
-  EXPECT_LT(0, counter.GetCount(ax::mojom::Event::kAlert));
+  EXPECT_EQ(1, counter.GetCount(ax::mojom::Event::kAlert));
 }
 
 // Regression test for https://crbug.com/1081393.
diff --git a/chrome/browser/ui/views/sharing/sharing_dialog_view.cc b/chrome/browser/ui/views/sharing/sharing_dialog_view.cc
index 7553e1d..00a59494 100644
--- a/chrome/browser/ui/views/sharing/sharing_dialog_view.cc
+++ b/chrome/browser/ui/views/sharing/sharing_dialog_view.cc
@@ -82,25 +82,18 @@
              web_contents->GetMainFrame()->GetLastCommittedOrigin());
 }
 
-base::string16 PrepareHelpTextWithoutOrigin(const SharingDialogData& data,
-                                            const base::string16& link,
-                                            size_t* link_offset) {
+base::string16 PrepareHelpTextWithoutOrigin(const SharingDialogData& data) {
   DCHECK_NE(0, data.help_text_id);
-  return l10n_util::GetStringFUTF16(data.help_text_id, link, link_offset);
+  return l10n_util::GetStringUTF16(data.help_text_id);
 }
 
-base::string16 PrepareHelpTextWithOrigin(const SharingDialogData& data,
-                                         const base::string16& link,
-                                         size_t* link_offset) {
+base::string16 PrepareHelpTextWithOrigin(const SharingDialogData& data) {
   DCHECK_NE(0, data.help_text_origin_id);
   base::string16 origin = url_formatter::FormatOriginForSecurityDisplay(
       *data.initiating_origin,
       url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS);
-  std::vector<size_t> offsets;
-  base::string16 text = l10n_util::GetStringFUTF16(data.help_text_origin_id,
-                                                   {origin, link}, &offsets);
-  *link_offset = offsets[1];
-  return text;
+
+  return l10n_util::GetStringFUTF16(data.help_text_origin_id, origin);
 }
 
 std::unique_ptr<views::View> CreateOriginView(const SharingDialogData& data) {
@@ -342,22 +335,11 @@
 }
 
 std::unique_ptr<views::StyledLabel> SharingDialogView::CreateHelpText() {
-  DCHECK_NE(0, data_.help_link_text_id);
-  const base::string16 link =
-      l10n_util::GetStringUTF16(data_.help_link_text_id);
-  size_t offset;
   auto label = std::make_unique<views::StyledLabel>();
-  label->SetText(ShouldShowOrigin(data_, web_contents())
-                     ? PrepareHelpTextWithOrigin(data_, link, &offset)
-                     : PrepareHelpTextWithoutOrigin(data_, link, &offset));
-  views::StyledLabel::RangeStyleInfo link_style =
-      views::StyledLabel::RangeStyleInfo::CreateForLink(base::BindRepeating(
-          &SharingDialogView::HelpLinkClicked, base::Unretained(this)));
-  label->AddStyleRange(gfx::Range(offset, offset + link.length()), link_style);
-  return label;
-}
 
-void SharingDialogView::HelpLinkClicked() {
-  std::move(data_.help_callback).Run(GetDialogType());
-  CloseBubble();
+  label->SetText(ShouldShowOrigin(data_, web_contents())
+                     ? PrepareHelpTextWithOrigin(data_)
+                     : PrepareHelpTextWithoutOrigin(data_));
+
+  return label;
 }
diff --git a/chrome/browser/ui/views/sharing/sharing_dialog_view.h b/chrome/browser/ui/views/sharing/sharing_dialog_view.h
index 9ea368cd..b2b6e95 100644
--- a/chrome/browser/ui/views/sharing/sharing_dialog_view.h
+++ b/chrome/browser/ui/views/sharing/sharing_dialog_view.h
@@ -74,9 +74,6 @@
 
   std::unique_ptr<views::StyledLabel> CreateHelpText();
 
-  // Called when the "help" link is clicked.
-  void HelpLinkClicked();
-
   SharingDialogData data_;
 
   // References to device and app buttons views.
diff --git a/chrome/browser/ui/views/sharing/sharing_dialog_view_unittest.cc b/chrome/browser/ui/views/sharing/sharing_dialog_view_unittest.cc
index 566a517..ab71e56 100644
--- a/chrome/browser/ui/views/sharing/sharing_dialog_view_unittest.cc
+++ b/chrome/browser/ui/views/sharing/sharing_dialog_view_unittest.cc
@@ -14,6 +14,7 @@
 #include "chrome/browser/sharing/sharing_app.h"
 #include "chrome/browser/sharing/sharing_metrics.h"
 #include "chrome/browser/ui/views/hover_button.h"
+#include "chrome/grit/chromium_strings.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "components/sync_device_info/device_info.h"
 #include "components/url_formatter/elide_url.h"
@@ -115,13 +116,9 @@
         IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES;
     data.help_text_origin_id =
         IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES_ORIGIN;
-    data.help_link_text_id =
-        IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_TROUBLESHOOT_LINK;
     data.origin_text_id =
         IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_INITIATING_ORIGIN;
 
-    data.help_callback = base::BindLambdaForTesting(
-        [&](SharingDialogType type) { help_callback_.Call(type); });
     data.device_callback =
         base::BindLambdaForTesting([&](const syncer::DeviceInfo& device) {
           device_callback_.Call(device);
@@ -132,7 +129,6 @@
     return data;
   }
 
-  testing::MockFunction<void(SharingDialogType)> help_callback_;
   testing::MockFunction<void(const syncer::DeviceInfo&)> device_callback_;
   testing::MockFunction<void(const SharingApp&)> app_callback_;
   content::WebContents* web_contents_ = nullptr;
@@ -175,25 +171,6 @@
   dialog->ButtonPressed(dialog->dialog_buttons_[3], event);
 }
 
-TEST_F(SharingDialogViewTest, HelpTextClickedEmpty) {
-  EXPECT_CALL(help_callback_, Call(SharingDialogType::kEducationalDialog));
-
-  auto dialog_data = CreateDialogData(/*devices=*/0, /*apps=*/0);
-  auto dialog = CreateDialogView(std::move(dialog_data));
-
-  dialog->HelpLinkClicked();
-}
-
-TEST_F(SharingDialogViewTest, HelpTextClickedOnlyApps) {
-  EXPECT_CALL(help_callback_,
-              Call(SharingDialogType::kDialogWithoutDevicesWithApp));
-
-  auto dialog_data = CreateDialogData(/*devices=*/0, /*apps=*/1);
-  auto dialog = CreateDialogView(std::move(dialog_data));
-
-  dialog->HelpLinkClicked();
-}
-
 TEST_F(SharingDialogViewTest, ThemeChangedEmptyList) {
   auto dialog_data = CreateDialogData(/*devices=*/1, /*apps=*/1);
   dialog_data.type = SharingDialogType::kErrorDialog;
@@ -231,15 +208,13 @@
 TEST_F(SharingDialogViewTest, HelpTextContent) {
   url::Origin current_origin = url::Origin::Create(GURL("https://google.com"));
   url::Origin other_origin = url::Origin::Create(GURL("https://example.com"));
-  base::string16 link_text = l10n_util::GetStringUTF16(
-      IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_TROUBLESHOOT_LINK);
   base::string16 origin_text = url_formatter::FormatOriginForSecurityDisplay(
       other_origin, url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS);
-  base::string16 expected_default = l10n_util::GetStringFUTF16(
-      IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES, link_text);
+  base::string16 expected_default = l10n_util::GetStringUTF16(
+      IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES);
   base::string16 expected_origin = l10n_util::GetStringFUTF16(
       IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES_ORIGIN,
-      origin_text, link_text);
+      origin_text);
 
   // Expect default help text if no initiating origin is set.
   auto dialog_data = CreateDialogData(/*devices=*/0, /*apps=*/1);
diff --git a/chrome/browser/ui/views/tab_search/OWNERS b/chrome/browser/ui/views/tab_search/OWNERS
index bd78779..380c76c 100644
--- a/chrome/browser/ui/views/tab_search/OWNERS
+++ b/chrome/browser/ui/views/tab_search/OWNERS
@@ -1,4 +1,5 @@
 robliao@chromium.org
 tluk@chromium.org
+yuhengh@chromium.org
 
 # COMPONENT: UI>Browser>TabSearch
diff --git a/chrome/browser/ui/views/tab_search/tab_search_bubble_view.cc b/chrome/browser/ui/views/tab_search/tab_search_bubble_view.cc
index defe0298..17eb961 100644
--- a/chrome/browser/ui/views/tab_search/tab_search_bubble_view.cc
+++ b/chrome/browser/ui/views/tab_search/tab_search_bubble_view.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/views/tab_search/tab_search_bubble_view.h"
 
 #include "base/metrics/histogram_functions.h"
+#include "chrome/browser/extensions/chrome_extension_web_contents_observer.h"
 #include "chrome/browser/ui/webui/tab_search/tab_search_ui.h"
 #include "chrome/common/webui_url_constants.h"
 #include "ui/gfx/geometry/rounded_corners_f.h"
@@ -66,6 +67,11 @@
   set_margins(gfx::Insets());
 
   SetLayoutManager(std::make_unique<views::FillLayout>());
+  // Required for intercepting extension function calls when the page is loaded
+  // in a bubble (not a full tab, thus tab helpers are not registered
+  // automatically).
+  extensions::ChromeExtensionWebContentsObserver::CreateForWebContents(
+      web_view_->GetWebContents());
   web_view_->EnableSizingFromWebContents(kMinSize, kMaxSize);
   web_view_->LoadInitialURL(GURL(chrome::kChromeUITabSearchURL));
 
diff --git a/chrome/browser/ui/webui/chromeos/emulator/device_emulator_message_handler.cc b/chrome/browser/ui/webui/chromeos/emulator/device_emulator_message_handler.cc
index 40bff04..c10b83d 100644
--- a/chrome/browser/ui/webui/chromeos/emulator/device_emulator_message_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/emulator/device_emulator_message_handler.cc
@@ -645,4 +645,8 @@
   FireWebUIListener("mouse-exists-changed", base::Value(exists));
 }
 
+void DeviceEmulatorMessageHandler::PointingStickExists(bool exists) {
+  // TODO(crbug.com/1114828): support fake pointing sticks.
+}
+
 }  // namespace chromeos
diff --git a/chrome/browser/ui/webui/chromeos/emulator/device_emulator_message_handler.h b/chrome/browser/ui/webui/chromeos/emulator/device_emulator_message_handler.h
index 9a59d74..4d99b15 100644
--- a/chrome/browser/ui/webui/chromeos/emulator/device_emulator_message_handler.h
+++ b/chrome/browser/ui/webui/chromeos/emulator/device_emulator_message_handler.h
@@ -124,6 +124,7 @@
   // system::PointerDeviceObserver::Observer:
   void TouchpadExists(bool exists) override;
   void MouseExists(bool exists) override;
+  void PointingStickExists(bool exists) override;
 
   bluez::FakeBluetoothDeviceClient* fake_bluetooth_device_client_;
   std::unique_ptr<BluetoothObserver> bluetooth_observer_;
diff --git a/chrome/browser/ui/webui/discards/BUILD.gn b/chrome/browser/ui/webui/discards/BUILD.gn
index ec52c2e..e339a60 100644
--- a/chrome/browser/ui/webui/discards/BUILD.gn
+++ b/chrome/browser/ui/webui/discards/BUILD.gn
@@ -4,7 +4,7 @@
 
 import("//mojo/public/tools/bindings/mojom.gni")
 
-if (is_win || is_mac || is_desktop_linux || is_chromeos) {
+if (is_win || is_mac || is_linux || is_chromeos) {
   mojom("mojo_bindings") {
     sources = [
       "discards.mojom",
diff --git a/chrome/browser/ui/webui/settings/chromeos/device_pointer_handler.cc b/chrome/browser/ui/webui/settings/chromeos/device_pointer_handler.cc
index c1c5e94..a79fa29 100644
--- a/chrome/browser/ui/webui/settings/chromeos/device_pointer_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/device_pointer_handler.cc
@@ -44,6 +44,10 @@
   FireWebUIListener("has-mouse-changed", base::Value(exists));
 }
 
+void PointerHandler::PointingStickExists(bool exists) {
+  FireWebUIListener("has-pointing-stick-changed", base::Value(exists));
+}
+
 void PointerHandler::HandleInitialize(const base::ListValue* args) {
   AllowJavascript();
 
diff --git a/chrome/browser/ui/webui/settings/chromeos/device_pointer_handler.h b/chrome/browser/ui/webui/settings/chromeos/device_pointer_handler.h
index 25f54393..adc1b28 100644
--- a/chrome/browser/ui/webui/settings/chromeos/device_pointer_handler.h
+++ b/chrome/browser/ui/webui/settings/chromeos/device_pointer_handler.h
@@ -35,6 +35,7 @@
   // PointerDeviceObserver implementation.
   void TouchpadExists(bool exists) override;
   void MouseExists(bool exists) override;
+  void PointingStickExists(bool exists) override;
 
   // Initializes the page with the current pointer information.
   void HandleInitialize(const base::ListValue* args);
diff --git a/chrome/browser/ui/webui/settings/chromeos/device_section.cc b/chrome/browser/ui/webui/settings/chromeos/device_section.cc
index 7c15906c..6e180aae 100644
--- a/chrome/browser/ui/webui/settings/chromeos/device_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/device_section.cc
@@ -963,6 +963,10 @@
     updater.RemoveSearchTags(GetMouseSearchConcepts());
 }
 
+void DeviceSection::PointingStickExists(bool exists) {
+  // TODO(crbug.com/1114828): manage search tags when the UI is implemented.
+}
+
 void DeviceSection::OnDeviceListsComplete() {
   UpdateStylusSearchTags();
 }
diff --git a/chrome/browser/ui/webui/settings/chromeos/device_section.h b/chrome/browser/ui/webui/settings/chromeos/device_section.h
index 35031782..47bae70 100644
--- a/chrome/browser/ui/webui/settings/chromeos/device_section.h
+++ b/chrome/browser/ui/webui/settings/chromeos/device_section.h
@@ -57,6 +57,7 @@
   // system::PointerDeviceObserver::Observer:
   void TouchpadExists(bool exists) override;
   void MouseExists(bool exists) override;
+  void PointingStickExists(bool exists) override;
 
   // ui::InputDeviceObserver:
   void OnDeviceListsComplete() override;
diff --git a/chrome/browser/ui/webui/signin/OWNERS b/chrome/browser/ui/webui/signin/OWNERS
index 865f5d6..1c786a8 100644
--- a/chrome/browser/ui/webui/signin/OWNERS
+++ b/chrome/browser/ui/webui/signin/OWNERS
@@ -1,5 +1,7 @@
+file://components/signin/OWNERS
+
+# For ChromeOS changes
 achuith@chromium.org
-msarda@chromium.org
 xiyuan@chromium.org
 
 per-file inline_login_handler_impl*=file://chrome/credential_provider/OWNERS
diff --git a/chrome/browser/version/BUILD.gn b/chrome/browser/version/BUILD.gn
new file mode 100644
index 0000000..e75c23707
--- /dev/null
+++ b/chrome/browser/version/BUILD.gn
@@ -0,0 +1,28 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/android/rules.gni")
+import("//build/config/chrome_build.gni")  # For branding_file_path.
+import("//build/util/process_version.gni")
+
+_chrome_version_java_file = "$target_gen_dir/templates/org/chromium/chrome/browser/version/ChromeVersionConstants.java"
+
+android_library("java") {
+  deps = [
+    ":chrome_version_constants",
+    "//components/version_info/android:version_constants_java",
+  ]
+
+  sources = [
+    "java/src/org/chromium/chrome/browser/version/ChromeVersionInfo.java",
+    _chrome_version_java_file,
+  ]
+}
+
+process_version("chrome_version_constants") {
+  process_only = true
+  template_file = "java/ChromeVersionConstants.java.version"
+  sources = [ branding_file_path ]
+  output = _chrome_version_java_file
+}
diff --git a/chrome/browser/version/OWNERS b/chrome/browser/version/OWNERS
new file mode 100644
index 0000000..60448b82
--- /dev/null
+++ b/chrome/browser/version/OWNERS
@@ -0,0 +1,5 @@
+file://chrome/android/OWNERS
+
+# COMPONENT: Build
+# TEAM: chrome-android-app@chromium.org
+# OS: Android
diff --git a/chrome/android/java/ChromeVersionConstants.java.version b/chrome/browser/version/java/ChromeVersionConstants.java.version
similarity index 90%
rename from chrome/android/java/ChromeVersionConstants.java.version
rename to chrome/browser/version/java/ChromeVersionConstants.java.version
index 21b4184a..0f6b672 100644
--- a/chrome/android/java/ChromeVersionConstants.java.version
+++ b/chrome/browser/version/java/ChromeVersionConstants.java.version
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser;
+package org.chromium.chrome.browser.version;
 
 import org.chromium.components.version_info.VersionConstants;
 
diff --git a/chrome/browser/version/java/src/org/chromium/chrome/browser/version/ChromeVersionInfo.java b/chrome/browser/version/java/src/org/chromium/chrome/browser/version/ChromeVersionInfo.java
new file mode 100644
index 0000000..5c1eb63
--- /dev/null
+++ b/chrome/browser/version/java/src/org/chromium/chrome/browser/version/ChromeVersionInfo.java
@@ -0,0 +1,77 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.version;
+
+import org.chromium.components.version_info.Channel;
+
+/**
+ * A utility class for querying information about the current Chrome build.
+ * Intentionally doesn't depend on native so that the data can be accessed before
+ * libchrome.so is loaded.
+ */
+public class ChromeVersionInfo {
+    /**
+     * @return Whether this build is a local build.
+     */
+    public static boolean isLocalBuild() {
+        return ChromeVersionConstants.CHANNEL == Channel.DEFAULT;
+    }
+
+    /**
+     * @return Whether this build is a canary build.
+     */
+    public static boolean isCanaryBuild() {
+        return ChromeVersionConstants.CHANNEL == Channel.CANARY;
+    }
+
+    /**
+     * @return Whether this build is a dev build.
+     */
+    public static boolean isDevBuild() {
+        return ChromeVersionConstants.CHANNEL == Channel.DEV;
+    }
+
+    /**
+     * @return Whether this build is a beta build.
+     */
+    public static boolean isBetaBuild() {
+        return ChromeVersionConstants.CHANNEL == Channel.BETA;
+    }
+
+    /**
+     * @return Whether this build is a stable build.
+     */
+    public static boolean isStableBuild() {
+        return ChromeVersionConstants.CHANNEL == Channel.STABLE;
+    }
+
+    /**
+     * @return Whether this is an official (i.e. Google Chrome) build.
+     */
+    public static boolean isOfficialBuild() {
+        return ChromeVersionConstants.IS_OFFICIAL_BUILD;
+    }
+
+    /**
+     * @return The version number.
+     */
+    public static String getProductVersion() {
+        return ChromeVersionConstants.PRODUCT_VERSION;
+    }
+
+    /**
+     * @return The major version number.
+     */
+    public static int getProductMajorVersion() {
+        return ChromeVersionConstants.PRODUCT_MAJOR_VERSION;
+    }
+
+    /**
+     * @return The build number.
+     */
+    public static int getBuildVersion() {
+        return ChromeVersionConstants.PRODUCT_BUILD_VERSION;
+    }
+}
diff --git a/chrome/browser/video_tutorials/BUILD.gn b/chrome/browser/video_tutorials/BUILD.gn
index 1150617..0f0be6f 100644
--- a/chrome/browser/video_tutorials/BUILD.gn
+++ b/chrome/browser/video_tutorials/BUILD.gn
@@ -65,6 +65,7 @@
       "android/java/src/org/chromium/chrome/browser/video_tutorials/VideoTutorialService.java",
       "android/java/src/org/chromium/chrome/browser/video_tutorials/iph/VideoIPHCoordinator.java",
       "android/java/src/org/chromium/chrome/browser/video_tutorials/iph/VideoTutorialIPHUtils.java",
+      "android/java/src/org/chromium/chrome/browser/video_tutorials/list/TutorialListCoordinator.java",
       "android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerCoordinator.java",
     ]
 
@@ -85,9 +86,13 @@
 
   android_resources("java_resources") {
     create_srcjar = false
-    sources = [ "android/java/res/layout/video_tutorial_iph_card.xml" ]
+    sources = [
+      "android/java/res/layout/video_tutorial_iph_card.xml",
+      "android/java/res/layout/video_tutorial_list.xml",
+    ]
 
     deps = [
+      "//chrome/browser/download/android:java_resources",
       "//chrome/browser/ui/android/strings:ui_strings_grd",
       "//components/browser_ui/widget/android:java_resources",
       "//ui/android:ui_java_resources",
@@ -109,6 +114,7 @@
       "//chrome/browser/profiles/android:java",
       "//components/embedder_support/android:content_view_java",
       "//content/public/android:content_java",
+      "//third_party/android_deps:androidx_recyclerview_recyclerview_java",
     ]
 
     # This internal file will be replaced by a generated file so the resulting
diff --git a/chrome/browser/video_tutorials/android/java/res/layout/video_tutorial_list.xml b/chrome/browser/video_tutorials/android/java/res/layout/video_tutorial_list.xml
new file mode 100644
index 0000000..b1377268
--- /dev/null
+++ b/chrome/browser/video_tutorials/android/java/res/layout/video_tutorial_list.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2020 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/video_tutorial_list"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:paddingTop="30dp"
+    android:paddingStart="@dimen/promo_compact_padding"
+    android:paddingEnd="@dimen/promo_compact_padding">
+
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <!-- TODO(shaktisahu): Use correct string after the string CL lands. -->
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/close"
+            android:textAppearance="@style/TextAppearance.TextLarge.Primary" />
+
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_weight="1" />
+
+        <org.chromium.ui.widget.ChromeImageButton
+            android:id="@+id/close_button"
+            android:layout_height="24dp"
+            android:layout_width="24dp"
+            android:background="?attr/selectableItemBackground"
+            android:contentDescription="@string/close"
+            android:src="@drawable/btn_close" />
+    </LinearLayout>
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/recycler_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginTop="16dp" />
+</LinearLayout>
diff --git a/chrome/browser/video_tutorials/android/java/src/org/chromium/chrome/browser/video_tutorials/list/TutorialListCoordinator.java b/chrome/browser/video_tutorials/android/java/src/org/chromium/chrome/browser/video_tutorials/list/TutorialListCoordinator.java
new file mode 100644
index 0000000..bad45747a
--- /dev/null
+++ b/chrome/browser/video_tutorials/android/java/src/org/chromium/chrome/browser/video_tutorials/list/TutorialListCoordinator.java
@@ -0,0 +1,10 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.video_tutorials.list;
+
+/**
+ *  The top level coordinator for the video tutorials list UI.
+ */
+public interface TutorialListCoordinator {}
\ No newline at end of file
diff --git a/chrome/browser/video_tutorials/internal/BUILD.gn b/chrome/browser/video_tutorials/internal/BUILD.gn
index 1dab91ec..d33cb507 100644
--- a/chrome/browser/video_tutorials/internal/BUILD.gn
+++ b/chrome/browser/video_tutorials/internal/BUILD.gn
@@ -71,6 +71,10 @@
       "android/java/src/org/chromium/chrome/browser/video_tutorials/languages/LanguagePickerMediator.java",
       "android/java/src/org/chromium/chrome/browser/video_tutorials/languages/LanguagePickerProperties.java",
       "android/java/src/org/chromium/chrome/browser/video_tutorials/languages/LanguagePickerView.java",
+      "android/java/src/org/chromium/chrome/browser/video_tutorials/list/TutorialCardProperties.java",
+      "android/java/src/org/chromium/chrome/browser/video_tutorials/list/TutorialCardViewBinder.java",
+      "android/java/src/org/chromium/chrome/browser/video_tutorials/list/TutorialListCoordinatorImpl.java",
+      "android/java/src/org/chromium/chrome/browser/video_tutorials/list/TutorialListMediator.java",
       "android/java/src/org/chromium/chrome/browser/video_tutorials/metrics/VideoTutorialMetrics.java",
       "android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerCoordinatorImpl.java",
       "android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerMediator.java",
@@ -113,6 +117,7 @@
       "android/java/res/layout/language_picker.xml",
       "android/java/res/layout/video_player_controls.xml",
       "android/java/res/layout/video_player_loading.xml",
+      "android/java/res/layout/video_tutorial_large_card.xml",
     ]
 
     deps = [
@@ -163,11 +168,15 @@
   android_library("javatests") {
     testonly = true
 
-    sources = [ "android/java/src/org/chromium/chrome/browser/video_tutorials/languages/LanguagePickerTest.java" ]
+    sources = [
+      "android/java/src/org/chromium/chrome/browser/video_tutorials/iph/VideoIPHTest.java",
+      "android/java/src/org/chromium/chrome/browser/video_tutorials/languages/LanguagePickerTest.java",
+    ]
     deps = [
       ":java",
       "//base:base_java",
       "//base:base_java_test_support",
+      "//chrome/browser/image_fetcher:java",
       "//chrome/browser/video_tutorials:java",
       "//chrome/browser/video_tutorials:test_support_java",
       "//chrome/test/android:chrome_java_test_support",
@@ -176,6 +185,7 @@
       "//third_party/android_deps:androidx_test_runner_java",
       "//third_party/android_deps:espresso_java",
       "//third_party/android_support_test_runner:rules_java",
+      "//third_party/gif_player:gif_player_java",
       "//third_party/hamcrest:hamcrest_core_java",
       "//third_party/junit",
       "//third_party/mockito:mockito_java",
diff --git a/chrome/browser/video_tutorials/internal/android/java/res/layout/video_tutorial_large_card.xml b/chrome/browser/video_tutorials/internal/android/java/res/layout/video_tutorial_large_card.xml
new file mode 100644
index 0000000..4b52d233
--- /dev/null
+++ b/chrome/browser/video_tutorials/internal/android/java/res/layout/video_tutorial_large_card.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2020 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+
+<androidx.gridlayout.widget.GridLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:clickable="true"
+    android:focusable="true"
+    android:background="@drawable/hairline_border_card_background"
+    app:columnCount="1"
+    app:rowCount="2">
+
+    <org.chromium.ui.widget.ChromeImageView
+        android:id="@+id/thumbnail"
+        android:layout_width="match_parent"
+        android:layout_height="200dp"
+        android:layout_marginTop="1dp"
+        android:layout_marginStart="1dp"
+        android:layout_marginEnd="1dp"
+        android:scaleType="centerCrop"
+        android:adjustViewBounds="true"
+        app:layout_column="0"
+        app:layout_row="0"
+        app:layout_gravity="center"
+        app:cornerRadiusTopStart="@dimen/download_manager_thumbnail_corner_radius"
+        app:cornerRadiusTopEnd="@dimen/download_manager_thumbnail_corner_radius"
+        app:roundedfillColor="@color/modern_grey_300"/>
+
+    <org.chromium.ui.widget.ChromeImageButton
+        android:id="@+id/action_button"
+        app:layout_column="0"
+        app:layout_row="0"
+        app:layout_gravity="center"
+        android:elevation="2dp"
+        android:clickable="false"
+        style="@style/LargeMediaPlayButton"/>
+
+    <TextView
+        android:id="@+id/video_length"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="11dp"
+        android:layout_marginBottom="11dp"
+        app:layout_column="0"
+        app:layout_row="0"
+        app:layout_gravity="bottom"
+        android:textAppearance="@style/TextAppearance.TextMedium.Secondary" />
+
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="11dp"
+        android:layout_marginTop="11dp"
+        android:layout_marginBottom="11dp"
+        android:maxLines="1"
+        android:ellipsize="end"
+        app:layout_column="0"
+        app:layout_row="1"
+        android:textAppearance="@style/TextAppearance.TextMedium.Secondary" />
+
+</androidx.gridlayout.widget.GridLayout>
diff --git a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/VideoTutorialServiceFactory.java b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/VideoTutorialServiceFactory.java
index bf2158a..9ea23fa 100644
--- a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/VideoTutorialServiceFactory.java
+++ b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/VideoTutorialServiceFactory.java
@@ -8,6 +8,8 @@
 import android.util.Pair;
 import android.view.ViewStub;
 
+import androidx.recyclerview.widget.RecyclerView;
+
 import org.chromium.base.Callback;
 import org.chromium.base.annotations.NativeMethods;
 import org.chromium.base.supplier.Supplier;
@@ -15,6 +17,8 @@
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.video_tutorials.iph.VideoIPHCoordinator;
 import org.chromium.chrome.browser.video_tutorials.iph.VideoIPHCoordinatorImpl;
+import org.chromium.chrome.browser.video_tutorials.list.TutorialListCoordinator;
+import org.chromium.chrome.browser.video_tutorials.list.TutorialListCoordinatorImpl;
 import org.chromium.chrome.browser.video_tutorials.player.VideoPlayerCoordinator;
 import org.chromium.chrome.browser.video_tutorials.player.VideoPlayerCoordinatorImpl;
 import org.chromium.components.embedder_support.view.ContentView;
@@ -52,7 +56,14 @@
                 context, videoTutorialService, webContentsFactory, closeCallback);
     }
 
-    /** For testing only. */
+    /** See {@link TutorialListCoordinator}.*/
+    public static TutorialListCoordinator createTutorialListCoordinator(RecyclerView recyclerView,
+            VideoTutorialService videoTutorialService, ImageFetcher imageFetcher,
+            Callback<Tutorial> clickCallback) {
+        return new TutorialListCoordinatorImpl(
+                recyclerView, videoTutorialService, imageFetcher, clickCallback);
+    }
+
     public static void setVideoTutorialServiceForTesting(VideoTutorialService provider) {
         sVideoTutorialServiceForTesting = provider;
     }
diff --git a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/iph/VideoIPHCoordinatorImpl.java b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/iph/VideoIPHCoordinatorImpl.java
index 678b749..8918b059 100644
--- a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/iph/VideoIPHCoordinatorImpl.java
+++ b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/iph/VideoIPHCoordinatorImpl.java
@@ -53,7 +53,7 @@
 
         mModel.set(VideoIPHProperties.THUMBNAIL, null);
         ImageFetcher.Params params = ImageFetcher.Params.create(
-                tutorial.posterUrl, ImageFetcher.VIDEO_TUTORIALS_UMA_CLIENT_NAME);
+                tutorial.posterUrl, ImageFetcher.VIDEO_TUTORIALS_IPH_UMA_CLIENT_NAME);
         mImageFetcher.fetchImage(
                 params, bitmap -> mModel.set(VideoIPHProperties.THUMBNAIL, bitmap));
     }
diff --git a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/iph/VideoIPHTest.java b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/iph/VideoIPHTest.java
new file mode 100644
index 0000000..54c4a75
--- /dev/null
+++ b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/iph/VideoIPHTest.java
@@ -0,0 +1,126 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.video_tutorials.iph;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.support.test.rule.ActivityTestRule;
+import android.view.ViewStub;
+import android.widget.FrameLayout;
+
+import androidx.annotation.Nullable;
+import androidx.test.espresso.action.ViewActions;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import org.chromium.base.Callback;
+import org.chromium.chrome.browser.image_fetcher.ImageFetcher;
+import org.chromium.chrome.browser.image_fetcher.ImageFetcherConfig;
+import org.chromium.chrome.browser.video_tutorials.FeatureType;
+import org.chromium.chrome.browser.video_tutorials.R;
+import org.chromium.chrome.browser.video_tutorials.Tutorial;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.ui.test.util.DummyUiActivity;
+
+import jp.tomorrowkey.android.gifplayer.BaseGifImage;
+
+/**
+ * Tests for {@link LanguagePickerCoordinator}.
+ */
+@RunWith(ChromeJUnit4ClassRunner.class)
+public class VideoIPHTest {
+    @Rule
+    public ActivityTestRule<DummyUiActivity> mActivityTestRule =
+            new ActivityTestRule<>(DummyUiActivity.class);
+
+    private Activity mActivity;
+    private VideoIPHCoordinator mCoordinator;
+
+    @Mock
+    private Callback<Tutorial> mOnClickListener;
+    @Mock
+    private Callback<Tutorial> mOnDismissListener;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mActivity = mActivityTestRule.getActivity();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            FrameLayout parentView = new FrameLayout(mActivity);
+            mActivity.setContentView(parentView);
+            ViewStub viewStub = new ViewStub(mActivity);
+            viewStub.setLayoutResource(R.layout.video_tutorial_iph_card);
+            parentView.addView(viewStub);
+
+            Bitmap testImage = BitmapFactory.decodeResource(mActivity.getResources(),
+                    org.chromium.chrome.browser.video_tutorials.R.drawable.btn_close);
+            TestImageFetcher imageFetcher = new TestImageFetcher(testImage);
+            mCoordinator = new VideoIPHCoordinatorImpl(
+                    viewStub, imageFetcher, mOnClickListener, mOnDismissListener);
+        });
+    }
+
+    @Test
+    @SmallTest
+    public void testShowIPH() {
+        final Tutorial tutorial = createDummyTutorial();
+        TestThreadUtils.runOnUiThreadBlocking(() -> { mCoordinator.showVideoIPH(tutorial); });
+        onView(withText(tutorial.displayTitle)).check(matches(isDisplayed()));
+        onView(withText("5:35")).check(matches(isDisplayed()));
+        onView(withText(tutorial.displayTitle)).perform(ViewActions.click());
+        Mockito.verify(mOnClickListener).onResult(Mockito.any());
+        onView(withId(R.id.close_button)).perform(ViewActions.click());
+        Mockito.verify(mOnDismissListener).onResult(Mockito.any());
+    }
+
+    private Tutorial createDummyTutorial() {
+        return new Tutorial(FeatureType.DOWNLOAD,
+                "How to use Google Chrome's download functionality",
+                "https://xyz.example.com/xyz.mp4", "https://xyz.example.com/xyz.png",
+                "https://xyz.example.com/xyz.vtt", "https://xyz.example.com/xyz.mp4", 335);
+    }
+
+    private static class TestImageFetcher extends ImageFetcher.ImageFetcherForTesting {
+        private final Bitmap mBitmapToFetch;
+
+        TestImageFetcher(@Nullable Bitmap bitmapToFetch) {
+            mBitmapToFetch = bitmapToFetch;
+        }
+
+        @Override
+        public void fetchGif(final ImageFetcher.Params params, Callback<BaseGifImage> callback) {}
+
+        @Override
+        public void fetchImage(ImageFetcher.Params params, Callback<Bitmap> callback) {
+            callback.onResult(mBitmapToFetch);
+        }
+
+        @Override
+        public void clear() {}
+
+        @Override
+        public @ImageFetcherConfig int getConfig() {
+            return ImageFetcherConfig.IN_MEMORY_ONLY;
+        }
+
+        @Override
+        public void destroy() {}
+    }
+}
diff --git a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/list/TutorialCardProperties.java b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/list/TutorialCardProperties.java
new file mode 100644
index 0000000..86877ad
--- /dev/null
+++ b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/list/TutorialCardProperties.java
@@ -0,0 +1,42 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.video_tutorials.list;
+
+import android.graphics.drawable.Drawable;
+
+import org.chromium.base.Callback;
+import org.chromium.ui.modelutil.PropertyKey;
+import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey;
+
+/**
+ * Properties for a video tutorial card.
+ */
+class TutorialCardProperties {
+    /** The view type used by the recycler view to show the tutorial cards. */
+    public static final int VIDEO_TUTORIAL_CARD_VIEW_TYPE = 3;
+
+    /** An interface to provide thumbnails for the videos. */
+    interface VisualsProvider {
+        /** Method to get visuals to display the thumbnail image. */
+        void getVisuals(Callback<Drawable> callback);
+    }
+
+    /** The title to be shown.*/
+    static final WritableObjectPropertyKey<String> TITLE = new WritableObjectPropertyKey<>();
+
+    /** Text representing the length of the video.*/
+    static final WritableObjectPropertyKey<String> VIDEO_LENGTH = new WritableObjectPropertyKey<>();
+
+    /** The callback to invoke when the card is clicked.*/
+    static final WritableObjectPropertyKey<Runnable> CLICK_CALLBACK =
+            new WritableObjectPropertyKey<>();
+
+    /** The provider to retrieve the visuals for the video thumbnail.*/
+    static final WritableObjectPropertyKey<VisualsProvider> VISUALS_PROVIDER =
+            new WritableObjectPropertyKey<>();
+
+    static final PropertyKey[] ALL_KEYS =
+            new PropertyKey[] {TITLE, VIDEO_LENGTH, CLICK_CALLBACK, VISUALS_PROVIDER};
+}
diff --git a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/list/TutorialCardViewBinder.java b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/list/TutorialCardViewBinder.java
new file mode 100644
index 0000000..621c9a8
--- /dev/null
+++ b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/list/TutorialCardViewBinder.java
@@ -0,0 +1,51 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.video_tutorials.list;
+
+import android.graphics.drawable.ColorDrawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.chromium.chrome.browser.video_tutorials.R;
+import org.chromium.ui.modelutil.PropertyKey;
+import org.chromium.ui.modelutil.PropertyModel;
+
+/**
+ * This class is responsible for building and binding the video tutorial card.
+ */
+class TutorialCardViewBinder {
+    /** Builder method to create the card view. */
+    static View buildView(ViewGroup parent) {
+        return LayoutInflater.from(parent.getContext())
+                .inflate(R.layout.video_tutorial_large_card, parent, false);
+    }
+
+    /** Binder method to bind the card view with the model properties. */
+    static void bindView(PropertyModel model, View view, PropertyKey propertyKey) {
+        if (propertyKey == TutorialCardProperties.TITLE) {
+            TextView title = view.findViewById((R.id.title));
+            title.setText(model.get(TutorialCardProperties.TITLE));
+        } else if (propertyKey == TutorialCardProperties.VIDEO_LENGTH) {
+            TextView title = view.findViewById((R.id.video_length));
+            title.setText(model.get(TutorialCardProperties.VIDEO_LENGTH));
+        } else if (propertyKey == TutorialCardProperties.CLICK_CALLBACK) {
+            view.setOnClickListener(
+                    v -> { model.get(TutorialCardProperties.CLICK_CALLBACK).run(); });
+        } else if (propertyKey == TutorialCardProperties.VISUALS_PROVIDER) {
+            final ImageView thumbnail = view.findViewById(R.id.thumbnail);
+            thumbnail.setImageDrawable(new ColorDrawable(thumbnail.getResources().getColor(
+                    org.chromium.components.browser_ui.widget.R.color.image_loading_color)));
+            model.get(TutorialCardProperties.VISUALS_PROVIDER).getVisuals(drawable -> {
+                thumbnail.setImageDrawable(drawable);
+            });
+        } else {
+            throw new IllegalArgumentException(
+                    "Cannot update the view for propertyKey: " + propertyKey);
+        }
+    }
+}
diff --git a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/list/TutorialListCoordinatorImpl.java b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/list/TutorialListCoordinatorImpl.java
new file mode 100644
index 0000000..933f6de
--- /dev/null
+++ b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/list/TutorialListCoordinatorImpl.java
@@ -0,0 +1,67 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.video_tutorials.list;
+
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.ItemDecoration;
+import androidx.recyclerview.widget.RecyclerView.State;
+
+import org.chromium.base.Callback;
+import org.chromium.chrome.browser.image_fetcher.ImageFetcher;
+import org.chromium.chrome.browser.video_tutorials.R;
+import org.chromium.chrome.browser.video_tutorials.Tutorial;
+import org.chromium.chrome.browser.video_tutorials.VideoTutorialService;
+import org.chromium.ui.modelutil.MVCListAdapter;
+import org.chromium.ui.modelutil.SimpleRecyclerViewAdapter;
+
+/**
+ *  The top level coordinator for the video tutorials list UI.
+ */
+public class TutorialListCoordinatorImpl implements TutorialListCoordinator {
+    private final TutorialListMediator mMediator;
+
+    /**
+     * Constructor.
+     * @param recyclerView The {@link RecyclerView} associated with this coordinator.
+     * @param videoTutorialService The video tutorial service backend.
+     * @param imageFetcher An {@link ImageFetcher} to provide thumbnail images.
+     * @param clickCallback A callback to be invoked when a tutorial is clicked.
+     */
+    public TutorialListCoordinatorImpl(RecyclerView recyclerView,
+            VideoTutorialService videoTutorialService, ImageFetcher imageFetcher,
+            Callback<Tutorial> clickCallback) {
+        MVCListAdapter.ModelList listModel = new MVCListAdapter.ModelList();
+        SimpleRecyclerViewAdapter adapter = new SimpleRecyclerViewAdapter(listModel);
+        adapter.registerType(TutorialCardProperties.VIDEO_TUTORIAL_CARD_VIEW_TYPE,
+                TutorialCardViewBinder::buildView, TutorialCardViewBinder::bindView);
+        recyclerView.setAdapter(adapter);
+        recyclerView.setLayoutManager(new LinearLayoutManager(
+                recyclerView.getContext(), LinearLayoutManager.VERTICAL, false));
+        recyclerView.addItemDecoration(new ItemDecorationImpl(recyclerView.getResources()));
+
+        mMediator = new TutorialListMediator(listModel, recyclerView.getContext(),
+                videoTutorialService, imageFetcher, clickCallback);
+    }
+
+    private class ItemDecorationImpl extends ItemDecoration {
+        private final int mInterImagePaddingPx;
+        public ItemDecorationImpl(Resources resources) {
+            mInterImagePaddingPx = resources.getDimensionPixelOffset(R.dimen.card_padding);
+        }
+
+        @Override
+        public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
+                @NonNull RecyclerView parent, @NonNull State state) {
+            outRect.top = mInterImagePaddingPx / 2;
+            outRect.bottom = mInterImagePaddingPx / 2;
+        }
+    }
+}
\ No newline at end of file
diff --git a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/list/TutorialListMediator.java b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/list/TutorialListMediator.java
new file mode 100644
index 0000000..75fa115
--- /dev/null
+++ b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/list/TutorialListMediator.java
@@ -0,0 +1,76 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.video_tutorials.list;
+
+import android.content.Context;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+
+import org.chromium.base.Callback;
+import org.chromium.chrome.browser.image_fetcher.ImageFetcher;
+import org.chromium.chrome.browser.video_tutorials.Tutorial;
+import org.chromium.chrome.browser.video_tutorials.VideoTutorialService;
+import org.chromium.ui.modelutil.MVCListAdapter;
+import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
+import org.chromium.ui.modelutil.PropertyModel;
+
+import java.util.List;
+
+/**
+ *  The mediator associated with the recycler view in the video tutorials home UI.
+ */
+public class TutorialListMediator {
+    private final Context mContext;
+    private final MVCListAdapter.ModelList mListModel;
+    private final VideoTutorialService mVideoTutorialService;
+    private final ImageFetcher mImageFetcher;
+    private final Callback<Tutorial> mClickCallback;
+
+    /**
+     * Constructor.
+     * @param context The activity context.
+     * @param videoTutorialService The video tutorial service backend.
+     */
+    public TutorialListMediator(MVCListAdapter.ModelList listModel, Context context,
+            VideoTutorialService videoTutorialService, ImageFetcher imageFetcher,
+            Callback<Tutorial> clickCallback) {
+        mListModel = listModel;
+        mContext = context;
+        mVideoTutorialService = videoTutorialService;
+        mImageFetcher = imageFetcher;
+        mClickCallback = clickCallback;
+        videoTutorialService.getTutorials(this::populateList);
+    }
+
+    private void populateList(List<Tutorial> tutorials) {
+        assert mListModel.size() == 0;
+        for (Tutorial tutorial : tutorials) {
+            ListItem listItem = new ListItem(TutorialCardProperties.VIDEO_TUTORIAL_CARD_VIEW_TYPE,
+                    buildModelFromTutorial(tutorial));
+            mListModel.add(listItem);
+        }
+    }
+
+    private PropertyModel buildModelFromTutorial(Tutorial tutorial) {
+        return new PropertyModel.Builder(TutorialCardProperties.ALL_KEYS)
+                .with(TutorialCardProperties.TITLE, tutorial.displayTitle)
+                // TODO(shaktisahu): Provide a string in mm:ss format.
+                .with(TutorialCardProperties.VIDEO_LENGTH, Integer.toString(tutorial.videoLength))
+                .with(TutorialCardProperties.CLICK_CALLBACK,
+                        () -> mClickCallback.onResult(tutorial))
+                .with(TutorialCardProperties.VISUALS_PROVIDER,
+                        callback -> getBitmap(tutorial, callback))
+                .build();
+    }
+
+    private void getBitmap(Tutorial tutorial, Callback<Drawable> callback) {
+        ImageFetcher.Params params = ImageFetcher.Params.create(
+                tutorial.posterUrl, ImageFetcher.VIDEO_TUTORIALS_LIST_UMA_CLIENT_NAME);
+        mImageFetcher.fetchImage(params, bitmap -> {
+            Drawable drawable = new BitmapDrawable(mContext.getResources(), bitmap);
+            callback.onResult(drawable);
+        });
+    }
+}
\ No newline at end of file
diff --git a/chrome/browser/web_applications/components/BUILD.gn b/chrome/browser/web_applications/components/BUILD.gn
index 031345d..60d4469 100644
--- a/chrome/browser/web_applications/components/BUILD.gn
+++ b/chrome/browser/web_applications/components/BUILD.gn
@@ -88,7 +88,7 @@
     sources += [ "web_app_shortcut_chromeos.cc" ]
   }
 
-  if (is_desktop_linux) {
+  if (is_linux) {
     # Desktop linux, doesn't count ChromeOS.
     sources += [
       "web_app_file_handler_registration_linux.cc",
@@ -187,7 +187,7 @@
     ]
   }
 
-  if (is_desktop_linux) {
+  if (is_linux) {
     # Desktop linux, doesn't count ChromeOS.
     sources += [
       "web_app_file_handler_registration_linux_unittest.cc",
@@ -224,7 +224,7 @@
 
   sources = [ "web_app_url_loader_browsertest.cc" ]
 
-  if (is_desktop_linux) {
+  if (is_linux) {
     sources += [ "web_app_file_handler_registration_linux_browsertest.cc" ]
   }
 
diff --git a/chrome/common/features.gni b/chrome/common/features.gni
index 2ff23e5..c89beefd 100644
--- a/chrome/common/features.gni
+++ b/chrome/common/features.gni
@@ -38,15 +38,15 @@
   enable_basic_print_dialog = enable_basic_printing && !is_chromeos
 
   # Enables the Click to Call feature on desktop platforms.
-  enable_click_to_call = is_mac || is_win || is_desktop_linux || is_chromeos
+  enable_click_to_call = is_mac || is_win || is_linux || is_chromeos
 
   # Hangout services is an extension that adds extra features to Hangouts.
   # It is enableable separately to facilitate testing.
   enable_hangout_services_extension = is_chrome_branded
 
   # Enables usage of the system-provided notification center.
-  enable_native_notifications = is_android || is_mac || is_win ||
-                                (is_desktop_linux && use_dbus) || is_chromeos
+  enable_native_notifications =
+      is_android || is_mac || is_win || (is_linux && use_dbus) || is_chromeos
 
   enable_one_click_signin =
       is_win || is_mac || (is_linux && !is_chromeos && !is_chromecast)
diff --git a/chrome/installer/linux/BUILD.gn b/chrome/installer/linux/BUILD.gn
index f5543a32..4fceedf 100644
--- a/chrome/installer/linux/BUILD.gn
+++ b/chrome/installer/linux/BUILD.gn
@@ -113,7 +113,7 @@
     target_cpu,
     "deb_{{source_name_part}}.deps",
   ]
-  if (is_desktop_linux && target_cpu == "x64" && enable_distro_version_check) {
+  if (is_linux && target_cpu == "x64" && enable_distro_version_check) {
     args += [ "--distro-check" ]
   }
 }
@@ -147,7 +147,7 @@
     "rpm_{{source_name_part}}.deps",
   ]
   args += rebase_path(packaging_files_shlibs, root_build_dir)
-  if (is_desktop_linux && target_cpu == "x64" && enable_distro_version_check) {
+  if (is_linux && target_cpu == "x64" && enable_distro_version_check) {
     args += [ "--distro-check" ]
   }
 }
diff --git a/chrome/renderer/BUILD.gn b/chrome/renderer/BUILD.gn
index 11e52c2..ce49b592 100644
--- a/chrome/renderer/BUILD.gn
+++ b/chrome/renderer/BUILD.gn
@@ -260,8 +260,6 @@
 
   if (safe_browsing_mode != 0) {
     sources += [
-      "safe_browsing/features.cc",
-      "safe_browsing/features.h",
       "safe_browsing/murmurhash3_util.cc",
       "safe_browsing/murmurhash3_util.h",
       "safe_browsing/phishing_classifier.cc",
@@ -280,6 +278,7 @@
     deps += [
       "//chrome/common/safe_browsing:proto",
       "//components/safe_browsing/content/renderer",
+      "//components/safe_browsing/content/renderer/phishing_classifier",
       "//components/safe_browsing/core:client_model_proto",
       "//components/safe_browsing/core:csd_proto",
       "//components/safe_browsing/core/common",
@@ -431,13 +430,12 @@
   sources = [
     "chrome_mock_render_thread.cc",
     "chrome_mock_render_thread.h",
-    "safe_browsing/test_utils.cc",
-    "safe_browsing/test_utils.h",
   ]
 
   deps = [
     ":renderer",
     "//chrome/common/search:mojo_bindings",
+    "//components/safe_browsing/content/renderer/phishing_classifier:unit_tests_support",
     "//content/test:test_support",
     "//extensions/buildflags",
     "//testing/gmock",
diff --git a/chrome/renderer/safe_browsing/phishing_classifier.cc b/chrome/renderer/safe_browsing/phishing_classifier.cc
index db00a32..c19e183 100644
--- a/chrome/renderer/safe_browsing/phishing_classifier.cc
+++ b/chrome/renderer/safe_browsing/phishing_classifier.cc
@@ -21,12 +21,12 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "cc/paint/skia_paint_canvas.h"
 #include "chrome/common/url_constants.h"
-#include "chrome/renderer/safe_browsing/features.h"
 #include "chrome/renderer/safe_browsing/phishing_dom_feature_extractor.h"
 #include "chrome/renderer/safe_browsing/phishing_term_feature_extractor.h"
 #include "chrome/renderer/safe_browsing/phishing_url_feature_extractor.h"
 #include "chrome/renderer/safe_browsing/scorer.h"
 #include "components/paint_preview/common/paint_preview_tracker.h"
+#include "components/safe_browsing/content/renderer/phishing_classifier/features.h"
 #include "components/safe_browsing/core/proto/csd.pb.h"
 #include "content/public/renderer/render_frame.h"
 #include "content/public/renderer/render_thread.h"
diff --git a/chrome/renderer/safe_browsing/phishing_classifier_browsertest.cc b/chrome/renderer/safe_browsing/phishing_classifier_browsertest.cc
index 9029c3e..a1004c1 100644
--- a/chrome/renderer/safe_browsing/phishing_classifier_browsertest.cc
+++ b/chrome/renderer/safe_browsing/phishing_classifier_browsertest.cc
@@ -14,10 +14,10 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/test_discardable_memory_allocator.h"
 #include "chrome/renderer/chrome_content_renderer_client.h"
-#include "chrome/renderer/safe_browsing/features.h"
 #include "chrome/renderer/safe_browsing/murmurhash3_util.h"
 #include "chrome/renderer/safe_browsing/scorer.h"
 #include "chrome/test/base/chrome_render_view_test.h"
+#include "components/safe_browsing/content/renderer/phishing_classifier/features.h"
 #include "components/safe_browsing/core/proto/client_model.pb.h"
 #include "components/safe_browsing/core/proto/csd.pb.h"
 #include "content/public/renderer/render_frame.h"
diff --git a/chrome/renderer/safe_browsing/phishing_classifier_delegate_browsertest.cc b/chrome/renderer/safe_browsing/phishing_classifier_delegate_browsertest.cc
index 0ae43da8..590eb0c 100644
--- a/chrome/renderer/safe_browsing/phishing_classifier_delegate_browsertest.cc
+++ b/chrome/renderer/safe_browsing/phishing_classifier_delegate_browsertest.cc
@@ -8,12 +8,12 @@
 
 #include "base/bind.h"
 #include "base/strings/utf_string_conversions.h"
-#include "chrome/renderer/safe_browsing/features.h"
 #include "chrome/renderer/safe_browsing/phishing_classifier.h"
 #include "chrome/renderer/safe_browsing/scorer.h"
 #include "chrome/test/base/chrome_render_view_test.h"
 #include "chrome/test/base/chrome_unit_test_suite.h"
 #include "components/safe_browsing/content/common/safe_browsing.mojom-shared.h"
+#include "components/safe_browsing/content/renderer/phishing_classifier/features.h"
 #include "components/safe_browsing/core/proto/csd.pb.h"
 #include "content/public/renderer/render_frame.h"
 #include "content/public/renderer/render_view.h"
diff --git a/chrome/renderer/safe_browsing/phishing_dom_feature_extractor.cc b/chrome/renderer/safe_browsing/phishing_dom_feature_extractor.cc
index c31a9732..2603fd2c 100644
--- a/chrome/renderer/safe_browsing/phishing_dom_feature_extractor.cc
+++ b/chrome/renderer/safe_browsing/phishing_dom_feature_extractor.cc
@@ -15,7 +15,7 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/default_tick_clock.h"
 #include "base/time/time.h"
-#include "chrome/renderer/safe_browsing/features.h"
+#include "components/safe_browsing/content/renderer/phishing_classifier/features.h"
 #include "content/public/renderer/render_view.h"
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
 #include "third_party/blink/public/platform/web_string.h"
diff --git a/chrome/renderer/safe_browsing/phishing_dom_feature_extractor_browsertest.cc b/chrome/renderer/safe_browsing/phishing_dom_feature_extractor_browsertest.cc
index 0586f5a..7f19c40 100644
--- a/chrome/renderer/safe_browsing/phishing_dom_feature_extractor_browsertest.cc
+++ b/chrome/renderer/safe_browsing/phishing_dom_feature_extractor_browsertest.cc
@@ -12,9 +12,9 @@
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
 #include "chrome/renderer/chrome_content_renderer_client.h"
-#include "chrome/renderer/safe_browsing/features.h"
-#include "chrome/renderer/safe_browsing/test_utils.h"
 #include "chrome/test/base/chrome_render_view_test.h"
+#include "components/safe_browsing/content/renderer/phishing_classifier/features.h"
+#include "components/safe_browsing/content/renderer/phishing_classifier/test_utils.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/renderer/render_frame.h"
 #include "content/public/test/test_utils.h"
diff --git a/chrome/renderer/safe_browsing/phishing_term_feature_extractor.cc b/chrome/renderer/safe_browsing/phishing_term_feature_extractor.cc
index 61d0457..a1350e6 100644
--- a/chrome/renderer/safe_browsing/phishing_term_feature_extractor.cc
+++ b/chrome/renderer/safe_browsing/phishing_term_feature_extractor.cc
@@ -21,8 +21,8 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/default_tick_clock.h"
 #include "base/time/time.h"
-#include "chrome/renderer/safe_browsing/features.h"
 #include "chrome/renderer/safe_browsing/murmurhash3_util.h"
+#include "components/safe_browsing/content/renderer/phishing_classifier/features.h"
 #include "crypto/sha2.h"
 
 namespace safe_browsing {
diff --git a/chrome/renderer/safe_browsing/phishing_term_feature_extractor_unittest.cc b/chrome/renderer/safe_browsing/phishing_term_feature_extractor_unittest.cc
index 53fc9a6..8e5ad3c 100644
--- a/chrome/renderer/safe_browsing/phishing_term_feature_extractor_unittest.cc
+++ b/chrome/renderer/safe_browsing/phishing_term_feature_extractor_unittest.cc
@@ -23,9 +23,9 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
-#include "chrome/renderer/safe_browsing/features.h"
 #include "chrome/renderer/safe_browsing/murmurhash3_util.h"
-#include "chrome/renderer/safe_browsing/test_utils.h"
+#include "components/safe_browsing/content/renderer/phishing_classifier/features.h"
+#include "components/safe_browsing/content/renderer/phishing_classifier/test_utils.h"
 #include "crypto/sha2.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/chrome/renderer/safe_browsing/phishing_url_feature_extractor.cc b/chrome/renderer/safe_browsing/phishing_url_feature_extractor.cc
index 403d1d06..cdd9c35c 100644
--- a/chrome/renderer/safe_browsing/phishing_url_feature_extractor.cc
+++ b/chrome/renderer/safe_browsing/phishing_url_feature_extractor.cc
@@ -12,7 +12,7 @@
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
 #include "base/timer/elapsed_timer.h"
-#include "chrome/renderer/safe_browsing/features.h"
+#include "components/safe_browsing/content/renderer/phishing_classifier/features.h"
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
 #include "url/gurl.h"
 
diff --git a/chrome/renderer/safe_browsing/phishing_url_feature_extractor_unittest.cc b/chrome/renderer/safe_browsing/phishing_url_feature_extractor_unittest.cc
index c87ab3d..3cd982a3e 100644
--- a/chrome/renderer/safe_browsing/phishing_url_feature_extractor_unittest.cc
+++ b/chrome/renderer/safe_browsing/phishing_url_feature_extractor_unittest.cc
@@ -8,8 +8,8 @@
 #include <vector>
 #include "base/format_macros.h"
 #include "base/strings/stringprintf.h"
-#include "chrome/renderer/safe_browsing/features.h"
-#include "chrome/renderer/safe_browsing/test_utils.h"
+#include "components/safe_browsing/content/renderer/phishing_classifier/features.h"
+#include "components/safe_browsing/content/renderer/phishing_classifier/test_utils.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
diff --git a/chrome/renderer/safe_browsing/scorer.cc b/chrome/renderer/safe_browsing/scorer.cc
index b41de5fc..58b575e6 100644
--- a/chrome/renderer/safe_browsing/scorer.cc
+++ b/chrome/renderer/safe_browsing/scorer.cc
@@ -15,8 +15,8 @@
 #include "base/strings/string_piece.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
-#include "chrome/renderer/safe_browsing/features.h"
 #include "components/safe_browsing/content/password_protection/visual_utils.h"
+#include "components/safe_browsing/content/renderer/phishing_classifier/features.h"
 #include "components/safe_browsing/core/proto/client_model.pb.h"
 #include "components/safe_browsing/core/proto/csd.pb.h"
 #include "content/public/renderer/render_thread.h"
diff --git a/chrome/renderer/safe_browsing/scorer_unittest.cc b/chrome/renderer/safe_browsing/scorer_unittest.cc
index a117495..85ead0a 100644
--- a/chrome/renderer/safe_browsing/scorer_unittest.cc
+++ b/chrome/renderer/safe_browsing/scorer_unittest.cc
@@ -16,7 +16,7 @@
 #include "base/test/bind_test_util.h"
 #include "base/test/task_environment.h"
 #include "base/threading/thread.h"
-#include "chrome/renderer/safe_browsing/features.h"
+#include "components/safe_browsing/content/renderer/phishing_classifier/features.h"
 #include "components/safe_browsing/core/proto/client_model.pb.h"
 #include "components/safe_browsing/core/proto/csd.pb.h"
 #include "testing/gmock/include/gmock/gmock.h"
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index d0ef54ea..80fe182 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2928,7 +2928,7 @@
     if (is_mac || is_win || is_chromeos) {
       sources += [ "../browser/extensions/api/networking_cast_private/networking_cast_private_apitest.cc" ]
     }
-    if ((is_desktop_linux && !chromeos_is_browser_only) || is_mac) {
+    if ((is_linux && !chromeos_is_browser_only) || is_mac) {
       sources +=
           [ "../browser/first_run/first_run_internal_posix_browsertest.cc" ]
     }
@@ -3349,7 +3349,7 @@
     "../browser/component_updater/chrome_component_updater_configurator_unittest.cc",
     "../browser/component_updater/crl_set_component_installer_unittest.cc",
     "../browser/component_updater/first_party_sets_component_installer_unittest.cc",
-    "../browser/component_updater/floc_blocklist_component_installer_unittest.cc",
+    "../browser/component_updater/floc_component_installer_unittest.cc",
     "../browser/component_updater/games_component_installer_unittest.cc",
     "../browser/component_updater/optimization_hints_component_installer_unittest.cc",
     "../browser/component_updater/origin_trials_component_installer_unittest.cc",
@@ -4677,7 +4677,7 @@
 
   if (enable_native_notifications) {
     # TODO(crbug.com/1052397): Rename chromeos_is_browser_only to is_lacros.
-    if (is_desktop_linux && !chromeos_is_browser_only) {
+    if (is_linux && !chromeos_is_browser_only) {
       sources += [ "../browser/notifications/notification_platform_bridge_linux_unittest.cc" ]
     }
 
@@ -5460,7 +5460,6 @@
       "../common/safe_browsing/ipc_protobuf_message_test_messages.h",
       "../common/safe_browsing/ipc_protobuf_message_unittest.cc",
       "../common/safe_browsing/mach_o_image_reader_mac_unittest.cc",
-      "../renderer/safe_browsing/features_unittest.cc",
       "../renderer/safe_browsing/murmurhash3_util_unittest.cc",
       "../renderer/safe_browsing/phishing_term_feature_extractor_unittest.cc",
       "../renderer/safe_browsing/phishing_url_feature_extractor_unittest.cc",
@@ -5828,7 +5827,7 @@
       "../browser/ui/views/translate/translate_bubble_view_unittest.cc",
       "../browser/ui/views/window_name_prompt_unittest.cc",
     ]
-    if (is_desktop_linux) {
+    if (is_linux) {
       sources += [ "../browser/ui/views/frame/desktop_linux_browser_frame_view_layout_unittest.cc" ]
     }
     if (enable_plugins) {
@@ -5915,7 +5914,7 @@
     ]
   }
 
-  if (enable_widevine_cdm_component && is_desktop_linux) {
+  if (enable_widevine_cdm_component && is_linux) {
     sources +=
         [ "../common/media/component_widevine_cdm_hint_file_linux_unittest.cc" ]
   }
@@ -6278,6 +6277,7 @@
       "//chrome/browser/devtools:test_support",
       "//chrome/browser/resource_coordinator:tab_metrics_event_proto",
       "//chrome/renderer",
+      "//components/feature_engagement/test:test_support",
       "//components/keep_alive_registry",
       "//components/media_router/browser:test_support",
       "//components/resources",
@@ -6359,6 +6359,7 @@
         "../browser/ui/views/frame/browser_window_property_manager_browsertest_win.cc",
         "../browser/ui/views/frame/tab_strip_region_view_interactive_uitest.cc",
         "../browser/ui/views/fullscreen_control/fullscreen_control_view_interactive_uitest.cc",
+        "../browser/ui/views/in_product_help/feature_promo_snooze_interactive_uitest.cc",
         "../browser/ui/views/keyboard_access_browsertest.cc",
         "../browser/ui/views/location_bar/location_icon_view_interactive_uitest.cc",
         "../browser/ui/views/location_bar/selected_keyword_view_interactive_uitest.cc",
diff --git a/chrome/test/base/testing_browser_process.cc b/chrome/test/base/testing_browser_process.cc
index c5d35f8..fafabee 100644
--- a/chrome/test/base/testing_browser_process.cc
+++ b/chrome/test/base/testing_browser_process.cc
@@ -28,6 +28,7 @@
 #include "chrome/common/chrome_paths.h"
 #include "chrome/test/base/testing_browser_process_platform_part.h"
 #include "components/federated_learning/floc_blocklist_service.h"
+#include "components/federated_learning/floc_sorting_lsh_clusters_service.h"
 #include "components/network_time/network_time_tracker.h"
 #include "components/optimization_guide/optimization_guide_service.h"
 #include "components/permissions/permissions_client.h"
@@ -285,6 +286,11 @@
   return floc_blocklist_service_.get();
 }
 
+federated_learning::FlocSortingLshClustersService*
+TestingBrowserProcess::floc_sorting_lsh_clusters_service() {
+  return floc_sorting_lsh_clusters_service_.get();
+}
+
 optimization_guide::OptimizationGuideService*
 TestingBrowserProcess::optimization_guide_service() {
   return optimization_guide_service_.get();
@@ -517,6 +523,12 @@
   floc_blocklist_service_.swap(service);
 }
 
+void TestingBrowserProcess::SetFlocSortingLshClustersService(
+    std::unique_ptr<federated_learning::FlocSortingLshClustersService>
+        service) {
+  floc_sorting_lsh_clusters_service_.swap(service);
+}
+
 void TestingBrowserProcess::SetOptimizationGuideService(
     std::unique_ptr<optimization_guide::OptimizationGuideService>
         optimization_guide_service) {
diff --git a/chrome/test/base/testing_browser_process.h b/chrome/test/base/testing_browser_process.h
index 4fa6f8e..8953733 100644
--- a/chrome/test/base/testing_browser_process.h
+++ b/chrome/test/base/testing_browser_process.h
@@ -99,6 +99,8 @@
   subresource_filter::RulesetService* subresource_filter_ruleset_service()
       override;
   federated_learning::FlocBlocklistService* floc_blocklist_service() override;
+  federated_learning::FlocSortingLshClustersService*
+  floc_sorting_lsh_clusters_service() override;
   optimization_guide::OptimizationGuideService* optimization_guide_service()
       override;
   BrowserProcessPlatformPart* platform_part() override;
@@ -152,6 +154,9 @@
       std::unique_ptr<subresource_filter::RulesetService> ruleset_service);
   void SetFlocBlocklistService(
       std::unique_ptr<federated_learning::FlocBlocklistService> service);
+  void SetFlocSortingLshClustersService(
+      std::unique_ptr<federated_learning::FlocSortingLshClustersService>
+          service);
   void SetOptimizationGuideService(
       std::unique_ptr<optimization_guide::OptimizationGuideService>
           optimization_guide_service);
@@ -206,6 +211,8 @@
       subresource_filter_ruleset_service_;
   std::unique_ptr<federated_learning::FlocBlocklistService>
       floc_blocklist_service_;
+  std::unique_ptr<federated_learning::FlocSortingLshClustersService>
+      floc_sorting_lsh_clusters_service_;
   std::unique_ptr<optimization_guide::OptimizationGuideService>
       optimization_guide_service_;
 
diff --git a/chrome/test/data/pdf/viewer_thumbnail_bar_test.js b/chrome/test/data/pdf/viewer_thumbnail_bar_test.js
index 8035dd9..61863951 100644
--- a/chrome/test/data/pdf/viewer_thumbnail_bar_test.js
+++ b/chrome/test/data/pdf/viewer_thumbnail_bar_test.js
@@ -18,6 +18,18 @@
   return thumbnailBar;
 }
 
+/** @return {number} */
+function getTestThumbnailBarHeight() {
+  // Create a viewer-thumbnail element to get the standard height.
+  document.body.innerHTML = '';
+  const sizerThumbnail = document.createElement('viewer-thumbnail');
+  sizerThumbnail.pageNumber = 1;
+  document.body.appendChild(sizerThumbnail);
+  // Add 24 to cover padding between thumbnails.
+  const thumbnailBarHeight = sizerThumbnail.offsetHeight + 24;
+  return thumbnailBarHeight;
+}
+
 // Unit tests for the viewer-thumbnail-bar element.
 const tests = [
   // Test that the thumbnail bar has the correct number of thumbnails and
@@ -60,15 +72,10 @@
     });
   },
   function testTriggerPaint() {
-    // Create a viewer-thumbnail element to get the standard height.
-    document.body.innerHTML = '';
-    const sizerThumbnail = document.createElement('viewer-thumbnail');
-    document.body.appendChild(sizerThumbnail);
-    // Add 24 to cover padding between thumbnails.
-    const thumbnailBarHeight = sizerThumbnail.offsetHeight + 24;
+    const thumbnailBarHeight = getTestThumbnailBarHeight();
 
     // Clear HTML for just the thumbnail bar.
-    const testDocLength = 4;
+    const testDocLength = 8;
     const thumbnailBar = createThumbnailBar();
     thumbnailBar.docLength = testDocLength;
 
@@ -77,6 +84,10 @@
     thumbnailBar.style.height = `${thumbnailBarHeight}px`;
     thumbnailBar.style.display = 'block';
 
+    // Remove any padding from the scroller.
+    const scroller = thumbnailBar.shadowRoot.querySelector('#thumbnails');
+    scroller.style.padding = '';
+
     flush();
 
     const thumbnails =
@@ -99,35 +110,52 @@
       });
     }
 
-    const whenRequestedPaintingFirst = [
-      paintThumbnailToPromise(thumbnails[0]),
-      paintThumbnailToPromise(thumbnails[1]),
-    ];
-
     testAsync(async () => {
+      // Only two thumbnails should be "painted" upon load.
+      const whenRequestedPaintingFirst = [
+        paintThumbnailToPromise(thumbnails[0]),
+        paintThumbnailToPromise(thumbnails[1]),
+      ];
       await Promise.all(whenRequestedPaintingFirst);
 
-      // Only two thumbnails should be pending.
+      chrome.test.assertEq(testDocLength, thumbnails.length);
       for (let i = 0; i < thumbnails.length; i++) {
-        chrome.test.assertEq(i < 2, thumbnails[i].hasAttribute('pending'));
+        chrome.test.assertEq(i < 2, thumbnails[i].isPainted());
       }
 
-      // Test that scrolling to the bottom triggers 'paint-thumbnail' events for
-      // the remaining thumbnails.
-      const whenRequestedPaintingLast = [
-        paintThumbnailToPromise(thumbnails[2]),
-        paintThumbnailToPromise(thumbnails[3]),
-      ];
+      // Test that scrolling to the sixth thumbnail triggers 'paint-thumbnail'
+      // for thumbnails 3 through 7. When on the sixth thumbnail, five
+      // thumbnails above and one thumbnail below should also be painted because
+      // of the 500% top and 100% bottom root margins.
+      const whenRequestedPaintingNext = [];
+      for (let i = 2; i < 7; i++) {
+        whenRequestedPaintingNext.push(paintThumbnailToPromise(thumbnails[i]));
+      }
+      scroller.scrollTop = 5 * thumbnailBarHeight;
+      await Promise.all(whenRequestedPaintingNext);
 
-      thumbnailBar.shadowRoot.querySelector('#thumbnails').scrollTop =
-          2 * thumbnailBarHeight;
+      // First seven thumbnails should be painted.
+      for (let i = 0; i < thumbnails.length; i++) {
+        chrome.test.assertEq(i < 7, thumbnails[i].isPainted());
+      }
+
+      // Test that scrolling down to the eighth thumbnail will clear the
+      // thumbnails outside the root margin, namely the first two. A paint
+      // should also be triggered for the eighth thumbnail.
+      const whenRequestedPaintingLast = [
+        paintThumbnailToPromise(thumbnails[7]),
+        eventToPromise('clear-thumbnail-for-testing', thumbnails[0]),
+        eventToPromise('clear-thumbnail-for-testing', thumbnails[1]),
+      ];
+      scroller.scrollTop = 7 * thumbnailBarHeight;
       await Promise.all(whenRequestedPaintingLast);
 
-      for (const thumbnail of thumbnails) {
-        chrome.test.assertTrue(thumbnail.hasAttribute('pending'));
+      // Only first two thumbnails should not be painted.
+      for (let i = 0; i < thumbnails.length; i++) {
+        chrome.test.assertEq(i > 1, thumbnails[i].isPainted());
       }
     });
-  }
+  },
 ];
 
 chrome.test.runTests(tests);
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index e95e9fc..ddc7ff6 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -8219,5 +8219,14 @@
         "prefs": { "kaleidoscope.enabled_by_policy": { } }
       }
     ]
+  },
+  "EduCoexistenceToSVersion": {
+    "os": ["chromeos"],
+    "policy_pref_mapping_test": [
+      {
+        "policies": {"EduCoexistenceToSVersion": "333024512" },
+        "prefs": { "family_link_user.edu_coexistence_tos_version": {} }
+      }
+    ]
   }
 }
diff --git a/chrome/test/data/webui/BUILD.gn b/chrome/test/data/webui/BUILD.gn
index 68f680b..c7de1d53 100644
--- a/chrome/test/data/webui/BUILD.gn
+++ b/chrome/test/data/webui/BUILD.gn
@@ -360,7 +360,7 @@
 
     deps = [ "//chrome/browser/ui" ]
 
-    if (is_win || is_mac || is_desktop_linux || is_chromeos) {
+    if (is_win || is_mac || is_linux || is_chromeos) {
       sources += [ "discards/discards_browsertest.js" ]
     }
 
diff --git a/chrome/test/data/webui/new_tab_page/modules/shopping_tasks/module_test.js b/chrome/test/data/webui/new_tab_page/modules/shopping_tasks/module_test.js
index 85f8778..02f4fbd 100644
--- a/chrome/test/data/webui/new_tab_page/modules/shopping_tasks/module_test.js
+++ b/chrome/test/data/webui/new_tab_page/modules/shopping_tasks/module_test.js
@@ -4,7 +4,6 @@
 
 import {shoppingTasksDescriptor, ShoppingTasksHandlerProxy} from 'chrome://new-tab-page/new_tab_page.js';
 import {TestBrowserProxy} from 'chrome://test/test_browser_proxy.m.js';
-import {flushTasks} from 'chrome://test/test_util.m.js';
 
 suite('NewTabPageModulesShoppingTasksModuleTest', () => {
   /**
@@ -103,45 +102,4 @@
     assertEquals('https://blub.com/', pills[1].href);
     assertEquals('blub', pills[1].querySelector('.search-text').innerText);
   });
-
-  test('products and pills are hidden when cutoff', async () => {
-    const repeat = (n, fn) => Array(n).fill(0).map(fn);
-    testProxy.handler.setResultFor('getPrimaryShoppingTask', Promise.resolve({
-      shoppingTask: {
-        title: 'Hello world',
-        products: repeat(20, () => ({
-                               name: 'foo',
-                               imageUrl: {url: 'https://foo.com/img.png'},
-                               price: '1 gazillion dollars',
-                               info: 'foo info',
-                               targetUrl: {url: 'https://foo.com'},
-                             })),
-        relatedSearches: repeat(20, () => ({
-                                      text: 'baz',
-                                      targetUrl: {url: 'https://baz.com'},
-                                    })),
-      }
-    }));
-    await shoppingTasksDescriptor.initialize();
-    const moduleElement = shoppingTasksDescriptor.element;
-    document.body.append(moduleElement);
-    moduleElement.$.productsRepeat.render();
-    moduleElement.$.relatedSearchesRepeat.render();
-    const getElements = () => Array.from(
-        moduleElement.shadowRoot.querySelectorAll('.product, .pill'));
-    assertEquals(40, getElements().length);
-    const hiddenCount = () =>
-        getElements().filter(el => el.style.visibility === 'hidden').length;
-    const checkHidden = async (width, count) => {
-      moduleElement.style.width = width;
-      await flushTasks();
-      await flushTasks();
-      await flushTasks();
-      assertEquals(count, hiddenCount());
-    };
-    await checkHidden('500px', 31);
-    await checkHidden('300px', 35);
-    await checkHidden('700px', 26);
-    await checkHidden('500px', 31);
-  });
 });
diff --git a/chrome/test/data/webui/settings/chromeos/device_page_tests.js b/chrome/test/data/webui/settings/chromeos/device_page_tests.js
index ebee507..4f0dd422 100644
--- a/chrome/test/data/webui/settings/chromeos/device_page_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/device_page_tests.js
@@ -39,6 +39,7 @@
     initializePointers: function() {
       // Enable mouse and touchpad.
       cr.webUIListenerCallback('has-mouse-changed', true);
+      cr.webUIListenerCallback('has-pointing-stick-changed', true);
       cr.webUIListenerCallback('has-touchpad-changed', true);
     },
 
@@ -591,6 +592,8 @@
 
       cr.webUIListenerCallback('has-mouse-changed', false);
       expectLT(0, devicePage.$$('#pointersRow').offsetHeight);
+      cr.webUIListenerCallback('has-pointing-stick-changed', false);
+      expectLT(0, devicePage.$$('#pointersRow').offsetHeight);
       cr.webUIListenerCallback('has-touchpad-changed', false);
       expectEquals(0, devicePage.$$('#pointersRow').offsetHeight);
       cr.webUIListenerCallback('has-mouse-changed', true);
@@ -625,6 +628,15 @@
         assertEquals(0, pointersPage.$$('#mouse h2').offsetHeight);
         assertEquals(0, pointersPage.$$('#touchpad h2').offsetHeight);
 
+        cr.webUIListenerCallback('has-pointing-stick-changed', false);
+        assertEquals(
+            settings.routes.POINTERS,
+            settings.Router.getInstance().getCurrentRoute());
+        assertLT(0, pointersPage.$$('#mouse').offsetHeight);
+        assertEquals(0, pointersPage.$$('#touchpad').offsetHeight);
+        assertEquals(0, pointersPage.$$('#mouse h2').offsetHeight);
+        assertEquals(0, pointersPage.$$('#touchpad h2').offsetHeight);
+
         cr.webUIListenerCallback('has-mouse-changed', false);
         assertEquals(
             settings.routes.DEVICE,
diff --git a/chrome/test/data/webui/settings/password_check_test.js b/chrome/test/data/webui/settings/password_check_test.js
index 3d0a704..a5e8bd2 100644
--- a/chrome/test/data/webui/settings/password_check_test.js
+++ b/chrome/test/data/webui/settings/password_check_test.js
@@ -929,7 +929,7 @@
     assertTrue(isElementVisible(section.$.compromisedCredentialsBody));
     assertTrue(isElementVisible(section.$.signedOutUserLabel));
     expectEquals(
-        section.i18n('signedOutUserLabel'),
+        section.i18n('signedOutUserHasCompromisedCredentialsLabel'),
         section.$.signedOutUserLabel.textContent.trim());
     assertTrue(isElementVisible(section.$.compromisedPasswordsDescription));
     expectEquals(
diff --git a/chromeos/chromeos_strings.grd b/chromeos/chromeos_strings.grd
index 2069be18..25a009f 100644
--- a/chromeos/chromeos_strings.grd
+++ b/chromeos/chromeos_strings.grd
@@ -472,6 +472,22 @@
       <message name="IDS_SCANNING_APP_SOURCE_DROPDOWN_LABEL" desc="The label for the dropdown that displays available scan sources (e.g. flatbed, document feeder, etc.).">
         Source
       </message>
+
+      <!-- Diagnostics App -->
+      <!-- TODO(michaelcheco): Update with finalized copies of the strings -->
+      <message name="IDS_DIAGNOSTICS_TITLE" desc="The title of the diagnostics app." translateable="false">
+        Diagnostics
+      </message>
+      <message name="IDS_DIAGNOSTICS_MEMORY_TITLE" desc="The title of the diagnostics memory section which shows users' information about their computer memory." translateable="false">
+        Memory
+      </message>
+      <message name="IDS_DIAGNOSTICS_BATTERY_TITLE" desc="The title of the diagnostics battery section which shows users' information about their computer battery." translateable="false">
+        Battery
+      </message>
+      <message name="IDS_DIAGNOSTICS_CPU_TITLE" desc="The title of the diagnostics CPU section which shows users' information about their computer CPU usage." translateable="false">
+        CPU
+      </message>
+
     </messages>
   </release>
 </grit>
diff --git a/chromeos/components/diagnostics_ui/BUILD.gn b/chromeos/components/diagnostics_ui/BUILD.gn
index fadc28bc..c3d404d 100644
--- a/chromeos/components/diagnostics_ui/BUILD.gn
+++ b/chromeos/components/diagnostics_ui/BUILD.gn
@@ -17,7 +17,9 @@
     "//chromeos/components/diagnostics_ui/mojom",
     "//chromeos/constants",
     "//chromeos/resources:diagnostics_app_resources",
+    "//chromeos/strings/",
     "//content/public/browser",
+    "//ui/base",
     "//ui/resources",
     "//ui/webui",
   ]
diff --git a/chromeos/components/diagnostics_ui/DEPS b/chromeos/components/diagnostics_ui/DEPS
index 377b9fdc..8091ae84 100644
--- a/chromeos/components/diagnostics_ui/DEPS
+++ b/chromeos/components/diagnostics_ui/DEPS
@@ -1,7 +1,9 @@
 include_rules = [
   "-chrome",
   "+chromeos/grit/chromeos_diagnostics_app_resources.h",
+  "+chromeos/strings/grit/chromeos_strings.h",
   "+content/public/browser",
+  "+ui/base",
   "+ui/resources",
   "+ui/webui",
 ]
diff --git a/chromeos/components/diagnostics_ui/backend/system_data_provider.cc b/chromeos/components/diagnostics_ui/backend/system_data_provider.cc
index b005809..ea2f0c46 100644
--- a/chromeos/components/diagnostics_ui/backend/system_data_provider.cc
+++ b/chromeos/components/diagnostics_ui/backend/system_data_provider.cc
@@ -10,7 +10,10 @@
 
 #include "base/bind.h"
 #include "base/callback.h"
+#include "base/i18n/time_formatting.h"
 #include "base/optional.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
 #include "chromeos/components/diagnostics_ui/backend/cros_healthd_helpers.h"
 #include "chromeos/components/diagnostics_ui/backend/power_manager_client_conversions.h"
 #include "chromeos/dbus/power_manager/power_supply_properties.pb.h"
@@ -26,6 +29,10 @@
 using PowerSupplyProperties = power_manager::PowerSupplyProperties;
 using ProbeCategories = healthd::ProbeCategoryEnum;
 
+constexpr int kBatteryHealthRefreshIntervalInSeconds = 60;
+constexpr int kChargeStatusRefreshIntervalInSeconds = 15;
+constexpr int kMilliampsInAnAmp = 1000;
+
 void PopulateBoardName(const healthd::SystemInfo& system_info,
                        mojom::SystemInfo& out_system_info) {
   const base::Optional<std::string>& product_name = system_info.product_name;
@@ -81,9 +88,8 @@
 void PopulateBatteryInfo(const healthd::BatteryInfo& battery_info,
                          mojom::BatteryInfo& out_battery_info) {
   out_battery_info.manufacturer = battery_info.vendor;
-  // Multiply by 1000 to convert amps to milliamps.
   out_battery_info.charge_full_design_milliamp_hours =
-      battery_info.charge_full_design * 1000;
+      battery_info.charge_full_design * kMilliampsInAnAmp;
 }
 
 void PopulatePowerInfo(const PowerSupplyProperties& power_supply_properties,
@@ -104,14 +110,29 @@
     mojom::BatteryChargeStatus& out_charge_status) {
   PopulatePowerInfo(power_supply_properties, out_charge_status);
 
-  // Multiply by 1000 to convert amps to milliamps.
-  out_charge_status.current_now_milliamps = battery_info.current_now * 1000;
-  out_charge_status.charge_now_milliamp_hours = battery_info.charge_now * 1000;
+  out_charge_status.current_now_milliamps =
+      battery_info.current_now * kMilliampsInAnAmp;
+  out_charge_status.charge_now_milliamp_hours =
+      battery_info.charge_now * kMilliampsInAnAmp;
+}
+
+void PopulateBatteryHealth(const healthd::BatteryInfo& battery_info,
+                           mojom::BatteryHealth& out_battery_health) {
+  out_battery_health.charge_full_now_milliamp_hours =
+      battery_info.charge_full * kMilliampsInAnAmp;
+  out_battery_health.charge_full_design_milliamp_hours =
+      battery_info.charge_full_design * kMilliampsInAnAmp;
+  out_battery_health.cycle_count = battery_info.cycle_count;
+  out_battery_health.battery_wear_percentage =
+      out_battery_health.charge_full_now_milliamp_hours /
+      out_battery_health.charge_full_design_milliamp_hours;
 }
 
 }  // namespace
 
 SystemDataProvider::SystemDataProvider() {
+  battery_charge_status_timer_ = std::make_unique<base::RepeatingTimer>();
+  battery_health_timer_ = std::make_unique<base::RepeatingTimer>();
   PowerManagerClient::Get()->AddObserver(this);
 }
 
@@ -138,7 +159,40 @@
                      base::Unretained(this), std::move(callback)));
 }
 
-void SystemDataProvider::PowerChanged(const PowerSupplyProperties& proto) {
+void SystemDataProvider::ObserveBatteryChargeStatus(
+    mojo::PendingRemote<mojom::BatteryChargeStatusObserver> observer) {
+  battery_charge_status_observers_.Add(std::move(observer));
+
+  if (!battery_charge_status_timer_->IsRunning()) {
+    battery_charge_status_timer_->Start(
+        FROM_HERE,
+        base::TimeDelta::FromSeconds(kChargeStatusRefreshIntervalInSeconds),
+        base::BindRepeating(&SystemDataProvider::UpdateBatteryChargeStatus,
+                            base::Unretained(this)));
+  }
+  UpdateBatteryChargeStatus();
+}
+
+void SystemDataProvider::ObserveBatteryHealth(
+    mojo::PendingRemote<mojom::BatteryHealthObserver> observer) {
+  battery_health_observers_.Add(std::move(observer));
+
+  if (!battery_health_timer_->IsRunning()) {
+    battery_health_timer_->Start(
+        FROM_HERE,
+        base::TimeDelta::FromSeconds(kBatteryHealthRefreshIntervalInSeconds),
+        base::BindRepeating(&SystemDataProvider::UpdateBatteryHealth,
+                            base::Unretained(this)));
+  }
+  UpdateBatteryHealth();
+}
+
+void SystemDataProvider::PowerChanged(
+    const power_manager::PowerSupplyProperties& proto) {
+  if (battery_charge_status_observers_.empty()) {
+    return;
+  }
+
   // Fetch updated data from CrosHealthd
   BindCrosHealthdProbeServiceIfNeccessary();
   probe_service_->ProbeTelemetryInfo(
@@ -147,6 +201,16 @@
                      base::Unretained(this), proto));
 }
 
+void SystemDataProvider::SetBatteryChargeStatusTimerForTesting(
+    std::unique_ptr<base::RepeatingTimer> timer) {
+  battery_charge_status_timer_ = std::move(timer);
+}
+
+void SystemDataProvider::SetBatteryHealthTimerForTesting(
+    std::unique_ptr<base::RepeatingTimer> timer) {
+  battery_health_timer_ = std::move(timer);
+}
+
 void SystemDataProvider::OnSystemInfoProbeResponse(
     GetSystemInfoCallback callback,
     healthd::TelemetryInfoPtr info_ptr) {
@@ -228,6 +292,15 @@
                      base::Unretained(this), properties));
 }
 
+void SystemDataProvider::UpdateBatteryHealth() {
+  BindCrosHealthdProbeServiceIfNeccessary();
+
+  probe_service_->ProbeTelemetryInfo(
+      {ProbeCategories::kBattery},
+      base::BindOnce(&SystemDataProvider::OnBatteryHealthUpdated,
+                     base::Unretained(this)));
+}
+
 void SystemDataProvider::OnBatteryChargeStatusUpdated(
     const base::Optional<PowerSupplyProperties>& power_supply_properties,
     healthd::TelemetryInfoPtr info_ptr) {
@@ -237,12 +310,14 @@
   if (info_ptr.is_null()) {
     LOG(ERROR) << "Null response from croshealthd::ProbeTelemetryInfo.";
     NotifyBatteryChargeStatusObservers(battery_charge_status);
+    battery_charge_status_timer_.reset();
     return;
   }
 
   if (!power_supply_properties.has_value()) {
     LOG(ERROR) << "Null response from power_manager_client::GetLastStatus.";
     NotifyBatteryChargeStatusObservers(battery_charge_status);
+    battery_charge_status_timer_.reset();
     return;
   }
 
@@ -252,6 +327,7 @@
               DoesDeviceHaveBattery(*power_supply_properties))
         << "Sources should not disagree about whether there is a battery.";
     NotifyBatteryChargeStatusObservers(battery_charge_status);
+    battery_charge_status_timer_.reset();
     return;
   }
 
@@ -261,9 +337,40 @@
   NotifyBatteryChargeStatusObservers(battery_charge_status);
 }
 
+void SystemDataProvider::OnBatteryHealthUpdated(
+    healthd::TelemetryInfoPtr info_ptr) {
+  mojom::BatteryHealthPtr battery_health = mojom::BatteryHealth::New();
+
+  if (info_ptr.is_null()) {
+    LOG(ERROR) << "Null response from croshealthd::ProbeTelemetryInfo.";
+    NotifyBatteryHealthObservers(battery_health);
+    battery_health_timer_.reset();
+    return;
+  }
+
+  if (!DoesDeviceHaveBattery(*info_ptr)) {
+    NotifyBatteryHealthObservers(battery_health);
+    battery_health_timer_.reset();
+    return;
+  }
+
+  PopulateBatteryHealth(*diagnostics::GetBatteryInfo(*info_ptr),
+                        *battery_health.get());
+  NotifyBatteryHealthObservers(battery_health);
+}
+
 void SystemDataProvider::NotifyBatteryChargeStatusObservers(
     const mojom::BatteryChargeStatusPtr& battery_charge_status) {
-  NOTIMPLEMENTED();  // Implemented in subsequent CL.
+  for (auto& observer : battery_charge_status_observers_) {
+    observer->OnBatteryChargeStatusUpdated(battery_charge_status.Clone());
+  }
+}
+
+void SystemDataProvider::NotifyBatteryHealthObservers(
+    const mojom::BatteryHealthPtr& battery_health) {
+  for (auto& observer : battery_health_observers_) {
+    observer->OnBatteryHealthUpdated(battery_health.Clone());
+  }
 }
 
 void SystemDataProvider::BindCrosHealthdProbeServiceIfNeccessary() {
diff --git a/chromeos/components/diagnostics_ui/backend/system_data_provider.h b/chromeos/components/diagnostics_ui/backend/system_data_provider.h
index 9624ecd8..70959957 100644
--- a/chromeos/components/diagnostics_ui/backend/system_data_provider.h
+++ b/chromeos/components/diagnostics_ui/backend/system_data_provider.h
@@ -12,6 +12,11 @@
 #include "chromeos/dbus/power/power_manager_client.h"
 #include "chromeos/services/cros_healthd/public/mojom/cros_healthd.mojom.h"
 #include "mojo/public/cpp/bindings/remote.h"
+#include "mojo/public/cpp/bindings/remote_set.h"
+
+namespace base {
+class RepeatingTimer;
+}  // namespace base
 
 namespace chromeos {
 namespace diagnostics {
@@ -29,10 +34,21 @@
   // mojom::SystemDataProvider:
   void GetSystemInfo(GetSystemInfoCallback callback) override;
   void GetBatteryInfo(GetBatteryInfoCallback callback) override;
+  void ObserveBatteryChargeStatus(
+      mojo::PendingRemote<mojom::BatteryChargeStatusObserver> observer)
+      override;
+  void ObserveBatteryHealth(
+      mojo::PendingRemote<mojom::BatteryHealthObserver> observer) override;
 
   // PowerManagerClient::Observer:
   void PowerChanged(const power_manager::PowerSupplyProperties& proto) override;
 
+  void SetBatteryChargeStatusTimerForTesting(
+      std::unique_ptr<base::RepeatingTimer> timer);
+
+  void SetBatteryHealthTimerForTesting(
+      std::unique_ptr<base::RepeatingTimer> timer);
+
  private:
   void BindCrosHealthdProbeServiceIfNeccessary();
 
@@ -48,15 +64,28 @@
 
   void UpdateBatteryChargeStatus();
 
+  void UpdateBatteryHealth();
+
   void NotifyBatteryChargeStatusObservers(
       const mojom::BatteryChargeStatusPtr& battery_charge_status);
 
+  void NotifyBatteryHealthObservers(
+      const mojom::BatteryHealthPtr& battery_health);
+
   void OnBatteryChargeStatusUpdated(
       const base::Optional<power_manager::PowerSupplyProperties>&
           power_supply_properties,
       cros_healthd::mojom::TelemetryInfoPtr info_ptr);
 
+  void OnBatteryHealthUpdated(cros_healthd::mojom::TelemetryInfoPtr info_ptr);
+
   mojo::Remote<cros_healthd::mojom::CrosHealthdProbeService> probe_service_;
+  mojo::RemoteSet<mojom::BatteryChargeStatusObserver>
+      battery_charge_status_observers_;
+  mojo::RemoteSet<mojom::BatteryHealthObserver> battery_health_observers_;
+
+  std::unique_ptr<base::RepeatingTimer> battery_charge_status_timer_;
+  std::unique_ptr<base::RepeatingTimer> battery_health_timer_;
 };
 
 }  // namespace diagnostics
diff --git a/chromeos/components/diagnostics_ui/backend/system_data_provider_unittest.cc b/chromeos/components/diagnostics_ui/backend/system_data_provider_unittest.cc
index b1b7921..feef02b 100644
--- a/chromeos/components/diagnostics_ui/backend/system_data_provider_unittest.cc
+++ b/chromeos/components/diagnostics_ui/backend/system_data_provider_unittest.cc
@@ -5,16 +5,21 @@
 #include "chromeos/components/diagnostics_ui/backend/system_data_provider.h"
 
 #include <cstdint>
+#include <vector>
 
 #include "base/bind.h"
 #include "base/run_loop.h"
+#include "base/strings/string16.h"
 #include "base/system/sys_info.h"
 #include "base/test/bind_test_util.h"
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
+#include "base/timer/mock_timer.h"
+#include "chromeos/components/diagnostics_ui/backend/power_manager_client_conversions.h"
 #include "chromeos/dbus/cros_healthd/cros_healthd_client.h"
 #include "chromeos/dbus/cros_healthd/fake_cros_healthd_client.h"
 #include "chromeos/dbus/power/fake_power_manager_client.h"
+#include "chromeos/dbus/power_manager/power_supply_properties.pb.h"
 #include "chromeos/services/cros_healthd/public/mojom/cros_healthd.mojom.h"
 #include "chromeos/services/cros_healthd/public/mojom/cros_healthd_probe.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -131,6 +136,47 @@
       /*temperature=*/0);
 }
 
+cros_healthd::mojom::BatteryInfoPtr
+CreateCrosHealthdBatteryChargeStatusResponse(double charge_now,
+                                             double current_now) {
+  return CreateCrosHealthdBatteryInfoResponse(
+      /*cycle_count=*/0,
+      /*voltage_now=*/0,
+      /*vendor=*/"",
+      /*serial_number=*/"",
+      /*charge_full_design=*/0,
+      /*charge_full=*/0,
+      /*voltage_min_design=*/0,
+      /*model_name=*/"",
+      /*charge_now=*/charge_now,
+      /*current_now=*/current_now,
+      /*technology=*/"",
+      /*status=*/"",
+      /*manufacture_date=*/base::nullopt,
+      /*temperature=*/0);
+}
+
+cros_healthd::mojom::BatteryInfoPtr CreateCrosHealthdBatteryHealthResponse(
+    double charge_full_now,
+    double charge_full_design,
+    int32_t cycle_count) {
+  return CreateCrosHealthdBatteryInfoResponse(
+      /*cycle_count=*/cycle_count,
+      /*voltage_now=*/0,
+      /*vendor=*/"",
+      /*serial_number=*/"",
+      /*charge_full_design=*/charge_full_design,
+      /*charge_full=*/charge_full_now,
+      /*voltage_min_design=*/0,
+      /*model_name=*/"",
+      /*charge_now=*/0,
+      /*current_now=*/0,
+      /*technology=*/"",
+      /*status=*/"",
+      /*manufacture_date=*/base::nullopt,
+      /*temperature=*/0);
+}
+
 void SetCrosHealthdBatteryInfoResponse(const std::string& vendor,
                                        double charge_full_design) {
   cros_healthd::mojom::BatteryInfoPtr battery_info =
@@ -140,8 +186,165 @@
                                 /*memory_info=*/nullptr);
 }
 
+void SetCrosHealthdBatteryChargeStatusResponse(double charge_now,
+                                               double current_now) {
+  cros_healthd::mojom::BatteryInfoPtr battery_info =
+      CreateCrosHealthdBatteryChargeStatusResponse(charge_now, current_now);
+  SetProbeTelemetryInfoResponse(std::move(battery_info), /*cpu_info=*/nullptr,
+                                /*memory_info=*/nullptr,
+                                /*memory_info=*/nullptr);
+}
+
+void SetCrosHealthdBatteryHealthResponse(double charge_full_now,
+                                         double charge_full_design,
+                                         int32_t cycle_count) {
+  cros_healthd::mojom::BatteryInfoPtr battery_info =
+      CreateCrosHealthdBatteryHealthResponse(charge_full_now,
+                                             charge_full_design, cycle_count);
+  SetProbeTelemetryInfoResponse(std::move(battery_info), /*cpu_info=*/nullptr,
+                                /*memory_info=*/nullptr,
+                                /*memory_info=*/nullptr);
+}
+
+bool AreValidPowerTimes(int64_t time_to_full, int64_t time_to_empty) {
+  // Exactly one of |time_to_full| or |time_to_empty| must be zero. The other
+  // can be a positive integer to represent the time to charge/discharge or -1
+  // to represent that the time is being calculated.
+  return (time_to_empty == 0 && (time_to_full > 0 || time_to_full == -1)) ||
+         (time_to_full == 0 && (time_to_empty > 0 || time_to_empty == -1));
+}
+
+power_manager::PowerSupplyProperties ConstructPowerSupplyProperties(
+    power_manager::PowerSupplyProperties::ExternalPower power_source,
+    power_manager::PowerSupplyProperties::BatteryState battery_state,
+    bool is_calculating_battery_time,
+    int64_t time_to_full,
+    int64_t time_to_empty) {
+  power_manager::PowerSupplyProperties props;
+  props.set_external_power(power_source);
+  props.set_battery_state(battery_state);
+
+  if (battery_state ==
+      power_manager::PowerSupplyProperties_BatteryState_NOT_PRESENT) {
+    // Leave |time_to_full| and |time_to_empty| unset.
+    return props;
+  }
+
+  DCHECK(AreValidPowerTimes(time_to_full, time_to_empty));
+
+  props.set_is_calculating_battery_time(is_calculating_battery_time);
+  props.set_battery_time_to_full_sec(time_to_full);
+  props.set_battery_time_to_empty_sec(time_to_empty);
+
+  return props;
+}
+
+// Sets the PowerSupplyProperties on FakePowerManagerClient. Calling this
+// method immediately notifies PowerManagerClient observers. One of
+// |time_to_full| or |time_to_empty| must be either -1 or a positive number.
+// The other must be 0. If |battery_state| is NOT_PRESENT, both |time_to_full|
+// and |time_to_empty| will be left unset.
+void SetPowerManagerProperties(
+    power_manager::PowerSupplyProperties::ExternalPower power_source,
+    power_manager::PowerSupplyProperties::BatteryState battery_state,
+    bool is_calculating_battery_time,
+    int64_t time_to_full,
+    int64_t time_to_empty) {
+  power_manager::PowerSupplyProperties props = ConstructPowerSupplyProperties(
+      power_source, battery_state, is_calculating_battery_time, time_to_full,
+      time_to_empty);
+  FakePowerManagerClient::Get()->UpdatePowerProperties(props);
+}
+
+void VerifyChargeStatusResult(
+    const mojom::BatteryChargeStatusPtr& update,
+    double charge_now,
+    double current_now,
+    power_manager::PowerSupplyProperties::ExternalPower power_source,
+    power_manager::PowerSupplyProperties::BatteryState battery_state,
+    bool is_calculating_battery_time,
+    int64_t time_to_full,
+    int64_t time_to_empty) {
+  const uint32_t expected_charge_now_milliamp_hours = charge_now * 1000;
+  const int32_t expected_current_now_milliamps = current_now * 1000;
+  mojom::ExternalPowerSource expected_power_source =
+      ConvertPowerSourceFromProto(power_source);
+  mojom::BatteryState expected_battery_state =
+      ConvertBatteryStateFromProto(battery_state);
+
+  EXPECT_EQ(expected_charge_now_milliamp_hours,
+            update->charge_now_milliamp_hours);
+  EXPECT_EQ(expected_current_now_milliamps, update->current_now_milliamps);
+  EXPECT_EQ(expected_power_source, update->power_adapter_status);
+  EXPECT_EQ(expected_battery_state, update->battery_state);
+
+  if (expected_battery_state == mojom::BatteryState::kFull) {
+    EXPECT_EQ(base::string16(), update->power_time);
+    return;
+  }
+
+  DCHECK(AreValidPowerTimes(time_to_full, time_to_empty));
+
+  const power_manager::PowerSupplyProperties props =
+      ConstructPowerSupplyProperties(power_source, battery_state,
+                                     is_calculating_battery_time, time_to_full,
+                                     time_to_empty);
+  base::string16 expected_power_time =
+      ConstructPowerTime(expected_battery_state, props);
+
+  EXPECT_EQ(expected_power_time, update->power_time);
+}
+
+void VerifyHealthResult(const mojom::BatteryHealthPtr& update,
+                        double charge_full_now,
+                        double charge_full_design,
+                        int32_t expected_cycle_count) {
+  const int32_t expected_charge_full_now_milliamp_hours =
+      charge_full_now * 1000;
+  const int32_t expected_charge_full_design_milliamp_hours =
+      charge_full_design * 1000;
+  const int8_t expected_battery_wear_percentage =
+      expected_charge_full_now_milliamp_hours /
+      expected_charge_full_design_milliamp_hours;
+
+  EXPECT_EQ(expected_charge_full_now_milliamp_hours,
+            update->charge_full_now_milliamp_hours);
+  EXPECT_EQ(expected_charge_full_design_milliamp_hours,
+            update->charge_full_design_milliamp_hours);
+  EXPECT_EQ(expected_cycle_count, update->cycle_count);
+  EXPECT_EQ(expected_battery_wear_percentage, update->battery_wear_percentage);
+}
+
 }  // namespace
 
+struct FakeBatteryChargeStatusObserver
+    : public mojom::BatteryChargeStatusObserver {
+  // mojom::BatteryChargeStatusObserver
+  void OnBatteryChargeStatusUpdated(
+      mojom::BatteryChargeStatusPtr status_ptr) override {
+    updates.emplace_back(std::move(status_ptr));
+  }
+
+  // Tracks calls to OnBatteryChargeStatusUpdated. Each call adds an element to
+  // the vector.
+  std::vector<mojom::BatteryChargeStatusPtr> updates;
+
+  mojo::Receiver<mojom::BatteryChargeStatusObserver> receiver{this};
+};
+
+struct FakeBatteryHealthObserver : public mojom::BatteryHealthObserver {
+  // mojom::BatteryHealthObserver
+  void OnBatteryHealthUpdated(mojom::BatteryHealthPtr status_ptr) override {
+    updates.emplace_back(std::move(status_ptr));
+  }
+
+  // Tracks calls to OnBatteryHealthUpdated. Each call adds an element to
+  // the vector.
+  std::vector<mojom::BatteryHealthPtr> updates;
+
+  mojo::Receiver<mojom::BatteryHealthObserver> receiver{this};
+};
+
 class SystemDataProviderTest : public testing::Test {
  public:
   SystemDataProviderTest() {
@@ -247,5 +450,114 @@
   run_loop.Run();
 }
 
+TEST_F(SystemDataProviderTest, BatteryChargeStatusObserver) {
+  // Setup Timer
+  auto timer = std::make_unique<base::MockRepeatingTimer>();
+  auto* timer_ptr = timer.get();
+  system_data_provider_->SetBatteryChargeStatusTimerForTesting(
+      std::move(timer));
+
+  // Setup initial data
+  const double charge_now_amp_hours = 20;
+  const double current_now_amps = 2;
+  const auto power_source =
+      power_manager::PowerSupplyProperties_ExternalPower_AC;
+  const auto battery_state =
+      power_manager::PowerSupplyProperties_BatteryState_CHARGING;
+  const bool is_calculating_battery_time = false;
+  const int64_t time_to_full_secs = 1000;
+  const int64_t time_to_empty_secs = 0;
+
+  SetCrosHealthdBatteryChargeStatusResponse(charge_now_amp_hours,
+                                            current_now_amps);
+  SetPowerManagerProperties(power_source, battery_state,
+                            is_calculating_battery_time, time_to_full_secs,
+                            time_to_empty_secs);
+
+  // Registering as an observer should trigger one update.
+  FakeBatteryChargeStatusObserver charge_status_observer;
+  system_data_provider_->ObserveBatteryChargeStatus(
+      charge_status_observer.receiver.BindNewPipeAndPassRemote());
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(1u, charge_status_observer.updates.size());
+  VerifyChargeStatusResult(charge_status_observer.updates[0],
+                           charge_now_amp_hours, current_now_amps, power_source,
+                           battery_state, is_calculating_battery_time,
+                           time_to_full_secs, time_to_empty_secs);
+
+  // Firing the timer should trigger another.
+  timer_ptr->Fire();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(2u, charge_status_observer.updates.size());
+  VerifyChargeStatusResult(charge_status_observer.updates[0],
+                           charge_now_amp_hours, current_now_amps, power_source,
+                           battery_state, is_calculating_battery_time,
+                           time_to_full_secs, time_to_empty_secs);
+
+  // Updating the PowerManagerClient Properties should trigger yet another.
+  const int64_t new_time_to_full_secs = time_to_full_secs - 10;
+  SetPowerManagerProperties(
+      power_manager::PowerSupplyProperties_ExternalPower_AC,
+      power_manager::PowerSupplyProperties_BatteryState_CHARGING,
+      is_calculating_battery_time, new_time_to_full_secs, time_to_empty_secs);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(3u, charge_status_observer.updates.size());
+  VerifyChargeStatusResult(charge_status_observer.updates[0],
+                           charge_now_amp_hours, current_now_amps, power_source,
+                           battery_state, is_calculating_battery_time,
+                           new_time_to_full_secs, time_to_empty_secs);
+}
+
+TEST_F(SystemDataProviderTest, BatteryHealthObserver) {
+  // Setup Timer
+  auto timer = std::make_unique<base::MockRepeatingTimer>();
+  auto* timer_ptr = timer.get();
+  system_data_provider_->SetBatteryHealthTimerForTesting(std::move(timer));
+
+  // Setup initial data
+  const double charge_full_now = 20;
+  const double charge_full_design = 26;
+  const int32_t cycle_count = 500;
+
+  SetCrosHealthdBatteryHealthResponse(charge_full_now, charge_full_design,
+                                      cycle_count);
+
+  // Registering as an observer should trigger one update.
+  FakeBatteryHealthObserver health_observer;
+  system_data_provider_->ObserveBatteryHealth(
+      health_observer.receiver.BindNewPipeAndPassRemote());
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(1u, health_observer.updates.size());
+  VerifyHealthResult(health_observer.updates[0], charge_full_now,
+                     charge_full_design, cycle_count);
+
+  // Firing the timer should trigger another.
+  timer_ptr->Fire();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(2u, health_observer.updates.size());
+  VerifyHealthResult(health_observer.updates[1], charge_full_now,
+                     charge_full_design, cycle_count);
+
+  // Updating the information in Croshealthd does not trigger an update until
+  // the timer fires
+  const int32_t new_cycle_count = cycle_count + 1;
+  SetCrosHealthdBatteryHealthResponse(charge_full_now, charge_full_design,
+                                      new_cycle_count);
+
+  EXPECT_EQ(2u, health_observer.updates.size());
+
+  timer_ptr->Fire();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(3u, health_observer.updates.size());
+  VerifyHealthResult(health_observer.updates[2], charge_full_now,
+                     charge_full_design, new_cycle_count);
+}
+
 }  // namespace diagnostics
 }  // namespace chromeos
diff --git a/chromeos/components/diagnostics_ui/diagnostics_ui.cc b/chromeos/components/diagnostics_ui/diagnostics_ui.cc
index e8a7c9bf..a5d5e980 100644
--- a/chromeos/components/diagnostics_ui/diagnostics_ui.cc
+++ b/chromeos/components/diagnostics_ui/diagnostics_ui.cc
@@ -11,10 +11,12 @@
 #include "chromeos/components/diagnostics_ui/url_constants.h"
 #include "chromeos/grit/chromeos_diagnostics_app_resources.h"
 #include "chromeos/grit/chromeos_diagnostics_app_resources_map.h"
+#include "chromeos/strings/grit/chromeos_strings.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_ui.h"
 #include "content/public/browser/web_ui_data_source.h"
 #include "services/network/public/mojom/content_security_policy.mojom.h"
+#include "ui/base/webui/web_ui_util.h"
 #include "ui/resources/grit/webui_resources.h"
 
 namespace chromeos {
@@ -24,6 +26,18 @@
 constexpr char kGeneratedPath[] =
     "@out_folder@/gen/chromeos/components/diagnostics_ui/resources/";
 
+void AddDiagnosticsStrings(content::WebUIDataSource* html_source) {
+  static constexpr webui::LocalizedString kLocalizedStrings[] = {
+      {"batteryTitle", IDS_DIAGNOSTICS_BATTERY_TITLE},
+      {"cpuTitle", IDS_DIAGNOSTICS_CPU_TITLE},
+      {"diagnosticsTitle", IDS_DIAGNOSTICS_TITLE},
+      {"memoryTitle", IDS_DIAGNOSTICS_MEMORY_TITLE},
+  };
+  for (const auto& str : kLocalizedStrings) {
+    html_source->AddLocalizedString(str.name, str.id);
+  }
+  html_source->UseStringsJs();
+}
 // TODO(jimmyxgong): Replace with webui::SetUpWebUIDataSource() once it no
 // longer requires a dependency on //chrome/browser.
 void SetUpWebUIDataSource(content::WebUIDataSource* source,
@@ -58,7 +72,7 @@
                                          kChromeosDiagnosticsAppResourcesSize);
   SetUpWebUIDataSource(html_source.get(), resources, kGeneratedPath,
                        IDR_DIAGNOSTICS_APP_INDEX_HTML);
-
+  AddDiagnosticsStrings(html_source.get());
   content::WebUIDataSource::Add(web_ui->GetWebContents()->GetBrowserContext(),
                                 html_source.release());
 }
diff --git a/chromeos/components/diagnostics_ui/mojom/system_data_provider.mojom b/chromeos/components/diagnostics_ui/mojom/system_data_provider.mojom
index 1fa0693..1bba4fe5 100644
--- a/chromeos/components/diagnostics_ui/mojom/system_data_provider.mojom
+++ b/chromeos/components/diagnostics_ui/mojom/system_data_provider.mojom
@@ -55,6 +55,33 @@
   ExternalPowerSource power_adapter_status;
 };
 
+// Contains information about the health of the battery.
+struct BatteryHealth {
+  int32 charge_full_now_milliamp_hours;
+  int32 charge_full_design_milliamp_hours;
+  int32 cycle_count;
+  int8 battery_wear_percentage;
+};
+
+// Implemented by clients that wish to be updated periodically about changes to
+// the battery charge status.
+interface BatteryChargeStatusObserver {
+  // OnBatteryChargeStatus calls can be triggered by any of 3 conditions:
+  // 1) A BatteryChargeStatusObserver is registered with SystemDataProvider
+  // 2) A PowerManagerClient 'PowerChanged' event is received
+  // 3) A periodic update is sent by SystemDataProvider
+  OnBatteryChargeStatusUpdated(BatteryChargeStatus battery_charge_status);
+};
+
+// Implemented by clients that wish to be updated periodically about the health
+// of the device battery.
+interface BatteryHealthObserver {
+  // OnBatteryHealthUpdated calls can be triggered by either of 2 conditions:
+  // 1) A BatteryHealthObserver is registered with SystemDataProvider
+  // 2) A periodic update is sent by SystemDataProvider
+  OnBatteryHealthUpdated(BatteryHealth battery_health);
+};
+
 // Provides telemetric information about the system. This API is exposed to the
 // Diagnostics SWA.
 interface SystemDataProvider {
@@ -66,4 +93,11 @@
   // information that does not change during the lifetime fo the service.
   // If the device does not have a battery, returns an empty BatteryInfo.
   GetBatteryInfo() => (BatteryInfo battery_info);
+
+  // Registers an observer of BatteryChargeStatus information.
+  ObserveBatteryChargeStatus(
+      pending_remote<BatteryChargeStatusObserver> observer);
+
+  // Registers an observer of BatteryHealth inforamation.
+  ObserveBatteryHealth(pending_remote<BatteryHealthObserver> observer);
 };
diff --git a/chromeos/components/diagnostics_ui/resources/BUILD.gn b/chromeos/components/diagnostics_ui/resources/BUILD.gn
index a9f29d4..c40eaa7 100644
--- a/chromeos/components/diagnostics_ui/resources/BUILD.gn
+++ b/chromeos/components/diagnostics_ui/resources/BUILD.gn
@@ -32,6 +32,7 @@
   deps = [
     ":data_point",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:i18n_behavior.m",
   ]
 }
 
@@ -39,6 +40,7 @@
   deps = [
     ":data_point",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:i18n_behavior.m",
   ]
 }
 
@@ -57,6 +59,7 @@
     ":overview_card",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_elements/cr_button:cr_button.m",
+    "//ui/webui/resources/js:i18n_behavior.m",
   ]
 }
 
@@ -89,6 +92,7 @@
   deps = [
     ":data_point",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:i18n_behavior.m",
   ]
 }
 
diff --git a/chromeos/components/diagnostics_ui/resources/battery_status_card.html b/chromeos/components/diagnostics_ui/resources/battery_status_card.html
index 1d8c0d5..ea91361 100644
--- a/chromeos/components/diagnostics_ui/resources/battery_status_card.html
+++ b/chromeos/components/diagnostics_ui/resources/battery_status_card.html
@@ -3,8 +3,7 @@
 
 <diagnostics-card>
   <div slot="title">
-    <!-- TODO(zentaro) Localize once strings are confirmed -->
-    <div id="cardTitle">Battery Status</div>
+    <div id="cardTitle">[[i18n('batteryTitle')]]</div>
   </div>
   <data-point slot="body" id="currentNow"
     value="[[batteryChargeStatus_.current_now_milliamps]]">
diff --git a/chromeos/components/diagnostics_ui/resources/battery_status_card.js b/chromeos/components/diagnostics_ui/resources/battery_status_card.js
index e59ec3f1..a0adeb6f 100644
--- a/chromeos/components/diagnostics_ui/resources/battery_status_card.js
+++ b/chromeos/components/diagnostics_ui/resources/battery_status_card.js
@@ -10,6 +10,8 @@
 import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {BatteryChargeStatus, BatteryHealth, BatteryInfo, SystemDataProviderInterface} from './diagnostics_types.js'
 import {getSystemDataProvider} from './mojo_interface_provider.js';
+import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
+import './strings.js';
 
 /**
  * @fileoverview
@@ -20,6 +22,8 @@
 
   _template: html`{__html_template__}`,
 
+  behaviors: [I18nBehavior],
+
   /**
    * @private {?SystemDataProviderInterface}
    */
diff --git a/chromeos/components/diagnostics_ui/resources/cpu_card.html b/chromeos/components/diagnostics_ui/resources/cpu_card.html
index 73fef05..8e07f55 100644
--- a/chromeos/components/diagnostics_ui/resources/cpu_card.html
+++ b/chromeos/components/diagnostics_ui/resources/cpu_card.html
@@ -3,8 +3,7 @@
 
 <diagnostics-card>
   <div slot="title">
-    <!-- TODO(zentaro) Localize once strings are confirmed -->
-    <div id="cardTitle">CPU</div>
+    <div id="cardTitle">[[i18n('cpuTitle')]]</div>
   </div>
   <data-point slot="body" id="cpuTemp"
     value="[[cpuUsage_.cpu_temp_degrees_celcius]]">
diff --git a/chromeos/components/diagnostics_ui/resources/cpu_card.js b/chromeos/components/diagnostics_ui/resources/cpu_card.js
index 810eef7..51e3957 100644
--- a/chromeos/components/diagnostics_ui/resources/cpu_card.js
+++ b/chromeos/components/diagnostics_ui/resources/cpu_card.js
@@ -9,6 +9,8 @@
 import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {CpuUsage, SystemDataProviderInterface} from './diagnostics_types.js'
 import {getSystemDataProvider} from './mojo_interface_provider.js';
+import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
+import './strings.js';
 
 /**
  * @fileoverview
@@ -19,6 +21,8 @@
 
   _template: html`{__html_template__}`,
 
+  behaviors: [I18nBehavior],
+
   /**
    * @private {?SystemDataProviderInterface}
    */
diff --git a/chromeos/components/diagnostics_ui/resources/diagnostics_app.html b/chromeos/components/diagnostics_ui/resources/diagnostics_app.html
index 97cf92a..a99387534 100644
--- a/chromeos/components/diagnostics_ui/resources/diagnostics_app.html
+++ b/chromeos/components/diagnostics_ui/resources/diagnostics_app.html
@@ -30,9 +30,8 @@
     padding: 5px;
   }
 </style>
-<div id="diagnosticsContainer" class="cr-centered-card-container">
-  <!-- TODO(zentaro) Localize once strings are confirmed -->
-  <div id="header">Diagnostics</div>
+<div id="diagnosticsContainer"class="cr-centered-card-container">
+  <div id="header">[[i18n('diagnosticsTitle')]]</div>
   <div class="overview-container">
     <overview-card id="overviewCard"></overview-card>
   </div>
diff --git a/chromeos/components/diagnostics_ui/resources/diagnostics_app.js b/chromeos/components/diagnostics_ui/resources/diagnostics_app.js
index e28d74ad..9f2cb9e 100644
--- a/chromeos/components/diagnostics_ui/resources/diagnostics_app.js
+++ b/chromeos/components/diagnostics_ui/resources/diagnostics_app.js
@@ -11,6 +11,8 @@
 import './overview_card.js';
 
 import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
+import './strings.js';
 
 /**
  * @fileoverview
@@ -22,6 +24,8 @@
 
   _template: html`{__html_template__}`,
 
+  behaviors: [I18nBehavior],
+
   /** @override */
   ready() {
   },
diff --git a/chromeos/components/diagnostics_ui/resources/diagnostics_fonts_css.html b/chromeos/components/diagnostics_ui/resources/diagnostics_fonts_css.html
index 0558090..df0933d 100644
--- a/chromeos/components/diagnostics_ui/resources/diagnostics_fonts_css.html
+++ b/chromeos/components/diagnostics_ui/resources/diagnostics_fonts_css.html
@@ -8,11 +8,13 @@
       --diagnostics-header-font-size: 22px;
       --diagnostics-data-point-title-font-size: 12px;
       --diagnostics-data-point-subtitle-font-size: 15px;
+      --diagnostics-overview-font-size: 13px;
 
       --diagnostics-default-font-weight: 500;
       --diagnostics-header-font-weight: 600;
       --diagnostics-data-point-title-font-weight: 500;
       --diagnostics-data-point-subtitle-font-weight: 550;
+      --diagnostics-overview-font-weight: 565;
 
       --diagnostics-default-text-color: var(--google-grey-900);
       --diagnostics-header-text-color: var(--google-grey-900);
@@ -38,6 +40,11 @@
           font-size: var(--diagnostics-data-point-subtitle-font-size);
           font-weight: var(--diagnostics-data-point-subtitle-font-weight);
       };
+      --diagnostics-overview-font: {
+          font-family: var(--diagnostics-default-font-family);
+          font-size: var(--diagnostics-overview-font-size);
+          font-weight: var(--diagnostics-overview-font-weight);
+      };
     }
   </style>
 </template>
\ No newline at end of file
diff --git a/chromeos/components/diagnostics_ui/resources/fake_system_data_provider.js b/chromeos/components/diagnostics_ui/resources/fake_system_data_provider.js
index 8e56ff6..397f7867 100644
--- a/chromeos/components/diagnostics_ui/resources/fake_system_data_provider.js
+++ b/chromeos/components/diagnostics_ui/resources/fake_system_data_provider.js
@@ -60,18 +60,12 @@
    * @return {!Promise}
    */
   observeBatteryChargeStatus(remote) {
-    return new Promise((resolve) => {
-      this.observables_.observe(
-          'BatteryChargeStatusObserver_onBatteryChargeStatusUpdated',
-          (batteryChargeStatus) => {
-            remote.onBatteryChargeStatusUpdated(
-                /** @type {!BatteryChargeStatus} */ (batteryChargeStatus));
-          });
-
-      this.observables_.trigger(
-          'BatteryChargeStatusObserver_onBatteryChargeStatusUpdated');
-      resolve();
-    });
+    return this.observe_(
+        'BatteryChargeStatusObserver_onBatteryChargeStatusUpdated',
+        (batteryChargeStatus) => {
+          remote.onBatteryChargeStatusUpdated(
+              /** @type {!BatteryChargeStatus} */ (batteryChargeStatus));
+        });
   }
 
   /**
@@ -90,16 +84,11 @@
    * @return {!Promise}
    */
   observeBatteryHealth(remote) {
-    return new Promise((resolve) => {
-      this.observables_.observe(
-          'BatteryHealthObserver_onBatteryHealthUpdated', (batteryHealth) => {
-            remote.onBatteryHealthUpdated(
-                /** @type {!BatteryHealth} */ (batteryHealth));
-          });
-
-      this.observables_.trigger('BatteryHealthObserver_onBatteryHealthUpdated');
-      resolve();
-    });
+    return this.observe_(
+        'BatteryHealthObserver_onBatteryHealthUpdated', (batteryHealth) => {
+          remote.onBatteryHealthUpdated(
+              /** @type {!BatteryHealth} */ (batteryHealth));
+        });
   }
 
   /**
@@ -117,15 +106,9 @@
    * @return {!Promise}
    */
   observeCpuUsage(remote) {
-    return new Promise((resolve) => {
-      this.observables_.observe(
-          'CpuUsageObserver_onCpuUsageUpdated', (cpuUsage) => {
-            remote.onCpuUsageUpdated(
-                /** @type {!CpuUsage} */ (cpuUsage));
-          });
-
-      this.observables_.trigger('CpuUsageObserver_onCpuUsageUpdated');
-      resolve();
+    return this.observe_('CpuUsageObserver_onCpuUsageUpdated', (cpuUsage) => {
+      remote.onCpuUsageUpdated(
+          /** @type {!CpuUsage} */ (cpuUsage));
     });
   }
 
@@ -144,16 +127,11 @@
    * @return {!Promise}
    */
   observeMemoryUsage(remote) {
-    return new Promise((resolve) => {
-      this.observables_.observe(
-          'MemoryUsageObserver_onMemoryUsageUpdated', (memoryUsage) => {
-            remote.onMemoryUsageUpdated(
-                /** @type {!MemoryUsage} */ (memoryUsage));
-          });
-
-      this.observables_.trigger('MemoryUsageObserver_onMemoryUsageUpdated');
-      resolve();
-    });
+    return this.observe_(
+        'MemoryUsageObserver_onMemoryUsageUpdated', (memoryUsage) => {
+          remote.onMemoryUsageUpdated(
+              /** @type {!MemoryUsage} */ (memoryUsage));
+        });
   }
 
   /**
@@ -217,4 +195,20 @@
     this.registerMethods();
     this.registerObservables();
   }
+
+  /*
+   * Sets up an observer for methodName.
+   * @template T
+   * @param {string} methodName
+   * @param {!function(!T)} callback
+   * @return {!Promise}
+   * @private
+   */
+  observe_(methodName, callback) {
+    return new Promise((resolve) => {
+      this.observables_.observe(methodName, callback);
+      this.observables_.trigger(methodName);
+      resolve();
+    });
+  }
 }
diff --git a/chromeos/components/diagnostics_ui/resources/memory_card.html b/chromeos/components/diagnostics_ui/resources/memory_card.html
index 0c07c800..bf44245 100644
--- a/chromeos/components/diagnostics_ui/resources/memory_card.html
+++ b/chromeos/components/diagnostics_ui/resources/memory_card.html
@@ -3,8 +3,7 @@
 
 <diagnostics-card>
   <div slot="title">
-    <!-- TODO(zentaro) Localize once strings are confirmed -->
-    <div id="cardTitle">Memory</div>
+    <div id="cardTitle">[[i18n('memoryTitle')]]</div>
   </div>
   <data-point slot="body" id="memoryTotal"
     value="[[memoryUsage_.total_memory_kib]]">
diff --git a/chromeos/components/diagnostics_ui/resources/memory_card.js b/chromeos/components/diagnostics_ui/resources/memory_card.js
index a5b3bbd..65fc777 100644
--- a/chromeos/components/diagnostics_ui/resources/memory_card.js
+++ b/chromeos/components/diagnostics_ui/resources/memory_card.js
@@ -10,6 +10,8 @@
 import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {MemoryUsage, SystemDataProviderInterface} from './diagnostics_types.js'
 import {getSystemDataProvider} from './mojo_interface_provider.js';
+import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
+import './strings.js';
 
 /**
  * @fileoverview
@@ -20,6 +22,8 @@
 
   _template: html`{__html_template__}`,
 
+  behaviors: [I18nBehavior],
+
   /**
    * @private {?SystemDataProviderInterface}
    */
diff --git a/chromeos/components/diagnostics_ui/resources/overview_card.html b/chromeos/components/diagnostics_ui/resources/overview_card.html
index 46b3fc7..a280b598 100644
--- a/chromeos/components/diagnostics_ui/resources/overview_card.html
+++ b/chromeos/components/diagnostics_ui/resources/overview_card.html
@@ -1,11 +1,15 @@
 <style include="diagnostics-shared diagnostics-fonts">
   #overviewCardContainer {
-    color: var(--diagnostics-overview-text-color);
     display: flex;
   }
 
   #overviewCardContainer > div {
+    @apply --diagnostics-overview-font;
+    background-color: var(--google-grey-100);
+    border-radius: 16px;
+    color: var(--diagnostics-overview-text-color);
     margin-right: 15px;
+    padding: 10px;
   }
 
   #version {
diff --git a/chromeos/components/telemetry_extension_ui/BUILD.gn b/chromeos/components/telemetry_extension_ui/BUILD.gn
index 53c94cc..9b26849 100644
--- a/chromeos/components/telemetry_extension_ui/BUILD.gn
+++ b/chromeos/components/telemetry_extension_ui/BUILD.gn
@@ -13,10 +13,14 @@
     "diagnostics_service.h",
     "diagnostics_service_converters.cc",
     "diagnostics_service_converters.h",
+    "lid_observer.cc",
+    "lid_observer.h",
     "probe_service.cc",
     "probe_service.h",
     "probe_service_converters.cc",
     "probe_service_converters.h",
+    "system_events_service.cc",
+    "system_events_service.h",
     "telemetry_extension_ui.cc",
     "telemetry_extension_ui.h",
     "telemetry_extension_untrusted_source.cc",
@@ -46,6 +50,7 @@
     "diagnostics_service_converters_unittest.cc",
     "probe_service_converters_unittest.cc",
     "probe_service_unittest.cc",
+    "system_events_service_unittest.cc",
   ]
   deps = [
     ":telemetry_extension_ui",
diff --git a/chromeos/components/telemetry_extension_ui/lid_observer.cc b/chromeos/components/telemetry_extension_ui/lid_observer.cc
new file mode 100644
index 0000000..f5e40d9
--- /dev/null
+++ b/chromeos/components/telemetry_extension_ui/lid_observer.cc
@@ -0,0 +1,54 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/components/telemetry_extension_ui/lid_observer.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "chromeos/components/telemetry_extension_ui/mojom/system_events_service.mojom.h"
+#include "chromeos/services/cros_healthd/public/cpp/service_connection.h"
+
+namespace chromeos {
+
+LidObserver::LidObserver() : receiver_{this} {
+  Connect();
+}
+
+LidObserver::~LidObserver() = default;
+
+void LidObserver::AddObserver(
+    mojo::PendingRemote<health::mojom::LidObserver> observer) {
+  health::mojom::LidObserverPtr ptr{std::move(observer)};
+  observers_.Add(ptr.PassInterface());
+}
+
+void LidObserver::OnLidClosed() {
+  for (auto& observer : observers_) {
+    observer->OnLidClosed();
+  }
+}
+
+void LidObserver::OnLidOpened() {
+  for (auto& observer : observers_) {
+    observer->OnLidOpened();
+  }
+}
+
+void LidObserver::Connect() {
+  receiver_.reset();
+  cros_healthd::ServiceConnection::GetInstance()->AddLidObserver(
+      receiver_.BindNewPipeAndPassRemote());
+
+  // We try to reconnect right after disconnect because Mojo will queue the
+  // request and connect to cros_healthd when it becomes available.
+  receiver_.set_disconnect_handler(
+      base::BindOnce(&LidObserver::Connect, base::Unretained(this)));
+}
+
+void LidObserver::FlushForTesting() {
+  receiver_.FlushForTesting();
+}
+
+}  // namespace chromeos
diff --git a/chromeos/components/telemetry_extension_ui/lid_observer.h b/chromeos/components/telemetry_extension_ui/lid_observer.h
new file mode 100644
index 0000000..c65f914
--- /dev/null
+++ b/chromeos/components/telemetry_extension_ui/lid_observer.h
@@ -0,0 +1,45 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_COMPONENTS_TELEMETRY_EXTENSION_UI_LID_OBSERVER_H_
+#define CHROMEOS_COMPONENTS_TELEMETRY_EXTENSION_UI_LID_OBSERVER_H_
+
+#if defined(OFFICIAL_BUILD)
+#error Lid observer should only be included in unofficial builds.
+#endif
+
+#include "chromeos/components/telemetry_extension_ui/mojom/system_events_service.mojom-forward.h"
+#include "chromeos/services/cros_healthd/public/mojom/cros_healthd_events.mojom.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote_set.h"
+
+namespace chromeos {
+
+class LidObserver : public cros_healthd::mojom::CrosHealthdLidObserver {
+ public:
+  LidObserver();
+  LidObserver(const LidObserver&) = delete;
+  LidObserver& operator=(const LidObserver&) = delete;
+  ~LidObserver() override;
+
+  void AddObserver(mojo::PendingRemote<health::mojom::LidObserver> observer);
+
+  void OnLidClosed() override;
+  void OnLidOpened() override;
+
+  // Waits until disconnect handler will be triggered if fake cros_healthd was
+  // shutdown.
+  void FlushForTesting();
+
+ private:
+  void Connect();
+
+  mojo::Receiver<cros_healthd::mojom::CrosHealthdLidObserver> receiver_;
+  mojo::RemoteSet<health::mojom::LidObserver> observers_;
+};
+
+}  // namespace chromeos
+
+#endif  // CHROMEOS_COMPONENTS_TELEMETRY_EXTENSION_UI_LID_OBSERVER_H_
diff --git a/chromeos/components/telemetry_extension_ui/mojom/BUILD.gn b/chromeos/components/telemetry_extension_ui/mojom/BUILD.gn
index 882cb5b..a6fe4ca3 100644
--- a/chromeos/components/telemetry_extension_ui/mojom/BUILD.gn
+++ b/chromeos/components/telemetry_extension_ui/mojom/BUILD.gn
@@ -8,5 +8,6 @@
   sources = [
     "diagnostics_service.mojom",
     "probe_service.mojom",
+    "system_events_service.mojom",
   ]
 }
diff --git a/chromeos/components/telemetry_extension_ui/mojom/system_events_service.mojom b/chromeos/components/telemetry_extension_ui/mojom/system_events_service.mojom
new file mode 100644
index 0000000..fae377c6
--- /dev/null
+++ b/chromeos/components/telemetry_extension_ui/mojom/system_events_service.mojom
@@ -0,0 +1,33 @@
+// 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.
+//
+// Definitions for the System Events API exposed by the telemetry
+// system web extension.
+// This API is consumed by chrome://.
+//
+// This Mojo interface will go through security review before shipping.
+//
+// This is a subset of the cros_healthd event service interface which is
+// src/chrome/services/cros_healthd/public/mojom/cros_healthd.mojom and
+// src/chrome/services/cros_healthd/public/mojom/cros_healthd_events.mojom.
+
+module chromeos.health.mojom;
+
+// System Events interface exposed by the Telemetry SWX.
+interface SystemEventsService {
+  // Adds an observer to be notified on lid events. The caller can remove the
+  // observer created by this call by closing their end of the message pipe.
+  //
+  // The request:
+  // * |observer| - lid observer to be added to system events service.
+  AddLidObserver(pending_remote<LidObserver> observer);
+};
+
+// Implemented by clients who desire lid notifications.
+interface LidObserver {
+  // Fired when the device's lid is closed.
+  OnLidClosed();
+  // Fired when the device's lid is opened.
+  OnLidOpened();
+};
diff --git a/chromeos/components/telemetry_extension_ui/resources/index.html b/chromeos/components/telemetry_extension_ui/resources/index.html
index 8da03fa2..66c891f 100644
--- a/chromeos/components/telemetry_extension_ui/resources/index.html
+++ b/chromeos/components/telemetry_extension_ui/resources/index.html
@@ -11,4 +11,5 @@
 
 <script src="diagnostics_service.mojom-lite.js"></script>
 <script src="probe_service.mojom-lite.js"></script>
+<script src="system_events_service.mojom-lite.js"></script>
 <script src="trusted_scripts.js"></script>
diff --git a/chromeos/components/telemetry_extension_ui/resources/message_types.js b/chromeos/components/telemetry_extension_ui/resources/message_types.js
index 153b19a..b196901 100644
--- a/chromeos/components/telemetry_extension_ui/resources/message_types.js
+++ b/chromeos/components/telemetry_extension_ui/resources/message_types.js
@@ -43,6 +43,7 @@
   DIAGNOSTICS_RUN_BATTERY_CHARGE_ROUTINE:
       'DiagnosticsService.RunBatteryChargeRoutine',
   PROBE_TELEMETRY_INFO: 'ProbeService.ProbeTelemetryInfo',
+  SYSTEM_EVENTS_SERVICE_EVENTS: 'SystemEventsService.Events',
 };
 
 /**
@@ -190,6 +191,12 @@
 dpsl_internal.DiagnosticsRunRoutineResponse;
 
 /**
+ * Event response from System Events Service.
+ * @typedef {{ type: !string }}
+ */
+dpsl_internal.Event;
+
+/**
  * Request message sent by the unprivileged context to request the privileged
  * context to probe telemetry information
  * @typedef { !Array<!string> }
diff --git a/chromeos/components/telemetry_extension_ui/resources/telemetry_extension_resources.grd b/chromeos/components/telemetry_extension_ui/resources/telemetry_extension_resources.grd
index d3abe60..1b76e8a3 100644
--- a/chromeos/components/telemetry_extension_ui/resources/telemetry_extension_resources.grd
+++ b/chromeos/components/telemetry_extension_ui/resources/telemetry_extension_resources.grd
@@ -19,6 +19,7 @@
         <include name="IDR_TELEMETRY_EXTENSION_TRUSTED_SCRIPTS_JS" file="trusted_scripts.js" flattenhtml="true" type="BINDATA" compress="gzip" />
         <include name="IDR_TELEMETRY_EXTENSION_DIAGNOSTICS_SERVICE_MOJO_LITE_JS" file="${root_gen_dir}/chromeos/components/telemetry_extension_ui/mojom/diagnostics_service.mojom-lite.js" compress="gzip" use_base_dir="false" type="BINDATA" />
         <include name="IDR_TELEMETRY_EXTENSION_PROBE_SERVICE_MOJO_LITE_JS" file="${root_gen_dir}/chromeos/components/telemetry_extension_ui/mojom/probe_service.mojom-lite.js" compress="gzip" use_base_dir="false" type="BINDATA" />
+        <include name="IDR_TELEMETRY_EXTENSION_SYSTEM_EVENTS_SERVICE_MOJO_LITE_JS" file="${root_gen_dir}/chromeos/components/telemetry_extension_ui/mojom/system_events_service.mojom-lite.js" compress="gzip" use_base_dir="false" type="BINDATA" />
 
         <!-- Untrusted app host contents. -->
         <include name="IDR_TELEMETRY_EXTENSION_DPSL_JS" file="dpsl.js" flattenhtml="true" type="BINDATA" compress="gzip" />
diff --git a/chromeos/components/telemetry_extension_ui/resources/trusted.js b/chromeos/components/telemetry_extension_ui/resources/trusted.js
index 3b02fed..898290c 100644
--- a/chromeos/components/telemetry_extension_ui/resources/trusted.js
+++ b/chromeos/components/telemetry_extension_ui/resources/trusted.js
@@ -4,6 +4,7 @@
 
 let diagnosticsService = null;
 let probeService = null;
+let systemEventsService = null;
 
 /**
  * Lazy creates pointer to remote implementation of diagnostics service.
@@ -30,6 +31,18 @@
 }
 
 /**
+ * Lazy creates pointer to remote implementation of system events service.
+ * @return {!chromeos.health.mojom.SystemEventsServiceRemote}
+ */
+function getOrCreateSystemEventsService() {
+  if (systemEventsService === null) {
+    systemEventsService = chromeos.health.mojom.SystemEventsService.getRemote();
+  }
+  return /** @type {!chromeos.health.mojom.SystemEventsServiceRemote} */ (
+      systemEventsService);
+}
+
+/**
  * Alias for Mojo RunRoutine response.
  * @typedef { !Promise<{response: !chromeos.health.mojom.RunRoutineResponse}>
  * }
@@ -852,6 +865,50 @@
 
 const telemetryProxy = new TelemetryProxy();
 
+/**
+ * Proxying event requests between SystemEventsRequester on
+ * chrome-untrusted:// side with WebIDL types and SystemEventsService on
+ * chrome:// side with Mojo types.
+ */
+class SystemEventsProxy {
+  /**
+   * @param {!MessagePipe} messagePipe
+   * @param {!Promise<undefined>} iframeReady
+   */
+  constructor(messagePipe, iframeReady) {
+    this.messagePipe = messagePipe;
+    this.iframeReady = iframeReady;
+
+    this.events = {
+      ON_LID_CLOSED: 'lid-closed',
+      ON_LID_OPENED: 'lid-opened',
+    };
+
+    this.lidObserverCallbackRouter_ =
+        new chromeos.health.mojom.LidObserverCallbackRouter();
+
+    this.lidObserverCallbackRouter_.onLidClosed.addListener(
+        () => this.sendEvent(this.events.ON_LID_CLOSED));
+    this.lidObserverCallbackRouter_.onLidOpened.addListener(
+        () => this.sendEvent(this.events.ON_LID_OPENED));
+
+    getOrCreateSystemEventsService().addLidObserver(
+        this.lidObserverCallbackRouter_.$.bindNewPipeAndPassRemote());
+  }
+
+  /**
+   * Sends event type to chrome-untrusted://.
+   * @param {!string} type
+   */
+  async sendEvent(type) {
+    /* Prevent events from being sent until untrusted iframe is loaded. */
+    await this.iframeReady;
+    this.messagePipe.sendMessage(
+        dpsl_internal.Message.SYSTEM_EVENTS_SERVICE_EVENTS,
+        /** @type {!dpsl_internal.Event} */ ({type: type}));
+  }
+}
+
 const untrustedMessagePipe =
     new MessagePipe('chrome-untrusted://telemetry-extension');
 
@@ -956,3 +1013,6 @@
     resolve();
   });
 });
+
+const systemEventsProxy =
+    new SystemEventsProxy(untrustedMessagePipe, iframeReady);
diff --git a/chromeos/components/telemetry_extension_ui/resources/untrusted.js b/chromeos/components/telemetry_extension_ui/resources/untrusted.js
index e77f3db..632edc4 100644
--- a/chromeos/components/telemetry_extension_ui/resources/untrusted.js
+++ b/chromeos/components/telemetry_extension_ui/resources/untrusted.js
@@ -370,9 +370,17 @@
 
   /**
    * DPSL Telemetry Requester.
+   * @suppress {checkTypes}
    */
-  class TelemetryRequester {
-    constructor() {}
+  class TelemetryRequester extends EventTarget {
+    constructor() {
+      super();
+      messagePipe.registerHandler(
+          dpsl_internal.Message.SYSTEM_EVENTS_SERVICE_EVENTS, (message) => {
+            const event = /** @type {!dpsl_internal.Event} */ (message);
+            this.dispatchEvent(new Event(event.type));
+          });
+    }
 
     /**
      * Requests telemetry info.
diff --git a/chromeos/components/telemetry_extension_ui/system_events_service.cc b/chromeos/components/telemetry_extension_ui/system_events_service.cc
new file mode 100644
index 0000000..e643fe7
--- /dev/null
+++ b/chromeos/components/telemetry_extension_ui/system_events_service.cc
@@ -0,0 +1,28 @@
+// 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 "chromeos/components/telemetry_extension_ui/system_events_service.h"
+
+#include <utility>
+
+#include "mojo/public/cpp/bindings/pending_remote.h"
+
+namespace chromeos {
+
+SystemEventsService::SystemEventsService(
+    mojo::PendingReceiver<health::mojom::SystemEventsService> receiver)
+    : receiver_(this, std::move(receiver)) {}
+
+SystemEventsService::~SystemEventsService() = default;
+
+void SystemEventsService::AddLidObserver(
+    mojo::PendingRemote<health::mojom::LidObserver> observer) {
+  lid_observer_.AddObserver(std::move(observer));
+}
+
+void SystemEventsService::FlushForTesting() {
+  lid_observer_.FlushForTesting();
+}
+
+}  // namespace chromeos
diff --git a/chromeos/components/telemetry_extension_ui/system_events_service.h b/chromeos/components/telemetry_extension_ui/system_events_service.h
new file mode 100644
index 0000000..d9b294e7
--- /dev/null
+++ b/chromeos/components/telemetry_extension_ui/system_events_service.h
@@ -0,0 +1,41 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_COMPONENTS_TELEMETRY_EXTENSION_UI_SYSTEM_EVENTS_SERVICE_H_
+#define CHROMEOS_COMPONENTS_TELEMETRY_EXTENSION_UI_SYSTEM_EVENTS_SERVICE_H_
+
+#if defined(OFFICIAL_BUILD)
+#error System events service should only be included in unofficial builds.
+#endif
+
+#include "chromeos/components/telemetry_extension_ui/lid_observer.h"
+#include "chromeos/components/telemetry_extension_ui/mojom/system_events_service.mojom.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+
+namespace chromeos {
+
+class SystemEventsService : public health::mojom::SystemEventsService {
+ public:
+  explicit SystemEventsService(
+      mojo::PendingReceiver<health::mojom::SystemEventsService> receiver);
+  SystemEventsService(const SystemEventsService&) = delete;
+  SystemEventsService& operator=(const SystemEventsService&) = delete;
+  ~SystemEventsService() override;
+
+  void AddLidObserver(
+      mojo::PendingRemote<health::mojom::LidObserver> observer) override;
+
+  void FlushForTesting();
+
+ private:
+  mojo::Receiver<health::mojom::SystemEventsService> receiver_;
+
+  LidObserver lid_observer_;
+};
+
+}  // namespace chromeos
+
+#endif  // CHROMEOS_COMPONENTS_TELEMETRY_EXTENSION_UI_SYSTEM_EVENTS_SERVICE_H_
diff --git a/chromeos/components/telemetry_extension_ui/system_events_service_unittest.cc b/chromeos/components/telemetry_extension_ui/system_events_service_unittest.cc
new file mode 100644
index 0000000..8a55179
--- /dev/null
+++ b/chromeos/components/telemetry_extension_ui/system_events_service_unittest.cc
@@ -0,0 +1,130 @@
+// 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.
+
+#if defined(OFFICIAL_BUILD)
+#error System events service unit tests should only be included in unofficial builds.
+#endif
+
+#include "chromeos/components/telemetry_extension_ui/system_events_service.h"
+
+#include <memory>
+
+#include "base/run_loop.h"
+#include "base/test/task_environment.h"
+#include "chromeos/components/telemetry_extension_ui/mojom/system_events_service.mojom.h"
+#include "chromeos/dbus/cros_healthd/cros_healthd_client.h"
+#include "chromeos/dbus/cros_healthd/fake_cros_healthd_client.h"
+#include "chromeos/services/cros_healthd/public/cpp/service_connection.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+
+namespace {
+
+class MockLidObserver : public health::mojom::LidObserver {
+ public:
+  MockLidObserver() : receiver_{this} {}
+  MockLidObserver(const MockLidObserver&) = delete;
+  MockLidObserver& operator=(const MockLidObserver&) = delete;
+
+  mojo::PendingRemote<health::mojom::LidObserver> pending_remote() {
+    return receiver_.BindNewPipeAndPassRemote();
+  }
+
+  MOCK_METHOD(void, OnLidClosed, (), (override));
+  MOCK_METHOD(void, OnLidOpened, (), (override));
+
+ private:
+  mojo::Receiver<health::mojom::LidObserver> receiver_;
+};
+
+}  // namespace
+
+class SystemEventsServiceTest : public testing::Test {
+ public:
+  SystemEventsServiceTest() {
+    CrosHealthdClient::InitializeFake();
+    system_events_service_ = std::make_unique<SystemEventsService>(
+        remote_system_events_service_.BindNewPipeAndPassReceiver());
+    mock_lid_observer_ =
+        std::make_unique<testing::StrictMock<MockLidObserver>>();
+
+    // Force other tasks to be processed.
+    cros_healthd::ServiceConnection::GetInstance()->FlushForTesting();
+  }
+
+  ~SystemEventsServiceTest() override {
+    CrosHealthdClient::Shutdown();
+    cros_healthd::ServiceConnection::GetInstance()->FlushForTesting();
+  }
+
+  health::mojom::SystemEventsServiceProxy* remote_system_events_service()
+      const {
+    return remote_system_events_service_.get();
+  }
+
+  SystemEventsService* system_events_service() const {
+    return system_events_service_.get();
+  }
+
+  mojo::PendingRemote<health::mojom::LidObserver> lid_observer() const {
+    return mock_lid_observer_->pending_remote();
+  }
+
+  MockLidObserver* mock_lid_observer() const {
+    return mock_lid_observer_.get();
+  }
+
+ private:
+  base::test::TaskEnvironment task_environment_;
+  mojo::Remote<health::mojom::SystemEventsService>
+      remote_system_events_service_;
+  std::unique_ptr<SystemEventsService> system_events_service_;
+  std::unique_ptr<testing::StrictMock<MockLidObserver>> mock_lid_observer_;
+};
+
+// Tests that in case of cros_healthd crash Lid Observer will reconnect.
+TEST_F(SystemEventsServiceTest, LidObserverReconnect) {
+  remote_system_events_service()->AddLidObserver(lid_observer());
+
+  base::RunLoop run_loop1;
+  EXPECT_CALL(*mock_lid_observer(), OnLidClosed).WillOnce([&run_loop1]() {
+    run_loop1.Quit();
+  });
+  cros_healthd::FakeCrosHealthdClient::Get()->EmitLidClosedEventForTesting();
+  run_loop1.Run();
+
+  // Shutdown cros_healthd to simulate crash.
+  CrosHealthdClient::Shutdown();
+
+  // Ensure ServiceConnection is disconnected from cros_healthd.
+  cros_healthd::ServiceConnection::GetInstance()->FlushForTesting();
+
+  // Restart cros_healthd.
+  CrosHealthdClient::InitializeFake();
+
+  // Ensure disconnect handler is called for lid observer from System Event
+  // Service. After this call, we will have a Mojo pending connection task in
+  // Mojo message queue.
+  system_events_service()->FlushForTesting();
+
+  // Ensure that Mojo pending connection task from lid observer gets processed
+  // and observer is bound. After this call, we are sure that lid observer
+  // reconnected and we can safely emit events.
+  cros_healthd::ServiceConnection::GetInstance()->FlushForTesting();
+
+  base::RunLoop run_loop2;
+  EXPECT_CALL(*mock_lid_observer(), OnLidClosed).WillOnce([&run_loop2]() {
+    run_loop2.Quit();
+  });
+
+  cros_healthd::FakeCrosHealthdClient::Get()->EmitLidClosedEventForTesting();
+  run_loop2.Run();
+}
+
+}  // namespace chromeos
diff --git a/chromeos/components/telemetry_extension_ui/telemetry_extension_ui.cc b/chromeos/components/telemetry_extension_ui/telemetry_extension_ui.cc
index 4066c7dd..2e1cb4c 100644
--- a/chromeos/components/telemetry_extension_ui/telemetry_extension_ui.cc
+++ b/chromeos/components/telemetry_extension_ui/telemetry_extension_ui.cc
@@ -10,7 +10,9 @@
 #include "chromeos/components/telemetry_extension_ui/diagnostics_service.h"
 #include "chromeos/components/telemetry_extension_ui/mojom/diagnostics_service.mojom.h"
 #include "chromeos/components/telemetry_extension_ui/mojom/probe_service.mojom.h"
+#include "chromeos/components/telemetry_extension_ui/mojom/system_events_service.mojom.h"
 #include "chromeos/components/telemetry_extension_ui/probe_service.h"
+#include "chromeos/components/telemetry_extension_ui/system_events_service.h"
 #include "chromeos/components/telemetry_extension_ui/telemetry_extension_untrusted_source.h"
 #include "chromeos/components/telemetry_extension_ui/url_constants.h"
 #include "chromeos/grit/chromeos_telemetry_extension_resources.h"
@@ -40,6 +42,9 @@
   trusted_source->AddResourcePath(
       "probe_service.mojom-lite.js",
       IDR_TELEMETRY_EXTENSION_PROBE_SERVICE_MOJO_LITE_JS);
+  trusted_source->AddResourcePath(
+      "system_events_service.mojom-lite.js",
+      IDR_TELEMETRY_EXTENSION_SYSTEM_EVENTS_SERVICE_MOJO_LITE_JS);
 
 #if !DCHECK_IS_ON()
   // If a user goes to an invalid url and non-DCHECK mode (DHECK = debug mode)
@@ -106,6 +111,12 @@
   probe_service_ = std::make_unique<ProbeService>(std::move(receiver));
 }
 
+void TelemetryExtensionUI::BindInterface(
+    mojo::PendingReceiver<health::mojom::SystemEventsService> receiver) {
+  system_events_service_ =
+      std::make_unique<SystemEventsService>(std::move(receiver));
+}
+
 WEB_UI_CONTROLLER_TYPE_IMPL(TelemetryExtensionUI)
 
 }  // namespace chromeos
diff --git a/chromeos/components/telemetry_extension_ui/telemetry_extension_ui.h b/chromeos/components/telemetry_extension_ui/telemetry_extension_ui.h
index bec8296..567f59e7 100644
--- a/chromeos/components/telemetry_extension_ui/telemetry_extension_ui.h
+++ b/chromeos/components/telemetry_extension_ui/telemetry_extension_ui.h
@@ -13,6 +13,7 @@
 
 #include "chromeos/components/telemetry_extension_ui/mojom/diagnostics_service.mojom-forward.h"
 #include "chromeos/components/telemetry_extension_ui/mojom/probe_service.mojom-forward.h"
+#include "chromeos/components/telemetry_extension_ui/mojom/system_events_service.mojom-forward.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "ui/webui/mojo_web_ui_controller.h"
 
@@ -32,10 +33,14 @@
   void BindInterface(
       mojo::PendingReceiver<health::mojom::ProbeService> receiver);
 
+  void BindInterface(
+      mojo::PendingReceiver<health::mojom::SystemEventsService> receiver);
+
  private:
   // Replaced when |BindInterface| is called.
   std::unique_ptr<health::mojom::DiagnosticsService> diagnostics_service_;
   std::unique_ptr<health::mojom::ProbeService> probe_service_;
+  std::unique_ptr<health::mojom::SystemEventsService> system_events_service_;
 
   WEB_UI_CONTROLLER_TYPE_DECL();
 };
diff --git a/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.cc b/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.cc
index c0bc846..c6512b8 100644
--- a/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.cc
+++ b/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.cc
@@ -5,8 +5,12 @@
 #include "chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.h"
 
 #include "base/base_paths.h"
+#include "base/bind.h"
 #include "base/files/file_path.h"
+#include "base/location.h"
 #include "base/path_service.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
 #include "chrome/browser/chromeos/wilco_dtc_supportd/mojo_utils.h"
 #include "chromeos/components/telemetry_extension_ui/url_constants.h"
 #include "chromeos/components/web_applications/test/sandboxed_web_ui_test_base.h"
@@ -288,6 +292,8 @@
   chromeos::cros_healthd::FakeCrosHealthdClient::Get()
       ->SetProbeTelemetryInfoResponseForTesting(telemetry_info);
 
+  ConfigureSystemEventsServiceToEmitEvents();
+
   SandboxedWebUiAppTestBase::SetUpOnMainThread();
 }
 
@@ -427,3 +433,18 @@
   chromeos::cros_healthd::FakeCrosHealthdClient::Get()
       ->SetProbeTelemetryInfoResponseForTesting(telemetry_info);
 }
+
+void TelemetryExtensionUiBrowserTest::
+    ConfigureSystemEventsServiceToEmitEvents() {
+  chromeos::cros_healthd::FakeCrosHealthdClient::Get()
+      ->EmitLidClosedEventForTesting();
+  chromeos::cros_healthd::FakeCrosHealthdClient::Get()
+      ->EmitLidOpenedEventForTesting();
+
+  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(&TelemetryExtensionUiBrowserTest::
+                         ConfigureSystemEventsServiceToEmitEvents,
+                     system_events_weak_ptr_factory_.GetWeakPtr()),
+      base::TimeDelta::FromSeconds(1));
+}
diff --git a/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.h b/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.h
index 965df5b..cd15227 100644
--- a/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.h
+++ b/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.h
@@ -6,6 +6,7 @@
 #define CHROMEOS_COMPONENTS_TELEMETRY_EXTENSION_UI_TEST_TELEMETRY_EXTENSION_UI_BROWSERTEST_H_
 
 #include "base/command_line.h"
+#include "base/memory/weak_ptr.h"
 #include "chromeos/components/web_applications/test/sandboxed_web_ui_test_base.h"
 
 class TelemetryExtensionUiBrowserTest : public SandboxedWebUiAppTestBase {
@@ -26,6 +27,13 @@
   void ConfigureDiagnosticsForNonInteractiveUpdate();
 
   void ConfigureProbeServiceToReturnErrors();
+
+  void ConfigureSystemEventsServiceToEmitEvents();
+
+ private:
+  // Use to post and cancel tasks for emitting system events.
+  base::WeakPtrFactory<TelemetryExtensionUiBrowserTest>
+      system_events_weak_ptr_factory_{this};
 };
 
 #endif  // CHROMEOS_COMPONENTS_TELEMETRY_EXTENSION_UI_TEST_TELEMETRY_EXTENSION_UI_BROWSERTEST_H_
diff --git a/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.js b/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.js
index afba56c2..f14fcc41 100644
--- a/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.js
+++ b/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.js
@@ -408,6 +408,7 @@
   ['UntrustedDiagnosticsRequestRunBatteryDischargeRoutine'],
   ['UntrustedDiagnosticsRequestRunBatteryChargeRoutineInvalidInput'],
   ['UntrustedDiagnosticsRequestRunBatteryChargeRoutine'],
+  ['UntrustedLidEventListener'],
   ['UntrustedRequestTelemetryInfoUnknownCategory'],
   ['UntrustedRequestTelemetryInfo'],
   [
diff --git a/chromeos/components/telemetry_extension_ui/test/untrusted_browsertest.js b/chromeos/components/telemetry_extension_ui/test/untrusted_browsertest.js
index 465d24ef..73339d6 100644
--- a/chromeos/components/telemetry_extension_ui/test/untrusted_browsertest.js
+++ b/chromeos/components/telemetry_extension_ui/test/untrusted_browsertest.js
@@ -441,6 +441,18 @@
       assertDeepEquals(response, {id: 123456789, status: 'ready'});
     });
 
+// Tests that addEventListener receives system lid events.
+UNTRUSTED_TEST('UntrustedLidEventListener', async () => {
+  await Promise.all([
+    new Promise(
+        (resolve) =>
+            chromeos.telemetry.addEventListener('lid-closed', resolve)),
+    new Promise(
+        (resolve) =>
+            chromeos.telemetry.addEventListener('lid-opened', resolve)),
+  ]);
+});
+
 // Tests that TelemetryInfo throws an error if category is unknown.
 UNTRUSTED_TEST('UntrustedRequestTelemetryInfoUnknownCategory', async () => {
   let caughtError = {};
diff --git a/chromeos/constants/chromeos_features.cc b/chromeos/constants/chromeos_features.cc
index 78fd3346..7ec85ef 100644
--- a/chromeos/constants/chromeos_features.cc
+++ b/chromeos/constants/chromeos_features.cc
@@ -427,7 +427,7 @@
 // Controls whether to show printer statuses on the Print Preview destination
 // dialog.
 const base::Feature kPrinterStatusDialog{"PrinterStatusDialog",
-                                         base::FEATURE_DISABLED_BY_DEFAULT};
+                                         base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Controls whether to enable the Print Job Management App.
 const base::Feature kPrintJobManagementApp{"PrintJobManagementApp",
diff --git a/chromeos/constants/chromeos_pref_names.cc b/chromeos/constants/chromeos_pref_names.cc
index 716ddf3..15605ab 100644
--- a/chromeos/constants/chromeos_pref_names.cc
+++ b/chromeos/constants/chromeos_pref_names.cc
@@ -68,6 +68,11 @@
 const char kEduCoexistenceSecondaryAccountsInvalidationVersion[] =
     "account_manager.edu_coexistence_secondary_accounts_invalidation_version";
 
+// A string pref containing valid version of Edu Coexistence Terms of Service.
+// Controlled by EduCoexistenceToSVersion policy.
+const char kEduCoexistenceToSVersion[] =
+    "family_link_user.edu_coexistence_tos_version";
+
 // A dictionary of info for Quirks Client/Server interaction, mostly last server
 // request times, keyed to display product_id's.
 const char kQuirksClientLastServerCheck[] = "quirks_client.last_server_check";
diff --git a/chromeos/constants/chromeos_pref_names.h b/chromeos/constants/chromeos_pref_names.h
index f7a66f3..d5263d8 100644
--- a/chromeos/constants/chromeos_pref_names.h
+++ b/chromeos/constants/chromeos_pref_names.h
@@ -31,6 +31,8 @@
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const char kEduCoexistenceSecondaryAccountsInvalidationVersion[];
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
+extern const char kEduCoexistenceToSVersion[];
+COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const char kQuirksClientLastServerCheck[];
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const char kDeviceWiFiFastTransitionEnabled[];
diff --git a/chromeos/dbus/cros_healthd/fake_cros_healthd_client.cc b/chromeos/dbus/cros_healthd/fake_cros_healthd_client.cc
index 31d4915d..7493b29 100644
--- a/chromeos/dbus/cros_healthd/fake_cros_healthd_client.cc
+++ b/chromeos/dbus/cros_healthd/fake_cros_healthd_client.cc
@@ -92,6 +92,13 @@
   fake_service_.EmitLidClosedEventForTesting();
 }
 
+void FakeCrosHealthdClient::EmitLidOpenedEventForTesting() {
+  // Flush the receiver, so any pending observers are registered before the
+  // event is emitted.
+  receiver_.FlushForTesting();
+  fake_service_.EmitLidOpenedEventForTesting();
+}
+
 void FakeCrosHealthdClient::RequestNetworkHealthForTesting(
     chromeos::network_health::mojom::NetworkHealthService::
         GetHealthSnapshotCallback callback) {
diff --git a/chromeos/dbus/cros_healthd/fake_cros_healthd_client.h b/chromeos/dbus/cros_healthd/fake_cros_healthd_client.h
index 2706f040..270b47f9 100644
--- a/chromeos/dbus/cros_healthd/fake_cros_healthd_client.h
+++ b/chromeos/dbus/cros_healthd/fake_cros_healthd_client.h
@@ -72,6 +72,9 @@
   // Calls the lid event OnLidClosed on all registered lid observers.
   void EmitLidClosedEventForTesting();
 
+  // Calls the lid event OnLidOpened on all registered lid observers.
+  void EmitLidOpenedEventForTesting();
+
   // Requests the network health state using the NetworkHealthService remote.
   void RequestNetworkHealthForTesting(
       chromeos::network_health::mojom::NetworkHealthService::
diff --git a/chromeos/dbus/cros_healthd/fake_cros_healthd_service.cc b/chromeos/dbus/cros_healthd/fake_cros_healthd_service.cc
index dd6ceac..5f9a9001 100644
--- a/chromeos/dbus/cros_healthd/fake_cros_healthd_service.cc
+++ b/chromeos/dbus/cros_healthd/fake_cros_healthd_service.cc
@@ -293,6 +293,11 @@
     observer->OnLidClosed();
 }
 
+void FakeCrosHealthdService::EmitLidOpenedEventForTesting() {
+  for (auto& observer : lid_observers_)
+    observer->OnLidOpened();
+}
+
 void FakeCrosHealthdService::RequestNetworkHealthForTesting(
     chromeos::network_health::mojom::NetworkHealthService::
         GetHealthSnapshotCallback callback) {
diff --git a/chromeos/dbus/cros_healthd/fake_cros_healthd_service.h b/chromeos/dbus/cros_healthd/fake_cros_healthd_service.h
index 28aa46f7..717f09d 100644
--- a/chromeos/dbus/cros_healthd/fake_cros_healthd_service.h
+++ b/chromeos/dbus/cros_healthd/fake_cros_healthd_service.h
@@ -152,6 +152,9 @@
   // Calls the lid event OnLidClosed for all registered lid observers.
   void EmitLidClosedEventForTesting();
 
+  // Calls the lid event OnLidOpened for all registered lid observers.
+  void EmitLidOpenedEventForTesting();
+
   // Requests the network health state using the network_health_remote_.
   void RequestNetworkHealthForTesting(
       chromeos::network_health::mojom::NetworkHealthService::
diff --git a/chromeos/dbus/fake_lorgnette_manager_client.cc b/chromeos/dbus/fake_lorgnette_manager_client.cc
index 371e55bd..ca87798 100644
--- a/chromeos/dbus/fake_lorgnette_manager_client.cc
+++ b/chromeos/dbus/fake_lorgnette_manager_client.cc
@@ -34,7 +34,7 @@
 }
 
 void FakeLorgnetteManagerClient::StartScan(
-    std::string device_name,
+    const std::string& device_name,
     const lorgnette::ScanSettings& settings,
     VoidDBusMethodCallback completion_callback,
     base::RepeatingCallback<void(std::string)> page_callback,
diff --git a/chromeos/dbus/fake_lorgnette_manager_client.h b/chromeos/dbus/fake_lorgnette_manager_client.h
index 2d5f58f..a17a6de2 100644
--- a/chromeos/dbus/fake_lorgnette_manager_client.h
+++ b/chromeos/dbus/fake_lorgnette_manager_client.h
@@ -30,7 +30,7 @@
   void GetScannerCapabilities(
       const std::string& device_name,
       DBusMethodCallback<lorgnette::ScannerCapabilities> callback) override;
-  void StartScan(std::string device_name,
+  void StartScan(const std::string& device_name,
                  const lorgnette::ScanSettings& settings,
                  VoidDBusMethodCallback completion_callback,
                  base::RepeatingCallback<void(std::string)> page_callback,
diff --git a/chromeos/dbus/lorgnette_manager_client.cc b/chromeos/dbus/lorgnette_manager_client.cc
index 6195785..cecf07d 100644
--- a/chromeos/dbus/lorgnette_manager_client.cc
+++ b/chromeos/dbus/lorgnette_manager_client.cc
@@ -66,7 +66,7 @@
   }
 
   // LorgnetteManagerClient override.
-  void StartScan(std::string device_name,
+  void StartScan(const std::string& device_name,
                  const lorgnette::ScanSettings& settings,
                  VoidDBusMethodCallback completion_callback,
                  base::RepeatingCallback<void(std::string)> page_callback,
@@ -266,7 +266,7 @@
   }
 
   // Called when scan data read is completed.
-  void OnScanDataCompleted(std::string uuid,
+  void OnScanDataCompleted(const std::string& uuid,
                            bool more_pages,
                            base::Optional<std::string> data) {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
diff --git a/chromeos/dbus/lorgnette_manager_client.h b/chromeos/dbus/lorgnette_manager_client.h
index f5601a31..ce6b2af 100644
--- a/chromeos/dbus/lorgnette_manager_client.h
+++ b/chromeos/dbus/lorgnette_manager_client.h
@@ -50,7 +50,7 @@
   // If |progress_callback| is provided, it will be called as scan progress
   // increases. The progress will be passed as a value from 0-100.
   virtual void StartScan(
-      std::string device_name,
+      const std::string& device_name,
       const lorgnette::ScanSettings& settings,
       VoidDBusMethodCallback completion_callback,
       base::RepeatingCallback<void(std::string)> page_callback,
diff --git a/chromeos/profiles/airmont.afdo.newest.txt b/chromeos/profiles/airmont.afdo.newest.txt
index a0277db..b2c3f59 100644
--- a/chromeos/profiles/airmont.afdo.newest.txt
+++ b/chromeos/profiles/airmont.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-airmont-87-4265.0-1601286022-benchmark-87.0.4277.0-r1-redacted.afdo.xz
+chromeos-chrome-amd64-airmont-87-4265.0-1601286022-benchmark-87.0.4278.0-r1-redacted.afdo.xz
diff --git a/chromeos/profiles/broadwell.afdo.newest.txt b/chromeos/profiles/broadwell.afdo.newest.txt
index 230906a..be62a77 100644
--- a/chromeos/profiles/broadwell.afdo.newest.txt
+++ b/chromeos/profiles/broadwell.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-broadwell-87-4258.0-1601290734-benchmark-87.0.4277.0-r1-redacted.afdo.xz
+chromeos-chrome-amd64-broadwell-87-4258.0-1601290734-benchmark-87.0.4278.0-r1-redacted.afdo.xz
diff --git a/chromeos/profiles/silvermont.afdo.newest.txt b/chromeos/profiles/silvermont.afdo.newest.txt
index 72682c4c..592d465 100644
--- a/chromeos/profiles/silvermont.afdo.newest.txt
+++ b/chromeos/profiles/silvermont.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-silvermont-87-4265.0-1601289755-benchmark-87.0.4277.0-r1-redacted.afdo.xz
+chromeos-chrome-amd64-silvermont-87-4265.0-1601289755-benchmark-87.0.4278.0-r1-redacted.afdo.xz
diff --git a/components/BUILD.gn b/components/BUILD.gn
index 26cab65..d0b111b 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -440,6 +440,10 @@
     deps += [ "//components/safe_browsing/android:unit_tests_mobile" ]
   }
 
+  if (safe_browsing_mode == 1) {
+    deps += [ "//components/safe_browsing/content/renderer/phishing_classifier:unit_tests" ]
+  }
+
   if (!is_ios) {
     deps += [
       "//components/safe_browsing/core/realtime:unit_tests",
diff --git a/components/autofill/core/browser/BUILD.gn b/components/autofill/core/browser/BUILD.gn
index 45378f3..3447ad7 100644
--- a/components/autofill/core/browser/BUILD.gn
+++ b/components/autofill/core/browser/BUILD.gn
@@ -629,6 +629,7 @@
     "logging/log_manager_unittest.cc",
     "logging/log_router_unittest.cc",
     "pattern_provider/pattern_provider_unittest.cc",
+    "payments/autofill_offer_manager_unittest.cc",
     "payments/credit_card_access_manager_unittest.cc",
     "payments/credit_card_cvc_authenticator_unittest.cc",
     "payments/credit_card_save_manager_unittest.cc",
diff --git a/components/autofill/core/browser/autofill_client.cc b/components/autofill/core/browser/autofill_client.cc
index 656176f7..54d977b 100644
--- a/components/autofill/core/browser/autofill_client.cc
+++ b/components/autofill/core/browser/autofill_client.cc
@@ -35,6 +35,10 @@
   return version_info::Channel::UNKNOWN;
 }
 
+AutofillOfferManager* AutofillClient::GetAutofillOfferManager() {
+  return nullptr;
+}
+
 std::string AutofillClient::GetVariationConfigCountryCode() const {
   return std::string();
 }
diff --git a/components/autofill/core/browser/autofill_client.h b/components/autofill/core/browser/autofill_client.h
index 310d734..d8bb5ce 100644
--- a/components/autofill/core/browser/autofill_client.h
+++ b/components/autofill/core/browser/autofill_client.h
@@ -59,6 +59,7 @@
 
 class AddressNormalizer;
 class AutocompleteHistoryManager;
+class AutofillOfferManager;
 class AutofillPopupDelegate;
 class CardUnmaskDelegate;
 class CreditCard;
@@ -275,6 +276,10 @@
   // Gets an AddressNormalizer instance (can be null).
   virtual AddressNormalizer* GetAddressNormalizer() = 0;
 
+  // Gets an AutofillOfferManager instance (can be null for unsupported
+  // platforms).
+  virtual AutofillOfferManager* GetAutofillOfferManager();
+
   // Gets the virtual URL of the last committed page of this client's
   // associated WebContents.
   virtual const GURL& GetLastCommittedURL() = 0;
diff --git a/components/autofill/core/browser/autofill_manager.cc b/components/autofill/core/browser/autofill_manager.cc
index c41cc66..68e6295 100644
--- a/components/autofill/core/browser/autofill_manager.cc
+++ b/components/autofill/core/browser/autofill_manager.cc
@@ -61,6 +61,7 @@
 #include "components/autofill/core/browser/geo/phone_number_i18n.h"
 #include "components/autofill/core/browser/logging/log_manager.h"
 #include "components/autofill/core/browser/metrics/form_events.h"
+#include "components/autofill/core/browser/payments/autofill_offer_manager.h"
 #include "components/autofill/core/browser/payments/credit_card_access_manager.h"
 #include "components/autofill/core/browser/payments/payments_client.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
@@ -1590,6 +1591,7 @@
       personal_data_, client_));
   credit_card_access_manager_.reset(new CreditCardAccessManager(
       driver(), client_, personal_data_, credit_card_form_event_logger_.get()));
+
   has_logged_autofill_enabled_ = false;
   has_logged_address_suggestions_count_ = false;
   did_show_suggestions_ = false;
@@ -1651,6 +1653,7 @@
         driver, this, GetAPIKeyForUrl(channel), client_->GetLogManager()));
   }
   CountryNames::SetLocaleString(app_locale_);
+  offer_manager_ = client_->GetAutofillOfferManager();
 }
 
 bool AutofillManager::RefreshDataModels() {
@@ -2007,6 +2010,12 @@
       personal_data_->GetCreditCardSuggestions(
           type, SanitizeCreditCardFieldValue(field.value),
           client_->AreServerCardsSupported());
+  if (base::FeatureList::IsEnabled(
+          features::kAutofillEnableOffersInDownstream) &&
+      offer_manager_) {
+    offer_manager_->UpdateSuggestionsWithOffers(client_->GetLastCommittedURL(),
+                                                suggestions);
+  }
   *should_display_gpay_logo =
       credit_card_access_manager_->ShouldDisplayGPayLogo();
 
diff --git a/components/autofill/core/browser/autofill_manager.h b/components/autofill/core/browser/autofill_manager.h
index c13666fa..fc6e8bc4 100644
--- a/components/autofill/core/browser/autofill_manager.h
+++ b/components/autofill/core/browser/autofill_manager.h
@@ -32,6 +32,7 @@
 #include "components/autofill/core/browser/form_types.h"
 #include "components/autofill/core/browser/metrics/address_form_event_logger.h"
 #include "components/autofill/core/browser/metrics/credit_card_form_event_logger.h"
+#include "components/autofill/core/browser/payments/autofill_offer_manager.h"
 #include "components/autofill/core/browser/payments/card_unmask_delegate.h"
 #include "components/autofill/core/browser/payments/credit_card_access_manager.h"
 #include "components/autofill/core/browser/payments/full_card_request.h"
@@ -681,6 +682,10 @@
   // The credit card access manager, used to access local and server cards.
   std::unique_ptr<CreditCardAccessManager> credit_card_access_manager_;
 
+  // The autofill offer manager, used to to retrieve offers for card
+  // suggestions.
+  AutofillOfferManager* offer_manager_;
+
   // Collected information about the autofill form where a credit card will be
   // filled.
   AutofillDriver::RendererFormDataAction credit_card_action_;
diff --git a/components/autofill/core/browser/payments/autofill_offer_manager.cc b/components/autofill/core/browser/payments/autofill_offer_manager.cc
index 127700eb..5cf79e3 100644
--- a/components/autofill/core/browser/payments/autofill_offer_manager.cc
+++ b/components/autofill/core/browser/payments/autofill_offer_manager.cc
@@ -4,10 +4,114 @@
 
 #include "components/autofill/core/browser/payments/autofill_offer_manager.h"
 
+#include <map>
+
+#include "base/bind.h"
+#include "base/ranges/algorithm.h"
+#include "base/ranges/ranges.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "components/autofill/core/browser/autofill_client.h"
+#include "components/autofill/core/browser/data_model/autofill_offer_data.h"
+#include "components/autofill/core/browser/payments/payments_client.h"
+#include "components/autofill/core/common/autofill_clock.h"
+#include "components/strings/grit/components_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "url/gurl.h"
+
 namespace autofill {
 
-AutofillOfferManager::AutofillOfferManager() = default;
+namespace {
+// Ensure the offer is not expired and is valid for the current page.
+bool IsOfferEligible(const AutofillOfferData& offer,
+                     const GURL& last_committed_url) {
+  bool is_eligible = (offer.expiry > AutofillClock::Now());
+  is_eligible &= base::ranges::count(offer.merchant_domain, last_committed_url);
+  return is_eligible;
+}
+}  // namespace
 
-AutofillOfferManager::~AutofillOfferManager() = default;
+AutofillOfferManager::AutofillOfferManager(PersonalDataManager* personal_data)
+    : personal_data_(personal_data) {
+  personal_data_->AddObserver(this);
+  UpdateEligibleMerchantDomains();
+}
 
-}  // namespace autofill
\ No newline at end of file
+AutofillOfferManager::~AutofillOfferManager() {
+  personal_data_->RemoveObserver(this);
+}
+
+void AutofillOfferManager::OnPersonalDataChanged() {
+  UpdateEligibleMerchantDomains();
+}
+
+void AutofillOfferManager::UpdateSuggestionsWithOffers(
+    const GURL& last_committed_url,
+    std::vector<Suggestion>& suggestions) {
+  if (eligible_merchant_domains_.count(last_committed_url) == 0) {
+    return;
+  }
+
+  AutofillOfferManager::OffersMap eligible_offers_map =
+      CreateOffersMap(last_committed_url);
+
+  // Update |offer_label| for each suggestion.
+  for (auto& suggestion : suggestions) {
+    std::string id = suggestion.backend_id;
+    if (eligible_offers_map.count(id)) {
+      base::string16 reward_amount =
+          base::UTF8ToUTF16(eligible_offers_map[id]->offer_reward_amount);
+
+      auto string_id = (reward_amount.find('%') == std::string::npos)
+                           ? IDS_AUTOFILL_OFFERS_DISCOUNT
+                           : IDS_AUTOFILL_OFFERS_CASHBACK;
+      suggestion.offer_label =
+          l10n_util::GetStringFUTF16(string_id, reward_amount);
+    }
+  }
+}
+
+void AutofillOfferManager::UpdateEligibleMerchantDomains() {
+  eligible_merchant_domains_.clear();
+  std::vector<AutofillOfferData*> offers =
+      personal_data_->GetCreditCardOffers();
+
+  for (auto* offer : offers) {
+    for (auto& domain : offer->merchant_domain) {
+      eligible_merchant_domains_.emplace(domain);
+    }
+  }
+}
+
+AutofillOfferManager::OffersMap AutofillOfferManager::CreateOffersMap(
+    const GURL& last_committed_url) const {
+  AutofillOfferManager::OffersMap offers_map;
+
+  std::vector<AutofillOfferData*> offers =
+      personal_data_->GetCreditCardOffers();
+  std::vector<CreditCard*> cards = personal_data_->GetCreditCards();
+
+  for (auto* offer : offers) {
+    // Ensure the offer is valid.
+    if (!IsOfferEligible(*offer, last_committed_url)) {
+      continue;
+    }
+
+    // Find card with corresponding instrument ID and add its guid to the map.
+    for (const auto* card : cards) {
+      // If card has an offer, add the backend ID to the map. There is currently
+      // a one-to-one mapping between cards and offer data, however, this may
+      // change in the future.
+      if (std::count(offer->eligible_instrument_id.begin(),
+                     offer->eligible_instrument_id.end(),
+                     card->instrument_id())) {
+        offers_map[card->guid()] = offer;
+      }
+    }
+  }
+
+  return offers_map;
+}
+
+}  // namespace autofill
diff --git a/components/autofill/core/browser/payments/autofill_offer_manager.h b/components/autofill/core/browser/payments/autofill_offer_manager.h
index ce93163..caba3e7 100644
--- a/components/autofill/core/browser/payments/autofill_offer_manager.h
+++ b/components/autofill/core/browser/payments/autofill_offer_manager.h
@@ -5,38 +5,56 @@
 #ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_AUTOFILL_OFFER_MANAGER_H_
 #define COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_AUTOFILL_OFFER_MANAGER_H_
 
+#include <stdint.h>
+#include <map>
 #include <string>
 #include <vector>
 
 #include "base/strings/string16.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "components/autofill/core/browser/autofill_client.h"
+#include "components/autofill/core/browser/data_model/autofill_offer_data.h"
+#include "components/autofill/core/browser/personal_data_manager.h"
+#include "components/autofill/core/browser/personal_data_manager_observer.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "url/gurl.h"
 
 namespace autofill {
 
-struct AutofillOfferData {
-  AutofillOfferData();
-  ~AutofillOfferData();
-  // The description of this offer.
-  base::string16 description;
-  // The name of this offer.
-  base::string16 name;
-  // The unique server id of this offer.
-  std::string offer_id;
-  // The ids of the cards this offer can be applied to.
-  std::vector<std::string> eligible_card_id;
-  // The merchant URL where this offer can be redeemed.
-  std::vector<std::string> merchant_domain;
-};
-
 // Manages all Autofill related offers. One per frame; owned by the
 // AutofillManager.
-class AutofillOfferManager {
+class AutofillOfferManager : public KeyedService,
+                             public PersonalDataManagerObserver {
  public:
-  AutofillOfferManager();
-  virtual ~AutofillOfferManager();
+  // Mapping from suggestion backend ID to offer data.
+  using OffersMap = std::map<std::string, AutofillOfferData*>;
+
+  explicit AutofillOfferManager(PersonalDataManager* personal_data);
+  ~AutofillOfferManager() override;
   AutofillOfferManager(const AutofillOfferManager&) = delete;
   AutofillOfferManager& operator=(const AutofillOfferManager&) = delete;
+
+  // PersonalDataManagerObserver:
+  void OnPersonalDataChanged() override;
+
+  // Modifies any suggestion in |suggestions| if it has related offer data.
+  void UpdateSuggestionsWithOffers(const GURL& last_committed_url,
+                                   std::vector<Suggestion>& suggestions);
+
+ private:
+  // Queries |personal_data_| to reset the elements of
+  // |eligible_merchant_domains_|
+  void UpdateEligibleMerchantDomains();
+
+  // Creates a mapping from Suggestion Backend ID's to eligible Credit Card
+  // Offers.
+  OffersMap CreateOffersMap(const GURL& last_committed_url) const;
+
+  PersonalDataManager* personal_data_;
+  std::set<GURL> eligible_merchant_domains_ = {};
 };
 
 }  // namespace autofill
 
-#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_AUTOFILL_OFFER_MANAGER_H_
\ No newline at end of file
+#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_AUTOFILL_OFFER_MANAGER_H_
diff --git a/components/autofill/core/browser/payments/autofill_offer_manager_unittest.cc b/components/autofill/core/browser/payments/autofill_offer_manager_unittest.cc
new file mode 100644
index 0000000..c0b9d3e
--- /dev/null
+++ b/components/autofill/core/browser/payments/autofill_offer_manager_unittest.cc
@@ -0,0 +1,136 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/task_environment.h"
+#include "base/time/time.h"
+#include "components/autofill/core/browser/autofill_test_utils.h"
+#include "components/autofill/core/browser/payments/autofill_offer_manager.h"
+#include "components/autofill/core/browser/test_autofill_client.h"
+#include "components/autofill/core/browser/test_personal_data_manager.h"
+#include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
+#include "components/autofill/core/common/autofill_clock.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "url/gurl.h"
+
+namespace autofill {
+
+namespace {
+const char kTestGuid[] = "00000000-0000-0000-0000-000000000001";
+const char kTestNumber[] = "4234567890123456";  // Visa
+const char kTestUrl[] = "http://www.example.com/";
+
+}  // namespace
+
+class AutofillOfferManagerTest : public testing::Test {
+ public:
+  AutofillOfferManagerTest() = default;
+  ~AutofillOfferManagerTest() override = default;
+
+  void SetUp() override {
+    autofill_client_.SetPrefs(test::PrefServiceForTesting());
+    personal_data_manager_.Init(/*profile_database=*/database_,
+                                /*account_database=*/nullptr,
+                                /*pref_service=*/autofill_client_.GetPrefs(),
+                                /*identity_manager=*/nullptr,
+                                /*client_profile_validator=*/nullptr,
+                                /*history_service=*/nullptr,
+                                /*is_off_the_record=*/false);
+    personal_data_manager_.SetPrefService(autofill_client_.GetPrefs());
+    autofill_offer_manager_ =
+        std::make_unique<AutofillOfferManager>(&personal_data_manager_);
+  }
+
+  CreditCard CreateCreditCard(std::string guid,
+                              std::string number = kTestNumber) {
+    CreditCard card = CreditCard();
+    test::SetCreditCardInfo(&card, "Jane Doe", number.c_str(),
+                            test::NextMonth().c_str(), test::NextYear().c_str(),
+                            "1");
+    card.set_guid(guid);
+    card.set_record_type(CreditCard::MASKED_SERVER_CARD);
+
+    personal_data_manager_.AddServerCreditCard(card);
+    return card;
+  }
+
+  void CreateCreditCardOfferForCard(const CreditCard& card,
+                                    std::string offer_reward_amount,
+                                    bool expired = false) {
+    AutofillOfferData offer_data;
+    offer_data.offer_id = 4444;
+    offer_data.offer_reward_amount = offer_reward_amount;
+    if (expired) {
+      offer_data.expiry = AutofillClock::Now() - base::TimeDelta::FromDays(2);
+    } else {
+      offer_data.expiry = AutofillClock::Now() + base::TimeDelta::FromDays(2);
+    }
+    offer_data.merchant_domain = {GURL(kTestUrl)};
+    offer_data.eligible_instrument_id = {card.instrument_id()};
+    personal_data_manager_.AddCreditCardOfferData(offer_data);
+  }
+
+ protected:
+  base::test::TaskEnvironment task_environment_{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  TestAutofillClient autofill_client_;
+  scoped_refptr<AutofillWebDataService> database_;
+  TestPersonalDataManager personal_data_manager_;
+  std::unique_ptr<AutofillOfferManager> autofill_offer_manager_ = nullptr;
+};
+
+TEST_F(AutofillOfferManagerTest, UpdateSuggestionsWithOffers_EligibleDiscount) {
+  CreditCard card = CreateCreditCard(kTestGuid);
+  CreateCreditCardOfferForCard(card, "$4");
+
+  std::vector<Suggestion> suggestions = {Suggestion()};
+  suggestions[0].backend_id = kTestGuid;
+  autofill_offer_manager_->UpdateSuggestionsWithOffers(GURL(kTestUrl),
+                                                       suggestions);
+
+  EXPECT_EQ(suggestions[0].offer_label, base::UTF8ToUTF16("$4 Off"));
+}
+
+TEST_F(AutofillOfferManagerTest, UpdateSuggestionsWithOffers_EligibleCashback) {
+  CreditCard card = CreateCreditCard(kTestGuid);
+  CreateCreditCardOfferForCard(card, "5%");
+
+  std::vector<Suggestion> suggestions = {Suggestion()};
+  suggestions[0].backend_id = kTestGuid;
+  autofill_offer_manager_->UpdateSuggestionsWithOffers(GURL(kTestUrl),
+                                                       suggestions);
+
+  EXPECT_EQ(suggestions[0].offer_label, base::UTF8ToUTF16("5% Cash Back"));
+}
+
+TEST_F(AutofillOfferManagerTest, UpdateSuggestionsWithOffers_ExpiredOffer) {
+  CreditCard card = CreateCreditCard(kTestGuid);
+  CreateCreditCardOfferForCard(card, "5%", /*expired=*/true);
+
+  std::vector<Suggestion> suggestions = {Suggestion()};
+  suggestions[0].backend_id = kTestGuid;
+  autofill_offer_manager_->UpdateSuggestionsWithOffers(GURL(kTestUrl),
+                                                       suggestions);
+
+  EXPECT_TRUE(suggestions[0].offer_label.empty());
+}
+
+TEST_F(AutofillOfferManagerTest, UpdateSuggestionsWithOffers_WrongUrl) {
+  CreditCard card = CreateCreditCard(kTestGuid);
+  CreateCreditCardOfferForCard(card, "5%");
+
+  std::vector<Suggestion> suggestions = {Suggestion()};
+  suggestions[0].backend_id = kTestGuid;
+  autofill_offer_manager_->UpdateSuggestionsWithOffers(
+      GURL("http://wrongurl.com/"), suggestions);
+
+  EXPECT_TRUE(suggestions[0].offer_label.empty());
+}
+
+}  // namespace autofill
diff --git a/components/autofill/core/browser/test_personal_data_manager.cc b/components/autofill/core/browser/test_personal_data_manager.cc
index 8aaf994a..79b2f682 100644
--- a/components/autofill/core/browser/test_personal_data_manager.cc
+++ b/components/autofill/core/browser/test_personal_data_manager.cc
@@ -294,6 +294,10 @@
   server_credit_card_cloud_token_data_.clear();
 }
 
+void TestPersonalDataManager::ClearCreditCardOfferData() {
+  autofill_offer_data_.clear();
+}
+
 AutofillProfile* TestPersonalDataManager::GetProfileWithGUID(const char* guid) {
   for (AutofillProfile* profile : GetProfiles()) {
     if (!profile->guid().compare(guid))
@@ -326,6 +330,14 @@
   NotifyPersonalDataObserver();
 }
 
+void TestPersonalDataManager::AddCreditCardOfferData(
+    const AutofillOfferData& offer_data) {
+  std::unique_ptr<AutofillOfferData> data =
+      std::make_unique<AutofillOfferData>(offer_data);
+  autofill_offer_data_.emplace_back(std::move(data));
+  NotifyPersonalDataObserver();
+}
+
 void TestPersonalDataManager::SetNicknameForCardWithGUID(
     const char* guid,
     const std::string& nickname) {
diff --git a/components/autofill/core/browser/test_personal_data_manager.h b/components/autofill/core/browser/test_personal_data_manager.h
index beccfef..fe77396 100644
--- a/components/autofill/core/browser/test_personal_data_manager.h
+++ b/components/autofill/core/browser/test_personal_data_manager.h
@@ -75,6 +75,9 @@
   // Clears |server_credit_card_cloud_token_data_|.
   void ClearCloudTokenData();
 
+  // Clears |autofill_offer_data_|.
+  void ClearCreditCardOfferData();
+
   // Gets a profile based on the provided |guid|.
   AutofillProfile* GetProfileWithGUID(const char* guid);
 
@@ -88,6 +91,9 @@
   // Adds a cloud token data to |server_credit_card_cloud_token_data_|.
   void AddCloudTokenData(const CreditCardCloudTokenData& cloud_token_data);
 
+  // Adds offer data to |autofill_offer_data_|.
+  void AddCreditCardOfferData(const AutofillOfferData& offer_data);
+
   // Sets a local/server card's nickname based on the provided |guid|.
   void SetNicknameForCardWithGUID(const char* guid,
                                   const std::string& nickname);
diff --git a/components/autofill/core/browser/ui/suggestion.h b/components/autofill/core/browser/ui/suggestion.h
index 42ba0f8..c69aa387 100644
--- a/components/autofill/core/browser/ui/suggestion.h
+++ b/components/autofill/core/browser/ui/suggestion.h
@@ -50,6 +50,9 @@
 
   base::string16 value;
   base::string16 label;
+  // A label to be shown beneath |label| that will display information about any
+  // credit card offers or rewards.
+  base::string16 offer_label;
   // Used only for passwords to show the password value.
   // Also used to display an extra line of information if two line
   // display is enabled.
diff --git a/components/autofill_payments_strings.grdp b/components/autofill_payments_strings.grdp
index 492f07b..f1ad220e8 100644
--- a/components/autofill_payments_strings.grdp
+++ b/components/autofill_payments_strings.grdp
@@ -505,4 +505,12 @@
     No thanks
   </message>
 
+  <!-- Credit card offers and rewards related strings -->
+  <message name="IDS_AUTOFILL_OFFERS_CASHBACK" desc="Displays that a cashback offer will be rewarded if credit card is used on current page. Part of Autofill suggestions popup.">
+    <ph name="PERCENT">$1<ex>5%</ex></ph> Cash Back
+  </message>
+  <message name="IDS_AUTOFILL_OFFERS_DISCOUNT" desc="Displays a discount that will be rewarded if credit card is used on current page. Part of Autofill suggestions popup.">
+    <ph name="DISCOUNT">$1<ex>$$3</ex></ph> Off
+  </message>
+
 </grit-part>
diff --git a/components/autofill_payments_strings_grdp/IDS_AUTOFILL_OFFERS_CASHBACK.png.sha1 b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_OFFERS_CASHBACK.png.sha1
new file mode 100644
index 0000000..ef49ef3
--- /dev/null
+++ b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_OFFERS_CASHBACK.png.sha1
@@ -0,0 +1 @@
+378244426ac8f525d17518614e3d7b1508061cf9
\ No newline at end of file
diff --git a/components/autofill_payments_strings_grdp/IDS_AUTOFILL_OFFERS_DISCOUNT.png.sha1 b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_OFFERS_DISCOUNT.png.sha1
new file mode 100644
index 0000000..0f0c9a19
--- /dev/null
+++ b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_OFFERS_DISCOUNT.png.sha1
@@ -0,0 +1 @@
+a09ae4c895b292cf8ce625cf9843a477d86dfc35
\ No newline at end of file
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsCategory.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsCategory.java
index f843d86..23e7955 100644
--- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsCategory.java
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsCategory.java
@@ -287,25 +287,6 @@
     }
 
     /**
-     * Returns whether the Category type is correlated with PermissionInfo
-     */
-    public boolean isPermissionInfo() {
-        switch (mCategory) {
-            case Type.AUGMENTED_REALITY:
-            case Type.CAMERA:
-            case Type.CLIPBOARD:
-            case Type.DEVICE_LOCATION:
-            case Type.NFC:
-            case Type.NOTIFICATIONS:
-            case Type.PROTECTED_MEDIA:
-            case Type.VIRTUAL_REALITY:
-                return true;
-            default:
-                return false;
-        }
-    }
-
-    /**
      * Returns whether this category is the specified type.
      */
     public boolean showSites(@Type int type) {
diff --git a/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/EventConstants.java b/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/EventConstants.java
index f2ec94b..9bf92d6 100644
--- a/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/EventConstants.java
+++ b/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/EventConstants.java
@@ -157,6 +157,9 @@
     public static final String KEYBOARD_ACCESSORY_PAYMENT_AUTOFILLED =
             "keyboard_accessory_payment_suggestion_accepted";
 
+    /** The keyboard accessory was swiped to reveal more suggestions. */
+    public static final String KEYBOARD_ACCESSORY_BAR_SWIPED = "keyboard_accessory_bar_swiped";
+
     /** The Explore Sites tile was tapped. */
     public static final String EXPLORE_SITES_TILE_TAPPED = "explore_sites_tile_tapped";
 
diff --git a/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/FeatureConstants.java b/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/FeatureConstants.java
index 548bbd38..ffbfabf3 100644
--- a/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/FeatureConstants.java
+++ b/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/FeatureConstants.java
@@ -57,6 +57,7 @@
     String KEYBOARD_ACCESSORY_ADDRESS_FILL_FEATURE = "IPH_KeyboardAccessoryAddressFilling";
     String KEYBOARD_ACCESSORY_PASSWORD_FILLING_FEATURE = "IPH_KeyboardAccessoryPasswordFilling";
     String KEYBOARD_ACCESSORY_PAYMENT_FILLING_FEATURE = "IPH_KeyboardAccessoryPaymentFilling";
+    String KEYBOARD_ACCESSORY_BAR_SWIPING_FEATURE = "IPH_KeyboardAccessoryBarSwiping";
     String PREVIEWS_OMNIBOX_UI_FEATURE = "IPH_PreviewsOmniboxUI";
     String TRANSLATE_MENU_BUTTON_FEATURE = "IPH_TranslateMenuButton";
     String EXPLORE_SITES_TILE_FEATURE = "IPH_ExploreSitesTile";
diff --git a/components/feature_engagement/public/feature_constants.cc b/components/feature_engagement/public/feature_constants.cc
index 9d211f2..952866f 100644
--- a/components/feature_engagement/public/feature_constants.cc
+++ b/components/feature_engagement/public/feature_constants.cc
@@ -83,6 +83,8 @@
                                             base::FEATURE_DISABLED_BY_DEFAULT};
 const base::Feature kIPHKeyboardAccessoryAddressFillingFeature{
     "IPH_KeyboardAccessoryAddressFilling", base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kIPHKeyboardAccessoryBarSwipingFeature{
+    "IPH_KeyboardAccessoryBarSwiping", base::FEATURE_DISABLED_BY_DEFAULT};
 const base::Feature kIPHKeyboardAccessoryPasswordFillingFeature{
     "IPH_KeyboardAccessoryPasswordFilling", base::FEATURE_DISABLED_BY_DEFAULT};
 const base::Feature kIPHKeyboardAccessoryPaymentFillingFeature{
diff --git a/components/feature_engagement/public/feature_constants.h b/components/feature_engagement/public/feature_constants.h
index 252ac56e..cb86cccd 100644
--- a/components/feature_engagement/public/feature_constants.h
+++ b/components/feature_engagement/public/feature_constants.h
@@ -58,6 +58,7 @@
 extern const base::Feature kIPHHomepagePromoCardFeature;
 extern const base::Feature kIPHIdentityDiscFeature;
 extern const base::Feature kIPHKeyboardAccessoryAddressFillingFeature;
+extern const base::Feature kIPHKeyboardAccessoryBarSwipingFeature;
 extern const base::Feature kIPHKeyboardAccessoryPasswordFillingFeature;
 extern const base::Feature kIPHKeyboardAccessoryPaymentFillingFeature;
 extern const base::Feature kIPHNewTabPageButtonFeature;
diff --git a/components/feature_engagement/public/feature_list.cc b/components/feature_engagement/public/feature_list.cc
index 4393eb7e..988b29b 100644
--- a/components/feature_engagement/public/feature_list.cc
+++ b/components/feature_engagement/public/feature_list.cc
@@ -42,6 +42,7 @@
     &kIPHHomepagePromoCardFeature,
     &kIPHIdentityDiscFeature,
     &kIPHKeyboardAccessoryAddressFillingFeature,
+    &kIPHKeyboardAccessoryBarSwipingFeature,
     &kIPHKeyboardAccessoryPasswordFillingFeature,
     &kIPHKeyboardAccessoryPaymentFillingFeature,
     &kIPHPreviewsOmniboxUIFeature,
diff --git a/components/feature_engagement/public/feature_list.h b/components/feature_engagement/public/feature_list.h
index 5897b41c..9ab05a7 100644
--- a/components/feature_engagement/public/feature_list.h
+++ b/components/feature_engagement/public/feature_list.h
@@ -87,6 +87,8 @@
 DEFINE_VARIATION_PARAM(kIPHIdentityDiscFeature, "IPH_IdentityDisc");
 DEFINE_VARIATION_PARAM(kIPHKeyboardAccessoryAddressFillingFeature,
                        "IPH_KeyboardAccessoryAddressFilling");
+DEFINE_VARIATION_PARAM(kIPHKeyboardAccessoryBarSwipingFeature,
+                       "IPH_KeyboardAccessoryBarSwiping");
 DEFINE_VARIATION_PARAM(kIPHKeyboardAccessoryPasswordFillingFeature,
                        "IPH_KeyboardAccessoryPasswordFilling");
 DEFINE_VARIATION_PARAM(kIPHKeyboardAccessoryPaymentFillingFeature,
@@ -172,6 +174,7 @@
         VARIATION_ENTRY(kIPHHomepagePromoCardFeature),
         VARIATION_ENTRY(kIPHIdentityDiscFeature),
         VARIATION_ENTRY(kIPHKeyboardAccessoryAddressFillingFeature),
+        VARIATION_ENTRY(kIPHKeyboardAccessoryBarSwipingFeature),
         VARIATION_ENTRY(kIPHKeyboardAccessoryPasswordFillingFeature),
         VARIATION_ENTRY(kIPHKeyboardAccessoryPaymentFillingFeature),
         VARIATION_ENTRY(kIPHPreviewsOmniboxUIFeature),
diff --git a/components/federated_learning/BUILD.gn b/components/federated_learning/BUILD.gn
index 406236f..c87dc27 100644
--- a/components/federated_learning/BUILD.gn
+++ b/components/federated_learning/BUILD.gn
@@ -10,6 +10,8 @@
     "floc_constants.h",
     "floc_id.cc",
     "floc_id.h",
+    "floc_sorting_lsh_clusters_service.cc",
+    "floc_sorting_lsh_clusters_service.h",
     "sim_hash.cc",
     "sim_hash.h",
   ]
@@ -26,6 +28,7 @@
   testonly = true
   sources = [
     "floc_blocklist_service_unittest.cc",
+    "floc_sorting_lsh_clusters_service_unittest.cc",
     "sim_hash_unittest.cc",
   ]
   deps = [
diff --git a/components/federated_learning/floc_constants.cc b/components/federated_learning/floc_constants.cc
index 4d3b1cf..6d1e77b1 100644
--- a/components/federated_learning/floc_constants.cc
+++ b/components/federated_learning/floc_constants.cc
@@ -6,17 +6,25 @@
 
 namespace federated_learning {
 
-const char kManifestBlocklistFormatKey[] = "blocklist_format";
+// This is only for experimentation and won't be served to websites.
+const uint8_t kMaxNumberOfBitsInFloc = 50;
+static_assert(kMaxNumberOfBitsInFloc > 0 &&
+                  kMaxNumberOfBitsInFloc <=
+                      std::numeric_limits<uint64_t>::digits,
+              "Number of bits in the floc id must be greater than 0 and no "
+              "greater than 64.");
 
-const int kCurrentBlocklistFormatVersion = 1;
+const char kManifestFlocComponentFormatKey[] = "floc_component_format";
+
+const int kCurrentFlocComponentFormatVersion = 1;
 
 const base::FilePath::CharType kTopLevelDirectoryName[] =
     FILE_PATH_LITERAL("Floc");
 
-const base::FilePath::CharType kBlocklistBaseDirectoryName[] =
-    FILE_PATH_LITERAL("Blocklist");
-
 const base::FilePath::CharType kBlocklistFileName[] =
     FILE_PATH_LITERAL("Blocklist");
 
+const base::FilePath::CharType kSortingLshClustersFileName[] =
+    FILE_PATH_LITERAL("SortingLshClusters");
+
 }  // namespace federated_learning
diff --git a/components/federated_learning/floc_constants.h b/components/federated_learning/floc_constants.h
index 50fbd5b..6d9ef09 100644
--- a/components/federated_learning/floc_constants.h
+++ b/components/federated_learning/floc_constants.h
@@ -9,9 +9,11 @@
 
 namespace federated_learning {
 
-extern const char kManifestBlocklistFormatKey[];
+extern const uint8_t kMaxNumberOfBitsInFloc;
 
-extern const int kCurrentBlocklistFormatVersion;
+extern const char kManifestFlocComponentFormatKey[];
+
+extern const int kCurrentFlocComponentFormatVersion;
 
 // The name of the top-level directory under the user data directory that
 // contains all files and subdirectories related to the floc.
@@ -20,16 +22,12 @@
 // Paths under |kTopLevelDirectoryName|
 // ------------------------------------
 
-// The name of the subdirectory under the top-level directory that stores
-// blocklist downloaded through the component updater.
-extern const base::FilePath::CharType kBlocklistBaseDirectoryName[];
-
-// Paths under kBlocklistBaseDirectoryName
-// ---------------------------------------
-
 // The name of the file that stores the blocklist.
 extern const base::FilePath::CharType kBlocklistFileName[];
 
+// The name of the file that stores the sorting-lsh clusters.
+extern const base::FilePath::CharType kSortingLshClustersFileName[];
+
 }  // namespace federated_learning
 
 #endif  // COMPONENTS_FEDERATED_LEARNING_FLOC_CONSTANTS_H_
\ No newline at end of file
diff --git a/components/federated_learning/floc_id.cc b/components/federated_learning/floc_id.cc
index 7e425fa..423e2e6 100644
--- a/components/federated_learning/floc_id.cc
+++ b/components/federated_learning/floc_id.cc
@@ -6,6 +6,7 @@
 
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
+#include "components/federated_learning/floc_constants.h"
 #include "components/federated_learning/sim_hash.h"
 
 namespace federated_learning {
@@ -14,19 +15,12 @@
 
 constexpr char kFlocVersion[] = "1.0.0";
 
-// This is only for experimentation and won't be served to websites.
-constexpr size_t kNumberOfBitsInFloc = 50;
-static_assert(kNumberOfBitsInFloc > 0 &&
-                  kNumberOfBitsInFloc <= std::numeric_limits<uint64_t>::digits,
-              "Number of bits in the floc id must be greater than 0 and no "
-              "greater than 64.");
-
 }  // namespace
 
 // static
 FlocId FlocId::CreateFromHistory(
     const std::unordered_set<std::string>& domains) {
-  return FlocId(SimHashStrings(domains, kNumberOfBitsInFloc));
+  return FlocId(SimHashStrings(domains, kMaxNumberOfBitsInFloc));
 }
 
 FlocId::FlocId() = default;
diff --git a/components/federated_learning/floc_sorting_lsh_clusters_service.cc b/components/federated_learning/floc_sorting_lsh_clusters_service.cc
new file mode 100644
index 0000000..e9bc065
--- /dev/null
+++ b/components/federated_learning/floc_sorting_lsh_clusters_service.cc
@@ -0,0 +1,144 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/federated_learning/floc_sorting_lsh_clusters_service.h"
+
+#include <utility>
+
+#include "base/files/file.h"
+#include "base/files/file_util.h"
+#include "base/task/post_task.h"
+#include "base/task_runner_util.h"
+#include "components/federated_learning/floc_constants.h"
+#include "third_party/protobuf/src/google/protobuf/io/coded_stream.h"
+#include "third_party/protobuf/src/google/protobuf/io/zero_copy_stream_impl_lite.h"
+
+namespace federated_learning {
+
+namespace {
+
+class CopyingFileInputStream : public google::protobuf::io::CopyingInputStream {
+ public:
+  explicit CopyingFileInputStream(base::File file) : file_(std::move(file)) {}
+
+  CopyingFileInputStream(const CopyingFileInputStream&) = delete;
+  CopyingFileInputStream& operator=(const CopyingFileInputStream&) = delete;
+
+  ~CopyingFileInputStream() override = default;
+
+  // google::protobuf::io::CopyingInputStream:
+  int Read(void* buffer, int size) override {
+    return file_.ReadAtCurrentPosNoBestEffort(static_cast<char*>(buffer), size);
+  }
+
+ private:
+  base::File file_;
+};
+
+FlocId ApplySortingLshOnBackgroundThread(const FlocId& raw_floc_id,
+                                         const base::FilePath& file_path) {
+  DCHECK(raw_floc_id.IsValid());
+  base::File sorting_lsh_clusters_file(
+      file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
+  if (!sorting_lsh_clusters_file.IsValid())
+    return FlocId();
+
+  CopyingFileInputStream copying_stream(std::move(sorting_lsh_clusters_file));
+  google::protobuf::io::CopyingInputStreamAdaptor zero_copy_stream_adaptor(
+      &copying_stream);
+
+  google::protobuf::io::CodedInputStream input_stream(
+      &zero_copy_stream_adaptor);
+
+  // The file should contain a list of integers within the range [0,
+  // MaxNumberOfBitsInFloc]. Suppose the list is l, then 2^(l[i]) represents the
+  // the number of hashes that can be associated with this floc id. The
+  // cumulative sum of 2^(l[i]) represents the boundary floc values in
+  // |raw_floc_id|'s space. We will use the higher index to encode
+  // |raw_floc_id|, i.e. if raw_floc_id is within range
+  // [CumSum(2^(l[i-1])), CumSum(2^(l[i]))), |i| will be output floc.
+  //
+  // 0 is always an implicit CumSum boundary, i.e. if
+  // 0 <= |raw_floc_id| < 2^(l[0]), then the index 0 will be the output floc.
+  //
+  // Input sanitization: As we compute on the fly, we will check to make sure
+  // each encountered entry is within [0, MaxNumberOfBitsInFloc]. Besides, the
+  // cumulative sum should be no greater than 2^MaxNumberOfBitsInFloc at any
+  // given time. If we cannot find an index i, it means the the final cumulative
+  // sum is less than 2^MaxNumberOfBitsInFloc, while we expect it to be exactly
+  // 2^MaxNumberOfBitsInFloc, and we should also fail in this case. When some
+  // check fails, we will output an invalid floc id.
+  //
+  // A stricter sanitization would be to always stream all numbers and check
+  // properties. We skip doing this to save some computation cost.
+  uint64_t raw_floc_id_as_int = raw_floc_id.ToUint64();
+  const uint64_t kExpectedFinalCumulativeSum = (1ULL << kMaxNumberOfBitsInFloc);
+  DCHECK(raw_floc_id_as_int < kExpectedFinalCumulativeSum);
+
+  uint64_t cumulative_sum = 0;
+  uint32_t next;
+
+  // TODO: Add metrics for when we return an invalid floc, which indicates a
+  // wrong/corrupted file.
+
+  for (uint64_t index = 0; input_stream.ReadVarint32(&next); ++index) {
+    if (next > kMaxNumberOfBitsInFloc)
+      return FlocId();
+
+    cumulative_sum += (1ULL << next);
+
+    if (cumulative_sum > kExpectedFinalCumulativeSum)
+      return FlocId();
+
+    if (cumulative_sum > raw_floc_id_as_int)
+      return FlocId(index);
+  }
+
+  return FlocId();
+}
+
+}  // namespace
+
+FlocSortingLshClustersService::FlocSortingLshClustersService()
+    : background_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
+          {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+           base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})),
+      weak_ptr_factory_(this) {}
+
+FlocSortingLshClustersService::~FlocSortingLshClustersService() = default;
+
+void FlocSortingLshClustersService::AddObserver(Observer* observer) {
+  observers_.AddObserver(observer);
+}
+
+void FlocSortingLshClustersService::RemoveObserver(Observer* observer) {
+  observers_.RemoveObserver(observer);
+}
+
+void FlocSortingLshClustersService::SetBackgroundTaskRunnerForTesting(
+    scoped_refptr<base::SequencedTaskRunner> background_task_runner) {
+  background_task_runner_ = background_task_runner;
+}
+
+void FlocSortingLshClustersService::ApplySortingLsh(
+    const FlocId& raw_floc_id,
+    ApplySortingLshCallback callback) {
+  DCHECK(raw_floc_id.IsValid());
+  DCHECK(sorting_lsh_clusters_file_path_.has_value());
+  base::PostTaskAndReplyWithResult(
+      background_task_runner_.get(), FROM_HERE,
+      base::BindOnce(&ApplySortingLshOnBackgroundThread, raw_floc_id,
+                     sorting_lsh_clusters_file_path_.value()),
+      std::move(callback));
+}
+
+void FlocSortingLshClustersService::OnSortingLshClustersFileReady(
+    const base::FilePath& file_path) {
+  sorting_lsh_clusters_file_path_ = file_path;
+
+  for (auto& observer : observers_)
+    observer.OnSortingLshClustersFileReady();
+}
+
+}  // namespace federated_learning
diff --git a/components/federated_learning/floc_sorting_lsh_clusters_service.h b/components/federated_learning/floc_sorting_lsh_clusters_service.h
new file mode 100644
index 0000000..8db8863
--- /dev/null
+++ b/components/federated_learning/floc_sorting_lsh_clusters_service.h
@@ -0,0 +1,73 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_FEDERATED_LEARNING_FLOC_SORTING_LSH_CLUSTERS_SERVICE_H_
+#define COMPONENTS_FEDERATED_LEARNING_FLOC_SORTING_LSH_CLUSTERS_SERVICE_H_
+
+#include <stdint.h>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/optional.h"
+#include "components/federated_learning/floc_id.h"
+
+namespace base {
+class FilePath;
+class SequencedTaskRunner;
+}  // namespace base
+
+namespace federated_learning {
+
+// Responsible for loading the sorting-lsh clusters with custom encoding and
+// and calculating the sorting-lsh based floc.
+//
+// File reading and parsing is posted to |background_task_runner_|.
+class FlocSortingLshClustersService {
+ public:
+  using ApplySortingLshCallback = base::OnceCallback<void(FlocId)>;
+
+  class Observer {
+   public:
+    virtual void OnSortingLshClustersFileReady() = 0;
+  };
+
+  FlocSortingLshClustersService();
+  virtual ~FlocSortingLshClustersService();
+
+  FlocSortingLshClustersService(const FlocSortingLshClustersService&) = delete;
+  FlocSortingLshClustersService& operator=(
+      const FlocSortingLshClustersService&) = delete;
+
+  // Adds/Removes an Observer.
+  void AddObserver(Observer* observer);
+  void RemoveObserver(Observer* observer);
+
+  void SetBackgroundTaskRunnerForTesting(
+      scoped_refptr<base::SequencedTaskRunner> background_task_runner);
+
+  void ApplySortingLsh(const FlocId& raw_floc_id,
+                       ApplySortingLshCallback callback);
+
+  // Virtual for testing.
+  virtual void OnSortingLshClustersFileReady(const base::FilePath& file_path);
+
+ private:
+  friend class FlocSortingLshClustersServiceTest;
+
+  // Runner for tasks that do not influence user experience.
+  scoped_refptr<base::SequencedTaskRunner> background_task_runner_;
+
+  base::ObserverList<Observer>::Unchecked observers_;
+
+  base::Optional<base::FilePath> sorting_lsh_clusters_file_path_;
+
+  base::WeakPtrFactory<FlocSortingLshClustersService> weak_ptr_factory_;
+};
+
+}  // namespace federated_learning
+
+#endif  // COMPONENTS_FEDERATED_LEARNING_FLOC_SORTING_LSH_CLUSTERS_SERVICE_H_
diff --git a/components/federated_learning/floc_sorting_lsh_clusters_service_unittest.cc b/components/federated_learning/floc_sorting_lsh_clusters_service_unittest.cc
new file mode 100644
index 0000000..82b6640
--- /dev/null
+++ b/components/federated_learning/floc_sorting_lsh_clusters_service_unittest.cc
@@ -0,0 +1,238 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/federated_learning/floc_sorting_lsh_clusters_service.h"
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/task_environment.h"
+#include "base/test/test_simple_task_runner.h"
+#include "components/federated_learning/floc_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/protobuf/src/google/protobuf/io/coded_stream.h"
+#include "third_party/protobuf/src/google/protobuf/io/zero_copy_stream_impl_lite.h"
+
+namespace federated_learning {
+
+namespace {
+
+class CopyingFileOutputStream
+    : public google::protobuf::io::CopyingOutputStream {
+ public:
+  explicit CopyingFileOutputStream(base::File file) : file_(std::move(file)) {}
+
+  CopyingFileOutputStream(const CopyingFileOutputStream&) = delete;
+  CopyingFileOutputStream& operator=(const CopyingFileOutputStream&) = delete;
+
+  ~CopyingFileOutputStream() override = default;
+
+  // google::protobuf::io::CopyingOutputStream:
+  bool Write(const void* buffer, int size) override {
+    return file_.WriteAtCurrentPos(static_cast<const char*>(buffer), size) ==
+           size;
+  }
+
+ private:
+  base::File file_;
+};
+
+}  // namespace
+
+class FlocSortingLshClustersServiceTest : public ::testing::Test {
+ public:
+  FlocSortingLshClustersServiceTest()
+      : background_task_runner_(
+            base::MakeRefCounted<base::TestSimpleTaskRunner>()) {}
+
+  FlocSortingLshClustersServiceTest(const FlocSortingLshClustersServiceTest&) =
+      delete;
+  FlocSortingLshClustersServiceTest& operator=(
+      const FlocSortingLshClustersServiceTest&) = delete;
+
+ protected:
+  void SetUp() override {
+    service_ = std::make_unique<FlocSortingLshClustersService>();
+    service_->SetBackgroundTaskRunnerForTesting(background_task_runner_);
+  }
+
+  base::FilePath GetUniqueTemporaryPath() {
+    CHECK(scoped_temp_dir_.IsValid() || scoped_temp_dir_.CreateUniqueTempDir());
+    return scoped_temp_dir_.GetPath().AppendASCII(
+        base::NumberToString(next_unique_file_suffix_++));
+  }
+
+  base::FilePath CreateTestSortingLshClustersFile(
+      const std::vector<uint32_t>& sorting_lsh_clusters) {
+    base::FilePath file_path = GetUniqueTemporaryPath();
+    base::File file(file_path, base::File::FLAG_CREATE | base::File::FLAG_READ |
+                                   base::File::FLAG_WRITE);
+    CHECK(file.IsValid());
+
+    CopyingFileOutputStream copying_stream(std::move(file));
+    google::protobuf::io::CopyingOutputStreamAdaptor zero_copy_stream_adaptor(
+        &copying_stream);
+
+    google::protobuf::io::CodedOutputStream output_stream(
+        &zero_copy_stream_adaptor);
+
+    for (uint32_t next : sorting_lsh_clusters)
+      output_stream.WriteVarint32(next);
+
+    CHECK(!output_stream.HadError());
+
+    return file_path;
+  }
+
+  base::FilePath InitializeSortingLshClustersFile(
+      const std::vector<uint32_t>& sorting_lsh_clusters) {
+    base::FilePath file_path =
+        CreateTestSortingLshClustersFile(sorting_lsh_clusters);
+    service()->OnSortingLshClustersFileReady(file_path);
+    EXPECT_TRUE(sorting_lsh_clusters_file_path().has_value());
+    return file_path;
+  }
+
+  FlocId MaxFlocId() { return FlocId((1ULL << kMaxNumberOfBitsInFloc) - 1); }
+
+  FlocSortingLshClustersService* service() { return service_.get(); }
+
+  const base::Optional<base::FilePath>& sorting_lsh_clusters_file_path() {
+    return service()->sorting_lsh_clusters_file_path_;
+  }
+
+  FlocId ApplySortingLsh(const FlocId& floc_id) {
+    FlocId result;
+
+    base::RunLoop run_loop;
+    auto cb = base::BindLambdaForTesting([&](FlocId floc_id) {
+      result = floc_id;
+      run_loop.Quit();
+    });
+
+    service()->ApplySortingLsh(floc_id, std::move(cb));
+    background_task_runner_->RunPendingTasks();
+    run_loop.Run();
+
+    return result;
+  }
+
+ protected:
+  base::test::TaskEnvironment task_environment_;
+  base::ScopedTempDir scoped_temp_dir_;
+  int next_unique_file_suffix_ = 1;
+
+  scoped_refptr<base::TestSimpleTaskRunner> background_task_runner_;
+
+  std::unique_ptr<FlocSortingLshClustersService> service_;
+};
+
+TEST_F(FlocSortingLshClustersServiceTest, NoFilePath) {
+  EXPECT_FALSE(sorting_lsh_clusters_file_path().has_value());
+}
+
+TEST_F(FlocSortingLshClustersServiceTest, EmptyList) {
+  InitializeSortingLshClustersFile({});
+  EXPECT_EQ(FlocId(), ApplySortingLsh(FlocId(0)));
+  EXPECT_EQ(FlocId(), ApplySortingLsh(FlocId(1)));
+  EXPECT_EQ(FlocId(), ApplySortingLsh(MaxFlocId()));
+}
+
+TEST_F(FlocSortingLshClustersServiceTest, List_0) {
+  InitializeSortingLshClustersFile({0});
+
+  EXPECT_EQ(FlocId(0), ApplySortingLsh(FlocId(0)));
+  EXPECT_EQ(FlocId(), ApplySortingLsh(FlocId(1)));
+  EXPECT_EQ(FlocId(), ApplySortingLsh(MaxFlocId()));
+}
+
+TEST_F(FlocSortingLshClustersServiceTest, List_1) {
+  InitializeSortingLshClustersFile({1});
+
+  EXPECT_EQ(FlocId(0), ApplySortingLsh(FlocId(0)));
+  EXPECT_EQ(FlocId(0), ApplySortingLsh(FlocId(1)));
+  EXPECT_EQ(FlocId(), ApplySortingLsh(FlocId(2)));
+  EXPECT_EQ(FlocId(), ApplySortingLsh(MaxFlocId()));
+}
+
+TEST_F(FlocSortingLshClustersServiceTest, List_0_0) {
+  InitializeSortingLshClustersFile({0, 0});
+
+  EXPECT_EQ(FlocId(0), ApplySortingLsh(FlocId(0)));
+  EXPECT_EQ(FlocId(1), ApplySortingLsh(FlocId(1)));
+  EXPECT_EQ(FlocId(), ApplySortingLsh(FlocId(2)));
+  EXPECT_EQ(FlocId(), ApplySortingLsh(MaxFlocId()));
+}
+
+TEST_F(FlocSortingLshClustersServiceTest, List_0_1) {
+  InitializeSortingLshClustersFile({0, 1});
+
+  EXPECT_EQ(FlocId(0), ApplySortingLsh(FlocId(0)));
+  EXPECT_EQ(FlocId(1), ApplySortingLsh(FlocId(1)));
+  EXPECT_EQ(FlocId(1), ApplySortingLsh(FlocId(2)));
+  EXPECT_EQ(FlocId(), ApplySortingLsh(FlocId(3)));
+  EXPECT_EQ(FlocId(), ApplySortingLsh(MaxFlocId()));
+}
+
+TEST_F(FlocSortingLshClustersServiceTest, List_1_0) {
+  InitializeSortingLshClustersFile({1, 0});
+
+  EXPECT_EQ(FlocId(0), ApplySortingLsh(FlocId(0)));
+  EXPECT_EQ(FlocId(0), ApplySortingLsh(FlocId(1)));
+  EXPECT_EQ(FlocId(1), ApplySortingLsh(FlocId(2)));
+  EXPECT_EQ(FlocId(), ApplySortingLsh(FlocId(3)));
+  EXPECT_EQ(FlocId(), ApplySortingLsh(MaxFlocId()));
+}
+
+TEST_F(FlocSortingLshClustersServiceTest, List_SingleCluster) {
+  InitializeSortingLshClustersFile({kMaxNumberOfBitsInFloc});
+  EXPECT_EQ(FlocId(0), ApplySortingLsh(FlocId(0)));
+  EXPECT_EQ(FlocId(0), ApplySortingLsh(FlocId(1)));
+  EXPECT_EQ(FlocId(0), ApplySortingLsh(FlocId(12345)));
+  EXPECT_EQ(FlocId(0), ApplySortingLsh(MaxFlocId()));
+}
+
+TEST_F(FlocSortingLshClustersServiceTest, List_TwoClustersEqualSize) {
+  InitializeSortingLshClustersFile(
+      {kMaxNumberOfBitsInFloc - 1, kMaxNumberOfBitsInFloc - 1});
+
+  uint64_t middle_value = (1ULL << (kMaxNumberOfBitsInFloc - 1));
+  EXPECT_EQ(FlocId(0), ApplySortingLsh(FlocId(0)));
+  EXPECT_EQ(FlocId(0), ApplySortingLsh(FlocId(1)));
+  EXPECT_EQ(FlocId(0), ApplySortingLsh(FlocId(middle_value - 1)));
+  EXPECT_EQ(FlocId(1), ApplySortingLsh(FlocId(middle_value)));
+  EXPECT_EQ(FlocId(1), ApplySortingLsh(FlocId(middle_value + 1)));
+  EXPECT_EQ(FlocId(1), ApplySortingLsh(MaxFlocId()));
+}
+
+TEST_F(FlocSortingLshClustersServiceTest,
+       FileDeletedAfterSortingLshTaskScheduled) {
+  base::FilePath file_path = InitializeSortingLshClustersFile({0});
+
+  base::RunLoop run_loop;
+  auto cb = base::BindLambdaForTesting([&](FlocId floc_id) {
+    // Since the file has been deleted, expect an invalid floc id.
+    EXPECT_EQ(FlocId(), floc_id);
+    run_loop.Quit();
+  });
+
+  service()->ApplySortingLsh(FlocId(0), std::move(cb));
+  base::DeleteFile(file_path);
+
+  background_task_runner_->RunPendingTasks();
+  run_loop.Run();
+}
+
+TEST_F(FlocSortingLshClustersServiceTest, MultipleUpdate_LatestOneUsed) {
+  InitializeSortingLshClustersFile({});
+  InitializeSortingLshClustersFile({0});
+  EXPECT_EQ(FlocId(0), ApplySortingLsh(FlocId(0)));
+}
+
+}  // namespace federated_learning
diff --git a/components/keyed_service/content/browser_context_dependency_manager.h b/components/keyed_service/content/browser_context_dependency_manager.h
index 18440bb..2f2bec56 100644
--- a/components/keyed_service/content/browser_context_dependency_manager.h
+++ b/components/keyed_service/content/browser_context_dependency_manager.h
@@ -9,6 +9,7 @@
 
 #include "base/callback_forward.h"
 #include "base/callback_list.h"
+#include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "components/keyed_service/core/dependency_manager.h"
 #include "components/keyed_service/core/keyed_service_export.h"
@@ -67,7 +68,7 @@
   // builders for the keyed services.
   std::unique_ptr<CreateServicesCallbackList::Subscription>
   RegisterCreateServicesCallbackForTesting(
-      const CreateServicesCallback& callback);
+      const CreateServicesCallback& callback) WARN_UNUSED_RESULT;
 
   // Runtime assertion called as a part of GetServiceForBrowserContext() to
   // check if |context| is considered stale. This will NOTREACHED() or
diff --git a/components/omnibox/browser/omnibox_field_trial.cc b/components/omnibox/browser/omnibox_field_trial.cc
index 215d526..928c2c1 100644
--- a/components/omnibox/browser/omnibox_field_trial.cc
+++ b/components/omnibox/browser/omnibox_field_trial.cc
@@ -1035,6 +1035,15 @@
     OmniboxFieldTrial::kEntitySuggestionsReduceLatencyDecoderWakeupParam[] =
         "EntitySuggestionsReduceLatencyDecoderWakeup";
 
+extern const char OmniboxFieldTrial::kBookmarkPathsUiReplaceTitle[] =
+    "OmniboxBookmarkPathsUiReplaceTitle";
+extern const char OmniboxFieldTrial::kBookmarkPathsUiReplaceUrl[] =
+    "OmniboxBookmarkPathsUiReplaceUrl";
+extern const char OmniboxFieldTrial::kBookmarkPathsUiAppendAfterTitle[] =
+    "OmniboxBookmarkPathsUiAppendAfterTitle";
+extern const char OmniboxFieldTrial::kBookmarkPathsUiDynamicReplaceUrl[] =
+    "OmniboxBookmarkPathsUiDynamicReplaceUrl";
+
 std::string OmniboxFieldTrial::internal::GetValueForRuleInContext(
     const std::string& rule,
     OmniboxEventProto::PageClassification page_classification) {
diff --git a/components/omnibox/browser/omnibox_field_trial.h b/components/omnibox/browser/omnibox_field_trial.h
index 09ef37d..0da99ca 100644
--- a/components/omnibox/browser/omnibox_field_trial.h
+++ b/components/omnibox/browser/omnibox_field_trial.h
@@ -575,6 +575,13 @@
 extern const char kEntitySuggestionsReduceLatencyDecoderTimeoutParam[];
 extern const char kEntitySuggestionsReduceLatencyDecoderWakeupParam[];
 
+// Parameter names used for bookmark path variations that determine whether
+// bookmark suggestion texts will contain the title, URL, and/or path.
+extern const char kBookmarkPathsUiReplaceTitle[];
+extern const char kBookmarkPathsUiReplaceUrl[];
+extern const char kBookmarkPathsUiAppendAfterTitle[];
+extern const char kBookmarkPathsUiDynamicReplaceUrl[];
+
 namespace internal {
 // The bundled omnibox experiment comes with a set of parameters
 // (key-value pairs).  Each key indicates a certain rule that applies in
diff --git a/components/omnibox/common/omnibox_features.cc b/components/omnibox/common/omnibox_features.cc
index ddf350b..20404974 100644
--- a/components/omnibox/common/omnibox_features.cc
+++ b/components/omnibox/common/omnibox_features.cc
@@ -304,6 +304,13 @@
     "OmniboxHistoryQuickProviderAllowMidwordContinuations",
     base::FEATURE_ENABLED_BY_DEFAULT};
 
+// If enabled, inputs may match bookmark paths. These path matches won't
+// contribute to scoring. E.g. 'planets jupiter' can suggest a bookmark titled
+// 'Jupiter' with URL 'en.wikipedia.org/wiki/Jupiter' located in a path
+// containing 'planet.'
+const base::Feature kBookmarkPaths{"OmniboxBookmarkPaths",
+                                   base::FEATURE_DISABLED_BY_DEFAULT};
+
 // If enabled, shows slightly more compact suggestions, allowing the
 // kAdaptiveSuggestionsCount feature to fit more suggestions on screen.
 const base::Feature kCompactSuggestions{"OmniboxCompactSuggestions",
diff --git a/components/omnibox/common/omnibox_features.h b/components/omnibox/common/omnibox_features.h
index fb1e0dff..639b76fe 100644
--- a/components/omnibox/common/omnibox_features.h
+++ b/components/omnibox/common/omnibox_features.h
@@ -82,6 +82,7 @@
 
 // Suggestions UI - these affect the UI or function of the suggestions popup.
 extern const base::Feature kAdaptiveSuggestionsCount;
+extern const base::Feature kBookmarkPaths;
 extern const base::Feature kCompactSuggestions;
 extern const base::Feature kDeferredKeyboardPopup;
 extern const base::Feature kMostVisitedTiles;
diff --git a/components/os_crypt/BUILD.gn b/components/os_crypt/BUILD.gn
index 380c8a7..e2321183 100644
--- a/components/os_crypt/BUILD.gn
+++ b/components/os_crypt/BUILD.gn
@@ -49,8 +49,7 @@
 
   defines = [ "IS_OS_CRYPT_IMPL" ]
 
-  if ((is_posix || is_fuchsia) && !is_apple &&
-      (!is_desktop_linux || is_chromecast)) {
+  if ((is_posix || is_fuchsia) && !is_apple && (!is_linux || is_chromecast)) {
     sources += [ "os_crypt_posix.cc" ]
   }
 
@@ -67,7 +66,7 @@
     libs = [ "crypt32.lib" ]
   }
 
-  if (is_desktop_linux && !is_chromecast) {
+  if (is_linux && !is_chromecast) {
     sources += [
       "key_storage_config_linux.cc",
       "key_storage_config_linux.h",
@@ -123,7 +122,7 @@
     "//base",
     "//testing/gtest",
   ]
-  if (is_desktop_linux && !is_chromecast) {
+  if (is_linux && !is_chromecast) {
     sources += [
       "os_crypt_mocker_linux.cc",
       "os_crypt_mocker_linux.h",
@@ -160,7 +159,7 @@
     sources += [ "keychain_password_mac_unittest.mm" ]
   }
 
-  if (is_desktop_linux && !is_chromecast) {
+  if (is_linux && !is_chromecast) {
     sources += [
       "key_storage_linux_unittest.cc",
       "key_storage_util_linux_unittest.cc",
diff --git a/components/os_crypt/features.gni b/components/os_crypt/features.gni
index f67c8b97..e9f9acf 100644
--- a/components/os_crypt/features.gni
+++ b/components/os_crypt/features.gni
@@ -7,5 +7,5 @@
 declare_args() {
   # Whether to use libgnome-keyring (deprecated by libsecret).
   # See http://crbug.com/466975 and http://crbug.com/355223.
-  use_gnome_keyring = is_desktop_linux && use_glib
+  use_gnome_keyring = is_linux && use_glib
 }
diff --git a/components/page_info_strings.grdp b/components/page_info_strings.grdp
index 15c90b87..e4f6268b 100644
--- a/components/page_info_strings.grdp
+++ b/components/page_info_strings.grdp
@@ -318,7 +318,7 @@
       Window placement
     </message>
     <message name="IDS_PAGE_INFO_TYPE_FONT_ACCESS" desc="The label used for the Font Access permission controls in the Page Info popup.">
-      Local font access
+      Fonts
     </message>
     <message name="IDS_PAGE_INFO_TYPE_HID" desc="The label used for the HID permission controls in the Page Info popup.">
       HID devices
diff --git a/components/page_info_strings_grdp/IDS_PAGE_INFO_TYPE_FONT_ACCESS.png.sha1 b/components/page_info_strings_grdp/IDS_PAGE_INFO_TYPE_FONT_ACCESS.png.sha1
index f3e1535..182eaeb 100644
--- a/components/page_info_strings_grdp/IDS_PAGE_INFO_TYPE_FONT_ACCESS.png.sha1
+++ b/components/page_info_strings_grdp/IDS_PAGE_INFO_TYPE_FONT_ACCESS.png.sha1
@@ -1 +1 @@
-f65007ebf343f1b2fcc7aee10525b367c1620fcf
\ No newline at end of file
+7cf27979a002fb8456031c8d1de5af6670a7316a
\ No newline at end of file
diff --git a/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/frame/PlayerFrameBitmapStateController.java b/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/frame/PlayerFrameBitmapStateController.java
index 3526d42..d4f40b2 100644
--- a/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/frame/PlayerFrameBitmapStateController.java
+++ b/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/frame/PlayerFrameBitmapStateController.java
@@ -50,9 +50,9 @@
                 (mLoadingBitmapState == null) ? mVisibleBitmapState : mLoadingBitmapState;
         if (scaleUpdated || activeLoadingState == null) {
             invalidateLoadingBitmaps();
-            mLoadingBitmapState =
-                    new PlayerFrameBitmapState(mGuid, mViewport.getWidth(), mViewport.getHeight(),
-                            mViewport.getScale(), mContentSize, mCompositorDelegate, this);
+            mLoadingBitmapState = new PlayerFrameBitmapState(mGuid, mViewport.getWidth() / 2,
+                    mViewport.getHeight() / 2, mViewport.getScale(), mContentSize,
+                    mCompositorDelegate, this);
             if (mVisibleBitmapState == null) {
                 mLoadingBitmapState.skipWaitingForVisibleBitmaps();
                 swap(mLoadingBitmapState);
diff --git a/components/paint_preview/player/android/junit/src/org/chromium/components/paintpreview/player/frame/PlayerFrameMediatorTest.java b/components/paint_preview/player/android/junit/src/org/chromium/components/paintpreview/player/frame/PlayerFrameMediatorTest.java
index 5d6c0b97..abfc468 100644
--- a/components/paint_preview/player/android/junit/src/org/chromium/components/paintpreview/player/frame/PlayerFrameMediatorTest.java
+++ b/components/paint_preview/player/android/junit/src/org/chromium/components/paintpreview/player/frame/PlayerFrameMediatorTest.java
@@ -271,7 +271,7 @@
         // columns. Because we set the initial scale factor to view port width over content width,
         // we should have only one column.
         Bitmap[][] bitmapMatrix = mModel.get(PlayerFrameProperties.BITMAP_MATRIX);
-        Assert.assertTrue(Arrays.deepEquals(bitmapMatrix, new Bitmap[2][1]));
+        Assert.assertTrue(Arrays.deepEquals(bitmapMatrix, new Bitmap[4][2]));
         Assert.assertEquals(new ArrayList<Pair<View, Rect>>(),
                 mModel.get(PlayerFrameProperties.SUBFRAME_VIEWS));
     }
@@ -287,16 +287,16 @@
 
         // Requests for bitmaps in all tiles that are visible in the view port as well as their
         // adjacent tiles should've been made.
-        // The current view port fully matches the top left bitmap tile, so we expect requests for
-        // the top left bitmap, one bitmap to its right, and one to its bottom.
+        // The current view port fully matches the top left bitmap tiles, so we expect requests for
+        // the top left bitmaps, plus bitmaps tothe right, and below.
         // Below is a schematic of the entire bitmap matrix. Those marked with number should have
         // been requested, in the order of numbers.
         // -------------------------
-        // | 1 | 3 |   |   |   |   |
+        // | 1 | 3 | 6 |   |   |   |
         // -------------------------
-        // | 2 |   |   |   |   |   |
+        // | 2 | 4 | 8 |   |   |   |
         // -------------------------
-        // |   |   |   |   |   |   |
+        // | 5 | 7 |   |   |   |   |
         // -------------------------
         // |   |   |   |   |   |   |
         // -------------------------
@@ -305,11 +305,21 @@
         // |   |   |   |   |   |   |
         List<RequestedBitmap> expectedRequestedBitmaps = new ArrayList<>();
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 0, 0), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 0, 0), 1f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 1, 0), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 1, 0), 1f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 0, 1), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 0, 1), 1f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 1, 1), 1f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 2, 0), 1f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 0, 2), 1f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 2, 1), 1f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 1, 2), 1f));
         Assert.assertEquals(expectedRequestedBitmaps, mCompositorDelegate.mRequestedBitmap);
 
         mScrollController.scrollBy(10, 20);
@@ -318,77 +328,79 @@
         Rect expectedViewPort = new Rect(10, 20, 110, 220);
         Assert.assertEquals(expectedViewPort, mModel.get(PlayerFrameProperties.VIEWPORT));
 
-        // The current viewport covers portions of the 4 top left bitmap tiles. We have requested
-        // bitmaps for 3 of them before. Make sure requests for the 4th bitmap, as well adjacent
+        // The current viewport covers portions of the top left bitmap tiles. We have requested
+        // bitmaps for 8 of them before. Make sure requests for the 4th bitmap, as well adjacent
         // bitmaps are made.
         // Below is a schematic of the entire bitmap matrix. Those marked with number should have
         // been requested, in the order of numbers.
         // -------------------------
-        // | x | x | 3 |   |   |   |
+        // | x | x | x | 3 |   |   |
         // -------------------------
-        // | x | 1 | 5 |   |   |   |
+        // | x | x | x | 5 |   |   |
         // -------------------------
-        // | 2 | 4 |   |   |   |   |
+        // | x | x | 1 | 7 |   |   |
+        // -------------------------
+        // | 2 | 4 | 6 |   |   |   |
         // -------------------------
         // |   |   |   |   |   |   |
         // -------------------------
         // |   |   |   |   |   |   |
-        // -------------------------
-        // |   |   |   |   |   |   |
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 1, 1), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 2, 2), 1f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 2, 0), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 3, 0), 1f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 0, 2), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 3, 1), 1f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 2, 1), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 0, 3), 1f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 1, 2), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 1, 3), 1f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 3, 2), 1f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 2, 3), 1f));
         Assert.assertEquals(expectedRequestedBitmaps, mCompositorDelegate.mRequestedBitmap);
 
-        // Move the view port slightly. It is still covered by the same 4 tiles. Since there were
+        // Move the view port slightly. It is still covered by the same tiles. Since there were
         // already bitmap requests out for those tiles and their adjacent tiles, we shouldn't have
         // made new requests.
         mScrollController.scrollBy(10, 20);
         Assert.assertEquals(expectedRequestedBitmaps, mCompositorDelegate.mRequestedBitmap);
 
         // Move the view port to the bottom right so it covers portions of the 4 bottom right bitmap
-        // tiles. 4 new bitmap requests should be made.
-        // Below is a schematic of the entire bitmap matrix. Those marked with number should have
-        // been requested, in the order of numbers.
-        // -------------------------
-        // | x | x | x |   |   |   |
-        // -------------------------
-        // | x | x | x |   |   |   |
-        // -------------------------
-        // | x | x |   |   |   |   |
-        // -------------------------
-        // |   |   |   |   | 5 | 8 |
-        // -------------------------
-        // |   |   |   | 6 | 1 | 3 |
-        // -------------------------
-        // |   |   |   | 7 | 2 | 4 |
+        // tiles. New bitmap requests should be made.
         mScrollController.scrollBy(430, 900);
         expectedViewPort.set(450, 940, 550, 1140);
         Assert.assertEquals(expectedViewPort, mModel.get(PlayerFrameProperties.VIEWPORT));
 
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 4, 4), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 9, 9), 1f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 5, 4), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 10, 9), 1f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 4, 5), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 11, 9), 1f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 5, 5), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 9, 10), 1f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 3, 4), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 10, 10), 1f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 4, 3), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 11, 10), 1f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 5, 3), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 8, 9), 1f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 3, 5), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 9, 8), 1f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 10, 8), 1f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 11, 8), 1f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 8, 10), 1f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 9, 11), 1f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 10, 11), 1f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 11, 11), 1f));
         Assert.assertEquals(expectedRequestedBitmaps, mCompositorDelegate.mRequestedBitmap);
     }
 
@@ -402,18 +414,18 @@
         // Initial view port setup.
         mMediator.updateViewportSize(100, 200, 1f);
 
-        boolean[][] expectedRequiredBitmaps = new boolean[6][6];
+        boolean[][] expectedRequiredBitmaps = new boolean[12][12];
 
         // The current view port fully matches the top left bitmap tile.
         // Below is a schematic of the entire bitmap matrix. Tiles marked with x are required for
         // the current view port.
         // -------------------------
+        // | x | x | x |   |   |   |
+        // -------------------------
+        // | x | x | x |   |   |   |
+        // -------------------------
         // | x | x |   |   |   |   |
         // -------------------------
-        // | x |   |   |   |   |   |
-        // -------------------------
-        // |   |   |   |   |   |   |
-        // -------------------------
         // |   |   |   |   |   |   |
         // -------------------------
         // |   |   |   |   |   |   |
@@ -421,25 +433,8 @@
         // |   |   |   |   |   |   |
         expectedRequiredBitmaps[0][0] = true;
         expectedRequiredBitmaps[0][1] = true;
-        expectedRequiredBitmaps[1][0] = true;
-        Assert.assertTrue(Arrays.deepEquals(
-                expectedRequiredBitmaps, getVisibleBitmapState().getRequiredBitmapsForTest()));
-
-        mScrollController.scrollBy(10, 15);
-        // The current viewport covers portions of the 4 top left bitmap tiles.
-        // -------------------------
-        // | x | x | x |   |   |   |
-        // -------------------------
-        // | x | x | x |   |   |   |
-        // -------------------------
-        // | x | x |   |   |   |   |
-        // -------------------------
-        // |   |   |   |   |   |   |
-        // -------------------------
-        // |   |   |   |   |   |   |
-        // -------------------------
-        // |   |   |   |   |   |   |
         expectedRequiredBitmaps[0][2] = true;
+        expectedRequiredBitmaps[1][0] = true;
         expectedRequiredBitmaps[1][1] = true;
         expectedRequiredBitmaps[1][2] = true;
         expectedRequiredBitmaps[2][0] = true;
@@ -447,72 +442,93 @@
         Assert.assertTrue(Arrays.deepEquals(
                 expectedRequiredBitmaps, getVisibleBitmapState().getRequiredBitmapsForTest()));
 
-        mScrollController.scrollBy(200, 400);
-        // The current view port contains portions of the middle 4 tiles.
-        // Tiles marked with x are required for the current view port.
+        mScrollController.scrollBy(10, 15);
+        // The current viewport covers portions of the 4 top left bitmap tiles.
+        // -------------------------
+        // | x | x | x | x |   |   |
+        // -------------------------
+        // | x | x | x | x |   |   |
+        // -------------------------
+        // | x | x | x | x |   |   |
+        // -------------------------
+        // | x | x | x |   |   |   |
         // -------------------------
         // |   |   |   |   |   |   |
         // -------------------------
-        // |   |   | x | x |   |   |
-        // -------------------------
-        // |   | x | x | x | x |   |
-        // -------------------------
-        // |   | x | x | x | x |   |
-        // -------------------------
-        // |   |   | x | x |   |   |
-        // -------------------------
         // |   |   |   |   |   |   |
-        expectedRequiredBitmaps[0][0] = false;
-        expectedRequiredBitmaps[0][1] = false;
-        expectedRequiredBitmaps[0][2] = false;
-        expectedRequiredBitmaps[1][0] = false;
-        expectedRequiredBitmaps[1][1] = false;
-        expectedRequiredBitmaps[2][0] = false;
+        expectedRequiredBitmaps[0][3] = true;
         expectedRequiredBitmaps[1][3] = true;
-        expectedRequiredBitmaps[2][2] = true;
         expectedRequiredBitmaps[2][3] = true;
-        expectedRequiredBitmaps[2][4] = true;
+        expectedRequiredBitmaps[2][2] = true;
+        expectedRequiredBitmaps[3][0] = true;
         expectedRequiredBitmaps[3][1] = true;
         expectedRequiredBitmaps[3][2] = true;
-        expectedRequiredBitmaps[3][3] = true;
-        expectedRequiredBitmaps[3][4] = true;
-        expectedRequiredBitmaps[4][2] = true;
-        expectedRequiredBitmaps[4][3] = true;
         Assert.assertTrue(Arrays.deepEquals(
                 expectedRequiredBitmaps, getVisibleBitmapState().getRequiredBitmapsForTest()));
 
         mScrollController.scrollBy(200, 400);
-        // The current view port contains portions of the 4 bottom right tiles.
-        // Tiles marked with x are required for the current view port.
-        // -------------------------
-        // |   |   |   |   |   |   |
-        // -------------------------
-        // |   |   |   |   |   |   |
-        // -------------------------
-        // |   |   |   |   |   |   |
-        // -------------------------
-        // |   |   |   |   | x | x |
-        // -------------------------
-        // |   |   |   | x | x | x |
-        // -------------------------
-        // |   |   |   | x | x | x |
-        expectedRequiredBitmaps[1][2] = false;
-        expectedRequiredBitmaps[1][3] = false;
-        expectedRequiredBitmaps[2][1] = false;
-        expectedRequiredBitmaps[2][2] = false;
-        expectedRequiredBitmaps[2][3] = false;
-        expectedRequiredBitmaps[2][4] = false;
-        expectedRequiredBitmaps[3][1] = false;
-        expectedRequiredBitmaps[3][2] = false;
-        expectedRequiredBitmaps[3][3] = false;
-        expectedRequiredBitmaps[4][2] = false;
-
+        // The current view port contains portions of the middle 9 tiles.
+        // ---------------------
+        // |   | x  | x | x |  |
+        // ---------------------
+        // | x | x | x | x | x |
+        // ---------------------
+        // | x | x | x | x | x |
+        // ---------------------
+        // | x | x | x | x | x |
+        // ---------------------
+        // |   | x | x | x |   |
+        // ---------------------
+        expectedRequiredBitmaps = new boolean[12][12];
+        expectedRequiredBitmaps[3][4] = true;
         expectedRequiredBitmaps[3][5] = true;
+        expectedRequiredBitmaps[3][6] = true;
+        expectedRequiredBitmaps[4][3] = true;
         expectedRequiredBitmaps[4][4] = true;
         expectedRequiredBitmaps[4][5] = true;
+        expectedRequiredBitmaps[4][6] = true;
+        expectedRequiredBitmaps[4][7] = true;
         expectedRequiredBitmaps[5][3] = true;
         expectedRequiredBitmaps[5][4] = true;
         expectedRequiredBitmaps[5][5] = true;
+        expectedRequiredBitmaps[5][6] = true;
+        expectedRequiredBitmaps[5][7] = true;
+        expectedRequiredBitmaps[6][3] = true;
+        expectedRequiredBitmaps[6][4] = true;
+        expectedRequiredBitmaps[6][5] = true;
+        expectedRequiredBitmaps[6][6] = true;
+        expectedRequiredBitmaps[6][7] = true;
+        expectedRequiredBitmaps[7][4] = true;
+        expectedRequiredBitmaps[7][5] = true;
+        expectedRequiredBitmaps[7][6] = true;
+        Assert.assertTrue(Arrays.deepEquals(
+                expectedRequiredBitmaps, getVisibleBitmapState().getRequiredBitmapsForTest()));
+
+        mScrollController.scrollBy(200, 400);
+        // The current view port contains portions of the 9 bottom right tiles.
+        // Tiles marked with x are required for the current view port.
+        expectedRequiredBitmaps = new boolean[12][12];
+        expectedRequiredBitmaps[7][8] = true;
+        expectedRequiredBitmaps[7][9] = true;
+        expectedRequiredBitmaps[7][10] = true;
+        expectedRequiredBitmaps[8][7] = true;
+        expectedRequiredBitmaps[8][8] = true;
+        expectedRequiredBitmaps[8][9] = true;
+        expectedRequiredBitmaps[8][10] = true;
+        expectedRequiredBitmaps[8][11] = true;
+        expectedRequiredBitmaps[9][7] = true;
+        expectedRequiredBitmaps[9][8] = true;
+        expectedRequiredBitmaps[9][9] = true;
+        expectedRequiredBitmaps[9][10] = true;
+        expectedRequiredBitmaps[9][11] = true;
+        expectedRequiredBitmaps[10][7] = true;
+        expectedRequiredBitmaps[10][8] = true;
+        expectedRequiredBitmaps[10][9] = true;
+        expectedRequiredBitmaps[10][10] = true;
+        expectedRequiredBitmaps[10][11] = true;
+        expectedRequiredBitmaps[11][8] = true;
+        expectedRequiredBitmaps[11][9] = true;
+        expectedRequiredBitmaps[11][10] = true;
         Assert.assertTrue(Arrays.deepEquals(
                 expectedRequiredBitmaps, getVisibleBitmapState().getRequiredBitmapsForTest()));
     }
@@ -523,8 +539,8 @@
      */
     @Test
     public void testBitmapRequestResponse() {
-        // Sets the bitmap tile size to 150x200 and triggers bitmap request for the upper left tile
-        // and its adjacent tiles.
+        // Sets the bitmap tile size to 150x200 and triggers bitmap request for the upper left tiles
+        // and their adjacent tiles.
         mMediator.updateViewportSize(150, 200, 1f);
 
         // Create mock bitmaps for response.
@@ -534,50 +550,71 @@
         Bitmap bitmap01 = Mockito.mock(Bitmap.class);
         Bitmap bitmap11 = Mockito.mock(Bitmap.class);
         Bitmap bitmap21 = Mockito.mock(Bitmap.class);
+        Bitmap bitmap31 = Mockito.mock(Bitmap.class);
         Bitmap bitmap02 = Mockito.mock(Bitmap.class);
         Bitmap bitmap12 = Mockito.mock(Bitmap.class);
+        Bitmap bitmap22 = Mockito.mock(Bitmap.class);
+        Bitmap bitmap32 = Mockito.mock(Bitmap.class);
+        Bitmap bitmap03 = Mockito.mock(Bitmap.class);
+        Bitmap bitmap13 = Mockito.mock(Bitmap.class);
+        Bitmap bitmap23 = Mockito.mock(Bitmap.class);
 
-        Bitmap[][] expectedBitmapMatrix = new Bitmap[6][4];
+        Bitmap[][] expectedBitmapMatrix = new Bitmap[12][8];
         expectedBitmapMatrix[0][0] = bitmap00;
-        expectedBitmapMatrix[1][0] = bitmap10;
         expectedBitmapMatrix[0][1] = bitmap01;
+        expectedBitmapMatrix[0][2] = bitmap02;
+        expectedBitmapMatrix[1][0] = bitmap10;
+        expectedBitmapMatrix[1][1] = bitmap11;
+        expectedBitmapMatrix[1][2] = bitmap12;
+        expectedBitmapMatrix[2][0] = bitmap20;
+        expectedBitmapMatrix[2][1] = bitmap21;
 
         // Call the request callback with mock bitmaps and assert they're added to the model.
         mCompositorDelegate.mRequestedBitmap.get(0).mBitmapCallback.onResult(bitmap00);
         mCompositorDelegate.mRequestedBitmap.get(1).mBitmapCallback.onResult(bitmap10);
         mCompositorDelegate.mRequestedBitmap.get(2).mBitmapCallback.onResult(bitmap01);
-        Assert.assertTrue(Arrays.deepEquals(
-                expectedBitmapMatrix, mModel.get(PlayerFrameProperties.BITMAP_MATRIX)));
-
-        // Move the viewport to an area that is covered by 4 top left tiles.
-        mScrollController.scrollBy(10, 10);
-
-        // Scroll should've triggered bitmap requests for an the 4th new tile as well as adjacent
-        // tiles. See comments on {@link #testBitmapRequest} for details on which tiles will be
-        // requested.
-        // Call the request callback with mock bitmaps and assert they're added to the model.
-        expectedBitmapMatrix[1][1] = bitmap11;
-        expectedBitmapMatrix[0][2] = bitmap02;
-        expectedBitmapMatrix[2][1] = bitmap21;
-        expectedBitmapMatrix[1][2] = bitmap12;
         mCompositorDelegate.mRequestedBitmap.get(3).mBitmapCallback.onResult(bitmap11);
-        // Mock a compositing failure for this tile. No bitmaps should be added.
-        mCompositorDelegate.mRequestedBitmap.get(4).mErrorCallback.run();
+        mCompositorDelegate.mRequestedBitmap.get(4).mBitmapCallback.onResult(bitmap20);
         mCompositorDelegate.mRequestedBitmap.get(5).mBitmapCallback.onResult(bitmap02);
         mCompositorDelegate.mRequestedBitmap.get(6).mBitmapCallback.onResult(bitmap21);
         mCompositorDelegate.mRequestedBitmap.get(7).mBitmapCallback.onResult(bitmap12);
+        Bitmap[][] mat = mModel.get(PlayerFrameProperties.BITMAP_MATRIX);
         Assert.assertTrue(Arrays.deepEquals(
                 expectedBitmapMatrix, mModel.get(PlayerFrameProperties.BITMAP_MATRIX)));
 
-        // Assert 8 bitmap requests have been made in total.
-        Assert.assertEquals(8, mCompositorDelegate.mRequestedBitmap.size());
+        // Move the viewport slightly..
+        mScrollController.scrollBy(10, 10);
 
-        // Move the view port while staying within the 4 bitmap tiles in order to trigger the
+        // Scroll should've triggered bitmap requests for an the new tiles as well as adjacent
+        // tiles. See comments on {@link #testBitmapRequest} for details on which tiles will be
+        // requested.
+        // Call the request callback with mock bitmaps and assert they're added to the model.
+        expectedBitmapMatrix[2][2] = bitmap22;
+        expectedBitmapMatrix[0][3] = bitmap03;
+        expectedBitmapMatrix[3][1] = bitmap31;
+        expectedBitmapMatrix[1][3] = bitmap13;
+        expectedBitmapMatrix[3][2] = bitmap32;
+        expectedBitmapMatrix[2][3] = bitmap23;
+        mCompositorDelegate.mRequestedBitmap.get(8).mBitmapCallback.onResult(bitmap22);
+        // Mock a compositing failure for this tile. No bitmaps should be added.
+        mCompositorDelegate.mRequestedBitmap.get(9).mErrorCallback.run();
+        mCompositorDelegate.mRequestedBitmap.get(10).mBitmapCallback.onResult(bitmap31);
+        mCompositorDelegate.mRequestedBitmap.get(11).mBitmapCallback.onResult(bitmap03);
+        mCompositorDelegate.mRequestedBitmap.get(12).mBitmapCallback.onResult(bitmap13);
+        mCompositorDelegate.mRequestedBitmap.get(13).mBitmapCallback.onResult(bitmap32);
+        mCompositorDelegate.mRequestedBitmap.get(14).mBitmapCallback.onResult(bitmap23);
+        Assert.assertTrue(Arrays.deepEquals(
+                expectedBitmapMatrix, mModel.get(PlayerFrameProperties.BITMAP_MATRIX)));
+
+        // Assert 15 bitmap requests have been made in total.
+        Assert.assertEquals(15, mCompositorDelegate.mRequestedBitmap.size());
+
+        // Move the view port while staying within the current tiles in order to trigger the
         // request logic again. Make sure only one new request is added, for the tile with a
         // compositing failure.
         mScrollController.scrollBy(10, 10);
-        Assert.assertEquals(9, mCompositorDelegate.mRequestedBitmap.size());
-        Assert.assertEquals(new RequestedBitmap(mFrameGuid, getRectForTile(150, 200, 2, 0), 1f),
+        Assert.assertEquals(16, mCompositorDelegate.mRequestedBitmap.size());
+        Assert.assertEquals(new RequestedBitmap(mFrameGuid, getRectForTile(75, 100, 3, 0), 1f),
                 mCompositorDelegate.mRequestedBitmap.get(
                         mCompositorDelegate.mRequestedBitmap.size() - 1));
     }
@@ -742,21 +779,26 @@
         // Below is a schematic of the entire bitmap matrix. Tiles marked with x are required for
         // the current view port.
         // -------------------------
+        // | x | x | x |   |   |   |
+        // -------------------------
+        // | x | x | x |   |   |   |
+        // -------------------------
         // | x | x |   |   |   |   |
         // -------------------------
-        // | x |   |   |   |   |   |
-        // -------------------------
         // |   |   |   |   |   |   |
         // -------------------------
         // |   |   |   |   |   |   |
         // -------------------------
         // |   |   |   |   |   |   |
-        // -------------------------
-        // |   |   |   |   |   |   |
-        boolean[][] expectedRequiredBitmaps = new boolean[6][6];
+        boolean[][] expectedRequiredBitmaps = new boolean[12][12];
         expectedRequiredBitmaps[0][0] = true;
         expectedRequiredBitmaps[0][1] = true;
+        expectedRequiredBitmaps[0][2] = true;
         expectedRequiredBitmaps[1][0] = true;
+        expectedRequiredBitmaps[1][1] = true;
+        expectedRequiredBitmaps[1][2] = true;
+        expectedRequiredBitmaps[2][0] = true;
+        expectedRequiredBitmaps[2][1] = true;
         mBitmapStateController.swapForTest();
         Assert.assertTrue(Arrays.deepEquals(
                 expectedRequiredBitmaps, getVisibleBitmapState().getRequiredBitmapsForTest()));
@@ -767,10 +809,15 @@
         Assert.assertTrue(mScaleController.scaleFinished(1f, 0, 0));
         mBitmapStateController.swapForTest();
 
-        expectedRequiredBitmaps = new boolean[12][12];
+        expectedRequiredBitmaps = new boolean[23][23];
         expectedRequiredBitmaps[0][0] = true;
         expectedRequiredBitmaps[0][1] = true;
+        expectedRequiredBitmaps[0][2] = true;
         expectedRequiredBitmaps[1][0] = true;
+        expectedRequiredBitmaps[1][1] = true;
+        expectedRequiredBitmaps[1][2] = true;
+        expectedRequiredBitmaps[2][0] = true;
+        expectedRequiredBitmaps[2][1] = true;
         Assert.assertTrue(Arrays.deepEquals(
                 expectedRequiredBitmaps, getVisibleBitmapState().getRequiredBitmapsForTest()));
 
@@ -779,10 +826,15 @@
         Assert.assertTrue(mScaleController.scaleFinished(1f, 0, 0));
         mBitmapStateController.swapForTest();
 
-        expectedRequiredBitmaps = new boolean[6][6];
+        expectedRequiredBitmaps = new boolean[12][12];
         expectedRequiredBitmaps[0][0] = true;
         expectedRequiredBitmaps[0][1] = true;
+        expectedRequiredBitmaps[0][2] = true;
         expectedRequiredBitmaps[1][0] = true;
+        expectedRequiredBitmaps[1][1] = true;
+        expectedRequiredBitmaps[1][2] = true;
+        expectedRequiredBitmaps[2][0] = true;
+        expectedRequiredBitmaps[2][1] = true;
         Assert.assertTrue(Arrays.deepEquals(
                 expectedRequiredBitmaps, getVisibleBitmapState().getRequiredBitmapsForTest()));
     }
@@ -795,34 +847,50 @@
         // Initial view port setup.
         mMediator.updateViewportSize(100, 200, 1f);
 
-        boolean[][] expectedRequiredBitmaps = new boolean[6][6];
+        boolean[][] expectedRequiredBitmaps = new boolean[12][12];
 
         // STEP 1: Original request.
         // The current view port fully matches the top left bitmap tile.
         // Below is a schematic of the entire bitmap matrix. Tiles marked with x are required for
         // the current view port.
         // -------------------------
+        // | x | x | x |   |   |   |
+        // -------------------------
+        // | x | x | x |   |   |   |
+        // -------------------------
         // | x | x |   |   |   |   |
         // -------------------------
-        // | x |   |   |   |   |   |
-        // -------------------------
         // |   |   |   |   |   |   |
         // -------------------------
         // |   |   |   |   |   |   |
         // -------------------------
         // |   |   |   |   |   |   |
         // -------------------------
-        // |   |   |   |   |   |   |
         expectedRequiredBitmaps[0][0] = true;
         expectedRequiredBitmaps[0][1] = true;
+        expectedRequiredBitmaps[0][2] = true;
         expectedRequiredBitmaps[1][0] = true;
+        expectedRequiredBitmaps[1][1] = true;
+        expectedRequiredBitmaps[1][2] = true;
+        expectedRequiredBitmaps[2][0] = true;
+        expectedRequiredBitmaps[2][1] = true;
         List<RequestedBitmap> expectedRequestedBitmaps = new ArrayList<>();
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 0, 0), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 0, 0), 1f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 1, 0), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 1, 0), 1f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 0, 1), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 0, 1), 1f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 1, 1), 1f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 2, 0), 1f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 0, 2), 1f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 2, 1), 1f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 1, 2), 1f));
 
         // Both matricies should be identity to start.
         assertViewportStateIs(1f, 0f, 0f, mMediator.getViewport());
@@ -834,37 +902,42 @@
 
         // STEP 2: Scroll slightly.
         mScrollController.scrollBy(10, 15);
-        // The current viewport covers portions of the 4 top left bitmap tiles.
+        // -------------------------
+        // | x | x | x | x |   |   |
+        // -------------------------
+        // | x | x | x | x |   |   |
+        // -------------------------
+        // | x | x | x | x |   |   |
         // -------------------------
         // | x | x | x |   |   |   |
         // -------------------------
-        // | x | x | x |   |   |   |
-        // -------------------------
-        // | x | x |   |   |   |   |
-        // -------------------------
         // |   |   |   |   |   |   |
         // -------------------------
         // |   |   |   |   |   |   |
-        // -------------------------
-        // |   |   |   |   |   |   |
-        expectedRequiredBitmaps[0][2] = true;
-        expectedRequiredBitmaps[1][1] = true;
-        expectedRequiredBitmaps[1][2] = true;
-        expectedRequiredBitmaps[2][0] = true;
-        expectedRequiredBitmaps[2][1] = true;
+        expectedRequiredBitmaps[0][3] = true;
+        expectedRequiredBitmaps[1][3] = true;
+        expectedRequiredBitmaps[2][2] = true;
+        expectedRequiredBitmaps[2][3] = true;
+        expectedRequiredBitmaps[3][0] = true;
+        expectedRequiredBitmaps[3][1] = true;
+        expectedRequiredBitmaps[3][2] = true;
         Assert.assertTrue(Arrays.deepEquals(
                 expectedRequiredBitmaps, getVisibleBitmapState().getRequiredBitmapsForTest()));
 
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 1, 1), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 2, 2), 1f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 2, 0), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 3, 0), 1f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 0, 2), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 3, 1), 1f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 2, 1), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 0, 3), 1f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 1, 2), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 1, 3), 1f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 3, 2), 1f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 2, 3), 1f));
         Assert.assertEquals(expectedRequestedBitmaps, mCompositorDelegate.mRequestedBitmap);
 
         // The viewport matrix should track scroll and zoom.
@@ -897,34 +970,73 @@
         Assert.assertTrue(mScaleController.scaleFinished(1f, 0, 0));
         mBitmapStateController.swapForTest();
 
-        expectedRequiredBitmaps = new boolean[12][12];
-        expectedRequiredBitmaps[0][0] = true;
+        expectedRequiredBitmaps = new boolean[23][23];
         expectedRequiredBitmaps[0][1] = true;
-        expectedRequiredBitmaps[1][0] = true;
         expectedRequiredBitmaps[0][2] = true;
+        expectedRequiredBitmaps[0][3] = true;
+        expectedRequiredBitmaps[1][0] = true;
         expectedRequiredBitmaps[1][1] = true;
         expectedRequiredBitmaps[1][2] = true;
+        expectedRequiredBitmaps[1][3] = true;
+        expectedRequiredBitmaps[1][4] = true;
         expectedRequiredBitmaps[2][0] = true;
         expectedRequiredBitmaps[2][1] = true;
+        expectedRequiredBitmaps[2][2] = true;
+        expectedRequiredBitmaps[2][3] = true;
+        expectedRequiredBitmaps[2][4] = true;
+        expectedRequiredBitmaps[3][0] = true;
+        expectedRequiredBitmaps[3][1] = true;
+        expectedRequiredBitmaps[3][2] = true;
+        expectedRequiredBitmaps[3][3] = true;
+        expectedRequiredBitmaps[3][4] = true;
+        expectedRequiredBitmaps[4][1] = true;
+        expectedRequiredBitmaps[4][2] = true;
+        expectedRequiredBitmaps[4][3] = true;
         Assert.assertTrue(Arrays.deepEquals(
                 expectedRequiredBitmaps, getVisibleBitmapState().getRequiredBitmapsForTest()));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 1, 1), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 2, 1), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 3, 1), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 1, 2), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 2, 2), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 3, 2), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 1, 3), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 2, 3), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 3, 3), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 0, 1), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 1, 0), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 2, 0), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 4, 1), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 3, 0), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 0, 2), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 4, 2), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 0, 3), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 1, 4), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 2, 4), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 4, 3), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 3, 4), 2f));
 
-        expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 0, 0), 2f));
-        expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 1, 0), 2f));
-        expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 0, 1), 2f));
-        expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 1, 1), 2f));
-        expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 2, 0), 2f));
-        expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 0, 2), 2f));
-        expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 2, 1), 2f));
-        expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 1, 2), 2f));
         Assert.assertEquals(expectedRequestedBitmaps, mCompositorDelegate.mRequestedBitmap);
 
         // The bitmap matrix should be cleared.
@@ -949,34 +1061,55 @@
         Assert.assertTrue(mScaleController.scaleFinished(1f, 0, 0));
         mBitmapStateController.swapForTest();
 
-        expectedRequiredBitmaps = new boolean[6][6];
+        expectedRequiredBitmaps = new boolean[12][12];
         expectedRequiredBitmaps[0][0] = true;
         expectedRequiredBitmaps[0][1] = true;
-        expectedRequiredBitmaps[1][0] = true;
         expectedRequiredBitmaps[0][2] = true;
+        expectedRequiredBitmaps[0][3] = true;
+        expectedRequiredBitmaps[1][0] = true;
         expectedRequiredBitmaps[1][1] = true;
         expectedRequiredBitmaps[1][2] = true;
+        expectedRequiredBitmaps[1][3] = true;
         expectedRequiredBitmaps[2][0] = true;
         expectedRequiredBitmaps[2][1] = true;
+        expectedRequiredBitmaps[2][2] = true;
+        expectedRequiredBitmaps[2][3] = true;
+        expectedRequiredBitmaps[3][0] = true;
+        expectedRequiredBitmaps[3][1] = true;
+        expectedRequiredBitmaps[3][2] = true;
         Assert.assertTrue(Arrays.deepEquals(
                 expectedRequiredBitmaps, getVisibleBitmapState().getRequiredBitmapsForTest()));
 
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 0, 0), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 0, 0), 1f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 1, 0), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 1, 0), 1f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 0, 1), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 2, 0), 1f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 1, 1), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 0, 1), 1f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 2, 0), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 1, 1), 1f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 0, 2), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 2, 1), 1f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 2, 1), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 0, 2), 1f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 1, 2), 1f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 1, 2), 1f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 2, 2), 1f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 3, 0), 1f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 3, 1), 1f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 0, 3), 1f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 1, 3), 1f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 3, 2), 1f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 2, 3), 1f));
         Assert.assertEquals(expectedRequestedBitmaps, mCompositorDelegate.mRequestedBitmap);
 
         expectedBitmapMatrix.reset();
@@ -985,15 +1118,15 @@
         // Now a scale factor of 2 will be applied. This will happen at a focal point of 100, 200.
         // Due to the position of the focal point the required bitmaps will move.
         // -------------------------
-        // |   | x | x |   |   |   |
+        // |   | x | x | x |   |   |
         // -------------------------
-        // | x | x | x | x |   |   |
+        // | x | x | x | x | x |   |
         // -------------------------
-        // | x | x | x | x |   |   |
+        // | x | x | x | x | x |   |
         // -------------------------
-        // |   | x | x |   |   |   |
+        // | x | x | x | x | x |   |
         // -------------------------
-        // |   |   |   |   |   |   |
+        // |   | x | x | x |   |   |
         // -------------------------
         // |   |   |   |   |   |   |
         Assert.assertTrue(mScaleController.scaleBy(2f, 100f, 200f));
@@ -1010,46 +1143,73 @@
         Assert.assertTrue(mScaleController.scaleFinished(1f, 0, 0));
         mBitmapStateController.swapForTest();
 
-        expectedRequiredBitmaps = new boolean[12][12];
-        expectedRequiredBitmaps[0][1] = true;
-        expectedRequiredBitmaps[0][2] = true;
-        expectedRequiredBitmaps[1][0] = true;
-        expectedRequiredBitmaps[1][1] = true;
+        expectedRequiredBitmaps = new boolean[23][23];
         expectedRequiredBitmaps[1][2] = true;
         expectedRequiredBitmaps[1][3] = true;
-        expectedRequiredBitmaps[2][0] = true;
+        expectedRequiredBitmaps[1][4] = true;
         expectedRequiredBitmaps[2][1] = true;
         expectedRequiredBitmaps[2][2] = true;
         expectedRequiredBitmaps[2][3] = true;
+        expectedRequiredBitmaps[2][4] = true;
+        expectedRequiredBitmaps[2][5] = true;
         expectedRequiredBitmaps[3][1] = true;
         expectedRequiredBitmaps[3][2] = true;
+        expectedRequiredBitmaps[3][3] = true;
+        expectedRequiredBitmaps[3][4] = true;
+        expectedRequiredBitmaps[3][5] = true;
+        expectedRequiredBitmaps[4][1] = true;
+        expectedRequiredBitmaps[4][2] = true;
+        expectedRequiredBitmaps[4][3] = true;
+        expectedRequiredBitmaps[4][4] = true;
+        expectedRequiredBitmaps[4][5] = true;
+        expectedRequiredBitmaps[5][2] = true;
+        expectedRequiredBitmaps[5][3] = true;
+        expectedRequiredBitmaps[5][4] = true;
         Assert.assertTrue(Arrays.deepEquals(
                 expectedRequiredBitmaps, getVisibleBitmapState().getRequiredBitmapsForTest()));
 
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 1, 1), 2f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 2, 2), 2f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 2, 1), 2f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 3, 2), 2f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 1, 2), 2f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 4, 2), 2f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 2, 2), 2f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 2, 3), 2f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 0, 1), 2f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 3, 3), 2f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 1, 0), 2f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 4, 3), 2f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 3, 1), 2f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 2, 4), 2f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 2, 0), 2f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 3, 4), 2f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 0, 2), 2f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 4, 4), 2f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 1, 3), 2f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 1, 2), 2f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 3, 2), 2f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 2, 1), 2f));
         expectedRequestedBitmaps.add(
-                new RequestedBitmap(mFrameGuid, getRectForTile(100, 200, 2, 3), 2f));
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 3, 1), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 5, 2), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 4, 1), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 1, 3), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 5, 3), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 1, 4), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 2, 5), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 3, 5), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 5, 4), 2f));
+        expectedRequestedBitmaps.add(
+                new RequestedBitmap(mFrameGuid, getRectForTile(50, 100, 4, 5), 2f));
         Assert.assertEquals(expectedRequestedBitmaps, mCompositorDelegate.mRequestedBitmap);
 
         expectedBitmapMatrix.reset();
diff --git a/components/password_manager/core/browser/BUILD.gn b/components/password_manager/core/browser/BUILD.gn
index ee42c33..e3a66b10 100644
--- a/components/password_manager/core/browser/BUILD.gn
+++ b/components/password_manager/core/browser/BUILD.gn
@@ -211,8 +211,6 @@
     "site_affiliation/affiliation_service_impl.h",
     "site_affiliation/asset_link_data.cc",
     "site_affiliation/asset_link_data.h",
-    "site_affiliation/hash_affiliation_fetcher.cc",
-    "site_affiliation/hash_affiliation_fetcher.h",
     "sql_table_builder.cc",
     "sql_table_builder.h",
     "statistics_table.cc",
@@ -650,7 +648,6 @@
     "psl_matching_helper_unittest.cc",
     "site_affiliation/affiliation_service_impl_unittest.cc",
     "site_affiliation/asset_link_data_unittest.cc",
-    "site_affiliation/hash_affiliation_fetcher_unittest.cc",
     "sql_table_builder_unittest.cc",
     "statistics_table_unittest.cc",
     "store_metrics_reporter_unittest.cc",
diff --git a/components/password_manager/core/browser/site_affiliation/hash_affiliation_fetcher.cc b/components/password_manager/core/browser/site_affiliation/hash_affiliation_fetcher.cc
deleted file mode 100644
index 4352c4412..0000000
--- a/components/password_manager/core/browser/site_affiliation/hash_affiliation_fetcher.cc
+++ /dev/null
@@ -1,41 +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 "components/password_manager/core/browser/site_affiliation/hash_affiliation_fetcher.h"
-
-#include <memory>
-
-#include "components/password_manager/core/browser/android_affiliation/affiliation_utils.h"
-#include "google_apis/google_api_keys.h"
-#include "net/base/url_util.h"
-#include "services/network/public/cpp/shared_url_loader_factory.h"
-#include "url/gurl.h"
-
-namespace password_manager {
-
-HashAffiliationFetcher::HashAffiliationFetcher(
-    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-    AffiliationFetcherDelegate* delegate)
-    : AffiliationFetcherBase(std::move(url_loader_factory), delegate) {}
-
-HashAffiliationFetcher::~HashAffiliationFetcher() = default;
-
-// TODO: Add an implementation.
-void HashAffiliationFetcher::StartRequest(
-    const std::vector<FacetURI>& facet_uris,
-    RequestInfo request_info) {}
-
-const std::vector<FacetURI>& HashAffiliationFetcher::GetRequestedFacetURIs()
-    const {
-  return requested_facet_uris_;
-}
-// static
-GURL HashAffiliationFetcher::BuildQueryURL() {
-  return net::AppendQueryParameter(
-      GURL("https://www.googleapis.com/affiliation/v1/"
-           "affiliation:lookupByHashPrefix"),
-      "key", google_apis::GetAPIKey());
-}
-
-}  // namespace password_manager
diff --git a/components/password_manager/core/browser/site_affiliation/hash_affiliation_fetcher.h b/components/password_manager/core/browser/site_affiliation/hash_affiliation_fetcher.h
deleted file mode 100644
index 1539395..0000000
--- a/components/password_manager/core/browser/site_affiliation/hash_affiliation_fetcher.h
+++ /dev/null
@@ -1,38 +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_PASSWORD_MANAGER_CORE_BROWSER_SITE_AFFILIATION_HASH_AFFILIATION_FETCHER_H_
-#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_SITE_AFFILIATION_HASH_AFFILIATION_FETCHER_H_
-
-#include "components/password_manager/core/browser/site_affiliation/affiliation_fetcher_base.h"
-
-namespace password_manager {
-
-// Fetches authoritative information about facets' affiliations with additional
-// privacy layer. It uses SHA-256 to hash facet URLs and sends only a specified
-// amount of hash prefixes to eventually retrieve a larger group of affiliations
-// including those actually required.
-class HashAffiliationFetcher : public AffiliationFetcherBase {
- public:
-  HashAffiliationFetcher(
-      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-      AffiliationFetcherDelegate* delegate);
-  ~HashAffiliationFetcher() override;
-
-  void StartRequest(const std::vector<FacetURI>& facet_uris,
-                    RequestInfo request_info) override;
-
-  // AffiliationFetcherInterface
-  const std::vector<FacetURI>& GetRequestedFacetURIs() const override;
-
-  // Builds the URL for the Affiliation API's lookup method.
-  static GURL BuildQueryURL();
-
- private:
-  std::vector<FacetURI> requested_facet_uris_;
-};
-
-}  // namespace password_manager
-
-#endif  // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_SITE_AFFILIATION_HASH_AFFILIATION_FETCHER_H_
diff --git a/components/password_manager/core/browser/site_affiliation/hash_affiliation_fetcher_unittest.cc b/components/password_manager/core/browser/site_affiliation/hash_affiliation_fetcher_unittest.cc
deleted file mode 100644
index b03e783..0000000
--- a/components/password_manager/core/browser/site_affiliation/hash_affiliation_fetcher_unittest.cc
+++ /dev/null
@@ -1,35 +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 "components/password_manager/core/browser/site_affiliation/hash_affiliation_fetcher.h"
-
-#include "components/password_manager/core/browser/android_affiliation/mock_affiliation_fetcher_delegate.h"
-#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
-#include "services/network/test/test_url_loader_factory.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "url/gurl.h"
-
-namespace password_manager {
-
-class HashAffiliationFetcherTest : public testing::Test {
- public:
-  HashAffiliationFetcherTest() = default;
-  ~HashAffiliationFetcherTest() override = default;
-};
-
-TEST_F(HashAffiliationFetcherTest, BuildQueryURL) {
-  network::TestURLLoaderFactory test_url_loader_factory;
-  scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory =
-      base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
-          &test_url_loader_factory);
-  MockAffiliationFetcherDelegate mock_delegate;
-
-  HashAffiliationFetcher fetcher(test_shared_loader_factory, &mock_delegate);
-
-  EXPECT_EQ(GURL("https://www.googleapis.com/affiliation/v1/"
-                 "affiliation:lookupByHashPrefix?key=dummytoken"),
-            fetcher.BuildQueryURL());
-}
-
-}  // namespace password_manager
diff --git a/components/password_manager/core/browser/ui/insecure_credentials_manager.cc b/components/password_manager/core/browser/ui/insecure_credentials_manager.cc
index a3151d3..8db07a2 100644
--- a/components/password_manager/core/browser/ui/insecure_credentials_manager.cc
+++ b/components/password_manager/core/browser/ui/insecure_credentials_manager.cc
@@ -10,6 +10,7 @@
 
 #include "base/bind.h"
 #include "base/containers/flat_set.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/string16.h"
 #include "base/strings/utf_string_conversions.h"
@@ -242,7 +243,7 @@
       base::BindOnce(&BulkWeakCheck,
                      ExtractPasswords(presenter_->GetSavedPasswords())),
       base::BindOnce(&InsecureCredentialsManager::OnWeakCheckDone,
-                     weak_ptr_factory_.GetWeakPtr()));
+                     weak_ptr_factory_.GetWeakPtr(), base::ElapsedTimer()));
 }
 
 void InsecureCredentialsManager::SaveCompromisedCredential(
@@ -338,12 +339,15 @@
 }
 
 void InsecureCredentialsManager::OnWeakCheckDone(
+    base::ElapsedTimer timer_since_weak_check_start,
     base::flat_set<base::string16> weak_passwords) {
   weak_passwords_ = std::move(weak_passwords);
 
   credentials_to_forms_ = JoinInsecureCredentialsWithSavedPasswords(
       compromised_credentials_, weak_passwords_,
       presenter_->GetSavedPasswords());
+  base::UmaHistogramTimes("PasswordManager.WeakCheck.Time",
+                          timer_since_weak_check_start.Elapsed());
   NotifyWeakCredentialsChanged();
 }
 
diff --git a/components/password_manager/core/browser/ui/insecure_credentials_manager.h b/components/password_manager/core/browser/ui/insecure_credentials_manager.h
index 77d28050..3b6dda0 100644
--- a/components/password_manager/core/browser/ui/insecure_credentials_manager.h
+++ b/components/password_manager/core/browser/ui/insecure_credentials_manager.h
@@ -14,6 +14,7 @@
 #include "base/observer_list.h"
 #include "base/observer_list_types.h"
 #include "base/scoped_observer.h"
+#include "base/timer/elapsed_timer.h"
 #include "base/util/type_safety/strong_alias.h"
 #include "components/password_manager/core/browser/compromised_credentials_consumer.h"
 #include "components/password_manager/core/browser/compromised_credentials_table.h"
@@ -199,7 +200,8 @@
 
   // Updates |weak_passwords| set and notifies observers that weak credentials
   // were changed.
-  void OnWeakCheckDone(base::flat_set<base::string16> weak_passwords);
+  void OnWeakCheckDone(base::ElapsedTimer timer_since_weak_check_start,
+                       base::flat_set<base::string16> weak_passwords);
 
   // CompromisedCredentialsReader::Observer:
   void OnCompromisedCredentialsChanged(
diff --git a/components/password_manager/core/browser/ui/insecure_credentials_manager_unittest.cc b/components/password_manager/core/browser/ui/insecure_credentials_manager_unittest.cc
index 00cadbd1..244a1080 100644
--- a/components/password_manager/core/browser/ui/insecure_credentials_manager_unittest.cc
+++ b/components/password_manager/core/browser/ui/insecure_credentials_manager_unittest.cc
@@ -7,7 +7,9 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/strings/string_piece_forward.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
+#include "base/timer/elapsed_timer.h"
 #include "components/password_manager/core/browser/compromised_credentials_table.h"
 #include "components/password_manager/core/browser/password_form.h"
 #include "components/password_manager/core/browser/test_password_store.h"
@@ -38,6 +40,9 @@
 using ::testing::ElementsAreArray;
 using ::testing::IsEmpty;
 
+// Delay in milliseconds.
+const int kDelay = 2;
+
 struct MockInsecureCredentialsManagerObserver
     : InsecureCredentialsManager::Observer {
   MOCK_METHOD(void,
@@ -136,9 +141,14 @@
     return std::string();
   }
 
+  base::HistogramTester& histogram_tester() { return histogram_tester_; }
+
+  void AdvanceClock(base::TimeDelta time) { task_env_.AdvanceClock(time); }
+
  private:
   base::test::TaskEnvironment task_env_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  base::HistogramTester histogram_tester_;
   scoped_refptr<TestPasswordStore> store_ =
       base::MakeRefCounted<TestPasswordStore>();
   SavedPasswordsPresenter presenter_{store_};
@@ -498,6 +508,26 @@
               ElementsAreArray(store().stored_passwords().at(kExampleOrg)));
 }
 
+TEST_F(InsecureCredentialsManagerTest, StartWeakCheckOnEmptyPasswordsList) {
+  EXPECT_THAT(
+      histogram_tester().GetTotalCountsForPrefix("PasswordManager.WeakCheck"),
+      IsEmpty());
+
+  RunUntilIdle();
+  provider().StartWeakCheck();
+  AdvanceClock(base::TimeDelta::FromMilliseconds(kDelay));
+  RunUntilIdle();
+
+  EXPECT_THAT(provider().GetWeakCredentials(), IsEmpty());
+
+  histogram_tester().ExpectUniqueSample(
+      "PasswordManager.WeakCheck.CheckedPasswords", 0, 1);
+  histogram_tester().ExpectUniqueSample("PasswordManager.WeakCheck.Time",
+                                        kDelay, 1);
+  histogram_tester().ExpectUniqueSample(
+      "PasswordManager.WeakCheck.WeakPasswords", 0, 1);
+}
+
 TEST_F(InsecureCredentialsManagerTest, WeakCredentialsNotFound) {
   std::vector<PasswordForm> passwords = {
       MakeSavedPassword(kExampleCom, kUsername1, kStrongPassword1),
@@ -505,12 +535,25 @@
 
   store().AddLogin(passwords[0]);
   store().AddLogin(passwords[1]);
+  EXPECT_THAT(
+      histogram_tester().GetTotalCountsForPrefix("PasswordManager.WeakCheck"),
+      IsEmpty());
 
   RunUntilIdle();
   provider().StartWeakCheck();
+  AdvanceClock(base::TimeDelta::FromMilliseconds(2 * kDelay));
   RunUntilIdle();
 
   EXPECT_THAT(provider().GetWeakCredentials(), IsEmpty());
+
+  histogram_tester().ExpectUniqueSample(
+      "PasswordManager.WeakCheck.CheckedPasswords", 2, 1);
+  histogram_tester().ExpectUniqueSample("PasswordManager.WeakCheck.Time",
+                                        2 * kDelay, 1);
+  histogram_tester().ExpectUniqueSample(
+      "PasswordManager.WeakCheck.WeakPasswords", 0, 1);
+  histogram_tester().ExpectUniqueSample(
+      "PasswordManager.WeakCheck.PasswordScore", 4, 2);
 }
 
 TEST_F(InsecureCredentialsManagerTest, DetectedWeakCredential) {
@@ -520,9 +563,13 @@
 
   store().AddLogin(passwords[0]);
   store().AddLogin(passwords[1]);
+  EXPECT_THAT(
+      histogram_tester().GetTotalCountsForPrefix("PasswordManager.WeakCheck"),
+      IsEmpty());
 
   RunUntilIdle();
   provider().StartWeakCheck();
+  AdvanceClock(base::TimeDelta::FromMilliseconds(kDelay));
   RunUntilIdle();
 
   std::vector<CredentialWithPassword> weak_credentials =
@@ -531,6 +578,15 @@
   ASSERT_EQ(weak_credentials.size(), 1u);
   EXPECT_EQ(base::UTF16ToUTF8(weak_credentials[0].password), kWeakPassword1);
   EXPECT_TRUE(IsWeak(weak_credentials[0].insecure_type));
+
+  histogram_tester().ExpectUniqueSample(
+      "PasswordManager.WeakCheck.CheckedPasswords", 2, 1);
+  histogram_tester().ExpectUniqueSample("PasswordManager.WeakCheck.Time",
+                                        kDelay, 1);
+  histogram_tester().ExpectUniqueSample(
+      "PasswordManager.WeakCheck.WeakPasswords", 1, 1);
+  histogram_tester().ExpectTotalCount("PasswordManager.WeakCheck.PasswordScore",
+                                      2);
 }
 
 // Tests that credentials with the same signon_realm and username, but different
@@ -546,6 +602,7 @@
 
   RunUntilIdle();
   provider().StartWeakCheck();
+  AdvanceClock(base::TimeDelta::FromMilliseconds(kDelay));
   RunUntilIdle();
 
   std::vector<CredentialWithPassword> weak_credentials =
@@ -554,6 +611,15 @@
   ASSERT_EQ(weak_credentials.size(), 2u);
   EXPECT_TRUE(IsWeak(weak_credentials[0].insecure_type));
   EXPECT_TRUE(IsWeak(weak_credentials[1].insecure_type));
+
+  histogram_tester().ExpectUniqueSample(
+      "PasswordManager.WeakCheck.CheckedPasswords", 2, 1);
+  histogram_tester().ExpectUniqueSample("PasswordManager.WeakCheck.Time",
+                                        kDelay, 1);
+  histogram_tester().ExpectUniqueSample(
+      "PasswordManager.WeakCheck.WeakPasswords", 2, 1);
+  histogram_tester().ExpectTotalCount("PasswordManager.WeakCheck.PasswordScore",
+                                      2);
 }
 
 // Tests that credentials with the same signon_realm, username and passwords
@@ -569,6 +635,7 @@
 
   RunUntilIdle();
   provider().StartWeakCheck();
+  AdvanceClock(base::TimeDelta::FromMilliseconds(kDelay));
   RunUntilIdle();
 
   std::vector<CredentialWithPassword> weak_credentials =
@@ -577,6 +644,15 @@
   ASSERT_EQ(weak_credentials.size(), 1u);
   EXPECT_EQ(base::UTF16ToUTF8(weak_credentials[0].password), kWeakPassword1);
   EXPECT_TRUE(IsWeak(weak_credentials[0].insecure_type));
+
+  histogram_tester().ExpectUniqueSample(
+      "PasswordManager.WeakCheck.CheckedPasswords", 1, 1);
+  histogram_tester().ExpectUniqueSample("PasswordManager.WeakCheck.Time",
+                                        kDelay, 1);
+  histogram_tester().ExpectUniqueSample(
+      "PasswordManager.WeakCheck.WeakPasswords", 1, 1);
+  histogram_tester().ExpectUniqueSample(
+      "PasswordManager.WeakCheck.PasswordScore", 0, 1);
 }
 
 TEST_F(InsecureCredentialsManagerTest, BothWeakAndCompromisedCredentialsExist) {
@@ -594,6 +670,7 @@
 
   RunUntilIdle();
   provider().StartWeakCheck();
+  AdvanceClock(base::TimeDelta::FromMilliseconds(kDelay));
   RunUntilIdle();
 
   std::vector<CredentialWithPassword> returned_weak_credentials =
@@ -609,6 +686,15 @@
   ASSERT_EQ(returned_compromised_credentials.size(), 2u);
   EXPECT_TRUE(IsCompromised(returned_compromised_credentials[0].insecure_type));
   EXPECT_TRUE(IsCompromised(returned_compromised_credentials[1].insecure_type));
+
+  histogram_tester().ExpectUniqueSample(
+      "PasswordManager.WeakCheck.CheckedPasswords", 2, 1);
+  histogram_tester().ExpectUniqueSample("PasswordManager.WeakCheck.Time",
+                                        kDelay, 1);
+  histogram_tester().ExpectUniqueSample(
+      "PasswordManager.WeakCheck.WeakPasswords", 1, 1);
+  histogram_tester().ExpectTotalCount("PasswordManager.WeakCheck.PasswordScore",
+                                      2);
 }
 
 // Checks that for a credential that is both weak and compromised,
@@ -625,6 +711,7 @@
 
   RunUntilIdle();
   provider().StartWeakCheck();
+  AdvanceClock(base::TimeDelta::FromMilliseconds(kDelay));
   RunUntilIdle();
 
   std::vector<CredentialWithPassword> returned_weak_credentials =
@@ -646,6 +733,15 @@
             kWeakPassword1);
   EXPECT_TRUE(IsWeak(returned_compromised_credentials[0].insecure_type));
   EXPECT_TRUE(IsCompromised(returned_compromised_credentials[0].insecure_type));
+
+  histogram_tester().ExpectUniqueSample(
+      "PasswordManager.WeakCheck.CheckedPasswords", 1, 1);
+  histogram_tester().ExpectUniqueSample("PasswordManager.WeakCheck.Time",
+                                        kDelay, 1);
+  histogram_tester().ExpectUniqueSample(
+      "PasswordManager.WeakCheck.WeakPasswords", 1, 1);
+  histogram_tester().ExpectUniqueSample(
+      "PasswordManager.WeakCheck.PasswordScore", 0, 1);
 }
 
 // Test verifies that saving LeakCheckCredential via provider adds expected
diff --git a/components/password_manager/core/browser/ui/weak_check_utility.cc b/components/password_manager/core/browser/ui/weak_check_utility.cc
index 3a182bb5..567ae29d 100644
--- a/components/password_manager/core/browser/ui/weak_check_utility.cc
+++ b/components/password_manager/core/browser/ui/weak_check_utility.cc
@@ -4,6 +4,7 @@
 
 #include "components/password_manager/core/browser/ui/weak_check_utility.h"
 
+#include "base/metrics/histogram_functions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "third_party/zxcvbn-cpp/native-src/zxcvbn/matching.hpp"
 #include "third_party/zxcvbn-cpp/native-src/zxcvbn/scoring.hpp"
@@ -13,6 +14,17 @@
 
 namespace {
 
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum class PasswordWeaknessScore {
+  kTooGuessablePassword = 0,
+  kVeryGuessablePassword = 1,
+  kSomewhatGuessablePassword = 2,
+  kSafelyUnguessablePassword = 3,
+  kVeryUnguessablePassword = 4,
+  kMaxValue = kVeryUnguessablePassword,
+};
+
 // Passwords longer than this constant should not be checked for weakness using
 // the zxcvbn-cpp library. This is because the runtime grows extremely, starting
 // at a password length of 40.
@@ -33,16 +45,25 @@
   std::vector<zxcvbn::Match> matches = zxcvbn::omnimatch(password);
   zxcvbn::ScoringResult result =
       zxcvbn::most_guessable_match_sequence(password, matches);
-  return zxcvbn::estimate_attack_times(result.guesses).score;
+
+  int score = zxcvbn::estimate_attack_times(result.guesses).score;
+  base::UmaHistogramEnumeration("PasswordManager.WeakCheck.PasswordScore",
+                                static_cast<PasswordWeaknessScore>(score));
+  return score;
 }
 
 }  // namespace
 
 base::flat_set<base::string16> BulkWeakCheck(
     base::flat_set<base::string16> passwords) {
+  base::UmaHistogramCounts1000("PasswordManager.WeakCheck.CheckedPasswords",
+                               passwords.size());
   base::EraseIf(passwords, [](const auto& password) {
     return kLowSeverityScore < PasswordWeakCheck(password);
   });
+
+  base::UmaHistogramCounts1000("PasswordManager.WeakCheck.WeakPasswords",
+                               passwords.size());
   return passwords;
 }
 
diff --git a/components/performance_manager/public/v8_memory/v8_per_frame_memory_decorator.h b/components/performance_manager/public/v8_memory/v8_per_frame_memory_decorator.h
index 770703c..98d4dc6c 100644
--- a/components/performance_manager/public/v8_memory/v8_per_frame_memory_decorator.h
+++ b/components/performance_manager/public/v8_memory/v8_per_frame_memory_decorator.h
@@ -248,6 +248,11 @@
       public ProcessNode::ObserverDefaultImpl,
       public NodeDataDescriberDefaultImpl {
  public:
+  // A priority queue of memory requests. The decorator will hold a global
+  // queue of requests that measure every process, and each ProcessNode will
+  // have a queue of requests that measure only that process.
+  class MeasurementRequestQueue;
+
   V8PerFrameMemoryDecorator();
   ~V8PerFrameMemoryDecorator() override;
 
@@ -261,23 +266,27 @@
 
   // ProcessNodeObserver overrides.
   void OnProcessNodeAdded(const ProcessNode* process_node) override;
+  void OnBeforeProcessNodeRemoved(const ProcessNode* process_node) override;
 
   // NodeDataDescriber overrides.
   base::Value DescribeFrameNodeData(const FrameNode* node) const override;
   base::Value DescribeProcessNodeData(const ProcessNode* node) const override;
 
   // Returns the next measurement request that should be scheduled.
-  V8PerFrameMemoryRequest* GetNextRequest() const;
+  const V8PerFrameMemoryRequest* GetNextRequest() const;
 
   // Returns the next measurement request with mode kBounded or
   // kEagerForTesting that should be scheduled.
-  V8PerFrameMemoryRequest* GetNextBoundedRequest() const;
+  const V8PerFrameMemoryRequest* GetNextBoundedRequest() const;
 
   // Implementation details below this point.
 
   // V8PerFrameMemoryRequest objects register themselves with the decorator.
+  // If |process_node| is null, the request will be sent to every process,
+  // otherwise it will be sent only to |process_node|.
   void AddMeasurementRequest(util::PassKey<V8PerFrameMemoryRequest>,
-                             V8PerFrameMemoryRequest* request);
+                             V8PerFrameMemoryRequest* request,
+                             const ProcessNode* process_node = nullptr);
   void RemoveMeasurementRequest(util::PassKey<V8PerFrameMemoryRequest>,
                                 V8PerFrameMemoryRequest* request);
 
@@ -289,13 +298,18 @@
       const ProcessNode* process_node) const;
 
  private:
+  using RequestQueueCallback =
+      base::RepeatingCallback<void(MeasurementRequestQueue*)>;
+
+  // Runs the given |callback| for every MeasurementRequestQueue (global and
+  // per-process).
+  void ApplyToAllRequestQueues(RequestQueueCallback callback) const;
+
   void UpdateProcessMeasurementSchedules() const;
 
   Graph* graph_ = nullptr;
 
-  // Lists of requests sorted by min_time_between_requests (lowest first).
-  std::vector<V8PerFrameMemoryRequest*> bounded_measurement_requests_;
-  std::vector<V8PerFrameMemoryRequest*> lazy_measurement_requests_;
+  std::unique_ptr<MeasurementRequestQueue> measurement_requests_;
 
   SEQUENCE_CHECKER(sequence_checker_);
 };
@@ -354,10 +368,16 @@
 
   MeasurementMode mode() const { return mode_; }
 
-  // Requests measurements for all ProcessNode's in |graph|. This must only be
-  // called once for each V8PerFrameMemoryRequest.
+  // Requests measurements for all ProcessNode's in |graph|. There must be at
+  // most one call to this or StartMeasurementForProcess for each
+  // V8PerFrameMemoryRequest.
   void StartMeasurement(Graph* graph);
 
+  // Requests measurements only for the given |process_node|, which must be a
+  // renderer process. There must be at most one call to this or
+  // StartMeasurement for each V8PerFrameMemoryRequest.
+  void StartMeasurementForProcess(const ProcessNode* process_node);
+
   // Adds/removes an observer.
   void AddObserver(V8PerFrameMemoryObserver* observer);
   void RemoveObserver(V8PerFrameMemoryObserver* observer);
@@ -374,17 +394,21 @@
       MeasurementMode mode,
       base::WeakPtr<V8PerFrameMemoryRequestAnySeq> off_sequence_request);
 
-  // V8PerFrameMemoryDecorator calls OnDecoratorUnregistered when it is removed
-  // from the graph.
-  void OnDecoratorUnregistered(util::PassKey<V8PerFrameMemoryDecorator>);
+  // V8PerFrameMemoryDecorator::MeasurementRequestQueue calls
+  // OnOwnerUnregistered for all requests in the queue when the owning
+  // decorator or process node is removed from the graph.
+  void OnOwnerUnregistered(
+      util::PassKey<V8PerFrameMemoryDecorator::MeasurementRequestQueue>);
 
-  // V8PerFrameMemoryDecorator calls NotifyObserversOnMeasurementAvailable when
-  // a measurement is received.
+  // V8PerFrameMemoryDecorator::MeasurementRequestQueue calls
+  // NotifyObserversOnMeasurementAvailable when a measurement is received.
   void NotifyObserversOnMeasurementAvailable(
-      util::PassKey<V8PerFrameMemoryDecorator>,
+      util::PassKey<V8PerFrameMemoryDecorator::MeasurementRequestQueue>,
       const ProcessNode* process_node) const;
 
  private:
+  void StartMeasurementImpl(Graph* graph, const ProcessNode* process_node);
+
   base::TimeDelta min_time_between_requests_;
   MeasurementMode mode_;
   V8PerFrameMemoryDecorator* decorator_ = nullptr;
diff --git a/components/performance_manager/v8_memory/v8_per_frame_memory_decorator.cc b/components/performance_manager/v8_memory/v8_per_frame_memory_decorator.cc
index 4f4d32d..3820dd7 100644
--- a/components/performance_manager/v8_memory/v8_per_frame_memory_decorator.cc
+++ b/components/performance_manager/v8_memory/v8_per_frame_memory_decorator.cc
@@ -31,6 +31,37 @@
 
 namespace v8_memory {
 
+class V8PerFrameMemoryDecorator::MeasurementRequestQueue {
+ public:
+  MeasurementRequestQueue() = default;
+
+  ~MeasurementRequestQueue();
+
+  const V8PerFrameMemoryRequest* GetNextRequest() const;
+  const V8PerFrameMemoryRequest* GetNextBoundedRequest() const;
+
+  void AddMeasurementRequest(V8PerFrameMemoryRequest* request);
+
+  // Removes |request| if it is part of this queue, and returns the number of
+  // elements removed (will be 0 or 1).
+  size_t RemoveMeasurementRequest(V8PerFrameMemoryRequest* request);
+
+  void NotifyObserversOnMeasurementAvailable(
+      const ProcessNode* process_node) const;
+
+  void OnOwnerUnregistered();
+
+  // Check the data invariant on the measurement request lists.
+  void Validate();
+
+ private:
+  // Lists of requests sorted by min_time_between_requests (lowest first).
+  std::vector<V8PerFrameMemoryRequest*> bounded_measurement_requests_;
+  std::vector<V8PerFrameMemoryRequest*> lazy_measurement_requests_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+};
+
 // This class is allowed to access
 // V8PerFrameMemoryDecorator::NotifyObserversOnMeasurementAvailable.
 class V8PerFrameMemoryDecorator::ObserverNotifier {
@@ -71,6 +102,25 @@
   }
 }
 
+// Returns the higher priority request of |a| and |b|, either of which can be
+// null, or nullptr if both are null.
+const V8PerFrameMemoryRequest* ChooseHigherPriorityRequest(
+    const V8PerFrameMemoryRequest* a,
+    const V8PerFrameMemoryRequest* b) {
+  if (!a)
+    return b;
+  if (!b)
+    return a;
+  if (a->min_time_between_requests() < b->min_time_between_requests())
+    return a;
+  if (b->min_time_between_requests() < a->min_time_between_requests())
+    return b;
+  // Break ties by prioritizing bounded requests.
+  if (IsMeasurementBounded(a->mode()))
+    return a;
+  return b;
+}
+
 internal::BindV8DetailedMemoryReporterCallback* g_test_bind_callback = nullptr;
 
 #if DCHECK_IS_ON()
@@ -163,6 +213,13 @@
   NodeAttachedProcessData(const NodeAttachedProcessData&) = delete;
   NodeAttachedProcessData& operator=(const NodeAttachedProcessData&) = delete;
 
+  // Runs the given |callback| for every ProcessNode in |graph| with type
+  // PROCESS_TYPE_RENDERER, passing the NodeAttachedProcessData attached to the
+  // node.
+  static void ApplyToAllRenderers(
+      Graph* graph,
+      base::RepeatingCallback<void(NodeAttachedProcessData*)> callback);
+
   const V8PerFrameMemoryProcessData* data() const {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
     return data_available_ ? &data_ : nullptr;
@@ -170,6 +227,11 @@
 
   void ScheduleNextMeasurement();
 
+  V8PerFrameMemoryDecorator::MeasurementRequestQueue&
+  process_measurement_requests() {
+    return process_measurement_requests_;
+  }
+
  private:
   void StartMeasurement(MeasurementMode mode);
   void ScheduleUpgradeToBoundedMeasurement();
@@ -179,6 +241,10 @@
 
   const ProcessNode* const process_node_;
 
+  // Measurement requests that will be sent to this process only.
+  V8PerFrameMemoryDecorator::MeasurementRequestQueue
+      process_measurement_requests_;
+
   mojo::Remote<blink::mojom::V8DetailedMemoryReporter> resource_usage_reporter_;
 
   // State transitions:
@@ -219,8 +285,25 @@
   ScheduleNextMeasurement();
 }
 
+// static
+void NodeAttachedProcessData::ApplyToAllRenderers(
+    Graph* graph,
+    base::RepeatingCallback<void(NodeAttachedProcessData*)> callback) {
+  for (const ProcessNode* node : graph->GetAllProcessNodes()) {
+    NodeAttachedProcessData* process_data = NodeAttachedProcessData::Get(node);
+    if (!process_data) {
+      // NodeAttachedProcessData should have been created for all renderer
+      // processes in OnProcessNodeAdded.
+      DCHECK_NE(content::PROCESS_TYPE_RENDERER, node->GetProcessType());
+      continue;
+    }
+    callback.Run(process_data);
+  }
+}
+
 void NodeAttachedProcessData::ScheduleNextMeasurement() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  process_measurement_requests_.Validate();
 
   if (state_ == State::kMeasuringLazy) {
     // Upgrade to a bounded measurement if the lazy measurement is taking too
@@ -236,11 +319,15 @@
     return;
   }
 
-  V8PerFrameMemoryRequest* next_request = nullptr;
+  // Find the next request for this process, checking both the per-process
+  // queue and the global queue.
+  const V8PerFrameMemoryRequest* next_request =
+      process_measurement_requests_.GetNextRequest();
   auto* decorator =
       V8PerFrameMemoryDecorator::GetFromGraph(process_node_->GetGraph());
   if (decorator) {
-    next_request = decorator->GetNextRequest();
+    next_request =
+        ChooseHigherPriorityRequest(next_request, decorator->GetNextRequest());
   }
 
   if (!next_request) {
@@ -316,11 +403,13 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK_EQ(state_, State::kMeasuringLazy);
 
-  V8PerFrameMemoryRequest* bounded_request = nullptr;
+  const V8PerFrameMemoryRequest* bounded_request =
+      process_measurement_requests_.GetNextBoundedRequest();
   auto* decorator =
       V8PerFrameMemoryDecorator::GetFromGraph(process_node_->GetGraph());
   if (decorator) {
-    bounded_request = decorator->GetNextBoundedRequest();
+    bounded_request = ChooseHigherPriorityRequest(
+        bounded_request, decorator->GetNextBoundedRequest());
   }
   if (!bounded_request) {
     // All measurements have been cancelled, or decorator was removed from
@@ -422,6 +511,8 @@
     ScheduleNextMeasurement();
   }
 
+  process_measurement_requests_.NotifyObserversOnMeasurementAvailable(
+      process_node_);
   V8PerFrameMemoryDecorator::ObserverNotifier()
       .NotifyObserversOnMeasurementAvailable(process_node_);
 }
@@ -524,17 +615,15 @@
 
 void V8PerFrameMemoryRequest::StartMeasurement(Graph* graph) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK_EQ(nullptr, decorator_);
-  decorator_ = V8PerFrameMemoryDecorator::GetFromGraph(graph);
-  if (!decorator_) {
-    // Create the decorator when the first measurement starts.
-    auto decorator_ptr = std::make_unique<V8PerFrameMemoryDecorator>();
-    decorator_ = decorator_ptr.get();
-    graph->PassToGraph(std::move(decorator_ptr));
-  }
+  StartMeasurementImpl(graph, nullptr);
+}
 
-  decorator_->AddMeasurementRequest(util::PassKey<V8PerFrameMemoryRequest>(),
-                                    this);
+void V8PerFrameMemoryRequest::StartMeasurementForProcess(
+    const ProcessNode* process_node) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(process_node);
+  DCHECK_EQ(process_node->GetProcessType(), content::PROCESS_TYPE_RENDERER);
+  StartMeasurementImpl(process_node->GetGraph(), process_node);
 }
 
 void V8PerFrameMemoryRequest::AddObserver(V8PerFrameMemoryObserver* observer) {
@@ -549,14 +638,14 @@
   observers_.RemoveObserver(observer);
 }
 
-void V8PerFrameMemoryRequest::OnDecoratorUnregistered(
-    util::PassKey<V8PerFrameMemoryDecorator>) {
+void V8PerFrameMemoryRequest::OnOwnerUnregistered(
+    util::PassKey<V8PerFrameMemoryDecorator::MeasurementRequestQueue>) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   decorator_ = nullptr;
 }
 
 void V8PerFrameMemoryRequest::NotifyObserversOnMeasurementAvailable(
-    util::PassKey<V8PerFrameMemoryDecorator>,
+    util::PassKey<V8PerFrameMemoryDecorator::MeasurementRequestQueue>,
     const ProcessNode* process_node) const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   const auto* process_data =
@@ -596,6 +685,24 @@
   }
 }
 
+void V8PerFrameMemoryRequest::StartMeasurementImpl(
+    Graph* graph,
+    const ProcessNode* process_node) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK_EQ(nullptr, decorator_);
+  DCHECK(!process_node || graph == process_node->GetGraph());
+  decorator_ = V8PerFrameMemoryDecorator::GetFromGraph(graph);
+  if (!decorator_) {
+    // Create the decorator when the first measurement starts.
+    auto decorator_ptr = std::make_unique<V8PerFrameMemoryDecorator>();
+    decorator_ = decorator_ptr.get();
+    graph->PassToGraph(std::move(decorator_ptr));
+  }
+
+  decorator_->AddMeasurementRequest(util::PassKey<V8PerFrameMemoryRequest>(),
+                                    this, process_node);
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // V8PerFrameMemoryFrameData
 
@@ -617,12 +724,10 @@
 ////////////////////////////////////////////////////////////////////////////////
 // V8PerFrameMemoryDecorator
 
-V8PerFrameMemoryDecorator::V8PerFrameMemoryDecorator() = default;
+V8PerFrameMemoryDecorator::V8PerFrameMemoryDecorator()
+    : measurement_requests_(std::make_unique<MeasurementRequestQueue>()) {}
 
-V8PerFrameMemoryDecorator::~V8PerFrameMemoryDecorator() {
-  DCHECK(bounded_measurement_requests_.empty());
-  DCHECK(lazy_measurement_requests_.empty());
-}
+V8PerFrameMemoryDecorator::~V8PerFrameMemoryDecorator() = default;
 
 void V8PerFrameMemoryDecorator::OnPassedToGraph(Graph* graph) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -643,17 +748,9 @@
 void V8PerFrameMemoryDecorator::OnTakenFromGraph(Graph* graph) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK_EQ(graph, graph_);
-  for (V8PerFrameMemoryRequest* request : bounded_measurement_requests_) {
-    request->OnDecoratorUnregistered(
-        util::PassKey<V8PerFrameMemoryDecorator>());
-  }
-  bounded_measurement_requests_.clear();
-  for (V8PerFrameMemoryRequest* request : lazy_measurement_requests_) {
-    request->OnDecoratorUnregistered(
-        util::PassKey<V8PerFrameMemoryDecorator>());
-  }
-  lazy_measurement_requests_.clear();
 
+  ApplyToAllRequestQueues(
+      base::BindRepeating(&MeasurementRequestQueue::OnOwnerUnregistered));
   UpdateProcessMeasurementSchedules();
 
   graph->GetNodeDataDescriberRegistry()->UnregisterDescriber(this);
@@ -676,6 +773,18 @@
   NodeAttachedProcessData::GetOrCreate(process_node);
 }
 
+void V8PerFrameMemoryDecorator::OnBeforeProcessNodeRemoved(
+    const ProcessNode* process_node) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // Only renderer processes have data.
+  if (process_node->GetProcessType() != content::PROCESS_TYPE_RENDERER)
+    return;
+
+  auto* process_data = NodeAttachedProcessData::Get(process_node);
+  DCHECK(process_data);
+  process_data->process_measurement_requests().OnOwnerUnregistered();
+}
+
 base::Value V8PerFrameMemoryDecorator::DescribeFrameNodeData(
     const FrameNode* frame_node) const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -705,23 +814,95 @@
   return dict;
 }
 
-V8PerFrameMemoryRequest* V8PerFrameMemoryDecorator::GetNextRequest() const {
+const V8PerFrameMemoryRequest* V8PerFrameMemoryDecorator::GetNextRequest()
+    const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  V8PerFrameMemoryRequest* next_bounded_request = GetNextBoundedRequest();
-  if (lazy_measurement_requests_.empty())
-    return next_bounded_request;
-  V8PerFrameMemoryRequest* next_lazy_request =
-      lazy_measurement_requests_.front();
-  // Prioritize bounded requests.
-  if (next_bounded_request &&
-      next_bounded_request->min_time_between_requests() <=
-          next_lazy_request->min_time_between_requests()) {
-    return next_bounded_request;
-  }
-  return next_lazy_request;
+  return measurement_requests_->GetNextRequest();
 }
 
-V8PerFrameMemoryRequest* V8PerFrameMemoryDecorator::GetNextBoundedRequest()
+const V8PerFrameMemoryRequest*
+V8PerFrameMemoryDecorator::GetNextBoundedRequest() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return measurement_requests_->GetNextBoundedRequest();
+}
+
+void V8PerFrameMemoryDecorator::AddMeasurementRequest(
+    util::PassKey<V8PerFrameMemoryRequest> key,
+    V8PerFrameMemoryRequest* request,
+    const ProcessNode* process_node) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (process_node) {
+    auto* process_data = NodeAttachedProcessData::Get(process_node);
+    DCHECK(process_data);
+    process_data->process_measurement_requests().AddMeasurementRequest(request);
+  } else {
+    measurement_requests_->AddMeasurementRequest(request);
+  }
+  UpdateProcessMeasurementSchedules();
+}
+
+void V8PerFrameMemoryDecorator::RemoveMeasurementRequest(
+    util::PassKey<V8PerFrameMemoryRequest> key,
+    V8PerFrameMemoryRequest* request) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // Attempt to remove this request from all process-specific queues and the
+  // global queue. It will only be in one of them.
+  size_t removal_count = 0;
+  // Unretained is safe because this callback is synchronous.
+  ApplyToAllRequestQueues(base::BindRepeating(
+      [](V8PerFrameMemoryRequest* request, size_t* removal_count,
+         MeasurementRequestQueue* queue) {
+        (*removal_count) += queue->RemoveMeasurementRequest(request);
+      },
+      base::Unretained(request), base::Unretained(&removal_count)));
+  DCHECK_EQ(removal_count, 1ULL);
+  UpdateProcessMeasurementSchedules();
+}
+
+void V8PerFrameMemoryDecorator::ApplyToAllRequestQueues(
+    RequestQueueCallback callback) const {
+  callback.Run(measurement_requests_.get());
+  NodeAttachedProcessData::ApplyToAllRenderers(
+      graph_, base::BindRepeating(
+                  [](RequestQueueCallback callback,
+                     NodeAttachedProcessData* process_data) {
+                    callback.Run(&process_data->process_measurement_requests());
+                  },
+                  std::move(callback)));
+}
+
+void V8PerFrameMemoryDecorator::UpdateProcessMeasurementSchedules() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(graph_);
+  measurement_requests_->Validate();
+  NodeAttachedProcessData::ApplyToAllRenderers(
+      graph_,
+      base::BindRepeating(&NodeAttachedProcessData::ScheduleNextMeasurement));
+}
+
+void V8PerFrameMemoryDecorator::NotifyObserversOnMeasurementAvailable(
+    util::PassKey<ObserverNotifier> key,
+    const ProcessNode* process_node) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  measurement_requests_->NotifyObserversOnMeasurementAvailable(process_node);
+}
+
+V8PerFrameMemoryDecorator::MeasurementRequestQueue::~MeasurementRequestQueue() {
+  DCHECK(bounded_measurement_requests_.empty());
+  DCHECK(lazy_measurement_requests_.empty());
+}
+
+const V8PerFrameMemoryRequest*
+V8PerFrameMemoryDecorator::MeasurementRequestQueue::GetNextRequest() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return ChooseHigherPriorityRequest(GetNextBoundedRequest(),
+                                     lazy_measurement_requests_.empty()
+                                         ? nullptr
+                                         : lazy_measurement_requests_.front());
+}
+
+const V8PerFrameMemoryRequest*
+V8PerFrameMemoryDecorator::MeasurementRequestQueue::GetNextBoundedRequest()
     const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return bounded_measurement_requests_.empty()
@@ -729,8 +910,7 @@
              : bounded_measurement_requests_.front();
 }
 
-void V8PerFrameMemoryDecorator::AddMeasurementRequest(
-    util::PassKey<V8PerFrameMemoryRequest> key,
+void V8PerFrameMemoryDecorator::MeasurementRequestQueue::AddMeasurementRequest(
     V8PerFrameMemoryRequest* request) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(request);
@@ -739,7 +919,7 @@
                                             : lazy_measurement_requests_;
   DCHECK(!base::Contains(measurement_requests, request))
       << "V8PerFrameMemoryRequest object added twice";
-  // Each user of this decorator is expected to issue a single
+  // Each user of the decorator is expected to issue a single
   // V8PerFrameMemoryRequest, so the size of measurement_requests is too low
   // to make the complexity of real priority queue worthwhile.
   for (std::vector<V8PerFrameMemoryRequest*>::const_iterator it =
@@ -748,33 +928,52 @@
     if (request->min_time_between_requests() <
         (*it)->min_time_between_requests()) {
       measurement_requests.insert(it, request);
-      UpdateProcessMeasurementSchedules();
       return;
     }
   }
   measurement_requests.push_back(request);
-  UpdateProcessMeasurementSchedules();
 }
 
-void V8PerFrameMemoryDecorator::RemoveMeasurementRequest(
-    util::PassKey<V8PerFrameMemoryRequest> key,
+size_t
+V8PerFrameMemoryDecorator::MeasurementRequestQueue::RemoveMeasurementRequest(
     V8PerFrameMemoryRequest* request) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(request);
-  size_t num_erased = base::Erase(IsMeasurementBounded(request->mode())
-                                      ? bounded_measurement_requests_
-                                      : lazy_measurement_requests_,
-                                  request);
-  DCHECK_EQ(num_erased, 1ULL);
-  UpdateProcessMeasurementSchedules();
+  return base::Erase(IsMeasurementBounded(request->mode())
+                         ? bounded_measurement_requests_
+                         : lazy_measurement_requests_,
+                     request);
 }
 
-void V8PerFrameMemoryDecorator::UpdateProcessMeasurementSchedules() const {
+void V8PerFrameMemoryDecorator::MeasurementRequestQueue::
+    NotifyObserversOnMeasurementAvailable(
+        const ProcessNode* process_node) const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(graph_);
+  for (const V8PerFrameMemoryRequest* request : bounded_measurement_requests_) {
+    request->NotifyObserversOnMeasurementAvailable(
+        util::PassKey<MeasurementRequestQueue>(), process_node);
+  }
+  for (const V8PerFrameMemoryRequest* request : lazy_measurement_requests_) {
+    request->NotifyObserversOnMeasurementAvailable(
+        util::PassKey<MeasurementRequestQueue>(), process_node);
+  }
+}
+
+void V8PerFrameMemoryDecorator::MeasurementRequestQueue::OnOwnerUnregistered() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  for (V8PerFrameMemoryRequest* request : bounded_measurement_requests_) {
+    request->OnOwnerUnregistered(util::PassKey<MeasurementRequestQueue>());
+  }
+  bounded_measurement_requests_.clear();
+  for (V8PerFrameMemoryRequest* request : lazy_measurement_requests_) {
+    request->OnOwnerUnregistered(util::PassKey<MeasurementRequestQueue>());
+  }
+  lazy_measurement_requests_.clear();
+}
+
+void V8PerFrameMemoryDecorator::MeasurementRequestQueue::Validate() {
 #if DCHECK_IS_ON()
-  // Check the data invariant on measurement_requests, which will be used by
-  // ScheduleNextMeasurement.
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   auto check_invariants =
       [](const std::vector<V8PerFrameMemoryRequest*>& measurement_requests,
          bool is_bounded) {
@@ -792,30 +991,6 @@
   check_invariants(bounded_measurement_requests_, true);
   check_invariants(lazy_measurement_requests_, false);
 #endif
-  for (const ProcessNode* node : graph_->GetAllProcessNodes()) {
-    NodeAttachedProcessData* process_data = NodeAttachedProcessData::Get(node);
-    if (!process_data) {
-      DCHECK_NE(content::PROCESS_TYPE_RENDERER, node->GetProcessType())
-          << "NodeAttachedProcessData should have been created for all "
-             "renderer processes in OnProcessNodeAdded.";
-      continue;
-    }
-    process_data->ScheduleNextMeasurement();
-  }
-}
-
-void V8PerFrameMemoryDecorator::NotifyObserversOnMeasurementAvailable(
-    util::PassKey<ObserverNotifier> key,
-    const ProcessNode* process_node) const {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  for (V8PerFrameMemoryRequest* request : bounded_measurement_requests_) {
-    request->NotifyObserversOnMeasurementAvailable(
-        util::PassKey<V8PerFrameMemoryDecorator>(), process_node);
-  }
-  for (V8PerFrameMemoryRequest* request : lazy_measurement_requests_) {
-    request->NotifyObserversOnMeasurementAvailable(
-        util::PassKey<V8PerFrameMemoryDecorator>(), process_node);
-  }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/components/performance_manager/v8_memory/v8_per_frame_memory_decorator_unittest.cc b/components/performance_manager/v8_memory/v8_per_frame_memory_decorator_unittest.cc
index 9224b0f..459d436 100644
--- a/components/performance_manager/v8_memory/v8_per_frame_memory_decorator_unittest.cc
+++ b/components/performance_manager/v8_memory/v8_per_frame_memory_decorator_unittest.cc
@@ -40,9 +40,11 @@
 using ::testing::_;
 using ::testing::Eq;
 using ::testing::InSequence;
+using ::testing::Invoke;
 using ::testing::Mock;
 using ::testing::Property;
 using ::testing::StrictMock;
+using ::testing::WithArg;
 
 constexpr RenderProcessHostId kTestProcessID = RenderProcessHostId(0xFAB);
 constexpr uint64_t kUnassociatedBytes = 0xABBA;
@@ -191,16 +193,18 @@
       RenderProcessHostId expected_process_id = kTestProcessID,
       ExpectedMode expected_mode = ExpectedMode::DEFAULT) {
     InSequence seq;
-    EXPECT_CALL(*this, BindReceiverWithProxyHost(_, _))
-        .WillOnce(
-            [mock_reporter, expected_process_id](
-                mojo::PendingReceiver<blink::mojom::V8DetailedMemoryReporter>
-                    pending_receiver,
-                RenderProcessHostProxy proxy) {
-              DCHECK_EQ(expected_process_id, proxy.render_process_host_id());
-              mock_reporter->Bind(std::move(pending_receiver));
-            });
-
+    // Arg 0 is a
+    // mojo::PendingReceiver<blink::mojom::V8DetailedMemoryReporter>. Pass it
+    // to mock_reporter->Bind().
+    //
+    // Arg 1 is a RenderProcessHostProxy. Expect it to have the expected
+    // process ID.
+    EXPECT_CALL(*this,
+                BindReceiverWithProxyHost(
+                    _, Property(&RenderProcessHostProxy::render_process_host_id,
+                                Eq(expected_process_id))))
+        .WillOnce(WithArg<0>(
+            Invoke(mock_reporter, &MockV8DetailedMemoryReporter::Bind)));
     ExpectQueryAndReply(mock_reporter, std::move(data), expected_mode);
   }
 
@@ -290,6 +294,18 @@
   ExpectedMode expected_bounded_mode_;
 };
 
+class V8PerFrameMemoryDecoratorSingleProcessModeTest
+    : public V8PerFrameMemoryDecoratorTest,
+      public ::testing::WithParamInterface<MeasurementMode> {
+ public:
+  V8PerFrameMemoryDecoratorSingleProcessModeTest()
+      : single_process_mode_(GetParam()) {}
+
+ protected:
+  // The mode that will be used for single-process requests.
+  MeasurementMode single_process_mode_;
+};
+
 using V8PerFrameMemoryDecoratorDeathTest = V8PerFrameMemoryDecoratorTest;
 
 class V8PerFrameMemoryRequestAnySeqTest
@@ -520,7 +536,7 @@
   EXPECT_GT(process2_request_time, process1_request_time);
 }
 
-TEST_F(V8PerFrameMemoryDecoratorTest, MultipleisolatesInRenderer) {
+TEST_F(V8PerFrameMemoryDecoratorTest, MultipleIsolatesInRenderer) {
   V8PerFrameMemoryRequest memory_request(kMinTimeBetweenRequests, graph());
 
   MockV8DetailedMemoryReporter reporter;
@@ -1287,6 +1303,187 @@
   memory_request.RemoveObserver(&observer);
 }
 
+TEST_F(V8PerFrameMemoryDecoratorTest, SingleProcessRequest) {
+  // Create 2 renderer processes. Create one request that measures both of
+  // them, and one request that measures only one.
+  constexpr RenderProcessHostId kProcessId1 = RenderProcessHostId(0xFAB);
+  auto process1 = CreateNode<ProcessNodeImpl>(
+      content::PROCESS_TYPE_RENDERER,
+      RenderProcessHostProxy::CreateForTesting(kProcessId1));
+  constexpr RenderProcessHostId kProcessId2 = RenderProcessHostId(0xBAF);
+  auto process2 = CreateNode<ProcessNodeImpl>(
+      content::PROCESS_TYPE_RENDERER,
+      RenderProcessHostProxy::CreateForTesting(kProcessId2));
+
+  // Set the all process request to only send once within the test.
+  V8PerFrameMemoryRequest all_process_request(kMinTimeBetweenRequests * 100);
+  all_process_request.StartMeasurement(graph());
+
+  auto process1_request =
+      std::make_unique<V8PerFrameMemoryRequest>(kMinTimeBetweenRequests);
+  process1_request->StartMeasurementForProcess(process1.get());
+
+  MockV8DetailedMemoryReporter mock_reporter1;
+  MockV8DetailedMemoryReporter mock_reporter2;
+  {
+    // Response to initial request in process 1.
+    auto data = NewPerProcessV8MemoryUsage(1);
+    data->isolates[0]->unassociated_bytes_used = 1U;
+    ExpectBindAndRespondToQuery(&mock_reporter1, std::move(data), kProcessId1);
+
+    // Response to initial request in process 2.
+    data = NewPerProcessV8MemoryUsage(1);
+    data->isolates[0]->unassociated_bytes_used = 2U;
+    ExpectBindAndRespondToQuery(&mock_reporter2, std::move(data), kProcessId2);
+  }
+
+  // All the following FastForwardBy calls will place the clock 1 sec after a
+  // measurement is expected.
+  task_env().FastForwardBy(base::TimeDelta::FromSeconds(1));
+  testing::Mock::VerifyAndClearExpectations(&mock_reporter1);
+  testing::Mock::VerifyAndClearExpectations(&mock_reporter2);
+
+  ASSERT_TRUE(V8PerFrameMemoryProcessData::ForProcessNode(process1.get()));
+  EXPECT_EQ(1U, V8PerFrameMemoryProcessData::ForProcessNode(process1.get())
+                    ->unassociated_v8_bytes_used());
+
+  ASSERT_TRUE(V8PerFrameMemoryProcessData::ForProcessNode(process2.get()));
+  EXPECT_EQ(2U, V8PerFrameMemoryProcessData::ForProcessNode(process2.get())
+                    ->unassociated_v8_bytes_used());
+
+  // After kMinTimeBetweenRequests another request should be sent to process1,
+  // but not process2.
+  {
+    auto data = NewPerProcessV8MemoryUsage(1);
+    data->isolates[0]->unassociated_bytes_used = 3U;
+    ExpectQueryAndDelayReply(&mock_reporter1, kMinTimeBetweenRequests,
+                             std::move(data));
+  }
+
+  task_env().FastForwardBy(kMinTimeBetweenRequests);
+  testing::Mock::VerifyAndClearExpectations(&mock_reporter1);
+  testing::Mock::VerifyAndClearExpectations(&mock_reporter2);
+
+  // Delete process1 request while waiting for measurement result.
+  process1_request.reset();
+  task_env().FastForwardBy(kMinTimeBetweenRequests);
+  testing::Mock::VerifyAndClearExpectations(&mock_reporter1);
+  testing::Mock::VerifyAndClearExpectations(&mock_reporter2);
+
+  ASSERT_TRUE(V8PerFrameMemoryProcessData::ForProcessNode(process1.get()));
+  EXPECT_EQ(3U, V8PerFrameMemoryProcessData::ForProcessNode(process1.get())
+                    ->unassociated_v8_bytes_used());
+
+  // Recreate process1 request. The new request will be sent immediately since
+  // enough time has passed since the last request.
+  {
+    auto data = NewPerProcessV8MemoryUsage(1);
+    data->isolates[0]->unassociated_bytes_used = 4U;
+    ExpectQueryAndReply(&mock_reporter1, std::move(data));
+  }
+
+  process1_request =
+      std::make_unique<V8PerFrameMemoryRequest>(kMinTimeBetweenRequests);
+  process1_request->StartMeasurementForProcess(process1.get());
+
+  // Test observers of single-process requests.
+  MockV8PerFrameMemoryObserver mock_observer;
+  process1_request->AddObserver(&mock_observer);
+  mock_observer.ExpectObservationOnProcess(process1.get(), 4U);
+
+  task_env().FastForwardBy(base::TimeDelta::FromSeconds(1));
+  testing::Mock::VerifyAndClearExpectations(&mock_reporter1);
+  testing::Mock::VerifyAndClearExpectations(&mock_reporter2);
+  testing::Mock::VerifyAndClearExpectations(&mock_observer);
+
+  ASSERT_TRUE(V8PerFrameMemoryProcessData::ForProcessNode(process1.get()));
+  EXPECT_EQ(4U, V8PerFrameMemoryProcessData::ForProcessNode(process1.get())
+                    ->unassociated_v8_bytes_used());
+
+  // Delete process1 while the request still exists. Nothing should crash.
+  process1.reset();
+  task_env().FastForwardBy(kMinTimeBetweenRequests);
+  testing::Mock::VerifyAndClearExpectations(&mock_reporter1);
+  testing::Mock::VerifyAndClearExpectations(&mock_reporter2);
+  testing::Mock::VerifyAndClearExpectations(&mock_observer);
+
+  // Clean up.
+  process1_request->RemoveObserver(&mock_observer);
+}
+
+TEST_P(V8PerFrameMemoryDecoratorSingleProcessModeTest,
+       SingleProcessLazyRequest) {
+  // Create a single process node so both "all process" and "single process"
+  // requests will have a single expectation, which reduces boilerplate.
+  auto process = CreateNode<ProcessNodeImpl>(
+      content::PROCESS_TYPE_RENDERER,
+      RenderProcessHostProxy::CreateForTesting(kTestProcessID));
+
+  V8PerFrameMemoryRequest lazy_request(kMinTimeBetweenRequests,
+                                       MeasurementMode::kLazy);
+  V8PerFrameMemoryRequest bounded_request(kMinTimeBetweenRequests * 2,
+                                          MeasurementMode::kBounded);
+  if (single_process_mode_ == MeasurementMode::kLazy) {
+    // Test that lazy single-process requests can't starve bounded all-process
+    // requests.
+    lazy_request.StartMeasurementForProcess(process.get());
+    bounded_request.StartMeasurement(graph());
+  } else {
+    // Test that lazy all-process requests can't starve bounded single-process
+    // requests.
+    lazy_request.StartMeasurement(graph());
+    bounded_request.StartMeasurementForProcess(process.get());
+  }
+
+  MockV8DetailedMemoryReporter mock_reporter;
+  {
+    // Response to initial request which is sent immediately. This will use the
+    // LAZY mode from |lazy_request| because it has a lower frequency.
+    auto data = NewPerProcessV8MemoryUsage(1);
+    data->isolates[0]->unassociated_bytes_used = 1U;
+    ExpectBindAndRespondToQuery(&mock_reporter, std::move(data), kTestProcessID,
+                                ExpectedMode::LAZY);
+  }
+
+  // All the following FastForwardBy calls will place the clock 1 sec after a
+  // measurement is expected.
+  task_env().FastForwardBy(base::TimeDelta::FromSeconds(1));
+  testing::Mock::VerifyAndClearExpectations(&mock_reporter);
+
+  // Delay next lazy reply and expect |bounded_request| to be sent while
+  // waiting.
+  {
+    auto data = NewPerProcessV8MemoryUsage(1);
+    data->isolates[0]->unassociated_bytes_used = 3U;
+    ExpectQueryAndDelayReply(&mock_reporter, 2 * kMinTimeBetweenRequests,
+                             std::move(data), ExpectedMode::LAZY);
+  }
+
+  task_env().FastForwardBy(kMinTimeBetweenRequests);
+  testing::Mock::VerifyAndClearExpectations(&mock_reporter);
+
+  // Lazy request sent, now 2*kMinTimeBetweenRequests until reply and
+  // 3*kMinTimeBetweenRequests until next lazy request. Advancing the clock
+  // should send |bounded_request| to both processes.
+  {
+    auto data = NewPerProcessV8MemoryUsage(1);
+    data->isolates[0]->unassociated_bytes_used = 4U;
+    ExpectQueryAndReply(&mock_reporter, std::move(data), ExpectedMode::DEFAULT);
+  }
+
+  task_env().FastForwardBy(kMinTimeBetweenRequests);
+  testing::Mock::VerifyAndClearExpectations(&mock_reporter);
+
+  ASSERT_TRUE(V8PerFrameMemoryProcessData::ForProcessNode(process.get()));
+  EXPECT_EQ(4U, V8PerFrameMemoryProcessData::ForProcessNode(process.get())
+                    ->unassociated_v8_bytes_used());
+}
+
+INSTANTIATE_TEST_SUITE_P(SingleProcessLazyOrBounded,
+                         V8PerFrameMemoryDecoratorSingleProcessModeTest,
+                         ::testing::Values(MeasurementMode::kLazy,
+                                           MeasurementMode::kBounded));
+
 TEST_F(V8PerFrameMemoryDecoratorDeathTest, MultipleStartMeasurement) {
   EXPECT_DCHECK_DEATH({
     V8PerFrameMemoryRequest request(kMinTimeBetweenRequests);
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index dfbb1a02..f7795a4 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -1150,13 +1150,15 @@
       'name': 'ParentalSupervision',
       'type': 'group',
       'caption': '''Parental supervision settings''',
-      'desc': '''Controls parental supervision policies, that are applied to child accounts only.''',
+      'desc': '''Controls parental supervision policies, that are applied to child accounts only.
+      These policies are not set in the admin console, but configured directly by Kids API Server.''',
       'policies': [
         'ParentAccessCodeConfig',
         'PerAppTimeLimits',
         'PerAppTimeLimitsWhitelist',
         'PerAppTimeLimitsAllowlist',
         'UsageTimeLimit',
+        'EduCoexistenceToSVersion',
       ],
     },
     {
@@ -3490,6 +3492,7 @@
       'features': {
         'dynamic_refresh': False,
         'per_profile': False,
+        'platform_only': True,
       },
       'example_value': '${users}/${user_name}/Chrome',
       'id': 63,
@@ -3619,15 +3622,15 @@
 
        Setting the policy to:
 
-      * Block dangerous downloads means all downloads are allowed, except for those that carry Safe Browsing warnings.
+      * Block dangerous downloads means all downloads are allowed, except for those that carry safety warnings.
 
-      * Block potentially dangerous downloads means all downloads allowed, except for those that carry Safe Browsing warnings of potentially dangerous downloads.
+      * Block potentially dangerous downloads means all downloads allowed, except for those that carry safety warnings of potentially dangerous downloads.
 
       * Block all downloads means all downloads are blocked.
 
-      * Block malicious downloads means all downloads are allowed, except for those that Safe Browsing assesses to be malware with high confidence. Unlike with dangerous downloads, this does not take into account file type, but does take into account the host.
+      * Block malicious downloads means all downloads are allowed, except for those assessed to be malware with high confidence. Unlike with dangerous downloads, this does not take into account file type, but does take into account the host.
 
-      * No special restrictions means the downloads go through the usual security restrictions based on Safe Browsing analysis results.
+      * No special restrictions means the downloads go through the usual security restrictions based on safety analysis results.
 
       Note: These restrictions apply to downloads triggered from webpage content, as well as the Download link... menu option. They don't apply to the download of the currently displayed page or to saving as PDF from the printing options. Read more about Safe Browsing ( https://developers.google.com/safe-browsing ).''',
       'label': '''Download restrictions''',
@@ -13654,7 +13657,7 @@
       'tags': [],
       'desc': '''Setting the policy to a valid value means <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> won't use SSL/TLS versions less than the specified version. Unrecognized values are ignored.
 
-      If this policy is not set, then <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> uses a default minimum version of TLS 1.0.''',
+      If this policy is not set, then <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> will show an error for TLS 1.0 and TLS 1.1, but the user will be able to bypass it.''',
     },
     {
       'name': 'SSLVersionFallbackMin',
@@ -16678,6 +16681,7 @@
       'features': {
         'dynamic_refresh': False,
         'per_profile': False,
+        'platform_only': True,
       },
       'example_value': False,
       'id': 381,
@@ -17664,6 +17668,7 @@
       'features': {
         'dynamic_refresh': False,
         'per_profile': False,
+        'platform_only': True,
       },
       'example_value': '37185d02-e055-11e7-80c1-9a214cf093ae',
       'id': 428,
@@ -17684,6 +17689,7 @@
       'features': {
         'dynamic_refresh': False,
         'per_profile': False,
+        'platform_only': True,
       },
       'example_value': '37185d02-e055-11e7-80c1-9a214cf093ae',
       'id': 510,
@@ -17703,6 +17709,7 @@
       'features': {
         'dynamic_refresh': False,
         'per_profile': False,
+        'platform_only': True,
       },
       'example_value': True,
       'id': 505,
@@ -23205,6 +23212,7 @@
       'features': {
         'dynamic_refresh': False,
         'per_profile': False,
+        'platform_only': True,
       },
       'example_value': True,
       'id': 760,
@@ -23477,6 +23485,31 @@
       If this policy is set to false (or not configured), no additional rules will be applied to Family Link accounts.
       If this policy is set to true, new Family Link user accounts will be allowed additionally to those defined in <ph name="DEVICE_USER_ALLOWLIST_POLICY_NAME">DeviceUserAllowlist</ph>.''',
     },
+    {
+      'name': 'EduCoexistenceToSVersion',
+      'owners': ['agawronska@chromium.org', 'danan@chromium.org', 'yilkal@chromium.org', 'cros-families-eng@google.com'],
+      'type': 'string',
+      'schema': {
+        'type': 'string',
+        'description': '''The valid version of Terms of Service derived from Google3 cl that introduced new Terms version.'''
+      },
+      'future_on': ['chrome_os'],
+      'tags': [],
+      'features': {
+        'dynamic_refresh': True,
+        'per_profile': True,
+      },
+      'example_value': '333024512',
+      'id': 790,
+      'caption': '''The valid version of Edu Coexistence Terms of Service''',
+      'desc': '''This policy indicates current valid version of Edu Coexistence Terms of Service.
+      It is compared with the version last accepted by the parent and used to prompt parent permission renewal when needed.
+
+      When this policy is set Terms of Service version can be validated.
+      When this policy is unset it is not possible to verify validity of Edu Coexistence Terms of Service.
+
+      This policy is only used for Family Link users.''',
+    },
   ],
 
   'messages': {
@@ -24406,6 +24439,6 @@
   ],
   'placeholders': [],
   'deleted_policy_ids': [114, 115, 412, 476, 544, 546, 562, 569, 578],
-  'highest_id_currently_used': 789,
+  'highest_id_currently_used': 790,
   'highest_atomic_group_id_currently_used': 40
 }
diff --git a/components/printing/renderer/print_render_frame_helper.cc b/components/printing/renderer/print_render_frame_helper.cc
index 8568856..959aa10 100644
--- a/components/printing/renderer/print_render_frame_helper.cc
+++ b/components/printing/renderer/print_render_frame_helper.cc
@@ -2467,6 +2467,10 @@
                "page_number", page_number);
 
 #if BUILDFLAG(ENABLE_TAGGED_PDF)
+  // Make sure the RenderFrame is alive before taking the snapshot.
+  if (render_frame_gone_)
+    snapshotter_.reset();
+
   // For tagged PDF exporting, send a snapshot of the accessibility tree
   // along with page 0. The accessibility tree contains the content for
   // all of the pages of the main frame.
diff --git a/components/safe_browsing/content/renderer/phishing_classifier/BUILD.gn b/components/safe_browsing/content/renderer/phishing_classifier/BUILD.gn
new file mode 100644
index 0000000..dfcfd89
--- /dev/null
+++ b/components/safe_browsing/content/renderer/phishing_classifier/BUILD.gn
@@ -0,0 +1,42 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/features.gni")
+import("//components/safe_browsing/buildflags.gni")
+
+source_set("phishing_classifier") {
+  if (safe_browsing_mode != 0) {
+    sources = [
+      "features.cc",
+      "features.h",
+    ]
+    deps = [
+      "//components/safe_browsing/content/renderer",
+      "//components/safe_browsing/core/common",
+    ]
+  }
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [ "features_unittest.cc" ]
+  deps = [
+    ":phishing_classifier",
+    "//base:base",
+    "//components/safe_browsing/content/renderer/phishing_classifier:unit_tests_support",
+    "//testing/gtest",
+  ]
+}
+
+source_set("unit_tests_support") {
+  testonly = true
+  sources = [
+    "test_utils.cc",
+    "test_utils.h",
+  ]
+  deps = [
+    ":phishing_classifier",
+    "//testing/gmock",
+  ]
+}
diff --git a/chrome/renderer/safe_browsing/features.cc b/components/safe_browsing/content/renderer/phishing_classifier/features.cc
similarity index 96%
rename from chrome/renderer/safe_browsing/features.cc
rename to components/safe_browsing/content/renderer/phishing_classifier/features.cc
index a40462c..cc75371 100644
--- a/chrome/renderer/safe_browsing/features.cc
+++ b/components/safe_browsing/content/renderer/phishing_classifier/features.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/renderer/safe_browsing/features.h"
+#include "components/safe_browsing/content/renderer/phishing_classifier/features.h"
 
 #include "base/metrics/histogram_macros.h"
 
diff --git a/chrome/renderer/safe_browsing/features.h b/components/safe_browsing/content/renderer/phishing_classifier/features.h
similarity index 96%
rename from chrome/renderer/safe_browsing/features.h
rename to components/safe_browsing/content/renderer/phishing_classifier/features.h
index e6f21df..7764e0e 100644
--- a/chrome/renderer/safe_browsing/features.h
+++ b/components/safe_browsing/content/renderer/phishing_classifier/features.h
@@ -22,8 +22,8 @@
 // just a thin wrapper around a map of feature name to value.  The entire set
 // of features for a URL is extracted before we do any scoring.
 
-#ifndef CHROME_RENDERER_SAFE_BROWSING_FEATURES_H_
-#define CHROME_RENDERER_SAFE_BROWSING_FEATURES_H_
+#ifndef COMPONENTS_SAFE_BROWSING_CONTENT_RENDERER_PHISHING_CLASSIFIER_FEATURES_H_
+#define COMPONENTS_SAFE_BROWSING_CONTENT_RENDERER_PHISHING_CLASSIFIER_FEATURES_H_
 
 #include <stddef.h>
 #include <string>
@@ -178,4 +178,4 @@
 }  // namespace features
 }  // namespace safe_browsing
 
-#endif  // CHROME_RENDERER_SAFE_BROWSING_FEATURES_H_
+#endif  // COMPONENTS_SAFE_BROWSING_CONTENT_RENDERER_PHISHING_CLASSIFIER_FEATURES_H_
diff --git a/chrome/renderer/safe_browsing/features_unittest.cc b/components/safe_browsing/content/renderer/phishing_classifier/features_unittest.cc
similarity index 78%
rename from chrome/renderer/safe_browsing/features_unittest.cc
rename to components/safe_browsing/content/renderer/phishing_classifier/features_unittest.cc
index 0ba14e7..03c3b6e0 100644
--- a/chrome/renderer/safe_browsing/features_unittest.cc
+++ b/components/safe_browsing/content/renderer/phishing_classifier/features_unittest.cc
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/renderer/safe_browsing/features.h"
+#include "components/safe_browsing/content/renderer/phishing_classifier/features.h"
 
 #include <stddef.h>
 
 #include "base/format_macros.h"
 #include "base/strings/stringprintf.h"
-#include "chrome/renderer/safe_browsing/test_utils.h"
+#include "components/safe_browsing/content/renderer/phishing_classifier/test_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace safe_browsing {
@@ -16,15 +16,15 @@
 TEST(PhishingFeaturesTest, TooManyFeatures) {
   FeatureMap features;
   for (size_t i = 0; i < FeatureMap::kMaxFeatureMapSize; ++i) {
-    EXPECT_TRUE(features.AddBooleanFeature(
-        base::StringPrintf("Feature%" PRIuS, i)));
+    EXPECT_TRUE(
+        features.AddBooleanFeature(base::StringPrintf("Feature%" PRIuS, i)));
   }
   EXPECT_EQ(FeatureMap::kMaxFeatureMapSize, features.features().size());
 
   // Attempting to add more features should fail.
   for (size_t i = 0; i < 3; ++i) {
-    EXPECT_FALSE(features.AddBooleanFeature(
-        base::StringPrintf("Extra%" PRIuS, i)));
+    EXPECT_FALSE(
+        features.AddBooleanFeature(base::StringPrintf("Extra%" PRIuS, i)));
   }
   EXPECT_EQ(FeatureMap::kMaxFeatureMapSize, features.features().size());
 }
diff --git a/chrome/renderer/safe_browsing/test_utils.cc b/components/safe_browsing/content/renderer/phishing_classifier/test_utils.cc
similarity index 81%
rename from chrome/renderer/safe_browsing/test_utils.cc
rename to components/safe_browsing/content/renderer/phishing_classifier/test_utils.cc
index b3d8f9b..9961c11 100644
--- a/chrome/renderer/safe_browsing/test_utils.cc
+++ b/components/safe_browsing/content/renderer/phishing_classifier/test_utils.cc
@@ -2,12 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/renderer/safe_browsing/test_utils.h"
+#include "components/safe_browsing/content/renderer/phishing_classifier/test_utils.h"
 
 #include <map>
 #include <string>
 
-#include "chrome/renderer/safe_browsing/features.h"
+#include "components/safe_browsing/content/renderer/phishing_classifier/features.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
 namespace safe_browsing {
diff --git a/chrome/renderer/safe_browsing/test_utils.h b/components/safe_browsing/content/renderer/phishing_classifier/test_utils.h
similarity index 69%
rename from chrome/renderer/safe_browsing/test_utils.h
rename to components/safe_browsing/content/renderer/phishing_classifier/test_utils.h
index cbe2061c..6266e48 100644
--- a/chrome/renderer/safe_browsing/test_utils.h
+++ b/components/safe_browsing/content/renderer/phishing_classifier/test_utils.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_RENDERER_SAFE_BROWSING_TEST_UTILS_H_
-#define CHROME_RENDERER_SAFE_BROWSING_TEST_UTILS_H_
+#ifndef COMPONENTS_SAFE_BROWSING_CONTENT_RENDERER_PHISHING_CLASSIFIER_TEST_UTILS_H_
+#define COMPONENTS_SAFE_BROWSING_CONTENT_RENDERER_PHISHING_CLASSIFIER_TEST_UTILS_H_
 
 namespace safe_browsing {
 class FeatureMap;
@@ -16,4 +16,4 @@
 
 }  // namespace safe_browsing
 
-#endif  // CHROME_RENDERER_SAFE_BROWSING_TEST_UTILS_H_
+#endif  // COMPONENTS_SAFE_BROWSING_CONTENT_RENDERER_PHISHING_CLASSIFIER_TEST_UTILS_H_
diff --git a/components/sync/BUILD.gn b/components/sync/BUILD.gn
index 85d7d69fd..01059c3 100644
--- a/components/sync/BUILD.gn
+++ b/components/sync/BUILD.gn
@@ -30,7 +30,6 @@
 # etc, but currently they all depend on each other.
 static_library("rest_of_sync") {
   sources = [
-    "engine/commit_queue.cc",
     "engine/commit_queue.h",
     "engine/configure_reason.h",
     "engine/cycle/commit_counters.cc",
@@ -41,7 +40,6 @@
     "engine/cycle/status_counters.h",
     "engine/cycle/sync_cycle_snapshot.cc",
     "engine/cycle/sync_cycle_snapshot.h",
-    "engine/cycle/type_debug_info_observer.cc",
     "engine/cycle/type_debug_info_observer.h",
     "engine/cycle/update_counters.cc",
     "engine/cycle/update_counters.h",
@@ -58,7 +56,6 @@
     "engine/engine_util.h",
     "engine/events/protocol_event.cc",
     "engine/events/protocol_event.h",
-    "engine/events/protocol_event_observer.cc",
     "engine/events/protocol_event_observer.h",
     "engine/forwarding_model_type_processor.cc",
     "engine/forwarding_model_type_processor.h",
@@ -66,9 +63,7 @@
     "engine/model_safe_worker.h",
     "engine/model_type_configurer.cc",
     "engine/model_type_configurer.h",
-    "engine/model_type_connector.cc",
     "engine/model_type_connector.h",
-    "engine/model_type_processor.cc",
     "engine/model_type_processor.h",
     "engine/model_type_processor_proxy.cc",
     "engine/model_type_processor_proxy.h",
@@ -87,12 +82,10 @@
     "engine/sync_auth_provider.h",
     "engine/sync_backend_registrar.cc",
     "engine/sync_backend_registrar.h",
-    "engine/sync_credentials.cc",
     "engine/sync_credentials.h",
     "engine/sync_encryption_handler.h",
     "engine/sync_engine.cc",
     "engine/sync_engine.h",
-    "engine/sync_engine_host.cc",
     "engine/sync_engine_host.h",
     "engine/sync_engine_switches.cc",
     "engine/sync_engine_switches.h",
@@ -102,7 +95,6 @@
     "engine/sync_manager_factory.h",
     "engine/sync_status.cc",
     "engine/sync_status.h",
-    "engine/sync_status_observer.cc",
     "engine/sync_status_observer.h",
     "engine/sync_string_conversions.cc",
     "engine/sync_string_conversions.h",
@@ -114,9 +106,7 @@
     "engine_impl/bookmark_update_preprocessing.h",
     "engine_impl/commit.cc",
     "engine_impl/commit.h",
-    "engine_impl/commit_contribution.cc",
     "engine_impl/commit_contribution.h",
-    "engine_impl/commit_contributor.cc",
     "engine_impl/commit_contributor.h",
     "engine_impl/commit_processor.cc",
     "engine_impl/commit_processor.h",
@@ -189,16 +179,13 @@
     "engine_impl/net/url_translator.h",
     "engine_impl/non_blocking_type_commit_contribution.cc",
     "engine_impl/non_blocking_type_commit_contribution.h",
-    "engine_impl/nudge_handler.cc",
     "engine_impl/nudge_handler.h",
     "engine_impl/nudge_source.h",
     "engine_impl/sync_cycle_event.cc",
     "engine_impl/sync_cycle_event.h",
-    "engine_impl/sync_engine_event_listener.cc",
     "engine_impl/sync_engine_event_listener.h",
     "engine_impl/sync_manager_impl.cc",
     "engine_impl/sync_manager_impl.h",
-    "engine_impl/sync_scheduler.cc",
     "engine_impl/sync_scheduler.h",
     "engine_impl/sync_scheduler_impl.cc",
     "engine_impl/sync_scheduler_impl.h",
@@ -208,7 +195,6 @@
     "engine_impl/syncer_proto_util.h",
     "engine_impl/traffic_logger.cc",
     "engine_impl/traffic_logger.h",
-    "engine_impl/update_handler.cc",
     "engine_impl/update_handler.h",
     "model/blocking_model_type_store.h",
     "model/conflict_resolution.h",
@@ -227,7 +213,6 @@
     "model/metadata_change_list.h",
     "model/model_error.cc",
     "model/model_error.h",
-    "model/model_type_change_processor.cc",
     "model/model_type_change_processor.h",
     "model/model_type_store.h",
     "model/model_type_store_base.cc",
@@ -245,7 +230,6 @@
     "model/sync_data.h",
     "model/sync_error.cc",
     "model/sync_error.h",
-    "model/sync_error_factory.cc",
     "model/sync_error_factory.h",
     "model/sync_metadata_store.h",
     "model/syncable_service.h",
diff --git a/components/sync/base/BUILD.gn b/components/sync/base/BUILD.gn
index 07a8d1c..70a7095 100644
--- a/components/sync/base/BUILD.gn
+++ b/components/sync/base/BUILD.gn
@@ -12,7 +12,6 @@
 static_library("base") {
   sources = [
     "bind_to_task_runner.h",
-    "cancelation_observer.cc",
     "cancelation_observer.h",
     "cancelation_signal.cc",
     "cancelation_signal.h",
diff --git a/components/sync/base/cancelation_observer.cc b/components/sync/base/cancelation_observer.cc
deleted file mode 100644
index 8f23a88..0000000
--- a/components/sync/base/cancelation_observer.cc
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/sync/base/cancelation_observer.h"
-
-namespace syncer {
-
-CancelationObserver::CancelationObserver() {}
-
-CancelationObserver::~CancelationObserver() {}
-
-}  // namespace syncer
diff --git a/components/sync/base/cancelation_observer.h b/components/sync/base/cancelation_observer.h
index 898c8902..1d7f1109 100644
--- a/components/sync/base/cancelation_observer.h
+++ b/components/sync/base/cancelation_observer.h
@@ -10,8 +10,8 @@
 // Interface for classes that handle signals from the CancelationSignal.
 class CancelationObserver {
  public:
-  CancelationObserver();
-  virtual ~CancelationObserver() = 0;
+  CancelationObserver() = default;
+  virtual ~CancelationObserver() = default;
 
   // This may be called from a foreign thread while the CancelationSignal's lock
   // is held.  The callee should avoid performing slow or blocking operations.
diff --git a/components/sync/base/sync_prefs.cc b/components/sync/base/sync_prefs.cc
index 6d0d1f3b..1e8d1e6 100644
--- a/components/sync/base/sync_prefs.cc
+++ b/components/sync/base/sync_prefs.cc
@@ -685,12 +685,11 @@
 }
 #endif  // defined(OS_ANDROID)
 
-void SyncPrefs::GetInvalidationVersions(
-    std::map<ModelType, int64_t>* invalidation_versions) const {
+std::map<ModelType, int64_t> SyncPrefs::GetInvalidationVersions() const {
+  std::map<ModelType, int64_t> invalidation_versions;
   const base::DictionaryValue* invalidation_dictionary =
       pref_service_->GetDictionary(prefs::kSyncInvalidationVersions);
-  ModelTypeSet protocol_types = ProtocolTypes();
-  for (ModelType type : protocol_types) {
+  for (ModelType type : ProtocolTypes()) {
     std::string key = ModelTypeToString(type);
     std::string version_str;
     if (!invalidation_dictionary->GetString(key, &version_str))
@@ -698,8 +697,9 @@
     int64_t version = 0;
     if (!base::StringToInt64(version_str, &version))
       continue;
-    (*invalidation_versions)[type] = version;
+    invalidation_versions[type] = version;
   }
+  return invalidation_versions;
 }
 
 void SyncPrefs::UpdateInvalidationVersions(
diff --git a/components/sync/base/sync_prefs.h b/components/sync/base/sync_prefs.h
index 3b1ed71..6c9bd564 100644
--- a/components/sync/base/sync_prefs.h
+++ b/components/sync/base/sync_prefs.h
@@ -184,8 +184,7 @@
   void SetManagedForTest(bool is_managed);
 
   // Get/set for the last known sync invalidation versions.
-  void GetInvalidationVersions(
-      std::map<ModelType, int64_t>* invalidation_versions) const;
+  std::map<ModelType, int64_t> GetInvalidationVersions() const;
   void UpdateInvalidationVersions(
       const std::map<ModelType, int64_t>& invalidation_versions);
 
diff --git a/components/sync/base/sync_prefs_unittest.cc b/components/sync/base/sync_prefs_unittest.cc
index da2e6880..0a1e83f 100644
--- a/components/sync/base/sync_prefs_unittest.cc
+++ b/components/sync/base/sync_prefs_unittest.cc
@@ -73,8 +73,8 @@
 
   sync_prefs_->UpdateInvalidationVersions(versions);
 
-  std::map<ModelType, int64_t> versions2;
-  sync_prefs_->GetInvalidationVersions(&versions2);
+  std::map<ModelType, int64_t> versions2 =
+      sync_prefs_->GetInvalidationVersions();
 
   EXPECT_EQ(versions.size(), versions2.size());
   for (auto map_iter : versions2) {
diff --git a/components/sync/driver/BUILD.gn b/components/sync/driver/BUILD.gn
index 8baa714..8476be77 100644
--- a/components/sync/driver/BUILD.gn
+++ b/components/sync/driver/BUILD.gn
@@ -14,7 +14,6 @@
     "configure_context.h",
     "data_type_controller.cc",
     "data_type_controller.h",
-    "data_type_encryption_handler.cc",
     "data_type_encryption_handler.h",
     "data_type_manager.cc",
     "data_type_manager.h",
@@ -44,7 +43,6 @@
     "sync_auth_manager.h",
     "sync_auth_util.cc",
     "sync_auth_util.h",
-    "sync_client.cc",
     "sync_client.h",
     "sync_driver_switches.cc",
     "sync_driver_switches.h",
@@ -60,7 +58,6 @@
     "sync_session_durations_metrics_recorder.h",
     "sync_stopped_reporter.cc",
     "sync_stopped_reporter.h",
-    "sync_token_status.cc",
     "sync_token_status.h",
     "sync_type_preference_provider.h",
     "sync_user_settings.h",
diff --git a/components/sync/driver/data_type_encryption_handler.cc b/components/sync/driver/data_type_encryption_handler.cc
deleted file mode 100644
index 5baf7e88..0000000
--- a/components/sync/driver/data_type_encryption_handler.cc
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/sync/driver/data_type_encryption_handler.h"
-
-namespace syncer {
-
-DataTypeEncryptionHandler::DataTypeEncryptionHandler() {}
-DataTypeEncryptionHandler::~DataTypeEncryptionHandler() {}
-}
diff --git a/components/sync/driver/data_type_encryption_handler.h b/components/sync/driver/data_type_encryption_handler.h
index 1ab4a89d..486168b5 100644
--- a/components/sync/driver/data_type_encryption_handler.h
+++ b/components/sync/driver/data_type_encryption_handler.h
@@ -12,8 +12,8 @@
 // The DataTypeEncryptionHandler provides the status of datatype encryption.
 class DataTypeEncryptionHandler {
  public:
-  DataTypeEncryptionHandler();
-  virtual ~DataTypeEncryptionHandler();
+  DataTypeEncryptionHandler() = default;
+  virtual ~DataTypeEncryptionHandler() = default;
 
   // Returns whether there is an error that prevents encryption or decryption
   // from proceeding. This does not necessarily mean that the UI will display an
diff --git a/components/sync/driver/glue/sync_engine_impl_unittest.cc b/components/sync/driver/glue/sync_engine_impl_unittest.cc
index 7b74e68..63d3da0 100644
--- a/components/sync/driver/glue/sync_engine_impl_unittest.cc
+++ b/components/sync/driver/glue/sync_engine_impl_unittest.cc
@@ -249,7 +249,7 @@
     params.http_factory_getter = base::BindOnce(&CreateHttpBridgeFactory);
     params.authenticated_account_id = CoreAccountId("account_id");
     params.sync_manager_factory = std::move(fake_manager_factory_);
-    sync_prefs_->GetInvalidationVersions(&params.invalidation_versions);
+    params.invalidation_versions = sync_prefs_->GetInvalidationVersions();
 
     backend_->Initialize(std::move(params));
 
diff --git a/components/sync/driver/profile_sync_service.cc b/components/sync/driver/profile_sync_service.cc
index 66ca47c..94c28b8 100644
--- a/components/sync/driver/profile_sync_service.cc
+++ b/components/sync/driver/profile_sync_service.cc
@@ -618,7 +618,7 @@
   params.engine_components_factory =
       std::make_unique<EngineComponentsFactoryImpl>(
           EngineSwitchesFromCommandLine());
-  sync_prefs_.GetInvalidationVersions(&params.invalidation_versions);
+  params.invalidation_versions = sync_prefs_.GetInvalidationVersions();
   params.poll_interval = sync_prefs_.GetPollInterval();
   if (params.poll_interval.is_zero()) {
     params.poll_interval =
diff --git a/components/sync/driver/sync_client.cc b/components/sync/driver/sync_client.cc
deleted file mode 100644
index 9e9bb668..0000000
--- a/components/sync/driver/sync_client.cc
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/sync/driver/sync_client.h"
-
-namespace syncer {
-
-SyncClient::SyncClient() {}
-SyncClient::~SyncClient() {}
-
-}  // namespace syncer
diff --git a/components/sync/driver/sync_client.h b/components/sync/driver/sync_client.h
index 320d7fb..8ca8e5e 100644
--- a/components/sync/driver/sync_client.h
+++ b/components/sync/driver/sync_client.h
@@ -42,8 +42,8 @@
 // to handle these scenarios gracefully.
 class SyncClient {
  public:
-  SyncClient();
-  virtual ~SyncClient();
+  SyncClient() = default;
+  virtual ~SyncClient() = default;
 
   // Returns the current profile's preference service.
   virtual PrefService* GetPrefService() = 0;
diff --git a/components/sync/driver/sync_service_observer.h b/components/sync/driver/sync_service_observer.h
index 12e5f3c..84d694e 100644
--- a/components/sync/driver/sync_service_observer.h
+++ b/components/sync/driver/sync_service_observer.h
@@ -37,7 +37,8 @@
   virtual void OnSyncShutdown(SyncService* sync) {}
 
  protected:
-  virtual ~SyncServiceObserver() {}
+  SyncServiceObserver() = default;
+  virtual ~SyncServiceObserver() = default;
 };
 
 }  // namespace syncer
diff --git a/components/sync/driver/sync_token_status.cc b/components/sync/driver/sync_token_status.cc
deleted file mode 100644
index 7487116..0000000
--- a/components/sync/driver/sync_token_status.cc
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/sync/driver/sync_token_status.h"
-
-namespace syncer {
-
-SyncTokenStatus::SyncTokenStatus() = default;
-
-}  // namespace syncer
diff --git a/components/sync/driver/sync_token_status.h b/components/sync/driver/sync_token_status.h
index 0063c460..f476c36 100644
--- a/components/sync/driver/sync_token_status.h
+++ b/components/sync/driver/sync_token_status.h
@@ -13,7 +13,8 @@
 
 // Status of sync server connection, OAuth2 access token and token request.
 struct SyncTokenStatus {
-  SyncTokenStatus();
+  SyncTokenStatus() = default;
+  ~SyncTokenStatus() = default;
 
   // Sync server connection status reported by the sync engine.
   base::Time connection_status_update_time;
diff --git a/components/sync/engine/commit_queue.cc b/components/sync/engine/commit_queue.cc
deleted file mode 100644
index 370efa5..0000000
--- a/components/sync/engine/commit_queue.cc
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/sync/engine/commit_queue.h"
-
-namespace syncer {
-
-CommitQueue::CommitQueue() {}
-
-CommitQueue::~CommitQueue() {}
-
-}  // namespace syncer
diff --git a/components/sync/engine/commit_queue.h b/components/sync/engine/commit_queue.h
index 5494d89..65e1cc6 100644
--- a/components/sync/engine/commit_queue.h
+++ b/components/sync/engine/commit_queue.h
@@ -12,8 +12,8 @@
 // Interface used by a synced data type to issue requests to the sync backend.
 class CommitQueue {
  public:
-  CommitQueue();
-  virtual ~CommitQueue();
+  CommitQueue() = default;
+  virtual ~CommitQueue() = default;
 
   // Nudge sync engine to indicate the datatype has local changes to commit.
   virtual void NudgeForCommit() = 0;
diff --git a/components/sync/engine/cycle/type_debug_info_observer.h b/components/sync/engine/cycle/type_debug_info_observer.h
index 8a63d21..cc6a534d 100644
--- a/components/sync/engine/cycle/type_debug_info_observer.h
+++ b/components/sync/engine/cycle/type_debug_info_observer.h
@@ -16,8 +16,8 @@
 // Interface for classes that observe per-type sync debug counters.
 class TypeDebugInfoObserver {
  public:
-  TypeDebugInfoObserver();
-  virtual ~TypeDebugInfoObserver();
+  TypeDebugInfoObserver() = default;
+  virtual ~TypeDebugInfoObserver() = default;
 
   virtual void OnCommitCountersUpdated(ModelType type,
                                        const CommitCounters& counters) = 0;
diff --git a/components/sync/engine/events/protocol_event_observer.cc b/components/sync/engine/events/protocol_event_observer.cc
deleted file mode 100644
index 083abfc..0000000
--- a/components/sync/engine/events/protocol_event_observer.cc
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/sync/engine/events/protocol_event_observer.h"
-
-namespace syncer {
-
-ProtocolEventObserver::ProtocolEventObserver() {}
-
-ProtocolEventObserver::~ProtocolEventObserver() {}
-
-}  // namespace syncer
diff --git a/components/sync/engine/events/protocol_event_observer.h b/components/sync/engine/events/protocol_event_observer.h
index 7e736610..ed187a0 100644
--- a/components/sync/engine/events/protocol_event_observer.h
+++ b/components/sync/engine/events/protocol_event_observer.h
@@ -11,8 +11,8 @@
 
 class ProtocolEventObserver {
  public:
-  ProtocolEventObserver();
-  virtual ~ProtocolEventObserver();
+  ProtocolEventObserver() = default;
+  virtual ~ProtocolEventObserver() = default;
 
   virtual void OnProtocolEvent(const ProtocolEvent& event) = 0;
 };
diff --git a/components/sync/engine/model_type_connector.cc b/components/sync/engine/model_type_connector.cc
deleted file mode 100644
index 7e61f64f2..0000000
--- a/components/sync/engine/model_type_connector.cc
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/sync/engine/model_type_connector.h"
-
-namespace syncer {
-
-ModelTypeConnector::ModelTypeConnector() {}
-
-ModelTypeConnector::~ModelTypeConnector() {}
-
-}  // namespace syncer
diff --git a/components/sync/engine/model_type_connector.h b/components/sync/engine/model_type_connector.h
index 8f6aead..699fa20 100644
--- a/components/sync/engine/model_type_connector.h
+++ b/components/sync/engine/model_type_connector.h
@@ -21,8 +21,8 @@
 // model type side for non-blocking types.
 class ModelTypeConnector {
  public:
-  ModelTypeConnector();
-  virtual ~ModelTypeConnector();
+  ModelTypeConnector() = default;
+  virtual ~ModelTypeConnector() = default;
 
   // Connect a worker on the sync thread and |type|'s processor on the model
   // thread. Note that in production |activation_response| actually
diff --git a/components/sync/engine/model_type_processor.cc b/components/sync/engine/model_type_processor.cc
deleted file mode 100644
index 12d9859..0000000
--- a/components/sync/engine/model_type_processor.cc
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/sync/engine/model_type_processor.h"
-
-namespace syncer {
-
-ModelTypeProcessor::ModelTypeProcessor() = default;
-
-ModelTypeProcessor::~ModelTypeProcessor() = default;
-
-void ModelTypeProcessor::OnCommitFailed(SyncCommitError commit_error) {}
-
-}  // namespace syncer
diff --git a/components/sync/engine/model_type_processor.h b/components/sync/engine/model_type_processor.h
index b61ddc0..8470ac2 100644
--- a/components/sync/engine/model_type_processor.h
+++ b/components/sync/engine/model_type_processor.h
@@ -17,8 +17,8 @@
 // Interface used by sync backend to issue requests to a synced data type.
 class ModelTypeProcessor {
  public:
-  ModelTypeProcessor();
-  virtual ~ModelTypeProcessor();
+  ModelTypeProcessor() = default;
+  virtual ~ModelTypeProcessor() = default;
 
   // Connect this processor to the sync engine via |commit_queue|. Once called,
   // the processor will send any pending and future commits via this channel.
@@ -48,7 +48,7 @@
 
   // Informs this object that a commit attempt failed, e.g. due to network or
   // server issues. The commit may not include all pending entities.
-  virtual void OnCommitFailed(SyncCommitError commit_error);
+  virtual void OnCommitFailed(SyncCommitError commit_error) {}
 
   // Informs this object that there are some incoming updates it should
   // handle.
diff --git a/components/sync/engine/sync_credentials.cc b/components/sync/engine/sync_credentials.cc
deleted file mode 100644
index 87ed9fa..0000000
--- a/components/sync/engine/sync_credentials.cc
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/sync/engine/sync_credentials.h"
-
-namespace syncer {
-
-SyncCredentials::SyncCredentials() = default;
-
-SyncCredentials::SyncCredentials(const SyncCredentials& other) = default;
-
-SyncCredentials::~SyncCredentials() = default;
-
-}  // namespace syncer
diff --git a/components/sync/engine/sync_credentials.h b/components/sync/engine/sync_credentials.h
index bfa46e79..66f7fef 100644
--- a/components/sync/engine/sync_credentials.h
+++ b/components/sync/engine/sync_credentials.h
@@ -13,9 +13,9 @@
 
 // Contains everything needed to talk to and identify a user account.
 struct SyncCredentials {
-  SyncCredentials();
-  SyncCredentials(const SyncCredentials& other);
-  ~SyncCredentials();
+  SyncCredentials() = default;
+  SyncCredentials(const SyncCredentials& other) = default;
+  ~SyncCredentials() = default;
 
   // Account_id of signed in account.
   CoreAccountId account_id;
diff --git a/components/sync/engine/sync_engine_host.cc b/components/sync/engine/sync_engine_host.cc
deleted file mode 100644
index 7a19e8c1..0000000
--- a/components/sync/engine/sync_engine_host.cc
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/sync/engine/sync_engine_host.h"
-
-namespace syncer {
-
-SyncEngineHost::SyncEngineHost() {}
-SyncEngineHost::~SyncEngineHost() {}
-
-}  // namespace syncer
diff --git a/components/sync/engine/sync_engine_host.h b/components/sync/engine/sync_engine_host.h
index f1ef8b7e..6e1980fd 100644
--- a/components/sync/engine/sync_engine_host.h
+++ b/components/sync/engine/sync_engine_host.h
@@ -27,8 +27,8 @@
 // SyncEngine always has exactly one.
 class SyncEngineHost {
  public:
-  SyncEngineHost();
-  virtual ~SyncEngineHost();
+  SyncEngineHost() = default;
+  virtual ~SyncEngineHost() = default;
 
   // The engine has completed initialization and it is now ready to accept and
   // process changes. If success is false, initialization wasn't able to be
diff --git a/components/sync/engine/sync_status_observer.cc b/components/sync/engine/sync_status_observer.cc
deleted file mode 100644
index bcfe9df..0000000
--- a/components/sync/engine/sync_status_observer.cc
+++ /dev/null
@@ -1,13 +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 "components/sync/engine/sync_status_observer.h"
-
-namespace syncer {
-
-SyncStatusObserver::SyncStatusObserver() = default;
-
-SyncStatusObserver::~SyncStatusObserver() = default;
-
-}  // namespace syncer
diff --git a/components/sync/engine/sync_status_observer.h b/components/sync/engine/sync_status_observer.h
index 32c76c4..dcd0abbd9 100644
--- a/components/sync/engine/sync_status_observer.h
+++ b/components/sync/engine/sync_status_observer.h
@@ -11,8 +11,8 @@
 
 class SyncStatusObserver {
  public:
-  SyncStatusObserver();
-  virtual ~SyncStatusObserver();
+  SyncStatusObserver() = default;
+  virtual ~SyncStatusObserver() = default;
 
   // This event is sent when SyncStatus changes.
   virtual void OnSyncStatusChanged(const SyncStatus& status) = 0;
diff --git a/components/sync/engine_impl/commit.cc b/components/sync/engine_impl/commit.cc
index 9820b36..6feea43 100644
--- a/components/sync/engine_impl/commit.cc
+++ b/components/sync/engine_impl/commit.cc
@@ -155,8 +155,8 @@
   }
 
   if (cycle->context()->debug_info_getter()) {
-    sync_pb::DebugInfo* debug_info = message_.mutable_debug_info();
-    cycle->context()->debug_info_getter()->GetDebugInfo(debug_info);
+    *message_.mutable_debug_info() =
+        cycle->context()->debug_info_getter()->GetDebugInfo();
   }
 
   DVLOG(1) << "Sending commit message.";
diff --git a/components/sync/engine_impl/commit_contribution.cc b/components/sync/engine_impl/commit_contribution.cc
deleted file mode 100644
index 3de4a07..0000000
--- a/components/sync/engine_impl/commit_contribution.cc
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/sync/engine_impl/commit_contribution.h"
-
-namespace syncer {
-
-CommitContribution::CommitContribution() {}
-
-CommitContribution::~CommitContribution() {}
-
-}  // namespace syncer
diff --git a/components/sync/engine_impl/commit_contribution.h b/components/sync/engine_impl/commit_contribution.h
index b99835ee..55e24cd 100644
--- a/components/sync/engine_impl/commit_contribution.h
+++ b/components/sync/engine_impl/commit_contribution.h
@@ -22,8 +22,8 @@
 // This class handles the bookkeeping related to the commit of these items.
 class CommitContribution {
  public:
-  CommitContribution();
-  virtual ~CommitContribution() = 0;
+  CommitContribution() = default;
+  virtual ~CommitContribution() = default;
 
   // Serialize this contribution's entries to the given commit request |msg|.
   //
diff --git a/components/sync/engine_impl/commit_contributor.cc b/components/sync/engine_impl/commit_contributor.cc
deleted file mode 100644
index b909293..0000000
--- a/components/sync/engine_impl/commit_contributor.cc
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/sync/engine_impl/commit_contributor.h"
-
-namespace syncer {
-
-CommitContributor::CommitContributor() {}
-
-CommitContributor::~CommitContributor() {}
-
-}  // namespace syncer
diff --git a/components/sync/engine_impl/commit_contributor.h b/components/sync/engine_impl/commit_contributor.h
index 76b3b38..a215ebb 100644
--- a/components/sync/engine_impl/commit_contributor.h
+++ b/components/sync/engine_impl/commit_contributor.h
@@ -19,8 +19,8 @@
 // items to be committed from this source.
 class CommitContributor {
  public:
-  CommitContributor();
-  virtual ~CommitContributor() = 0;
+  CommitContributor() = default;
+  virtual ~CommitContributor() = default;
 
   // Gathers up to |max_entries| unsynced items from this contributor into a
   // CommitContribution.  Returns null when the contributor has nothing to
diff --git a/components/sync/engine_impl/cycle/debug_info_getter.h b/components/sync/engine_impl/cycle/debug_info_getter.h
index 6e75fcc..ded0443 100644
--- a/components/sync/engine_impl/cycle/debug_info_getter.h
+++ b/components/sync/engine_impl/cycle/debug_info_getter.h
@@ -15,7 +15,7 @@
  public:
   // Gets the client debug info. Be sure to clear the info to ensure the data
   // isn't sent multiple times.
-  virtual void GetDebugInfo(sync_pb::DebugInfo* debug_info) = 0;
+  virtual sync_pb::DebugInfo GetDebugInfo() const = 0;
 
   // Clears the debug info.
   virtual void ClearDebugInfo() = 0;
diff --git a/components/sync/engine_impl/cycle/mock_debug_info_getter.cc b/components/sync/engine_impl/cycle/mock_debug_info_getter.cc
index f71c136..e049afd 100644
--- a/components/sync/engine_impl/cycle/mock_debug_info_getter.cc
+++ b/components/sync/engine_impl/cycle/mock_debug_info_getter.cc
@@ -10,8 +10,8 @@
 
 MockDebugInfoGetter::~MockDebugInfoGetter() {}
 
-void MockDebugInfoGetter::GetDebugInfo(sync_pb::DebugInfo* debug_info) {
-  debug_info->CopyFrom(debug_info_);
+sync_pb::DebugInfo MockDebugInfoGetter::GetDebugInfo() const {
+  return debug_info_;
 }
 
 void MockDebugInfoGetter::ClearDebugInfo() {
diff --git a/components/sync/engine_impl/cycle/mock_debug_info_getter.h b/components/sync/engine_impl/cycle/mock_debug_info_getter.h
index 4d6d883..61109fb 100644
--- a/components/sync/engine_impl/cycle/mock_debug_info_getter.h
+++ b/components/sync/engine_impl/cycle/mock_debug_info_getter.h
@@ -20,7 +20,7 @@
   ~MockDebugInfoGetter() override;
 
   // DebugInfoGetter implementation.
-  void GetDebugInfo(sync_pb::DebugInfo* debug_info) override;
+  sync_pb::DebugInfo GetDebugInfo() const override;
   void ClearDebugInfo() override;
 
   void AddDebugEvent();
diff --git a/components/sync/engine_impl/cycle/sync_cycle.cc b/components/sync/engine_impl/cycle/sync_cycle.cc
index bced06e7..30a3d113 100644
--- a/components/sync/engine_impl/cycle/sync_cycle.cc
+++ b/components/sync/engine_impl/cycle/sync_cycle.cc
@@ -33,8 +33,8 @@
     if (update_handler == nullptr) {
       continue;
     }
-    sync_pb::DataTypeProgressMarker progress_marker;
-    update_handler->GetDownloadProgress(&progress_marker);
+    sync_pb::DataTypeProgressMarker progress_marker =
+        update_handler->GetDownloadProgress();
     download_progress_markers[type] = progress_marker.SerializeAsString();
   }
 
diff --git a/components/sync/engine_impl/debug_info_event_listener.cc b/components/sync/engine_impl/debug_info_event_listener.cc
index 7ff9037..69fa714 100644
--- a/components/sync/engine_impl/debug_info_event_listener.cc
+++ b/components/sync/engine_impl/debug_info_event_listener.cc
@@ -141,20 +141,20 @@
   AddEventToQueue(event_info);
 }
 
-void DebugInfoEventListener::GetDebugInfo(sync_pb::DebugInfo* debug_info) {
+sync_pb::DebugInfo DebugInfoEventListener::GetDebugInfo() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK_LE(events_.size(), kMaxEntries);
 
-  for (DebugEventInfoQueue::const_iterator iter = events_.begin();
-       iter != events_.end(); ++iter) {
-    sync_pb::DebugEventInfo* event_info = debug_info->add_events();
-    event_info->CopyFrom(*iter);
+  sync_pb::DebugInfo debug_info;
+  for (const sync_pb::DebugEventInfo& event : events_) {
+    *debug_info.add_events() = event;
   }
 
-  debug_info->set_events_dropped(events_dropped_);
-  debug_info->set_cryptographer_ready(cryptographer_can_encrypt_);
-  debug_info->set_cryptographer_has_pending_keys(
+  debug_info.set_events_dropped(events_dropped_);
+  debug_info.set_cryptographer_ready(cryptographer_can_encrypt_);
+  debug_info.set_cryptographer_has_pending_keys(
       cryptographer_has_pending_keys_);
+  return debug_info;
 }
 
 void DebugInfoEventListener::ClearDebugInfo() {
diff --git a/components/sync/engine_impl/debug_info_event_listener.h b/components/sync/engine_impl/debug_info_event_listener.h
index e3d89ae..7e805d7 100644
--- a/components/sync/engine_impl/debug_info_event_listener.h
+++ b/components/sync/engine_impl/debug_info_event_listener.h
@@ -74,7 +74,7 @@
   void OnNudgeFromDatatype(ModelType datatype);
 
   // DebugInfoGetter implementation.
-  void GetDebugInfo(sync_pb::DebugInfo* debug_info) override;
+  sync_pb::DebugInfo GetDebugInfo() const override;
 
   // DebugInfoGetter implementation.
   void ClearDebugInfo() override;
diff --git a/components/sync/engine_impl/debug_info_event_listener_unittest.cc b/components/sync/engine_impl/debug_info_event_listener_unittest.cc
index b64c6e3..899bcba1 100644
--- a/components/sync/engine_impl/debug_info_event_listener_unittest.cc
+++ b/components/sync/engine_impl/debug_info_event_listener_unittest.cc
@@ -28,8 +28,7 @@
     debug_info_event_listener.CreateAndAddEvent(
         sync_pb::SyncEnums::ENCRYPTION_COMPLETE);
   }
-  sync_pb::DebugInfo debug_info;
-  debug_info_event_listener.GetDebugInfo(&debug_info);
+  sync_pb::DebugInfo debug_info = debug_info_event_listener.GetDebugInfo();
   debug_info_event_listener.ClearDebugInfo();
   ASSERT_TRUE(debug_info.events_dropped());
   ASSERT_EQ(static_cast<int>(kMaxEntries), debug_info.events_size());
@@ -40,8 +39,7 @@
   debug_info_event_listener.CreateAndAddEvent(
       sync_pb::SyncEnums::ENCRYPTION_COMPLETE);
   ASSERT_EQ(debug_info_event_listener.events_.size(), 1U);
-  sync_pb::DebugInfo debug_info;
-  debug_info_event_listener.GetDebugInfo(&debug_info);
+  sync_pb::DebugInfo debug_info = debug_info_event_listener.GetDebugInfo();
   ASSERT_EQ(debug_info_event_listener.events_.size(), 1U);
   ASSERT_EQ(debug_info.events_size(), 1);
   ASSERT_TRUE(debug_info.events(0).has_singleton_event());
diff --git a/components/sync/engine_impl/get_updates_processor.cc b/components/sync/engine_impl/get_updates_processor.cc
index fa410396..5f57026 100644
--- a/components/sync/engine_impl/get_updates_processor.cc
+++ b/components/sync/engine_impl/get_updates_processor.cc
@@ -202,11 +202,10 @@
         << "Failed to look up handler for " << ModelTypeToString(type);
     sync_pb::DataTypeProgressMarker* progress_marker =
         get_updates->add_from_progress_marker();
-    handler_it->second->GetDownloadProgress(progress_marker);
+    *progress_marker = handler_it->second->GetDownloadProgress();
     progress_marker->clear_gc_directive();
 
-    sync_pb::DataTypeContext context;
-    handler_it->second->GetDataTypeContext(&context);
+    sync_pb::DataTypeContext context = handler_it->second->GetDataTypeContext();
     if (!context.context().empty())
       get_updates->add_client_contexts()->Swap(&context);
   }
@@ -223,8 +222,8 @@
   bool need_encryption_key = ShouldRequestEncryptionKey(cycle->context());
 
   if (cycle->context()->debug_info_getter()) {
-    sync_pb::DebugInfo* debug_info = msg->mutable_debug_info();
-    CopyClientDebugInfo(cycle->context()->debug_info_getter(), debug_info);
+    *msg->mutable_debug_info() =
+        cycle->context()->debug_info_getter()->GetDebugInfo();
   }
 
   SyncerProtoUtil::AddRequiredFieldsToClientToServerMessage(cycle, msg);
@@ -373,11 +372,4 @@
   delegate_.ApplyUpdates(gu_types, status_controller, update_handler_map_);
 }
 
-void GetUpdatesProcessor::CopyClientDebugInfo(
-    DebugInfoGetter* debug_info_getter,
-    sync_pb::DebugInfo* debug_info) {
-  DVLOG(1) << "Copying client debug info to send.";
-  debug_info_getter->GetDebugInfo(debug_info);
-}
-
 }  // namespace syncer
diff --git a/components/sync/engine_impl/get_updates_processor.h b/components/sync/engine_impl/get_updates_processor.h
index c5560ec..0380d982 100644
--- a/components/sync/engine_impl/get_updates_processor.h
+++ b/components/sync/engine_impl/get_updates_processor.h
@@ -21,7 +21,6 @@
 
 namespace syncer {
 
-class DebugInfoGetter;
 class GetUpdatesDelegate;
 class StatusController;
 class SyncCycle;
@@ -72,9 +71,6 @@
       const sync_pb::GetUpdatesResponse& gu_response,
       StatusController* status_controller);
 
-  static void CopyClientDebugInfo(DebugInfoGetter* debug_info_getter,
-                                  sync_pb::DebugInfo* debug_info);
-
   FRIEND_TEST_ALL_PREFIXES(GetUpdatesProcessorTest, BookmarkNudge);
   FRIEND_TEST_ALL_PREFIXES(GetUpdatesProcessorTest, NotifyMany);
   FRIEND_TEST_ALL_PREFIXES(GetUpdatesProcessorTest, InitialSyncRequest);
diff --git a/components/sync/engine_impl/get_updates_processor_unittest.cc b/components/sync/engine_impl/get_updates_processor_unittest.cc
index 4ad331da..75a0ba61 100644
--- a/components/sync/engine_impl/get_updates_processor_unittest.cc
+++ b/components/sync/engine_impl/get_updates_processor_unittest.cc
@@ -479,20 +479,4 @@
   MockDebugInfoGetter debug_info_getter_;
 };
 
-// Verify CopyClientDebugInfo when there are no events to upload.
-TEST_F(DownloadUpdatesDebugInfoTest, VerifyCopyClientDebugInfo_Empty) {
-  sync_pb::DebugInfo debug_info;
-  GetUpdatesProcessor::CopyClientDebugInfo(debug_info_getter(), &debug_info);
-  EXPECT_EQ(0, debug_info.events_size());
-}
-
-TEST_F(DownloadUpdatesDebugInfoTest, VerifyCopyOverwrites) {
-  sync_pb::DebugInfo debug_info;
-  AddDebugEvent();
-  GetUpdatesProcessor::CopyClientDebugInfo(debug_info_getter(), &debug_info);
-  EXPECT_EQ(1, debug_info.events_size());
-  GetUpdatesProcessor::CopyClientDebugInfo(debug_info_getter(), &debug_info);
-  EXPECT_EQ(1, debug_info.events_size());
-}
-
 }  // namespace syncer
diff --git a/components/sync/engine_impl/model_type_worker.cc b/components/sync/engine_impl/model_type_worker.cc
index 13280dbd..eaf9cdc5 100644
--- a/components/sync/engine_impl/model_type_worker.cc
+++ b/components/sync/engine_impl/model_type_worker.cc
@@ -142,16 +142,15 @@
   return model_type_state_.initial_sync_done();
 }
 
-void ModelTypeWorker::GetDownloadProgress(
-    sync_pb::DataTypeProgressMarker* progress_marker) const {
+const sync_pb::DataTypeProgressMarker& ModelTypeWorker::GetDownloadProgress()
+    const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  progress_marker->CopyFrom(model_type_state_.progress_marker());
+  return model_type_state_.progress_marker();
 }
 
-void ModelTypeWorker::GetDataTypeContext(
-    sync_pb::DataTypeContext* context) const {
+const sync_pb::DataTypeContext& ModelTypeWorker::GetDataTypeContext() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  context->CopyFrom(model_type_state_.type_context());
+  return model_type_state_.type_context();
 }
 
 SyncerError ModelTypeWorker::ProcessGetUpdatesResponse(
diff --git a/components/sync/engine_impl/model_type_worker.h b/components/sync/engine_impl/model_type_worker.h
index 7ae4a58..a594319c 100644
--- a/components/sync/engine_impl/model_type_worker.h
+++ b/components/sync/engine_impl/model_type_worker.h
@@ -87,9 +87,8 @@
 
   // UpdateHandler implementation.
   bool IsInitialSyncEnded() const override;
-  void GetDownloadProgress(
-      sync_pb::DataTypeProgressMarker* progress_marker) const override;
-  void GetDataTypeContext(sync_pb::DataTypeContext* context) const override;
+  const sync_pb::DataTypeProgressMarker& GetDownloadProgress() const override;
+  const sync_pb::DataTypeContext& GetDataTypeContext() const override;
   SyncerError ProcessGetUpdatesResponse(
       const sync_pb::DataTypeProgressMarker& progress_marker,
       const sync_pb::DataTypeContext& mutated_context,
diff --git a/components/sync/engine_impl/nudge_handler.cc b/components/sync/engine_impl/nudge_handler.cc
deleted file mode 100644
index 93aef71..0000000
--- a/components/sync/engine_impl/nudge_handler.cc
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/sync/engine_impl/nudge_handler.h"
-
-namespace syncer {
-
-NudgeHandler::NudgeHandler() {}
-
-NudgeHandler::~NudgeHandler() {}
-
-}  // namespace syncer
diff --git a/components/sync/engine_impl/nudge_handler.h b/components/sync/engine_impl/nudge_handler.h
index 9be772c..51b2120 100644
--- a/components/sync/engine_impl/nudge_handler.h
+++ b/components/sync/engine_impl/nudge_handler.h
@@ -12,8 +12,8 @@
 
 class NudgeHandler {
  public:
-  NudgeHandler();
-  virtual ~NudgeHandler();
+  NudgeHandler() = default;
+  virtual ~NudgeHandler() = default;
 
   virtual void NudgeForInitialDownload(ModelType type) = 0;
   virtual void NudgeForCommit(ModelType type) = 0;
diff --git a/components/sync/engine_impl/sync_engine_event_listener.cc b/components/sync/engine_impl/sync_engine_event_listener.cc
deleted file mode 100644
index 394fd47..0000000
--- a/components/sync/engine_impl/sync_engine_event_listener.cc
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/sync/engine_impl/sync_engine_event_listener.h"
-
-namespace syncer {
-
-SyncEngineEventListener::SyncEngineEventListener() {}
-
-SyncEngineEventListener::~SyncEngineEventListener() {}
-
-}  // namespace syncer
diff --git a/components/sync/engine_impl/sync_engine_event_listener.h b/components/sync/engine_impl/sync_engine_event_listener.h
index d9f4393..e6ecb0d 100644
--- a/components/sync/engine_impl/sync_engine_event_listener.h
+++ b/components/sync/engine_impl/sync_engine_event_listener.h
@@ -16,7 +16,7 @@
 
 class SyncEngineEventListener {
  public:
-  SyncEngineEventListener();
+  SyncEngineEventListener() = default;
 
   // Generated at various points during the sync cycle.
   virtual void OnSyncCycleEvent(const SyncCycleEvent& event) = 0;
@@ -43,7 +43,7 @@
   virtual void OnProtocolEvent(const ProtocolEvent& event) = 0;
 
  protected:
-  virtual ~SyncEngineEventListener();
+  virtual ~SyncEngineEventListener() = default;
 };
 
 }  // namespace syncer
diff --git a/components/sync/engine_impl/sync_scheduler.cc b/components/sync/engine_impl/sync_scheduler.cc
deleted file mode 100644
index c18c158..0000000
--- a/components/sync/engine_impl/sync_scheduler.cc
+++ /dev/null
@@ -1,12 +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 "components/sync/engine_impl/sync_scheduler.h"
-
-namespace syncer {
-
-SyncScheduler::SyncScheduler() {}
-SyncScheduler::~SyncScheduler() {}
-
-}  // namespace syncer
diff --git a/components/sync/engine_impl/sync_scheduler.h b/components/sync/engine_impl/sync_scheduler.h
index a510020..c50cfc7 100644
--- a/components/sync/engine_impl/sync_scheduler.h
+++ b/components/sync/engine_impl/sync_scheduler.h
@@ -58,8 +58,8 @@
   // All methods of SyncScheduler must be called on the same thread
   // (except for RequestEarlyExit()).
 
-  SyncScheduler();
-  ~SyncScheduler() override;
+  SyncScheduler() = default;
+  ~SyncScheduler() override = default;
 
   // Start the scheduler with the given mode.  If the scheduler is
   // already started, switch to the given mode, although some
diff --git a/components/sync/engine_impl/update_handler.cc b/components/sync/engine_impl/update_handler.cc
deleted file mode 100644
index 2f6be6a..0000000
--- a/components/sync/engine_impl/update_handler.cc
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/sync/engine_impl/update_handler.h"
-
-namespace syncer {
-
-UpdateHandler::UpdateHandler() {}
-
-UpdateHandler::~UpdateHandler() {}
-
-}  // namespace syncer
diff --git a/components/sync/engine_impl/update_handler.h b/components/sync/engine_impl/update_handler.h
index 83e4c7c..12382dd36 100644
--- a/components/sync/engine_impl/update_handler.h
+++ b/components/sync/engine_impl/update_handler.h
@@ -25,19 +25,17 @@
 // from the sync server.
 class UpdateHandler {
  public:
-  UpdateHandler();
-  virtual ~UpdateHandler() = 0;
+  virtual ~UpdateHandler() = default;
 
   // Returns true if initial sync was performed for this type.
   virtual bool IsInitialSyncEnded() const = 0;
 
-  // Fills the given parameter with the stored progress marker for this type.
-  virtual void GetDownloadProgress(
-      sync_pb::DataTypeProgressMarker* progress_marker) const = 0;
+  // Returns the stored progress marker for this type.
+  virtual const sync_pb::DataTypeProgressMarker& GetDownloadProgress()
+      const = 0;
 
-  // Fills |context| with the per-client datatype context, if one exists. Clears
-  // |context| otherwise.
-  virtual void GetDataTypeContext(sync_pb::DataTypeContext* context) const = 0;
+  // Returns the per-client datatype context.
+  virtual const sync_pb::DataTypeContext& GetDataTypeContext() const = 0;
 
   // Processes the contents of a GetUpdates response message.
   //
diff --git a/components/sync/model/model_type_change_processor.cc b/components/sync/model/model_type_change_processor.cc
deleted file mode 100644
index 2b42fa6..0000000
--- a/components/sync/model/model_type_change_processor.cc
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/sync/model/model_type_change_processor.h"
-
-namespace syncer {
-
-ModelTypeChangeProcessor::ModelTypeChangeProcessor() {}
-ModelTypeChangeProcessor::~ModelTypeChangeProcessor() {}
-
-}  // namespace syncer
diff --git a/components/sync/model/model_type_change_processor.h b/components/sync/model/model_type_change_processor.h
index 0815125..ee9de0df 100644
--- a/components/sync/model/model_type_change_processor.h
+++ b/components/sync/model/model_type_change_processor.h
@@ -27,8 +27,8 @@
 // Interface used by the ModelTypeSyncBridge to inform sync of local changes.
 class ModelTypeChangeProcessor {
  public:
-  ModelTypeChangeProcessor();
-  virtual ~ModelTypeChangeProcessor();
+  ModelTypeChangeProcessor() = default;
+  virtual ~ModelTypeChangeProcessor() = default;
 
   // Inform the processor of a new or updated entity. The |entity_data| param
   // does not need to be fully set, but it should at least have specifics and
diff --git a/components/sync/model/sync_error_factory.cc b/components/sync/model/sync_error_factory.cc
deleted file mode 100644
index 5c06988..0000000
--- a/components/sync/model/sync_error_factory.cc
+++ /dev/null
@@ -1,13 +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 "components/sync/model/sync_error_factory.h"
-
-namespace syncer {
-
-SyncErrorFactory::SyncErrorFactory() {}
-
-SyncErrorFactory::~SyncErrorFactory() {}
-
-}  // namespace syncer
diff --git a/components/sync/model/sync_error_factory.h b/components/sync/model/sync_error_factory.h
index 66d471f..0259ed0 100644
--- a/components/sync/model/sync_error_factory.h
+++ b/components/sync/model/sync_error_factory.h
@@ -14,8 +14,8 @@
 
 class SyncErrorFactory {
  public:
-  SyncErrorFactory();
-  virtual ~SyncErrorFactory();
+  SyncErrorFactory() = default;
+  virtual ~SyncErrorFactory() = default;
 
   // Creates a SyncError object and uploads this call stack to breakpad.
   virtual SyncError CreateAndUploadError(const base::Location& location,
diff --git a/components/sync/test/engine/mock_update_handler.cc b/components/sync/test/engine/mock_update_handler.cc
index 0df42ef..db39f77 100644
--- a/components/sync/test/engine/mock_update_handler.cc
+++ b/components/sync/test/engine/mock_update_handler.cc
@@ -22,14 +22,13 @@
   return false;
 }
 
-void MockUpdateHandler::GetDownloadProgress(
-    sync_pb::DataTypeProgressMarker* progress_marker) const {
-  progress_marker->CopyFrom(progress_marker_);
+const sync_pb::DataTypeProgressMarker& MockUpdateHandler::GetDownloadProgress()
+    const {
+  return progress_marker_;
 }
 
-void MockUpdateHandler::GetDataTypeContext(
-    sync_pb::DataTypeContext* context) const {
-  context->Clear();
+const sync_pb::DataTypeContext& MockUpdateHandler::GetDataTypeContext() const {
+  return kEmptyDataTypeContext;
 }
 
 SyncerError MockUpdateHandler::ProcessGetUpdatesResponse(
diff --git a/components/sync/test/engine/mock_update_handler.h b/components/sync/test/engine/mock_update_handler.h
index 38c8d3f..ca72ec7 100644
--- a/components/sync/test/engine/mock_update_handler.h
+++ b/components/sync/test/engine/mock_update_handler.h
@@ -19,9 +19,8 @@
 
   // UpdateHandler implementation.
   bool IsInitialSyncEnded() const override;
-  void GetDownloadProgress(
-      sync_pb::DataTypeProgressMarker* progress_marker) const override;
-  void GetDataTypeContext(sync_pb::DataTypeContext* context) const override;
+  const sync_pb::DataTypeProgressMarker& GetDownloadProgress() const override;
+  const sync_pb::DataTypeContext& GetDataTypeContext() const override;
   SyncerError ProcessGetUpdatesResponse(
       const sync_pb::DataTypeProgressMarker& progress_marker,
       const sync_pb::DataTypeContext& mutated_context,
@@ -38,6 +37,7 @@
 
  private:
   sync_pb::DataTypeProgressMarker progress_marker_;
+  const sync_pb::DataTypeContext kEmptyDataTypeContext;
 
   int apply_updates_count_;
   int passive_apply_updates_count_;
diff --git a/components/system_media_controls/linux/buildflags/buildflags.gni b/components/system_media_controls/linux/buildflags/buildflags.gni
index cc386dfb..4c3bf42b 100644
--- a/components/system_media_controls/linux/buildflags/buildflags.gni
+++ b/components/system_media_controls/linux/buildflags/buildflags.gni
@@ -7,5 +7,5 @@
 declare_args() {
   # Enables Chromium implementation of the MPRIS D-Bus interface for controlling
   # media playback. See ../README.md for details.
-  use_mpris = is_desktop_linux && use_dbus
+  use_mpris = is_linux && use_dbus
 }
diff --git a/components/vector_icons/vector_icons.gni b/components/vector_icons/vector_icons.gni
index d068c55a..0bff996 100644
--- a/components/vector_icons/vector_icons.gni
+++ b/components/vector_icons/vector_icons.gni
@@ -66,8 +66,8 @@
 
 # Deprecated version. TODO(estade): remove when all clients are updated.
 template("aggregate_vector_icons") {
-  assert(defined(invoker.icons),
-         "Need icons in $target_name listing the icon files.")
+  assert(defined(invoker.sources),
+         "Need sources in $target_name listing the icon files.")
   assert(
       defined(invoker.icon_directory),
       "Need icon_directory in $target_name where the icons and templates live.")
@@ -85,7 +85,7 @@
       "vector_icons.h.template",
     ]
     inputs =
-        rebase_path(templates + invoker.icons, ".", invoker.icon_directory) +
+        rebase_path(templates + invoker.sources, ".", invoker.icon_directory) +
         [ "//components/vector_icons/aggregate_vector_icons.py" ]
 
     outputs = [
@@ -94,7 +94,7 @@
     ]
 
     response_file_contents =
-        rebase_path(invoker.icons, root_build_dir, invoker.icon_directory)
+        rebase_path(invoker.sources, root_build_dir, invoker.icon_directory)
 
     args = [
       "--working_directory=" +
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index fcf6229..a66ccb3a 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -2107,7 +2107,7 @@
     ]
   }
 
-  if (is_desktop_linux) {
+  if (is_linux) {
     sources += [ "speech/tts_linux.cc" ]
     deps += [ "//third_party/speech-dispatcher" ]
   }
@@ -2386,7 +2386,7 @@
     # Ozone/Linux, but requires to use pangocairo. We probably want to continue
     # using pango on Linux and switch to something else for other systems that
     # use Ozone.
-    if (use_ozone && (!is_desktop_linux || !use_pangocairo)) {
+    if (use_ozone && (!is_linux || !use_pangocairo)) {
       sources += [ "renderer_host/pepper/pepper_truetype_font_list_ozone.cc" ]
     } else if (use_pangocairo) {
       sources += [ "renderer_host/pepper/pepper_truetype_font_list_pango.cc" ]
diff --git a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
index 50d26c6d..036ebd5 100644
--- a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
@@ -1550,6 +1550,11 @@
   RunHtmlTest(FILE_PATH_LITERAL("generated-content-after-hidden-input.html"));
 }
 
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
+                       AccessibilityGeneratedContentInEmptyPage) {
+  RunHtmlTest(FILE_PATH_LITERAL("generated-content-in-empty-page.html"));
+}
+
 IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityHead) {
   RunHtmlTest(FILE_PATH_LITERAL("head.html"));
 }
diff --git a/content/browser/browsing_instance.cc b/content/browser/browsing_instance.cc
index b5a6cce5..732f271 100644
--- a/content/browser/browsing_instance.cc
+++ b/content/browser/browsing_instance.cc
@@ -73,10 +73,10 @@
 }
 
 scoped_refptr<SiteInstanceImpl> BrowsingInstance::GetSiteInstanceForURL(
-    const GURL& url,
+    const UrlInfo& url_info,
     bool allow_default_instance) {
   scoped_refptr<SiteInstanceImpl> site_instance =
-      GetSiteInstanceForURLHelper(url, allow_default_instance);
+      GetSiteInstanceForURLHelper(url_info, allow_default_instance);
 
   if (site_instance)
     return site_instance;
@@ -86,30 +86,30 @@
 
   // Set the site of this new SiteInstance, which will register it with us,
   // unless this URL should leave the SiteInstance's site unassigned.
-  if (SiteInstance::ShouldAssignSiteForURL(url))
-    instance->SetSite(url);
+  if (SiteInstance::ShouldAssignSiteForURL(url_info.url))
+    instance->SetSite(url_info);
   return instance;
 }
 
-SiteInfo BrowsingInstance::GetSiteInfoForURL(const GURL& url,
+SiteInfo BrowsingInstance::GetSiteInfoForURL(const UrlInfo& url_info,
                                              bool allow_default_instance) {
   scoped_refptr<SiteInstanceImpl> site_instance =
-      GetSiteInstanceForURLHelper(url, allow_default_instance);
+      GetSiteInstanceForURLHelper(url_info, allow_default_instance);
 
   if (site_instance)
     return site_instance->GetSiteInfo();
 
-  return ComputeSiteInfoForURL(url);
+  return ComputeSiteInfoForURL(url_info);
 }
 
 bool BrowsingInstance::TrySettingDefaultSiteInstance(
     SiteInstanceImpl* site_instance,
-    const GURL& url) {
+    const UrlInfo& url_info) {
   DCHECK(!site_instance->HasSite());
-  const SiteInfo site_info = ComputeSiteInfoForURL(url);
+  const SiteInfo site_info = ComputeSiteInfoForURL(url_info);
   if (default_site_instance_ ||
-      !SiteInstanceImpl::CanBePlacedInDefaultSiteInstance(isolation_context_,
-                                                          url, site_info)) {
+      !SiteInstanceImpl::CanBePlacedInDefaultSiteInstance(
+          isolation_context_, url_info.url, site_info)) {
     return false;
   }
 
@@ -122,9 +122,9 @@
 }
 
 scoped_refptr<SiteInstanceImpl> BrowsingInstance::GetSiteInstanceForURLHelper(
-    const GURL& url,
+    const UrlInfo& url_info,
     bool allow_default_instance) {
-  const SiteInfo site_info = ComputeSiteInfoForURL(url);
+  const SiteInfo site_info = ComputeSiteInfoForURL(url_info);
   auto i = site_instance_map_.find(site_info);
   if (i != site_instance_map_.end())
     return i->second;
@@ -132,8 +132,8 @@
   // Check to see if we can use the default SiteInstance for sites that don't
   // need to be isolated in their own process.
   if (allow_default_instance &&
-      SiteInstanceImpl::CanBePlacedInDefaultSiteInstance(isolation_context_,
-                                                         url, site_info)) {
+      SiteInstanceImpl::CanBePlacedInDefaultSiteInstance(
+          isolation_context_, url_info.url, site_info)) {
     DCHECK(!default_process_);
     scoped_refptr<SiteInstanceImpl> site_instance = default_site_instance_;
     if (!site_instance) {
@@ -222,9 +222,10 @@
   policy->RemoveOptInIsolatedOriginsForBrowsingInstance(isolation_context_);
 }
 
-SiteInfo BrowsingInstance::ComputeSiteInfoForURL(const GURL& url) const {
+SiteInfo BrowsingInstance::ComputeSiteInfoForURL(
+    const UrlInfo& url_info) const {
   return SiteInstanceImpl::ComputeSiteInfo(
-      isolation_context_, url, is_coop_coep_cross_origin_isolated_,
+      isolation_context_, url_info, is_coop_coep_cross_origin_isolated_,
       coop_coep_cross_origin_isolated_origin_);
 }
 
diff --git a/content/browser/browsing_instance.h b/content/browser/browsing_instance.h
index bc0c882..75a58c7 100644
--- a/content/browser/browsing_instance.h
+++ b/content/browser/browsing_instance.h
@@ -26,6 +26,7 @@
 class RenderProcessHost;
 class SiteInfo;
 class SiteInstanceImpl;
+struct UrlInfo;
 
 ///////////////////////////////////////////////////////////////////////////////
 //
@@ -111,7 +112,7 @@
   // the site of |site_info|.
   bool HasSiteInstance(const SiteInfo& site_info);
 
-  // Get the SiteInstance responsible for rendering the given URL.  Should
+  // Get the SiteInstance responsible for rendering the given UrlInfo.  Should
   // create a new one if necessary, but should not create more than one
   // SiteInstance per site.
   //
@@ -122,30 +123,31 @@
   // GetSiteURL() and lock_url() calls because the default instance is not
   // bound to a single site.
   scoped_refptr<SiteInstanceImpl> GetSiteInstanceForURL(
-      const GURL& url,
+      const UrlInfo& url_info,
       bool allow_default_instance);
 
-  // Returns a SiteInfo with site and process-lock URLs for |url| that are
+  // Returns a SiteInfo with site and process-lock URLs for |url_info| that are
   // identical with what these values would be if we called
-  // GetSiteInstanceForURL() with the same |url| and |allow_default_instance|.
-  // This method is used when we need this information, but do not want to
-  // create a SiteInstance yet.
+  // GetSiteInstanceForURL() with the same |url_info| and
+  // |allow_default_instance|. This method is used when we need this
+  // information, but do not want to create a SiteInstance yet.
   //
   // Note: Unlike ComputeSiteInfoForURL() this method can return a SiteInfo for
-  // a default SiteInstance, if |url| can be placed in the default SiteInstance
-  // and |allow_default_instance| is true.
-  SiteInfo GetSiteInfoForURL(const GURL& url, bool allow_default_instance);
+  // a default SiteInstance, if |url_info| can be placed in the default
+  // SiteInstance and |allow_default_instance| is true.
+  SiteInfo GetSiteInfoForURL(const UrlInfo& url_info,
+                             bool allow_default_instance);
 
   // Helper function used by GetSiteInstanceForURL() and GetSiteInfoForURL()
   // that returns an existing SiteInstance from |site_instance_map_| or
   // returns |default_site_instance_| if |allow_default_instance| is true and
   // other conditions are met. If there is no existing SiteInstance that is
-  // appropriate for |url|, |allow_default_instance| combination, then a nullptr
-  // is returned.
+  // appropriate for |url_info|, |allow_default_instance| combination, then a
+  // nullptr is returned.
   //
   // Note: This method is not intended to be called by code outside this object.
   scoped_refptr<SiteInstanceImpl> GetSiteInstanceForURLHelper(
-      const GURL& url,
+      const UrlInfo& url_info,
       bool allow_default_instance);
 
   // Adds the given SiteInstance to our map, to ensure that we do not create
@@ -184,20 +186,20 @@
   bool IsSiteInDefaultSiteInstance(const GURL& site_url) const;
 
   // Attempts to convert |site_instance| into a default SiteInstance,
-  // if |url| can be placed inside a default SiteInstance, and the default
+  // if |url_info| can be placed inside a default SiteInstance, and the default
   // SiteInstance has not already been set for this object.
   // Returns true if |site_instance| was successfully converted to a default
   // SiteInstance. Otherwise, returns false.
   bool TrySettingDefaultSiteInstance(SiteInstanceImpl* site_instance,
-                                     const GURL& url);
+                                     const UrlInfo& url_info);
 
   // Helper function used by other methods in this class to ensure consistent
-  // mapping between |url| and SiteInfo. This method will never return a
+  // mapping between |url_info| and SiteInfo. This method will never return a
   // SiteInfo for the default SiteInstance. It will always return something
-  // specific to |url|.
+  // specific to |url_info|.
   //
   // Note: This should not be used by code outside this class.
-  SiteInfo ComputeSiteInfoForURL(const GURL& url) const;
+  SiteInfo ComputeSiteInfoForURL(const UrlInfo& url_info) const;
 
   // Map of SiteInfo to SiteInstance, to ensure we only have one SiteInstance
   // per SiteInfo. See https://crbug.com/1085275#c2 for the rationale behind
diff --git a/content/browser/child_process_security_policy_impl.cc b/content/browser/child_process_security_policy_impl.cc
index 766077f..dfa87d0 100644
--- a/content/browser/child_process_security_policy_impl.cc
+++ b/content/browser/child_process_security_policy_impl.cc
@@ -1491,17 +1491,17 @@
     int child_id,
     const IsolationContext& isolation_context,
     const url::Origin& origin,
-    const GURL& url,
+    const UrlInfo& url_info,
     bool is_coop_coep_cross_origin_isolated,
     const base::Optional<url::Origin>& coop_coep_cross_origin_isolated_origin) {
-  const url::Origin url_origin = url::Origin::Resolve(url, origin);
+  const url::Origin url_origin = url::Origin::Resolve(url_info.url, origin);
   if (!CanAccessDataForOrigin(child_id, url_origin)) {
     // Check for special cases, like blob:null/ and data: URLs, where the
     // origin does not contain information to match against the process lock,
     // but using the whole URL can result in a process lock match.
     const ProcessLock expected_process_lock =
         SiteInstanceImpl::DetermineProcessLock(
-            isolation_context, url, is_coop_coep_cross_origin_isolated,
+            isolation_context, url_info, is_coop_coep_cross_origin_isolated,
             coop_coep_cross_origin_isolated_origin);
     const ProcessLock& actual_process_lock = GetProcessLock(child_id);
     if (actual_process_lock == expected_process_lock)
@@ -1544,7 +1544,7 @@
     //
     // TODO(1020201): Make CreateWithReferenceOrigin() & Resolve() consistent
     // with each other and then remove this exception.
-    if (base::Contains(url::GetNoAccessSchemes(), url.scheme()))
+    if (base::Contains(url::GetNoAccessSchemes(), url_info.url.scheme()))
       return CanCommitStatus::CAN_COMMIT_ORIGIN_AND_URL;
 
     return CanCommitStatus::CANNOT_COMMIT_ORIGIN;
@@ -1640,12 +1640,24 @@
       // Since we are dealing with a valid ProcessLock at this point, we know
       // the lock contains valid COOP/COEP information because that information
       // must be provided when creating the locks.
+      //
+      // At this point, any origin opt-in isolation requests should be complete,
+      // so to avoid the possibility of opting something set
+      // |origin_requests_isolation| = false below. Note: We might need to
+      // revisit this if CanAccessDataForOrigin() needs to be called while a
+      // SiteInstance is being determined for a navigation, i.e. during
+      // GetSiteInstanceForNavigationRequest().  If this happens, we'd need to
+      // plumb UrlInfo::origin_requests_isolation value from the ongoing
+      // NavigationRequest into here. Also, we would likely need to attach the
+      // BrowsingInstanceID to UrlInfo once the SiteInstance has been determined
+      // in case the RenderProcess has multiple BrowsingInstances in it.
       // TODO(acolwell): Provide a way for callers, that know
       // their request's require COOP/COEP handling, to pass in their COOP/COEP
       // information so it can be used here instead of the values in
       // |actual_process_lock|.
       expected_process_lock = SiteInstanceImpl::DetermineProcessLock(
-          isolation_context, url,
+          isolation_context,
+          UrlInfo(url, false /* origin_requests_isolation */),
           actual_process_lock.is_coop_coep_cross_origin_isolated(),
           actual_process_lock.coop_coep_cross_origin_isolated_origin());
 
@@ -1708,8 +1720,11 @@
             return true;
         }
 
+        // See the DetermineProcessLock() call above regarding why we pass
+        // 'false' for |origin_requests_isolation| below.
         SiteInfo site_info = SiteInstanceImpl::ComputeSiteInfo(
-            isolation_context, url,
+            isolation_context,
+            UrlInfo(url, false /* origin_requests_isolation */),
             actual_process_lock.is_coop_coep_cross_origin_isolated(),
             actual_process_lock.coop_coep_cross_origin_isolated_origin());
 
@@ -1941,9 +1956,11 @@
 
 bool ChildProcessSecurityPolicyImpl::IsIsolatedOrigin(
     const IsolationContext& isolation_context,
-    const url::Origin& origin) {
+    const url::Origin& origin,
+    bool origin_requests_isolation) {
   url::Origin unused_result;
-  return GetMatchingIsolatedOrigin(isolation_context, origin, &unused_result);
+  return GetMatchingIsolatedOrigin(isolation_context, origin,
+                                   origin_requests_isolation, &unused_result);
 }
 
 bool ChildProcessSecurityPolicyImpl::IsGloballyIsolatedOriginForTesting(
@@ -1952,7 +1969,7 @@
   BrowsingInstanceId null_browsing_instance_id;
   IsolationContext isolation_context(null_browsing_instance_id,
                                      no_browser_context);
-  return IsIsolatedOrigin(isolation_context, origin);
+  return IsIsolatedOrigin(isolation_context, origin, false);
 }
 
 std::vector<url::Origin> ChildProcessSecurityPolicyImpl::GetIsolatedOrigins(
@@ -1985,6 +2002,7 @@
 bool ChildProcessSecurityPolicyImpl::GetMatchingIsolatedOrigin(
     const IsolationContext& isolation_context,
     const url::Origin& origin,
+    bool origin_requests_isolation,
     url::Origin* result) {
   // GetSiteForOrigin() is used to look up the site URL of |origin| to speed
   // up the isolated origin lookup.  This only performs a straightforward
@@ -1993,14 +2011,15 @@
   // here, but *is* typically needed for making process model decisions. Be
   // very careful about using GetSiteForOrigin() elsewhere, and consider
   // whether you should be using GetSiteForURL() instead.
-  return GetMatchingIsolatedOrigin(isolation_context, origin,
-                                   SiteInstanceImpl::GetSiteForOrigin(origin),
-                                   result);
+  return GetMatchingIsolatedOrigin(
+      isolation_context, origin, origin_requests_isolation,
+      SiteInstanceImpl::GetSiteForOrigin(origin), result);
 }
 
 bool ChildProcessSecurityPolicyImpl::GetMatchingIsolatedOrigin(
     const IsolationContext& isolation_context,
     const url::Origin& origin,
+    bool origin_requests_isolation,
     const GURL& site_url,
     url::Origin* result) {
   DCHECK(IsRunningOnExpectedThread());
@@ -2025,7 +2044,8 @@
     // false, or true with result set to |origin|. We give priority to origins
     // requesting opt-in isolation over command-line isolation, but don't check
     // for opt-in if we didn't get a valid BrowsingInstance id.
-    if (ShouldOriginGetOptInIsolation(isolation_context, origin)) {
+    if (ShouldOriginGetOptInIsolation(isolation_context, origin,
+                                      origin_requests_isolation)) {
       *result = origin;
       return true;
     }
@@ -2100,7 +2120,8 @@
 
 bool ChildProcessSecurityPolicyImpl::ShouldOriginGetOptInIsolation(
     const IsolationContext& isolation_context,
-    const url::Origin& origin) {
+    const url::Origin& origin,
+    bool origin_requests_isolation) {
   // Note: we cannot check the feature flags and early-out here, because the
   // origin trial might be active (in which case no feature flags are active).
 
@@ -2137,19 +2158,9 @@
     }
   }
 
-  // Opt-in origin isolation is specific to (and consistent throughout) a
-  // BrowsingInstance.  There is no global mode for each origin, and instead the
-  // opt-in request comes via the NavigationRequest.  If we haven't already
-  // decided that this origin is isolated or non-isolated above, then base the
-  // decision on that request (which gets stored in the temporary
-  // scoped_isolation_request_origin_ because it's awkward to pass in as a
-  // parameter).
-  // The thread-check is needed since this function can be called from the IO
-  // thread, though it is only safe to access the scoped request on the UI
-  // thread. Calls on the IO thread do not depend on this value for correctness
-  // because they are not adding new origins; they can rely on the maps above.
-  return BrowserThread::CurrentlyOn(BrowserThread::UI) &&
-         scoped_isolation_request_origin_ == origin;
+  // If we get to this point, then |origin| is neither opted-in nor opted-out.
+  // At this point we allow opting in if it's requested.
+  return origin_requests_isolation;
 }
 
 bool ChildProcessSecurityPolicyImpl::HasOriginEverRequestedOptInIsolation(
@@ -2369,33 +2380,6 @@
   return Handle(child_id, /* duplicating_handle */ false);
 }
 
-// static
-std::unique_ptr<
-    ChildProcessSecurityPolicyImpl::ScopedOriginIsolationOptInRequest>
-ChildProcessSecurityPolicyImpl::ScopedOriginIsolationOptInRequest::
-    GetScopedOriginIsolationOptInRequest(const url::Origin& origin_to_isolate) {
-  ChildProcessSecurityPolicyImpl* instance = GetInstance();
-  // Nested calls are not allowed, even for the same origin.
-  CHECK(!instance->scoped_isolation_request_origin_);
-  return base::WrapUnique<ScopedOriginIsolationOptInRequest>(
-      new ScopedOriginIsolationOptInRequest(origin_to_isolate));
-}
-
-ChildProcessSecurityPolicyImpl::ScopedOriginIsolationOptInRequest::
-    ScopedOriginIsolationOptInRequest(const url::Origin& origin_to_isolate) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  ChildProcessSecurityPolicyImpl* instance = GetInstance();
-  DCHECK(!instance->scoped_isolation_request_origin_);
-  instance->scoped_isolation_request_origin_ = origin_to_isolate;
-}
-
-ChildProcessSecurityPolicyImpl::ScopedOriginIsolationOptInRequest::
-    ~ScopedOriginIsolationOptInRequest() {
-  ChildProcessSecurityPolicyImpl* instance = GetInstance();
-  DCHECK(instance->scoped_isolation_request_origin_);
-  instance->scoped_isolation_request_origin_ = base::nullopt;
-}
-
 bool ChildProcessSecurityPolicyImpl::AddProcessReference(
     int child_id,
     bool duplicating_handle) {
diff --git a/content/browser/child_process_security_policy_impl.h b/content/browser/child_process_security_policy_impl.h
index 6ec7d47b..014c53d 100644
--- a/content/browser/child_process_security_policy_impl.h
+++ b/content/browser/child_process_security_policy_impl.h
@@ -256,35 +256,6 @@
     int child_id_;
   };
 
-  // This scoped class is used to temporarily store an origin requesting
-  // opt-in isolation in scoped_isolation_request_origin_, while the request is
-  // being processed. This is only necessary because it is difficult to plumb
-  // the parameter through the site URL computations that need it, and it is
-  // safe because overlapping and nested occurrences cannot occur. Callers
-  // should stack allocate the result so that the temporary result is
-  // automatically cleared when the result goes out of scope. The stored
-  // scoped_isolation_request_origin_ value is only safe to access from the UI
-  // thread, where this object must be created.
-  class CONTENT_EXPORT ScopedOriginIsolationOptInRequest {
-   public:
-    // This returns a new unique_ptr to ScopedOriginIsolationOptInRequest if
-    // no origin is already scoped for an isolation request, or a null
-    // unique_ptr if |origin_to_isolate| has already been scoped. If
-    // a second call occurs while this is set, this will fail on a CHECK.
-    static std::unique_ptr<ScopedOriginIsolationOptInRequest>
-    GetScopedOriginIsolationOptInRequest(const url::Origin& origin_to_isolate);
-
-    ~ScopedOriginIsolationOptInRequest();
-
-   private:
-    explicit ScopedOriginIsolationOptInRequest(
-        const url::Origin& origin_to_isolate);
-
-    friend class ChildProcessSecurityPolicyImpl;
-
-    DISALLOW_COPY_AND_ASSIGN(ScopedOriginIsolationOptInRequest);
-  };
-
   // Object can only be created through GetInstance() so the constructor is
   // private.
   ~ChildProcessSecurityPolicyImpl() override;
@@ -367,7 +338,7 @@
       int child_id,
       const IsolationContext& isolation_context,
       const url::Origin& origin,
-      const GURL& url,
+      const UrlInfo& url_info,
       bool is_coop_coep_cross_origin_isolated,
       const base::Optional<url::Origin>&
           coop_coep_cross_origin_isolated_origin);
@@ -400,6 +371,7 @@
   // will only affect future BrowsingInstances.
   bool GetMatchingIsolatedOrigin(const IsolationContext& isolation_context,
                                  const url::Origin& origin,
+                                 bool origin_requests_isolation,
                                  url::Origin* result);
 
   // Removes any origin isolation opt-in entries associated with the
@@ -426,10 +398,10 @@
   //    |origin_isolation_by_browsing_instance_|, in which case we follow the
   //    same policy, or
   // 2) if it's not currently tracked as described above, whether |origin| is
-  //    currently requesting isolation via |scoped_isolation_request_origin_|,
-  //    as part of an in-progress navigation.
+  //    currently requesting isolation via |origin_requests_isolation|.
   bool ShouldOriginGetOptInIsolation(const IsolationContext& isolation_context,
-                                     const url::Origin& origin);
+                                     const url::Origin& origin,
+                                     bool origin_requests_isolation);
 
   // This function adds |origin| to the master list of origins that have
   // ever requested opt-in isolation, either via an OriginPolicy or opt-in
@@ -443,6 +415,7 @@
   // already known to avoid recomputing it internally.
   bool GetMatchingIsolatedOrigin(const IsolationContext& isolation_context,
                                  const url::Origin& origin,
+                                 bool origin_requests_isolation,
                                  const GURL& site_url,
                                  url::Origin* result);
 
@@ -590,9 +563,13 @@
   //
   // |isolation_context| is used to determine which origins are isolated in
   // this context.  For example, isolated origins that are dynamically added
-  // will only affect future BrowsingInstances.
+  // will only affect future BrowsingInstances. |origin_requests_isolation| may
+  // be true during navigation requests, and allows us to correctly determine
+  // isolation status for an origin that may not have had its isolation status
+  // recorded in the BrowsingInstance yet.
   bool IsIsolatedOrigin(const IsolationContext& isolation_context,
-                        const url::Origin& origin);
+                        const url::Origin& origin,
+                        bool origin_requests_isolation);
 
   // Removes a previously added isolated origin, currently only used in tests.
   //
@@ -914,7 +891,7 @@
   // tracked so we know which origins need to be tracked when non-isolated in
   // any given BrowsingInstance. Origins requesting isolation, if successful,
   // are marked as isolated via ShouldOriginGetOptInIsolation's checking
-  // |scoped_isolation_request_origin_|.
+  // |origin_requests_isolation|.
   base::flat_set<url::Origin> origin_isolation_opt_ins_
       GUARDED_BY(origins_isolation_opt_in_lock_);
   // A map to track origins that have been isolated within a given
@@ -931,13 +908,6 @@
       origin_isolation_non_isolated_by_browsing_instance_
           GUARDED_BY(origins_isolation_opt_in_lock_);
 
-  // This origin is set during a call to NavigationRequest::OnResponseStarted
-  // that requests isolation for an origin, via the creation of a
-  // ScopedOriginIsolationOptInRequest object. This value's state is read by
-  // ShouldOriginGetOptInIsolation(), and it is only safe to access on the UI
-  // thread.
-  base::Optional<url::Origin> scoped_isolation_request_origin_;
-
   DISALLOW_COPY_AND_ASSIGN(ChildProcessSecurityPolicyImpl);
 };
 
diff --git a/content/browser/child_process_security_policy_unittest.cc b/content/browser/child_process_security_policy_unittest.cc
index db37e91..7e6489a 100644
--- a/content/browser/child_process_security_policy_unittest.cc
+++ b/content/browser/child_process_security_policy_unittest.cc
@@ -83,7 +83,9 @@
                          BrowserContext* browser_context,
                          const GURL& url) {
   scoped_refptr<SiteInstanceImpl> site_instance =
-      SiteInstanceImpl::CreateForURL(browser_context, url);
+      SiteInstanceImpl::CreateForUrlInfo(
+          browser_context, UrlInfo::CreateForTesting(url),
+          false /* is_coop_coep_cross_origin_isolated */);
   if (site_instance->RequiresDedicatedProcess() &&
       SiteInstanceImpl::ShouldLockProcess(site_instance->GetIsolationContext(),
                                           site_instance->GetSiteInfo(),
@@ -183,7 +185,7 @@
     return p->IsIsolatedOrigin(
         IsolationContext(
             BrowsingInstanceId::FromUnsafeValue(browsing_instance_id), context),
-        origin);
+        origin, false /* origin_requests_isolation */);
   }
 
   // Returns the number of isolated origin entries for a particular origin.
@@ -204,8 +206,9 @@
   void CheckGetSiteForURL(BrowserContext* context,
                           std::map<GURL, GURL> to_test) {
     for (const auto& entry : to_test) {
-      EXPECT_EQ(SiteInstanceImpl::GetSiteForURL(IsolationContext(context),
-                                                entry.first),
+      EXPECT_EQ(SiteInstanceImpl::GetSiteForURL(
+                    IsolationContext(context),
+                    UrlInfo::CreateForTesting(entry.first)),
                 entry.second);
     }
   }
@@ -1421,7 +1424,9 @@
 
   // Lock process to |http_url| origin.
   scoped_refptr<SiteInstanceImpl> foo_instance =
-      SiteInstanceImpl::CreateForURL(&browser_context, foo_http_url);
+      SiteInstanceImpl::CreateForUrlInfo(
+          &browser_context, UrlInfo::CreateForTesting(foo_http_url),
+          false /* is_coop_coep_cross_origin_isolated */);
   EXPECT_FALSE(foo_instance->IsDefaultSiteInstance());
   LockProcessIfNeeded(kRendererID, &browser_context, foo_http_url);
 
@@ -1536,7 +1541,9 @@
 
   // Lock process to |foo_origin| origin.
   scoped_refptr<SiteInstanceImpl> foo_instance =
-      SiteInstanceImpl::CreateForURL(&browser_context, foo_origin.GetURL());
+      SiteInstanceImpl::CreateForUrlInfo(
+          &browser_context, UrlInfo::CreateForTesting(foo_origin.GetURL()),
+          false /* is_coop_coep_cross_origin_isolated */);
   EXPECT_FALSE(foo_instance->IsDefaultSiteInstance());
   LockProcessIfNeeded(kRendererID, &browser_context, foo_origin.GetURL());
 
@@ -2031,7 +2038,9 @@
   // Create a new BrowsingInstance.  Its ID will be |initial_id|.
   TestBrowserContext context;
   scoped_refptr<SiteInstanceImpl> foo_instance =
-      SiteInstanceImpl::CreateForURL(&context, GURL("https://foo.com/"));
+      SiteInstanceImpl::CreateForUrlInfo(
+          &context, UrlInfo::CreateForTesting(GURL("https://foo.com/")),
+          false /* is_coop_coep_cross_origin_isolated */);
   EXPECT_EQ(BrowsingInstanceId::FromUnsafeValue(initial_id),
             foo_instance->GetIsolationContext().browsing_instance_id());
   EXPECT_EQ(BrowsingInstanceId::FromUnsafeValue(initial_id + 1),
@@ -2056,7 +2065,9 @@
 
   // Create another BrowsingInstance.
   scoped_refptr<SiteInstanceImpl> bar_instance =
-      SiteInstanceImpl::CreateForURL(&context, GURL("https://bar.com/"));
+      SiteInstanceImpl::CreateForUrlInfo(
+          &context, UrlInfo::CreateForTesting(GURL("https://bar.com/")),
+          false /* is_coop_coep_cross_origin_isolated */);
   EXPECT_EQ(BrowsingInstanceId::FromUnsafeValue(initial_id + 1),
             bar_instance->GetIsolationContext().browsing_instance_id());
   EXPECT_EQ(BrowsingInstanceId::FromUnsafeValue(initial_id + 2),
@@ -2097,10 +2108,14 @@
 
   // An IsolationContext constructed without a BrowsingInstance ID should
   // return the latest available isolated origins.
-  EXPECT_TRUE(p->IsIsolatedOrigin(IsolationContext(&context), foo));
-  EXPECT_TRUE(p->IsIsolatedOrigin(IsolationContext(&context), bar));
-  EXPECT_TRUE(p->IsIsolatedOrigin(IsolationContext(&context), baz));
-  EXPECT_TRUE(p->IsIsolatedOrigin(IsolationContext(&context), qux));
+  EXPECT_TRUE(p->IsIsolatedOrigin(IsolationContext(&context), foo,
+                                  false /* origin_requests_isolation */));
+  EXPECT_TRUE(p->IsIsolatedOrigin(IsolationContext(&context), bar,
+                                  false /* origin_requests_isolation */));
+  EXPECT_TRUE(p->IsIsolatedOrigin(IsolationContext(&context), baz,
+                                  false /* origin_requests_isolation */));
+  EXPECT_TRUE(p->IsIsolatedOrigin(IsolationContext(&context), qux,
+                                  false /* origin_requests_isolation */));
 
   p->RemoveIsolatedOriginForTesting(foo);
   p->RemoveIsolatedOriginForTesting(bar);
@@ -2115,9 +2130,11 @@
       ChildProcessSecurityPolicyImpl::GetInstance();
   TestBrowserContext context;
   EXPECT_FALSE(p->IsIsolatedOrigin(IsolationContext(&context),
-                                   url::Origin::Create(GURL())));
+                                   url::Origin::Create(GURL()),
+                                   false /* origin_requests_isolation */));
   EXPECT_FALSE(p->IsIsolatedOrigin(IsolationContext(&context),
-                                   url::Origin::Create(GURL("file:///foo"))));
+                                   url::Origin::Create(GURL("file:///foo")),
+                                   false /* origin_requests_isolation */));
 }
 
 // Verifies the API for restricting isolated origins to a specific
@@ -2160,7 +2177,9 @@
 
   // Create a new BrowsingInstance.  Its ID will be |initial_id|.
   scoped_refptr<SiteInstanceImpl> foo_instance =
-      SiteInstanceImpl::CreateForURL(&context1, GURL("https://foo.com/"));
+      SiteInstanceImpl::CreateForUrlInfo(
+          &context1, UrlInfo::CreateForTesting(GURL("https://foo.com/")),
+          false /* is_coop_coep_cross_origin_isolated */);
   EXPECT_EQ(BrowsingInstanceId::FromUnsafeValue(initial_id),
             foo_instance->GetIsolationContext().browsing_instance_id());
   EXPECT_EQ(BrowsingInstanceId::FromUnsafeValue(initial_id + 1),
@@ -2561,8 +2580,10 @@
 
   // Requesting isolated_origin_with_port should return the same origin but with
   // the default port for the scheme.
-  EXPECT_TRUE(p->GetMatchingIsolatedOrigin(
-      isolation_context, isolated_origin_with_port, &lookup_origin));
+  const bool kOriginRequestsIsolation = false;
+  EXPECT_TRUE(
+      p->GetMatchingIsolatedOrigin(isolation_context, isolated_origin_with_port,
+                                   kOriginRequestsIsolation, &lookup_origin));
   EXPECT_EQ(url::DefaultPortForScheme(lookup_origin.scheme().data(),
                                       lookup_origin.scheme().length()),
             lookup_origin.port());
@@ -2574,6 +2595,7 @@
   // also return the default port for the origin's scheme, not the report of the
   // requested origin.
   EXPECT_TRUE(p->GetMatchingIsolatedOrigin(isolation_context, wild_with_port,
+                                           kOriginRequestsIsolation,
                                            &lookup_origin));
   EXPECT_EQ(url::DefaultPortForScheme(lookup_origin.scheme().data(),
                                       lookup_origin.scheme().length()),
diff --git a/content/browser/eye_dropper_chooser_impl.cc b/content/browser/eye_dropper_chooser_impl.cc
index 1fca17b..82b9c09 100644
--- a/content/browser/eye_dropper_chooser_impl.cc
+++ b/content/browser/eye_dropper_chooser_impl.cc
@@ -5,6 +5,8 @@
 #include "content/browser/eye_dropper_chooser_impl.h"
 
 #include "base/callback.h"
+#include "content/browser/renderer_host/frame_tree_node.h"
+#include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/public/browser/eye_dropper.h"
 #include "content/public/browser/eye_dropper_listener.h"
 #include "content/public/browser/web_contents.h"
@@ -18,6 +20,19 @@
     RenderFrameHost* render_frame_host,
     mojo::PendingReceiver<blink::mojom::EyeDropperChooser> receiver) {
   DCHECK(render_frame_host);
+
+  // Renderer process should already check for user activation before sending
+  // this request. Double check in case of compromised renderer and consume
+  // the activation.
+  if (!static_cast<RenderFrameHostImpl*>(render_frame_host)
+           ->frame_tree_node()
+           ->UpdateUserActivationState(
+               blink::mojom::UserActivationUpdateType::
+                   kConsumeTransientActivation,
+               blink::mojom::UserActivationNotificationType::kNone)) {
+    return;
+  }
+
   new EyeDropperChooserImpl(render_frame_host, std::move(receiver));
 }
 
diff --git a/content/browser/hid/hid_service_unittest.cc b/content/browser/hid/hid_service_unittest.cc
index 13cc8876..3e7b94c7 100644
--- a/content/browser/hid/hid_service_unittest.cc
+++ b/content/browser/hid/hid_service_unittest.cc
@@ -370,17 +370,18 @@
   EXPECT_TRUE(contents()->IsConnectedToHidDevice());
   EXPECT_TRUE(connection);
 
+  base::RunLoop disconnect_loop;
+  connection.set_disconnect_handler(
+      base::BindLambdaForTesting([&] { disconnect_loop.Quit(); }));
+
   // Simulate user revoking permission.
   EXPECT_CALL(hid_delegate(), HasDevicePermission).WillOnce(Return(false));
   url::Origin origin = url::Origin::Create(GURL(kTestUrl));
   hid_delegate().OnPermissionRevoked(origin, origin);
 
-  // TODO(mattreynolds): Use a disconnect handler with a run loop instead of the
-  // potentially flaky `RunUntilIdle`. This depends on fixing
-  // `FakeHidConnection` to monitor the watcher just as `HidConnectionImpl`
-  // does.
-  base::RunLoop().RunUntilIdle();
+  disconnect_loop.Run();
   EXPECT_FALSE(contents()->IsConnectedToHidDevice());
+  EXPECT_FALSE(connection.is_connected());
 }
 
 }  // namespace content
diff --git a/content/browser/indexed_db/indexed_db_database.cc b/content/browser/indexed_db/indexed_db_database.cc
index 65d7549..29c76bc 100644
--- a/content/browser/indexed_db/indexed_db_database.cc
+++ b/content/browser/indexed_db/indexed_db_database.cc
@@ -1140,8 +1140,8 @@
   DCHECK_NE(transaction->mode(), blink::mojom::IDBTransactionMode::ReadOnly);
   bool key_was_generated = false;
   Status s = Status::OK();
-  transaction->set_in_flight_memory(transaction->in_flight_memory() -
-                                    params->value.SizeEstimate());
+  transaction->in_flight_memory() -= params->value.SizeEstimate();
+  DCHECK(transaction->in_flight_memory().IsValid());
 
   if (!IsObjectStoreIdInMetadata(params->object_store_id)) {
     IndexedDBDatabaseError error = CreateError(
@@ -1285,11 +1285,8 @@
   DCHECK_NE(transaction->mode(), blink::mojom::IDBTransactionMode::ReadOnly);
   bool key_was_generated = false;
   Status s = Status::OK();
-  // TODO(nums): Add checks to prevent overflow and underflow
-  // https://crbug.com/1116075
-  transaction->set_in_flight_memory(
-      transaction->in_flight_memory() -
-      base::checked_cast<int64_t>(size_estimate.ValueOrDie()));
+  transaction->in_flight_memory() -= size_estimate.ValueOrDefault(0);
+  DCHECK(transaction->in_flight_memory().IsValid());
 
   if (!IsObjectStoreIdInMetadata(object_store_id)) {
     IndexedDBDatabaseError error = CreateError(
diff --git a/content/browser/indexed_db/indexed_db_database_unittest.cc b/content/browser/indexed_db/indexed_db_database_unittest.cc
index 27513037..1542d08 100644
--- a/content/browser/indexed_db/indexed_db_database_unittest.cc
+++ b/content/browser/indexed_db/indexed_db_database_unittest.cc
@@ -680,6 +680,10 @@
   std::vector<IndexedDBIndexKeys> index_keys;
   base::MockCallback<blink::mojom::IDBTransaction::PutCallback> callback;
 
+  // Set in-flight memory to a reasonably large number to prevent underflow in
+  // |PutOperation|
+  transaction_->in_flight_memory() += 1000;
+
   auto put_params = std::make_unique<IndexedDBDatabase::PutOperationParams>();
   put_params->object_store_id = store_id;
   put_params->value = value;
diff --git a/content/browser/indexed_db/indexed_db_transaction.h b/content/browser/indexed_db/indexed_db_transaction.h
index 84c47f1dd..116b374 100644
--- a/content/browser/indexed_db/indexed_db_transaction.h
+++ b/content/browser/indexed_db/indexed_db_transaction.h
@@ -151,10 +151,7 @@
 
   // in_flight_memory() is used to keep track of all memory scheduled to be
   // written using ScheduleTask. This is reported to memory dumps.
-  int64_t in_flight_memory() const { return in_flight_memory_; }
-  void set_in_flight_memory(int64_t in_flight_memory) {
-    in_flight_memory_ = in_flight_memory;
-  }
+  base::CheckedNumeric<size_t>& in_flight_memory() { return in_flight_memory_; }
 
  protected:
   // Test classes may derive, but most creation should be done via
@@ -247,7 +244,7 @@
   // Metrics for quota.
   int64_t size_ = 0;
 
-  int64_t in_flight_memory_ = 0;
+  base::CheckedNumeric<size_t> in_flight_memory_ = 0;
 
   class TaskQueue {
    public:
diff --git a/content/browser/indexed_db/transaction_impl.cc b/content/browser/indexed_db/transaction_impl.cc
index 4f78820..7254b33c 100644
--- a/content/browser/indexed_db/transaction_impl.cc
+++ b/content/browser/indexed_db/transaction_impl.cc
@@ -148,8 +148,7 @@
   params->callback = std::move(aborting_callback);
   params->index_keys = index_keys;
   // This is decremented in IndexedDBDatabase::PutOperation.
-  transaction_->set_in_flight_memory(transaction_->in_flight_memory() +
-                                     output_value.SizeEstimate());
+  transaction_->in_flight_memory() += output_value.SizeEstimate();
   transaction_->ScheduleTask(BindWeakOperation(
       &IndexedDBDatabase::PutOperation, connection->database()->AsWeakPtr(),
       std::move(params)));
@@ -218,11 +217,8 @@
           blink::mojom::IDBTransactionPutAllResultPtr>(
           std::move(callback), transaction_->AsWeakPtr());
 
-  // TODO(nums): Add checks to prevent overflow and underflow
-  // https://crbug.com/1116075
-  transaction_->set_in_flight_memory(
-      transaction_->in_flight_memory() +
-      base::checked_cast<int64_t>(size_estimate.ValueOrDie()));
+  transaction_->in_flight_memory() += size_estimate.ValueOrDefault(0);
+  DCHECK(transaction_->in_flight_memory().IsValid());
   transaction_->ScheduleTask(BindWeakOperation(
       &IndexedDBDatabase::PutAllOperation, connection->database()->AsWeakPtr(),
       object_store_id, std::move(put_params), std::move(aborting_callback)));
diff --git a/content/browser/isolated_origin_browsertest.cc b/content/browser/isolated_origin_browsertest.cc
index 849fbf7..c71b2e2d 100644
--- a/content/browser/isolated_origin_browsertest.cc
+++ b/content/browser/isolated_origin_browsertest.cc
@@ -71,7 +71,8 @@
     auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
     IsolationContext isolation_context(
         shell()->web_contents()->GetBrowserContext());
-    return policy->IsIsolatedOrigin(isolation_context, origin);
+    return policy->IsIsolatedOrigin(isolation_context, origin,
+                                    false /* origin_requests_isolation */);
   }
 
   bool IsIsolatedOrigin(const GURL& url) {
@@ -191,7 +192,8 @@
 
     return ChildProcessSecurityPolicyImpl::GetInstance()
         ->ShouldOriginGetOptInIsolation(site_instance->GetIsolationContext(),
-                                        origin);
+                                        origin,
+                                        false /* origin_requests_isolation */);
   }
 
  protected:
@@ -350,10 +352,12 @@
   auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
   EXPECT_TRUE(policy->ShouldOriginGetOptInIsolation(
       root->current_frame_host()->GetSiteInstance()->GetIsolationContext(),
-      url::Origin::Create(isolated_base_origin_url)));
+      url::Origin::Create(isolated_base_origin_url),
+      false /* origin_requests_isolation */));
   EXPECT_FALSE(policy->ShouldOriginGetOptInIsolation(
       root->current_frame_host()->GetSiteInstance()->GetIsolationContext(),
-      url::Origin::Create(non_isolated_sub_origin)));
+      url::Origin::Create(non_isolated_sub_origin),
+      false /* origin_requests_isolation */));
   // Make sure the child (i.e. sub-origin) is not isolated.
   EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
             child_frame_node->current_frame_host()->GetSiteInstance());
@@ -364,7 +368,8 @@
   // between command-line isolation and opt-in isolation.
   EXPECT_TRUE(policy->IsIsolatedOrigin(
       root->current_frame_host()->GetSiteInstance()->GetIsolationContext(),
-      url::Origin::Create(non_isolated_sub_origin)));
+      url::Origin::Create(non_isolated_sub_origin),
+      false /* origin_requests_isolation */));
 
   // Make sure the opt-in isolated origin is origin-keyed, and the non-opt-in
   // origin is site-keyed.
@@ -751,7 +756,8 @@
   // Make sure the current browsing instance does *not* isolate the origin.
   EXPECT_FALSE(policy->ShouldOriginGetOptInIsolation(
       root->current_frame_host()->GetSiteInstance()->GetIsolationContext(),
-      url::Origin::Create(isolated_suborigin_url)));
+      url::Origin::Create(isolated_suborigin_url),
+      false /* origin_requests_isolation */));
 }
 
 // This test makes sure that a different tab in the same BrowsingInstance where
@@ -797,7 +803,8 @@
   auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
   EXPECT_FALSE(policy->ShouldOriginGetOptInIsolation(
       root->current_frame_host()->GetSiteInstance()->GetIsolationContext(),
-      url::Origin::Create(isolated_suborigin_url)));
+      url::Origin::Create(isolated_suborigin_url),
+      false /* origin_requests_isolation */));
 }
 
 // This test handles the case where the base origin is isolated, but a
@@ -831,17 +838,19 @@
   auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
   EXPECT_TRUE(policy->ShouldOriginGetOptInIsolation(
       root->current_frame_host()->GetSiteInstance()->GetIsolationContext(),
-      url::Origin::Create(test_url)));
+      url::Origin::Create(test_url), false /* origin_requests_isolation */));
   EXPECT_FALSE(policy->ShouldOriginGetOptInIsolation(
       child_frame_node1->current_frame_host()
           ->GetSiteInstance()
           ->GetIsolationContext(),
-      url::Origin::Create(non_isolated_sub_origin1)));
+      url::Origin::Create(non_isolated_sub_origin1),
+      false /* origin_requests_isolation */));
   EXPECT_FALSE(policy->ShouldOriginGetOptInIsolation(
       child_frame_node2->current_frame_host()
           ->GetSiteInstance()
           ->GetIsolationContext(),
-      url::Origin::Create(non_isolated_sub_origin2)));
+      url::Origin::Create(non_isolated_sub_origin2),
+      false /* origin_requests_isolation */));
 
   // Base origin and subdomains should have different SiteInstances.
   EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
@@ -936,17 +945,20 @@
   auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
   EXPECT_TRUE(policy->ShouldOriginGetOptInIsolation(
       root->current_frame_host()->GetSiteInstance()->GetIsolationContext(),
-      url::Origin::Create(isolated_base_origin_url)));
+      url::Origin::Create(isolated_base_origin_url),
+      false /* origin_requests_isolation */));
   EXPECT_FALSE(policy->ShouldOriginGetOptInIsolation(
       child_frame_node1->current_frame_host()
           ->GetSiteInstance()
           ->GetIsolationContext(),
-      url::Origin::Create(non_isolated_sub_origin_url_a)));
+      url::Origin::Create(non_isolated_sub_origin_url_a),
+      false /* origin_requests_isolation */));
   EXPECT_FALSE(policy->ShouldOriginGetOptInIsolation(
       child_frame_node2->current_frame_host()
           ->GetSiteInstance()
           ->GetIsolationContext(),
-      url::Origin::Create(non_isolated_sub_origin_url_b)));
+      url::Origin::Create(non_isolated_sub_origin_url_b),
+      false /* origin_requests_isolation */));
   // Base origin and subdomains should have different SiteInstances.
   EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
             child_frame_node1->current_frame_host()->GetSiteInstance());
@@ -1026,9 +1038,6 @@
 
   // Since the same origin exists in two tabs, but one is isolated and the other
   // isn't, we expect them to be in different BrowsingInstances.
-  using ScopedOriginIsolationOptInRequest =
-      ChildProcessSecurityPolicyImpl::ScopedOriginIsolationOptInRequest;
-
   EXPECT_NE(tab1_root->current_frame_host()->GetSiteInstance(),
             tab2_child->current_frame_host()->GetSiteInstance());
   EXPECT_NE(tab1_root->current_frame_host()
@@ -1039,26 +1048,21 @@
                 ->GetSiteInstance()
                 ->GetIsolationContext()
                 .browsing_instance_id());
-  auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
-  url::Origin isolated_origin = url::Origin::Create(isolated_origin_url);
-  {
-    // Verify that |isolated origin| is in the non-opt-in list for tab2's
-    // child's BrowsingInstance. We do this by requesting opt-in for the origin,
-    // then verifying that it is denied by DoesOriginRequestOptInIsolation.
-    std::unique_ptr<ScopedOriginIsolationOptInRequest> scoped_request =
-        ScopedOriginIsolationOptInRequest::GetScopedOriginIsolationOptInRequest(
-            isolated_origin);
 
-    EXPECT_FALSE(
-        policy->ShouldOriginGetOptInIsolation(tab2_child->current_frame_host()
-                                                  ->GetSiteInstance()
-                                                  ->GetIsolationContext(),
-                                              isolated_origin));
-  }
+  url::Origin isolated_origin = url::Origin::Create(isolated_origin_url);
+  auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
+  // Verify that |isolated origin| is in the non-opt-in list for tab2's
+  // child's BrowsingInstance. We do this by requesting opt-in for the origin,
+  // then verifying that it is denied by DoesOriginRequestOptInIsolation.
+  EXPECT_FALSE(policy->ShouldOriginGetOptInIsolation(
+      tab2_child->current_frame_host()
+          ->GetSiteInstance()
+          ->GetIsolationContext(),
+      isolated_origin, true /* origin_requests_isolation */));
   // Verify that |isolated_origin| in tab1 is indeed isolated.
   EXPECT_TRUE(policy->ShouldOriginGetOptInIsolation(
       tab1_root->current_frame_host()->GetSiteInstance()->GetIsolationContext(),
-      isolated_origin));
+      isolated_origin, false /* origin_requests_isolation */));
   // Verify that the tab2 child frame has no FrameNavigationEntry.
   // TODO(wjmaclean): when https://crbug.com/524208 is fixed, this next check
   // will fail, and it should be removed with the CL that fixes 524208.
@@ -1128,24 +1132,20 @@
   // Despite the non-isolated navigation only being at pending-commit when we
   // got the response for the isolated navigation, it should be properly
   // registered as non-isolated in its browsing instance.
-  using ScopedOriginIsolationOptInRequest =
-      ChildProcessSecurityPolicyImpl::ScopedOriginIsolationOptInRequest;
-  auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
-  url::Origin isolated_origin = url::Origin::Create(isolated_origin_url);
-  {
-    // Verify that |isolated origin| is in the non-opt-in list for tab1's
-    // BrowsingInstance. We do this by requesting opt-in for the origin, then
-    // verifying that it is denied by ShouldOriginGetOptInIsolation.
-    std::unique_ptr<ScopedOriginIsolationOptInRequest> scoped_request =
-        ScopedOriginIsolationOptInRequest::GetScopedOriginIsolationOptInRequest(
-            isolated_origin);
 
-    EXPECT_FALSE(policy->ShouldOriginGetOptInIsolation(
-        tab1_site_instance->GetIsolationContext(), isolated_origin));
-  }
+  url::Origin isolated_origin = url::Origin::Create(isolated_origin_url);
+  auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
+  // Verify that |isolated origin| is in the non-opt-in list for tab1's
+  // BrowsingInstance. We do this by requesting opt-in for the origin, then
+  // verifying that it is denied by ShouldOriginGetOptInIsolation.
+  EXPECT_FALSE(policy->ShouldOriginGetOptInIsolation(
+      tab1_site_instance->GetIsolationContext(), isolated_origin,
+      true /* origin_requests_isolation */));
+
   // Verify that |isolated_origin| in tab2 is indeed isolated.
   EXPECT_TRUE(policy->ShouldOriginGetOptInIsolation(
-      tab2_site_instance->GetIsolationContext(), isolated_origin));
+      tab2_site_instance->GetIsolationContext(), isolated_origin,
+      false /* origin_requests_isolation */));
 }
 
 // Helper class to navigate a second tab to a specified URL that requests opt-in
@@ -1235,24 +1235,19 @@
   // Despite the non-isolated navigation only being at pending-commit when we
   // got the response for the isolated navigation, it should be properly
   // registered as non-isolated in its browsing instance.
-  using ScopedOriginIsolationOptInRequest =
-      ChildProcessSecurityPolicyImpl::ScopedOriginIsolationOptInRequest;
   auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
   url::Origin isolated_origin = url::Origin::Create(isolated_origin_url);
-  {
-    // Verify that |isolated origin| is in the non-opt-in list for tab1's
-    // BrowsingInstance. We do this by requesting opt-in for the origin, then
-    // verifying that it is denied by DoesOriginRequestOptInIsolation.
-    std::unique_ptr<ScopedOriginIsolationOptInRequest> scoped_request =
-        ScopedOriginIsolationOptInRequest::GetScopedOriginIsolationOptInRequest(
-            isolated_origin);
+  // Verify that |isolated origin| is in the non-opt-in list for tab1's
+  // BrowsingInstance. We do this by requesting opt-in for the origin, then
+  // verifying that it is denied by DoesOriginRequestOptInIsolation.
+  EXPECT_FALSE(policy->ShouldOriginGetOptInIsolation(
+      tab1_site_instance->GetIsolationContext(), isolated_origin,
+      true /* origin_requests_isolation*/));
 
-    EXPECT_FALSE(policy->ShouldOriginGetOptInIsolation(
-        tab1_site_instance->GetIsolationContext(), isolated_origin));
-  }
   // Verify that |isolated_origin| in tab2 is indeed isolated.
   EXPECT_TRUE(policy->ShouldOriginGetOptInIsolation(
-      tab2_site_instance->GetIsolationContext(), isolated_origin));
+      tab2_site_instance->GetIsolationContext(), isolated_origin,
+      false /* origin_requests_isolation */));
 }
 
 class StrictOriginIsolationTest : public IsolatedOriginTestBase {
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index bf8573c..7253ef9c 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -1400,7 +1400,7 @@
     render_frame_host_ =
         frame_tree_node_->render_manager()->GetFrameHostForNavigation(this);
     if (!Navigator::CheckWebUIRendererDoesNotDisplayNormalURL(
-            render_frame_host_, common_params_->url,
+            render_frame_host_, GetUrlInfo(),
             /* is_renderer_initiated_check */ false)) {
       // TODO(nasko): Convert this to CHECK once it is confirmed that it does
       // not happen in reality.
@@ -1986,8 +1986,10 @@
   const url::Origin origin = url::Origin::Create(common_params_->url);
   const IsolationContext& isolation_context =
       render_frame_host_->GetSiteInstance()->GetIsolationContext();
-  const bool got_isolated =
-      policy->ShouldOriginGetOptInIsolation(isolation_context, origin);
+  const bool got_isolated = policy->ShouldOriginGetOptInIsolation(
+      isolation_context, origin,
+      check_result !=
+          OptInIsolationCheckResult::NONE /* origin_requests_isolation */);
 
   switch (check_result) {
     case OptInIsolationCheckResult::NONE:
@@ -2072,6 +2074,11 @@
                            origin.Serialize().c_str()));
 }
 
+UrlInfo NavigationRequest::GetUrlInfo() {
+  return UrlInfo(GetURL(), IsOptInIsolationRequested(GetURL()) !=
+                               OptInIsolationCheckResult::NONE);
+}
+
 void NavigationRequest::OnResponseStarted(
     network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints,
     network::mojom::URLResponseHeadPtr response_head,
@@ -2102,31 +2109,6 @@
   ssl_info_ = response_head_->ssl_info;
   auth_challenge_info_ = response_head_->auth_challenge_info;
 
-  // If origin isolation opt-in is being requested, set temporary state on
-  // ChildProcessSecurityPolicy that allows it to know the origin is requesting
-  // isolation without having to plumb it through all the calls that might get
-  // to ShouldOriginGetOptInIsolation. This is set in OnResponseStarted when
-  // headers are available to detect isolation requests, and early in the method
-  // to ensure all relevant calls will have the state available.
-  using ScopedOriginIsolationOptInRequest =
-      ChildProcessSecurityPolicyImpl::ScopedOriginIsolationOptInRequest;
-  std::unique_ptr<ScopedOriginIsolationOptInRequest>
-      scoped_origin_isolation_opt_in_request;
-  OptInIsolationCheckResult opt_in_isolation =
-      IsOptInIsolationRequested(GetURL());
-  if (opt_in_isolation != OptInIsolationCheckResult::NONE) {
-    // This origin conversion won't be correct for about:blank, but origin
-    // isolation shouldn't need to care about that case because a previous
-    // instance of the origin would already have determined its isolation status
-    // in that BrowsingInstance.
-    // TODO(https://crbug.com/888079): Use the computed origin here just to be
-    // safe.
-    url::Origin origin_to_isolate(url::Origin::Create(GetURL()));
-    scoped_origin_isolation_opt_in_request =
-        ScopedOriginIsolationOptInRequest::GetScopedOriginIsolationOptInRequest(
-            origin_to_isolate);
-  }
-
   // The navigation may have encountered an origin policy or Origin-Isolation
   // header that requests isolation for the url's origin. Before we pick the
   // renderer, make sure we update the origin-isolation opt-ins appropriately.
@@ -2307,7 +2289,7 @@
         frame_tree_node_->render_manager()->GetFrameHostForNavigation(this);
 
     if (!Navigator::CheckWebUIRendererDoesNotDisplayNormalURL(
-            render_frame_host_, common_params_->url,
+            render_frame_host_, GetUrlInfo(),
             /* is_renderer_initiated_check */ false)) {
       // TODO(nasko): Convert this to CHECK once it is confirmed that it does
       // not happen in reality.
@@ -2319,7 +2301,7 @@
   DCHECK(render_frame_host_ || !response_should_be_rendered_);
 
   if (render_frame_host_) {
-    DetermineOriginIsolationEndResult(opt_in_isolation);
+    DetermineOriginIsolationEndResult(IsOptInIsolationRequested(GetURL()));
 
     // TODO(pmeuleman, ahemery): Only set COEP values on RenderFrameHost when
     // the navigation commits. In the meantime, keep them in NavigationRequest.
@@ -2383,13 +2365,13 @@
     SiteInstanceImpl* instance = render_frame_host_->GetSiteInstance();
     const IsolationContext& isolation_context = instance->GetIsolationContext();
     auto site_info = SiteInstanceImpl::ComputeSiteInfo(
-        isolation_context, common_params_->url,
+        isolation_context, GetUrlInfo(),
         instance->IsCoopCoepCrossOriginIsolated(),
         instance->CoopCoepCrossOriginIsolatedOrigin());
     if (!instance->HasSite() &&
         SiteInstanceImpl::DoesSiteInfoRequireDedicatedProcess(isolation_context,
                                                               site_info)) {
-      instance->ConvertToDefaultOrSetSite(common_params_->url);
+      instance->ConvertToDefaultOrSetSite(GetUrlInfo());
     }
 
     // If this navigation request didn't opt-in to origin isolation, we need
@@ -2571,7 +2553,7 @@
   if (SiteIsolationPolicy::IsErrorPageIsolationEnabled(
           frame_tree_node_->IsMainFrame())) {
     if (!Navigator::CheckWebUIRendererDoesNotDisplayNormalURL(
-            render_frame_host_, common_params_->url,
+            render_frame_host_, GetUrlInfo(),
             /* is_renderer_initiated_check */ false)) {
       // TODO(nasko): Convert this to CHECK once it is confirmed that it does
       // not happen in reality.
@@ -3091,7 +3073,7 @@
       frame_tree_node_->render_manager()->current_frame_host();
   if (!GetRenderFrameHost()
            ->ShouldDispatchPagehideAndVisibilitychangeDuringCommit(
-               old_frame_host, GetURL())) {
+               old_frame_host, GetUrlInfo())) {
     return;
   }
   DCHECK(!IsSameDocument());
@@ -4150,12 +4132,11 @@
 
 SiteInfo NavigationRequest::GetSiteInfoForCommonParamsURL(
     bool is_coop_coep_cross_origin_isolated,
-    const base::Optional<url::Origin>& coop_coep_cross_origin_isolated_origin)
-    const {
+    const base::Optional<url::Origin>& coop_coep_cross_origin_isolated_origin) {
   // TODO(alexmos): Using |starting_site_instance_|'s IsolationContext may not
   // be correct for cross-BrowsingInstance redirects.
   return SiteInstanceImpl::ComputeSiteInfo(
-      starting_site_instance_->GetIsolationContext(), common_params_->url,
+      starting_site_instance_->GetIsolationContext(), GetUrlInfo(),
       is_coop_coep_cross_origin_isolated,
       coop_coep_cross_origin_isolated_origin);
 }
diff --git a/content/browser/renderer_host/navigation_request.h b/content/browser/renderer_host/navigation_request.h
index 6837765..8ed463f 100644
--- a/content/browser/renderer_host/navigation_request.h
+++ b/content/browser/renderer_host/navigation_request.h
@@ -721,6 +721,10 @@
     return frame_entry_document_sequence_number_;
   }
 
+  // Returns the current url from GetURL() packaged with other state required to
+  // properly determine SiteInstances and process allocation.
+  UrlInfo GetUrlInfo();
+
  private:
   friend class NavigationRequestTest;
 
@@ -979,8 +983,8 @@
   // Note: |site_info_| should only be updated with the result of this function.
   SiteInfo GetSiteInfoForCommonParamsURL(
       bool is_coop_coep_cross_origin_isolated,
-      const base::Optional<url::Origin>& coop_coep_cross_origin_isolated_origin)
-      const;
+      const base::Optional<url::Origin>&
+          coop_coep_cross_origin_isolated_origin);
 
   // Updates the state of the navigation handle after encountering a server
   // redirect.
diff --git a/content/browser/renderer_host/navigator.cc b/content/browser/renderer_host/navigator.cc
index 88c6d58..b24d81b 100644
--- a/content/browser/renderer_host/navigator.cc
+++ b/content/browser/renderer_host/navigator.cc
@@ -111,8 +111,9 @@
 // static
 bool Navigator::CheckWebUIRendererDoesNotDisplayNormalURL(
     RenderFrameHostImpl* render_frame_host,
-    const GURL& url,
+    const UrlInfo& url_info,
     bool is_renderer_initiated_check) {
+  const GURL& url = url_info.url;
   // In single process mode, everything runs in the same process, so the checks
   // below are irrelevant.
   if (RenderProcessHost::run_renderer_in_process())
@@ -137,7 +138,7 @@
   // this method should take this into account.
   SiteInstanceImpl* site_instance = render_frame_host->GetSiteInstance();
   SiteInfo site_info = SiteInstanceImpl::ComputeSiteInfo(
-      site_instance->GetIsolationContext(), url,
+      site_instance->GetIsolationContext(), url_info,
       site_instance->IsCoopCoepCrossOriginIsolated(),
       site_instance->CoopCoepCrossOriginIsolatedOrigin());
   bool should_lock_process =
@@ -248,10 +249,13 @@
                                     bad_message::NI_IN_PAGE_NAVIGATION);
     is_same_document_navigation = false;
   }
+  // At this point we have already chosen a SiteInstance for this navigation, so
+  // set |origin_requests_isolation| = false in the conversion to UrlInfo below.
+  const UrlInfo url_info(params.url, false /* origin_requests_isolation */);
   bool is_cross_document_same_site_navigation =
       !is_same_document_navigation &&
       old_frame_host->IsNavigationSameSite(
-          params.url,
+          url_info,
           render_frame_host->GetSiteInstance()->IsCoopCoepCrossOriginIsolated(),
           render_frame_host->GetSiteInstance()
               ->CoopCoepCrossOriginIsolatedOrigin());
@@ -346,8 +350,8 @@
   // page.
   SiteInstanceImpl* site_instance = render_frame_host->GetSiteInstance();
   if (!site_instance->HasSite() &&
-      SiteInstanceImpl::ShouldAssignSiteForURL(params.url)) {
-    site_instance->ConvertToDefaultOrSetSite(params.url);
+      SiteInstanceImpl::ShouldAssignSiteForURL(url_info.url)) {
+    site_instance->ConvertToDefaultOrSetSite(url_info);
   }
 
   // Need to update MIME type here because it's referred to in
diff --git a/content/browser/renderer_host/navigator.h b/content/browser/renderer_host/navigator.h
index a433de0a..4db476c8 100644
--- a/content/browser/renderer_host/navigator.h
+++ b/content/browser/renderer_host/navigator.h
@@ -45,6 +45,7 @@
 class RenderFrameHostImpl;
 class WebBundleHandleTracker;
 struct LoadCommittedDetails;
+struct UrlInfo;
 
 // Navigator is responsible for performing navigations in nodes of the
 // FrameTree. Its lifetime is bound to the FrameTree.
@@ -66,7 +67,7 @@
   // of this method are migrated to use CHECK instead of DumpWithoutCrashing.
   static WARN_UNUSED_RESULT bool CheckWebUIRendererDoesNotDisplayNormalURL(
       RenderFrameHostImpl* render_frame_host,
-      const GURL& url,
+      const UrlInfo& url_info,
       bool is_renderer_initiated_check);
 
   static bool ShouldIgnoreIncomingRendererRequest(
diff --git a/content/browser/renderer_host/navigator_unittest.cc b/content/browser/renderer_host/navigator_unittest.cc
index f3ec185..0c0f5627d 100644
--- a/content/browser/renderer_host/navigator_unittest.cc
+++ b/content/browser/renderer_host/navigator_unittest.cc
@@ -1074,7 +1074,8 @@
   GURL kUrlSameSiteAs1("http://www.a.com/foo");
   {
     SiteInstanceDescriptor descriptor(
-        kUrlSameSiteAs1, SiteInstanceRelation::RELATED,
+        UrlInfo::CreateForTesting(kUrlSameSiteAs1),
+        SiteInstanceRelation::RELATED,
         false /* is_coop_coep_cross_origin_isolated */);
     scoped_refptr<SiteInstance> converted_instance =
         ConvertToSiteInstance(rfhm, descriptor, nullptr);
@@ -1087,7 +1088,8 @@
   scoped_refptr<SiteInstance> related_instance;
   {
     SiteInstanceDescriptor descriptor(
-        kUrlSameSiteAs2, SiteInstanceRelation::RELATED,
+        UrlInfo::CreateForTesting(kUrlSameSiteAs2),
+        SiteInstanceRelation::RELATED,
         false /* is_coop_coep_cross_origin_isolated */);
     related_instance = ConvertToSiteInstance(rfhm, descriptor, nullptr);
     // If kUrlSameSiteAs2 requires a dedicated process on this platform, this
@@ -1112,7 +1114,8 @@
   // current one, several times, with and without candidate sites.
   {
     SiteInstanceDescriptor descriptor(
-        kUrlSameSiteAs1, SiteInstanceRelation::UNRELATED,
+        UrlInfo::CreateForTesting(kUrlSameSiteAs1),
+        SiteInstanceRelation::UNRELATED,
         false /* is_coop_coep_cross_origin_isolated */);
     scoped_refptr<SiteInstance> converted_instance_1 =
         ConvertToSiteInstance(rfhm, descriptor, nullptr);
@@ -1151,7 +1154,8 @@
   // related_instance and using it as a candidate.
   {
     SiteInstanceDescriptor descriptor(
-        kUrlSameSiteAs2, SiteInstanceRelation::UNRELATED,
+        UrlInfo::CreateForTesting(kUrlSameSiteAs2),
+        SiteInstanceRelation::UNRELATED,
         false /* is_coop_coep_cross_origin_isolated */);
     scoped_refptr<SiteInstance> converted_instance_1 =
         ConvertToSiteInstance(rfhm, descriptor, related_instance.get());
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 65442474..0c57f10 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -5505,9 +5505,10 @@
   }
 
   // Verify that if this RenderFrameHost is for a WebUI it is not committing a
-  // URL which is not allowed in a WebUI process.
+  // URL which is not allowed in a WebUI process. As we are at the commit stage,
+  // set |origin_requests_isolation| = false.
   if (!Navigator::CheckWebUIRendererDoesNotDisplayNormalURL(
-          this, url,
+          this, UrlInfo(url, false /* origin_requests_isolation */),
           /* is_renderer_initiated_check */ true)) {
     return CanCommitStatus::CANNOT_COMMIT_URL;
   }
@@ -5545,7 +5546,8 @@
   auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
   const CanCommitStatus can_commit_status = policy->CanCommitOriginAndUrl(
       GetProcess()->GetID(), GetSiteInstance()->GetIsolationContext(), origin,
-      url, GetSiteInstance()->IsCoopCoepCrossOriginIsolated(),
+      UrlInfo(url, false /* origin_requests_isolation */),
+      GetSiteInstance()->IsCoopCoepCrossOriginIsolated(),
       GetSiteInstance()->CoopCoepCrossOriginIsolatedOrigin());
   if (can_commit_status != CanCommitStatus::CAN_COMMIT_ORIGIN_AND_URL) {
     LogCanCommitOriginAndUrlFailureReason("cpspi_disallowed_commit");
@@ -5912,7 +5914,7 @@
 
 bool RenderFrameHostImpl::ShouldDispatchPagehideAndVisibilitychangeDuringCommit(
     RenderFrameHostImpl* old_frame_host,
-    const GURL& dest_url) {
+    const UrlInfo& dest_url_info) {
   // Only return true if this is a same-site navigation and we did a proactive
   // BrowsingInstance swap but we're reusing the old page's renderer process.
   DCHECK(old_frame_host);
@@ -5924,7 +5926,7 @@
     return false;
   }
   if (!old_frame_host->IsNavigationSameSite(
-          dest_url, GetSiteInstance()->IsCoopCoepCrossOriginIsolated(),
+          dest_url_info, GetSiteInstance()->IsCoopCoepCrossOriginIsolated(),
           GetSiteInstance()->CoopCoepCrossOriginIsolatedOrigin())) {
     return false;
   }
@@ -8046,11 +8048,14 @@
 }
 
 void RenderFrameHostImpl::SetLastCommittedSiteInfo(const GURL& url) {
+  // Since |url| has already committed, |origin_requests_isolation| below should
+  // be set to false.
   SiteInfo site_info =
       url.is_empty()
           ? SiteInfo()
           : SiteInstanceImpl::ComputeSiteInfo(
-                GetSiteInstance()->GetIsolationContext(), url,
+                GetSiteInstance()->GetIsolationContext(),
+                UrlInfo(url, false /* origin_requests_isolation */),
                 GetSiteInstance()->IsCoopCoepCrossOriginIsolated(),
                 GetSiteInstance()->CoopCoepCrossOriginIsolatedOrigin());
 
@@ -8217,7 +8222,7 @@
 }
 
 bool RenderFrameHostImpl::IsNavigationSameSite(
-    const GURL& dest_url,
+    const UrlInfo& dest_url_info,
     bool is_coop_coep_cross_origin_isolated,
     base::Optional<url::Origin> coop_coep_cross_origin_isolated_origin) {
   if (GetSiteInstance()->IsCoopCoepCrossOriginIsolated() !=
@@ -8228,7 +8233,7 @@
   }
   return GetSiteInstance()->IsNavigationSameSite(
       last_successful_url(), GetLastCommittedOrigin(),
-      frame_tree_node()->IsMainFrame(), dest_url);
+      frame_tree_node()->IsMainFrame(), dest_url_info);
 }
 
 bool RenderFrameHostImpl::ValidateDidCommitParams(
diff --git a/content/browser/renderer_host/render_frame_host_impl.h b/content/browser/renderer_host/render_frame_host_impl.h
index 873a702c..f1ac18ef 100644
--- a/content/browser/renderer_host/render_frame_host_impl.h
+++ b/content/browser/renderer_host/render_frame_host_impl.h
@@ -572,9 +572,9 @@
   // Return the http status code of the last committed navigation.
   int last_http_status_code() { return last_http_status_code_; }
 
-  // Returns true if |dest_url| should be considered the same site as the
+  // Returns true if |dest_url_info| should be considered the same site as the
   // current contents of this frame. This is the primary entry point for
-  // determining if a navigation to |dest_url| should stay in this
+  // determining if a navigation to |dest_url_info| should stay in this
   // RenderFrameHost's SiteInstance.
   //
   // |is_coop_coep_cross_origin_isolated| should be true if the response for
@@ -584,7 +584,7 @@
   // |coop_coep_cross_origin_isolated_origin| indicates the top level origin
   // of the page.
   bool IsNavigationSameSite(
-      const GURL& dest_url,
+      const UrlInfo& dest_url_info,
       bool is_coop_coep_cross_origin_isolated,
       base::Optional<url::Origin> coop_coep_cross_origin_isolated_origin);
 
@@ -1777,7 +1777,7 @@
   // TODO(crbug.com/1110744): Support unload-in-commit.
   bool ShouldDispatchPagehideAndVisibilitychangeDuringCommit(
       RenderFrameHostImpl* old_frame_host,
-      const GURL& dest_url);
+      const UrlInfo& dest_url_info);
 
   mojo::PendingRemote<network::mojom::CookieAccessObserver>
   CreateCookieAccessObserver();
diff --git a/content/browser/renderer_host/render_frame_host_manager.cc b/content/browser/renderer_host/render_frame_host_manager.cc
index 7e89a9d32c..0e0e806b5 100644
--- a/content/browser/renderer_host/render_frame_host_manager.cc
+++ b/content/browser/renderer_host/render_frame_host_manager.cc
@@ -82,19 +82,20 @@
 }
 
 // Helper function to determine whether a navigation from |current_rfh| to
-// |destination_effective_url| should swap BrowsingInstances to ensure that
-// |destination_effective_url| ends up in a dedicated process.  This is the case
-// when |destination_effective_url| has an origin that was just isolated
+// |destination_effective_url_info| should swap BrowsingInstances to ensure that
+// |destination_effective_url_info| ends up in a dedicated process.  This is the
+// case when |destination_effective_url| has an origin that was just isolated
 // dynamically, where leaving the navigation in the current BrowsingInstance
-// would leave |destination_effective_url| without a dedicated process, since
-// dynamic origin isolation applies only to future BrowsingInstances.  In the
-// common case where |current_rfh| is a main frame, and there are no scripting
-// references to it from other windows, it is safe to swap BrowsingInstances to
-// ensure the new isolated origin takes effect.  Note that this applies even to
-// same-site navigations, as well as to renderer-initiated navigations.
+// would leave |destination_effective_url_info| without a dedicated process,
+// since dynamic origin isolation applies only to future BrowsingInstances.  In
+// the common case where |current_rfh| is a main frame, and there are no
+// scripting references to it from other windows, it is safe to swap
+// BrowsingInstances to ensure the new isolated origin takes effect.  Note that
+// this applies even to same-site navigations, as well as to renderer-initiated
+// navigations.
 bool ShouldSwapBrowsingInstancesForDynamicIsolation(
     RenderFrameHostImpl* current_rfh,
-    const GURL& destination_effective_url,
+    const UrlInfo& destination_effective_url_info,
     bool is_coop_coep_cross_origin_isolated,
     const base::Optional<url::Origin>& coop_coep_cross_origin_isolated_origin) {
   // Only main frames are eligible to swap BrowsingInstances.
@@ -106,27 +107,27 @@
   if (current_instance->GetRelatedActiveContentsCount() > 1u)
     return false;
 
-  // Check whether |destination_effective_url| would require a dedicated process
-  // if we left it in the current BrowsingInstance.  If so, there's no need to
-  // swap BrowsingInstances.
+  // Check whether |destination_effective_url_info| would require a dedicated
+  // process if we left it in the current BrowsingInstance.  If so, there's no
+  // need to swap BrowsingInstances.
   auto& current_isolation_context = current_instance->GetIsolationContext();
   if (SiteInstanceImpl::DoesSiteInfoRequireDedicatedProcess(
           current_isolation_context,
           SiteInstanceImpl::ComputeSiteInfo(
-              current_isolation_context, destination_effective_url,
+              current_isolation_context, destination_effective_url_info,
               is_coop_coep_cross_origin_isolated,
               coop_coep_cross_origin_isolated_origin))) {
     return false;
   }
 
-  // Finally, check whether |destination_effective_url| would require a
+  // Finally, check whether |destination_effective_url_info| would require a
   // dedicated process if we were to swap to a fresh BrowsingInstance.  To check
   // this, use a new IsolationContext, rather than
   // current_instance->GetIsolationContext().
   IsolationContext future_isolation_context(
       current_instance->GetBrowserContext());
   auto future_site_info = SiteInstanceImpl::ComputeSiteInfo(
-      future_isolation_context, destination_effective_url,
+      future_isolation_context, destination_effective_url_info,
       is_coop_coep_cross_origin_isolated,
       coop_coep_cross_origin_isolated_origin);
   return SiteInstanceImpl::DoesSiteInfoRequireDedicatedProcess(
@@ -1081,11 +1082,11 @@
 }
 
 RenderFrameHostManager::SiteInstanceDescriptor::SiteInstanceDescriptor(
-    GURL dest_url,
+    UrlInfo dest_url_info,
     SiteInstanceRelation relation_to_current,
     bool is_coop_coep_cross_origin_isolated)
     : existing_site_instance(nullptr),
-      dest_url(dest_url),
+      dest_url_info(dest_url_info),
       relation(relation_to_current),
       is_coop_coep_cross_origin_isolated(is_coop_coep_cross_origin_isolated) {}
 
@@ -1187,7 +1188,7 @@
     SiteInstanceImpl* source_instance,
     SiteInstanceImpl* current_instance,
     SiteInstance* destination_instance,
-    const GURL& destination_url,
+    const UrlInfo& destination_url_info,
     bool is_coop_coep_cross_origin_isolated,
     const base::Optional<url::Origin>& coop_coep_cross_origin_isolated_origin,
     bool destination_is_view_source_mode,
@@ -1199,6 +1200,7 @@
     bool was_server_redirect,
     bool should_replace_current_entry,
     bool is_speculative) {
+  const GURL& destination_url = destination_url_info.url;
   // A subframe must stay in the same BrowsingInstance as its parent.
   if (!frame_tree_node_->IsMainFrame())
     return ShouldSwapBrowsingInstance::kNo_NotMainFrame;
@@ -1321,7 +1323,9 @@
   // after user has typed in a password) can utilize a dedicated process when
   // possible (e.g., when there are no existing script references).
   if (ShouldSwapBrowsingInstancesForDynamicIsolation(
-          render_frame_host_.get(), destination_effective_url,
+          render_frame_host_.get(),
+          UrlInfo(destination_effective_url,
+                  destination_url_info.origin_requests_isolation),
           is_coop_coep_cross_origin_isolated,
           coop_coep_cross_origin_isolated_origin)) {
     return ShouldSwapBrowsingInstance::kYes_ForceSwap;
@@ -1338,7 +1342,7 @@
                         frame_tree_node_->IsMainFrame());
   if (current_instance->HasSite() &&
       !render_frame_host_->IsNavigationSameSite(
-          destination_url, is_coop_coep_cross_origin_isolated,
+          destination_url_info, is_coop_coep_cross_origin_isolated,
           coop_coep_cross_origin_isolated_origin) &&
       !CanUseSourceSiteInstance(
           destination_url, source_instance, was_server_redirect, is_failure,
@@ -1353,14 +1357,14 @@
   // Experimental mode to swap BrowsingInstances on most navigations when there
   // are no other windows in the BrowsingInstance.
   return ShouldProactivelySwapBrowsingInstance(
-      destination_url, is_coop_coep_cross_origin_isolated,
+      destination_url_info, is_coop_coep_cross_origin_isolated,
       coop_coep_cross_origin_isolated_origin, is_reload,
       should_replace_current_entry);
 }
 
 ShouldSwapBrowsingInstance
 RenderFrameHostManager::ShouldProactivelySwapBrowsingInstance(
-    const GURL& destination_url,
+    const UrlInfo& destination_url_info,
     bool is_coop_coep_cross_origin_isolated,
     const base::Optional<url::Origin>& coop_coep_cross_origin_isolated_origin,
     bool is_reload,
@@ -1403,7 +1407,7 @@
     return ShouldSwapBrowsingInstance::kNo_SourceURLSchemeIsNotHTTPOrHTTPS;
 
   const GURL& destination_effective_url = SiteInstanceImpl::GetEffectiveURL(
-      current_instance->GetBrowserContext(), destination_url);
+      current_instance->GetBrowserContext(), destination_url_info.url);
   if (!destination_effective_url.SchemeIsHTTPOrHTTPS())
     return ShouldSwapBrowsingInstance::kNo_DestinationURLSchemeIsNotHTTPOrHTTPS;
 
@@ -1426,7 +1430,7 @@
   // SAME_PAGE and will reuse the history entry.
   // TODO(crbug.com/536102): When the SAME_PAGE navigation type gets removed,
   // we should remove this part as well.
-  bool is_same_page = current_url.EqualsIgnoringRef(destination_url);
+  bool is_same_page = current_url.EqualsIgnoringRef(destination_url_info.url);
   if (is_same_page)
     return ShouldSwapBrowsingInstance::kNo_SamePageNavigation;
   // 3) Reloads. Note that most reloads will not actually reach this part, as
@@ -1438,7 +1442,7 @@
     return ShouldSwapBrowsingInstance::kNo_Reload;
 
   bool is_same_site = render_frame_host_->IsNavigationSameSite(
-      destination_url, is_coop_coep_cross_origin_isolated,
+      destination_url_info, is_coop_coep_cross_origin_isolated,
       coop_coep_cross_origin_isolated_origin);
   if (is_same_site) {
     // If it's a same-site navigation, we should only swap if same-site
@@ -1487,7 +1491,7 @@
 
 scoped_refptr<SiteInstance>
 RenderFrameHostManager::GetSiteInstanceForNavigation(
-    const GURL& dest_url,
+    const UrlInfo& dest_url_info,
     bool is_coop_coep_cross_origin_isolated,
     const base::Optional<url::Origin>& coop_coep_cross_origin_isolated_origin,
     SiteInstanceImpl* source_instance,
@@ -1504,6 +1508,7 @@
     bool should_replace_current_entry,
     bool is_speculative,
     bool* did_same_site_proactive_browsing_instance_swap) {
+  const GURL& dest_url = dest_url_info.url;
   // Make sure |did_same_site_proactive_browsing_instance_swap| is initialized
   // to false at first, as the function might return early before setting this
   // to the actual value (and if we return early, the actual value will always
@@ -1561,7 +1566,7 @@
   ShouldSwapBrowsingInstance should_swap_result =
       ShouldSwapBrowsingInstancesForNavigation(
           current_effective_url, current_is_view_source_mode, source_instance,
-          current_instance_impl, dest_instance, dest_url,
+          current_instance_impl, dest_instance, dest_url_info,
           is_coop_coep_cross_origin_isolated,
           coop_coep_cross_origin_isolated_origin, dest_is_view_source_mode,
           transition, is_failure, is_reload, is_same_document,
@@ -1580,7 +1585,7 @@
         should_swap_result);
   }
   SiteInstanceDescriptor new_instance_descriptor = DetermineSiteInstanceForURL(
-      dest_url, is_coop_coep_cross_origin_isolated,
+      dest_url_info, is_coop_coep_cross_origin_isolated,
       coop_coep_cross_origin_isolated_origin, source_instance, current_instance,
       dest_instance, transition, is_failure, dest_is_restore,
       dest_is_view_source_mode, should_swap, was_server_redirect,
@@ -1678,7 +1683,7 @@
   if (is_same_site_proactive_swap_enabled && is_history_navigation &&
       swapped_browsing_instance &&
       render_frame_host_->IsNavigationSameSite(
-          dest_url, is_coop_coep_cross_origin_isolated,
+          dest_url_info, is_coop_coep_cross_origin_isolated,
           coop_coep_cross_origin_isolated_origin)) {
     reuse_current_process_if_possible = true;
   }
@@ -1743,7 +1748,7 @@
 
 RenderFrameHostManager::SiteInstanceDescriptor
 RenderFrameHostManager::DetermineSiteInstanceForURL(
-    const GURL& dest_url,
+    const UrlInfo& dest_url_info,
     bool is_coop_coep_cross_origin_isolated,
     const base::Optional<url::Origin>& coop_coep_cross_origin_isolated_origin,
     SiteInstance* source_instance,
@@ -1775,7 +1780,7 @@
     if (IsSiteInstanceCompatibleWithErrorIsolation(
             dest_instance, frame_tree_node_->IsMainFrame(), is_failure)) {
       if (IsSiteInstanceCompatibleWithCoopCoepCrossOriginIsolation(
-              dest_instance, frame_tree_node_->IsMainFrame(), dest_url,
+              dest_instance, frame_tree_node_->IsMainFrame(), dest_url_info.url,
               is_coop_coep_cross_origin_isolated, is_speculative)) {
         // TODO(nasko,creis): The check whether data: or about: URLs are allowed
         // to commit in the current process should be in IsSuitableForURL.
@@ -1784,8 +1789,8 @@
         // approach and explicitly check before calling IsSuitableForURL.
         SiteInstanceImpl* dest_instance_impl =
             static_cast<SiteInstanceImpl*>(dest_instance);
-        if (IsDataOrAbout(dest_url) ||
-            dest_instance_impl->IsSuitableForURL(dest_url)) {
+        if (IsDataOrAbout(dest_url_info.url) ||
+            dest_instance_impl->IsSuitableForUrlInfo(dest_url_info)) {
           // If we are forcing a swap, this should be in a different
           // BrowsingInstance.
           if (force_browsing_instance_swap) {
@@ -1806,18 +1811,21 @@
     // in a new BrowsingInstance, since the scripting relationships would
     // have been broken anyway if there were no error. Otherwise, we keep it
     // in the same BrowsingInstance to preserve scripting relationships after
-    // reloads.
-    return SiteInstanceDescriptor(GURL(kUnreachableWebDataURL),
-                                  force_browsing_instance_swap
-                                      ? SiteInstanceRelation::UNRELATED
-                                      : SiteInstanceRelation::RELATED,
-                                  is_coop_coep_cross_origin_isolated);
+    // reloads. In UrlInfo below we use 'false' for |origin_requests_isolation|
+    // since error pages cannot request origin isolation.
+    return SiteInstanceDescriptor(
+        UrlInfo(GURL(kUnreachableWebDataURL),
+                false /* origin_requests_isolation */),
+        force_browsing_instance_swap ? SiteInstanceRelation::UNRELATED
+                                     : SiteInstanceRelation::RELATED,
+        is_coop_coep_cross_origin_isolated);
   }
 
   // If a swap is required, we need to force the SiteInstance AND
   // BrowsingInstance to be different ones, using CreateForURL.
   if (force_browsing_instance_swap) {
-    return SiteInstanceDescriptor(dest_url, SiteInstanceRelation::UNRELATED,
+    return SiteInstanceDescriptor(dest_url_info,
+                                  SiteInstanceRelation::UNRELATED,
                                   is_coop_coep_cross_origin_isolated);
   }
 
@@ -1828,7 +1836,7 @@
     SiteInstance* parent_site_instance =
         frame_tree_node_->parent()->GetSiteInstance();
     if (GetContentClient()->browser()->ShouldStayInParentProcessForNTP(
-            dest_url, parent_site_instance)) {
+            dest_url_info.url, parent_site_instance)) {
       // NTP does not define COOP/COEP.
       DCHECK(!is_coop_coep_cross_origin_isolated);
       return SiteInstanceDescriptor(parent_site_instance);
@@ -1839,9 +1847,9 @@
   // URLs.  Preferring |source_instance| over a site-less |current_instance| is
   // important in session restore scenarios which should commit in the
   // SiteInstance based on FrameNavigationEntry's initiator_origin.
-  if (CanUseSourceSiteInstance(dest_url, source_instance, was_server_redirect,
-                               is_failure, is_coop_coep_cross_origin_isolated,
-                               is_speculative)) {
+  if (CanUseSourceSiteInstance(
+          dest_url_info.url, source_instance, was_server_redirect, is_failure,
+          is_coop_coep_cross_origin_isolated, is_speculative)) {
     return SiteInstanceDescriptor(source_instance);
   }
 
@@ -1868,7 +1876,7 @@
     DCHECK_EQ(controller.GetBrowserContext(),
               current_instance_impl->GetBrowserContext());
     const SiteInfo dest_site_info = SiteInstanceImpl::ComputeSiteInfo(
-        current_instance_impl->GetIsolationContext(), dest_url,
+        current_instance_impl->GetIsolationContext(), dest_url_info,
         is_coop_coep_cross_origin_isolated,
         coop_coep_cross_origin_isolated_origin);
     bool use_process_per_site =
@@ -1880,7 +1888,7 @@
     if (current_instance_impl->HasRelatedSiteInstance(dest_site_info) ||
         use_process_per_site) {
       return SiteInstanceDescriptor(
-          dest_url, SiteInstanceRelation::RELATED,
+          dest_url_info, SiteInstanceRelation::RELATED,
           false /* is_coop_coep_cross_origin_isolated */);
     }
 
@@ -1888,9 +1896,9 @@
     // not want to use the |current_instance_impl| if it has no site, since it
     // will have a non-privileged RenderProcessHost. Create a new SiteInstance
     // for this URL instead (with the correct process type).
-    if (!current_instance_impl->IsSuitableForURL(dest_url)) {
+    if (!current_instance_impl->IsSuitableForUrlInfo(dest_url_info)) {
       return SiteInstanceDescriptor(
-          dest_url, SiteInstanceRelation::RELATED,
+          dest_url_info, SiteInstanceRelation::RELATED,
           false /* is_coop_coep_cross_origin_isolated */);
     }
 
@@ -1908,19 +1916,21 @@
     // renderers created for particular chrome urls (e.g. the chrome-native://
     // scheme) can be reused for subsequent navigations in the same WebContents.
     // See http://crbug.com/386542.
-    if (dest_is_restore && SiteInstanceImpl::ShouldAssignSiteForURL(dest_url))
-      current_instance_impl->ConvertToDefaultOrSetSite(dest_url);
+    if (dest_is_restore &&
+        SiteInstanceImpl::ShouldAssignSiteForURL(dest_url_info.url)) {
+      current_instance_impl->ConvertToDefaultOrSetSite(dest_url_info);
+    }
 
     return SiteInstanceDescriptor(current_instance_impl);
   }
 
   // Use the current SiteInstance for same site navigations.
   if (render_frame_host_->IsNavigationSameSite(
-          dest_url, is_coop_coep_cross_origin_isolated,
+          dest_url_info, is_coop_coep_cross_origin_isolated,
           coop_coep_cross_origin_isolated_origin) &&
       IsSiteInstanceCompatibleWithCoopCoepCrossOriginIsolation(
           render_frame_host_->GetSiteInstance(),
-          frame_tree_node_->IsMainFrame(), dest_url,
+          frame_tree_node_->IsMainFrame(), dest_url_info.url,
           is_coop_coep_cross_origin_isolated, is_speculative)) {
     return SiteInstanceDescriptor(render_frame_host_->GetSiteInstance());
   }
@@ -1943,12 +1953,12 @@
   if (!frame_tree_node_->IsMainFrame()) {
     RenderFrameHostImpl* main_frame =
         frame_tree_node_->frame_tree()->root()->current_frame_host();
-    if (IsCandidateSameSite(main_frame, dest_url,
+    if (IsCandidateSameSite(main_frame, dest_url_info,
                             is_coop_coep_cross_origin_isolated,
                             coop_coep_cross_origin_isolated_origin))
       return SiteInstanceDescriptor(main_frame->GetSiteInstance());
     RenderFrameHostImpl* parent = frame_tree_node_->parent();
-    if (IsCandidateSameSite(parent, dest_url,
+    if (IsCandidateSameSite(parent, dest_url_info,
                             is_coop_coep_cross_origin_isolated,
                             coop_coep_cross_origin_isolated_origin))
       return SiteInstanceDescriptor(parent->GetSiteInstance());
@@ -1956,7 +1966,7 @@
   if (frame_tree_node_->opener()) {
     RenderFrameHostImpl* opener_frame =
         frame_tree_node_->opener()->current_frame_host();
-    if (IsCandidateSameSite(opener_frame, dest_url,
+    if (IsCandidateSameSite(opener_frame, dest_url_info,
                             is_coop_coep_cross_origin_isolated,
                             coop_coep_cross_origin_isolated_origin))
       return SiteInstanceDescriptor(opener_frame->GetSiteInstance());
@@ -1985,7 +1995,7 @@
           SiteInstanceImpl::DoesSiteInfoRequireDedicatedProcess(
               parent_isolation_context,
               SiteInstanceImpl::ComputeSiteInfo(
-                  parent_isolation_context, dest_url,
+                  parent_isolation_context, dest_url_info,
                   is_coop_coep_cross_origin_isolated,
                   coop_coep_cross_origin_isolated_origin));
       if (!parent->GetSiteInstance()->RequiresDedicatedProcess() &&
@@ -1999,12 +2009,13 @@
   // cannot be hosted by it.
   if (IsSiteInstanceCompatibleWithCoopCoepCrossOriginIsolation(
           render_frame_host_->GetSiteInstance(),
-          frame_tree_node_->IsMainFrame(), dest_url,
+          frame_tree_node_->IsMainFrame(), dest_url_info.url,
           is_coop_coep_cross_origin_isolated, is_speculative)) {
-    return SiteInstanceDescriptor(dest_url, SiteInstanceRelation::RELATED,
+    return SiteInstanceDescriptor(dest_url_info, SiteInstanceRelation::RELATED,
                                   is_coop_coep_cross_origin_isolated);
   } else {
-    return SiteInstanceDescriptor(dest_url, SiteInstanceRelation::UNRELATED,
+    return SiteInstanceDescriptor(dest_url_info,
+                                  SiteInstanceRelation::UNRELATED,
                                   is_coop_coep_cross_origin_isolated);
   }
 }
@@ -2074,24 +2085,25 @@
   // guaranteed to return a SiteInstance that will be compatible with
   // |descriptor.is_coop_coep_cross_origin_isolated|."
   if (descriptor.relation == SiteInstanceRelation::RELATED)
-    return current_instance->GetRelatedSiteInstance(descriptor.dest_url);
+    return current_instance->GetRelatedSiteInstanceImpl(
+        descriptor.dest_url_info);
 
   // At this point we know an unrelated site instance must be returned. First
   // check if the candidate matches.
   if (candidate_instance &&
       IsSiteInstanceCompatibleWithCoopCoepCrossOriginIsolation(
           candidate_instance, frame_tree_node_->IsMainFrame(),
-          descriptor.dest_url, descriptor.is_coop_coep_cross_origin_isolated,
-          is_speculative) &&
+          descriptor.dest_url_info.url,
+          descriptor.is_coop_coep_cross_origin_isolated, is_speculative) &&
       !current_instance->IsRelatedSiteInstance(candidate_instance) &&
-      candidate_instance->DoesSiteInfoForURLMatch(descriptor.dest_url)) {
+      candidate_instance->DoesSiteInfoForURLMatch(descriptor.dest_url_info)) {
     return candidate_instance;
   }
 
   // Otherwise return a newly created one.
-  return SiteInstanceImpl::CreateForURL(
+  return SiteInstanceImpl::CreateForUrlInfo(
       delegate_->GetControllerForRenderManager().GetBrowserContext(),
-      descriptor.dest_url, descriptor.is_coop_coep_cross_origin_isolated);
+      descriptor.dest_url_info, descriptor.is_coop_coep_cross_origin_isolated);
 }
 
 bool RenderFrameHostManager::CanUseSourceSiteInstance(
@@ -2144,7 +2156,7 @@
 
 bool RenderFrameHostManager::IsCandidateSameSite(
     RenderFrameHostImpl* candidate,
-    const GURL& dest_url,
+    const UrlInfo& dest_url_info,
     bool is_coop_coep_cross_origin_isolated,
     base::Optional<url::Origin> coop_coep_cross_origin_isolated_origin) {
   DCHECK_EQ(delegate_->GetControllerForRenderManager().GetBrowserContext(),
@@ -2162,7 +2174,7 @@
   // position of this object in the frame tree.
   return candidate->GetSiteInstance()->IsNavigationSameSite(
       candidate->last_successful_url(), candidate->GetLastCommittedOrigin(),
-      frame_tree_node_->IsMainFrame(), dest_url);
+      frame_tree_node_->IsMainFrame(), dest_url_info);
 }
 
 void RenderFrameHostManager::CreateProxiesForNewRenderFrameHost(
@@ -2589,7 +2601,7 @@
                                       &coop_coep_cross_origin_isolated_origin);
 
   scoped_refptr<SiteInstance> dest_site_instance = GetSiteInstanceForNavigation(
-      request->common_params().url, is_coop_coep_cross_origin_isolated,
+      request->GetUrlInfo(), is_coop_coep_cross_origin_isolated,
       coop_coep_cross_origin_isolated_origin, request->GetSourceSiteInstance(),
       request->dest_site_instance(), candidate_site_instance,
       request->common_params().transition,
diff --git a/content/browser/renderer_host/render_frame_host_manager.h b/content/browser/renderer_host/render_frame_host_manager.h
index b8d5110b..614a5899 100644
--- a/content/browser/renderer_host/render_frame_host_manager.h
+++ b/content/browser/renderer_host/render_frame_host_manager.h
@@ -558,16 +558,16 @@
     ATTACHED
   };
 
-  // Stores information regarding a SiteInstance targeted at a specific URL to
-  // allow for comparisons without having to actually create new instances. It
-  // can point to an existing one or store the details needed to create a new
+  // Stores information regarding a SiteInstance targeted at a specific UrlInfo
+  // to allow for comparisons without having to actually create new instances.
+  // It can point to an existing one or store the details needed to create a new
   // one.
   struct CONTENT_EXPORT SiteInstanceDescriptor {
     explicit SiteInstanceDescriptor(content::SiteInstance* site_instance)
         : existing_site_instance(site_instance),
           relation(SiteInstanceRelation::PREEXISTING) {}
 
-    SiteInstanceDescriptor(GURL dest_url,
+    SiteInstanceDescriptor(UrlInfo dest_url_info,
                            SiteInstanceRelation relation_to_current,
                            bool is_coop_coep_cross_origin_isolated);
 
@@ -575,7 +575,7 @@
     content::SiteInstance* existing_site_instance;
 
     // In case |existing_site_instance| is null, specify a destination URL.
-    GURL dest_url;
+    UrlInfo dest_url_info;
 
     // Specifies how the new site is related to the current BrowsingInstance.
     // This is PREEXISTING iff |existing_site_instance| is defined.
@@ -599,7 +599,7 @@
   void DeleteRenderFrameProxyHost(SiteInstance* site_instance);
 
   // Returns true if for the navigation from |current_effective_url| to
-  // |destination_url|, a new SiteInstance and BrowsingInstance should be
+  // |destination_url_info|, a new SiteInstance and BrowsingInstance should be
   // created (even if we are in a process model that doesn't usually swap).
   // This forces a process swap and severs script connections with existing
   // tabs.  Cases where this can happen include transitions between WebUI and
@@ -615,7 +615,7 @@
   // If there is no current NavigationEntry, then |current_is_view_source_mode|
   // should be the same as |dest_is_view_source_mode|.
   //
-  // We use the effective URL here, since that's what is used in the
+  // UrlInfo uses the effective URL here, since that's what is used in the
   // SiteInstance's site and when we later call IsSameSite.  If there is no
   // current NavigationEntry, check the current SiteInstance's site, which might
   // already be committed to a Web UI URL (such as the NTP). Note that we don't
@@ -628,7 +628,7 @@
       SiteInstanceImpl* source_instance,
       SiteInstanceImpl* current_instance,
       SiteInstance* destination_instance,
-      const GURL& destination_url,
+      const UrlInfo& destination_url_info,
       bool is_coop_coep_cross_origin_isolated,
       const base::Optional<url::Origin>& coop_coep_cross_origin_isolated_origin,
       bool destination_is_view_source_mode,
@@ -642,7 +642,7 @@
       bool is_speculative);
 
   ShouldSwapBrowsingInstance ShouldProactivelySwapBrowsingInstance(
-      const GURL& destination_url,
+      const UrlInfo& destination_url_info,
       bool is_coop_coep_cross_origin_isolated,
       const base::Optional<url::Origin>& coop_coep_cross_origin_isolated_origin,
       bool is_reload,
@@ -650,7 +650,7 @@
 
   // Returns the SiteInstance to use for the navigation.
   scoped_refptr<SiteInstance> GetSiteInstanceForNavigation(
-      const GURL& dest_url,
+      const UrlInfo& dest_url_info,
       bool is_coop_coep_cross_origin_isolated,
       const base::Optional<url::Origin>& coop_coep_cross_origin_isolated_origin,
       SiteInstanceImpl* source_instance,
@@ -669,7 +669,7 @@
       bool* did_same_site_proactive_browsing_instance_swap);
 
   // Returns a descriptor of the appropriate SiteInstance object for the given
-  // |dest_url|, possibly reusing the current, source or destination
+  // |dest_url_info|, possibly reusing the current, source or destination
   // SiteInstance. The actual SiteInstance can then be obtained calling
   // ConvertToSiteInstance with the descriptor.
   //
@@ -695,7 +695,7 @@
   //
   // This is a helper function for GetSiteInstanceForNavigation.
   SiteInstanceDescriptor DetermineSiteInstanceForURL(
-      const GURL& dest_url,
+      const UrlInfo& dest_url_info,
       bool is_coop_coep_cross_origin_isolated,
       const base::Optional<url::Origin>& coop_coep_cross_origin_isolated_origin,
       SiteInstance* source_instance,
@@ -749,12 +749,12 @@
       bool is_speculative);
 
   // Returns true if |candidate| is currently on the same web site as
-  // |dest_url|. This method is a special case for handling hosted apps in
+  // |dest_url_info|. This method is a special case for handling hosted apps in
   // this object. Most code should call IsNavigationSameSite() on
   // |candidate| instead of this method.
   bool IsCandidateSameSite(
       RenderFrameHostImpl* candidate,
-      const GURL& dest_url,
+      const UrlInfo& dest_url_info,
       bool is_coop_coep_cross_origin_isolated,
       base::Optional<url::Origin> coop_coep_cross_origin_isolated_origin);
 
diff --git a/content/browser/renderer_host/render_frame_host_manager_browsertest.cc b/content/browser/renderer_host/render_frame_host_manager_browsertest.cc
index 52c8aa86..3630ab3e 100644
--- a/content/browser/renderer_host/render_frame_host_manager_browsertest.cc
+++ b/content/browser/renderer_host/render_frame_host_manager_browsertest.cc
@@ -5893,7 +5893,9 @@
 
   // Create a new process and set it as the sole process host for B.
   scoped_refptr<SiteInstanceImpl> placeholder_b_site_instance =
-      SiteInstanceImpl::CreateForURL(web_contents->GetBrowserContext(), b_url);
+      SiteInstanceImpl::CreateForUrlInfo(
+          web_contents->GetBrowserContext(), UrlInfo::CreateForTesting(b_url),
+          false /* is_coop_coep_cross_origin_isolated */);
   RenderProcessHost* process_for_b =
       RenderProcessHostImpl::CreateRenderProcessHost(
           web_contents->GetBrowserContext(), placeholder_b_site_instance.get());
@@ -8510,17 +8512,20 @@
   EXPECT_FALSE(instance1->HasProcess());
   if (AreAllSitesIsolatedForTesting()) {
     // In site-per-process, we cannot use foo.com's SiteInstance for a.com.
-    EXPECT_FALSE(instance1->IsSuitableForURL(url1));
+    EXPECT_FALSE(
+        instance1->IsSuitableForUrlInfo(UrlInfo::CreateForTesting(url1)));
   } else if (AreDefaultSiteInstancesEnabled()) {
     // Since |instance1| is a default SiteInstance AND this test explicitly
     // ensures that ShouldAssignSiteForURL(url1) will return false, |url1|
     // cannot be placed in the default SiteInstance. This also means that |url1|
     // cannot be placed in the same process as the default SiteInstance.
-    EXPECT_FALSE(instance1->IsSuitableForURL(url1));
+    EXPECT_FALSE(
+        instance1->IsSuitableForUrlInfo(UrlInfo::CreateForTesting(url1)));
   } else {
     // If neither foo.com nor a.com require dedicated processes, then we can use
     // the same process.
-    EXPECT_TRUE(instance1->IsSuitableForURL(url1));
+    EXPECT_TRUE(
+        instance1->IsSuitableForUrlInfo(UrlInfo::CreateForTesting(url1)));
   }
 
   // Go back to url1's entry, which should swap to a new SiteInstance with an
diff --git a/content/browser/renderer_host/render_frame_host_manager_unittest.cc b/content/browser/renderer_host/render_frame_host_manager_unittest.cc
index f2f860d..a94e367 100644
--- a/content/browser/renderer_host/render_frame_host_manager_unittest.cc
+++ b/content/browser/renderer_host/render_frame_host_manager_unittest.cc
@@ -915,7 +915,7 @@
   EXPECT_TRUE(host == manager->current_frame_host());
   ASSERT_TRUE(host);
   EXPECT_FALSE(host->GetSiteInstance()->HasSite());
-  host->GetSiteInstance()->SetSite(kUrl1);
+  host->GetSiteInstance()->SetSite(UrlInfo::CreateForTesting(kUrl1));
 
   manager->GetRenderWidgetHostView()->SetBackgroundColor(SK_ColorRED);
 
@@ -1540,7 +1540,7 @@
   // Commit to SiteInstance should be delayed until RenderFrame commits.
   EXPECT_EQ(host, manager->current_frame_host());
   EXPECT_FALSE(host->GetSiteInstance()->HasSite());
-  host->GetSiteInstance()->SetSite(kUrl1);
+  host->GetSiteInstance()->SetSite(UrlInfo::CreateForTesting(kUrl1));
 
   // 2) Cross-site navigate to next site. -------------------------
   const GURL kUrl2("http://www.example.com");
@@ -2537,7 +2537,7 @@
   const GURL kInitUrl(GetWebUIURL("foo"));
   scoped_refptr<SiteInstanceImpl> initial_instance =
       SiteInstanceImpl::Create(browser_context());
-  initial_instance->SetSite(kInitUrl);
+  initial_instance->SetSite(UrlInfo::CreateForTesting(kInitUrl));
   std::unique_ptr<TestWebContents> web_contents(
       TestWebContents::Create(browser_context(), initial_instance));
   RenderFrameHostManager* manager = web_contents->GetRenderManagerForTesting();
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index a6e715f..2e94f37 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -3423,6 +3423,7 @@
     // Please keep these in alphabetical order.
     blink::switches::kAllowPreCommitInput,
     blink::switches::kBlinkSettings,
+    blink::switches::kDarkModeSettings,
     blink::switches::kDefaultTileWidth,
     blink::switches::kDefaultTileHeight,
     blink::switches::kDisableImageAnimationResync,
diff --git a/content/browser/renderer_host/render_process_host_unittest.cc b/content/browser/renderer_host/render_process_host_unittest.cc
index 6cc627c..d837f39 100644
--- a/content/browser/renderer_host/render_process_host_unittest.cc
+++ b/content/browser/renderer_host/render_process_host_unittest.cc
@@ -34,7 +34,14 @@
 
 namespace content {
 
-class RenderProcessHostUnitTest : public RenderViewHostImplTestHarness {};
+class RenderProcessHostUnitTest : public RenderViewHostImplTestHarness {
+ public:
+  scoped_refptr<SiteInstanceImpl> CreateForUrl(const GURL& url) {
+    return SiteInstanceImpl::CreateForUrlInfo(
+        browser_context(), UrlInfo::CreateForTesting(url),
+        false /* is_coop_coep_cross_origin_isolated */);
+  }
+};
 
 // Tests that guest RenderProcessHosts are not considered suitable hosts when
 // searching for RenderProcessHost.
@@ -44,8 +51,7 @@
   MockRenderProcessHost guest_host(browser_context(),
                                    /*is_for_guest_only=*/true);
 
-  scoped_refptr<SiteInstanceImpl> site_instance =
-      SiteInstanceImpl::CreateForURL(browser_context(), test_url);
+  scoped_refptr<SiteInstanceImpl> site_instance = CreateForUrl(test_url);
   EXPECT_FALSE(RenderProcessHostImpl::IsSuitableHost(
       &guest_host, site_instance->GetIsolationContext(),
       site_instance->GetSiteInfo(), site_instance->IsGuest()));
@@ -189,8 +195,7 @@
   // A process for a SiteInstance with a preassigned site should be considered
   // "used" from the point the process is created via GetProcess().
   {
-    scoped_refptr<SiteInstanceImpl> site_instance =
-        SiteInstanceImpl::CreateForURL(browser_context(), kUrl1);
+    scoped_refptr<SiteInstanceImpl> site_instance = CreateForUrl(kUrl1);
     EXPECT_FALSE(site_instance->GetProcess()->IsUnused());
   }
 }
@@ -217,8 +222,7 @@
 
   // Getting a RenderProcessHost for a navigation to the same site must reuse
   // the newest unmatched service worker's process (i.e., sw_host2).
-  scoped_refptr<SiteInstanceImpl> site_instance1 =
-      SiteInstanceImpl::CreateForURL(browser_context(), kUrl);
+  scoped_refptr<SiteInstanceImpl> site_instance1 = CreateForUrl(kUrl);
   EXPECT_EQ(sw_host2, site_instance1->GetProcess());
   EXPECT_EQ(SiteInstanceProcessAssignment::REUSED_EXISTING_PROCESS,
             site_instance1->GetLastProcessAssignmentOutcome());
@@ -227,16 +231,14 @@
   // the newest unmatched service worker's process (i.e., sw_host1). sw_host2
   // is no longer unmatched, so sw_host1 is now the newest (and only) process
   // with a corresponding unmatched service worker.
-  scoped_refptr<SiteInstanceImpl> site_instance2 =
-      SiteInstanceImpl::CreateForURL(browser_context(), kUrl);
+  scoped_refptr<SiteInstanceImpl> site_instance2 = CreateForUrl(kUrl);
   EXPECT_EQ(sw_host1, site_instance2->GetProcess());
   EXPECT_EQ(SiteInstanceProcessAssignment::REUSED_EXISTING_PROCESS,
             site_instance2->GetLastProcessAssignmentOutcome());
 
   // Getting a RenderProcessHost for a navigation should return a new process
   // because there is no unmatched service worker's process.
-  scoped_refptr<SiteInstanceImpl> site_instance3 =
-      SiteInstanceImpl::CreateForURL(browser_context(), kUrl);
+  scoped_refptr<SiteInstanceImpl> site_instance3 = CreateForUrl(kUrl);
   EXPECT_NE(sw_host1, site_instance3->GetProcess());
   EXPECT_NE(sw_host2, site_instance3->GetProcess());
   EXPECT_EQ(SiteInstanceProcessAssignment::CREATED_NEW_PROCESS,
@@ -284,8 +286,7 @@
   // Now, getting a RenderProcessHost for a navigation to the same site should
   // not reuse the unmatched service worker's process (i.e., |sw_host|), as
   // it's unsuitable.
-  scoped_refptr<SiteInstanceImpl> site_instance =
-      SiteInstanceImpl::CreateForURL(browser_context(), kUrl);
+  scoped_refptr<SiteInstanceImpl> site_instance = CreateForUrl(kUrl);
   EXPECT_NE(sw_host, site_instance->GetProcess());
   EXPECT_EQ(SiteInstanceProcessAssignment::CREATED_NEW_PROCESS,
             sw_site_instance->GetLastProcessAssignmentOutcome());
@@ -342,8 +343,7 @@
 
   // Getting a RenderProcessHost for a navigation to the same site must reuse
   // the newest unmatched service worker's process (i.e., sw_host2).
-  scoped_refptr<SiteInstanceImpl> site_instance1 =
-      SiteInstanceImpl::CreateForURL(browser_context(), kUrl);
+  scoped_refptr<SiteInstanceImpl> site_instance1 = CreateForUrl(kUrl);
   EXPECT_EQ(sw_host2, site_instance1->GetProcess());
   EXPECT_EQ(SiteInstanceProcessAssignment::REUSED_EXISTING_PROCESS,
             site_instance1->GetLastProcessAssignmentOutcome());
@@ -352,8 +352,7 @@
   // the newest unmatched service worker's process (i.e., sw_host1). sw_host2
   // is no longer unmatched, so sw_host1 is now the newest (and only) process
   // with a corresponding unmatched service worker.
-  scoped_refptr<SiteInstanceImpl> site_instance2 =
-      SiteInstanceImpl::CreateForURL(browser_context(), kUrl);
+  scoped_refptr<SiteInstanceImpl> site_instance2 = CreateForUrl(kUrl);
   EXPECT_EQ(sw_host1, site_instance2->GetProcess());
   EXPECT_EQ(SiteInstanceProcessAssignment::REUSED_EXISTING_PROCESS,
             site_instance2->GetLastProcessAssignmentOutcome());
@@ -383,8 +382,7 @@
 
   // Getting a RenderProcessHost for a navigation to the same site with
   // process-per-site flag should reuse the unmatched service worker's process.
-  scoped_refptr<SiteInstanceImpl> sw_site_instance3 =
-      SiteInstanceImpl::CreateForURL(browser_context(), kUrl);
+  scoped_refptr<SiteInstanceImpl> sw_site_instance3 = CreateForUrl(kUrl);
   RenderProcessHost* sw_host3 = sw_site_instance3->GetProcess();
   EXPECT_EQ(sw_host1, sw_host3);
   EXPECT_EQ(SiteInstanceProcessAssignment::REUSED_EXISTING_PROCESS,
@@ -392,8 +390,7 @@
 
   // Getting a RenderProcessHost for a navigation to the same site again with
   // process-per-site flag should reuse the unmatched service worker's process.
-  scoped_refptr<SiteInstanceImpl> sw_site_instance4 =
-      SiteInstanceImpl::CreateForURL(browser_context(), kUrl);
+  scoped_refptr<SiteInstanceImpl> sw_site_instance4 = CreateForUrl(kUrl);
   RenderProcessHost* sw_host4 = sw_site_instance4->GetProcess();
   EXPECT_EQ(sw_host1, sw_host4);
   EXPECT_EQ(SiteInstanceProcessAssignment::REUSED_EXISTING_PROCESS,
@@ -413,8 +410,7 @@
 
   // Getting a RenderProcessHost for a service worker of a different site should
   // return a new process because there is no reusable process.
-  scoped_refptr<SiteInstanceImpl> sw_site_instance2 =
-      SiteInstanceImpl::CreateForURL(browser_context(), kUrl2);
+  scoped_refptr<SiteInstanceImpl> sw_site_instance2 = CreateForUrl(kUrl2);
   EXPECT_NE(sw_host1, sw_site_instance2->GetProcess());
   EXPECT_EQ(SiteInstanceProcessAssignment::CREATED_NEW_PROCESS,
             sw_site_instance2->GetLastProcessAssignmentOutcome());
@@ -910,8 +906,7 @@
                {GURL("http:"), false},
                {GURL("http://user:pass@google.com:99/foo;bar?q=a#ref"), true}};
   for (const auto& test : tests) {
-    scoped_refptr<SiteInstanceImpl> site_instance =
-        SiteInstanceImpl::CreateForURL(browser_context(), test.test_url);
+    scoped_refptr<SiteInstanceImpl> site_instance = CreateForUrl(test.test_url);
     auto* host =
         static_cast<MockRenderProcessHost*>(site_instance->GetProcess());
     if (AreAllSitesIsolatedForTesting())
@@ -926,8 +921,7 @@
 TEST_F(RenderProcessHostUnitTest, ProcessAssignmentDefault) {
   const GURL kUrl("https://foo.com");
 
-  scoped_refptr<SiteInstanceImpl> site_instance =
-      SiteInstanceImpl::CreateForURL(browser_context(), kUrl);
+  scoped_refptr<SiteInstanceImpl> site_instance = CreateForUrl(kUrl);
   EXPECT_EQ(SiteInstanceProcessAssignment::UNKNOWN,
             site_instance->GetLastProcessAssignmentOutcome());
   EXPECT_FALSE(site_instance->HasProcess());
@@ -1079,7 +1073,9 @@
   // unnecessary resource contention when 2 processes try to launch at the same
   // time).
   scoped_refptr<SiteInstanceImpl> site_instance =
-      SiteInstanceImpl::CreateForURL(browser_context(), GURL("http://foo.com"));
+      SiteInstanceImpl::CreateForUrlInfo(
+          browser_context(), UrlInfo::CreateForTesting(GURL("http://foo.com")),
+          false /* is_coop_coep_cross_origin_isolated */);
   RenderProcessHost* site_instance_process = site_instance->GetProcess();
   // We need to ensure the MockRenderProcessHost gets destroyed at the end of
   // the test.
diff --git a/content/browser/renderer_host/render_widget_host_view_android.cc b/content/browser/renderer_host/render_widget_host_view_android.cc
index fae059b3..8ab4fa8 100644
--- a/content/browser/renderer_host/render_widget_host_view_android.cc
+++ b/content/browser/renderer_host/render_widget_host_view_android.cc
@@ -1477,7 +1477,7 @@
       << "Hide called when the widget should be shown.";
 
   // Only preserve the frontbuffer if the activity was stopped while the
-  // window is still visible. This avoids visual artificts when transitioning
+  // window is still visible. This avoids visual artifacts when transitioning
   // between activities.
   bool hide_frontbuffer = is_window_activity_started_ || !is_window_visible_;
 
@@ -2095,7 +2095,11 @@
                "RenderWidgetHostViewAndroid::OnRootWindowVisibilityChanged",
                "visible", visible);
   DCHECK(observing_root_window_);
-  if (is_window_visible_ == visible)
+
+  // Don't early out if visibility hasn't changed and visible. This is necessary
+  // as OnDetachedFromWindow() sets |is_window_visible_| to true, so that this
+  // may be called when ShowInternal() needs to be called.
+  if (is_window_visible_ == visible && !visible)
     return;
 
   is_window_visible_ = visible;
diff --git a/content/browser/renderer_host/render_widget_host_view_android_unittest.cc b/content/browser/renderer_host/render_widget_host_view_android_unittest.cc
index 624708ef2..44059f6 100644
--- a/content/browser/renderer_host/render_widget_host_view_android_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_android_unittest.cc
@@ -18,6 +18,7 @@
 #include "content/test/test_view_android_delegate.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/android/view_android.h"
+#include "ui/android/window_android.h"
 
 namespace content {
 
@@ -42,6 +43,8 @@
   void SetUp() override;
   void TearDown() override;
 
+  ui::ViewAndroid* parent_view() { return &parent_view_; }
+
   std::unique_ptr<TestViewAndroidDelegate> test_view_android_delegate_;
 
  private:
@@ -162,4 +165,35 @@
   EXPECT_TRUE(rwhva->GetLocalSurfaceId().IsNewerThan(inset_surface));
 }
 
+TEST_F(RenderWidgetHostViewAndroidTest, HideWindowRemoveViewAddViewShowWindow) {
+  std::unique_ptr<ui::WindowAndroid> window(
+      ui::WindowAndroid::CreateForTesting());
+  window->AddChild(parent_view());
+  EXPECT_TRUE(render_widget_host_view_android()->IsShowing());
+  // The layer should be visible once attached to a window.
+  EXPECT_FALSE(render_widget_host_view_android()
+                   ->GetNativeView()
+                   ->GetLayer()
+                   ->hide_layer_and_subtree());
+
+  // Hiding the window should and removing the view should hide the layer.
+  window->OnVisibilityChanged(nullptr, nullptr, false);
+  parent_view()->RemoveFromParent();
+  EXPECT_TRUE(render_widget_host_view_android()->IsShowing());
+  EXPECT_TRUE(render_widget_host_view_android()
+                  ->GetNativeView()
+                  ->GetLayer()
+                  ->hide_layer_and_subtree());
+
+  // Adding the view back to a window and notifying the window is visible should
+  // make the layer visible again.
+  window->AddChild(parent_view());
+  window->OnVisibilityChanged(nullptr, nullptr, true);
+  EXPECT_TRUE(render_widget_host_view_android()->IsShowing());
+  EXPECT_FALSE(render_widget_host_view_android()
+                   ->GetNativeView()
+                   ->GetLayer()
+                   ->hide_layer_and_subtree());
+}
+
 }  // namespace content
diff --git a/content/browser/resources/gpu/gpu_internals.html b/content/browser/resources/gpu/gpu_internals.html
index 788af89..c876715d 100644
--- a/content/browser/resources/gpu/gpu_internals.html
+++ b/content/browser/resources/gpu/gpu_internals.html
@@ -27,19 +27,13 @@
   top: 0;
 }
 
-tabbox tabpanels {
-  padding: 10px;
-}
-
 </style>
 <link rel="stylesheet" href="info_view.css">
-<link rel="stylesheet" href="chrome://resources/css/tabs.css">
 <link rel="stylesheet" href="chrome://resources/css/widgets.css">
 <script src="chrome://resources/js/cr.js"></script>
 <script src="chrome://resources/js/cr/event_target.js"></script>
 <script src="chrome://resources/js/cr/ui.js"></script>
 <script src="chrome://resources/js/cr/ui/focus_outline_manager.js"></script>
-<script src="chrome://resources/js/cr/ui/tabs.js"></script>
 <script src="chrome://resources/js/load_time_data.js"></script>
 <script src="chrome://resources/js/util.js"></script>
 <script src="chrome://resources/mojo/mojo/public/js/mojo_bindings_lite.js"></script>
diff --git a/content/browser/resources/gpu/info_view.html b/content/browser/resources/gpu/info_view.html
index 6facc01..fc5403c 100644
--- a/content/browser/resources/gpu/info_view.html
+++ b/content/browser/resources/gpu/info_view.html
@@ -3,7 +3,7 @@
 Use of this source code is governed by a BSD-style license that can be
 found in the LICENSE file.
 -->
-<tabpanel id="info-view">
+<div id="info-view">
   <div>
     <input type="button" id="copy-to-clipboard" value="Copy Report to Clipboard">
   </div>
@@ -132,4 +132,4 @@
       </table>
     </div>
   </div>
-</tabpanel>
+</div>
diff --git a/content/browser/resources/gpu/info_view.js b/content/browser/resources/gpu/info_view.js
index d83a767..b6f9473b 100644
--- a/content/browser/resources/gpu/info_view.js
+++ b/content/browser/resources/gpu/info_view.js
@@ -12,16 +12,13 @@
   /**
    * Provides information on the GPU process and underlying graphics hardware.
    * @constructor
-   * @extends {cr.ui.TabPanel}
    */
-  const InfoView = cr.ui.define(cr.ui.TabPanel);
+  const InfoView = cr.ui.define('div');
 
   InfoView.prototype = {
-    __proto__: cr.ui.TabPanel.prototype,
+    __proto__: HTMLDivElement.prototype,
 
     decorate: function() {
-      cr.ui.TabPanel.prototype.decorate.apply(this);
-
       browserBridge.addEventListener('gpuInfoUpdate', this.refresh.bind(this));
       browserBridge.addEventListener(
           'logMessagesChange', this.refresh.bind(this));
diff --git a/content/browser/site_instance_impl.cc b/content/browser/site_instance_impl.cc
index 7eb2353..55f8ac07 100644
--- a/content/browser/site_instance_impl.cc
+++ b/content/browser/site_instance_impl.cc
@@ -196,22 +196,22 @@
 }
 
 // static
-scoped_refptr<SiteInstanceImpl> SiteInstanceImpl::CreateForURL(
+scoped_refptr<SiteInstanceImpl> SiteInstanceImpl::CreateForUrlInfo(
     BrowserContext* browser_context,
-    const GURL& url,
+    const UrlInfo& url_info,
     bool is_coop_coep_cross_origin_isolated) {
   DCHECK(browser_context);
   // This will create a new SiteInstance and BrowsingInstance.
   scoped_refptr<BrowsingInstance> instance(new BrowsingInstance(
       browser_context, is_coop_coep_cross_origin_isolated,
       is_coop_coep_cross_origin_isolated
-          ? base::Optional<url::Origin>(url::Origin::Create(url))
+          ? base::Optional<url::Origin>(url::Origin::Create(url_info.url))
           : base::nullopt));
 
   // Note: The |allow_default_instance| value used here MUST match the value
   // used in DoesSiteForURLMatch().
   return instance->GetSiteInstanceForURL(
-      url, kCreateForURLAllowsDefaultSiteInstance);
+      url_info, kCreateForURLAllowsDefaultSiteInstance);
 }
 
 // static
@@ -235,8 +235,11 @@
 
     // We do NOT want to allow the default site instance here because workers
     // need to be kept separate from other sites.
+    // TODO(ahemery): What, if anything, do we need to do regarding opt-in
+    // isolation and COOP/COEP for service workers.
     site_instance = instance->GetSiteInstanceForURL(
-        url, /* allow_default_instance */ false);
+        UrlInfo(url, false /* origin_requests_isolation */),
+        /* allow_default_instance */ false);
   }
   site_instance->is_for_service_worker_ = true;
 
@@ -288,9 +291,9 @@
   scoped_refptr<BrowsingInstance> instance(new BrowsingInstance(
       browser_context, false /* is_coop_coep_cross_origin_isolated */,
       base::nullopt));
-  auto site_instance =
-      instance->GetSiteInstanceForURL(url,
-                                      /* allow_default_instance */ false);
+  auto site_instance = instance->GetSiteInstanceForURL(
+      UrlInfo(url, false /* origin_requests_isolation */),
+      /* allow_default_instance */ false);
   site_instance->set_process_reuse_policy(
       SiteInstanceImpl::ProcessReusePolicy::REUSE_PENDING_OR_COMMITTED_SITE);
   return site_instance;
@@ -496,7 +499,8 @@
   can_associate_with_spare_process_ = false;
 }
 
-void SiteInstanceImpl::SetSite(const GURL& url) {
+void SiteInstanceImpl::SetSite(const UrlInfo& url_info) {
+  const GURL& url = url_info.url;
   // TODO(creis): Consider calling ShouldAssignSiteForURL internally, rather
   // than before multiple call sites.  See https://crbug.com/949220.
   TRACE_EVENT2("navigation", "SiteInstanceImpl::SetSite", "site id", id_, "url",
@@ -513,7 +517,7 @@
   // SetSiteInfoInternal(). We must do this transformation for any arbitrary
   // URL we get from a user, a navigation, or script.
   SetSiteInfoInternal(browsing_instance_->GetSiteInfoForURL(
-      url, /* allow_default_instance */ false));
+      url_info, /* allow_default_instance */ false));
 }
 
 void SiteInstanceImpl::SetSiteInfoToDefault() {
@@ -572,13 +576,13 @@
   }
 }
 
-void SiteInstanceImpl::ConvertToDefaultOrSetSite(const GURL& url) {
+void SiteInstanceImpl::ConvertToDefaultOrSetSite(const UrlInfo& url_info) {
   DCHECK(!has_site_);
 
-  if (browsing_instance_->TrySettingDefaultSiteInstance(this, url))
+  if (browsing_instance_->TrySettingDefaultSiteInstance(this, url_info))
     return;
 
-  SetSite(url);
+  SetSite(url_info);
 }
 
 SiteInstanceProcessAssignment
@@ -608,8 +612,14 @@
 
 scoped_refptr<SiteInstance> SiteInstanceImpl::GetRelatedSiteInstance(
     const GURL& url) {
+  return GetRelatedSiteInstanceImpl(
+      UrlInfo(url, false /* origin_requests_isolation */));
+}
+
+scoped_refptr<SiteInstanceImpl> SiteInstanceImpl::GetRelatedSiteInstanceImpl(
+    const UrlInfo& url_info) {
   return browsing_instance_->GetSiteInstanceForURL(
-      url, /* allow_default_instance */ true);
+      url_info, /* allow_default_instance */ true);
 }
 
 bool SiteInstanceImpl::IsRelatedSiteInstance(const SiteInstance* instance) {
@@ -622,7 +632,8 @@
   return browsing_instance_->active_contents_count();
 }
 
-bool SiteInstanceImpl::IsSuitableForURL(const GURL& url) {
+bool SiteInstanceImpl::IsSuitableForUrlInfo(const UrlInfo& url_info) {
+  const GURL& url = url_info.url;
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   // If the URL to navigate to can be associated with any site instance,
   // we want to keep it in the same process.
@@ -647,7 +658,7 @@
   // would be reported in the SiteInstance returned by
   // GetRelatedSiteInstance(url).
   SiteInfo site_info = browsing_instance_->GetSiteInfoForURL(
-      url, /* allow_default_instance */ true);
+      url_info, /* allow_default_instance */ true);
 
   // If this is a default SiteInstance and the BrowsingInstance gives us a
   // non-default site URL even when we explicitly allow the default SiteInstance
@@ -748,7 +759,9 @@
     BrowserContext* browser_context,
     const GURL& url) {
   DCHECK(browser_context);
-  return SiteInstanceImpl::CreateForURL(browser_context, url);
+  return SiteInstanceImpl::CreateForUrlInfo(
+      browser_context, UrlInfo(url, false /* origin_requests_isolation */),
+      false /* is_coop_coep_cross_origin_isolated */);
 }
 
 // static
@@ -765,6 +778,12 @@
 }
 
 bool SiteInstanceImpl::IsSameSiteWithURL(const GURL& url) {
+  return IsSameSiteWithURLInfo(
+      UrlInfo(url, false /* origin_requests_isolation */));
+}
+
+bool SiteInstanceImpl::IsSameSiteWithURLInfo(const UrlInfo& url_info) {
+  const GURL& url = url_info.url;
   if (IsDefaultSiteInstance()) {
     // about:blank URLs should always be considered same site just like they are
     // in IsSameSite().
@@ -778,7 +797,7 @@
     // prevent SiteInstances with no site URL from being used for URLs
     // that should be routed to the default SiteInstance.
     DCHECK_EQ(site_info_.site_url(), GetDefaultSiteURL());
-    auto site_info = ComputeSiteInfo(GetIsolationContext(), url,
+    auto site_info = ComputeSiteInfo(GetIsolationContext(), url_info,
                                      IsCoopCoepCrossOriginIsolated(),
                                      CoopCoepCrossOriginIsolatedOrigin());
     return CanBePlacedInDefaultSiteInstance(GetIsolationContext(), url,
@@ -786,9 +805,10 @@
            !browsing_instance_->HasSiteInstance(site_info);
   }
 
-  return SiteInstanceImpl::IsSameSite(GetIsolationContext(),
-                                      site_info_.site_url(), url,
-                                      true /* should_compare_effective_urls */);
+  return SiteInstanceImpl::IsSameSite(
+      GetIsolationContext(),
+      UrlInfo(site_info_.site_url(), false /* origin_requests_isolation */),
+      url_info, true /* should_compare_effective_urls */);
 }
 
 bool SiteInstanceImpl::IsGuest() {
@@ -832,20 +852,30 @@
 }
 
 bool SiteInstanceImpl::IsOriginalUrlSameSite(
-    const GURL& dest_url,
+    const UrlInfo& dest_url_info,
     bool should_compare_effective_urls) {
   if (IsDefaultSiteInstance())
-    return IsSameSiteWithURL(dest_url);
+    return IsSameSiteWithURLInfo(dest_url_info);
 
-  return IsSameSite(GetIsolationContext(), original_url_, dest_url,
-                    should_compare_effective_urls);
+  // Here we use |origin_requests_isolation| when converting |original_url_| to
+  // UrlInfo, since (i) the isolation status of this SiteInstance was determined
+  // at the time |original_url_| was set, and in this case it is |dest_url_info|
+  // that is currently navigating, and that's where the current isolation
+  // request (if any) is stored. Whether or not this SiteInstance has origin
+  // isolation is a separate question, and not what the UrlInfo for
+  // |original_url_| is supposed to reflect.
+  return IsSameSite(
+      GetIsolationContext(),
+      UrlInfo(original_url_, false /* origin_requests_isolation */),
+      dest_url_info, should_compare_effective_urls);
 }
 
 bool SiteInstanceImpl::IsNavigationSameSite(
     const GURL& last_successful_url,
     const url::Origin last_committed_origin,
     bool for_main_frame,
-    const GURL& dest_url) {
+    const UrlInfo& dest_url_info) {
+  const GURL& dest_url = dest_url_info.url;
   BrowserContext* browser_context = GetBrowserContext();
 
   // Ask embedder whether effective URLs should be used when determining if
@@ -873,7 +903,7 @@
   bool should_check_for_wrong_process =
       should_compare_effective_urls ||
       (!src_has_effective_url && !dest_has_effective_url);
-  if (should_check_for_wrong_process && !IsSuitableForURL(dest_url))
+  if (should_check_for_wrong_process && !IsSuitableForUrlInfo(dest_url_info))
     return false;
 
   // If we don't have a last successful URL, we can't trust the origin or URL
@@ -883,21 +913,29 @@
   // original_url() and not the site URL, so that we can do this comparison
   // without the effective URL resolution if needed.
   if (last_successful_url.is_empty())
-    return IsOriginalUrlSameSite(dest_url, should_compare_effective_urls);
+    return IsOriginalUrlSameSite(dest_url_info, should_compare_effective_urls);
 
   // In the common case, we use the last successful URL. Thus, we compare
   // against the last successful commit when deciding whether to swap this time.
-  if (IsSameSite(GetIsolationContext(), last_successful_url, dest_url,
-                 should_compare_effective_urls)) {
+  // We convert |last_successful_url| to UrlInfo with
+  // |origin_requests_isolation| = false since it isn't currently navigating.
+  if (IsSameSite(
+          GetIsolationContext(),
+          UrlInfo(last_successful_url, false /* origin_requests_isolation */),
+          dest_url_info, should_compare_effective_urls)) {
     return true;
   }
 
   // It is possible that last_successful_url was a nonstandard scheme (for
   // example, "about:blank"). If so, examine the last committed origin to
   // determine the site.
+  // Similar to above, convert |last_committed_origin| to UrlInfo with
+  // |origin_requests_isolation| = false.
   if (!last_committed_origin.opaque() &&
-      IsSameSite(GetIsolationContext(), GURL(last_committed_origin.Serialize()),
-                 dest_url, should_compare_effective_urls)) {
+      IsSameSite(GetIsolationContext(),
+                 UrlInfo(GURL(last_committed_origin.Serialize()),
+                         false /* origin_requests_isolation */),
+                 dest_url_info, should_compare_effective_urls)) {
     return true;
   }
 
@@ -908,7 +946,7 @@
   // tests rely on that behavior.  To accomplish this, compare |dest_url|
   // against the site URL.
   if (last_successful_url.IsAboutBlank() && last_committed_origin.opaque() &&
-      IsOriginalUrlSameSite(dest_url, should_compare_effective_urls)) {
+      IsOriginalUrlSameSite(dest_url_info, should_compare_effective_urls)) {
     return true;
   }
 
@@ -918,9 +956,12 @@
 
 // static
 bool SiteInstanceImpl::IsSameSite(const IsolationContext& isolation_context,
-                                  const GURL& real_src_url,
-                                  const GURL& real_dest_url,
+                                  const UrlInfo& real_src_url_info,
+                                  const UrlInfo& real_dest_url_info,
                                   bool should_compare_effective_urls) {
+  const GURL& real_src_url = real_src_url_info.url;
+  const GURL& real_dest_url = real_dest_url_info.url;
+
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   BrowserContext* browser_context =
       isolation_context.browser_or_resource_context().ToBrowserContext();
@@ -988,9 +1029,11 @@
   url::Origin src_isolated_origin;
   url::Origin dest_isolated_origin;
   bool src_origin_is_isolated = policy->GetMatchingIsolatedOrigin(
-      isolation_context, src_origin, &src_isolated_origin);
+      isolation_context, src_origin,
+      real_src_url_info.origin_requests_isolation, &src_isolated_origin);
   bool dest_origin_is_isolated = policy->GetMatchingIsolatedOrigin(
-      isolation_context, dest_origin, &dest_isolated_origin);
+      isolation_context, dest_origin,
+      real_dest_url_info.origin_requests_isolation, &dest_isolated_origin);
   if (src_origin_is_isolated || dest_origin_is_isolated) {
     // Compare most specific matching origins to ensure that a subdomain of an
     // isolated origin (e.g., https://subdomain.isolated.foo.com) also matches
@@ -1001,15 +1044,16 @@
   return true;
 }
 
-bool SiteInstanceImpl::DoesSiteInfoForURLMatch(const GURL& url) {
+bool SiteInstanceImpl::DoesSiteInfoForURLMatch(const UrlInfo& url_info) {
   // TODO(acolwell, ahemery): Update callers to pass in COOP/COEP info into
   // this method. The code is currently safe because the caller checks to make
   // sure the COOP/COEP info matches on this object before calling this method.
-  auto site_info = ComputeSiteInfo(GetIsolationContext(), url,
+  auto site_info = ComputeSiteInfo(GetIsolationContext(), url_info,
                                    IsCoopCoepCrossOriginIsolated(),
                                    CoopCoepCrossOriginIsolatedOrigin());
   if (kCreateForURLAllowsDefaultSiteInstance &&
-      CanBePlacedInDefaultSiteInstance(GetIsolationContext(), url, site_info)) {
+      CanBePlacedInDefaultSiteInstance(GetIsolationContext(), url_info.url,
+                                       site_info)) {
     site_info = SiteInfo::CreateForDefaultSiteInstance(
         IsCoopCoepCrossOriginIsolated(), CoopCoepCrossOriginIsolatedOrigin());
   }
@@ -1040,26 +1084,29 @@
   // where needed.  Eventually, GetSiteForURL should always require an
   // IsolationContext to be passed in, and this implementation should just
   // become SiteInstanceImpl::GetSiteForURL.
-  return SiteInstanceImpl::GetSiteForURL(IsolationContext(browser_context),
-                                         url);
+  return SiteInstanceImpl::GetSiteForURL(
+      IsolationContext(browser_context),
+      UrlInfo(url, false /* origin_requests_isolation */));
 }
 
 // static
 SiteInfo SiteInstanceImpl::ComputeSiteInfo(
     const IsolationContext& isolation_context,
-    const GURL& url,
+    const UrlInfo& url_info,
     bool is_coop_coep_cross_origin_isolated,
     const base::Optional<url::Origin>& cross_origin_isolated_origin) {
   // The call to GetSiteForURL() below is only allowed on the UI thread, due to
   // its possible use of effective urls.
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   // This function will expand as more information is included in SiteInfo.
-  bool is_origin_keyed = ChildProcessSecurityPolicyImpl::GetInstance()
-                             ->ShouldOriginGetOptInIsolation(
-                                 isolation_context, url::Origin::Create(url));
+  bool is_origin_keyed =
+      ChildProcessSecurityPolicyImpl::GetInstance()
+          ->ShouldOriginGetOptInIsolation(isolation_context,
+                                          url::Origin::Create(url_info.url),
+                                          url_info.origin_requests_isolation);
 
-  return SiteInfo(GetSiteForURL(isolation_context, url),
-                  DetermineProcessLockURL(isolation_context, url),
+  return SiteInfo(GetSiteForURL(isolation_context, url_info),
+                  DetermineProcessLockURL(isolation_context, url_info),
                   is_origin_keyed, is_coop_coep_cross_origin_isolated,
                   cross_origin_isolated_origin);
 }
@@ -1069,26 +1116,29 @@
     const IsolationContext& isolation_context,
     const GURL& url) {
   return ComputeSiteInfo(
-      isolation_context, url, false /* is_coop_coep_cross_origin_isolated */,
+      isolation_context, UrlInfo(url, false /* origin_requests_isolation */),
+      false /* is_coop_coep_cross_origin_isolated */,
       base::nullopt /* coop_coep_cross_origin_isolated_origin */);
 }
 
 // static
 ProcessLock SiteInstanceImpl::DetermineProcessLock(
     const IsolationContext& isolation_context,
-    const GURL& url,
+    const UrlInfo& url_info,
     bool is_coop_coep_cross_origin_isolated,
     base::Optional<url::Origin> coop_coep_cross_origin_isolated_origin) {
   if (BrowserThread::CurrentlyOn(BrowserThread::UI))
-    return ProcessLock(ComputeSiteInfo(isolation_context, url,
+    return ProcessLock(ComputeSiteInfo(isolation_context, url_info,
                                        is_coop_coep_cross_origin_isolated,
                                        coop_coep_cross_origin_isolated_origin));
 
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  GURL lock_url = DetermineProcessLockURL(isolation_context, url);
-  bool is_origin_keyed = ChildProcessSecurityPolicyImpl::GetInstance()
-                             ->ShouldOriginGetOptInIsolation(
-                                 isolation_context, url::Origin::Create(url));
+  GURL lock_url = DetermineProcessLockURL(isolation_context, url_info);
+  bool is_origin_keyed =
+      ChildProcessSecurityPolicyImpl::GetInstance()
+          ->ShouldOriginGetOptInIsolation(isolation_context,
+                                          url::Origin::Create(url_info.url),
+                                          url_info.origin_requests_isolation);
   // In the SiteInfo constructor below we pass the lock url as the site URL
   // also, assuming the IO-thread caller won't be looking at the site url.
   return ProcessLock(SiteInfo(lock_url, lock_url, is_origin_keyed,
@@ -1101,25 +1151,26 @@
 // removed.
 GURL SiteInstanceImpl::DetermineProcessLockURL(
     const IsolationContext& isolation_context,
-    const GURL& url) {
+    const UrlInfo& url_info) {
   // For the process lock URL, convert |url| to a site without resolving |url|
   // to an effective URL.
   return SiteInstanceImpl::GetSiteForURLInternal(
-      isolation_context, url, false /* should_use_effective_urls */);
+      isolation_context, url_info, false /* should_use_effective_urls */);
 }
 
 // static
 GURL SiteInstanceImpl::GetSiteForURL(const IsolationContext& isolation_context,
-                                     const GURL& real_url) {
-  return GetSiteForURLInternal(isolation_context, real_url,
+                                     const UrlInfo& real_url_info) {
+  return GetSiteForURLInternal(isolation_context, real_url_info,
                                true /* should_use_effective_urls */);
 }
 
 // static
 GURL SiteInstanceImpl::GetSiteForURLInternal(
     const IsolationContext& isolation_context,
-    const GURL& real_url,
+    const UrlInfo& real_url_info,
     bool should_use_effective_urls) {
+  const GURL& real_url = real_url_info.url;
   // Explicitly group chrome-error: URLs based on their host component.
   // These URLs are special because we want to group them like other URLs
   // with a host even though they are considered "no access" and
@@ -1160,8 +1211,9 @@
     // resolved prior to the isolated origin lookup.
     auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
     url::Origin isolated_origin;
-    if (policy->GetMatchingIsolatedOrigin(isolation_context, origin, site_url,
-                                          &isolated_origin)) {
+    if (policy->GetMatchingIsolatedOrigin(
+            isolation_context, origin, real_url_info.origin_requests_isolation,
+            site_url, &isolated_origin)) {
       return isolated_origin.GetURL();
     }
   } else {
@@ -1285,9 +1337,10 @@
 
   // Always require a dedicated process for isolated origins.
   auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
-  if (policy->IsIsolatedOrigin(isolation_context,
-                               url::Origin::Create(site_url)))
+  if (policy->IsIsolatedOrigin(isolation_context, url::Origin::Create(site_url),
+                               site_info.is_origin_keyed())) {
     return true;
+  }
 
   // Error pages in main frames do require isolation, however since this is
   // missing the context whether this is for a main frame or not, that part
diff --git a/content/browser/site_instance_impl.h b/content/browser/site_instance_impl.h
index c8ae97da..6b317de 100644
--- a/content/browser/site_instance_impl.h
+++ b/content/browser/site_instance_impl.h
@@ -170,6 +170,49 @@
 CONTENT_EXPORT std::ostream& operator<<(std::ostream& out,
                                         const SiteInfo& site_info);
 
+// This struct is used to package a GURL together with extra state required to
+// make SiteInstance/process allocation decisions, e.g. whether the url's origin
+// is requesting isolation as determined by response headers in the
+// corresponding navigation request. The extra state is generally most relevant
+// when navigation to the URL is in progress, since once placed into a
+// SiteInstance, the extra state will be available via SiteInfo. Otherwise, most
+// callsites requiring a UrlInfo can create with a GURL, specifying false for
+// |origin_requests_isolation|. Some examples of where passing false for
+// |origin_requests_isolation| is safe are:
+// * at DidCommitNavigation time, since at that point the SiteInstance has
+//   already been picked and the navigation can be considered finished,
+// * before a response is received (the only way to request isolation is via
+//   response headers), and
+// * outside of a navigation.
+//
+// If UrlInfo::origin_requests_isolation is false, that does *not* imply that
+// the url will not be origin-isolated, and vice versa.  The origin isolation
+// decision involves both response headers and consistency within a
+// BrowsingInstance, and once we decide on the isolation outcome for an origin,
+// it won't change for the lifetime of the BrowsingInstance.  To check whether
+// or not a frame is origin-isolated, see SiteInfo::is_origin_keyed() on its
+// SiteInstance.
+//
+// Note: it is not expected that this struct will be exposed in content/public.
+struct CONTENT_EXPORT UrlInfo {
+ public:
+  UrlInfo() = default;  // Needed for inclusion in SiteInstanceDescriptor.
+  UrlInfo(const GURL& url_in, bool origin_requests_isolation_in)
+      : url(url_in), origin_requests_isolation(origin_requests_isolation_in) {}
+  static inline UrlInfo CreateForTesting(const GURL& url_in) {
+    // Used to convert GURL to UrlInfo in tests where opt-in isolation is not
+    // being tested.
+    return UrlInfo(url_in, false);
+  }
+
+  GURL url;
+  // This flag is only relevant (1) during a navigation request, (2) up to the
+  // point where the origin is placed into a SiteInstance, thus determining the
+  // opt-in isolation status of the origin. Other than these cases, this should
+  // be set to false.
+  bool origin_requests_isolation;
+};
+
 class CONTENT_EXPORT SiteInstanceImpl final : public SiteInstance,
                                               public RenderProcessHostObserver {
  public:
@@ -188,14 +231,19 @@
   // are on the SiteInstance::Create* methods with the same name.
   static scoped_refptr<SiteInstanceImpl> Create(
       BrowserContext* browser_context);
+  // |url_info| contains the GURL for which we want to create a SiteInstance,
+  // along with other state relevant to making process allocation decisions.
   // |is_coop_coep_cross_origin_isolated| is not exposed in content/public. It
   // sets the BrowsingInstance is_coop_coep_cross_origin_isolated_ property.
   // Once this property is set it cannot be changed and is used in process
   // allocation decisions.
-  static scoped_refptr<SiteInstanceImpl> CreateForURL(
+  // TODO(wjmaclean): absorb |is_coop_coep_cross_origin_isolated| and related
+  // parameters into UrlInfo.
+  static scoped_refptr<SiteInstanceImpl> CreateForUrlInfo(
       BrowserContext* browser_context,
-      const GURL& url,
-      bool is_coop_coep_cross_origin_isolated = false);
+      const UrlInfo& url_info,
+      bool is_coop_coep_cross_origin_isolated);
+
   static scoped_refptr<SiteInstanceImpl> CreateForGuest(
       content::BrowserContext* browser_context,
       const GURL& guest_site_url);
@@ -223,6 +271,14 @@
 
   static bool ShouldAssignSiteForURL(const GURL& url);
 
+  // Use this to get a related SiteInstance during navigations, where UrlInfo
+  // may be requesting opt-in isolation. Outside of navigations, callers just
+  // looking up an existing SiteInstance based on a GURL can use
+  // GetRelatedSiteInstance (overridden from SiteInstance).
+  scoped_refptr<SiteInstanceImpl> GetRelatedSiteInstanceImpl(
+      const UrlInfo& url_info);
+  bool IsSameSiteWithURLInfo(const UrlInfo& url_info);
+
   // SiteInstance interface overrides.
   int32_t GetId() override;
   int32_t GetBrowsingInstanceId() override;
@@ -303,8 +359,9 @@
   // most callers should use that API.
   //
   // Returns true if navigating a frame with (|last_successful_url| and
-  // |last_committed_origin|) to |dest_url| should stay in the same SiteInstance
-  // to preserve scripting relationships.
+  // |last_committed_origin|) to |dest_url_info| should stay in the same
+  // SiteInstance to preserve scripting relationships. |dest_url_info| carries
+  // additional state, e.g. if the destination url requests origin isolation.
   //
   // |for_main_frame| is set to true if the caller is interested in an
   // answer for a main frame. This is set to false for subframe navigations.
@@ -313,7 +370,7 @@
   bool IsNavigationSameSite(const GURL& last_successful_url,
                             const url::Origin last_committed_origin,
                             bool for_main_frame,
-                            const GURL& dest_url);
+                            const UrlInfo& dest_url_info);
 
   // SiteInfo related functions.
 
@@ -347,24 +404,24 @@
   // Note: eventually this function will replace GetSiteForURL().
   static SiteInfo ComputeSiteInfo(
       const IsolationContext& isolation_context,
-      const GURL& url,
+      const UrlInfo& url_info,
       bool is_coop_coep_cross_origin_isolated,
       const base::Optional<url::Origin>& cross_origin_isolated_origin);
 
   // Helper method for tests that don't trigger special COOP/COEP
-  // functionality.
+  // functionality, or test opt-in origin isolation.
   static SiteInfo ComputeSiteInfoForTesting(
       const IsolationContext& isolation_context,
       const GURL& url);
 
-  // Returns the site for the given URL, which includes only the scheme and
+  // Returns the site for the given UrlInfo, which includes only the scheme and
   // registered domain.  Returns an empty GURL if the URL has no host.
   // |url| will be resolved to an effective URL (via
   // ContentBrowserClient::GetEffectiveURL()) before determining the site.
   // NOTE: This function will soon be removed, and replaced by
   // ComputeSiteInfo(). New code should use that function instead.
   static GURL GetSiteForURL(const IsolationContext& isolation_context,
-                            const GURL& url);
+                            const UrlInfo& url_info);
 
   // Returns the site of a given |origin|.  Unlike GetSiteForURL(), this does
   // not utilize effective URLs, isolated origins, or other special logic.  It
@@ -378,7 +435,7 @@
   // this will return a ProcessLock that doesn't consider effective URLs.
   static ProcessLock DetermineProcessLock(
       const IsolationContext& isolation_context,
-      const GURL& url,
+      const UrlInfo& url_info,
       bool is_coop_coep_cross_origin_isolated,
       base::Optional<url::Origin> coop_coep_cross_origin_isolated_origin);
 
@@ -386,14 +443,16 @@
   // This includes the scheme and registered domain, but not the port.  If the
   // URL does not have a valid registered domain, then the full hostname is
   // stored. This method does not convert this instance into a default
-  // SiteInstance, but the BrowsingInstance will call this method with |url|
-  // set to GetDefaultSiteURL(), when it is creating its default SiteInstance.
-  void SetSite(const GURL& url);
+  // SiteInstance, but the BrowsingInstance will call this method with
+  // |url_info| set to GetDefaultSiteURL(), when it is creating its default
+  // SiteInstance.
+  void SetSite(const UrlInfo& url_info);
 
   // Similar to SetSite(), but first attempts to convert this object to a
-  // default SiteInstance if |url| can be placed inside a default SiteInstance.
-  // If conversion is not possible, then the normal SetSite() logic is run.
-  void ConvertToDefaultOrSetSite(const GURL& url);
+  // default SiteInstance if |url_info| can be placed inside a default
+  // SiteInstance. If conversion is not possible, then the normal SetSite()
+  // logic is run.
+  void ConvertToDefaultOrSetSite(const UrlInfo& url_info);
 
   // Returns whether SetSite() has been called.
   bool HasSite() const;
@@ -404,9 +463,9 @@
   bool HasRelatedSiteInstance(const SiteInfo& site_info);
 
   // Returns whether this SiteInstance is compatible with and can host the given
-  // |url|. If not, the browser should force a SiteInstance swap when
-  // navigating to |url|.
-  bool IsSuitableForURL(const GURL& url);
+  // |url_info|. If not, the browser should force a SiteInstance swap when
+  // navigating to the URL in |url_info|.
+  bool IsSuitableForUrlInfo(const UrlInfo& url_info);
 
   // Increase the number of active frames in this SiteInstance. This is
   // increased when a frame is created.
@@ -508,9 +567,9 @@
   // associated with its default SiteInstance.
   bool IsSiteInDefaultSiteInstance(const GURL& site_url) const;
 
-  // Returns true if the SiteInfo for |url| matches the SiteInfo for this
+  // Returns true if the SiteInfo for |url_info| matches the SiteInfo for this
   // instance (i.e. GetSiteInfo()). Otherwise returns false.
-  bool DoesSiteInfoForURLMatch(const GURL& url);
+  bool DoesSiteInfoForURLMatch(const UrlInfo& url_info);
 
   // Adds |origin| as a non-isolated origin within this BrowsingInstance due to
   // an existing instance at the time of opt-in, so that future instances of it
@@ -560,11 +619,11 @@
   // "lock_to_site" lock.
   void LockProcessIfNeeded();
 
-  // Returns the URL to which a process should be locked for the given URL.
+  // Returns the URL to which a process should be locked for the given UrlInfo.
   // This is computed similarly to the site URL (see GetSiteForURL), but
   // without resolving effective URLs.
   static GURL DetermineProcessLockURL(const IsolationContext& isolation_context,
-                                      const GURL& url);
+                                      const UrlInfo& url_info);
 
   // If kProcessSharingWithStrictSiteInstances is enabled, this will check
   // whether both a site and a process have been assigned to this SiteInstance,
@@ -591,12 +650,12 @@
   void SetProcessInternal(RenderProcessHost* process);
 
   // Returns true if |original_url()| is the same site as
-  // |dest_url| or this object is a default SiteInstance and can be
-  // considered the same site as |dest_url|.
-  bool IsOriginalUrlSameSite(const GURL& dest_url,
+  // |dest_url_info| or this object is a default SiteInstance and can be
+  // considered the same site as |dest_url_info|.
+  bool IsOriginalUrlSameSite(const UrlInfo& dest_url_info,
                              bool should_compare_effective_urls);
 
-  // Return whether both URLs must share a process to preserve script
+  // Return whether both UrlInfos must share a process to preserve script
   // relationships.  The decision is based on a variety of factors such as
   // the registered domain of the URLs (google.com, bbc.co.uk), the scheme
   // (https, http), and isolated origins.  Note that if the destination is a
@@ -610,17 +669,17 @@
   // apps. Most code outside this class should call
   // RenderFrameHostImpl::IsNavigationSameSite() instead.
   static bool IsSameSite(const IsolationContext& isolation_context,
-                         const GURL& src_url,
-                         const GURL& dest_url,
+                         const UrlInfo& src_url_info,
+                         const UrlInfo& dest_url_info,
                          bool should_compare_effective_urls);
 
-  // Returns the site for the given URL, which includes only the scheme and
-  // registered domain.  Returns an empty GURL if the URL has no host.
+  // Returns the site for the given UrlInfo, which includes only the scheme and
+  // registered domain.  Returns an empty GURL if the UrlInfo has no host.
   // |should_use_effective_urls| specifies whether to resolve |url| to an
   // effective URL (via ContentBrowserClient::GetEffectiveURL()) before
   // determining the site.
   static GURL GetSiteForURLInternal(const IsolationContext& isolation_context,
-                                    const GURL& url,
+                                    const UrlInfo& url,
                                     bool should_use_effective_urls);
 
   // True if |url| resolves to an effective URL that is different from |url|.
diff --git a/content/browser/site_instance_impl_unittest.cc b/content/browser/site_instance_impl_unittest.cc
index dfe9e43..c6fe1b1 100644
--- a/content/browser/site_instance_impl_unittest.cc
+++ b/content/browser/site_instance_impl_unittest.cc
@@ -132,6 +132,12 @@
     url::AddStandardScheme(kCustomStandardScheme, url::SCHEME_WITH_HOST);
   }
 
+  GURL GetSiteForURL(const IsolationContext& isolation_context,
+                     const GURL& url) {
+    return SiteInstanceImpl::GetSiteForURL(
+        isolation_context, UrlInfo(url, false /* origin_requests_isolation */));
+  }
+
   void SetUp() override {
     old_browser_client_ = SetBrowserClientForTesting(&browser_client_);
     RenderProcessHostImpl::set_render_process_host_factory_for_testing(
@@ -176,8 +182,8 @@
     // globally isolated origins.
     IsolationContext isolation_context(&context_);
     auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
-    return policy->IsIsolatedOrigin(isolation_context,
-                                    url::Origin::Create(url));
+    return policy->IsIsolatedOrigin(isolation_context, url::Origin::Create(url),
+                                    false /* origin_requests_isolation */);
   }
 
   BrowserContext* context() { return &context_; }
@@ -190,8 +196,11 @@
   static bool IsSameSite(BrowserContext* context,
                          const GURL& url1,
                          const GURL& url2) {
-    return SiteInstanceImpl::IsSameSite(IsolationContext(context), url1, url2,
-                                        /*should_compare_effective_urls=*/true);
+    return SiteInstanceImpl::IsSameSite(
+        IsolationContext(context),
+        UrlInfo(url1, false /* origin_requests_isolation */),
+        UrlInfo(url2, false /* origin_requests_isolation */),
+        /*should_compare_effective_urls=*/true);
   }
 
  private:
@@ -437,13 +446,14 @@
 
   // Ensure that default SiteInstances are deleted when all references to them
   // are gone.
-  auto site_instance =
-      SiteInstanceImpl::CreateForURL(&browser_context, GURL("http://foo.com"));
+  auto site_instance = SiteInstanceImpl::CreateForUrlInfo(
+      &browser_context, UrlInfo::CreateForTesting(GURL("http://foo.com")),
+      false /* is_coop_coep_cross_origin_isolated */);
   if (AreDefaultSiteInstancesEnabled()) {
     EXPECT_TRUE(site_instance->IsDefaultSiteInstance());
   } else {
     // TODO(958060): Remove the creation of this second instance once
-    // CreateForURL() starts returning a default SiteInstance without
+    // CreateForUrlInfo() starts returning a default SiteInstance without
     // the need to specify a command-line flag.
     EXPECT_FALSE(site_instance->IsDefaultSiteInstance());
     auto related_instance =
@@ -490,7 +500,8 @@
   EXPECT_FALSE(instance->HasSite());
   EXPECT_TRUE(instance->GetSiteURL().is_empty());
 
-  instance->SetSite(GURL("http://www.google.com/index.html"));
+  instance->SetSite(
+      UrlInfo::CreateForTesting(GURL("http://www.google.com/index.html")));
   EXPECT_EQ(GURL("http://google.com"), instance->GetSiteURL());
 
   EXPECT_TRUE(instance->HasSite());
@@ -639,11 +650,13 @@
   // (foo.com).
   {
     GURL site_url = SiteInstanceImpl::GetSiteForURLInternal(
-        isolation_context, test_url, false /* use_effective_urls */);
+        isolation_context, UrlInfo::CreateForTesting(test_url),
+        false /* use_effective_urls */);
     EXPECT_EQ(nonapp_site_url, site_url);
 
     site_url = SiteInstanceImpl::GetSiteForURLInternal(
-        isolation_context, test_url, true /* use_effective_urls */);
+        isolation_context, UrlInfo::CreateForTesting(test_url),
+        true /* use_effective_urls */);
     EXPECT_EQ(app_url, site_url);
   }
 
@@ -656,7 +669,9 @@
   // New SiteInstance in a new BrowsingInstance with a predetermined URL.
   {
     scoped_refptr<SiteInstanceImpl> site_instance =
-        SiteInstanceImpl::CreateForURL(browser_context.get(), test_url);
+        SiteInstanceImpl::CreateForUrlInfo(
+            browser_context.get(), UrlInfo::CreateForTesting(test_url),
+            false /* is_coop_coep_cross_origin_isolated */);
     EXPECT_EQ(expected_site_info, site_instance->GetSiteInfo());
   }
 
@@ -664,8 +679,10 @@
   // predetermined URL.
   {
     scoped_refptr<SiteInstanceImpl> bar_site_instance =
-        SiteInstanceImpl::CreateForURL(browser_context.get(),
-                                       GURL("https://bar.com/"));
+        SiteInstanceImpl::CreateForUrlInfo(
+            browser_context.get(),
+            UrlInfo::CreateForTesting(GURL("https://bar.com/")),
+            false /* is_coop_coep_cross_origin_isolated */);
     scoped_refptr<SiteInstance> site_instance =
         bar_site_instance->GetRelatedSiteInstance(test_url);
     auto* site_instance_impl =
@@ -678,7 +695,7 @@
     scoped_refptr<SiteInstanceImpl> site_instance =
         SiteInstanceImpl::Create(browser_context.get());
     EXPECT_FALSE(site_instance->HasSite());
-    site_instance->SetSite(test_url);
+    site_instance->SetSite(UrlInfo::CreateForTesting(test_url));
     EXPECT_EQ(expected_site_info, site_instance->GetSiteInfo());
   }
 
@@ -763,14 +780,16 @@
 
   const GURL url_a1("http://www.google.com/1.html");
   scoped_refptr<SiteInstanceImpl> site_instance_a1(
-      browsing_instance->GetSiteInstanceForURL(url_a1, false));
+      browsing_instance->GetSiteInstanceForURL(
+          UrlInfo::CreateForTesting(url_a1), false));
   EXPECT_TRUE(site_instance_a1.get() != nullptr);
 
   // A separate site should create a separate SiteInstance.
   const GURL url_b1("http://www.yahoo.com/");
   scoped_refptr<SiteInstanceImpl> site_instance_b1(
 
-      browsing_instance->GetSiteInstanceForURL(url_b1, false));
+      browsing_instance->GetSiteInstanceForURL(
+          UrlInfo::CreateForTesting(url_b1), false));
   EXPECT_NE(site_instance_a1.get(), site_instance_b1.get());
   EXPECT_TRUE(site_instance_a1->IsRelatedSiteInstance(site_instance_b1.get()));
 
@@ -782,7 +801,8 @@
   // A second visit to the original site should return the same SiteInstance.
   const GURL url_a2("http://www.google.com/2.html");
   EXPECT_EQ(site_instance_a1.get(),
-            browsing_instance->GetSiteInstanceForURL(url_a2, false));
+            browsing_instance->GetSiteInstanceForURL(
+                UrlInfo::CreateForTesting(url_a2), false));
   EXPECT_EQ(site_instance_a1.get(),
             site_instance_a1->GetRelatedSiteInstance(url_a2));
 
@@ -793,7 +813,8 @@
       base::nullopt /* coop_coep_cross_origin_isolated_origin */);
   // Ensure the new SiteInstance is ref counted so that it gets deleted.
   scoped_refptr<SiteInstanceImpl> site_instance_a2_2(
-      browsing_instance2->GetSiteInstanceForURL(url_a2, false));
+      browsing_instance2->GetSiteInstanceForURL(
+          UrlInfo::CreateForTesting(url_a2), false));
   EXPECT_NE(site_instance_a1.get(), site_instance_a2_2.get());
   EXPECT_FALSE(
       site_instance_a1->IsRelatedSiteInstance(site_instance_a2_2.get()));
@@ -837,14 +858,16 @@
 
   const GURL url_a1("http://www.google.com/1.html");
   scoped_refptr<SiteInstanceImpl> site_instance_a1(
-      browsing_instance->GetSiteInstanceForURL(url_a1, false));
+      browsing_instance->GetSiteInstanceForURL(
+          UrlInfo::CreateForTesting(url_a1), false));
   EXPECT_TRUE(site_instance_a1.get() != nullptr);
   std::unique_ptr<RenderProcessHost> process_a1(site_instance_a1->GetProcess());
 
   // A separate site should create a separate SiteInstance.
   const GURL url_b1("http://www.yahoo.com/");
   scoped_refptr<SiteInstanceImpl> site_instance_b1(
-      browsing_instance->GetSiteInstanceForURL(url_b1, false));
+      browsing_instance->GetSiteInstanceForURL(
+          UrlInfo::CreateForTesting(url_b1), false));
   EXPECT_NE(site_instance_a1.get(), site_instance_b1.get());
   EXPECT_TRUE(site_instance_a1->IsRelatedSiteInstance(site_instance_b1.get()));
 
@@ -856,7 +879,8 @@
   // A second visit to the original site should return the same SiteInstance.
   const GURL url_a2("http://www.google.com/2.html");
   EXPECT_EQ(site_instance_a1.get(),
-            browsing_instance->GetSiteInstanceForURL(url_a2, false));
+            browsing_instance->GetSiteInstanceForURL(
+                UrlInfo::CreateForTesting(url_a2), false));
   EXPECT_EQ(site_instance_a1.get(),
             site_instance_a1->GetRelatedSiteInstance(url_a2));
 
@@ -866,7 +890,8 @@
       browser_context.get(), false /* is_coop_coep_cross_origin_isolated */,
       base::nullopt /* coop_coep_cross_origin_isolated_origin */);
   scoped_refptr<SiteInstanceImpl> site_instance_a1_2(
-      browsing_instance2->GetSiteInstanceForURL(url_a1, false));
+      browsing_instance2->GetSiteInstanceForURL(
+          UrlInfo::CreateForTesting(url_a1), false));
   EXPECT_TRUE(site_instance_a1.get() != nullptr);
   EXPECT_NE(site_instance_a1.get(), site_instance_a1_2.get());
   EXPECT_EQ(process_a1.get(), site_instance_a1_2->GetProcess());
@@ -879,7 +904,8 @@
       browser_context2.get(), false /* is_coop_coep_cross_origin_isolated */,
       base::nullopt /* coop_coep_cross_origin_isolated_origin */);
   scoped_refptr<SiteInstanceImpl> site_instance_a2_3(
-      browsing_instance3->GetSiteInstanceForURL(url_a2, false));
+      browsing_instance3->GetSiteInstanceForURL(
+          UrlInfo::CreateForTesting(url_a2), false));
   EXPECT_TRUE(site_instance_a2_3.get() != nullptr);
   std::unique_ptr<RenderProcessHost> process_a2_3(
       site_instance_a2_3->GetProcess());
@@ -908,9 +934,9 @@
   DrainMessageLoop();
 }
 
-// Test to ensure that IsSuitableForURL behaves properly for different types of
-// URLs.
-TEST_F(SiteInstanceTest, IsSuitableForURL) {
+// Test to ensure that IsSuitableForUrlInfo behaves properly for different types
+// of URLs.
+TEST_F(SiteInstanceTest, IsSuitableForUrlInfo) {
   std::unique_ptr<TestBrowserContext> browser_context(new TestBrowserContext());
   std::unique_ptr<RenderProcessHost> host;
   scoped_refptr<SiteInstanceImpl> instance(
@@ -921,9 +947,10 @@
 
   // Check prior to assigning a site or process to the instance, which is
   // expected to return false to allow the SiteInstance to be used for anything.
-  EXPECT_TRUE(instance->IsSuitableForURL(GURL("http://google.com")));
+  EXPECT_TRUE(instance->IsSuitableForUrlInfo(
+      UrlInfo::CreateForTesting(GURL("http://google.com"))));
 
-  instance->SetSite(GURL("http://evernote.com/"));
+  instance->SetSite(UrlInfo::CreateForTesting(GURL("http://evernote.com/")));
   EXPECT_TRUE(instance->HasSite());
 
   // The call to GetProcess actually creates a new real process, which works
@@ -932,17 +959,19 @@
   EXPECT_TRUE(host.get() != nullptr);
   EXPECT_TRUE(instance->HasProcess());
 
-  EXPECT_TRUE(instance->IsSuitableForURL(GURL("http://evernote.com")));
-  EXPECT_TRUE(instance->IsSuitableForURL(
-      GURL("javascript:alert(document.location.href);")));
+  EXPECT_TRUE(instance->IsSuitableForUrlInfo(
+      UrlInfo::CreateForTesting(GURL("http://evernote.com"))));
+  EXPECT_TRUE(instance->IsSuitableForUrlInfo(UrlInfo::CreateForTesting(
+      GURL("javascript:alert(document.location.href);"))));
 
-  EXPECT_FALSE(instance->IsSuitableForURL(GetWebUIURL(kChromeUIGpuHost)));
+  EXPECT_FALSE(instance->IsSuitableForUrlInfo(
+      UrlInfo::CreateForTesting(GetWebUIURL(kChromeUIGpuHost))));
 
   // Test that WebUI SiteInstances reject normal web URLs.
   const GURL webui_url(GetWebUIURL(kChromeUIGpuHost));
   scoped_refptr<SiteInstanceImpl> webui_instance(
       SiteInstanceImpl::Create(browser_context.get()));
-  webui_instance->SetSite(webui_url);
+  webui_instance->SetSite(UrlInfo::CreateForTesting(webui_url));
   std::unique_ptr<RenderProcessHost> webui_host(webui_instance->GetProcess());
 
   // Simulate granting WebUI bindings for the process.
@@ -950,25 +979,30 @@
       webui_host->GetID(), BINDINGS_POLICY_WEB_UI);
 
   EXPECT_TRUE(webui_instance->HasProcess());
-  EXPECT_TRUE(webui_instance->IsSuitableForURL(webui_url));
-  EXPECT_FALSE(webui_instance->IsSuitableForURL(GURL("http://google.com")));
-  EXPECT_FALSE(webui_instance->IsSuitableForURL(GURL("http://gpu")));
+  EXPECT_TRUE(webui_instance->IsSuitableForUrlInfo(
+      UrlInfo::CreateForTesting(webui_url)));
+  EXPECT_FALSE(webui_instance->IsSuitableForUrlInfo(
+      UrlInfo::CreateForTesting(GURL("http://google.com"))));
+  EXPECT_FALSE(webui_instance->IsSuitableForUrlInfo(
+      UrlInfo::CreateForTesting(GURL("http://gpu"))));
 
   // WebUI uses process-per-site, so another instance will use the same process
-  // even if we haven't called GetProcess yet.  Make sure IsSuitableForURL
+  // even if we haven't called GetProcess yet.  Make sure IsSuitableForUrlInfo
   // doesn't crash (http://crbug.com/137070).
   scoped_refptr<SiteInstanceImpl> webui_instance2(
       SiteInstanceImpl::Create(browser_context.get()));
-  webui_instance2->SetSite(webui_url);
-  EXPECT_TRUE(webui_instance2->IsSuitableForURL(webui_url));
-  EXPECT_FALSE(webui_instance2->IsSuitableForURL(GURL("http://google.com")));
+  webui_instance2->SetSite(UrlInfo::CreateForTesting(webui_url));
+  EXPECT_TRUE(webui_instance2->IsSuitableForUrlInfo(
+      UrlInfo::CreateForTesting(webui_url)));
+  EXPECT_FALSE(webui_instance2->IsSuitableForUrlInfo(
+      UrlInfo::CreateForTesting(GURL("http://google.com"))));
 
   DrainMessageLoop();
 }
 
-// Test to ensure that IsSuitableForURL behaves properly even when
+// Test to ensure that IsSuitableForUrlInfo behaves properly even when
 // --site-per-process is used (http://crbug.com/160671).
-TEST_F(SiteInstanceTest, IsSuitableForURLInSitePerProcess) {
+TEST_F(SiteInstanceTest, IsSuitableForUrlInfoInSitePerProcess) {
   IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
 
   std::unique_ptr<TestBrowserContext> browser_context(new TestBrowserContext());
@@ -978,9 +1012,10 @@
 
   // Check prior to assigning a site or process to the instance, which is
   // expected to return false to allow the SiteInstance to be used for anything.
-  EXPECT_TRUE(instance->IsSuitableForURL(GURL("http://google.com")));
+  EXPECT_TRUE(instance->IsSuitableForUrlInfo(
+      UrlInfo::CreateForTesting(GURL("http://google.com"))));
 
-  instance->SetSite(GURL("http://evernote.com/"));
+  instance->SetSite(UrlInfo::CreateForTesting(GURL("http://evernote.com/")));
   EXPECT_TRUE(instance->HasSite());
 
   // The call to GetProcess actually creates a new real process, which works
@@ -989,11 +1024,13 @@
   EXPECT_TRUE(host.get() != nullptr);
   EXPECT_TRUE(instance->HasProcess());
 
-  EXPECT_TRUE(instance->IsSuitableForURL(GURL("http://evernote.com")));
-  EXPECT_TRUE(instance->IsSuitableForURL(
-      GURL("javascript:alert(document.location.href);")));
+  EXPECT_TRUE(instance->IsSuitableForUrlInfo(
+      UrlInfo::CreateForTesting(GURL("http://evernote.com"))));
+  EXPECT_TRUE(instance->IsSuitableForUrlInfo(UrlInfo::CreateForTesting(
+      GURL("javascript:alert(document.location.href);"))));
 
-  EXPECT_FALSE(instance->IsSuitableForURL(GetWebUIURL(kChromeUIGpuHost)));
+  EXPECT_FALSE(instance->IsSuitableForUrlInfo(
+      UrlInfo::CreateForTesting(GetWebUIURL(kChromeUIGpuHost))));
 
   DrainMessageLoop();
 }
@@ -1013,7 +1050,7 @@
   // Simulate navigating to a WebUI URL in a process that does not have WebUI
   // bindings.  This already requires bypassing security checks.
   const GURL webui_url(GetWebUIURL(kChromeUIGpuHost));
-  instance->SetSite(webui_url);
+  instance->SetSite(UrlInfo::CreateForTesting(webui_url));
   EXPECT_TRUE(instance->HasSite());
 
   // The call to GetProcess actually creates a new real process.
@@ -1022,14 +1059,15 @@
   EXPECT_TRUE(instance->HasProcess());
 
   // Without bindings, this should look like the wrong process.
-  EXPECT_FALSE(instance->IsSuitableForURL(webui_url));
+  EXPECT_FALSE(
+      instance->IsSuitableForUrlInfo(UrlInfo::CreateForTesting(webui_url)));
 
   // WebUI uses process-per-site, so another instance would normally use the
   // same process.  Make sure it doesn't use the same process if the bindings
   // are missing.
   scoped_refptr<SiteInstanceImpl> instance2(
       SiteInstanceImpl::Create(browser_context.get()));
-  instance2->SetSite(webui_url);
+  instance2->SetSite(UrlInfo::CreateForTesting(webui_url));
   host2.reset(instance2->GetProcess());
   EXPECT_TRUE(host2.get() != nullptr);
   EXPECT_TRUE(instance2->HasProcess());
@@ -1048,7 +1086,7 @@
   scoped_refptr<SiteInstanceImpl> instance(
       SiteInstanceImpl::Create(browser_context.get()));
 
-  instance->SetSite(GURL());
+  instance->SetSite(UrlInfo());
   EXPECT_TRUE(instance->HasSite());
   EXPECT_TRUE(instance->GetSiteURL().is_empty());
   host.reset(instance->GetProcess());
@@ -1111,33 +1149,26 @@
   IsolationContext isolation_context(&browser_context);
 
   EXPECT_FALSE(IsSameSite(context(), isolated1_foo_url, isolated2_foo_url));
-  EXPECT_NE(
-      SiteInstanceImpl::GetSiteForURL(isolation_context, isolated1_foo_url),
-      SiteInstanceImpl::GetSiteForURL(isolation_context, isolated2_foo_url));
+  EXPECT_NE(GetSiteForURL(isolation_context, isolated1_foo_url),
+            GetSiteForURL(isolation_context, isolated2_foo_url));
 
   // A bunch of special cases of origins.
   GURL secure_foo("https://foo.com");
-  EXPECT_EQ(SiteInstanceImpl::GetSiteForURL(isolation_context, secure_foo),
-            secure_foo);
+  EXPECT_EQ(GetSiteForURL(isolation_context, secure_foo), secure_foo);
   GURL foo_with_port("http://foo.com:1234");
-  EXPECT_EQ(SiteInstanceImpl::GetSiteForURL(isolation_context, foo_with_port),
-            foo_with_port);
+  EXPECT_EQ(GetSiteForURL(isolation_context, foo_with_port), foo_with_port);
   GURL local_host("http://localhost");
-  EXPECT_EQ(SiteInstanceImpl::GetSiteForURL(isolation_context, local_host),
-            local_host);
+  EXPECT_EQ(GetSiteForURL(isolation_context, local_host), local_host);
   GURL ip_local_host("http://127.0.0.1");
-  EXPECT_EQ(SiteInstanceImpl::GetSiteForURL(isolation_context, ip_local_host),
-            ip_local_host);
+  EXPECT_EQ(GetSiteForURL(isolation_context, ip_local_host), ip_local_host);
 
   // The following should not get origin-specific SiteInstances, as they don't
   // have valid hosts.
   GURL about_url("about:flags");
-  EXPECT_NE(SiteInstanceImpl::GetSiteForURL(isolation_context, about_url),
-            about_url);
+  EXPECT_NE(GetSiteForURL(isolation_context, about_url), about_url);
 
   GURL file_url("file:///home/user/foo");
-  EXPECT_NE(SiteInstanceImpl::GetSiteForURL(isolation_context, file_url),
-            file_url);
+  EXPECT_NE(GetSiteForURL(isolation_context, file_url), file_url);
 }
 
 TEST_F(SiteInstanceTest, IsolatedOrigins) {
@@ -1187,18 +1218,17 @@
   // The site URL for an isolated origin should be the full origin rather than
   // eTLD+1.
   IsolationContext isolation_context(context());
-  EXPECT_EQ(isolated_foo_url, SiteInstanceImpl::GetSiteForURL(
-                                  isolation_context, isolated_foo_url));
   EXPECT_EQ(isolated_foo_url,
-            SiteInstanceImpl::GetSiteForURL(
-                isolation_context, GURL("http://isolated.foo.com:12345")));
-  EXPECT_EQ(isolated_bar_url, SiteInstanceImpl::GetSiteForURL(
-                                  isolation_context, isolated_bar_url));
-  EXPECT_EQ(isolated_foo_url, SiteInstanceImpl::GetSiteForURL(
-                                  isolation_context, isolated_blob_foo_url));
+            GetSiteForURL(isolation_context, isolated_foo_url));
+  EXPECT_EQ(
+      isolated_foo_url,
+      GetSiteForURL(isolation_context, GURL("http://isolated.foo.com:12345")));
+  EXPECT_EQ(isolated_bar_url,
+            GetSiteForURL(isolation_context, isolated_bar_url));
   EXPECT_EQ(isolated_foo_url,
-            SiteInstanceImpl::GetSiteForURL(isolation_context,
-                                            isolated_filesystem_foo_url));
+            GetSiteForURL(isolation_context, isolated_blob_foo_url));
+  EXPECT_EQ(isolated_foo_url,
+            GetSiteForURL(isolation_context, isolated_filesystem_foo_url));
 
   // Isolated origins always require a dedicated process.
   EXPECT_TRUE(
@@ -1239,10 +1269,10 @@
   EXPECT_TRUE(IsIsolatedOrigin(isolated_foo_with_port));
 
   IsolationContext isolation_context(context());
-  EXPECT_EQ(isolated_foo_url, SiteInstanceImpl::GetSiteForURL(
-                                  isolation_context, isolated_foo_url));
-  EXPECT_EQ(isolated_foo_url, SiteInstanceImpl::GetSiteForURL(
-                                  isolation_context, isolated_foo_with_port));
+  EXPECT_EQ(isolated_foo_url,
+            GetSiteForURL(isolation_context, isolated_foo_url));
+  EXPECT_EQ(isolated_foo_url,
+            GetSiteForURL(isolation_context, isolated_foo_with_port));
 
   // Cleanup.
   policy->RemoveIsolatedOriginForTesting(url::Origin::Create(isolated_foo_url));
@@ -1313,8 +1343,7 @@
   // should use the isolated origin's host and not its own host as the site
   // URL.
   IsolationContext isolation_context(context());
-  EXPECT_EQ(isolated_url, SiteInstanceImpl::GetSiteForURL(isolation_context,
-                                                          foo_isolated_url));
+  EXPECT_EQ(isolated_url, GetSiteForURL(isolation_context, foo_isolated_url));
 
   EXPECT_TRUE(
       DoesURLRequireDedicatedProcess(isolation_context, foo_isolated_url));
@@ -1350,14 +1379,13 @@
   EXPECT_TRUE(IsIsolatedOrigin(baz_isolated_foo_url));
 
   IsolationContext isolation_context(context());
-  EXPECT_EQ(foo_url,
-            SiteInstanceImpl::GetSiteForURL(isolation_context, foo_url));
-  EXPECT_EQ(isolated_foo_url, SiteInstanceImpl::GetSiteForURL(
-                                  isolation_context, isolated_foo_url));
-  EXPECT_EQ(isolated_foo_url, SiteInstanceImpl::GetSiteForURL(
-                                  isolation_context, bar_isolated_foo_url));
-  EXPECT_EQ(isolated_foo_url, SiteInstanceImpl::GetSiteForURL(
-                                  isolation_context, baz_isolated_foo_url));
+  EXPECT_EQ(foo_url, GetSiteForURL(isolation_context, foo_url));
+  EXPECT_EQ(isolated_foo_url,
+            GetSiteForURL(isolation_context, isolated_foo_url));
+  EXPECT_EQ(isolated_foo_url,
+            GetSiteForURL(isolation_context, bar_isolated_foo_url));
+  EXPECT_EQ(isolated_foo_url,
+            GetSiteForURL(isolation_context, baz_isolated_foo_url));
 
   if (!AreAllSitesIsolatedForTesting()) {
     EXPECT_FALSE(DoesURLRequireDedicatedProcess(isolation_context, foo_url));
@@ -1401,14 +1429,11 @@
   EXPECT_TRUE(IsIsolatedOrigin(baz_bar_foo_url));
   EXPECT_TRUE(IsIsolatedOrigin(qux_baz_bar_foo_url));
 
-  EXPECT_EQ(foo_url,
-            SiteInstanceImpl::GetSiteForURL(isolation_context, foo_url));
-  EXPECT_EQ(foo_url,
-            SiteInstanceImpl::GetSiteForURL(isolation_context, bar_foo_url));
-  EXPECT_EQ(baz_bar_foo_url, SiteInstanceImpl::GetSiteForURL(isolation_context,
-                                                             baz_bar_foo_url));
-  EXPECT_EQ(baz_bar_foo_url, SiteInstanceImpl::GetSiteForURL(
-                                 isolation_context, qux_baz_bar_foo_url));
+  EXPECT_EQ(foo_url, GetSiteForURL(isolation_context, foo_url));
+  EXPECT_EQ(foo_url, GetSiteForURL(isolation_context, bar_foo_url));
+  EXPECT_EQ(baz_bar_foo_url, GetSiteForURL(isolation_context, baz_bar_foo_url));
+  EXPECT_EQ(baz_bar_foo_url,
+            GetSiteForURL(isolation_context, qux_baz_bar_foo_url));
 
   EXPECT_TRUE(DoesURLRequireDedicatedProcess(isolation_context, foo_url));
   EXPECT_TRUE(DoesURLRequireDedicatedProcess(isolation_context, bar_foo_url));
@@ -1454,7 +1479,9 @@
   // |original_url|.
   {
     scoped_refptr<SiteInstanceImpl> site_instance =
-        SiteInstanceImpl::CreateForURL(browser_context.get(), original_url);
+        SiteInstanceImpl::CreateForUrlInfo(
+            browser_context.get(), UrlInfo::CreateForTesting(original_url),
+            false /* is_coop_coep_cross_origin_isolated */);
     EXPECT_EQ(expected_site_info, site_instance->GetSiteInfo());
     EXPECT_EQ(original_url, site_instance->original_url());
   }
@@ -1463,8 +1490,10 @@
   // predetermined URL.
   {
     scoped_refptr<SiteInstanceImpl> bar_site_instance =
-        SiteInstanceImpl::CreateForURL(browser_context.get(),
-                                       GURL("https://bar.com/"));
+        SiteInstanceImpl::CreateForUrlInfo(
+            browser_context.get(),
+            UrlInfo::CreateForTesting(GURL("https://bar.com/")),
+            false /* is_coop_coep_cross_origin_isolated */);
     scoped_refptr<SiteInstance> site_instance =
         bar_site_instance->GetRelatedSiteInstance(original_url);
     auto* site_instance_impl =
@@ -1479,7 +1508,7 @@
         SiteInstanceImpl::Create(browser_context.get());
     EXPECT_FALSE(site_instance->HasSite());
     EXPECT_TRUE(site_instance->original_url().is_empty());
-    site_instance->SetSite(original_url);
+    site_instance->SetSite(UrlInfo::CreateForTesting(original_url));
     EXPECT_EQ(expected_site_info, site_instance->GetSiteInfo());
     EXPECT_EQ(original_url, site_instance->original_url());
   }
@@ -1542,7 +1571,7 @@
   policy->RemoveStateForBrowserContext(*context());
 }
 
-TEST_F(SiteInstanceTest, CreateForURL) {
+TEST_F(SiteInstanceTest, CreateForUrlInfo) {
   class CustomBrowserClient : public EffectiveURLContentBrowserClient {
    public:
     CustomBrowserClient(const GURL& url_to_modify, const GURL& url_to_return)
@@ -1574,12 +1603,21 @@
   ChildProcessSecurityPolicyImpl::GetInstance()->AddIsolatedOrigins(
       {url::Origin::Create(kIsolatedUrl)}, IsolatedOriginSource::TEST);
 
-  auto instance1 = SiteInstanceImpl::CreateForURL(context(), kNonIsolatedUrl);
-  auto instance2 = SiteInstanceImpl::CreateForURL(context(), kIsolatedUrl);
-  auto instance3 = SiteInstanceImpl::CreateForURL(context(), kFileUrl);
-  auto instance4 =
-      SiteInstanceImpl::CreateForURL(context(), GURL(url::kAboutBlankURL));
-  auto instance5 = SiteInstanceImpl::CreateForURL(context(), kCustomUrl);
+  auto instance1 = SiteInstanceImpl::CreateForUrlInfo(
+      context(), UrlInfo::CreateForTesting(kNonIsolatedUrl),
+      false /* is_coop_coep_cross_origin_isolated */);
+  auto instance2 = SiteInstanceImpl::CreateForUrlInfo(
+      context(), UrlInfo::CreateForTesting(kIsolatedUrl),
+      false /* is_coop_coep_cross_origin_isolated */);
+  auto instance3 = SiteInstanceImpl::CreateForUrlInfo(
+      context(), UrlInfo::CreateForTesting(kFileUrl),
+      false /* is_coop_coep_cross_origin_isolated */);
+  auto instance4 = SiteInstanceImpl::CreateForUrlInfo(
+      context(), UrlInfo::CreateForTesting(GURL(url::kAboutBlankURL)),
+      false /* is_coop_coep_cross_origin_isolated */);
+  auto instance5 = SiteInstanceImpl::CreateForUrlInfo(
+      context(), UrlInfo::CreateForTesting(kCustomUrl),
+      false /* is_coop_coep_cross_origin_isolated */);
 
   if (AreDefaultSiteInstancesEnabled()) {
     EXPECT_TRUE(instance1->IsDefaultSiteInstance());
@@ -1587,17 +1625,20 @@
     EXPECT_FALSE(instance1->IsDefaultSiteInstance());
     EXPECT_EQ(kNonIsolatedUrl, instance1->GetSiteURL());
   }
-  EXPECT_TRUE(instance1->DoesSiteInfoForURLMatch(kNonIsolatedUrl));
+  EXPECT_TRUE(instance1->DoesSiteInfoForURLMatch(
+      UrlInfo::CreateForTesting(kNonIsolatedUrl)));
   EXPECT_TRUE(instance1->IsSameSiteWithURL(kNonIsolatedUrl));
 
   EXPECT_FALSE(instance2->IsDefaultSiteInstance());
   EXPECT_EQ(kIsolatedUrl, instance2->GetSiteURL());
-  EXPECT_TRUE(instance2->DoesSiteInfoForURLMatch(kIsolatedUrl));
+  EXPECT_TRUE(instance2->DoesSiteInfoForURLMatch(
+      UrlInfo::CreateForTesting(kIsolatedUrl)));
   EXPECT_TRUE(instance2->IsSameSiteWithURL(kIsolatedUrl));
 
   EXPECT_FALSE(instance3->IsDefaultSiteInstance());
   EXPECT_EQ(GURL("file:"), instance3->GetSiteURL());
-  EXPECT_TRUE(instance3->DoesSiteInfoForURLMatch(kFileUrl));
+  EXPECT_TRUE(
+      instance3->DoesSiteInfoForURLMatch(UrlInfo::CreateForTesting(kFileUrl)));
   // Not same site because file URL's don't have a host.
   EXPECT_FALSE(instance3->IsSameSiteWithURL(kFileUrl));
 
@@ -1606,7 +1647,8 @@
   // site URL will be set at a later time.
   EXPECT_FALSE(instance4->IsDefaultSiteInstance());
   EXPECT_FALSE(instance4->HasSite());
-  EXPECT_FALSE(instance4->DoesSiteInfoForURLMatch(GURL(url::kAboutBlankURL)));
+  EXPECT_FALSE(instance4->DoesSiteInfoForURLMatch(
+      UrlInfo::CreateForTesting(GURL(url::kAboutBlankURL))));
   EXPECT_FALSE(instance4->IsSameSiteWithURL(GURL(url::kAboutBlankURL)));
 
   // Test the standard effective URL case.
@@ -1618,7 +1660,8 @@
     EXPECT_EQ("custom-standard://custom/", instance5->GetSiteURL());
     EXPECT_EQ("http://foo.com/", instance5->GetSiteInfo().process_lock_url());
   }
-  EXPECT_TRUE(instance5->DoesSiteInfoForURLMatch(kCustomUrl));
+  EXPECT_TRUE(instance5->DoesSiteInfoForURLMatch(
+      UrlInfo::CreateForTesting(kCustomUrl)));
   EXPECT_TRUE(instance5->IsSameSiteWithURL(kCustomUrl));
 
   // Test the "do not assign site" case with an effective URL.
@@ -1628,16 +1671,20 @@
     // Verify that the default SiteInstance is no longer a site match
     // with |kCustomUrl| because this URL now requires a SiteInstance that
     // does not have its site set.
-    EXPECT_FALSE(instance5->DoesSiteInfoForURLMatch(kCustomUrl));
+    EXPECT_FALSE(instance5->DoesSiteInfoForURLMatch(
+        UrlInfo::CreateForTesting(kCustomUrl)));
     EXPECT_FALSE(instance5->IsSameSiteWithURL(kCustomUrl));
   }
 
   // Verify that |kCustomUrl| will always construct a SiteInstance without
   // a site set now.
-  auto instance6 = SiteInstanceImpl::CreateForURL(context(), kCustomUrl);
+  auto instance6 = SiteInstanceImpl::CreateForUrlInfo(
+      context(), UrlInfo::CreateForTesting(kCustomUrl),
+      false /* is_coop_coep_cross_origin_isolated */);
   EXPECT_FALSE(instance6->IsDefaultSiteInstance());
   EXPECT_FALSE(instance6->HasSite());
-  EXPECT_FALSE(instance6->DoesSiteInfoForURLMatch(kCustomUrl));
+  EXPECT_FALSE(instance6->DoesSiteInfoForURLMatch(
+      UrlInfo::CreateForTesting(kCustomUrl)));
   EXPECT_FALSE(instance6->IsSameSiteWithURL(kCustomUrl));
 
   SetBrowserClientForTesting(regular_client);
@@ -1646,10 +1693,12 @@
 TEST_F(SiteInstanceTest, CreateForGuest) {
   const GURL kGuestUrl(std::string(kGuestScheme) + "://abc123/path");
 
-  // Verify that a SiteInstance created with CreateForURL() is not considered
-  // a <webview> guest and has the path removed for the site URL like any other
-  // standard URL.
-  auto instance1 = SiteInstanceImpl::CreateForURL(context(), kGuestUrl);
+  // Verify that a SiteInstance created with CreateForUrlInfo() is not
+  // considered a <webview> guest and has the path removed for the site URL like
+  // any other standard URL.
+  auto instance1 = SiteInstanceImpl::CreateForUrlInfo(
+      context(), UrlInfo::CreateForTesting(kGuestUrl),
+      false /* is_coop_coep_cross_origin_isolated */);
   EXPECT_FALSE(instance1->IsGuest());
   if (AreAllSitesIsolatedForTesting()) {
     EXPECT_NE(kGuestUrl, instance1->GetSiteURL());
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index b76139eb..cecc4ef 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -14778,11 +14778,11 @@
   int process_id = root->current_frame_host()->GetProcess()->GetID();
   IsolationContext isolation_context(controller.GetBrowserContext());
   auto start_url_lock = SiteInstanceImpl::DetermineProcessLock(
-      isolation_context, start_url,
+      isolation_context, UrlInfo::CreateForTesting(start_url),
       false /* is_coop_coep_cross_origin_isolated */,
       base::nullopt /* coop_coep_cross_origin_isolated_origin */);
   auto another_url_lock = SiteInstanceImpl::DetermineProcessLock(
-      isolation_context, another_url,
+      isolation_context, UrlInfo::CreateForTesting(another_url),
       false /* is_coop_coep_cross_origin_isolated */,
       base::nullopt /* coop_coep_cross_origin_isolated_origin */);
   EXPECT_EQ(start_url_lock, policy->GetProcessLock(process_id));
diff --git a/content/browser/speech/tts_linux.cc b/content/browser/speech/tts_linux.cc
index afd36df9ed..7753f0599 100644
--- a/content/browser/speech/tts_linux.cc
+++ b/content/browser/speech/tts_linux.cc
@@ -12,7 +12,7 @@
 #include "base/command_line.h"
 #include "base/debug/leak_annotations.h"
 #include "base/macros.h"
-#include "base/memory/singleton.h"
+#include "base/no_destructor.h"
 #include "base/synchronization/lock.h"
 #include "base/task/thread_pool.h"
 #include "content/browser/speech/tts_platform_impl.h"
@@ -37,6 +37,9 @@
 
 class TtsPlatformImplLinux : public TtsPlatformImpl {
  public:
+  TtsPlatformImplLinux(const TtsPlatformImplLinux&) = delete;
+  TtsPlatformImplLinux& operator=(const TtsPlatformImplLinux&) = delete;
+
   bool PlatformImplAvailable() override;
   void Speak(int utterance_id,
              const std::string& utterance,
@@ -56,8 +59,8 @@
   static TtsPlatformImplLinux* GetInstance();
 
  private:
+  friend base::NoDestructor<TtsPlatformImplLinux>;
   TtsPlatformImplLinux();
-  ~TtsPlatformImplLinux() override;
 
   // Initiate the connection with the speech dispatcher.
   void Initialize();
@@ -89,23 +92,17 @@
 
   // These apply to the current utterance only.
   std::string utterance_;
-  int utterance_id_;
+  int utterance_id_ = 0;
 
   // Map a string composed of a voicename and module to the voicename. Used to
   // uniquely identify a voice across all available modules.
   std::unique_ptr<std::map<std::string, SPDChromeVoice>> all_native_voices_;
-
-  friend struct base::DefaultSingletonTraits<TtsPlatformImplLinux>;
-
-  base::WeakPtrFactory<TtsPlatformImplLinux> weak_factory_{this};
-
-  DISALLOW_COPY_AND_ASSIGN(TtsPlatformImplLinux);
 };
 
 // static
 SPDNotificationType TtsPlatformImplLinux::current_notification_ = SPD_EVENT_END;
 
-TtsPlatformImplLinux::TtsPlatformImplLinux() : utterance_id_(0) {
+TtsPlatformImplLinux::TtsPlatformImplLinux() {
   const base::CommandLine& command_line =
       *base::CommandLine::ForCurrentProcess();
   if (!command_line.HasSwitch(switches::kEnableSpeechDispatcher))
@@ -146,14 +143,6 @@
   libspeechd_loader_.spd_set_notification_on(conn_, SPD_RESUME);
 }
 
-TtsPlatformImplLinux::~TtsPlatformImplLinux() {
-  base::AutoLock lock(initialization_lock_);
-  if (conn_) {
-    libspeechd_loader_.spd_close(conn_);
-    conn_ = nullptr;
-  }
-}
-
 void TtsPlatformImplLinux::Reset() {
   base::AutoLock lock(initialization_lock_);
   if (conn_)
@@ -186,7 +175,7 @@
   // Parse SSML and process speech.
   TtsController::GetInstance()->StripSSML(
       utterance, base::BindOnce(&TtsPlatformImplLinux::ProcessSpeech,
-                                weak_factory_.GetWeakPtr(), utterance_id, lang,
+                                base::Unretained(this), utterance_id, lang,
                                 voice, params, std::move(on_speak_finished)));
 }
 
@@ -305,7 +294,6 @@
 }
 
 void TtsPlatformImplLinux::OnSpeechEvent(SPDNotificationType type) {
-  // hummmmmm
   TtsController* controller = TtsController::GetInstance();
   switch (type) {
     case SPD_EVENT_BEGIN:
@@ -374,9 +362,8 @@
 
 // static
 TtsPlatformImplLinux* TtsPlatformImplLinux::GetInstance() {
-  return base::Singleton<
-      TtsPlatformImplLinux,
-      base::LeakySingletonTraits<TtsPlatformImplLinux>>::get();
+  static base::NoDestructor<TtsPlatformImplLinux> tts_platform;
+  return tts_platform.get();
 }
 
 // static
diff --git a/content/browser/speech/tts_win.cc b/content/browser/speech/tts_win.cc
index a99e986..e79e9a2 100644
--- a/content/browser/speech/tts_win.cc
+++ b/content/browser/speech/tts_win.cc
@@ -12,7 +12,7 @@
 
 #include "base/bind.h"
 #include "base/macros.h"
-#include "base/memory/singleton.h"
+#include "base/no_destructor.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/utf_string_conversions.h"
@@ -34,6 +34,9 @@
 
 class TtsPlatformImplWin : public TtsPlatformImpl {
  public:
+  TtsPlatformImplWin(const TtsPlatformImplWin&) = delete;
+  TtsPlatformImplWin& operator=(const TtsPlatformImplWin&) = delete;
+
   bool PlatformImplAvailable() override { return true; }
 
   void Speak(int utterance_id,
@@ -59,8 +62,8 @@
   static void __stdcall SpeechEventCallback(WPARAM w_param, LPARAM l_param);
 
  private:
+  friend base::NoDestructor<TtsPlatformImplWin>;
   TtsPlatformImplWin();
-  ~TtsPlatformImplWin() override {}
 
   void OnSpeechEvent();
 
@@ -77,19 +80,13 @@
 
   // These apply to the current utterance only.
   std::wstring utterance_;
-  int utterance_id_;
-  int prefix_len_;
-  ULONG stream_number_;
-  int char_position_;
-  int char_length_;
-  bool paused_;
+  int utterance_id_ = 0;
+  int prefix_len_ = 0;
+  ULONG stream_number_ = 0u;
+  int char_position_ = 0;
+  int char_length_ = 0;
+  bool paused_ = false;
   std::string last_voice_name_;
-
-  friend struct base::DefaultSingletonTraits<TtsPlatformImplWin>;
-
-  base::WeakPtrFactory<TtsPlatformImplWin> weak_factory_{this};
-
-  DISALLOW_COPY_AND_ASSIGN(TtsPlatformImplWin);
 };
 
 // static
@@ -107,7 +104,7 @@
   // Parse SSML and process speech.
   TtsController::GetInstance()->StripSSML(
       utterance, base::BindOnce(&TtsPlatformImplWin::ProcessSpeech,
-                                weak_factory_.GetWeakPtr(), utterance_id, lang,
+                                base::Unretained(this), utterance_id, lang,
                                 voice, params, std::move(on_speak_finished)));
 }
 
@@ -333,12 +330,7 @@
   }
 }
 
-TtsPlatformImplWin::TtsPlatformImplWin()
-    : utterance_id_(0),
-      prefix_len_(0),
-      stream_number_(0),
-      char_position_(0),
-      paused_(false) {
+TtsPlatformImplWin::TtsPlatformImplWin() {
   ::CoCreateInstance(CLSID_SpVoice, nullptr, CLSCTX_ALL,
                      IID_PPV_ARGS(&speech_synthesizer_));
   if (speech_synthesizer_.Get()) {
@@ -354,8 +346,8 @@
 
 // static
 TtsPlatformImplWin* TtsPlatformImplWin::GetInstance() {
-  return base::Singleton<TtsPlatformImplWin,
-                         base::LeakySingletonTraits<TtsPlatformImplWin>>::get();
+  static base::NoDestructor<TtsPlatformImplWin> tts_platform;
+  return tts_platform.get();
 }
 
 // static
diff --git a/content/gpu/BUILD.gn b/content/gpu/BUILD.gn
index 44aa83dc..7e36d5a1 100644
--- a/content/gpu/BUILD.gn
+++ b/content/gpu/BUILD.gn
@@ -129,7 +129,7 @@
   }
 
   # Use DRI on desktop Linux builds.
-  if (current_cpu != "s390x" && current_cpu != "ppc64" && is_desktop_linux &&
+  if (current_cpu != "s390x" && current_cpu != "ppc64" && is_linux &&
       (!is_chromecast || is_cast_desktop_build)) {
     configs += [ "//build/config/linux/dri" ]
   }
diff --git a/content/public/test/content_browser_test_utils.cc b/content/public/test/content_browser_test_utils.cc
index d14bdbbd..5254ae4 100644
--- a/content/public/test/content_browser_test_utils.cc
+++ b/content/public/test/content_browser_test_utils.cc
@@ -235,9 +235,11 @@
       new_site_instance->IsRelatedSiteInstance(old_site_instance.get()));
   for (const url::Origin& origin : origins_to_isolate) {
     EXPECT_FALSE(policy->IsIsolatedOrigin(
-        old_site_instance->GetIsolationContext(), origin));
+        old_site_instance->GetIsolationContext(), origin,
+        false /* origin_requests_isolation */));
     EXPECT_TRUE(policy->IsIsolatedOrigin(
-        new_site_instance->GetIsolationContext(), origin));
+        new_site_instance->GetIsolationContext(), origin,
+        false /* origin_requests_isolation */));
   }
 }
 
diff --git a/content/test/data/accessibility/aria/aria-menuitemradio-expected-android.txt b/content/test/data/accessibility/aria/aria-menuitemradio-expected-android.txt
index 1b93ee55..6a9e169d 100644
--- a/content/test/data/accessibility/aria/aria-menuitemradio-expected-android.txt
+++ b/content/test/data/accessibility/aria/aria-menuitemradio-expected-android.txt
@@ -2,4 +2,4 @@
 ++android.view.View role_description='menu'
 ++++android.view.MenuItem role_description='radio button' checkable clickable name='Menu item 1'
 ++++android.view.MenuItem role_description='radio button' checkable checked clickable name='Menu item 2' item_index=1 row_index=1
-++++android.view.MenuItem role_description='radio button' checkable clickable name='Menu item 3' item_index=2 row_index=2
+++++android.view.MenuItem role_description='radio button' checkable clickable name='Menu item 3' state_description='Partially Checked' item_index=2 row_index=2
\ No newline at end of file
diff --git a/content/test/data/accessibility/aria/aria-tree-expected-android.txt b/content/test/data/accessibility/aria/aria-tree-expected-android.txt
index 9c11ee00..5c56669 100644
--- a/content/test/data/accessibility/aria/aria-tree-expected-android.txt
+++ b/content/test/data/accessibility/aria/aria-tree-expected-android.txt
@@ -1,6 +1,6 @@
 android.webkit.WebView focusable focused scrollable
 ++android.view.View role_description='tree' collection hierarchical item_count=2 row_count=2
-++++android.view.View role_description='tree item' checkable clickable collection_item name='Animals'
+++++android.view.View role_description='tree item' checkable clickable collection_item name='Animals' state_description='Partially Checked'
 ++++++android.view.View role_description='link' clickable focusable link name='Animals'
 ++++++++android.widget.TextView name='Animals'
 ++++++android.view.View
@@ -19,4 +19,4 @@
 ++++++++++++android.widget.TextView name='Wild'
 ++++android.view.View role_description='tree item' clickable collection_item name='Plants' item_index=1 row_index=1
 ++++++android.view.View role_description='link' clickable focusable link name='Plants'
-++++++++android.widget.TextView name='Plants'
+++++++++android.widget.TextView name='Plants'
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/generated-content-in-empty-page-expected-blink.txt b/content/test/data/accessibility/html/generated-content-in-empty-page-expected-blink.txt
new file mode 100644
index 0000000..c016f1d
--- /dev/null
+++ b/content/test/data/accessibility/html/generated-content-in-empty-page-expected-blink.txt
@@ -0,0 +1,7 @@
+rootWebArea
+++genericContainer ignored
+++++staticText name='"'
+++++++inlineTextBox name='"'
+++++genericContainer
+++++++staticText name='''
+++++++++inlineTextBox name='''
diff --git a/content/test/data/accessibility/html/generated-content-in-empty-page.html b/content/test/data/accessibility/html/generated-content-in-empty-page.html
new file mode 100644
index 0000000..fb511a1b
--- /dev/null
+++ b/content/test/data/accessibility/html/generated-content-in-empty-page.html
@@ -0,0 +1 @@
+<style type="text/css">*::before{content: open-quote;}</style>
diff --git a/content/test/gpu/gpu_tests/common_browser_args.py b/content/test/gpu/gpu_tests/common_browser_args.py
index 7af2533c..d033df4 100644
--- a/content/test/gpu/gpu_tests/common_browser_args.py
+++ b/content/test/gpu/gpu_tests/common_browser_args.py
@@ -24,5 +24,5 @@
 ENABLE_GPU_BENCHMARKING = '--enable-gpu-benchmarking'
 ENSURE_FORCED_COLOR_PROFILE = '--ensure-forced-color-profile'
 FORCE_COLOR_PROFILE_SRGB = '--force-color-profile=srgb'
-FORCE_GPU_RASTERIZATION = '--force-gpu-rasterization'
+ENABLE_GPU_RASTERIZATION = '--enable-gpu-rasterization'
 TEST_TYPE_GPU = '--test-type=gpu'
diff --git a/content/test/gpu/gpu_tests/pixel_test_pages.py b/content/test/gpu/gpu_tests/pixel_test_pages.py
index aa23c23..1cf7b6e 100644
--- a/content/test/gpu/gpu_tests/pixel_test_pages.py
+++ b/content/test/gpu/gpu_tests/pixel_test_pages.py
@@ -330,7 +330,7 @@
   @staticmethod
   def GpuRasterizationPages(base_name):
     browser_args = [
-        cba.FORCE_GPU_RASTERIZATION,
+        cba.ENABLE_GPU_RASTERIZATION,
         cba.DISABLE_SOFTWARE_COMPOSITING_FALLBACK,
     ]
     return [
diff --git a/content/test/gpu/gpu_tests/screenshot_sync_integration_test.py b/content/test/gpu/gpu_tests/screenshot_sync_integration_test.py
index 926afaa..861c706 100644
--- a/content/test/gpu/gpu_tests/screenshot_sync_integration_test.py
+++ b/content/test/gpu/gpu_tests/screenshot_sync_integration_test.py
@@ -90,9 +90,9 @@
     yield ('ScreenshotSync_SWRasterWithDivs', 'screenshot_sync_divs.html',
            ('--disable-gpu-rasterization'))
     yield ('ScreenshotSync_GPURasterWithCanvas', 'screenshot_sync_canvas.html',
-           (cba.FORCE_GPU_RASTERIZATION))
+           (cba.ENABLE_GPU_RASTERIZATION))
     yield ('ScreenshotSync_GPURasterWithDivs', 'screenshot_sync_divs.html',
-           (cba.FORCE_GPU_RASTERIZATION))
+           (cba.ENABLE_GPU_RASTERIZATION))
 
   def _Navigate(self, test_path):
     url = self.UrlOfStaticFilePath(test_path)
diff --git a/device/fido/credential_management.cc b/device/fido/credential_management.cc
index 3e9a9b6..9ec1fdfc 100644
--- a/device/fido/credential_management.cc
+++ b/device/fido/credential_management.cc
@@ -15,31 +15,11 @@
 
 namespace device {
 
-namespace {
-std::array<uint8_t, 16> MakePINAuth(base::span<const uint8_t> pin_token,
-                                    base::span<const uint8_t> pin_auth_bytes) {
-  DCHECK(!pin_token.empty() && !pin_auth_bytes.empty());
-  std::array<uint8_t, SHA256_DIGEST_LENGTH> hmac;
-  unsigned hmac_len;
-  CHECK(HMAC(EVP_sha256(), pin_token.data(), pin_token.size(),
-             pin_auth_bytes.data(), pin_auth_bytes.size(), hmac.data(),
-             &hmac_len));
-  DCHECK_EQ(hmac.size(), static_cast<size_t>(hmac_len));
-  std::array<uint8_t, 16> pin_auth;
-  std::copy(hmac.begin(), hmac.begin() + 16, pin_auth.begin());
-  return pin_auth;
-}
-}  // namespace
-
 CredentialManagementRequest::CredentialManagementRequest(
     Version version_,
     CredentialManagementSubCommand subcommand_,
-    base::Optional<cbor::Value::MapValue> params_,
-    base::Optional<std::array<uint8_t, 16>> pin_auth_)
-    : version(version_),
-      subcommand(subcommand_),
-      params(std::move(params_)),
-      pin_auth(std::move(pin_auth_)) {}
+    base::Optional<cbor::Value::MapValue> params_)
+    : version(version_), subcommand(subcommand_), params(std::move(params_)) {}
 CredentialManagementRequest::CredentialManagementRequest(
     CredentialManagementRequest&&) = default;
 CredentialManagementRequest& CredentialManagementRequest::operator=(
@@ -49,25 +29,25 @@
 // static
 CredentialManagementRequest CredentialManagementRequest::ForGetCredsMetadata(
     Version version,
-    base::span<const uint8_t> pin_token) {
-  return CredentialManagementRequest(
+    const pin::TokenResponse& token) {
+  CredentialManagementRequest request(
       version, CredentialManagementSubCommand::kGetCredsMetadata,
-      /*params=*/base::nullopt,
-      MakePINAuth(pin_token,
-                  {{static_cast<uint8_t>(
-                      CredentialManagementSubCommand::kGetCredsMetadata)}}));
+      /*params=*/base::nullopt);
+  request.pin_auth = token.PinAuth({{static_cast<uint8_t>(
+      CredentialManagementSubCommand::kGetCredsMetadata)}});
+  return request;
 }
 
 // static
 CredentialManagementRequest CredentialManagementRequest::ForEnumerateRPsBegin(
     Version version,
-    base::span<const uint8_t> pin_token) {
-  return CredentialManagementRequest(
+    const pin::TokenResponse& token) {
+  CredentialManagementRequest request(
       version, CredentialManagementSubCommand::kEnumerateRPsBegin,
-      /*params=*/base::nullopt,
-      MakePINAuth(pin_token,
-                  {{static_cast<uint8_t>(
-                      CredentialManagementSubCommand::kEnumerateRPsBegin)}}));
+      /*params=*/base::nullopt);
+  request.pin_auth = token.PinAuth({{static_cast<uint8_t>(
+      CredentialManagementSubCommand::kEnumerateRPsBegin)}});
+  return request;
 }
 
 // static
@@ -75,30 +55,30 @@
     Version version) {
   return CredentialManagementRequest(
       version, CredentialManagementSubCommand::kEnumerateRPsGetNextRP,
-      /*params=*/base::nullopt,
-      /*pin_auth=*/base::nullopt);
+      /*params=*/base::nullopt);
 }
 
 // static
 CredentialManagementRequest
 CredentialManagementRequest::ForEnumerateCredentialsBegin(
     Version version,
-    base::span<const uint8_t> pin_token,
+    const pin::TokenResponse& token,
     std::array<uint8_t, kRpIdHashLength> rp_id_hash) {
   cbor::Value::MapValue params_map;
   params_map.emplace(
       static_cast<int>(CredentialManagementRequestParamKey::kRPIDHash),
       std::move(rp_id_hash));
-  base::Optional<std::vector<uint8_t>> pin_auth_bytes =
-      cbor::Writer::Write(cbor::Value(params_map));
-  DCHECK(pin_auth_bytes);
-  pin_auth_bytes->insert(
-      pin_auth_bytes->begin(),
+  std::vector<uint8_t> pin_auth_bytes =
+      *cbor::Writer::Write(cbor::Value(params_map));
+  CredentialManagementRequest request(
+      version, CredentialManagementSubCommand::kEnumerateCredentialsBegin,
+      std::move(params_map));
+  pin_auth_bytes.insert(
+      pin_auth_bytes.begin(),
       static_cast<uint8_t>(
           CredentialManagementSubCommand::kEnumerateCredentialsBegin));
-  return CredentialManagementRequest(
-      version, CredentialManagementSubCommand::kEnumerateCredentialsBegin,
-      std::move(params_map), MakePINAuth(pin_token, *pin_auth_bytes));
+  request.pin_auth = token.PinAuth(pin_auth_bytes);
+  return request;
 }
 
 // static
@@ -107,27 +87,28 @@
   return CredentialManagementRequest(
       version,
       CredentialManagementSubCommand::kEnumerateCredentialsGetNextCredential,
-      /*params=*/base::nullopt, /*pin_auth=*/base::nullopt);
+      /*params=*/base::nullopt);
 }
 
 // static
 CredentialManagementRequest CredentialManagementRequest::ForDeleteCredential(
     Version version,
-    base::span<const uint8_t> pin_token,
+    const pin::TokenResponse& token,
     const PublicKeyCredentialDescriptor& credential_id) {
   cbor::Value::MapValue params_map;
   params_map.emplace(
       static_cast<int>(CredentialManagementRequestParamKey::kCredentialID),
       AsCBOR(credential_id));
-  base::Optional<std::vector<uint8_t>> pin_auth_bytes =
-      cbor::Writer::Write(cbor::Value(params_map));
-  DCHECK(pin_auth_bytes);
-  pin_auth_bytes->insert(
-      pin_auth_bytes->begin(),
-      static_cast<uint8_t>(CredentialManagementSubCommand::kDeleteCredential));
-  return CredentialManagementRequest(
+  std::vector<uint8_t> pin_auth_bytes =
+      *cbor::Writer::Write(cbor::Value(params_map));
+  CredentialManagementRequest request(
       version, CredentialManagementSubCommand::kDeleteCredential,
-      std::move(params_map), MakePINAuth(pin_token, *pin_auth_bytes));
+      std::move(params_map));
+  pin_auth_bytes.insert(
+      pin_auth_bytes.begin(),
+      static_cast<uint8_t>(CredentialManagementSubCommand::kDeleteCredential));
+  request.pin_auth = token.PinAuth(pin_auth_bytes);
+  return request;
 }
 
 // static
@@ -349,8 +330,9 @@
     : rp(std::move(rp_)), credentials() {}
 AggregatedEnumerateCredentialsResponse::AggregatedEnumerateCredentialsResponse(
     AggregatedEnumerateCredentialsResponse&&) = default;
-AggregatedEnumerateCredentialsResponse& AggregatedEnumerateCredentialsResponse::
-operator=(AggregatedEnumerateCredentialsResponse&&) = default;
+AggregatedEnumerateCredentialsResponse&
+AggregatedEnumerateCredentialsResponse::operator=(
+    AggregatedEnumerateCredentialsResponse&&) = default;
 AggregatedEnumerateCredentialsResponse::
     ~AggregatedEnumerateCredentialsResponse() = default;
 
diff --git a/device/fido/credential_management.h b/device/fido/credential_management.h
index 7f24fae..b0d3725e 100644
--- a/device/fido/credential_management.h
+++ b/device/fido/credential_management.h
@@ -8,6 +8,7 @@
 #include "base/component_export.h"
 #include "base/optional.h"
 #include "device/fido/fido_constants.h"
+#include "device/fido/pin.h"
 #include "device/fido/public_key_credential_descriptor.h"
 #include "device/fido/public_key_credential_rp_entity.h"
 #include "device/fido/public_key_credential_user_entity.h"
@@ -96,40 +97,36 @@
 
   static CredentialManagementRequest ForGetCredsMetadata(
       Version version,
-      base::span<const uint8_t> pin_token);
+      const pin::TokenResponse& token);
   static CredentialManagementRequest ForEnumerateRPsBegin(
       Version version,
-      base::span<const uint8_t> pin_token);
+      const pin::TokenResponse& token);
   static CredentialManagementRequest ForEnumerateRPsGetNext(Version version);
   static CredentialManagementRequest ForEnumerateCredentialsBegin(
       Version version,
-      base::span<const uint8_t> pin_token,
+      const pin::TokenResponse& token,
       std::array<uint8_t, kRpIdHashLength> rp_id_hash);
   static CredentialManagementRequest ForEnumerateCredentialsGetNext(
       Version version);
   static CredentialManagementRequest ForDeleteCredential(
       Version version,
-      base::span<const uint8_t> pin_token,
+      const pin::TokenResponse& token,
       const PublicKeyCredentialDescriptor& credential_id);
 
+  CredentialManagementRequest(Version version,
+                              CredentialManagementSubCommand subcommand,
+                              base::Optional<cbor::Value::MapValue> params);
   CredentialManagementRequest(CredentialManagementRequest&&);
   CredentialManagementRequest& operator=(CredentialManagementRequest&&);
+  CredentialManagementRequest(const CredentialManagementRequest&) = delete;
+  CredentialManagementRequest& operator=(const CredentialManagementRequest&) =
+      delete;
   ~CredentialManagementRequest();
 
   Version version;
   CredentialManagementSubCommand subcommand;
   base::Optional<cbor::Value::MapValue> params;
-  base::Optional<std::array<uint8_t, 16>> pin_auth;
-
- private:
-  CredentialManagementRequest() = delete;
-  CredentialManagementRequest(Version version,
-                              CredentialManagementSubCommand subcommand,
-                              base::Optional<cbor::Value::MapValue> params,
-                              base::Optional<std::array<uint8_t, 16>> pin_auth);
-  CredentialManagementRequest(const CredentialManagementRequest&) = delete;
-  CredentialManagementRequest& operator=(const CredentialManagementRequest&) =
-      delete;
+  base::Optional<std::vector<uint8_t>> pin_auth;
 };
 
 struct CredentialsMetadataResponse {
diff --git a/device/fido/credential_management_handler.cc b/device/fido/credential_management_handler.cc
index 8d8c6fa..409bb4c 100644
--- a/device/fido/credential_management_handler.cc
+++ b/device/fido/credential_management_handler.cc
@@ -157,7 +157,7 @@
   }
 
   state_ = State::kReady;
-  pin_token_ = response->token();
+  pin_token_ = response;
   std::move(ready_callback_).Run();
 }
 
diff --git a/device/fido/credential_management_handler.h b/device/fido/credential_management_handler.h
index 757f399..63424609 100644
--- a/device/fido/credential_management_handler.h
+++ b/device/fido/credential_management_handler.h
@@ -127,7 +127,7 @@
 
   State state_ = State::kWaitingForTouch;
   FidoAuthenticator* authenticator_ = nullptr;
-  base::Optional<std::vector<uint8_t>> pin_token_;
+  base::Optional<pin::TokenResponse> pin_token_;
 
   ReadyCallback ready_callback_;
   GetPINCallback get_pin_callback_;
diff --git a/device/fido/fido_authenticator.cc b/device/fido/fido_authenticator.cc
index 8eedc0d..8f442c9 100644
--- a/device/fido/fido_authenticator.cc
+++ b/device/fido/fido_authenticator.cc
@@ -73,19 +73,19 @@
 }
 
 void FidoAuthenticator::GetCredentialsMetadata(
-    base::span<const uint8_t> pin_token,
+    const pin::TokenResponse& pin_token,
     GetCredentialsMetadataCallback callback) {
   NOTREACHED();
 }
 
 void FidoAuthenticator::EnumerateCredentials(
-    base::span<const uint8_t> pin_token,
+    const pin::TokenResponse& pin_token,
     EnumerateCredentialsCallback callback) {
   NOTREACHED();
 }
 
 void FidoAuthenticator::DeleteCredential(
-    base::span<const uint8_t> pin_token,
+    const pin::TokenResponse& pin_token,
     const PublicKeyCredentialDescriptor& credential_id,
     DeleteCredentialCallback callback) {
   NOTREACHED();
diff --git a/device/fido/fido_authenticator.h b/device/fido/fido_authenticator.h
index df697a57..5154563 100644
--- a/device/fido/fido_authenticator.h
+++ b/device/fido/fido_authenticator.h
@@ -182,12 +182,12 @@
       const CtapGetAssertionRequest& request,
       const FidoRequestHandlerBase::Observer* observer);
 
-  virtual void GetCredentialsMetadata(base::span<const uint8_t> pin_token,
+  virtual void GetCredentialsMetadata(const pin::TokenResponse& pin_token,
                                       GetCredentialsMetadataCallback callback);
-  virtual void EnumerateCredentials(base::span<const uint8_t> pin_token,
+  virtual void EnumerateCredentials(const pin::TokenResponse& pin_token,
                                     EnumerateCredentialsCallback callback);
   virtual void DeleteCredential(
-      base::span<const uint8_t> pin_token,
+      const pin::TokenResponse& pin_token,
       const PublicKeyCredentialDescriptor& credential_id,
       DeleteCredentialCallback callback);
 
diff --git a/device/fido/fido_device_authenticator.cc b/device/fido/fido_device_authenticator.cc
index 9cdbf59..0690ea3 100644
--- a/device/fido/fido_device_authenticator.cc
+++ b/device/fido/fido_device_authenticator.cc
@@ -341,7 +341,6 @@
                : MakeCredentialPINDisposition::kNoPIN;
   }
 
-
   // CTAP 2.0 requires a PIN for credential creation once a PIN has been set.
   // Thus, if fallback to U2F isn't possible, a PIN will be needed if set.
   const bool u2f_fallback_possible =
@@ -439,7 +438,7 @@
 }
 
 void FidoDeviceAuthenticator::GetCredentialsMetadata(
-    base::span<const uint8_t> pin_token,
+    const pin::TokenResponse& pin_token,
     GetCredentialsMetadataCallback callback) {
   DCHECK(Options()->supports_credential_management ||
          Options()->supports_credential_management_preview);
@@ -451,11 +450,12 @@
 }
 
 struct FidoDeviceAuthenticator::EnumerateCredentialsState {
-  EnumerateCredentialsState() = default;
+  explicit EnumerateCredentialsState(pin::TokenResponse pin_token_)
+      : pin_token(pin_token_) {}
   EnumerateCredentialsState(EnumerateCredentialsState&&) = default;
   EnumerateCredentialsState& operator=(EnumerateCredentialsState&&) = default;
 
-  std::vector<uint8_t> pin_token;
+  pin::TokenResponse pin_token;
   bool is_first_rp = true;
   bool is_first_credential = true;
   size_t rp_count;
@@ -466,13 +466,12 @@
 };
 
 void FidoDeviceAuthenticator::EnumerateCredentials(
-    base::span<const uint8_t> pin_token,
+    const pin::TokenResponse& pin_token,
     EnumerateCredentialsCallback callback) {
   DCHECK(Options()->supports_credential_management ||
          Options()->supports_credential_management_preview);
 
-  EnumerateCredentialsState state;
-  state.pin_token = fido_parsing_utils::Materialize(pin_token);
+  EnumerateCredentialsState state(pin_token);
   state.callback = std::move(callback);
   RunOperation<CredentialManagementRequest, EnumerateRPsResponse>(
       CredentialManagementRequest::ForEnumerateRPsBegin(
@@ -629,7 +628,7 @@
 }
 
 void FidoDeviceAuthenticator::DeleteCredential(
-    base::span<const uint8_t> pin_token,
+    const pin::TokenResponse& pin_token,
     const PublicKeyCredentialDescriptor& credential_id,
     DeleteCredentialCallback callback) {
   DCHECK(Options()->supports_credential_management ||
@@ -702,11 +701,11 @@
 }
 
 void FidoDeviceAuthenticator::BioEnrollEnumerate(
-    const pin::TokenResponse& response,
+    const pin::TokenResponse& pin_token,
     BioEnrollmentCallback callback) {
   RunOperation<BioEnrollmentRequest, BioEnrollmentResponse>(
       BioEnrollmentRequest::ForEnumerate(
-          GetBioEnrollmentRequestVersion(*Options()), std::move(response)),
+          GetBioEnrollmentRequestVersion(*Options()), std::move(pin_token)),
       std::move(callback), base::BindOnce(&BioEnrollmentResponse::Parse));
 }
 
diff --git a/device/fido/fido_device_authenticator.h b/device/fido/fido_device_authenticator.h
index df08fce..06e875b 100644
--- a/device/fido/fido_device_authenticator.h
+++ b/device/fido/fido_device_authenticator.h
@@ -73,11 +73,11 @@
       const CtapGetAssertionRequest& request,
       const FidoRequestHandlerBase::Observer* observer) override;
 
-  void GetCredentialsMetadata(base::span<const uint8_t> pin_token,
+  void GetCredentialsMetadata(const pin::TokenResponse& pin_token,
                               GetCredentialsMetadataCallback callback) override;
-  void EnumerateCredentials(base::span<const uint8_t> pin_token,
+  void EnumerateCredentials(const pin::TokenResponse& pin_token,
                             EnumerateCredentialsCallback callback) override;
-  void DeleteCredential(base::span<const uint8_t> pin_token,
+  void DeleteCredential(const pin::TokenResponse& pin_token,
                         const PublicKeyCredentialDescriptor& credential_id,
                         DeleteCredentialCallback callback) override;
 
diff --git a/device/vr/buildflags/buildflags.gni b/device/vr/buildflags/buildflags.gni
index 46792daa..0f2b39aa 100644
--- a/device/vr/buildflags/buildflags.gni
+++ b/device/vr/buildflags/buildflags.gni
@@ -30,8 +30,8 @@
   # the binary size impact is small and allows many VR tests to run on Linux
   enable_vr = enable_gvr_services || enable_oculus_vr || enable_windows_mr ||
               enable_openxr ||
-              (is_desktop_linux &&
-               (current_cpu == "x64" || current_cpu == "x86") && !is_chromecast)
+              (is_linux && (current_cpu == "x64" || current_cpu == "x86") &&
+               !is_chromecast)
 
   # Whether to include VR extras like test APKs in non-VR-specific targets
   include_vr_data = false
diff --git a/extensions/browser/event_router_unittest.cc b/extensions/browser/event_router_unittest.cc
index 54bf7cc..31a67f7 100644
--- a/extensions/browser/event_router_unittest.cc
+++ b/extensions/browser/event_router_unittest.cc
@@ -20,6 +20,7 @@
 #include "extensions/browser/extensions_test.h"
 #include "extensions/common/extension_builder.h"
 #include "extensions/common/extension_messages.h"
+#include "extensions/common/scoped_worker_based_extensions_channel.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using base::DictionaryValue;
@@ -124,6 +125,17 @@
   return builder.Build();
 }
 
+scoped_refptr<const Extension> CreateServiceWorkerExtension() {
+  ExtensionBuilder builder;
+  auto manifest = std::make_unique<base::DictionaryValue>();
+  manifest->SetString("name", "foo");
+  manifest->SetString("version", "1.0.0");
+  manifest->SetInteger("manifest_version", 2);
+  manifest->SetString("background.service_worker", "worker.js");
+  builder.SetManifest(std::move(manifest));
+  return builder.Build();
+}
+
 std::unique_ptr<DictionaryValue> CreateHostSuffixFilter(
     const std::string& suffix) {
   auto filter_dict = std::make_unique<DictionaryValue>();
@@ -153,32 +165,26 @@
                              int component_count,
                              int persistent_count,
                              int suspended_count,
-                             int running_count) {
-    if (dispatch_count) {
-      histogram_tester_.ExpectBucketCount("Extensions.Events.Dispatch",
-                                          events::HistogramValue::FOR_TEST,
-                                          dispatch_count);
-    }
-    if (component_count) {
-      histogram_tester_.ExpectBucketCount(
-          "Extensions.Events.DispatchToComponent",
-          events::HistogramValue::FOR_TEST, component_count);
-    }
-    if (persistent_count) {
-      histogram_tester_.ExpectBucketCount(
-          "Extensions.Events.DispatchWithPersistentBackgroundPage",
-          events::HistogramValue::FOR_TEST, persistent_count);
-    }
-    if (suspended_count) {
-      histogram_tester_.ExpectBucketCount(
-          "Extensions.Events.DispatchWithSuspendedEventPage",
-          events::HistogramValue::FOR_TEST, suspended_count);
-    }
-    if (running_count) {
-      histogram_tester_.ExpectBucketCount(
-          "Extensions.Events.DispatchWithRunningEventPage",
-          events::HistogramValue::FOR_TEST, running_count);
-    }
+                             int running_count,
+                             int service_worker_count) {
+    histogram_tester_.ExpectBucketCount("Extensions.Events.Dispatch",
+                                        events::HistogramValue::FOR_TEST,
+                                        dispatch_count);
+    histogram_tester_.ExpectBucketCount("Extensions.Events.DispatchToComponent",
+                                        events::HistogramValue::FOR_TEST,
+                                        component_count);
+    histogram_tester_.ExpectBucketCount(
+        "Extensions.Events.DispatchWithPersistentBackgroundPage",
+        events::HistogramValue::FOR_TEST, persistent_count);
+    histogram_tester_.ExpectBucketCount(
+        "Extensions.Events.DispatchWithSuspendedEventPage",
+        events::HistogramValue::FOR_TEST, suspended_count);
+    histogram_tester_.ExpectBucketCount(
+        "Extensions.Events.DispatchWithRunningEventPage",
+        events::HistogramValue::FOR_TEST, running_count);
+    histogram_tester_.ExpectBucketCount(
+        "Extensions.Events.DispatchWithServiceWorkerBackground",
+        events::HistogramValue::FOR_TEST, service_worker_count);
   }
 
  private:
@@ -347,34 +353,42 @@
   ExpectHistogramCounts(1 /** Dispatch */, 0 /** DispatchToComponent */,
                         0 /** DispatchWithPersistentBackgroundPage */,
                         0 /** DispatchWithSuspendedEventPage */,
-                        0 /** DispatchWithRunningEventPage */);
+                        0 /** DispatchWithRunningEventPage */,
+                        0 /** DispatchWithServiceWorkerBackground */);
 
   scoped_refptr<const Extension> component =
       CreateExtension(true /** component */, true /** persistent */);
   router.ReportEvent(events::HistogramValue::FOR_TEST, component.get(),
                      false /** did_enqueue */);
-  ExpectHistogramCounts(2, 1, 1, 0, 0);
+  ExpectHistogramCounts(2, 1, 1, 0, 0, 0);
 
   scoped_refptr<const Extension> persistent = CreateExtension(false, true);
   router.ReportEvent(events::HistogramValue::FOR_TEST, persistent.get(),
                      false /** did_enqueue */);
-  ExpectHistogramCounts(3, 1, 2, 0, 0);
+  ExpectHistogramCounts(3, 1, 2, 0, 0, 0);
 
   scoped_refptr<const Extension> event = CreateExtension(false, false);
   router.ReportEvent(events::HistogramValue::FOR_TEST, event.get(),
                      false /** did_enqueue */);
-  ExpectHistogramCounts(4, 1, 2, 0, 0);
+  ExpectHistogramCounts(4, 1, 2, 0, 1, 0);
   router.ReportEvent(events::HistogramValue::FOR_TEST, event.get(),
                      true /** did_enqueue */);
-  ExpectHistogramCounts(5, 1, 2, 1, 1);
+  ExpectHistogramCounts(5, 1, 2, 1, 1, 0);
 
   scoped_refptr<const Extension> component_event = CreateExtension(true, false);
   router.ReportEvent(events::HistogramValue::FOR_TEST, component_event.get(),
                      false /** did_enqueue */);
-  ExpectHistogramCounts(6, 2, 2, 1, 2);
+  ExpectHistogramCounts(6, 2, 2, 1, 2, 0);
   router.ReportEvent(events::HistogramValue::FOR_TEST, component_event.get(),
                      true /** did_enqueue */);
-  ExpectHistogramCounts(7, 3, 2, 2, 2);
+  ExpectHistogramCounts(7, 3, 2, 2, 2, 0);
+
+  ScopedWorkerBasedExtensionsChannel current_channel_override;
+  scoped_refptr<const Extension> service_worker_extension =
+      CreateServiceWorkerExtension();
+  router.ReportEvent(events::HistogramValue::FOR_TEST,
+                     service_worker_extension.get(), true /** did_enqueue */);
+  ExpectHistogramCounts(8, 3, 2, 2, 2, 1);
 }
 
 // Tests adding and removing events with filters.
diff --git a/extensions/shell/BUILD.gn b/extensions/shell/BUILD.gn
index 2b14759..f912641 100644
--- a/extensions/shell/BUILD.gn
+++ b/extensions/shell/BUILD.gn
@@ -202,7 +202,7 @@
     }
   }
 
-  if (is_desktop_linux) {
+  if (is_linux) {
     sources += [
       "app/shell_crash_reporter_client.cc",
       "app/shell_crash_reporter_client.h",
@@ -433,7 +433,7 @@
   }
 }
 
-if (is_desktop_linux && is_official_build) {
+if (is_linux && is_official_build) {
   extract_symbols("app_shell_linux_symbols") {
     # testonly because :app_shell is testonly. See :app_shell_lib comment.
     testonly = true
diff --git a/fuchsia/base/message_port.cc b/fuchsia/base/message_port.cc
index d4f16bc..c355cbe 100644
--- a/fuchsia/base/message_port.cc
+++ b/fuchsia/base/message_port.cc
@@ -49,14 +49,18 @@
   if (fidl_message.has_outgoing_transfer()) {
     for (fuchsia::web::OutgoingTransferable& transferrable :
          *fidl_message.mutable_outgoing_transfer()) {
+      if (!transferrable.is_message_port())
+        return fuchsia::web::FrameError::INTERNAL_ERROR;
       blink_message->ports.push_back(
           BlinkMessagePortFromFidl(std::move(transferrable.message_port())));
     }
   } else if (fidl_message.has_incoming_transfer()) {
-    for (fuchsia::web::IncomingTransferable& incoming :
+    for (fuchsia::web::IncomingTransferable& transferrable :
          *fidl_message.mutable_incoming_transfer()) {
+      if (!transferrable.is_message_port())
+        return fuchsia::web::FrameError::INTERNAL_ERROR;
       blink_message->ports.push_back(
-          BlinkMessagePortFromFidl(std::move(incoming.message_port())));
+          BlinkMessagePortFromFidl(std::move(transferrable.message_port())));
     }
   }
 
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc
index 76365bd..f6edc0fbc 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc
@@ -1289,8 +1289,14 @@
 }
 
 error::Error GLES2DecoderPassthroughImpl::DoFinish() {
+  // Finish can take a long time, make sure the watchdog gives it the most
+  // amount of time to complete.
+  group_->ReportProgress();
+
   api()->glFinishFn();
 
+  group_->ReportProgress();
+
   error::Error error = ProcessReadPixels(true);
   if (error != error::kNoError) {
     return error;
@@ -1518,9 +1524,11 @@
   }
 
   std::vector<char> name_buffer(active_attribute_max_length, 0);
-  api()->glGetActiveAttribFn(service_id, index, name_buffer.size(), nullptr,
+  GLsizei length = 0;
+  api()->glGetActiveAttribFn(service_id, index, name_buffer.size(), &length,
                              size, type, name_buffer.data());
-  *name = std::string(name_buffer.data());
+  DCHECK(length <= active_attribute_max_length);
+  *name = length > 0 ? std::string(name_buffer.data(), length) : std::string();
   *success = CheckErrorCallbackState() ? 0 : 1;
   return error::kNoError;
 }
@@ -1543,9 +1551,11 @@
   }
 
   std::vector<char> name_buffer(active_uniform_max_length, 0);
-  api()->glGetActiveUniformFn(service_id, index, name_buffer.size(), nullptr,
+  GLsizei length = 0;
+  api()->glGetActiveUniformFn(service_id, index, name_buffer.size(), &length,
                               size, type, name_buffer.data());
-  *name = std::string(name_buffer.data());
+  DCHECK(length <= active_uniform_max_length);
+  *name = length > 0 ? std::string(name_buffer.data(), length) : std::string();
   *success = CheckErrorCallbackState() ? 0 : 1;
   return error::kNoError;
 }
@@ -2080,10 +2090,12 @@
   }
 
   std::vector<char> name_buffer(transform_feedback_varying_max_length, 0);
+  GLsizei length = 0;
   api()->glGetTransformFeedbackVaryingFn(service_id, index, name_buffer.size(),
-                                         nullptr, size, type,
+                                         &length, size, type,
                                          name_buffer.data());
-  *name = std::string(name_buffer.data());
+  DCHECK(length <= transform_feedback_varying_max_length);
+  *name = length > 0 ? std::string(name_buffer.data(), length) : std::string();
   *success = CheckErrorCallbackState() ? 0 : 1;
   return error::kNoError;
 }
@@ -4388,9 +4400,13 @@
 
   if (translated_source_length > 0) {
     std::vector<char> buffer(translated_source_length, 0);
+    GLsizei length = 0;
     api()->glGetTranslatedShaderSourceANGLEFn(
-        service_id, translated_source_length, nullptr, buffer.data());
-    *source = std::string(buffer.data());
+        service_id, translated_source_length, &length, buffer.data());
+    DCHECK(length <= translated_source_length);
+    *source = length > 0 ? std::string(buffer.data(), length) : std::string();
+  } else {
+    *source = std::string();
   }
   return error::kNoError;
 }
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_d3d.cc b/gpu/command_buffer/service/shared_image_backing_factory_d3d.cc
index 70585fe..02d5d09 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_d3d.cc
+++ b/gpu/command_buffer/service/shared_image_backing_factory_d3d.cc
@@ -4,7 +4,10 @@
 
 #include "gpu/command_buffer/service/shared_image_backing_factory_d3d.h"
 
+#include <d3d11_1.h>
+
 #include "components/viz/common/resources/resource_format_utils.h"
+#include "gpu/command_buffer/common/shared_image_usage.h"
 #include "gpu/command_buffer/service/shared_image_backing_d3d.h"
 #include "ui/gfx/buffer_format_util.h"
 #include "ui/gl/direct_composition_surface_win.h"
@@ -141,13 +144,9 @@
       return nullptr;
     }
   } else if (shared_handle.IsValid()) {
-    const HRESULT hr = d3d11_texture.As(&dxgi_keyed_mutex);
-    if (FAILED(hr)) {
-      DLOG(ERROR) << "Unable to QueryInterface for IDXGIKeyedMutex on texture "
-                     "with shared handle "
-                  << std::hex;
-      return nullptr;
-    }
+    // Keyed mutexes are required for Dawn interop but are not used
+    // for XR composition where fences are used instead.
+    d3d11_texture.As(&dxgi_keyed_mutex);
   }
   DCHECK(d3d11_texture);
 
@@ -380,15 +379,58 @@
     GrSurfaceOrigin surface_origin,
     SkAlphaType alpha_type,
     uint32_t usage) {
-  NOTIMPLEMENTED();
-  return nullptr;
+  // TODO: Add support for shared memory GMBs.
+  DCHECK_EQ(handle.type, gfx::DXGI_SHARED_HANDLE);
+  if (!handle.dxgi_handle.IsValid()) {
+    DLOG(ERROR) << "Invalid handle type passed to CreateSharedImage";
+    return nullptr;
+  }
+
+  if (!gpu::IsImageSizeValidForGpuMemoryBufferFormat(size, format)) {
+    DLOG(ERROR) << "Invalid image size " << size.ToString() << " for "
+                << gfx::BufferFormatToString(format);
+    return nullptr;
+  }
+
+  Microsoft::WRL::ComPtr<ID3D11Device1> d3d11_device1;
+  HRESULT hr = d3d11_device_.As(&d3d11_device1);
+  if (FAILED(hr)) {
+    DLOG(ERROR) << "Failed to query for ID3D11Device1. Error: "
+                << logging::SystemErrorCodeToString(hr);
+    return nullptr;
+  }
+
+  Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture;
+  hr = d3d11_device1->OpenSharedResource1(handle.dxgi_handle.Get(),
+                                          IID_PPV_ARGS(&d3d11_texture));
+  if (FAILED(hr)) {
+    DLOG(ERROR) << "Unable to open shared resource from DXGI handle. Error: "
+                << logging::SystemErrorCodeToString(hr);
+    return nullptr;
+  }
+
+  D3D11_TEXTURE2D_DESC desc;
+  d3d11_texture->GetDesc(&desc);
+
+  // TODO: Add checks for device specific limits.
+  if (desc.Width != static_cast<UINT>(size.width()) ||
+      desc.Height != static_cast<UINT>(size.height())) {
+    DLOG(ERROR)
+        << "Size passed to CreateSharedImage must match texture being opened";
+    return nullptr;
+  }
+
+  return MakeBacking(mailbox, viz::GetResourceFormat(format), size, color_space,
+                     surface_origin, alpha_type, usage, /*swap_chain=*/nullptr,
+                     /*buffer_index=*/0, std::move(d3d11_texture),
+                     std::move(handle.dxgi_handle));
 }
 
 // Returns true if the specified GpuMemoryBufferType can be imported using
 // this factory.
 bool SharedImageBackingFactoryD3D::CanImportGpuMemoryBuffer(
     gfx::GpuMemoryBufferType memory_buffer_type) {
-  return false;
+  return (memory_buffer_type == gfx::DXGI_SHARED_HANDLE);
 }
 
 }  // namespace gpu
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_d3d.h b/gpu/command_buffer/service/shared_image_backing_factory_d3d.h
index 7abb0b0..3a8f9b8 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_d3d.h
+++ b/gpu/command_buffer/service/shared_image_backing_factory_d3d.h
@@ -96,6 +96,10 @@
   bool CanImportGpuMemoryBuffer(
       gfx::GpuMemoryBufferType memory_buffer_type) override;
 
+  Microsoft::WRL::ComPtr<ID3D11Device> GetDeviceForTesting() const {
+    return d3d11_device_;
+  }
+
  private:
   // Wraps the optional swap chain buffer (front buffer/back buffer) and texture
   // into GLimage and creates a GL texture and stores it as gles2::Texture or as
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_d3d_unittest.cc b/gpu/command_buffer/service/shared_image_backing_factory_d3d_unittest.cc
index c5ed569..82025f30 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_d3d_unittest.cc
+++ b/gpu/command_buffer/service/shared_image_backing_factory_d3d_unittest.cc
@@ -11,6 +11,7 @@
 #include "gpu/command_buffer/common/shared_image_usage.h"
 #include "gpu/command_buffer/service/service_utils.h"
 #include "gpu/command_buffer/service/shared_context_state.h"
+#include "gpu/command_buffer/service/shared_image_backing_d3d.h"
 #include "gpu/command_buffer/service/shared_image_factory.h"
 #include "gpu/command_buffer/service/shared_image_manager.h"
 #include "gpu/command_buffer/service/shared_image_representation.h"
@@ -846,5 +847,72 @@
           skia_representation->BeginScopedReadAccess(nullptr, nullptr);
   EXPECT_EQ(scoped_read_access, nullptr);
 }
+
+TEST_F(SharedImageBackingFactoryD3DTest, CreateSharedImageFromHandle) {
+  if (!IsD3DSharedImageSupported())
+    return;
+
+  EXPECT_TRUE(
+      shared_image_factory_->CanImportGpuMemoryBuffer(gfx::DXGI_SHARED_HANDLE));
+
+  Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
+      shared_image_factory_->GetDeviceForTesting();
+
+  const gfx::Size size(1, 1);
+  D3D11_TEXTURE2D_DESC desc;
+  desc.Width = size.width();
+  desc.Height = size.height();
+  desc.MipLevels = 1;
+  desc.ArraySize = 1;
+  desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
+  desc.SampleDesc.Count = 1;
+  desc.SampleDesc.Quality = 0;
+  desc.Usage = D3D11_USAGE_DEFAULT;
+  desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
+  desc.CPUAccessFlags = 0;
+  desc.MiscFlags =
+      D3D11_RESOURCE_MISC_SHARED_NTHANDLE | D3D11_RESOURCE_MISC_SHARED;
+  Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture;
+  HRESULT hr = d3d11_device->CreateTexture2D(&desc, nullptr, &d3d11_texture);
+  ASSERT_EQ(hr, S_OK);
+
+  Microsoft::WRL::ComPtr<IDXGIResource1> dxgi_resource;
+  hr = d3d11_texture.As(&dxgi_resource);
+  ASSERT_EQ(hr, S_OK);
+
+  HANDLE shared_handle;
+  hr = dxgi_resource->CreateSharedHandle(
+      nullptr, DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE, nullptr,
+      &shared_handle);
+  ASSERT_EQ(hr, S_OK);
+
+  gfx::GpuMemoryBufferHandle gpu_memory_buffer_handle;
+  gpu_memory_buffer_handle.dxgi_handle.Set(shared_handle);
+  gpu_memory_buffer_handle.type = gfx::DXGI_SHARED_HANDLE;
+
+  auto mailbox = Mailbox::GenerateForSharedImage();
+  const auto format = gfx::BufferFormat::RGBA_8888;
+  const auto color_space = gfx::ColorSpace::CreateSRGB();
+  const uint32_t usage = SHARED_IMAGE_USAGE_GLES2 | SHARED_IMAGE_USAGE_DISPLAY;
+  const gpu::SurfaceHandle surface_handle = gpu::kNullSurfaceHandle;
+  const GrSurfaceOrigin surface_origin = kTopLeft_GrSurfaceOrigin;
+  const SkAlphaType alpha_type = kPremul_SkAlphaType;
+  auto backing = shared_image_factory_->CreateSharedImage(
+      mailbox, 0, std::move(gpu_memory_buffer_handle), format, surface_handle,
+      size, color_space, surface_origin, alpha_type, usage);
+  ASSERT_NE(backing, nullptr);
+
+  EXPECT_EQ(backing->format(), viz::RGBA_8888);
+  EXPECT_EQ(backing->size(), size);
+  EXPECT_EQ(backing->color_space(), color_space);
+  EXPECT_EQ(backing->surface_origin(), surface_origin);
+  EXPECT_EQ(backing->alpha_type(), alpha_type);
+  EXPECT_EQ(backing->mailbox(), mailbox);
+
+  SharedImageBackingD3D* backing_d3d =
+      static_cast<SharedImageBackingD3D*>(backing.get());
+  EXPECT_EQ(backing_d3d->GetSharedHandle(), shared_handle);
+}
+
 }  // anonymous namespace
 }  // namespace gpu
diff --git a/gpu/ipc/client/shared_image_interface_proxy.cc b/gpu/ipc/client/shared_image_interface_proxy.cc
index 3aa5610..e811c13 100644
--- a/gpu/ipc/client/shared_image_interface_proxy.cc
+++ b/gpu/ipc/client/shared_image_interface_proxy.cc
@@ -162,6 +162,7 @@
     uint32_t usage) {
   DCHECK(gpu_memory_buffer->GetType() == gfx::NATIVE_PIXMAP ||
          gpu_memory_buffer->GetType() == gfx::ANDROID_HARDWARE_BUFFER ||
+         gpu_memory_buffer->GetType() == gfx::DXGI_SHARED_HANDLE ||
          gpu_memory_buffer_manager);
 
   auto mailbox = Mailbox::GenerateForSharedImage();
diff --git a/ios/chrome/app/application_delegate/app_state.h b/ios/chrome/app/application_delegate/app_state.h
index d2cc0fa..f5d3b6e 100644
--- a/ios/chrome/app/application_delegate/app_state.h
+++ b/ios/chrome/app/application_delegate/app_state.h
@@ -90,6 +90,10 @@
 // The last window which received a tap.
 @property(nonatomic, weak) UIWindow* lastTappedWindow;
 
+// The SceneSession ID for the last session, where the Device doesn't support
+// multiple windows.
+@property(nonatomic, strong) NSString* previousSingleWindowSessionID;
+
 // Saves the launchOptions to be used from -newTabFromLaunchOptions. If the
 // application is in background, initialize the browser to basic. If not, launch
 // the browser.
diff --git a/ios/chrome/app/main_controller.mm b/ios/chrome/app/main_controller.mm
index 14b2a1d9..5ef4941 100644
--- a/ios/chrome/app/main_controller.mm
+++ b/ios/chrome/app/main_controller.mm
@@ -480,6 +480,12 @@
           moveAsideSessionInformationForBrowserState:chromeBrowserState];
     }
   }
+  if (!IsMultipleScenesSupported() && IsMultiwindowSupported()) {
+    NSSet<NSString*>* previousSessions =
+        [PreviousSessionInfo sharedInstance].connectedSceneSessionsIDs;
+    DCHECK(previousSessions.count <= 1);
+    self.appState.previousSingleWindowSessionID = [previousSessions anyObject];
+  }
   [[PreviousSessionInfo sharedInstance] resetConnectedSceneSessionIDs];
 
   // Initialize and set the main browser state.
diff --git a/ios/chrome/browser/pref_names.cc b/ios/chrome/browser/pref_names.cc
index dd443512..84ffec7 100644
--- a/ios/chrome/browser/pref_names.cc
+++ b/ios/chrome/browser/pref_names.cc
@@ -102,15 +102,10 @@
 // Boolean that is true when Suggest support is enabled.
 const char kSearchSuggestEnabled[] = "search.suggest_enabled";
 
-// A boolean pref set to true if prediction of network actions is allowed.
-// Actions include prerendering of web pages.
-// NOTE: The "dns_prefetching.enabled" value is used so that historical user
-// preferences are not lost.
-const char kNetworkPredictionEnabled[] = "dns_prefetching.enabled";
-
-// Preference that hold a boolean indicating whether network prediction should
-// be limited to wifi (when enabled).
-const char kNetworkPredictionWifiOnly[] = "ios.dns_prefetching.wifi_only";
+// An integer set to one of the NetworkPredictionSetting enum values indicating
+// network prediction settings.
+const char kNetworkPredictionSetting[] =
+    "ios.prerender.network_prediction_settings";
 
 // Which bookmarks folder should be visible on the new tab page v4.
 const char kNtpShownBookmarksFolder[] = "ntp.shown_bookmarks_folder";
diff --git a/ios/chrome/browser/pref_names.h b/ios/chrome/browser/pref_names.h
index e0e5e1e2..eb2194f 100644
--- a/ios/chrome/browser/pref_names.h
+++ b/ios/chrome/browser/pref_names.h
@@ -33,10 +33,7 @@
 extern const char kSavingBrowserHistoryDisabled[];
 extern const char kSearchSuggestEnabled[];
 
-// TODO(crbug.com/538573): Consider migrating from these two bools to an integer
-// since only three cases are supported.
-extern const char kNetworkPredictionEnabled[];
-extern const char kNetworkPredictionWifiOnly[];
+extern const char kNetworkPredictionSetting[];
 
 extern const char kNtpShownBookmarksFolder[];
 extern const char kShowMemoryDebuggingTools[];
diff --git a/ios/chrome/browser/prefs/BUILD.gn b/ios/chrome/browser/prefs/BUILD.gn
index a9b264d4..61cc7e3 100644
--- a/ios/chrome/browser/prefs/BUILD.gn
+++ b/ios/chrome/browser/prefs/BUILD.gn
@@ -77,6 +77,7 @@
     "//ios/chrome/browser/memory",
     "//ios/chrome/browser/metrics",
     "//ios/chrome/browser/net",
+    "//ios/chrome/browser/prerender:prerender_pref",
     "//ios/chrome/browser/signin",
     "//ios/chrome/browser/ui/authentication",
     "//ios/chrome/browser/ui/bookmarks",
diff --git a/ios/chrome/browser/prefs/browser_prefs.mm b/ios/chrome/browser/prefs/browser_prefs.mm
index 1e8530e4..3a3c235d 100644
--- a/ios/chrome/browser/prefs/browser_prefs.mm
+++ b/ios/chrome/browser/prefs/browser_prefs.mm
@@ -60,6 +60,7 @@
 #import "ios/chrome/browser/metrics/ios_chrome_metrics_service_client.h"
 #include "ios/chrome/browser/notification_promo.h"
 #include "ios/chrome/browser/pref_names.h"
+#include "ios/chrome/browser/prerender/prerender_pref.h"
 #import "ios/chrome/browser/ui/authentication/signin_promo_view_mediator.h"
 #import "ios/chrome/browser/ui/bookmarks/bookmark_mediator.h"
 #import "ios/chrome/browser/ui/bookmarks/bookmark_path_cache.h"
@@ -172,6 +173,7 @@
   payments::RegisterProfilePrefs(registry);
   policy::URLBlocklistManager::RegisterProfilePrefs(registry);
   PrefProxyConfigTrackerImpl::RegisterProfilePrefs(registry);
+  prerender_prefs::RegisterNetworkPredictionPrefs(registry);
   RegisterVoiceSearchBrowserStatePrefs(registry);
   safe_browsing::RegisterProfilePrefs(registry);
   sync_sessions::SessionSyncPrefs::RegisterProfilePrefs(registry);
@@ -200,12 +202,6 @@
   registry->RegisterStringPref(prefs::kDefaultCharset,
                                l10n_util::GetStringUTF8(IDS_DEFAULT_ENCODING),
                                user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
-  registry->RegisterBooleanPref(
-      prefs::kNetworkPredictionEnabled, true,
-      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
-  registry->RegisterBooleanPref(
-      prefs::kNetworkPredictionWifiOnly, true,
-      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
   registry->RegisterStringPref(prefs::kContextualSearchEnabled, std::string(),
                                user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
   registry->RegisterBooleanPref(
@@ -288,4 +284,5 @@
   // Added 9/2020.
   prefs->ClearPref(kPasswordManagerOnboardingState);
   prefs->ClearPref(kWasOnboardingFeatureCheckedBefore);
+  prerender_prefs::MigrateNetworkPredictionPrefs(prefs);
 }
diff --git a/ios/chrome/browser/prerender/BUILD.gn b/ios/chrome/browser/prerender/BUILD.gn
index 3342f6f..f5d7eb8 100644
--- a/ios/chrome/browser/prerender/BUILD.gn
+++ b/ios/chrome/browser/prerender/BUILD.gn
@@ -2,6 +2,19 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+source_set("prerender_pref") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  public = [ "prerender_pref.h" ]
+  sources = [ "prerender_pref.mm" ]
+
+  deps = [
+    "//base",
+    "//components/pref_registry",
+    "//components/prefs",
+    "//ios/chrome/browser:pref_names",
+  ]
+}
+
 source_set("prerender") {
   configs += [ "//build/config/compiler:enable_arc" ]
   public = [
@@ -20,6 +33,7 @@
   friend = [ ":unit_tests" ]
 
   deps = [
+    ":prerender_pref",
     "//base",
     "//components/keyed_service/core",
     "//components/keyed_service/ios",
@@ -69,6 +83,7 @@
   ]
   deps = [
     ":prerender",
+    ":prerender_pref",
     "//base",
     "//components/prefs",
     "//ios/chrome/browser",
diff --git a/ios/chrome/browser/prerender/preload_controller.mm b/ios/chrome/browser/prerender/preload_controller.mm
index a5497c9..60b1ae7 100644
--- a/ios/chrome/browser/prerender/preload_controller.mm
+++ b/ios/chrome/browser/prerender/preload_controller.mm
@@ -22,6 +22,7 @@
 #import "ios/chrome/browser/itunes_urls/itunes_urls_handler_tab_helper.h"
 #include "ios/chrome/browser/pref_names.h"
 #include "ios/chrome/browser/prerender/preload_controller_delegate.h"
+#import "ios/chrome/browser/prerender/prerender_pref.h"
 #import "ios/chrome/browser/signin/account_consistency_service_factory.h"
 #import "ios/chrome/browser/tabs/tab_helper_util.h"
 #import "ios/web/public/navigation/navigation_item.h"
@@ -185,14 +186,12 @@
 // there is no prerender scheduled.
 @property(nonatomic, readonly) const GURL& scheduledURL;
 
-// Whether or not the preference is enabled.
-@property(nonatomic, getter=isPreferenceEnabled) BOOL preferenceEnabled;
-
-// Whether or not prerendering is only when on wifi.
-@property(nonatomic, getter=isWifiOnly) BOOL wifiOnly;
+// Network prediction settings.
+@property(nonatomic)
+    prerender_prefs::NetworkPredictionSetting networkPredictionSetting;
 
 // Whether or not the current connection is using WWAN.
-@property(nonatomic, getter=isUsingWWAN) BOOL usingWWAN;
+@property(nonatomic, assign) BOOL isOnCellularNetwork;
 
 // Number of successful prerenders (i.e. the user viewed the prerendered page)
 // during the lifetime of this controller.
@@ -230,22 +229,21 @@
   DCHECK_CURRENTLY_ON(web::WebThread::UI);
   if ((self = [super init])) {
     _browserState = browserState;
-    _preferenceEnabled =
-        _browserState->GetPrefs()->GetBoolean(prefs::kNetworkPredictionEnabled);
-    _wifiOnly = _browserState->GetPrefs()->GetBoolean(
-        prefs::kNetworkPredictionWifiOnly);
-    _usingWWAN = net::NetworkChangeNotifier::IsConnectionCellular(
+    _networkPredictionSetting =
+        static_cast<prerender_prefs::NetworkPredictionSetting>(
+            _browserState->GetPrefs()->GetInteger(
+                prefs::kNetworkPredictionSetting));
+    _isOnCellularNetwork = net::NetworkChangeNotifier::IsConnectionCellular(
         net::NetworkChangeNotifier::GetConnectionType());
     _webStateDelegate = std::make_unique<web::WebStateDelegateBridge>(self);
     _webStateObserver = std::make_unique<web::WebStateObserverBridge>(self);
     _observerBridge = std::make_unique<PrefObserverBridge>(self);
     _prefChangeRegistrar.Init(_browserState->GetPrefs());
     _observerBridge->ObserveChangesForPreference(
-        prefs::kNetworkPredictionEnabled, &_prefChangeRegistrar);
-    _observerBridge->ObserveChangesForPreference(
-        prefs::kNetworkPredictionWifiOnly, &_prefChangeRegistrar);
+        prefs::kNetworkPredictionSetting, &_prefChangeRegistrar);
     _dialogPresenter = std::make_unique<PreloadJavaScriptDialogPresenter>(self);
-    if (_preferenceEnabled && _wifiOnly) {
+    if (_networkPredictionSetting ==
+        prerender_prefs::NetworkPredictionSetting::kEnabledWifiOnly) {
       _connectionTypeObserver =
           std::make_unique<ConnectionTypeObserverBridge>(self);
     }
@@ -273,11 +271,27 @@
 
 - (BOOL)isEnabled {
   DCHECK_CURRENTLY_ON(web::WebThread::UI);
-  return !IsPrerenderTabEvictionExperimentalGroup() && self.preferenceEnabled &&
-         !ios::device_util::IsSingleCoreDevice() &&
-         ios::device_util::RamIsAtLeast512Mb() &&
-         !net::NetworkChangeNotifier::IsOffline() &&
-         (!self.wifiOnly || !self.usingWWAN);
+
+  if (IsPrerenderTabEvictionExperimentalGroup() ||
+      ios::device_util::IsSingleCoreDevice() ||
+      !ios::device_util::RamIsAtLeast512Mb() ||
+      net::NetworkChangeNotifier::IsOffline()) {
+    return false;
+  }
+
+  switch (self.networkPredictionSetting) {
+    case prerender_prefs::NetworkPredictionSetting::kEnabledWifiOnly: {
+      return !self.isOnCellularNetwork;
+    }
+
+    case prerender_prefs::NetworkPredictionSetting::kEnabledWifiAndCellular: {
+      return true;
+    }
+
+    case prerender_prefs::NetworkPredictionSetting::kDisabled: {
+      return false;
+    }
+  }
 }
 
 - (void)setLoadCompleted:(BOOL)loadCompleted {
@@ -384,9 +398,13 @@
 
 - (void)connectionTypeChanged:(net::NetworkChangeNotifier::ConnectionType)type {
   DCHECK_CURRENTLY_ON(web::WebThread::UI);
-  self.usingWWAN = net::NetworkChangeNotifier::IsConnectionCellular(type);
-  if (self.wifiOnly && self.usingWWAN)
+  self.isOnCellularNetwork =
+      net::NetworkChangeNotifier::IsConnectionCellular(type);
+  if (self.networkPredictionSetting ==
+          prerender_prefs::NetworkPredictionSetting::kEnabledWifiOnly &&
+      self.isOnCellularNetwork) {
     [self cancelPrerender];
+  }
 }
 
 #pragma mark - CRWWebStateDelegate
@@ -480,29 +498,39 @@
 #pragma mark - PrefObserverDelegate
 
 - (void)onPreferenceChanged:(const std::string&)preferenceName {
-  if (preferenceName == prefs::kNetworkPredictionEnabled ||
-      preferenceName == prefs::kNetworkPredictionWifiOnly) {
+  if (preferenceName == prefs::kNetworkPredictionSetting) {
     DCHECK_CURRENTLY_ON(web::WebThread::UI);
     // The logic is simpler if both preferences changes are handled equally.
-    self.preferenceEnabled = self.browserState->GetPrefs()->GetBoolean(
-        prefs::kNetworkPredictionEnabled);
-    self.wifiOnly = self.browserState->GetPrefs()->GetBoolean(
-        prefs::kNetworkPredictionWifiOnly);
+    self.networkPredictionSetting =
+        static_cast<prerender_prefs::NetworkPredictionSetting>(
+            self.browserState->GetPrefs()->GetInteger(
+                prefs::kNetworkPredictionSetting));
 
-    if (self.wifiOnly && self.preferenceEnabled) {
-      if (!_connectionTypeObserver.get()) {
-        self.usingWWAN = net::NetworkChangeNotifier::IsConnectionCellular(
-            net::NetworkChangeNotifier::GetConnectionType());
-        _connectionTypeObserver.reset(new ConnectionTypeObserverBridge(self));
+    switch (self.networkPredictionSetting) {
+      case prerender_prefs::NetworkPredictionSetting::kEnabledWifiOnly: {
+        if (!_connectionTypeObserver.get()) {
+          self.isOnCellularNetwork =
+              net::NetworkChangeNotifier::IsConnectionCellular(
+                  net::NetworkChangeNotifier::GetConnectionType());
+          _connectionTypeObserver =
+              std::make_unique<ConnectionTypeObserverBridge>(self);
+        }
+        if (self.isOnCellularNetwork) {
+          [self cancelPrerender];
+        }
+        break;
       }
-      if (self.usingWWAN) {
+
+      case prerender_prefs::NetworkPredictionSetting::kEnabledWifiAndCellular: {
+        _connectionTypeObserver.reset();
+        break;
+      }
+
+      case prerender_prefs::NetworkPredictionSetting::kDisabled: {
         [self cancelPrerender];
+        _connectionTypeObserver.reset();
+        break;
       }
-    } else if (self.preferenceEnabled) {
-      _connectionTypeObserver.reset();
-    } else {
-      [self cancelPrerender];
-      _connectionTypeObserver.reset();
     }
   }
 }
diff --git a/ios/chrome/browser/prerender/preload_controller_unittest.mm b/ios/chrome/browser/prerender/preload_controller_unittest.mm
index f36bfc7..737ea174 100644
--- a/ios/chrome/browser/prerender/preload_controller_unittest.mm
+++ b/ios/chrome/browser/prerender/preload_controller_unittest.mm
@@ -11,6 +11,7 @@
 #include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
 #include "ios/chrome/browser/pref_names.h"
 #import "ios/chrome/browser/prerender/preload_controller.h"
+#import "ios/chrome/browser/prerender/prerender_pref.h"
 #include "ios/web/public/test/web_task_environment.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/platform_test.h"
@@ -65,24 +66,25 @@
 
   // Set the "Preload webpages" setting to "Always".
   void PreloadWebpagesAlways() {
-    chrome_browser_state_->GetPrefs()->SetBoolean(
-        prefs::kNetworkPredictionEnabled, YES);
-    chrome_browser_state_->GetPrefs()->SetBoolean(
-        prefs::kNetworkPredictionWifiOnly, NO);
+    chrome_browser_state_->GetPrefs()->SetInteger(
+        prefs::kNetworkPredictionSetting,
+        static_cast<int>(prerender_prefs::NetworkPredictionSetting::
+                             kEnabledWifiAndCellular));
   }
 
   // Set the "Preload webpages" setting to "Only on Wi-Fi".
   void PreloadWebpagesWiFiOnly() {
-    chrome_browser_state_->GetPrefs()->SetBoolean(
-        prefs::kNetworkPredictionEnabled, YES);
-    chrome_browser_state_->GetPrefs()->SetBoolean(
-        prefs::kNetworkPredictionWifiOnly, YES);
+    chrome_browser_state_->GetPrefs()->SetInteger(
+        prefs::kNetworkPredictionSetting,
+        static_cast<int>(
+            prerender_prefs::NetworkPredictionSetting::kEnabledWifiOnly));
   }
 
   // Set the "Preload webpages" setting to "Never".
   void PreloadWebpagesNever() {
-    chrome_browser_state_->GetPrefs()->SetBoolean(
-        prefs::kNetworkPredictionEnabled, NO);
+    chrome_browser_state_->GetPrefs()->SetInteger(
+        prefs::kNetworkPredictionSetting,
+        static_cast<int>(prerender_prefs::NetworkPredictionSetting::kDisabled));
   }
 
   void SimulateWiFiConnection() {
diff --git a/ios/chrome/browser/prerender/prerender_pref.h b/ios/chrome/browser/prerender/prerender_pref.h
new file mode 100644
index 0000000..9a2b14d
--- /dev/null
+++ b/ios/chrome/browser/prerender/prerender_pref.h
@@ -0,0 +1,32 @@
+// 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 IOS_CHROME_BROWSER_PRERENDER_PRERENDER_PREF_H_
+#define IOS_CHROME_BROWSER_PRERENDER_PRERENDER_PREF_H_
+
+class PrefService;
+namespace user_prefs {
+class PrefRegistrySyncable;
+}  // namespace user_prefs
+
+namespace prerender_prefs {
+
+// Setting for prerender network prediction.
+// Keep these values consistent when changing this enum as it's saved in a pref.
+enum class NetworkPredictionSetting {
+  kDisabled = 0,
+  kEnabledWifiOnly = 1,
+  kEnabledWifiAndCellular = 2,
+};
+
+// Register prerender network prediction preferences.
+void RegisterNetworkPredictionPrefs(user_prefs::PrefRegistrySyncable* registry);
+
+// Migrate kNetworkPredictionEnabled, kNetworkPredictionWifiOnly to the new
+// kNetworkPredictionSetting pref.
+void MigrateNetworkPredictionPrefs(PrefService* prefs);
+
+}  // namespace prerender_prefs
+
+#endif  // IOS_CHROME_BROWSER_PRERENDER_PRERENDER_PREF_H_
diff --git a/ios/chrome/browser/prerender/prerender_pref.mm b/ios/chrome/browser/prerender/prerender_pref.mm
new file mode 100644
index 0000000..d33b301f
--- /dev/null
+++ b/ios/chrome/browser/prerender/prerender_pref.mm
@@ -0,0 +1,77 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/prerender/prerender_pref.h"
+
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/pref_service.h"
+#include "ios/chrome/browser/pref_names.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace prerender_prefs {
+
+// A boolean pref set to true if prediction of network actions is allowed.
+// Actions include prerendering of web pages.
+// NOTE: The "dns_prefetching.enabled" value is used so that historical user
+// preferences are not lost.
+const char kNetworkPredictionEnabled[] = "dns_prefetching.enabled";
+
+// Preference that hold a boolean indicating whether network prediction should
+// be limited to wifi (when enabled).
+const char kNetworkPredictionWifiOnly[] = "ios.dns_prefetching.wifi_only";
+
+void RegisterNetworkPredictionPrefs(
+    user_prefs::PrefRegistrySyncable* registry) {
+  registry->RegisterIntegerPref(
+      prefs::kNetworkPredictionSetting,
+      static_cast<int>(NetworkPredictionSetting::kEnabledWifiOnly),
+      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+
+  registry->RegisterBooleanPref(
+      kNetworkPredictionEnabled, true,
+      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+  registry->RegisterBooleanPref(
+      kNetworkPredictionWifiOnly, true,
+      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+}
+
+void MigrateNetworkPredictionPrefs(PrefService* pref_service) {
+  if (pref_service->GetUserPrefValue(prefs::kNetworkPredictionSetting)) {
+    // Already migrated
+    return;
+  }
+
+  // Check if the old setting was ever set.
+  if (!pref_service->GetUserPrefValue(kNetworkPredictionEnabled) &&
+      !pref_service->GetUserPrefValue(kNetworkPredictionWifiOnly)) {
+    // Nothing to migrate.
+    pref_service->ClearPref(kNetworkPredictionEnabled);
+    pref_service->ClearPref(kNetworkPredictionWifiOnly);
+    return;
+  }
+
+  // Migrate kNetworkPredictionEnabled and kNetworkPredictionWifiOnly to
+  // kNetworkPredictionSetting.
+  bool networkPredictionEnabled =
+      pref_service->GetUserPrefValue(kNetworkPredictionEnabled);
+  bool networkPredictionWifiOnly =
+      pref_service->GetUserPrefValue(kNetworkPredictionWifiOnly);
+
+  NetworkPredictionSetting new_value =
+      networkPredictionEnabled
+          ? (networkPredictionWifiOnly
+                 ? NetworkPredictionSetting::kEnabledWifiOnly
+                 : NetworkPredictionSetting::kEnabledWifiAndCellular)
+          : NetworkPredictionSetting::kDisabled;
+
+  pref_service->SetInteger(prefs::kNetworkPredictionSetting,
+                           static_cast<int>(new_value));
+  pref_service->ClearPref(kNetworkPredictionEnabled);
+  pref_service->ClearPref(kNetworkPredictionWifiOnly);
+}
+
+}  // namespace prerender_prefs
diff --git a/ios/chrome/browser/sessions/session_restoration_browser_agent.mm b/ios/chrome/browser/sessions/session_restoration_browser_agent.mm
index ee29195a..88826e7 100644
--- a/ios/chrome/browser/sessions/session_restoration_browser_agent.mm
+++ b/ios/chrome/browser/sessions/session_restoration_browser_agent.mm
@@ -71,9 +71,6 @@
 
 void SessionRestorationBrowserAgent::SetSessionID(
     const std::string& session_identifier) {
-  // It's probably incorrect to set this more than once.
-  DCHECK(session_identifier_.empty() ||
-         session_identifier_ == session_identifier);
   session_identifier_ = session_identifier;
 }
 
diff --git a/ios/chrome/browser/ui/bubble/bubble_presenter.mm b/ios/chrome/browser/ui/bubble/bubble_presenter.mm
index 1c8fd9e1..fe0975306 100644
--- a/ios/chrome/browser/ui/bubble/bubble_presenter.mm
+++ b/ios/chrome/browser/ui/bubble/bubble_presenter.mm
@@ -163,11 +163,7 @@
   // The bottom toolbar and Discover feed header menu don't use the
   // isUserEngaged, so don't check if the user is engaged here.
   [self presentBottomToolbarTipBubble];
-
-  // TODO(crbug.com/1132757): Reenable this once the DCHECKS for bubble size in
-  // [BubbleViewControllerPresenter frameForBubbleInRect:atAnchorPoint:] stop
-  // being hit.
-  // [self presentDiscoverFeedHeaderTipBubble];
+  [self presentDiscoverFeedHeaderTipBubble];
 }
 
 - (void)presentLongPressBubble {
@@ -264,9 +260,6 @@
 
 // Presents a bubble associated with the Discover feed header's menu button.
 - (void)presentDiscoverFeedHeaderTipBubble {
-  if (![self canPresentBubble])
-    return;
-
   BubbleArrowDirection arrowDirection = BubbleArrowDirectionDown;
   NSString* text =
       l10n_util::GetNSStringWithFixup(IDS_IOS_DISCOVER_FEED_HEADER_IPH);
@@ -275,6 +268,12 @@
                                            view:self.rootViewController.view];
   DCHECK(guide);
   UIView* menuButton = guide.constrainedView;
+  // Checks "canPresentBubble" after checking that the NTP with feed is visible.
+  // This ensures that the feature tracker doesn't trigger the IPH event if the
+  // bubble isn't shown, which would prevent it from ever being shown again.
+  if (!menuButton || ![self canPresentBubble]) {
+    return;
+  }
   CGPoint discoverFeedHeaderAnchor =
       [menuButton.superview convertPoint:menuButton.frame.origin toView:nil];
   discoverFeedHeaderAnchor.x += menuButton.frame.size.width / 2;
diff --git a/ios/chrome/browser/ui/main/BUILD.gn b/ios/chrome/browser/ui/main/BUILD.gn
index 9812939..7c450a97 100644
--- a/ios/chrome/browser/ui/main/BUILD.gn
+++ b/ios/chrome/browser/ui/main/BUILD.gn
@@ -145,6 +145,7 @@
     "//base",
     "//components/translate/core/browser",
     "//ios/chrome/app:mode",
+    "//ios/chrome/app/application_delegate:app_state_header",
     "//ios/chrome/app/resources:launchscreen_xib",
     "//ios/chrome/browser",
     "//ios/chrome/browser/app_launcher",
diff --git a/ios/chrome/browser/ui/main/browser_view_wrangler.mm b/ios/chrome/browser/ui/main/browser_view_wrangler.mm
index 6470203..bcdabedc7 100644
--- a/ios/chrome/browser/ui/main/browser_view_wrangler.mm
+++ b/ios/chrome/browser/ui/main/browser_view_wrangler.mm
@@ -7,6 +7,7 @@
 #include "base/feature_list.h"
 #include "base/files/file_path.h"
 #include "base/strings/sys_string_conversions.h"
+#import "ios/chrome/app/application_delegate/app_state.h"
 #include "ios/chrome/browser/application_context.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #include "ios/chrome/browser/crash_report/crash_report_helper.h"
@@ -167,10 +168,19 @@
   std::string sessionID = base::SysNSStringToUTF8(self.sessionID);
   SnapshotBrowserAgent::FromBrowser(_mainBrowser.get())
       ->SetSessionID(sessionID);
-  SessionRestorationBrowserAgent::FromBrowser(_mainBrowser.get())
-      ->SetSessionID(sessionID);
-  SessionRestorationBrowserAgent::FromBrowser(_mainBrowser.get())
-      ->RestoreSession();
+
+  // If the OS doesn't support multiple scenes, use the previous run scene ID
+  // for the session restoration.
+  NSString* restoreSessionID = self.sessionID;
+  if (_sceneState.appState.previousSingleWindowSessionID) {
+    restoreSessionID = _sceneState.appState.previousSingleWindowSessionID;
+  }
+  SessionRestorationBrowserAgent* restorationAgent =
+      SessionRestorationBrowserAgent::FromBrowser(_mainBrowser.get());
+  restorationAgent->SetSessionID(base::SysNSStringToUTF8(restoreSessionID));
+  restorationAgent->RestoreSession();
+  restorationAgent->SetSessionID(sessionID);
+
   breakpad::MonitorTabStateForWebStateList(_mainBrowser->GetWebStateList());
   // Follow loaded URLs in the main tab model to send those in case of
   // crashes.
diff --git a/ios/chrome/browser/ui/main/scene_controller.mm b/ios/chrome/browser/ui/main/scene_controller.mm
index 30a9be9..6cff175 100644
--- a/ios/chrome/browser/ui/main/scene_controller.mm
+++ b/ios/chrome/browser/ui/main/scene_controller.mm
@@ -363,8 +363,13 @@
   if (self.hasInitializedUI && level == SceneActivationLevelUnattached) {
     if (IsMultiwindowSupported()) {
       if (@available(iOS 13, *)) {
-        [[PreviousSessionInfo sharedInstance]
-            removeSceneSessionID:sceneState.scene.session.persistentIdentifier];
+        if (IsMultipleScenesSupported()) {
+          // If Multiple scenes are not supported, the session shouldn't be
+          // removed as it can be used for normal restoration.
+          [[PreviousSessionInfo sharedInstance]
+              removeSceneSessionID:sceneState.scene.session
+                                       .persistentIdentifier];
+        }
       }
     }
     [self teardownUI];
diff --git a/ios/chrome/browser/ui/settings/BUILD.gn b/ios/chrome/browser/ui/settings/BUILD.gn
index c1ce14be..eb36f3c 100644
--- a/ios/chrome/browser/ui/settings/BUILD.gn
+++ b/ios/chrome/browser/ui/settings/BUILD.gn
@@ -136,6 +136,7 @@
     "//ios/chrome/browser/mailto:feature_flags",
     "//ios/chrome/browser/main",
     "//ios/chrome/browser/passwords",
+    "//ios/chrome/browser/prerender:prerender_pref",
     "//ios/chrome/browser/search_engines",
     "//ios/chrome/browser/signin",
     "//ios/chrome/browser/sync",
@@ -304,6 +305,7 @@
     "//ios/chrome/browser/main:test_support",
     "//ios/chrome/browser/passwords",
     "//ios/chrome/browser/prefs:browser_prefs",
+    "//ios/chrome/browser/prerender:prerender_pref",
     "//ios/chrome/browser/search_engines",
     "//ios/chrome/browser/signin",
     "//ios/chrome/browser/signin:test_support",
diff --git a/ios/chrome/browser/ui/settings/bandwidth_management_table_view_controller.mm b/ios/chrome/browser/ui/settings/bandwidth_management_table_view_controller.mm
index 39271cf..42963375 100644
--- a/ios/chrome/browser/ui/settings/bandwidth_management_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/bandwidth_management_table_view_controller.mm
@@ -75,10 +75,7 @@
     // Register to observe any changes on Perf backed values displayed by the
     // screen.
     _prefObserverBridge->ObserveChangesForPreference(
-        prefs::kNetworkPredictionEnabled,
-        &_prefChangeRegistrarApplicationContext);
-    _prefObserverBridge->ObserveChangesForPreference(
-        prefs::kNetworkPredictionWifiOnly,
+        prefs::kNetworkPredictionSetting,
         &_prefChangeRegistrarApplicationContext);
   }
   return self;
@@ -143,8 +140,7 @@
         l10n_util::GetNSString(IDS_IOS_OPTIONS_PRELOAD_WEBPAGES);
     UIViewController* controller = [[DataplanUsageTableViewController alloc]
         initWithPrefs:_browserState->GetPrefs()
-             basePref:prefs::kNetworkPredictionEnabled
-             wifiPref:prefs::kNetworkPredictionWifiOnly
+          settingPref:prefs::kNetworkPredictionSetting
                 title:preloadTitle];
     [self.navigationController pushViewController:controller animated:YES];
   }
@@ -153,12 +149,10 @@
 #pragma mark - PrefObserverDelegate
 
 - (void)onPreferenceChanged:(const std::string&)preferenceName {
-  if (preferenceName == prefs::kNetworkPredictionEnabled ||
-      preferenceName == prefs::kNetworkPredictionWifiOnly) {
+  if (preferenceName == prefs::kNetworkPredictionSetting) {
     NSString* detailText = [DataplanUsageTableViewController
         currentLabelForPreference:_browserState->GetPrefs()
-                         basePref:prefs::kNetworkPredictionEnabled
-                         wifiPref:prefs::kNetworkPredictionWifiOnly];
+                      settingPref:prefs::kNetworkPredictionSetting];
 
     _preloadWebpagesDetailItem.detailText = detailText;
 
@@ -173,8 +167,7 @@
 - (TableViewDetailIconItem*)preloadWebpagesItem {
   NSString* detailText = [DataplanUsageTableViewController
       currentLabelForPreference:_browserState->GetPrefs()
-                       basePref:prefs::kNetworkPredictionEnabled
-                       wifiPref:prefs::kNetworkPredictionWifiOnly];
+                    settingPref:prefs::kNetworkPredictionSetting];
   _preloadWebpagesDetailItem =
       [[TableViewDetailIconItem alloc] initWithType:ItemTypePreload];
 
diff --git a/ios/chrome/browser/ui/settings/bandwidth_management_table_view_controller_unittest.mm b/ios/chrome/browser/ui/settings/bandwidth_management_table_view_controller_unittest.mm
index f2393d9..d45a8989 100644
--- a/ios/chrome/browser/ui/settings/bandwidth_management_table_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/settings/bandwidth_management_table_view_controller_unittest.mm
@@ -82,8 +82,7 @@
       l10n_util::GetNSString(IDS_IOS_OPTIONS_PRELOAD_WEBPAGES);
   NSString* expected_subtitle = [DataplanUsageTableViewController
       currentLabelForPreference:chrome_browser_state_->GetPrefs()
-                       basePref:prefs::kNetworkPredictionEnabled
-                       wifiPref:prefs::kNetworkPredictionWifiOnly];
+                    settingPref:prefs::kNetworkPredictionSetting];
   CheckTextCellTextAndDetailText(expected_title, expected_subtitle, 0, 0);
   EXPECT_NE(nil, [controller().tableViewModel footerForSection:0]);
 }
diff --git a/ios/chrome/browser/ui/settings/dataplan_usage_table_view_controller.h b/ios/chrome/browser/ui/settings/dataplan_usage_table_view_controller.h
index db48f1e..81d0f96 100644
--- a/ios/chrome/browser/ui/settings/dataplan_usage_table_view_controller.h
+++ b/ios/chrome/browser/ui/settings/dataplan_usage_table_view_controller.h
@@ -14,8 +14,7 @@
 @interface DataplanUsageTableViewController : SettingsRootTableViewController
 
 - (instancetype)initWithPrefs:(PrefService*)prefs
-                     basePref:(const char*)basePreference
-                     wifiPref:(const char*)wifiPreference
+                  settingPref:(const char*)settingPreference
                         title:(NSString*)title NS_DESIGNATED_INITIALIZER;
 
 - (instancetype)initWithStyle:(UITableViewStyle)style NS_UNAVAILABLE;
@@ -24,8 +23,7 @@
 // preferences.  Kept in this class, so that all of the code to translate from
 // preferences to UI is in one place.
 + (NSString*)currentLabelForPreference:(PrefService*)prefs
-                              basePref:(const char*)basePreference
-                              wifiPref:(const char*)wifiPreference;
+                           settingPref:(const char*)settingsPreference;
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_SETTINGS_DATAPLAN_USAGE_TABLE_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/ui/settings/dataplan_usage_table_view_controller.mm b/ios/chrome/browser/ui/settings/dataplan_usage_table_view_controller.mm
index ff0e4ed..7c4bfdb6 100644
--- a/ios/chrome/browser/ui/settings/dataplan_usage_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/dataplan_usage_table_view_controller.mm
@@ -8,6 +8,7 @@
 #import "base/mac/foundation_util.h"
 #include "components/prefs/pref_member.h"
 #include "components/prefs/pref_service.h"
+#import "ios/chrome/browser/prerender/prerender_pref.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_detail_text_item.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_text_item.h"
 #import "ios/chrome/browser/ui/table_view/chrome_table_view_styler.h"
@@ -21,30 +22,57 @@
 #error "This file requires ARC support."
 #endif
 
+using prerender_prefs::NetworkPredictionSetting;
+
 namespace {
 
 typedef NS_ENUM(NSInteger, SectionIdentifier) {
   SectionIdentifierOptions = kSectionIdentifierEnumZero,
 };
 
+// Item types to enumerate the table items.
 typedef NS_ENUM(NSInteger, ItemType) {
-  ItemTypeOptionsAlways = kItemTypeEnumZero,
-  ItemTypeOptionsOnlyOnWiFi,
+  ItemTypeOptionsEnabledAlways = kItemTypeEnumZero,
+  ItemTypeOptionsWifiOnly,
   ItemTypeOptionsNever,
 };
 
+// Converts an NetworkPredictionSetting, to a corresponding ItemType.
+ItemType ItemTypeWithSetting(NetworkPredictionSetting setting) {
+  switch (setting) {
+    case NetworkPredictionSetting::kEnabledWifiAndCellular: {
+      return ItemTypeOptionsEnabledAlways;
+    }
+    case NetworkPredictionSetting::kEnabledWifiOnly: {
+      return ItemTypeOptionsWifiOnly;
+    }
+    case NetworkPredictionSetting::kDisabled: {
+      return ItemTypeOptionsNever;
+    }
+  }
+}
+
+// Converts an ItemType, to a corresponding NetworkPredictionSetting.
+NetworkPredictionSetting SettingWithItemType(ItemType item_type) {
+  switch (item_type) {
+    case ItemTypeOptionsEnabledAlways: {
+      return NetworkPredictionSetting::kEnabledWifiAndCellular;
+    }
+    case ItemTypeOptionsWifiOnly: {
+      return NetworkPredictionSetting::kEnabledWifiOnly;
+    }
+    case ItemTypeOptionsNever: {
+      return NetworkPredictionSetting::kDisabled;
+    }
+  }
+}
+
 }  // namespace.
 
 @interface DataplanUsageTableViewController () {
-  BooleanPrefMember _basePreference;
-  BooleanPrefMember _wifiPreference;
+  IntegerPrefMember _settingPreference;
 }
 
-// Updates the checked state of the cells to match the preferences.
-- (void)updateCheckedState;
-
-// Updates the PrefService with the given values.
-- (void)updateBasePref:(BOOL)basePref wifiPref:(BOOL)wifiPref;
 @end
 
 @implementation DataplanUsageTableViewController
@@ -52,8 +80,7 @@
 #pragma mark - Initialization
 
 - (instancetype)initWithPrefs:(PrefService*)prefs
-                     basePref:(const char*)basePreference
-                     wifiPref:(const char*)wifiPreference
+                  settingPref:(const char*)settingPreference
                         title:(NSString*)title {
   UITableViewStyle style = base::FeatureList::IsEnabled(kSettingsRefresh)
                                ? UITableViewStylePlain
@@ -61,8 +88,7 @@
   self = [super initWithStyle:style];
   if (self) {
     self.title = title;
-    _basePreference.Init(basePreference, prefs);
-    _wifiPreference.Init(wifiPreference, prefs);
+    _settingPreference.Init(settingPreference, prefs);
   }
   return self;
 }
@@ -81,20 +107,22 @@
   TableViewModel<TableViewItem*>* model = self.tableViewModel;
   [model addSectionWithIdentifier:SectionIdentifierOptions];
 
-  TableViewDetailTextItem* always =
-      [[TableViewDetailTextItem alloc] initWithType:ItemTypeOptionsAlways];
+  TableViewDetailTextItem* always = [[TableViewDetailTextItem alloc]
+      initWithType:ItemTypeWithSetting(
+                       NetworkPredictionSetting::kEnabledWifiAndCellular)];
   [always setText:l10n_util::GetNSString(IDS_IOS_OPTIONS_DATA_USAGE_ALWAYS)];
   [always setAccessibilityTraits:UIAccessibilityTraitButton];
   [model addItem:always toSectionWithIdentifier:SectionIdentifierOptions];
 
-  TableViewDetailTextItem* wifi =
-      [[TableViewDetailTextItem alloc] initWithType:ItemTypeOptionsOnlyOnWiFi];
+  TableViewDetailTextItem* wifi = [[TableViewDetailTextItem alloc]
+      initWithType:ItemTypeWithSetting(
+                       NetworkPredictionSetting::kEnabledWifiOnly)];
   [wifi setText:l10n_util::GetNSString(IDS_IOS_OPTIONS_DATA_USAGE_ONLY_WIFI)];
   [wifi setAccessibilityTraits:UIAccessibilityTraitButton];
   [model addItem:wifi toSectionWithIdentifier:SectionIdentifierOptions];
 
-  TableViewDetailTextItem* never =
-      [[TableViewDetailTextItem alloc] initWithType:ItemTypeOptionsNever];
+  TableViewDetailTextItem* never = [[TableViewDetailTextItem alloc]
+      initWithType:ItemTypeWithSetting(NetworkPredictionSetting::kDisabled)];
   [never setText:l10n_util::GetNSString(IDS_IOS_OPTIONS_DATA_USAGE_NEVER)];
   [never setAccessibilityTraits:UIAccessibilityTraitButton];
   [model addItem:never toSectionWithIdentifier:SectionIdentifierOptions];
@@ -102,26 +130,23 @@
   [self updateCheckedState];
 }
 
+// Updates the checked state of the cells to match the preferences.
 - (void)updateCheckedState {
-  BOOL basePrefOn = _basePreference.GetValue();
-  BOOL wifiPrefOn = _wifiPreference.GetValue();
-  TableViewModel<TableViewItem*>* model = self.tableViewModel;
+  NetworkPredictionSetting setting =
+      static_cast<prerender_prefs::NetworkPredictionSetting>(
+          _settingPreference.GetValue());
 
-  std::unordered_map<NSInteger, bool> optionsMap = {
-      {ItemTypeOptionsAlways, basePrefOn && !wifiPrefOn},
-      {ItemTypeOptionsOnlyOnWiFi, basePrefOn && wifiPrefOn},
-      {ItemTypeOptionsNever, !basePrefOn},
-  };
+  TableViewModel<TableViewItem*>* model = self.tableViewModel;
 
   NSMutableArray* modifiedItems = [NSMutableArray array];
   for (TableViewDetailTextItem* item in
        [model itemsInSectionWithIdentifier:SectionIdentifierOptions]) {
-    auto value = optionsMap.find(item.type);
-    DCHECK(value != optionsMap.end());
+    NetworkPredictionSetting itemSetting =
+        SettingWithItemType(static_cast<ItemType>(item.type));
 
     UITableViewCellAccessoryType desiredType =
-        value->second ? UITableViewCellAccessoryCheckmark
-                      : UITableViewCellAccessoryNone;
+        itemSetting == setting ? UITableViewCellAccessoryCheckmark
+                               : UITableViewCellAccessoryNone;
     if (item.accessoryType != desiredType) {
       item.accessoryType = desiredType;
       [modifiedItems addObject:item];
@@ -131,44 +156,43 @@
   [self reconfigureCellsForItems:modifiedItems];
 }
 
-- (void)updateBasePref:(BOOL)basePref wifiPref:(BOOL)wifiPref {
-  _basePreference.SetValue(basePref);
-  _wifiPreference.SetValue(wifiPref);
-  [self updateCheckedState];
-}
-
 #pragma mark - Internal methods
 
 + (NSString*)currentLabelForPreference:(PrefService*)prefs
-                              basePref:(const char*)basePreference
-                              wifiPref:(const char*)wifiPreference {
+                           settingPref:(const char*)settingPreference {
   if (!prefs)
     return nil;
-  if (prefs->GetBoolean(basePreference)) {
-    if (prefs->GetBoolean(wifiPreference))
+
+  NetworkPredictionSetting setting =
+      static_cast<prerender_prefs::NetworkPredictionSetting>(
+          prefs->GetInteger(settingPreference));
+  switch (setting) {
+    case NetworkPredictionSetting::kDisabled: {
+      return l10n_util::GetNSString(IDS_IOS_OPTIONS_DATA_USAGE_NEVER);
+    }
+    case NetworkPredictionSetting::kEnabledWifiOnly: {
       return l10n_util::GetNSString(IDS_IOS_OPTIONS_DATA_USAGE_ONLY_WIFI);
-    else
+    }
+
+    case NetworkPredictionSetting::kEnabledWifiAndCellular: {
       return l10n_util::GetNSString(IDS_IOS_OPTIONS_DATA_USAGE_ALWAYS);
+    }
   }
-  return l10n_util::GetNSString(IDS_IOS_OPTIONS_DATA_USAGE_NEVER);
+}
+
+- (void)updateSetting:(prerender_prefs::NetworkPredictionSetting)newSetting {
+  _settingPreference.SetValue(static_cast<int>(newSetting));
+  [self updateCheckedState];
 }
 
 #pragma mark - UITableViewDelegate
 
 - (void)tableView:(UITableView*)tableView
     didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
-  NSInteger type = [self.tableViewModel itemTypeForIndexPath:indexPath];
-  switch (type) {
-    case ItemTypeOptionsAlways:
-      [self updateBasePref:YES wifiPref:NO];
-      break;
-    case ItemTypeOptionsOnlyOnWiFi:
-      [self updateBasePref:YES wifiPref:YES];
-      break;
-    case ItemTypeOptionsNever:
-      [self updateBasePref:NO wifiPref:NO];
-      break;
-  }
+  NetworkPredictionSetting chosenSetting =
+      SettingWithItemType(static_cast<ItemType>(
+          [self.tableViewModel itemTypeForIndexPath:indexPath]));
+  [self updateSetting:chosenSetting];
 
   [tableView deselectRowAtIndexPath:indexPath animated:YES];
 }
diff --git a/ios/chrome/browser/ui/settings/dataplan_usage_table_view_controller_unittest.mm b/ios/chrome/browser/ui/settings/dataplan_usage_table_view_controller_unittest.mm
index db20567..6625974 100644
--- a/ios/chrome/browser/ui/settings/dataplan_usage_table_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/settings/dataplan_usage_table_view_controller_unittest.mm
@@ -13,6 +13,7 @@
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 #include "components/sync_preferences/pref_service_mock_factory.h"
+#import "ios/chrome/browser/prerender/prerender_pref.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_detail_text_item.h"
 #import "ios/chrome/browser/ui/table_view/chrome_table_view_controller_test.h"
 #include "ios/chrome/grit/ios_strings.h"
@@ -23,14 +24,15 @@
 #error "This file requires ARC support."
 #endif
 
+using prerender_prefs::NetworkPredictionSetting;
+
 @interface DataplanUsageTableViewController (ExposedForTesting)
-- (void)updateBasePref:(BOOL)basePref wifiPref:(BOOL)wifiPref;
+- (void)updateSetting:(prerender_prefs::NetworkPredictionSetting)newSetting;
 @end
 
 namespace {
 
-const char* kBasePref = "BasePref";
-const char* kWifiPref = "WifiPref";
+const char* kPrefName = "SettingPref";
 
 class DataplanUsageTableViewControllerTest
     : public ChromeTableViewControllerTest {
@@ -44,16 +46,15 @@
   ChromeTableViewController* InstantiateController() override {
     dataplanController_ = [[DataplanUsageTableViewController alloc]
         initWithPrefs:pref_service_.get()
-             basePref:kBasePref
-             wifiPref:kWifiPref
+          settingPref:kPrefName
                 title:@"CollectionTitle"];
     return dataplanController_;
   }
 
   std::unique_ptr<PrefService> CreateLocalState() {
     scoped_refptr<PrefRegistrySimple> registry(new PrefRegistrySimple());
-    registry->RegisterBooleanPref(kBasePref, false);
-    registry->RegisterBooleanPref(kWifiPref, false);
+    registry->RegisterIntegerPref(
+        kPrefName, static_cast<int>(NetworkPredictionSetting::kDisabled));
 
     sync_preferences::PrefServiceMockFactory factory;
     base::FilePath path("DataplanUsageTableViewControllerTest.pref");
@@ -97,22 +98,19 @@
   ASSERT_EQ(1, NumberOfSections());
   ASSERT_EQ(3, NumberOfItemsInSection(0));
 
-  [dataplanController_ updateBasePref:YES wifiPref:YES];
+  [dataplanController_
+      updateSetting:NetworkPredictionSetting::kEnabledWifiOnly];
   CheckTextItemAccessoryType(UITableViewCellAccessoryNone, 0, 0);
   CheckTextItemAccessoryType(UITableViewCellAccessoryCheckmark, 0, 1);
   CheckTextItemAccessoryType(UITableViewCellAccessoryNone, 0, 2);
 
-  [dataplanController_ updateBasePref:YES wifiPref:NO];
+  [dataplanController_
+      updateSetting:NetworkPredictionSetting::kEnabledWifiAndCellular];
   CheckTextItemAccessoryType(UITableViewCellAccessoryCheckmark, 0, 0);
   CheckTextItemAccessoryType(UITableViewCellAccessoryNone, 0, 1);
   CheckTextItemAccessoryType(UITableViewCellAccessoryNone, 0, 2);
 
-  [dataplanController_ updateBasePref:NO wifiPref:YES];
-  CheckTextItemAccessoryType(UITableViewCellAccessoryNone, 0, 0);
-  CheckTextItemAccessoryType(UITableViewCellAccessoryNone, 0, 1);
-  CheckTextItemAccessoryType(UITableViewCellAccessoryCheckmark, 0, 2);
-
-  [dataplanController_ updateBasePref:NO wifiPref:NO];
+  [dataplanController_ updateSetting:NetworkPredictionSetting::kDisabled];
   CheckTextItemAccessoryType(UITableViewCellAccessoryNone, 0, 0);
   CheckTextItemAccessoryType(UITableViewCellAccessoryNone, 0, 1);
   CheckTextItemAccessoryType(UITableViewCellAccessoryCheckmark, 0, 2);
diff --git a/ios/web_view/shell/shell_autofill_delegate.m b/ios/web_view/shell/shell_autofill_delegate.m
index 8afd6bf..e69e9493 100644
--- a/ios/web_view/shell/shell_autofill_delegate.m
+++ b/ios/web_view/shell/shell_autofill_delegate.m
@@ -159,6 +159,16 @@
 }
 
 - (void)autofillController:(CWVAutofillController*)autofillController
+    confirmCreditCardNameWithFixer:(CWVCreditCardNameFixer*)fixer {
+  [fixer acceptWithName:fixer.inferredCardHolderName ?: @""];
+}
+
+- (void)autofillController:(CWVAutofillController*)autofillController
+    confirmCreditCardExpirationWithFixer:(CWVCreditCardExpirationFixer*)fixer {
+  [fixer cancel];
+}
+
+- (void)autofillController:(CWVAutofillController*)autofillController
     decideSavePolicyForPassword:(CWVPassword*)password
                 decisionHandler:(void (^)(CWVPasswordUserDecision decision))
                                     decisionHandler {
diff --git a/net/base/features.cc b/net/base/features.cc
index be6707b8..8c19303 100644
--- a/net/base/features.cc
+++ b/net/base/features.cc
@@ -177,5 +177,23 @@
     "LimitOpenUDPSocketsMax",
     6000);
 
+const base::Feature kTimeoutTcpConnectAttempt{
+    "TimeoutTcpConnectAttempt", base::FEATURE_DISABLED_BY_DEFAULT};
+
+extern const base::FeatureParam<double> kTimeoutTcpConnectAttemptRTTMultiplier(
+    &kTimeoutTcpConnectAttempt,
+    "TimeoutTcpConnectAttemptRTTMultiplier",
+    5.0);
+
+extern const base::FeatureParam<base::TimeDelta> kTimeoutTcpConnectAttemptMin(
+    &kTimeoutTcpConnectAttempt,
+    "TimeoutTcpConnectAttemptMin",
+    base::TimeDelta::FromSeconds(8));
+
+extern const base::FeatureParam<base::TimeDelta> kTimeoutTcpConnectAttemptMax(
+    &kTimeoutTcpConnectAttempt,
+    "TimeoutTcpConnectAttemptMax",
+    base::TimeDelta::FromSeconds(30));
+
 }  // namespace features
 }  // namespace net
diff --git a/net/base/features.h b/net/base/features.h
index 050f74c..491b1e5 100644
--- a/net/base/features.h
+++ b/net/base/features.h
@@ -266,6 +266,28 @@
 // this will result in a failure (ERR_INSUFFICIENT_RESOURCES).
 NET_EXPORT extern const base::FeatureParam<int> kLimitOpenUDPSocketsMax;
 
+// Enables a timeout on individual TCP connect attempts, based on
+// the parameter values.
+NET_EXPORT extern const base::Feature kTimeoutTcpConnectAttempt;
+
+// FeatureParams associated with kTimeoutTcpConnectAttempt.
+
+// When there is an estimated RTT available, the experimental TCP connect
+// attempt timeout is calculated as:
+//
+//  clamp(kTimeoutTcpConnectAttemptMin,
+//        kTimeoutTcpConnectAttemptMax,
+//        <Estimated RTT> * kTimeoutTcpConnectAttemptRTTMultiplier);
+//
+// Otherwise the TCP connect attempt timeout is set to
+// kTimeoutTcpConnectAttemptMax.
+NET_EXPORT extern const base::FeatureParam<double>
+    kTimeoutTcpConnectAttemptRTTMultiplier;
+NET_EXPORT extern const base::FeatureParam<base::TimeDelta>
+    kTimeoutTcpConnectAttemptMin;
+NET_EXPORT extern const base::FeatureParam<base::TimeDelta>
+    kTimeoutTcpConnectAttemptMax;
+
 }  // namespace features
 }  // namespace net
 
diff --git a/net/dns/public/doh_provider_entry.cc b/net/dns/public/doh_provider_entry.cc
index 80e9e02..90b9b95 100644
--- a/net/dns/public/doh_provider_entry.cc
+++ b/net/dns/public/doh_provider_entry.cc
@@ -179,6 +179,14 @@
           "https://www.quad9.net/home/privacy/" /* privacy_policy */,
           true /* display_globally */, {} /* display_countries */),
       new DohProviderEntry(
+          "Quickline", base::nullopt /* provider_id_for_histogram */,
+          {"212.60.61.246", "212.60.63.246", "2001:1a88:10:ffff::1",
+           "2001:1a88:10:ffff::2"},
+          {"dot.quickline.ch"} /* dns_over_tls_hostnames */,
+          "https://doh.quickline.ch/dns-query{?dns}", "" /* ui_name */,
+          "" /* privacy_policy */, false /* display_globally */,
+          {} /* display_countries */),
+      new DohProviderEntry(
           "Spectrum1", base::nullopt /* provider_id_for_histogram */,
           {"209.18.47.61", "209.18.47.62", "2001:1998:0f00:0001::1",
            "2001:1998:0f00:0002::1"},
diff --git a/net/socket/tcp_client_socket.cc b/net/socket/tcp_client_socket.cc
index f00a3eb..9e62f5e 100644
--- a/net/socket/tcp_client_socket.cc
+++ b/net/socket/tcp_client_socket.cc
@@ -13,6 +13,7 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/notreached.h"
 #include "base/time/time.h"
+#include "net/base/features.h"
 #include "net/base/io_buffer.h"
 #include "net/base/ip_endpoint.h"
 #include "net/base/net_errors.h"
@@ -259,6 +260,17 @@
     socket_->socket_performance_watcher()->OnConnectionChanged();
 
   start_connect_attempt_ = base::TimeTicks::Now();
+
+  // Start a timer to fail the connect attempt if it takes too long.
+  base::TimeDelta attempt_timeout = GetConnectAttemptTimeout();
+  if (!attempt_timeout.is_max()) {
+    DCHECK(!connect_attempt_timer_.IsRunning());
+    connect_attempt_timer_.Start(
+        FROM_HERE, attempt_timeout,
+        base::BindOnce(&TCPClientSocket::OnConnectAttemptTimeout,
+                       base::Unretained(this)));
+  }
+
   return ConnectInternal(endpoint);
 }
 
@@ -266,6 +278,7 @@
   if (start_connect_attempt_) {
     EmitConnectAttemptHistograms(result);
     start_connect_attempt_ = base::nullopt;
+    connect_attempt_timer_.Stop();
   }
 
   if (result == OK)
@@ -292,6 +305,10 @@
   return result;
 }
 
+void TCPClientSocket::OnConnectAttemptTimeout() {
+  DidCompleteConnect(ERR_TIMED_OUT);
+}
+
 int TCPClientSocket::ConnectInternal(const IPEndPoint& endpoint) {
   // |socket_| is owned by this class and the callback won't be run once
   // |socket_| is gone. Therefore, it is safe to use base::Unretained() here.
@@ -318,6 +335,7 @@
   if (start_connect_attempt_) {
     EmitConnectAttemptHistograms(ERR_ABORTED);
     start_connect_attempt_ = base::nullopt;
+    connect_attempt_timer_.Stop();
   }
 
   total_received_bytes_ = 0;
@@ -602,4 +620,31 @@
   }
 }
 
+base::TimeDelta TCPClientSocket::GetConnectAttemptTimeout() {
+  if (!base::FeatureList::IsEnabled(features::kTimeoutTcpConnectAttempt))
+    return base::TimeDelta::Max();
+
+  base::Optional<base::TimeDelta> transport_rtt = base::nullopt;
+  if (network_quality_estimator_)
+    transport_rtt = network_quality_estimator_->GetTransportRTT();
+
+  base::TimeDelta min_timeout = features::kTimeoutTcpConnectAttemptMin.Get();
+  base::TimeDelta max_timeout = features::kTimeoutTcpConnectAttemptMax.Get();
+
+  if (!transport_rtt)
+    return max_timeout;
+
+  base::TimeDelta adaptive_timeout =
+      transport_rtt.value() *
+      features::kTimeoutTcpConnectAttemptRTTMultiplier.Get();
+
+  if (adaptive_timeout <= min_timeout)
+    return min_timeout;
+
+  if (adaptive_timeout >= max_timeout)
+    return max_timeout;
+
+  return adaptive_timeout;
+}
+
 }  // namespace net
diff --git a/net/socket/tcp_client_socket.h b/net/socket/tcp_client_socket.h
index 37e462e05..d5d62bd 100644
--- a/net/socket/tcp_client_socket.h
+++ b/net/socket/tcp_client_socket.h
@@ -13,6 +13,7 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/power_monitor/power_observer.h"
+#include "base/timer/timer.h"
 #include "build/build_config.h"
 #include "net/base/address_list.h"
 #include "net/base/completion_once_callback.h"
@@ -153,6 +154,8 @@
   int DoConnect();
   int DoConnectComplete(int result);
 
+  void OnConnectAttemptTimeout();
+
   // Calls the connect method of |socket_|. Used in tests, to ensure a socket
   // never connects.
   virtual int ConnectInternal(const IPEndPoint& endpoint);
@@ -176,6 +179,12 @@
   // |result|.
   void EmitConnectAttemptHistograms(int result);
 
+  // Gets the timeout to use for the next TCP connect attempt. This is an
+  // experimentally controlled value based on the estimated transport round
+  // trip time. If no timeout is to be enforced, returns
+  // base::TimeDelta::Max().
+  base::TimeDelta GetConnectAttemptTimeout();
+
   std::unique_ptr<TCPSocket> socket_;
 
   // Local IP address and port we are bound to. Set to NULL if Bind()
@@ -222,6 +231,8 @@
   // Can be nullptr.
   NetworkQualityEstimator* network_quality_estimator_;
 
+  base::OneShotTimer connect_attempt_timer_;
+
   base::WeakPtrFactory<TCPClientSocket> weak_ptr_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(TCPClientSocket);
diff --git a/net/socket/tcp_client_socket_unittest.cc b/net/socket/tcp_client_socket_unittest.cc
index e1797ca2..8d7ce63 100644
--- a/net/socket/tcp_client_socket_unittest.cc
+++ b/net/socket/tcp_client_socket_unittest.cc
@@ -12,14 +12,18 @@
 
 #include "base/power_monitor/power_monitor.h"
 #include "base/power_monitor/power_monitor_source.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/test/bind_test_util.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "build/build_config.h"
+#include "net/base/features.h"
 #include "net/base/ip_address.h"
 #include "net/base/ip_endpoint.h"
 #include "net/base/net_errors.h"
 #include "net/base/test_completion_callback.h"
 #include "net/log/net_log_source.h"
+#include "net/nqe/network_quality_estimator_test_util.h"
 #include "net/socket/socket_performance_watcher.h"
 #include "net/socket/socket_test_util.h"
 #include "net/socket/tcp_server_socket.h"
@@ -417,6 +421,33 @@
 }
 #endif  // defined(OS_ANDROID)
 
+// TCP socket that hangs indefinitely when establishing a connection.
+class NeverConnectingTCPClientSocket : public TCPClientSocket {
+ public:
+  NeverConnectingTCPClientSocket(
+      const AddressList& addresses,
+      std::unique_ptr<SocketPerformanceWatcher> socket_performance_watcher,
+      NetworkQualityEstimator* network_quality_estimator,
+      net::NetLog* net_log,
+      const net::NetLogSource& source)
+      : TCPClientSocket(addresses,
+                        std::move(socket_performance_watcher),
+                        network_quality_estimator,
+                        net_log,
+                        source) {}
+
+  // Returns the number of times that ConnectInternal() was called.
+  int connect_internal_counter() const { return connect_internal_counter_; }
+
+ private:
+  int ConnectInternal(const IPEndPoint& endpoint) override {
+    connect_internal_counter_++;
+    return ERR_IO_PENDING;
+  }
+
+  int connect_internal_counter_ = 0;
+};
+
 // Tests for closing sockets on suspend mode.
 #if defined(TCP_CLIENT_SOCKET_OBSERVES_SUSPEND)
 
@@ -460,27 +491,6 @@
   EXPECT_TRUE(accepted_socket->IsConnected());
 }
 
-// TCP socket that hangs when establishing a connection. This is needed to make
-// sure establishing a connection doesn't succeed synchronously.
-class NeverConnectingTCPClientSocket : public TCPClientSocket {
- public:
-  NeverConnectingTCPClientSocket(
-      const AddressList& addresses,
-      std::unique_ptr<SocketPerformanceWatcher> socket_performance_watcher,
-      net::NetLog* net_log,
-      const net::NetLogSource& source)
-      : TCPClientSocket(addresses,
-                        std::move(socket_performance_watcher),
-                        nullptr,
-                        net_log,
-                        source) {}
-
- private:
-  int ConnectInternal(const IPEndPoint& endpoint) override {
-    return ERR_IO_PENDING;
-  }
-};
-
 TEST_F(TCPClientSocketTest, SuspendDuringConnect) {
   IPAddress lo_address = IPAddress::IPv4Localhost();
 
@@ -490,7 +500,7 @@
   ASSERT_THAT(server.GetLocalAddress(&server_address), IsOk());
 
   NeverConnectingTCPClientSocket socket(AddressList(server_address), nullptr,
-                                        nullptr, NetLogSource());
+                                        nullptr, nullptr, NetLogSource());
 
   EXPECT_THAT(socket.Bind(IPEndPoint(lo_address, 0)), IsOk());
 
@@ -519,7 +529,7 @@
       IPEndPoint(IPAddress(127, 0, 0, 1), server_address.port()));
   address_list.push_back(
       IPEndPoint(IPAddress(127, 0, 0, 2), server_address.port()));
-  NeverConnectingTCPClientSocket socket(address_list, nullptr, nullptr,
+  NeverConnectingTCPClientSocket socket(address_list, nullptr, nullptr, nullptr,
                                         NetLogSource());
 
   EXPECT_THAT(socket.Bind(IPEndPoint(lo_address, 0)), IsOk());
@@ -749,6 +759,252 @@
 
 #endif  // defined(TCP_CLIENT_SOCKET_OBSERVES_SUSPEND)
 
+// Scoped helper to override the TCP connect attempt policy.
+class OverrideTcpConnectAttemptTimeout {
+ public:
+  OverrideTcpConnectAttemptTimeout(double rtt_multipilier,
+                                   base::TimeDelta min_timeout,
+                                   base::TimeDelta max_timeout) {
+    base::FieldTrialParams params;
+    params[features::kTimeoutTcpConnectAttemptRTTMultiplier.name] =
+        base::NumberToString(rtt_multipilier);
+    params[features::kTimeoutTcpConnectAttemptMin.name] =
+        base::NumberToString(min_timeout.InMilliseconds()) + "ms";
+    params[features::kTimeoutTcpConnectAttemptMax.name] =
+        base::NumberToString(max_timeout.InMilliseconds()) + "ms";
+
+    scoped_feature_list_.InitAndEnableFeatureWithParameters(
+        features::kTimeoutTcpConnectAttempt, params);
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+// Test fixture that uses a MOCK_TIME test environment, so time can
+// be advanced programmatically.
+class TCPClientSocketMockTimeTest : public testing::Test {
+ public:
+  TCPClientSocketMockTimeTest()
+      : task_environment_(base::test::TaskEnvironment::MainThreadType::IO,
+                          base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
+
+ protected:
+  base::test::TaskEnvironment task_environment_;
+};
+
+// Tests that no TCP connect timeout is enforced by default (i.e.
+// when the feature is disabled).
+TEST_F(TCPClientSocketMockTimeTest, NoConnectAttemptTimeoutByDefault) {
+  IPEndPoint server_address(IPAddress::IPv4Localhost(), 80);
+  NeverConnectingTCPClientSocket socket(AddressList(server_address), nullptr,
+                                        nullptr, nullptr, NetLogSource());
+
+  TestCompletionCallback connect_callback;
+  int rv = socket.Connect(connect_callback.callback());
+  ASSERT_THAT(rv, IsError(ERR_IO_PENDING));
+
+  // After 4 minutes, the socket should still be connecting.
+  task_environment_.FastForwardBy(base::TimeDelta::FromMinutes(4));
+  EXPECT_FALSE(connect_callback.have_result());
+  EXPECT_FALSE(socket.IsConnected());
+
+  // 1 attempt was made.
+  EXPECT_EQ(1, socket.connect_internal_counter());
+}
+
+// Tests that the maximum timeout is used when there is no estimated
+// RTT.
+TEST_F(TCPClientSocketMockTimeTest, ConnectAttemptTimeoutUsesMaxWhenNoRTT) {
+  OverrideTcpConnectAttemptTimeout override_timeout(
+      1, base::TimeDelta::FromSeconds(4), base::TimeDelta::FromSeconds(10));
+
+  IPEndPoint server_address(IPAddress::IPv4Localhost(), 80);
+
+  // Pass a null NetworkQualityEstimator, so the TCPClientSocket is unable to
+  // estimate the RTT.
+  NeverConnectingTCPClientSocket socket(AddressList(server_address), nullptr,
+                                        nullptr, nullptr, NetLogSource());
+
+  // Start connecting.
+  TestCompletionCallback connect_callback;
+  int rv = socket.Connect(connect_callback.callback());
+  ASSERT_THAT(rv, IsError(ERR_IO_PENDING));
+
+  // Advance to t=3.1s
+  // Should still be pending, as this is before the minimum timeout.
+  task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(3100));
+  EXPECT_FALSE(connect_callback.have_result());
+  EXPECT_FALSE(socket.IsConnected());
+
+  // Advance to t=4.1s
+  // Should still be pending. This is after the minimum timeout, but before the
+  // maximum.
+  task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
+  EXPECT_FALSE(connect_callback.have_result());
+  EXPECT_FALSE(socket.IsConnected());
+
+  // Advance to t=10.1s
+  // Should now be timed out, as this is after the maximum timeout.
+  task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(6));
+  rv = connect_callback.GetResult(rv);
+  ASSERT_THAT(rv, IsError(ERR_TIMED_OUT));
+
+  // 1 attempt was made.
+  EXPECT_EQ(1, socket.connect_internal_counter());
+}
+
+// Tests that the minimum timeout is used when the adaptive timeout using RTT
+// ends up being too low.
+TEST_F(TCPClientSocketMockTimeTest, ConnectAttemptTimeoutUsesMinWhenRTTLow) {
+  OverrideTcpConnectAttemptTimeout override_timeout(
+      5, base::TimeDelta::FromSeconds(4), base::TimeDelta::FromSeconds(10));
+
+  // Set the estimated RTT to 1 millisecond.
+  TestNetworkQualityEstimator network_quality_estimator;
+  network_quality_estimator.SetStartTimeNullTransportRtt(
+      base::TimeDelta::FromMilliseconds(1));
+
+  IPEndPoint server_address(IPAddress::IPv4Localhost(), 80);
+
+  NeverConnectingTCPClientSocket socket(AddressList(server_address), nullptr,
+                                        &network_quality_estimator, nullptr,
+                                        NetLogSource());
+
+  // Start connecting.
+  TestCompletionCallback connect_callback;
+  int rv = socket.Connect(connect_callback.callback());
+  ASSERT_THAT(rv, IsError(ERR_IO_PENDING));
+
+  // Advance to t=1.1s
+  // Should be pending, since although the adaptive timeout has been reached, it
+  // is lower than the minimum timeout.
+  task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(1100));
+  EXPECT_FALSE(connect_callback.have_result());
+  EXPECT_FALSE(socket.IsConnected());
+
+  // Advance to t=4.1s
+  // Should have timed out due to hitting the minimum timeout.
+  task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(3));
+  rv = connect_callback.GetResult(rv);
+  ASSERT_THAT(rv, IsError(ERR_TIMED_OUT));
+
+  // 1 attempt was made.
+  EXPECT_EQ(1, socket.connect_internal_counter());
+}
+
+// Tests that the maximum timeout is used when the adaptive timeout from RTT is
+// too high.
+TEST_F(TCPClientSocketMockTimeTest, ConnectAttemptTimeoutUsesMinWhenRTTHigh) {
+  OverrideTcpConnectAttemptTimeout override_timeout(
+      5, base::TimeDelta::FromSeconds(4), base::TimeDelta::FromSeconds(10));
+
+  // Set the estimated RTT to 5 seconds.
+  TestNetworkQualityEstimator network_quality_estimator;
+  network_quality_estimator.SetStartTimeNullTransportRtt(
+      base::TimeDelta::FromSeconds(5));
+
+  IPEndPoint server_address(IPAddress::IPv4Localhost(), 80);
+
+  NeverConnectingTCPClientSocket socket(AddressList(server_address), nullptr,
+                                        &network_quality_estimator, nullptr,
+                                        NetLogSource());
+
+  // Start connecting.
+  TestCompletionCallback connect_callback;
+  int rv = socket.Connect(connect_callback.callback());
+  ASSERT_THAT(rv, IsError(ERR_IO_PENDING));
+
+  // Advance to t=10.1s
+  // The socket should have timed out due to hitting the maximum timeout. Had
+  // the adaptive timeout been used, the socket would instead be timing out at
+  // t=25s.
+  task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(10100));
+  rv = connect_callback.GetResult(rv);
+  ASSERT_THAT(rv, IsError(ERR_TIMED_OUT));
+
+  // 1 attempt was made.
+  EXPECT_EQ(1, socket.connect_internal_counter());
+}
+
+// Tests that an adaptive timeout is used for TCP connection attempts based on
+// the estimated RTT.
+TEST_F(TCPClientSocketMockTimeTest, ConnectAttemptTimeoutUsesRTT) {
+  OverrideTcpConnectAttemptTimeout override_timeout(
+      5, base::TimeDelta::FromSeconds(4), base::TimeDelta::FromSeconds(10));
+
+  // Set the estimated RTT to 1 second. Since the multiplier is set to 5, the
+  // total adaptive timeout will be 5 seconds.
+  TestNetworkQualityEstimator network_quality_estimator;
+  network_quality_estimator.SetStartTimeNullTransportRtt(
+      base::TimeDelta::FromSeconds(1));
+
+  IPEndPoint server_address(IPAddress::IPv4Localhost(), 80);
+
+  NeverConnectingTCPClientSocket socket(AddressList(server_address), nullptr,
+                                        &network_quality_estimator, nullptr,
+                                        NetLogSource());
+
+  // Start connecting.
+  TestCompletionCallback connect_callback;
+  int rv = socket.Connect(connect_callback.callback());
+  ASSERT_THAT(rv, IsError(ERR_IO_PENDING));
+
+  // Advance to t=4.1s
+  // The socket should still be pending. Had the minimum timeout been enforced,
+  // it would instead have timed out now.
+  task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(4100));
+  EXPECT_FALSE(connect_callback.have_result());
+  EXPECT_FALSE(socket.IsConnected());
+
+  // Advance to t=5.1s
+  // The adaptive timeout was at t=5s, so it should now be timed out.
+  task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
+  rv = connect_callback.GetResult(rv);
+  ASSERT_THAT(rv, IsError(ERR_TIMED_OUT));
+
+  // 1 attempt was made.
+  EXPECT_EQ(1, socket.connect_internal_counter());
+}
+
+// Tests that when multiple TCP connect attempts are made, the timeout for each
+// one is applied independently.
+TEST_F(TCPClientSocketMockTimeTest, ConnectAttemptTimeoutIndependent) {
+  OverrideTcpConnectAttemptTimeout override_timeout(
+      5, base::TimeDelta::FromSeconds(4), base::TimeDelta::FromSeconds(10));
+
+  // This test will attempt connecting to 5 endpoints.
+  const size_t kNumIps = 5;
+
+  AddressList addresses;
+  for (size_t i = 0; i < kNumIps; ++i)
+    addresses.push_back(IPEndPoint(IPAddress::IPv4Localhost(), 80 + i));
+
+  NeverConnectingTCPClientSocket socket(addresses, nullptr, nullptr, nullptr,
+                                        NetLogSource());
+
+  // Start connecting.
+  TestCompletionCallback connect_callback;
+  int rv = socket.Connect(connect_callback.callback());
+  ASSERT_THAT(rv, IsError(ERR_IO_PENDING));
+
+  // Advance to t=49s
+  // Should still be pending.
+  task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(49));
+  EXPECT_FALSE(connect_callback.have_result());
+  EXPECT_FALSE(socket.IsConnected());
+
+  // Advance to t=50.1s
+  // All attempts should take 50 seconds to complete (5 attempts, 10 seconds
+  // each). So by this point the overall connect attempt will have timed out.
+  task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(1100));
+  rv = connect_callback.GetResult(rv);
+  ASSERT_THAT(rv, IsError(ERR_TIMED_OUT));
+
+  // 5 attempts were made.
+  EXPECT_EQ(5, socket.connect_internal_counter());
+}
+
 }  // namespace
 
 }  // namespace net
diff --git a/net/third_party/quiche/BUILD.gn b/net/third_party/quiche/BUILD.gn
index ffab81e..8beaeb9 100644
--- a/net/third_party/quiche/BUILD.gn
+++ b/net/third_party/quiche/BUILD.gn
@@ -1455,7 +1455,7 @@
   ]
   public_deps = []
 
-  if (is_desktop_linux) {
+  if (is_linux) {
     public_deps += [ "//net:epoll_quic_tools" ]
   }
   if (is_linux || is_chromeos) {
diff --git a/printing/BUILD.gn b/printing/BUILD.gn
index 5307040..e67ae471 100644
--- a/printing/BUILD.gn
+++ b/printing/BUILD.gn
@@ -34,7 +34,7 @@
 # Enable the CUPS IPP printing backend.
 # TODO(crbug.com/226176): Remove this after CUPS PPD API calls are removed.
 declare_args() {
-  use_cups_ipp = use_cups && !is_desktop_linux
+  use_cups_ipp = use_cups && !is_linux
 }
 
 # Several targets want to include this header file. We separate it out
@@ -141,7 +141,7 @@
     ]
   }
 
-  if (is_desktop_linux) {
+  if (is_linux) {
     sources += [
       "printed_document_linux.cc",
       "printing_context_linux.cc",
diff --git a/printing/buildflags/buildflags.gni b/printing/buildflags/buildflags.gni
index bb9dabfc..86febe49 100644
--- a/printing/buildflags/buildflags.gni
+++ b/printing/buildflags/buildflags.gni
@@ -24,7 +24,7 @@
     # For fuzzing, just restrict to chromeos and linux.
     use_cups = true
   } else {
-    use_cups = (is_chromeos_device || is_desktop_linux || is_mac) &&
-               !is_chromecast && !is_fuchsia
+    use_cups = (is_chromeos_device || is_linux || is_mac) && !is_chromecast &&
+               !is_fuchsia
   }
 }
diff --git a/remoting/host/BUILD.gn b/remoting/host/BUILD.gn
index af7d74ba..0f821cf 100644
--- a/remoting/host/BUILD.gn
+++ b/remoting/host/BUILD.gn
@@ -374,7 +374,7 @@
       "//remoting/host/linux:x11",
       "//ui/gfx/x",
     ]
-    if (is_desktop_linux) {
+    if (is_linux) {
       deps += [ "//build/config/linux/gtk" ]
     }
   } else {
@@ -758,7 +758,7 @@
       deps += [ "//components/policy:generated" ]
     }
 
-    if (is_desktop_linux) {
+    if (is_linux) {
       deps += [ "//build/config/linux/gtk" ]
     }
     if ((is_linux && !is_chromeos) || is_mac) {
diff --git a/remoting/host/file_transfer/BUILD.gn b/remoting/host/file_transfer/BUILD.gn
index 029f890..e2c0714 100644
--- a/remoting/host/file_transfer/BUILD.gn
+++ b/remoting/host/file_transfer/BUILD.gn
@@ -45,7 +45,7 @@
     sources -= [ "get_desktop_directory.cc" ]
   }
 
-  if (is_desktop_linux) {
+  if (is_linux) {
     sources += [ "file_chooser_linux.cc" ]
     deps += [ "//build/config/linux/gtk" ]
   }
@@ -57,6 +57,7 @@
     "file_transfer_message_handler.h",
     "ipc_file_operations.h",
     "local_file_operations.h",
+    "rtc_log_file_operations.h",
     "session_file_operations_handler.h",
   ]
 
@@ -68,6 +69,7 @@
     "file_transfer_message_handler.cc",
     "ipc_file_operations.cc",
     "local_file_operations.cc",
+    "rtc_log_file_operations.cc",
     "session_file_operations_handler.cc",
   ]
 
@@ -103,6 +105,7 @@
     "file_transfer_message_handler_unittest.cc",
     "ipc_file_operations_unittest.cc",
     "local_file_operations_unittest.cc",
+    "rtc_log_file_operations_unittest.cc",
   ]
 
   deps = [
diff --git a/remoting/host/file_transfer/rtc_log_file_operations.cc b/remoting/host/file_transfer/rtc_log_file_operations.cc
new file mode 100644
index 0000000..f30d715
--- /dev/null
+++ b/remoting/host/file_transfer/rtc_log_file_operations.cc
@@ -0,0 +1,195 @@
+// 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 "remoting/host/file_transfer/rtc_log_file_operations.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "base/notreached.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/time/time.h"
+#include "remoting/protocol/connection_to_client.h"
+#include "remoting/protocol/file_transfer_helpers.h"
+#include "remoting/protocol/webrtc_event_log_data.h"
+
+namespace remoting {
+
+namespace {
+
+class RtcLogFileReader : public FileOperations::Reader {
+ public:
+  explicit RtcLogFileReader(protocol::ConnectionToClient* connection);
+  ~RtcLogFileReader() override;
+  RtcLogFileReader(const RtcLogFileReader&) = delete;
+  RtcLogFileReader& operator=(const RtcLogFileReader&) = delete;
+
+  // FileOperations::Reader interface.
+  void Open(OpenCallback callback) override;
+  void ReadChunk(std::size_t size, ReadCallback callback) override;
+  const base::FilePath& filename() const override;
+  std::uint64_t size() const override;
+  FileOperations::State state() const override;
+
+ private:
+  using LogSection = protocol::WebrtcEventLogData::LogSection;
+
+  void DoOpen(OpenCallback callback);
+  void DoReadChunk(std::size_t size, ReadCallback callback);
+
+  // Reads up to |maximum_to_read| bytes from the event log, and appends them
+  // to |output| and returns the number of bytes appended. This only reads from
+  // a single LogSection, and it takes care of advancing to the next LogSection
+  // if the end is reached. Returns 0 if there is no more data to be read.
+  int ReadPartially(int maximum_to_read, std::vector<std::uint8_t>& output);
+
+  protocol::ConnectionToClient* connection_;
+  base::FilePath filename_;
+  base::circular_deque<LogSection> data_;
+  FileOperations::State state_ = FileOperations::kCreated;
+
+  // Points to the current LogSection being read from, or data_.end() if
+  // reading is finished.
+  base::circular_deque<LogSection>::const_iterator current_log_section_;
+
+  // Points to the current read position inside |current_log_section_| or is
+  // undefined if current_log_section_ == data_.end(). Note that each
+  // LogSection of |data_| is always non-empty. If the end of a LogSection is
+  // reached, |current_log_section_| will advance to the next section, and this
+  // position will be reset to the beginning of the new section.
+  LogSection::const_iterator current_position_;
+
+  base::WeakPtrFactory<RtcLogFileReader> weak_factory_{this};
+};
+
+RtcLogFileReader::RtcLogFileReader(protocol::ConnectionToClient* connection)
+    : connection_(connection) {}
+RtcLogFileReader::~RtcLogFileReader() = default;
+
+void RtcLogFileReader::Open(OpenCallback callback) {
+  state_ = FileOperations::kBusy;
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&RtcLogFileReader::DoOpen, weak_factory_.GetWeakPtr(),
+                     std::move(callback)));
+}
+
+void RtcLogFileReader::ReadChunk(std::size_t size, ReadCallback callback) {
+  state_ = FileOperations::kBusy;
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&RtcLogFileReader::DoReadChunk, weak_factory_.GetWeakPtr(),
+                     size, std::move(callback)));
+}
+
+const base::FilePath& RtcLogFileReader::filename() const {
+  return filename_;
+}
+
+std::uint64_t RtcLogFileReader::size() const {
+  std::uint64_t result = 0;
+  for (const auto& section : data_) {
+    result += section.size();
+  }
+  return result;
+}
+
+FileOperations::State RtcLogFileReader::state() const {
+  return state_;
+}
+
+void RtcLogFileReader::DoOpen(OpenCallback callback) {
+  protocol::WebrtcEventLogData* rtc_log = connection_->rtc_event_log();
+  if (!rtc_log) {
+    // This is a protocol error because RTC log is only supported for WebRTC
+    // connections.
+    state_ = FileOperations::kFailed;
+    std::move(callback).Run(protocol::MakeFileTransferError(
+        FROM_HERE, protocol::FileTransfer_Error_Type_PROTOCOL_ERROR));
+    return;
+  }
+
+  base::Time::Exploded exploded;
+  base::Time::NowFromSystemTime().LocalExplode(&exploded);
+  std::string filename = base::StringPrintf(
+      "host-rtc-log-%d-%d-%d_%d-%d-%d", exploded.year, exploded.month,
+      exploded.day_of_month, exploded.hour, exploded.minute, exploded.second);
+  filename_ = base::FilePath::FromUTF8Unsafe(filename);
+
+  data_ = rtc_log->TakeLogData();
+  current_log_section_ = data_.begin();
+  if (!data_.empty()) {
+    current_position_ = (*current_log_section_).begin();
+  }
+
+  state_ = FileOperations::kReady;
+  std::move(callback).Run(kSuccessTag);
+}
+
+void RtcLogFileReader::DoReadChunk(std::size_t size, ReadCallback callback) {
+  std::vector<std::uint8_t> result;
+  int bytes_read;
+  int bytes_remaining = static_cast<int>(size);
+  while (bytes_remaining &&
+         (bytes_read = ReadPartially(bytes_remaining, result)) > 0) {
+    bytes_remaining -= bytes_read;
+  }
+
+  state_ = result.empty() ? FileOperations::kComplete : FileOperations::kReady;
+  std::move(callback).Run(result);
+}
+
+int RtcLogFileReader::ReadPartially(int maximum_to_read,
+                                    std::vector<std::uint8_t>& output) {
+  if (data_.empty()) {
+    return 0;
+  }
+
+  if (current_log_section_ == data_.end()) {
+    return 0;
+  }
+
+  const auto& section = *current_log_section_;
+  DCHECK(section.begin() <= current_position_);
+  DCHECK(current_position_ < section.end());
+
+  int remaining_in_section = section.end() - current_position_;
+  int read_amount = std::min(remaining_in_section, maximum_to_read);
+
+  output.insert(output.end(), current_position_,
+                current_position_ + read_amount);
+  current_position_ += read_amount;
+
+  if (current_position_ == section.end()) {
+    // Advance to beginning of next LogSection.
+    current_log_section_++;
+    if (current_log_section_ != data_.end()) {
+      current_position_ = (*current_log_section_).begin();
+    }
+  }
+
+  return read_amount;
+}
+
+}  // namespace
+
+RtcLogFileOperations::RtcLogFileOperations(
+    protocol::ConnectionToClient* connection)
+    : connection_(connection) {}
+
+RtcLogFileOperations::~RtcLogFileOperations() = default;
+
+std::unique_ptr<FileOperations::Reader> RtcLogFileOperations::CreateReader() {
+  return std::make_unique<RtcLogFileReader>(connection_);
+}
+
+std::unique_ptr<FileOperations::Writer> RtcLogFileOperations::CreateWriter() {
+  NOTREACHED() << "RTC event log is read-only.";
+  return nullptr;
+}
+
+}  // namespace remoting
diff --git a/remoting/host/file_transfer/rtc_log_file_operations.h b/remoting/host/file_transfer/rtc_log_file_operations.h
new file mode 100644
index 0000000..d44f080
--- /dev/null
+++ b/remoting/host/file_transfer/rtc_log_file_operations.h
@@ -0,0 +1,36 @@
+// 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 REMOTING_HOST_FILE_TRANSFER_RTC_LOG_FILE_OPERATIONS_H_
+#define REMOTING_HOST_FILE_TRANSFER_RTC_LOG_FILE_OPERATIONS_H_
+
+#include "remoting/host/file_transfer/file_operations.h"
+
+namespace remoting {
+
+namespace protocol {
+class ConnectionToClient;
+}  // namespace protocol
+
+// Implementation of FileOperations that sends the RTC event log to the client.
+// As the event log is held in memory, there is no need for blocking IO and
+// this class can run on the network thread.
+class RtcLogFileOperations : public FileOperations {
+ public:
+  explicit RtcLogFileOperations(protocol::ConnectionToClient* connection);
+  ~RtcLogFileOperations() override;
+  RtcLogFileOperations(const RtcLogFileOperations&) = delete;
+  RtcLogFileOperations& operator=(const RtcLogFileOperations&) = delete;
+
+  // FileOperations interface.
+  std::unique_ptr<Reader> CreateReader() override;
+  std::unique_ptr<Writer> CreateWriter() override;
+
+ private:
+  protocol::ConnectionToClient* connection_;
+};
+
+}  // namespace remoting
+
+#endif  // REMOTING_HOST_FILE_TRANSFER_RTC_LOG_FILE_OPERATIONS_H_
diff --git a/remoting/host/file_transfer/rtc_log_file_operations_unittest.cc b/remoting/host/file_transfer/rtc_log_file_operations_unittest.cc
new file mode 100644
index 0000000..41c2932
--- /dev/null
+++ b/remoting/host/file_transfer/rtc_log_file_operations_unittest.cc
@@ -0,0 +1,177 @@
+// 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 "remoting/host/file_transfer/rtc_log_file_operations.h"
+
+#include "base/test/task_environment.h"
+#include "remoting/protocol/fake_connection_to_client.h"
+#include "remoting/protocol/session.h"
+#include "remoting/protocol/webrtc_event_log_data.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace remoting {
+
+namespace {
+
+std::string ToString(const std::vector<std::uint8_t>& data) {
+  return std::string(data.begin(), data.end());
+}
+
+class FakeConnectionWithRtcLog : public protocol::FakeConnectionToClient {
+ public:
+  explicit FakeConnectionWithRtcLog(protocol::WebrtcEventLogData* event_log)
+      : protocol::FakeConnectionToClient(nullptr), event_log_(event_log) {}
+  ~FakeConnectionWithRtcLog() override = default;
+  FakeConnectionWithRtcLog(const FakeConnectionWithRtcLog&) = delete;
+  FakeConnectionWithRtcLog& operator=(const FakeConnectionWithRtcLog&) = delete;
+
+  protocol::WebrtcEventLogData* rtc_event_log() override { return event_log_; }
+
+  void set_event_log(protocol::WebrtcEventLogData* log) { event_log_ = log; }
+
+ private:
+  protocol::WebrtcEventLogData* event_log_;
+};
+
+}  // namespace
+
+class RtcLogFileOperationsTest : public testing::Test {
+ public:
+  RtcLogFileOperationsTest()
+      : connection_(&event_log_), file_operations_(&connection_) {}
+
+  void SetUp() override { reader_ = file_operations_.CreateReader(); }
+
+ protected:
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  protocol::WebrtcEventLogData event_log_;
+  FakeConnectionWithRtcLog connection_;
+  RtcLogFileOperations file_operations_;
+  std::unique_ptr<FileOperations::Reader> reader_;
+
+  // These are the most-recent results from the callbacks.
+  base::Optional<FileOperations::Reader::OpenResult> open_result_;
+  base::Optional<FileOperations::Reader::ReadResult> read_result_;
+
+  FileOperations::Reader::OpenCallback MakeOpenCallback() {
+    return base::BindOnce(&RtcLogFileOperationsTest::OnOpenResult,
+                          base::Unretained(this));
+  }
+
+  FileOperations::Reader::ReadCallback MakeReadCallback() {
+    return base::BindOnce(&RtcLogFileOperationsTest::OnReadResult,
+                          base::Unretained(this));
+  }
+
+ private:
+  void OnOpenResult(FileOperations::Reader::OpenResult result) {
+    open_result_ = result;
+  }
+
+  void OnReadResult(FileOperations::Reader::ReadResult result) {
+    read_result_ = result;
+  }
+};
+
+TEST_F(RtcLogFileOperationsTest, InitialState_IsCreated) {
+  EXPECT_EQ(reader_->state(), FileOperations::State::kCreated);
+}
+
+TEST_F(RtcLogFileOperationsTest, NonWebrtcConnection_RaisesProtocolError) {
+  connection_.set_event_log(nullptr);
+
+  reader_->Open(MakeOpenCallback());
+  task_environment_.RunUntilIdle();
+
+  ASSERT_TRUE(open_result_);
+  ASSERT_FALSE(*open_result_);
+  EXPECT_EQ(open_result_->error().type(),
+            protocol::FileTransfer_Error_Type_PROTOCOL_ERROR);
+}
+
+TEST_F(RtcLogFileOperationsTest, EmptyLog_ZeroBytesSent) {
+  // No setup needed, the RTC log is initially empty.
+  reader_->Open(MakeOpenCallback());
+  task_environment_.RunUntilIdle();
+  reader_->ReadChunk(1U, MakeReadCallback());
+  task_environment_.RunUntilIdle();
+
+  ASSERT_TRUE(open_result_);
+  ASSERT_TRUE(*open_result_);
+  ASSERT_TRUE(read_result_);
+  ASSERT_TRUE(*read_result_);
+
+  EXPECT_TRUE((*read_result_)->empty());
+  EXPECT_EQ(reader_->state(), FileOperations::State::kComplete);
+}
+
+TEST_F(RtcLogFileOperationsTest, FileSizeOfMultipleLogSections_IsCorrect) {
+  event_log_.SetMaxSectionSizeForTest(3);
+  event_log_.Write("aaa");
+  event_log_.Write("bb");
+  event_log_.Write("cc");
+
+  reader_->Open(MakeOpenCallback());
+  task_environment_.RunUntilIdle();
+
+  EXPECT_EQ(reader_->size(), 7U);
+}
+
+TEST_F(RtcLogFileOperationsTest, PartialTransfer_IsReady) {
+  event_log_.Write("aaa");
+
+  reader_->Open(MakeOpenCallback());
+  task_environment_.RunUntilIdle();
+  reader_->ReadChunk(2U, MakeReadCallback());
+  task_environment_.RunUntilIdle();
+
+  EXPECT_EQ(reader_->state(), FileOperations::State::kReady);
+}
+
+TEST_F(RtcLogFileOperationsTest, CompleteTransfer_IsComplete) {
+  event_log_.Write("aaa");
+
+  reader_->Open(MakeOpenCallback());
+  task_environment_.RunUntilIdle();
+  reader_->ReadChunk(3U, MakeReadCallback());
+  task_environment_.RunUntilIdle();
+
+  // State becomes Complete only after a final read has returned 0 bytes.
+  reader_->ReadChunk(3U, MakeReadCallback());
+  task_environment_.RunUntilIdle();
+
+  ASSERT_TRUE(read_result_);
+  ASSERT_TRUE(*read_result_);
+  EXPECT_TRUE((*read_result_)->empty());
+  EXPECT_EQ(reader_->state(), FileOperations::State::kComplete);
+}
+
+TEST_F(RtcLogFileOperationsTest,
+       MultipleReadsOverMultipleSections_CorrectDataIsSent) {
+  event_log_.SetMaxSectionSizeForTest(3);
+  event_log_.Write("aaa");
+  event_log_.Write("bbb");
+  event_log_.Write("ccc");
+
+  reader_->Open(MakeOpenCallback());
+  task_environment_.RunUntilIdle();
+  reader_->ReadChunk(2U, MakeReadCallback());
+  task_environment_.RunUntilIdle();
+
+  ASSERT_TRUE(read_result_);
+  ASSERT_TRUE(*read_result_);
+  EXPECT_EQ(ToString(**read_result_), "aa");
+
+  reader_->ReadChunk(5U, MakeReadCallback());
+  task_environment_.RunUntilIdle();
+
+  EXPECT_EQ(ToString(**read_result_), "abbbc");
+
+  reader_->ReadChunk(100U, MakeReadCallback());
+  task_environment_.RunUntilIdle();
+
+  EXPECT_EQ(ToString(**read_result_), "cc");
+}
+
+}  // namespace remoting
diff --git a/remoting/host/it2me/BUILD.gn b/remoting/host/it2me/BUILD.gn
index 7035f31..11172fb1 100644
--- a/remoting/host/it2me/BUILD.gn
+++ b/remoting/host/it2me/BUILD.gn
@@ -58,7 +58,7 @@
     "//remoting/resources",
     "//remoting/signaling",
   ]
-  if (is_desktop_linux) {
+  if (is_linux) {
     deps += [
       "//build/config/linux/gtk",
 
@@ -234,7 +234,7 @@
         }
       }
 
-      if (is_desktop_linux) {
+      if (is_linux) {
         deps += [ "//build/config/linux/gtk" ]
       }
     }
diff --git a/remoting/host/linux/BUILD.gn b/remoting/host/linux/BUILD.gn
index eafed2b..95d6e764 100644
--- a/remoting/host/linux/BUILD.gn
+++ b/remoting/host/linux/BUILD.gn
@@ -79,7 +79,7 @@
   if (use_x11) {
     deps += [ ":x11" ]
   }
-  if (is_desktop_linux) {
+  if (is_linux) {
     deps += [ "//build/config/linux/gtk" ]
   }
 }
diff --git a/remoting/test/BUILD.gn b/remoting/test/BUILD.gn
index 3d04281..019a97e 100644
--- a/remoting/test/BUILD.gn
+++ b/remoting/test/BUILD.gn
@@ -136,7 +136,7 @@
 
     deps = [ ":it2me_standalone_host" ]
 
-    if (is_desktop_linux) {
+    if (is_linux) {
       deps += [ "//build/config/linux/gtk" ]
     }
 
diff --git a/services/device/public/cpp/hid/fake_hid_manager.cc b/services/device/public/cpp/hid/fake_hid_manager.cc
index 12db20e..69617c8 100644
--- a/services/device/public/cpp/hid/fake_hid_manager.cc
+++ b/services/device/public/cpp/hid/fake_hid_manager.cc
@@ -13,8 +13,23 @@
 
 namespace device {
 
-FakeHidConnection::FakeHidConnection(mojom::HidDeviceInfoPtr device)
-    : device_(std::move(device)) {}
+FakeHidConnection::FakeHidConnection(
+    mojom::HidDeviceInfoPtr device,
+    mojo::PendingReceiver<mojom::HidConnection> receiver,
+    mojo::PendingRemote<mojom::HidConnectionClient> connection_client,
+    mojo::PendingRemote<mojom::HidConnectionWatcher> watcher)
+    : receiver_(this, std::move(receiver)),
+      device_(std::move(device)),
+      watcher_(std::move(watcher)) {
+  receiver_.set_disconnect_handler(base::BindOnce(
+      [](FakeHidConnection* self) { delete self; }, base::Unretained(this)));
+  if (watcher_) {
+    watcher_.set_disconnect_handler(base::BindOnce(
+        [](FakeHidConnection* self) { delete self; }, base::Unretained(this)));
+  }
+  if (connection_client)
+    client_.Bind(std::move(connection_client));
+}
 
 FakeHidConnection::~FakeHidConnection() = default;
 
@@ -135,9 +150,10 @@
   }
 
   mojo::PendingRemote<mojom::HidConnection> connection;
-  mojo::MakeSelfOwnedReceiver(
-      std::make_unique<FakeHidConnection>(devices_[device_guid]->Clone()),
-      connection.InitWithNewPipeAndPassReceiver());
+  // FakeHidConnection is self-owned.
+  new FakeHidConnection(devices_[device_guid]->Clone(),
+                        connection.InitWithNewPipeAndPassReceiver(),
+                        std::move(connection_client), std::move(watcher));
   std::move(callback).Run(std::move(connection));
 }
 
diff --git a/services/device/public/cpp/hid/fake_hid_manager.h b/services/device/public/cpp/hid/fake_hid_manager.h
index 2bc31f5..623efab4 100644
--- a/services/device/public/cpp/hid/fake_hid_manager.h
+++ b/services/device/public/cpp/hid/fake_hid_manager.h
@@ -19,7 +19,11 @@
 
 class FakeHidConnection : public mojom::HidConnection {
  public:
-  explicit FakeHidConnection(mojom::HidDeviceInfoPtr device);
+  FakeHidConnection(
+      mojom::HidDeviceInfoPtr device,
+      mojo::PendingReceiver<mojom::HidConnection> receiver,
+      mojo::PendingRemote<mojom::HidConnectionClient> connection_client,
+      mojo::PendingRemote<mojom::HidConnectionWatcher> watcher);
   FakeHidConnection(FakeHidConnection&) = delete;
   FakeHidConnection& operator=(FakeHidConnection&) = delete;
   ~FakeHidConnection() override;
@@ -37,7 +41,10 @@
                          SendFeatureReportCallback callback) override;
 
  private:
+  mojo::Receiver<mojom::HidConnection> receiver_;
   mojom::HidDeviceInfoPtr device_;
+  mojo::Remote<mojom::HidConnectionClient> client_;
+  mojo::Remote<mojom::HidConnectionWatcher> watcher_;
 };
 
 class FakeHidManager : public mojom::HidManager {
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index da1c7a19..9db6439c 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -29552,8 +29552,7 @@
         "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android23.textpb",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_m.unit_tests.filter"
+          "--avd-config=../../tools/android/avd/proto/generic_android23.textpb"
         ],
         "merge": {
           "args": [
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index d159dcf..6771fdb1 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -30422,7 +30422,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -30434,7 +30434,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "absl_hardening_tests_iPhone X 14.0",
+        "name": "absl_hardening_tests_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -30464,7 +30464,7 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -30476,7 +30476,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "base_unittests_iPad Air 2 14.0",
+        "name": "base_unittests_iPad Air 2 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -30506,7 +30506,7 @@
           "--platform",
           "iPhone 6s Plus",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -30518,7 +30518,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "base_unittests_iPhone 6s Plus 14.0",
+        "name": "base_unittests_iPhone 6s Plus 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -30548,7 +30548,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -30560,7 +30560,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "base_unittests_iPhone X 14.0",
+        "name": "base_unittests_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -30590,7 +30590,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -30602,7 +30602,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "boringssl_crypto_tests_iPhone X 14.0",
+        "name": "boringssl_crypto_tests_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -30632,7 +30632,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -30644,7 +30644,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "boringssl_ssl_tests_iPhone X 14.0",
+        "name": "boringssl_ssl_tests_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -30674,7 +30674,7 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -30686,7 +30686,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "components_unittests_iPad Air 2 14.0",
+        "name": "components_unittests_iPad Air 2 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -30716,7 +30716,7 @@
           "--platform",
           "iPhone 6s Plus",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -30728,7 +30728,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "components_unittests_iPhone 6s Plus 14.0",
+        "name": "components_unittests_iPhone 6s Plus 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -30758,7 +30758,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -30770,7 +30770,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "components_unittests_iPhone X 14.0",
+        "name": "components_unittests_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -30800,7 +30800,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -30812,7 +30812,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "crypto_unittests_iPhone X 14.0",
+        "name": "crypto_unittests_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -30842,7 +30842,7 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -30854,7 +30854,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "gfx_unittests_iPad Air 2 14.0",
+        "name": "gfx_unittests_iPad Air 2 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -30884,7 +30884,7 @@
           "--platform",
           "iPhone 6s Plus",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -30896,7 +30896,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "gfx_unittests_iPhone 6s Plus 14.0",
+        "name": "gfx_unittests_iPhone 6s Plus 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -30926,7 +30926,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -30938,7 +30938,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "gfx_unittests_iPhone X 14.0",
+        "name": "gfx_unittests_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -30968,7 +30968,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -30980,7 +30980,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "google_apis_unittests_iPhone X 14.0",
+        "name": "google_apis_unittests_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -31010,7 +31010,7 @@
           "--platform",
           "iPad (6th generation)",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -31022,7 +31022,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_bookmarks_eg2tests_module_iPad (6th generation) 14.0",
+        "name": "ios_chrome_bookmarks_eg2tests_module_iPad (6th generation) 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -31052,7 +31052,7 @@
           "--platform",
           "iPad Air (3rd generation)",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -31064,7 +31064,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_bookmarks_eg2tests_module_iPad Air (3rd generation) 14.0",
+        "name": "ios_chrome_bookmarks_eg2tests_module_iPad Air (3rd generation) 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -31094,7 +31094,7 @@
           "--platform",
           "iPhone 7",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -31106,7 +31106,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_bookmarks_eg2tests_module_iPhone 7 14.0",
+        "name": "ios_chrome_bookmarks_eg2tests_module_iPhone 7 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -31136,7 +31136,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -31148,7 +31148,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_bookmarks_eg2tests_module_iPhone X 14.0",
+        "name": "ios_chrome_bookmarks_eg2tests_module_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -31178,7 +31178,7 @@
           "--platform",
           "iPad (6th generation)",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -31190,7 +31190,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_integration_eg2tests_module_iPad (6th generation) 14.0",
+        "name": "ios_chrome_integration_eg2tests_module_iPad (6th generation) 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -31221,7 +31221,7 @@
           "--platform",
           "iPad Pro (12.9-inch) (2nd generation)",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -31233,7 +31233,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_integration_eg2tests_module_iPad Pro (12.9-inch) (2nd generation) 14.0",
+        "name": "ios_chrome_integration_eg2tests_module_iPad Pro (12.9-inch) (2nd generation) 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -31264,7 +31264,7 @@
           "--platform",
           "iPhone 7",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -31276,7 +31276,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_integration_eg2tests_module_iPhone 7 14.0",
+        "name": "ios_chrome_integration_eg2tests_module_iPhone 7 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -31307,7 +31307,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -31319,7 +31319,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_integration_eg2tests_module_iPhone X 14.0",
+        "name": "ios_chrome_integration_eg2tests_module_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -31350,7 +31350,7 @@
           "--platform",
           "iPad (6th generation)",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -31362,7 +31362,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_settings_eg2tests_module_iPad (6th generation) 14.0",
+        "name": "ios_chrome_settings_eg2tests_module_iPad (6th generation) 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -31393,7 +31393,7 @@
           "--platform",
           "iPad Air (3rd generation)",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -31405,7 +31405,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_settings_eg2tests_module_iPad Air (3rd generation) 14.0",
+        "name": "ios_chrome_settings_eg2tests_module_iPad Air (3rd generation) 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -31436,7 +31436,7 @@
           "--platform",
           "iPhone 7",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -31448,7 +31448,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_settings_eg2tests_module_iPhone 7 14.0",
+        "name": "ios_chrome_settings_eg2tests_module_iPhone 7 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -31479,7 +31479,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -31491,7 +31491,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_settings_eg2tests_module_iPhone X 14.0",
+        "name": "ios_chrome_settings_eg2tests_module_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -31522,7 +31522,7 @@
           "--platform",
           "iPad (6th generation)",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -31534,7 +31534,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_signin_eg2tests_module_iPad (6th generation) 14.0",
+        "name": "ios_chrome_signin_eg2tests_module_iPad (6th generation) 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -31564,7 +31564,7 @@
           "--platform",
           "iPad Pro (12.9-inch) (2nd generation)",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -31576,7 +31576,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_signin_eg2tests_module_iPad Pro (12.9-inch) (2nd generation) 14.0",
+        "name": "ios_chrome_signin_eg2tests_module_iPad Pro (12.9-inch) (2nd generation) 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -31606,7 +31606,7 @@
           "--platform",
           "iPhone 7",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -31618,7 +31618,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_signin_eg2tests_module_iPhone 7 14.0",
+        "name": "ios_chrome_signin_eg2tests_module_iPhone 7 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -31648,7 +31648,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -31660,7 +31660,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_signin_eg2tests_module_iPhone X 14.0",
+        "name": "ios_chrome_signin_eg2tests_module_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -31690,7 +31690,7 @@
           "--platform",
           "iPad (6th generation)",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -31702,7 +31702,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_smoke_eg2tests_module_iPad (6th generation) 14.0",
+        "name": "ios_chrome_smoke_eg2tests_module_iPad (6th generation) 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -31732,7 +31732,7 @@
           "--platform",
           "iPad Air (3rd generation)",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -31744,7 +31744,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_smoke_eg2tests_module_iPad Air (3rd generation) 14.0",
+        "name": "ios_chrome_smoke_eg2tests_module_iPad Air (3rd generation) 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -31774,7 +31774,7 @@
           "--platform",
           "iPhone 7",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -31786,7 +31786,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_smoke_eg2tests_module_iPhone 7 14.0",
+        "name": "ios_chrome_smoke_eg2tests_module_iPhone 7 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -31816,7 +31816,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -31828,7 +31828,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_smoke_eg2tests_module_iPhone X 14.0",
+        "name": "ios_chrome_smoke_eg2tests_module_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -31858,7 +31858,7 @@
           "--platform",
           "iPad (6th generation)",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -31870,7 +31870,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_ui_eg2tests_module_iPad (6th generation) 14.0",
+        "name": "ios_chrome_ui_eg2tests_module_iPad (6th generation) 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -31901,7 +31901,7 @@
           "--platform",
           "iPad Pro (12.9-inch) (2nd generation)",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -31913,7 +31913,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_ui_eg2tests_module_iPad Pro (12.9-inch) (2nd generation) 14.0",
+        "name": "ios_chrome_ui_eg2tests_module_iPad Pro (12.9-inch) (2nd generation) 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -31944,7 +31944,7 @@
           "--platform",
           "iPhone 7",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -31956,7 +31956,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_ui_eg2tests_module_iPhone 7 14.0",
+        "name": "ios_chrome_ui_eg2tests_module_iPhone 7 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -31987,7 +31987,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -31999,7 +31999,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_ui_eg2tests_module_iPhone X 14.0",
+        "name": "ios_chrome_ui_eg2tests_module_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -32030,7 +32030,7 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -32042,7 +32042,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_unittests_iPad Air 2 14.0",
+        "name": "ios_chrome_unittests_iPad Air 2 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -32072,7 +32072,7 @@
           "--platform",
           "iPhone 6s Plus",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -32084,7 +32084,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_unittests_iPhone 6s Plus 14.0",
+        "name": "ios_chrome_unittests_iPhone 6s Plus 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -32114,7 +32114,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -32126,7 +32126,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_unittests_iPhone X 14.0",
+        "name": "ios_chrome_unittests_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -32156,7 +32156,7 @@
           "--platform",
           "iPad (6th generation)",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -32168,7 +32168,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_web_eg2tests_module_iPad (6th generation) 14.0",
+        "name": "ios_chrome_web_eg2tests_module_iPad (6th generation) 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -32198,7 +32198,7 @@
           "--platform",
           "iPad Air (3rd generation)",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -32210,7 +32210,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_web_eg2tests_module_iPad Air (3rd generation) 14.0",
+        "name": "ios_chrome_web_eg2tests_module_iPad Air (3rd generation) 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -32240,7 +32240,7 @@
           "--platform",
           "iPhone 7",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -32252,7 +32252,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_web_eg2tests_module_iPhone 7 14.0",
+        "name": "ios_chrome_web_eg2tests_module_iPhone 7 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -32282,7 +32282,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -32294,7 +32294,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_web_eg2tests_module_iPhone X 14.0",
+        "name": "ios_chrome_web_eg2tests_module_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -32324,7 +32324,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -32336,7 +32336,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_components_unittests_iPhone X 14.0",
+        "name": "ios_components_unittests_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -32366,7 +32366,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -32378,7 +32378,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_net_unittests_iPhone X 14.0",
+        "name": "ios_net_unittests_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -32408,7 +32408,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -32420,7 +32420,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_remoting_unittests_iPhone X 14.0",
+        "name": "ios_remoting_unittests_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -32450,7 +32450,7 @@
           "--platform",
           "iPad (6th generation)",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -32462,7 +32462,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_showcase_eg2tests_module_iPad (6th generation) 14.0",
+        "name": "ios_showcase_eg2tests_module_iPad (6th generation) 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -32492,7 +32492,7 @@
           "--platform",
           "iPad Air (3rd generation)",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -32504,7 +32504,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_showcase_eg2tests_module_iPad Air (3rd generation) 14.0",
+        "name": "ios_showcase_eg2tests_module_iPad Air (3rd generation) 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -32534,7 +32534,7 @@
           "--platform",
           "iPhone 7",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -32546,7 +32546,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_showcase_eg2tests_module_iPhone 7 14.0",
+        "name": "ios_showcase_eg2tests_module_iPhone 7 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -32576,7 +32576,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -32588,7 +32588,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_showcase_eg2tests_module_iPhone X 14.0",
+        "name": "ios_showcase_eg2tests_module_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -32618,7 +32618,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -32630,7 +32630,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_testing_unittests_iPhone X 14.0",
+        "name": "ios_testing_unittests_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -32660,7 +32660,7 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -32672,7 +32672,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_inttests_iPad Air 2 14.0",
+        "name": "ios_web_inttests_iPad Air 2 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -32702,7 +32702,7 @@
           "--platform",
           "iPhone 6s Plus",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -32714,7 +32714,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_inttests_iPhone 6s Plus 14.0",
+        "name": "ios_web_inttests_iPhone 6s Plus 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -32744,7 +32744,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -32756,7 +32756,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_inttests_iPhone X 14.0",
+        "name": "ios_web_inttests_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -32786,7 +32786,7 @@
           "--platform",
           "iPad (6th generation)",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -32798,7 +32798,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_shell_eg2tests_module_iPad (6th generation) 14.0",
+        "name": "ios_web_shell_eg2tests_module_iPad (6th generation) 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -32828,7 +32828,7 @@
           "--platform",
           "iPad Pro (12.9-inch) (2nd generation)",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -32840,7 +32840,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_shell_eg2tests_module_iPad Pro (12.9-inch) (2nd generation) 14.0",
+        "name": "ios_web_shell_eg2tests_module_iPad Pro (12.9-inch) (2nd generation) 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -32870,7 +32870,7 @@
           "--platform",
           "iPhone 7",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -32882,7 +32882,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_shell_eg2tests_module_iPhone 7 14.0",
+        "name": "ios_web_shell_eg2tests_module_iPhone 7 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -32912,7 +32912,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -32924,7 +32924,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_shell_eg2tests_module_iPhone X 14.0",
+        "name": "ios_web_shell_eg2tests_module_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -32954,7 +32954,7 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -32966,7 +32966,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_unittests_iPad Air 2 14.0",
+        "name": "ios_web_unittests_iPad Air 2 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -32996,7 +32996,7 @@
           "--platform",
           "iPhone 6s Plus",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -33008,7 +33008,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_unittests_iPhone 6s Plus 14.0",
+        "name": "ios_web_unittests_iPhone 6s Plus 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -33038,7 +33038,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -33050,7 +33050,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_unittests_iPhone X 14.0",
+        "name": "ios_web_unittests_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -33080,7 +33080,7 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -33092,7 +33092,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_view_inttests_iPad Air 2 14.0",
+        "name": "ios_web_view_inttests_iPad Air 2 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -33122,7 +33122,7 @@
           "--platform",
           "iPhone 6s Plus",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -33134,7 +33134,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_view_inttests_iPhone 6s Plus 14.0",
+        "name": "ios_web_view_inttests_iPhone 6s Plus 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -33164,7 +33164,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -33176,7 +33176,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_view_inttests_iPhone X 14.0",
+        "name": "ios_web_view_inttests_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -33206,7 +33206,7 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -33218,7 +33218,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_view_unittests_iPad Air 2 14.0",
+        "name": "ios_web_view_unittests_iPad Air 2 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -33248,7 +33248,7 @@
           "--platform",
           "iPhone 6s Plus",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -33260,7 +33260,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_view_unittests_iPhone 6s Plus 14.0",
+        "name": "ios_web_view_unittests_iPhone 6s Plus 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -33290,7 +33290,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -33302,7 +33302,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_view_unittests_iPhone X 14.0",
+        "name": "ios_web_view_unittests_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -33332,7 +33332,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -33344,7 +33344,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "net_unittests_iPhone X 14.0",
+        "name": "net_unittests_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -33374,7 +33374,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -33386,7 +33386,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "services_unittests_iPhone X 14.0",
+        "name": "services_unittests_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -33416,7 +33416,7 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -33428,7 +33428,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "skia_unittests_iPad Air 2 14.0",
+        "name": "skia_unittests_iPad Air 2 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -33458,7 +33458,7 @@
           "--platform",
           "iPhone 6s Plus",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -33470,7 +33470,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "skia_unittests_iPhone 6s Plus 14.0",
+        "name": "skia_unittests_iPhone 6s Plus 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -33500,7 +33500,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -33512,7 +33512,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "skia_unittests_iPhone X 14.0",
+        "name": "skia_unittests_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -33542,7 +33542,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -33554,7 +33554,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "sql_unittests_iPhone X 14.0",
+        "name": "sql_unittests_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -33584,7 +33584,7 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -33596,7 +33596,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ui_base_unittests_iPad Air 2 14.0",
+        "name": "ui_base_unittests_iPad Air 2 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -33626,7 +33626,7 @@
           "--platform",
           "iPhone 6s Plus",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -33638,7 +33638,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ui_base_unittests_iPhone 6s Plus 14.0",
+        "name": "ui_base_unittests_iPhone 6s Plus 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -33668,7 +33668,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -33680,7 +33680,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ui_base_unittests_iPhone X 14.0",
+        "name": "ui_base_unittests_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -33710,7 +33710,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -33722,7 +33722,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "url_unittests_iPhone X 14.0",
+        "name": "url_unittests_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -33759,7 +33759,7 @@
           "--platform",
           "iPhone 6s",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -33771,7 +33771,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "absl_hardening_tests_iPhone 6s 14.0",
+        "name": "absl_hardening_tests_iPhone 6s 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -33801,7 +33801,7 @@
           "--platform",
           "iPhone 6s",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -33813,7 +33813,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "base_unittests_iPhone 6s 14.0",
+        "name": "base_unittests_iPhone 6s 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -33843,7 +33843,7 @@
           "--platform",
           "iPhone 6s Plus",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -33855,7 +33855,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "base_unittests_iPhone 6s Plus 14.0",
+        "name": "base_unittests_iPhone 6s Plus 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -33885,7 +33885,7 @@
           "--platform",
           "iPhone 6s",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -33897,7 +33897,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "boringssl_crypto_tests_iPhone 6s 14.0",
+        "name": "boringssl_crypto_tests_iPhone 6s 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -33927,7 +33927,7 @@
           "--platform",
           "iPhone 6s",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -33939,7 +33939,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "boringssl_ssl_tests_iPhone 6s 14.0",
+        "name": "boringssl_ssl_tests_iPhone 6s 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -33969,7 +33969,7 @@
           "--platform",
           "iPhone 6s",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -33981,7 +33981,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "components_unittests_iPhone 6s 14.0",
+        "name": "components_unittests_iPhone 6s 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -34011,7 +34011,7 @@
           "--platform",
           "iPhone 6s Plus",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -34023,7 +34023,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "components_unittests_iPhone 6s Plus 14.0",
+        "name": "components_unittests_iPhone 6s Plus 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -34053,7 +34053,7 @@
           "--platform",
           "iPhone 6s",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -34065,7 +34065,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "crypto_unittests_iPhone 6s 14.0",
+        "name": "crypto_unittests_iPhone 6s 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -34095,7 +34095,7 @@
           "--platform",
           "iPhone 6s",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -34107,7 +34107,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "gfx_unittests_iPhone 6s 14.0",
+        "name": "gfx_unittests_iPhone 6s 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -34137,7 +34137,7 @@
           "--platform",
           "iPhone 6s Plus",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -34149,7 +34149,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "gfx_unittests_iPhone 6s Plus 14.0",
+        "name": "gfx_unittests_iPhone 6s Plus 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -34179,7 +34179,7 @@
           "--platform",
           "iPhone 6s",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -34191,7 +34191,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "google_apis_unittests_iPhone 6s 14.0",
+        "name": "google_apis_unittests_iPhone 6s 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -34221,7 +34221,7 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -34233,7 +34233,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_bookmarks_eg2tests_module_iPad Air 2 14.0",
+        "name": "ios_chrome_bookmarks_eg2tests_module_iPad Air 2 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -34263,7 +34263,7 @@
           "--platform",
           "iPhone 7",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -34275,7 +34275,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_bookmarks_eg2tests_module_iPhone 7 14.0",
+        "name": "ios_chrome_bookmarks_eg2tests_module_iPhone 7 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -34305,7 +34305,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -34317,7 +34317,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_bookmarks_eg2tests_module_iPhone X 14.0",
+        "name": "ios_chrome_bookmarks_eg2tests_module_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -34347,7 +34347,7 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -34359,7 +34359,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_integration_eg2tests_module_iPad Air 2 14.0",
+        "name": "ios_chrome_integration_eg2tests_module_iPad Air 2 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -34390,7 +34390,7 @@
           "--platform",
           "iPad Pro (12.9-inch) (2nd generation)",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -34402,7 +34402,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_integration_eg2tests_module_iPad Pro (12.9-inch) (2nd generation) 14.0",
+        "name": "ios_chrome_integration_eg2tests_module_iPad Pro (12.9-inch) (2nd generation) 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -34433,7 +34433,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -34445,7 +34445,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_integration_eg2tests_module_iPhone X 14.0",
+        "name": "ios_chrome_integration_eg2tests_module_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -34476,7 +34476,7 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -34488,7 +34488,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_settings_eg2tests_module_iPad Air 2 14.0",
+        "name": "ios_chrome_settings_eg2tests_module_iPad Air 2 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -34519,7 +34519,7 @@
           "--platform",
           "iPhone 7",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -34531,7 +34531,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_settings_eg2tests_module_iPhone 7 14.0",
+        "name": "ios_chrome_settings_eg2tests_module_iPhone 7 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -34562,7 +34562,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -34574,7 +34574,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_settings_eg2tests_module_iPhone X 14.0",
+        "name": "ios_chrome_settings_eg2tests_module_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -34605,7 +34605,7 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -34617,7 +34617,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_signin_eg2tests_module_iPad Air 2 14.0",
+        "name": "ios_chrome_signin_eg2tests_module_iPad Air 2 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -34647,7 +34647,7 @@
           "--platform",
           "iPad Pro (12.9-inch) (2nd generation)",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -34659,7 +34659,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_signin_eg2tests_module_iPad Pro (12.9-inch) (2nd generation) 14.0",
+        "name": "ios_chrome_signin_eg2tests_module_iPad Pro (12.9-inch) (2nd generation) 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -34689,7 +34689,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -34701,7 +34701,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_signin_eg2tests_module_iPhone X 14.0",
+        "name": "ios_chrome_signin_eg2tests_module_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -34731,7 +34731,7 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -34743,7 +34743,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_smoke_eg2tests_module_iPad Air 2 14.0",
+        "name": "ios_chrome_smoke_eg2tests_module_iPad Air 2 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -34773,7 +34773,7 @@
           "--platform",
           "iPhone 7",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -34785,7 +34785,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_smoke_eg2tests_module_iPhone 7 14.0",
+        "name": "ios_chrome_smoke_eg2tests_module_iPhone 7 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -34815,7 +34815,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -34827,7 +34827,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_smoke_eg2tests_module_iPhone X 14.0",
+        "name": "ios_chrome_smoke_eg2tests_module_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -34857,7 +34857,7 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -34869,7 +34869,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_ui_eg2tests_module_iPad Air 2 14.0",
+        "name": "ios_chrome_ui_eg2tests_module_iPad Air 2 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -34900,7 +34900,7 @@
           "--platform",
           "iPad Pro (12.9-inch) (2nd generation)",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -34912,7 +34912,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_ui_eg2tests_module_iPad Pro (12.9-inch) (2nd generation) 14.0",
+        "name": "ios_chrome_ui_eg2tests_module_iPad Pro (12.9-inch) (2nd generation) 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -34943,7 +34943,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -34955,7 +34955,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_ui_eg2tests_module_iPhone X 14.0",
+        "name": "ios_chrome_ui_eg2tests_module_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -34986,7 +34986,7 @@
           "--platform",
           "iPhone 6s",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -34998,7 +34998,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_unittests_iPhone 6s 14.0",
+        "name": "ios_chrome_unittests_iPhone 6s 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -35028,7 +35028,7 @@
           "--platform",
           "iPhone 6s Plus",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -35040,7 +35040,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_unittests_iPhone 6s Plus 14.0",
+        "name": "ios_chrome_unittests_iPhone 6s Plus 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -35070,7 +35070,7 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -35082,7 +35082,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_web_eg2tests_module_iPad Air 2 14.0",
+        "name": "ios_chrome_web_eg2tests_module_iPad Air 2 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -35112,7 +35112,7 @@
           "--platform",
           "iPhone 7",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -35124,7 +35124,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_web_eg2tests_module_iPhone 7 14.0",
+        "name": "ios_chrome_web_eg2tests_module_iPhone 7 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -35154,7 +35154,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -35166,7 +35166,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_web_eg2tests_module_iPhone X 14.0",
+        "name": "ios_chrome_web_eg2tests_module_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -35196,7 +35196,7 @@
           "--platform",
           "iPhone 6s",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -35208,7 +35208,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_components_unittests_iPhone 6s 14.0",
+        "name": "ios_components_unittests_iPhone 6s 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -35238,7 +35238,7 @@
           "--platform",
           "iPhone 6s",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -35250,7 +35250,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_net_unittests_iPhone 6s 14.0",
+        "name": "ios_net_unittests_iPhone 6s 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -35280,7 +35280,7 @@
           "--platform",
           "iPhone 6s",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -35292,7 +35292,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_remoting_unittests_iPhone 6s 14.0",
+        "name": "ios_remoting_unittests_iPhone 6s 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -35322,7 +35322,7 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -35334,7 +35334,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_showcase_eg2tests_module_iPad Air 2 14.0",
+        "name": "ios_showcase_eg2tests_module_iPad Air 2 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -35364,7 +35364,7 @@
           "--platform",
           "iPhone 7",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -35376,7 +35376,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_showcase_eg2tests_module_iPhone 7 14.0",
+        "name": "ios_showcase_eg2tests_module_iPhone 7 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -35406,7 +35406,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -35418,7 +35418,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_showcase_eg2tests_module_iPhone X 14.0",
+        "name": "ios_showcase_eg2tests_module_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -35448,7 +35448,7 @@
           "--platform",
           "iPhone 6s",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -35460,7 +35460,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_testing_unittests_iPhone 6s 14.0",
+        "name": "ios_testing_unittests_iPhone 6s 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -35490,7 +35490,7 @@
           "--platform",
           "iPhone 6s",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -35502,7 +35502,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_inttests_iPhone 6s 14.0",
+        "name": "ios_web_inttests_iPhone 6s 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -35532,7 +35532,7 @@
           "--platform",
           "iPhone 6s Plus",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -35544,7 +35544,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_inttests_iPhone 6s Plus 14.0",
+        "name": "ios_web_inttests_iPhone 6s Plus 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -35574,7 +35574,7 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -35586,7 +35586,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_shell_eg2tests_module_iPad Air 2 14.0",
+        "name": "ios_web_shell_eg2tests_module_iPad Air 2 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -35616,7 +35616,7 @@
           "--platform",
           "iPad Pro (12.9-inch) (2nd generation)",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -35628,7 +35628,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_shell_eg2tests_module_iPad Pro (12.9-inch) (2nd generation) 14.0",
+        "name": "ios_web_shell_eg2tests_module_iPad Pro (12.9-inch) (2nd generation) 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -35658,7 +35658,7 @@
           "--platform",
           "iPhone X",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -35670,7 +35670,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_shell_eg2tests_module_iPhone X 14.0",
+        "name": "ios_web_shell_eg2tests_module_iPhone X 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -35700,7 +35700,7 @@
           "--platform",
           "iPhone 6s",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -35712,7 +35712,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_unittests_iPhone 6s 14.0",
+        "name": "ios_web_unittests_iPhone 6s 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -35742,7 +35742,7 @@
           "--platform",
           "iPhone 6s Plus",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -35754,7 +35754,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_unittests_iPhone 6s Plus 14.0",
+        "name": "ios_web_unittests_iPhone 6s Plus 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -35784,7 +35784,7 @@
           "--platform",
           "iPhone 6s",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -35796,7 +35796,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_view_inttests_iPhone 6s 14.0",
+        "name": "ios_web_view_inttests_iPhone 6s 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -35826,7 +35826,7 @@
           "--platform",
           "iPhone 6s Plus",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -35838,7 +35838,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_view_inttests_iPhone 6s Plus 14.0",
+        "name": "ios_web_view_inttests_iPhone 6s Plus 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -35868,7 +35868,7 @@
           "--platform",
           "iPhone 6s",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -35880,7 +35880,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_view_unittests_iPhone 6s 14.0",
+        "name": "ios_web_view_unittests_iPhone 6s 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -35910,7 +35910,7 @@
           "--platform",
           "iPhone 6s Plus",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -35922,7 +35922,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_view_unittests_iPhone 6s Plus 14.0",
+        "name": "ios_web_view_unittests_iPhone 6s Plus 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -35952,7 +35952,7 @@
           "--platform",
           "iPhone 6s",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -35964,7 +35964,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "net_unittests_iPhone 6s 14.0",
+        "name": "net_unittests_iPhone 6s 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -35994,7 +35994,7 @@
           "--platform",
           "iPhone 6s",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -36006,7 +36006,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "services_unittests_iPhone 6s 14.0",
+        "name": "services_unittests_iPhone 6s 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -36036,7 +36036,7 @@
           "--platform",
           "iPhone 6s",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -36048,7 +36048,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "skia_unittests_iPhone 6s 14.0",
+        "name": "skia_unittests_iPhone 6s 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -36078,7 +36078,7 @@
           "--platform",
           "iPhone 6s Plus",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -36090,7 +36090,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "skia_unittests_iPhone 6s Plus 14.0",
+        "name": "skia_unittests_iPhone 6s Plus 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -36120,7 +36120,7 @@
           "--platform",
           "iPhone 6s",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -36132,7 +36132,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "sql_unittests_iPhone 6s 14.0",
+        "name": "sql_unittests_iPhone 6s 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -36162,7 +36162,7 @@
           "--platform",
           "iPhone 6s",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -36174,7 +36174,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ui_base_unittests_iPhone 6s 14.0",
+        "name": "ui_base_unittests_iPhone 6s 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -36204,7 +36204,7 @@
           "--platform",
           "iPhone 6s Plus",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -36216,7 +36216,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ui_base_unittests_iPhone 6s Plus 14.0",
+        "name": "ui_base_unittests_iPhone 6s Plus 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -36246,7 +36246,7 @@
           "--platform",
           "iPhone 6s",
           "--version",
-          "14.0",
+          "14.2",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
@@ -36258,7 +36258,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "url_unittests_iPhone 6s 14.0",
+        "name": "url_unittests_iPhone 6s 14.2",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
diff --git a/testing/buildbot/filters/BUILD.gn b/testing/buildbot/filters/BUILD.gn
index 384e2fa3c..11a9329 100644
--- a/testing/buildbot/filters/BUILD.gn
+++ b/testing/buildbot/filters/BUILD.gn
@@ -191,7 +191,6 @@
 
   data = [
     "//testing/buildbot/filters/android.asan.unit_tests.filter",
-    "//testing/buildbot/filters/android.emulator_m.unit_tests.filter",
     "//testing/buildbot/filters/bfcache.unit_tests.filter",
     "//testing/buildbot/filters/lacros.unit_tests.filter",
   ]
diff --git a/testing/buildbot/filters/android.emulator_m.unit_tests.filter b/testing/buildbot/filters/android.emulator_m.unit_tests.filter
deleted file mode 100644
index f3770296..0000000
--- a/testing/buildbot/filters/android.emulator_m.unit_tests.filter
+++ /dev/null
@@ -1,5 +0,0 @@
-# crbug.com/1131181
--MediaRouterAndroidTest.DetachRoute
--MediaRouterAndroidTest.OnRouteClosed
--MediaRouterAndroidTest.OnRouteClosedWithError
--MediaRouterAndroidTest.OnRouteTerminated
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl
index 8a47f998..d363395 100644
--- a/testing/buildbot/gn_isolate_map.pyl
+++ b/testing/buildbot/gn_isolate_map.pyl
@@ -245,12 +245,8 @@
     "type": "console_test_launcher",
   },
   "blink_python_tests": {
-    "args": [
-      "../../third_party/blink/tools/run_blinkpy_tests.py",
-    ],
     "label": "//:blink_python_tests",
-    "script": "//testing/scripts/run_isolated_script_test.py",
-    "type": "script",
+    "type": "generated_script",
   },
   "blink_tests": {
     "label": "//:blink_tests",
@@ -261,23 +257,12 @@
     "type": "console_test_launcher",
   },
   "blink_web_tests": {
+    "label": "//:blink_web_tests",
+    "type": "generated_script",
     "args": [
-      "../../third_party/blink/tools/run_web_tests.py",
-      "--seed",
-      "4",
-      "--no-show-results",
-      "--zero-tests-executed-ok",
-      "--clobber-old-results",
-      "--exit-after-n-failures",
-      "5000",
-      "--exit-after-n-crashes-or-timeouts",
-      "100",
       "--results-directory",
       "${ISOLATED_OUTDIR}",
     ],
-    "label": "//:blink_web_tests",
-    "script": "//testing/scripts/run_isolated_script_test.py",
-    "type": "script",
   },
   "boundary_interface_example_apk": {
     "label": "//android_webview/support_library/boundary_interfaces:boundary_interface_example_apk",
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index 82d9133..fe468cd 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -2678,11 +2678,6 @@
           'shards': 8,
         },
       },
-      'android-marshmallow-x86-rel': {
-        'args': [
-          '--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_m.unit_tests.filter',
-        ],
-      },
       'linux-chromeos-chrome': {
         'args': [
           '--test-launcher-filter-file=../../testing/buildbot/filters/chromeos.unit_tests.filter',
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index f467aea..83a103a 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -5588,25 +5588,25 @@
           'xctest',
         ],
         'variants': [
-          'SIM_IPHONE_X_14_0',
+          'SIM_IPHONE_X_14_2',
         ],
       },
       'ios_eg2_tests': {
         'mixins': ['xcode_parallelization'],
         'variants': [
-          'SIM_IPHONE_X_14_0',
-          'SIM_IPHONE_7_14_0',
-          'SIM_IPAD_AIR_3RD_GEN_14_0',
-          'SIM_IPAD_6_GEN_14_0',
+          'SIM_IPHONE_X_14_2',
+          'SIM_IPHONE_7_14_2',
+          'SIM_IPAD_AIR_3RD_GEN_14_2',
+          'SIM_IPAD_6_GEN_14_2',
         ]
       },
       'ios_eg2_cq_tests': {
         'mixins': ['xcode_parallelization'],
         'variants': [
-          'SIM_IPHONE_7_14_0',
-          'SIM_IPHONE_X_14_0',
-          'SIM_IPAD_PRO_2ND_GEN_14_0',
-          'SIM_IPAD_6_GEN_14_0',
+          'SIM_IPHONE_7_14_2',
+          'SIM_IPHONE_X_14_2',
+          'SIM_IPAD_PRO_2ND_GEN_14_2',
+          'SIM_IPAD_6_GEN_14_2',
         ]
       },
       'ios_screen_size_dependent_tests': {
@@ -5614,9 +5614,9 @@
           'xctest',
         ],
         'variants': [
-          'SIM_IPHONE_6S_PLUS_14_0',
-          'SIM_IPHONE_X_14_0',
-          'SIM_IPAD_AIR_2_14_0',
+          'SIM_IPHONE_6S_PLUS_14_2',
+          'SIM_IPHONE_X_14_2',
+          'SIM_IPAD_AIR_2_14_2',
         ],
       },
     },
@@ -5627,26 +5627,26 @@
           'xctest',
         ],
         'variants': [
-          # 14.0 Sims
-          'SIM_IPHONE_6S_14_0',
+          # 14.2 Sims
+          'SIM_IPHONE_6S_14_2',
         ],
       },
       'ios_eg2_tests': {
         'mixins': ['xcode_parallelization'],
         'variants': [
-          # 14.0 Sims
-          'SIM_IPHONE_7_14_0',
-          'SIM_IPAD_AIR_2_14_0',
-          'SIM_IPHONE_X_14_0',
+          # 14.2 Sims
+          'SIM_IPHONE_7_14_2',
+          'SIM_IPAD_AIR_2_14_2',
+          'SIM_IPHONE_X_14_2',
         ]
       },
       'ios_eg2_cq_tests': {
         'mixins': ['xcode_parallelization'],
         'variants': [
-          # 14.0 Sims
-          'SIM_IPHONE_X_14_0',
-          'SIM_IPAD_AIR_2_14_0',
-          'SIM_IPAD_PRO_2ND_GEN_14_0',
+          # 14.2 Sims
+          'SIM_IPHONE_X_14_2',
+          'SIM_IPAD_AIR_2_14_2',
+          'SIM_IPAD_PRO_2ND_GEN_14_2',
         ]
       },
       'ios_screen_size_dependent_tests': {
@@ -5654,9 +5654,9 @@
           'xctest',
         ],
         'variants': [
-          # 14.0 Sims
-          'SIM_IPHONE_6S_PLUS_14_0',
-          'SIM_IPHONE_6S_14_0',
+          # 14.2 Sims
+          'SIM_IPHONE_6S_PLUS_14_2',
+          'SIM_IPHONE_6S_14_2',
         ],
       },
     },
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index cfea455..fb8e44d 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -89,34 +89,43 @@
     ],
     'identifier': 'iPad Air 2 14.0'
   },
-  'SIM_IPAD_AIR_3RD_GEN_14_0': {
+  'SIM_IPAD_AIR_2_14_2': {
+    'args': [
+      '--platform',
+      'iPad Air 2',
+      '--version',
+      '14.2'
+    ],
+    'identifier': 'iPad Air 2 14.2'
+  },
+  'SIM_IPAD_AIR_3RD_GEN_14_2': {
     'args': [
       '--platform',
       'iPad Air (3rd generation)',
       '--version',
-      '14.0'
+      '14.2'
     ],
-    'identifier': 'iPad Air (3rd generation) 14.0'
+    'identifier': 'iPad Air (3rd generation) 14.2'
   },
   # In Xcode 12, "iPad Pro (12.9-inch)" requires a generation suffix in
   # "platform" arg.
-  'SIM_IPAD_PRO_2ND_GEN_14_0': {
+  'SIM_IPAD_PRO_2ND_GEN_14_2': {
     'args': [
       '--platform',
       'iPad Pro (12.9-inch) (2nd generation)',
       '--version',
-      '14.0',
+      '14.2',
     ],
-    'identifier': 'iPad Pro (12.9-inch) (2nd generation) 14.0'
+    'identifier': 'iPad Pro (12.9-inch) (2nd generation) 14.2'
   },
-  'SIM_IPAD_6_GEN_14_0': {
+  'SIM_IPAD_6_GEN_14_2': {
     'args': [
       '--platform',
       'iPad (6th generation)',
       '--version',
-      '14.0',
+      '14.2',
     ],
-    'identifier': 'iPad (6th generation) 14.0'
+    'identifier': 'iPad (6th generation) 14.2'
   },
   'SIM_IPHONE_6S_12_4': {
     'args': [
@@ -136,15 +145,6 @@
     ],
     'identifier': 'iPhone 6s 13.6'
   },
-  'SIM_IPHONE_6S_13_6': {
-    'args': [
-      '--platform',
-      'iPhone 6s',
-      '--version',
-      '13.6',
-    ],
-    'identifier': 'iPhone 6s 13.6'
-  },
   'SIM_IPHONE_6S_14_0': {
     'args': [
       '--platform',
@@ -154,6 +154,15 @@
     ],
     'identifier': 'iPhone 6s 14.0'
   },
+  'SIM_IPHONE_6S_14_2': {
+    'args': [
+      '--platform',
+      'iPhone 6s',
+      '--version',
+      '14.2',
+    ],
+    'identifier': 'iPhone 6s 14.2'
+  },
   'SIM_IPHONE_6S_PLUS_13_6': {
     'args': [
       '--platform',
@@ -181,6 +190,15 @@
     ],
     'identifier': 'iPhone 6s Plus 14.0'
   },
+  'SIM_IPHONE_6S_PLUS_14_2': {
+    'args': [
+      '--platform',
+      'iPhone 6s Plus',
+      '--version',
+      '14.2',
+    ],
+    'identifier': 'iPhone 6s Plus 14.2'
+  },
   'SIM_IPHONE_7_13_6': {
     'args': [
       '--platform',
@@ -208,6 +226,15 @@
     ],
     'identifier': 'iPhone 7 14.0'
   },
+  'SIM_IPHONE_7_14_2': {
+    'args': [
+      '--platform',
+      'iPhone 7',
+      '--version',
+      '14.2',
+    ],
+    'identifier': 'iPhone 7 14.2'
+  },
   'SIM_IPHONE_SE_1ST_GEN_13_6': {
     'args': [
       '--platform',
@@ -271,6 +298,15 @@
     ],
     'identifier': 'iPhone X 14.0'
   },
+  'SIM_IPHONE_X_14_2': {
+    'args': [
+      '--platform',
+      'iPhone X',
+      '--version',
+      '14.2',
+    ],
+    'identifier': 'iPhone X 14.2'
+  },
   'WEBLAYER_IMPL_SKEW_TESTS_NTH_MILESTONE': {
     'args': [
       '--test-runner-outdir',
diff --git a/testing/scripts/run_wpt_tests.py b/testing/scripts/run_wpt_tests.py
index c4300196..b3f730a 100755
--- a/testing/scripts/run_wpt_tests.py
+++ b/testing/scripts/run_wpt_tests.py
@@ -16,13 +16,20 @@
 
 import json
 import os
+import shutil
 import sys
 
 import common
 import wpt_common
 
+# The checked-in manifest is copied to a temporary working directory so it can
+# be mutated by wptrunner
+WPT_CHECKED_IN_MANIFEST = (
+    "../../third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json")
+WPT_WORKING_COPY_MANIFEST = "../../out/Release/MANIFEST.json"
+
 WPT_CHECKED_IN_METADATA_DIR = "../../third_party/blink/web_tests/external/wpt"
-WPT_METADATA_OUTPUT_DIR = "../../wpt_expectations_metadata/"
+WPT_METADATA_OUTPUT_DIR = "../../out/Release/wpt_expectations_metadata/"
 WPT_OVERRIDE_EXPECTATIONS_PATH = (
     "../../third_party/blink/web_tests/WPTOverrideExpectations")
 
@@ -69,8 +76,8 @@
             # a lengthy import/export cycle to refresh. So we allow WPT to
             # update the manifest in cast it's stale.
             #"--no-manifest-update",
-            "--manifest=../../third_party/blink/web_tests/external/"
-                "WPT_BASE_MANIFEST_8.json",
+            "--manifest",
+            WPT_WORKING_COPY_MANIFEST,
             # (crbug.com/1023835) The flags below are temporary to aid debugging
             "--log-mach=-",
             "--log-mach-verbose",
@@ -78,6 +85,7 @@
             # TODO(lpz): Consider removing --processes and compute automatically
             # from multiprocessing.cpu_count()
             #"--processes=5",
+            "--mojojs-path=../../out/Release/gen/",
         ])
         return rest_args
 
@@ -86,6 +94,9 @@
                             help="List of tests or test directories to run")
 
     def do_pre_test_run_tasks(self):
+        # Copy the checked-in manifest to the temporary working directory
+        shutil.copy(WPT_CHECKED_IN_MANIFEST, WPT_WORKING_COPY_MANIFEST)
+
         # Generate WPT metadata files.
         common.run_command([
             sys.executable,
diff --git a/testing/test.gni b/testing/test.gni
index 0187b24f..91a2ce768 100644
--- a/testing/test.gni
+++ b/testing/test.gni
@@ -24,6 +24,7 @@
 
 if (is_chromeos) {
   import("//build/config/chromeos/rules.gni")
+  import("//build/util/generate_wrapper.gni")
 }
 
 if (is_ios) {
@@ -555,6 +556,66 @@
   }
 }
 
+# Defines a type of test that invokes a script to run, rather than
+# invoking an executable.
+#
+# The script must implement the
+# [test executable API](//docs/testing/test_executable_api.md).
+#
+# The template must be passed the `script` parameter, which specifies
+# the path to the script to run. It may optionally be passed a
+# `script_args` parameter, which can be used to include a list of
+# args to be specified by default. The template will produce
+# a `$root_build_dir/run_$target_name` wrapper and write the runtime_deps
+# for the target to $root_build_dir/${target_name}.runtime_deps, as per
+# the conventions listed in the
+# [test wrapper API](//docs/testing/test_wrapper_api.md).
+template("script_test") {
+  generate_wrapper(target_name) {
+    testonly = true
+    wrapper_script = "${root_build_dir}/bin/run_${target_name}"
+
+    executable = "//testing/test_env.py"
+
+    executable_args =
+        [ "@WrappedPath(" + rebase_path(invoker.script, root_build_dir) + ")" ]
+    if (defined(invoker.args)) {
+      executable_args += invoker.args
+    }
+
+    data = [
+      "//.vpython",
+      "//testing/test_env.py",
+
+      # These aren't needed by *every* test, but they're likely needed often
+      # enough to just make it easier to declare them here.
+      "//testing/xvfb.py",
+      "//testing/scripts/common.py",
+
+      invoker.script,
+    ]
+    if (defined(invoker.data)) {
+      data += invoker.data
+    }
+    data_deps = []
+    if (defined(invoker.data_deps)) {
+      data_deps += invoker.data_deps
+    }
+
+    forward_variables_from(invoker,
+                           [ "*" ],
+                           [
+                             "data",
+                             "data_deps",
+                             "script",
+                             "script_args",
+                             "testonly",
+                           ])
+
+    write_runtime_deps = "${root_build_dir}/${target_name}.runtime_deps"
+  }
+}
+
 # Test defaults.
 set_defaults("test") {
   if (is_android) {
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 1e41d7e..aa01977b 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -3863,6 +3863,28 @@
             ]
         }
     ],
+    "KeyboardAccessorySwipingIPH": [
+        {
+            "platforms": [
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "Swiping_IPH_Enabled",
+                    "params": {
+                        "availability": "any",
+                        "event_trigger": "name:keyboard_accessory_bar_swiping_iph_trigger;comparator:<1;window:90;storage:360",
+                        "event_used": "name:keyboard_accessory_bar_swiped;comparator:<2;window:90;storage:360",
+                        "session_rate": "<1"
+                    },
+                    "enable_features": [
+                        "IPH_KeyboardAccessoryBarSwiping"
+                    ],
+                    "disable_features": []
+                }
+            ]
+        }
+    ],
     "LauncherSettingsSearch": [
         {
             "platforms": [
diff --git a/third_party/.gitignore b/third_party/.gitignore
index f9ee2bc..e45971b0 100644
--- a/third_party/.gitignore
+++ b/third_party/.gitignore
@@ -81,7 +81,6 @@
 /freetype/src
 /fuchsia-sdk/images
 /fuchsia-sdk/sdk
-/fuchsia-sdk-arm64
 /gles2_conform
 /glfw/src
 /glslang/src
diff --git a/third_party/blink/common/switches.cc b/third_party/blink/common/switches.cc
index 74b28d08..e658a6a 100644
--- a/third_party/blink/common/switches.cc
+++ b/third_party/blink/common/switches.cc
@@ -18,6 +18,20 @@
 // value of the enum value. Applied after other command line flags and prefs.
 const char kBlinkSettings[] = "blink-settings";
 
+// Sets dark mode settings. Format is [<param>=<value>],[<param>=<value>],...
+// The params take either int or float values. If params are not specified,
+// the default dark mode settings is used. Valid params are given below.
+// "InversionAlgorithm" takes int value of DarkModeInversionAlgorithm enum.
+// "ImagePolicy" takes int value of DarkModeImagePolicy enum.
+// "IsGrayScale" takes 1 or 0, 1 means grayscale is true, false otherwise.
+// "TextBrightnessThreshold" takes 0 to 255 int value.
+// "BackgroundBrightnessThreshold" takes 0 to 255 int value.
+// "ContrastPercent" takes -1.0 to 1.0 float value. Higher the value, more
+// the contrast.
+// "ImageGrayScalePercent" takes 0.0 to 1.0 float value. Higher the value,
+// image would be more grayish.
+const char kDarkModeSettings[] = "dark-mode-settings";
+
 // Sets the tile size used by composited layers.
 const char kDefaultTileWidth[] = "default-tile-width";
 const char kDefaultTileHeight[] = "default-tile-height";
diff --git a/third_party/blink/public/common/privacy_budget/identifiable_surface.h b/third_party/blink/public/common/privacy_budget/identifiable_surface.h
index bface9c..5f17ae4 100644
--- a/third_party/blink/public/common/privacy_budget/identifiable_surface.h
+++ b/third_party/blink/public/common/privacy_budget/identifiable_surface.h
@@ -184,6 +184,9 @@
     // will key this type on a digest of both the enums' values.
     kWebGLShaderPrecisionFormat = 16,
 
+    // Represents a call to GPU.requestAdapter. Input is the options filter.
+    kGPU_RequestAdapter = 20,
+
     // We can use values up to and including |kMax|.
     kMax = (1 << kTypeBits) - 1
   };
diff --git a/third_party/blink/public/common/switches.h b/third_party/blink/public/common/switches.h
index 54892bc..b56d08a3 100644
--- a/third_party/blink/public/common/switches.h
+++ b/third_party/blink/public/common/switches.h
@@ -16,6 +16,7 @@
 // alongside the definition of their values in the .cc file.
 BLINK_COMMON_EXPORT extern const char kAllowPreCommitInput[];
 BLINK_COMMON_EXPORT extern const char kBlinkSettings[];
+BLINK_COMMON_EXPORT extern const char kDarkModeSettings[];
 BLINK_COMMON_EXPORT extern const char kDefaultTileWidth[];
 BLINK_COMMON_EXPORT extern const char kDefaultTileHeight[];
 BLINK_COMMON_EXPORT extern const char kDisableImageAnimationResync[];
diff --git a/third_party/blink/public/mojom/web_feature/web_feature.mojom b/third_party/blink/public/mojom/web_feature/web_feature.mojom
index e828b4f..ff2a338 100644
--- a/third_party/blink/public/mojom/web_feature/web_feature.mojom
+++ b/third_party/blink/public/mojom/web_feature/web_feature.mojom
@@ -2825,10 +2825,10 @@
   kIdentifiabilityStudyReserved3497 = 3497,
   kIdentifiabilityStudyReserved3498 = 3498,
   kV8BackgroundFetchRegistration_FailureReason_AttributeGetter = 3499,
-  kIdentifiabilityStudyReserved3500 = 3500,
-  kIdentifiabilityStudyReserved3501 = 3501,
-  kIdentifiabilityStudyReserved3502 = 3502,
-  kIdentifiabilityStudyReserved3503 = 3503,
+  kV8Document_ElementFromPoint_Method = 3500,
+  kV8Document_ElementsFromPoint_Method = 3501,
+  kV8ShadowRoot_ElementFromPoint_Method = 3502,
+  kV8ShadowRoot_ElementsFromPoint_Method = 3503,
   kIdentifiabilityStudyReserved3504 = 3504,
   kIdentifiabilityStudyReserved3505 = 3505,
   kIdentifiabilityStudyReserved3506 = 3506,
diff --git a/third_party/blink/renderer/bindings/core/v8/scheduled_action.cc b/third_party/blink/renderer/bindings/core/v8/scheduled_action.cc
index c329bd3c..2790c8f 100644
--- a/third_party/blink/renderer/bindings/core/v8/scheduled_action.cc
+++ b/third_party/blink/renderer/bindings/core/v8/scheduled_action.cc
@@ -39,7 +39,6 @@
 #include "third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
-#include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/workers/worker_global_scope.h"
 #include "third_party/blink/renderer/core/workers/worker_thread.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
@@ -57,9 +56,9 @@
     : script_state_(
           MakeGarbageCollected<ScriptStateProtectingContext>(script_state)) {
   if (script_state->World().IsWorkerWorld() ||
-      BindingSecurity::ShouldAllowAccessToFrame(
+      BindingSecurity::ShouldAllowAccessTo(
           EnteredDOMWindow(script_state->GetIsolate()),
-          To<LocalDOMWindow>(target)->GetFrame(),
+          To<LocalDOMWindow>(target),
           BindingSecurity::ErrorReportOption::kDoNotReport)) {
     function_ = handler;
     arguments_ = arguments;
@@ -74,9 +73,9 @@
     : script_state_(
           MakeGarbageCollected<ScriptStateProtectingContext>(script_state)) {
   if (script_state->World().IsWorkerWorld() ||
-      BindingSecurity::ShouldAllowAccessToFrame(
+      BindingSecurity::ShouldAllowAccessTo(
           EnteredDOMWindow(script_state->GetIsolate()),
-          To<LocalDOMWindow>(target)->GetFrame(),
+          To<LocalDOMWindow>(target),
           BindingSecurity::ErrorReportOption::kDoNotReport)) {
     code_ = handler;
   } else {
@@ -119,22 +118,38 @@
   // ExecutionContext::CanExecuteScripts() relies on the current context to
   // determine if it is allowed. Enter the scope here.
   ScriptState::Scope scope(script_state_->Get());
+  if (!context->CanExecuteScripts(kAboutToExecuteScript)) {
+    DVLOG(1) << "ScheduledAction::execute " << this
+             << ": window can not execute scripts";
+    return;
+  }
 
+  // https://html.spec.whatwg.org/C/#timer-initialisation-steps
+  if (function_) {
+    DVLOG(1) << "ScheduledAction::execute " << this << ": have function";
+    function_->InvokeAndReportException(context->ToScriptWrappable(),
+                                        arguments_);
+    return;
+  }
+
+  // We're using |SanitizeScriptErrors::kDoNotSanitize| to keep the existing
+  // behavior, but this causes failures on
+  // wpt/html/webappapis/scripting/processing-model-2/compile-error-cross-origin-setTimeout.html
+  // and friends.
+  DVLOG(1) << "ScheduledAction::execute " << this << ": executing from source";
   if (LocalDOMWindow* window = DynamicTo<LocalDOMWindow>(context)) {
-    LocalFrame* frame = window->GetFrame();
-    if (!frame) {
-      DVLOG(1) << "ScheduledAction::execute " << this << ": no frame";
-      return;
-    }
-    if (!context->CanExecuteScripts(kAboutToExecuteScript)) {
-      DVLOG(1) << "ScheduledAction::execute " << this
-               << ": frame can not execute scripts";
-      return;
-    }
-    Execute(frame);
+    window->GetScriptController().ExecuteScriptAndReturnValue(
+        script_state_->GetContext(),
+        ScriptSourceCode(code_,
+                         ScriptSourceLocationType::kEvalForScheduledAction),
+        KURL(), SanitizeScriptErrors::kDoNotSanitize);
   } else {
-    DVLOG(1) << "ScheduledAction::execute " << this << ": worker scope";
-    Execute(To<WorkerGlobalScope>(context));
+    WorkerGlobalScope* worker = To<WorkerGlobalScope>(context);
+    DCHECK(worker->GetThread()->IsCurrentThread());
+    worker->ScriptController()->EvaluateAndReturnValue(
+        ScriptSourceCode(code_,
+                         ScriptSourceLocationType::kEvalForScheduledAction),
+        SanitizeScriptErrors::kDoNotSanitize);
   }
 }
 
@@ -144,50 +159,4 @@
   visitor->Trace(arguments_);
 }
 
-void ScheduledAction::Execute(LocalFrame* frame) {
-  DCHECK(script_state_->ContextIsValid());
-
-  // https://html.spec.whatwg.org/C/#timer-initialisation-steps
-  TRACE_EVENT0("v8", "ScheduledAction::execute");
-  if (function_) {
-    DVLOG(1) << "ScheduledAction::execute " << this << ": have function";
-    function_->InvokeAndReportException(frame->DomWindow(), arguments_);
-  } else {
-    DVLOG(1) << "ScheduledAction::execute " << this
-             << ": executing from source";
-    // We're using |SanitizeScriptErrors::kDoNotSanitize| to keep the existing
-    // behavior, but this causes failures on
-    // wpt/html/webappapis/scripting/processing-model-2/compile-error-cross-origin-setTimeout.html
-    // and friends.
-    frame->DomWindow()->GetScriptController().ExecuteScriptAndReturnValue(
-        script_state_->GetContext(),
-        ScriptSourceCode(code_,
-                         ScriptSourceLocationType::kEvalForScheduledAction),
-        KURL(), SanitizeScriptErrors::kDoNotSanitize);
-  }
-
-  // The frame might be invalid at this point because JavaScript could have
-  // released it.
-}
-
-void ScheduledAction::Execute(WorkerGlobalScope* worker) {
-  DCHECK(script_state_->ContextIsValid());
-  DCHECK(worker->GetThread()->IsCurrentThread());
-
-  // https://html.spec.whatwg.org/C/#timer-initialisation-steps
-  if (function_) {
-    function_->InvokeAndReportException(worker, arguments_);
-  } else {
-    // We're using |SanitizeScriptErrors::kDoNotSanitize| to keep the existing
-    // behavior, but this causes failures on
-    // wpt/html/webappapis/scripting/processing-model-2/compile-error-cross-origin-setTimeout.html
-    // and friends.
-    ScriptState::Scope scope(worker->ScriptController()->GetScriptState());
-    worker->ScriptController()->EvaluateAndReturnValue(
-        ScriptSourceCode(code_,
-                         ScriptSourceLocationType::kEvalForScheduledAction),
-        SanitizeScriptErrors::kDoNotSanitize);
-  }
-}
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/bindings/core/v8/scheduled_action.h b/third_party/blink/renderer/bindings/core/v8/scheduled_action.h
index c8d7a4c..42b43a0f 100644
--- a/third_party/blink/renderer/bindings/core/v8/scheduled_action.h
+++ b/third_party/blink/renderer/bindings/core/v8/scheduled_action.h
@@ -40,12 +40,10 @@
 namespace blink {
 
 class ExecutionContext;
-class LocalFrame;
 class ScriptState;
 class ScriptStateProtectingContext;
 class ScriptValue;
 class V8Function;
-class WorkerGlobalScope;
 
 class ScheduledAction final : public GarbageCollected<ScheduledAction>,
                               public NameClient {
@@ -71,9 +69,6 @@
   const char* NameInHeapSnapshot() const override { return "ScheduledAction"; }
 
  private:
-  void Execute(LocalFrame*);
-  void Execute(WorkerGlobalScope*);
-
   Member<ScriptStateProtectingContext> script_state_;
   Member<V8Function> function_;
   HeapVector<ScriptValue> arguments_;
diff --git a/third_party/blink/renderer/core/accessibility/BUILD.gn b/third_party/blink/renderer/core/accessibility/BUILD.gn
index a51a5c9..4e9124a 100644
--- a/third_party/blink/renderer/core/accessibility/BUILD.gn
+++ b/third_party/blink/renderer/core/accessibility/BUILD.gn
@@ -6,8 +6,6 @@
 
 blink_core_sources("accessibility") {
   sources = [
-    "apply_dark_mode.cc",
-    "apply_dark_mode.h",
     "ax_context.cc",
     "ax_context.h",
     "ax_object_cache.cc",
diff --git a/third_party/blink/renderer/core/accessibility/apply_dark_mode.cc b/third_party/blink/renderer/core/accessibility/apply_dark_mode.cc
deleted file mode 100644
index 7a3e94f..0000000
--- a/third_party/blink/renderer/core/accessibility/apply_dark_mode.cc
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "third_party/blink/renderer/core/accessibility/apply_dark_mode.h"
-
-#include "base/metrics/field_trial_params.h"
-#include "third_party/blink/public/common/features.h"
-#include "third_party/blink/public/common/forcedark/forcedark_switches.h"
-#include "third_party/blink/renderer/core/css/properties/css_property.h"
-#include "third_party/blink/renderer/core/style/computed_style.h"
-#include "third_party/blink/renderer/platform/graphics/color.h"
-#include "third_party/blink/renderer/platform/graphics/dark_mode_color_classifier.h"
-#include "third_party/blink/renderer/platform/graphics/dark_mode_settings.h"
-
-namespace blink {
-namespace {
-
-DarkModeInversionAlgorithm GetMode(const Settings& frame_settings) {
-  switch (features::kForceDarkInversionMethodParam.Get()) {
-    case ForceDarkInversionMethod::kUseBlinkSettings:
-      return frame_settings.GetForceDarkModeInversionAlgorithm();
-    case ForceDarkInversionMethod::kCielabBased:
-      return DarkModeInversionAlgorithm::kInvertLightnessLAB;
-    case ForceDarkInversionMethod::kHslBased:
-      return DarkModeInversionAlgorithm::kInvertLightness;
-    case ForceDarkInversionMethod::kRgbBased:
-      return DarkModeInversionAlgorithm::kInvertBrightness;
-  }
-}
-
-DarkModeImagePolicy GetImagePolicy(const Settings& frame_settings) {
-  switch (features::kForceDarkImageBehaviorParam.Get()) {
-    case ForceDarkImageBehavior::kUseBlinkSettings:
-      return frame_settings.GetForceDarkModeImagePolicy();
-    case ForceDarkImageBehavior::kInvertNone:
-      return DarkModeImagePolicy::kFilterNone;
-    case ForceDarkImageBehavior::kInvertSelectively:
-      return DarkModeImagePolicy::kFilterSmart;
-  }
-}
-
-int GetTextBrightnessThreshold(const Settings& frame_settings) {
-  const int flag_value = base::GetFieldTrialParamByFeatureAsInt(
-      features::kForceWebContentsDarkMode,
-      features::kForceDarkTextLightnessThresholdParam.name, -1);
-  return flag_value >= 0
-             ? flag_value
-             : frame_settings.GetForceDarkModeTextBrightnessThreshold();
-}
-
-int GetBackgroundBrightnessThreshold(const Settings& frame_settings) {
-  const int flag_value = base::GetFieldTrialParamByFeatureAsInt(
-      features::kForceWebContentsDarkMode,
-      features::kForceDarkBackgroundLightnessThresholdParam.name, -1);
-  return flag_value >= 0
-             ? flag_value
-             : frame_settings.GetForceDarkModeBackgroundBrightnessThreshold();
-}
-
-DarkModeSettings GetEnabledSettings(const Settings& frame_settings) {
-  DarkModeSettings settings;
-  settings.mode = GetMode(frame_settings);
-  settings.image_policy = GetImagePolicy(frame_settings);
-  settings.text_brightness_threshold =
-      GetTextBrightnessThreshold(frame_settings);
-  settings.background_brightness_threshold =
-      GetBackgroundBrightnessThreshold(frame_settings);
-
-  settings.grayscale = frame_settings.GetForceDarkModeGrayscale();
-  settings.contrast = frame_settings.GetForceDarkModeContrast();
-  settings.image_grayscale_percent =
-      frame_settings.GetForceDarkModeImageGrayscale();
-  return settings;
-}
-
-DarkModeSettings GetDisabledSettings() {
-  DarkModeSettings settings;
-  settings.mode = DarkModeInversionAlgorithm::kOff;
-  return settings;
-}
-
-}  // namespace
-
-DarkModeSettings BuildDarkModeSettings(const Settings& frame_settings,
-                                       bool content_has_dark_color_scheme) {
-  if (content_has_dark_color_scheme)
-    return GetDisabledSettings();
-
-  return frame_settings.GetForceDarkModeEnabled()
-             ? GetEnabledSettings(frame_settings)
-             : GetDisabledSettings();
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/core/accessibility/apply_dark_mode.h b/third_party/blink/renderer/core/accessibility/apply_dark_mode.h
deleted file mode 100644
index dc78205..0000000
--- a/third_party/blink/renderer/core/accessibility/apply_dark_mode.h
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_ACCESSIBILITY_APPLY_DARK_MODE_H_
-#define THIRD_PARTY_BLINK_RENDERER_CORE_ACCESSIBILITY_APPLY_DARK_MODE_H_
-
-#include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/core/frame/settings.h"
-#include "third_party/blink/renderer/platform/graphics/dark_mode_settings.h"
-
-namespace blink {
-
-// If content has dark color scheme set then return disabled settings, otherwise
-// return enabled settings based on force dark mode enabled.
-DarkModeSettings CORE_EXPORT
-BuildDarkModeSettings(const Settings& settings,
-                      bool content_has_dark_color_scheme);
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_ACCESSIBILITY_APPLY_DARK_MODE_H_
diff --git a/third_party/blink/renderer/core/animation/css/css_keyframe_effect_model.cc b/third_party/blink/renderer/core/animation/css/css_keyframe_effect_model.cc
index 8bfce64..f37380d 100644
--- a/third_party/blink/renderer/core/animation/css/css_keyframe_effect_model.cc
+++ b/third_party/blink/renderer/core/animation/css/css_keyframe_effect_model.cc
@@ -8,7 +8,10 @@
 #include "third_party/blink/renderer/core/animation/animation_utils.h"
 #include "third_party/blink/renderer/core/animation/property_handle.h"
 #include "third_party/blink/renderer/core/animation/string_keyframe.h"
+#include "third_party/blink/renderer/core/css/properties/computed_style_utils.h"
 #include "third_party/blink/renderer/core/css/properties/css_property.h"
+#include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
+#include "third_party/blink/renderer/core/dom/node_computed_style.h"
 
 namespace blink {
 
@@ -41,6 +44,11 @@
                           const PropertyHandleSet& keyframe_properties,
                           StringKeyframe* keyframe) {
   for (const auto& property : all_properties) {
+    // At present, custom properties are to be excluded from the keyframes.
+    // https://github.com/w3c/csswg-drafts/issues/5126.
+    if (property.IsCSSCustomProperty())
+      continue;
+
     if (keyframe_properties.Contains(property))
       continue;
 
@@ -48,15 +56,35 @@
         AnimationInputHelpers::PropertyHandleToKeyframeAttribute(property);
     if (property_map.Contains(property_name)) {
       const String& value = property_map.at(property_name);
-      if (property.IsCSSCustomProperty()) {
-        keyframe->SetCSSPropertyValue(property.CustomPropertyName(), value,
-                                      SecureContextMode::kInsecureContext,
-                                      nullptr);
-      } else {
-        keyframe->SetCSSPropertyValue(
-            property.GetCSSProperty().PropertyID(), value,
-            SecureContextMode::kInsecureContext, nullptr);
-      }
+      keyframe->SetCSSPropertyValue(property.GetCSSProperty().PropertyID(),
+                                    value, SecureContextMode::kInsecureContext,
+                                    nullptr);
+    }
+  }
+}
+
+void ResolveComputedValues(Element* element, StringKeyframe* keyframe) {
+  DCHECK(element);
+  // Styles are flushed when getKeyframes is called on a CSS animation.
+  DCHECK(element->GetComputedStyle());
+  for (const auto& property : keyframe->Properties()) {
+    if (property.IsCSSCustomProperty()) {
+      // At present, custom properties are to be excluded from the keyframes.
+      // https://github.com/w3c/csswg-drafts/issues/5126.
+      // TODO(csswg/issues/5126): Revisit once issue regarding inclusion of
+      // custom properties is resolved. Perhaps registered should likely be
+      // included since they can be animated in Blink. Pruning unregistered
+      // variables seems justifiable.
+      keyframe->RemoveCustomCSSProperty(property);
+    } else if (property.IsCSSProperty()) {
+      const CSSValue& value = keyframe->CssPropertyValue(property);
+      const CSSPropertyName property_name =
+          property.IsCSSCustomProperty()
+              ? CSSPropertyName(property.CustomPropertyName())
+              : CSSPropertyName(property.GetCSSProperty().PropertyID());
+      const CSSValue* computed_value =
+          StyleResolver::ComputeValue(element, property_name, value);
+      keyframe->SetCSSPropertyValue(property.GetCSSProperty(), *computed_value);
     }
   }
 }
@@ -83,14 +111,16 @@
     Keyframe* keyframe = keyframes[i];
     // TODO(crbug.com/1070627): Use computed values, prune variable references,
     // and convert logical properties to physical properties.
-    computed_keyframes.push_back(keyframe->Clone());
+    StringKeyframe* computed_keyframe = To<StringKeyframe>(keyframe->Clone());
+    ResolveComputedValues(element, computed_keyframe);
+    computed_keyframes.push_back(computed_keyframe);
     double offset = computed_offsets[i];
     if (offset == 0) {
-      for (const auto& property : keyframe->Properties()) {
+      for (const auto& property : computed_keyframe->Properties()) {
         from_properties.insert(property);
       }
     } else if (offset == 1) {
-      for (const auto& property : keyframe->Properties()) {
+      for (const auto& property : computed_keyframe->Properties()) {
         to_properties.insert(property);
       }
     }
diff --git a/third_party/blink/renderer/core/animation/string_keyframe.cc b/third_party/blink/renderer/core/animation/string_keyframe.cc
index b247c2b..2b34764 100644
--- a/third_party/blink/renderer/core/animation/string_keyframe.cc
+++ b/third_party/blink/renderer/core/animation/string_keyframe.cc
@@ -145,6 +145,13 @@
   InvalidateCssPropertyMap();
 }
 
+void StringKeyframe::RemoveCustomCSSProperty(const PropertyHandle& property) {
+  DCHECK(property.IsCSSCustomProperty());
+  if (css_property_map_)
+    css_property_map_->RemoveProperty(property.CustomPropertyName());
+  input_properties_.erase(property);
+}
+
 void StringKeyframe::SetPresentationAttributeValue(
     const CSSProperty& property,
     const String& value,
diff --git a/third_party/blink/renderer/core/animation/string_keyframe.h b/third_party/blink/renderer/core/animation/string_keyframe.h
index b4f72d7c..22f3d4f 100644
--- a/third_party/blink/renderer/core/animation/string_keyframe.h
+++ b/third_party/blink/renderer/core/animation/string_keyframe.h
@@ -42,6 +42,7 @@
       SecureContextMode,
       StyleSheetContents*);
   void SetCSSPropertyValue(const CSSProperty&, const CSSValue&);
+  void RemoveCustomCSSProperty(const PropertyHandle& property);
 
   void SetPresentationAttributeValue(const CSSProperty&,
                                      const String& value,
diff --git a/third_party/blink/renderer/core/css/parser/css_selector_parser.cc b/third_party/blink/renderer/core/css/parser/css_selector_parser.cc
index ee1a668..19b04e9 100644
--- a/third_party/blink/renderer/core/css/parser/css_selector_parser.cc
+++ b/third_party/blink/renderer/core/css/parser/css_selector_parser.cc
@@ -554,7 +554,14 @@
             return nullptr;
         }
         break;
-      default:;
+      case CSSSelector::kPseudoShadow:
+      case CSSSelector::kPseudoContent:
+        if (disallow_shadow_dom_v0_)
+          return nullptr;
+        disallow_nested_complex_ = true;
+        break;
+      default:
+        break;
     }
   }
 
@@ -578,6 +585,9 @@
     case CSSSelector::kPseudoIs: {
       if (!RuntimeEnabledFeatures::CSSPseudoIsEnabled())
         break;
+      if (disallow_nested_complex_)
+        return nullptr;
+      disallow_shadow_dom_v0_ = true;
 
       DisallowPseudoElementsScope scope(this);
 
@@ -592,6 +602,9 @@
     case CSSSelector::kPseudoWhere: {
       if (!RuntimeEnabledFeatures::CSSPseudoWhereEnabled())
         break;
+      if (disallow_nested_complex_)
+        return nullptr;
+      disallow_shadow_dom_v0_ = true;
 
       DisallowPseudoElementsScope scope(this);
 
@@ -723,6 +736,10 @@
       const CSSParserToken& slash = range.ConsumeIncludingWhitespace();
       if (slash.GetType() != kDelimiterToken || slash.Delimiter() != '/')
         failed_parsing_ = true;
+      if (disallow_shadow_dom_v0_)
+        failed_parsing_ = true;
+      else
+        disallow_nested_complex_ = true;
       return context_->IsLiveProfile() ? CSSSelector::kShadowDeepAsDescendant
                                        : CSSSelector::kShadowDeep;
     }
diff --git a/third_party/blink/renderer/core/css/parser/css_selector_parser.h b/third_party/blink/renderer/core/css/parser/css_selector_parser.h
index 7145d241..40722bd 100644
--- a/third_party/blink/renderer/core/css/parser/css_selector_parser.h
+++ b/third_party/blink/renderer/core/css/parser/css_selector_parser.h
@@ -90,6 +90,12 @@
 
   bool failed_parsing_ = false;
   bool disallow_pseudo_elements_ = false;
+  // We don't allow mixing ShadowDOMv0 features and nested complex selectors,
+  // such as :is(). When :is() or :where() is encountered, ShadowDOM V0 features
+  // are disallowed, and whenever /deep/, ::content or ::shadow is encountered
+  // we disallow :is()/:where().
+  bool disallow_shadow_dom_v0_ = false;
+  bool disallow_nested_complex_ = false;
 
   class DisallowPseudoElementsScope {
     STACK_ALLOCATED();
diff --git a/third_party/blink/renderer/core/css/parser/css_selector_parser_test.cc b/third_party/blink/renderer/core/css/parser/css_selector_parser_test.cc
index ead76aa..66b3faf 100644
--- a/third_party/blink/renderer/core/css/parser/css_selector_parser_test.cc
+++ b/third_party/blink/renderer/core/css/parser/css_selector_parser_test.cc
@@ -426,6 +426,44 @@
   }
 }
 
+TEST(CSSSelectorParserTest, ShadowDomV0WithIsAndWhere) {
+  // To reduce complexity, ShadowDOM v0 features are not supported in
+  // combination with :is/:where.
+  const char* test_cases[] = {
+      // clang-format off
+      ":is(.a) ::content",
+      ":is(.a /deep/ .b)",
+      ":is(::content)",
+      ":is(::shadow)",
+      ":is(::content .a)",
+      ":is(::shadow .b)",
+      ":is(.a)::shadow",
+      ":is(.a) ::content",
+      ":is(.a) ::shadow",
+      "::content :is(.a)",
+      "::shadow :is(.a)",
+      ":is(.a) /deep/ .b",
+      ":.a /deep/ :is(.b)",
+      ":where(.a /deep/ .b)",
+      ":where(.a) ::shadow",
+      // clang-format on
+  };
+
+  auto* context = MakeGarbageCollected<CSSParserContext>(
+      kHTMLStandardMode, SecureContextMode::kInsecureContext);
+  auto* sheet = MakeGarbageCollected<StyleSheetContents>(context);
+
+  for (auto* test_case : test_cases) {
+    SCOPED_TRACE(test_case);
+    CSSTokenizer tokenizer(test_case);
+    const auto tokens = tokenizer.TokenizeToEOF();
+    CSSParserTokenRange range(tokens);
+    CSSSelectorList list =
+        CSSSelectorParser::ParseSelector(range, context, sheet);
+    EXPECT_FALSE(list.IsValid());
+  }
+}
+
 namespace {
 
 const auto TagLocalName = [](const CSSSelector* selector) {
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.cc b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
index 2095acf..d545f24 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
@@ -50,6 +50,7 @@
 #include "third_party/blink/renderer/core/css/font_face.h"
 #include "third_party/blink/renderer/core/css/page_rule_collector.h"
 #include "third_party/blink/renderer/core/css/part_names.h"
+#include "third_party/blink/renderer/core/css/properties/computed_style_utils.h"
 #include "third_party/blink/renderer/core/css/properties/css_property.h"
 #include "third_party/blink/renderer/core/css/properties/css_property_ref.h"
 #include "third_party/blink/renderer/core/css/resolver/match_result.h"
@@ -1614,6 +1615,31 @@
   return true;
 }
 
+const CSSValue* StyleResolver::ComputeValue(
+    Element* element,
+    const CSSPropertyName& property_name,
+    const CSSValue& value) {
+  const ComputedStyle* base_style = element->GetComputedStyle();
+  StyleResolverState state(element->GetDocument(), *element);
+  STACK_UNINITIALIZED StyleCascade cascade(state);
+  state.SetStyle(ComputedStyle::Clone(*base_style));
+  auto* set =
+      MakeGarbageCollected<MutableCSSPropertyValueSet>(state.GetParserMode());
+  if (property_name.IsCustomProperty()) {
+    set->SetProperty(CSSPropertyValue(property_name, value));
+  } else {
+    set->SetProperty(property_name.Id(), value);
+  }
+  cascade.MutableMatchResult().FinishAddingUARules();
+  cascade.MutableMatchResult().FinishAddingUserRules();
+  cascade.MutableMatchResult().AddMatchedProperties(set);
+  cascade.Apply();
+
+  CSSPropertyRef property_ref(property_name, element->GetDocument());
+  return ComputedStyleUtils::ComputedPropertyValue(property_ref.GetProperty(),
+                                                   *state.Style());
+}
+
 scoped_refptr<ComputedStyle> StyleResolver::StyleForInterpolations(
     Element& element,
     ActiveInterpolationsMap& interpolations) {
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.h b/third_party/blink/renderer/core/css/resolver/style_resolver.h
index a956a53c..486269d 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver.h
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver.h
@@ -139,6 +139,10 @@
 
   static bool CanReuseBaseComputedStyle(const StyleResolverState& state);
 
+  static const CSSValue* ComputeValue(Element* element,
+                                      const CSSPropertyName&,
+                                      const CSSValue&);
+
   scoped_refptr<ComputedStyle> StyleForInterpolations(
       Element& element,
       ActiveInterpolationsMap& animations);
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver_test.cc b/third_party/blink/renderer/core/css/resolver/style_resolver_test.cc
index e5b14eb..8684549 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver_test.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver_test.cc
@@ -9,6 +9,7 @@
 #include "third_party/blink/renderer/core/animation/document_timeline.h"
 #include "third_party/blink/renderer/core/animation/element_animations.h"
 #include "third_party/blink/renderer/core/css/css_image_value.h"
+#include "third_party/blink/renderer/core/css/css_test_helpers.h"
 #include "third_party/blink/renderer/core/css/css_value_list.h"
 #include "third_party/blink/renderer/core/css/properties/computed_style_utils.h"
 #include "third_party/blink/renderer/core/css/properties/css_property_ref.h"
@@ -812,4 +813,55 @@
             fallback_style->VisitedDependentColor(GetCSSPropertyColor()));
 }
 
+TEST_F(StyleResolverTest, ComputeValueStandardProperty) {
+  GetDocument().body()->setInnerHTML(R"HTML(
+    <style>
+      #target { --color: green }
+    </style>
+    <div id="target"></div>
+  )HTML");
+  UpdateAllLifecyclePhasesForTest();
+
+  Element* target = GetDocument().getElementById("target");
+  ASSERT_TRUE(target);
+
+  // Unable to parse a variable reference with css_test_helpers::ParseLonghand.
+  CSSPropertyID property_id = CSSPropertyID::kColor;
+  auto* set =
+      MakeGarbageCollected<MutableCSSPropertyValueSet>(kHTMLStandardMode);
+  MutableCSSPropertyValueSet::SetResult result = set->SetProperty(
+      property_id, "var(--color)", false, SecureContextMode::kInsecureContext,
+      /*style_sheet_contents=*/nullptr);
+  ASSERT_TRUE(result.did_parse);
+  const CSSValue* parsed_value = set->GetPropertyCSSValue(property_id);
+  ASSERT_TRUE(parsed_value);
+  const CSSValue* computed_value = StyleResolver::ComputeValue(
+      target, CSSPropertyName(property_id), *parsed_value);
+  ASSERT_TRUE(computed_value);
+  EXPECT_EQ("rgb(0, 128, 0)", computed_value->CssText());
+}
+
+TEST_F(StyleResolverTest, ComputeValueCustomProperty) {
+  GetDocument().body()->setInnerHTML(R"HTML(
+    <style>
+      #target { --color: green }
+    </style>
+    <div id="target"></div>
+  )HTML");
+  UpdateAllLifecyclePhasesForTest();
+
+  Element* target = GetDocument().getElementById("target");
+  ASSERT_TRUE(target);
+
+  AtomicString custom_property_name = "--color";
+  const CSSValue* parsed_value = css_test_helpers::ParseLonghand(
+      GetDocument(), CustomProperty(custom_property_name, GetDocument()),
+      "blue");
+  ASSERT_TRUE(parsed_value);
+  const CSSValue* computed_value = StyleResolver::ComputeValue(
+      target, CSSPropertyName(custom_property_name), *parsed_value);
+  ASSERT_TRUE(computed_value);
+  EXPECT_EQ("blue", computed_value->CssText());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/dom/document_or_shadow_root.idl b/third_party/blink/renderer/core/dom/document_or_shadow_root.idl
index 2e74a68..a642d35f 100644
--- a/third_party/blink/renderer/core/dom/document_or_shadow_root.idl
+++ b/third_party/blink/renderer/core/dom/document_or_shadow_root.idl
@@ -15,8 +15,8 @@
     [RuntimeEnabled=WebAnimationsAPI, Measure] sequence<Animation> getAnimations();
     // CSSOM View Module
     // https://drafts.csswg.org/cssom-view/#extensions-to-the-document-interface
-    Element? elementFromPoint(double x, double y);
-    sequence<Element> elementsFromPoint(double x, double y);
+    [Measure] Element? elementFromPoint(double x, double y);
+    [Measure] sequence<Element> elementsFromPoint(double x, double y);
     [Affects=Nothing] readonly attribute Element? activeElement;
     [SameObject] readonly attribute StyleSheetList styleSheets;
     // PointerLock API
diff --git a/third_party/blink/renderer/core/execution_context/execution_context.h b/third_party/blink/renderer/core/execution_context/execution_context.h
index d755fae..da9bb4a0 100644
--- a/third_party/blink/renderer/core/execution_context/execution_context.h
+++ b/third_party/blink/renderer/core/execution_context/execution_context.h
@@ -87,6 +87,7 @@
 class ResourceFetcher;
 class SecurityOrigin;
 class ScriptState;
+class ScriptWrappable;
 class TrustedTypePolicyFactory;
 
 enum class TaskType : unsigned char;
@@ -394,6 +395,14 @@
     return base::nullopt;
   }
 
+  // ExecutionContext subclasses are usually the V8 global object, which means
+  // they are also a ScriptWrappable. This casts the ExecutionContext to a
+  // ScriptWrappable if possible.
+  virtual ScriptWrappable* ToScriptWrappable() {
+    NOTREACHED();
+    return nullptr;
+  }
+
  protected:
   explicit ExecutionContext(v8::Isolate* isolate, Agent*);
   ExecutionContext(const ExecutionContext&) = delete;
diff --git a/third_party/blink/renderer/core/exported/web_page_popup_impl.cc b/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
index 552747361..18fa914 100644
--- a/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
@@ -643,6 +643,9 @@
       suppress_next_keypress_event_ = true;
     }
   }
+  LocalFrame::NotifyUserActivation(
+      popup_client_->OwnerElement().GetDocument().GetFrame(),
+      mojom::blink::UserActivationNotificationType::kInteraction);
   return MainFrame().GetEventHandler().KeyEvent(event);
 }
 
@@ -689,6 +692,9 @@
       Cancel();
       return WebInputEventResult::kNotHandled;
     }
+    LocalFrame::NotifyUserActivation(
+        popup_client_->OwnerElement().GetDocument().GetFrame(),
+        mojom::blink::UserActivationNotificationType::kInteraction);
     CheckScreenPointInOwnerWindowAndCount(
         event.PositionInScreen(),
         WebFeature::kPopupGestureTapExceedsOwnerWindowBounds);
@@ -702,6 +708,9 @@
                                        const WebMouseEvent& event) {
   if (IsViewportPointInWindow(event.PositionInWidget().x(),
                               event.PositionInWidget().y())) {
+    LocalFrame::NotifyUserActivation(
+        popup_client_->OwnerElement().GetDocument().GetFrame(),
+        mojom::blink::UserActivationNotificationType::kInteraction);
     CheckScreenPointInOwnerWindowAndCount(
         event.PositionInScreen(),
         WebFeature::kPopupMouseDownExceedsOwnerWindowBounds);
diff --git a/third_party/blink/renderer/core/frame/local_dom_window.h b/third_party/blink/renderer/core/frame/local_dom_window.h
index c206ca9..3f18d75 100644
--- a/third_party/blink/renderer/core/frame/local_dom_window.h
+++ b/third_party/blink/renderer/core/frame/local_dom_window.h
@@ -163,6 +163,7 @@
   TrustedTypePolicyFactory* GetTrustedTypes() const final {
     return trustedTypes();
   }
+  ScriptWrappable* ToScriptWrappable() final { return this; }
   void CountPotentialFeaturePolicyViolation(
       mojom::blink::FeaturePolicyFeature) const final;
   void ReportFeaturePolicyViolation(
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 146f68f..dc779bb 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -46,7 +46,6 @@
 #include "third_party/blink/public/platform/task_type.h"
 #include "third_party/blink/public/platform/web_rect.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_scroll_into_view_options.h"
-#include "third_party/blink/renderer/core/accessibility/apply_dark_mode.h"
 #include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
 #include "third_party/blink/renderer/core/animation/document_animations.h"
 #include "third_party/blink/renderer/core/css/font_face_set_document.h"
@@ -2936,8 +2935,9 @@
     } else {
       GraphicsContext graphics_context(*paint_controller_);
       if (Settings* settings = frame_->GetSettings()) {
-        graphics_context.SetDarkMode(BuildDarkModeSettings(
-            *settings, GetLayoutView()->StyleRef().DarkColorScheme()));
+        graphics_context.SetDarkModeEnabled(
+            settings->GetForceDarkModeEnabled() &&
+            !GetLayoutView()->StyleRef().DarkColorScheme());
       }
 
       bool painted_full_screen_overlay = false;
diff --git a/third_party/blink/renderer/core/frame/settings.cc b/third_party/blink/renderer/core/frame/settings.cc
index ba1c1ab..a362ca7 100644
--- a/third_party/blink/renderer/core/frame/settings.cc
+++ b/third_party/blink/renderer/core/frame/settings.cc
@@ -32,7 +32,6 @@
 #include "base/memory/ptr_util.h"
 #include "build/build_config.h"
 #include "third_party/blink/public/common/features.h"
-#include "third_party/blink/renderer/platform/graphics/dark_mode_settings.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/frame/settings.h b/third_party/blink/renderer/core/frame/settings.h
index ae5d8d7..68ce5061 100644
--- a/third_party/blink/renderer/core/frame/settings.h
+++ b/third_party/blink/renderer/core/frame/settings.h
@@ -50,7 +50,6 @@
 #include "third_party/blink/renderer/core/settings_macros.h"
 #include "third_party/blink/renderer/platform/fonts/generic_font_family_settings.h"
 #include "third_party/blink/renderer/platform/geometry/int_size.h"
-#include "third_party/blink/renderer/platform/graphics/dark_mode_settings.h"
 #include "third_party/blink/renderer/platform/timer.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
 #include "ui/base/pointer/pointer_device.h"
diff --git a/third_party/blink/renderer/core/frame/settings.json5 b/third_party/blink/renderer/core/frame/settings.json5
index d39d56af..5d28657 100644
--- a/third_party/blink/renderer/core/frame/settings.json5
+++ b/third_party/blink/renderer/core/frame/settings.json5
@@ -887,47 +887,7 @@
       initial: false,
       invalidate: "ForceDark",
     },
-    {
-      name: "forceDarkModeInversionAlgorithm",
-      initial: "DarkModeInversionAlgorithm::kInvertLightnessLAB",
-      type: "DarkModeInversionAlgorithm",
-      invalidate: "ForceDark",
-    },
-    {
-      name: "forceDarkModeGrayscale",
-      initial: false,
-      invalidate: "ForceDark",
-    },
-    {
-      name: "forceDarkModeContrast",
-      initial: 0,
-      type: "double",
-      invalidate: "ForceDark",
-    },
-    {
-      name: "forceDarkModeImagePolicy",
-      initial: "DarkModeImagePolicy::kFilterNone",
-      type: "DarkModeImagePolicy",
-      invalidate: "ForceDark",
-    },
-    {
-      name: "forceDarkModeTextBrightnessThreshold",
-      initial: "150",
-      type: "int",
-      invalidate: "ForceDark",
-    },
-    {
-      name: "forceDarkModeBackgroundBrightnessThreshold",
-      initial: "205",
-      type: "int",
-      invalidate: "ForceDark",
-    },
-    {
-      name: "forceDarkModeImageGrayscale",
-      initial: "0.0",
-      type: "float",
-      invalidate: "ForceDark",
-    },
+
     {
       name: "navigatorPlatformOverride",
       type: "String",
diff --git a/third_party/blink/renderer/core/html/forms/color_chooser_popup_ui_controller.cc b/third_party/blink/renderer/core/html/forms/color_chooser_popup_ui_controller.cc
index fa31838b..29d67c2 100644
--- a/third_party/blink/renderer/core/html/forms/color_chooser_popup_ui_controller.cc
+++ b/third_party/blink/renderer/core/html/forms/color_chooser_popup_ui_controller.cc
@@ -276,6 +276,9 @@
 }
 
 void ColorChooserPopupUIController::OpenEyeDropper() {
+  if (!LocalFrame::HasTransientUserActivation(frame_))
+    return;
+
   frame_->GetBrowserInterfaceBroker().GetInterface(
       eye_dropper_chooser_.BindNewPipeAndPassReceiver(
           frame_->GetTaskRunner(TaskType::kUserInteraction)));
diff --git a/third_party/blink/renderer/core/html/forms/external_popup_menu.cc b/third_party/blink/renderer/core/html/forms/external_popup_menu.cc
index 7e45644f..0ce9cea8 100644
--- a/third_party/blink/renderer/core/html/forms/external_popup_menu.cc
+++ b/third_party/blink/renderer/core/html/forms/external_popup_menu.cc
@@ -232,9 +232,6 @@
 }
 
 void ExternalPopupMenu::DidCancel() {
-  local_frame_->NotifyUserActivation(
-      mojom::blink::UserActivationNotificationType::kInteraction);
-
   if (owner_element_)
     owner_element_->PopupDidHide();
   Reset();
diff --git a/third_party/blink/renderer/core/layout/layout_box.cc b/third_party/blink/renderer/core/layout/layout_box.cc
index a9c4684..9e29aa37 100644
--- a/third_party/blink/renderer/core/layout/layout_box.cc
+++ b/third_party/blink/renderer/core/layout/layout_box.cc
@@ -3836,10 +3836,13 @@
 bool LayoutBox::ShouldComputeLogicalWidthFromAspectRatio(
     LayoutUnit* out_logical_height) const {
   NOT_DESTROYED();
-  if (!ShouldComputeLogicalWidthFromAspectRatioAndInsets() &&
-      (StyleRef().AspectRatio().IsAuto() ||
-       (!StyleRef().LogicalHeight().IsFixed() &&
-        !StyleRef().LogicalHeight().IsPercentOrCalc()))) {
+  if (StyleRef().AspectRatio().IsAuto())
+    return false;
+
+  if (!HasOverrideLogicalHeight() &&
+      !ShouldComputeLogicalWidthFromAspectRatioAndInsets() &&
+      !StyleRef().LogicalHeight().IsFixed() &&
+      !StyleRef().LogicalHeight().IsPercentOrCalc()) {
     return false;
   }
 
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.cc b/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.cc
index 472c564..19cf8c3 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.cc
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.cc
@@ -195,20 +195,31 @@
   int selection_start_pos =
       ephemeral_range.StartPosition().ComputeOffsetInContainerNode();
   start_text.Ensure16Bit();
-  int word_start = FindWordStartBoundary(
+  int first_word_start = FindWordStartBoundary(
       start_text.Characters16(), start_text.length(), selection_start_pos);
 
   String end_text = end_container->textContent();
   int selection_end_pos =
       ephemeral_range.EndPosition().ComputeOffsetInContainerNode();
   end_text.Ensure16Bit();
-  int word_end = FindWordEndBoundary(end_text.Characters16(), end_text.length(),
-                                     selection_end_pos);
-  if (word_start != selection_start_pos || word_end != selection_end_pos) {
+
+  // If |selection_end_pos| is at the beginning of a new word then don't search
+  // for the word end as it will be the end of the next word, which was not
+  // included in the selection.
+  int last_word_end = selection_end_pos;
+  if (selection_end_pos != FindWordStartBoundary(end_text.Characters16(),
+                                                 end_text.length(),
+                                                 selection_end_pos)) {
+    last_word_end = FindWordEndBoundary(end_text.Characters16(),
+                                        end_text.length(), selection_end_pos);
+  }
+
+  if (first_word_start != selection_start_pos ||
+      last_word_end != selection_end_pos) {
     selection_range_ =
         MakeGarbageCollected<Range>(selection_range_->OwnerDocument(),
-                                    Position(start_container, word_start),
-                                    Position(end_container, word_end));
+                                    Position(start_container, first_word_start),
+                                    Position(end_container, last_word_end));
   }
 }
 
@@ -338,7 +349,11 @@
   // by a block. Example: <div>start of the selection <div> sub block </div>end
   // of the selection</div>.
 
-  String selected_text = PlainText(ephemeral_range);
+  String selected_text = PlainText(ephemeral_range).StripWhiteSpace();
+  if (selected_text.IsEmpty()) {
+    state_ = kFailure;
+    return;
+  }
 
   // If too long should use ranges.
   if (selected_text.length() > kExactTextMaxChars) {
@@ -347,8 +362,7 @@
   }
 
   selector_ = std::make_unique<TextFragmentSelector>(
-      TextFragmentSelector::SelectorType::kExact,
-      selected_text.StripWhiteSpace(), "", "", "");
+      TextFragmentSelector::SelectorType::kExact, selected_text, "", "", "");
 
   // If too short should use exact selector, but should add context.
   if (selected_text.length() < kNoContextMinChars) {
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator_test.cc b/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator_test.cc
index 8c3ec0e..6cd1460 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator_test.cc
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator_test.cc
@@ -52,9 +52,29 @@
 
     EXPECT_TRUE(callback_called);
   }
+
+  void VerifySelectorFailed(Position selected_start, Position selected_end) {
+    GenerateAndVerifySelector(selected_start, selected_end, "");
+  }
 };
 
 // Basic exact selector case.
+TEST_F(TextFragmentSelectorGeneratorTest, EmptySelection) {
+  SimRequest request("https://example.com/test.html", "text/html");
+  LoadURL("https://example.com/test.html");
+  request.Complete(R"HTML(
+    <!DOCTYPE html>
+    <p id='first'>First paragraph</p>
+  )HTML");
+  Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
+  const auto& selected_start = Position(first_paragraph, 5);
+  const auto& selected_end = Position(first_paragraph, 6);
+  ASSERT_EQ(" ", PlainText(EphemeralRange(selected_start, selected_end)));
+
+  VerifySelectorFailed(selected_start, selected_end);
+}
+
+// Basic exact selector case.
 TEST_F(TextFragmentSelectorGeneratorTest, ExactTextSelector) {
   SimRequest request("https://example.com/test.html", "text/html");
   LoadURL("https://example.com/test.html");
@@ -271,7 +291,7 @@
   ASSERT_EQ("not unique snippet of text",
             PlainText(EphemeralRange(selected_start, selected_end)));
 
-  GenerateAndVerifySelector(selected_start, selected_end, "");
+  VerifySelectorFailed(selected_start, selected_end);
 }
 
 // Exact selector with context test. Case when available prefix and suffix for
@@ -295,7 +315,7 @@
   ASSERT_EQ("not unique snippet of text",
             PlainText(EphemeralRange(selected_start, selected_end)));
 
-  GenerateAndVerifySelector(selected_start, selected_end, "");
+  VerifySelectorFailed(selected_start, selected_end);
 }
 
 // Exact selector with context test. Case when no prefix is available.
@@ -544,7 +564,7 @@
 text_text_text_text_text_text_text_text_text_and_last_text",
       PlainText(EphemeralRange(selected_start, selected_end)));
 
-  GenerateAndVerifySelector(selected_start, selected_end, "");
+  VerifySelectorFailed(selected_start, selected_end);
 }
 
 // Selection should be autocompleted to contain full words.
@@ -588,6 +608,27 @@
                             "paragraph%20text%20that%20is%20longer");
 }
 
+// When selection starts at the end of a word, selection shouldn't be
+// autocompleted to contain extra words.
+TEST_F(TextFragmentSelectorGeneratorTest,
+       WordLimit_SelectionStartsAndEndsAtWordLimit) {
+  SimRequest request("https://example.com/test.html", "text/html");
+  LoadURL("https://example.com/test.html");
+  request.Complete(R"HTML(
+    <!DOCTYPE html>
+    <div>Test page</div>
+    <p id='first'>First paragraph text that is longer  than 20 chars</p>
+  )HTML");
+  Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
+  const auto& selected_start = Position(first_paragraph, 5);
+  const auto& selected_end = Position(first_paragraph, 37);
+  ASSERT_EQ(" paragraph text that is longer ",
+            PlainText(EphemeralRange(selected_start, selected_end)));
+
+  GenerateAndVerifySelector(selected_start, selected_end,
+                            "paragraph%20text%20that%20is%20longer");
+}
+
 // Basic test case for |GetNextTextBlock|.
 TEST_F(TextFragmentSelectorGeneratorTest, GetPreviousTextBlock) {
   SimRequest request("https://example.com/test.html", "text/html");
diff --git a/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc b/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
index 872af2d..31f53b8 100644
--- a/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
+++ b/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
@@ -28,7 +28,6 @@
 #include <memory>
 
 #include "cc/layers/picture_layer.h"
-#include "third_party/blink/renderer/core/accessibility/apply_dark_mode.h"
 #include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h"
 #include "third_party/blink/renderer/core/dom/dom_node_ids.h"
 #include "third_party/blink/renderer/core/exported/web_plugin_container_impl.h"
@@ -1635,8 +1634,9 @@
       paint_info.paint_layer->GetLayoutObject().GetFrame());
   context.SetDeviceScaleFactor(device_scale_factor);
   Settings* settings = GetLayoutObject().GetFrame()->GetSettings();
-  context.SetDarkMode(BuildDarkModeSettings(
-      *settings, GetLayoutObject().View()->StyleRef().DarkColorScheme()));
+  context.SetDarkModeEnabled(
+      settings->GetForceDarkModeEnabled() &&
+      !GetLayoutObject().View()->StyleRef().DarkColorScheme());
 
   // As a composited layer may be painted directly, we need to traverse the
   // effect tree starting from the current node all the way up through the
diff --git a/third_party/blink/renderer/core/paint/scoped_svg_paint_state.cc b/third_party/blink/renderer/core/paint/scoped_svg_paint_state.cc
index 3b6d05b..771442e 100644
--- a/third_party/blink/renderer/core/paint/scoped_svg_paint_state.cc
+++ b/third_party/blink/renderer/core/paint/scoped_svg_paint_state.cc
@@ -34,13 +34,12 @@
 
 ScopedSVGPaintState::~ScopedSVGPaintState() {
   if (should_paint_clip_path_as_mask_image_) {
-    ClipPathClipper::PaintClipPathAsMaskImage(GetPaintInfo().context, object_,
-                                              display_item_client_,
-                                              PhysicalOffset());
+    ClipPathClipper::PaintClipPathAsMaskImage(
+        paint_info_.context, object_, display_item_client_, PhysicalOffset());
   }
 }
 
-bool ScopedSVGPaintState::ApplyEffects() {
+void ScopedSVGPaintState::ApplyEffects() {
 #if DCHECK_IS_ON()
   DCHECK(!apply_effects_called_);
   apply_effects_called_ = true;
@@ -57,7 +56,7 @@
     DCHECK(!object_.IsSVGRoot());
     if (properties && properties->ClipPathMask())
       should_paint_clip_path_as_mask_image_ = true;
-    return true;
+    return;
   }
 
   // LayoutSVGRoot and LayoutSVGForeignObject always have a self-painting
@@ -72,8 +71,6 @@
   }
 
   ApplyMaskIfNecessary();
-  // TODO(fs): Change return value to void.
-  return true;
 }
 
 void ScopedSVGPaintState::ApplyPaintPropertyState(
@@ -82,7 +79,6 @@
   // applied as stacking context effect by PaintLayerPainter.
   if (object_.IsSVGRoot())
     return;
-
   auto& paint_controller = paint_info_.context.GetPaintController();
   auto state = paint_controller.CurrentPaintChunkProperties();
   if (const auto* filter = properties.Filter())
diff --git a/third_party/blink/renderer/core/paint/scoped_svg_paint_state.h b/third_party/blink/renderer/core/paint/scoped_svg_paint_state.h
index e23d3b44..fcf1c23 100644
--- a/third_party/blink/renderer/core/paint/scoped_svg_paint_state.h
+++ b/third_party/blink/renderer/core/paint/scoped_svg_paint_state.h
@@ -80,23 +80,19 @@
  public:
   ScopedSVGPaintState(const LayoutObject& object, const PaintInfo& paint_info)
       : ScopedSVGPaintState(object, paint_info, object) {}
-
   ScopedSVGPaintState(const LayoutObject& object,
                       const PaintInfo& paint_info,
                       const DisplayItemClient& display_item_client)
       : object_(object),
         paint_info_(paint_info),
-        display_item_client_(display_item_client) {}
-
+        display_item_client_(display_item_client) {
+    if (paint_info.phase == PaintPhase::kForeground)
+      ApplyEffects();
+  }
   ~ScopedSVGPaintState();
 
-  const PaintInfo& GetPaintInfo() const { return paint_info_; }
-
-  // Return true if these operations aren't necessary or if they are
-  // successfully applied.
-  bool ApplyEffects();
-
  private:
+  void ApplyEffects();
   void ApplyPaintPropertyState(const ObjectPaintProperties&);
   void ApplyMaskIfNecessary();
 
diff --git a/third_party/blink/renderer/core/paint/svg_container_painter.cc b/third_party/blink/renderer/core/paint/svg_container_painter.cc
index 0ec0ecda..a72bc3ca 100644
--- a/third_party/blink/renderer/core/paint/svg_container_painter.cc
+++ b/third_party/blink/renderer/core/paint/svg_container_painter.cc
@@ -81,25 +81,19 @@
 
     ScopedSVGPaintState paint_state(layout_svg_container_,
                                     paint_info_before_filtering);
-    bool continue_rendering = true;
-    if (paint_state.GetPaintInfo().phase == PaintPhase::kForeground)
-      continue_rendering = paint_state.ApplyEffects();
+    // When a filter applies to the container we need to make sure
+    // that it is applied even if nothing is painted.
+    if (paint_info_before_filtering.phase == PaintPhase::kForeground &&
+        properties && HasReferenceFilterEffect(*properties))
+      paint_info_before_filtering.context.GetPaintController().EnsureChunk();
 
-    if (continue_rendering) {
-      // When a filter applies to the container we need to make sure
-      // that it is applied even if nothing is painted.
-      if (paint_state.GetPaintInfo().phase == PaintPhase::kForeground &&
-          properties && HasReferenceFilterEffect(*properties))
-        paint_state.GetPaintInfo().context.GetPaintController().EnsureChunk();
-
-      for (LayoutObject* child = layout_svg_container_.FirstChild(); child;
-           child = child->NextSibling()) {
-        if (auto* foreign_object = DynamicTo<LayoutSVGForeignObject>(*child)) {
-          SVGForeignObjectPainter(*foreign_object)
-              .PaintLayer(paint_state.GetPaintInfo());
-        } else {
-          child->Paint(paint_state.GetPaintInfo());
-        }
+    for (LayoutObject* child = layout_svg_container_.FirstChild(); child;
+         child = child->NextSibling()) {
+      if (auto* foreign_object = DynamicTo<LayoutSVGForeignObject>(*child)) {
+        SVGForeignObjectPainter(*foreign_object)
+            .PaintLayer(paint_info_before_filtering);
+      } else {
+        child->Paint(paint_info_before_filtering);
       }
     }
   }
diff --git a/third_party/blink/renderer/core/paint/svg_foreign_object_painter.cc b/third_party/blink/renderer/core/paint/svg_foreign_object_painter.cc
index c3df514ef0..5d0f5254 100644
--- a/third_party/blink/renderer/core/paint/svg_foreign_object_painter.cc
+++ b/third_party/blink/renderer/core/paint/svg_foreign_object_painter.cc
@@ -51,13 +51,8 @@
 }
 
 void SVGForeignObjectPainter::Paint(const PaintInfo& paint_info) {
+  // ScopedSVGPaintState only applies masks (and clips-within-clips) here.
   ScopedSVGPaintState paint_state(layout_svg_foreign_object_, paint_info);
-  // ScopedSVGPaintState only applies masks (and clips-within-clips)
-  // here and thus does not mutate PaintInfo, so we can use the passed
-  // in PaintInfo below.
-  if (paint_info.phase == PaintPhase::kForeground &&
-      !paint_state.ApplyEffects())
-    return;
 
   PaintTiming& timing = PaintTiming::From(
       layout_svg_foreign_object_.GetElement()->GetDocument().TopDocument());
diff --git a/third_party/blink/renderer/core/paint/svg_image_painter.cc b/third_party/blink/renderer/core/paint/svg_image_painter.cc
index 8c6726a..931ab80 100644
--- a/third_party/blink/renderer/core/paint/svg_image_painter.cc
+++ b/third_party/blink/renderer/core/paint/svg_image_painter.cc
@@ -43,16 +43,12 @@
       paint_info, layout_svg_image_, layout_svg_image_.LocalSVGTransform());
   {
     ScopedSVGPaintState paint_state(layout_svg_image_, paint_info);
-    if (paint_state.ApplyEffects() &&
-        !DrawingRecorder::UseCachedDrawingIfPossible(
-            paint_state.GetPaintInfo().context, layout_svg_image_,
-            paint_state.GetPaintInfo().phase)) {
-      SVGModelObjectPainter::RecordHitTestData(layout_svg_image_,
-                                               paint_state.GetPaintInfo());
-      SVGDrawingRecorder recorder(paint_state.GetPaintInfo().context,
-                                  layout_svg_image_,
-                                  paint_state.GetPaintInfo().phase);
-      PaintForeground(paint_state.GetPaintInfo());
+    if (!DrawingRecorder::UseCachedDrawingIfPossible(
+            paint_info.context, layout_svg_image_, paint_info.phase)) {
+      SVGModelObjectPainter::RecordHitTestData(layout_svg_image_, paint_info);
+      SVGDrawingRecorder recorder(paint_info.context, layout_svg_image_,
+                                  paint_info.phase);
+      PaintForeground(paint_info);
     }
   }
 
diff --git a/third_party/blink/renderer/core/paint/svg_inline_flow_box_painter.cc b/third_party/blink/renderer/core/paint/svg_inline_flow_box_painter.cc
index 3e436a0..46e2f08 100644
--- a/third_party/blink/renderer/core/paint/svg_inline_flow_box_painter.cc
+++ b/third_party/blink/renderer/core/paint/svg_inline_flow_box_painter.cc
@@ -38,13 +38,9 @@
   ScopedSVGPaintState paint_state(*LineLayoutAPIShim::ConstLayoutObjectFrom(
                                       svg_inline_flow_box_.GetLineLayoutItem()),
                                   paint_info, svg_inline_flow_box_);
-  if (!paint_state.ApplyEffects())
-    return;
   for (InlineBox* child = svg_inline_flow_box_.FirstChild(); child;
-       child = child->NextOnLine()) {
-    child->Paint(paint_state.GetPaintInfo(), paint_offset, LayoutUnit(),
-                 LayoutUnit());
-  }
+       child = child->NextOnLine())
+    child->Paint(paint_info, paint_offset, LayoutUnit(), LayoutUnit());
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/svg_root_inline_box_painter.cc b/third_party/blink/renderer/core/paint/svg_root_inline_box_painter.cc
index 7b20442..19ba1fe 100644
--- a/third_party/blink/renderer/core/paint/svg_root_inline_box_painter.cc
+++ b/third_party/blink/renderer/core/paint/svg_root_inline_box_painter.cc
@@ -46,13 +46,9 @@
   }
 
   ScopedSVGPaintState paint_state(layout_object, paint_info);
-  if (!paint_state.ApplyEffects())
-    return;
   for (InlineBox* child = svg_root_inline_box_.FirstChild(); child;
-       child = child->NextOnLine()) {
-    child->Paint(paint_state.GetPaintInfo(), paint_offset, LayoutUnit(),
-                 LayoutUnit());
-  }
+       child = child->NextOnLine())
+    child->Paint(paint_info, paint_offset, LayoutUnit(), LayoutUnit());
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/svg_root_painter.cc b/third_party/blink/renderer/core/paint/svg_root_painter.cc
index 6b90785..88651e11 100644
--- a/third_party/blink/renderer/core/paint/svg_root_painter.cc
+++ b/third_party/blink/renderer/core/paint/svg_root_painter.cc
@@ -51,11 +51,7 @@
     return;
 
   ScopedSVGPaintState paint_state(layout_svg_root_, paint_info);
-  if (paint_state.GetPaintInfo().phase == PaintPhase::kForeground &&
-      !paint_state.ApplyEffects())
-    return;
-
-  BoxPainter(layout_svg_root_).PaintChildren(paint_state.GetPaintInfo());
+  BoxPainter(layout_svg_root_).PaintChildren(paint_info);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/svg_shape_painter.cc b/third_party/blink/renderer/core/paint/svg_shape_painter.cc
index 533ecd27..0fba9887 100644
--- a/third_party/blink/renderer/core/paint/svg_shape_painter.cc
+++ b/third_party/blink/renderer/core/paint/svg_shape_painter.cc
@@ -59,15 +59,11 @@
       paint_info, layout_svg_shape_, layout_svg_shape_.LocalSVGTransform());
   {
     ScopedSVGPaintState paint_state(layout_svg_shape_, paint_info);
-    if (paint_state.ApplyEffects() &&
-        !DrawingRecorder::UseCachedDrawingIfPossible(
-            paint_state.GetPaintInfo().context, layout_svg_shape_,
-            paint_state.GetPaintInfo().phase)) {
-      SVGModelObjectPainter::RecordHitTestData(layout_svg_shape_,
-                                               paint_state.GetPaintInfo());
-      SVGDrawingRecorder recorder(paint_state.GetPaintInfo().context,
-                                  layout_svg_shape_,
-                                  paint_state.GetPaintInfo().phase);
+    if (!DrawingRecorder::UseCachedDrawingIfPossible(
+            paint_info.context, layout_svg_shape_, paint_info.phase)) {
+      SVGModelObjectPainter::RecordHitTestData(layout_svg_shape_, paint_info);
+      SVGDrawingRecorder recorder(paint_info.context, layout_svg_shape_,
+                                  paint_info.phase);
       const SVGComputedStyle& svg_style =
           layout_svg_shape_.StyleRef().SvgStyle();
 
@@ -79,19 +75,17 @@
           case PT_FILL: {
             PaintFlags fill_flags;
             if (!SVGObjectPainter(layout_svg_shape_)
-                     .PreparePaint(paint_state.GetPaintInfo(),
-                                   layout_svg_shape_.StyleRef(),
+                     .PreparePaint(paint_info, layout_svg_shape_.StyleRef(),
                                    kApplyToFillMode, fill_flags))
               break;
             fill_flags.setAntiAlias(should_anti_alias);
-            FillShape(paint_state.GetPaintInfo().context, fill_flags,
-                      FillRuleFromStyle(paint_state.GetPaintInfo(), svg_style));
+            FillShape(paint_info.context, fill_flags,
+                      FillRuleFromStyle(paint_info, svg_style));
             break;
           }
           case PT_STROKE:
             if (svg_style.HasVisibleStroke()) {
-              GraphicsContextStateSaver state_saver(
-                  paint_state.GetPaintInfo().context, false);
+              GraphicsContextStateSaver state_saver(paint_info.context, false);
               AffineTransform non_scaling_transform;
               const AffineTransform* additional_paint_server_transform =
                   nullptr;
@@ -110,8 +104,7 @@
 
               PaintFlags stroke_flags;
               if (!SVGObjectPainter(layout_svg_shape_)
-                       .PreparePaint(paint_state.GetPaintInfo(),
-                                     layout_svg_shape_.StyleRef(),
+                       .PreparePaint(paint_info, layout_svg_shape_.StyleRef(),
                                      kApplyToStrokeMode, stroke_flags,
                                      additional_paint_server_transform))
                 break;
@@ -123,11 +116,11 @@
                   layout_svg_shape_.DashScaleFactor());
               stroke_data.SetupPaint(&stroke_flags);
 
-              StrokeShape(paint_state.GetPaintInfo().context, stroke_flags);
+              StrokeShape(paint_info.context, stroke_flags);
             }
             break;
           case PT_MARKERS:
-            PaintMarkers(paint_state.GetPaintInfo());
+            PaintMarkers(paint_info);
             break;
           default:
             NOTREACHED();
diff --git a/third_party/blink/renderer/core/paint/theme_painter_default.cc b/third_party/blink/renderer/core/paint/theme_painter_default.cc
index 5e46e81a..820ab90 100644
--- a/third_party/blink/renderer/core/paint/theme_painter_default.cc
+++ b/third_party/blink/renderer/core/paint/theme_painter_default.cc
@@ -231,8 +231,7 @@
   // incorrectly (e.g. https://crbug.com/937872).
   // TODO(gilmanmh): Implement a more permanent solution that allows use of
   // native dark themes.
-  if (paint_info.context.dark_mode_settings().mode !=
-      DarkModeInversionAlgorithm::kOff)
+  if (paint_info.context.IsDarkModeEnabled())
     return true;
 
   ControlPart part = style.EffectiveAppearance();
diff --git a/third_party/blink/renderer/core/workers/worker_global_scope.h b/third_party/blink/renderer/core/workers/worker_global_scope.h
index e9ba425..033762b8 100644
--- a/third_party/blink/renderer/core/workers/worker_global_scope.h
+++ b/third_party/blink/renderer/core/workers/worker_global_scope.h
@@ -120,6 +120,7 @@
   HttpsState GetHttpsState() const override { return https_state_; }
   scheduler::WorkerScheduler* GetScheduler() final;
   ukm::UkmRecorder* UkmRecorder() final;
+  ScriptWrappable* ToScriptWrappable() final { return this; }
 
   void AddConsoleMessageImpl(ConsoleMessage*, bool discard_duplicates) final;
   BrowserInterfaceBrokerProxy& GetBrowserInterfaceBroker() final;
diff --git a/third_party/blink/renderer/modules/accessibility/ax_position.cc b/third_party/blink/renderer/modules/accessibility/ax_position.cc
index 33f82d3..d346db9 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_position.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_position.cc
@@ -768,7 +768,7 @@
         return CreateNextPosition().AsValidDOMPosition(adjustment_behavior);
       case AXPositionAdjustmentBehavior::kMoveLeft:
         const AXPosition result = CreatePreviousPosition();
-        if (result != *this)
+        if (result && result != *this)
           return result.AsValidDOMPosition(adjustment_behavior);
         return {};
     }
diff --git a/third_party/blink/renderer/modules/cache_storage/cache.cc b/third_party/blink/renderer/modules/cache_storage/cache.cc
index 3d42240..3fc19537 100644
--- a/third_party/blink/renderer/modules/cache_storage/cache.cc
+++ b/third_party/blink/renderer/modules/cache_storage/cache.cc
@@ -205,13 +205,13 @@
   void CompletedResponse(int index,
                          Response* response,
                          scoped_refptr<BlobDataHandle> blob) {
+    if (stopped_)
+      return;
+
     DCHECK(!response_list_[index]);
     DCHECK(!blob_list_[index]);
     DCHECK_LT(num_complete_, request_list_.size());
 
-    if (stopped_)
-      return;
-
     response_list_[index] = response;
     blob_list_[index] = std::move(blob);
     num_complete_ += 1;
@@ -223,6 +223,7 @@
       cache_->PutImpl(resolver_, method_name_, request_list_, response_list_,
                       blob_list_, exception_state, trace_id_);
       blob_list_.clear();
+      stopped_ = true;
     }
   }
 
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
index 30ecb08..ffbbcba 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
@@ -38,6 +38,8 @@
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/privacy_budget/identifiability_metric_builder.h"
 #include "third_party/blink/public/common/privacy_budget/identifiability_metrics.h"
+#include "third_party/blink/public/common/privacy_budget/identifiability_study_settings.h"
+#include "third_party/blink/public/common/privacy_budget/identifiable_surface.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/task_type.h"
 #include "third_party/blink/renderer/bindings/modules/v8/rendering_context.h"
@@ -680,12 +682,13 @@
     int sw,
     int sh,
     ExceptionState& exception_state) {
-  blink::IdentifiabilityMetricBuilder(ukm_source_id_)
-      .Set(blink::IdentifiableSurface::FromTypeAndToken(
-               blink::IdentifiableSurface::Type::kCanvasReadback,
-               GetContextType()),
-           0)
-      .Record(ukm_recorder_);
+  const IdentifiableSurface surface = IdentifiableSurface::FromTypeAndToken(
+      IdentifiableSurface::Type::kCanvasReadback, GetContextType());
+  if (IdentifiabilityStudySettings::Get()->IsSurfaceAllowed(surface)) {
+    blink::IdentifiabilityMetricBuilder(ukm_source_id_)
+        .Set(surface, 0)
+        .Record(ukm_recorder_);
+  }
   return BaseRenderingContext2D::getImageData(sx, sy, sw, sh, exception_state);
 }
 
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
index ed0dd0e..98aadee5 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
@@ -3282,7 +3282,10 @@
 namespace {
 
 // WebGL parameters which can be used to identify users.
+// These parameters should each be uniquely defined,
+// see third_party/khronos/GLES2/gl2.h for their definitions.
 static const GLenum kIdentifiableGLParams[] = {
+    // getParameter()
     GL_ALIASED_LINE_WIDTH_RANGE,          // GetWebGLFloatArrayParameter
     GL_ALIASED_POINT_SIZE_RANGE,          // GetWebGLFloatArrayParameter
     GL_ALPHA_BITS,                        // GetIntParameter
@@ -3307,6 +3310,15 @@
     GL_VERSION,
     WebGLDebugRendererInfo::kUnmaskedRendererWebgl,
     WebGLDebugRendererInfo::kUnmaskedVendorWebgl,
+
+    // getRenderBufferParameter()
+    GL_RENDERBUFFER_GREEN_SIZE,
+    GL_RENDERBUFFER_BLUE_SIZE,
+    GL_RENDERBUFFER_RED_SIZE,
+    GL_RENDERBUFFER_ALPHA_SIZE,
+    GL_RENDERBUFFER_DEPTH_SIZE,
+    GL_RENDERBUFFER_STENCIL_SIZE,
+    GL_RENDERBUFFER_SAMPLES,
 };
 
 bool ShouldMeasureGLParam(GLenum pname) {
@@ -3783,10 +3795,9 @@
     case GL_RENDERBUFFER_BLUE_SIZE:
     case GL_RENDERBUFFER_ALPHA_SIZE:
     case GL_RENDERBUFFER_DEPTH_SIZE:
-      ContextGL()->GetRenderbufferParameteriv(target, pname, &value);
-      return WebGLAny(script_state, value);
     case GL_RENDERBUFFER_STENCIL_SIZE:
       ContextGL()->GetRenderbufferParameteriv(target, pname, &value);
+      RecordIdentifiableGLParameterDigest(pname, value);
       return WebGLAny(script_state, value);
     case GL_RENDERBUFFER_INTERNAL_FORMAT:
       return WebGLAny(script_state, renderbuffer_binding_->InternalFormat());
diff --git a/third_party/blink/renderer/modules/webgpu/gpu.cc b/third_party/blink/renderer/modules/webgpu/gpu.cc
index ac1d023..68ca377 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu.cc
@@ -7,6 +7,9 @@
 #include <utility>
 
 #include "gpu/command_buffer/client/webgpu_interface.h"
+#include "third_party/blink/public/common/privacy_budget/identifiability_metric_builder.h"
+#include "third_party/blink/public/common/privacy_budget/identifiability_study_settings.h"
+#include "third_party/blink/public/common/privacy_budget/identifiable_token_builder.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/web_graphics_context_3d_provider.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
@@ -17,6 +20,7 @@
 #include "third_party/blink/renderer/modules/webgpu/gpu_adapter.h"
 #include "third_party/blink/renderer/platform/graphics/gpu/dawn_control_client_holder.h"
 #include "third_party/blink/renderer/platform/heap/heap.h"
+#include "third_party/blink/renderer/platform/privacy_budget/identifiability_digest_helpers.h"
 #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
@@ -98,7 +102,9 @@
   dawn_control_client_->Destroy();
 }
 
-void GPU::OnRequestAdapterCallback(ScriptPromiseResolver* resolver,
+void GPU::OnRequestAdapterCallback(ScriptState* script_state,
+                                   const GPURequestAdapterOptions* options,
+                                   ScriptPromiseResolver* resolver,
                                    int32_t adapter_server_id,
                                    const WGPUDeviceProperties& properties) {
   GPUAdapter* adapter = nullptr;
@@ -106,9 +112,43 @@
     adapter = MakeGarbageCollected<GPUAdapter>(
         "Default", adapter_server_id, properties, dawn_control_client_);
   }
+  RecordAdapterForIdentifiability(script_state, options, adapter);
   resolver->Resolve(adapter);
 }
 
+void GPU::RecordAdapterForIdentifiability(
+    ScriptState* script_state,
+    const GPURequestAdapterOptions* options,
+    GPUAdapter* adapter) const {
+  constexpr IdentifiableSurface::Type type =
+      IdentifiableSurface::Type::kGPU_RequestAdapter;
+  if (!IdentifiabilityStudySettings::Get()->IsTypeAllowed(type))
+    return;
+  ExecutionContext* context = GetExecutionContext();
+  if (!context)
+    return;
+
+  IdentifiableTokenBuilder input_builder;
+  if (options && options->hasPowerPreference()) {
+    input_builder.AddToken(
+        IdentifiabilityBenignStringToken(options->powerPreference()));
+  }
+  const auto surface =
+      IdentifiableSurface::FromTypeAndToken(type, input_builder.GetToken());
+
+  IdentifiableTokenBuilder output_builder;
+  if (adapter) {
+    output_builder.AddToken(IdentifiabilityBenignStringToken(adapter->name()));
+    for (const auto& extension : adapter->extensions(script_state)) {
+      output_builder.AddToken(IdentifiabilityBenignStringToken(extension));
+    }
+  }
+
+  IdentifiabilityMetricBuilder(context->UkmSourceID())
+      .Set(surface, output_builder.GetToken())
+      .Record(context->UkmRecorder());
+}
+
 ScriptPromise GPU::requestAdapter(ScriptState* script_state,
                                   const GPURequestAdapterOptions* options) {
   auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
@@ -148,6 +188,7 @@
   if (!dawn_control_client_->GetInterface()->RequestAdapterAsync(
           power_preference,
           WTF::Bind(&GPU::OnRequestAdapterCallback, WrapPersistent(this),
+                    WrapPersistent(script_state), WrapPersistent(options),
                     WrapPersistent(resolver)))) {
     resolver->Reject(MakeGarbageCollected<DOMException>(
         DOMExceptionCode::kOperationError, "Fail to request GPUAdapter"));
diff --git a/third_party/blink/renderer/modules/webgpu/gpu.h b/third_party/blink/renderer/modules/webgpu/gpu.h
index 9e71904..d7d7581c9 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu.h
@@ -15,6 +15,7 @@
 
 namespace blink {
 
+class GPUAdapter;
 class GPURequestAdapterOptions;
 class ScriptPromiseResolver;
 class ScriptState;
@@ -40,10 +41,16 @@
                                const GPURequestAdapterOptions* options);
 
  private:
-  void OnRequestAdapterCallback(ScriptPromiseResolver* resolver,
+  void OnRequestAdapterCallback(ScriptState* script_state,
+                                const GPURequestAdapterOptions* options,
+                                ScriptPromiseResolver* resolver,
                                 int32_t adapter_server_id,
                                 const WGPUDeviceProperties& properties);
 
+  void RecordAdapterForIdentifiability(ScriptState* script_state,
+                                       const GPURequestAdapterOptions* options,
+                                       GPUAdapter* adapter) const;
+
   scoped_refptr<DawnControlClientHolder> dawn_control_client_;
 
   DISALLOW_COPY_AND_ASSIGN(GPU);
diff --git a/third_party/blink/renderer/modules/webtransport/bidirectional_stream.cc b/third_party/blink/renderer/modules/webtransport/bidirectional_stream.cc
index fa602811..e716d36 100644
--- a/third_party/blink/renderer/modules/webtransport/bidirectional_stream.cc
+++ b/third_party/blink/renderer/modules/webtransport/bidirectional_stream.cc
@@ -39,11 +39,12 @@
 
 void BidirectionalStream::OnIncomingStreamClosed(bool fin_received) {
   incoming_stream_->OnIncomingStreamClosed(fin_received);
-  // TODO(ricea): Review this behaviour when adding detail to the specification.
-  if (!sent_fin_) {
-    ScriptState::Scope scope(outgoing_stream_->GetScriptState());
-    outgoing_stream_->Reset();
+  if (outgoing_stream_->GetState() == OutgoingStream::State::kSentFin) {
+    return;
   }
+
+  ScriptState::Scope scope(outgoing_stream_->GetScriptState());
+  outgoing_stream_->Reset();
 }
 
 void BidirectionalStream::Reset() {
@@ -59,15 +60,15 @@
 
 void BidirectionalStream::SendFin() {
   quic_transport_->SendFin(stream_id_);
-  sent_fin_ = true;
   // The IncomingStream will be closed on the network service side.
 }
 
 void BidirectionalStream::OnOutgoingStreamAbort() {
-  DCHECK(!sent_fin_);
   quic_transport_->AbortStream(stream_id_);
   quic_transport_->ForgetStream(stream_id_);
-  incoming_stream_->Reset();
+  if (incoming_stream_->GetState() == IncomingStream::State::kOpen) {
+    incoming_stream_->Reset();
+  }
 }
 
 void BidirectionalStream::Trace(Visitor* visitor) const {
@@ -81,6 +82,9 @@
 
 void BidirectionalStream::OnIncomingStreamAbort() {
   quic_transport_->ForgetStream(stream_id_);
+  if (outgoing_stream_->GetState() == OutgoingStream::State::kAborted) {
+    return;
+  }
   ScriptState::Scope scope(outgoing_stream_->GetScriptState());
   outgoing_stream_->Reset();
 }
diff --git a/third_party/blink/renderer/modules/webtransport/bidirectional_stream.h b/third_party/blink/renderer/modules/webtransport/bidirectional_stream.h
index 1fcef34e..1e1663a 100644
--- a/third_party/blink/renderer/modules/webtransport/bidirectional_stream.h
+++ b/third_party/blink/renderer/modules/webtransport/bidirectional_stream.h
@@ -77,7 +77,6 @@
   const Member<IncomingStream> incoming_stream_;
   const Member<QuicTransport> quic_transport_;
   const uint32_t stream_id_;
-  bool sent_fin_ = false;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webtransport/bidirectional_stream_test.cc b/third_party/blink/renderer/modules/webtransport/bidirectional_stream_test.cc
index 088b21cb..744fd9a 100644
--- a/third_party/blink/renderer/modules/webtransport/bidirectional_stream_test.cc
+++ b/third_party/blink/renderer/modules/webtransport/bidirectional_stream_test.cc
@@ -22,6 +22,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_iterator_result_value.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_uint8_array.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_bidirectional_stream.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_quic_transport_options.h"
@@ -380,6 +381,25 @@
       kDefaultStreamId, true);
   scoped_quic_transport.Stub()->InputProducer().reset();
 
+  auto* script_state = scope.GetScriptState();
+  auto* reader = bidirectional_stream->readable()->getReader(
+      script_state, ASSERT_NO_EXCEPTION);
+
+  ScriptPromise read_promise = reader->read(script_state, ASSERT_NO_EXCEPTION);
+
+  ScriptPromiseTester read_tester(script_state, read_promise);
+  read_tester.WaitUntilSettled();
+  EXPECT_TRUE(read_tester.IsFulfilled());
+
+  v8::Local<v8::Value> result = read_tester.Value().V8Value();
+  DCHECK(result->IsObject());
+  v8::Local<v8::Value> v8value;
+  bool done = false;
+  EXPECT_TRUE(
+      V8UnpackIteratorResult(script_state, result.As<v8::Object>(), &done)
+          .ToLocal(&v8value));
+  EXPECT_TRUE(done);
+
   ScriptPromiseTester tester(scope.GetScriptState(),
                              bidirectional_stream->writingAborted());
   tester.WaitUntilSettled();
diff --git a/third_party/blink/renderer/modules/webtransport/incoming_stream.cc b/third_party/blink/renderer/modules/webtransport/incoming_stream.cc
index 4bc9591..997fa91 100644
--- a/third_party/blink/renderer/modules/webtransport/incoming_stream.cc
+++ b/third_party/blink/renderer/modules/webtransport/incoming_stream.cc
@@ -105,6 +105,9 @@
   DVLOG(1) << "IncomingStream::OnIncomingStreamClosed(" << fin_received
            << ") this=" << this;
 
+  DCHECK_NE(state_, State::kClosed);
+  state_ = State::kClosed;
+
   DCHECK(!fin_received_.has_value());
 
   fin_received_ = fin_received;
@@ -314,6 +317,8 @@
     reading_aborted_resolver_ = nullptr;
   }
 
+  state_ = State::kAborted;
+
   if (on_abort_) {
     // Cause QuicTransport to drop its reference to us.
     std::move(on_abort_).Run();
diff --git a/third_party/blink/renderer/modules/webtransport/incoming_stream.h b/third_party/blink/renderer/modules/webtransport/incoming_stream.h
index dd529b7..b83a684 100644
--- a/third_party/blink/renderer/modules/webtransport/incoming_stream.h
+++ b/third_party/blink/renderer/modules/webtransport/incoming_stream.h
@@ -35,6 +35,12 @@
   USING_PRE_FINALIZER(IncomingStream, Dispose);
 
  public:
+  enum class State {
+    kOpen,
+    kAborted,
+    kClosed,
+  };
+
   IncomingStream(ScriptState*,
                  base::OnceClosure on_abort,
                  mojo::ScopedDataPipeConsumerHandle);
@@ -68,6 +74,8 @@
   // Does not execute JavaScript.
   void ContextDestroyed();
 
+  State GetState() const { return state_; }
+
   void Trace(Visitor*) const;
 
  private:
@@ -136,6 +144,8 @@
   ScriptPromise reading_aborted_;
   Member<ScriptPromiseResolver> reading_aborted_resolver_;
 
+  State state_ = State::kOpen;
+
   // This is set when OnIncomingStreamClosed() is called.
   base::Optional<bool> fin_received_;
 
diff --git a/third_party/blink/renderer/modules/webtransport/outgoing_stream.cc b/third_party/blink/renderer/modules/webtransport/outgoing_stream.cc
index 61bba5dc1..63548a2 100644
--- a/third_party/blink/renderer/modules/webtransport/outgoing_stream.cc
+++ b/third_party/blink/renderer/modules/webtransport/outgoing_stream.cc
@@ -66,6 +66,7 @@
     DCHECK(!outgoing_stream_->write_promise_resolver_);
 
     if (outgoing_stream_->client_) {
+      outgoing_stream_->state_ = State::kSentFin;
       outgoing_stream_->client_->SendFin();
       outgoing_stream_->client_ = nullptr;
     }
@@ -389,6 +390,8 @@
   }
 
   if (client_) {
+    DCHECK_EQ(state_, State::kOpen);
+    state_ = State::kAborted;
     client_->OnOutgoingStreamAbort();
     client_ = nullptr;
   }
diff --git a/third_party/blink/renderer/modules/webtransport/outgoing_stream.h b/third_party/blink/renderer/modules/webtransport/outgoing_stream.h
index d24b2d5..73ff45a6 100644
--- a/third_party/blink/renderer/modules/webtransport/outgoing_stream.h
+++ b/third_party/blink/renderer/modules/webtransport/outgoing_stream.h
@@ -52,6 +52,12 @@
     virtual void OnOutgoingStreamAbort() = 0;
   };
 
+  enum class State {
+    kOpen,
+    kSentFin,
+    kAborted,
+  };
+
   OutgoingStream(ScriptState*, Client*, mojo::ScopedDataPipeProducerHandle);
   ~OutgoingStream();
 
@@ -76,6 +82,8 @@
   // scope to be entered.
   void Reset();
 
+  State GetState() const { return state_; }
+
   // Called from QuicTransport rather than using
   // ExecutionContextLifecycleObserver to ensure correct destruction order.
   // Does not execute JavaScript.
@@ -180,6 +188,8 @@
   // If an asynchronous write() on the underlying sink object is pending, this
   // will be non-null.
   Member<ScriptPromiseResolver> write_promise_resolver_;
+
+  State state_ = State::kOpen;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 892a496..6e76f3d 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -886,6 +886,8 @@
     "graphics/dark_mode_image_classifier.h",
     "graphics/dark_mode_lab_color_space.h",
     "graphics/dark_mode_settings.h",
+    "graphics/dark_mode_settings_builder.cc",
+    "graphics/dark_mode_settings_builder.h",
     "graphics/dark_mode_types.h",
     "graphics/darkmode/darkmode_classifier.cc",
     "graphics/darkmode/darkmode_classifier.h",
diff --git a/third_party/blink/renderer/platform/graphics/DEPS b/third_party/blink/renderer/platform/graphics/DEPS
index 08cca2d..3c99a424 100644
--- a/third_party/blink/renderer/platform/graphics/DEPS
+++ b/third_party/blink/renderer/platform/graphics/DEPS
@@ -6,6 +6,9 @@
     "+third_party/blink/renderer/platform/graphics",
 
     # Dependencies.
+    "+base/strings/string_number_conversions.h",
+    "+base/strings/string_split.h",
+    "+base/strings/string_util.h",
     "+base/threading/sequenced_task_runner_handle.h",
     "+base/threading/thread_restrictions.h",
     "+base/barrier_closure.h",
diff --git a/third_party/blink/renderer/platform/graphics/dark_mode_color_filter.cc b/third_party/blink/renderer/platform/graphics/dark_mode_color_filter.cc
index 3ca9c05..9340f50 100644
--- a/third_party/blink/renderer/platform/graphics/dark_mode_color_filter.cc
+++ b/third_party/blink/renderer/platform/graphics/dark_mode_color_filter.cc
@@ -108,9 +108,6 @@
 std::unique_ptr<DarkModeColorFilter> DarkModeColorFilter::FromSettings(
     const DarkModeSettings& settings) {
   switch (settings.mode) {
-    case DarkModeInversionAlgorithm::kOff:
-      return nullptr;
-
     case DarkModeInversionAlgorithm::kSimpleInvertForTesting:
       uint8_t identity[256], invert[256];
       for (int i = 0; i < 256; ++i) {
diff --git a/third_party/blink/renderer/platform/graphics/dark_mode_filter.cc b/third_party/blink/renderer/platform/graphics/dark_mode_filter.cc
index b0f879efd..8d7f374 100644
--- a/third_party/blink/renderer/platform/graphics/dark_mode_filter.cc
+++ b/third_party/blink/renderer/platform/graphics/dark_mode_filter.cc
@@ -21,29 +21,6 @@
 namespace blink {
 namespace {
 
-#if DCHECK_IS_ON()
-
-// Floats that differ by this amount or less are considered to be equal.
-const float kFloatEqualityEpsilon = 0.0001;
-
-bool AreFloatsEqual(float a, float b) {
-  return std::fabs(a - b) <= kFloatEqualityEpsilon;
-}
-
-void VerifySettingsAreUnchanged(const DarkModeSettings& a,
-                                const DarkModeSettings& b) {
-  if (a.mode == DarkModeInversionAlgorithm::kOff)
-    return;
-
-  DCHECK_EQ(a.image_policy, b.image_policy);
-  DCHECK_EQ(a.text_brightness_threshold, b.text_brightness_threshold);
-  DCHECK_EQ(a.grayscale, b.grayscale);
-  DCHECK(AreFloatsEqual(a.contrast, b.contrast));
-  DCHECK(AreFloatsEqual(a.image_grayscale_percent, b.image_grayscale_percent));
-}
-
-#endif  // DCHECK_IS_ON()
-
 const size_t kMaxCacheSize = 1024u;
 const int kMinImageLength = 8;
 const int kMaxImageLength = 100;
@@ -94,24 +71,12 @@
       image_filter_(nullptr),
       inverted_color_cache_(new DarkModeInvertedColorCache()) {
   DarkModeSettings default_settings;
-  default_settings.mode = DarkModeInversionAlgorithm::kOff;
   UpdateSettings(default_settings);
 }
 
 DarkModeFilter::~DarkModeFilter() {}
 
 void DarkModeFilter::UpdateSettings(const DarkModeSettings& new_settings) {
-  // Dark mode can be activated or deactivated on a per-page basis, depending on
-  // whether the original page theme is already dark. However, there is
-  // currently no mechanism to change the other settings after starting Chrome.
-  // As such, if the mode doesn't change, we don't need to do anything.
-  if (settings_.mode == new_settings.mode) {
-#if DCHECK_IS_ON()
-    VerifySettingsAreUnchanged(settings_, new_settings);
-#endif
-    return;
-  }
-
   inverted_color_cache_->Clear();
 
   settings_ = new_settings;
@@ -134,7 +99,7 @@
 }
 
 SkColor DarkModeFilter::InvertColorIfNeeded(SkColor color, ElementRole role) {
-  if (!IsDarkModeActive())
+  if (!color_filter_)
     return color;
 
   if (role_override_.has_value())
@@ -193,7 +158,7 @@
 base::Optional<cc::PaintFlags> DarkModeFilter::ApplyToFlagsIfNeeded(
     const cc::PaintFlags& flags,
     ElementRole role) {
-  if (!IsDarkModeActive())
+  if (!color_filter_)
     return base::nullopt;
 
   if (role_override_.has_value())
@@ -210,14 +175,6 @@
   return base::make_optional<cc::PaintFlags>(std::move(dark_mode_flags));
 }
 
-bool DarkModeFilter::IsDarkModeActive() const {
-  return !!color_filter_;
-}
-
-// We don't check IsDarkModeActive() because the caller is expected to have
-// already done so. This allows the caller to exit earlier if it needs to
-// perform some other logic in between confirming dark mode is active and
-// checking the color classifiers.
 bool DarkModeFilter::ShouldApplyToColor(SkColor color, ElementRole role) {
   switch (role) {
     case ElementRole::kText:
@@ -259,14 +216,14 @@
     GraphicsContext* graphics_context,
     DarkModeFilter::ElementRole role)
     : graphics_context_(graphics_context) {
-  DarkModeFilter& dark_mode_filter = graphics_context->dark_mode_filter_;
-  previous_role_override_ = dark_mode_filter.role_override_;
-  dark_mode_filter.role_override_ = role;
+  previous_role_override_ =
+      graphics_context_->GetDarkModeFilter()->role_override_;
+  graphics_context_->GetDarkModeFilter()->role_override_ = role;
 }
 
 ScopedDarkModeElementRoleOverride::~ScopedDarkModeElementRoleOverride() {
-  DarkModeFilter& dark_mode_filter = graphics_context_->dark_mode_filter_;
-  dark_mode_filter.role_override_ = previous_role_override_;
+  graphics_context_->GetDarkModeFilter()->role_override_ =
+      previous_role_override_;
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/dark_mode_filter.h b/third_party/blink/renderer/platform/graphics/dark_mode_filter.h
index 6e9d550..d2426e7 100644
--- a/third_party/blink/renderer/platform/graphics/dark_mode_filter.h
+++ b/third_party/blink/renderer/platform/graphics/dark_mode_filter.h
@@ -30,8 +30,6 @@
   DarkModeFilter();
   ~DarkModeFilter();
 
-  bool IsDarkModeActive() const;
-
   const DarkModeSettings& settings() const { return settings_; }
   void UpdateSettings(const DarkModeSettings& new_settings);
 
diff --git a/third_party/blink/renderer/platform/graphics/dark_mode_filter_helper.cc b/third_party/blink/renderer/platform/graphics/dark_mode_filter_helper.cc
index bb6e84f..f661ea4 100644
--- a/third_party/blink/renderer/platform/graphics/dark_mode_filter_helper.cc
+++ b/third_party/blink/renderer/platform/graphics/dark_mode_filter_helper.cc
@@ -5,37 +5,49 @@
 #include "third_party/blink/renderer/platform/graphics/dark_mode_filter_helper.h"
 
 #include "base/hash/hash.h"
-#include "third_party/blink/renderer/platform/graphics/dark_mode_filter.h"
 #include "third_party/blink/renderer/platform/graphics/dark_mode_image_cache.h"
+#include "third_party/blink/renderer/platform/graphics/graphics_context.h"
 #include "third_party/blink/renderer/platform/graphics/image.h"
 
 namespace blink {
 
 // static
-void DarkModeFilterHelper::ApplyToImageIfNeeded(
-    DarkModeFilter* dark_mode_filter,
-    Image* image,
-    cc::PaintFlags* flags,
-    const SkRect& src,
-    const SkRect& dst) {
-  DCHECK(dark_mode_filter);
+SkColor DarkModeFilterHelper::ApplyToColorIfNeeded(
+    GraphicsContext* context,
+    SkColor color,
+    DarkModeFilter::ElementRole role) {
+  DCHECK(context);
+  return context->IsDarkModeEnabled()
+             ? context->GetDarkModeFilter()->InvertColorIfNeeded(color, role)
+             : color;
+}
+
+// static
+void DarkModeFilterHelper::ApplyToImageIfNeeded(GraphicsContext* context,
+                                                Image* image,
+                                                cc::PaintFlags* flags,
+                                                const SkRect& src,
+                                                const SkRect& dst) {
+  DCHECK(context && context->GetDarkModeFilter());
   DCHECK(image);
   DCHECK(flags);
 
-  // The Image::AsSkBitmapForCurrentFrame() is expensive due creation of paint
-  // image and bitmap, so ensure IsDarkModeActive() is checked prior to calling
-  // this function. See: https://crbug.com/1094781.
-  DCHECK(dark_mode_filter->IsDarkModeActive());
+  // The Image::AsSkBitmapForCurrentFrame() is expensive due paint image and
+  // bitmap creation, so return if dark mode is not enabled. For details see:
+  // https://crbug.com/1094781.
+  if (!context->IsDarkModeEnabled())
+    return;
 
   SkIRect rounded_src = src.roundOut();
   SkIRect rounded_dst = dst.roundOut();
 
   sk_sp<SkColorFilter> filter;
   DarkModeResult result =
-      dark_mode_filter->AnalyzeShouldApplyToImage(rounded_src, rounded_dst);
+      context->GetDarkModeFilter()->AnalyzeShouldApplyToImage(rounded_src,
+                                                              rounded_dst);
 
   if (result == DarkModeResult::kApplyFilter) {
-    filter = dark_mode_filter->GetImageFilter();
+    filter = context->GetDarkModeFilter()->GetImageFilter();
   } else if (result == DarkModeResult::kNotClassified) {
     DarkModeImageCache* cache = image->GetDarkModeImageCache();
     DCHECK(cache);
@@ -48,7 +60,8 @@
           image->AsSkBitmapForCurrentFrame(kDoNotRespectImageOrientation);
       SkPixmap pixmap;
       bitmap.peekPixels(&pixmap);
-      filter = dark_mode_filter->ApplyToImage(pixmap, rounded_src, rounded_dst);
+      filter = context->GetDarkModeFilter()->ApplyToImage(pixmap, rounded_src,
+                                                          rounded_dst);
 
       // Using blink side dark mode for images, it is hard to implement caching
       // mechanism for partially loaded bitmap image content, as content id for
diff --git a/third_party/blink/renderer/platform/graphics/dark_mode_filter_helper.h b/third_party/blink/renderer/platform/graphics/dark_mode_filter_helper.h
index 8308ad7..68896316 100644
--- a/third_party/blink/renderer/platform/graphics/dark_mode_filter_helper.h
+++ b/third_party/blink/renderer/platform/graphics/dark_mode_filter_helper.h
@@ -5,18 +5,23 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_DARK_MODE_FILTER_HELPER_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_DARK_MODE_FILTER_HELPER_H_
 
+#include "third_party/blink/renderer/platform/graphics/dark_mode_filter.h"
 #include "third_party/blink/renderer/platform/graphics/paint/paint_image.h"
 #include "third_party/blink/renderer/platform/platform_export.h"
 #include "third_party/skia/include/core/SkRect.h"
 
 namespace blink {
 
-class DarkModeFilter;
+class GraphicsContext;
 class Image;
 
 class PLATFORM_EXPORT DarkModeFilterHelper {
  public:
-  static void ApplyToImageIfNeeded(DarkModeFilter* dark_mode_filter,
+  // TODO(prashant.n): Move ElementRole to dark_mode_types.h.
+  static SkColor ApplyToColorIfNeeded(GraphicsContext* context,
+                                      SkColor color,
+                                      DarkModeFilter::ElementRole role);
+  static void ApplyToImageIfNeeded(GraphicsContext* context,
                                    Image* image,
                                    cc::PaintFlags* flags,
                                    const SkRect& src,
diff --git a/third_party/blink/renderer/platform/graphics/dark_mode_filter_test.cc b/third_party/blink/renderer/platform/graphics/dark_mode_filter_test.cc
index d0d77e9d..0e58941 100644
--- a/third_party/blink/renderer/platform/graphics/dark_mode_filter_test.cc
+++ b/third_party/blink/renderer/platform/graphics/dark_mode_filter_test.cc
@@ -13,25 +13,6 @@
 namespace blink {
 namespace {
 
-TEST(DarkModeFilterTest, DoNotApplyFilterWhenDarkModeIsOff) {
-  DarkModeFilter filter;
-
-  DarkModeSettings settings;
-  settings.mode = DarkModeInversionAlgorithm::kOff;
-  filter.UpdateSettings(settings);
-
-  EXPECT_EQ(SK_ColorWHITE,
-            filter.InvertColorIfNeeded(
-                SK_ColorWHITE, DarkModeFilter::ElementRole::kBackground));
-  EXPECT_EQ(SK_ColorBLACK,
-            filter.InvertColorIfNeeded(
-                SK_ColorBLACK, DarkModeFilter::ElementRole::kBackground));
-
-  EXPECT_EQ(base::nullopt,
-            filter.ApplyToFlagsIfNeeded(
-                cc::PaintFlags(), DarkModeFilter::ElementRole::kBackground));
-}
-
 TEST(DarkModeFilterTest, ApplyDarkModeToColorsAndFlags) {
   DarkModeFilter filter;
 
diff --git a/third_party/blink/renderer/platform/graphics/dark_mode_settings.h b/third_party/blink/renderer/platform/graphics/dark_mode_settings.h
index eefe72a..d6b57286 100644
--- a/third_party/blink/renderer/platform/graphics/dark_mode_settings.h
+++ b/third_party/blink/renderer/platform/graphics/dark_mode_settings.h
@@ -8,31 +8,30 @@
 namespace blink {
 
 enum class DarkModeInversionAlgorithm {
-  // Default, drawing is unfiltered.
-  // TODO(https://crbug.com/1002664): This value is deprecated and in the
-  // process of being removed.
-  kOff,
   // For testing only, does a simple 8-bit invert of every RGB pixel component.
   kSimpleInvertForTesting,
   kInvertBrightness,
   kInvertLightness,
   kInvertLightnessLAB,
+
+  kFirst = kSimpleInvertForTesting,  // First enum value.
+  kLast = kInvertLightnessLAB,       // Last enum value.
 };
 
 enum class DarkModeImagePolicy {
-  // Apply dark-mode filter to all images.
-  kFilterAll,
-  // Never apply dark-mode filter to any images.
-  kFilterNone,
-  // Apply dark-mode based on image content.
-  kFilterSmart,
+  kFilterAll,    // Apply dark-mode filter to all images.
+  kFilterNone,   // Never apply dark-mode filter to any images.
+  kFilterSmart,  // Apply dark-mode based on image content.
+
+  kFirst = kFilterAll,   // First enum value.
+  kLast = kFilterSmart,  // Last enum value.
 };
 
-// New variables added to this struct should also be added to
-// BuildDarkModeSettings() in
-//   //src/third_party/blink/renderer/core/accessibility/apply_dark_mode.h
+// New variables added to this struct should be considered in
+// third_party/blink/renderer/platform/graphics/dark_mode_settings_builder.h
 struct DarkModeSettings {
-  DarkModeInversionAlgorithm mode = DarkModeInversionAlgorithm::kOff;
+  DarkModeInversionAlgorithm mode =
+      DarkModeInversionAlgorithm::kInvertLightnessLAB;
   bool grayscale = false;
   float image_grayscale_percent = 0.0;  // Valid range from 0.0 to 1.0
   float contrast = 0.0;                 // Valid range from -1.0 to 1.0
diff --git a/third_party/blink/renderer/platform/graphics/dark_mode_settings_builder.cc b/third_party/blink/renderer/platform/graphics/dark_mode_settings_builder.cc
new file mode 100644
index 0000000..b0ac0a9
--- /dev/null
+++ b/third_party/blink/renderer/platform/graphics/dark_mode_settings_builder.cc
@@ -0,0 +1,169 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/platform/graphics/dark_mode_settings_builder.h"
+
+#include "base/command_line.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/common/forcedark/forcedark_switches.h"
+#include "third_party/blink/renderer/platform/graphics/dark_mode_settings.h"
+
+#include <string>
+
+namespace blink {
+
+namespace {
+
+// Default values for dark mode settings.
+const constexpr DarkModeInversionAlgorithm kDefaultDarkModeInversionAlgorithm =
+    DarkModeInversionAlgorithm::kInvertLightnessLAB;
+const constexpr DarkModeImagePolicy kDefaultDarkModeImagePolicy =
+    DarkModeImagePolicy::kFilterNone;
+const constexpr int kDefaultTextBrightnessThreshold = 150;
+const constexpr int kDefaultBackgroundBrightnessThreshold = 205;
+const constexpr bool kDefaultDarkModeIsGrayscale = false;
+const constexpr float kDefaultDarkModeContrastPercent = 0.0f;
+const constexpr float kDefaultDarkModeImageGrayscalePercent = 0.0f;
+
+typedef std::unordered_map<std::string, std::string> SwitchParams;
+
+SwitchParams ParseDarkModeSettings() {
+  SwitchParams switch_params;
+
+  if (!base::CommandLine::ForCurrentProcess()->HasSwitch("dark-mode-settings"))
+    return switch_params;
+
+  std::vector<std::string> param_values = base::SplitString(
+      base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+          "dark-mode-settings"),
+      ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+
+  for (auto param_value : param_values) {
+    std::vector<std::string> pair = base::SplitString(
+        param_value, "=", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+
+    if (pair.size() == 2)
+      switch_params[base::ToLowerASCII(pair[0])] = base::ToLowerASCII(pair[1]);
+  }
+
+  return switch_params;
+}
+
+template <typename T>
+T GetIntegerSwitchParamValue(const SwitchParams& switch_params,
+                             std::string param,
+                             T default_value) {
+  auto it = switch_params.find(base::ToLowerASCII(param));
+  if (it == switch_params.end())
+    return default_value;
+
+  int result;
+  return base::StringToInt(it->second, &result) ? static_cast<T>(result)
+                                                : default_value;
+}
+
+float GetFloatSwitchParamValue(const SwitchParams& switch_params,
+                               std::string param,
+                               float default_value) {
+  auto it = switch_params.find(base::ToLowerASCII(param));
+  if (it == switch_params.end())
+    return default_value;
+
+  double result;
+  return base::StringToDouble(it->second, &result) ? static_cast<float>(result)
+                                                   : default_value;
+}
+
+DarkModeInversionAlgorithm GetMode(const SwitchParams& switch_params) {
+  switch (features::kForceDarkInversionMethodParam.Get()) {
+    case ForceDarkInversionMethod::kUseBlinkSettings:
+      return GetIntegerSwitchParamValue<DarkModeInversionAlgorithm>(
+          switch_params, "InversionAlgorithm",
+          kDefaultDarkModeInversionAlgorithm);
+    case ForceDarkInversionMethod::kCielabBased:
+      return DarkModeInversionAlgorithm::kInvertLightnessLAB;
+    case ForceDarkInversionMethod::kHslBased:
+      return DarkModeInversionAlgorithm::kInvertLightness;
+    case ForceDarkInversionMethod::kRgbBased:
+      return DarkModeInversionAlgorithm::kInvertBrightness;
+  }
+  NOTREACHED();
+}
+
+DarkModeImagePolicy GetImagePolicy(const SwitchParams& switch_params) {
+  switch (features::kForceDarkImageBehaviorParam.Get()) {
+    case ForceDarkImageBehavior::kUseBlinkSettings:
+      return GetIntegerSwitchParamValue<DarkModeImagePolicy>(
+          switch_params, "ImagePolicy", kDefaultDarkModeImagePolicy);
+    case ForceDarkImageBehavior::kInvertNone:
+      return DarkModeImagePolicy::kFilterNone;
+    case ForceDarkImageBehavior::kInvertSelectively:
+      return DarkModeImagePolicy::kFilterSmart;
+  }
+}
+
+int GetTextBrightnessThreshold(const SwitchParams& switch_params) {
+  const int flag_value = base::GetFieldTrialParamByFeatureAsInt(
+      features::kForceWebContentsDarkMode,
+      features::kForceDarkTextLightnessThresholdParam.name, -1);
+  return flag_value >= 0 ? flag_value
+                         : GetIntegerSwitchParamValue<int>(
+                               switch_params, "TextBrightnessThreshold",
+                               kDefaultTextBrightnessThreshold);
+}
+
+int GetBackgroundBrightnessThreshold(const SwitchParams& switch_params) {
+  const int flag_value = base::GetFieldTrialParamByFeatureAsInt(
+      features::kForceWebContentsDarkMode,
+      features::kForceDarkBackgroundLightnessThresholdParam.name, -1);
+  return flag_value >= 0 ? flag_value
+                         : GetIntegerSwitchParamValue<int>(
+                               switch_params, "BackgroundBrightnessThreshold",
+                               kDefaultBackgroundBrightnessThreshold);
+}
+
+template <typename T>
+T Clamp(T value, T min_value, T max_value) {
+  return std::max(min_value, std::min(value, max_value));
+}
+
+DarkModeSettings BuildDarkModeSettings() {
+  SwitchParams switch_params = ParseDarkModeSettings();
+
+  DarkModeSettings settings;
+  settings.mode = Clamp<DarkModeInversionAlgorithm>(
+      GetMode(switch_params), DarkModeInversionAlgorithm::kFirst,
+      DarkModeInversionAlgorithm::kLast);
+  settings.image_policy = Clamp<DarkModeImagePolicy>(
+      GetImagePolicy(switch_params), DarkModeImagePolicy::kFirst,
+      DarkModeImagePolicy::kLast);
+  settings.text_brightness_threshold =
+      Clamp<int>(GetTextBrightnessThreshold(switch_params), 0, 255);
+  settings.background_brightness_threshold =
+      Clamp<int>(GetBackgroundBrightnessThreshold(switch_params), 0, 255);
+  settings.grayscale = GetIntegerSwitchParamValue<bool>(
+      switch_params, "IsGrayScale", kDefaultDarkModeIsGrayscale);
+  settings.contrast =
+      Clamp<float>(GetFloatSwitchParamValue(switch_params, "ContrastPercent",
+                                            kDefaultDarkModeContrastPercent),
+                   -1.0f, 1.0f);
+  settings.image_grayscale_percent = Clamp<float>(
+      GetFloatSwitchParamValue(switch_params, "ImageGrayScalePercent",
+                               kDefaultDarkModeImageGrayscalePercent),
+      0.0f, 1.0f);
+
+  return settings;
+}
+
+}  // namespace
+
+DarkModeSettings GetCurrentDarkModeSettings() {
+  static DarkModeSettings settings = BuildDarkModeSettings();
+  return settings;
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/dark_mode_settings_builder.h b/third_party/blink/renderer/platform/graphics/dark_mode_settings_builder.h
new file mode 100644
index 0000000..d008b88
--- /dev/null
+++ b/third_party/blink/renderer/platform/graphics/dark_mode_settings_builder.h
@@ -0,0 +1,17 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_DARK_MODE_SETTINGS_BUILDER_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_DARK_MODE_SETTINGS_BUILDER_H_
+
+#include "third_party/blink/renderer/platform/graphics/dark_mode_settings.h"
+#include "third_party/blink/renderer/platform/platform_export.h"
+
+namespace blink {
+
+DarkModeSettings PLATFORM_EXPORT GetCurrentDarkModeSettings();
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_DARK_MODE_SETTINGS_BUILDER_H_
diff --git a/third_party/blink/renderer/platform/graphics/graphics_context.cc b/third_party/blink/renderer/platform/graphics/graphics_context.cc
index 9f6d65edc..09e91221 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_context.cc
+++ b/third_party/blink/renderer/platform/graphics/graphics_context.cc
@@ -36,8 +36,8 @@
 #include "third_party/blink/renderer/platform/geometry/float_rect.h"
 #include "third_party/blink/renderer/platform/geometry/float_rounded_rect.h"
 #include "third_party/blink/renderer/platform/geometry/int_rect.h"
-#include "third_party/blink/renderer/platform/graphics/dark_mode_filter.h"
 #include "third_party/blink/renderer/platform/graphics/dark_mode_filter_helper.h"
+#include "third_party/blink/renderer/platform/graphics/dark_mode_settings_builder.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h"
 #include "third_party/blink/renderer/platform/graphics/interpolation_space.h"
 #include "third_party/blink/renderer/platform/graphics/paint/paint_canvas.h"
@@ -95,13 +95,16 @@
 
  public:
   // This helper's lifetime should never exceed |flags|'.
-  DarkModeFlags(GraphicsContext* gc,
+  DarkModeFlags(GraphicsContext* context,
                 const PaintFlags& flags,
                 DarkModeFilter::ElementRole role) {
-    dark_mode_flags_ = gc->dark_mode_filter_.ApplyToFlagsIfNeeded(flags, role);
-    if (dark_mode_flags_) {
-      flags_ = &dark_mode_flags_.value();
-      return;
+    if (context->IsDarkModeEnabled()) {
+      dark_mode_flags_ =
+          context->GetDarkModeFilter()->ApplyToFlagsIfNeeded(flags, role);
+      if (dark_mode_flags_) {
+        flags_ = &dark_mode_flags_.value();
+        return;
+      }
     }
     flags_ = &flags;
   }
@@ -129,11 +132,15 @@
       device_scale_factor_(1.0f),
       printing_(false),
       is_painting_preview_(false),
-      in_drawing_recorder_(false) {
+      in_drawing_recorder_(false),
+      is_dark_mode_enabled_(false) {
   // FIXME: Do some tests to determine how many states are typically used, and
   // allocate several here.
   paint_state_stack_.push_back(std::make_unique<GraphicsContextState>());
   paint_state_ = paint_state_stack_.back().get();
+
+  dark_mode_filter_ = std::make_unique<DarkModeFilter>();
+  dark_mode_filter_->UpdateSettings(GetCurrentDarkModeSettings());
 }
 
 GraphicsContext::~GraphicsContext() {
@@ -147,6 +154,11 @@
 #endif
 }
 
+void GraphicsContext::UpdateDarkModeSettingsForTest(
+    const DarkModeSettings& settings) {
+  GetDarkModeFilter()->UpdateSettings(settings);
+}
+
 void GraphicsContext::Save() {
   paint_state_->IncrementSaveCount();
 
@@ -184,10 +196,6 @@
 }
 #endif
 
-void GraphicsContext::SetDarkMode(const DarkModeSettings& settings) {
-  dark_mode_filter_.UpdateSettings(settings);
-}
-
 void GraphicsContext::SaveLayer(const SkRect* bounds, const PaintFlags* flags) {
   DCHECK(canvas_);
   canvas_->saveLayer(bounds, flags);
@@ -378,8 +386,8 @@
                                         float border_radius) {
   DrawPlatformFocusRing(
       path, canvas_,
-      dark_mode_filter_.InvertColorIfNeeded(
-          color.Rgb(), DarkModeFilter::ElementRole::kBackground),
+      DarkModeFilterHelper::ApplyToColorIfNeeded(
+          this, color.Rgb(), DarkModeFilter::ElementRole::kBackground),
       width, border_radius);
 }
 
@@ -389,8 +397,8 @@
                                         float border_radius) {
   DrawPlatformFocusRing(
       rect, canvas_,
-      dark_mode_filter_.InvertColorIfNeeded(
-          color.Rgb(), DarkModeFilter::ElementRole::kBackground),
+      DarkModeFilterHelper::ApplyToColorIfNeeded(
+          this, color.Rgb(), DarkModeFilter::ElementRole::kBackground),
       width, border_radius);
 }
 
@@ -501,8 +509,8 @@
                                       float shadow_blur,
                                       float shadow_spread,
                                       Edges clipped_edges) {
-  SkColor shadow_color = dark_mode_filter_.InvertColorIfNeeded(
-      orig_shadow_color.Rgb(), DarkModeFilter::ElementRole::kBackground);
+  SkColor shadow_color = DarkModeFilterHelper::ApplyToColorIfNeeded(
+      this, orig_shadow_color.Rgb(), DarkModeFilter::ElementRole::kBackground);
 
   FloatRect hole_rect(rect.Rect());
   hole_rect.Inflate(-shadow_spread);
@@ -878,9 +886,9 @@
   image_flags.setFilterQuality(ComputeFilterQuality(image, dest, src));
 
   // Do not classify the image if the element has any CSS filters.
-  if (!has_filter_property && dark_mode_filter_.IsDarkModeActive()) {
-    DarkModeFilterHelper::ApplyToImageIfNeeded(&dark_mode_filter_, image,
-                                               &image_flags, src, dest);
+  if (!has_filter_property) {
+    DarkModeFilterHelper::ApplyToImageIfNeeded(this, image, &image_flags, src,
+                                               dest);
   }
 
   image->Draw(canvas_, image_flags, dest, src, should_respect_image_orientation,
@@ -918,10 +926,8 @@
   image_flags.setFilterQuality(
       ComputeFilterQuality(image, dest.Rect(), src_rect));
 
-  if (dark_mode_filter_.IsDarkModeActive()) {
-    DarkModeFilterHelper::ApplyToImageIfNeeded(
-        &dark_mode_filter_, image, &image_flags, src_rect, dest.Rect());
-  }
+  DarkModeFilterHelper::ApplyToImageIfNeeded(this, image, &image_flags,
+                                             src_rect, dest.Rect());
 
   bool use_shader = (visible_src == src_rect) &&
                     (respect_orientation == kDoNotRespectImageOrientation ||
@@ -1126,8 +1132,8 @@
       canvas_->drawDRRect(outer, inner, ImmutableState()->FillFlags());
     } else {
       PaintFlags flags(ImmutableState()->FillFlags());
-      flags.setColor(dark_mode_filter_.InvertColorIfNeeded(
-          color.Rgb(), DarkModeFilter::ElementRole::kBackground));
+      flags.setColor(DarkModeFilterHelper::ApplyToColorIfNeeded(
+          this, color.Rgb(), DarkModeFilter::ElementRole::kBackground));
       canvas_->drawDRRect(outer, inner, flags);
     }
 
@@ -1140,8 +1146,8 @@
   stroke_r_rect.inset(stroke_width / 2, stroke_width / 2);
 
   PaintFlags stroke_flags(ImmutableState()->FillFlags());
-  stroke_flags.setColor(dark_mode_filter_.InvertColorIfNeeded(
-      color.Rgb(), DarkModeFilter::ElementRole::kBackground));
+  stroke_flags.setColor(DarkModeFilterHelper::ApplyToColorIfNeeded(
+      this, color.Rgb(), DarkModeFilter::ElementRole::kBackground));
   stroke_flags.setStyle(PaintFlags::kStroke_Style);
   stroke_flags.setStrokeWidth(stroke_width);
 
@@ -1296,8 +1302,8 @@
     const FloatRoundedRect& rounded_hole_rect,
     const Color& color) {
   PaintFlags flags(ImmutableState()->FillFlags());
-  flags.setColor(dark_mode_filter_.InvertColorIfNeeded(
-      color.Rgb(), DarkModeFilter::ElementRole::kBackground));
+  flags.setColor(DarkModeFilterHelper::ApplyToColorIfNeeded(
+      this, color.Rgb(), DarkModeFilter::ElementRole::kBackground));
   canvas_->drawDRRect(SkRRect::MakeRect(rect), rounded_hole_rect, flags);
 }
 
diff --git a/third_party/blink/renderer/platform/graphics/graphics_context.h b/third_party/blink/renderer/platform/graphics/graphics_context.h
index 0aa64ef..793fd21 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_context.h
+++ b/third_party/blink/renderer/platform/graphics/graphics_context.h
@@ -85,9 +85,12 @@
     return paint_controller_;
   }
 
-  const DarkModeSettings& dark_mode_settings() const {
-    return dark_mode_filter_.settings();
-  }
+  bool IsDarkModeEnabled() const { return is_dark_mode_enabled_; }
+  void SetDarkModeEnabled(bool enabled) { is_dark_mode_enabled_ = enabled; }
+
+  DarkModeFilter* GetDarkModeFilter() { return dark_mode_filter_.get(); }
+
+  void UpdateDarkModeSettingsForTest(const DarkModeSettings&);
 
   // ---------- State management methods -----------------
   void Save();
@@ -97,8 +100,6 @@
   unsigned SaveCount() const;
 #endif
 
-  void SetDarkMode(const DarkModeSettings&);
-
   float StrokeThickness() const {
     return ImmutableState()->GetStrokeData().Thickness();
   }
@@ -545,12 +546,12 @@
 
   float device_scale_factor_;
 
-  // TODO(gilmanmh): Investigate making this base::Optional<DarkModeFilter>
-  DarkModeFilter dark_mode_filter_;
+  std::unique_ptr<DarkModeFilter> dark_mode_filter_;
 
   unsigned printing_ : 1;
   unsigned is_painting_preview_ : 1;
   unsigned in_drawing_recorder_ : 1;
+  unsigned is_dark_mode_enabled_ : 1;
 
   // The current node ID, which is used for marked content in a tagged PDF.
   DOMNodeId dom_node_id_ = kInvalidDOMNodeId;
diff --git a/third_party/blink/renderer/platform/graphics/graphics_context_test.cc b/third_party/blink/renderer/platform/graphics/graphics_context_test.cc
index 5c36463..36b606a 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_context_test.cc
+++ b/third_party/blink/renderer/platform/graphics/graphics_context_test.cc
@@ -152,44 +152,34 @@
     bitmap_.eraseColor(0);
     canvas_ = std::make_unique<SkiaPaintCanvas>(bitmap_);
     paint_controller_ = std::make_unique<PaintController>();
-    context_ = std::make_unique<GraphicsContext>(*paint_controller_);
-    context_->BeginRecording(FloatRect(0, 0, 4, 1));
   }
 
-  void DrawColorsToContext() {
-    context_->FillRect(FloatRect(0, 0, 1, 1), Color(SK_ColorBLACK));
-    context_->FillRect(FloatRect(1, 0, 1, 1), Color(SK_ColorWHITE));
-    context_->FillRect(FloatRect(2, 0, 1, 1), Color(SK_ColorRED));
-    context_->FillRect(FloatRect(3, 0, 1, 1), Color(SK_ColorGRAY));
+  void DrawColorsToContext(bool is_dark_mode_on,
+                           const DarkModeSettings& settings) {
+    GraphicsContext context(*paint_controller_);
+    context.SetDarkModeEnabled(is_dark_mode_on);
+    if (is_dark_mode_on)
+      context.UpdateDarkModeSettingsForTest(settings);
+    context.BeginRecording(FloatRect(0, 0, 4, 1));
+    context.FillRect(FloatRect(0, 0, 1, 1), Color(SK_ColorBLACK));
+    context.FillRect(FloatRect(1, 0, 1, 1), Color(SK_ColorWHITE));
+    context.FillRect(FloatRect(2, 0, 1, 1), Color(SK_ColorRED));
+    context.FillRect(FloatRect(3, 0, 1, 1), Color(SK_ColorGRAY));
     // Capture the result in the bitmap.
-    canvas_->drawPicture(context_->EndRecording());
+    canvas_->drawPicture(context.EndRecording());
   }
 
   SkBitmap bitmap_;
   std::unique_ptr<SkiaPaintCanvas> canvas_;
   std::unique_ptr<PaintController> paint_controller_;
-  std::unique_ptr<GraphicsContext> context_;
 };
 
-// This is just a baseline test, compare against the other variants
-// of the test below, where dark mode is enabled.
-TEST_F(GraphicsContextDarkModeTest, NoDarkMode) {
-  DrawColorsToContext();
-
-  EXPECT_EQ(SK_ColorBLACK, bitmap_.getColor(0, 0));
-  EXPECT_EQ(SK_ColorWHITE, bitmap_.getColor(1, 0));
-  EXPECT_EQ(SK_ColorRED, bitmap_.getColor(2, 0));
-  EXPECT_EQ(SK_ColorGRAY, bitmap_.getColor(3, 0));
-}
-
+// This is a baseline test where dark mode is turned off. Compare other variants
+// of the test where dark mode is enabled.
 TEST_F(GraphicsContextDarkModeTest, DarkModeOff) {
   DarkModeSettings settings;
-  settings.mode = DarkModeInversionAlgorithm::kOff;
-  settings.grayscale = false;
-  settings.contrast = 0;
-  context_->SetDarkMode(settings);
 
-  DrawColorsToContext();
+  DrawColorsToContext(false, settings);
 
   EXPECT_EQ(SK_ColorBLACK, bitmap_.getColor(0, 0));
   EXPECT_EQ(SK_ColorWHITE, bitmap_.getColor(1, 0));
@@ -204,9 +194,8 @@
   settings.mode = DarkModeInversionAlgorithm::kSimpleInvertForTesting;
   settings.grayscale = false;
   settings.contrast = 0;
-  context_->SetDarkMode(settings);
 
-  DrawColorsToContext();
+  DrawColorsToContext(true, settings);
 
   EXPECT_EQ(SK_ColorWHITE, bitmap_.getColor(0, 0));
   EXPECT_EQ(SK_ColorBLACK, bitmap_.getColor(1, 0));
@@ -220,9 +209,8 @@
   settings.mode = DarkModeInversionAlgorithm::kInvertBrightness;
   settings.grayscale = false;
   settings.contrast = 0;
-  context_->SetDarkMode(settings);
 
-  DrawColorsToContext();
+  DrawColorsToContext(true, settings);
 
   EXPECT_EQ(SK_ColorWHITE, bitmap_.getColor(0, 0));
   EXPECT_EQ(SK_ColorBLACK, bitmap_.getColor(1, 0));
@@ -236,9 +224,8 @@
   settings.mode = DarkModeInversionAlgorithm::kInvertLightness;
   settings.grayscale = false;
   settings.contrast = 0;
-  context_->SetDarkMode(settings);
 
-  DrawColorsToContext();
+  DrawColorsToContext(true, settings);
 
   EXPECT_EQ(SK_ColorWHITE, bitmap_.getColor(0, 0));
   EXPECT_EQ(SK_ColorBLACK, bitmap_.getColor(1, 0));
@@ -252,9 +239,8 @@
   settings.mode = DarkModeInversionAlgorithm::kInvertLightness;
   settings.grayscale = true;
   settings.contrast = 0;
-  context_->SetDarkMode(settings);
 
-  DrawColorsToContext();
+  DrawColorsToContext(true, settings);
 
   EXPECT_EQ(SK_ColorWHITE, bitmap_.getColor(0, 0));
   EXPECT_EQ(SK_ColorBLACK, bitmap_.getColor(1, 0));
@@ -267,9 +253,8 @@
   settings.mode = DarkModeInversionAlgorithm::kInvertLightness;
   settings.grayscale = false;
   settings.contrast = 0.2;
-  context_->SetDarkMode(settings);
 
-  DrawColorsToContext();
+  DrawColorsToContext(true, settings);
 
   EXPECT_EQ(SK_ColorWHITE, bitmap_.getColor(0, 0));
   EXPECT_EQ(SK_ColorBLACK, bitmap_.getColor(1, 0));
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 58b6468..d940b74 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
@@ -30,7 +30,7 @@
   context_ =
       std::make_unique<GraphicsContext>(*paint_controller_, metafile, tracker);
   if (containing_context) {
-    context_->SetDarkMode(containing_context->dark_mode_settings());
+    context_->SetDarkModeEnabled(containing_context->IsDarkModeEnabled());
     context_->SetDeviceScaleFactor(containing_context->DeviceScaleFactor());
     context_->SetPrinting(containing_context->Printing());
     context_->SetIsPaintingPreview(containing_context->IsPaintingPreview());
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 437ad78e..114d44f 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -2124,6 +2124,7 @@
     },
     {
       name: "WebXRDepth",
+      origin_trial_feature_name: "WebXRDepth",
       depends_on: ["WebXRARModule"],
       status: "experimental",
     },
diff --git a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
index bc893ba..e086c38 100644
--- a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
+++ b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
@@ -121,8 +121,6 @@
 crbug.com/1045668 external/wpt/css/css-sizing/aspect-ratio/abspos-013.tentative.html [ Pass ]
 crbug.com/1045668 external/wpt/css/css-sizing/aspect-ratio/block-aspect-ratio-024.tentative.html [ Failure ]
 crbug.com/1045668 external/wpt/css/css-sizing/aspect-ratio/block-aspect-ratio-028.tentative.html [ Failure ]
-crbug.com/1045668 external/wpt/css/css-sizing/aspect-ratio/flex-aspect-ratio-009.tentative.html [ Failure ]
-crbug.com/1045668 external/wpt/css/css-sizing/aspect-ratio/flex-aspect-ratio-014.tentative.html [ Failure ]
 crbug.com/1045668 external/wpt/css/css-sizing/aspect-ratio/percentage-resolution-002.tentative.html [ Failure ]
 crbug.com/1045668 external/wpt/css/css-sizing/aspect-ratio/quirks-mode-001.tentative.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-sizing/clone-nowrap-intrinsic-size-bidi.html [ Failure ]
diff --git a/third_party/blink/web_tests/SlowTests b/third_party/blink/web_tests/SlowTests
index 7acd5f01..b00ea4d 100644
--- a/third_party/blink/web_tests/SlowTests
+++ b/third_party/blink/web_tests/SlowTests
@@ -299,7 +299,6 @@
 crbug.com/874695 fast/dnd/dropEffect-for-link.html [ Slow ]
 crbug.com/874695 fast/dom/HTMLLinkElement/link-preload-unused.html [ Slow ]
 crbug.com/874695 fast/dom/timer-throttling-hidden-page.html [ Slow ]
-crbug.com/874695 fast/events/autoscroll-iframe-no-scrolling.html [ Slow ]
 crbug.com/874695 fast/events/frame-detached-in-mousedown.html [ Slow ]
 crbug.com/874695 fast/events/popup-blocking-timers4.html [ Slow ]
 crbug.com/874695 fast/forms/color/* [ Slow ]
@@ -310,6 +309,7 @@
 crbug.com/874695 fast/peerconnection/RTCPeerConnection-many.html [ Slow ]
 crbug.com/874695 fast/peerconnection/RTCRtpSender-setParameters.html [ Slow ]
 crbug.com/874695 fast/scroll-behavior/overscroll-behavior.html [ Slow ]
+crbug.com/874695 fast/scrolling/autoscroll-iframe-no-scrolling.html [ Slow ]
 crbug.com/874695 fast/table/multiple-captions-crash3.html [ Slow ]
 crbug.com/874695 fast/table/multiple-captions-crash4.html [ Slow ]
 crbug.com/874695 fast/webgl/canvas-toDataURL-crash.html [ Slow ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index cae7ec8..ba46407 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -5315,7 +5315,6 @@
 crbug.com/862589 virtual/threaded/fast/idle-callback/long_idle_periods.html [ Timeout Pass ]
 
 # fast/events/middleClickAutoscroll-* are failing on Mac
-crbug.com/874162 [ Mac ] fast/events/autoscroll-iframe-no-scrolling.html [ Skip ]
 crbug.com/874162 [ Mac ] fast/events/middleClickAutoscroll-click-hyperlink.html [ Skip ]
 crbug.com/874162 [ Mac ] fast/events/middleClickAutoscroll-click.html [ Skip ]
 crbug.com/874162 [ Mac ] fast/events/middleClickAutoscroll-drag.html [ Skip ]
@@ -5328,6 +5327,9 @@
 crbug.com/874162 [ Mac ] fast/events/middleClickAutoscroll-nested-divs.html [ Skip ]
 crbug.com/874162 [ Mac ] fast/events/selection-autoscroll-borderbelt.html [ Skip ]
 
+# Passes in threaded mode, fails without it. This is a real bug.
+crbug.com/1112183 fast/scrolling/autoscroll-iframe-no-scrolling.html [ Failure ]
+
 # Sheriff 2018-09-10
 crbug.com/881207 fast/js/regress/splice-to-remove.html [ Timeout Pass ]
 
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 779a674..43d392c 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -269,17 +269,20 @@
   {
     "prefix": "dark-mode-images-filter-all",
     "bases": ["dark-mode/images"],
-    "args": ["--blink-settings=forceDarkModeEnabled=true,forceDarkModeImagePolicy=0"]
+    "args": ["--blink-settings=forceDarkModeEnabled=true",
+             "--dark-mode-settings=ImagePolicy=0"]
   },
   {
     "prefix": "dark-mode-images-filter-none",
     "bases": ["dark-mode/images"],
-    "args": ["--blink-settings=forceDarkModeEnabled=true,forceDarkModeImagePolicy=1,"]
+    "args": ["--blink-settings=forceDarkModeEnabled=true",
+             "--dark-mode-settings=ImagePolicy=1"]
   },
   {
     "prefix": "dark-mode-images-grayscale",
     "bases": ["dark-mode/images"],
-    "args": ["--blink-settings=forceDarkModeEnabled=true,forceDarkModeImagePolicy=0,forceDarkModeImageGrayscale=1.0"]
+    "args": ["--blink-settings=forceDarkModeEnabled=true",
+             "--dark-mode-settings=ImagePolicy=0,ImageGrayScalePercent=1.0"]
   },
   {
     "prefix": "presentation",
diff --git a/third_party/blink/web_tests/W3CImportExpectations b/third_party/blink/web_tests/W3CImportExpectations
index b9b7018..d71f6337 100644
--- a/third_party/blink/web_tests/W3CImportExpectations
+++ b/third_party/blink/web_tests/W3CImportExpectations
@@ -127,11 +127,6 @@
 # (if it's a blink/test tooling issue) or an upstream bug reference
 # (if the test itself is buggy).
 
-# wpt.config.json overrides the following files with LayoutTests/resources
-# files. Having unused files would be confusing.
-external/wpt/resources/testdriver-vendor.js [ Skip ]
-external/wpt/resources/testharnessreport.js [ Skip ]
-
 # crbug.com/490939: The following tests are too large.  They cause time out frequently.
 # [ Slow ] didn't help the following svg-in-*.html tests.
 external/wpt/html/rendering/replaced-elements/svg-embedded-sizing/svg-in-iframe-auto.html [ Skip ]
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index 992d26fe..3f412d7e 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -49447,6 +49447,32 @@
        {}
       ]
      ],
+     "float-stretching-bfc-000.html": [
+      "c56e9a998c999a3f08554ec44f8950e4d13f5ef6",
+      [
+       null,
+       [
+        [
+         "/css/reference/ref-filled-green-100px-square.xht",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "float-stretching-bfc-001.html": [
+      "4c6a9f63ea6614bc8bbc7ba0ba75c4beda6d0f2f",
+      [
+       null,
+       [
+        [
+         "/css/reference/ref-filled-green-100px-square.xht",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "floats-and-text-narrow-and-short-dynamic.html": [
       "09e5021555d50ca0c6919082bfffc81652f65092",
       [
@@ -81547,6 +81573,19 @@
        {}
       ]
      ],
+     "multicol-span-all-013.html": [
+      "38bd05f0b80dbaa809ed8475c6e9c521ac9a1add",
+      [
+       null,
+       [
+        [
+         "/css/reference/ref-filled-green-100px-square.xht",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "multicol-span-all-block-sibling-003.xht": [
       "abaa45f0a85023f3f07a9db483629b74d2b09d71",
       [
@@ -189811,14 +189850,6 @@
        "08064714fb27e51535251a70f54642e94ff82f8a",
        []
       ],
-      "overflow-computed-expected.txt": [
-       "51c28e10e120505ee3f52e83bdffd80a7b366d4c",
-       []
-      ],
-      "overflow-valid-expected.txt": [
-       "9be204b5b5d4b8ca27c794227d1fbeaf06b72f68",
-       []
-      ],
       "webkit-line-clamp-valid-expected.txt": [
        "35c61feb644f43f23842aabc8592db4a166fe5b5",
        []
@@ -192401,7 +192432,7 @@
      "aspect-ratio": {
       "parsing": {
        "contain-intrinsic-size-valid-expected.txt": [
-        "c8550803b6fdce57e3977abb71f3771e32fd465f",
+        "034b6db192f1fbc3b4c4b544602a6672b325f695",
         []
        ]
       },
@@ -198504,10 +198535,6 @@
         "fb9ee73ef5d9006a3376173647ce41c178856b35",
         []
        ],
-       "overflow-expected.txt": [
-        "3a800d9cd628f8fd4d838fcbb527c26432cf4243",
-        []
-       ],
        "overflow-wrap-expected.txt": [
         "f49bd673eca458403ff44c667f900bda8db2642a",
         []
@@ -227562,7 +227589,7 @@
      []
     ],
     "webauthn.idl": [
-     "4af14a861f9ccf620ad4d9033ae09419e0954dbf",
+     "9da49220c40a85e7bb33932ca9832f067fd9e5d9",
      []
     ],
     "webdriver.idl": [
@@ -233282,7 +233309,7 @@
      ],
      "inheritance": {
       "iframe-inheritance-about-blank-expected.txt": [
-       "a71d38db76e9aa94fcd7311d6d9770ba2b497258",
+       "66681371a832f0b031884e080fa93d6d1b417de1",
        []
       ],
       "iframe-inheritance-javascript-child-expected.txt": [
@@ -248292,7 +248319,7 @@
    },
    "webhid": {
     "idlharness.https.window-expected.txt": [
-     "dd981326d8c2c76391b89ed0bf5b5e62692e99fb",
+     "0fe24102d08e6f78bcbb7aa56915e1bb6143e9b9",
      []
     ]
    },
@@ -252196,7 +252223,7 @@
        []
       ],
       "004.any.sharedworker-expected.txt": [
-       "d834acc47ce940bf6d1c9eeb9c36bd76d49b2a33",
+       "7e4635f6554ad6f64867d14071735b1df001659e",
        []
       ]
      },
@@ -288053,6 +288080,13 @@
        null,
        {}
       ]
+     ],
+     "scrollable-overflow-float.html": [
+      "f75c0a66cfffdfe9872c6e472966ee3cf639eae6",
+      [
+       null,
+       {}
+      ]
      ]
     },
     "css-overscroll-behavior": {
@@ -291247,7 +291281,7 @@
         ]
        ],
        "contain-intrinsic-size-valid.html": [
-        "c7add59d1e408ad7f7aee459da49f9f072de845f",
+        "12f035d26f1e0d19e12fde8f981c05f36afb2c0e",
         [
          null,
          {}
@@ -346896,7 +346930,7 @@
      "reporting": {
       "access-reporting": {
        "access-from-coop-page-to-openee_coop-ro.https.html": [
-        "53746410c3194baba37cc7065318b9c4027f8dad",
+        "2744b8e6519089d5e79efeb7f93444e5ab7b07c7",
         [
          null,
          {
@@ -346905,7 +346939,7 @@
         ]
        ],
        "access-from-coop-page-to-openee_coop-ro_cross-origin.https.html": [
-        "66f13b6390ec95020783cabad331670cf024bd34",
+        "8acbd44dae9bbfa3a451601c2983dec028cbbb60",
         [
          null,
          {
@@ -346914,7 +346948,7 @@
         ]
        ],
        "access-from-coop-page-to-opener_coop-ro.https.html": [
-        "2f8ce00b80c1a17589f5b5d5a64aa4cceb6ae59e",
+        "b8be3be406a4fe8d490188e591a6337c192531b6",
         [
          null,
          {
@@ -346923,7 +346957,7 @@
         ]
        ],
        "access-from-coop-page-to-opener_coop-ro_cross-origin.https.html": [
-        "17e0ebd96e63d816947733b1975b2eb9b3ec7349",
+        "2c78d01d9cd90bb6e184af19e46c875c8befa00b",
         [
          null,
          {
@@ -346932,7 +346966,7 @@
         ]
        ],
        "access-from-coop-page-to-other_coop-ro.https.html": [
-        "67be395711d251f2d643737a0242cf9cbe524e71",
+        "e3e957488b031870e07fe7d6b6db1b14719dfd04",
         [
          null,
          {
@@ -346941,7 +346975,7 @@
         ]
        ],
        "access-from-coop-page-to-other_coop-ro_cross-origin.https.html": [
-        "01e082d9ba0b00fc1827b7b09d085d76596eaf40",
+        "6d68d2c87b294cbfe01268e7d8fad0a8555bd102",
         [
          null,
          {
@@ -346950,7 +346984,7 @@
         ]
        ],
        "access-to-coop-page-from-openee_coop-ro.https.html": [
-        "4a5be8b12ac1bcb3081473fa9a37c3de423a4c4b",
+        "8681ab4f360e81a4635848a1f2fe6a6723c81b42",
         [
          null,
          {
@@ -346959,7 +346993,7 @@
         ]
        ],
        "access-to-coop-page-from-openee_coop-ro_cross-origin.https.html": [
-        "e48f5f7efdd96526f4291dc9ec3199330c062b01",
+        "1380e50c3c819407d27027d1f505aa5dc47e53c6",
         [
          null,
          {
@@ -346968,7 +347002,7 @@
         ]
        ],
        "access-to-coop-page-from-opener_coop-ro.https.html": [
-        "10bdf896addd32d1b1dbedce95520f7820780b3c",
+        "c7bcf8ba26fc211566bdb6f6f0764f07e11e5f7e",
         [
          null,
          {
@@ -346977,7 +347011,7 @@
         ]
        ],
        "access-to-coop-page-from-opener_coop-ro_cross-origin.https.html": [
-        "da87746346e5f7f0e7ab1a5f3f27957cc6202574",
+        "1ed7c3a36d76563191022e2e7901b38cc15a019e",
         [
          null,
          {
@@ -346986,7 +347020,7 @@
         ]
        ],
        "access-to-coop-page-from-other_coop-ro.https.html": [
-        "4ed609a9c879f87dc87792de701d12e0b481ca96",
+        "ecbc03cfa76406d9d35481d889458173dd3bf207",
         [
          null,
          {
@@ -346995,7 +347029,7 @@
         ]
        ],
        "access-to-coop-page-from-other_coop-ro_cross-origin.https.html": [
-        "1e7cf0a1b440a882fba656bf30acce1e1c8a87c7",
+        "01315fc9a28e9877efb0e2c6223bda458ceb325f",
         [
          null,
          {
@@ -347136,7 +347170,7 @@
         ]
        ],
        "reporting-observer.html": [
-        "435f6471b75d052720257fc9708f9b6a789f29e9",
+        "1d73b566105a384a826d7d4f1ad96be075143f63",
         [
          null,
          {
@@ -388943,7 +388977,7 @@
      ],
      "inheritance": {
       "iframe-inheritance-about-blank.html": [
-       "2185ee29b9ac1ffded392c57defad54c13458987",
+       "60f91a5e47b431feb8784a650a8c6ad304e904f8",
        [
         null,
         {}
@@ -413947,7 +413981,7 @@
    },
    "webhid": {
     "idlharness.https.window.js": [
-     "fa763e0d80ac7e196cd276a9aed5a7a33f9d8331",
+     "bdc8419ba8bf4c40ac62634b04ebd962d118c36b",
      [
       "webhid/idlharness.https.window.html",
       {
@@ -415001,7 +415035,7 @@
      ]
     ],
     "RTCDtlsTransport-getRemoteCertificates.html": [
-     "8af95a84d574e20f9b0bbff964ed064e4e17e62f",
+     "899e603cbe0fe1f023c8d763055c578ef62bb96f",
      [
       null,
       {}
@@ -424975,7 +425009,7 @@
        ]
       ],
       "004.any.js": [
-       "65f59a0b377886cc96053f7d7b8a3c8199a8594b",
+       "ea96197ce19aa47d2d351549f6d570afeffe7920",
        [
         "workers/semantics/interface-objects/004.any.sharedworker.html",
         {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/KeyframeEffect-getKeyframes.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-animations/KeyframeEffect-getKeyframes.tentative-expected.txt
index 9dc4691..bfffc514 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-animations/KeyframeEffect-getKeyframes.tentative-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/css/css-animations/KeyframeEffect-getKeyframes.tentative-expected.txt
@@ -15,13 +15,13 @@
 PASS KeyframeEffect.getKeyframes() returns expected frames for an animation with multiple keyframes for the same time and with different easing functions
 PASS KeyframeEffect.getKeyframes() returns expected frames for an animation with multiple keyframes for the same time and with different but equivalent easing functions
 PASS KeyframeEffect.getKeyframes() returns expected frames for overlapping keyframes
-PASS KeyframeEffect.getKeyframes() returns expected values for animations with filter properties and missing keyframes
+FAIL KeyframeEffect.getKeyframes() returns expected values for animations with filter properties and missing keyframes assert_equals: value for 'filter' on Keyframe #1 should match expected "blur(5px) sepia(60%) saturate(30%)" but got "blur(5px) sepia(0.6) saturate(0.3)"
 PASS KeyframeEffect.getKeyframes() returns expected values for animation with drop-shadow of filter property
 PASS KeyframeEffect.getKeyframes() returns expected values for animations with text-shadow properties and missing keyframes
 PASS KeyframeEffect.getKeyframes() returns expected values for animations with background-size properties and missing keyframes
-FAIL KeyframeEffect.getKeyframes() returns expected values for animations with CSS variables as keyframe values assert_equals: value for 'transform' on Keyframe #1 should match expected "translate(100px)" but got "translate(var(--var-100px), 0) "
-FAIL KeyframeEffect.getKeyframes() returns expected values for animations with CSS variables as keyframe values in a shorthand property assert_equals: value for 'marginBottom' on Keyframe #1 should match expected "100px" but got ""
-FAIL KeyframeEffect.getKeyframes() returns expected values for animations with a CSS variable which is overriden by the value in keyframe assert_array_equals: properties on Keyframe #0 should match lengths differ, expected array ["color", "composite", "computedOffset", "easing", "offset"] length 5, got ["--end-color", "color", "composite", "computedOffset", "easing", "offset"] length 6
+FAIL KeyframeEffect.getKeyframes() returns expected values for animations with CSS variables as keyframe values assert_equals: value for 'transform' on Keyframe #1 should match expected "translate(100px)" but got "translate(100px, 0px)"
+PASS KeyframeEffect.getKeyframes() returns expected values for animations with CSS variables as keyframe values in a shorthand property
+PASS KeyframeEffect.getKeyframes() returns expected values for animations with a CSS variable which is overriden by the value in keyframe
 FAIL KeyframeEffect.getKeyframes() returns expected values for animations with only custom property in a keyframe assert_equals: value for 'transform' on Keyframe #0 should match expected "translate(100px)" but got "translate(100px, 0px)"
 PASS KeyframeEffect.getKeyframes() reflects changes to @keyframes rules
 Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/webauthn.idl b/third_party/blink/web_tests/external/wpt/interfaces/webauthn.idl
index 4af14a86..9da4922 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/webauthn.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/webauthn.idl
@@ -198,9 +198,15 @@
     AuthenticationExtensionsLargeBlobInputs largeBlob;
 };
 
+enum LargeBlobSupport {
+  "required",
+  "preferred",
+};
+
 dictionary AuthenticationExtensionsLargeBlobInputs {
+    DOMString support;
     boolean read;
-    ArrayBuffer write;
+    BufferSource write;
 };
 
 partial dictionary AuthenticationExtensionsClientOutputs {
@@ -208,6 +214,7 @@
 };
 
 dictionary AuthenticationExtensionsLargeBlobOutputs {
+    boolean supported;
     ArrayBuffer blob;
     boolean written;
 };
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/RTCDtlsTransport-getRemoteCertificates.html b/third_party/blink/web_tests/external/wpt/webrtc/RTCDtlsTransport-getRemoteCertificates.html
index 8af95a84..899e603 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc/RTCDtlsTransport-getRemoteCertificates.html
+++ b/third_party/blink/web_tests/external/wpt/webrtc/RTCDtlsTransport-getRemoteCertificates.html
@@ -42,23 +42,13 @@
     const pc2 = new RTCPeerConnection();
     t.add_cleanup(() => pc2.close());
 
-    pc1.createDataChannel('test');
+    pc1.addTrack(trackFactories.audio());
     exchangeIceCandidates(pc1, pc2);
 
     exchangeOfferAnswer(pc1, pc2)
     .then(t.step_func(() => {
-      // pc.sctp is set when set*Description(answer) is called
-      const sctpTransport1 = pc1.sctp;
-      const sctpTransport2 = pc2.sctp;
-
-      assert_true(sctpTransport1 instanceof RTCSctpTransport,
-        'Expect pc.sctp to be set to valid RTCSctpTransport');
-
-      assert_true(sctpTransport2 instanceof RTCSctpTransport,
-        'Expect pc.sctp to be set to valid RTCSctpTransport');
-
-      const dtlsTransport1 = sctpTransport1.transport;
-      const dtlsTransport2 = sctpTransport2.transport;
+      const dtlsTransport1 = pc1.getSenders()[0].transport;
+      const dtlsTransport2 = pc2.getReceivers()[0].transport;
 
       const testedTransports = new Set();
 
diff --git a/third_party/blink/web_tests/fast/events/autoscroll-over-scrollbar.html b/third_party/blink/web_tests/fast/events/autoscroll-over-scrollbar.html
deleted file mode 100644
index 2ef3e8f5..0000000
--- a/third_party/blink/web_tests/fast/events/autoscroll-over-scrollbar.html
+++ /dev/null
@@ -1,74 +0,0 @@
-<!DOCTYPE HTML>
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<script src="../../resources/compositor-controls.js"></script>
-<style>
-  body {
-    margin: 0px;
-    height: 1000px;
-    width: 1000px;
-  }
-  #scrollable {
-    background-color: #FF7F7F;
-    height: 600px;
-    width: 600px;
-    overflow: scroll;
-  }
-  #content {
-    height: 700px;
-    width: 700px;
-  }
-</style>
-
-<div id="scrollable">
-  <div id="content"> </div>
-</div>
-
-<script>
-setAnimationRequiresRaster();
-var scrollable = document.getElementById('scrollable');
-function autoScroll(start_x, start_y, end_x, end_y) {
-  return new Promise((resolve, reject) => {
-    if (!window.eventSender) {
-      reject();
-    } else {
-      const MIDDLE_BUTTON = 1;
-      eventSender.mouseMoveTo(start_x, start_y);
-      eventSender.mouseDown(MIDDLE_BUTTON);
-      eventSender.mouseUp(MIDDLE_BUTTON);
-      eventSender.mouseMoveTo(end_x, end_y);
-      resolve();
-    }
-  });
-}
-
-function waitForScrollAndCheck() {
-  const MAX_FRAME = 500;
-  return new Promise((resolve, reject) => {
-    function tick(frames) {
-    // We requestAnimationFrame either for 500 frames or until scrollable scrolls.
-      if (frames >= MAX_FRAME || scrollable.scrollTop > 0)
-        resolve();
-      else
-        requestAnimationFrame(tick.bind(this, frames + 1));
-    }
-    tick(0);
-  });
-}
-
-promise_test(t => {
-  const MIDDLE_CLICK_AUTOSCROLL_RADIUS = 15; // from blink::kNoMiddleClickAutoscrollRadius
-  var rect = scrollable.getBoundingClientRect();
-  var startX = rect.right - 5;
-  var startY = rect.top + 20;
-  var endX = startX;
-  var endY = startY + 2 * MIDDLE_CLICK_AUTOSCROLL_RADIUS;
-  assert_equals(scrollable.scrollTop, 0);
-  return autoScroll(startX, startY, endX, endY)
-  .then(waitForScrollAndCheck)
-  .then(() => {
-      assert_greater_than(scrollable.scrollTop, 0);
-  });
-});
-
-</script>
diff --git a/third_party/blink/web_tests/fast/events/autoscroll-iframe-no-scrolling.html b/third_party/blink/web_tests/fast/scrolling/autoscroll-iframe-no-scrolling.html
similarity index 71%
rename from third_party/blink/web_tests/fast/events/autoscroll-iframe-no-scrolling.html
rename to third_party/blink/web_tests/fast/scrolling/autoscroll-iframe-no-scrolling.html
index ce07b80f..7e76fbf 100644
--- a/third_party/blink/web_tests/fast/events/autoscroll-iframe-no-scrolling.html
+++ b/third_party/blink/web_tests/fast/scrolling/autoscroll-iframe-no-scrolling.html
@@ -1,4 +1,5 @@
 <!DOCTYPE HTML>
+<script src="../../resources/gesture-util.js"></script>
 <script src="../../resources/testharness.js"></script>
 <script src="../../resources/testharnessreport.js"></script>
 <script src="../../resources/compositor-controls.js"></script>
@@ -21,9 +22,8 @@
   This test ensures that autoscrolling doesn't scroll an iframe that's marked
   as unscrollable via `scrolling="no"`. To test manually, fully scroll the blue
   inner scroller and use middle click autoscroll (Windows feature) from the
-  inner blue scroller to scroll downwards until the main window begins to
-  scroll. This test passes if the iframe that contains the blue scroller
-  doesn't scroll.
+  inner blue scroller or the rest of the iframe to scroll downwards. This test
+  passes if the iframe that contains the blue scroller doesn't scroll.
 </p>
 <iframe id="scrollable" scrolling="no" srcdoc="
   <!DOCTYPE html>
@@ -67,41 +67,35 @@
   });
 }
 
-function waitForScrollAndCheck() {
-  return new Promise((resolve, reject) => {
-    function tick(frames) {
-      // Wait until the scroll bubbles up to the main window or the iframe scrolls.
-      if (window.scrollY > 0 || scrollable.contentWindow.scrollY > 0)
-        resolve();
-      else
-        requestAnimationFrame(tick.bind(this, frames + 1));
-    }
-    tick(0);
-  });
-}
-
 window.addEventListener('load', () => {
     var inner_scroller = frames[0].document.getElementById('scroller');
     inner_scroller.scrollTop = 1000;
-    promise_test(t => {
+    promise_test(async t => {
       const MIDDLE_CLICK_AUTOSCROLL_RADIUS = 15; // from blink::kNoMiddleClickAutoscrollRadius
       var rect = scrollable.getBoundingClientRect();
       var startX = rect.left + 50;
       var startY = rect.top + 50;
       var endX = startX;
-      var endY = startY + 5 * MIDDLE_CLICK_AUTOSCROLL_RADIUS;
+      var endY = startY + 30 * MIDDLE_CLICK_AUTOSCROLL_RADIUS;
       assert_equals(inner_scroller.scrollTop,
                     inner_scroller.scrollHeight - inner_scroller.clientHeight,
                     "Inner scroller starts fully scrolled.");
       assert_equals(window.scrollY, 0, "Main window starts unscrolled.");
       assert_equals(frames[0].window.scrollY, 0, "IFrame starts unscrolled.");
 
-      return autoScroll(startX, startY, endX, endY)
-      .then(waitForScrollAndCheck)
-      .then(() => {
-          assert_greater_than(window.scrollY, 0, "Main window must be scrolled.");
-          assert_equals(frames[0].window.scrollY, 0, "IFrame must NOT scroll.");
-      });
+      // Autoscroll over the inner scroller.
+      await autoScroll(startX, startY, endX, endY);
+      await waitForAnimationEndTimeBased( () => { return window.scrollY; } );
+      assert_equals(window.scrollY, 0, "Main frame should not scroll");
+
+      // Autoscroll over the iframe.
+      startX = rect.right - 20;
+      endX = startX;
+      await autoScroll(startX, startY, endX, endY);
+      await waitForAnimationEndTimeBased( () => { return window.scrollY; } );
+      assert_equals(window.scrollY, 0, "Main frame should not scroll");
+
+      assert_equals(frames[0].window.scrollY, 0, "IFrame must NOT scroll.");
     });
 });
 
diff --git a/third_party/blink/web_tests/fast/scrolling/autoscroll-over-scrollbar.html b/third_party/blink/web_tests/fast/scrolling/autoscroll-over-scrollbar.html
new file mode 100644
index 0000000..7f58d41
--- /dev/null
+++ b/third_party/blink/web_tests/fast/scrolling/autoscroll-over-scrollbar.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="../../resources/gesture-util.js"></script>
+<script src="../../resources/compositor-controls.js"></script>
+<style>
+  body {
+    margin: 0px;
+    height: 1000px;
+    width: 1000px;
+  }
+  #scrollable {
+    background-color: #FF7F7F;
+    height: 600px;
+    width: 600px;
+    overflow: scroll;
+  }
+  #content {
+    height: 700px;
+    width: 700px;
+  }
+</style>
+
+<div id="scrollable">
+  <div id="content"> </div>
+</div>
+
+<script>
+setAnimationRequiresRaster();
+var scrollable = document.getElementById('scrollable');
+async function autoScroll(start_x, start_y, end_x, end_y) {
+  const MIDDLE_BUTTON = 1;
+  await mouseMoveTo(start_x, start_y);
+  await mouseClickOn(start_x, start_y, MIDDLE_BUTTON);
+  await mouseMoveTo(end_x, end_y);
+}
+
+promise_test(async t => {
+  const MIDDLE_CLICK_AUTOSCROLL_RADIUS = 15; // from blink::kNoMiddleClickAutoscrollRadius
+  var rect = scrollable.getBoundingClientRect();
+  var startX = rect.right - 5;
+  var startY = rect.top + 20;
+  var endX = startX;
+  var endY = startY + 2 * MIDDLE_CLICK_AUTOSCROLL_RADIUS;
+  assert_equals(scrollable.scrollTop, 0);
+  await autoScroll(startX, startY, endX, endY);
+  await waitUntil(() => {return scrollable.scrollTop > 0;});
+
+  assert_greater_than(scrollable.scrollTop, 0);
+});
+
+</script>
diff --git a/third_party/blink/web_tests/resources/gesture-util.js b/third_party/blink/web_tests/resources/gesture-util.js
index 409ca56f..ad25837 100644
--- a/third_party/blink/web_tests/resources/gesture-util.js
+++ b/third_party/blink/web_tests/resources/gesture-util.js
@@ -134,7 +134,7 @@
       }
 
       if (cur_time - START_TIME > TIMEOUT_MS) {
-        reject();
+        reject(new Error("Timeout waiting for animation to end"));
         return;
       }
 
diff --git a/third_party/blink/web_tests/virtual/dark-mode-images-filter-all/dark-mode/images/README.txt b/third_party/blink/web_tests/virtual/dark-mode-images-filter-all/dark-mode/images/README.txt
index 5ac3f08..091e87a 100644
--- a/third_party/blink/web_tests/virtual/dark-mode-images-filter-all/dark-mode/images/README.txt
+++ b/third_party/blink/web_tests/virtual/dark-mode-images-filter-all/dark-mode/images/README.txt
@@ -1,3 +1,4 @@
 # This suite runs the tests in web_tests/dark-mode/images with
-# --blink-settings=forceDarkModeEnabled=true,forceDarkModeImagePolicy=0
+# --blink-settings=forceDarkModeEnabled=true and
+# --dark-mode-settings=ImagePolicy=0
 # See the virtual_test_suites() method in tools/blinkpy/web_tests/port/base.py.
diff --git a/third_party/blink/web_tests/virtual/dark-mode-images-filter-none/dark-mode/images/README.txt b/third_party/blink/web_tests/virtual/dark-mode-images-filter-none/dark-mode/images/README.txt
index 775a1ba..95b908d 100644
--- a/third_party/blink/web_tests/virtual/dark-mode-images-filter-none/dark-mode/images/README.txt
+++ b/third_party/blink/web_tests/virtual/dark-mode-images-filter-none/dark-mode/images/README.txt
@@ -1,3 +1,4 @@
 # This suite runs the tests in web_tests/dark-mode/images with
-# --blink-settings=forceDarkModeEnabled=true,forceDarkModeImagePolicy=1
+# --blink-settings=forceDarkModeEnabled=true and
+# --dark-mode-settings=ImagePolicy=1
 # See the virtual_test_suites() method in tools/blinkpy/web_tests/port/base.py.
diff --git a/third_party/blink/web_tests/virtual/dark-mode-images-grayscale/dark-mode/images/README.txt b/third_party/blink/web_tests/virtual/dark-mode-images-grayscale/dark-mode/images/README.txt
index 57b7a47..0891b51f 100644
--- a/third_party/blink/web_tests/virtual/dark-mode-images-grayscale/dark-mode/images/README.txt
+++ b/third_party/blink/web_tests/virtual/dark-mode-images-grayscale/dark-mode/images/README.txt
@@ -1,4 +1,5 @@
 # This suite runs the tests in web_tests/dark-mode/images with
-# --blink-settings=forceDarkModeEnabled=true,forceDarkModeImagePolicy=0,forceDarkModeImageGrayscale=1.0
+# --blink-settings=forceDarkModeEnabled=true and
+# --dark-mode-settings=ImagePolicy=0,ImageGrayScalePercent=1.0
 # See the virtual_test_suites() method in tools/blinkpy/web_tests/port/base.py.
 
diff --git a/third_party/blink/web_tests/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCDtlsTransport-getRemoteCertificates-expected.txt b/third_party/blink/web_tests/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCDtlsTransport-getRemoteCertificates-expected.txt
index 81f49d68..206f276 100644
--- a/third_party/blink/web_tests/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCDtlsTransport-getRemoteCertificates-expected.txt
+++ b/third_party/blink/web_tests/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCDtlsTransport-getRemoteCertificates-expected.txt
@@ -1,4 +1,4 @@
 This is a testharness.js-based test.
-FAIL RTCDtlsTransport.prototype.getRemoteCertificates assert_true: Expect pc.sctp to be set to valid RTCSctpTransport expected true got false
+FAIL RTCDtlsTransport.prototype.getRemoteCertificates Cannot read property 'state' of null
 Harness: the test ran to completion.
 
diff --git a/third_party/fuchsia-sdk/LICENSE b/third_party/fuchsia-sdk/LICENSE
deleted file mode 100644
index 6d6ddbcc..0000000
--- a/third_party/fuchsia-sdk/LICENSE
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//    * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-//    * Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following disclaimer
-// in the documentation and/or other materials provided with the
-// distribution.
-//    * Neither the name of Google Inc. nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/third_party/fuchsia-sdk/README.chromium b/third_party/fuchsia-sdk/README.chromium
index f7d8c22..613c974 100644
--- a/third_party/fuchsia-sdk/README.chromium
+++ b/third_party/fuchsia-sdk/README.chromium
@@ -1,10 +1,11 @@
-Name: Fuchsia NDK
-URL: https://fuchsia.googlesource.com/docs/+/master/development/sdk/README.md
+Name: Fuchsia SDK
+URL: https://fuchsia.dev/fuchsia-src/development/sdk/download
 Version: 0
-Security Critical: no
-License: BSD 3-Clause, MIT, GPL v2
+Security Critical: yes
+License: BSD 3-Clause, Apache 2.0, MIT
+License File: sdk/LICENSE
 
 Description:
 This directory contains the current Fuchsia SDK. The SDK contains headers,
 libraries and tools that are needed to build Chromium for Fuchsia. It also
-contains qemu, which is used to run tests on Fuchsia.
+contains boot images that are used to run tests on Fuchsia in emulation.
diff --git a/third_party/minigbm/BUILD.gn b/third_party/minigbm/BUILD.gn
index 79ebcab..a611409 100644
--- a/third_party/minigbm/BUILD.gn
+++ b/third_party/minigbm/BUILD.gn
@@ -11,7 +11,7 @@
   # Controls whether the build should use the version of minigbm library shipped
   # with the system. In release builds of desktop Linux and Chrome OS we use the
   # system version.
-  use_system_minigbm = is_desktop_linux && !is_chromecast
+  use_system_minigbm = is_linux && !is_chromecast
 
   use_amdgpu_minigbm = false
   use_exynos_minigbm = false
diff --git a/tools/grit/grit_defines.gni b/tools/grit/grit_defines.gni
index 4b70dd5..1a17cb2c 100644
--- a/tools/grit/grit_defines.gni
+++ b/tools/grit/grit_defines.gni
@@ -55,7 +55,7 @@
   ]
 }
 
-if (is_desktop_linux) {
+if (is_linux) {
   grit_defines += [
     "-D",
     "desktop_linux",
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 57004a75..00e31ea9c 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -21874,6 +21874,7 @@
   <int value="787" label="WebRtcAllowLegacyTLSProtocols"/>
   <int value="788" label="MediaRecommendationsEnabled"/>
   <int value="789" label="DeviceFamilyLinkAccountsAllowed"/>
+  <int value="790" label="EduCoexistenceToSVersion"/>
 </enum>
 
 <enum name="EnterprisePolicyDeviceIdValidity">
@@ -29329,10 +29330,10 @@
   <int value="3498" label="IdentifiabilityStudyReserved3498"/>
   <int value="3499"
       label="V8BackgroundFetchRegistration_FailureReason_AttributeGetter"/>
-  <int value="3500" label="IdentifiabilityStudyReserved3500"/>
-  <int value="3501" label="IdentifiabilityStudyReserved3501"/>
-  <int value="3502" label="IdentifiabilityStudyReserved3502"/>
-  <int value="3503" label="IdentifiabilityStudyReserved3503"/>
+  <int value="3500" label="V8Document_ElementFromPoint_Method"/>
+  <int value="3501" label="V8Document_ElementsFromPoint_Method"/>
+  <int value="3502" label="V8ShadowRoot_ElementFromPoint_Method"/>
+  <int value="3503" label="V8ShadowRoot_ElementsFromPoint_Method"/>
   <int value="3504" label="IdentifiabilityStudyReserved3504"/>
   <int value="3505" label="IdentifiabilityStudyReserved3505"/>
   <int value="3506" label="IdentifiabilityStudyReserved3506"/>
@@ -32279,6 +32280,10 @@
   <int value="19" label="kSamplingProfiler"/>
   <int value="20" label="kSnapshotCreator"/>
   <int value="21" label="kTesting"/>
+  <int value="22" label="kExternalFinalize"/>
+  <int value="23" label="kGlobalAllocationLimit"/>
+  <int value="24" label="kMeasureMemory"/>
+  <int value="25" label="kBackgroundAllocationFailure"/>
 </enum>
 
 <enum name="GATTCharacteristicHash">
@@ -44749,6 +44754,7 @@
   <int value="1747279677" label="disable-delegated-renderer"/>
   <int value="1748481830" label="AppManagement:enabled"/>
   <int value="1749028785" label="SubresourceRedirect:disabled"/>
+  <int value="1750658031" label="OmniboxBookmarkPaths:enabled"/>
   <int value="1750693293" label="AutofillPreventMixedFormsFilling:enabled"/>
   <int value="1752168018" label="enable-stale-while-revalidate"/>
   <int value="1755024316" label="HostWindowsInAppShimProcess:disabled"/>
@@ -44764,6 +44770,7 @@
   <int value="1771548551" label="DisableKeepaliveFetch:enabled"/>
   <int value="1772454319" label="enable-storage-manager"/>
   <int value="1775475563" label="malware-interstitial-v3"/>
+  <int value="1775652804" label="OmniboxBookmarkPaths:disabled"/>
   <int value="1775730290" label="OmniboxKeywordSearchButton:enabled"/>
   <int value="1776475705" label="show-composited-layer-borders"/>
   <int value="1777059507" label="trust-autofill-server-name-types"/>
@@ -56280,6 +56287,14 @@
   <int value="4" label="DB Error"/>
 </enum>
 
+<enum name="PasswordWeaknessScore">
+  <int value="0" label="Too guessable password"/>
+  <int value="1" label="Very guessable password"/>
+  <int value="2" label="Somewhat guessable password"/>
+  <int value="3" label="Safely unguessable password"/>
+  <int value="4" label="Very unguessable password"/>
+</enum>
+
 <enum name="PaymentRequestAbortReason">
   <int value="0" label="DismissedByUser"/>
   <int value="1" label="AbortedByMerchant"/>
@@ -66906,6 +66921,13 @@
   <int value="9" label="Notification interaction"/>
 </enum>
 
+<enum name="SiteInstanceProcessAssignment">
+  <int value="0" label="Unknown"/>
+  <int value="1" label="Reused existing render process"/>
+  <int value="2" label="Used spare process"/>
+  <int value="3" label="Created new process"/>
+</enum>
+
 <enum name="SiteIsolatedCodeCacheJSBehaviour">
   <int value="0" label="Hit">Available in code cache</int>
   <int value="1" label="Miss">Not available in cache</int>
@@ -69598,12 +69620,18 @@
 </enum>
 
 <enum name="SwapThrashingLevel">
+  <obsolete>
+    Obsolete as of 07/2020.
+  </obsolete>
   <int value="0" label="No swap thrashing"/>
   <int value="1" label="Suspiscion of swap thrashing"/>
   <int value="2" label="Confirmed swap thrashing"/>
 </enum>
 
 <enum name="SwapThrashingLevelChanges">
+  <obsolete>
+    Obsolete as of 07/2020.
+  </obsolete>
   <int value="0" label="None to Suspected"/>
   <int value="1" label="Suspected to Confirmed"/>
   <int value="2" label="Confirmed to Suspected"/>
diff --git a/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml b/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
index 5357b45..35e8e50 100644
--- a/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
+++ b/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
@@ -5320,6 +5320,7 @@
   <suffix name="Quad9Cdn" label="Quad9 cdn service."/>
   <suffix name="Quad9Insecure" label="Quad9 insecure service."/>
   <suffix name="Quad9Secure" label="Quad9 secure service."/>
+  <suffix name="Quickline" label="Quickline AS15600"/>
   <suffix name="Spectrum1"
       label="Charter Communications/Spectrum DoH endpoint1."/>
   <suffix name="Spectrum2"
@@ -7443,8 +7444,10 @@
   <suffix name="OfflinePages"
       label="Showing cache patterns only for OfflinePages."/>
   <suffix name="QueryTiles" label="Showing Query tiles images."/>
-  <suffix name="VideoTutorials"
-      label="Showing thumbnails for video tutorials."/>
+  <suffix name="VideoTutorialsIPH"
+      label="Showing thumbnails for video tutorials IPH."/>
+  <suffix name="VideoTutorialsList"
+      label="Showing thumbnails for video tutorials list view."/>
   <affected-histogram name="ImageFetcher.Events"/>
   <affected-histogram name="ImageFetcher.ImageLoadFromCacheTime"/>
   <affected-histogram name="ImageFetcher.ImageLoadFromCacheTimeJava"/>
diff --git a/tools/metrics/histograms/histograms_xml/memory/histograms.xml b/tools/metrics/histograms/histograms_xml/memory/histograms.xml
index 636c91d7..87573cca3 100644
--- a/tools/metrics/histograms/histograms_xml/memory/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/memory/histograms.xml
@@ -1111,25 +1111,6 @@
   </summary>
 </histogram>
 
-<histogram name="Memory.Experimental.SwapThrashingLevel"
-    enum="SwapThrashingLevel" expires_after="M85">
-  <owner>sebmarchand@chromium.org</owner>
-  <summary>
-    The swap thrashing level, which is recorded periodically. This shows the
-    cumulative number of seconds that systems spend in each of the swap
-    thrashing states. Only available on Windows.
-  </summary>
-</histogram>
-
-<histogram name="Memory.Experimental.SwapThrashingLevelChanges"
-    enum="SwapThrashingLevelChanges" expires_after="M85">
-  <owner>sebmarchand@chromium.org</owner>
-  <summary>
-    The number of swap-thrashing level state changes for each possible pairwise
-    state change. Only available on Windows.
-  </summary>
-</histogram>
-
 <histogram name="Memory.Experimental.Total2.PrivateMemoryFootprint" units="MB"
     expires_after="2022-01-10">
   <owner>erikchen@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/obsolete_histograms.xml b/tools/metrics/histograms/histograms_xml/obsolete_histograms.xml
index 6e6263c..092784b 100644
--- a/tools/metrics/histograms/histograms_xml/obsolete_histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/obsolete_histograms.xml
@@ -33201,6 +33201,31 @@
   </summary>
 </histogram>
 
+<histogram name="Memory.Experimental.SwapThrashingLevel"
+    enum="SwapThrashingLevel" expires_after="M85">
+  <obsolete>
+    Obsolete as of 07/2020.
+  </obsolete>
+  <owner>sebmarchand@chromium.org</owner>
+  <summary>
+    The swap thrashing level, which is recorded periodically. This shows the
+    cumulative number of seconds that systems spend in each of the swap
+    thrashing states. Only available on Windows.
+  </summary>
+</histogram>
+
+<histogram name="Memory.Experimental.SwapThrashingLevelChanges"
+    enum="SwapThrashingLevelChanges" expires_after="M85">
+  <obsolete>
+    Obsolete as of 07/2020.
+  </obsolete>
+  <owner>sebmarchand@chromium.org</owner>
+  <summary>
+    The number of swap-thrashing level state changes for each possible pairwise
+    state change. Only available on Windows.
+  </summary>
+</histogram>
+
 <histogram name="Memory.Experimental.WMIRefresher.Init.AddEnumDuration"
     units="ms" expires_after="2019-09-30">
   <obsolete>
diff --git a/tools/metrics/histograms/histograms_xml/oobe/histograms.xml b/tools/metrics/histograms/histograms_xml/oobe/histograms.xml
index 715d4967..952d73a 100644
--- a/tools/metrics/histograms/histograms_xml/oobe/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/oobe/histograms.xml
@@ -40,7 +40,7 @@
 </histogram>
 
 <histogram name="OOBE.ErrorScreensTime.Enrollment" units="ms"
-    expires_after="2020-10-01">
+    expires_after="2021-08-01">
   <owner>raleksandrov@google.com</owner>
   <owner>cros-oac@google.com</owner>
   <summary>
@@ -56,7 +56,7 @@
 </histogram>
 
 <histogram name="OOBE.ErrorScreensTime.Supervised" units="ms"
-    expires_after="2020-10-01">
+    expires_after="2021-08-01">
   <owner>raleksandrov@google.com</owner>
   <owner>cros-oac@google.com</owner>
   <summary>
@@ -344,7 +344,7 @@
 </histogram>
 
 <histogram name="OOBE.UpdateScreen.UpdateDownloadingTime" units="ms"
-    expires_after="2020-10-01">
+    expires_after="2021-08-01">
   <owner>raleksandrov@google.com</owner>
   <owner>cros-oac@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/password/histograms.xml b/tools/metrics/histograms/histograms_xml/password/histograms.xml
index bc762e9..acbdb6e 100644
--- a/tools/metrics/histograms/histograms_xml/password/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/password/histograms.xml
@@ -2156,6 +2156,40 @@
   </summary>
 </histogram>
 
+<histogram name="PasswordManager.WeakCheck.CheckedPasswords" units="passwords"
+    expires_after="M92">
+  <owner>jdoerrie@chromium.org</owner>
+  <owner>vasilii@chromium.org</owner>
+  <summary>
+    The number of passwords analyzed during the passwords weak check.
+  </summary>
+</histogram>
+
+<histogram name="PasswordManager.WeakCheck.PasswordScore"
+    enum="PasswordWeaknessScore" expires_after="M92">
+  <owner>jdoerrie@chromium.org</owner>
+  <owner>vasilii@chromium.org</owner>
+  <summary>
+    The score of the password that was checked by the passwords weak check. The
+    score indicates how guessable the password is.
+  </summary>
+</histogram>
+
+<histogram name="PasswordManager.WeakCheck.Time" units="ms" expires_after="M92">
+  <owner>jdoerrie@chromium.org</owner>
+  <owner>vasilii@chromium.org</owner>
+  <summary>The time it took to complete the passwords weak check.</summary>
+</histogram>
+
+<histogram name="PasswordManager.WeakCheck.WeakPasswords" units="passwords"
+    expires_after="M92">
+  <owner>jdoerrie@chromium.org</owner>
+  <owner>vasilii@chromium.org</owner>
+  <summary>
+    The number of weak passwords found when the passwords weak check completed.
+  </summary>
+</histogram>
+
 <histogram
     name="PasswordManager.WellKnownChangePassword.GetChangePasswordUsage"
     enum="GetChangePasswordUrlMetric" expires_after="2021-01-31">
diff --git a/tools/metrics/histograms/histograms_xml/web_apk/histograms.xml b/tools/metrics/histograms/histograms_xml/web_apk/histograms.xml
index 1f9165d..c8ab691 100644
--- a/tools/metrics/histograms/histograms_xml/web_apk/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/web_apk/histograms.xml
@@ -104,6 +104,7 @@
 <histogram name="WebApk.Install.RequestTokenDuration" units="ms"
     expires_after="2021-03-15">
   <owner>hartmanng@chromium.org</owner>
+  <owner>rayankans@chromium.org</owner>
   <owner>
     src/chrome/android/java/src/org/chromium/chrome/browser/webapps/OWNERS
   </owner>
@@ -116,6 +117,19 @@
   </summary>
 </histogram>
 
+<histogram name="WebApk.Install.RequestTokenDurationV2" units="ms"
+    expires_after="2021-03-15">
+  <owner>hartmanng@chromium.org</owner>
+  <owner>rayankans@chromium.org</owner>
+  <owner>
+    src/chrome/android/java/src/org/chromium/chrome/browser/webapps/OWNERS
+  </owner>
+  <summary>
+    Same as WebApk.Install.RequestTokenDuration, but for WebAPKs minted using
+    the new architecture (v2).
+  </summary>
+</histogram>
+
 <histogram name="WebApk.Launch.NetworkError" enum="NetErrorCodes"
     expires_after="2021-02-01">
   <owner>hartmanng@chromium.org</owner>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index 02fb90b..6de2fca0 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -9350,6 +9350,23 @@
       nearest 10.
     </summary>
   </metric>
+  <metric name="SiteInstanceRenderProcessAssignment"
+      enum="SiteInstanceProcessAssignment">
+    <summary>
+      Records how the main frame's site instance was assigned to a renderer
+      process.
+    </summary>
+    <aggregation>
+      <history>
+        <index fields="profile.country"/>
+        <index fields="profile.form_factor"/>
+        <index fields="profile.system_ram"/>
+        <statistics>
+          <enumeration/>
+        </statistics>
+      </history>
+    </aggregation>
+  </metric>
   <metric name="ThirdPartyCookieBlockingEnabledForSite" enum="Boolean">
     <owner>dullweber@chromium.org</owner>
     <owner>huanzhong@chromium.org</owner>
diff --git a/tools/perf/chrome_telemetry_build/BUILD.gn b/tools/perf/chrome_telemetry_build/BUILD.gn
index 280bb75..4725b3a 100644
--- a/tools/perf/chrome_telemetry_build/BUILD.gn
+++ b/tools/perf/chrome_telemetry_build/BUILD.gn
@@ -49,7 +49,7 @@
     # CrOS currently has issues with the locally compiled version of
     # crashpad_database_util, so only include it on traditional Linux
     # platforms.
-    if (is_desktop_linux) {
+    if (is_linux) {
       data_deps +=
           [ "//third_party/crashpad/crashpad/tools:crashpad_database_util" ]
     }
diff --git a/ui/accessibility/platform/BUILD.gn b/ui/accessibility/platform/BUILD.gn
index 9975fe48..2f015b9 100644
--- a/ui/accessibility/platform/BUILD.gn
+++ b/ui/accessibility/platform/BUILD.gn
@@ -15,13 +15,6 @@
   import("//build/toolchain/win/midl.gni")
 }
 
-# This file depends on the legacy global sources assignment filter. It should
-# be converted to check target platform before assigning source files to the
-# sources variable. Remove this import and set_sources_assignment_filter call
-# when the file has been converted. See https://crbug.com/1018739 for details.
-import("//build/config/deprecated_default_sources_assignment_filter.gni")
-set_sources_assignment_filter(deprecated_default_sources_assignment_filter)
-
 if (is_win) {
   midl("ichromeaccessible") {
     sources = [ "ichromeaccessible.idl" ]
@@ -73,30 +66,33 @@
 
   if (has_native_accessibility) {
     sources += [
-      "ax_fragment_root_delegate_win.h",
-      "ax_fragment_root_win.cc",
-      "ax_fragment_root_win.h",
-      "ax_platform_node_delegate_utils_win.cc",
-      "ax_platform_node_delegate_utils_win.h",
-      "ax_platform_node_textchildprovider_win.cc",
-      "ax_platform_node_textchildprovider_win.h",
-      "ax_platform_node_textprovider_win.cc",
-      "ax_platform_node_textprovider_win.h",
-      "ax_platform_node_textrangeprovider_win.cc",
-      "ax_platform_node_textrangeprovider_win.h",
-      "ax_platform_node_win.cc",
-      "ax_platform_node_win.h",
-      "ax_platform_relation_win.cc",
-      "ax_platform_relation_win.h",
       "ax_platform_text_boundary.cc",
       "ax_platform_text_boundary.h",
-      "ax_system_caret_win.cc",
-      "ax_system_caret_win.h",
-      "uia_registrar_win.cc",
-      "uia_registrar_win.h",
     ]
 
     if (is_win) {
+      sources += [
+        "ax_fragment_root_delegate_win.h",
+        "ax_fragment_root_win.cc",
+        "ax_fragment_root_win.h",
+        "ax_platform_node_delegate_utils_win.cc",
+        "ax_platform_node_delegate_utils_win.h",
+        "ax_platform_node_textchildprovider_win.cc",
+        "ax_platform_node_textchildprovider_win.h",
+        "ax_platform_node_textprovider_win.cc",
+        "ax_platform_node_textprovider_win.h",
+        "ax_platform_node_textrangeprovider_win.cc",
+        "ax_platform_node_textrangeprovider_win.h",
+        "ax_platform_node_win.cc",
+        "ax_platform_node_win.h",
+        "ax_platform_relation_win.cc",
+        "ax_platform_relation_win.h",
+        "ax_system_caret_win.cc",
+        "ax_system_caret_win.h",
+        "uia_registrar_win.cc",
+        "uia_registrar_win.h",
+      ]
+
       public_deps += [
         "//third_party/iaccessible2",
         "//ui/accessibility/platform:ichromeaccessible",
diff --git a/ui/aura/BUILD.gn b/ui/aura/BUILD.gn
index eba8f05..76a5742 100644
--- a/ui/aura/BUILD.gn
+++ b/ui/aura/BUILD.gn
@@ -5,13 +5,6 @@
 import("//build/config/ui.gni")
 import("//testing/test.gni")
 
-# This file depends on the legacy global sources assignment filter. It should
-# be converted to check target platform before assigning source files to the
-# sources variable. Remove this import and set_sources_assignment_filter call
-# when the file has been converted. See https://crbug.com/1018739 for details.
-import("//build/config/deprecated_default_sources_assignment_filter.gni")
-set_sources_assignment_filter(deprecated_default_sources_assignment_filter)
-
 component("aura") {
   public = [
     "client/aura_constants.h",
@@ -39,7 +32,6 @@
     "env_observer.h",
     "event_injector.h",
     "input_state_lookup.h",
-    "input_state_lookup_win.h",
     "layout_manager.h",
     "null_window_targeter.h",
     "scoped_enable_unadjusted_mouse_events.h",
@@ -81,12 +73,9 @@
     "env_input_state_controller.cc",
     "event_injector.cc",
     "input_state_lookup.cc",
-    "input_state_lookup_win.cc",
     "layout_manager.cc",
     "native_window_occlusion_tracker.cc",
     "native_window_occlusion_tracker.h",
-    "native_window_occlusion_tracker_win.cc",
-    "native_window_occlusion_tracker_win.h",
     "null_window_targeter.cc",
     "scoped_keyboard_hook.cc",
     "scoped_simple_keyboard_hook.cc",
@@ -147,6 +136,15 @@
     "//ui/compositor",
   ]
 
+  if (is_win) {
+    public += [ "input_state_lookup_win.h" ]
+    sources += [
+      "input_state_lookup_win.cc",
+      "native_window_occlusion_tracker_win.cc",
+      "native_window_occlusion_tracker_win.h",
+    ]
+  }
+
   if (use_x11) {
     public_deps += [ "//ui/base/x" ]
     deps += [
diff --git a/ui/aura_extra/BUILD.gn b/ui/aura_extra/BUILD.gn
index b46ef76..cc402e9 100644
--- a/ui/aura_extra/BUILD.gn
+++ b/ui/aura_extra/BUILD.gn
@@ -4,22 +4,11 @@
 
 import("//build/config/ui.gni")
 
-# This file depends on the legacy global sources assignment filter. It should
-# be converted to check target platform before assigning source files to the
-# sources variable. Remove this import and set_sources_assignment_filter call
-# when the file has been converted. See https://crbug.com/1018739 for details.
-import("//build/config/deprecated_default_sources_assignment_filter.gni")
-set_sources_assignment_filter(deprecated_default_sources_assignment_filter)
-
 component("aura_extra") {
   sources = [
     "aura_extra_export.h",
     "image_window_delegate.cc",
     "image_window_delegate.h",
-    "window_occlusion_impl_win.cc",
-    "window_occlusion_impl_win.h",
-    "window_occlusion_win.cc",
-    "window_occlusion_win.h",
   ]
 
   defines = [ "AURA_EXTRA_IMPLEMENTATION" ]
@@ -34,6 +23,15 @@
     "//ui/gfx",
     "//ui/gfx/geometry",
   ]
+
+  if (is_win) {
+    sources += [
+      "window_occlusion_impl_win.cc",
+      "window_occlusion_impl_win.h",
+      "window_occlusion_win.cc",
+      "window_occlusion_win.h",
+    ]
+  }
 }
 
 source_set("vector_resource") {
@@ -66,10 +64,7 @@
 source_set("tests") {
   testonly = true
 
-  sources = [
-    "window_occlusion_impl_unittest_win.cc",
-    "window_position_in_root_monitor_unittest.cc",
-  ]
+  sources = [ "window_position_in_root_monitor_unittest.cc" ]
 
   deps = [
     ":aura_extra",
@@ -82,4 +77,8 @@
     "//ui/gfx/geometry",
     "//ui/platform_window",
   ]
+
+  if (is_win) {
+    sources += [ "window_occlusion_impl_unittest_win.cc" ]
+  }
 }
diff --git a/ui/base/BUILD.gn b/ui/base/BUILD.gn
index 60d5874..8526911 100644
--- a/ui/base/BUILD.gn
+++ b/ui/base/BUILD.gn
@@ -481,7 +481,7 @@
     deps += [ "//ui/events" ]
   }
 
-  if ((is_desktop_linux || is_chromeos) && (use_x11 || ozone_platform_x11)) {
+  if ((is_linux || is_chromeos) && (use_x11 || ozone_platform_x11)) {
     public_deps += [ "//ui/base/x" ]
 
     # X11 drag and drop wants to use common drag and drop types.
diff --git a/ui/base/clipboard/BUILD.gn b/ui/base/clipboard/BUILD.gn
index 55bef30..b906cc7 100644
--- a/ui/base/clipboard/BUILD.gn
+++ b/ui/base/clipboard/BUILD.gn
@@ -35,7 +35,7 @@
   }
 
   if (use_aura) {
-    if ((use_x11 && is_desktop_linux) || !is_win) {
+    if ((use_x11 && is_linux) || !is_win) {
       sources += [ "clipboard_format_type_aura.cc" ]
     }
   }
@@ -114,7 +114,7 @@
 
   if (use_aura) {
     # Linux clipboard implementations.
-    if (is_desktop_linux && !is_chromecast) {
+    if (is_linux && !is_chromecast) {
       sources += [ "clipboard_linux.cc" ]
       if (use_ozone) {
         sources += [
diff --git a/ui/base/ui_base_features.cc b/ui/base/ui_base_features.cc
index e912d1b..d35c5b6 100644
--- a/ui/base/ui_base_features.cc
+++ b/ui/base/ui_base_features.cc
@@ -60,8 +60,8 @@
 const base::Feature kSystemKeyboardLock{"SystemKeyboardLock",
                                         base::FEATURE_ENABLED_BY_DEFAULT};
 
-const base::Feature kNotificationIndicator = {"EnableNotificationIndicator",
-                                              base::FEATURE_ENABLED_BY_DEFAULT};
+const base::Feature kNotificationIndicator = {
+    "EnableNotificationIndicator", base::FEATURE_DISABLED_BY_DEFAULT};
 
 bool IsNotificationIndicatorEnabled() {
   return base::FeatureList::IsEnabled(kNotificationIndicator);
diff --git a/ui/base/x/BUILD.gn b/ui/base/x/BUILD.gn
index 69fc36422..2ce6d374 100644
--- a/ui/base/x/BUILD.gn
+++ b/ui/base/x/BUILD.gn
@@ -54,7 +54,7 @@
     "x11_workspace_handler.h",
   ]
 
-  if (is_desktop_linux || is_chromeos) {
+  if (is_linux || is_chromeos) {
     sources += [
       "selection_owner.cc",
       "selection_owner.h",
diff --git a/ui/events/ozone/BUILD.gn b/ui/events/ozone/BUILD.gn
index 313dfdc..02e3967 100644
--- a/ui/events/ozone/BUILD.gn
+++ b/ui/events/ozone/BUILD.gn
@@ -5,21 +5,12 @@
 import("//build/config/features.gni")
 import("//build/config/ui.gni")
 
-# This file depends on the legacy global sources assignment filter. It should
-# be converted to check target platform before assigning source files to the
-# sources variable. Remove this import and set_sources_assignment_filter call
-# when the file has been converted. See https://crbug.com/1018739 for details.
-import("//build/config/deprecated_default_sources_assignment_filter.gni")
-set_sources_assignment_filter(deprecated_default_sources_assignment_filter)
-
 assert(use_ozone)
 
 component("ozone") {
   output_name = "events_ozone"
 
   sources = [
-    "chromeos/cursor_controller.cc",
-    "chromeos/cursor_controller.h",
     "device/device_event.cc",
     "device/device_event.h",
     "device/device_event_observer.h",
@@ -49,6 +40,13 @@
 
   defines = [ "IS_EVENTS_OZONE_IMPL" ]
 
+  if (is_chromeos) {
+    sources += [
+      "chromeos/cursor_controller.cc",
+      "chromeos/cursor_controller.h",
+    ]
+  }
+
   if (use_udev) {
     sources += [
       "device/udev/device_manager_udev.cc",
diff --git a/ui/events/ozone/evdev/input_controller_evdev.cc b/ui/events/ozone/evdev/input_controller_evdev.cc
index 9fd60dca..e96c2ea 100644
--- a/ui/events/ozone/evdev/input_controller_evdev.cc
+++ b/ui/events/ozone/evdev/input_controller_evdev.cc
@@ -52,8 +52,11 @@
 }
 
 bool InputControllerEvdev::HasMouse() {
-  // TODO(crbug.com/1114828): add a separate HasPointingStick method.
-  return has_mouse_ || has_pointing_stick_;
+  return has_mouse_;
+}
+
+bool InputControllerEvdev::HasPointingStick() {
+  return has_pointing_stick_;
 }
 
 bool InputControllerEvdev::HasTouchpad() {
diff --git a/ui/events/ozone/evdev/input_controller_evdev.h b/ui/events/ozone/evdev/input_controller_evdev.h
index 1933aaff..414f0fb 100644
--- a/ui/events/ozone/evdev/input_controller_evdev.h
+++ b/ui/events/ozone/evdev/input_controller_evdev.h
@@ -40,6 +40,7 @@
 
   // InputController:
   bool HasMouse() override;
+  bool HasPointingStick() override;
   bool HasTouchpad() override;
   bool IsCapsLockEnabled() override;
   void SetCapsLockEnabled(bool enabled) override;
diff --git a/ui/gfx/animation/BUILD.gn b/ui/gfx/animation/BUILD.gn
index b0184a6a..34625ed2 100644
--- a/ui/gfx/animation/BUILD.gn
+++ b/ui/gfx/animation/BUILD.gn
@@ -44,7 +44,7 @@
     sources += [ "animation_win.cc" ]
   }
 
-  if (is_desktop_linux) {
+  if (is_linux) {
     sources += [
       "animation_linux.cc",
       "animation_settings_provider_linux.cc",
diff --git a/ui/gl/BUILD.gn b/ui/gl/BUILD.gn
index dae7f9d..b26305d 100644
--- a/ui/gl/BUILD.gn
+++ b/ui/gl/BUILD.gn
@@ -575,7 +575,7 @@
     sources += [ "gl_image_ahardwarebuffer_unittest.cc" ]
   }
 
-  if (is_desktop_linux) {
+  if (is_linux) {
     sources += [ "gl_image_native_pixmap_unittest.cc" ]
   }
 
diff --git a/ui/message_center/BUILD.gn b/ui/message_center/BUILD.gn
index b224f69..22c65394 100644
--- a/ui/message_center/BUILD.gn
+++ b/ui/message_center/BUILD.gn
@@ -8,13 +8,6 @@
 import("//testing/test.gni")
 import("//ui/base/ui_features.gni")
 
-# This file depends on the legacy global sources assignment filter. It should
-# be converted to check target platform before assigning source files to the
-# sources variable. Remove this import and set_sources_assignment_filter call
-# when the file has been converted. See https://crbug.com/1018739 for details.
-import("//build/config/deprecated_default_sources_assignment_filter.gni")
-set_sources_assignment_filter(deprecated_default_sources_assignment_filter)
-
 aggregate_vector_icons2("message_center_vector_icons") {
   icon_directory = "vector_icons"
 
@@ -102,7 +95,6 @@
         "views/message_popup_collection.h",
         "views/message_popup_view.cc",
         "views/message_popup_view.h",
-        "views/message_popup_view_mac.mm",
         "views/message_view.cc",
         "views/message_view.h",
         "views/message_view_factory.cc",
@@ -122,6 +114,9 @@
         "views/relative_time_formatter.cc",
         "views/relative_time_formatter.h",
       ]
+      if (is_mac) {
+        sources += [ "views/message_popup_view_mac.mm" ]
+      }
       deps += [
         "//ui/compositor",
         "//ui/events",
diff --git a/ui/ozone/ozone.gni b/ui/ozone/ozone.gni
index 7a4976c..1f70faf 100644
--- a/ui/ozone/ozone.gni
+++ b/ui/ozone/ozone.gni
@@ -69,7 +69,7 @@
       ozone_platform = "x11"
       ozone_platform_drm = true
       ozone_platform_x11 = true
-    } else if (is_desktop_linux) {
+    } else if (is_linux) {
       ozone_platform = "x11"
       ozone_platform_wayland = true
       ozone_platform_x11 = true
diff --git a/ui/ozone/platform/drm/BUILD.gn b/ui/ozone/platform/drm/BUILD.gn
index b3d0bc68..92e9ecb 100644
--- a/ui/ozone/platform/drm/BUILD.gn
+++ b/ui/ozone/platform/drm/BUILD.gn
@@ -75,8 +75,6 @@
     "gpu/hardware_display_plane.h",
     "gpu/hardware_display_plane_atomic.cc",
     "gpu/hardware_display_plane_atomic.h",
-    "gpu/hardware_display_plane_dummy.cc",
-    "gpu/hardware_display_plane_dummy.h",
     "gpu/hardware_display_plane_manager.cc",
     "gpu/hardware_display_plane_manager.h",
     "gpu/hardware_display_plane_manager_atomic.cc",
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_plane.cc b/ui/ozone/platform/drm/gpu/hardware_display_plane.cc
index 272a86d8..c2a2426 100644
--- a/ui/ozone/platform/drm/gpu/hardware_display_plane.cc
+++ b/ui/ozone/platform/drm/gpu/hardware_display_plane.cc
@@ -12,36 +12,10 @@
 #include "ui/ozone/platform/drm/gpu/drm_device.h"
 #include "ui/ozone/platform/drm/gpu/drm_gpu_util.h"
 
-#ifndef DRM_PLANE_TYPE_OVERLAY
-#define DRM_PLANE_TYPE_OVERLAY 0
-#endif
-
-#ifndef DRM_PLANE_TYPE_PRIMARY
-#define DRM_PLANE_TYPE_PRIMARY 1
-#endif
-
-#ifndef DRM_PLANE_TYPE_CURSOR
-#define DRM_PLANE_TYPE_CURSOR 2
-#endif
-
 namespace ui {
 
 namespace {
 
-HardwareDisplayPlane::Type GetPlaneType(int value) {
-  switch (value) {
-    case DRM_PLANE_TYPE_CURSOR:
-      return HardwareDisplayPlane::kCursor;
-    case DRM_PLANE_TYPE_PRIMARY:
-      return HardwareDisplayPlane::kPrimary;
-    case DRM_PLANE_TYPE_OVERLAY:
-      return HardwareDisplayPlane::kOverlay;
-    default:
-      NOTREACHED();
-      return HardwareDisplayPlane::kDummy;
-  }
-}
-
 void ParseSupportedFormatsAndModifiers(
     drmModePropertyBlobPtr blob,
     std::vector<uint32_t>* supported_formats,
@@ -91,7 +65,7 @@
   }
 
   if (properties_.type.id)
-    type_ = GetPlaneType(properties_.type.value);
+    type_ = properties_.type.value;
 
   if (properties_.plane_color_encoding.id) {
     color_encoding_bt601_ =
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_plane.h b/ui/ozone/platform/drm/gpu/hardware_display_plane.h
index ea61486..aea531a7 100644
--- a/ui/ozone/platform/drm/gpu/hardware_display_plane.h
+++ b/ui/ozone/platform/drm/gpu/hardware_display_plane.h
@@ -20,8 +20,6 @@
 
 class HardwareDisplayPlane {
  public:
-  enum Type { kDummy, kPrimary, kOverlay, kCursor };
-
   HardwareDisplayPlane(uint32_t id);
 
   virtual ~HardwareDisplayPlane();
@@ -39,8 +37,7 @@
 
   uint32_t id() const { return id_; }
 
-  Type type() const { return type_; }
-  void set_type(const Type type) { type_ = type; }
+  uint32_t type() const { return type_; }
 
   void set_owning_crtc(uint32_t crtc) { owning_crtc_ = crtc; }
   uint32_t owning_crtc() const { return owning_crtc_; }
@@ -80,7 +77,7 @@
   uint32_t owning_crtc_ = 0;
   uint32_t last_used_format_ = 0;
   bool in_use_ = false;
-  Type type_ = kPrimary;
+  uint32_t type_ = DRM_PLANE_TYPE_PRIMARY;
   std::vector<uint32_t> supported_formats_;
   std::vector<drm_format_modifier> supported_format_modifiers_;
 
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_plane_dummy.cc b/ui/ozone/platform/drm/gpu/hardware_display_plane_dummy.cc
deleted file mode 100644
index d00a0a1..0000000
--- a/ui/ozone/platform/drm/gpu/hardware_display_plane_dummy.cc
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ui/ozone/platform/drm/gpu/hardware_display_plane_dummy.h"
-
-#include <drm_fourcc.h>
-
-namespace ui {
-
-HardwareDisplayPlaneDummy::HardwareDisplayPlaneDummy(uint32_t id,
-                                                     uint32_t crtc_mask)
-    : HardwareDisplayPlane(id) {
-  crtc_mask_ = crtc_mask;
-}
-
-HardwareDisplayPlaneDummy::~HardwareDisplayPlaneDummy() {}
-
-bool HardwareDisplayPlaneDummy::Initialize(DrmDevice* drm) {
-  type_ = kDummy;
-  supported_formats_.push_back(DRM_FORMAT_XRGB8888);
-  supported_formats_.push_back(DRM_FORMAT_XBGR8888);
-  return true;
-}
-
-}  // namespace ui
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_plane_dummy.h b/ui/ozone/platform/drm/gpu/hardware_display_plane_dummy.h
deleted file mode 100644
index 8aadee8..0000000
--- a/ui/ozone/platform/drm/gpu/hardware_display_plane_dummy.h
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef UI_OZONE_PLATFORM_DRM_GPU_HARDWARE_DISPLAY_PLANE_DUMMY_H_
-#define UI_OZONE_PLATFORM_DRM_GPU_HARDWARE_DISPLAY_PLANE_DUMMY_H_
-
-#include "ui/ozone/platform/drm/gpu/hardware_display_plane.h"
-
-namespace ui {
-
-// Fake plane used with DRM legacy when universal planes are not supported and
-// the kernel doesn't report the primary plane.
-class HardwareDisplayPlaneDummy : public HardwareDisplayPlane {
- public:
-  HardwareDisplayPlaneDummy(uint32_t id, uint32_t crtc_mask);
-  ~HardwareDisplayPlaneDummy() override;
-
-  bool Initialize(DrmDevice* drm) override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(HardwareDisplayPlaneDummy);
-};
-
-}  // namespace ui
-
-#endif  // UI_OZONE_PLATFORM_DRM_GPU_HARDWARE_DISPLAY_PLANE_DUMMY_H_
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.cc b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.cc
index 50dc7dbe..e15964c0 100644
--- a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.cc
+++ b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.cc
@@ -111,7 +111,7 @@
 bool HardwareDisplayPlaneManager::IsCompatible(HardwareDisplayPlane* plane,
                                                const DrmOverlayPlane& overlay,
                                                uint32_t crtc_index) const {
-  if (plane->type() == HardwareDisplayPlane::kCursor ||
+  if (plane->type() == DRM_PLANE_TYPE_CURSOR ||
       !plane->CanUseForCrtc(crtc_index))
     return false;
 
@@ -180,17 +180,15 @@
     }
 
     gfx::Rect fixed_point_rect;
-    if (hw_plane->type() != HardwareDisplayPlane::kDummy) {
-      const gfx::Size& size = plane.buffer->size();
-      gfx::RectF crop_rectf = plane.crop_rect;
-      crop_rectf.Scale(size.width(), size.height());
-      // DrmOverlayManager::CanHandleCandidate guarantees this is safe.
-      gfx::Rect crop_rect = gfx::ToNearestRect(crop_rectf);
-      // Convert to 16.16 fixed point required by the DRM overlay APIs.
-      fixed_point_rect =
-          gfx::Rect(crop_rect.x() << 16, crop_rect.y() << 16,
-                    crop_rect.width() << 16, crop_rect.height() << 16);
-    }
+    const gfx::Size& size = plane.buffer->size();
+    gfx::RectF crop_rectf = plane.crop_rect;
+    crop_rectf.Scale(size.width(), size.height());
+    // DrmOverlayManager::CanHandleCandidate guarantees this is safe.
+    gfx::Rect crop_rect = gfx::ToNearestRect(crop_rectf);
+    // Convert to 16.16 fixed point required by the DRM overlay APIs.
+    fixed_point_rect =
+        gfx::Rect(crop_rect.x() << 16, crop_rect.y() << 16,
+                  crop_rect.width() << 16, crop_rect.height() << 16);
 
     if (!SetPlaneData(plane_list, hw_plane, plane, crtc_id, fixed_point_rect)) {
       ResetCurrentPlaneList(plane_list);
@@ -216,7 +214,7 @@
 
   for (const auto& plane : planes_) {
     if (plane->CanUseForCrtc(crtc_index) &&
-        plane->type() == HardwareDisplayPlane::kPrimary) {
+        plane->type() == DRM_PLANE_TYPE_PRIMARY) {
       return plane->ModifiersForFormat(format);
     }
   }
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_legacy.cc b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_legacy.cc
index 4ad0ff6..1fbdc30f 100644
--- a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_legacy.cc
+++ b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_legacy.cc
@@ -20,7 +20,6 @@
 #include "ui/ozone/platform/drm/gpu/drm_device.h"
 #include "ui/ozone/platform/drm/gpu/drm_framebuffer.h"
 #include "ui/ozone/platform/drm/gpu/hardware_display_plane.h"
-#include "ui/ozone/platform/drm/gpu/hardware_display_plane_dummy.h"
 #include "ui/ozone/platform/drm/gpu/page_flip_request.h"
 
 namespace ui {
@@ -119,7 +118,7 @@
   DCHECK(std::find_if(plane_list->old_plane_list.begin(),
                       plane_list->old_plane_list.end(),
                       [](HardwareDisplayPlane* plane) {
-                        return plane->type() == HardwareDisplayPlane::kOverlay;
+                        return plane->type() == DRM_PLANE_TYPE_OVERLAY;
                       }) == plane_list->old_plane_list.end());
   return true;
 }
@@ -166,31 +165,12 @@
 
     // Overlays are not supported on the legacy path, so ignore all overlay
     // planes.
-    if (plane->type() == HardwareDisplayPlane::kOverlay)
+    if (plane->type() == DRM_PLANE_TYPE_OVERLAY)
       continue;
 
     planes_.push_back(std::move(plane));
   }
 
-  // https://crbug.com/464085: if driver reports no primary planes for a crtc,
-  // create a dummy plane for which we can assign exactly one overlay.
-  if (!has_universal_planes_) {
-    for (size_t i = 0; i < crtc_state_.size(); ++i) {
-      uint32_t id = crtc_state_[i].properties.id - 1;
-      if (std::find_if(
-              planes_.begin(), planes_.end(),
-              [id](const std::unique_ptr<HardwareDisplayPlane>& plane) {
-                return plane->id() == id;
-              }) == planes_.end()) {
-        std::unique_ptr<HardwareDisplayPlane> dummy_plane(
-            new HardwareDisplayPlaneDummy(id, 1 << i));
-        if (dummy_plane->Initialize(drm_)) {
-          planes_.push_back(std::move(dummy_plane));
-        }
-      }
-    }
-  }
-
   return true;
 }
 
@@ -219,7 +199,7 @@
     HardwareDisplayPlane* plane,
     const DrmOverlayPlane& overlay,
     uint32_t crtc_index) const {
-  if (plane->type() == HardwareDisplayPlane::kCursor ||
+  if (plane->type() == DRM_PLANE_TYPE_CURSOR ||
       !plane->CanUseForCrtc(crtc_index))
     return false;
 
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_unittest.cc b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_unittest.cc
index bfd7f388..5e0beab9 100644
--- a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_unittest.cc
+++ b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_unittest.cc
@@ -351,7 +351,7 @@
 
   bool cursor_found = false;
   for (const auto& plane : fake_drm_->plane_manager()->planes()) {
-    if (plane->type() == ui::HardwareDisplayPlane::kCursor) {
+    if (plane->type() == DRM_PLANE_TYPE_CURSOR) {
       cursor_found = true;
       break;
     }
diff --git a/ui/ozone/public/input_controller.cc b/ui/ozone/public/input_controller.cc
index 43a27b4..43e5fca 100644
--- a/ui/ozone/public/input_controller.cc
+++ b/ui/ozone/public/input_controller.cc
@@ -21,6 +21,7 @@
 
   // InputController:
   bool HasMouse() override { return false; }
+  bool HasPointingStick() override { return false; }
   bool HasTouchpad() override { return false; }
   bool IsCapsLockEnabled() override { return false; }
   void SetCapsLockEnabled(bool enabled) override {}
diff --git a/ui/ozone/public/input_controller.h b/ui/ozone/public/input_controller.h
index cbf9482..27b5ad3 100644
--- a/ui/ozone/public/input_controller.h
+++ b/ui/ozone/public/input_controller.h
@@ -43,6 +43,7 @@
 
   // Functions for checking devices existence.
   virtual bool HasMouse() = 0;
+  virtual bool HasPointingStick() = 0;
   virtual bool HasTouchpad() = 0;
 
   // Keyboard settings.
diff --git a/ui/strings/ui_strings.grd b/ui/strings/ui_strings.grd
index e97599f..b41cb06 100644
--- a/ui/strings/ui_strings.grd
+++ b/ui/strings/ui_strings.grd
@@ -1111,15 +1111,6 @@
       <message name="IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_TITLE_NO_DEVICES" desc="The label to be shown as the title of the dialog when user click on a phone number, if there are no phones or apps to choose from.">
         Call this number from your phone?
       </message>
-      <message name="IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES" desc="The label to be shown as a help text of the dialog when user click on a phone number, if there are no phones to choose from.">
-        To send a number from here to your Android phone, <ph name="TROUBLESHOOT_LINK">$1<ex>turn on sync</ex></ph> for both devices in settings.
-      </message>
-      <message name="IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES_ORIGIN" desc="The label to be shown as a help text of the dialog when user click on a phone number, if there are no phones to choose from.">
-        To send a number from <ph name="ORIGIN">$1<ex>www.google.com</ex></ph> to your Android phone, <ph name="TROUBLESHOOT_LINK">$2<ex>turn on sync</ex></ph> for both devices in settings.
-      </message>
-      <message name="IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_TROUBLESHOOT_LINK" desc="The label to be shown on the dialog when user click on a phone number for the link to a help page to turn on sync.">
-        turn on sync
-      </message>
       <message name="IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_CALL_BUTTON_LABEL" desc="The button label to make a call, shown in the Click to Call dialog.">
         Make call
       </message>
@@ -1149,9 +1140,6 @@
         Couldn't share <ph name="CONTENT_TYPE">$1<ex>video</ex></ph>
       </message>
 
-      <message name="IDS_BROWSER_SHARING_ERROR_DIALOG_TEXT_DEVICE_NOT_FOUND" desc="The text to be shown on the dialog when an error occurred because the device is not synced.">
-        Make sure <ph name="TARGET_DEVICE_NAME">$1<ex>Pixel XL</ex></ph> has sync turned on in Chrome, and then try sending again.
-      </message>
       <message name="IDS_BROWSER_SHARING_ERROR_DIALOG_TEXT_NETWORK_ERROR" desc="The text to be shown on the dialog when the sender device has network issues.">
         Make sure this device is connected to the internet.
       </message>
diff --git a/ui/strings/ui_strings_grd/IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES.png.sha1 b/ui/strings/ui_strings_grd/IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES.png.sha1
deleted file mode 100644
index e248058..0000000
--- a/ui/strings/ui_strings_grd/IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-7e22e64a75f074df199380752b3ceef4cbc76758
\ No newline at end of file
diff --git a/ui/strings/ui_strings_grd/IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES_ORIGIN.png.sha1 b/ui/strings/ui_strings_grd/IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES_ORIGIN.png.sha1
deleted file mode 100644
index c7a1666d..0000000
--- a/ui/strings/ui_strings_grd/IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_HELP_TEXT_NO_DEVICES_ORIGIN.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-2beec5e7565eecbec2fc331a27565ff7e2795820
\ No newline at end of file
diff --git a/ui/strings/ui_strings_grd/IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_TROUBLESHOOT_LINK.png.sha1 b/ui/strings/ui_strings_grd/IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_TROUBLESHOOT_LINK.png.sha1
deleted file mode 100644
index 6ef4ea9..0000000
--- a/ui/strings/ui_strings_grd/IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_TROUBLESHOOT_LINK.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-a5eae26416ab8f82745704c4f1e6610181f7353c
\ No newline at end of file
diff --git a/ui/strings/ui_strings_grd/IDS_BROWSER_SHARING_ERROR_DIALOG_TEXT_DEVICE_NOT_FOUND.png.sha1 b/ui/strings/ui_strings_grd/IDS_BROWSER_SHARING_ERROR_DIALOG_TEXT_DEVICE_NOT_FOUND.png.sha1
deleted file mode 100644
index ebe4fda7..0000000
--- a/ui/strings/ui_strings_grd/IDS_BROWSER_SHARING_ERROR_DIALOG_TEXT_DEVICE_NOT_FOUND.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-445ef567c151a596c38dae0d734fa5d7f08573db
\ No newline at end of file
diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn
index 4f506ca3..eed146b4 100644
--- a/ui/views/BUILD.gn
+++ b/ui/views/BUILD.gn
@@ -1312,7 +1312,7 @@
     }
   }
 
-  if (is_desktop_linux && use_ozone) {
+  if (is_linux && use_ozone) {
     data_deps +=
         [ "//testing/buildbot/filters:linux_ozone_views_unittests_filters" ]
   }
@@ -1378,7 +1378,7 @@
       "//ui/wm/public",
     ]
 
-    if (is_desktop_linux) {
+    if (is_linux) {
       sources += [ "widget/desktop_aura/desktop_window_tree_host_linux_interactive_uitest.cc" ]
     }
 
diff --git a/ui/views/window/dialog_delegate.cc b/ui/views/window/dialog_delegate.cc
index b21dc28..e689e58 100644
--- a/ui/views/window/dialog_delegate.cc
+++ b/ui/views/window/dialog_delegate.cc
@@ -455,12 +455,4 @@
   return this;
 }
 
-void DialogDelegateView::ViewHierarchyChanged(
-    const ViewHierarchyChangedDetails& details) {
-  if (details.is_add && details.child == this && GetWidget() &&
-      ui::IsAlert(GetAccessibleWindowRole())) {
-    NotifyAccessibilityEvent(ax::mojom::Event::kAlert, true);
-  }
-}
-
 }  // namespace views
diff --git a/ui/views/window/dialog_delegate.h b/ui/views/window/dialog_delegate.h
index cdeeaeb..b543d96 100644
--- a/ui/views/window/dialog_delegate.h
+++ b/ui/views/window/dialog_delegate.h
@@ -358,10 +358,6 @@
   const Widget* GetWidget() const override;
   View* GetContentsView() override;
 
-  // View:
-  void ViewHierarchyChanged(
-      const ViewHierarchyChangedDetails& details) override;
-
  private:
   DISALLOW_COPY_AND_ASSIGN(DialogDelegateView);
 };
diff --git a/weblayer/public/java/org/chromium/weblayer/Browser.java b/weblayer/public/java/org/chromium/weblayer/Browser.java
index 725f1ac8..d568ed02 100644
--- a/weblayer/public/java/org/chromium/weblayer/Browser.java
+++ b/weblayer/public/java/org/chromium/weblayer/Browser.java
@@ -388,6 +388,7 @@
             for (TabListCallback callback : mTabListCallbacks) {
                 callback.onTabRemoved(tab);
             }
+            tab.onRemovedFromBrowser();
         }
 
         @Override
diff --git a/weblayer/public/java/org/chromium/weblayer/Tab.java b/weblayer/public/java/org/chromium/weblayer/Tab.java
index 6f27fa0..32495e0 100644
--- a/weblayer/public/java/org/chromium/weblayer/Tab.java
+++ b/weblayer/public/java/org/chromium/weblayer/Tab.java
@@ -59,6 +59,10 @@
     // Id from the remote side.
     private final int mId;
 
+    // See comments in onTabDestroyed() for details.
+    // TODO(sky): this is necessary for < 87. Remove in 90.
+    private boolean mDestroyOnRemove;
+
     // Constructor for test mocking.
     protected Tab() {
         mImpl = null;
@@ -728,6 +732,15 @@
         }
     }
 
+    // Called by Browser when removed.
+    void onRemovedFromBrowser() {
+        if (mDestroyOnRemove) {
+            unregisterTab(this);
+            mDestroyOnRemove = false;
+            mImpl = null;
+        }
+    }
+
     private static final class WebMessageCallbackClientImpl extends IWebMessageCallbackClient.Stub {
         private final WebMessageCallback mCallback;
         // Maps from id of IWebMessageReplyProxy to WebMessageReplyProxy. This is done to avoid AIDL
@@ -794,10 +807,12 @@
             // Browser.prepareForDestroy()).
             if (WebLayer.getSupportedMajorVersionInternal() >= 87) {
                 unregisterTab(Tab.this);
+                // Ensure that the app will fail fast if the embedder mistakenly tries to call back
+                // into the implementation via this Tab.
+                mImpl = null;
+            } else {
+                mDestroyOnRemove = true;
             }
-            // Ensure that the app will fail fast if the embedder mistakenly tries to call back
-            // into the implementation via this Tab.
-            mImpl = null;
         }
 
         @Override