diff --git a/DEPS b/DEPS
index cfd1cfc..7c06721 100644
--- a/DEPS
+++ b/DEPS
@@ -209,7 +209,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '917f9193d24437f4b004fbf732152c0a6de93b7f',
+  'skia_revision': '163cc912425976a06b42e4ac890312f8aadf0b36',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -221,7 +221,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '7c253fca72fcff1b94f6c0286c0ea3a1deb06d6b',
+  'angle_revision': '69f2fb0066e835463020500ff0b2dd1c6fc551c6',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -236,7 +236,7 @@
   #
   # Note this revision should be updated with
   # third_party/boringssl/roll_boringssl.py, not roll-dep.
-  'boringssl_revision': '3dd9864feace08641e1f856edd2bcca63ba7887f',
+  'boringssl_revision': '7fffa4636cf7647daf981914286d5d32f1beab6d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling google-toolbox-for-mac
   # and whatever else without interference from each other.
@@ -244,7 +244,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling googletest
   # and whatever else without interference from each other.
-  'googletest_revision': '53495a2a7d6ba7e0691a7f3602e9a5324bba6e45',
+  'googletest_revision': '5bcd8e3bb929714e031a542d303f818e5a5af45d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling lighttpd
   # and whatever else without interference from each other.
@@ -280,7 +280,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '859c953d28cdafdd5f5cb800d5cb93254ba05aaa',
+  'catapult_revision': 'df0a59ab0257c0554d034a8012e277fcbbfc0ab4',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -288,7 +288,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': '9ac07f536e4319db5e57758794ff89d23f76e852',
+  'devtools_frontend_revision': '5eb699fc084a164e1197dbbb0930251bfb41278d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -328,7 +328,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': 'cabd60d991487b64ddc99dc283b9fa2090d668e9',
+  'dawn_revision': '9c375faf4cba48d876f30806e97678ec0ecd59bf',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -966,7 +966,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'b9236628c6b695f933d0dcc7ef2b90d3507c90ce',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '4bb3a7d00d3729799234529309eb125caef35a3b',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1352,7 +1352,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '853a6d0426035a895bd99a1a89764b9d8d7fac20',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'b9f666ed45833fe05f0a3cb51b4676ca713d72a4',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1550,7 +1550,7 @@
   'src/third_party/usrsctp/usrsctplib':
     Var('chromium_git') + '/external/github.com/sctplab/usrsctp' + '@' + '22ba62ffe79c3881581ab430368bf3764d9533eb',
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@3bb8c29ac9c715d4745bd297bfff1edf94cbe9ab',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@4d0e65d25f375c67b5712a9c9015ca818e37932c',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + '732a76d9d3c70d6aa487216495eeb28518349c3a',
@@ -1580,7 +1580,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'a0b8774ce8cec1dc8f4308810bf05eb8867c62de',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '6e5cab434bacd6fcbb833b8e5cb8ff47495d4a64',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'c870b50928bb9234f99a2631a9b2a79124dc6da8',
 
   'src/third_party/webrtc':
     Var('webrtc_git') + '/src.git' + '@' + '2e1546887be3362743e111b1e3b99637f9c73aba',
@@ -1644,7 +1644,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@c80e28e8f5994ea0cc6e269a364622ad6d444da7',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@29ee958645e4c40c63a1fe5aaaaa7e904f378a46',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/java/src/org/chromium/android_webview/common/SafeModeController.java b/android_webview/java/src/org/chromium/android_webview/common/SafeModeController.java
index e9a16f4..5d7df50f 100644
--- a/android_webview/java/src/org/chromium/android_webview/common/SafeModeController.java
+++ b/android_webview/java/src/org/chromium/android_webview/common/SafeModeController.java
@@ -12,6 +12,8 @@
 import androidx.annotation.VisibleForTesting;
 
 import org.chromium.base.ContextUtils;
+import org.chromium.base.Log;
+import org.chromium.base.StrictModeContext;
 import org.chromium.build.BuildConfig;
 
 import java.util.HashSet;
@@ -24,6 +26,8 @@
     public static final String SAFE_MODE_STATE_COMPONENT =
             "org.chromium.android_webview.SafeModeState";
 
+    private static final String TAG = "WebViewSafeMode";
+
     private SafeModeAction[] mRegisteredActions;
 
     private SafeModeController() {}
@@ -79,7 +83,12 @@
         }
         for (SafeModeAction action : mRegisteredActions) {
             if (actionsToExecute.contains(action.getId())) {
-                action.execute();
+                // Allow SafeModeActions in general to perform disk reads and writes.
+                try (StrictModeContext ignored = StrictModeContext.allowDiskWrites()) {
+                    Log.i(TAG, "Starting to execute %s", action.getId());
+                    action.execute();
+                    Log.i(TAG, "Finished executing %s", action.getId());
+                }
             }
         }
     }
diff --git a/android_webview/java/src/org/chromium/android_webview/variations/VariationsSeedSafeModeAction.java b/android_webview/java/src/org/chromium/android_webview/variations/VariationsSeedSafeModeAction.java
index eafb664..5e58b47 100644
--- a/android_webview/java/src/org/chromium/android_webview/variations/VariationsSeedSafeModeAction.java
+++ b/android_webview/java/src/org/chromium/android_webview/variations/VariationsSeedSafeModeAction.java
@@ -35,8 +35,14 @@
     }
 
     private static void deleteIfExists(File file) {
-        if (file.exists() && !file.delete()) {
-            Log.e(TAG, "Failed to delete " + file);
+        if (!file.exists()) {
+            Log.i(TAG, "File does not exist (skipping): %s", file);
+            return;
+        }
+        if (file.delete()) {
+            Log.i(TAG, "Successfully deleted %s", file);
+        } else {
+            Log.e(TAG, "Failed to delete %s", file);
         }
     }
 }
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 185201aa..9246f38 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -3716,6 +3716,16 @@
         Unknown
       </message>
 
+      <!-- SODA Download strings -->
+      <message name="IDS_ASH_ACCESSIBILITY_DICTATION_SETTING_SUBTITLE_SODA_DOWNLOAD_COMPLETE" desc="Description explaining that the speech recognition library download has completed.">
+        Speech files downloaded
+      </message>
+      <message name="IDS_ASH_ACCESSIBILITY_DICTATION_SETTING_SUBTITLE_SODA_DOWNLOAD_PROGRESS" desc="Description explaining the progress for the speech recognition library download.">
+        Downloading speech recognition files... <ph name="PERCENT">$1<ex>17</ex></ph>%
+      </message>
+      <message name="IDS_ASH_ACCESSIBILITY_DICTATION_SETTING_SUBTITLE_SODA_DOWNLOAD_ERROR" desc="Description explaining that there was an error with the speech recognition library download.">
+        Can't download speech files. Try again later.
+      </message>
     </messages>
   </release>
 </grit>
diff --git a/ash/ash_strings_grd/IDS_ASH_ACCESSIBILITY_DICTATION_SETTING_SUBTITLE_SODA_DOWNLOAD_COMPLETE.png.sha1 b/ash/ash_strings_grd/IDS_ASH_ACCESSIBILITY_DICTATION_SETTING_SUBTITLE_SODA_DOWNLOAD_COMPLETE.png.sha1
new file mode 100644
index 0000000..cbac5c3
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_ACCESSIBILITY_DICTATION_SETTING_SUBTITLE_SODA_DOWNLOAD_COMPLETE.png.sha1
@@ -0,0 +1 @@
+cf863cd2db3089c7b4f2148920f89cf64162ea0e
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_ACCESSIBILITY_DICTATION_SETTING_SUBTITLE_SODA_DOWNLOAD_ERROR.png.sha1 b/ash/ash_strings_grd/IDS_ASH_ACCESSIBILITY_DICTATION_SETTING_SUBTITLE_SODA_DOWNLOAD_ERROR.png.sha1
new file mode 100644
index 0000000..b834c87
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_ACCESSIBILITY_DICTATION_SETTING_SUBTITLE_SODA_DOWNLOAD_ERROR.png.sha1
@@ -0,0 +1 @@
+57566ae221e8244cd3331693cd7f6063b2136b55
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_ACCESSIBILITY_DICTATION_SETTING_SUBTITLE_SODA_DOWNLOAD_PROGRESS.png.sha1 b/ash/ash_strings_grd/IDS_ASH_ACCESSIBILITY_DICTATION_SETTING_SUBTITLE_SODA_DOWNLOAD_PROGRESS.png.sha1
new file mode 100644
index 0000000..8d46ec8
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_ACCESSIBILITY_DICTATION_SETTING_SUBTITLE_SODA_DOWNLOAD_PROGRESS.png.sha1
@@ -0,0 +1 @@
+7ca1c7938e309dcddcfb26417382c9f3c54b33c4
\ No newline at end of file
diff --git a/ash/components/audio/audio_devices_pref_handler_impl.cc b/ash/components/audio/audio_devices_pref_handler_impl.cc
index 0559e09e..05c04f7 100644
--- a/ash/components/audio/audio_devices_pref_handler_impl.cc
+++ b/ash/components/audio/audio_devices_pref_handler_impl.cc
@@ -405,7 +405,7 @@
   registry->RegisterDictionaryPref(prefs::kAudioDevicesGainPercent);
   registry->RegisterDictionaryPref(prefs::kAudioDevicesMute);
   registry->RegisterDictionaryPref(prefs::kAudioDevicesState);
-  registry->RegisterBooleanPref(prefs::kInputNoiseCancellationEnabled, false);
+  registry->RegisterBooleanPref(prefs::kInputNoiseCancellationEnabled, true);
 
   // Register the prefs backing the audio muting policies.
   // Policy for audio input is handled by kAudioCaptureAllowed in the Chrome
diff --git a/ash/components/audio/audio_devices_pref_handler_impl_unittest.cc b/ash/components/audio/audio_devices_pref_handler_impl_unittest.cc
index 9f0d8e8..fac0c22 100644
--- a/ash/components/audio/audio_devices_pref_handler_impl_unittest.cc
+++ b/ash/components/audio/audio_devices_pref_handler_impl_unittest.cc
@@ -453,9 +453,9 @@
 }
 
 TEST_P(AudioDevicesPrefHandlerTest, InputNoiseCancellationPrefRegistered) {
-  EXPECT_FALSE(audio_pref_handler_->GetNoiseCancellationState());
-  audio_pref_handler_->SetNoiseCancellationState(true);
   EXPECT_TRUE(audio_pref_handler_->GetNoiseCancellationState());
+  audio_pref_handler_->SetNoiseCancellationState(false);
+  EXPECT_FALSE(audio_pref_handler_->GetNoiseCancellationState());
 }
 
 }  // namespace ash
diff --git a/ash/components/audio/audio_devices_pref_handler_stub.h b/ash/components/audio/audio_devices_pref_handler_stub.h
index 880d2fc..9b754c0d 100644
--- a/ash/components/audio/audio_devices_pref_handler_stub.h
+++ b/ash/components/audio/audio_devices_pref_handler_stub.h
@@ -57,7 +57,7 @@
   AudioDeviceVolumeGain audio_device_volume_gain_map_;
   AudioDeviceStateMap audio_device_state_map_;
 
-  bool noise_cancellation_state_ = false;
+  bool noise_cancellation_state_ = true;
 
   DISALLOW_COPY_AND_ASSIGN(AudioDevicesPrefHandlerStub);
 };
diff --git a/ash/components/audio/cras_audio_handler.cc b/ash/components/audio/cras_audio_handler.cc
index d326647f..fd2eafa 100644
--- a/ash/components/audio/cras_audio_handler.cc
+++ b/ash/components/audio/cras_audio_handler.cc
@@ -1937,7 +1937,6 @@
 }
 
 bool CrasAudioHandler::noise_cancellation_supported() const {
-  DCHECK(main_task_runner_->BelongsToCurrentThread());
   return noise_cancellation_supported_;
 }
 
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index c8997836..fc0d68d5 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -462,7 +462,7 @@
 
 // Enables or disables noise cancellation UI toggle.
 const base::Feature kEnableInputNoiseCancellationUi{
-    "EnableInputNoiseCancellationUi", base::FEATURE_DISABLED_BY_DEFAULT};
+    "EnableInputNoiseCancellationUi", base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Enables to use lacros-chrome as a primary web browser on Chrome OS.
 // This works only when LacrosSupport below is enabled.
diff --git a/ash/content/file_manager/BUILD.gn b/ash/content/file_manager/BUILD.gn
index 55381e0..55c6bfde 100644
--- a/ash/content/file_manager/BUILD.gn
+++ b/ash/content/file_manager/BUILD.gn
@@ -2,15 +2,14 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//build/buildflag_header.gni")
-import("//chrome/common/features.gni")
-
 assert(is_chromeos, "File Manager is Chrome OS only")
 assert(!is_official_build, "File Manager is only built for unofficial builds")
 
-buildflag_header("buildflags") {
-  header = "buildflags.h"
-  flags = [ "OPTIMIZE_WEBUI=$optimize_webui" ]
+static_library("constants") {
+  sources = [
+    "url_constants.cc",
+    "url_constants.h",
+  ]
 }
 
 static_library("file_manager_ui") {
@@ -25,7 +24,7 @@
   ]
 
   deps = [
-    ":buildflags",
+    ":constants",
     "//ash/content/file_manager/mojom",
     "//ash/content/file_manager/resources:file_manager_swa_resources",
     "//base",
diff --git a/ash/content/file_manager/file_manager_ui.cc b/ash/content/file_manager/file_manager_ui.cc
index 660ffc9..19817a7 100644
--- a/ash/content/file_manager/file_manager_ui.cc
+++ b/ash/content/file_manager/file_manager_ui.cc
@@ -4,15 +4,12 @@
 
 #include "ash/content/file_manager/file_manager_ui.h"
 
-#include "ash/content/file_manager/buildflags.h"
 #include "ash/content/file_manager/file_manager_page_handler.h"
 #include "ash/content/file_manager/resources/grit/file_manager_swa_resources.h"
 #include "ash/content/file_manager/resources/grit/file_manager_swa_resources_map.h"
 #include "ash/content/file_manager/url_constants.h"
 #include "base/memory/ptr_util.h"
-#include "base/strings/strcat.h"
 #include "base/strings/string_util.h"
-#include "build/build_config.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_ui.h"
 #include "content/public/browser/web_ui_data_source.h"
@@ -35,15 +32,6 @@
       // so we remove the leading file_manager/ to match the existing paths.
       base::ReplaceFirstSubstringAfterOffset(&path, 0, "file_manager/", "");
       source->AddResourcePath(path, entries[i].id);
-
-#if !BUILDFLAG(OPTIMIZE_WEBUI)
-      // When optmize_webui=false, the SWA loads individual JS files from the
-      // file_manager extension, so we add the same resource with the path
-      // relative from the SWA to the extension:
-      std::string path_from_swa(
-          base::StrCat({"ui/file_manager/", entries[i].path}));
-      source->AddResourcePath(path_from_swa, entries[i].id);
-#endif
     }
   }
 }
diff --git a/ash/content/file_manager/resources/BUILD.gn b/ash/content/file_manager/resources/BUILD.gn
index 4809b9f..3b6177c9 100644
--- a/ash/content/file_manager/resources/BUILD.gn
+++ b/ash/content/file_manager/resources/BUILD.gn
@@ -33,17 +33,22 @@
   deps = [ "//ash/content/file_manager/mojom:mojom_webui_js" ]
 }
 
+files_app_path =
+    rebase_path("$root_gen_dir/ui/file_manager/preprocessed/file_manager",
+                root_build_dir)
+
 js_type_check("closure_compile") {
   deps = [
     ":main_js",
     "//ui/webui/resources/js:cr",
     "//ui/webui/resources/js:load_time_data",
   ]
-  closure_flags =
-      default_closure_args + mojom_js_args + [
-        "js_module_root=" + rebase_path(".", root_build_dir),
-        "js_module_root=" + rebase_path(mojom_webui_folder, root_build_dir),
-      ]
+  closure_flags = default_closure_args + mojom_js_args + [
+                    "js_module_root=" + rebase_path(".", root_build_dir),
+                    "js_module_root=" +
+                        rebase_path(mojom_webui_folder, root_build_dir),
+                    "browser_resolver_prefix_replacements=\"chrome-extension://hhaomjibdihmijegdhdafkllkbggdgoj/=../../ui/file_manager/file_manager/\"",
+                  ]
 }
 
 action("gen_main_html") {
@@ -102,6 +107,9 @@
 
   excludes = [ "chrome://resources/mojo/mojo/public/js/bindings.js" ]
 
+  external_paths =
+      [ "chrome-extension://hhaomjibdihmijegdhdafkllkbggdgoj|$files_app_path" ]
+
   deps = [
     ":preprocess",
     ":preprocess_mojo",
diff --git a/ash/content/file_manager/resources/crt0.js b/ash/content/file_manager/resources/crt0.js
index 0bbf4b1..cdb0e3a9 100644
--- a/ash/content/file_manager/resources/crt0.js
+++ b/ash/content/file_manager/resources/crt0.js
@@ -10,6 +10,15 @@
 window.isSWA = true;
 
 /**
+ * Sets window.IN_TEST if this code is run in the test environment. We
+ * detect this by checking for presence of domAutomationController.
+ * @const {boolean}
+ */
+window.IN_TEST = window.IN_TEST || (() => {
+  return window.domAutomationController ? true : undefined;
+})();
+
+/**
  * Listener service to local chrome.*{add,remove}Listener clients.
  */
 // eslint-disable-next-line
diff --git a/ash/content/file_manager/resources/main.js b/ash/content/file_manager/resources/main.js
index 828f126..399b98b 100644
--- a/ash/content/file_manager/resources/main.js
+++ b/ash/content/file_manager/resources/main.js
@@ -14,9 +14,9 @@
  */
 import {BrowserProxy} from './browser_proxy.js'
 import {ScriptLoader} from './script_loader.js'
-import {VolumeManagerImpl} from '../../../../../ui/file_manager/file_manager/background/js/volume_manager_impl.m.js';
-import '../../../../../ui/file_manager/file_manager/background/js/metrics_start.m.js';
-import {background} from '../../../../../ui/file_manager/file_manager/background/js/background.m.js';
+import {VolumeManagerImpl} from 'chrome-extension://hhaomjibdihmijegdhdafkllkbggdgoj/background/js/volume_manager_impl.m.js';
+import 'chrome-extension://hhaomjibdihmijegdhdafkllkbggdgoj/background/js/metrics_start.m.js';
+import {background} from 'chrome-extension://hhaomjibdihmijegdhdafkllkbggdgoj/background/js/background.m.js';
 
 /**
  * Represents file manager application. Starting point for the application
diff --git a/ash/public/cpp/ash_features.cc b/ash/public/cpp/ash_features.cc
index f3b5748..0da3110 100644
--- a/ash/public/cpp/ash_features.cc
+++ b/ash/public/cpp/ash_features.cc
@@ -8,7 +8,6 @@
 #include "base/command_line.h"
 #include "base/feature_list.h"
 #include "build/build_config.h"
-#include "components/full_restore/features.h"
 
 namespace ash {
 namespace features {
diff --git a/ash/system/accessibility/tray_accessibility.cc b/ash/system/accessibility/tray_accessibility.cc
index 1d27150..585bd85 100644
--- a/ash/system/accessibility/tray_accessibility.cc
+++ b/ash/system/accessibility/tray_accessibility.cc
@@ -25,8 +25,10 @@
 #include "ash/system/tray/tri_view.h"
 #include "base/bind.h"
 #include "base/metrics/user_metrics.h"
+#include "ui/accessibility/accessibility_switches.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/image/image.h"
+#include "ui/views/controls/label.h"
 #include "ui/views/controls/separator.h"
 
 namespace ash {
@@ -77,6 +79,12 @@
   AppendAccessibilityList();
   CreateTitleRow(IDS_ASH_STATUS_TRAY_ACCESSIBILITY_TITLE);
   Layout();
+  UpdateSodaInstallerObserverStatus();
+}
+
+AccessibilityDetailedView::~AccessibilityDetailedView() {
+  if (::switches::IsExperimentalAccessibilityDictationOfflineEnabled())
+    speech::SodaInstaller::GetInstance()->RemoveObserver(this);
 }
 
 void AccessibilityDetailedView::OnAccessibilityStatusChanged() {
@@ -102,6 +110,7 @@
     dictation_enabled_ = controller->dictation().enabled();
     TrayPopupUtils::UpdateCheckMarkVisibility(dictation_view_,
                                               dictation_enabled_);
+    UpdateSodaInstallerObserverStatus();
   }
 
   if (high_contrast_view_ && controller->IsHighContrastSettingVisibleInTray()) {
@@ -526,5 +535,62 @@
   }
 }
 
+void AccessibilityDetailedView::UpdateSodaInstallerObserverStatus() {
+  if (!::switches::IsExperimentalAccessibilityDictationOfflineEnabled())
+    return;
+
+  bool dictation_enabled =
+      Shell::Get()->accessibility_controller()->dictation().enabled();
+  speech::SodaInstaller* soda_installer = speech::SodaInstaller::GetInstance();
+  if (!dictation_enabled)
+    soda_installer->RemoveObserver(this);
+
+  if (dictation_enabled && !soda_installer->IsSodaInstalled()) {
+    // Make sure this view observes SODA installation. We only want to update
+    // the user of the installation status if dictation is enabled.
+    soda_installer->AddObserver(this);
+  }
+}
+
+// SodaInstaller::Observer:
+void AccessibilityDetailedView::OnSodaInstalled() {
+  speech::SodaInstaller::GetInstance()->RemoveObserver(this);
+  AccessibilityControllerImpl* controller =
+      Shell::Get()->accessibility_controller();
+  if (dictation_view_ && controller->IsDictationSettingVisibleInTray()) {
+    dictation_view_->SetSubText(l10n_util::GetStringUTF16(
+        IDS_ASH_ACCESSIBILITY_DICTATION_SETTING_SUBTITLE_SODA_DOWNLOAD_COMPLETE));
+  }
+}
+
+void AccessibilityDetailedView::OnSodaError() {
+  AccessibilityControllerImpl* controller =
+      Shell::Get()->accessibility_controller();
+  if (dictation_view_ && controller->IsDictationSettingVisibleInTray()) {
+    dictation_view_->SetSubText(l10n_util::GetStringUTF16(
+        IDS_ASH_ACCESSIBILITY_DICTATION_SETTING_SUBTITLE_SODA_DOWNLOAD_ERROR));
+  }
+}
+
+void AccessibilityDetailedView::OnSodaProgress(int combined_progress) {
+  AccessibilityControllerImpl* controller =
+      Shell::Get()->accessibility_controller();
+  if (dictation_view_ && controller->IsDictationSettingVisibleInTray()) {
+    dictation_view_->SetSubText(l10n_util::GetStringFUTF16Int(
+        IDS_ASH_ACCESSIBILITY_DICTATION_SETTING_SUBTITLE_SODA_DOWNLOAD_PROGRESS,
+        combined_progress));
+  }
+}
+
+void AccessibilityDetailedView::SetDictationViewSubtitleTextForTesting(
+    std::u16string text) {
+  dictation_view_->SetSubText(text);
+}
+
+std::u16string
+AccessibilityDetailedView::GetDictationViewSubtitleTextForTesting() {
+  return dictation_view_->sub_text_label()->GetText();
+}
+
 }  // namespace tray
 }  // namespace ash
diff --git a/ash/system/accessibility/tray_accessibility.h b/ash/system/accessibility/tray_accessibility.h
index 4177cfe..6698e24 100644
--- a/ash/system/accessibility/tray_accessibility.h
+++ b/ash/system/accessibility/tray_accessibility.h
@@ -12,6 +12,7 @@
 #include "ash/public/cpp/session/session_observer.h"
 #include "ash/system/tray/tray_detailed_view.h"
 #include "base/macros.h"
+#include "components/soda/soda_installer.h"
 #include "ui/gfx/font.h"
 #include "ui/views/controls/button/button.h"
 #include "ui/views/view.h"
@@ -35,12 +36,14 @@
 namespace tray {
 
 // Create the detailed view of accessibility tray.
-class ASH_EXPORT AccessibilityDetailedView : public TrayDetailedView {
+class ASH_EXPORT AccessibilityDetailedView
+    : public TrayDetailedView,
+      public speech::SodaInstaller::Observer {
  public:
   static constexpr char kClassName[] = "AccessibilityDetailedView";
 
   explicit AccessibilityDetailedView(DetailedViewDelegate* delegate);
-  ~AccessibilityDetailedView() override {}
+  ~AccessibilityDetailedView() override;
 
   void OnAccessibilityStatusChanged();
 
@@ -65,6 +68,22 @@
   // Add the accessibility feature list.
   void AppendAccessibilityList();
 
+  void UpdateSodaInstallerObserverStatus();
+
+  // SodaInstaller::Observer:
+  void OnSodaInstalled() override;
+  void OnSodaLanguagePackInstalled(
+      speech::LanguageCode language_code) override {}
+  void OnSodaError() override;
+  void OnSodaLanguagePackError(speech::LanguageCode language_code) override {}
+  void OnSodaProgress(int combined_progress) override;
+  void OnSodaLanguagePackProgress(int language_progress,
+                                  speech::LanguageCode language_code) override {
+  }
+
+  void SetDictationViewSubtitleTextForTesting(std::u16string text);
+  std::u16string GetDictationViewSubtitleTextForTesting();
+
   HoverHighlightView* spoken_feedback_view_ = nullptr;
   HoverHighlightView* select_to_speak_view_ = nullptr;
   HoverHighlightView* dictation_view_ = nullptr;
diff --git a/ash/system/accessibility/tray_accessibility_unittest.cc b/ash/system/accessibility/tray_accessibility_unittest.cc
index 6f1154b..698a5c5 100644
--- a/ash/system/accessibility/tray_accessibility_unittest.cc
+++ b/ash/system/accessibility/tray_accessibility_unittest.cc
@@ -15,8 +15,11 @@
 #include "ash/system/tray/detailed_view_delegate.h"
 #include "ash/system/tray/hover_highlight_view.h"
 #include "ash/test/ash_test_base.h"
+#include "base/command_line.h"
 #include "base/macros.h"
 #include "components/prefs/pref_service.h"
+#include "components/soda/soda_installer_impl_chromeos.h"
+#include "ui/accessibility/accessibility_switches.h"
 #include "ui/accessibility/ax_enums.mojom-shared.h"
 #include "ui/accessibility/ax_node_data.h"
 
@@ -103,9 +106,18 @@
   void SetUp() override {
     AshTestBase::SetUp();
     Shell::Get()->accessibility_controller()->AddObserver(this);
+    // Since this test suite is part of ash unit tests, the
+    // SodaInstallerImplChromeOS is never created (it's normally created when
+    // ChromeBrowserMainPartsChromeos initializes). Create it here so that
+    // calling speech::SodaInstaller::GetInstance() returns a valid instance.
+    soda_installer_impl_ =
+        std::make_unique<speech::SodaInstallerImplChromeOS>();
+    speech::SodaInstaller::GetInstance()->UninstallSodaForTesting();
   }
 
   void TearDown() override {
+    speech::SodaInstaller::GetInstance()->UninstallSodaForTesting();
+    soda_installer_impl_.reset();
     Shell::Get()->accessibility_controller()->RemoveObserver(this);
     AshTestBase::TearDown();
   }
@@ -361,6 +373,22 @@
     return detailed_menu_->GetClassName();
   }
 
+  void OnSodaInstalled() { detailed_menu_->OnSodaInstalled(); }
+
+  void OnSodaError() { detailed_menu_->OnSodaError(); }
+
+  void OnSodaProgress(int progress) {
+    detailed_menu_->OnSodaProgress(progress);
+  }
+
+  void SetDictationViewSubtitleText(std::u16string text) {
+    detailed_menu_->SetDictationViewSubtitleTextForTesting(text);
+  }
+
+  std::u16string GetDictationViewSubtitleText() {
+    return detailed_menu_->GetDictationViewSubtitleTextForTesting();
+  }
+
  private:
   // AccessibilityObserver:
   void OnAccessibilityStatusChanged() override {
@@ -373,6 +401,7 @@
 
   std::unique_ptr<DetailedViewDelegate> delegate_;
   std::unique_ptr<tray::AccessibilityDetailedView> detailed_menu_;
+  std::unique_ptr<speech::SodaInstallerImplChromeOS> soda_installer_impl_;
 
   DISALLOW_COPY_AND_ASSIGN(TrayAccessibilityTest);
 };
@@ -624,6 +653,67 @@
             GetDetailedViewClassName());
 }
 
+// Ensures that the dictation subtitle text is changed to the correct value
+// when calling various methods.
+TEST_F(TrayAccessibilityTest, SodaDownloadUnitTest) {
+  CreateDetailedMenu();
+  // We need to set the subtitle text before we try to retrieve it.
+  SetDictationViewSubtitleText(u"This is a test");
+  EXPECT_EQ(u"This is a test", GetDictationViewSubtitleText());
+  OnSodaInstalled();
+  EXPECT_EQ(u"Speech files downloaded", GetDictationViewSubtitleText());
+
+  SetDictationViewSubtitleText(u"This is a test");
+  EXPECT_EQ(u"This is a test", GetDictationViewSubtitleText());
+  OnSodaError();
+  EXPECT_EQ(u"Can't download speech files. Try again later.",
+            GetDictationViewSubtitleText());
+
+  SetDictationViewSubtitleText(u"This is a test");
+  EXPECT_EQ(u"This is a test", GetDictationViewSubtitleText());
+  OnSodaProgress(50);
+  EXPECT_EQ(u"Downloading speech recognition files… 50%",
+            GetDictationViewSubtitleText());
+}
+
+// Ensures that we don't respond to soda download updates when dictation is off.
+TEST_F(TrayAccessibilityTest, SodaDownloadDictationDisabled) {
+  base::CommandLine::ForCurrentProcess()->AppendSwitch(
+      ::switches::kEnableExperimentalAccessibilityDictationOffline);
+  CreateDetailedMenu();
+  EnableDictation(false);
+  SetDictationViewSubtitleText(u"This is a test");
+  EXPECT_EQ(u"This is a test", GetDictationViewSubtitleText());
+  speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting();
+  EXPECT_EQ(u"This is a test", GetDictationViewSubtitleText());
+}
+
+// Ensures that we respond to soda download updates when dictation is on.
+// For this test, we enable dictation before the menu is created.
+TEST_F(TrayAccessibilityTest, SodaDownloadDictationEnabledBeforeMenuCreated) {
+  base::CommandLine::ForCurrentProcess()->AppendSwitch(
+      ::switches::kEnableExperimentalAccessibilityDictationOffline);
+  EnableDictation(true);
+  CreateDetailedMenu();
+  SetDictationViewSubtitleText(u"This is a test");
+  EXPECT_EQ(u"This is a test", GetDictationViewSubtitleText());
+  speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting();
+  EXPECT_EQ(u"Speech files downloaded", GetDictationViewSubtitleText());
+}
+
+// Ensures that we respond to soda download updates when dictation is on.
+// For this test, we enable dictation after the menu is created.
+TEST_F(TrayAccessibilityTest, SodaDownloadDictationEnabledAfterMenuCreated) {
+  base::CommandLine::ForCurrentProcess()->AppendSwitch(
+      ::switches::kEnableExperimentalAccessibilityDictationOffline);
+  CreateDetailedMenu();
+  EnableDictation(true);
+  SetDictationViewSubtitleText(u"This is a test");
+  EXPECT_EQ(u"This is a test", GetDictationViewSubtitleText());
+  speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting();
+  EXPECT_EQ(u"Speech files downloaded", GetDictationViewSubtitleText());
+}
+
 class TrayAccessibilityLoginScreenTest : public TrayAccessibilityTest {
  protected:
   TrayAccessibilityLoginScreenTest() { set_start_session(false); }
diff --git a/ash/system/audio/unified_audio_detailed_view_controller_unittest.cc b/ash/system/audio/unified_audio_detailed_view_controller_unittest.cc
index f5bcf2f..51a5107 100644
--- a/ash/system/audio/unified_audio_detailed_view_controller_unittest.cc
+++ b/ash/system/audio/unified_audio_detailed_view_controller_unittest.cc
@@ -231,7 +231,7 @@
 
   views::ToggleButton* toggle =
       (views::ToggleButton*)toggles_map_[internal_mic.id]->children()[1];
-  EXPECT_FALSE(toggle->GetIsOn());
+  EXPECT_TRUE(toggle->GetIsOn());
 }
 
 TEST_F(UnifiedAudioDetailedViewControllerTest,
@@ -239,7 +239,7 @@
   scoped_feature_list_.InitAndEnableFeature(
       features::kEnableInputNoiseCancellationUi);
 
-  audio_pref_handler_->SetNoiseCancellationState(true);
+  audio_pref_handler_->SetNoiseCancellationState(false);
 
   fake_cras_audio_client()->SetAudioNodesAndNotifyObserversForTesting(
       GenerateAudioNodeList({kInternalMic, kMicJack, kFrontMic, kRearMic}));
@@ -256,8 +256,8 @@
       (views::ToggleButton*)toggles_map_[internal_mic.id]->children()[1];
 
   // The toggle loaded the pref correctly.
-  EXPECT_TRUE(toggle->GetIsOn());
-  EXPECT_TRUE(audio_pref_handler_->GetNoiseCancellationState());
+  EXPECT_FALSE(toggle->GetIsOn());
+  EXPECT_FALSE(audio_pref_handler_->GetNoiseCancellationState());
 
   ui::MouseEvent press(ui::ET_MOUSE_PRESSED, gfx::PointF(), gfx::PointF(),
                        ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
@@ -266,11 +266,11 @@
   // Flipping the toggle.
   views::test::ButtonTestApi(toggle).NotifyClick(press);
   // The new state of the toggle must be saved to the prefs.
-  EXPECT_FALSE(audio_pref_handler_->GetNoiseCancellationState());
+  EXPECT_TRUE(audio_pref_handler_->GetNoiseCancellationState());
 
   // Flipping back and checking the prefs again.
   views::test::ButtonTestApi(toggle).NotifyClick(press);
-  EXPECT_TRUE(audio_pref_handler_->GetNoiseCancellationState());
+  EXPECT_FALSE(audio_pref_handler_->GetNoiseCancellationState());
 }
 
 // TODO(1205197): Remove this test once the flag is removed.
diff --git a/base/allocator/allocator_interception_mac.mm b/base/allocator/allocator_interception_mac.mm
index 470b958..c7beb55 100644
--- a/base/allocator/allocator_interception_mac.mm
+++ b/base/allocator/allocator_interception_mac.mm
@@ -234,7 +234,7 @@
 #if defined(OS_IOS)
   return !base::ios::IsRunningOnOrLater(14, 0, 0);
 #else
-  return !base::mac::IsOSLaterThan11_DontCallThis();
+  return !base::mac::IsOSLaterThan12_DontCallThis();
 #endif
 }
 
diff --git a/base/mac/mac_util.h b/base/mac/mac_util.h
index 23c46c6..8b15b95 100644
--- a/base/mac/mac_util.h
+++ b/base/mac/mac_util.h
@@ -117,9 +117,6 @@
     return internal::MacOSVersion() >= 1000 + V;                    \
   }
 
-// TODO(https://crbug.com/1105187): Update MAC_OS_X_VERSION_MIN_REQUIRED to
-// whatever macro it turns into in the future.
-
 #define DEFINE_IS_OS_FUNCS_CR_MIN_REQUIRED(V, DEPLOYMENT_TARGET_TEST) \
   inline bool IsOS##V() {                                             \
     DEPLOYMENT_TARGET_TEST(>, V, false)                               \
@@ -161,21 +158,22 @@
 DEFINE_OLD_IS_OS_FUNCS(12, OLD_TEST_DEPLOYMENT_TARGET)
 DEFINE_OLD_IS_OS_FUNCS(13, OLD_TEST_DEPLOYMENT_TARGET)
 DEFINE_OLD_IS_OS_FUNCS(14, OLD_TEST_DEPLOYMENT_TARGET)
+DEFINE_OLD_IS_OS_FUNCS(15, OLD_TEST_DEPLOYMENT_TARGET)
 
 // Versions of macOS supported at runtime and whose SDK is supported for
 // building.
-#ifdef MAC_OS_X_VERSION_10_15
-DEFINE_OLD_IS_OS_FUNCS(15, OLD_TEST_DEPLOYMENT_TARGET)
-#else
-DEFINE_OLD_IS_OS_FUNCS(15, IGNORE_DEPLOYMENT_TARGET)
-#endif
-
 #ifdef MAC_OS_VERSION_11_0
 DEFINE_IS_OS_FUNCS(11, TEST_DEPLOYMENT_TARGET)
 #else
 DEFINE_IS_OS_FUNCS(11, IGNORE_DEPLOYMENT_TARGET)
 #endif
 
+#ifdef MAC_OS_VERSION_12_0
+DEFINE_IS_OS_FUNCS(12, TEST_DEPLOYMENT_TARGET)
+#else
+DEFINE_IS_OS_FUNCS(12, IGNORE_DEPLOYMENT_TARGET)
+#endif
+
 #undef DEFINE_OLD_IS_OS_FUNCS_CR_MIN_REQUIRED
 #undef DEFINE_OLD_IS_OS_FUNCS
 #undef DEFINE_IS_OS_FUNCS_CR_MIN_REQUIRED
@@ -187,8 +185,8 @@
 // This should be infrequently used. It only makes sense to use this to avoid
 // codepaths that are very likely to break on future (unreleased, untested,
 // unborn) OS releases, or to log when the OS is newer than any known version.
-inline bool IsOSLaterThan11_DontCallThis() {
-  return !IsAtMostOS11();
+inline bool IsOSLaterThan12_DontCallThis() {
+  return !IsAtMostOS12();
 }
 
 enum class CPUType {
diff --git a/base/mac/mac_util.mm b/base/mac/mac_util.mm
index 7c7c3e8..3587a5ca 100644
--- a/base/mac/mac_util.mm
+++ b/base/mac/mac_util.mm
@@ -388,9 +388,6 @@
   // correspondence between Darwin's major version numbers and macOS major
   // version numbers.
   int macos_major_version = darwin_major_version - 9;
-  DLOG_IF(WARNING, darwin_major_version > 20)
-      << "Assuming Darwin " << base::NumberToString(darwin_major_version)
-      << " is macOS " << base::NumberToString(macos_major_version);
 
   return macos_major_version * 100;
 }
diff --git a/base/mac/mac_util_unittest.mm b/base/mac/mac_util_unittest.mm
index f0a40d18..d21610aa 100644
--- a/base/mac/mac_util_unittest.mm
+++ b/base/mac/mac_util_unittest.mm
@@ -183,8 +183,9 @@
       TEST_FOR_FUTURE_10_OS(14);
       TEST_FOR_FUTURE_10_OS(15);
       TEST_FOR_FUTURE_OS(11);
+      TEST_FOR_FUTURE_OS(12);
 
-      EXPECT_FALSE(IsOSLaterThan11_DontCallThis());
+      EXPECT_FALSE(IsOSLaterThan12_DontCallThis());
     } else if (minor == 12) {
       EXPECT_FALSE(IsOS10_11());
       EXPECT_FALSE(IsAtMostOS10_11());
@@ -194,8 +195,9 @@
       TEST_FOR_FUTURE_10_OS(14);
       TEST_FOR_FUTURE_10_OS(15);
       TEST_FOR_FUTURE_OS(11);
+      TEST_FOR_FUTURE_OS(12);
 
-      EXPECT_FALSE(IsOSLaterThan11_DontCallThis());
+      EXPECT_FALSE(IsOSLaterThan12_DontCallThis());
     } else if (minor == 13) {
       EXPECT_FALSE(IsOS10_11());
       EXPECT_FALSE(IsAtMostOS10_11());
@@ -205,8 +207,9 @@
       TEST_FOR_FUTURE_10_OS(14);
       TEST_FOR_FUTURE_10_OS(15);
       TEST_FOR_FUTURE_OS(11);
+      TEST_FOR_FUTURE_OS(12);
 
-      EXPECT_FALSE(IsOSLaterThan11_DontCallThis());
+      EXPECT_FALSE(IsOSLaterThan12_DontCallThis());
     } else if (minor == 14) {
       EXPECT_FALSE(IsOS10_11());
       EXPECT_FALSE(IsAtMostOS10_11());
@@ -216,8 +219,9 @@
       TEST_FOR_SAME_10_OS(14);
       TEST_FOR_FUTURE_10_OS(15);
       TEST_FOR_FUTURE_OS(11);
+      TEST_FOR_FUTURE_OS(12);
 
-      EXPECT_FALSE(IsOSLaterThan11_DontCallThis());
+      EXPECT_FALSE(IsOSLaterThan12_DontCallThis());
     } else if (minor == 15) {
       EXPECT_FALSE(IsOS10_11());
       EXPECT_FALSE(IsAtMostOS10_11());
@@ -227,11 +231,12 @@
       TEST_FOR_PAST_10_OS(14);
       TEST_FOR_SAME_10_OS(15);
       TEST_FOR_FUTURE_OS(11);
+      TEST_FOR_FUTURE_OS(12);
 
-      EXPECT_FALSE(IsOSLaterThan11_DontCallThis());
+      EXPECT_FALSE(IsOSLaterThan12_DontCallThis());
     } else {
       // macOS 10.15 was the end of the line.
-      EXPECT_TRUE(false);
+      FAIL() << "Unexpected 10.x macOS.";
     }
   } else if (major == 11) {
     EXPECT_FALSE(IsOS10_11());
@@ -242,11 +247,24 @@
     TEST_FOR_PAST_10_OS(14);
     TEST_FOR_PAST_10_OS(15);
     TEST_FOR_SAME_OS(11);
+    TEST_FOR_FUTURE_OS(12);
 
-    EXPECT_FALSE(IsOSLaterThan11_DontCallThis());
+    EXPECT_FALSE(IsOSLaterThan12_DontCallThis());
+  } else if (major == 12) {
+    EXPECT_FALSE(IsOS10_11());
+    EXPECT_FALSE(IsAtMostOS10_11());
+
+    TEST_FOR_PAST_10_OS(12);
+    TEST_FOR_PAST_10_OS(13);
+    TEST_FOR_PAST_10_OS(14);
+    TEST_FOR_PAST_10_OS(15);
+    TEST_FOR_PAST_OS(11);
+    TEST_FOR_SAME_OS(12);
+
+    EXPECT_FALSE(IsOSLaterThan12_DontCallThis());
   } else {
     // The spooky future.
-    EXPECT_FALSE(true);
+    FAIL() << "Time to update the OS macros!";
   }
 }
 
diff --git a/base/threading/platform_thread_linux.cc b/base/threading/platform_thread_linux.cc
index a03c2bb0..15a3f15 100644
--- a/base/threading/platform_thread_linux.cc
+++ b/base/threading/platform_thread_linux.cc
@@ -218,8 +218,15 @@
 
   FilePath latency_sensitive_file = FilePath(thread_dir + "latency_sensitive");
 
-  if (!PathExists(latency_sensitive_file))
-    return;
+  if (PathExists(latency_sensitive_file)) {
+    if (is_urgent && latency_sensitive_urgent) {
+      PLOG_IF(ERROR, !WriteFile(latency_sensitive_file, "1", 1))
+          << "Failed to write latency file.\n";
+    } else {
+      PLOG_IF(ERROR, !WriteFile(latency_sensitive_file, "0", 1))
+          << "Failed to write latency file.\n";
+    }
+  }
 
   // Silently ignore if getattr fails due to sandboxing.
   if (sched_getattr(thread_id, &attr, sizeof(attr), 0) == -1 ||
@@ -239,14 +246,6 @@
       break;
   }
 
-  if (is_urgent && latency_sensitive_urgent) {
-    PLOG_IF(ERROR, !WriteFile(latency_sensitive_file, "1", 1))
-        << "Failed to write latency file.\n";
-  } else {
-    PLOG_IF(ERROR, !WriteFile(latency_sensitive_file, "0", 1))
-        << "Failed to write latency file.\n";
-  }
-
   attr.sched_flags |= SCHED_FLAG_UTIL_CLAMP_MIN;
   attr.sched_flags |= SCHED_FLAG_UTIL_CLAMP_MAX;
 
diff --git a/build/android/pylib/utils/logdog_helper.py b/build/android/pylib/utils/logdog_helper.py
index 68a7ba5..3000a2f7 100644
--- a/build/android/pylib/utils/logdog_helper.py
+++ b/build/android/pylib/utils/logdog_helper.py
@@ -11,9 +11,11 @@
 from pylib import constants
 from pylib.utils import decorators
 
-sys.path.insert(0, os.path.abspath(os.path.join(
-    constants.DIR_SOURCE_ROOT, 'tools', 'swarming_client')))
-from libs.logdog import bootstrap # pylint: disable=import-error
+sys.path.insert(
+    0,
+    os.path.abspath(
+        os.path.join(constants.DIR_SOURCE_ROOT, 'third_party', 'logdog')))
+from logdog import bootstrap  # pylint: disable=import-error
 
 
 @decorators.NoRaiseException(default_return_value='',
diff --git a/build/android/test_runner.pydeps b/build/android/test_runner.pydeps
index 660f8f83..b011417 100644
--- a/build/android/test_runner.pydeps
+++ b/build/android/test_runner.pydeps
@@ -119,15 +119,14 @@
 ../../third_party/jinja2/tests.py
 ../../third_party/jinja2/utils.py
 ../../third_party/jinja2/visitor.py
+../../third_party/logdog/logdog/__init__.py
+../../third_party/logdog/logdog/bootstrap.py
+../../third_party/logdog/logdog/stream.py
+../../third_party/logdog/logdog/streamname.py
+../../third_party/logdog/logdog/varint.py
 ../../third_party/markupsafe/__init__.py
 ../../third_party/markupsafe/_compat.py
 ../../third_party/markupsafe/_native.py
-../../tools/swarming_client/libs/__init__.py
-../../tools/swarming_client/libs/logdog/__init__.py
-../../tools/swarming_client/libs/logdog/bootstrap.py
-../../tools/swarming_client/libs/logdog/stream.py
-../../tools/swarming_client/libs/logdog/streamname.py
-../../tools/swarming_client/libs/logdog/varint.py
 ../gn_helpers.py
 ../print_python_deps.py
 ../skia_gold_common/__init__.py
diff --git a/buildtools/reclient_cfgs/win-cross-experiments/rewrapper_windows.cfg b/buildtools/reclient_cfgs/win-cross-experiments/rewrapper_windows.cfg
index d2b0aa0d..1448dfa 100644
--- a/buildtools/reclient_cfgs/win-cross-experiments/rewrapper_windows.cfg
+++ b/buildtools/reclient_cfgs/win-cross-experiments/rewrapper_windows.cfg
@@ -1,4 +1,4 @@
-platform=container-image=docker://gcr.io/goma-foundry-experiments/re-client/chromium-win-cross@sha256:3a61bf4e67593e6b4f676dc0949fda581129e12309b36ca66148e8d31137c5fe,OSFamily=Linux

+platform=container-image=docker://gcr.io/goma-foundry-experiments/re-client/chromium-win-cross@sha256:f18b6081b813cb64beab5c6d9b6f5608daffbc4abde6137a5a2185779da6332d,OSFamily=Linux

 server_address=pipe://reproxy.pipe

 labels=type=compile,compiler=clang-cl,lang=cpp

 exec_strategy=remote_local_fallback

diff --git a/chrome/VERSION b/chrome/VERSION
index 8bbecf8..0034c9d 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=93
 MINOR=0
-BUILD=4537
+BUILD=4538
 PATCH=0
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 9e454a8..6456095 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -713,6 +713,7 @@
     "//chrome/browser/tabmodel/internal:java",
     "//chrome/browser/touch_to_fill/android/internal:java",
     "//chrome/browser/ui/android/appmenu/internal:java",
+    "//chrome/browser/ui/android/autofill/internal:java",
     "//chrome/browser/ui/android/webid/internal:java",
     "//chrome/browser/video_tutorials/internal:java",
     "//components/browser_ui/bottomsheet/android/internal:java",
@@ -955,6 +956,7 @@
     "//chrome/browser/thumbnail:java",
     "//chrome/browser/ui/android/appmenu:java",
     "//chrome/browser/ui/android/appmenu/internal:junit",
+    "//chrome/browser/ui/android/autofill/internal:junit",
     "//chrome/browser/ui/android/default_browser_promo:java",
     "//chrome/browser/ui/android/default_browser_promo:junit",
     "//chrome/browser/ui/android/favicon:java",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 8ba8c0f..f7ef25de 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -950,20 +950,11 @@
 
 const FeatureEntry::FeatureVariation kMemoriesVariations[] = {
     {
-        "Persist Context + Limit 1k",
-        (FeatureEntry::FeatureParam[]){
-            {"MemoriesPersistContextAnnotationsInHistoryDb", "true"}},
+        "Visit Limit 10k",
+        (FeatureEntry::FeatureParam[]){{"MemoriesMaxVisitsToCluster", "10000"}},
         1,
         nullptr,
     },
-    {
-        "Persist Context + Limit 10k",
-        (FeatureEntry::FeatureParam[]){
-            {"MemoriesPersistContextAnnotationsInHistoryDb", "true"},
-            {"MemoriesMaxVisitsToCluster", "10000"}},
-        2,
-        nullptr,
-    },
 };
 
 #if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_MAC) || \
@@ -7118,14 +7109,12 @@
      FEATURE_VALUE_TYPE(
          autofill::features::kAutofillAutoTriggerManualFallbackForCards)},
 
-    {"autofill-suggest-virtual-cards-only-on-full-form-detection",
-     flag_descriptions::kAutofillSuggestVirtualCardsOnlyOnFullFormDetectionName,
-     flag_descriptions::
-         kAutofillSuggestVirtualCardsOnlyOnFullFormDetectionDescription,
+    {"autofill-suggest-virtual-cards-on-incomplete-form",
+     flag_descriptions::kAutofillSuggestVirtualCardsOnIncompleteFormName,
+     flag_descriptions::kAutofillSuggestVirtualCardsOnIncompleteFormDescription,
      kOsDesktop | kOsAndroid,
      FEATURE_VALUE_TYPE(
-         autofill::features::
-             kAutofillSuggestVirtualCardsOnlyOnFullFormDetection)},
+         autofill::features::kAutofillSuggestVirtualCardsOnIncompleteForm)},
 
     {"enable-penetrating-image-selection",
      flag_descriptions::kEnablePenetratingImageSelectionName,
diff --git a/chrome/browser/apps/app_service/app_service_proxy_base.cc b/chrome/browser/apps/app_service/app_service_proxy_base.cc
index 0bce161..1063376 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_base.cc
+++ b/chrome/browser/apps/app_service/app_service_proxy_base.cc
@@ -356,9 +356,11 @@
 
 std::vector<std::string> AppServiceProxyBase::GetAppIdsForUrl(
     const GURL& url,
-    bool exclude_browsers) {
+    bool exclude_browsers,
+    bool exclude_browser_tab_apps) {
   auto intent_launch_info =
-      GetAppsForIntent(apps_util::CreateIntentFromUrl(url), exclude_browsers);
+      GetAppsForIntent(apps_util::CreateIntentFromUrl(url), exclude_browsers,
+                       exclude_browser_tab_apps);
   std::vector<std::string> app_ids;
   for (auto& entry : intent_launch_info) {
     app_ids.push_back(std::move(entry.app_id));
@@ -368,7 +370,8 @@
 
 std::vector<IntentLaunchInfo> AppServiceProxyBase::GetAppsForIntent(
     const apps::mojom::IntentPtr& intent,
-    bool exclude_browsers) {
+    bool exclude_browsers,
+    bool exclude_browser_tab_apps) {
   std::vector<IntentLaunchInfo> intent_launch_info;
   if (apps_util::OnlyShareToDrive(intent) ||
       !apps_util::IsIntentValid(intent)) {
@@ -377,12 +380,17 @@
 
   if (app_service_.is_bound()) {
     app_registry_cache_.ForEachApp([&intent_launch_info, &intent,
-                                    &exclude_browsers](
+                                    &exclude_browsers,
+                                    &exclude_browser_tab_apps](
                                        const apps::AppUpdate& update) {
       if (!apps_util::IsInstalled(update.Readiness()) ||
           update.ShowInLauncher() != apps::mojom::OptionalBool::kTrue) {
         return;
       }
+      if (exclude_browser_tab_apps &&
+          update.WindowMode() == mojom::WindowMode::kBrowser) {
+        return;
+      }
       std::set<std::string> existing_activities;
       for (const auto& filter : update.IntentFilters()) {
         if (exclude_browsers && apps_util::IsBrowserFilter(filter)) {
diff --git a/chrome/browser/apps/app_service/app_service_proxy_base.h b/chrome/browser/apps/app_service/app_service_proxy_base.h
index a5ca5408..9370806 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_base.h
+++ b/chrome/browser/apps/app_service/app_service_proxy_base.h
@@ -183,15 +183,21 @@
 
   // Returns a list of apps (represented by their ids) which can handle |url|.
   // If |exclude_browsers| is true, then exclude the browser apps.
-  std::vector<std::string> GetAppIdsForUrl(const GURL& url,
-                                           bool exclude_browsers = false);
+  // If |exclude_browser_tab_apps| is true then exclude apps that open in
+  // browser tabs.
+  std::vector<std::string> GetAppIdsForUrl(
+      const GURL& url,
+      bool exclude_browsers = false,
+      bool exclude_browser_tab_apps = true);
 
   // Returns a list of apps (represented by their ids) and activities (if
   // applied) which can handle |intent|. If |exclude_browsers| is true, then
-  // exclude the browser apps.
+  // exclude the browser apps. If |exclude_browser_tab_apps| is true then
+  // exclude apps that open in browser tabs.
   std::vector<IntentLaunchInfo> GetAppsForIntent(
       const apps::mojom::IntentPtr& intent,
-      bool exclude_browsers = false);
+      bool exclude_browsers = false,
+      bool exclude_browser_tab_apps = true);
 
   // Returns a list of apps (represented by their ids) and activities (if
   // applied) which can handle |filesystem_urls| and |mime_types|.
diff --git a/chrome/browser/apps/intent_helper/intent_picker_internal.cc b/chrome/browser/apps/intent_helper/intent_picker_internal.cc
index 17a329cf..c72c1db8 100644
--- a/chrome/browser/apps/intent_helper/intent_picker_internal.cc
+++ b/chrome/browser/apps/intent_helper/intent_picker_internal.cc
@@ -76,6 +76,11 @@
     return apps;
 
   auto* const provider = web_app::WebAppProviderBase::GetProviderBase(profile);
+  if (provider->registrar().GetAppUserDisplayMode(*app_id) ==
+      web_app::DisplayMode::kBrowser) {
+    return apps;
+  }
+
   ui::ImageModel icon_model =
       ui::ImageModel::FromImage(gfx::Image::CreateFrom1xBitmap(
           provider->icon_manager().GetFavicon(*app_id)));
diff --git a/chrome/browser/apps/platform_apps/app_browsertest.cc b/chrome/browser/apps/platform_apps/app_browsertest.cc
index 88c179a..9247142 100644
--- a/chrome/browser/apps/platform_apps/app_browsertest.cc
+++ b/chrome/browser/apps/platform_apps/app_browsertest.cc
@@ -308,17 +308,16 @@
 
 // Tests that platform apps received the "launch" event when launched.
 IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest, OnLaunchedEvent) {
-  ASSERT_TRUE(RunExtensionTest(
-      {.name = "platform_apps/launch", .launch_as_platform_app = true}))
+  ASSERT_TRUE(RunExtensionTest("platform_apps/launch",
+                               {.launch_as_platform_app = true}))
       << message_;
 }
 
 // Tests that platform apps cannot use certain disabled window properties, but
 // can override them and then use them.
 IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest, DisabledWindowProperties) {
-  ASSERT_TRUE(
-      RunExtensionTest({.name = "platform_apps/disabled_window_properties",
-                        .launch_as_platform_app = true}))
+  ASSERT_TRUE(RunExtensionTest("platform_apps/disabled_window_properties",
+                               {.launch_as_platform_app = true}))
       << message_;
 }
 
@@ -479,8 +478,8 @@
   TabsAddedNotificationObserver observer(browser(), 1);
 
   ASSERT_TRUE(StartEmbeddedTestServer());
-  ASSERT_TRUE(RunExtensionTest(
-      {.name = "platform_apps/navigation", .launch_as_platform_app = true}))
+  ASSERT_TRUE(RunExtensionTest("platform_apps/navigation",
+                               {.launch_as_platform_app = true}))
       << message_;
 
   observer.Wait();
@@ -494,9 +493,8 @@
   // and window.open(). Only the external urls should succeed in opening tabs.
   const size_t kExpectedNumberOfTabs = 2u;
   TabsAddedNotificationObserver observer(browser(), kExpectedNumberOfTabs);
-  ASSERT_TRUE(
-      RunExtensionTest({.name = "platform_apps/background_page_navigation",
-                        .launch_as_platform_app = true}))
+  ASSERT_TRUE(RunExtensionTest("platform_apps/background_page_navigation",
+                               {.launch_as_platform_app = true}))
       << message_;
   observer.Wait();
   ASSERT_EQ(kExpectedNumberOfTabs, observer.tabs().size());
@@ -518,27 +516,27 @@
 #endif
 IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest, MAYBE_Iframes) {
   ASSERT_TRUE(StartEmbeddedTestServer());
-  ASSERT_TRUE(RunExtensionTest(
-      {.name = "platform_apps/iframes", .launch_as_platform_app = true}))
+  ASSERT_TRUE(RunExtensionTest("platform_apps/iframes",
+                               {.launch_as_platform_app = true}))
       << message_;
 }
 
 // Tests that localStorage and WebSQL are disabled for platform apps.
 IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest, DisallowStorage) {
-  ASSERT_TRUE(RunExtensionTest(
-      {.name = "platform_apps/storage", .launch_as_platform_app = true}))
+  ASSERT_TRUE(RunExtensionTest("platform_apps/storage",
+                               {.launch_as_platform_app = true}))
       << message_;
 }
 
 IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest, Restrictions) {
-  ASSERT_TRUE(RunExtensionTest(
-      {.name = "platform_apps/restrictions", .launch_as_platform_app = true}))
+  ASSERT_TRUE(RunExtensionTest("platform_apps/restrictions",
+                               {.launch_as_platform_app = true}))
       << message_;
 }
 
 // Tests that extensions can't use platform-app-only APIs.
 IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest, PlatformAppsOnly) {
-  ASSERT_TRUE(RunExtensionTest({.name = "platform_apps/apps_only"},
+  ASSERT_TRUE(RunExtensionTest("platform_apps/apps_only", {},
                                {.ignore_manifest_warnings = true}))
       << message_;
 }
@@ -566,8 +564,8 @@
 
   // Let the platform app request the same URL, and make sure that it doesn't
   // see the cookie.
-  ASSERT_TRUE(RunExtensionTest(
-      {.name = "platform_apps/isolation", .launch_as_platform_app = true}))
+  ASSERT_TRUE(RunExtensionTest("platform_apps/isolation",
+                               {.launch_as_platform_app = true}))
       << message_;
 }
 
@@ -834,8 +832,8 @@
 }
 
 IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest, MutationEventsDisabled) {
-  ASSERT_TRUE(RunExtensionTest({.name = "platform_apps/mutation_events",
-                                .launch_as_platform_app = true}))
+  ASSERT_TRUE(RunExtensionTest("platform_apps/mutation_events",
+                               {.launch_as_platform_app = true}))
       << message_;
 }
 
@@ -848,8 +846,8 @@
 #define MAYBE_AppWindowRestoreState AppWindowRestoreState
 #endif
 IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest, MAYBE_AppWindowRestoreState) {
-  ASSERT_TRUE(RunExtensionTest(
-      {.name = "platform_apps/restore_state", .launch_as_platform_app = true}));
+  ASSERT_TRUE(RunExtensionTest("platform_apps/restore_state",
+                               {.launch_as_platform_app = true}));
 }
 
 IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest,
@@ -1237,8 +1235,8 @@
 IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest,
                        WindowDotPrintShouldBringUpPrintPreview) {
   ScopedPreviewTestDelegate preview_delegate;
-  ASSERT_TRUE(RunExtensionTest(
-      {.name = "platform_apps/print_api", .launch_as_platform_app = true}))
+  ASSERT_TRUE(RunExtensionTest("platform_apps/print_api",
+                               {.launch_as_platform_app = true}))
       << message_;
   preview_delegate.WaitUntilPreviewIsReady();
 }
@@ -1247,8 +1245,8 @@
 IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest,
                        DISABLED_ClosingWindowWhilePrintingShouldNotCrash) {
   ScopedPreviewTestDelegate preview_delegate;
-  ASSERT_TRUE(RunExtensionTest(
-      {.name = "platform_apps/print_api", .launch_as_platform_app = true}))
+  ASSERT_TRUE(RunExtensionTest("platform_apps/print_api",
+                               {.launch_as_platform_app = true}))
       << message_;
   preview_delegate.WaitUntilPreviewIsReady();
   GetFirstAppWindow()->GetBaseWindow()->Close();
@@ -1455,19 +1453,19 @@
 }
 
 IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest, NewWindowWithNonExistingFile) {
-  ASSERT_TRUE(RunExtensionTest(
-      {.name = "platform_apps/new_window_with_non_existing_file",
-       .launch_as_platform_app = true}));
+  ASSERT_TRUE(
+      RunExtensionTest("platform_apps/new_window_with_non_existing_file",
+                       {.launch_as_platform_app = true}));
 }
 
 IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest, SandboxedLocalFile) {
-  ASSERT_TRUE(RunExtensionTest({.name = "platform_apps/sandboxed_local_file",
-                                .launch_as_platform_app = true}));
+  ASSERT_TRUE(RunExtensionTest("platform_apps/sandboxed_local_file",
+                               {.launch_as_platform_app = true}));
 }
 
 IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest, NewWindowAboutBlank) {
-  ASSERT_TRUE(RunExtensionTest({.name = "platform_apps/new_window_about_blank",
-                                .launch_as_platform_app = true}));
+  ASSERT_TRUE(RunExtensionTest("platform_apps/new_window_about_blank",
+                               {.launch_as_platform_app = true}));
 }
 
 // Test that an app window sees the synthetic wheel events of a touchpad pinch.
diff --git a/chrome/browser/ash/apps/intent_helper/common_apps_navigation_throttle.cc b/chrome/browser/ash/apps/intent_helper/common_apps_navigation_throttle.cc
index 1782b0d..bf45ac8 100644
--- a/chrome/browser/ash/apps/intent_helper/common_apps_navigation_throttle.cc
+++ b/chrome/browser/ash/apps/intent_helper/common_apps_navigation_throttle.cc
@@ -185,7 +185,7 @@
       Profile::FromBrowserContext(web_contents->GetBrowserContext());
   std::vector<std::string> app_ids =
       apps::AppServiceProxyFactory::GetForProfile(profile)->GetAppIdsForUrl(
-          url, /*exclude_browser=*/true);
+          url, /*exclude_browser=*/true, /*exclude_browser_tab_apps=*/false);
 
   for (auto app_id : app_ids) {
     if (IsAppDisabled(app_id)) {
diff --git a/chrome/browser/ash/arc/accessibility/arc_accessibility_util.cc b/chrome/browser/ash/arc/accessibility/arc_accessibility_util.cc
index 797d017..70b7b9f 100644
--- a/chrome/browser/ash/arc/accessibility/arc_accessibility_util.cc
+++ b/chrome/browser/ash/arc/accessibility/arc_accessibility_util.cc
@@ -178,6 +178,67 @@
   }
 }
 
+ax::mojom::Action ConvertToChromeAction(
+    const mojom::AccessibilityActionType action) {
+  switch (action) {
+    case arc::mojom::AccessibilityActionType::CLICK:
+      return ax::mojom::Action::kDoDefault;
+    case arc::mojom::AccessibilityActionType::ACCESSIBILITY_FOCUS:
+      // TODO: there are multiple actions converted to ACCESSIBILITY_FOCUS.
+      //  Consider if this is appropriate.
+      return ax::mojom::Action::kSetSequentialFocusNavigationStartingPoint;
+    case arc::mojom::AccessibilityActionType::SHOW_ON_SCREEN:
+      return ax::mojom::Action::kScrollToMakeVisible;
+    case arc::mojom::AccessibilityActionType::SCROLL_BACKWARD:
+      return ax::mojom::Action::kScrollBackward;
+    case arc::mojom::AccessibilityActionType::SCROLL_FORWARD:
+      return ax::mojom::Action::kScrollForward;
+    case arc::mojom::AccessibilityActionType::SCROLL_UP:
+      return ax::mojom::Action::kScrollUp;
+    case arc::mojom::AccessibilityActionType::SCROLL_DOWN:
+      return ax::mojom::Action::kScrollDown;
+    case arc::mojom::AccessibilityActionType::SCROLL_LEFT:
+      return ax::mojom::Action::kScrollLeft;
+    case arc::mojom::AccessibilityActionType::SCROLL_RIGHT:
+      return ax::mojom::Action::kScrollRight;
+    case arc::mojom::AccessibilityActionType::CUSTOM_ACTION:
+      return ax::mojom::Action::kCustomAction;
+    case arc::mojom::AccessibilityActionType::CLEAR_ACCESSIBILITY_FOCUS:
+      return ax::mojom::Action::kClearAccessibilityFocus;
+    case arc::mojom::AccessibilityActionType::GET_TEXT_LOCATION:
+      return ax::mojom::Action::kGetTextLocation;
+    case arc::mojom::AccessibilityActionType::SHOW_TOOLTIP:
+      return ax::mojom::Action::kShowTooltip;
+    case arc::mojom::AccessibilityActionType::HIDE_TOOLTIP:
+      return ax::mojom::Action::kHideTooltip;
+    case arc::mojom::AccessibilityActionType::COLLAPSE:
+      return ax::mojom::Action::kCollapse;
+    case arc::mojom::AccessibilityActionType::EXPAND:
+      return ax::mojom::Action::kExpand;
+    case arc::mojom::AccessibilityActionType::LONG_CLICK:
+      return ax::mojom::Action::kShowContextMenu;
+    // Below are actions not mapped in ConvertToAndroidAction().
+    case arc::mojom::AccessibilityActionType::FOCUS:
+    case arc::mojom::AccessibilityActionType::CLEAR_FOCUS:
+    case arc::mojom::AccessibilityActionType::SELECT:
+    case arc::mojom::AccessibilityActionType::CLEAR_SELECTION:
+    case arc::mojom::AccessibilityActionType::NEXT_AT_MOVEMENT_GRANULARITY:
+    case arc::mojom::AccessibilityActionType::PREVIOUS_AT_MOVEMENT_GRANULARITY:
+    case arc::mojom::AccessibilityActionType::NEXT_HTML_ELEMENT:
+    case arc::mojom::AccessibilityActionType::PREVIOUS_HTML_ELEMENT:
+    case arc::mojom::AccessibilityActionType::COPY:
+    case arc::mojom::AccessibilityActionType::PASTE:
+    case arc::mojom::AccessibilityActionType::CUT:
+    case arc::mojom::AccessibilityActionType::SET_SELECTION:
+    case arc::mojom::AccessibilityActionType::DISMISS:
+    case arc::mojom::AccessibilityActionType::SET_TEXT:
+    case arc::mojom::AccessibilityActionType::CONTEXT_CLICK:
+    case arc::mojom::AccessibilityActionType::SCROLL_TO_POSITION:
+    case arc::mojom::AccessibilityActionType::SET_PROGRESS:
+      return ax::mojom::Action::kNone;
+  }
+}
+
 AccessibilityInfoDataWrapper* GetSelectedNodeInfoFromAdapterViewEvent(
     const mojom::AccessibilityEventData& event_data,
     AccessibilityInfoDataWrapper* source_node) {
diff --git a/chrome/browser/ash/arc/accessibility/arc_accessibility_util.h b/chrome/browser/ash/arc/accessibility/arc_accessibility_util.h
index 8e62f039..1e8dff21 100644
--- a/chrome/browser/ash/arc/accessibility/arc_accessibility_util.h
+++ b/chrome/browser/ash/arc/accessibility/arc_accessibility_util.h
@@ -37,6 +37,9 @@
 absl::optional<mojom::AccessibilityActionType> ConvertToAndroidAction(
     ax::mojom::Action action);
 
+ax::mojom::Action ConvertToChromeAction(
+    const mojom::AccessibilityActionType action);
+
 AccessibilityInfoDataWrapper* GetSelectedNodeInfoFromAdapterViewEvent(
     const mojom::AccessibilityEventData& event_data,
     AccessibilityInfoDataWrapper* source_node);
diff --git a/chrome/browser/ash/arc/accessibility/ax_tree_source_arc.cc b/chrome/browser/ash/arc/accessibility/ax_tree_source_arc.cc
index 7e9027ce..84621b2 100644
--- a/chrome/browser/ash/arc/accessibility/ax_tree_source_arc.cc
+++ b/chrome/browser/ash/arc/accessibility/ax_tree_source_arc.cc
@@ -253,9 +253,14 @@
       GetFromId(event_data.source_id), focused_node);
   event.id = event_data.source_id;
 
-  if (HasProperty(event_data.int_properties,
-                  arc::mojom::AccessibilityEventIntProperty::ACTION)) {
+  int event_from_action;
+  if (GetProperty(event_data.int_properties,
+                  arc::mojom::AccessibilityEventIntProperty::ACTION,
+                  &event_from_action)) {
     event.event_from = ax::mojom::EventFrom::kAction;
+
+    event.event_from_action = ConvertToChromeAction(
+        static_cast<mojom::AccessibilityActionType>(event_from_action));
   }
 
   events.push_back(std::move(event));
diff --git a/chrome/browser/ash/arc/accessibility/ax_tree_source_arc_unittest.cc b/chrome/browser/ash/arc/accessibility/ax_tree_source_arc_unittest.cc
index 3486ee7..a8e58c6 100644
--- a/chrome/browser/ash/arc/accessibility/ax_tree_source_arc_unittest.cc
+++ b/chrome/browser/ash/arc/accessibility/ax_tree_source_arc_unittest.cc
@@ -57,8 +57,8 @@
                                    std::vector<ui::AXEvent> events) override {
     for (auto&& event : events) {
       event_count_[event.event_type]++;
-      last_event_type_ = event.event_type;
     }
+    last_dispatched_events_ = std::move(events);
 
     for (const auto& update : updates)
       tree_.Unserialize(update);
@@ -80,13 +80,15 @@
       const ui::AXActionData& data,
       const absl::optional<gfx::Rect>& rect) override {}
 
-  ax::mojom::Event last_event_type() const { return last_event_type_; }
+  std::vector<ui::AXEvent> last_dispatched_events() const {
+    return last_dispatched_events_;
+  }
 
   std::map<ax::mojom::Event, int> event_count_;
   ui::AXTree tree_;
 
  private:
-  ax::mojom::Event last_event_type_;
+  std::vector<ui::AXEvent> last_dispatched_events_;
 };
 
 class AXTreeSourceArcTest : public testing::Test,
@@ -141,8 +143,8 @@
     return router_->event_count_[type];
   }
 
-  ax::mojom::Event last_dispatched_event_type() const {
-    return router_->last_event_type();
+  std::vector<ui::AXEvent> last_dispatched_events() const {
+    return router_->last_dispatched_events();
   }
 
   ui::AXTree* tree() { return router_->tree(); }
@@ -1166,12 +1168,12 @@
               content_change_types);
   CallNotifyAccessibilityEvent(event.get());
   EXPECT_EQ(ax::mojom::Event::kAriaAttributeChanged,
-            last_dispatched_event_type());
+            last_dispatched_events()[0].event_type);
 
   event->event_type = AXEventType::WINDOW_CONTENT_CHANGED;
   CallNotifyAccessibilityEvent(event.get());
   EXPECT_EQ(ax::mojom::Event::kAriaAttributeChanged,
-            last_dispatched_event_type());
+            last_dispatched_events()[0].event_type);
 
   // State description changed event from non range widget.
   event->node_data.push_back(AXNodeInfoData::New());
@@ -1182,12 +1184,12 @@
   event->event_type = AXEventType::WINDOW_STATE_CHANGED;
   CallNotifyAccessibilityEvent(event.get());
   EXPECT_EQ(ax::mojom::Event::kAriaAttributeChanged,
-            last_dispatched_event_type());
+            last_dispatched_events()[0].event_type);
 
   event->event_type = AXEventType::WINDOW_CONTENT_CHANGED;
   CallNotifyAccessibilityEvent(event.get());
   EXPECT_EQ(ax::mojom::Event::kAriaAttributeChanged,
-            last_dispatched_event_type());
+            last_dispatched_events()[0].event_type);
 }
 
 TEST_F(AXTreeSourceArcTest, EventWithWrongSourceId) {
@@ -1418,4 +1420,36 @@
   EXPECT_TRUE(data.HasState(ax::mojom::State::kCollapsed));
 }
 
+TEST_F(AXTreeSourceArcTest, EventFrom) {
+  auto event = AXEventData::New();
+  event->source_id = 1;
+  event->task_id = 1;
+  event->event_type = AXEventType::VIEW_FOCUSED;
+
+  event->node_data.push_back(AXNodeInfoData::New());
+  AXNodeInfoData* node = event->node_data.back().get();
+  node->id = 10;
+
+  event->window_data = std::vector<mojom::AccessibilityWindowInfoDataPtr>();
+  event->window_data->push_back(AXWindowInfoData::New());
+  AXWindowInfoData* root = event->window_data->back().get();
+  root->window_id = 1;
+  root->root_node_id = node->id;
+
+  // By default, event_from and event_from_action are None.
+  CallNotifyAccessibilityEvent(event.get());
+
+  ui::AXEvent actual = last_dispatched_events()[0];
+  EXPECT_EQ(ax::mojom::EventFrom::kNone, actual.event_from);
+  EXPECT_EQ(ax::mojom::Action::kNone, actual.event_from_action);
+
+  // With |Action| field, event_from and event_from_action are populated.
+  SetProperty(event.get(), AXEventIntProperty::ACTION,
+              static_cast<int32_t>(arc::mojom::AccessibilityActionType::CLICK));
+  CallNotifyAccessibilityEvent(event.get());
+
+  actual = last_dispatched_events()[0];
+  EXPECT_EQ(ax::mojom::EventFrom::kAction, actual.event_from);
+  EXPECT_EQ(ax::mojom::Action::kDoDefault, actual.event_from_action);
+}
 }  // namespace arc
diff --git a/chrome/browser/ash/arc/boot_phase_monitor/arc_boot_phase_monitor_bridge.cc b/chrome/browser/ash/arc/boot_phase_monitor/arc_boot_phase_monitor_bridge.cc
index d12422e..9767cdd6 100644
--- a/chrome/browser/ash/arc/boot_phase_monitor/arc_boot_phase_monitor_bridge.cc
+++ b/chrome/browser/ash/arc/boot_phase_monitor/arc_boot_phase_monitor_bridge.cc
@@ -33,6 +33,8 @@
 class DefaultDelegateImpl : public ArcBootPhaseMonitorBridge::Delegate {
  public:
   DefaultDelegateImpl() = default;
+  DefaultDelegateImpl(const DefaultDelegateImpl&) = delete;
+  DefaultDelegateImpl& operator=(const DefaultDelegateImpl&) = delete;
   ~DefaultDelegateImpl() override = default;
 
   void RecordFirstAppLaunchDelayUMA(base::TimeDelta delta) override {
@@ -42,9 +44,6 @@
                                base::TimeDelta::FromMilliseconds(1),
                                base::TimeDelta::FromMinutes(2), 50);
   }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(DefaultDelegateImpl);
 };
 
 }  // namespace
@@ -109,21 +108,6 @@
   observers_.RemoveObserver(observer);
 }
 
-void ArcBootPhaseMonitorBridge::RecordFirstAppLaunchDelayUMAInternal() {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  if (first_app_launch_delay_recorded_)
-    return;
-  first_app_launch_delay_recorded_ = true;
-
-  if (boot_completed_) {
-    VLOG(2) << "ARC has already fully started. Recording the UMA now.";
-    if (delegate_)
-      delegate_->RecordFirstAppLaunchDelayUMA(base::TimeDelta());
-    return;
-  }
-  app_launch_time_ = base::TimeTicks::Now();
-}
-
 void ArcBootPhaseMonitorBridge::OnBootCompleted() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   VLOG(2) << "OnBootCompleted";
@@ -151,15 +135,30 @@
   Reset();
 }
 
+void ArcBootPhaseMonitorBridge::SetDelegateForTesting(
+    std::unique_ptr<Delegate> delegate) {
+  delegate_ = std::move(delegate);
+}
+
+void ArcBootPhaseMonitorBridge::RecordFirstAppLaunchDelayUMAInternal() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  if (first_app_launch_delay_recorded_)
+    return;
+  first_app_launch_delay_recorded_ = true;
+
+  if (boot_completed_) {
+    VLOG(2) << "ARC has already fully started. Recording the UMA now.";
+    if (delegate_)
+      delegate_->RecordFirstAppLaunchDelayUMA(base::TimeDelta());
+    return;
+  }
+  app_launch_time_ = base::TimeTicks::Now();
+}
+
 void ArcBootPhaseMonitorBridge::Reset() {
   app_launch_time_ = base::TimeTicks();
   first_app_launch_delay_recorded_ = false;
   boot_completed_ = false;
 }
 
-void ArcBootPhaseMonitorBridge::SetDelegateForTesting(
-    std::unique_ptr<Delegate> delegate) {
-  delegate_ = std::move(delegate);
-}
-
 }  // namespace arc
diff --git a/chrome/browser/ash/arc/boot_phase_monitor/arc_boot_phase_monitor_bridge.h b/chrome/browser/ash/arc/boot_phase_monitor/arc_boot_phase_monitor_bridge.h
index 9a447b0..20b7716 100644
--- a/chrome/browser/ash/arc/boot_phase_monitor/arc_boot_phase_monitor_bridge.h
+++ b/chrome/browser/ash/arc/boot_phase_monitor/arc_boot_phase_monitor_bridge.h
@@ -7,7 +7,6 @@
 
 #include <memory>
 
-#include "base/macros.h"
 #include "base/memory/singleton.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
@@ -68,6 +67,9 @@
 
   ArcBootPhaseMonitorBridge(content::BrowserContext* context,
                             ArcBridgeService* bridge_service);
+  ArcBootPhaseMonitorBridge(const ArcBootPhaseMonitorBridge&) = delete;
+  ArcBootPhaseMonitorBridge& operator=(const ArcBootPhaseMonitorBridge&) =
+      delete;
   ~ArcBootPhaseMonitorBridge() override;
 
   // If ARC has already been booted, OnBootCompleted() is called immediately for
@@ -106,8 +108,6 @@
 
   // This has to be the last member variable in the class.
   base::WeakPtrFactory<ArcBootPhaseMonitorBridge> weak_ptr_factory_{this};
-
-  DISALLOW_COPY_AND_ASSIGN(ArcBootPhaseMonitorBridge);
 };
 
 // Singleton factory for ArcBootPhaseMonitorBridge.
diff --git a/chrome/browser/ash/arc/boot_phase_monitor/arc_boot_phase_monitor_bridge_unittest.cc b/chrome/browser/ash/arc/boot_phase_monitor/arc_boot_phase_monitor_bridge_unittest.cc
index eecbd979..34327fdb 100644
--- a/chrome/browser/ash/arc/boot_phase_monitor/arc_boot_phase_monitor_bridge_unittest.cc
+++ b/chrome/browser/ash/arc/boot_phase_monitor/arc_boot_phase_monitor_bridge_unittest.cc
@@ -61,6 +61,10 @@
         std::make_unique<TestDelegateImpl>(this));
   }
 
+  ArcBootPhaseMonitorBridgeTest(const ArcBootPhaseMonitorBridgeTest&) = delete;
+  ArcBootPhaseMonitorBridgeTest& operator=(
+      const ArcBootPhaseMonitorBridgeTest&) = delete;
+
   ~ArcBootPhaseMonitorBridgeTest() override {
     boot_phase_monitor_bridge_->Shutdown();
     testing_profile_.reset();
@@ -107,6 +111,8 @@
    public:
     explicit TestDelegateImpl(ArcBootPhaseMonitorBridgeTest* test)
         : test_(test) {}
+    TestDelegateImpl(const TestDelegateImpl&) = delete;
+    TestDelegateImpl& operator=(const TestDelegateImpl&) = delete;
     ~TestDelegateImpl() override = default;
 
     void RecordFirstAppLaunchDelayUMA(base::TimeDelta delta) override {
@@ -116,8 +122,6 @@
 
    private:
     ArcBootPhaseMonitorBridgeTest* const test_;
-
-    DISALLOW_COPY_AND_ASSIGN(TestDelegateImpl);
   };
 
   ash::FakeChromeUserManager* GetFakeUserManager() const {
@@ -135,8 +139,6 @@
   size_t record_uma_counter_ = 0;
   base::TimeDelta last_time_delta_;
   size_t on_boot_completed_counter_ = 0;
-
-  DISALLOW_COPY_AND_ASSIGN(ArcBootPhaseMonitorBridgeTest);
 };
 
 // Tests that ArcBootPhaseMonitorBridge can be constructed and destructed.
diff --git a/chrome/browser/ash/arc/fileapi/arc_file_system_bridge_unittest.cc b/chrome/browser/ash/arc/fileapi/arc_file_system_bridge_unittest.cc
index 778aeb7..879852c 100644
--- a/chrome/browser/ash/arc/fileapi/arc_file_system_bridge_unittest.cc
+++ b/chrome/browser/ash/arc/fileapi/arc_file_system_bridge_unittest.cc
@@ -62,13 +62,12 @@
     ASSERT_TRUE(profile_manager_->SetUp());
     profile_ = profile_manager_->CreateTestingProfile(kTestingProfileName);
     auto fake_provider =
-        chromeos::file_system_provider::FakeExtensionProvider::Create(
-            kExtensionId);
+        ash::file_system_provider::FakeExtensionProvider::Create(kExtensionId);
     const auto kProviderId = fake_provider->GetId();
-    auto* service = chromeos::file_system_provider::Service::Get(profile_);
+    auto* service = ash::file_system_provider::Service::Get(profile_);
     service->RegisterProvider(std::move(fake_provider));
     service->MountFileSystem(kProviderId,
-                             chromeos::file_system_provider::MountOptions(
+                             ash::file_system_provider::MountOptions(
                                  kFileSystemId, "Test FileSystem"));
 
     arc_file_system_bridge_ =
diff --git a/chrome/browser/ash/arc/nearby_share/OWNERS b/chrome/browser/ash/arc/nearby_share/OWNERS
new file mode 100644
index 0000000..b0570ed
--- /dev/null
+++ b/chrome/browser/ash/arc/nearby_share/OWNERS
@@ -0,0 +1,3 @@
+alanding@chromium.org
+melzhang@chromium.org
+mxcai@chromium.org
diff --git a/chrome/browser/ash/arc/nearby_share/arc_nearby_share_bridge.cc b/chrome/browser/ash/arc/nearby_share/arc_nearby_share_bridge.cc
new file mode 100644
index 0000000..725e1cf
--- /dev/null
+++ b/chrome/browser/ash/arc/nearby_share/arc_nearby_share_bridge.cc
@@ -0,0 +1,86 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/arc/nearby_share/arc_nearby_share_bridge.h"
+
+#include <utility>
+
+#include "ash/public/cpp/app_types_util.h"
+#include "base/memory/singleton.h"
+#include "chrome/browser/ash/arc/arc_util.h"
+#include "chrome/browser/ash/arc/nearby_share/nearby_share_session_impl.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "components/arc/arc_browser_context_keyed_service_factory_base.h"
+#include "components/arc/arc_util.h"
+#include "components/arc/intent_helper/custom_tab.h"
+#include "components/arc/mojom/nearby_share.mojom.h"
+#include "components/arc/session/arc_bridge_service.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace arc {
+
+namespace {
+
+// Singleton factory for ArcNearbyShareBridgeFactory.
+class ArcNearbyShareBridgeFactory
+    : public internal::ArcBrowserContextKeyedServiceFactoryBase<
+          ArcNearbyShareBridge,
+          ArcNearbyShareBridgeFactory> {
+ public:
+  // Factory name used by ArcBrowserContextKeyedServiceFactoryBase.
+  static constexpr const char* kName = "ArcNearbyShareBridgeFactory";
+
+  static ArcNearbyShareBridgeFactory* GetInstance() {
+    return base::Singleton<ArcNearbyShareBridgeFactory>::get();
+  }
+
+ private:
+  friend base::DefaultSingletonTraits<ArcNearbyShareBridgeFactory>;
+  ArcNearbyShareBridgeFactory() = default;
+  ~ArcNearbyShareBridgeFactory() override = default;
+};
+
+}  // namespace
+
+// static
+ArcNearbyShareBridge* ArcNearbyShareBridge::GetForBrowserContext(
+    content::BrowserContext* context) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  return ArcNearbyShareBridgeFactory::GetForBrowserContext(context);
+}
+
+// static
+ArcNearbyShareBridge* ArcNearbyShareBridge::GetForBrowserContextForTesting(
+    content::BrowserContext* context) {
+  return ArcNearbyShareBridgeFactory::GetForBrowserContextForTesting(context);
+}
+
+ArcNearbyShareBridge::ArcNearbyShareBridge(content::BrowserContext* context,
+                                           ArcBridgeService* bridge_service)
+    : arc_bridge_service_(bridge_service),
+      profile_(Profile::FromBrowserContext(context)) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  arc_bridge_service_->nearby_share()->SetHost(this);
+}
+
+ArcNearbyShareBridge::~ArcNearbyShareBridge() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  arc_bridge_service_->nearby_share()->SetHost(nullptr);
+}
+
+void ArcNearbyShareBridge::StartNearbyShare(
+    int32_t task_id,
+    mojom::ShareIntentInfoPtr share_info,
+    mojo::PendingRemote<mojom::NearbyShareSessionInstance> session_instance,
+    StartNearbyShareCallback callback) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  VLOG(1) << "Creating Nearby Share session";
+  std::move(callback).Run(NearbyShareSessionImpl::Create(
+      CreateArcCustomTabWebContents(profile_, GURL("about:blank")), task_id,
+      std::move(share_info), std::move(session_instance)));
+}
+
+}  // namespace arc
diff --git a/chrome/browser/ash/arc/nearby_share/arc_nearby_share_bridge.h b/chrome/browser/ash/arc/nearby_share/arc_nearby_share_bridge.h
new file mode 100644
index 0000000..3314a27
--- /dev/null
+++ b/chrome/browser/ash/arc/nearby_share/arc_nearby_share_bridge.h
@@ -0,0 +1,63 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ASH_ARC_NEARBY_SHARE_ARC_NEARBY_SHARE_BRIDGE_H_
+#define CHROME_BROWSER_ASH_ARC_NEARBY_SHARE_ARC_NEARBY_SHARE_BRIDGE_H_
+
+#include <stdint.h>
+
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/ash/arc/nearby_share/nearby_share_session_impl.h"
+#include "components/arc/mojom/nearby_share.mojom.h"
+#include "components/keyed_service/core/keyed_service.h"
+
+class Profile;
+
+namespace content {
+class BrowserContext;
+}  // namespace content
+
+namespace arc {
+
+class ArcBridgeService;
+
+// This class handles nearby share related IPC from ARC++ and allows the Chrome
+// NearbyShare UI to be displayed and managed in Chrome instead of the
+// Android NearbyShare activity.
+class ArcNearbyShareBridge : public KeyedService,
+                             public mojom::NearbyShareHost {
+ public:
+  // Returns singleton instance for the given BrowserContext,
+  // or nullptr if the browser |context| is not allowed to use ARC.
+  static ArcNearbyShareBridge* GetForBrowserContext(
+      content::BrowserContext* context);
+  static ArcNearbyShareBridge* GetForBrowserContextForTesting(
+      content::BrowserContext* context);
+
+  ArcNearbyShareBridge(content::BrowserContext* context,
+                       ArcBridgeService* bridge_service);
+
+  ArcNearbyShareBridge(const ArcNearbyShareBridge&) = delete;
+  ArcNearbyShareBridge& operator=(const ArcNearbyShareBridge&) = delete;
+  ~ArcNearbyShareBridge() override;
+
+  // mojom::NearbyShareHost overrides.
+  void StartNearbyShare(
+      int32_t task_id,
+      mojom::ShareIntentInfoPtr info,
+      mojo::PendingRemote<mojom::NearbyShareSessionInstance> instance,
+      StartNearbyShareCallback callback) override;
+
+ private:
+  ArcBridgeService* const arc_bridge_service_;  // Owned by ArcServiceManager.
+
+  // Unowned pointer.
+  Profile* const profile_;
+
+  base::WeakPtrFactory<ArcNearbyShareBridge> weak_ptr_factory_{this};
+};
+
+}  // namespace arc
+
+#endif  // CHROME_BROWSER_ASH_ARC_NEARBY_SHARE_ARC_NEARBY_SHARE_BRIDGE_H_
diff --git a/chrome/browser/ash/arc/nearby_share/arc_nearby_share_bridge_unittest.cc b/chrome/browser/ash/arc/nearby_share/arc_nearby_share_bridge_unittest.cc
new file mode 100644
index 0000000..e8472bf
--- /dev/null
+++ b/chrome/browser/ash/arc/nearby_share/arc_nearby_share_bridge_unittest.cc
@@ -0,0 +1,48 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/arc/nearby_share/arc_nearby_share_bridge.h"
+
+#include "chrome/test/base/testing_profile.h"
+#include "components/arc/arc_service_manager.h"
+#include "components/arc/session/arc_bridge_service.h"
+#include "components/arc/test/connection_holder_util.h"
+#include "components/arc/test/fake_nearby_share_instance.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace arc {
+
+class ArcNearbyShareBridgeTest : public testing::Test {
+ public:
+  ArcNearbyShareBridgeTest() = default;
+  ~ArcNearbyShareBridgeTest() override = default;
+
+  ArcNearbyShareBridgeTest(const ArcNearbyShareBridgeTest&) = delete;
+  ArcNearbyShareBridgeTest& operator=(const ArcNearbyShareBridgeTest&) = delete;
+
+  void SetUp() override {
+    bridge_ = ArcNearbyShareBridge::GetForBrowserContextForTesting(&profile_);
+
+    EXPECT_EQ(0u, nearby_share_instance_.num_init_called());
+    ArcServiceManager::Get()->arc_bridge_service()->nearby_share()->SetInstance(
+        &nearby_share_instance_);
+    WaitForInstanceReady(
+        ArcServiceManager::Get()->arc_bridge_service()->nearby_share());
+    // Tests that FakeNearbyShareInstance's Init() method is called after the
+    // instance connects to the host.
+    EXPECT_EQ(1u, nearby_share_instance_.num_init_called());
+  }
+
+ protected:
+  content::BrowserTaskEnvironment task_environment_;
+  ArcServiceManager arc_service_manager_;
+  FakeNearbyShareInstance nearby_share_instance_;
+  TestingProfile profile_;
+  ArcNearbyShareBridge* bridge_ = nullptr;
+};
+
+TEST_F(ArcNearbyShareBridgeTest, ConstructDestruct) {}
+
+}  // namespace arc
diff --git a/chrome/browser/ash/arc/nearby_share/nearby_share_session_impl.cc b/chrome/browser/ash/arc/nearby_share/nearby_share_session_impl.cc
new file mode 100644
index 0000000..a464f48
--- /dev/null
+++ b/chrome/browser/ash/arc/nearby_share/nearby_share_session_impl.cc
@@ -0,0 +1,224 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/arc/nearby_share/nearby_share_session_impl.h"
+
+#include <limits>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "ash/public/cpp/app_types_util.h"
+#include "base/bind.h"
+#include "chrome/browser/apps/app_service/intent_util.h"
+#include "chrome/browser/ash/arc/arc_util.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/sharesheet/sharesheet_service.h"
+#include "chrome/browser/sharesheet/sharesheet_service_factory.h"
+#include "chrome/browser/sharesheet/sharesheet_types.h"
+#include "components/arc/arc_util.h"
+#include "components/arc/intent_helper/custom_tab.h"
+#include "components/services/app_service/public/cpp/intent_util.h"
+#include "components/services/app_service/public/mojom/types.mojom-forward.h"
+
+namespace arc {
+
+namespace {
+// Maximum time to wait for the ARC window to be initialized.
+// ARC Wayland messages and Mojo messages are sent across the same pipe. The
+// order in which the messages are sent is not deterministic. If the ARC
+// activity starts Nearby Share before the wayland message for the new has been
+// processed, the corresponding aura::Window for a given ARC activity task ID
+// will not be found. To get around this, NearbyShareSessionImpl will wait a
+// little while for the Wayland message to be processed and the window to be
+// initialized.
+constexpr base::TimeDelta kWindowInitializationTimeout =
+    base::TimeDelta::FromSeconds(1);
+
+constexpr char kIntentExtraText[] = "android.intent.extra.TEXT";
+}  // namespace
+
+// static
+mojo::PendingRemote<mojom::NearbyShareSessionHost>
+NearbyShareSessionImpl::Create(
+    std::unique_ptr<content::WebContents> web_contents,
+    int32_t task_id,
+    mojom::ShareIntentInfoPtr share_info,
+    mojo::PendingRemote<mojom::NearbyShareSessionInstance> instance) {
+  if (!instance) {
+    LOG(ERROR) << "instance is null. Unable to create NearbyShareSessionImpl";
+    return mojo::NullRemote();
+  }
+
+  auto* web_contents_ptr = web_contents.get();
+  mojo::PendingRemote<mojom::NearbyShareSessionHost> remote;
+  // The NearbyShareSessionImpl instance will be deleted when the mojo
+  // connection is closed.
+  NearbyShareSessionImpl::CreateForWebContents(
+      web_contents_ptr, std::move(web_contents), task_id, std::move(share_info),
+      std::move(instance), remote.InitWithNewPipeAndPassReceiver());
+  return remote;
+}
+
+NearbyShareSessionImpl::~NearbyShareSessionImpl() {
+  env_observation_.Reset();
+  arc_window_observation_.Reset();
+  web_contents_->RemoveUserData(UserDataKey());
+}
+
+void NearbyShareSessionImpl::OnNearbyShareClosed() {
+  session_instance_->OnNearbyShareViewClosed();
+}
+
+// Overridden from aura::EnvObserver:
+void NearbyShareSessionImpl::OnWindowInitialized(aura::Window* window) {
+  if (ash::IsArcWindow(window) && (arc::GetWindowTaskId(window) == task_id_)) {
+    env_observation_.Reset();
+    arc_window_observation_.Observe(window);
+  }
+}
+
+// Overridden from aura::WindowObserver
+void NearbyShareSessionImpl::OnWindowVisibilityChanged(aura::Window* window,
+                                                       bool visible) {
+  if (visible && (arc::GetWindowTaskId(window) == task_id_)) {
+    VLOG(1) << "ARC Window is visible";
+    window_initialization_timer_.Stop();
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE,
+        base::BindOnce(&NearbyShareSessionImpl::ShowNearbyBubble,
+                       weak_ptr_factory_.GetWeakPtr(), std::move(window)));
+  }
+}
+
+// Overridden from aura::WindowObserver
+void NearbyShareSessionImpl::OnWindowDestroying(aura::Window* removed_window) {
+  Close();
+}
+
+NearbyShareSessionImpl::NearbyShareSessionImpl(
+    content::WebContents* web_contents_ptr,
+    std::unique_ptr<content::WebContents> web_contents,
+    int32_t task_id,
+    mojom::ShareIntentInfoPtr share_info,
+    mojo::PendingRemote<mojom::NearbyShareSessionInstance> session_instance,
+    mojo::PendingReceiver<mojom::NearbyShareSessionHost> session_receiver)
+    : ArcCustomTabModalDialogHost(nullptr, web_contents.get()),
+      task_id_(task_id),
+      session_instance_(std::move(session_instance)),
+      session_receiver_(this, std::move(session_receiver)),
+      share_info_(std::move(share_info)),
+      web_contents_(std::move(web_contents)) {
+  session_receiver_.set_disconnect_handler(base::BindOnce(
+      &NearbyShareSessionImpl::Close, weak_ptr_factory_.GetWeakPtr()));
+
+  aura::Window* arc_window = GetArcWindow(task_id_);
+  if (arc_window) {
+    VLOG(1) << "ARC window found. Creating NearbySession.";
+    ShowNearbyBubble(std::move(arc_window));
+  } else {
+    VLOG(1) << "No ARC window found for task ID " << task_id_;
+    env_observation_.Observe(aura::Env::GetInstance());
+    window_initialization_timer_.Start(
+        FROM_HERE, kWindowInitializationTimeout,
+        base::BindOnce(&NearbyShareSessionImpl::OnTimerFired,
+                       weak_ptr_factory_.GetWeakPtr()));
+  }
+}
+
+void NearbyShareSessionImpl::ShowNearbyBubble(aura::Window* arc_window) {
+  // Create ARC Custom Tab for given ARC window
+  custom_tab_ = std::make_unique<CustomTab>(arc_window);
+
+  // Attach |web_contents_| to the Custom Tab and show it.
+  aura::Window* window = web_contents_->GetNativeView();
+  custom_tab_->Attach(window);
+  window->Show();
+
+  VLOG(1) << "Getting Sharesheet service";
+  content::BrowserContext* browser_context = web_contents_->GetBrowserContext();
+  sharesheet::SharesheetService* sharesheet_service =
+      sharesheet::SharesheetServiceFactory::GetForProfile(
+          Profile::FromBrowserContext(browser_context));
+  if (!sharesheet_service) {
+    LOG(ERROR) << "Cannot find sharesheet service";
+    return;
+  }
+
+  VLOG(1) << "Calling ShowNearbyShareBubble";
+  sharesheet_service->ShowNearbyShareBubble(
+      web_contents_.get(), ConvertShareIntentInfoToIntentFilter(),
+      sharesheet::SharesheetMetrics::LaunchSource::kArcNearbyShare,
+      base::BindOnce(&NearbyShareSessionImpl::OnNearbyShareBubbleShown,
+                     weak_ptr_factory_.GetWeakPtr()),
+      base::BindOnce(&NearbyShareSessionImpl::OnNearbyShareClosed,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+apps::mojom::IntentPtr
+NearbyShareSessionImpl::ConvertShareIntentInfoToIntentFilter() const {
+  // Sharing files & text
+  if (share_info_->files.has_value()) {
+    std::vector<GURL> content_urls;
+    std::vector<std::string> file_mime_types;
+    for (const mojom::FileInfoPtr& file_info : share_info_->files.value()) {
+      content_urls.emplace_back(GURL(file_info->content_uri));
+      file_mime_types.emplace_back(file_info->mime_type);
+    }
+    std::string text;
+    if (share_info_->extras.has_value() &&
+        share_info_->extras->contains(kIntentExtraText)) {
+      text = share_info_->extras->at(kIntentExtraText);
+    }
+    return apps_util::CreateShareIntentFromFiles(content_urls, file_mime_types,
+                                                 text, share_info_->title);
+  }
+
+  // Sharing only text
+  if (share_info_->extras.has_value() &&
+      share_info_->extras->contains(kIntentExtraText)) {
+    apps::mojom::IntentPtr share_intent = apps_util::CreateShareIntentFromText(
+        share_info_->extras->at(kIntentExtraText), share_info_->title);
+    share_intent->mime_type = share_info_->mime_type;
+    return share_intent;
+  }
+  VLOG(1) << "No Sharing info found";
+  return nullptr;
+}
+
+void NearbyShareSessionImpl::OnNearbyShareBubbleShown(
+    sharesheet::SharesheetResult result) {
+  if (VLOG_IS_ON(1)) {
+    switch (result) {
+      case sharesheet::SharesheetResult::kSuccess:
+        VLOG(1) << "OnNearbyShareBubbleShown: SUCCESS";
+        break;
+      case sharesheet::SharesheetResult::kCancel:
+        VLOG(1) << "OnNearbyShareBubbleShown: CANCEL";
+        break;
+      case sharesheet::SharesheetResult::kErrorAlreadyOpen:
+        VLOG(1) << "OnNearbyShareBubbleShown: ALREADY OPEN";
+        break;
+      default:
+        VLOG(1) << "OnNearbyShareBubbleShown: UNKNOWN";
+    }
+  }
+  if (result != sharesheet::SharesheetResult::kSuccess) {
+    session_instance_->OnNearbyShareViewClosed();
+  }
+}
+
+void NearbyShareSessionImpl::OnTimerFired() {
+  // TODO(phshah): Handle error case and add UMA metric.
+  LOG(ERROR) << " ARC window didn't get initialized in time.";
+  env_observation_.Reset();
+}
+
+void NearbyShareSessionImpl::Close() {
+  web_contents_->RemoveUserData(UserDataKey());
+}
+
+WEB_CONTENTS_USER_DATA_KEY_IMPL(NearbyShareSessionImpl)
+
+}  // namespace arc
diff --git a/chrome/browser/ash/arc/nearby_share/nearby_share_session_impl.h b/chrome/browser/ash/arc/nearby_share/nearby_share_session_impl.h
new file mode 100644
index 0000000..0cd1c86
--- /dev/null
+++ b/chrome/browser/ash/arc/nearby_share/nearby_share_session_impl.h
@@ -0,0 +1,124 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ASH_ARC_NEARBY_SHARE_NEARBY_SHARE_SESSION_IMPL_H_
+#define CHROME_BROWSER_ASH_ARC_NEARBY_SHARE_NEARBY_SHARE_SESSION_IMPL_H_
+
+#include <cstdint>
+#include <memory>
+
+#include "base/memory/weak_ptr.h"
+#include "base/scoped_observation.h"
+#include "base/timer/timer.h"
+#include "chrome/browser/sharesheet/sharesheet_service.h"
+#include "chrome/browser/ui/ash/arc_custom_tab_modal_dialog_host.h"
+#include "components/arc/mojom/nearby_share.mojom-forward.h"
+#include "components/arc/mojom/nearby_share.mojom.h"
+#include "content/public/browser/web_contents_user_data.h"
+#include "ui/aura/env.h"
+#include "ui/aura/env_observer.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_observer.h"
+
+namespace content {
+class WebContents;
+}  // namespace content
+
+namespace arc {
+
+// Implementation of NearbyShareSession interface.
+class NearbyShareSessionImpl
+    : public mojom::NearbyShareSessionHost,
+      public content::WebContentsUserData<NearbyShareSessionImpl>,
+      public ArcCustomTabModalDialogHost,
+      public aura::WindowObserver,
+      public aura::EnvObserver {
+ public:
+  static mojo::PendingRemote<mojom::NearbyShareSessionHost> Create(
+      std::unique_ptr<content::WebContents> web_contents,
+      int32_t task_id,
+      mojom::ShareIntentInfoPtr share_info,
+      mojo::PendingRemote<mojom::NearbyShareSessionInstance> instance);
+
+  NearbyShareSessionImpl(const NearbyShareSessionImpl&) = delete;
+  NearbyShareSessionImpl& operator=(const NearbyShareSessionImpl&) = delete;
+  ~NearbyShareSessionImpl() override;
+
+  // Called when NearbyShare is closed.
+  void OnNearbyShareClosed();
+
+  // aura::EnvObserver:
+  void OnWindowInitialized(aura::Window* window) override;
+
+  // aura::WindowObserver:
+  void OnWindowVisibilityChanged(aura::Window* window, bool visible) override;
+
+  // aura::WindowObserver:
+  void OnWindowDestroying(aura::Window* window) override;
+
+ private:
+  friend class content::WebContentsUserData<NearbyShareSessionImpl>;
+
+  NearbyShareSessionImpl(
+      content::WebContents* web_contents_ptr,
+      std::unique_ptr<content::WebContents> web_contents,
+      int32_t task_id,
+      mojom::ShareIntentInfoPtr share_info,
+      mojo::PendingRemote<mojom::NearbyShareSessionInstance> session_instance,
+      mojo::PendingReceiver<mojom::NearbyShareSessionHost> receiver);
+
+  // Creates the Chrome Custom Tab and calls
+  // |SharesheetService.ShowNearbyShareBubble()| to start the Chrome Nearby
+  // Share user flow.
+  void ShowNearbyBubble(aura::Window* arc_window);
+
+  // Converts |share_info_| to |apps::mojom::IntentPtr| type.
+  apps::mojom::IntentPtr ConvertShareIntentInfoToIntentFilter() const;
+
+  void OnNearbyShareBubbleShown(sharesheet::SharesheetResult result);
+
+  // Called back once the session duration exceeds the maximum duration.
+  void OnTimerFired();
+
+  // Close the ARC Custom Tab and NearbyShare Bubble if remote connection
+  // closes.
+  void Close();
+
+  // Android activity's task ID
+  int32_t task_id_;
+
+  // Used to send messages to ARC.
+  mojo::Remote<mojom::NearbyShareSessionInstance> session_instance_;
+
+  // Used to bind the NearbyShareSessionHost interface implementation to a
+  // message pipe.
+  mojo::Receiver<mojom::NearbyShareSessionHost> session_receiver_;
+
+  // Contents to be shared.
+  mojom::ShareIntentInfoPtr share_info_;
+
+  // Web contents for the ARC custom tab.
+  std::unique_ptr<content::WebContents> web_contents_;
+
+  // Timer used to wait for the ARC window to be asynchronously initialized and
+  // visible.
+  base::OneShotTimer window_initialization_timer_;
+
+  // Observes the ARC window.
+  base::ScopedObservation<aura::Window, aura::WindowObserver>
+      arc_window_observation_{this};
+
+  // Observes the Aura environment.
+  base::ScopedObservation<aura::Env, aura::EnvObserver> env_observation_{this};
+
+  WEB_CONTENTS_USER_DATA_KEY_DECL();
+
+  // Note: This should remain the last member so it'll be destroyed and
+  // invalidate its weak pointers before any other members are destroyed.
+  base::WeakPtrFactory<NearbyShareSessionImpl> weak_ptr_factory_{this};
+};
+
+}  // namespace arc
+
+#endif  // CHROME_BROWSER_ASH_ARC_NEARBY_SHARE_NEARBY_SHARE_SESSION_IMPL_H_
diff --git a/chrome/browser/ash/arc/session/arc_service_launcher.cc b/chrome/browser/ash/arc/session/arc_service_launcher.cc
index 4df2e6c..fb84461 100644
--- a/chrome/browser/ash/arc/session/arc_service_launcher.cc
+++ b/chrome/browser/ash/arc/session/arc_service_launcher.cc
@@ -32,6 +32,7 @@
 #include "chrome/browser/ash/arc/keymaster/arc_keymaster_bridge.h"
 #include "chrome/browser/ash/arc/kiosk/arc_kiosk_bridge.h"
 #include "chrome/browser/ash/arc/metrics/arc_metrics_service_proxy.h"
+#include "chrome/browser/ash/arc/nearby_share/arc_nearby_share_bridge.h"
 #include "chrome/browser/ash/arc/notification/arc_boot_error_notification.h"
 #include "chrome/browser/ash/arc/notification/arc_provision_notification_service.h"
 #include "chrome/browser/ash/arc/oemcrypto/arc_oemcrypto_bridge.h"
@@ -223,6 +224,7 @@
       }));
   ArcMetricsServiceProxy::GetForBrowserContext(profile);
   ArcMidisBridge::GetForBrowserContext(profile);
+  ArcNearbyShareBridge::GetForBrowserContext(profile);
   ArcNetHostImpl::GetForBrowserContext(profile)->SetPrefService(
       profile->GetPrefs());
   ArcOemCryptoBridge::GetForBrowserContext(profile);
diff --git a/chrome/browser/ash/borealis/borealis_context.cc b/chrome/browser/ash/borealis/borealis_context.cc
index 5b27154..b3a8cd0 100644
--- a/chrome/browser/ash/borealis/borealis_context.cc
+++ b/chrome/browser/ash/borealis/borealis_context.cc
@@ -118,6 +118,11 @@
 
 BorealisContext::~BorealisContext() = default;
 
+void BorealisContext::SetDiskManagerForTesting(
+    std::unique_ptr<BorealisDiskManager> disk_manager) {
+  disk_manager_ = std::move(disk_manager);
+}
+
 void BorealisContext::NotifyUnexpectedVmShutdown() {
   guest_os_stability_monitor_->LogUnexpectedVmShutdown();
 }
diff --git a/chrome/browser/ash/borealis/borealis_context.h b/chrome/browser/ash/borealis/borealis_context.h
index d0cbefb2..ad12e67e 100644
--- a/chrome/browser/ash/borealis/borealis_context.h
+++ b/chrome/browser/ash/borealis/borealis_context.h
@@ -47,6 +47,10 @@
   const base::FilePath& disk_path() const { return disk_path_; }
   void set_disk_path(base::FilePath path) { disk_path_ = std::move(path); }
 
+  BorealisDiskManager& get_disk_manager() { return *disk_manager_.get(); }
+  void SetDiskManagerForTesting(
+      std::unique_ptr<BorealisDiskManager> disk_manager);
+
   // Called to signal that this Borealis VM is being unexpectedly shut down.
   // Not to be called during intentional shutdowns.
   void NotifyUnexpectedVmShutdown();
diff --git a/chrome/browser/ash/borealis/borealis_context_manager_impl.cc b/chrome/browser/ash/borealis/borealis_context_manager_impl.cc
index 046bc78e..fb9834d 100644
--- a/chrome/browser/ash/borealis/borealis_context_manager_impl.cc
+++ b/chrome/browser/ash/borealis/borealis_context_manager_impl.cc
@@ -206,6 +206,7 @@
   task_queue.push(std::make_unique<StartBorealisVm>());
   task_queue.push(
       std::make_unique<AwaitBorealisStartup>(profile_, kBorealisVmName));
+  task_queue.push(std::make_unique<SyncBorealisDisk>());
   return task_queue;
 }
 
diff --git a/chrome/browser/ash/borealis/borealis_disk_manager.h b/chrome/browser/ash/borealis/borealis_disk_manager.h
index 557946b2..69bbb6db 100644
--- a/chrome/browser/ash/borealis/borealis_disk_manager.h
+++ b/chrome/browser/ash/borealis/borealis_disk_manager.h
@@ -45,6 +45,10 @@
   virtual void ReleaseSpace(
       uint64_t bytes_to_release,
       base::OnceCallback<void(Expected<uint64_t, std::string>)> callback) = 0;
+
+  // Assesses the disk and resizes it so that it fits within the desired
+  // constraints. Returns an empty string on success or an error.
+  virtual void SyncDiskSize(base::OnceCallback<void(std::string)> callback) = 0;
 };
 
 }  // namespace borealis
diff --git a/chrome/browser/ash/borealis/borealis_disk_manager_dispatcher_unittest.cc b/chrome/browser/ash/borealis/borealis_disk_manager_dispatcher_unittest.cc
index 1fda0be..33356b8b 100644
--- a/chrome/browser/ash/borealis/borealis_disk_manager_dispatcher_unittest.cc
+++ b/chrome/browser/ash/borealis/borealis_disk_manager_dispatcher_unittest.cc
@@ -31,6 +31,7 @@
               (uint64_t,
                base::OnceCallback<void(Expected<uint64_t, std::string>)>),
               ());
+  MOCK_METHOD(void, SyncDiskSize, (base::OnceCallback<void(std::string)>), ());
 };
 
 using DiskInfoCallbackFactory = NiceCallbackFactory<void(
diff --git a/chrome/browser/ash/borealis/borealis_disk_manager_impl.cc b/chrome/browser/ash/borealis/borealis_disk_manager_impl.cc
index 7fdb7e7..2f64f24f 100644
--- a/chrome/browser/ash/borealis/borealis_disk_manager_impl.cc
+++ b/chrome/browser/ash/borealis/borealis_disk_manager_impl.cc
@@ -26,10 +26,14 @@
 
 namespace borealis {
 
+struct Nothing {};
+
 constexpr int64_t kGiB = 1024 * 1024 * 1024;
 constexpr int64_t kDiskHeadroomBytes = 1 * kGiB;
 constexpr int64_t kTargetBufferBytes = 2 * kGiB;
 constexpr int64_t kDiskRoundingBytes = 2 * 1024 * 1024;
+constexpr int64_t kTargetBufferLowerBound = kTargetBufferBytes * 0.9;
+constexpr int64_t kTargetBufferUpperBound = kTargetBufferBytes * 1.1;
 
 void BorealisDiskManagerImpl::FreeSpaceProvider::Get(
     base::OnceCallback<void(int64_t)> callback) {
@@ -52,6 +56,8 @@
   int64_t min_size = 0;
   // Image type of the disk.
   vm_tools::concierge::DiskImageType disk_type;
+  // If the disk isn't fixed, we shouldn't be resizing it.
+  bool has_fixed_size = 0;
 };
 
 BorealisDiskManagerImpl::BorealisDiskManagerImpl(const BorealisContext* context)
@@ -129,6 +135,7 @@
     disk_info_->min_size = image->min_size();
     disk_info_->disk_size = image->size();
     disk_info_->disk_type = image->image_type();
+    disk_info_->has_fixed_size = image->user_chosen_size();
 
     Succeed(std::move(disk_info_));
   }
@@ -190,12 +197,11 @@
       return;
     }
     if (space_delta_ < 0) {
-      if (original_disk_info_.available_space + space_delta_ <=
+      if (original_disk_info_.available_space + space_delta_ <
           kTargetBufferBytes) {
         Fail("shrinking the disk would not leave enough space available");
         return;
       }
-
       if (original_disk_info_.disk_size + space_delta_ <=
           original_disk_info_.min_size) {
         Fail("cannot shrink the disk below its minimum size");
@@ -284,10 +290,90 @@
   BorealisDiskInfo updated_disk_info_;
   const BorealisContext* const context_;
   std::unique_ptr<BuildDiskInfo> build_disk_info_transition_;
-  std::unique_ptr<BorealisDiskManagerImpl::BorealisDiskInfo> start_instance_;
   base::WeakPtrFactory<ResizeDisk> weak_factory_;
 };
 
+class BorealisDiskManagerImpl::SyncDisk
+    : public Transition<Nothing, BorealisDiskInfo, std::string> {
+ public:
+  explicit SyncDisk(
+      BorealisDiskManagerImpl::FreeSpaceProvider* free_space_provider,
+      const BorealisContext* context)
+      : free_space_provider_(free_space_provider),
+        context_(context),
+        build_disk_info_transition_(free_space_provider_, context_),
+        weak_factory_(this) {}
+
+  void Start(std::unique_ptr<Nothing> start_instance) override {
+    build_disk_info_transition_.Begin(
+        std::make_unique<BorealisDiskInfo>(),
+        base::BindOnce(&SyncDisk::HandleDiskInfo, weak_factory_.GetWeakPtr()));
+  }
+
+ private:
+  void HandleDiskInfo(Expected<std::unique_ptr<BorealisDiskInfo>, std::string>
+                          disk_info_or_error) {
+    if (!disk_info_or_error) {
+      Fail("BuildDiskInfo failed: " + disk_info_or_error.Error());
+      return;
+    }
+    if (!disk_info_or_error.Value()->has_fixed_size) {
+      // We're currently not handling sparse sized disks.
+      Succeed(std::move(disk_info_or_error.Value()));
+      return;
+    }
+    const BorealisDiskInfo& disk_info = *disk_info_or_error.Value();
+    if (IsDiskSizeWithinBounds(disk_info)) {
+      Succeed(std::move(disk_info_or_error.Value()));
+      return;
+    }
+    int64_t delta = kTargetBufferBytes - disk_info.available_space;
+    if (delta > disk_info.expandable_space) {
+      delta = disk_info.expandable_space;
+    }
+    if (delta == 0) {
+      LOG(WARNING) << "borealis' available space is not within parameters, "
+                      "but there is not enough free space to expand it";
+      Succeed(std::move(disk_info_or_error.Value()));
+      return;
+    }
+    resize_disk_transition_ =
+        std::make_unique<ResizeDisk>(delta, free_space_provider_, context_);
+    resize_disk_transition_->Begin(
+        std::move(disk_info_or_error.Value()),
+        base::BindOnce(&SyncDisk::HandleResizeAttempt,
+                       weak_factory_.GetWeakPtr()));
+    return;
+  }
+
+  void HandleResizeAttempt(
+      Expected<std::unique_ptr<std::pair<BorealisDiskInfo, BorealisDiskInfo>>,
+               std::string> disk_info_or_error) {
+    if (!disk_info_or_error) {
+      Fail("resize failed: " + disk_info_or_error.Error());
+      return;
+    }
+    if (!IsDiskSizeWithinBounds(disk_info_or_error.Value()->second)) {
+      LOG(WARNING) << "disk resized successfully, but available space is not "
+                      "within its parameters";
+    }
+    Succeed(
+        std::make_unique<BorealisDiskInfo>(disk_info_or_error.Value()->second));
+    return;
+  }
+
+  bool IsDiskSizeWithinBounds(const BorealisDiskInfo& disk_info) {
+    return disk_info.available_space >= kTargetBufferLowerBound &&
+           disk_info.available_space <= kTargetBufferUpperBound;
+  }
+
+  BorealisDiskManagerImpl::FreeSpaceProvider* free_space_provider_;
+  const BorealisContext* const context_;
+  BuildDiskInfo build_disk_info_transition_;
+  std::unique_ptr<ResizeDisk> resize_disk_transition_;
+  base::WeakPtrFactory<SyncDisk> weak_factory_;
+};
+
 void BorealisDiskManagerImpl::GetDiskInfo(
     base::OnceCallback<void(Expected<GetDiskInfoResponse, std::string>)>
         callback) {
@@ -416,4 +502,33 @@
   RequestSpaceDelta(int64_t(bytes_to_release) * -1, std::move(callback));
 }
 
+void BorealisDiskManagerImpl::SyncDiskSize(
+    base::OnceCallback<void(std::string)> callback) {
+  if (sync_disk_transition_) {
+    std::string error = "another SyncDiskSize request is in progress";
+    LOG(ERROR) << error;
+    std::move(callback).Run(std::move(error));
+    return;
+  }
+  sync_disk_transition_ =
+      std::make_unique<SyncDisk>(free_space_provider_.get(), context_);
+  sync_disk_transition_->Begin(
+      std::make_unique<Nothing>(),
+      base::BindOnce(&BorealisDiskManagerImpl::OnSyncDiskSize,
+                     weak_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void BorealisDiskManagerImpl::OnSyncDiskSize(
+    base::OnceCallback<void(std::string)> callback,
+    Expected<std::unique_ptr<BorealisDiskInfo>, std::string>
+        disk_info_or_error) {
+  sync_disk_transition_.reset();
+  std::string error = "";
+  if (!disk_info_or_error) {
+    error = "SyncDiskSize failed: " + disk_info_or_error.Error();
+    LOG(ERROR) << error;
+  }
+  std::move(callback).Run(std::move(error));
+}
+
 }  // namespace borealis
diff --git a/chrome/browser/ash/borealis/borealis_disk_manager_impl.h b/chrome/browser/ash/borealis/borealis_disk_manager_impl.h
index 42c3b361..d3bf4bfd 100644
--- a/chrome/browser/ash/borealis/borealis_disk_manager_impl.h
+++ b/chrome/browser/ash/borealis/borealis_disk_manager_impl.h
@@ -44,6 +44,10 @@
   void ReleaseSpace(uint64_t bytes_to_release,
                     base::OnceCallback<void(Expected<uint64_t, std::string>)>
                         callback) override;
+  // TODO(b/174592560): Since there are differing success criteria, we may wish
+  // to split this into Expected<SuccessEnum, ErrorEnum> when we expand on the
+  // error handling.
+  void SyncDiskSize(base::OnceCallback<void(std::string)> callback) override;
 
   void SetFreeSpaceProviderForTesting(
       std::unique_ptr<FreeSpaceProvider> provider) {
@@ -65,6 +69,12 @@
   // resize).
   class ResizeDisk;
 
+  // Transition class representing the process of assessing the state of the VM
+  // disk and adjusting it to fit within the appropriate parameters if needed.
+  // Specifically, when the available space on the disk does not match the
+  // target buffer size +/-10%, we will resize the disk.
+  class SyncDisk;
+
   // Handles the results of a GetDiskInfo request.
   void BuildGetDiskInfoResponse(
       base::OnceCallback<void(Expected<GetDiskInfoResponse, std::string>)>
@@ -87,9 +97,14 @@
       Expected<std::unique_ptr<std::pair<BorealisDiskInfo, BorealisDiskInfo>>,
                std::string> disk_info_or_error);
 
+  void OnSyncDiskSize(base::OnceCallback<void(std::string)> callback,
+                      Expected<std::unique_ptr<BorealisDiskInfo>, std::string>
+                          disk_info_or_error);
+
   const BorealisContext* const context_;
   std::unique_ptr<BuildDiskInfo> build_disk_info_transition_;
   std::unique_ptr<ResizeDisk> resize_disk_transition_;
+  std::unique_ptr<SyncDisk> sync_disk_transition_;
   std::unique_ptr<FreeSpaceProvider> free_space_provider_;
   base::WeakPtrFactory<BorealisDiskManagerImpl> weak_factory_;
 };
diff --git a/chrome/browser/ash/borealis/borealis_disk_manager_unittest.cc b/chrome/browser/ash/borealis/borealis_disk_manager_unittest.cc
index 5db3309..95f8ba0b 100644
--- a/chrome/browser/ash/borealis/borealis_disk_manager_unittest.cc
+++ b/chrome/browser/ash/borealis/borealis_disk_manager_unittest.cc
@@ -29,6 +29,9 @@
 namespace borealis {
 namespace {
 
+using ::testing::_;
+using ::testing::Not;
+
 constexpr int64_t kGiB = 1024 * 1024 * 1024;
 
 class FreeSpaceProviderMock
@@ -45,6 +48,8 @@
 using RequestDeltaCallbackFactory =
     StrictCallbackFactory<void(Expected<uint64_t, std::string>)>;
 
+using SyncDiskCallbackFactory = NiceCallbackFactory<void(std::string)>;
+
 class BorealisDiskDispatcherMock : public BorealisDiskManagerDispatcher {
  public:
   BorealisDiskDispatcherMock() = default;
@@ -135,18 +140,16 @@
     profile_ = profile_builder.Build();
   }
 
-  vm_tools::concierge::ListVmDisksResponse BuildListVmDisksResponse(
-      bool success,
-      const std::string& vm_name,
-      vm_tools::concierge::DiskImageType image_type,
+  vm_tools::concierge::ListVmDisksResponse BuildValidListVmDisksResponse(
       int64_t min_size,
       int64_t size,
       int64_t available_space) {
     vm_tools::concierge::ListVmDisksResponse response;
     auto* image = response.add_images();
-    response.set_success(success);
-    image->set_name(vm_name);
-    image->set_image_type(image_type);
+    response.set_success(true);
+    image->set_name("vm_name1");
+    image->set_image_type(vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW);
+    image->set_user_chosen_size(true);
     image->set_min_size(min_size);
     image->set_size(size);
     image->set_available_space(available_space);
@@ -171,13 +174,13 @@
 };
 
 TEST_F(BorealisDiskManagerTest, GetDiskInfoFailsOnFreeSpaceProviderError) {
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(testing::Invoke([](base::OnceCallback<void(int64_t)> callback) {
         std::move(callback).Run(-1);
       }));
 
   DiskInfoCallbackFactory callback_factory;
-  EXPECT_CALL(callback_factory, Call(testing::_))
+  EXPECT_CALL(callback_factory, Call(_))
       .WillOnce(testing::Invoke(
           [](Expected<BorealisDiskManagerImpl::GetDiskInfoResponse, std::string>
                  response_or_error) { EXPECT_FALSE(response_or_error); }));
@@ -186,7 +189,7 @@
 }
 
 TEST_F(BorealisDiskManagerTest, GetDiskInfoFailsOnNoResponseFromConcierge) {
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
             // Concierge will return an empty ListVmDisksResponse.
@@ -196,7 +199,7 @@
           }));
 
   DiskInfoCallbackFactory callback_factory;
-  EXPECT_CALL(callback_factory, Call(testing::_))
+  EXPECT_CALL(callback_factory, Call(_))
       .WillOnce(testing::Invoke(
           [](Expected<BorealisDiskManagerImpl::GetDiskInfoResponse, std::string>
                  response_or_error) { EXPECT_FALSE(response_or_error); }));
@@ -206,20 +209,19 @@
 
 TEST_F(BorealisDiskManagerTest,
        GetDiskInfoFailsOnUnsuccessfulResponseFromConcierge) {
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
-            fake_concierge_client_->set_list_vm_disks_response(
-                BuildListVmDisksResponse(
-                    /*success=*/false, /*vm_name=*/"vm_name1", /*image_type=*/
-                    vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW,
-                    /*min_size=*/6 * kGiB, /*size=*/8 * kGiB,
-                    /*available_space=*/1 * kGiB));
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/8 * kGiB,
+                /*available_space=*/1 * kGiB);
+            response.set_success(false);
+            fake_concierge_client_->set_list_vm_disks_response(response);
             std::move(callback).Run(1 * kGiB);
           }));
 
   DiskInfoCallbackFactory callback_factory;
-  EXPECT_CALL(callback_factory, Call(testing::_))
+  EXPECT_CALL(callback_factory, Call(_))
       .WillOnce(testing::Invoke(
           [](Expected<BorealisDiskManagerImpl::GetDiskInfoResponse, std::string>
                  response_or_error) { EXPECT_FALSE(response_or_error); }));
@@ -228,21 +230,19 @@
 }
 
 TEST_F(BorealisDiskManagerTest, GetDiskInfoFailsOnVmMismatch) {
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
-            fake_concierge_client_->set_list_vm_disks_response(
-                BuildListVmDisksResponse(
-                    /*success=*/true,
-                    /*vm_name=*/"UNMATCHED_VM", /*image_type=*/
-                    vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW,
-                    /*min_size=*/6 * kGiB, /*size=*/8 * kGiB,
-                    /*available_space=*/1 * kGiB));
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/8 * kGiB,
+                /*available_space=*/1 * kGiB);
+            response.mutable_images()->at(0).set_name("UNMATCHED_VM");
+            fake_concierge_client_->set_list_vm_disks_response(response);
             std::move(callback).Run(1 * kGiB);
           }));
 
   DiskInfoCallbackFactory callback_factory;
-  EXPECT_CALL(callback_factory, Call(testing::_))
+  EXPECT_CALL(callback_factory, Call(_))
       .WillOnce(testing::Invoke(
           [](Expected<BorealisDiskManagerImpl::GetDiskInfoResponse, std::string>
                  response_or_error) { EXPECT_FALSE(response_or_error); }));
@@ -251,20 +251,18 @@
 }
 
 TEST_F(BorealisDiskManagerTest, GetDiskInfoSucceedsAndReturnsResponse) {
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
-            fake_concierge_client_->set_list_vm_disks_response(
-                BuildListVmDisksResponse(
-                    /*success=*/true, /*vm_name=*/"vm_name1", /*image_type=*/
-                    vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW,
-                    /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
-                    /*available_space=*/3 * kGiB));
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
+                /*available_space=*/3 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
             std::move(callback).Run(2 * kGiB);
           }));
 
   DiskInfoCallbackFactory callback_factory;
-  EXPECT_CALL(callback_factory, Call(testing::_))
+  EXPECT_CALL(callback_factory, Call(_))
       .WillOnce(testing::Invoke(
           [](Expected<BorealisDiskManagerImpl::GetDiskInfoResponse, std::string>
                  response_or_error) {
@@ -280,25 +278,23 @@
 }
 
 TEST_F(BorealisDiskManagerTest, GetDiskInfoFailsOnConcurrentAttempt) {
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
-            fake_concierge_client_->set_list_vm_disks_response(
-                BuildListVmDisksResponse(
-                    /*success=*/true, /*vm_name=*/"vm_name1", /*image_type=*/
-                    vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW,
-                    /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
-                    /*available_space=*/3 * kGiB));
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
+                /*available_space=*/3 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
             std::move(callback).Run(2 * kGiB);
           }));
 
   DiskInfoCallbackFactory first_callback_factory;
   DiskInfoCallbackFactory second_callback_factory;
-  EXPECT_CALL(first_callback_factory, Call(testing::_))
+  EXPECT_CALL(first_callback_factory, Call(_))
       .WillOnce(testing::Invoke(
           [](Expected<BorealisDiskManagerImpl::GetDiskInfoResponse, std::string>
                  response_or_error) { EXPECT_TRUE(response_or_error); }));
-  EXPECT_CALL(second_callback_factory, Call(testing::_))
+  EXPECT_CALL(second_callback_factory, Call(_))
       .WillOnce(testing::Invoke(
           [](Expected<BorealisDiskManagerImpl::GetDiskInfoResponse, std::string>
                  response_or_error) { EXPECT_FALSE(response_or_error); }));
@@ -308,17 +304,15 @@
 }
 
 TEST_F(BorealisDiskManagerTest, GetDiskInfoSubsequentAttemptSucceeds) {
-  vm_tools::concierge::ListVmDisksResponse response = BuildListVmDisksResponse(
-      /*success=*/true, /*vm_name=*/"vm_name1", /*image_type=*/
-      vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW,
-      /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
-      /*available_space=*/3 * kGiB);
+  auto response =
+      BuildValidListVmDisksResponse(/*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
+                                    /*available_space=*/3 * kGiB);
 
   // This object forces all EXPECT_CALLs to occur in the order they are
   // declared.
   testing::InSequence sequence;
 
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this, response = response](
                               base::OnceCallback<void(int64_t)> callback) {
@@ -327,14 +321,14 @@
           }));
 
   DiskInfoCallbackFactory first_callback_factory;
-  EXPECT_CALL(first_callback_factory, Call(testing::_))
+  EXPECT_CALL(first_callback_factory, Call(_))
       .WillOnce(testing::Invoke(
           [](Expected<BorealisDiskManagerImpl::GetDiskInfoResponse, std::string>
                  response_or_error) { EXPECT_TRUE(response_or_error); }));
   disk_manager_->GetDiskInfo(first_callback_factory.BindOnce());
   run_loop()->RunUntilIdle();
 
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this, response = response](
                               base::OnceCallback<void(int64_t)> callback) {
@@ -343,7 +337,7 @@
           }));
 
   DiskInfoCallbackFactory second_callback_factory;
-  EXPECT_CALL(second_callback_factory, Call(testing::_))
+  EXPECT_CALL(second_callback_factory, Call(_))
       .WillOnce(testing::Invoke(
           [](Expected<BorealisDiskManagerImpl::GetDiskInfoResponse, std::string>
                  response_or_error) { EXPECT_TRUE(response_or_error); }));
@@ -353,7 +347,7 @@
 
 TEST_F(BorealisDiskManagerTest, ReleaseSpaceFailsIfRequestExceedsInt64) {
   RequestDeltaCallbackFactory callback_factory;
-  EXPECT_CALL(callback_factory, Call(testing::_))
+  EXPECT_CALL(callback_factory, Call(_))
       .WillOnce(testing::Invoke(
           [](Expected<uint64_t, std::string> response_or_error) {
             EXPECT_FALSE(response_or_error);
@@ -364,21 +358,19 @@
 }
 
 TEST_F(BorealisDiskManagerTest, RequestDeltaFailsIfBuildDiskInfoFails) {
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
-            fake_concierge_client_->set_list_vm_disks_response(
-                BuildListVmDisksResponse(
-                    /*success=*/true,
-                    /*vm_name=*/"UNMATCHED_VM", /*image_type=*/
-                    vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW,
-                    /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
-                    /*available_space=*/3 * kGiB));
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
+                /*available_space=*/3 * kGiB);
+            response.mutable_images()->at(0).set_name("UNMATCHED_VM");
+            fake_concierge_client_->set_list_vm_disks_response(response);
             std::move(callback).Run(5 * kGiB);
           }));
 
   RequestDeltaCallbackFactory callback_factory;
-  EXPECT_CALL(callback_factory, Call(testing::_))
+  EXPECT_CALL(callback_factory, Call(_))
       .WillOnce(testing::Invoke(
           [](Expected<uint64_t, std::string> response_or_error) {
             EXPECT_FALSE(response_or_error);
@@ -388,20 +380,20 @@
 }
 
 TEST_F(BorealisDiskManagerTest, RequestDeltaFailsIfDiskTypeNotRaw) {
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
-            fake_concierge_client_->set_list_vm_disks_response(
-                BuildListVmDisksResponse(
-                    /*success=*/true, /*vm_name=*/"vm_name1", /*image_type=*/
-                    vm_tools::concierge::DiskImageType::DISK_IMAGE_AUTO,
-                    /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
-                    /*available_space=*/3 * kGiB));
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
+                /*available_space=*/3 * kGiB);
+            response.mutable_images()->at(0).set_image_type(
+                vm_tools::concierge::DiskImageType::DISK_IMAGE_AUTO);
+            fake_concierge_client_->set_list_vm_disks_response(response);
             std::move(callback).Run(5 * kGiB);
           }));
 
   RequestDeltaCallbackFactory callback_factory;
-  EXPECT_CALL(callback_factory, Call(testing::_))
+  EXPECT_CALL(callback_factory, Call(_))
       .WillOnce(testing::Invoke(
           [](Expected<uint64_t, std::string> response_or_error) {
             EXPECT_FALSE(response_or_error);
@@ -411,20 +403,18 @@
 }
 
 TEST_F(BorealisDiskManagerTest, RequestDeltaFailsIfRequestTooHigh) {
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
-            fake_concierge_client_->set_list_vm_disks_response(
-                BuildListVmDisksResponse(
-                    /*success=*/true, /*vm_name=*/"vm_name1", /*image_type=*/
-                    vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW,
-                    /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
-                    /*available_space=*/3 * kGiB));
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
+                /*available_space=*/3 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
             std::move(callback).Run(5 * kGiB);
           }));
 
   RequestDeltaCallbackFactory callback_factory;
-  EXPECT_CALL(callback_factory, Call(testing::_))
+  EXPECT_CALL(callback_factory, Call(_))
       .WillOnce(testing::Invoke(
           [](Expected<uint64_t, std::string> response_or_error) {
             EXPECT_FALSE(response_or_error);
@@ -436,20 +426,18 @@
 
 TEST_F(BorealisDiskManagerTest,
        RequestDeltaFailsIfRequestWouldNotLeaveEnoughSpace) {
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
-            fake_concierge_client_->set_list_vm_disks_response(
-                BuildListVmDisksResponse(
-                    /*success=*/true, /*vm_name=*/"vm_name1", /*image_type=*/
-                    vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW,
-                    /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
-                    /*available_space=*/3 * kGiB));
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
+                /*available_space=*/3 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
             std::move(callback).Run(5 * kGiB);
           }));
 
   RequestDeltaCallbackFactory callback_factory;
-  EXPECT_CALL(callback_factory, Call(testing::_))
+  EXPECT_CALL(callback_factory, Call(_))
       .WillOnce(testing::Invoke(
           [](Expected<uint64_t, std::string> response_or_error) {
             EXPECT_FALSE(response_or_error);
@@ -461,20 +449,18 @@
 }
 
 TEST_F(BorealisDiskManagerTest, RequestDeltaFailsIfRequestIsBelowMinimum) {
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
-            fake_concierge_client_->set_list_vm_disks_response(
-                BuildListVmDisksResponse(
-                    /*success=*/true, /*vm_name=*/"vm_name1", /*image_type=*/
-                    vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW,
-                    /*min_size=*/6 * kGiB, /*size=*/7 * kGiB,
-                    /*available_space=*/10 * kGiB));
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/7 * kGiB,
+                /*available_space=*/10 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
             std::move(callback).Run(5 * kGiB);
           }));
 
   RequestDeltaCallbackFactory callback_factory;
-  EXPECT_CALL(callback_factory, Call(testing::_))
+  EXPECT_CALL(callback_factory, Call(_))
       .WillOnce(testing::Invoke(
           [](Expected<uint64_t, std::string> response_or_error) {
             EXPECT_FALSE(response_or_error);
@@ -485,20 +471,18 @@
 }
 
 TEST_F(BorealisDiskManagerTest, RequestDeltaFailsOnNoResizeDiskResponse) {
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
-            fake_concierge_client_->set_list_vm_disks_response(
-                BuildListVmDisksResponse(
-                    /*success=*/true, /*vm_name=*/"vm_name1", /*image_type=*/
-                    vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW,
-                    /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
-                    /*available_space=*/3 * kGiB));
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
+                /*available_space=*/3 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
             std::move(callback).Run(5 * kGiB);
           }));
 
   RequestDeltaCallbackFactory callback_factory;
-  EXPECT_CALL(callback_factory, Call(testing::_))
+  EXPECT_CALL(callback_factory, Call(_))
       .WillOnce(testing::Invoke(
           [](Expected<uint64_t, std::string> response_or_error) {
             EXPECT_FALSE(response_or_error);
@@ -508,15 +492,13 @@
 }
 
 TEST_F(BorealisDiskManagerTest, RequestDeltaFailsOnFailedResizeDiskResponse) {
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
-            fake_concierge_client_->set_list_vm_disks_response(
-                BuildListVmDisksResponse(
-                    /*success=*/true, /*vm_name=*/"vm_name1", /*image_type=*/
-                    vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW,
-                    /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
-                    /*available_space=*/3 * kGiB));
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
+                /*available_space=*/3 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
             std::move(callback).Run(5 * kGiB);
           }));
 
@@ -526,7 +508,7 @@
   fake_concierge_client_->set_resize_disk_image_response(disk_response);
 
   RequestDeltaCallbackFactory callback_factory;
-  EXPECT_CALL(callback_factory, Call(testing::_))
+  EXPECT_CALL(callback_factory, Call(_))
       .WillOnce(testing::Invoke(
           [](Expected<uint64_t, std::string> response_or_error) {
             EXPECT_FALSE(response_or_error);
@@ -536,15 +518,13 @@
 }
 
 TEST_F(BorealisDiskManagerTest, RequestDeltaFailsOnDelayedConciergeFailure) {
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
-            fake_concierge_client_->set_list_vm_disks_response(
-                BuildListVmDisksResponse(
-                    /*success=*/true, /*vm_name=*/"vm_name1", /*image_type=*/
-                    vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW,
-                    /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
-                    /*available_space=*/3 * kGiB));
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
+                /*available_space=*/3 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
             std::move(callback).Run(5 * kGiB);
           }));
 
@@ -562,7 +542,7 @@
   fake_concierge_client_->set_disk_image_status_signals(signals);
 
   RequestDeltaCallbackFactory callback_factory;
-  EXPECT_CALL(callback_factory, Call(testing::_))
+  EXPECT_CALL(callback_factory, Call(_))
       .WillOnce(testing::Invoke(
           [](Expected<uint64_t, std::string> response_or_error) {
             EXPECT_FALSE(response_or_error);
@@ -576,15 +556,13 @@
   // declared.
   testing::InSequence sequence;
 
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
-            fake_concierge_client_->set_list_vm_disks_response(
-                BuildListVmDisksResponse(
-                    /*success=*/true, /*vm_name=*/"vm_name1", /*image_type=*/
-                    vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW,
-                    /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
-                    /*available_space=*/3 * kGiB));
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
+                /*available_space=*/3 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
             std::move(callback).Run(5 * kGiB);
           }));
 
@@ -593,13 +571,13 @@
       vm_tools::concierge::DiskImageStatus::DISK_STATUS_RESIZED);
   fake_concierge_client_->set_resize_disk_image_response(disk_response);
 
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(testing::Invoke([](base::OnceCallback<void(int64_t)> callback) {
         std::move(callback).Run(-1 * kGiB);
       }));
 
   RequestDeltaCallbackFactory callback_factory;
-  EXPECT_CALL(callback_factory, Call(testing::_))
+  EXPECT_CALL(callback_factory, Call(_))
       .WillOnce(testing::Invoke(
           [](Expected<uint64_t, std::string> response_or_error) {
             EXPECT_FALSE(response_or_error);
@@ -613,15 +591,13 @@
   // declared.
   testing::InSequence sequence;
 
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
-            fake_concierge_client_->set_list_vm_disks_response(
-                BuildListVmDisksResponse(
-                    /*success=*/true, /*vm_name=*/"vm_name1", /*image_type=*/
-                    vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW,
-                    /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
-                    /*available_space=*/3 * kGiB));
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
+                /*available_space=*/3 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
             std::move(callback).Run(5 * kGiB);
           }));
 
@@ -630,20 +606,18 @@
       vm_tools::concierge::DiskImageStatus::DISK_STATUS_RESIZED);
   fake_concierge_client_->set_resize_disk_image_response(disk_response);
 
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
-            fake_concierge_client_->set_list_vm_disks_response(
-                BuildListVmDisksResponse(
-                    /*success=*/true, /*vm_name=*/"vm_name1", /*image_type=*/
-                    vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW,
-                    /*min_size=*/6 * kGiB, /*size=*/21 * kGiB,
-                    /*available_space=*/4 * kGiB));
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/21 * kGiB,
+                /*available_space=*/4 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
             std::move(callback).Run(4 * kGiB);
           }));
 
   RequestDeltaCallbackFactory callback_factory;
-  EXPECT_CALL(callback_factory, Call(testing::_))
+  EXPECT_CALL(callback_factory, Call(_))
       .WillOnce(testing::Invoke(
           [](Expected<uint64_t, std::string> response_or_error) {
             EXPECT_FALSE(response_or_error);
@@ -657,15 +631,13 @@
   // declared.
   testing::InSequence sequence;
 
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
-            fake_concierge_client_->set_list_vm_disks_response(
-                BuildListVmDisksResponse(
-                    /*success=*/true, /*vm_name=*/"vm_name1", /*image_type=*/
-                    vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW,
-                    /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
-                    /*available_space=*/4 * kGiB));
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
+                /*available_space=*/4 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
             std::move(callback).Run(5 * kGiB);
           }));
 
@@ -674,20 +646,18 @@
       vm_tools::concierge::DiskImageStatus::DISK_STATUS_RESIZED);
   fake_concierge_client_->set_resize_disk_image_response(disk_response);
 
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
-            fake_concierge_client_->set_list_vm_disks_response(
-                BuildListVmDisksResponse(
-                    /*success=*/true, /*vm_name=*/"vm_name1", /*image_type=*/
-                    vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW,
-                    /*min_size=*/6 * kGiB, /*size=*/21 * kGiB,
-                    /*available_space=*/5 * kGiB));
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/21 * kGiB,
+                /*available_space=*/5 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
             std::move(callback).Run(4 * kGiB);
           }));
 
   RequestDeltaCallbackFactory callback_factory;
-  EXPECT_CALL(callback_factory, Call(testing::_))
+  EXPECT_CALL(callback_factory, Call(_))
       .WillOnce(testing::Invoke(
           [](Expected<uint64_t, std::string> response_or_error) {
             EXPECT_FALSE(response_or_error);
@@ -701,15 +671,13 @@
   // declared.
   testing::InSequence sequence;
 
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
-            fake_concierge_client_->set_list_vm_disks_response(
-                BuildListVmDisksResponse(
-                    /*success=*/true, /*vm_name=*/"vm_name1", /*image_type=*/
-                    vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW,
-                    /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
-                    /*available_space=*/3 * kGiB));
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
+                /*available_space=*/3 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
             std::move(callback).Run(5 * kGiB);
           }));
 
@@ -718,20 +686,18 @@
       vm_tools::concierge::DiskImageStatus::DISK_STATUS_RESIZED);
   fake_concierge_client_->set_resize_disk_image_response(disk_response);
 
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
-            fake_concierge_client_->set_list_vm_disks_response(
-                BuildListVmDisksResponse(
-                    /*success=*/true, /*vm_name=*/"vm_name1", /*image_type=*/
-                    vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW,
-                    /*min_size=*/6 * kGiB, /*size=*/22 * kGiB,
-                    /*available_space=*/5 * kGiB));
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/22 * kGiB,
+                /*available_space=*/5 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
             std::move(callback).Run(3 * kGiB);
           }));
 
   RequestDeltaCallbackFactory callback_factory;
-  EXPECT_CALL(callback_factory, Call(testing::_))
+  EXPECT_CALL(callback_factory, Call(_))
       .WillOnce(testing::Invoke(
           [](Expected<uint64_t, std::string> response_or_error) {
             EXPECT_TRUE(response_or_error);
@@ -746,15 +712,13 @@
   // declared.
   testing::InSequence sequence;
 
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
-            fake_concierge_client_->set_list_vm_disks_response(
-                BuildListVmDisksResponse(
-                    /*success=*/true, /*vm_name=*/"vm_name1", /*image_type=*/
-                    vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW,
-                    /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
-                    /*available_space=*/4 * kGiB));
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
+                /*available_space=*/4 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
             std::move(callback).Run(5 * kGiB);
           }));
 
@@ -763,20 +727,18 @@
       vm_tools::concierge::DiskImageStatus::DISK_STATUS_RESIZED);
   fake_concierge_client_->set_resize_disk_image_response(disk_response);
 
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
-            fake_concierge_client_->set_list_vm_disks_response(
-                BuildListVmDisksResponse(
-                    /*success=*/true, /*vm_name=*/"vm_name1", /*image_type=*/
-                    vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW,
-                    /*min_size=*/6 * kGiB, /*size=*/19 * kGiB,
-                    /*available_space=*/3 * kGiB));
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/19 * kGiB,
+                /*available_space=*/3 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
             std::move(callback).Run(6 * kGiB);
           }));
 
   RequestDeltaCallbackFactory callback_factory;
-  EXPECT_CALL(callback_factory, Call(testing::_))
+  EXPECT_CALL(callback_factory, Call(_))
       .WillOnce(testing::Invoke(
           [](Expected<uint64_t, std::string> response_or_error) {
             EXPECT_TRUE(response_or_error);
@@ -791,27 +753,23 @@
   // guaranteed, because of that we need to declare the free space provider
   // expectations in reverse order and retire the first expecation when
   // fulfilled.
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
-            fake_concierge_client_->set_list_vm_disks_response(
-                BuildListVmDisksResponse(
-                    /*success=*/true, /*vm_name=*/"vm_name1", /*image_type=*/
-                    vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW,
-                    /*min_size=*/6 * kGiB, /*size=*/22 * kGiB,
-                    /*available_space=*/5 * kGiB));
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/22 * kGiB,
+                /*available_space=*/5 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
             std::move(callback).Run(3 * kGiB);
           }));
 
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
-            fake_concierge_client_->set_list_vm_disks_response(
-                BuildListVmDisksResponse(
-                    /*success=*/true, /*vm_name=*/"vm_name1", /*image_type=*/
-                    vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW,
-                    /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
-                    /*available_space=*/3 * kGiB));
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
+                /*available_space=*/3 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
             std::move(callback).Run(5 * kGiB);
           }))
       .RetiresOnSaturation();
@@ -822,13 +780,13 @@
   fake_concierge_client_->set_resize_disk_image_response(disk_response);
 
   RequestDeltaCallbackFactory callback_factory;
-  EXPECT_CALL(callback_factory, Call(testing::_))
+  EXPECT_CALL(callback_factory, Call(_))
       .WillOnce(testing::Invoke(
           [](Expected<uint64_t, std::string> response_or_error) {
             EXPECT_TRUE(response_or_error);
           }));
   RequestDeltaCallbackFactory second_callback_factory;
-  EXPECT_CALL(second_callback_factory, Call(testing::_))
+  EXPECT_CALL(second_callback_factory, Call(_))
       .WillOnce(testing::Invoke(
           [](Expected<uint64_t, std::string> response_or_error) {
             EXPECT_FALSE(response_or_error);
@@ -843,15 +801,13 @@
   // declared.
   testing::InSequence sequence;
 
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
-            fake_concierge_client_->set_list_vm_disks_response(
-                BuildListVmDisksResponse(
-                    /*success=*/true, /*vm_name=*/"vm_name1", /*image_type=*/
-                    vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW,
-                    /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
-                    /*available_space=*/3 * kGiB));
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
+                /*available_space=*/3 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
             std::move(callback).Run(5 * kGiB);
           }));
 
@@ -860,20 +816,18 @@
       vm_tools::concierge::DiskImageStatus::DISK_STATUS_RESIZED);
   fake_concierge_client_->set_resize_disk_image_response(disk_response);
 
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
-            fake_concierge_client_->set_list_vm_disks_response(
-                BuildListVmDisksResponse(
-                    /*success=*/true, /*vm_name=*/"vm_name1", /*image_type=*/
-                    vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW,
-                    /*min_size=*/6 * kGiB, /*size=*/22 * kGiB,
-                    /*available_space=*/5 * kGiB));
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/22 * kGiB,
+                /*available_space=*/5 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
             std::move(callback).Run(3 * kGiB);
           }));
 
   RequestDeltaCallbackFactory callback_factory;
-  EXPECT_CALL(callback_factory, Call(testing::_))
+  EXPECT_CALL(callback_factory, Call(_))
       .WillOnce(testing::Invoke(
           [](Expected<uint64_t, std::string> response_or_error) {
             EXPECT_TRUE(response_or_error);
@@ -881,15 +835,13 @@
   disk_manager_->RequestSpace(2 * kGiB, callback_factory.BindOnce());
   run_loop()->RunUntilIdle();
 
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
-            fake_concierge_client_->set_list_vm_disks_response(
-                BuildListVmDisksResponse(
-                    /*success=*/true, /*vm_name=*/"vm_name1", /*image_type=*/
-                    vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW,
-                    /*min_size=*/6 * kGiB, /*size=*/22 * kGiB,
-                    /*available_space=*/5 * kGiB));
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/22 * kGiB,
+                /*available_space=*/5 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
             std::move(callback).Run(3 * kGiB);
           }));
 
@@ -898,20 +850,18 @@
       vm_tools::concierge::DiskImageStatus::DISK_STATUS_RESIZED);
   fake_concierge_client_->set_resize_disk_image_response(second_disk_response);
 
-  EXPECT_CALL(*free_space_provider_, Get(testing::_))
+  EXPECT_CALL(*free_space_provider_, Get(_))
       .WillOnce(
           testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
-            fake_concierge_client_->set_list_vm_disks_response(
-                BuildListVmDisksResponse(
-                    /*success=*/true, /*vm_name=*/"vm_name1", /*image_type=*/
-                    vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW,
-                    /*min_size=*/6 * kGiB, /*size=*/21 * kGiB,
-                    /*available_space=*/4 * kGiB));
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/21 * kGiB,
+                /*available_space=*/4 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
             std::move(callback).Run(4 * kGiB);
           }));
 
   RequestDeltaCallbackFactory second_callback_factory;
-  EXPECT_CALL(second_callback_factory, Call(testing::_))
+  EXPECT_CALL(second_callback_factory, Call(_))
       .WillOnce(testing::Invoke(
           [](Expected<uint64_t, std::string> response_or_error) {
             EXPECT_TRUE(response_or_error);
@@ -921,5 +871,220 @@
   run_loop()->RunUntilIdle();
 }
 
+TEST_F(BorealisDiskManagerTest, SyncDiskSizeFailsIfGetDiskInfoFails) {
+  EXPECT_CALL(*free_space_provider_, Get(_))
+      .WillOnce(
+          testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
+                /*available_space=*/3 * kGiB);
+            response.mutable_images()->at(0).set_name("UNMATCHED_VM");
+            fake_concierge_client_->set_list_vm_disks_response(response);
+            std::move(callback).Run(5 * kGiB);
+          }));
+
+  SyncDiskCallbackFactory callback_factory;
+  EXPECT_CALL(callback_factory, Call(Not("")));
+  disk_manager_->SyncDiskSize(callback_factory.BindOnce());
+  run_loop()->RunUntilIdle();
+}
+
+TEST_F(BorealisDiskManagerTest, SyncDiskSizeSucceedsIfDiskNotFixedSize) {
+  EXPECT_CALL(*free_space_provider_, Get(_))
+      .WillOnce(
+          testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
+                /*available_space=*/3 * kGiB);
+            response.mutable_images()->at(0).set_user_chosen_size(false);
+            fake_concierge_client_->set_list_vm_disks_response(response);
+            std::move(callback).Run(5 * kGiB);
+          }));
+
+  SyncDiskCallbackFactory callback_factory;
+  EXPECT_CALL(callback_factory, Call(""));
+  disk_manager_->SyncDiskSize(callback_factory.BindOnce());
+  run_loop()->RunUntilIdle();
+}
+
+TEST_F(BorealisDiskManagerTest, SyncDiskSizeSucceedsIfDiskCantExpand) {
+  EXPECT_CALL(*free_space_provider_, Get(_))
+      .WillOnce(
+          testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
+                /*available_space=*/1 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
+            std::move(callback).Run(0 * kGiB);
+          }));
+
+  SyncDiskCallbackFactory callback_factory;
+  EXPECT_CALL(callback_factory, Call(""));
+  disk_manager_->SyncDiskSize(callback_factory.BindOnce());
+  run_loop()->RunUntilIdle();
+}
+
+TEST_F(BorealisDiskManagerTest, SyncDiskSizeSucceedsIfDiskDoesntNeedToExpand) {
+  EXPECT_CALL(*free_space_provider_, Get(_))
+      .WillOnce(
+          testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/20 * kGiB,
+                /*available_space=*/2 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
+            std::move(callback).Run(10 * kGiB);
+          }));
+
+  SyncDiskCallbackFactory callback_factory;
+  EXPECT_CALL(callback_factory, Call(""));
+  disk_manager_->SyncDiskSize(callback_factory.BindOnce());
+  run_loop()->RunUntilIdle();
+}
+
+TEST_F(BorealisDiskManagerTest, SyncDiskSizeFailsIfResizeAttemptFails) {
+  // This object forces all EXPECT_CALLs to occur in the order they are
+  // declared.
+  testing::InSequence sequence;
+
+  EXPECT_CALL(*free_space_provider_, Get(_))
+      .Times(2)
+      .WillRepeatedly(
+          testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/6 * kGiB,
+                /*available_space=*/3 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
+            std::move(callback).Run(5 * kGiB);
+          }));
+
+  SyncDiskCallbackFactory callback_factory;
+  EXPECT_CALL(callback_factory, Call(Not("")));
+  disk_manager_->SyncDiskSize(callback_factory.BindOnce());
+  run_loop()->RunUntilIdle();
+}
+
+TEST_F(BorealisDiskManagerTest, SyncDiskSizePartialResizeSucceeds) {
+  // This is considered "partial" as it should log a warning. There is not
+  // enough space on the device to fully rebuild the buffer to 2GB, but it will
+  // succeed in rebuilding it to 1.5GB.
+
+  // This object forces all EXPECT_CALLs to occur in the order they are
+  // declared.
+  testing::InSequence sequence;
+
+  EXPECT_CALL(*free_space_provider_, Get(_))
+      .Times(2)
+      .WillRepeatedly(
+          testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/7 * kGiB,
+                /*available_space=*/1 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
+            std::move(callback).Run(1.5 * kGiB);
+          }));
+
+  vm_tools::concierge::ResizeDiskImageResponse disk_response;
+  disk_response.set_status(
+      vm_tools::concierge::DiskImageStatus::DISK_STATUS_RESIZED);
+  fake_concierge_client_->set_resize_disk_image_response(disk_response);
+
+  EXPECT_CALL(*free_space_provider_, Get(_))
+      .WillOnce(
+          testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/7.5 * kGiB,
+                /*available_space=*/1.5 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
+            std::move(callback).Run(1 * kGiB);
+          }));
+
+  SyncDiskCallbackFactory callback_factory;
+  EXPECT_CALL(callback_factory, Call(""));
+  disk_manager_->SyncDiskSize(callback_factory.BindOnce());
+  run_loop()->RunUntilIdle();
+}
+
+TEST_F(BorealisDiskManagerTest, SyncDiskSizeCompleteResizeSucceeds) {
+  // This is a complete success as the buffer is fully resized to 2GB.
+
+  // This object forces all EXPECT_CALLs to occur in the order they are
+  // declared.
+  testing::InSequence sequence;
+
+  EXPECT_CALL(*free_space_provider_, Get(_))
+      .Times(2)
+      .WillRepeatedly(
+          testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/7 * kGiB,
+                /*available_space=*/1 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
+            std::move(callback).Run(2 * kGiB);
+          }));
+
+  vm_tools::concierge::ResizeDiskImageResponse disk_response;
+  disk_response.set_status(
+      vm_tools::concierge::DiskImageStatus::DISK_STATUS_RESIZED);
+  fake_concierge_client_->set_resize_disk_image_response(disk_response);
+
+  EXPECT_CALL(*free_space_provider_, Get(_))
+      .WillOnce(
+          testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/8 * kGiB,
+                /*available_space=*/2 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
+            std::move(callback).Run(1 * kGiB);
+          }));
+
+  SyncDiskCallbackFactory callback_factory;
+  EXPECT_CALL(callback_factory, Call(""));
+  disk_manager_->SyncDiskSize(callback_factory.BindOnce());
+  run_loop()->RunUntilIdle();
+}
+
+TEST_F(BorealisDiskManagerTest, SyncDiskSizeConcurrentAttemptFails) {
+  // We don't use a sequence here because the ordering of expectations is not
+  // guaranteed, because of that we need to declare the free space provider
+  // expectations in reverse order and retire the first expecation when
+  // fulfilled.
+  EXPECT_CALL(*free_space_provider_, Get(_))
+      .WillOnce(
+          testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/8 * kGiB,
+                /*available_space=*/2 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
+            std::move(callback).Run(1 * kGiB);
+          }));
+
+  EXPECT_CALL(*free_space_provider_, Get(_))
+      .Times(2)
+      .WillRepeatedly(
+          testing::Invoke([this](base::OnceCallback<void(int64_t)> callback) {
+            auto response = BuildValidListVmDisksResponse(
+                /*min_size=*/6 * kGiB, /*size=*/7 * kGiB,
+                /*available_space=*/1 * kGiB);
+            fake_concierge_client_->set_list_vm_disks_response(response);
+            std::move(callback).Run(2 * kGiB);
+          }))
+      .RetiresOnSaturation();
+
+  vm_tools::concierge::ResizeDiskImageResponse disk_response;
+  disk_response.set_status(
+      vm_tools::concierge::DiskImageStatus::DISK_STATUS_RESIZED);
+  fake_concierge_client_->set_resize_disk_image_response(disk_response);
+
+  SyncDiskCallbackFactory callback_factory;
+  EXPECT_CALL(callback_factory, Call(""));
+
+  SyncDiskCallbackFactory second_callback_factory;
+  EXPECT_CALL(second_callback_factory, Call(Not("")));
+
+  disk_manager_->SyncDiskSize(callback_factory.BindOnce());
+  disk_manager_->SyncDiskSize(second_callback_factory.BindOnce());
+  run_loop()->RunUntilIdle();
+}
+
 }  // namespace
 }  // namespace borealis
diff --git a/chrome/browser/ash/borealis/borealis_metrics.cc b/chrome/browser/ash/borealis/borealis_metrics.cc
index 36ade9ff..b03ac29 100644
--- a/chrome/browser/ash/borealis/borealis_metrics.cc
+++ b/chrome/browser/ash/borealis/borealis_metrics.cc
@@ -91,5 +91,7 @@
       return stream << "Start VM Failed";
     case borealis::BorealisStartupResult::kAwaitBorealisStartupFailed:
       return stream << "Await Borealis Startup Failed";
+    case borealis::BorealisStartupResult::kSyncDiskFailed:
+      return stream << "Syncing Disk failed";
   }
 }
diff --git a/chrome/browser/ash/borealis/borealis_metrics.h b/chrome/browser/ash/borealis/borealis_metrics.h
index f28861c4..0795bb2b 100644
--- a/chrome/browser/ash/borealis/borealis_metrics.h
+++ b/chrome/browser/ash/borealis/borealis_metrics.h
@@ -61,7 +61,8 @@
   kDiskImageFailed = 3,
   kStartVmFailed = 4,
   kAwaitBorealisStartupFailed = 5,
-  kMaxValue = kAwaitBorealisStartupFailed,
+  kSyncDiskFailed = 6,
+  kMaxValue = kSyncDiskFailed,
 };
 
 // These values are persisted to logs. Entries should not be renumbered and
diff --git a/chrome/browser/ash/borealis/borealis_task.cc b/chrome/browser/ash/borealis/borealis_task.cc
index 9f3b699c..32242ded 100644
--- a/chrome/browser/ash/borealis/borealis_task.cc
+++ b/chrome/browser/ash/borealis/borealis_task.cc
@@ -13,6 +13,8 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "chrome/browser/ash/borealis/borealis_context.h"
+#include "chrome/browser/ash/borealis/borealis_disk_manager.h"
+#include "chrome/browser/ash/borealis/borealis_service.h"
 #include "chrome/browser/ash/borealis/borealis_util.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/profiles/profile.h"
@@ -180,4 +182,24 @@
   context->set_container_name(container.value());
   Complete(BorealisStartupResult::kSuccess, "");
 }
+
+SyncBorealisDisk::SyncBorealisDisk() = default;
+SyncBorealisDisk::~SyncBorealisDisk() = default;
+
+void SyncBorealisDisk::RunInternal(BorealisContext* context) {
+  context->get_disk_manager().SyncDiskSize(
+      base::BindOnce(&SyncBorealisDisk::OnSyncBorealisDisk,
+                     weak_factory_.GetWeakPtr(), context));
+}
+
+void SyncBorealisDisk::OnSyncBorealisDisk(BorealisContext* context,
+                                          std::string error) {
+  if (!error.empty()) {
+    Complete(BorealisStartupResult::kSyncDiskFailed,
+             "Failed to sync disk: " + error);
+    return;
+  }
+  Complete(BorealisStartupResult::kSuccess, "");
+}
+
 }  // namespace borealis
diff --git a/chrome/browser/ash/borealis/borealis_task.h b/chrome/browser/ash/borealis/borealis_task.h
index 2df0903..acfb14c 100644
--- a/chrome/browser/ash/borealis/borealis_task.h
+++ b/chrome/browser/ash/borealis/borealis_task.h
@@ -98,6 +98,17 @@
   base::WeakPtrFactory<AwaitBorealisStartup> weak_factory_{this};
 };
 
+// Checks the size of the disk and adjusts it if necessary.
+class SyncBorealisDisk : public BorealisTask {
+ public:
+  SyncBorealisDisk();
+  ~SyncBorealisDisk() override;
+  void RunInternal(BorealisContext* context) override;
+
+ private:
+  void OnSyncBorealisDisk(BorealisContext* context, std::string error);
+  base::WeakPtrFactory<SyncBorealisDisk> weak_factory_{this};
+};
 }  // namespace borealis
 
 #endif  // CHROME_BROWSER_ASH_BOREALIS_BOREALIS_TASK_H_
diff --git a/chrome/browser/ash/borealis/borealis_task_unittest.cc b/chrome/browser/ash/borealis/borealis_task_unittest.cc
index 2cd1d5948..cb16262 100644
--- a/chrome/browser/ash/borealis/borealis_task_unittest.cc
+++ b/chrome/browser/ash/borealis/borealis_task_unittest.cc
@@ -8,13 +8,13 @@
 
 #include "chrome/browser/ash/borealis/borealis_context.h"
 #include "chrome/browser/ash/borealis/borealis_context_manager.h"
+#include "chrome/browser/ash/borealis/borealis_disk_manager.h"
 #include "chrome/browser/ash/borealis/borealis_metrics.h"
 #include "chrome/browser/ash/borealis/testing/callback_factory.h"
+#include "chrome/browser/ash/borealis/testing/dbus.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/test/base/testing_profile.h"
-#include "chromeos/dbus/cicerone/cicerone_client.h"
 #include "chromeos/dbus/cicerone/fake_cicerone_client.h"
-#include "chromeos/dbus/concierge/concierge_client.h"
 #include "chromeos/dbus/concierge/fake_concierge_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/dlcservice/fake_dlcservice_client.h"
@@ -29,10 +29,32 @@
 
 namespace {
 
+class DiskManagerMock : public BorealisDiskManager {
+ public:
+  DiskManagerMock() = default;
+  ~DiskManagerMock() override = default;
+  MOCK_METHOD(
+      void,
+      GetDiskInfo,
+      (base::OnceCallback<void(Expected<GetDiskInfoResponse, std::string>)>),
+      ());
+  MOCK_METHOD(void,
+              RequestSpace,
+              (uint64_t,
+               base::OnceCallback<void(Expected<uint64_t, std::string>)>),
+              ());
+  MOCK_METHOD(void,
+              ReleaseSpace,
+              (uint64_t,
+               base::OnceCallback<void(Expected<uint64_t, std::string>)>),
+              ());
+  MOCK_METHOD(void, SyncDiskSize, (base::OnceCallback<void(std::string)>), ());
+};
+
 using CallbackFactory =
     StrictCallbackFactory<void(BorealisStartupResult, std::string)>;
 
-class BorealisTasksTest : public testing::Test {
+class BorealisTasksTest : public testing::Test, protected FakeVmServicesHelper {
  public:
   BorealisTasksTest() = default;
   ~BorealisTasksTest() override = default;
@@ -43,39 +65,19 @@
 
  protected:
   void SetUp() override {
-    chromeos::DBusThreadManager::Initialize();
-    chromeos::CiceroneClient::InitializeFake();
-    chromeos::ConciergeClient::InitializeFake();
-    chromeos::SeneschalClient::InitializeFake();
-    fake_concierge_client_ = chromeos::FakeConciergeClient::Get();
-    fake_cicerone_client_ = chromeos::FakeCiceroneClient::Get();
     CreateProfile();
     context_ = BorealisContext::CreateBorealisContextForTesting(profile_.get());
     context_->set_vm_name("borealis");
-
-    chromeos::DlcserviceClient::InitializeFake();
-    fake_dlcservice_client_ = static_cast<chromeos::FakeDlcserviceClient*>(
-        chromeos::DlcserviceClient::Get());
   }
 
   void TearDown() override {
     context_.reset();  // must destroy before DBus shutdown
     profile_.reset();
-
-    chromeos::DlcserviceClient::Shutdown();
-    chromeos::SeneschalClient::Shutdown();
-    chromeos::ConciergeClient::Shutdown();
-    chromeos::CiceroneClient::Shutdown();
-    chromeos::DBusThreadManager::Shutdown();
   }
 
   std::unique_ptr<TestingProfile> profile_;
   std::unique_ptr<BorealisContext> context_;
   content::BrowserTaskEnvironment task_environment_;
-  chromeos::FakeConciergeClient* fake_concierge_client_;
-  chromeos::FakeCiceroneClient* fake_cicerone_client_;
-  // Owned by chromeos::DBusThreadManager
-  chromeos::FakeDlcserviceClient* fake_dlcservice_client_;
 
  private:
   void CreateProfile() {
@@ -86,7 +88,7 @@
 };
 
 TEST_F(BorealisTasksTest, MountDlcSucceedsAndCallbackRanWithResults) {
-  fake_dlcservice_client_->set_install_error(dlcservice::kErrorNone);
+  FakeDlcserviceClient()->set_install_error(dlcservice::kErrorNone);
 
   CallbackFactory callback_factory;
   EXPECT_CALL(callback_factory, Call(BorealisStartupResult::kSuccess, _));
@@ -101,7 +103,7 @@
   base::FilePath path = base::FilePath("test/path");
   response.set_status(vm_tools::concierge::DISK_STATUS_CREATED);
   response.set_disk_path(path.AsUTF8Unsafe());
-  fake_concierge_client_->set_create_disk_image_response(std::move(response));
+  FakeConciergeClient()->set_create_disk_image_response(std::move(response));
   EXPECT_EQ(context_->disk_path(), base::FilePath());
 
   CallbackFactory callback_factory;
@@ -111,7 +113,7 @@
   task.Run(context_.get(), callback_factory.BindOnce());
   task_environment_.RunUntilIdle();
 
-  EXPECT_GE(fake_concierge_client_->create_disk_image_call_count(), 1);
+  EXPECT_GE(FakeConciergeClient()->create_disk_image_call_count(), 1);
   EXPECT_EQ(context_->disk_path(), path);
 }
 
@@ -121,7 +123,7 @@
   base::FilePath path = base::FilePath("test/path");
   response.set_status(vm_tools::concierge::DISK_STATUS_EXISTS);
   response.set_disk_path(path.AsUTF8Unsafe());
-  fake_concierge_client_->set_create_disk_image_response(std::move(response));
+  FakeConciergeClient()->set_create_disk_image_response(std::move(response));
   EXPECT_EQ(context_->disk_path(), base::FilePath());
 
   CallbackFactory callback_factory;
@@ -131,14 +133,14 @@
   task.Run(context_.get(), callback_factory.BindOnce());
   task_environment_.RunUntilIdle();
 
-  EXPECT_GE(fake_concierge_client_->create_disk_image_call_count(), 1);
+  EXPECT_GE(FakeConciergeClient()->create_disk_image_call_count(), 1);
   EXPECT_EQ(context_->disk_path(), path);
 }
 
 TEST_F(BorealisTasksTest, StartBorealisVmSucceedsAndCallbackRanWithResults) {
   vm_tools::concierge::StartVmResponse response;
   response.set_status(vm_tools::concierge::VM_STATUS_STARTING);
-  fake_concierge_client_->set_start_vm_response(std::move(response));
+  FakeConciergeClient()->set_start_vm_response(std::move(response));
 
   CallbackFactory callback_factory;
   EXPECT_CALL(callback_factory, Call(BorealisStartupResult::kSuccess, _));
@@ -147,14 +149,14 @@
   task.Run(context_.get(), callback_factory.BindOnce());
   task_environment_.RunUntilIdle();
 
-  EXPECT_GE(fake_concierge_client_->start_termina_vm_call_count(), 1);
+  EXPECT_GE(FakeConciergeClient()->start_termina_vm_call_count(), 1);
 }
 
 TEST_F(BorealisTasksTest,
        StartBorealisVmVmAlreadyRunningAndCallbackRanWithResults) {
   vm_tools::concierge::StartVmResponse response;
   response.set_status(vm_tools::concierge::VM_STATUS_RUNNING);
-  fake_concierge_client_->set_start_vm_response(std::move(response));
+  FakeConciergeClient()->set_start_vm_response(std::move(response));
 
   CallbackFactory callback_factory;
   EXPECT_CALL(callback_factory, Call(BorealisStartupResult::kSuccess, _));
@@ -163,7 +165,7 @@
   task.Run(context_.get(), callback_factory.BindOnce());
   task_environment_.RunUntilIdle();
 
-  EXPECT_GE(fake_concierge_client_->start_termina_vm_call_count(), 1);
+  EXPECT_GE(FakeConciergeClient()->start_termina_vm_call_count(), 1);
 }
 
 TEST_F(BorealisTasksTest,
@@ -179,7 +181,7 @@
 
   AwaitBorealisStartup task(context_->profile(), context_->vm_name());
   task.Run(context_.get(), callback_factory.BindOnce());
-  fake_cicerone_client_->NotifyContainerStarted(std::move(signal));
+  FakeCiceroneClient()->NotifyContainerStarted(std::move(signal));
 
   task_environment_.RunUntilIdle();
 }
@@ -196,7 +198,7 @@
   EXPECT_CALL(callback_factory, Call(BorealisStartupResult::kSuccess, _));
 
   AwaitBorealisStartup task(context_->profile(), context_->vm_name());
-  fake_cicerone_client_->NotifyContainerStarted(std::move(signal));
+  FakeCiceroneClient()->NotifyContainerStarted(std::move(signal));
   task.Run(context_.get(), callback_factory.BindOnce());
   task_environment_.RunUntilIdle();
 }
@@ -215,11 +217,45 @@
   task_environment_.RunUntilIdle();
 }
 
+TEST_F(BorealisTasksTest, SyncBorealisDiskFails) {
+  auto disk_mock = std::make_unique<DiskManagerMock>();
+  EXPECT_CALL(*disk_mock, SyncDiskSize(_))
+      .WillOnce(testing::Invoke(
+          [](base::OnceCallback<void(std::string error)> callback) {
+            std::move(callback).Run("Something went wrong");
+          }));
+
+  CallbackFactory callback_factory;
+  EXPECT_CALL(callback_factory,
+              Call(BorealisStartupResult::kSyncDiskFailed,
+                   "Failed to sync disk: Something went wrong"));
+  context_->SetDiskManagerForTesting(std::move(disk_mock));
+  SyncBorealisDisk task;
+  task.Run(context_.get(), callback_factory.BindOnce());
+  task_environment_.RunUntilIdle();
+}
+
+TEST_F(BorealisTasksTest, SyncBorealisDiskSucceeds) {
+  auto disk_mock = std::make_unique<DiskManagerMock>();
+  EXPECT_CALL(*disk_mock, SyncDiskSize(_))
+      .WillOnce(testing::Invoke(
+          [](base::OnceCallback<void(std::string error)> callback) {
+            std::move(callback).Run("");
+          }));
+
+  CallbackFactory callback_factory;
+  EXPECT_CALL(callback_factory, Call(BorealisStartupResult::kSuccess, _));
+  context_->SetDiskManagerForTesting(std::move(disk_mock));
+  SyncBorealisDisk task;
+  task.Run(context_.get(), callback_factory.BindOnce());
+  task_environment_.RunUntilIdle();
+}
+
 class BorealisTasksTestDlc : public BorealisTasksTest,
                              public testing::WithParamInterface<std::string> {};
 
 TEST_P(BorealisTasksTestDlc, MountDlcFailsAndCallbackRanWithResults) {
-  fake_dlcservice_client_->set_install_error(GetParam());
+  FakeDlcserviceClient()->set_install_error(GetParam());
   CallbackFactory callback_factory;
   EXPECT_CALL(callback_factory,
               Call(BorealisStartupResult::kMountFailed, StrNe("")));
@@ -246,7 +282,7 @@
 TEST_P(BorealisTasksTestDiskImage, CreateDiskFailsAndCallbackRanWithResults) {
   vm_tools::concierge::CreateDiskImageResponse response;
   response.set_status(GetParam());
-  fake_concierge_client_->set_create_disk_image_response(std::move(response));
+  FakeConciergeClient()->set_create_disk_image_response(std::move(response));
   EXPECT_EQ(context_->disk_path(), base::FilePath());
 
   CallbackFactory callback_factory;
@@ -257,7 +293,7 @@
   task.Run(context_.get(), callback_factory.BindOnce());
   task_environment_.RunUntilIdle();
 
-  EXPECT_GE(fake_concierge_client_->create_disk_image_call_count(), 1);
+  EXPECT_GE(FakeConciergeClient()->create_disk_image_call_count(), 1);
   EXPECT_EQ(context_->disk_path(), base::FilePath());
 }
 
@@ -279,7 +315,7 @@
        StartBorealisVmErrorsAndCallbackRanWithResults) {
   vm_tools::concierge::StartVmResponse response;
   response.set_status(GetParam());
-  fake_concierge_client_->set_start_vm_response(std::move(response));
+  FakeConciergeClient()->set_start_vm_response(std::move(response));
 
   CallbackFactory callback_factory;
   EXPECT_CALL(callback_factory,
@@ -289,7 +325,7 @@
   task.Run(context_.get(), callback_factory.BindOnce());
   task_environment_.RunUntilIdle();
 
-  EXPECT_GE(fake_concierge_client_->start_termina_vm_call_count(), 1);
+  EXPECT_GE(FakeConciergeClient()->start_termina_vm_call_count(), 1);
 }
 
 INSTANTIATE_TEST_SUITE_P(
diff --git a/chrome/browser/ash/borealis/testing/dbus.cc b/chrome/browser/ash/borealis/testing/dbus.cc
new file mode 100644
index 0000000..21917bbe
--- /dev/null
+++ b/chrome/browser/ash/borealis/testing/dbus.cc
@@ -0,0 +1,82 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/borealis/testing/dbus.h"
+
+#include "chromeos/dbus/cicerone/fake_cicerone_client.h"
+#include "chromeos/dbus/concierge/fake_concierge_client.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/dlcservice/fake_dlcservice_client.h"
+#include "chromeos/dbus/seneschal/fake_seneschal_client.h"
+
+namespace borealis {
+
+BasicDBusHelper::BasicDBusHelper() {
+  chromeos::DBusThreadManager::Initialize();
+}
+
+BasicDBusHelper::~BasicDBusHelper() {
+  chromeos::DBusThreadManager::Shutdown();
+}
+
+FakeCiceroneHelper::FakeCiceroneHelper(BasicDBusHelper* basic_helper) {
+  DCHECK(basic_helper);
+  chromeos::CiceroneClient::InitializeFake();
+}
+
+FakeCiceroneHelper::~FakeCiceroneHelper() {
+  chromeos::CiceroneClient::Shutdown();
+}
+
+chromeos::FakeCiceroneClient* FakeCiceroneHelper::FakeCiceroneClient() {
+  return chromeos::FakeCiceroneClient::Get();
+}
+
+FakeSeneschalHelper::FakeSeneschalHelper(BasicDBusHelper* basic_helper) {
+  DCHECK(basic_helper);
+  chromeos::SeneschalClient::InitializeFake();
+}
+
+FakeSeneschalHelper::~FakeSeneschalHelper() {
+  chromeos::SeneschalClient::Shutdown();
+}
+
+chromeos::FakeSeneschalClient* FakeSeneschalHelper::FakeSeneschalClient() {
+  return chromeos::FakeSeneschalClient::Get();
+}
+
+FakeDlcserviceHelper::FakeDlcserviceHelper(BasicDBusHelper* basic_helper) {
+  DCHECK(basic_helper);
+  chromeos::DlcserviceClient::InitializeFake();
+}
+
+FakeDlcserviceHelper::~FakeDlcserviceHelper() {
+  chromeos::DlcserviceClient::Shutdown();
+}
+
+chromeos::FakeDlcserviceClient* FakeDlcserviceHelper::FakeDlcserviceClient() {
+  return static_cast<chromeos::FakeDlcserviceClient*>(
+      chromeos::DlcserviceClient::Get());
+}
+
+FakeConciergeHelper::FakeConciergeHelper(FakeCiceroneHelper* cicerone_helper) {
+  DCHECK(cicerone_helper);
+  chromeos::ConciergeClient::InitializeFake();
+}
+
+FakeConciergeHelper::~FakeConciergeHelper() {
+  chromeos::ConciergeClient::Shutdown();
+}
+
+chromeos::FakeConciergeClient* FakeConciergeHelper::FakeConciergeClient() {
+  return chromeos::FakeConciergeClient::Get();
+}
+
+FakeVmServicesHelper::FakeVmServicesHelper()
+    : FakeCiceroneHelper(this),
+      FakeSeneschalHelper(this),
+      FakeDlcserviceHelper(this),
+      FakeConciergeHelper(this) {}
+
+}  // namespace borealis
diff --git a/chrome/browser/ash/borealis/testing/dbus.h b/chrome/browser/ash/borealis/testing/dbus.h
new file mode 100644
index 0000000..659c3b4
--- /dev/null
+++ b/chrome/browser/ash/borealis/testing/dbus.h
@@ -0,0 +1,69 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ASH_BOREALIS_TESTING_DBUS_H_
+#define CHROME_BROWSER_ASH_BOREALIS_TESTING_DBUS_H_
+
+namespace chromeos {
+class FakeCiceroneClient;
+class FakeConciergeClient;
+class FakeSeneschalClient;
+class FakeDlcserviceClient;
+}  // namespace chromeos
+
+namespace borealis {
+
+class BasicDBusHelper {
+ public:
+  BasicDBusHelper();
+  ~BasicDBusHelper();
+};
+
+class FakeCiceroneHelper {
+ public:
+  explicit FakeCiceroneHelper(BasicDBusHelper* basic_helper);
+  ~FakeCiceroneHelper();
+
+  // Returns a handle to the dbus fake for cicerone.
+  chromeos::FakeCiceroneClient* FakeCiceroneClient();
+};
+
+class FakeSeneschalHelper {
+ public:
+  explicit FakeSeneschalHelper(BasicDBusHelper* basic_helper);
+  ~FakeSeneschalHelper();
+
+  // Returns a handle to the dbus fake for seneschal.
+  chromeos::FakeSeneschalClient* FakeSeneschalClient();
+};
+
+class FakeDlcserviceHelper {
+ public:
+  explicit FakeDlcserviceHelper(BasicDBusHelper* basic_helper);
+  ~FakeDlcserviceHelper();
+
+  chromeos::FakeDlcserviceClient* FakeDlcserviceClient();
+};
+
+class FakeConciergeHelper {
+ public:
+  explicit FakeConciergeHelper(FakeCiceroneHelper* cicerone_helper);
+  ~FakeConciergeHelper();
+
+  // Returns a handle to the dbus fake for concierge.
+  chromeos::FakeConciergeClient* FakeConciergeClient();
+};
+
+class FakeVmServicesHelper : public BasicDBusHelper,
+                             public FakeCiceroneHelper,
+                             public FakeSeneschalHelper,
+                             public FakeDlcserviceHelper,
+                             public FakeConciergeHelper {
+ public:
+  FakeVmServicesHelper();
+};
+
+}  // namespace borealis
+
+#endif  // CHROME_BROWSER_ASH_BOREALIS_TESTING_DBUS_H_
diff --git a/chrome/browser/ash/file_system_provider/abort_callback.h b/chrome/browser/ash/file_system_provider/abort_callback.h
index a0ca6f9..d8e6675 100644
--- a/chrome/browser/ash/file_system_provider/abort_callback.h
+++ b/chrome/browser/ash/file_system_provider/abort_callback.h
@@ -8,19 +8,19 @@
 #include "base/callback.h"
 #include "storage/browser/file_system/async_file_util.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 typedef base::OnceClosure AbortCallback;
 
 }  // namespace file_system_provider
-}  // namespace chromeos
-
-// TODO(https://crbug.com/1164001): remove when moved to ash.
-namespace ash {
-namespace file_system_provider {
-using ::chromeos::file_system_provider::AbortCallback;
-}  // namespace file_system_provider
 }  // namespace ash
 
+// TODO(https://crbug.com/1164001): remove when ChromOS code migration is done.
+namespace chromeos {
+namespace file_system_provider {
+using ::ash::file_system_provider::AbortCallback;
+}  // namespace file_system_provider
+}  // namespace chromeos
+
 #endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_ABORT_CALLBACK_H_
diff --git a/chrome/browser/ash/file_system_provider/extension_provider.cc b/chrome/browser/ash/file_system_provider/extension_provider.cc
index b084724..ee0dcfc 100644
--- a/chrome/browser/ash/file_system_provider/extension_provider.cc
+++ b/chrome/browser/ash/file_system_provider/extension_provider.cc
@@ -20,7 +20,7 @@
 #include "extensions/browser/extension_registry.h"
 #include "extensions/common/permissions/permissions_data.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 namespace {
 
@@ -178,4 +178,4 @@
 }
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/extension_provider.h b/chrome/browser/ash/file_system_provider/extension_provider.h
index 3530d9b..70cf0d9 100644
--- a/chrome/browser/ash/file_system_provider/extension_provider.h
+++ b/chrome/browser/ash/file_system_provider/extension_provider.h
@@ -21,7 +21,7 @@
 class ExtensionRegistry;
 }  // namespace extensions
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 // Holds information for a providing extension.
@@ -71,6 +71,6 @@
 };
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
 
 #endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_EXTENSION_PROVIDER_H_
diff --git a/chrome/browser/ash/file_system_provider/fake_extension_provider.cc b/chrome/browser/ash/file_system_provider/fake_extension_provider.cc
index 5e73060..023b924 100644
--- a/chrome/browser/ash/file_system_provider/fake_extension_provider.cc
+++ b/chrome/browser/ash/file_system_provider/fake_extension_provider.cc
@@ -14,7 +14,7 @@
 #include "extensions/browser/extension_registry.h"
 #include "extensions/common/permissions/permissions_data.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 // static
@@ -64,4 +64,4 @@
       name_("Fake Extension Provider") {}
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/fake_extension_provider.h b/chrome/browser/ash/file_system_provider/fake_extension_provider.h
index 78eafab5..57aebf2 100644
--- a/chrome/browser/ash/file_system_provider/fake_extension_provider.h
+++ b/chrome/browser/ash/file_system_provider/fake_extension_provider.h
@@ -16,7 +16,7 @@
 
 class Profile;
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 class FakeExtensionProvider : public ProviderInterface {
@@ -49,13 +49,6 @@
 };
 
 }  // namespace file_system_provider
-}  // namespace chromeos
-
-// TODO(https://crbug.com/1164001): remove when moved to ash.
-namespace ash {
-namespace file_system_provider {
-using ::chromeos::file_system_provider::FakeExtensionProvider;
-}  // namespace file_system_provider
 }  // namespace ash
 
 #endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_FAKE_EXTENSION_PROVIDER_H_
diff --git a/chrome/browser/ash/file_system_provider/fake_provided_file_system.cc b/chrome/browser/ash/file_system_provider/fake_provided_file_system.cc
index 77517940..8da0ab6 100644
--- a/chrome/browser/ash/file_system_provider/fake_provided_file_system.cc
+++ b/chrome/browser/ash/file_system_provider/fake_provided_file_system.cc
@@ -14,7 +14,7 @@
 #include "components/services/filesystem/public/mojom/types.mojom.h"
 #include "net/base/io_buffer.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 namespace {
 
@@ -457,4 +457,4 @@
 }
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/fake_provided_file_system.h b/chrome/browser/ash/file_system_provider/fake_provided_file_system.h
index abf0df3..ada3dbf 100644
--- a/chrome/browser/ash/file_system_provider/fake_provided_file_system.h
+++ b/chrome/browser/ash/file_system_provider/fake_provided_file_system.h
@@ -35,7 +35,7 @@
 class IOBuffer;
 }  // namespace net
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 class RequestManager;
@@ -191,15 +191,6 @@
 };
 
 }  // namespace file_system_provider
-}  // namespace chromeos
-
-// TODO(https://crbug.com/1164001): remove when moved to ash.
-namespace ash {
-namespace file_system_provider {
-using ::chromeos::file_system_provider::FakeEntry;
-using ::chromeos::file_system_provider::FakeProvidedFileSystem;
-using ::chromeos::file_system_provider::kFakeFilePath;
-}  // namespace file_system_provider
 }  // namespace ash
 
 #endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_FAKE_PROVIDED_FILE_SYSTEM_H_
diff --git a/chrome/browser/ash/file_system_provider/fake_registry.cc b/chrome/browser/ash/file_system_provider/fake_registry.cc
index e836c84..e219699 100644
--- a/chrome/browser/ash/file_system_provider/fake_registry.cc
+++ b/chrome/browser/ash/file_system_provider/fake_registry.cc
@@ -9,7 +9,7 @@
 #include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
 #include "chrome/browser/ash/file_system_provider/watcher.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 FakeRegistry::FakeRegistry() {}
@@ -72,4 +72,4 @@
 }
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/fake_registry.h b/chrome/browser/ash/file_system_provider/fake_registry.h
index 9faa2878..ff5ec88 100644
--- a/chrome/browser/ash/file_system_provider/fake_registry.h
+++ b/chrome/browser/ash/file_system_provider/fake_registry.h
@@ -10,7 +10,7 @@
 #include "base/macros.h"
 #include "chrome/browser/ash/file_system_provider/watcher.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 class ProvidedFileSystemInfo;
@@ -40,6 +40,6 @@
 };
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
 
 #endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_FAKE_REGISTRY_H_
diff --git a/chrome/browser/ash/file_system_provider/fileapi/file_stream_reader.h b/chrome/browser/ash/file_system_provider/fileapi/file_stream_reader.h
index 79bbbc04..16372f2 100644
--- a/chrome/browser/ash/file_system_provider/fileapi/file_stream_reader.h
+++ b/chrome/browser/ash/file_system_provider/fileapi/file_stream_reader.h
@@ -12,8 +12,6 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
-// TODO(https://crbug.com/1164001): forward declare EntryMetaData when moved ash
-#include "chrome/browser/ash/file_system_provider/provided_file_system_interface.h"
 #include "net/base/completion_once_callback.h"
 #include "net/base/completion_repeating_callback.h"
 #include "storage/browser/file_system/file_stream_reader.h"
@@ -26,6 +24,8 @@
 namespace ash {
 namespace file_system_provider {
 
+struct EntryMetadata;
+
 // Implements a streamed file reader. It is lazily initialized by the first call
 // to Read().
 class FileStreamReader : public storage::FileStreamReader {
diff --git a/chrome/browser/ash/file_system_provider/icon_set.cc b/chrome/browser/ash/file_system_provider/icon_set.cc
index 934b7da..dc5310d 100644
--- a/chrome/browser/ash/file_system_provider/icon_set.cc
+++ b/chrome/browser/ash/file_system_provider/icon_set.cc
@@ -4,7 +4,7 @@
 
 #include "chrome/browser/ash/file_system_provider/icon_set.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 IconSet::IconSet() = default;
@@ -28,4 +28,4 @@
 }
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/icon_set.h b/chrome/browser/ash/file_system_provider/icon_set.h
index 2ef5da5..c718efe1 100644
--- a/chrome/browser/ash/file_system_provider/icon_set.h
+++ b/chrome/browser/ash/file_system_provider/icon_set.h
@@ -9,11 +9,11 @@
 
 #include "url/gurl.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 // Holds urls to icons with multiple dimensions.
-// TODO(mtomasz): Move this to chrome/browser/chromeos so it can be reused
+// TODO(mtomasz): Move this to chrome/browser/ash so it can be reused
 // by other components.
 class IconSet {
  public:
@@ -42,13 +42,13 @@
 };
 
 }  // namespace file_system_provider
-}  // namespace chromeos
-
-// TODO(https://crbug.com/1164001): remove when moved to ash.
-namespace ash {
-namespace file_system_provider {
-using ::chromeos::file_system_provider::IconSet;
-}  // namespace file_system_provider
 }  // namespace ash
 
+// TODO(https://crbug.com/1164001): remove when ChromOS code migration is done.
+namespace chromeos {
+namespace file_system_provider {
+using ::ash::file_system_provider::IconSet;
+}  // namespace file_system_provider
+}  // namespace chromeos
+
 #endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_ICON_SET_H_
diff --git a/chrome/browser/ash/file_system_provider/logging_observer.cc b/chrome/browser/ash/file_system_provider/logging_observer.cc
index 88c7636e..f7b90e6d 100644
--- a/chrome/browser/ash/file_system_provider/logging_observer.cc
+++ b/chrome/browser/ash/file_system_provider/logging_observer.cc
@@ -4,7 +4,7 @@
 
 #include "chrome/browser/ash/file_system_provider/logging_observer.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 LoggingObserver::LoggingObserver() {}
@@ -26,4 +26,4 @@
 }
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/logging_observer.h b/chrome/browser/ash/file_system_provider/logging_observer.h
index 27a9401..7244ea9 100644
--- a/chrome/browser/ash/file_system_provider/logging_observer.h
+++ b/chrome/browser/ash/file_system_provider/logging_observer.h
@@ -8,7 +8,7 @@
 #include "chrome/browser/ash/file_system_provider/observer.h"
 #include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 // Utility observer, logging events from file_system_provider::Service.
@@ -55,6 +55,6 @@
 };
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
 
 #endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_LOGGING_OBSERVER_H_
diff --git a/chrome/browser/ash/file_system_provider/mount_path_util.cc b/chrome/browser/ash/file_system_provider/mount_path_util.cc
index 48279dd..7a286904 100644
--- a/chrome/browser/ash/file_system_provider/mount_path_util.cc
+++ b/chrome/browser/ash/file_system_provider/mount_path_util.cc
@@ -22,7 +22,7 @@
 
 using content::BrowserThread;
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 namespace util {
 
@@ -54,7 +54,7 @@
                             const std::string& file_system_id) {
   const user_manager::User* const user =
       user_manager::UserManager::IsInitialized()
-          ? chromeos::ProfileHelper::Get()->GetUserByProfile(
+          ? ProfileHelper::Get()->GetUserByProfile(
                 profile->GetOriginalProfile())
           : NULL;
   const std::string safe_file_system_id = EscapeFileSystemId(file_system_id);
@@ -101,7 +101,7 @@
     Profile* original_profile = profiles[i]->GetOriginalProfile();
 
     if (original_profile != profiles[i] ||
-        !chromeos::ProfileHelper::IsRegularProfile(original_profile)) {
+        !ProfileHelper::IsRegularProfile(original_profile)) {
       continue;
     }
 
@@ -179,4 +179,4 @@
 
 }  // namespace util
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/mount_path_util.h b/chrome/browser/ash/file_system_provider/mount_path_util.h
index 6d8f9eb..f3d53b0 100644
--- a/chrome/browser/ash/file_system_provider/mount_path_util.h
+++ b/chrome/browser/ash/file_system_provider/mount_path_util.h
@@ -13,7 +13,7 @@
 
 class Profile;
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 class ProvidedFileSystemInterface;
@@ -75,17 +75,18 @@
 
   DISALLOW_COPY_AND_ASSIGN(LocalPathParser);
 };
-}  // namespace util
-}  // namespace file_system_provider
-}  // namespace chromeos
 
-// TODO(https://crbug.com/1164001): remove when moved to ash.
-namespace ash {
-namespace file_system_provider {
-namespace util {
-using ::chromeos::file_system_provider::util::FileSystemURLParser;
 }  // namespace util
 }  // namespace file_system_provider
 }  // namespace ash
 
+// TODO(https://crbug.com/1164001): remove when ChromeOS code migration is done.
+namespace chromeos {
+namespace file_system_provider {
+namespace util {
+using ::ash::file_system_provider::util::GetMountPath;
+}  // namespace util
+}  // namespace file_system_provider
+}  // namespace chromeos
+
 #endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_MOUNT_PATH_UTIL_H_
diff --git a/chrome/browser/ash/file_system_provider/mount_path_util_unittest.cc b/chrome/browser/ash/file_system_provider/mount_path_util_unittest.cc
index 496044de..55338c4 100644
--- a/chrome/browser/ash/file_system_provider/mount_path_util_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/mount_path_util_unittest.cc
@@ -30,7 +30,7 @@
 #include "storage/browser/file_system/isolated_context.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 namespace util {
 
@@ -309,4 +309,4 @@
 
 }  // namespace util
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/notification_manager.cc b/chrome/browser/ash/file_system_provider/notification_manager.cc
index a6bd748..e182646 100644
--- a/chrome/browser/ash/file_system_provider/notification_manager.cc
+++ b/chrome/browser/ash/file_system_provider/notification_manager.cc
@@ -16,7 +16,7 @@
 #include "ui/message_center/public/cpp/notification_types.h"
 #include "ui/message_center/public/cpp/notifier_id.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 namespace {
 
@@ -126,4 +126,4 @@
 }
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/notification_manager.h b/chrome/browser/ash/file_system_provider/notification_manager.h
index c4c3301..a2a43b5 100644
--- a/chrome/browser/ash/file_system_provider/notification_manager.h
+++ b/chrome/browser/ash/file_system_provider/notification_manager.h
@@ -25,7 +25,7 @@
 class ImageSkia;
 }  // message gfx
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 // Provided file systems's manager for showing notifications. Shows always
@@ -78,6 +78,6 @@
 };
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
 
 #endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_NOTIFICATION_MANAGER_H_
diff --git a/chrome/browser/ash/file_system_provider/notification_manager_interface.h b/chrome/browser/ash/file_system_provider/notification_manager_interface.h
index 65cf388..6fb7103 100644
--- a/chrome/browser/ash/file_system_provider/notification_manager_interface.h
+++ b/chrome/browser/ash/file_system_provider/notification_manager_interface.h
@@ -8,7 +8,7 @@
 #include "base/callback.h"
 #include "base/macros.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 // Handles notifications related to provided the file system.
@@ -36,6 +36,6 @@
 };
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
 
 #endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_NOTIFICATION_MANAGER_INTERFACE_H_
diff --git a/chrome/browser/ash/file_system_provider/observer.h b/chrome/browser/ash/file_system_provider/observer.h
index 1e495f8d..6d42c62 100644
--- a/chrome/browser/ash/file_system_provider/observer.h
+++ b/chrome/browser/ash/file_system_provider/observer.h
@@ -7,7 +7,7 @@
 
 #include "base/files/file.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 class ProvidedFileSystemInfo;
@@ -38,6 +38,6 @@
 };
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
 
 #endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_OBSERVER_H_
diff --git a/chrome/browser/ash/file_system_provider/operations/configure.h b/chrome/browser/ash/file_system_provider/operations/configure.h
index 54acd40f..dc5bd40 100644
--- a/chrome/browser/ash/file_system_provider/operations/configure.h
+++ b/chrome/browser/ash/file_system_provider/operations/configure.h
@@ -10,8 +10,6 @@
 #include "base/files/file.h"
 #include "base/macros.h"
 #include "chrome/browser/ash/file_system_provider/operations/operation.h"
-// TODO(https://crbug.com/1164001): forward declare when moved ash
-#include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
 #include "storage/browser/file_system/async_file_util.h"
 
 namespace extensions {
@@ -20,6 +18,9 @@
 
 namespace ash {
 namespace file_system_provider {
+
+class ProvidedFileSystemInfo;
+
 namespace operations {
 
 // Bridge between fileManagerPrivate's configure operation and providing
diff --git a/chrome/browser/ash/file_system_provider/operations/unmount.h b/chrome/browser/ash/file_system_provider/operations/unmount.h
index 9e44d35..6e53025 100644
--- a/chrome/browser/ash/file_system_provider/operations/unmount.h
+++ b/chrome/browser/ash/file_system_provider/operations/unmount.h
@@ -10,8 +10,6 @@
 #include "base/files/file.h"
 #include "base/macros.h"
 #include "chrome/browser/ash/file_system_provider/operations/operation.h"
-// TODO(https://crbug.com/1164001): forward declare when moved ash
-#include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
 #include "storage/browser/file_system/async_file_util.h"
 
 namespace extensions {
@@ -20,6 +18,9 @@
 
 namespace ash {
 namespace file_system_provider {
+
+class ProvidedFileSystemInfo;
+
 namespace operations {
 
 // Bridge between fileManagerPrivate's unmount operation and providing
diff --git a/chrome/browser/ash/file_system_provider/provided_file_system.cc b/chrome/browser/ash/file_system_provider/provided_file_system.cc
index 883c584..e6d1636 100644
--- a/chrome/browser/ash/file_system_provider/provided_file_system.cc
+++ b/chrome/browser/ash/file_system_provider/provided_file_system.cc
@@ -43,34 +43,9 @@
 class IOBuffer;
 }  // namespace net
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
-// TODO(https://crbug.com/1164001): remove when moved to ash.
-namespace {
-namespace operations {
-using ::ash::file_system_provider::operations::Abort;
-using ::ash::file_system_provider::operations::AddWatcher;
-using ::ash::file_system_provider::operations::CloseFile;
-using ::ash::file_system_provider::operations::Configure;
-using ::ash::file_system_provider::operations::CopyEntry;
-using ::ash::file_system_provider::operations::CreateDirectory;
-using ::ash::file_system_provider::operations::CreateFile;
-using ::ash::file_system_provider::operations::DeleteEntry;
-using ::ash::file_system_provider::operations::ExecuteAction;
-using ::ash::file_system_provider::operations::GetActions;
-using ::ash::file_system_provider::operations::GetMetadata;
-using ::ash::file_system_provider::operations::MoveEntry;
-using ::ash::file_system_provider::operations::OpenFile;
-using ::ash::file_system_provider::operations::ReadDirectory;
-using ::ash::file_system_provider::operations::ReadFile;
-using ::ash::file_system_provider::operations::RemoveWatcher;
-using ::ash::file_system_provider::operations::Truncate;
-using ::ash::file_system_provider::operations::Unmount;
-using ::ash::file_system_provider::operations::WriteFile;
-}  // namespace operations
-}  // namespace
-
 AutoUpdater::AutoUpdater(base::OnceClosure update_callback)
     : update_callback_(std::move(update_callback)),
       created_callbacks_(0),
@@ -863,4 +838,4 @@
 }
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/provided_file_system.h b/chrome/browser/ash/file_system_provider/provided_file_system.h
index ae9874f7..3665976 100644
--- a/chrome/browser/ash/file_system_provider/provided_file_system.h
+++ b/chrome/browser/ash/file_system_provider/provided_file_system.h
@@ -39,7 +39,7 @@
 class EventRouter;
 }  // namespace extensions
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 class NotificationManagerInterface;
@@ -256,6 +256,6 @@
 };
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
 
 #endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_PROVIDED_FILE_SYSTEM_H_
diff --git a/chrome/browser/ash/file_system_provider/provided_file_system_info.cc b/chrome/browser/ash/file_system_provider/provided_file_system_info.cc
index e8fd9b7b..07b8284 100644
--- a/chrome/browser/ash/file_system_provider/provided_file_system_info.cc
+++ b/chrome/browser/ash/file_system_provider/provided_file_system_info.cc
@@ -6,7 +6,7 @@
 
 #include "base/check_op.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 ProviderId::ProviderId(const std::string& internal_id,
@@ -140,4 +140,4 @@
 ProvidedFileSystemInfo::~ProvidedFileSystemInfo() {}
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/provided_file_system_info.h b/chrome/browser/ash/file_system_provider/provided_file_system_info.h
index 4a32e8f..79a1256 100644
--- a/chrome/browser/ash/file_system_provider/provided_file_system_info.h
+++ b/chrome/browser/ash/file_system_provider/provided_file_system_info.h
@@ -12,7 +12,7 @@
 #include "chrome/common/extensions/api/file_system_provider_capabilities/file_system_provider_capabilities_handler.h"
 #include "extensions/common/extension_id.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 // Options for creating the provided file system info.
@@ -138,15 +138,15 @@
 };
 
 }  // namespace file_system_provider
-}  // namespace chromeos
-
-// TODO(https://crbug.com/1164001): remove when moved to ash.
-namespace ash {
-namespace file_system_provider {
-using ::chromeos::file_system_provider::MountOptions;
-using ::chromeos::file_system_provider::ProvidedFileSystemInfo;
-using ::chromeos::file_system_provider::ProviderId;
-}  // namespace file_system_provider
 }  // namespace ash
 
+// TODO(https://crbug.com/1164001): remove when ChromeOS code migration is done.
+namespace chromeos {
+namespace file_system_provider {
+using ::ash::file_system_provider::MountOptions;
+using ::ash::file_system_provider::ProvidedFileSystemInfo;
+using ::ash::file_system_provider::ProviderId;
+}  // namespace file_system_provider
+}  // namespace chromeos
+
 #endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_PROVIDED_FILE_SYSTEM_INFO_H_
diff --git a/chrome/browser/ash/file_system_provider/provided_file_system_interface.cc b/chrome/browser/ash/file_system_provider/provided_file_system_interface.cc
index a66dcb9..5603c8fd 100644
--- a/chrome/browser/ash/file_system_provider/provided_file_system_interface.cc
+++ b/chrome/browser/ash/file_system_provider/provided_file_system_interface.cc
@@ -4,7 +4,7 @@
 
 #include "chrome/browser/ash/file_system_provider/provided_file_system_interface.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 EntryMetadata::EntryMetadata() {}
@@ -22,4 +22,4 @@
 }
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/provided_file_system_interface.h b/chrome/browser/ash/file_system_provider/provided_file_system_interface.h
index a3b219e6..e9c23b0 100644
--- a/chrome/browser/ash/file_system_provider/provided_file_system_interface.h
+++ b/chrome/browser/ash/file_system_provider/provided_file_system_interface.h
@@ -32,7 +32,7 @@
 class IOBuffer;
 }  // namespace net
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 class ProvidedFileSystemInfo;
@@ -282,19 +282,18 @@
 };
 
 }  // namespace file_system_provider
-}  // namespace chromeos
-
-// TODO(https://crbug.com/1164001): remove when moved to ash.
-namespace ash {
-namespace file_system_provider {
-using ::chromeos::file_system_provider::Action;
-using ::chromeos::file_system_provider::Actions;
-using ::chromeos::file_system_provider::EntryMetadata;
-using ::chromeos::file_system_provider::OPEN_FILE_MODE_READ;
-using ::chromeos::file_system_provider::OPEN_FILE_MODE_WRITE;
-using ::chromeos::file_system_provider::OpenFileMode;
-using ::chromeos::file_system_provider::ProvidedFileSystemInterface;
-}  // namespace file_system_provider
 }  // namespace ash
 
+// TODO(https://crbug.com/1164001): remove when ChromeOS code migration is done.
+namespace chromeos {
+namespace file_system_provider {
+using ::ash::file_system_provider::Action;
+using ::ash::file_system_provider::EntryMetadata;
+using ::ash::file_system_provider::OPEN_FILE_MODE_READ;
+using ::ash::file_system_provider::OPEN_FILE_MODE_WRITE;
+using ::ash::file_system_provider::OpenedFiles;
+using ::ash::file_system_provider::OpenFileMode;
+}  // namespace file_system_provider
+}  // namespace chromeos
+
 #endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_PROVIDED_FILE_SYSTEM_INTERFACE_H_
diff --git a/chrome/browser/ash/file_system_provider/provided_file_system_observer.cc b/chrome/browser/ash/file_system_provider/provided_file_system_observer.cc
index f53da1f8..b4d1331 100644
--- a/chrome/browser/ash/file_system_provider/provided_file_system_observer.cc
+++ b/chrome/browser/ash/file_system_provider/provided_file_system_observer.cc
@@ -4,7 +4,7 @@
 
 #include "chrome/browser/ash/file_system_provider/provided_file_system_observer.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 ProvidedFileSystemObserver::Change::Change()
@@ -15,4 +15,4 @@
 }
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/provided_file_system_observer.h b/chrome/browser/ash/file_system_provider/provided_file_system_observer.h
index b4e9066f..f4b278e 100644
--- a/chrome/browser/ash/file_system_provider/provided_file_system_observer.h
+++ b/chrome/browser/ash/file_system_provider/provided_file_system_observer.h
@@ -12,7 +12,7 @@
 #include "chrome/browser/ash/file_system_provider/watcher.h"
 #include "storage/browser/file_system/watcher_manager.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 class ProvidedFileSystemInfo;
@@ -57,6 +57,13 @@
 };
 
 }  // namespace file_system_provider
+}  // namespace ash
+
+// TODO(https://crbug.com/1164001): remove when ChromeOS code migration is done.
+namespace chromeos {
+namespace file_system_provider {
+using ::ash::file_system_provider::ProvidedFileSystemObserver;
+}  // namespace file_system_provider
 }  // namespace chromeos
 
 #endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_PROVIDED_FILE_SYSTEM_OBSERVER_H_
diff --git a/chrome/browser/ash/file_system_provider/provided_file_system_unittest.cc b/chrome/browser/ash/file_system_provider/provided_file_system_unittest.cc
index b59e2e0d..8e4fd98 100644
--- a/chrome/browser/ash/file_system_provider/provided_file_system_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/provided_file_system_unittest.cc
@@ -35,7 +35,7 @@
 #include "storage/browser/file_system/watcher_manager.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 namespace {
 
@@ -942,4 +942,4 @@
 }
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/provider_interface.h b/chrome/browser/ash/file_system_provider/provider_interface.h
index 12b676a..925375f 100644
--- a/chrome/browser/ash/file_system_provider/provider_interface.h
+++ b/chrome/browser/ash/file_system_provider/provider_interface.h
@@ -15,11 +15,11 @@
 
 class Profile;
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
-// TODO(https://crbug.com/1164001): forward declare ProviderId when moved ash
 class ProvidedFileSystemInterface;
+class ProviderId;
 
 struct Capabilities {
   Capabilities(bool configurable,
@@ -74,6 +74,15 @@
 };
 
 }  // namespace file_system_provider
+}  // namespace ash
+
+// TODO(https://crbug.com/1164001): remove when ChromeOS code migration is done.
+namespace chromeos {
+namespace file_system_provider {
+using ::ash::file_system_provider::Capabilities;
+using ::ash::file_system_provider::ProvidedFileSystemInterface;
+using ::ash::file_system_provider::ProviderInterface;
+}  // namespace file_system_provider
 }  // namespace chromeos
 
 #endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_PROVIDER_INTERFACE_H_
diff --git a/chrome/browser/ash/file_system_provider/queue.cc b/chrome/browser/ash/file_system_provider/queue.cc
index b581794e..f76375f9 100644
--- a/chrome/browser/ash/file_system_provider/queue.cc
+++ b/chrome/browser/ash/file_system_provider/queue.cc
@@ -11,7 +11,7 @@
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 Queue::Task::Task() : token(0) {
@@ -107,4 +107,4 @@
 }
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/queue.h b/chrome/browser/ash/file_system_provider/queue.h
index a76df97..536d221 100644
--- a/chrome/browser/ash/file_system_provider/queue.h
+++ b/chrome/browser/ash/file_system_provider/queue.h
@@ -17,7 +17,7 @@
 #include "chrome/browser/ash/file_system_provider/abort_callback.h"
 #include "storage/browser/file_system/async_file_util.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 // Queues arbitrary tasks. At most |max_in_parallel_| tasks will be running at
@@ -92,6 +92,6 @@
 };
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
 
 #endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_QUEUE_H_
diff --git a/chrome/browser/ash/file_system_provider/queue_unittest.cc b/chrome/browser/ash/file_system_provider/queue_unittest.cc
index 4264f31..b4c864b 100644
--- a/chrome/browser/ash/file_system_provider/queue_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/queue_unittest.cc
@@ -14,7 +14,7 @@
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 namespace {
 
@@ -287,4 +287,4 @@
 }
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/registry.cc b/chrome/browser/ash/file_system_provider/registry.cc
index 4c19ed0..e6b2498c 100644
--- a/chrome/browser/ash/file_system_provider/registry.cc
+++ b/chrome/browser/ash/file_system_provider/registry.cc
@@ -24,7 +24,7 @@
 #include "storage/browser/file_system/external_mount_points.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 const char kPrefKeyFileSystemId[] = "file-system-id";
@@ -285,4 +285,4 @@
 }
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/registry.h b/chrome/browser/ash/file_system_provider/registry.h
index e87feb2d..a1dea71 100644
--- a/chrome/browser/ash/file_system_provider/registry.h
+++ b/chrome/browser/ash/file_system_provider/registry.h
@@ -18,7 +18,7 @@
 class PrefRegistrySyncable;
 }  // namespace user_prefs
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 // Key names for preferences.
@@ -60,6 +60,6 @@
 };
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
 
 #endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_REGISTRY_H_
diff --git a/chrome/browser/ash/file_system_provider/registry_interface.cc b/chrome/browser/ash/file_system_provider/registry_interface.cc
index 6262992..a6aa2c9 100644
--- a/chrome/browser/ash/file_system_provider/registry_interface.cc
+++ b/chrome/browser/ash/file_system_provider/registry_interface.cc
@@ -4,7 +4,7 @@
 
 #include "chrome/browser/ash/file_system_provider/registry_interface.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 RegistryInterface::~RegistryInterface() {
@@ -20,4 +20,4 @@
 }
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/registry_interface.h b/chrome/browser/ash/file_system_provider/registry_interface.h
index 7af41be1..5dbcab9 100644
--- a/chrome/browser/ash/file_system_provider/registry_interface.h
+++ b/chrome/browser/ash/file_system_provider/registry_interface.h
@@ -12,7 +12,7 @@
 #include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
 #include "chrome/browser/ash/file_system_provider/watcher.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 // Remembers and restores file systems in a persistent storage.
@@ -59,6 +59,6 @@
 };
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
 
 #endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_REGISTRY_INTERFACE_H_
diff --git a/chrome/browser/ash/file_system_provider/registry_unittest.cc b/chrome/browser/ash/file_system_provider/registry_unittest.cc
index 0e239b2e..bed2c41 100644
--- a/chrome/browser/ash/file_system_provider/registry_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/registry_unittest.cc
@@ -23,7 +23,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 namespace {
 
@@ -326,4 +326,4 @@
 }
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/request_manager.cc b/chrome/browser/ash/file_system_provider/request_manager.cc
index c8f62db..7e90225 100644
--- a/chrome/browser/ash/file_system_provider/request_manager.cc
+++ b/chrome/browser/ash/file_system_provider/request_manager.cc
@@ -16,7 +16,7 @@
 #include "extensions/browser/app_window/app_window_registry.h"
 #include "extensions/common/constants.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 namespace {
 
@@ -257,4 +257,4 @@
 }
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/request_manager.h b/chrome/browser/ash/file_system_provider/request_manager.h
index 7b391f4..63492488 100644
--- a/chrome/browser/ash/file_system_provider/request_manager.h
+++ b/chrome/browser/ash/file_system_provider/request_manager.h
@@ -22,7 +22,7 @@
 
 class Profile;
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 // Request type, passed to RequestManager::CreateRequest. For logging purposes.
@@ -192,13 +192,13 @@
 };
 
 }  // namespace file_system_provider
-}  // namespace chromeos
-
-// TODO(https://crbug.com/1164001): remove when moved to ash.
-namespace ash {
-namespace file_system_provider {
-using ::chromeos::file_system_provider::RequestManager;
-}  // namespace file_system_provider
 }  // namespace ash
 
+// TODO(https://crbug.com/1164001): remove when ChromeOS code migration is done.
+namespace chromeos {
+namespace file_system_provider {
+using ::ash::file_system_provider::RequestManager;
+}  // namespace file_system_provider
+}  // namespace chromeos
+
 #endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_REQUEST_MANAGER_H_
diff --git a/chrome/browser/ash/file_system_provider/request_manager_unittest.cc b/chrome/browser/ash/file_system_provider/request_manager_unittest.cc
index c31d592..6df9179 100644
--- a/chrome/browser/ash/file_system_provider/request_manager_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/request_manager_unittest.cc
@@ -26,7 +26,7 @@
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 namespace {
 
@@ -820,4 +820,4 @@
 }
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/request_value.cc b/chrome/browser/ash/file_system_provider/request_value.cc
index 54aadcc..537523e6 100644
--- a/chrome/browser/ash/file_system_provider/request_value.cc
+++ b/chrome/browser/ash/file_system_provider/request_value.cc
@@ -7,7 +7,7 @@
 #include <memory>
 #include <utility>
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 RequestValue::RequestValue() {
@@ -80,4 +80,4 @@
 }
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/request_value.h b/chrome/browser/ash/file_system_provider/request_value.h
index 1f6130c..347ea0c 100644
--- a/chrome/browser/ash/file_system_provider/request_value.h
+++ b/chrome/browser/ash/file_system_provider/request_value.h
@@ -11,7 +11,7 @@
 #include "base/macros.h"
 #include "chrome/common/extensions/api/file_system_provider_internal.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 // Holds a parsed value returned by a file system provider. Each accessor can
@@ -128,13 +128,6 @@
 };
 
 }  // namespace file_system_provider
-}  // namespace chromeos
-
-// TODO(https://crbug.com/1164001): remove when moved to ash.
-namespace ash {
-namespace file_system_provider {
-using ::chromeos::file_system_provider::RequestValue;
-}  // namespace file_system_provider
 }  // namespace ash
 
 #endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_REQUEST_VALUE_H_
diff --git a/chrome/browser/ash/file_system_provider/scoped_file_opener.cc b/chrome/browser/ash/file_system_provider/scoped_file_opener.cc
index 4528bd4d..2beba50 100644
--- a/chrome/browser/ash/file_system_provider/scoped_file_opener.cc
+++ b/chrome/browser/ash/file_system_provider/scoped_file_opener.cc
@@ -8,7 +8,7 @@
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/ash/file_system_provider/abort_callback.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 using OpenFileCallback = ProvidedFileSystemInterface::OpenFileCallback;
@@ -142,4 +142,4 @@
 }
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/scoped_file_opener.h b/chrome/browser/ash/file_system_provider/scoped_file_opener.h
index f6310ab..afcbabc 100644
--- a/chrome/browser/ash/file_system_provider/scoped_file_opener.h
+++ b/chrome/browser/ash/file_system_provider/scoped_file_opener.h
@@ -12,7 +12,7 @@
 class FilePath;
 }  // namespace base
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 // Opens files and guarantees that they will be closed when the object gets out
@@ -33,13 +33,6 @@
 };
 
 }  // namespace file_system_provider
-}  // namespace chromeos
-
-// TODO(https://crbug.com/1164001): remove when moved to ash.
-namespace ash {
-namespace file_system_provider {
-using ::chromeos::file_system_provider::ScopedFileOpener;
-}  // namespace file_system_provider
 }  // namespace ash
 
 #endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_SCOPED_FILE_OPENER_H_
diff --git a/chrome/browser/ash/file_system_provider/scoped_file_opener_unittest.cc b/chrome/browser/ash/file_system_provider/scoped_file_opener_unittest.cc
index 0c957f5..dfc9fb6 100644
--- a/chrome/browser/ash/file_system_provider/scoped_file_opener_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/scoped_file_opener_unittest.cc
@@ -17,7 +17,7 @@
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 namespace {
 
@@ -132,4 +132,4 @@
 }
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/service.cc b/chrome/browser/ash/file_system_provider/service.cc
index b47df1d..a06f7165 100644
--- a/chrome/browser/ash/file_system_provider/service.cc
+++ b/chrome/browser/ash/file_system_provider/service.cc
@@ -30,7 +30,7 @@
 #include "storage/browser/file_system/external_mount_points.h"
 #include "storage/common/file_system/file_system_mount_option.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 namespace {
 
@@ -452,4 +452,4 @@
 }
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/service.h b/chrome/browser/ash/file_system_provider/service.h
index 8e568b34..b479495 100644
--- a/chrome/browser/ash/file_system_provider/service.h
+++ b/chrome/browser/ash/file_system_provider/service.h
@@ -42,13 +42,14 @@
 class PrefRegistrySyncable;
 }  // namespace user_prefs
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
-// TODO(https://crbug.com/1164001): forward declare MountOptions,
-// ProvidedFileSystemInfo when moved ash
-class ProvidedFileSystemInterface;
+// TODO(https://crbug.com/1164001): forward declare ProvidedFileSystemInterface
+// when //c/b/chromeos/smb_client is moved to ash.
+class ProvidedFileSystemInfo;
 class RegistryInterface;
+struct MountOptions;
 
 // Registers preferences to remember registered file systems between reboots.
 void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
@@ -213,13 +214,13 @@
 };
 
 }  // namespace file_system_provider
-}  // namespace chromeos
-
-// TODO(https://crbug.com/1164001): remove when moved to ash.
-namespace ash {
-namespace file_system_provider {
-using ::chromeos::file_system_provider::Service;
-}  // namespace file_system_provider
 }  // namespace ash
 
+// TODO(https://crbug.com/1164001): remove when ChromeOS code migration is done.
+namespace chromeos {
+namespace file_system_provider {
+using ::ash::file_system_provider::Service;
+}  // namespace file_system_provider
+}  // namespace chromeos
+
 #endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_SERVICE_H_
diff --git a/chrome/browser/ash/file_system_provider/service_factory.cc b/chrome/browser/ash/file_system_provider/service_factory.cc
index daefbde..333792c 100644
--- a/chrome/browser/ash/file_system_provider/service_factory.cc
+++ b/chrome/browser/ash/file_system_provider/service_factory.cc
@@ -12,7 +12,7 @@
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_factory.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 // static
@@ -56,4 +56,4 @@
 }
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/service_factory.h b/chrome/browser/ash/file_system_provider/service_factory.h
index ee2cd58d..f839b64 100644
--- a/chrome/browser/ash/file_system_provider/service_factory.h
+++ b/chrome/browser/ash/file_system_provider/service_factory.h
@@ -10,7 +10,7 @@
 #include "components/keyed_service/content/browser_context_keyed_service_factory.h"
 #include "content/public/browser/browser_context.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 class Service;
@@ -45,6 +45,13 @@
 };
 
 }  // namespace file_system_provider
+}  // namespace ash
+
+// TODO(https://crbug.com/1164001): remove when ChromeOS code migration is done.
+namespace chromeos {
+namespace file_system_provider {
+using ::ash::file_system_provider::ServiceFactory;
+}  // namespace file_system_provider
 }  // namespace chromeos
 
 #endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_SERVICE_FACTORY_H_
diff --git a/chrome/browser/ash/file_system_provider/service_unittest.cc b/chrome/browser/ash/file_system_provider/service_unittest.cc
index ddb9ad1..32f38bb 100644
--- a/chrome/browser/ash/file_system_provider/service_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/service_unittest.cc
@@ -41,7 +41,7 @@
 #include "storage/browser/file_system/external_mount_points.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 namespace {
 
@@ -497,4 +497,4 @@
 }
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/throttled_file_system.cc b/chrome/browser/ash/file_system_provider/throttled_file_system.cc
index 03f78a6..cb71b0f 100644
--- a/chrome/browser/ash/file_system_provider/throttled_file_system.cc
+++ b/chrome/browser/ash/file_system_provider/throttled_file_system.cc
@@ -14,7 +14,7 @@
 #include "base/files/file.h"
 #include "chrome/browser/ash/file_system_provider/queue.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 ThrottledFileSystem::ThrottledFileSystem(
@@ -249,4 +249,4 @@
 }
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/throttled_file_system.h b/chrome/browser/ash/file_system_provider/throttled_file_system.h
index 0b818aa2..da0fc236 100644
--- a/chrome/browser/ash/file_system_provider/throttled_file_system.h
+++ b/chrome/browser/ash/file_system_provider/throttled_file_system.h
@@ -31,7 +31,7 @@
 class FilePath;
 }  // namespace base
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 class Queue;
@@ -151,6 +151,6 @@
 };
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
 
 #endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_THROTTLED_FILE_SYSTEM_H_
diff --git a/chrome/browser/ash/file_system_provider/throttled_file_system_unittest.cc b/chrome/browser/ash/file_system_provider/throttled_file_system_unittest.cc
index 1c3e9f0f..cbc4039 100644
--- a/chrome/browser/ash/file_system_provider/throttled_file_system_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/throttled_file_system_unittest.cc
@@ -20,7 +20,7 @@
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 namespace {
 
@@ -160,4 +160,4 @@
 }
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/watcher.cc b/chrome/browser/ash/file_system_provider/watcher.cc
index efa338ac..54398e5 100644
--- a/chrome/browser/ash/file_system_provider/watcher.cc
+++ b/chrome/browser/ash/file_system_provider/watcher.cc
@@ -4,7 +4,7 @@
 
 #include "chrome/browser/ash/file_system_provider/watcher.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 WatcherKey::WatcherKey(const base::FilePath& entry_path, bool recursive)
@@ -38,4 +38,4 @@
 }
 
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/file_system_provider/watcher.h b/chrome/browser/ash/file_system_provider/watcher.h
index 919eb58..60dd3c9e 100644
--- a/chrome/browser/ash/file_system_provider/watcher.h
+++ b/chrome/browser/ash/file_system_provider/watcher.h
@@ -14,7 +14,7 @@
 #include "storage/browser/file_system/watcher_manager.h"
 #include "url/gurl.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 
 struct Watcher;
@@ -81,6 +81,13 @@
 };
 
 }  // namespace file_system_provider
+}  // namespace ash
+
+// TODO(https://crbug.com/1164001): remove when ChromeOS code migration is done.
+namespace chromeos {
+namespace file_system_provider {
+using ::ash::file_system_provider::Watchers;
+}  // namespace file_system_provider
 }  // namespace chromeos
 
 #endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_WATCHER_H_
diff --git a/chrome/browser/ash/power/smart_charging/smart_charging_manager.cc b/chrome/browser/ash/power/smart_charging/smart_charging_manager.cc
index ab468ff..2301fd05 100644
--- a/chrome/browser/ash/power/smart_charging/smart_charging_manager.cc
+++ b/chrome/browser/ash/power/smart_charging/smart_charging_manager.cc
@@ -29,7 +29,7 @@
 #include "ui/aura/env.h"
 #include "ui/compositor/compositor.h"
 
-namespace chromeos {
+namespace ash {
 namespace power {
 
 namespace {
@@ -203,7 +203,7 @@
   // TODO(crbug.com/1028853): we are collecting data from Chromebook only. Since
   // this action is discouraged, we will modify the condition latter using dbus
   // calls.
-  if (chromeos::GetDeviceType() != chromeos::DeviceType::kChromebook)
+  if (GetDeviceType() != DeviceType::kChromebook)
     return nullptr;
 
   ui::UserActivityDetector* const detector = ui::UserActivityDetector::Get();
@@ -634,4 +634,4 @@
 }
 
 }  // namespace power
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/power/smart_charging/smart_charging_manager.h b/chrome/browser/ash/power/smart_charging/smart_charging_manager.h
index 42c51a11..e0ee4da 100644
--- a/chrome/browser/ash/power/smart_charging/smart_charging_manager.h
+++ b/chrome/browser/ash/power/smart_charging/smart_charging_manager.h
@@ -13,8 +13,6 @@
 #include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "chrome/browser/ash/power/ml/boot_clock.h"
-// TODO(https://crbug.com/1164001): forward declare RecentEventsCounter
-#include "chrome/browser/ash/power/ml/recent_events_counter.h"
 #include "chrome/browser/ash/power/smart_charging/smart_charging_ukm_logger.h"
 #include "chrome/browser/ash/power/smart_charging/user_charging_event.pb.h"
 #include "chrome/browser/profiles/profile.h"
@@ -27,8 +25,11 @@
 #include "ui/base/user_activity/user_activity_detector.h"
 #include "ui/base/user_activity/user_activity_observer.h"
 
-namespace chromeos {
+namespace ash {
 namespace power {
+namespace ml {
+class RecentEventsCounter;
+}  // namespace ml
 
 using PastEvent = PastChargingEvents::Event;
 using EventReason = UserChargingEvent::Event::Reason;
@@ -186,6 +187,13 @@
 };
 
 }  // namespace power
+}  // namespace ash
+
+// TODO(https://crbug.com/1164001): remove when ChromeOS code migration is done.
+namespace chromeos {
+namespace power {
+using ::ash::power::SmartChargingManager;
+}  // namespace power
 }  // namespace chromeos
 
 #endif  // CHROME_BROWSER_ASH_POWER_SMART_CHARGING_SMART_CHARGING_MANAGER_H_
diff --git a/chrome/browser/ash/power/smart_charging/smart_charging_manager_unittest.cc b/chrome/browser/ash/power/smart_charging/smart_charging_manager_unittest.cc
index 4a6a06c..58bf7810 100644
--- a/chrome/browser/ash/power/smart_charging/smart_charging_manager_unittest.cc
+++ b/chrome/browser/ash/power/smart_charging/smart_charging_manager_unittest.cc
@@ -15,7 +15,7 @@
 #include "components/session_manager/core/session_manager.h"
 #include "ui/events/keycodes/dom/dom_code.h"
 
-namespace chromeos {
+namespace ash {
 namespace power {
 namespace {
 PastEvent CreateEvent(int time,
@@ -515,4 +515,4 @@
   EXPECT_EQ(features.battery_percentage_of_last_charge(), 80);
 }
 }  // namespace power
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/power/smart_charging/smart_charging_ukm_logger.cc b/chrome/browser/ash/power/smart_charging/smart_charging_ukm_logger.cc
index 082ea87..ed51848 100644
--- a/chrome/browser/ash/power/smart_charging/smart_charging_ukm_logger.cc
+++ b/chrome/browser/ash/power/smart_charging/smart_charging_ukm_logger.cc
@@ -9,7 +9,7 @@
 #include "services/metrics/public/cpp/ukm_recorder.h"
 #include "services/metrics/public/cpp/ukm_source_id.h"
 
-namespace chromeos {
+namespace ash {
 namespace power {
 
 void SmartChargingUkmLogger::LogEvent(
@@ -126,4 +126,4 @@
 }
 
 }  // namespace power
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/power/smart_charging/smart_charging_ukm_logger.h b/chrome/browser/ash/power/smart_charging/smart_charging_ukm_logger.h
index 1aa0e5a..06891d1b 100644
--- a/chrome/browser/ash/power/smart_charging/smart_charging_ukm_logger.h
+++ b/chrome/browser/ash/power/smart_charging/smart_charging_ukm_logger.h
@@ -7,7 +7,7 @@
 
 #include "services/metrics/public/cpp/ukm_source_id.h"
 
-namespace chromeos {
+namespace ash {
 namespace power {
 class UserChargingEvent;
 
@@ -21,6 +21,6 @@
   void LogEvent(const UserChargingEvent& user_charging_event) const;
 };
 }  // namespace power
-}  // namespace chromeos
+}  // namespace ash
 
 #endif  // CHROME_BROWSER_ASH_POWER_SMART_CHARGING_SMART_CHARGING_UKM_LOGGER_H_
diff --git a/chrome/browser/ash/power/smart_charging/smart_charging_ukm_logger_unittest.cc b/chrome/browser/ash/power/smart_charging/smart_charging_ukm_logger_unittest.cc
index 2abe9ba..32624f89 100644
--- a/chrome/browser/ash/power/smart_charging/smart_charging_ukm_logger_unittest.cc
+++ b/chrome/browser/ash/power/smart_charging/smart_charging_ukm_logger_unittest.cc
@@ -10,12 +10,11 @@
 #include "services/metrics/public/cpp/ukm_builders.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-namespace chromeos {
+namespace ash {
 namespace power {
 
 namespace {
 using UkmEntry = ukm::builders::SmartCharging;
-
 }  // namespace
 
 class SmartChargingUkmLoggerTest : public ChromeRenderViewHostTestHarness {
@@ -105,4 +104,4 @@
 }
 
 }  // namespace power
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chrome/browser/ash/power/smart_charging/user_charging_event.proto b/chrome/browser/ash/power/smart_charging/user_charging_event.proto
index 5738564..9cf8635a 100644
--- a/chrome/browser/ash/power/smart_charging/user_charging_event.proto
+++ b/chrome/browser/ash/power/smart_charging/user_charging_event.proto
@@ -4,7 +4,7 @@
 
 syntax = "proto2";
 
-package chromeos.power;
+package ash.power;
 
 option optimize_for = LITE_RUNTIME;
 
diff --git a/chrome/browser/ash/wallpaper/wallpaper_enumerator.cc b/chrome/browser/ash/wallpaper/wallpaper_enumerator.cc
new file mode 100644
index 0000000..68be0ac
--- /dev/null
+++ b/chrome/browser/ash/wallpaper/wallpaper_enumerator.cc
@@ -0,0 +1,71 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/wallpaper/wallpaper_enumerator.h"
+
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "chrome/browser/chromeos/file_manager/path_util.h"
+#include "chrome/browser/ui/ash/thumbnail_loader.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/webui/web_ui_util.h"
+
+namespace {
+
+constexpr char kPngFilePattern[] = "*.[pP][nN][gG]";
+constexpr char kJpgFilePattern[] = "*.[jJ][pP][gG]";
+constexpr char kJpegFilePattern[] = "*.[jJ][pP][eE][gG]";
+
+constexpr int kMaximumImageCount = 1000;
+
+void EnumerateFiles(const base::FilePath& path,
+                    const std::string& pattern,
+                    std::vector<base::FilePath>* out) {
+  base::FileEnumerator image_enumerator(
+      path, /*recursive=*/true, base::FileEnumerator::FILES,
+      FILE_PATH_LITERAL(pattern),
+      base::FileEnumerator::FolderSearchPolicy::ALL);
+
+  for (base::FilePath image_path = image_enumerator.Next();
+       !image_path.empty() && out->size() < kMaximumImageCount;
+       image_path = image_enumerator.Next()) {
+    out->push_back(image_path);
+  }
+}
+
+std::vector<base::FilePath> EnumerateAllImages(
+    const base::FilePath& search_path) {
+  std::vector<base::FilePath> image_paths;
+
+  EnumerateFiles(search_path, kPngFilePattern, &image_paths);
+  EnumerateFiles(search_path, kJpgFilePattern, &image_paths);
+  EnumerateFiles(search_path, kJpegFilePattern, &image_paths);
+
+  return image_paths;
+}
+
+}  // namespace
+
+namespace ash {
+
+void EnumerateLocalWallpaperFiles(
+    Profile* profile,
+    base::OnceCallback<void(const std::vector<base::FilePath>&)> callback) {
+  const base::FilePath search_path =
+      file_manager::util::GetMyFilesFolderForProfile(profile);
+
+  base::ThreadPool::PostTaskAndReplyWithResult(
+      FROM_HERE,
+      {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+       base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
+      base::BindOnce(&EnumerateAllImages, search_path), std::move(callback));
+}
+
+}  // namespace ash
diff --git a/chrome/browser/ash/wallpaper/wallpaper_enumerator.h b/chrome/browser/ash/wallpaper/wallpaper_enumerator.h
new file mode 100644
index 0000000..6d652906
--- /dev/null
+++ b/chrome/browser/ash/wallpaper/wallpaper_enumerator.h
@@ -0,0 +1,28 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ASH_WALLPAPER_WALLPAPER_ENUMERATOR_H_
+#define CHROME_BROWSER_ASH_WALLPAPER_WALLPAPER_ENUMERATOR_H_
+
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/files/file_path.h"
+
+class Profile;
+
+namespace ash {
+
+// Searches the user's files for jpg and png images. This is used for
+// displaying images that the user could select as a custom wallpaper.
+// TODO(crbug.com/810575): Add metrics on the number of files retrieved, and
+// support getting paths incrementally in case the user has a large number of
+// local images.
+void EnumerateLocalWallpaperFiles(
+    Profile* profile,
+    base::OnceCallback<void(const std::vector<base::FilePath>&)> callback);
+
+}  // namespace ash
+
+#endif  // CHROME_BROWSER_ASH_WALLPAPER_WALLPAPER_ENUMERATOR_H_
diff --git a/chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate.cc b/chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate.cc
index 5ddabe4..bdb60fb 100644
--- a/chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate.cc
+++ b/chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate.cc
@@ -17,21 +17,31 @@
 #include "base/bind.h"
 #include "base/notreached.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/unguessable_token.h"
 #include "chrome/browser/ash/backdrop_wallpaper_handlers/backdrop_wallpaper.pb.h"
 #include "chrome/browser/ash/backdrop_wallpaper_handlers/backdrop_wallpaper_handlers.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
+#include "chrome/browser/ash/wallpaper/wallpaper_enumerator.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/ash/thumbnail_loader.h"
 #include "chrome/browser/ui/ash/wallpaper_controller_client_impl.h"
-#include "chromeos/components/personalization_app/mojom/personalization_app.mojom-forward.h"
 #include "chromeos/components/personalization_app/mojom/personalization_app.mojom.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_ui.h"
+#include "mojo/public/cpp/bindings/message.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/type_converter.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/webui/web_ui_util.h"
+#include "ui/gfx/geometry/size.h"
 #include "url/gurl.h"
 
+namespace {
+constexpr int kLocalImageThumbnailSizeDip = 128;
+}  // namespace
+
 namespace mojo {
 
 template <>
@@ -74,6 +84,17 @@
   }
 };
 
+template <>
+struct TypeConverter<chromeos::personalization_app::mojom::LocalImagePtr,
+                     base::FilePath> {
+  static chromeos::personalization_app::mojom::LocalImagePtr Convert(
+      const base::FilePath& path) {
+    auto token = base::UnguessableToken::Create();
+    std::string name = path.BaseName().RemoveExtension().value();
+    return chromeos::personalization_app::mojom::LocalImage::New(token, name);
+  }
+};
+
 }  // namespace mojo
 
 ChromePersonalizationAppUiDelegate::ChromePersonalizationAppUiDelegate(
@@ -120,6 +141,40 @@
       base::Unretained(this), std::move(callback)));
 }
 
+void ChromePersonalizationAppUiDelegate::GetLocalImages(
+    GetLocalImagesCallback callback) {
+  // TODO(b/190062481) also load images from android files.
+  ash::EnumerateLocalWallpaperFiles(
+      profile_,
+      base::BindOnce(&ChromePersonalizationAppUiDelegate::OnGetLocalImages,
+                     backend_weak_ptr_factory_.GetWeakPtr(),
+                     std::move(callback)));
+}
+
+void ChromePersonalizationAppUiDelegate::GetLocalImageThumbnail(
+    const base::UnguessableToken& id,
+    GetLocalImageThumbnailCallback callback) {
+  const auto& entry = local_image_id_map_.find(id);
+  if (entry == local_image_id_map_.end()) {
+    mojo::ReportBadMessage("Invalid local image id received");
+    return;
+  }
+  const base::FilePath& file_path = entry->second;
+
+  if (!thumbnail_loader_)
+    thumbnail_loader_ = std::make_unique<ash::ThumbnailLoader>(profile_);
+
+  ash::ThumbnailLoader::ThumbnailRequest request(
+      file_path,
+      gfx::Size(kLocalImageThumbnailSizeDip, kLocalImageThumbnailSizeDip));
+
+  thumbnail_loader_->Load(
+      request,
+      base::BindOnce(
+          &ChromePersonalizationAppUiDelegate::OnGetLocalImageThumbnail,
+          base::Unretained(this), std::move(callback)));
+}
+
 void ChromePersonalizationAppUiDelegate::GetCurrentWallpaper(
     GetCurrentWallpaperCallback callback) {
   auto* client = WallpaperControllerClientImpl::Get();
@@ -149,8 +204,7 @@
       std::move(callback).Run(nullptr);
       return;
     case ash::WallpaperType::WALLPAPER_TYPE_COUNT:
-      NOTREACHED() << "Impossible WallpaperType";
-      std::move(callback).Run(nullptr);
+      mojo::ReportBadMessage("Impossible WallpaperType received");
       return;
   }
 }
@@ -161,8 +215,7 @@
   const auto& it = image_asset_id_map_.find(image_asset_id);
 
   if (it == image_asset_id_map_.end()) {
-    LOG(WARNING) << "Invalid image asset_id selected";
-    std::move(callback).Run(false);
+    mojo::ReportBadMessage("Invalid image asset_id selected");
     return;
   }
 
@@ -235,3 +288,32 @@
   std::move(callback).Run(std::move(result));
   wallpaper_images_info_fetcher_.reset();
 }
+
+void ChromePersonalizationAppUiDelegate::OnGetLocalImages(
+    GetLocalImagesCallback callback,
+    const std::vector<base::FilePath>& images) {
+  local_image_id_map_.clear();
+  std::vector<chromeos::personalization_app::mojom::LocalImagePtr> result;
+  for (const auto& image_path : images) {
+    auto local_image =
+        chromeos::personalization_app::mojom::LocalImage::From<base::FilePath>(
+            image_path);
+
+    local_image_id_map_.insert({local_image->id, image_path});
+    result.push_back(std::move(local_image));
+  }
+  std::move(callback).Run(std::move(result));
+}
+
+void ChromePersonalizationAppUiDelegate::OnGetLocalImageThumbnail(
+    GetLocalImageThumbnailCallback callback,
+    const SkBitmap* bitmap,
+    base::File::Error error) {
+  if (error != base::File::Error::FILE_OK) {
+    // Do not call |mojom::ReportBadMessage| here. The message is valid, but
+    // the file may be corrupt or unreadable.
+    std::move(callback).Run(std::string());
+    return;
+  }
+  std::move(callback).Run(webui::GetBitmapDataUrl(*bitmap));
+}
diff --git a/chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate.h b/chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate.h
index 40e3e9e..9bc8955 100644
--- a/chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate.h
+++ b/chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate.h
@@ -11,11 +11,18 @@
 
 #include <string>
 
+#include "base/memory/weak_ptr.h"
+#include "base/unguessable_token.h"
 #include "chromeos/components/personalization_app/mojom/personalization_app.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
+#include "third_party/skia/include/core/SkBitmap.h"
 #include "url/gurl.h"
 
+namespace ash {
+class ThumbnailLoader;
+}  // namespace ash
+
 namespace backdrop {
 class Collection;
 class Image;
@@ -60,6 +67,11 @@
       const std::string& collection_id,
       FetchImagesForCollectionCallback callback) override;
 
+  void GetLocalImages(GetLocalImagesCallback callback) override;
+
+  void GetLocalImageThumbnail(const base::UnguessableToken& file_path,
+                              GetLocalImageThumbnailCallback callback) override;
+
   void GetCurrentWallpaper(GetCurrentWallpaperCallback callback) override;
 
   void SelectWallpaper(uint64_t image_asset_id,
@@ -78,12 +90,21 @@
                                const std::string& collection_id,
                                const std::vector<backdrop::Image>& images);
 
+  void OnGetLocalImages(GetLocalImagesCallback callback,
+                        const std::vector<base::FilePath>& images);
+
+  void OnGetLocalImageThumbnail(GetLocalImageThumbnailCallback callback,
+                                const SkBitmap* bitmap,
+                                base::File::Error error);
+
   std::unique_ptr<backdrop_wallpaper_handlers::CollectionInfoFetcher>
       wallpaper_collection_info_fetcher_;
 
   std::unique_ptr<backdrop_wallpaper_handlers::ImageInfoFetcher>
       wallpaper_images_info_fetcher_;
 
+  std::unique_ptr<ash::ThumbnailLoader> thumbnail_loader_;
+
   struct ImageInfo {
     GURL image_url;
     std::string collection_id;
@@ -93,8 +114,17 @@
   // user wallpaper selections.
   std::map<uint64_t, ImageInfo> image_asset_id_map_;
 
+  // When local images are fetched, assign each one a random |UnguessableToken|
+  // id. Store a mapping from these tokens to |FilePath|. The SWA passes a token
+  // id to get an image thumbnail preview.
+  std::map<base::UnguessableToken, base::FilePath> local_image_id_map_;
+
   // Pointer to profile of user that opened personalization SWA. Not owned.
   Profile* const profile_ = nullptr;
+
+  // Used for interacting with local filesystem.
+  base::WeakPtrFactory<ChromePersonalizationAppUiDelegate>
+      backend_weak_ptr_factory_{this};
 };
 
 #endif  // CHROME_BROWSER_ASH_WEB_APPLICATIONS_PERSONALIZATION_APP_CHROME_PERSONALIZATION_APP_UI_DELEGATE_H_
diff --git a/chrome/browser/browsing_data/counters/browsing_data_counter_utils_unittest.cc b/chrome/browser/browsing_data/counters/browsing_data_counter_utils_unittest.cc
index 381ceca..01fb582 100644
--- a/chrome/browser/browsing_data/counters/browsing_data_counter_utils_unittest.cc
+++ b/chrome/browser/browsing_data/counters/browsing_data_counter_utils_unittest.cc
@@ -56,15 +56,17 @@
       {42, false, true,
        "Frees up less than 1 MB. Some sites may load more slowly on your next "
        "visit."},
-      {2.312 * kBytesInAMegabyte, false, false, "2.3 MB"},
-      {2.312 * kBytesInAMegabyte, false, true,
+      {static_cast<int>(2.312 * kBytesInAMegabyte), false, false, "2.3 MB"},
+      {static_cast<int>(2.312 * kBytesInAMegabyte), false, true,
        "Frees up 2.3 MB. Some sites may load more slowly on your next visit."},
-      {2.312 * kBytesInAMegabyte, true, false, "Less than 2.3 MB"},
-      {2.312 * kBytesInAMegabyte, true, true,
+      {static_cast<int>(2.312 * kBytesInAMegabyte), true, false,
+       "Less than 2.3 MB"},
+      {static_cast<int>(2.312 * kBytesInAMegabyte), true, true,
        "Frees up less than 2.3 MB. Some sites may load more slowly on your "
        "next visit."},
-      {500.2 * kBytesInAMegabyte, false, false, "500 MB"},
-      {500.2 * kBytesInAMegabyte, true, false, "Less than 500 MB"},
+      {static_cast<int>(500.2 * kBytesInAMegabyte), false, false, "500 MB"},
+      {static_cast<int>(500.2 * kBytesInAMegabyte), true, false,
+       "Less than 500 MB"},
   };
 
   for (const TestCase& test_case : kTestCases) {
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index c93d955..169a6bb 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -776,6 +776,10 @@
     "../ash/arc/kiosk/arc_kiosk_bridge.h",
     "../ash/arc/metrics/arc_metrics_service_proxy.cc",
     "../ash/arc/metrics/arc_metrics_service_proxy.h",
+    "../ash/arc/nearby_share/arc_nearby_share_bridge.cc",
+    "../ash/arc/nearby_share/arc_nearby_share_bridge.h",
+    "../ash/arc/nearby_share/nearby_share_session_impl.cc",
+    "../ash/arc/nearby_share/nearby_share_session_impl.h",
     "../ash/arc/notification/arc_boot_error_notification.cc",
     "../ash/arc/notification/arc_boot_error_notification.h",
     "../ash/arc/notification/arc_management_transition_notification.cc",
@@ -2018,6 +2022,8 @@
     "../ash/system_logs/ui_hierarchy_log_source.h",
     "../ash/usb/cros_usb_detector.cc",
     "../ash/usb/cros_usb_detector.h",
+    "../ash/wallpaper/wallpaper_enumerator.cc",
+    "../ash/wallpaper/wallpaper_enumerator.h",
     "../ash/web_applications/camera_system_web_app_info.cc",
     "../ash/web_applications/camera_system_web_app_info.h",
     "../ash/web_applications/chrome_camera_app_ui_constants.h",
@@ -3371,6 +3377,8 @@
     "../ash/borealis/borealis_window_manager_test_helper.cc",
     "../ash/borealis/borealis_window_manager_test_helper.h",
     "../ash/borealis/testing/callback_factory.h",
+    "../ash/borealis/testing/dbus.cc",
+    "../ash/borealis/testing/dbus.h",
     "../ash/cert_provisioning/mock_cert_provisioning_scheduler.cc",
     "../ash/cert_provisioning/mock_cert_provisioning_scheduler.h",
     "../ash/certificate_provider/test_certificate_provider_extension.cc",
@@ -3505,6 +3513,7 @@
     "//chromeos/dbus:test_support",
     "//chromeos/dbus/cryptohome",
     "//chromeos/dbus/cryptohome:cryptohome_proto",
+    "//chromeos/dbus/dlcservice",
     "//chromeos/dbus/session_manager",
     "//chromeos/dbus/userdataauth:userdataauth",
     "//chromeos/dbus/userdataauth:userdataauth_proto",
@@ -3606,6 +3615,7 @@
     "../ash/arc/intent_helper/arc_settings_service_unittest.cc",
     "../ash/arc/intent_helper/open_with_menu_unittest.cc",
     "../ash/arc/kiosk/arc_kiosk_bridge_unittest.cc",
+    "../ash/arc/nearby_share/arc_nearby_share_bridge_unittest.cc",
     "../ash/arc/notification/arc_management_transition_notification_unittest.cc",
     "../ash/arc/notification/arc_provision_notification_service_unittest.cc",
     "../ash/arc/optin/arc_terms_of_service_default_negotiator_unittest.cc",
diff --git a/chrome/browser/chromeos/chrome_browser_main_chromeos.h b/chrome/browser/chromeos/chrome_browser_main_chromeos.h
index 4807601..3e2c7b4d 100644
--- a/chrome/browser/chromeos/chrome_browser_main_chromeos.h
+++ b/chrome/browser/chromeos/chrome_browser_main_chromeos.h
@@ -24,6 +24,8 @@
 // TODO(https://crbug.com/1164001): remove and use forward declaration.
 #include "chrome/browser/ash/power/ml/adaptive_screen_brightness_manager.h"
 // TODO(https://crbug.com/1164001): remove and use forward declaration.
+#include "chrome/browser/ash/power/smart_charging/smart_charging_manager.h"
+// TODO(https://crbug.com/1164001): remove and use forward declaration.
 #include "chrome/browser/ash/settings/shutdown_policy_forwarder.h"
 // TODO(https://crbug.com/1164001): remove and use forward declaration.
 #include "chrome/browser/ash/system/breakpad_consent_watcher.h"
@@ -99,10 +101,6 @@
 class KeyPermissionsManager;
 }
 
-namespace power {
-class SmartChargingManager;
-}  // namespace power
-
 namespace system {
 class DarkResumeController;
 }  // namespace system
diff --git a/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc b/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc
index 0ec8f84..f1d4057 100644
--- a/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc
@@ -354,7 +354,7 @@
 };
 
 IN_PROC_BROWSER_TEST_F(FileManagerPrivateApiTest, Mount) {
-  using chromeos::file_system_provider::IconSet;
+  using ash::file_system_provider::IconSet;
   profile()->GetPrefs()->SetBoolean(drive::prefs::kDisableDrive, true);
 
   // Add a provided file system, to test passing the |configurable| and
@@ -364,8 +364,8 @@
                    GURL("chrome://resources/testing-provider-id-16.jpg"));
   icon_set.SetIcon(IconSet::IconSize::SIZE_32x32,
                    GURL("chrome://resources/testing-provider-id-32.jpg"));
-  chromeos::file_system_provider::ProvidedFileSystemInfo info(
-      "testing-provider-id", chromeos::file_system_provider::MountOptions(),
+  ash::file_system_provider::ProvidedFileSystemInfo info(
+      "testing-provider-id", ash::file_system_provider::MountOptions(),
       base::FilePath(), true /* configurable */, false /* watchable */,
       extensions::SOURCE_NETWORK, icon_set);
 
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_drive.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_drive.cc
index 4a2a800..19c43aa 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_drive.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_drive.cc
@@ -69,9 +69,9 @@
 
 using content::BrowserThread;
 
-using chromeos::file_system_provider::EntryMetadata;
-using chromeos::file_system_provider::ProvidedFileSystemInterface;
-using chromeos::file_system_provider::util::FileSystemURLParser;
+using ash::file_system_provider::EntryMetadata;
+using ash::file_system_provider::ProvidedFileSystemInterface;
+using ash::file_system_provider::util::FileSystemURLParser;
 using extensions::api::file_manager_private::EntryProperties;
 using extensions::api::file_manager_private::EntryPropertyName;
 using file_manager::util::EntryDefinition;
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
index b69fa3df..3ba3da7e 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
@@ -137,7 +137,7 @@
 bool ConvertURLsToProvidedInfo(
     const scoped_refptr<storage::FileSystemContext>& file_system_context,
     const std::vector<std::string>& urls,
-    chromeos::file_system_provider::ProvidedFileSystemInterface** file_system,
+    ash::file_system_provider::ProvidedFileSystemInterface** file_system,
     std::vector<base::FilePath>* paths,
     std::string* error) {
   DCHECK(file_system);
@@ -153,7 +153,7 @@
     const storage::FileSystemURL file_system_url(
         file_system_context->CrackURL(GURL(url)));
 
-    chromeos::file_system_provider::util::FileSystemURLParser parser(
+    ash::file_system_provider::util::FileSystemURLParser parser(
         file_system_url);
     if (!parser.Parse()) {
       *error = "Related provided file system not found.";
@@ -608,11 +608,11 @@
 
 ExtensionFunction::ResponseAction
 FileManagerPrivateGetProvidersFunction::Run() {
-  using chromeos::file_system_provider::Capabilities;
-  using chromeos::file_system_provider::IconSet;
-  using chromeos::file_system_provider::ProviderId;
-  using chromeos::file_system_provider::ProviderInterface;
-  using chromeos::file_system_provider::Service;
+  using ash::file_system_provider::Capabilities;
+  using ash::file_system_provider::IconSet;
+  using ash::file_system_provider::ProviderId;
+  using ash::file_system_provider::ProviderInterface;
+  using ash::file_system_provider::Service;
   const Service* const service = Service::Get(browser_context());
 
   using api::file_manager_private::Provider;
@@ -658,9 +658,9 @@
   const std::unique_ptr<Params> params(Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params);
 
-  using chromeos::file_system_provider::Service;
-  using chromeos::file_system_provider::ProvidingExtensionInfo;
-  using chromeos::file_system_provider::ProviderId;
+  using ash::file_system_provider::ProviderId;
+  using ash::file_system_provider::ProvidingExtensionInfo;
+  using ash::file_system_provider::Service;
   Service* const service = Service::Get(browser_context());
 
   if (!service->RequestMount(ProviderId::FromString(params->provider_id)))
@@ -691,11 +691,11 @@
 
   switch (volume->type()) {
     case file_manager::VOLUME_TYPE_PROVIDED: {
-      using chromeos::file_system_provider::Service;
+      using ash::file_system_provider::Service;
       Service* const service = Service::Get(browser_context());
       DCHECK(service);
 
-      using chromeos::file_system_provider::ProvidedFileSystemInterface;
+      using ash::file_system_provider::ProvidedFileSystemInterface;
       ProvidedFileSystemInterface* const file_system =
           service->GetProvidedFileSystem(volume->provider_id(),
                                          volume->file_system_id());
@@ -1016,8 +1016,7 @@
           Profile::FromBrowserContext(browser_context()), render_frame_host());
 
   std::vector<base::FilePath> paths;
-  chromeos::file_system_provider::ProvidedFileSystemInterface* file_system =
-      nullptr;
+  ash::file_system_provider::ProvidedFileSystemInterface* file_system = nullptr;
   std::string error;
 
   if (!ConvertURLsToProvidedInfo(file_system_context, params->urls,
@@ -1035,7 +1034,7 @@
 }
 
 void FileManagerPrivateInternalGetCustomActionsFunction::OnCompleted(
-    const chromeos::file_system_provider::Actions& actions,
+    const ash::file_system_provider::Actions& actions,
     base::File::Error result) {
   if (result != base::File::FILE_OK) {
     Respond(Error("Failed to fetch actions."));
@@ -1071,8 +1070,7 @@
           Profile::FromBrowserContext(browser_context()), render_frame_host());
 
   std::vector<base::FilePath> paths;
-  chromeos::file_system_provider::ProvidedFileSystemInterface* file_system =
-      nullptr;
+  ash::file_system_provider::ProvidedFileSystemInterface* file_system = nullptr;
   std::string error;
 
   if (!ConvertURLsToProvidedInfo(file_system_context, params->urls,
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.h b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.h
index 23835549..ff9f369e 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.h
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.h
@@ -452,7 +452,7 @@
 
  private:
   ResponseAction Run() override;
-  void OnCompleted(const chromeos::file_system_provider::Actions& actions,
+  void OnCompleted(const ash::file_system_provider::Actions& actions,
                    base::File::Error result);
 };
 
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_mount.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_mount.cc
index d0abab9..eac4880b 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_mount.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_mount.cc
@@ -127,8 +127,8 @@
       break;
     }
     case file_manager::VOLUME_TYPE_PROVIDED: {
-      chromeos::file_system_provider::Service* service =
-          chromeos::file_system_provider::Service::Get(browser_context());
+      auto* service =
+          ash::file_system_provider::Service::Get(browser_context());
       DCHECK(service);
       // TODO(mtomasz): Pass a more detailed error than just a bool.
       if (!service->RequestUnmount(volume->provider_id(),
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_util.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_util.cc
index 0f2870c2..fa12a4e 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_util.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_util.cc
@@ -380,9 +380,9 @@
 }
 
 void FillIconSet(file_manager_private::IconSet* output,
-                 const chromeos::file_system_provider::IconSet& input) {
+                 const ash::file_system_provider::IconSet& input) {
   DCHECK(output);
-  using chromeos::file_system_provider::IconSet;
+  using ash::file_system_provider::IconSet;
   if (input.HasIcon(IconSet::IconSize::SIZE_16x16)) {
     output->icon16x16_url = std::make_unique<std::string>(
         input.GetIcon(IconSet::IconSize::SIZE_16x16).spec());
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_util.h b/chrome/browser/chromeos/extensions/file_manager/private_api_util.h
index 98669ca..b24a4d9 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_util.h
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_util.h
@@ -98,7 +98,7 @@
 
 // Fills out IDL IconSet struct with the provided icon set.
 void FillIconSet(extensions::api::file_manager_private::IconSet* output,
-                 const chromeos::file_system_provider::IconSet& input);
+                 const ash::file_system_provider::IconSet& input);
 
 // Converts the |volume| to VolumeMetadata to communicate with JavaScript via
 // private API.
diff --git a/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.cc b/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.cc
index ef55585..3b0117d 100644
--- a/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.cc
+++ b/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.cc
@@ -23,15 +23,15 @@
 #include "chrome/common/extensions/api/file_system_provider_internal.h"
 #include "storage/browser/file_system/watcher_manager.h"
 
-using chromeos::file_system_provider::MountOptions;
-using chromeos::file_system_provider::OpenedFiles;
-using chromeos::file_system_provider::ProvidedFileSystemInfo;
-using chromeos::file_system_provider::ProvidedFileSystemInterface;
-using chromeos::file_system_provider::ProvidedFileSystemObserver;
-using chromeos::file_system_provider::ProviderId;
-using chromeos::file_system_provider::RequestValue;
-using chromeos::file_system_provider::Service;
-using chromeos::file_system_provider::Watchers;
+using ash::file_system_provider::MountOptions;
+using ash::file_system_provider::OpenedFiles;
+using ash::file_system_provider::ProvidedFileSystemInfo;
+using ash::file_system_provider::ProvidedFileSystemInterface;
+using ash::file_system_provider::ProvidedFileSystemObserver;
+using ash::file_system_provider::ProviderId;
+using ash::file_system_provider::RequestValue;
+using ash::file_system_provider::Service;
+using ash::file_system_provider::Watchers;
 
 namespace extensions {
 namespace {
@@ -102,11 +102,11 @@
     opened_file_item.open_request_id = opened_file.first;
     opened_file_item.file_path = opened_file.second.file_path.value();
     switch (opened_file.second.mode) {
-      case chromeos::file_system_provider::OPEN_FILE_MODE_READ:
+      case ash::file_system_provider::OPEN_FILE_MODE_READ:
         opened_file_item.mode =
             extensions::api::file_system_provider::OPEN_FILE_MODE_READ;
         break;
-      case chromeos::file_system_provider::OPEN_FILE_MODE_WRITE:
+      case ash::file_system_provider::OPEN_FILE_MODE_WRITE:
         opened_file_item.mode =
             extensions::api::file_system_provider::OPEN_FILE_MODE_WRITE;
         break;
@@ -198,9 +198,9 @@
   for (const auto& file_system_info : file_systems) {
     FileSystemInfo item;
 
-    chromeos::file_system_provider::ProvidedFileSystemInterface* const
-        file_system = service->GetProvidedFileSystem(
-            file_system_info.provider_id(), file_system_info.file_system_id());
+    ProvidedFileSystemInterface* const file_system =
+        service->GetProvidedFileSystem(file_system_info.provider_id(),
+                                       file_system_info.file_system_id());
 
     DCHECK(file_system);
 
@@ -226,8 +226,8 @@
       Service::Get(Profile::FromBrowserContext(browser_context()));
   DCHECK(service);
 
-  chromeos::file_system_provider::ProvidedFileSystemInterface* const
-      file_system = service->GetProvidedFileSystem(
+  ProvidedFileSystemInterface* const file_system =
+      service->GetProvidedFileSystem(
           ProviderId::CreateFromExtensionId(extension_id()),
           params->file_system_id);
 
diff --git a/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_apitest.cc b/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_apitest.cc
index 94133aff..308a72dd 100644
--- a/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_apitest.cc
+++ b/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_apitest.cc
@@ -28,15 +28,15 @@
 namespace extensions {
 namespace {
 
-using chromeos::file_system_provider::MountContext;
-using chromeos::file_system_provider::NotificationManagerInterface;
-using chromeos::file_system_provider::Observer;
-using chromeos::file_system_provider::ProvidedFileSystemInterface;
-using chromeos::file_system_provider::ProvidedFileSystemInfo;
-using chromeos::file_system_provider::RequestManager;
-using chromeos::file_system_provider::RequestType;
-using chromeos::file_system_provider::RequestValue;
-using chromeos::file_system_provider::Service;
+using ash::file_system_provider::MountContext;
+using ash::file_system_provider::NotificationManagerInterface;
+using ash::file_system_provider::Observer;
+using ash::file_system_provider::ProvidedFileSystemInfo;
+using ash::file_system_provider::ProvidedFileSystemInterface;
+using ash::file_system_provider::RequestManager;
+using ash::file_system_provider::RequestType;
+using ash::file_system_provider::RequestValue;
+using ash::file_system_provider::Service;
 
 // Clicks the default button on the notification as soon as request timeouts
 // and a unresponsiveness notification is shown.
diff --git a/chrome/browser/chromeos/extensions/file_system_provider/provider_function.cc b/chrome/browser/chromeos/extensions/file_system_provider/provider_function.cc
index 044565a..0e5de47 100644
--- a/chrome/browser/chromeos/extensions/file_system_provider/provider_function.cc
+++ b/chrome/browser/chromeos/extensions/file_system_provider/provider_function.cc
@@ -11,11 +11,11 @@
 #include "chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.h"
 #include "chrome/common/extensions/api/file_system_provider_internal.h"
 
-using chromeos::file_system_provider::ProvidedFileSystemInterface;
-using chromeos::file_system_provider::ProviderId;
-using chromeos::file_system_provider::RequestManager;
-using chromeos::file_system_provider::RequestValue;
-using chromeos::file_system_provider::Service;
+using ash::file_system_provider::ProvidedFileSystemInterface;
+using ash::file_system_provider::ProviderId;
+using ash::file_system_provider::RequestManager;
+using ash::file_system_provider::RequestValue;
+using ash::file_system_provider::Service;
 
 namespace {
 
@@ -127,7 +127,7 @@
 
 ExtensionFunction::ResponseAction
 FileSystemProviderInternalFunction::RejectRequest(
-    std::unique_ptr<chromeos::file_system_provider::RequestValue> value,
+    std::unique_ptr<RequestValue> value,
     base::File::Error error) {
   const base::File::Error result =
       request_manager_->RejectRequest(request_id_, std::move(value), error);
diff --git a/chrome/browser/chromeos/extensions/file_system_provider/provider_function.h b/chrome/browser/chromeos/extensions/file_system_provider/provider_function.h
index 75dc330..9f5aa18f 100644
--- a/chrome/browser/chromeos/extensions/file_system_provider/provider_function.h
+++ b/chrome/browser/chromeos/extensions/file_system_provider/provider_function.h
@@ -12,14 +12,12 @@
 #include "chrome/common/extensions/api/file_system_provider.h"
 #include "extensions/browser/extension_function.h"
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
-
 class RequestManager;
 class RequestValue;
-
 }  // namespace file_system_provider
-}  // namespace chromeos
+}  // namespace ash
 
 namespace extensions {
 
@@ -55,7 +53,7 @@
 
   // Rejects the request and returns a response for this API function.
   ResponseAction RejectRequest(
-      std::unique_ptr<chromeos::file_system_provider::RequestValue> value,
+      std::unique_ptr<ash::file_system_provider::RequestValue> value,
       base::File::Error error);
 
   // Fulfills the request with parsed arguments of this API function
@@ -63,7 +61,7 @@
   // If |has_more| is set to true, then the function will be called again for
   // this request.
   ResponseAction FulfillRequest(
-      std::unique_ptr<chromeos::file_system_provider::RequestValue> value,
+      std::unique_ptr<ash::file_system_provider::RequestValue> value,
       bool has_more);
 
  private:
@@ -71,7 +69,7 @@
   bool PreRunValidation(std::string* error) override;
 
   int request_id_;
-  chromeos::file_system_provider::RequestManager* request_manager_;
+  ash::file_system_provider::RequestManager* request_manager_;
 };
 
 }  // namespace extensions
diff --git a/chrome/browser/chromeos/extensions/printing/printing_apitest.cc b/chrome/browser/chromeos/extensions/printing/printing_apitest.cc
index e7b9bed..0af05db3 100644
--- a/chrome/browser/chromeos/extensions/printing/printing_apitest.cc
+++ b/chrome/browser/chromeos/extensions/printing/printing_apitest.cc
@@ -132,15 +132,14 @@
   GetPrintersManager()->AddPrinter(printer, chromeos::PrinterClass::kSaved);
 
   SetCustomArg(kName);
-  ASSERT_TRUE(
-      RunExtensionTest({.name = "printing", .page_url = "get_printers.html"}));
+  ASSERT_TRUE(RunExtensionTest("printing", {.page_url = "get_printers.html"}));
 }
 
 IN_PROC_BROWSER_TEST_F(PrintingApiTest, GetPrinterInfo) {
   AddAvailablePrinter(
       kId, std::make_unique<printing::PrinterSemanticCapsAndDefaults>());
-  ASSERT_TRUE(RunExtensionTest(
-      {.name = "printing", .page_url = "get_printer_info.html"}));
+  ASSERT_TRUE(
+      RunExtensionTest("printing", {.page_url = "get_printer_info.html"}));
 }
 
 // Verifies that:
@@ -161,8 +160,7 @@
   base::AutoReset<bool> skip_confirmation_dialog_reset(
       PrintJobSubmitter::SkipConfirmationDialogForTesting());
 
-  ASSERT_TRUE(
-      RunExtensionTest({.name = "printing", .page_url = "submit_job.html"}));
+  ASSERT_TRUE(RunExtensionTest("printing", {.page_url = "submit_job.html"}));
 }
 
 // Verifies that:
@@ -179,8 +177,7 @@
   base::AutoReset<bool> skip_confirmation_dialog_reset(
       PrintJobSubmitter::SkipConfirmationDialogForTesting());
 
-  ASSERT_TRUE(
-      RunExtensionTest({.name = "printing", .page_url = "cancel_job.html"}));
+  ASSERT_TRUE(RunExtensionTest("printing", {.page_url = "cancel_job.html"}));
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/chromeos/extensions/printing_metrics/printing_metrics_apitest.cc b/chrome/browser/chromeos/extensions/printing_metrics/printing_metrics_apitest.cc
index b5009c6..3b28803b 100644
--- a/chrome/browser/chromeos/extensions/printing_metrics/printing_metrics_apitest.cc
+++ b/chrome/browser/chromeos/extensions/printing_metrics/printing_metrics_apitest.cc
@@ -122,9 +122,9 @@
 // warning if they request the printingMetrics permission in the manifest and
 // that such extensions don't see the chrome.printingMetrics namespace.
 IN_PROC_BROWSER_TEST_F(PrintingMetricsApiTest, IsRestrictedToPolicyExtension) {
-  ASSERT_TRUE(RunExtensionTest(
-      {.name = "printing_metrics", .page_url = "api_not_available.html"},
-      {.ignore_manifest_warnings = true}));
+  ASSERT_TRUE(RunExtensionTest("printing_metrics",
+                               {.page_url = "api_not_available.html"},
+                               {.ignore_manifest_warnings = true}));
 
   base::FilePath extension_path =
       test_data_dir_.AppendASCII("printing_metrics");
diff --git a/chrome/browser/chromeos/extensions/wallpaper_private_api.cc b/chrome/browser/chromeos/extensions/wallpaper_private_api.cc
index 327fc49a..e7e63fed 100644
--- a/chrome/browser/chromeos/extensions/wallpaper_private_api.cc
+++ b/chrome/browser/chromeos/extensions/wallpaper_private_api.cc
@@ -27,8 +27,8 @@
 #include "chrome/browser/ash/backdrop_wallpaper_handlers/backdrop_wallpaper.pb.h"
 #include "chrome/browser/ash/backdrop_wallpaper_handlers/backdrop_wallpaper_handlers.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
+#include "chrome/browser/ash/wallpaper/wallpaper_enumerator.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/chromeos/file_manager/path_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/sync/sync_service_factory.h"
 #include "chrome/browser/ui/ash/wallpaper_controller_client_impl.h"
@@ -82,10 +82,6 @@
 // ships.
 constexpr char kSyncThemes[] = "syncThemes";
 
-constexpr char kPngFilePattern[] = "*.[pP][nN][gG]";
-constexpr char kJpgFilePattern[] = "*.[jJ][pP][gG]";
-constexpr char kJpegFilePattern[] = "*.[jJ][pP][eE][gG]";
-
 bool IsOEMDefaultWallpaper() {
   return base::CommandLine::ForCurrentProcess()->HasSwitch(
       chromeos::switches::kDefaultWallpaperIsOem);
@@ -152,38 +148,6 @@
   }
 }
 
-// Helper function to get the list of image paths under |path| that match
-// |pattern|.
-void EnumerateImages(const base::FilePath& path,
-                     const std::string& pattern,
-                     std::vector<std::string>* result_out) {
-  base::FileEnumerator image_enum(
-      path, true /* recursive */, base::FileEnumerator::FILES,
-      FILE_PATH_LITERAL(pattern),
-      base::FileEnumerator::FolderSearchPolicy::ALL);
-
-  for (base::FilePath image_path = image_enum.Next(); !image_path.empty();
-       image_path = image_enum.Next()) {
-    result_out->emplace_back(image_path.value());
-  }
-}
-
-// Recursively retrieves the paths of the image files under |path|.
-std::vector<std::string> GetImagePaths(const base::FilePath& path) {
-  WallpaperFunctionBase::AssertCalledOnWallpaperSequence(
-      WallpaperFunctionBase::GetNonBlockingTaskRunner());
-
-  // TODO(crbug.com/810575): Add metrics on the number of files retrieved, and
-  // support getting paths incrementally in case the user has a large number of
-  // local images.
-  std::vector<std::string> image_paths;
-  EnumerateImages(path, kPngFilePattern, &image_paths);
-  EnumerateImages(path, kJpgFilePattern, &image_paths);
-  EnumerateImages(path, kJpegFilePattern, &image_paths);
-
-  return image_paths;
-}
-
 // Helper function to parse the data from a |backdrop::Image| object and save it
 // to |image_info_out|.
 void ParseImageInfo(
@@ -773,11 +737,8 @@
 
 ExtensionFunction::ResponseAction
 WallpaperPrivateGetLocalImagePathsFunction::Run() {
-  base::FilePath path = file_manager::util::GetMyFilesFolderForProfile(
-      Profile::FromBrowserContext(browser_context()));
-  base::PostTaskAndReplyWithResult(
-      WallpaperFunctionBase::GetNonBlockingTaskRunner(), FROM_HERE,
-      base::BindOnce(&GetImagePaths, path),
+  ash::EnumerateLocalWallpaperFiles(
+      Profile::FromBrowserContext(browser_context()),
       base::BindOnce(
           &WallpaperPrivateGetLocalImagePathsFunction::OnGetImagePathsComplete,
           this));
@@ -785,8 +746,12 @@
 }
 
 void WallpaperPrivateGetLocalImagePathsFunction::OnGetImagePathsComplete(
-    const std::vector<std::string>& image_paths) {
-  Respond(ArgumentList(get_local_image_paths::Results::Create(image_paths)));
+    const std::vector<base::FilePath>& image_paths) {
+  std::vector<std::string> result;
+  std::transform(image_paths.begin(), image_paths.end(),
+                 std::back_inserter(result),
+                 [](const base::FilePath& path) { return path.value(); });
+  Respond(ArgumentList(get_local_image_paths::Results::Create(result)));
 }
 
 WallpaperPrivateGetLocalImageDataFunction::
diff --git a/chrome/browser/chromeos/extensions/wallpaper_private_api.h b/chrome/browser/chromeos/extensions/wallpaper_private_api.h
index ed7ff17c..25c1d001 100644
--- a/chrome/browser/chromeos/extensions/wallpaper_private_api.h
+++ b/chrome/browser/chromeos/extensions/wallpaper_private_api.h
@@ -333,7 +333,7 @@
 
  private:
   // Responds with the list of collected image paths.
-  void OnGetImagePathsComplete(const std::vector<std::string>& image_paths);
+  void OnGetImagePathsComplete(const std::vector<base::FilePath>& image_paths);
 
   DISALLOW_COPY_AND_ASSIGN(WallpaperPrivateGetLocalImagePathsFunction);
 };
diff --git a/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc b/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
index db7a2ddd..38f0f46d 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
@@ -269,6 +269,9 @@
     FilesAppBrowserTest,
     ::testing::Values(
         TestCase("fileDisplayDownloads"),
+#if !defined(OFFICIAL_BUILD)
+        TestCase("fileDisplayDownloads").FilesSwa(),
+#endif
         TestCase("fileDisplayDownloads").InGuestMode(),
         TestCase("fileDisplayDownloads").TabletMode(),
         TestCase("fileDisplayLaunchOnLocalFolder").DontObserveFileTasks(),
diff --git a/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc b/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc
index fc8deaf1..0b6c1f7 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc
@@ -12,6 +12,9 @@
 
 #include "ash/constants/ash_features.h"
 #include "ash/constants/ash_switches.h"
+#if !defined(OFFICIAL_BUILD)
+#include "ash/content/file_manager/url_constants.h"
+#endif
 #include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/test/shell_test_api.h"
 #include "base/bind.h"
@@ -63,8 +66,10 @@
 #include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ui/views/extensions/extension_dialog.h"
 #include "chrome/browser/ui/views/select_file_dialog_extension.h"
+#include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h"
 #include "chrome/browser/web_applications/components/web_app_helpers.h"
 #include "chrome/browser/web_applications/system_web_apps/system_web_app_manager.h"
+#include "chrome/browser/web_applications/system_web_apps/system_web_app_types.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/chrome_switches.h"
@@ -108,6 +113,7 @@
 #include "google_apis/drive/drive_api_parser.h"
 #include "google_apis/drive/test_util.h"
 #include "media/base/media_switches.h"
+#include "net/base/escape.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "storage/browser/file_system/external_mount_points.h"
 #include "storage/browser/file_system/file_system_context.h"
@@ -150,6 +156,25 @@
 namespace file_manager {
 namespace {
 
+// Specialization of the navigation observer that stores web content every time
+// the OnDidFinishNavigation is called.
+class WebContentCapturingObserver : public content::TestNavigationObserver {
+ public:
+  explicit WebContentCapturingObserver(const GURL& url)
+      : content::TestNavigationObserver(url) {}
+
+  content::WebContents* web_contents() { return web_contents_; }
+
+  void OnDidFinishNavigation(
+      content::NavigationHandle* navigation_handle) override {
+    web_contents_ = navigation_handle->GetWebContents();
+    content::TestNavigationObserver::OnDidFinishNavigation(navigation_handle);
+  }
+
+ private:
+  content::WebContents* web_contents_;
+};
+
 // During test, the test extensions can send a list of entries (directories
 // or files) to add to a target volume using an AddEntriesMessage command.
 //
@@ -586,6 +611,18 @@
   DISALLOW_COPY_AND_ASSIGN(TestVolume);
 };
 
+const std::string GetSourceFileContent(const std::string& file_name) {
+  base::ScopedAllowBlockingForTesting allow_blocking;
+  base::FilePath source_dir;
+  CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &source_dir));
+  base::FilePath path =
+      source_dir.Append(base::FilePath::FromUTF8Unsafe(file_name));
+  std::string contents;
+  CHECK(base::ReadFileToString(path, &contents))
+      << "failed reading test data file " << file_name;
+  return contents;
+}
+
 base::Lock& GetLockForBlockingDefaultFileTaskRunner() {
   static base::NoDestructor<base::Lock> lock;
   return *lock;
@@ -1501,7 +1538,7 @@
         &SmbfsTestVolume::CreateMounter, base::Unretained(this)));
 
     bool success = false;
-    chromeos::file_system_provider::MountOptions mount_options;
+    ash::file_system_provider::MountOptions mount_options;
     mount_options.display_name = "SMB Share";
     base::RunLoop run_loop;
     smb_service->Mount(
@@ -1887,8 +1924,6 @@
 
   // Enable System Web Apps if needed.
   if (options.media_swa || options.files_swa) {
-    files_app_swa_id_ =
-        web_app::GenerateAppIdFromURL(GURL("chrome://file-manager"));
     auto& system_web_app_manager =
         web_app::WebAppProvider::Get(profile())->system_web_app_manager();
     system_web_app_manager.InstallSystemAppsForTesting();
@@ -2008,6 +2043,12 @@
 
   base::ScopedAllowBlockingForTesting allow_blocking;
 
+  if (name == "isFilesAppSwa") {
+    // Return whether or not the test is run in Files SWA mode.
+    *output = options.files_swa ? "true" : "false";
+    return;
+  }
+
   if (name == "isInGuestMode") {
     // Obtain if the test runs in guest or incognito mode.
     LOG(INFO) << GetTestCaseName() << " is in " << options.guest_mode
@@ -2031,26 +2072,76 @@
     return;
   }
 
+#if !defined(OFFICIAL_BUILD)
   if (name == "launchFileManagerSwa") {
-    apps::AppLaunchParams params(
-        files_app_swa_id_, apps::mojom::LaunchContainer::kLaunchContainerWindow,
-        WindowOpenDisposition::NEW_FOREGROUND_TAB,
-        apps::mojom::AppLaunchSource::kSourceTest);
+    std::string launchDir;
+    std::string search;
+    if (value.GetString("launchDir", &launchDir)) {
+      base::DictionaryValue arg_value;
+      arg_value.SetString("currentDirectoryURL", launchDir);
+      std::string json_args;
+      base::JSONWriter::Write(arg_value, &json_args);
+      search = base::StrCat(
+          {"?", net::EscapeUrlEncodedData(json_args, /*use_plus=*/false)});
+    }
 
-    content::WebContents* web_contents =
-        apps::AppServiceProxyFactory::GetForProfile(profile())
-            ->BrowserAppLauncher()
-            ->LaunchAppWithParams(std::move(params));
-    CHECK(web_contents);
+    std::string baseURL = ash::file_manager::kChromeUIFileManagerURL;
+    GURL fileAppURL(base::StrCat({baseURL, search}));
+    web_app::SystemAppLaunchParams params;
+    params.url = fileAppURL;
+    params.launch_source = apps::mojom::LaunchSource::kFromTest;
 
-    content::WaitForLoadStop(web_contents);
-    LOG(INFO) << name << " url " << web_contents->GetLastCommittedURL();
-    files_app_web_contents_ = web_contents;
+    WebContentCapturingObserver observer(fileAppURL);
+    observer.StartWatchingNewWebContents();
+    web_app::LaunchSystemWebAppAsync(
+        profile(), web_app::SystemAppType::FILE_MANAGER, params);
+    observer.Wait();
+    ASSERT_TRUE(observer.last_navigation_succeeded());
+    CHECK(observer.web_contents());
+
+    // Load runtime and static test_utils.js. In Files.app test_utils.js is
+    // always loaded, while runtime_loaded_test_util.js is loaded on the first
+    // chrome.runtime.sendMessage is sent by the test extension. However, since
+    // we use callSwaTestMessageListener, rather than c.r.sendMessage to
+    // communicate with Files SWA, we need to explicitly load those files.
+    CHECK(content::ExecuteScript(
+        observer.web_contents(),
+        GetSourceFileContent(
+            "ui/file_manager/file_manager/background/js/test_util_swa.js")));
+    CHECK(content::ExecuteScript(
+        observer.web_contents(),
+        GetSourceFileContent("ui/file_manager/file_manager/background/js/"
+                             "runtime_loaded_test_util.js")));
+    CHECK(content::ExecuteScript(
+        observer.web_contents(),
+        GetSourceFileContent(
+            "ui/file_manager/file_manager/background/js/test_util.js")));
+    files_app_swa_id_ = base::StrCat({baseURL, launchDir});
+    files_app_web_contents_ = observer.web_contents();
 
     *output = files_app_swa_id_;
     return;
   }
 
+  if (name == "callSwaTestMessageListener") {
+    // Handles equivallent of remoteCall.callRemoteTestUtil for Files.app. By
+    // default Files SWA does not allow extenrnal callers to connect to it and
+    // send it messages via chrome.runtime.sendMessage. Rather than allowing
+    // this, which would potentially create a security vulnerability, we
+    // short-circuit sending messages by directly invoking dedicated function in
+    // Files SWA.
+    std::string data;
+    ASSERT_TRUE(value.GetString("data", &data));
+    // FIXME: determine WebContents based on |appId| from |value|. Currently we
+    // only launch one window so this works. Once muliple winows are enabled for
+    // SWAs, this code needs to map |appId| to specific SWA's WebContents.
+    CHECK(ExecuteScriptAndExtractString(
+        files_app_web_contents_,
+        base::StrCat({"test.swaTestMessageListener(", data, ")"}), output));
+    return;
+  }
+#endif
+
   if (name == "isDevtoolsCoverageActive") {
     bool devtools_coverage_active = !devtools_code_coverage_dir_.empty();
     LOG(INFO) << "isDevtoolsCoverageActive: " << devtools_coverage_active;
diff --git a/chrome/browser/chromeos/file_manager/fileapi_util_unittest.cc b/chrome/browser/chromeos/file_manager/fileapi_util_unittest.cc
index 9a2319e..8908763 100644
--- a/chrome/browser/chromeos/file_manager/fileapi_util_unittest.cc
+++ b/chrome/browser/chromeos/file_manager/fileapi_util_unittest.cc
@@ -186,14 +186,13 @@
   Profile* const profile = GetProfile();
   const std::string extension_id = "abc";
   auto fake_provider =
-      chromeos::file_system_provider::FakeExtensionProvider::Create(
-          extension_id);
+      ash::file_system_provider::FakeExtensionProvider::Create(extension_id);
   const auto kProviderId = fake_provider->GetId();
-  auto* service = chromeos::file_system_provider::Service::Get(profile);
+  auto* service = ash::file_system_provider::Service::Get(profile);
   service->RegisterProvider(std::move(fake_provider));
-  service->MountFileSystem(kProviderId,
-                           chromeos::file_system_provider::MountOptions(
-                               file_system_id_, "Test FileSystem"));
+  service->MountFileSystem(
+      kProviderId, ash::file_system_provider::MountOptions(file_system_id_,
+                                                           "Test FileSystem"));
 
   // Obtain the file system context.
   content::StoragePartition* const partition =
diff --git a/chrome/browser/chromeos/file_manager/filesystem_api_util.cc b/chrome/browser/chromeos/file_manager/filesystem_api_util.cc
index 60bdd89..b18f2a1 100644
--- a/chrome/browser/chromeos/file_manager/filesystem_api_util.cc
+++ b/chrome/browser/chromeos/file_manager/filesystem_api_util.cc
@@ -52,7 +52,7 @@
 // the mime type from the passed metadata from a providing extension.
 void GetMimeTypeAfterGetMetadataForProvidedFileSystem(
     base::OnceCallback<void(const absl::optional<std::string>&)> callback,
-    std::unique_ptr<chromeos::file_system_provider::EntryMetadata> metadata,
+    std::unique_ptr<ash::file_system_provider::EntryMetadata> metadata,
     base::File::Error result) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
@@ -194,9 +194,8 @@
     return;
   }
 
-  if (chromeos::file_system_provider::util::IsFileSystemProviderLocalPath(
-          path)) {
-    chromeos::file_system_provider::util::LocalPathParser parser(profile, path);
+  if (ash::file_system_provider::util::IsFileSystemProviderLocalPath(path)) {
+    ash::file_system_provider::util::LocalPathParser parser(profile, path);
     if (!parser.Parse()) {
       content::GetUIThreadTaskRunner({})->PostTask(
           FROM_HERE, base::BindOnce(std::move(callback), absl::nullopt));
@@ -205,7 +204,7 @@
 
     parser.file_system()->GetMetadata(
         parser.file_path(),
-        chromeos::file_system_provider::ProvidedFileSystemInterface::
+        ash::file_system_provider::ProvidedFileSystemInterface::
             METADATA_FIELD_MIME_TYPE,
         base::BindOnce(&GetMimeTypeAfterGetMetadataForProvidedFileSystem,
                        std::move(callback)));
diff --git a/chrome/browser/chromeos/file_manager/volume_manager.cc b/chrome/browser/chromeos/file_manager/volume_manager.cc
index 2dd3925..0da40a9f 100644
--- a/chrome/browser/chromeos/file_manager/volume_manager.cc
+++ b/chrome/browser/chromeos/file_manager/volume_manager.cc
@@ -293,8 +293,7 @@
 
 // static
 std::unique_ptr<Volume> Volume::CreateForProvidedFileSystem(
-    const chromeos::file_system_provider::ProvidedFileSystemInfo&
-        file_system_info,
+    const ash::file_system_provider::ProvidedFileSystemInfo& file_system_info,
     MountContext mount_context) {
   std::unique_ptr<Volume> volume(new Volume());
   volume->file_system_id_ = file_system_info.file_system_id();
@@ -413,10 +412,9 @@
   volume->watchable_ = false;
   volume->volume_id_ = arc::GetDocumentsProviderVolumeId(authority, root_id);
   if (!icon_url.is_empty()) {
-    chromeos::file_system_provider::IconSet icon_set;
-    icon_set.SetIcon(
-        chromeos::file_system_provider::IconSet::IconSize::SIZE_32x32,
-        icon_url);
+    ash::file_system_provider::IconSet icon_set;
+    icon_set.SetIcon(ash::file_system_provider::IconSet::IconSize::SIZE_32x32,
+                     icon_url);
     volume->icon_set_ = icon_set;
   }
   return volume;
@@ -480,7 +478,7 @@
     drive::DriveIntegrationService* drive_integration_service,
     chromeos::PowerManagerClient* power_manager_client,
     chromeos::disks::DiskMountManager* disk_mount_manager,
-    chromeos::file_system_provider::Service* file_system_provider_service,
+    ash::file_system_provider::Service* file_system_provider_service,
     GetMtpStorageInfoCallback get_mtp_storage_info_callback)
     : profile_(profile),
       drive_integration_service_(drive_integration_service),
@@ -544,7 +542,7 @@
   // Subscribe to FileSystemProviderService and register currently mounted
   // volumes for the profile.
   if (file_system_provider_service_) {
-    using chromeos::file_system_provider::ProvidedFileSystemInfo;
+    using ash::file_system_provider::ProvidedFileSystemInfo;
     file_system_provider_service_->AddObserver(this);
 
     std::vector<ProvidedFileSystemInfo> file_system_info_list =
@@ -1031,16 +1029,15 @@
 }
 
 void VolumeManager::OnProvidedFileSystemMount(
-    const chromeos::file_system_provider::ProvidedFileSystemInfo&
-        file_system_info,
-    chromeos::file_system_provider::MountContext context,
+    const ash::file_system_provider::ProvidedFileSystemInfo& file_system_info,
+    ash::file_system_provider::MountContext context,
     base::File::Error error) {
   MountContext volume_context = MOUNT_CONTEXT_UNKNOWN;
   switch (context) {
-    case chromeos::file_system_provider::MOUNT_CONTEXT_USER:
+    case ash::file_system_provider::MOUNT_CONTEXT_USER:
       volume_context = MOUNT_CONTEXT_USER;
       break;
-    case chromeos::file_system_provider::MOUNT_CONTEXT_RESTORE:
+    case ash::file_system_provider::MOUNT_CONTEXT_RESTORE:
       volume_context = MOUNT_CONTEXT_AUTO;
       break;
   }
@@ -1067,8 +1064,7 @@
 }
 
 void VolumeManager::OnProvidedFileSystemUnmount(
-    const chromeos::file_system_provider::ProvidedFileSystemInfo&
-        file_system_info,
+    const ash::file_system_provider::ProvidedFileSystemInfo& file_system_info,
     base::File::Error error) {
   // TODO(mtomasz): Introduce own type, and avoid using MountError internally,
   // since it is related to cros disks only.
diff --git a/chrome/browser/chromeos/file_manager/volume_manager.h b/chrome/browser/chromeos/file_manager/volume_manager.h
index a8516b9..99bbb6c7 100644
--- a/chrome/browser/chromeos/file_manager/volume_manager.h
+++ b/chrome/browser/chromeos/file_manager/volume_manager.h
@@ -99,8 +99,7 @@
       const chromeos::disks::DiskMountManager::MountPointInfo& mount_point,
       const chromeos::disks::Disk* disk);
   static std::unique_ptr<Volume> CreateForProvidedFileSystem(
-      const chromeos::file_system_provider::ProvidedFileSystemInfo&
-          file_system_info,
+      const ash::file_system_provider::ProvidedFileSystemInfo& file_system_info,
       MountContext mount_context);
   static std::unique_ptr<Volume> CreateForMTP(const base::FilePath& mount_path,
                                               const std::string& label,
@@ -140,7 +139,7 @@
   // Getters for all members. See below for details.
   const std::string& volume_id() const { return volume_id_; }
   const std::string& file_system_id() const { return file_system_id_; }
-  const chromeos::file_system_provider::ProviderId& provider_id() const {
+  const ash::file_system_provider::ProviderId& provider_id() const {
     return provider_id_;
   }
   Source source() const { return source_; }
@@ -175,7 +174,7 @@
   bool watchable() const { return watchable_; }
   const std::string& file_system_type() const { return file_system_type_; }
   const std::string& drive_label() const { return drive_label_; }
-  const chromeos::file_system_provider::IconSet& icon_set() const {
+  const ash::file_system_provider::IconSet& icon_set() const {
     return icon_set_;
   }
 
@@ -191,7 +190,7 @@
 
   // The ID of an extension or native provider providing the file system. If
   // other type, then equal to a ProviderId of the type INVALID.
-  chromeos::file_system_provider::ProviderId provider_id_;
+  ash::file_system_provider::ProviderId provider_id_;
 
   // The source of the volume's data.
   Source source_;
@@ -257,7 +256,7 @@
   std::string file_system_type_;
 
   // Volume icon set.
-  chromeos::file_system_provider::IconSet icon_set_;
+  ash::file_system_provider::IconSet icon_set_;
 
   // Device label of a physical removable device. Removable partitions
   // belonging to the same device share the same device label.
@@ -279,7 +278,7 @@
                       public arc::ArcSessionManagerObserver,
                       public drive::DriveIntegrationServiceObserver,
                       public chromeos::disks::DiskMountManager::Observer,
-                      public chromeos::file_system_provider::Observer,
+                      public ash::file_system_provider::Observer,
                       public storage_monitor::RemovableStorageObserver,
                       public DocumentsProviderRootManager::Observer {
  public:
@@ -297,7 +296,7 @@
       drive::DriveIntegrationService* drive_integration_service,
       chromeos::PowerManagerClient* power_manager_client,
       chromeos::disks::DiskMountManager* disk_mount_manager,
-      chromeos::file_system_provider::Service* file_system_provider_service,
+      ash::file_system_provider::Service* file_system_provider_service,
       GetMtpStorageInfoCallback get_mtp_storage_info_callback);
   ~VolumeManager() override;
 
@@ -407,15 +406,13 @@
                      const std::string& device_path,
                      const std::string& device_label) override;
 
-  // chromeos::file_system_provider::Observer overrides.
+  // ash::file_system_provider::Observer overrides.
   void OnProvidedFileSystemMount(
-      const chromeos::file_system_provider::ProvidedFileSystemInfo&
-          file_system_info,
-      chromeos::file_system_provider::MountContext context,
+      const ash::file_system_provider::ProvidedFileSystemInfo& file_system_info,
+      ash::file_system_provider::MountContext context,
       base::File::Error error) override;
   void OnProvidedFileSystemUnmount(
-      const chromeos::file_system_provider::ProvidedFileSystemInfo&
-          file_system_info,
+      const ash::file_system_provider::ProvidedFileSystemInfo& file_system_info,
       base::File::Error error) override;
 
   // arc::ArcSessionManagerObserver overrides.
@@ -479,7 +476,7 @@
   chromeos::disks::DiskMountManager* disk_mount_manager_;      // Not owned.
   PrefChangeRegistrar pref_change_registrar_;
   base::ObserverList<VolumeManagerObserver>::Unchecked observers_;
-  chromeos::file_system_provider::Service*
+  ash::file_system_provider::Service*
       file_system_provider_service_;  // Not owned by this class.
   GetMtpStorageInfoCallback get_mtp_storage_info_callback_;
   std::map<std::string, std::unique_ptr<Volume>> mounted_volumes_;
diff --git a/chrome/browser/chromeos/file_manager/volume_manager_factory.cc b/chrome/browser/chromeos/file_manager/volume_manager_factory.cc
index a7921117..95ed0d6 100644
--- a/chrome/browser/chromeos/file_manager/volume_manager_factory.cc
+++ b/chrome/browser/chromeos/file_manager/volume_manager_factory.cc
@@ -48,7 +48,7 @@
       profile, drive::DriveIntegrationServiceFactory::GetForProfile(profile),
       chromeos::PowerManagerClient::Get(),
       chromeos::disks::DiskMountManager::GetInstance(),
-      chromeos::file_system_provider::ServiceFactory::Get(context),
+      ash::file_system_provider::ServiceFactory::Get(context),
       VolumeManager::GetMtpStorageInfoCallback());
   instance->Initialize();
   return instance;
@@ -59,7 +59,7 @@
           "VolumeManagerFactory",
           BrowserContextDependencyManager::GetInstance()) {
   DependsOn(drive::DriveIntegrationServiceFactory::GetInstance());
-  DependsOn(chromeos::file_system_provider::ServiceFactory::GetInstance());
+  DependsOn(ash::file_system_provider::ServiceFactory::GetInstance());
 }
 
 VolumeManagerFactory::~VolumeManagerFactory() = default;
diff --git a/chrome/browser/chromeos/file_manager/volume_manager_unittest.cc b/chrome/browser/chromeos/file_manager/volume_manager_unittest.cc
index 4e47633..f4d6167 100644
--- a/chrome/browser/chromeos/file_manager/volume_manager_unittest.cc
+++ b/chrome/browser/chromeos/file_manager/volume_manager_unittest.cc
@@ -229,7 +229,7 @@
           extension_registry_(
               std::make_unique<extensions::ExtensionRegistry>(profile_.get())),
           file_system_provider_service_(
-              std::make_unique<chromeos::file_system_provider::Service>(
+              std::make_unique<ash::file_system_provider::Service>(
                   profile_.get(),
                   extension_registry_.get())),
           drive_integration_service_(
@@ -272,7 +272,7 @@
 
     std::unique_ptr<TestingProfile> profile_;
     std::unique_ptr<extensions::ExtensionRegistry> extension_registry_;
-    std::unique_ptr<chromeos::file_system_provider::Service>
+    std::unique_ptr<ash::file_system_provider::Service>
         file_system_provider_service_;
     std::unique_ptr<drive::DriveIntegrationService> drive_integration_service_;
     std::unique_ptr<VolumeManager> volume_manager_;
diff --git a/chrome/browser/chromeos/fileapi/external_file_url_loader_factory_unittest.cc b/chrome/browser/chromeos/fileapi/external_file_url_loader_factory_unittest.cc
index cdc4448..6e0b95c 100644
--- a/chrome/browser/chromeos/fileapi/external_file_url_loader_factory_unittest.cc
+++ b/chrome/browser/chromeos/fileapi/external_file_url_loader_factory_unittest.cc
@@ -60,15 +60,14 @@
     render_process_host_ =
         std::make_unique<content::MockRenderProcessHost>(profile);
 
-    auto* service = chromeos::file_system_provider::Service::Get(profile);
+    auto* service = ash::file_system_provider::Service::Get(profile);
     service->RegisterProvider(
-        chromeos::file_system_provider::FakeExtensionProvider::Create(
-            kExtensionId));
+        ash::file_system_provider::FakeExtensionProvider::Create(kExtensionId));
     const auto kProviderId =
-        chromeos::file_system_provider::ProviderId::CreateFromExtensionId(
+        ash::file_system_provider::ProviderId::CreateFromExtensionId(
             kExtensionId);
     service->MountFileSystem(kProviderId,
-                             chromeos::file_system_provider::MountOptions(
+                             ash::file_system_provider::MountOptions(
                                  kFileSystemId, "Test FileSystem"));
 
     // Create the URLLoaderFactory.
diff --git a/chrome/browser/chromeos/smb_client/smb_file_system.h b/chrome/browser/chromeos/smb_client/smb_file_system.h
index b6e44b73..27e8a4d 100644
--- a/chrome/browser/chromeos/smb_client/smb_file_system.h
+++ b/chrome/browser/chromeos/smb_client/smb_file_system.h
@@ -18,6 +18,8 @@
 #include "chrome/browser/ash/file_system_provider/abort_callback.h"
 #include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
 #include "chrome/browser/ash/file_system_provider/provided_file_system_interface.h"
+// TODO(https://crbug.com/1164001): forward declare when moved to ash.
+#include "chrome/browser/ash/file_system_provider/request_manager.h"
 #include "chrome/browser/ash/file_system_provider/watcher.h"
 #include "chrome/browser/chromeos/smb_client/smb_service.h"
 #include "chrome/browser/chromeos/smb_client/smb_task_queue.h"
@@ -34,8 +36,6 @@
 namespace chromeos {
 namespace smb_client {
 
-class RequestManager;
-
 // SMB provided file system implementation. For communication with SMB
 // filesystems.
 // SMB is an application level protocol used by Windows and Samba fileservers.
@@ -43,8 +43,8 @@
 class SmbFileSystem : public file_system_provider::ProvidedFileSystemInterface,
                       public base::SupportsWeakPtr<SmbFileSystem> {
  public:
-  using MountIdCallback =
-      base::RepeatingCallback<int32_t(const ProvidedFileSystemInfo&)>;
+  using MountIdCallback = base::RepeatingCallback<int32_t(
+      const file_system_provider::ProvidedFileSystemInfo&)>;
   using UnmountCallback = base::OnceCallback<base::File::Error(
       const std::string&,
       file_system_provider::Service::UnmountReason)>;
diff --git a/chrome/browser/chromeos/smb_client/smb_file_system_unittest.cc b/chrome/browser/chromeos/smb_client/smb_file_system_unittest.cc
index c11b19a3..600e8e1 100644
--- a/chrome/browser/chromeos/smb_client/smb_file_system_unittest.cc
+++ b/chrome/browser/chromeos/smb_client/smb_file_system_unittest.cc
@@ -49,14 +49,16 @@
       : task_environment_(content::BrowserTaskEnvironment::REAL_IO_THREAD) {}
 
   void SetUp() override {
-    ProvidedFileSystemInfo file_system_info(
+    file_system_provider::ProvidedFileSystemInfo file_system_info(
         kProviderId, {}, base::FilePath(kSharePath), false /* configurable */,
         false /* watchable */, extensions::SOURCE_NETWORK,
-        chromeos::file_system_provider::IconSet());
+        file_system_provider::IconSet());
     file_system_ = std::make_unique<SmbFileSystem>(
         file_system_info,
         base::BindRepeating(
-            [](const ProvidedFileSystemInfo&) { return kMountId; }),
+            [](const file_system_provider::ProvidedFileSystemInfo&) {
+              return kMountId;
+            }),
         SmbFileSystem::UnmountCallback(),
         SmbFileSystem::RequestCredentialsCallback(),
         SmbFileSystem::RequestUpdatedSharePathCallback());
diff --git a/chrome/browser/chromeos/smb_client/smb_provider.cc b/chrome/browser/chromeos/smb_client/smb_provider.cc
index f07fa143..56953f1c 100644
--- a/chrome/browser/chromeos/smb_client/smb_provider.cc
+++ b/chrome/browser/chromeos/smb_client/smb_provider.cc
@@ -25,7 +25,7 @@
     UnmountCallback unmount_callback,
     SmbFileSystem::RequestCredentialsCallback request_creds_callback,
     SmbFileSystem::RequestUpdatedSharePathCallback request_path_callback)
-    : provider_id_(ProviderId::CreateFromNativeId("smb")),
+    : provider_id_(file_system_provider::ProviderId::CreateFromNativeId("smb")),
       capabilities_(false /* configurable */,
                     false /* watchable */,
                     true /* multiple_mounts */,
@@ -35,29 +35,29 @@
       unmount_callback_(std::move(unmount_callback)),
       request_creds_callback_(std::move(request_creds_callback)),
       request_path_callback_(std::move(request_path_callback)) {
-  icon_set_.SetIcon(IconSet::IconSize::SIZE_16x16,
+  icon_set_.SetIcon(file_system_provider::IconSet::IconSize::SIZE_16x16,
                     GURL("chrome://theme/IDR_SMB_ICON"));
-  icon_set_.SetIcon(IconSet::IconSize::SIZE_32x32,
+  icon_set_.SetIcon(file_system_provider::IconSet::IconSize::SIZE_32x32,
                     GURL("chrome://theme/IDR_SMB_ICON@2x"));
 }
 
 SmbProvider::~SmbProvider() = default;
 
-std::unique_ptr<ProvidedFileSystemInterface>
+std::unique_ptr<file_system_provider::ProvidedFileSystemInterface>
 SmbProvider::CreateProvidedFileSystem(
     Profile* profile,
-    const ProvidedFileSystemInfo& file_system_info) {
+    const file_system_provider::ProvidedFileSystemInfo& file_system_info) {
   DCHECK(profile);
   return std::make_unique<SmbFileSystem>(
       file_system_info, mount_id_callback_, unmount_callback_,
       request_creds_callback_, request_path_callback_);
 }
 
-const Capabilities& SmbProvider::GetCapabilities() const {
+const file_system_provider::Capabilities& SmbProvider::GetCapabilities() const {
   return capabilities_;
 }
 
-const ProviderId& SmbProvider::GetId() const {
+const file_system_provider::ProviderId& SmbProvider::GetId() const {
   return provider_id_;
 }
 
@@ -65,7 +65,7 @@
   return name_;
 }
 
-const IconSet& SmbProvider::GetIconSet() const {
+const file_system_provider::IconSet& SmbProvider::GetIconSet() const {
   return icon_set_;
 }
 
diff --git a/chrome/browser/chromeos/smb_client/smb_provider.h b/chrome/browser/chromeos/smb_client/smb_provider.h
index fbdd703f..a901373a 100644
--- a/chrome/browser/chromeos/smb_client/smb_provider.h
+++ b/chrome/browser/chromeos/smb_client/smb_provider.h
@@ -19,14 +19,7 @@
 namespace chromeos {
 namespace smb_client {
 
-using file_system_provider::Capabilities;
-using file_system_provider::IconSet;
-using file_system_provider::ProvidedFileSystemInfo;
-using file_system_provider::ProvidedFileSystemInterface;
-using file_system_provider::ProviderId;
-using file_system_provider::ProviderInterface;
-
-class SmbProvider : public ProviderInterface {
+class SmbProvider : public file_system_provider::ProviderInterface {
  public:
   using MountIdCallback = SmbFileSystem::MountIdCallback;
   using UnmountCallback = base::RepeatingCallback<base::File::Error(
@@ -42,21 +35,22 @@
   SmbProvider& operator=(const SmbProvider&) = delete;
   ~SmbProvider() override;
 
-  // ProviderInterface overrides.
-  std::unique_ptr<ProvidedFileSystemInterface> CreateProvidedFileSystem(
-      Profile* profile,
-      const ProvidedFileSystemInfo& file_system_info) override;
-  const Capabilities& GetCapabilities() const override;
-  const ProviderId& GetId() const override;
+  // file_system_provider::ProviderInterface overrides.
+  std::unique_ptr<file_system_provider::ProvidedFileSystemInterface>
+  CreateProvidedFileSystem(Profile* profile,
+                           const file_system_provider::ProvidedFileSystemInfo&
+                               file_system_info) override;
+  const file_system_provider::Capabilities& GetCapabilities() const override;
+  const file_system_provider::ProviderId& GetId() const override;
   const std::string& GetName() const override;
-  const IconSet& GetIconSet() const override;
+  const file_system_provider::IconSet& GetIconSet() const override;
   bool RequestMount(Profile* profile) override;
 
  private:
-  ProviderId provider_id_;
-  Capabilities capabilities_;
+  file_system_provider::ProviderId provider_id_;
+  file_system_provider::Capabilities capabilities_;
   std::string name_;
-  IconSet icon_set_;
+  file_system_provider::IconSet icon_set_;
 
   MountIdCallback mount_id_callback_;
   UnmountCallback unmount_callback_;
diff --git a/chrome/browser/chromeos/smb_client/smb_service.cc b/chrome/browser/chromeos/smb_client/smb_service.cc
index cc4a49e0b..0a805d9 100644
--- a/chrome/browser/chromeos/smb_client/smb_service.cc
+++ b/chrome/browser/chromeos/smb_client/smb_service.cc
@@ -145,7 +145,7 @@
 
 SmbService::SmbService(Profile* profile,
                        std::unique_ptr<base::TickClock> tick_clock)
-    : provider_id_(ProviderId::CreateFromNativeId("smb")),
+    : provider_id_(file_system_provider::ProviderId::CreateFromNativeId("smb")),
       profile_(profile),
       tick_clock_(std::move(tick_clock)),
       registry_(profile) {
@@ -562,7 +562,8 @@
   std::move(callback).Run(SmbMountResult::kSuccess, mount_path);
 }
 
-int32_t SmbService::GetMountId(const ProvidedFileSystemInfo& info) const {
+int32_t SmbService::GetMountId(
+    const file_system_provider::ProvidedFileSystemInfo& info) const {
   const auto iter = mount_id_map_.find(info.file_system_id());
   if (iter == mount_id_map_.end()) {
     // Either the mount process has not yet completed, or it failed to provide
@@ -598,8 +599,9 @@
 }
 
 void SmbService::RestoreMounts() {
-  std::vector<ProvidedFileSystemInfo> provided_file_systems =
-      GetProviderService()->GetProvidedFileSystemInfoList(provider_id_);
+  std::vector<file_system_provider::ProvidedFileSystemInfo>
+      provided_file_systems =
+          GetProviderService()->GetProvidedFileSystemInfoList(provider_id_);
 
   std::vector<SmbUrl> preconfigured_shares =
       GetPreconfiguredSharePathsForPremount();
@@ -621,7 +623,8 @@
 }
 
 void SmbService::OnHostsDiscovered(
-    const std::vector<ProvidedFileSystemInfo>& file_systems,
+    const std::vector<file_system_provider::ProvidedFileSystemInfo>&
+        file_systems,
     const std::vector<SmbShareInfo>& saved_smbfs_shares,
     const std::vector<SmbUrl>& preconfigured_shares) {
   for (const auto& file_system : file_systems) {
@@ -647,7 +650,8 @@
   }
 }
 
-void SmbService::Remount(const ProvidedFileSystemInfo& file_system_info) {
+void SmbService::Remount(
+    const file_system_provider::ProvidedFileSystemInfo& file_system_info) {
   const base::FilePath share_path =
       GetSharePathFromFileSystemId(file_system_info.file_system_id());
   const bool is_kerberos_chromad =
@@ -877,7 +881,7 @@
 }
 
 bool SmbService::IsShareMounted(const SmbUrl& share) const {
-  std::vector<ProvidedFileSystemInfo> file_systems =
+  std::vector<file_system_provider::ProvidedFileSystemInfo> file_systems =
       GetProviderService()->GetProvidedFileSystemInfoList(provider_id_);
 
   for (const auto& info : file_systems) {
@@ -1016,7 +1020,7 @@
 }
 
 void SmbService::RecordMountCount() const {
-  const std::vector<ProvidedFileSystemInfo> file_systems =
+  const std::vector<file_system_provider::ProvidedFileSystemInfo> file_systems =
       GetProviderService()->GetProvidedFileSystemInfoList(provider_id_);
   UMA_HISTOGRAM_COUNTS_100("NativeSmbFileShare.MountCount",
                            file_systems.size() + smbfs_shares_.size());
diff --git a/chrome/browser/chromeos/smb_client/smb_service.h b/chrome/browser/chromeos/smb_client/smb_service.h
index 274b318..c3460f7 100644
--- a/chrome/browser/chromeos/smb_client/smb_service.h
+++ b/chrome/browser/chromeos/smb_client/smb_service.h
@@ -39,8 +39,6 @@
 namespace chromeos {
 namespace smb_client {
 
-using file_system_provider::ProvidedFileSystemInfo;
-
 class SmbKerberosCredentialsUpdater;
 class SmbShareInfo;
 
@@ -164,7 +162,8 @@
                         SmbMountResult result);
 
   // Retrieves the mount_id for |file_system_info|.
-  int32_t GetMountId(const ProvidedFileSystemInfo& info) const;
+  int32_t GetMountId(
+      const file_system_provider::ProvidedFileSystemInfo& info) const;
 
   // Calls file_system_provider::Service::UnmountFileSystem().
   base::File::Error Unmount(
@@ -180,7 +179,8 @@
   void RestoreMounts();
 
   void OnHostsDiscovered(
-      const std::vector<ProvidedFileSystemInfo>& file_systems,
+      const std::vector<file_system_provider::ProvidedFileSystemInfo>&
+          file_systems,
       const std::vector<SmbShareInfo>& saved_smbfs_shares,
       const std::vector<SmbUrl>& preconfigured_shares);
 
@@ -192,7 +192,8 @@
       StartReadDirIfSuccessfulCallback reply);
 
   // Attempts to remount a share with the information in |file_system_info|.
-  void Remount(const ProvidedFileSystemInfo& file_system_info);
+  void Remount(
+      const file_system_provider::ProvidedFileSystemInfo& file_system_info);
 
   // Handles the response from attempting to remount the file system. If
   // remounting fails, this logs and removes the file_system from the volume
diff --git a/chrome/browser/chromeos/smb_client/smb_service_unittest.cc b/chrome/browser/chromeos/smb_client/smb_service_unittest.cc
index 9b1b2f4d..b7aecea 100644
--- a/chrome/browser/chromeos/smb_client/smb_service_unittest.cc
+++ b/chrome/browser/chromeos/smb_client/smb_service_unittest.cc
@@ -321,7 +321,7 @@
   std::unique_ptr<user_manager::ScopedUserManager> user_manager_enabler_;
   std::unique_ptr<SmbService> smb_service_;
 
-  chromeos::file_system_provider::MountOptions mount_options_;
+  file_system_provider::MountOptions mount_options_;
 };
 
 TEST_F(SmbServiceWithSmbfsTest, InvalidUrls) {
diff --git a/chrome/browser/commerce/merchant_viewer/android/BUILD.gn b/chrome/browser/commerce/merchant_viewer/android/BUILD.gn
index c87c05b1..5d1d86d 100644
--- a/chrome/browser/commerce/merchant_viewer/android/BUILD.gn
+++ b/chrome/browser/commerce/merchant_viewer/android/BUILD.gn
@@ -7,9 +7,12 @@
 
 android_library("java") {
   sources = [
-    "java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustDetailsSheetContent.java",
-    "java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustDetailsTabCoordinator.java",
-    "java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustDetailsTabMediator.java",
+    "java/src/org/chromium/chrome/browser/merchant_viewer/BottomSheetToolbarProperties.java",
+    "java/src/org/chromium/chrome/browser/merchant_viewer/BottomSheetToolbarView.java",
+    "java/src/org/chromium/chrome/browser/merchant_viewer/BottomSheetToolbarViewBinder.java",
+    "java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetContent.java",
+    "java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetCoordinator.java",
+    "java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetMediator.java",
     "java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustMessageContext.java",
     "java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustMessageScheduler.java",
     "java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustMessageViewModel.java",
@@ -71,7 +74,7 @@
   testonly = true
 
   sources = [
-    "javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustDetailsTabMediatorTest.java",
+    "javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetMediatorTest.java",
     "javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustMessageContextTest.java",
     "javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustMessageSchedulerTest.java",
     "javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsCallbackHelper.java",
@@ -105,6 +108,7 @@
     "//components/optimization_guide/proto:optimization_guide_proto_java",
     "//components/security_state/content/android:java",
     "//components/security_state/core:security_state_enums_java",
+    "//components/thin_webview:java",
     "//content/public/android:content_java",
     "//content/public/test/android:content_java_test_support",
     "//third_party/android_deps:protobuf_lite_runtime_java",
@@ -125,7 +129,8 @@
   testonly = true
 
   sources = [
-    "javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustDetailsTabCoordinatorTest.java",
+    "javatests/src/org/chromium/chrome/browser/merchant_viewer/BottomSheetToolbarViewBinderTest.java",
+    "javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetCoordinatorTest.java",
     "javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustMessageViewTest.java",
     "javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsCoordinatorTest.java",
     "javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsEventLoadCallbackHelper.java",
@@ -145,9 +150,12 @@
     "//components/embedder_support/android:content_view_java",
     "//components/messages/android:java",
     "//components/messages/android/internal:java",
+    "//components/thin_webview:java",
+    "//components/url_formatter/android:url_formatter_java",
     "//content/public/android:content_full_java",
     "//content/public/test/android:content_java_test_support",
     "//third_party/android_support_test_runner:runner_java",
+    "//third_party/androidx:androidx_appcompat_appcompat_resources_java",
     "//third_party/androidx:androidx_test_core_java",
     "//third_party/androidx:androidx_test_runner_java",
     "//third_party/junit",
diff --git a/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/BottomSheetToolbarProperties.java b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/BottomSheetToolbarProperties.java
new file mode 100644
index 0000000..da1085d
--- /dev/null
+++ b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/BottomSheetToolbarProperties.java
@@ -0,0 +1,48 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.merchant_viewer;
+
+import org.chromium.ui.modelutil.PropertyKey;
+import org.chromium.ui.modelutil.PropertyModel.WritableBooleanPropertyKey;
+import org.chromium.ui.modelutil.PropertyModel.WritableFloatPropertyKey;
+import org.chromium.ui.modelutil.PropertyModel.WritableIntPropertyKey;
+import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey;
+import org.chromium.url.GURL;
+
+/** BottomSheetToolbar UI properties. */
+public class BottomSheetToolbarProperties {
+    public static final WritableObjectPropertyKey<GURL> URL = new WritableObjectPropertyKey<>();
+
+    public static final WritableObjectPropertyKey<String> TITLE = new WritableObjectPropertyKey<>();
+
+    public static final WritableFloatPropertyKey LOAD_PROGRESS = new WritableFloatPropertyKey();
+
+    public static final WritableBooleanPropertyKey PROGRESS_VISIBLE =
+            new WritableBooleanPropertyKey();
+
+    public static final WritableIntPropertyKey SECURITY_ICON = new WritableIntPropertyKey();
+
+    public static final WritableObjectPropertyKey<String> SECURITY_ICON_CONTENT_DESCRIPTION =
+            new WritableObjectPropertyKey<>();
+
+    public static final WritableObjectPropertyKey<Runnable> SECURITY_ICON_ON_CLICK_CALLBACK =
+            new WritableObjectPropertyKey<>();
+
+    public static final WritableObjectPropertyKey<Runnable> CLOSE_BUTTON_ON_CLICK_CALLBACK =
+            new WritableObjectPropertyKey<>();
+
+    public static final WritableIntPropertyKey FAVICON_ICON = new WritableIntPropertyKey();
+
+    public static final WritableBooleanPropertyKey FAVICON_ICON_VISIBLE =
+            new WritableBooleanPropertyKey();
+
+    public static final WritableBooleanPropertyKey OPEN_IN_NEW_TAB_VISIBLE =
+            new WritableBooleanPropertyKey();
+
+    public static final PropertyKey[] ALL_KEYS = new PropertyKey[] {URL, TITLE, LOAD_PROGRESS,
+            PROGRESS_VISIBLE, SECURITY_ICON, SECURITY_ICON_CONTENT_DESCRIPTION,
+            SECURITY_ICON_ON_CLICK_CALLBACK, CLOSE_BUTTON_ON_CLICK_CALLBACK, FAVICON_ICON,
+            FAVICON_ICON_VISIBLE, OPEN_IN_NEW_TAB_VISIBLE};
+}
diff --git a/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/BottomSheetToolbarView.java b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/BottomSheetToolbarView.java
new file mode 100644
index 0000000..38a77fe
--- /dev/null
+++ b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/BottomSheetToolbarView.java
@@ -0,0 +1,125 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.merchant_viewer;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import androidx.annotation.DrawableRes;
+
+import org.chromium.base.ApiCompatibilityUtils;
+import org.chromium.chrome.tab_ui.R;
+import org.chromium.components.browser_ui.widget.FadingShadow;
+import org.chromium.components.browser_ui.widget.FadingShadowView;
+import org.chromium.components.url_formatter.SchemeDisplay;
+import org.chromium.components.url_formatter.UrlFormatter;
+import org.chromium.url.GURL;
+
+/** BottomSheetToolbar UI. */
+public class BottomSheetToolbarView {
+    private final int mToolbarHeightPx;
+    private final View mToolbarView;
+
+    /**
+     * Construct the BottomSheetToolbarView.
+     *
+     * @param context The context where the bottom-sheet should be shown.
+     */
+    public BottomSheetToolbarView(Context context) {
+        mToolbarHeightPx =
+                context.getResources().getDimensionPixelSize(R.dimen.sheet_tab_toolbar_height);
+        mToolbarView = LayoutInflater.from(context).inflate(R.layout.sheet_tab_toolbar, null);
+
+        FadingShadowView shadow = mToolbarView.findViewById(R.id.shadow);
+        shadow.init(ApiCompatibilityUtils.getColor(
+                            context.getResources(), R.color.toolbar_shadow_color),
+                FadingShadow.POSITION_TOP);
+    }
+
+    /** Sets the title of the bottom sheet. */
+    public void setTitle(String title) {
+        TextView toolbarText = mToolbarView.findViewById(R.id.title);
+        toolbarText.setText(title);
+    }
+
+    /** Sets the second line in the toolbar to the the provided URL. */
+    public void setUrl(GURL url) {
+        TextView originView = mToolbarView.findViewById(R.id.origin);
+        originView.setText(
+                UrlFormatter.formatUrlForSecurityDisplay(url, SchemeDisplay.OMIT_HTTP_AND_HTTPS));
+    }
+
+    /** Sets the security icon. */
+    public void setSecurityIcon(@DrawableRes int resId) {
+        ImageView securityIcon = mToolbarView.findViewById(R.id.security_icon);
+        securityIcon.setImageResource(resId);
+    }
+
+    /** Sets the security icon content description. */
+    public void setSecurityIconDescription(String description) {
+        ImageView securityIcon = mToolbarView.findViewById(R.id.security_icon);
+        securityIcon.setContentDescription(description);
+    }
+
+    /** Sets the security icon click callback. */
+    public void setSecurityIconClickCallback(Runnable callback) {
+        ImageView securityIcon = mToolbarView.findViewById(R.id.security_icon);
+        securityIcon.setOnClickListener(v -> {
+            if (callback != null) callback.run();
+        });
+    }
+
+    /** Sets the close button click callback. */
+    public void setCloseButtonClickCallback(Runnable callback) {
+        ImageView closeButton = mToolbarView.findViewById(R.id.close);
+        closeButton.setOnClickListener(v -> {
+            if (callback != null) callback.run();
+        });
+    }
+
+    /** Sets the progress on the progress bar. */
+    public void setProgress(float progress) {
+        ProgressBar progressBar = mToolbarView.findViewById(R.id.progress_bar);
+        progressBar.setProgress(Math.round(progress * 100));
+    }
+
+    /** Called to show or hide the progress bar. */
+    public void setProgressVisible(boolean visible) {
+        ProgressBar progressBar = mToolbarView.findViewById(R.id.progress_bar);
+        progressBar.setVisibility(visible ? View.VISIBLE : View.GONE);
+    }
+
+    /** Sets the favicon icon. */
+    public void setFaviconIcon(@DrawableRes int resId) {
+        ImageView faviconIcon = mToolbarView.findViewById(R.id.favicon);
+        faviconIcon.setImageResource(resId);
+    }
+
+    /** Sets the visibility of favicon icon. */
+    public void setFaviconIconVisible(boolean visible) {
+        ImageView faviconIcon = mToolbarView.findViewById(R.id.favicon);
+        faviconIcon.setVisibility(visible ? View.VISIBLE : View.GONE);
+    }
+
+    /** Sets the visibility of open-in-new-tab button. */
+    public void setOpenInNewTabButtonVisible(boolean visible) {
+        ImageView openInNewTabButton = mToolbarView.findViewById(R.id.open_in_new_tab);
+        openInNewTabButton.setVisibility(visible ? View.VISIBLE : View.GONE);
+    }
+
+    /** @return The height of the toolbar in pixels. */
+    public int getToolbarHeightPx() {
+        return mToolbarHeightPx;
+    }
+
+    /** @return The android {@link View} representing this BottomSheetToolbar. */
+    public View getView() {
+        return mToolbarView;
+    }
+}
diff --git a/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/BottomSheetToolbarViewBinder.java b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/BottomSheetToolbarViewBinder.java
new file mode 100644
index 0000000..6c7d30f
--- /dev/null
+++ b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/BottomSheetToolbarViewBinder.java
@@ -0,0 +1,43 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.merchant_viewer;
+
+import org.chromium.ui.modelutil.PropertyKey;
+import org.chromium.ui.modelutil.PropertyModel;
+
+/** ViewBinder for BottomSheetToolbarView. */
+public class BottomSheetToolbarViewBinder {
+    public static void bind(
+            PropertyModel model, BottomSheetToolbarView view, PropertyKey propertyKey) {
+        if (BottomSheetToolbarProperties.URL == propertyKey) {
+            view.setUrl(model.get(BottomSheetToolbarProperties.URL));
+        } else if (BottomSheetToolbarProperties.TITLE == propertyKey) {
+            view.setTitle(model.get(BottomSheetToolbarProperties.TITLE));
+        } else if (BottomSheetToolbarProperties.LOAD_PROGRESS == propertyKey) {
+            view.setProgress(model.get(BottomSheetToolbarProperties.LOAD_PROGRESS));
+        } else if (BottomSheetToolbarProperties.PROGRESS_VISIBLE == propertyKey) {
+            view.setProgressVisible(model.get(BottomSheetToolbarProperties.PROGRESS_VISIBLE));
+        } else if (BottomSheetToolbarProperties.SECURITY_ICON == propertyKey) {
+            view.setSecurityIcon(model.get(BottomSheetToolbarProperties.SECURITY_ICON));
+        } else if (BottomSheetToolbarProperties.SECURITY_ICON_CONTENT_DESCRIPTION == propertyKey) {
+            view.setSecurityIconDescription(
+                    model.get(BottomSheetToolbarProperties.SECURITY_ICON_CONTENT_DESCRIPTION));
+        } else if (BottomSheetToolbarProperties.SECURITY_ICON_ON_CLICK_CALLBACK == propertyKey) {
+            view.setSecurityIconClickCallback(
+                    model.get(BottomSheetToolbarProperties.SECURITY_ICON_ON_CLICK_CALLBACK));
+        } else if (BottomSheetToolbarProperties.CLOSE_BUTTON_ON_CLICK_CALLBACK == propertyKey) {
+            view.setCloseButtonClickCallback(
+                    model.get(BottomSheetToolbarProperties.CLOSE_BUTTON_ON_CLICK_CALLBACK));
+        } else if (BottomSheetToolbarProperties.FAVICON_ICON == propertyKey) {
+            view.setFaviconIcon(model.get(BottomSheetToolbarProperties.FAVICON_ICON));
+        } else if (BottomSheetToolbarProperties.FAVICON_ICON_VISIBLE == propertyKey) {
+            view.setFaviconIconVisible(
+                    model.get(BottomSheetToolbarProperties.FAVICON_ICON_VISIBLE));
+        } else if (BottomSheetToolbarProperties.OPEN_IN_NEW_TAB_VISIBLE == propertyKey) {
+            view.setOpenInNewTabButtonVisible(
+                    model.get(BottomSheetToolbarProperties.OPEN_IN_NEW_TAB_VISIBLE));
+        }
+    }
+}
diff --git a/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetContent.java b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetContent.java
new file mode 100644
index 0000000..4b8f2f9
--- /dev/null
+++ b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetContent.java
@@ -0,0 +1,105 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.merchant_viewer;
+
+import android.view.View;
+
+import org.chromium.base.supplier.Supplier;
+import org.chromium.chrome.tab_ui.R;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent;
+
+/**
+ * An implementation of {@link BottomSheetContent} for the merchant trust bottom sheet experience.
+ */
+public class MerchantTrustBottomSheetContent implements BottomSheetContent {
+    /** Ratio of the height when in half mode. */
+    private static final float HALF_HEIGHT_RATIO = 0.6f;
+    /** Ratio of the height when in full mode. Used in half-open variation. */
+    public static final float FULL_HEIGHT_RATIO = 0.9f;
+
+    private final View mToolbarView;
+    private final View mContentView;
+    private final Supplier<Integer> mVerticalScrollOffset;
+    private final Supplier<Boolean> mBackPressHandler;
+
+    /**
+     * Creates a new instance.
+     */
+    public MerchantTrustBottomSheetContent(View toolbarView, View contentView,
+            Supplier<Integer> verticalScrollOffset, Supplier<Boolean> backPressHandler) {
+        mToolbarView = toolbarView;
+        mContentView = contentView;
+        mVerticalScrollOffset = verticalScrollOffset;
+        mBackPressHandler = backPressHandler;
+    }
+
+    @Override
+    public View getContentView() {
+        return mContentView;
+    }
+
+    @Override
+    public View getToolbarView() {
+        return mToolbarView;
+    }
+
+    @Override
+    public int getVerticalScrollOffset() {
+        return mVerticalScrollOffset.get();
+    }
+
+    @Override
+    public void destroy() {}
+
+    @Override
+    public int getPriority() {
+        return ContentPriority.HIGH;
+    }
+
+    @Override
+    public boolean swipeToDismissEnabled() {
+        return true;
+    }
+
+    @Override
+    public int getPeekHeight() {
+        return HeightMode.DISABLED;
+    }
+
+    @Override
+    public float getHalfHeightRatio() {
+        return HALF_HEIGHT_RATIO;
+    }
+
+    @Override
+    public float getFullHeightRatio() {
+        return FULL_HEIGHT_RATIO;
+    }
+
+    @Override
+    public boolean handleBackPress() {
+        return mBackPressHandler.get();
+    }
+
+    @Override
+    public int getSheetContentDescriptionStringId() {
+        return R.string.merchant_viewer_preview_sheet_title;
+    }
+
+    @Override
+    public int getSheetHalfHeightAccessibilityStringId() {
+        return R.string.merchant_viewer_preview_sheet_title;
+    }
+
+    @Override
+    public int getSheetFullHeightAccessibilityStringId() {
+        return R.string.merchant_viewer_preview_sheet_title;
+    }
+
+    @Override
+    public int getSheetClosedAccessibilityStringId() {
+        return R.string.merchant_viewer_preview_sheet_title;
+    }
+}
\ No newline at end of file
diff --git a/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetCoordinator.java b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetCoordinator.java
new file mode 100644
index 0000000..473845183
--- /dev/null
+++ b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetCoordinator.java
@@ -0,0 +1,206 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.merchant_viewer;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import androidx.annotation.VisibleForTesting;
+
+import org.chromium.base.supplier.Supplier;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.tab_ui.R;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.SheetState;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.StateChangeReason;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetObserver;
+import org.chromium.components.browser_ui.bottomsheet.EmptyBottomSheetObserver;
+import org.chromium.components.thinwebview.ThinWebView;
+import org.chromium.components.thinwebview.ThinWebViewConstraints;
+import org.chromium.components.thinwebview.ThinWebViewFactory;
+import org.chromium.ui.base.WindowAndroid;
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
+import org.chromium.url.GURL;
+
+/** Coordinator for managing the merchant trust bottom sheet experience. */
+public class MerchantTrustBottomSheetCoordinator implements View.OnLayoutChangeListener {
+    private final Context mContext;
+    private final BottomSheetController mBottomSheetController;
+    private final View mLayoutView;
+    private final MerchantTrustMetrics mMetrics;
+
+    private MerchantTrustBottomSheetMediator mMediator;
+    private BottomSheetObserver mBottomSheetObserver;
+    private MerchantTrustBottomSheetContent mSheetContent;
+    private int mCurrentMaxViewHeight;
+    private ThinWebView mThinWebView;
+    private BottomSheetToolbarView mToolbarView;
+    private PropertyModel mToolbarModel;
+    private PropertyModelChangeProcessor mModelChangeProcessor;
+
+    /**
+     * Creates a new instance.
+     * @param context current {@link Context} intsance.
+     * @param windowAndroid app's Adnroid window.
+     * @param bottomSheetController {@BottomSheetController} instance.
+     * @param tabSupplier provider to obtain {@link Tab}.
+     * @param layoutView decor view.
+     */
+    public MerchantTrustBottomSheetCoordinator(Context context, WindowAndroid windowAndroid,
+            BottomSheetController bottomSheetController, Supplier<Tab> tabSupplier, View layoutView,
+            MerchantTrustMetrics metrics) {
+        mContext = context;
+        mBottomSheetController = bottomSheetController;
+        mLayoutView = layoutView;
+        mMetrics = metrics;
+
+        mMediator = new MerchantTrustBottomSheetMediator(context, windowAndroid, metrics);
+    }
+
+    /** Displays the details tab sheet. */
+    public void requestOpenSheet(GURL url, String title) {
+        setupSheet();
+        mMediator.navigateToUrl(url, title);
+        mBottomSheetController.requestShowContent(mSheetContent, true);
+    }
+
+    /** Closes the bottom sheet. */
+    void closeSheet() {
+        mBottomSheetController.hideContent(mSheetContent, true);
+    }
+
+    private void setupSheet() {
+        if (mSheetContent != null) {
+            return;
+        }
+
+        createToolbarView();
+        createThinWebView();
+        mMediator.setupSheetWebContents(mThinWebView, mToolbarModel);
+        mSheetContent = new MerchantTrustBottomSheetContent(mToolbarView.getView(),
+                mThinWebView.getView(), () -> mMediator.getVerticalScrollOffset(), () -> {
+                    closeSheet();
+                    return true;
+                });
+
+        mBottomSheetObserver = new EmptyBottomSheetObserver() {
+            private int mCloseReason;
+
+            @Override
+            public void onSheetContentChanged(BottomSheetContent newContent) {
+                if (newContent != mSheetContent) {
+                    mMetrics.recordMetricsForBottomSheetClosed(mCloseReason);
+                    destroySheet();
+                }
+            }
+
+            @Override
+            public void onSheetOpened(@StateChangeReason int reason) {
+                mMetrics.recordMetricsForBottomSheetHalfOpened();
+            }
+
+            @Override
+            public void onSheetStateChanged(int newState) {
+                if (mSheetContent == null) return;
+                switch (newState) {
+                    case SheetState.PEEK:
+                        mMetrics.recordMetricsForBottomSheetPeeked();
+                        break;
+                    case SheetState.HALF:
+                        mMetrics.recordMetricsForBottomSheetHalfOpened();
+                        break;
+                    case SheetState.FULL:
+                        mMetrics.recordMetricsForBottomSheetFullyOpened();
+                        break;
+                }
+            }
+
+            @Override
+            public void onSheetClosed(int reason) {
+                mCloseReason = reason;
+            }
+        };
+        mBottomSheetController.addObserver(mBottomSheetObserver);
+
+        mLayoutView.addOnLayoutChangeListener(this);
+    }
+
+    @VisibleForTesting
+    void destroySheet() {
+        mLayoutView.removeOnLayoutChangeListener(this);
+        if (mBottomSheetObserver != null) {
+            mBottomSheetController.removeObserver(mBottomSheetObserver);
+        }
+        closeSheet();
+        if (mSheetContent != null) {
+            mSheetContent.destroy();
+        }
+        mSheetContent = null;
+        mMediator.destroyWebContents();
+        if (mThinWebView != null) {
+            mThinWebView.destroy();
+        }
+        mThinWebView = null;
+        if (mModelChangeProcessor != null) {
+            mModelChangeProcessor.destroy();
+        }
+        mToolbarModel = null;
+        mToolbarView = null;
+    }
+
+    private void createThinWebView() {
+        mThinWebView = ThinWebViewFactory.create(mContext, new ThinWebViewConstraints());
+        mThinWebView.getView().setLayoutParams(new FrameLayout.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                (int) (getMaxViewHeight() * MerchantTrustBottomSheetContent.FULL_HEIGHT_RATIO)));
+
+        mThinWebView.getView().setPadding(0, mToolbarView.getToolbarHeightPx(), 0, 0);
+    }
+
+    private void createToolbarView() {
+        mToolbarView = new BottomSheetToolbarView(mContext);
+        mToolbarModel = new PropertyModel.Builder(BottomSheetToolbarProperties.ALL_KEYS)
+                                .with(BottomSheetToolbarProperties.CLOSE_BUTTON_ON_CLICK_CALLBACK,
+                                        this::closeSheet)
+                                .with(BottomSheetToolbarProperties.FAVICON_ICON,
+                                        R.drawable.ic_logo_googleg_24dp)
+                                .with(BottomSheetToolbarProperties.FAVICON_ICON_VISIBLE, true)
+                                .with(BottomSheetToolbarProperties.OPEN_IN_NEW_TAB_VISIBLE, false)
+                                .build();
+        mModelChangeProcessor = PropertyModelChangeProcessor.create(
+                mToolbarModel, mToolbarView, BottomSheetToolbarViewBinder::bind);
+    }
+
+    // Returns the maximum bottom view height.
+    private int getMaxViewHeight() {
+        return mBottomSheetController.getContainerHeight();
+    }
+
+    @Override
+    public void onLayoutChange(View view, int left, int top, int right, int bottom, int oldLeft,
+            int oldTop, int oldRight, int oldBottom) {
+        if (mSheetContent == null) return;
+
+        int maxViewHeight = getMaxViewHeight();
+        if (maxViewHeight == 0 || mCurrentMaxViewHeight == maxViewHeight) return;
+        ViewGroup.LayoutParams layoutParams = mThinWebView.getView().getLayoutParams();
+        // This should never be more than the tab height for it to function correctly.
+        // We scale it by |FULL_HEIGHT_RATIO| to make the size equal to that of
+        // ThinWebView and so it can leave a portion of the page below it visible.
+        layoutParams.height =
+                (int) (maxViewHeight * MerchantTrustBottomSheetContent.FULL_HEIGHT_RATIO);
+        mThinWebView.getView().requestLayout();
+        mCurrentMaxViewHeight = maxViewHeight;
+    }
+
+    @VisibleForTesting
+    void setMediatorForTesting(MerchantTrustBottomSheetMediator mediator) {
+        mMediator = mediator;
+    }
+}
\ No newline at end of file
diff --git a/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetMediator.java b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetMediator.java
new file mode 100644
index 0000000..3b577d00
--- /dev/null
+++ b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetMediator.java
@@ -0,0 +1,218 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.merchant_viewer;
+
+import android.content.Context;
+import android.os.Handler;
+import android.view.ViewGroup;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.VisibleForTesting;
+
+import org.chromium.chrome.browser.version.ChromeVersionInfo;
+import org.chromium.chrome.tab_ui.R;
+import org.chromium.components.embedder_support.delegate.WebContentsDelegateAndroid;
+import org.chromium.components.embedder_support.util.UrlUtilitiesJni;
+import org.chromium.components.embedder_support.view.ContentView;
+import org.chromium.components.security_state.ConnectionSecurityLevel;
+import org.chromium.components.security_state.SecurityStateModel;
+import org.chromium.components.thinwebview.ThinWebView;
+import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.NavigationHandle;
+import org.chromium.content_public.browser.RenderCoordinates;
+import org.chromium.content_public.browser.WebContents;
+import org.chromium.content_public.browser.WebContentsObserver;
+import org.chromium.content_public.common.ResourceRequestBody;
+import org.chromium.ui.base.ViewAndroidDelegate;
+import org.chromium.ui.base.WindowAndroid;
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.url.GURL;
+
+/** Mediator class for the component. */
+public class MerchantTrustBottomSheetMediator {
+    private static final long HIDE_PROGRESS_BAR_DELAY_MS = 50;
+
+    private final Context mContext;
+    private final WindowAndroid mWindowAndroid;
+    private final MerchantTrustMetrics mMetrics;
+    private final int mTopControlsHeightDp;
+
+    private PropertyModel mToolbarModel;
+    private WebContents mWebContents;
+    private ContentView mWebContentView;
+    private WebContentsDelegateAndroid mWebContentsDelegate;
+    private WebContentsObserver mWebContentsObserver;
+    private WebContents mWebContentsForTesting;
+
+    /** Creates a new instance. */
+    MerchantTrustBottomSheetMediator(
+            Context context, WindowAndroid windowAndroid, MerchantTrustMetrics metrics) {
+        mContext = context;
+        mWindowAndroid = windowAndroid;
+        mMetrics = metrics;
+        mTopControlsHeightDp = (int) (mContext.getResources().getDimensionPixelSize(
+                                              R.dimen.toolbar_height_no_shadow)
+                / mWindowAndroid.getDisplay().getDipScale());
+    }
+
+    void setupSheetWebContents(ThinWebView thinWebView, PropertyModel toolbarModel) {
+        assert mWebContentsObserver == null && mWebContentsDelegate == null
+                && mToolbarModel == null;
+        mToolbarModel = toolbarModel;
+
+        createWebContents();
+
+        mWebContentsObserver = new WebContentsObserver(mWebContents) {
+            @Override
+            public void loadProgressChanged(float progress) {
+                if (mToolbarModel != null) {
+                    mToolbarModel.set(BottomSheetToolbarProperties.LOAD_PROGRESS, progress);
+                }
+            }
+
+            @Override
+            public void didStartNavigation(NavigationHandle navigation) {
+                mMetrics.recordNavigateLinkOnBottomSheet();
+            }
+
+            @Override
+            public void titleWasSet(String title) {
+                if (!MerchantViewerConfig.TRUST_SIGNALS_SHEET_USE_PAGE_TITLE.getValue()) return;
+                mToolbarModel.set(BottomSheetToolbarProperties.TITLE, title);
+            }
+
+            @Override
+            public void didFinishNavigation(NavigationHandle navigation) {
+                if (navigation.isInMainFrame() && navigation.hasCommitted()) {
+                    mToolbarModel.set(
+                            BottomSheetToolbarProperties.URL, mWebContents.get().getVisibleUrl());
+                }
+            }
+        };
+
+        mWebContentsDelegate = new WebContentsDelegateAndroid() {
+            @Override
+            public void visibleSSLStateChanged() {
+                if (mToolbarModel == null) return;
+                int securityLevel = SecurityStateModel.getSecurityLevelForWebContents(mWebContents);
+                mToolbarModel.set(BottomSheetToolbarProperties.SECURITY_ICON,
+                        getSecurityIconResource(securityLevel));
+                mToolbarModel.set(BottomSheetToolbarProperties.URL, mWebContents.getVisibleUrl());
+            }
+
+            @Override
+            public void openNewTab(GURL url, String extraHeaders, ResourceRequestBody postData,
+                    int disposition, boolean isRendererInitiated) {
+                loadUrl(url);
+            }
+
+            @Override
+            public boolean shouldCreateWebContents(GURL targetUrl) {
+                loadUrl(targetUrl);
+                return false;
+            }
+
+            @Override
+            public void loadingStateChanged(boolean toDifferentDocument) {
+                boolean isLoading = mWebContents != null && mWebContents.isLoading();
+                if (isLoading) {
+                    if (mToolbarModel == null) return;
+                    mToolbarModel.set(BottomSheetToolbarProperties.LOAD_PROGRESS, 0);
+                    mToolbarModel.set(BottomSheetToolbarProperties.PROGRESS_VISIBLE, true);
+                } else {
+                    // Make sure the progress bar is visible for a few frames.
+                    new Handler().postDelayed(() -> {
+                        if (mToolbarModel != null) {
+                            mToolbarModel.set(BottomSheetToolbarProperties.PROGRESS_VISIBLE, false);
+                        }
+                    }, HIDE_PROGRESS_BAR_DELAY_MS);
+                }
+            }
+
+            @Override
+            public int getTopControlsHeight() {
+                return mTopControlsHeightDp;
+            }
+        };
+        if ((mWebContentView != null) && (mWebContentView.getParent() != null)) {
+            ((ViewGroup) mWebContentView.getParent()).removeView(mWebContentView);
+        }
+        thinWebView.attachWebContents(mWebContents, mWebContentView, mWebContentsDelegate);
+    }
+
+    void navigateToUrl(GURL url, String title) {
+        assert isValidUrl(url) && mWebContents != null && mToolbarModel != null;
+
+        loadUrl(url);
+        mToolbarModel.set(BottomSheetToolbarProperties.TITLE, title);
+    }
+
+    int getVerticalScrollOffset() {
+        return mWebContents == null
+                ? 0
+                : RenderCoordinates.fromWebContents(mWebContents).getScrollYPixInt();
+    }
+
+    private void createWebContents() {
+        assert mWebContents == null;
+        if (mWebContentsForTesting != null) {
+            mWebContents = mWebContentsForTesting;
+            return;
+        }
+        mWebContents = WebContentsHelpers.createWebContents(false, false);
+        mWebContentView = ContentView.createContentView(mContext, null, mWebContents);
+        final ViewAndroidDelegate delegate =
+                ViewAndroidDelegate.createBasicDelegate(mWebContentView);
+        mWebContents.initialize(ChromeVersionInfo.getProductVersion(), delegate, mWebContentView,
+                mWindowAndroid, WebContents.createDefaultInternalsHolder());
+        WebContentsHelpers.setUserAgentOverride(mWebContents);
+    }
+
+    void destroyWebContents() {
+        if (mWebContentsObserver != null) {
+            mWebContentsObserver.destroy();
+            mWebContentsObserver = null;
+        }
+        if (mWebContents != null) {
+            mWebContents.destroy();
+            mWebContents = null;
+            mWebContentView = null;
+        }
+        mWebContentsDelegate = null;
+        mToolbarModel = null;
+    }
+
+    private void loadUrl(GURL url) {
+        if (mWebContents != null) {
+            mWebContents.getNavigationController().loadUrl(new LoadUrlParams(url.getSpec()));
+        }
+    }
+
+    @DrawableRes
+    private static int getSecurityIconResource(@ConnectionSecurityLevel int securityLevel) {
+        switch (securityLevel) {
+            case ConnectionSecurityLevel.NONE:
+            case ConnectionSecurityLevel.WARNING:
+                return R.drawable.omnibox_info;
+            case ConnectionSecurityLevel.DANGEROUS:
+                return R.drawable.omnibox_not_secure_warning;
+            case ConnectionSecurityLevel.SECURE_WITH_POLICY_INSTALLED_CERT:
+            case ConnectionSecurityLevel.SECURE:
+                return R.drawable.omnibox_https_valid;
+            default:
+                assert false;
+        }
+        return 0;
+    }
+
+    private boolean isValidUrl(GURL url) {
+        return UrlUtilitiesJni.get().isGoogleDomainUrl(url.getSpec(), true);
+    }
+
+    @VisibleForTesting
+    void setWebContentsForTesting(WebContents webContents) {
+        mWebContentsForTesting = webContents;
+    }
+}
\ No newline at end of file
diff --git a/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustDetailsSheetContent.java b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustDetailsSheetContent.java
deleted file mode 100644
index 9c776f51..0000000
--- a/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustDetailsSheetContent.java
+++ /dev/null
@@ -1,232 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-package org.chromium.chrome.browser.merchant_viewer;
-
-import android.content.Context;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-
-import androidx.annotation.DrawableRes;
-
-import org.chromium.base.ApiCompatibilityUtils;
-import org.chromium.base.supplier.Supplier;
-import org.chromium.chrome.tab_ui.R;
-import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent;
-import org.chromium.components.browser_ui.widget.FadingShadow;
-import org.chromium.components.browser_ui.widget.FadingShadowView;
-import org.chromium.components.embedder_support.delegate.WebContentsDelegateAndroid;
-import org.chromium.components.embedder_support.view.ContentView;
-import org.chromium.components.thinwebview.ThinWebView;
-import org.chromium.components.thinwebview.ThinWebViewConstraints;
-import org.chromium.components.thinwebview.ThinWebViewFactory;
-import org.chromium.components.url_formatter.SchemeDisplay;
-import org.chromium.components.url_formatter.UrlFormatter;
-import org.chromium.content_public.browser.RenderCoordinates;
-import org.chromium.content_public.browser.WebContents;
-import org.chromium.url.GURL;
-
-/**
- * An implementation of {@link BottomSheetContent} for the merchant trust details page experience.
- */
-public class MerchantTrustDetailsSheetContent implements BottomSheetContent {
-    private final Context mContext;
-    private final int mToolbarHeightPx;
-    private final Runnable mCloseButtonCallback;
-
-    private ViewGroup mToolbarView;
-    private FadingShadowView mShadow;
-    private WebContents mWebContents;
-    private ContentView mWebContentView;
-    private ThinWebView mThinWebView;
-    private ViewGroup mSheetContentView;
-    private ImageView mFaviconView;
-
-    /** Ratio of the height when in half mode. */
-    private static final float HALF_HEIGHT_RATIO = 0.6f;
-
-    /** Ratio of the height when in full mode. Used in half-open variation. */
-    private static final float FULL_HEIGHT_RATIO = 0.9f;
-
-    /**
-     * Creates a new instance.
-     * @param context context instance.
-     * @param maxViewHeight a provider to calculate the maximum height for the view.
-     */
-    public MerchantTrustDetailsSheetContent(
-            Context context, Runnable closeButtonCallback, Supplier<Integer> maxViewHeight) {
-        mContext = context;
-        mCloseButtonCallback = closeButtonCallback;
-        mToolbarHeightPx =
-                mContext.getResources().getDimensionPixelSize(R.dimen.sheet_tab_toolbar_height);
-        createThinWebView((int) (maxViewHeight.get() * FULL_HEIGHT_RATIO));
-        createToolbarView();
-    }
-
-    /**
-     * Add web contents to the sheet.
-     * @param webContents The {@link WebContents} to be displayed.
-     * @param contentView The {@link ContentView} associated with the web contents.
-     * @param delegate The {@link WebContentsDelegateAndroid} that handles requests on WebContents.
-     */
-    public void attachWebContents(
-            WebContents webContents, ContentView contentView, WebContentsDelegateAndroid delegate) {
-        mWebContents = webContents;
-        mWebContentView = contentView;
-        if (mWebContentView.getParent() != null) {
-            ((ViewGroup) mWebContentView.getParent()).removeView(mWebContentView);
-        }
-        mThinWebView.attachWebContents(mWebContents, mWebContentView, delegate);
-    }
-
-    /** Sets the title of the bottom sheet. */
-    public void setTitle(String title) {
-        TextView toolbarText = mToolbarView.findViewById(R.id.title);
-        toolbarText.setText(title);
-    }
-
-    /** Sets the second line in the toolbar to the the provided URL. */
-    public void setUrl(GURL url) {
-        TextView originView = mToolbarView.findViewById(R.id.origin);
-        originView.setText(
-                UrlFormatter.formatUrlForSecurityDisplay(url, SchemeDisplay.OMIT_HTTP_AND_HTTPS));
-    }
-
-    /** Sets the security icon. */
-    public void setSecurityIcon(@DrawableRes int resId) {
-        ImageView securityIcon = mToolbarView.findViewById(R.id.security_icon);
-        securityIcon.setImageResource(resId);
-    }
-
-    /** Sets the progress on the progress bar. */
-    public void setProgress(float progress) {
-        ProgressBar progressBar = mToolbarView.findViewById(R.id.progress_bar);
-        progressBar.setProgress(Math.round(progress * 100));
-    }
-
-    /** Called to show or hide the progress bar. */
-    public void setProgressVisible(boolean visible) {
-        ProgressBar progressBar = mToolbarView.findViewById(R.id.progress_bar);
-        progressBar.setVisibility(visible ? View.VISIBLE : View.GONE);
-    }
-
-    /**
-     * Resizes the thin webview as per the given new max height.
-     * @param maxViewHeight The maximum height of the view.
-     */
-    void updateContentHeight(int maxViewHeight) {
-        if (maxViewHeight == 0) return;
-        ViewGroup.LayoutParams layoutParams = mThinWebView.getView().getLayoutParams();
-
-        // This should never be more than the tab height for it to function correctly.
-        // We scale it by |FULL_HEIGHT_RATIO| to make the size equal to that of
-        // ThinWebView and so it can leave a portion of the page below it visible.
-        layoutParams.height = (int) (maxViewHeight * FULL_HEIGHT_RATIO) - mToolbarHeightPx;
-        mSheetContentView.requestLayout();
-    }
-
-    private void createThinWebView(int maxSheetHeight) {
-        mThinWebView = ThinWebViewFactory.create(mContext, new ThinWebViewConstraints());
-        mSheetContentView = new FrameLayout(mContext);
-        mThinWebView.getView().setLayoutParams(new FrameLayout.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT, maxSheetHeight - mToolbarHeightPx));
-        mSheetContentView.addView(mThinWebView.getView());
-
-        mSheetContentView.setPadding(0, mToolbarHeightPx, 0, 0);
-    }
-
-    @Override
-    public View getContentView() {
-        return mSheetContentView;
-    }
-
-    @Override
-    public View getToolbarView() {
-        return mToolbarView;
-    }
-
-    @Override
-    public int getVerticalScrollOffset() {
-        return mWebContents == null
-                ? 0
-                : RenderCoordinates.fromWebContents(mWebContents).getScrollYPixInt();
-    }
-
-    @Override
-    public void destroy() {
-        if (mThinWebView != null) {
-            mThinWebView.destroy();
-        }
-    }
-
-    @Override
-    public int getPriority() {
-        return ContentPriority.HIGH;
-    }
-
-    @Override
-    public boolean swipeToDismissEnabled() {
-        return true;
-    }
-
-    @Override
-    public int getPeekHeight() {
-        return HeightMode.DISABLED;
-    }
-
-    @Override
-    public float getHalfHeightRatio() {
-        return HALF_HEIGHT_RATIO;
-    }
-
-    @Override
-    public float getFullHeightRatio() {
-        return FULL_HEIGHT_RATIO;
-    }
-
-    @Override
-    public boolean handleBackPress() {
-        mCloseButtonCallback.run();
-        return true;
-    }
-
-    @Override
-    public int getSheetContentDescriptionStringId() {
-        return R.string.merchant_viewer_preview_sheet_title;
-    }
-
-    @Override
-    public int getSheetHalfHeightAccessibilityStringId() {
-        return R.string.merchant_viewer_preview_sheet_title;
-    }
-
-    @Override
-    public int getSheetFullHeightAccessibilityStringId() {
-        return R.string.merchant_viewer_preview_sheet_title;
-    }
-
-    @Override
-    public int getSheetClosedAccessibilityStringId() {
-        return R.string.merchant_viewer_preview_sheet_title;
-    }
-
-    private void createToolbarView() {
-        mToolbarView =
-                (ViewGroup) LayoutInflater.from(mContext).inflate(R.layout.sheet_tab_toolbar, null);
-        mShadow = mToolbarView.findViewById(R.id.shadow);
-        mShadow.init(ApiCompatibilityUtils.getColor(
-                             mContext.getResources(), R.color.toolbar_shadow_color),
-                FadingShadow.POSITION_TOP);
-
-        View closeButton = mToolbarView.findViewById(R.id.close);
-        closeButton.setOnClickListener(view -> mCloseButtonCallback.run());
-
-        mFaviconView = mToolbarView.findViewById(R.id.favicon);
-        mFaviconView.setImageResource(R.drawable.ic_logo_googleg_24dp);
-    }
-}
\ No newline at end of file
diff --git a/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustDetailsTabCoordinator.java b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustDetailsTabCoordinator.java
deleted file mode 100644
index 84a32e5..0000000
--- a/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustDetailsTabCoordinator.java
+++ /dev/null
@@ -1,188 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-package org.chromium.chrome.browser.merchant_viewer;
-
-import android.content.Context;
-import android.view.View;
-
-import androidx.annotation.VisibleForTesting;
-
-import org.chromium.base.supplier.Supplier;
-import org.chromium.chrome.browser.profiles.Profile;
-import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.browser.version.ChromeVersionInfo;
-import org.chromium.chrome.tab_ui.R;
-import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent;
-import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
-import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.SheetState;
-import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.StateChangeReason;
-import org.chromium.components.browser_ui.bottomsheet.BottomSheetObserver;
-import org.chromium.components.browser_ui.bottomsheet.EmptyBottomSheetObserver;
-import org.chromium.components.embedder_support.view.ContentView;
-import org.chromium.content_public.browser.WebContents;
-import org.chromium.ui.base.ViewAndroidDelegate;
-import org.chromium.ui.base.WindowAndroid;
-import org.chromium.url.GURL;
-
-/** Coordinator for managing the merchant trust details page experience. */
-public class MerchantTrustDetailsTabCoordinator implements View.OnLayoutChangeListener {
-    private final Context mContext;
-    private final WindowAndroid mWindowAndroid;
-    private final BottomSheetController mBottomSheetController;
-    private final Supplier<Tab> mTabSupplier;
-    private final View mLayoutView;
-    private final MerchantTrustMetrics mMetrics;
-
-    private MerchantTrustDetailsTabMediator mMediator;
-    private WebContents mWebContents;
-    private ContentView mWebContentView;
-    private BottomSheetObserver mBottomSheetObserver;
-    private MerchantTrustDetailsSheetContent mSheetContent;
-    private int mCurrentMaxViewHeight;
-
-    /**
-     * Creates a new instance.
-     * @param context current {@link Context} intsance.
-     * @param windowAndroid app's Adnroid window.
-     * @param bottomSheetController {@BottomSheetController} instance.
-     * @param tabSupplier provider to obtain {@link Tab}.
-     * @param layoutView decor view.
-     */
-    public MerchantTrustDetailsTabCoordinator(Context context, WindowAndroid windowAndroid,
-            BottomSheetController bottomSheetController, Supplier<Tab> tabSupplier, View layoutView,
-            MerchantTrustMetrics metrics) {
-        mContext = context;
-        mWindowAndroid = windowAndroid;
-        mTabSupplier = tabSupplier;
-        mBottomSheetController = bottomSheetController;
-        mLayoutView = layoutView;
-        mMetrics = metrics;
-
-        float topControlsHeight =
-                mContext.getResources().getDimensionPixelSize(R.dimen.toolbar_height_no_shadow)
-                / mWindowAndroid.getDisplay().getDipScale();
-        mMediator = new MerchantTrustDetailsTabMediator(
-                mBottomSheetController, (int) topControlsHeight, mMetrics);
-    }
-
-    /** Displays the details tab sheet. */
-    public void requestOpenSheet(GURL url, String title) {
-        Profile profile = Profile.getLastUsedRegularProfile();
-        setupSheetWebContentsIfNeeded(profile);
-        mMediator.requestShowContent(url, title);
-    }
-
-    /** Closes the bottom sheet. */
-    void close() {
-        mBottomSheetController.hideContent(mSheetContent, true);
-    }
-
-    private void createWebContents(Profile profile) {
-        assert mWebContents == null;
-        mWebContents = WebContentsHelpers.createWebContents(false, false);
-        mWebContentView = ContentView.createContentView(mContext, null, mWebContents);
-        final ViewAndroidDelegate delegate =
-                ViewAndroidDelegate.createBasicDelegate(mWebContentView);
-        mWebContents.initialize(ChromeVersionInfo.getProductVersion(), delegate, mWebContentView,
-                mWindowAndroid, WebContents.createDefaultInternalsHolder());
-        WebContentsHelpers.setUserAgentOverride(mWebContents);
-    }
-
-    // Calculates the maximum view height based on the height of the tab provided by mTabSupplier.
-    private int getMaxViewHeight() {
-        final Tab tab = mTabSupplier.get();
-        if (tab == null || tab.getView() == null) return 0;
-        return tab.getView().getHeight();
-    }
-
-    @VisibleForTesting
-    void destroyWebContents() {
-        if (mSheetContent != null) {
-            mSheetContent.destroy();
-        }
-        mSheetContent = null;
-
-        if (mWebContents != null) {
-            mWebContents.destroy();
-            mWebContents = null;
-            mWebContentView = null;
-        }
-
-        mMediator.destroyContent();
-        mLayoutView.removeOnLayoutChangeListener(this);
-        if (mBottomSheetObserver != null) {
-            mBottomSheetController.removeObserver(mBottomSheetObserver);
-        }
-    }
-
-    private void setupSheetWebContentsIfNeeded(Profile profile) {
-        if (mWebContents != null) {
-            return;
-        }
-
-        assert mSheetContent == null;
-        createWebContents(profile);
-
-        // TODO: Observe changes and log metrics.
-        mBottomSheetObserver = new EmptyBottomSheetObserver() {
-            private int mCloseReason;
-
-            @Override
-            public void onSheetContentChanged(BottomSheetContent newContent) {
-                if (newContent != mSheetContent) {
-                    mMetrics.recordMetricsForBottomSheetClosed(mCloseReason);
-                    destroyWebContents();
-                }
-            }
-
-            @Override
-            public void onSheetOpened(@StateChangeReason int reason) {
-                mMetrics.recordMetricsForBottomSheetHalfOpened();
-            }
-
-            @Override
-            public void onSheetStateChanged(int newState) {
-                if (mSheetContent == null) return;
-                switch (newState) {
-                    case SheetState.PEEK:
-                        mMetrics.recordMetricsForBottomSheetPeeked();
-                        break;
-                    case SheetState.HALF:
-                        mMetrics.recordMetricsForBottomSheetHalfOpened();
-                        break;
-                    case SheetState.FULL:
-                        mMetrics.recordMetricsForBottomSheetFullyOpened();
-                        break;
-                }
-            }
-
-            @Override
-            public void onSheetClosed(int reason) {
-                mCloseReason = reason;
-            }
-        };
-
-        mBottomSheetController.addObserver(mBottomSheetObserver);
-        mSheetContent =
-                new MerchantTrustDetailsSheetContent(mContext, this::close, this::getMaxViewHeight);
-        mMediator.init(mWebContents, mWebContentView, mSheetContent, profile);
-        mLayoutView.addOnLayoutChangeListener(this);
-    }
-
-    @Override
-    public void onLayoutChange(View view, int left, int top, int right, int bottom, int oldLeft,
-            int oldTop, int oldRight, int oldBottom) {
-        if (mSheetContent == null) return;
-
-        int maxViewHeight = getMaxViewHeight();
-        if (maxViewHeight == 0 || mCurrentMaxViewHeight == maxViewHeight) return;
-        mSheetContent.updateContentHeight(maxViewHeight);
-        mCurrentMaxViewHeight = maxViewHeight;
-    }
-
-    @VisibleForTesting
-    void setMediatorForTesting(MerchantTrustDetailsTabMediator mediator) {
-        mMediator = mediator;
-    }
-}
\ No newline at end of file
diff --git a/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustDetailsTabMediator.java b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustDetailsTabMediator.java
deleted file mode 100644
index 908662b..0000000
--- a/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustDetailsTabMediator.java
+++ /dev/null
@@ -1,176 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.merchant_viewer;
-
-import android.os.Handler;
-
-import androidx.annotation.DrawableRes;
-
-import org.chromium.chrome.browser.profiles.Profile;
-import org.chromium.chrome.tab_ui.R;
-import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
-import org.chromium.components.embedder_support.delegate.WebContentsDelegateAndroid;
-import org.chromium.components.embedder_support.util.UrlUtilitiesJni;
-import org.chromium.components.embedder_support.view.ContentView;
-import org.chromium.components.security_state.ConnectionSecurityLevel;
-import org.chromium.components.security_state.SecurityStateModel;
-import org.chromium.content_public.browser.LoadUrlParams;
-import org.chromium.content_public.browser.NavigationHandle;
-import org.chromium.content_public.browser.WebContents;
-import org.chromium.content_public.browser.WebContentsObserver;
-import org.chromium.content_public.common.ResourceRequestBody;
-import org.chromium.url.GURL;
-
-/** Mediator class for the component. */
-public class MerchantTrustDetailsTabMediator {
-    private final BottomSheetController mBottomSheetController;
-    private WebContents mWebContents;
-    private MerchantTrustDetailsSheetContent mSheetContent;
-    private final int mTopControlsHeightDp;
-    private Profile mProfile;
-    private WebContentsDelegateAndroid mWebContentsDelegate;
-    private WebContentsObserver mWebContentsObserver;
-    private final MerchantTrustMetrics mMetrics;
-    private static final long HIDE_PROGRESS_BAR_DELAY_MS = 50;
-
-    /** Creates a new instance. */
-    MerchantTrustDetailsTabMediator(BottomSheetController bottomSheetController,
-            int topControlsHeightDp, MerchantTrustMetrics metrics) {
-        mBottomSheetController = bottomSheetController;
-        mTopControlsHeightDp = topControlsHeightDp;
-        mMetrics = metrics;
-    }
-
-    /**
-     * Starts the navigation to the provided URL and requests the bottom sheet to be shown.
-     * @param url URL to navigate to. This is required to be a Google URL.
-     * @param title title of the bottom sheet.
-     */
-    void requestShowContent(GURL url, String title) {
-        assert isValidUrl(url) && mWebContents != null;
-
-        loadUrl(url);
-        mSheetContent.setTitle(title);
-        mBottomSheetController.requestShowContent(mSheetContent, true);
-    }
-
-    /**
-     * Initializes internal state needed for {@link ThinWebView} and {@link WebContents}.
-     */
-    void init(WebContents webContents, ContentView contentView,
-            MerchantTrustDetailsSheetContent sheetContent, Profile profile) {
-        assert mProfile == null && mWebContentsObserver == null && mWebContentsDelegate == null;
-        mProfile = profile;
-        mWebContents = webContents;
-        mSheetContent = sheetContent;
-        mWebContentsObserver = new WebContentsObserver(mWebContents) {
-            @Override
-            public void loadProgressChanged(float progress) {
-                if (mSheetContent != null) mSheetContent.setProgress(progress);
-            }
-
-            @Override
-            public void didStartNavigation(NavigationHandle navigation) {
-                mMetrics.recordNavigateLinkOnBottomSheet();
-            }
-
-            @Override
-            public void titleWasSet(String title) {
-                if (!MerchantViewerConfig.TRUST_SIGNALS_SHEET_USE_PAGE_TITLE.getValue()) return;
-                mSheetContent.setTitle(title);
-            }
-
-            @Override
-            public void didFinishNavigation(NavigationHandle navigation) {
-                if (navigation.isInMainFrame() && navigation.hasCommitted()) {
-                    mSheetContent.setUrl(mWebContents.get().getVisibleUrl());
-                }
-            }
-        };
-
-        mWebContentsDelegate = new WebContentsDelegateAndroid() {
-            @Override
-            public void visibleSSLStateChanged() {
-                if (mSheetContent == null) return;
-                int securityLevel = SecurityStateModel.getSecurityLevelForWebContents(mWebContents);
-                mSheetContent.setSecurityIcon(getSecurityIconResource(securityLevel));
-                mSheetContent.setUrl(mWebContents.getVisibleUrl());
-            }
-
-            @Override
-            public void openNewTab(GURL url, String extraHeaders, ResourceRequestBody postData,
-                    int disposition, boolean isRendererInitiated) {
-                loadUrl(url);
-            }
-
-            @Override
-            public boolean shouldCreateWebContents(GURL targetUrl) {
-                loadUrl(targetUrl);
-                return false;
-            }
-
-            @Override
-            public void loadingStateChanged(boolean toDifferentDocument) {
-                boolean isLoading = mWebContents != null && mWebContents.isLoading();
-                if (isLoading) {
-                    if (mSheetContent == null) return;
-                    mSheetContent.setProgress(0);
-                    mSheetContent.setProgressVisible(true);
-                } else {
-                    // Make sure the progress bar is visible for a few frames.
-                    new Handler().postDelayed(() -> {
-                        if (mSheetContent != null) mSheetContent.setProgressVisible(false);
-                    }, HIDE_PROGRESS_BAR_DELAY_MS);
-                }
-            }
-
-            @Override
-            public int getTopControlsHeight() {
-                return mTopControlsHeightDp;
-            }
-        };
-
-        mSheetContent.attachWebContents(mWebContents, contentView, mWebContentsDelegate);
-    }
-
-    /**
-     * Destroys the objects used for the current preview tab.
-     */
-    void destroyContent() {
-        if (mWebContentsObserver != null) {
-            mWebContentsObserver.destroy();
-            mWebContentsObserver = null;
-        }
-        mWebContentsDelegate = null;
-        mWebContents = null;
-        mSheetContent = null;
-        mProfile = null;
-    }
-
-    private void loadUrl(GURL url) {
-        mWebContents.getNavigationController().loadUrl(new LoadUrlParams(url.getSpec()));
-    }
-
-    @DrawableRes
-    private static int getSecurityIconResource(@ConnectionSecurityLevel int securityLevel) {
-        switch (securityLevel) {
-            case ConnectionSecurityLevel.NONE:
-            case ConnectionSecurityLevel.WARNING:
-                return R.drawable.omnibox_info;
-            case ConnectionSecurityLevel.DANGEROUS:
-                return R.drawable.omnibox_not_secure_warning;
-            case ConnectionSecurityLevel.SECURE_WITH_POLICY_INSTALLED_CERT:
-            case ConnectionSecurityLevel.SECURE:
-                return R.drawable.omnibox_https_valid;
-            default:
-                assert false;
-        }
-        return 0;
-    }
-
-    private boolean isValidUrl(GURL url) {
-        return UrlUtilitiesJni.get().isGoogleDomainUrl(url.getSpec(), true);
-    }
-}
\ No newline at end of file
diff --git a/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsCoordinator.java b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsCoordinator.java
index 3fc3a64..2752838c 100644
--- a/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsCoordinator.java
+++ b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsCoordinator.java
@@ -33,7 +33,7 @@
 public class MerchantTrustSignalsCoordinator {
     private final MerchantTrustSignalsMediator mMediator;
     private final MerchantTrustMessageScheduler mMessageScheduler;
-    private final MerchantTrustDetailsTabCoordinator mDetailsTabCoordinator;
+    private final MerchantTrustBottomSheetCoordinator mDetailsTabCoordinator;
     private final Context mContext;
     private final WindowAndroid mWindowAndroid;
     private final BottomSheetController mBottomSheetController;
@@ -51,7 +51,7 @@
         this(context, windowAndroid, bottomSheetController, layoutView, tabModelSelector,
                 new MerchantTrustMessageScheduler(messageDispatcher, metrics), tabSupplier,
                 new MerchantTrustSignalsDataProvider(), metrics,
-                new MerchantTrustDetailsTabCoordinator(context, windowAndroid,
+                new MerchantTrustBottomSheetCoordinator(context, windowAndroid,
                         bottomSheetController, tabSupplier, layoutView, metrics),
                 profileSupplier, new MerchantTrustSignalsStorageFactory(profileSupplier));
     }
@@ -61,7 +61,7 @@
             BottomSheetController bottomSheetController, View layoutView,
             TabModelSelector tabModelSelector, MerchantTrustMessageScheduler messageScheduler,
             Supplier<Tab> tabSupplier, MerchantTrustSignalsDataProvider dataProvider,
-            MerchantTrustMetrics metrics, MerchantTrustDetailsTabCoordinator detailsTabCoordinator,
+            MerchantTrustMetrics metrics, MerchantTrustBottomSheetCoordinator detailsTabCoordinator,
             ObservableSupplier<Profile> profileSupplier,
             MerchantTrustSignalsStorageFactory storageFactory) {
         mContext = context;
diff --git a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/BottomSheetToolbarViewBinderTest.java b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/BottomSheetToolbarViewBinderTest.java
new file mode 100644
index 0000000..ec81921
--- /dev/null
+++ b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/BottomSheetToolbarViewBinderTest.java
@@ -0,0 +1,181 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.merchant_viewer;
+
+import static org.junit.Assert.assertEquals;
+
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.UiThreadTest;
+import org.chromium.chrome.tab_ui.R;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.components.url_formatter.SchemeDisplay;
+import org.chromium.components.url_formatter.UrlFormatter;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
+import org.chromium.ui.test.util.DummyUiActivityTestCase;
+import org.chromium.url.GURL;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Tests for {@link BottomSheetToolbarViewBinder}.
+ */
+@RunWith(ChromeJUnit4ClassRunner.class)
+public class BottomSheetToolbarViewBinderTest extends DummyUiActivityTestCase {
+    private final AtomicBoolean mIconClicked = new AtomicBoolean();
+
+    private BottomSheetToolbarView mItemView;
+    private PropertyModel mItemViewModel;
+    private PropertyModelChangeProcessor mItemMCP;
+
+    @Override
+    public void setUpTest() throws Exception {
+        super.setUpTest();
+
+        ViewGroup view = new FrameLayout(getActivity());
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            getActivity().setContentView(view);
+
+            mItemView = new BottomSheetToolbarView(getActivity());
+            view.addView(mItemView.getView());
+
+            mItemViewModel =
+                    new PropertyModel.Builder(BottomSheetToolbarProperties.ALL_KEYS)
+                            .with(BottomSheetToolbarProperties.FAVICON_ICON,
+                                    R.drawable.ic_logo_googleg_24dp)
+                            .with(BottomSheetToolbarProperties.FAVICON_ICON_VISIBLE, true)
+                            .with(BottomSheetToolbarProperties.OPEN_IN_NEW_TAB_VISIBLE, false)
+                            .build();
+
+            mItemMCP = PropertyModelChangeProcessor.create(
+                    mItemViewModel, mItemView, BottomSheetToolbarViewBinder::bind);
+        });
+    }
+
+    @Test
+    @UiThreadTest
+    @SmallTest
+    public void testSetTitle() {
+        TextView toolbarText = mItemView.getView().findViewById(R.id.title);
+        assertEquals("", toolbarText.getText());
+
+        String title = "titleText";
+        mItemViewModel.set(BottomSheetToolbarProperties.TITLE, title);
+        assertEquals(title, toolbarText.getText());
+    }
+
+    @Test
+    @UiThreadTest
+    @SmallTest
+    public void testSetUrl() {
+        TextView originView = mItemView.getView().findViewById(R.id.origin);
+        assertEquals("", originView.getText());
+
+        GURL url = new GURL("www.test.com");
+        mItemViewModel.set(BottomSheetToolbarProperties.URL, url);
+        assertEquals(
+                UrlFormatter.formatUrlForSecurityDisplay(url, SchemeDisplay.OMIT_HTTP_AND_HTTPS),
+                originView.getText());
+    }
+
+    @Test
+    @UiThreadTest
+    @SmallTest
+    public void testSetSecurityIconDescription() {
+        ImageView securityIcon = mItemView.getView().findViewById(R.id.security_icon);
+        String content = "contentText";
+        mItemViewModel.set(BottomSheetToolbarProperties.SECURITY_ICON_CONTENT_DESCRIPTION, content);
+        assertEquals(content, securityIcon.getContentDescription());
+    }
+
+    @Test
+    @UiThreadTest
+    @SmallTest
+    public void testSetSecurityIconClickCallback() {
+        ImageView securityIcon = mItemView.getView().findViewById(R.id.security_icon);
+        mIconClicked.set(false);
+        mItemViewModel.set(BottomSheetToolbarProperties.SECURITY_ICON_ON_CLICK_CALLBACK,
+                () -> mIconClicked.set(true));
+        securityIcon.performClick();
+        assertEquals(true, mIconClicked.get());
+    }
+
+    @Test
+    @UiThreadTest
+    @SmallTest
+    public void testCloseButtonClickCallback() {
+        ImageView closeButton = mItemView.getView().findViewById(R.id.close);
+        mIconClicked.set(false);
+        mItemViewModel.set(BottomSheetToolbarProperties.CLOSE_BUTTON_ON_CLICK_CALLBACK,
+                () -> mIconClicked.set(true));
+        closeButton.performClick();
+        assertEquals(true, mIconClicked.get());
+    }
+
+    @Test
+    @UiThreadTest
+    @SmallTest
+    public void testSetProgress() {
+        ProgressBar progressBar = mItemView.getView().findViewById(R.id.progress_bar);
+        assertEquals(0f, progressBar.getProgress(), 0.1);
+
+        float progress = 0.2f;
+        mItemViewModel.set(BottomSheetToolbarProperties.LOAD_PROGRESS, progress);
+        assertEquals(Math.round(progress * 100), progressBar.getProgress());
+    }
+
+    @Test
+    @UiThreadTest
+    @SmallTest
+    public void testSetProgressVisible() {
+        ProgressBar progressBar = mItemView.getView().findViewById(R.id.progress_bar);
+        mItemViewModel.set(BottomSheetToolbarProperties.PROGRESS_VISIBLE, false);
+        assertEquals(View.GONE, progressBar.getVisibility());
+
+        mItemViewModel.set(BottomSheetToolbarProperties.PROGRESS_VISIBLE, true);
+        assertEquals(View.VISIBLE, progressBar.getVisibility());
+    }
+
+    @Test
+    @UiThreadTest
+    @SmallTest
+    public void testSetFaviconIconVisible() {
+        ImageView faviconIcon = mItemView.getView().findViewById(R.id.favicon);
+        assertEquals(View.VISIBLE, faviconIcon.getVisibility());
+
+        mItemViewModel.set(BottomSheetToolbarProperties.FAVICON_ICON_VISIBLE, false);
+        assertEquals(View.GONE, faviconIcon.getVisibility());
+    }
+
+    @Test
+    @UiThreadTest
+    @SmallTest
+    public void testSetOpenInNewTabButtonVisible() {
+        ImageView openInNewTabButton = mItemView.getView().findViewById(R.id.open_in_new_tab);
+        assertEquals(View.GONE, openInNewTabButton.getVisibility());
+
+        mItemViewModel.set(BottomSheetToolbarProperties.OPEN_IN_NEW_TAB_VISIBLE, true);
+        assertEquals(View.VISIBLE, openInNewTabButton.getVisibility());
+    }
+
+    @Override
+    public void tearDownTest() throws Exception {
+        mItemMCP.destroy();
+        super.tearDownTest();
+    }
+}
\ No newline at end of file
diff --git a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustDetailsTabCoordinatorTest.java b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetCoordinatorTest.java
similarity index 82%
rename from chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustDetailsTabCoordinatorTest.java
rename to chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetCoordinatorTest.java
index fc4d750..c32d0562 100644
--- a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustDetailsTabCoordinatorTest.java
+++ b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetCoordinatorTest.java
@@ -31,7 +31,6 @@
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
-import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
@@ -40,19 +39,19 @@
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.SheetState;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.StateChangeReason;
 import org.chromium.components.browser_ui.bottomsheet.EmptyBottomSheetObserver;
-import org.chromium.components.embedder_support.view.ContentView;
-import org.chromium.content_public.browser.WebContents;
+import org.chromium.components.thinwebview.ThinWebView;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.base.WindowAndroid;
+import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.url.GURL;
 
 /**
- * Tests for {@link MerchantTrustDetailsTabCoordinator}.
+ * Tests for {@link MerchantTrustBottomSheetCoordinator}.
  */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @Batch(Batch.PER_CLASS)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
-public class MerchantTrustDetailsTabCoordinatorTest {
+public class MerchantTrustBottomSheetCoordinatorTest {
     @ClassRule
     public static final ChromeTabbedActivityTestRule sActivityTestRule =
             new ChromeTabbedActivityTestRule();
@@ -80,26 +79,26 @@
     private GURL mMockGurl;
 
     @Mock
-    private MerchantTrustDetailsTabMediator mMockMediator;
+    private MerchantTrustBottomSheetMediator mMockMediator;
 
     @Captor
     private ArgumentCaptor<EmptyBottomSheetObserver> mBottomSheetObserverCaptor;
 
     @Captor
-    private ArgumentCaptor<MerchantTrustDetailsSheetContent> mSheetContentCaptor;
+    private ArgumentCaptor<MerchantTrustBottomSheetContent> mSheetContentCaptor;
 
     private static final String DUMMY_SHEET_TITLE = "DUMMY_TITLE";
 
     private Activity mActivity;
     private WindowAndroid mWindowAndroid;
-    private MerchantTrustDetailsTabCoordinator mDetailsTabCoordinator;
+    private MerchantTrustBottomSheetCoordinator mDetailsTabCoordinator;
 
     @Before
     public void setUp() {
         mActivity = sActivityTestRule.getActivity();
         TestThreadUtils.runOnUiThreadBlocking(
                 () -> { mWindowAndroid = new WindowAndroid(mActivity); });
-        mDetailsTabCoordinator = new MerchantTrustDetailsTabCoordinator(mActivity, mWindowAndroid,
+        mDetailsTabCoordinator = new MerchantTrustBottomSheetCoordinator(mActivity, mWindowAndroid,
                 mMockBottomSheetController, mMockTabProvider, mMockDecorView, mMockMetrics);
         mDetailsTabCoordinator.setMediatorForTesting(mMockMediator);
         requestOpenSheetAndVerify();
@@ -108,7 +107,7 @@
     @After
     public void tearDown() {
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            mDetailsTabCoordinator.destroyWebContents();
+            mDetailsTabCoordinator.destroySheet();
             mWindowAndroid.destroy();
         });
     }
@@ -116,20 +115,21 @@
     private void requestOpenSheetAndVerify() {
         TestThreadUtils.runOnUiThreadBlocking(
                 () -> { mDetailsTabCoordinator.requestOpenSheet(mMockGurl, DUMMY_SHEET_TITLE); });
+        verify(mMockMediator, times(1))
+                .setupSheetWebContents(any(ThinWebView.class), any(PropertyModel.class));
         verify(mMockBottomSheetController, times(1))
                 .addObserver(mBottomSheetObserverCaptor.capture());
-        verify(mMockMediator, times(1))
-                .init(any(WebContents.class), any(ContentView.class), mSheetContentCaptor.capture(),
-                        any(Profile.class));
         verify(mMockDecorView, times(1))
                 .addOnLayoutChangeListener(any(OnLayoutChangeListener.class));
-        verify(mMockMediator, times(1)).requestShowContent(any(GURL.class), eq(DUMMY_SHEET_TITLE));
+        verify(mMockMediator, times(1)).navigateToUrl(eq(mMockGurl), eq(DUMMY_SHEET_TITLE));
+        verify(mMockBottomSheetController, times(1))
+                .requestShowContent(mSheetContentCaptor.capture(), eq(true));
     }
 
     @Test
     @SmallTest
-    public void testClose() {
-        mDetailsTabCoordinator.close();
+    public void testCloseSheet() {
+        mDetailsTabCoordinator.closeSheet();
         verify(mMockBottomSheetController, times(1))
                 .hideContent(eq(mSheetContentCaptor.getValue()), eq(true));
     }
@@ -142,11 +142,13 @@
                 () -> { mBottomSheetObserverCaptor.getValue().onSheetContentChanged(null); });
         verify(mMockMetrics, times(1))
                 .recordMetricsForBottomSheetClosed(eq(StateChangeReason.BACK_PRESS));
-        verify(mMockMediator, times(1)).destroyContent();
         verify(mMockDecorView, times(1))
                 .removeOnLayoutChangeListener(any(OnLayoutChangeListener.class));
         verify(mMockBottomSheetController, times(1))
                 .removeObserver(eq(mBottomSheetObserverCaptor.getValue()));
+        verify(mMockBottomSheetController, times(1))
+                .hideContent(eq(mSheetContentCaptor.getValue()), eq(true));
+        verify(mMockMediator, times(1)).destroyWebContents();
     }
 
     @Test
diff --git a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetMediatorTest.java b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetMediatorTest.java
new file mode 100644
index 0000000..960588b
--- /dev/null
+++ b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetMediatorTest.java
@@ -0,0 +1,251 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.merchant_viewer;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.JniMocker;
+import org.chromium.chrome.tab_ui.R;
+import org.chromium.chrome.test.util.browser.Features;
+import org.chromium.components.embedder_support.delegate.WebContentsDelegateAndroid;
+import org.chromium.components.embedder_support.util.UrlUtilities;
+import org.chromium.components.embedder_support.util.UrlUtilitiesJni;
+import org.chromium.components.security_state.ConnectionSecurityLevel;
+import org.chromium.components.security_state.SecurityStateModel;
+import org.chromium.components.security_state.SecurityStateModelJni;
+import org.chromium.components.thinwebview.ThinWebView;
+import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.NavigationController;
+import org.chromium.content_public.browser.NavigationHandle;
+import org.chromium.content_public.browser.WebContents;
+import org.chromium.content_public.browser.WebContentsObserver;
+import org.chromium.ui.base.WindowAndroid;
+import org.chromium.ui.display.DisplayAndroid;
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.url.GURL;
+
+/**
+ * Tests for {@link MerchantTrustBottomSheetMediator}.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class MerchantTrustBottomSheetMediatorTest {
+    @Rule
+    public TestRule mProcessor = new Features.JUnitProcessor();
+
+    @Rule
+    public JniMocker mocker = new JniMocker();
+
+    @Mock
+    private WebContents mMockWebContents;
+
+    @Mock
+    private GURL mMockDestinationGurl;
+
+    @Mock
+    UrlUtilities.Natives mUrlUtilitiesJniMock;
+
+    @Mock
+    private NavigationController mMockNavigationController;
+
+    @Mock
+    private Context mMockContext;
+
+    @Mock
+    private Resources mMockResources;
+
+    @Mock
+    private WindowAndroid mMockWindowAndroid;
+
+    @Mock
+    private DisplayAndroid mMockDisplayAndroid;
+
+    @Mock
+    private MerchantTrustMetrics mMockMetrics;
+
+    @Mock
+    private ThinWebView mMockThinWebView;
+
+    @Mock
+    private NavigationHandle mMockNavigationHandle;
+
+    @Mock
+    SecurityStateModel.Natives mSecurityStateMocks;
+
+    @Captor
+    private ArgumentCaptor<WebContentsDelegateAndroid> mWebContentsDelegateCaptor;
+
+    @Captor
+    private ArgumentCaptor<WebContentsObserver> mWebContentsObserverCaptor;
+
+    private static final String DUMMY_SHEET_TITLE = "DUMMY_TITLE";
+    private static final String DUMMY_URL = "dummy://visible/url";
+
+    private MerchantTrustBottomSheetMediator mMediator;
+    private PropertyModel mToolbarModel;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        doReturn(mMockResources).when(mMockContext).getResources();
+        doReturn(56)
+                .when(mMockResources)
+                .getDimensionPixelSize(eq(R.dimen.toolbar_height_no_shadow));
+        doReturn(mMockDisplayAndroid).when(mMockWindowAndroid).getDisplay();
+        doReturn(1f).when(mMockDisplayAndroid).getDipScale();
+        doReturn(DUMMY_URL).when(mMockDestinationGurl).getSpec();
+        doReturn(mMockDestinationGurl).when(mMockWebContents).getVisibleUrl();
+        doReturn(mMockNavigationController).when(mMockWebContents).getNavigationController();
+        when(mUrlUtilitiesJniMock.isGoogleDomainUrl(anyString(), anyBoolean())).thenReturn(true);
+        when(mSecurityStateMocks.getSecurityLevelForWebContents(any(WebContents.class)))
+                .thenReturn(ConnectionSecurityLevel.SECURE);
+
+        mocker.mock(UrlUtilitiesJni.TEST_HOOKS, mUrlUtilitiesJniMock);
+        mocker.mock(SecurityStateModelJni.TEST_HOOKS, mSecurityStateMocks);
+
+        mMediator = new MerchantTrustBottomSheetMediator(
+                mMockContext, mMockWindowAndroid, mMockMetrics);
+        mMediator.setWebContentsForTesting(mMockWebContents);
+        mToolbarModel = new PropertyModel.Builder(BottomSheetToolbarProperties.ALL_KEYS).build();
+        setUpSheetWebContentsAndVerify();
+    }
+
+    @After
+    public void tearDown() {
+        mMediator.setWebContentsForTesting(null);
+    }
+
+    private void setUpSheetWebContentsAndVerify() {
+        mMediator.setupSheetWebContents(mMockThinWebView, mToolbarModel);
+        verify(mMockWebContents, times(1)).addObserver(mWebContentsObserverCaptor.capture());
+        verify(mMockThinWebView, times(1))
+                .attachWebContents(
+                        eq(mMockWebContents), eq(null), mWebContentsDelegateCaptor.capture());
+    }
+
+    @Test
+    public void testNavigateToUrl() {
+        mMediator.navigateToUrl(mMockDestinationGurl, DUMMY_SHEET_TITLE);
+        verify(mMockNavigationController, times(1)).loadUrl(any(LoadUrlParams.class));
+        assertEquals(DUMMY_SHEET_TITLE, mToolbarModel.get(BottomSheetToolbarProperties.TITLE));
+    }
+
+    @Test(expected = java.lang.AssertionError.class)
+    public void testNavigateToNonGoogleUrl() {
+        doReturn(false).when(mUrlUtilitiesJniMock).isGoogleDomainUrl(anyString(), anyBoolean());
+        mMediator.navigateToUrl(mMockDestinationGurl, DUMMY_SHEET_TITLE);
+    }
+
+    @Test
+    public void testDestroyWebContents() {
+        mMediator.destroyWebContents();
+        verify(mMockWebContents, times(1)).destroy();
+    }
+
+    @Test
+    public void testWebContentsDelegateSslChanges() {
+        mWebContentsDelegateCaptor.getValue().visibleSSLStateChanged();
+        assertEquals(mMockDestinationGurl, mToolbarModel.get(BottomSheetToolbarProperties.URL));
+        assertEquals(R.drawable.omnibox_https_valid,
+                mToolbarModel.get(BottomSheetToolbarProperties.SECURITY_ICON));
+    }
+
+    @Test
+    public void testWebContentsDelegateOpenNewTab() {
+        mWebContentsDelegateCaptor.getValue().openNewTab(mMockDestinationGurl, "", null, 0, true);
+        verify(mMockNavigationController, times(1)).loadUrl(any(LoadUrlParams.class));
+    }
+
+    @Test
+    public void testWebContentsDelegateShouldCreateWebContents() {
+        mWebContentsDelegateCaptor.getValue().shouldCreateWebContents(mMockDestinationGurl);
+        verify(mMockNavigationController, times(1)).loadUrl(any(LoadUrlParams.class));
+    }
+
+    @Test
+    public void testWebContentsDelegateGetTopControlsHeight() {
+        assertEquals(56, mWebContentsDelegateCaptor.getValue().getTopControlsHeight());
+    }
+
+    @Test
+    public void testWebContentsDelegateLoadingStateChanges() {
+        // Loading state.
+        doReturn(true).when(mMockWebContents).isLoading();
+        mWebContentsDelegateCaptor.getValue().loadingStateChanged(true);
+        assertEquals(0, mToolbarModel.get(BottomSheetToolbarProperties.LOAD_PROGRESS), 0.01);
+        assertEquals(true, mToolbarModel.get(BottomSheetToolbarProperties.PROGRESS_VISIBLE));
+    }
+
+    @Test
+    public void testWebContentsObserverLoadProgressChanged() {
+        float progress = 0.2f;
+        mWebContentsObserverCaptor.getValue().loadProgressChanged(progress);
+        assertEquals(progress, mToolbarModel.get(BottomSheetToolbarProperties.LOAD_PROGRESS), 0.01);
+    }
+
+    @Test
+    public void testWebContentsObserverDidStartNavigation() {
+        mWebContentsObserverCaptor.getValue().didStartNavigation(mMockNavigationHandle);
+        verify(mMockMetrics, times(1)).recordNavigateLinkOnBottomSheet();
+    }
+
+    @Test
+    public void testWebContentsObserverTitleWasSet() {
+        MerchantViewerConfig.TRUST_SIGNALS_SHEET_USE_PAGE_TITLE.setForTesting(false);
+        mWebContentsObserverCaptor.getValue().titleWasSet(DUMMY_SHEET_TITLE);
+        assertEquals(null, mToolbarModel.get(BottomSheetToolbarProperties.TITLE));
+
+        MerchantViewerConfig.TRUST_SIGNALS_SHEET_USE_PAGE_TITLE.setForTesting(true);
+        mWebContentsObserverCaptor.getValue().titleWasSet(DUMMY_SHEET_TITLE);
+        assertEquals(DUMMY_SHEET_TITLE, mToolbarModel.get(BottomSheetToolbarProperties.TITLE));
+    }
+
+    @Test
+    public void testWebContentsObserverDidFinishNavigation() {
+        doReturn(false).when(mMockNavigationHandle).isInMainFrame();
+        doReturn(false).when(mMockNavigationHandle).hasCommitted();
+        mWebContentsObserverCaptor.getValue().didFinishNavigation(mMockNavigationHandle);
+        assertEquals(null, mToolbarModel.get(BottomSheetToolbarProperties.URL));
+
+        doReturn(true).when(mMockNavigationHandle).isInMainFrame();
+        doReturn(false).when(mMockNavigationHandle).hasCommitted();
+        mWebContentsObserverCaptor.getValue().didFinishNavigation(mMockNavigationHandle);
+        assertEquals(null, mToolbarModel.get(BottomSheetToolbarProperties.URL));
+
+        doReturn(false).when(mMockNavigationHandle).isInMainFrame();
+        doReturn(true).when(mMockNavigationHandle).hasCommitted();
+        mWebContentsObserverCaptor.getValue().didFinishNavigation(mMockNavigationHandle);
+        assertEquals(null, mToolbarModel.get(BottomSheetToolbarProperties.URL));
+
+        doReturn(true).when(mMockNavigationHandle).isInMainFrame();
+        doReturn(true).when(mMockNavigationHandle).hasCommitted();
+        mWebContentsObserverCaptor.getValue().didFinishNavigation(mMockNavigationHandle);
+        assertEquals(mMockDestinationGurl, mToolbarModel.get(BottomSheetToolbarProperties.URL));
+    }
+}
\ No newline at end of file
diff --git a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustDetailsTabMediatorTest.java b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustDetailsTabMediatorTest.java
deleted file mode 100644
index b3e8962..0000000
--- a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustDetailsTabMediatorTest.java
+++ /dev/null
@@ -1,288 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.merchant_viewer;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.annotation.Config;
-
-import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.base.test.util.JniMocker;
-import org.chromium.chrome.browser.profiles.Profile;
-import org.chromium.chrome.test.util.browser.Features;
-import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
-import org.chromium.components.embedder_support.delegate.WebContentsDelegateAndroid;
-import org.chromium.components.embedder_support.util.UrlUtilities;
-import org.chromium.components.embedder_support.util.UrlUtilitiesJni;
-import org.chromium.components.embedder_support.view.ContentView;
-import org.chromium.components.security_state.SecurityStateModel;
-import org.chromium.components.security_state.SecurityStateModelJni;
-import org.chromium.content_public.browser.LoadUrlParams;
-import org.chromium.content_public.browser.NavigationController;
-import org.chromium.content_public.browser.NavigationHandle;
-import org.chromium.content_public.browser.WebContents;
-import org.chromium.content_public.browser.WebContentsObserver;
-import org.chromium.url.GURL;
-
-/**
- * Tests for {@link MerchantTrustDetailsTabMediator}.
- */
-@RunWith(BaseRobolectricTestRunner.class)
-@Config(manifest = Config.NONE)
-public class MerchantTrustDetailsTabMediatorTest {
-    @Rule
-    public TestRule mProcessor = new Features.JUnitProcessor();
-
-    @Mock
-    private WebContents mMockWebContents;
-
-    @Mock
-    private BottomSheetController mMockBottomSheetController;
-
-    @Mock
-    private GURL mMockDestinationGurl;
-
-    @Rule
-    public JniMocker mocker = new JniMocker();
-
-    @Mock
-    UrlUtilities.Natives mUrlUtilitiesJniMock;
-
-    @Mock
-    private NavigationController mMockNavigationController;
-
-    @Mock
-    private ContentView mMockContentView;
-    @Mock
-    private MerchantTrustDetailsSheetContent mMockSheetContent;
-
-    @Mock
-    private Profile mMockProfile;
-
-    @Mock
-    private MerchantTrustMetrics mMockMetrics;
-
-    @Mock
-    private NavigationHandle mMockNavigationHandle;
-
-    @Captor
-    private ArgumentCaptor<WebContentsDelegateAndroid> mWebContentsDelegateCaptor;
-
-    @Captor
-    private ArgumentCaptor<WebContentsObserver> mWebContentsObserverCaptor;
-
-    private static final String DUMMY_SHEET_TITLE = "DUMMY_TITLE";
-    private static final String DUMMY_URL = "dummy://visible/url";
-
-    @Mock
-    SecurityStateModel.Natives mSecurityStateMocks;
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        doReturn(DUMMY_URL).when(mMockDestinationGurl).getSpec();
-        doReturn(mMockDestinationGurl).when(mMockWebContents).getVisibleUrl();
-
-        doReturn(mMockNavigationController).when(mMockWebContents).getNavigationController();
-        when(mUrlUtilitiesJniMock.isGoogleDomainUrl(anyString(), anyBoolean())).thenReturn(true);
-
-        mocker.mock(UrlUtilitiesJni.TEST_HOOKS, mUrlUtilitiesJniMock);
-        mocker.mock(SecurityStateModelJni.TEST_HOOKS, mSecurityStateMocks);
-    }
-
-    @Test
-    public void testRequestShowContent() {
-        MerchantTrustDetailsTabMediator instance = getMediatorUnderTest();
-        instance.init(mMockWebContents, mMockContentView, mMockSheetContent, mMockProfile);
-        instance.requestShowContent(mMockDestinationGurl, DUMMY_SHEET_TITLE);
-
-        verify(mMockSheetContent, times(1)).setTitle(eq(DUMMY_SHEET_TITLE));
-
-        verify(mMockBottomSheetController, times(1))
-                .requestShowContent(eq(mMockSheetContent), eq(true));
-    }
-
-    @Test(expected = java.lang.AssertionError.class)
-    public void testRequestShowContentNonGoogleUrl() {
-        MerchantTrustDetailsTabMediator instance = getMediatorUnderTest();
-        instance.init(mMockWebContents, mMockContentView, mMockSheetContent, mMockProfile);
-        doReturn(false).when(mUrlUtilitiesJniMock).isGoogleDomainUrl(anyString(), anyBoolean());
-        instance.requestShowContent(mMockDestinationGurl, DUMMY_SHEET_TITLE);
-    }
-
-    @Test(expected = java.lang.AssertionError.class)
-    public void testRequestShowContentBeforeInitIsCalled() {
-        MerchantTrustDetailsTabMediator instance = getMediatorUnderTest();
-        doReturn(true).when(mUrlUtilitiesJniMock).isGoogleDomainUrl(anyString(), anyBoolean());
-        instance.requestShowContent(mMockDestinationGurl, DUMMY_SHEET_TITLE);
-    }
-
-    @Test
-    public void testInit() {
-        MerchantTrustDetailsTabMediator instance = getMediatorUnderTest();
-        instance.init(mMockWebContents, mMockContentView, mMockSheetContent, mMockProfile);
-
-        verify(mMockSheetContent, times(1))
-                .attachWebContents(eq(mMockWebContents), eq(mMockContentView),
-                        any(WebContentsDelegateAndroid.class));
-    }
-
-    @Test
-    public void testSslChanges() {
-        MerchantTrustDetailsTabMediator instance = getMediatorUnderTest();
-        instance.init(mMockWebContents, mMockContentView, mMockSheetContent, mMockProfile);
-
-        verify(mMockSheetContent, times(1))
-                .attachWebContents(eq(mMockWebContents), eq(mMockContentView),
-                        mWebContentsDelegateCaptor.capture());
-
-        instance.requestShowContent(mMockDestinationGurl, DUMMY_SHEET_TITLE);
-        mWebContentsDelegateCaptor.getValue().visibleSSLStateChanged();
-
-        verify(mMockSheetContent, times(1)).setUrl(eq(mMockDestinationGurl));
-        verify(mMockSheetContent, times(1)).setSecurityIcon(any(Integer.class));
-    }
-
-    @Test
-    public void testWebContentsOpenNewTab() {
-        MerchantTrustDetailsTabMediator instance = getMediatorUnderTest();
-        instance.init(mMockWebContents, mMockContentView, mMockSheetContent, mMockProfile);
-        verify(mMockSheetContent, times(1))
-                .attachWebContents(eq(mMockWebContents), eq(mMockContentView),
-                        mWebContentsDelegateCaptor.capture());
-        instance.requestShowContent(mMockDestinationGurl, DUMMY_SHEET_TITLE);
-        mWebContentsDelegateCaptor.getValue().openNewTab(mMockDestinationGurl, "", null, 0, true);
-
-        verify(mMockNavigationController, times(2)).loadUrl(any(LoadUrlParams.class));
-    }
-
-    @Test
-    public void testWebContentsDelegateShouldCreateWebContents() {
-        MerchantTrustDetailsTabMediator instance = getMediatorUnderTest();
-        instance.init(mMockWebContents, mMockContentView, mMockSheetContent, mMockProfile);
-        verify(mMockSheetContent, times(1))
-                .attachWebContents(eq(mMockWebContents), eq(mMockContentView),
-                        mWebContentsDelegateCaptor.capture());
-        instance.requestShowContent(mMockDestinationGurl, DUMMY_SHEET_TITLE);
-
-        assertFalse(mWebContentsDelegateCaptor.getValue().shouldCreateWebContents(
-                mMockDestinationGurl));
-
-        verify(mMockNavigationController, times(2)).loadUrl(any(LoadUrlParams.class));
-    }
-
-    @Test
-    public void testGetTopControlsHeight() {
-        MerchantTrustDetailsTabMediator instance = getMediatorUnderTest();
-        instance.init(mMockWebContents, mMockContentView, mMockSheetContent, mMockProfile);
-        verify(mMockSheetContent, times(1))
-                .attachWebContents(eq(mMockWebContents), eq(mMockContentView),
-                        mWebContentsDelegateCaptor.capture());
-        instance.requestShowContent(mMockDestinationGurl, DUMMY_SHEET_TITLE);
-
-        assertEquals(100, mWebContentsDelegateCaptor.getValue().getTopControlsHeight());
-    }
-
-    @Test
-    public void testLoadingStateChanges() {
-        MerchantTrustDetailsTabMediator instance = getMediatorUnderTest();
-        instance.init(mMockWebContents, mMockContentView, mMockSheetContent, mMockProfile);
-        verify(mMockSheetContent, times(1))
-                .attachWebContents(eq(mMockWebContents), eq(mMockContentView),
-                        mWebContentsDelegateCaptor.capture());
-        instance.requestShowContent(mMockDestinationGurl, DUMMY_SHEET_TITLE);
-
-        // Loading state.
-        doReturn(true).when(mMockWebContents).isLoading();
-        mWebContentsDelegateCaptor.getValue().loadingStateChanged(true);
-
-        verify(mMockSheetContent, times(1)).setProgress(eq(0f));
-        verify(mMockSheetContent, times(1)).setProgressVisible(eq(true));
-    }
-
-    @Test
-    public void testWebContentsObserverLoadProgressChanged() {
-        MerchantTrustDetailsTabMediator instance = getMediatorUnderTest();
-        instance.init(mMockWebContents, mMockContentView, mMockSheetContent, mMockProfile);
-        verify(mMockWebContents, times(1)).addObserver(mWebContentsObserverCaptor.capture());
-
-        float progress = 0.2f;
-        mWebContentsObserverCaptor.getValue().loadProgressChanged(progress);
-        verify(mMockSheetContent, times(1)).setProgress(eq(progress));
-    }
-
-    @Test
-    public void testWebContentsObserverDidStartNavigation() {
-        MerchantTrustDetailsTabMediator instance = getMediatorUnderTest();
-        instance.init(mMockWebContents, mMockContentView, mMockSheetContent, mMockProfile);
-        verify(mMockWebContents, times(1)).addObserver(mWebContentsObserverCaptor.capture());
-
-        mWebContentsObserverCaptor.getValue().didStartNavigation(mMockNavigationHandle);
-        verify(mMockMetrics, times(1)).recordNavigateLinkOnBottomSheet();
-    }
-
-    @Test
-    public void testWebContentsObserverTitleWasSet() {
-        MerchantTrustDetailsTabMediator instance = getMediatorUnderTest();
-        instance.init(mMockWebContents, mMockContentView, mMockSheetContent, mMockProfile);
-        verify(mMockWebContents, times(1)).addObserver(mWebContentsObserverCaptor.capture());
-
-        MerchantViewerConfig.TRUST_SIGNALS_SHEET_USE_PAGE_TITLE.setForTesting(false);
-        mWebContentsObserverCaptor.getValue().titleWasSet(DUMMY_SHEET_TITLE);
-        verify(mMockSheetContent, times(0)).setTitle(eq(DUMMY_SHEET_TITLE));
-
-        MerchantViewerConfig.TRUST_SIGNALS_SHEET_USE_PAGE_TITLE.setForTesting(true);
-        mWebContentsObserverCaptor.getValue().titleWasSet(DUMMY_SHEET_TITLE);
-        verify(mMockSheetContent, times(1)).setTitle(eq(DUMMY_SHEET_TITLE));
-    }
-
-    @Test
-    public void testWebContentsObserverDidFinishNavigation() {
-        MerchantTrustDetailsTabMediator instance = getMediatorUnderTest();
-        instance.init(mMockWebContents, mMockContentView, mMockSheetContent, mMockProfile);
-        verify(mMockWebContents, times(1)).addObserver(mWebContentsObserverCaptor.capture());
-
-        doReturn(false).when(mMockNavigationHandle).isInMainFrame();
-        doReturn(false).when(mMockNavigationHandle).hasCommitted();
-        mWebContentsObserverCaptor.getValue().didFinishNavigation(mMockNavigationHandle);
-        verify(mMockSheetContent, times(0)).setUrl(any(GURL.class));
-
-        doReturn(true).when(mMockNavigationHandle).isInMainFrame();
-        doReturn(false).when(mMockNavigationHandle).hasCommitted();
-        mWebContentsObserverCaptor.getValue().didFinishNavigation(mMockNavigationHandle);
-        verify(mMockSheetContent, times(0)).setUrl(any(GURL.class));
-
-        doReturn(false).when(mMockNavigationHandle).isInMainFrame();
-        doReturn(true).when(mMockNavigationHandle).hasCommitted();
-        mWebContentsObserverCaptor.getValue().didFinishNavigation(mMockNavigationHandle);
-        verify(mMockSheetContent, times(0)).setUrl(any(GURL.class));
-
-        doReturn(true).when(mMockNavigationHandle).isInMainFrame();
-        doReturn(true).when(mMockNavigationHandle).hasCommitted();
-        mWebContentsObserverCaptor.getValue().didFinishNavigation(mMockNavigationHandle);
-        verify(mMockSheetContent, times(1)).setUrl(any(GURL.class));
-    }
-
-    private MerchantTrustDetailsTabMediator getMediatorUnderTest() {
-        return new MerchantTrustDetailsTabMediator(mMockBottomSheetController, 100, mMockMetrics);
-    }
-}
\ No newline at end of file
diff --git a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsCoordinatorTest.java b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsCoordinatorTest.java
index f3847e5..0d9febc 100644
--- a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsCoordinatorTest.java
+++ b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsCoordinatorTest.java
@@ -135,7 +135,7 @@
     private MerchantTrustSignalsEvent mMockMerchantTrustSignalsEvent;
 
     @Mock
-    private MerchantTrustDetailsTabCoordinator mMockDetailsTabCoordinator;
+    private MerchantTrustBottomSheetCoordinator mMockDetailsTabCoordinator;
 
     @Captor
     private ArgumentCaptor<Callback> mOnMessageEnqueuedCallbackCaptor;
diff --git a/chrome/browser/extensions/api/automation/automation_apitest.cc b/chrome/browser/extensions/api/automation/automation_apitest.cc
index af91af9..aa29d76d 100644
--- a/chrome/browser/extensions/api/automation/automation_apitest.cc
+++ b/chrome/browser/extensions/api/automation/automation_apitest.cc
@@ -370,8 +370,8 @@
 
 IN_PROC_BROWSER_TEST_F(AutomationApiTest, EnumValidity) {
   StartEmbeddedTestServer();
-  ASSERT_TRUE(RunExtensionTest(
-      {.name = "automation/tests/tabs", .page_url = "enum_validity.html"}))
+  ASSERT_TRUE(RunExtensionTest("automation/tests/tabs",
+                               {.page_url = "enum_validity.html"}))
       << message_;
 }
 
diff --git a/chrome/browser/extensions/api/language_settings_private/language_settings_private_api.cc b/chrome/browser/extensions/api/language_settings_private/language_settings_private_api.cc
index 24fe99a..0a7f561 100644
--- a/chrome/browser/extensions/api/language_settings_private/language_settings_private_api.cc
+++ b/chrome/browser/extensions/api/language_settings_private/language_settings_private_api.cc
@@ -7,14 +7,15 @@
 #include <algorithm>
 #include <map>
 #include <memory>
+#include <set>
 #include <string>
-#include <unordered_set>
 #include <utility>
 #include <vector>
 
 #include "base/containers/contains.h"
 #include "base/containers/flat_set.h"
 #include "base/feature_list.h"
+#include "base/strings/string_piece.h"
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
@@ -43,6 +44,7 @@
 #include "components/translate/core/browser/translate_download_manager.h"
 #include "components/translate/core/browser/translate_prefs.h"
 #include "third_party/icu/source/i18n/unicode/coll.h"
+#include "ui/base/ime/chromeos/input_method_descriptor.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/l10n/l10n_util_collator.h"
 
@@ -71,35 +73,36 @@
 const size_t kNumImesToAutoEnableImeMenu = 2;
 
 // Returns the set of IDs of all enabled IMEs.
-std::unordered_set<std::string> GetEnabledIMEs(
+base::flat_set<std::string> GetEnabledIMEs(
     scoped_refptr<InputMethodManager::State> ime_state) {
-  const std::vector<std::string>& ime_ids(ime_state->GetActiveInputMethodIds());
-  return std::unordered_set<std::string>(ime_ids.begin(), ime_ids.end());
+  return ime_state->GetAllowedInputMethods();
 }
 
 // Returns the set of IDs of all allowed IMEs.
-std::unordered_set<std::string> GetAllowedIMEs(
+base::flat_set<std::string> GetAllowedIMEs(
     scoped_refptr<InputMethodManager::State> ime_state) {
-  const std::vector<std::string>& ime_ids(ime_state->GetAllowedInputMethods());
-  return std::unordered_set<std::string>(ime_ids.begin(), ime_ids.end());
+  return ime_state->GetAllowedInputMethods();
 }
 
 // Returns the set of IDs of enabled IMEs for the given pref.
-std::unordered_set<std::string> GetIMEsFromPref(PrefService* prefs,
-                                                const char* pref_name) {
-  std::vector<std::string> enabled_imes =
-      base::SplitString(prefs->GetString(pref_name), ",", base::TRIM_WHITESPACE,
-                        base::SPLIT_WANT_NONEMPTY);
-  return std::unordered_set<std::string>(enabled_imes.begin(),
-                                         enabled_imes.end());
+base::flat_set<std::string> GetIMEsFromPref(PrefService* prefs,
+                                            const char* pref_name) {
+  return base::SplitString(prefs->GetString(pref_name), ",",
+                           base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
 }
 
 // Returns the set of allowed UI locales.
-std::unordered_set<std::string> GetAllowedLanguages(PrefService* prefs) {
-  std::unordered_set<std::string> allowed_languages;
-  for (const base::Value& locale_value :
-       prefs->GetList(prefs::kAllowedLanguages)->GetList()) {
-    allowed_languages.insert(locale_value.GetString());
+base::flat_set<std::string> GetAllowedLanguages(PrefService* prefs) {
+  const auto& allowed_languages_values =
+      prefs->GetList(prefs::kAllowedLanguages)->GetList();
+
+  // Uses the O(n log n) base::flat_set constructor by pushing back to a vector
+  // instead of inserting into a set.
+  std::vector<std::string> allowed_languages;
+  allowed_languages.reserve(allowed_languages_values.size());
+
+  for (const base::Value& locale_value : allowed_languages_values) {
+    allowed_languages.push_back(locale_value.GetString());
   }
 
   return allowed_languages;
@@ -113,14 +116,15 @@
 std::vector<std::string> GetSortedComponentIMEs(
     InputMethodManager* manager,
     scoped_refptr<InputMethodManager::State> ime_state,
-    const std::unordered_set<std::string>& component_ime_set,
+    const base::flat_set<std::string>& component_ime_set,
     PrefService* prefs) {
   std::vector<std::string> enabled_languages =
       base::SplitString(prefs->GetString(language::prefs::kPreferredLanguages),
                         ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
 
   // Duplicate set for membership testing.
-  std::unordered_set<std::string> available_component_imes(component_ime_set);
+  std::set<std::string> available_component_imes(component_ime_set.begin(),
+                                                 component_ime_set.end());
   std::vector<std::string> component_ime_list;
 
   for (const auto& language_code : enabled_languages) {
@@ -132,7 +136,7 @@
     // Append the enabled ones to the new list. Also remove them from the set
     // so they aren't duplicated for other languages.
     for (const auto& input_method_id : input_method_ids) {
-      if (available_component_imes.count(input_method_id)) {
+      if (base::Contains(available_component_imes, input_method_id)) {
         component_ime_list.push_back(input_method_id);
         available_component_imes.erase(input_method_id);
       }
@@ -148,14 +152,14 @@
 // Sorts the third-party IMEs by the order of their associated languages.
 std::vector<std::string> GetSortedThirdPartyIMEs(
     scoped_refptr<InputMethodManager::State> ime_state,
-    const std::unordered_set<std::string>& third_party_ime_set,
+    const base::flat_set<std::string>& third_party_ime_set,
     PrefService* prefs) {
   std::vector<std::string> ime_list;
   std::string preferred_languages =
       prefs->GetString(language::prefs::kPreferredLanguages);
-  std::vector<std::string> enabled_languages =
-      base::SplitString(preferred_languages, ",", base::TRIM_WHITESPACE,
-                        base::SPLIT_WANT_NONEMPTY);
+  std::vector<base::StringPiece> enabled_languages =
+      base::SplitStringPiece(preferred_languages, ",", base::TRIM_WHITESPACE,
+                             base::SPLIT_WANT_NONEMPTY);
 
   // Add the fake language for ARC IMEs at the very last of the list. Unlike
   // Chrome OS IMEs, these ARC ones are not associated with any (real) language.
@@ -165,34 +169,35 @@
   ime_state->GetInputMethodExtensions(&descriptors);
 
   // Filter out the IMEs not in |third_party_ime_set|.
-  auto it = descriptors.begin();
-  while (it != descriptors.end()) {
-    if (third_party_ime_set.count(it->id()) == 0)
-      it = descriptors.erase(it);
-    else
-      it++;
-  }
+  descriptors.erase(
+      std::remove_if(
+          descriptors.begin(), descriptors.end(),
+          [&third_party_ime_set](const InputMethodDescriptor& descriptor) {
+            return !third_party_ime_set.contains(descriptor.id());
+          }),
+      descriptors.end());
+
+  // A set of the elements of |ime_list|.
+  std::set<std::string> ime_set;
 
   // For each language, add any candidate IMEs that support it.
   for (const auto& language : enabled_languages) {
-    auto it = descriptors.begin();
-    while (it != descriptors.end() && descriptors.size()) {
-      if (third_party_ime_set.count(it->id()) &&
-          base::Contains(it->language_codes(), language)) {
-        ime_list.push_back(it->id());
-        // Remove the added descriptor from the candidate list.
-        it = descriptors.erase(it);
-      } else {
-        it++;
+    for (const InputMethodDescriptor& descriptor : descriptors) {
+      const std::string& id = descriptor.id();
+      if (!base::Contains(ime_set, id) &&
+          base::Contains(descriptor.language_codes(), language)) {
+        ime_list.push_back(id);
+        ime_set.insert(id);
       }
     }
   }
 
   // Add the rest of the third party IMEs
-  auto item = descriptors.begin();
-  while (item != descriptors.end()) {
-    ime_list.push_back(item->id());
-    item = descriptors.erase(item);
+  for (const InputMethodDescriptor& descriptor : descriptors) {
+    const std::string& id = descriptor.id();
+    if (!base::Contains(ime_set, id)) {
+      ime_list.push_back(id);
+    }
   }
 
   return ime_list;
@@ -247,7 +252,7 @@
   // Build the language list.
   language_list_->Clear();
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-  const std::unordered_set<std::string> allowed_ui_locales(GetAllowedLanguages(
+  const base::flat_set<std::string> allowed_ui_locales(GetAllowedLanguages(
       Profile::FromBrowserContext(browser_context())->GetPrefs()));
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
   for (const auto& entry : languages) {
@@ -271,7 +276,7 @@
     }
 #if BUILDFLAG(IS_CHROMEOS_ASH)
     if (!allowed_ui_locales.empty() &&
-        allowed_ui_locales.count(language.code) == 0) {
+        !base::Contains(allowed_ui_locales, language.code)) {
       language.is_prohibited_language = std::make_unique<bool>(true);
     }
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
@@ -725,8 +730,8 @@
   if (!ime_state.get())
     return;
 
-  const std::unordered_set<std::string> active_ids(GetEnabledIMEs(ime_state));
-  const std::unordered_set<std::string> allowed_ids(GetAllowedIMEs(ime_state));
+  const base::flat_set<std::string> active_ids(GetEnabledIMEs(ime_state));
+  const base::flat_set<std::string> allowed_ids(GetAllowedIMEs(ime_state));
 
   // Collator used to sort display names in the given locale.
   UErrorCode error = U_ZERO_ERROR;
@@ -748,14 +753,14 @@
     input_method.display_name = util->GetLocalizedDisplayName(descriptor);
     input_method.language_codes = descriptor.language_codes();
     input_method.tags = GetInputMethodTags(&input_method);
-    if (active_ids.count(input_method.id) > 0)
+    if (base::Contains(active_ids, input_method.id))
       input_method.enabled = std::make_unique<bool>(true);
     if (descriptor.options_page_url().is_valid())
       input_method.has_options_page = std::make_unique<bool>(true);
     if (!allowed_ids.empty() &&
         (util->IsKeyboardLayout(input_method.id) ||
          chromeos::extension_ime_util::IsArcIME(input_method.id)) &&
-        allowed_ids.count(input_method.id) == 0) {
+        !base::Contains(allowed_ids, input_method.id)) {
       input_method.is_prohibited_by_policy = std::make_unique<bool>(true);
     }
     input_map[base::UTF8ToUTF16(util->GetLocalizedDisplayName(descriptor))] =
@@ -836,7 +841,7 @@
                               : prefs::kLanguageEnabledImes;
 
   // Get the input methods we are adding to.
-  std::unordered_set<std::string> input_method_set(
+  base::flat_set<std::string> input_method_set(
       GetIMEsFromPref(prefs, pref_name));
 
   // Add the new input method.
@@ -867,7 +872,7 @@
     const char* other_ime_list_pref_name = is_component_extension_ime
                                                ? prefs::kLanguageEnabledImes
                                                : prefs::kLanguagePreloadEngines;
-    std::unordered_set<std::string> other_input_method_set(
+    base::flat_set<std::string> other_input_method_set(
         GetIMEsFromPref(prefs, other_ime_list_pref_name));
     if (input_method_set.size() + other_input_method_set.size() ==
         kNumImesToAutoEnableImeMenu) {
diff --git a/chrome/browser/extensions/api/networking_private/networking_private_service_client_apitest.cc b/chrome/browser/extensions/api/networking_private/networking_private_service_client_apitest.cc
index 7fcaced..e0320df6 100644
--- a/chrome/browser/extensions/api/networking_private/networking_private_service_client_apitest.cc
+++ b/chrome/browser/extensions/api/networking_private/networking_private_service_client_apitest.cc
@@ -48,8 +48,8 @@
 
   bool RunNetworkingSubtest(const std::string& subtest) {
     const std::string page_url = "main.html?" + subtest;
-    return RunExtensionTest({.name = "networking_private/service_client",
-                             .page_url = page_url.c_str()},
+    return RunExtensionTest("networking_private/service_client",
+                            {.page_url = page_url.c_str()},
                             {.load_as_component = true});
   }
 
diff --git a/chrome/browser/extensions/api/virtual_keyboard_private/virtual_keyboard_private_apitest.cc b/chrome/browser/extensions/api/virtual_keyboard_private/virtual_keyboard_private_apitest.cc
index ff950ad..bdab800 100644
--- a/chrome/browser/extensions/api/virtual_keyboard_private/virtual_keyboard_private_apitest.cc
+++ b/chrome/browser/extensions/api/virtual_keyboard_private/virtual_keyboard_private_apitest.cc
@@ -83,7 +83,7 @@
   CopyBitmapItem();
   CopyFileItem();
 
-  ASSERT_TRUE(RunExtensionTest({.name = "virtual_keyboard_private"},
+  ASSERT_TRUE(RunExtensionTest("virtual_keyboard_private", {},
                                {.load_as_component = true}))
       << message_;
 }
diff --git a/chrome/browser/extensions/extension_apitest.cc b/chrome/browser/extensions/extension_apitest.cc
index 602dab78..3cbec1324 100644
--- a/chrome/browser/extensions/extension_apitest.cc
+++ b/chrome/browser/extensions/extension_apitest.cc
@@ -92,15 +92,6 @@
   return RunExtensionTest(extension_name, {}, {});
 }
 
-bool ExtensionApiTest::RunExtensionTest(const RunOptions& run_options) {
-  return RunExtensionTest(run_options.name, run_options, {});
-}
-
-bool ExtensionApiTest::RunExtensionTest(const RunOptions& run_options,
-                                        const LoadOptions& load_options) {
-  return RunExtensionTest(run_options.name, run_options, load_options);
-}
-
 bool ExtensionApiTest::RunExtensionTest(const char* extension_name,
                                         const RunOptions& run_options) {
   return RunExtensionTest(extension_name, run_options, {});
diff --git a/chrome/browser/extensions/extension_apitest.h b/chrome/browser/extensions/extension_apitest.h
index 840ae6a..72b3f6b 100644
--- a/chrome/browser/extensions/extension_apitest.h
+++ b/chrome/browser/extensions/extension_apitest.h
@@ -34,10 +34,6 @@
 class ExtensionApiTest : public ExtensionBrowserTest {
  public:
   struct RunOptions {
-    // Load the specified extension for the test. This is a subdirectory
-    // in "chrome/test/data/extensions/api_test".
-    const char* name = nullptr;
-
     // Start the test by opening the specified page URL. This must be an
     // absolute URL.
     const char* page_url = nullptr;
diff --git a/chrome/browser/extensions/extension_keybinding_apitest.cc b/chrome/browser/extensions/extension_keybinding_apitest.cc
index 66d0851..3afe18c 100644
--- a/chrome/browser/extensions/extension_keybinding_apitest.cc
+++ b/chrome/browser/extensions/extension_keybinding_apitest.cc
@@ -1037,7 +1037,7 @@
 
   bool is_incognito_enabled = GetParam();
 
-  ASSERT_TRUE(RunExtensionTest({.name = "keybinding/basics"},
+  ASSERT_TRUE(RunExtensionTest("keybinding/basics", {},
                                {.allow_in_incognito = is_incognito_enabled}))
       << message_;
 
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 8c96f7c..bc52afd 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -396,7 +396,7 @@
     "expiry_milestone": 86
   },
   {
-    "name":"autofill-suggest-virtual-cards-only-on-full-form-detection",
+    "name":"autofill-suggest-virtual-cards-on-incomplete-form",
     "owners": ["siashah", "siyua"],
     "expiry_milestone":98
   },
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index a6c544b..403e23e 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -324,10 +324,11 @@
     "it will offer to save it. If saved, it will be offered for filling in "
     "fields which expect a VPA.";
 
-const char kAutofillSuggestVirtualCardsOnlyOnFullFormDetectionName[] =
-    "Autofill suggests virtual cards only on full form detection";
-const char kAutofillSuggestVirtualCardsOnlyOnFullFormDetectionDescription[] =
-    "When enabled, merchant bound virtual cards will be suggested only if all "
+const char kAutofillSuggestVirtualCardsOnIncompleteFormName[] =
+    "Autofill suggests virtual cards on incomplete forms";
+const char kAutofillSuggestVirtualCardsOnIncompleteFormDescription[] =
+    "When enabled, merchant bound virtual cards will be suggested even if not "
+    "all "
     "of the card number, exp date and CVC fields are detected in a payment "
     "form.";
 
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 759d5ebc..af39a98 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -199,9 +199,8 @@
 extern const char kAutofillSaveAndFillVPAName[];
 extern const char kAutofillSaveAndFillVPADescription[];
 
-extern const char kAutofillSuggestVirtualCardsOnlyOnFullFormDetectionName[];
-extern const char
-    kAutofillSuggestVirtualCardsOnlyOnFullFormDetectionDescription[];
+extern const char kAutofillSuggestVirtualCardsOnIncompleteFormName[];
+extern const char kAutofillSuggestVirtualCardsOnIncompleteFormDescription[];
 
 extern const char kAutofillUseImprovedLabelDisambiguationName[];
 extern const char kAutofillUseImprovedLabelDisambiguationDescription[];
diff --git a/chrome/browser/history_clusters/history_clusters_tab_helper_unittest.cc b/chrome/browser/history_clusters/history_clusters_tab_helper_unittest.cc
index e6c50ec..b751d6c9 100644
--- a/chrome/browser/history_clusters/history_clusters_tab_helper_unittest.cc
+++ b/chrome/browser/history_clusters/history_clusters_tab_helper_unittest.cc
@@ -66,16 +66,17 @@
     helper_ = HistoryClustersTabHelper::FromWebContents(web_contents());
     ASSERT_TRUE(helper_);
 
-    history_clusters_service_test_api_ =
-        std::make_unique<history_clusters::HistoryClustersServiceTestApi>(
-            HistoryClustersServiceFactory::GetForBrowserContext(
-                web_contents()->GetBrowserContext()));
-    ASSERT_TRUE(history_clusters_service_test_api_);
-
     ASSERT_TRUE(profile()->CreateHistoryService());
     ASSERT_TRUE(history_service_ = HistoryServiceFactory::GetForProfile(
                     profile(), ServiceAccessType::IMPLICIT_ACCESS));
 
+    history_clusters_service_test_api_ =
+        std::make_unique<history_clusters::HistoryClustersServiceTestApi>(
+            HistoryClustersServiceFactory::GetForBrowserContext(
+                web_contents()->GetBrowserContext()),
+            history_service_);
+    ASSERT_TRUE(history_clusters_service_test_api_);
+
     BookmarkModelFactory::GetInstance()->SetTestingFactory(
         web_contents()->GetBrowserContext(),
         BookmarkModelFactory::GetDefaultFactory());
@@ -239,8 +240,8 @@
 
   DeleteContents();
   ASSERT_EQ(GetVisits().size(), 2u);
-  EXPECT_EQ(GetVisits()[0].url_row.url(), GURL{"https://github.com"});
-  EXPECT_EQ(GetVisits()[1].url_row.url(), GURL{"https://google.com"});
+  EXPECT_EQ(GetVisits()[0].url_row.url(), GURL{"https://google.com"});
+  EXPECT_EQ(GetVisits()[1].url_row.url(), GURL{"https://github.com"});
 }
 
 // For the remaining tests, all navigations will have at least 1 history visit.
@@ -271,19 +272,21 @@
   AddToHistory(GURL{"https://github.com"});
   helper_->OnUpdatedHistoryForNavigation(0, GURL{"https://google.com"});
   helper_->OnUpdatedHistoryForNavigation(1, GURL{"https://github.com"});
-  EXPECT_TRUE(GetVisits().empty());
 
   // Bookmarked after navigation ends, but before its history request resolved.
   AddBookmark(GURL{"https://google.com"});
   history::BlockUntilHistoryProcessesPendingRequests(history_service_);
+  EXPECT_EQ(GetVisits().size(), 1u);
   DeleteContents();
   // Bookmarked after navigation ends and its history request resolved.
   AddBookmark(GURL{"https://github.com"});
-  ASSERT_EQ(GetVisits().size(), 2u);
-  EXPECT_EQ(GetVisits()[0].url_row.url(), GURL{"https://google.com"});
-  EXPECT_TRUE(GetVisits()[0].context_annotations.is_new_bookmark);
-  EXPECT_EQ(GetVisits()[1].url_row.url(), GURL{"https://github.com"});
-  EXPECT_FALSE(GetVisits()[1].context_annotations.is_new_bookmark);
+  auto visits = GetVisits();
+  ASSERT_EQ(visits.size(), 2u);
+  EXPECT_EQ(visits[0].url_row.url(), GURL{"https://github.com"});
+  EXPECT_FALSE(visits[0].context_annotations.is_new_bookmark);
+  EXPECT_EQ(visits[1].url_row.url(), GURL{"https://google.com"});
+  // TODO(manukh): Re-enable line after is_new_bookmark peristence fixed.
+  // EXPECT_TRUE(visits[1].context_annotations.is_new_bookmark);
 }
 
 // History -> copy -> history resolve -> history -> history -> copy -> destroy
@@ -316,15 +319,16 @@
   history::BlockUntilHistoryProcessesPendingRequests(history_service_);
   helper_->OnOmniboxUrlCopied();
   ASSERT_EQ(GetVisits().size(), 2u);
-  EXPECT_FALSE(GetVisits()[1].context_annotations.omnibox_url_copied);
+  // This looks weird, but it's correct because the most recent visit is first.
+  EXPECT_FALSE(GetVisits()[0].context_annotations.omnibox_url_copied);
 
   DeleteContents();
   ASSERT_EQ(GetVisits().size(), 3u);
-  EXPECT_EQ(GetVisits()[0].url_row.url(), GURL{"https://github.com"});
+  EXPECT_EQ(GetVisits()[0].url_row.url(), GURL{"https://gmail.com"});
   EXPECT_TRUE(GetVisits()[0].context_annotations.omnibox_url_copied);
   EXPECT_EQ(GetVisits()[1].url_row.url(), GURL{"https://google.com"});
   EXPECT_FALSE(GetVisits()[1].context_annotations.omnibox_url_copied);
-  EXPECT_EQ(GetVisits()[2].url_row.url(), GURL{"https://gmail.com"});
+  EXPECT_EQ(GetVisits()[2].url_row.url(), GURL{"https://github.com"});
   EXPECT_TRUE(GetVisits()[2].context_annotations.omnibox_url_copied);
 }
 
@@ -426,7 +430,6 @@
   EXPECT_TRUE(GetVisits().empty());
   OnDestroyWebContentsObserver test_web_contents_observer(
       web_contents(), base::BindLambdaForTesting([&]() {
-        EXPECT_TRUE(GetVisits().empty());
         AddBookmark(GURL{"https://github.com"});
         history::BlockUntilHistoryProcessesPendingRequests(history_service_);
         run_loop_quit_.Run();
@@ -436,7 +439,8 @@
   run_loop_.Run();
   ASSERT_EQ(GetVisits().size(), 1u);
   EXPECT_EQ(GetVisits()[0].url_row.url(), GURL{"https://github.com"});
-  EXPECT_TRUE(GetVisits()[0].context_annotations.is_new_bookmark);
+  // TODO(manukh): Re-enable line after is_new_bookmark peristence fixed.
+  // EXPECT_TRUE(GetVisits()[0].context_annotations.is_new_bookmark);
   EXPECT_EQ(GetVisits()[0].context_annotations.page_end_reason,
             page_load_metrics::PageEndReason::END_OTHER);
 }
diff --git a/chrome/browser/mac/install_from_dmg.mm b/chrome/browser/mac/install_from_dmg.mm
index 9c620f11..21965bf 100644
--- a/chrome/browser/mac/install_from_dmg.mm
+++ b/chrome/browser/mac/install_from_dmg.mm
@@ -25,6 +25,7 @@
 #include "base/mac/bundle_locations.h"
 #include "base/mac/foundation_util.h"
 #include "base/mac/mac_logging.h"
+#include "base/mac/mac_util.h"
 #include "base/mac/mach_logging.h"
 #include "base/mac/scoped_authorizationref.h"
 #include "base/mac/scoped_cftyperef.h"
@@ -57,19 +58,18 @@
 namespace {
 
 // Given an io_service_t (expected to be of class IOMedia), walks the ancestor
-// chain, returning the closest ancestor that implements class IOHDIXHDDrive,
-// if any. If no such ancestor is found, returns NULL. Following the "copy"
-// rule, the caller assumes ownership of the returned value.
+// chain, returning the closest ancestor that implements the specified class,
+// if any. If no such ancestor is found, returns IO_OBJECT_NULL. Following
+// the "copy" rule, the caller assumes ownership of the returned value.
 //
-// Note that this looks for a class that inherits from IOHDIXHDDrive, but it
-// will not likely find a concrete IOHDIXHDDrive. It will be
+// Note that this looks for the class by conformance, not equality. The reason
+// for that is that for IOHDIXHDDrive the actual classes found will be
 // IOHDIXHDDriveOutKernel for disk images mounted "out-of-kernel" or
-// IOHDIXHDDriveInKernel for disk images mounted "in-kernel." Out-of-kernel is
-// the default as of Mac OS X 10.5. See the documentation for "hdiutil attach
-// -kernel" for more information.
-io_service_t CopyHDIXDriveServiceForMedia(io_service_t media) {
-  const char disk_image_class[] = "IOHDIXHDDrive";
-
+// IOHDIXHDDriveInKernel for disk images mounted "in-kernel." (See the
+// documentation for "hdiutil attach -kernel" for more information on the
+// distinction.)
+io_service_t CopyDiskImageAncestorForMedia(const char* disk_image_class,
+                                           io_service_t media) {
   // This is highly unlikely. media as passed in is expected to be of class
   // IOMedia. Since the media service's entire ancestor chain will be checked,
   // though, check it as well.
@@ -79,12 +79,9 @@
   }
 
   io_iterator_t iterator_ref;
-  kern_return_t kr =
-      IORegistryEntryCreateIterator(media,
-                                    kIOServicePlane,
-                                    kIORegistryIterateRecursively |
-                                        kIORegistryIterateParents,
-                                    &iterator_ref);
+  kern_return_t kr = IORegistryEntryCreateIterator(
+      media, kIOServicePlane,
+      kIORegistryIterateRecursively | kIORegistryIterateParents, &iterator_ref);
   if (kr != KERN_SUCCESS) {
     MACH_LOG(ERROR, kr) << "IORegistryEntryCreateIterator";
     return IO_OBJECT_NULL;
@@ -99,8 +96,7 @@
   // the image-path property.
   for (base::mac::ScopedIOObject<io_service_t> ancestor(
            IOIteratorNext(iterator));
-       ancestor;
-       ancestor.reset(IOIteratorNext(iterator))) {
+       ancestor; ancestor.reset(IOIteratorNext(iterator))) {
     if (IOObjectConformsTo(ancestor, disk_image_class)) {
       return ancestor.release();
     }
@@ -114,51 +110,106 @@
 // that service is on a disk image. If it is, returns true. If image_path is
 // present, it will be set to the pathname of the disk image file, encoded in
 // filesystem encoding.
+//
+// There are two SPI ways to do this: One is in the DiskImages private
+// framework: DIHLCopyImageForVolume(). One is a set of keys in
+// CFURLPriv: _kCFURLVolumeIsDiskImageKey and _kCFURLDiskImageBackingURLKey.
+// However, because downstream users want to use Chromium as a base for code in
+// the MAS, neither are used here. The request for a real API is FB9139935.
 bool MediaResidesOnDiskImage(io_service_t media, std::string* image_path) {
   if (image_path) {
     image_path->clear();
   }
 
-  base::mac::ScopedIOObject<io_service_t> hdix_drive(
-      CopyHDIXDriveServiceForMedia(media));
-  if (!hdix_drive) {
-    return false;
-  }
+  if (base::mac::IsAtLeastOS12()) {
+    // Starting with macOS 12 "Monterey", the IOMedia has an ancestor of
+    // type "AppleDiskImageDevice" that has a property "DiskImageURL" of string
+    // type.
 
-  if (image_path) {
-    base::ScopedCFTypeRef<CFTypeRef> image_path_cftyperef(
-        IORegistryEntryCreateCFProperty(
-            hdix_drive, CFSTR("image-path"), NULL, 0));
-    if (!image_path_cftyperef) {
-      LOG(ERROR) << "IORegistryEntryCreateCFProperty";
-      return true;
-    }
-    if (CFGetTypeID(image_path_cftyperef) != CFDataGetTypeID()) {
-      base::ScopedCFTypeRef<CFStringRef> observed_type_cf(
-          CFCopyTypeIDDescription(CFGetTypeID(image_path_cftyperef)));
-      std::string observed_type;
-      if (observed_type_cf) {
-        observed_type.assign(", observed ");
-        observed_type.append(base::SysCFStringRefToUTF8(observed_type_cf));
+    base::mac::ScopedIOObject<io_service_t> di_device(
+        CopyDiskImageAncestorForMedia("AppleDiskImageDevice", media));
+    if (di_device) {
+      if (image_path) {
+        base::ScopedCFTypeRef<CFTypeRef> disk_image_url_cftyperef(
+            IORegistryEntryCreateCFProperty(di_device, CFSTR("DiskImageURL"),
+                                            nullptr, 0));
+        if (!disk_image_url_cftyperef) {
+          LOG(ERROR)
+              << "IORegistryEntryCreateCFProperty failed for DiskImageURL";
+          return true;
+        }
+
+        CFStringRef disk_image_url_string =
+            base::mac::CFCast<CFStringRef>(disk_image_url_cftyperef.get());
+        if (!disk_image_url_string) {
+          base::ScopedCFTypeRef<CFStringRef> observed_type_cf(
+              CFCopyTypeIDDescription(CFGetTypeID(disk_image_url_cftyperef)));
+          LOG(ERROR) << "DiskImageURL: expected CFString, observed "
+                     << base::SysCFStringRefToUTF8(observed_type_cf);
+          return true;
+        }
+
+        base::ScopedCFTypeRef<CFURLRef> disk_image_url(CFURLCreateWithString(
+            kCFAllocatorDefault, disk_image_url_string, nullptr));
+        if (!disk_image_url) {
+          LOG(ERROR) << "CFURLCreateWithString failed";
+          return true;
+        }
+
+        base::ScopedCFTypeRef<CFStringRef> disk_image_path(
+            CFURLCopyFileSystemPath(disk_image_url, kCFURLPOSIXPathStyle));
+        if (!disk_image_path) {
+          LOG(ERROR) << "CFURLCopyFileSystemPath failed";
+          return true;
+        }
+
+        *image_path = base::SysCFStringRefToUTF8(disk_image_path);
       }
-      LOG(ERROR) << "image-path: expected CFData, observed " << observed_type;
-      return true;
-    }
 
-    CFDataRef image_path_data = static_cast<CFDataRef>(
-        image_path_cftyperef.get());
-    CFIndex length = CFDataGetLength(image_path_data);
-    if (length <= 0) {
-      LOG(ERROR) << "image_path_data is unexpectedly empty";
       return true;
     }
-    char* image_path_c = base::WriteInto(image_path, length + 1);
-    CFDataGetBytes(image_path_data,
-                   CFRangeMake(0, length),
-                   reinterpret_cast<UInt8*>(image_path_c));
+  } else {
+    // From the mists of time through macOS 11 "Big Sur", the IOMedia has an
+    // ancestor of type "IOHDIXHDDrive" that has a property "image-path" of data
+    // type.
+
+    base::mac::ScopedIOObject<io_service_t> hdix_drive(
+        CopyDiskImageAncestorForMedia("IOHDIXHDDrive", media));
+    if (hdix_drive) {
+      if (image_path) {
+        base::ScopedCFTypeRef<CFTypeRef> image_path_cftyperef(
+            IORegistryEntryCreateCFProperty(hdix_drive, CFSTR("image-path"),
+                                            nullptr, 0));
+        if (!image_path_cftyperef) {
+          LOG(ERROR) << "IORegistryEntryCreateCFProperty failed for image-path";
+          return true;
+        }
+
+        CFDataRef image_path_data =
+            base::mac::CFCast<CFDataRef>(image_path_cftyperef.get());
+        if (!image_path_data) {
+          base::ScopedCFTypeRef<CFStringRef> observed_type_cf(
+              CFCopyTypeIDDescription(CFGetTypeID(image_path_cftyperef)));
+          LOG(ERROR) << "image-path: expected CFData, observed "
+                     << base::SysCFStringRefToUTF8(observed_type_cf);
+          return true;
+        }
+
+        CFIndex length = CFDataGetLength(image_path_data);
+        if (length <= 0) {
+          LOG(ERROR) << "image_path_data is unexpectedly empty";
+          return true;
+        }
+        char* image_path_c = base::WriteInto(image_path, length + 1);
+        CFDataGetBytes(image_path_data, CFRangeMake(0, length),
+                       reinterpret_cast<UInt8*>(image_path_c));
+      }
+
+      return true;
+    }
   }
 
-  return true;
+  return false;
 }
 
 // Returns true if |path| is located on a read-only filesystem of a disk
diff --git a/chrome/browser/media/webrtc/capture_handle_browsertest.cc b/chrome/browser/media/webrtc/capture_handle_browsertest.cc
index 5d31d1c..0da3428 100644
--- a/chrome/browser/media/webrtc/capture_handle_browsertest.cc
+++ b/chrome/browser/media/webrtc/capture_handle_browsertest.cc
@@ -370,9 +370,18 @@
   EXPECT_EQ(capturing_tab.ReadCaptureHandleFromSettings(), kNoCaptureHandle);
 }
 
+// TODO(crbug.com/1217873): Test disabled on Mac due to multiple failing bots.
+#if defined(OS_MAC)
+#define MAYBE_HandleNotExposedIfTopLevelAllowlistedButCallingFrameNotAllowlisted \
+   DISABLED_HandleNotExposedIfTopLevelAllowlistedButCallingFrameNotAllowlisted
+#else
+#define MAYBE_HandleNotExposedIfTopLevelAllowlistedButCallingFrameNotAllowlisted \
+   HandleNotExposedIfTopLevelAllowlistedButCallingFrameNotAllowlisted
+#endif
+
 IN_PROC_BROWSER_TEST_F(
     CaptureHandleBrowserTest,
-    HandleNotExposedIfTopLevelAllowlistedButCallingFrameNotAllowlisted) {
+    MAYBE_HandleNotExposedIfTopLevelAllowlistedButCallingFrameNotAllowlisted) {
   TabInfo capturing_tab = SetUpCapturingPage(/*start_capturing=*/false);
 
   const url::Origin& top_level_capturer_origin =
@@ -400,9 +409,18 @@
                                       {top_level_capturer_origin.Serialize()});
 }
 
+// TODO(crbug.com/1217873): Test disabled on Mac due to multiple failing bots.
+#if defined(OS_MAC)
+#define MAYBE_HandleExposedIfCallingFrameAllowlistedEvenIfTopLevelNotAllowlisted \
+   DISABLED_HandleExposedIfCallingFrameAllowlistedEvenIfTopLevelNotAllowlisted
+#else
+#define MAYBE_HandleExposedIfCallingFrameAllowlistedEvenIfTopLevelNotAllowlisted \
+   HandleExposedIfCallingFrameAllowlistedEvenIfTopLevelNotAllowlisted
+#endif
+
 IN_PROC_BROWSER_TEST_F(
     CaptureHandleBrowserTest,
-    HandleExposedIfCallingFrameAllowlistedEvenIfTopLevelNotAllowlisted) {
+    MAYBE_HandleExposedIfCallingFrameAllowlistedEvenIfTopLevelNotAllowlisted) {
   TabInfo capturing_tab = SetUpCapturingPage(/*start_capturing=*/false);
 
   const url::Origin& top_level_capturer_origin =
diff --git a/chrome/browser/metrics/power/battery_level_provider_win.cc b/chrome/browser/metrics/power/battery_level_provider_win.cc
index 79a8959..895e3a8 100644
--- a/chrome/browser/metrics/power/battery_level_provider_win.cc
+++ b/chrome/browser/metrics/power/battery_level_provider_win.cc
@@ -165,9 +165,9 @@
   if (!battery_information.has_value() || !battery_status.has_value())
     return BatteryInterface(true);
 
-  return BatteryInterface({battery_status->PowerState & BATTERY_POWER_ON_LINE,
-                           battery_status->Capacity,
-                           battery_information->FullChargedCapacity});
+  return BatteryInterface(
+      {!!(battery_status->PowerState & BATTERY_POWER_ON_LINE),
+       battery_status->Capacity, battery_information->FullChargedCapacity});
 }
 
 std::vector<BatteryLevelProvider::BatteryInterface>
diff --git a/chrome/browser/page_load_metrics/observers/core/amp_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/core/amp_page_load_metrics_observer.cc
index 0b44b89..d60fed89 100644
--- a/chrome/browser/page_load_metrics/observers/core/amp_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/core/amp_page_load_metrics_observer.cc
@@ -169,7 +169,6 @@
     MaybeRecordAmpDocumentMetrics();
     current_main_frame_nav_info_->subframe_rfh = nullptr;
   }
-
   amp_subframe_info_.erase(rfh);
 }
 
@@ -186,6 +185,26 @@
   it->second.timing = timing.Clone();
 }
 
+void AMPPageLoadMetricsObserver::OnMobileFriendlinessUpdate(
+    const blink::MobileFriendliness& mf) {
+  if (mf == blink::MobileFriendliness() ||
+      current_main_frame_nav_info_ == nullptr ||
+      current_main_frame_nav_info_->subframe_rfh == nullptr)
+    return;
+
+  auto it = amp_subframe_info_.find(current_main_frame_nav_info_->subframe_rfh);
+  if (it == amp_subframe_info_.end())
+    return;
+
+  SubFrameInfo& subframe_info = it->second;
+  if (subframe_info.viewer_url != current_main_frame_nav_info_->url ||
+      !subframe_info.amp_document_loaded) {
+    return;
+  }
+
+  subframe_info.mobile_friendliness = mf;
+}
+
 void AMPPageLoadMetricsObserver::OnSubFrameRenderDataUpdate(
     content::RenderFrameHost* subframe_rfh,
     const page_load_metrics::mojom::FrameRenderDataUpdate& render_data) {
@@ -495,6 +514,7 @@
           page_load_metrics::LayoutShiftUmaValue(
               normalized_cls_data.session_windows_gap1000ms_max5000ms_max_cls));
     }
+    RecordMobileFriendliness(builder);
   } else {
     UMA_HISTOGRAM_COUNTS_100(
         std::string(kHistogramPrefix)
@@ -512,3 +532,49 @@
 
   builder.Record(ukm::UkmRecorder::Get());
 }
+
+void AMPPageLoadMetricsObserver::RecordMobileFriendliness(
+    ukm::builders::AmpPageLoad& builder) {
+  auto it = amp_subframe_info_.find(current_main_frame_nav_info_->subframe_rfh);
+  if (it == amp_subframe_info_.end())
+    return;
+
+  const SubFrameInfo& subframe_info = it->second;
+  if (subframe_info.viewer_url != current_main_frame_nav_info_->url)
+    return;
+
+  if (!subframe_info.amp_document_loaded)
+    return;
+
+  const blink::MobileFriendliness& mf = subframe_info.mobile_friendliness;
+
+  if (mf.viewport_device_width == blink::mojom::ViewportStatus::kYes)
+    builder.SetSubFrame_MobileFriendliness_ViewportDeviceWidth(true);
+  else if (mf.viewport_device_width == blink::mojom::ViewportStatus::kNo)
+    builder.SetSubFrame_MobileFriendliness_ViewportDeviceWidth(false);
+
+  if (mf.allow_user_zoom == blink::mojom::ViewportStatus::kYes)
+    builder.SetSubFrame_MobileFriendliness_AllowUserZoom(true);
+  else if (mf.allow_user_zoom == blink::mojom::ViewportStatus::kNo)
+    builder.SetSubFrame_MobileFriendliness_AllowUserZoom(false);
+
+  if (mf.small_text_ratio != -1)
+    builder.SetSubFrame_MobileFriendliness_SmallTextRatio(mf.small_text_ratio);
+
+  if (mf.viewport_initial_scale_x10 != -1) {
+    builder.SetSubFrame_MobileFriendliness_ViewportInitialScaleX10(
+        page_load_metrics::GetBucketedViewportInitialScale(mf));
+  }
+
+  if (mf.viewport_hardcoded_width != -1) {
+    builder.SetSubFrame_MobileFriendliness_ViewportHardcodedWidth(
+        page_load_metrics::GetBucketedViewportHardcodedWidth(mf));
+  }
+  if (mf.text_content_outside_viewport_percentage != -1) {
+    builder.SetSubFrame_MobileFriendliness_TextContentOutsideViewportPercentage(
+        mf.text_content_outside_viewport_percentage);
+  }
+  if (mf.bad_tap_targets_ratio != -1)
+    builder.SetSubFrame_MobileFriendliness_BadTapTargetsRatio(
+        mf.bad_tap_targets_ratio);
+}
diff --git a/chrome/browser/page_load_metrics/observers/core/amp_page_load_metrics_observer.h b/chrome/browser/page_load_metrics/observers/core/amp_page_load_metrics_observer.h
index 1abbe310..96da923 100644
--- a/chrome/browser/page_load_metrics/observers/core/amp_page_load_metrics_observer.h
+++ b/chrome/browser/page_load_metrics/observers/core/amp_page_load_metrics_observer.h
@@ -16,6 +16,11 @@
 namespace content {
 class NavigationHandle;
 }
+namespace ukm {
+namespace builders {
+class AmpPageLoad;
+}  // namespace builders
+}  // namespace ukm
 
 // Observer responsible for recording metrics for AMP documents. This includes
 // both AMP documents loaded in the main frame, and AMP documents loaded in a
@@ -59,6 +64,8 @@
   void OnTimingUpdate(
       content::RenderFrameHost* subframe_rfh,
       const page_load_metrics::mojom::PageLoadTiming& timing) override;
+  void OnMobileFriendlinessUpdate(
+      const blink::MobileFriendliness& mobile_friendliness) override;
   void OnSubFrameRenderDataUpdate(
       content::RenderFrameHost* subframe_rfh,
       const page_load_metrics::mojom::FrameRenderDataUpdate& render_data)
@@ -106,6 +113,9 @@
     page_load_metrics::PageRenderData render_data;
     page_load_metrics::LayoutShiftNormalization layout_shift_normalization;
 
+    // MobileFriendliness metrics observed in the AMP iframe.
+    blink::MobileFriendliness mobile_friendliness;
+
     // Whether an AMP document was loaded, based on observed
     // LoadingBehaviorFlags for this frame.
     bool amp_document_loaded = false;
@@ -115,6 +125,7 @@
 
   void ProcessMainFrameNavigation(content::NavigationHandle* navigation_handle);
   void MaybeRecordAmpDocumentMetrics();
+  void RecordMobileFriendliness(ukm::builders::AmpPageLoad& builder);
 
   // Information about the currently active AMP navigation in the main
   // frame. Will be null if there isn't an active AMP navigation in the main
diff --git a/chrome/browser/page_load_metrics/observers/core/amp_page_load_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/core/amp_page_load_metrics_observer_unittest.cc
index 70502266..b52ee664 100644
--- a/chrome/browser/page_load_metrics/observers/core/amp_page_load_metrics_observer_unittest.cc
+++ b/chrome/browser/page_load_metrics/observers/core/amp_page_load_metrics_observer_unittest.cc
@@ -321,6 +321,10 @@
       blink::LoadingBehaviorFlag::kLoadingBehaviorAmpDocumentLoaded;
   tester()->SimulateMetadataUpdate(metadata, subframe);
 
+  blink::MobileFriendliness mf;
+  mf.text_content_outside_viewport_percentage = 55;
+  tester()->SimulateMobileFriendlinessUpdate(mf, subframe);
+
   page_load_metrics::mojom::PageLoadTiming subframe_timing;
   page_load_metrics::InitPageLoadTimingForTest(&subframe_timing);
   subframe_timing.navigation_start = base::Time::FromDoubleT(2);
@@ -373,6 +377,9 @@
   tester()->test_ukm_recorder().ExpectEntryMetric(
       entry.get(), "SubFrame.PaintTiming.NavigationToLargestContentfulPaint",
       8);
+  tester()->test_ukm_recorder().ExpectEntryMetric(
+      entry.get(),
+      "SubFrame.MobileFriendliness.TextContentOutsideViewportPercentage", 55);
 }
 
 TEST_F(AMPPageLoadMetricsObserverTest, SubFrameMetrics_LayoutInstability) {
@@ -605,6 +612,10 @@
       blink::LoadingBehaviorFlag::kLoadingBehaviorAmpDocumentLoaded;
   tester()->SimulateMetadataUpdate(metadata, subframe);
 
+  blink::MobileFriendliness mf;
+  mf.small_text_ratio = 75;
+  tester()->SimulateMobileFriendlinessUpdate(mf, subframe);
+
   // Navigate the main frame to trigger metrics recording.
   NavigationSimulator::CreateRendererInitiated(GURL("https://www.example.com/"),
                                                main_rfh())
@@ -623,6 +634,12 @@
           entry.get(), "SubFrame.MainFrameToSubFrameNavigationDelta");
   EXPECT_NE(nullptr, nav_delta_metric);
   EXPECT_GE(*nav_delta_metric, 0ll);
+
+  const int64_t* small_text_ratio_metric =
+      tester()->test_ukm_recorder().GetEntryMetric(
+          entry.get(), "SubFrame.MobileFriendliness.SmallTextRatio");
+  EXPECT_NE(nullptr, small_text_ratio_metric);
+  EXPECT_EQ(*small_text_ratio_metric, 75ll);
 }
 
 TEST_F(AMPPageLoadMetricsObserverTest, SubFrameRecordOnFrameDeleted) {
@@ -647,6 +664,10 @@
       blink::LoadingBehaviorFlag::kLoadingBehaviorAmpDocumentLoaded;
   tester()->SimulateMetadataUpdate(metadata, subframe);
 
+  blink::MobileFriendliness mf;
+  mf.bad_tap_targets_ratio = 42;
+  tester()->SimulateMobileFriendlinessUpdate(mf, subframe);
+
   tester()->histogram_tester().ExpectTotalCount(
       "PageLoad.Clients.AMP.Experimental.PageTiming.InputToNavigation.Subframe",
       0);
@@ -667,6 +688,12 @@
           entry.get(), "SubFrame.MainFrameToSubFrameNavigationDelta");
   EXPECT_NE(nullptr, nav_delta_metric);
   EXPECT_GE(*nav_delta_metric, 0ll);
+
+  const int64_t* bad_tap_targets_ratio_metric =
+      tester()->test_ukm_recorder().GetEntryMetric(
+          entry.get(), "SubFrame.MobileFriendliness.BadTapTargetsRatio");
+  EXPECT_NE(nullptr, bad_tap_targets_ratio_metric);
+  EXPECT_GE(*bad_tap_targets_ratio_metric, 0ll);
 }
 
 TEST_F(AMPPageLoadMetricsObserverTest, SubFrameMultipleFrames) {
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 67d4095..56b763c 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
@@ -59,20 +59,6 @@
 #include "chrome/browser/offline_pages/offline_page_tab_helper.h"
 #endif
 
-namespace internal {
-
-int BucketWithOffsetAndUnit(int num, int offset, int unit) {
-  // Bucketing raw number with `offset` centered.
-  const int grid = (num - offset) / unit;
-  const int bucketed =
-      grid == 0 ? 0
-                : grid > 0 ? std::pow(2, static_cast<int>(std::log2(grid)))
-                           : -std::pow(2, static_cast<int>(std::log2(-grid)));
-  return bucketed * unit + offset;
-}
-
-}  // namespace internal
-
 namespace {
 
 const char kOfflinePreviewsMimeType[] = "multipart/related";
@@ -1168,6 +1154,7 @@
 void UkmPageLoadMetricsObserver::RecordMobileFriendlinessMetrics() {
   ukm::builders::MobileFriendliness builder(GetDelegate().GetPageUkmSourceId());
   const blink::MobileFriendliness& mf = GetDelegate().GetMobileFriendliness();
+
   if (mf.viewport_device_width == blink::mojom::ViewportStatus::kYes)
     builder.SetViewportDeviceWidth(true);
   else if (mf.viewport_device_width == blink::mojom::ViewportStatus::kNo)
@@ -1181,19 +1168,19 @@
   if (mf.small_text_ratio != -1)
     builder.SetSmallTextRatio(mf.small_text_ratio);
 
-  if (mf.viewport_initial_scale_x10 != -1) {
-    builder.SetViewportInitialScaleX10(internal::BucketWithOffsetAndUnit(
-        mf.viewport_initial_scale_x10, 10, 2));
-  }
+  if (mf.viewport_initial_scale_x10 != -1)
+    builder.SetViewportInitialScaleX10(
+        page_load_metrics::GetBucketedViewportInitialScale(mf));
 
-  if (mf.viewport_hardcoded_width != -1) {
-    builder.SetViewportHardcodedWidth(internal::BucketWithOffsetAndUnit(
-        mf.viewport_hardcoded_width, 500, 10));
-  }
+  if (mf.viewport_hardcoded_width != -1)
+    builder.SetViewportHardcodedWidth(
+        page_load_metrics::GetBucketedViewportHardcodedWidth(mf));
+
   if (mf.text_content_outside_viewport_percentage != -1) {
     builder.SetTextContentOutsideViewportPercentage(
         mf.text_content_outside_viewport_percentage);
   }
+
   if (mf.bad_tap_targets_ratio != -1)
     builder.SetBadTapTargetsRatio(mf.bad_tap_targets_ratio);
 
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 ee7543f..021ed8a 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
@@ -30,13 +30,6 @@
 }
 }  // namespace ukm
 
-namespace internal {
-
-// Exposed for tests.
-int BucketWithOffsetAndUnit(int num, int offset, int unit);
-
-}  // namespace internal
-
 // This enum represents the type of page load: abort, non-abort, or neither.
 // A page is of type NEVER_FOREGROUND if it was never in the foreground.
 // A page is of type ABORT if it was in the foreground at some point but did not
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 d8903e5c..9dbbf67 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
@@ -2303,32 +2303,69 @@
   RunBeforeInputOrScrollCase(true);
 }
 
-TEST_F(UkmPageLoadMetricsObserverTest, BucketWithOffsetAndUnit) {
-  EXPECT_EQ(500, internal::BucketWithOffsetAndUnit(500, 500, 10));
+void TestViewportInitialScale(int expected, int input) {
+  blink::MobileFriendliness mf;
+  mf.viewport_initial_scale_x10 = input;
+  EXPECT_EQ(expected, page_load_metrics::GetBucketedViewportInitialScale(mf));
+}
 
-  // large num
-  EXPECT_EQ(500, internal::BucketWithOffsetAndUnit(501, 500, 10));
-  EXPECT_EQ(510, internal::BucketWithOffsetAndUnit(510, 500, 10));
-  EXPECT_EQ(520, internal::BucketWithOffsetAndUnit(525, 500, 10));
-  EXPECT_EQ(540, internal::BucketWithOffsetAndUnit(550, 500, 10));
-  EXPECT_EQ(820, internal::BucketWithOffsetAndUnit(1000, 500, 10));
-  EXPECT_EQ(1780, internal::BucketWithOffsetAndUnit(2000, 500, 10));
+TEST_F(UkmPageLoadMetricsObserverTest, BucketingViewportInitialScale) {
+  // Default value to be ignored.
+  TestViewportInitialScale(-1, -1);
 
-  // small num
-  EXPECT_EQ(500, internal::BucketWithOffsetAndUnit(499, 500, 10));
-  EXPECT_EQ(490, internal::BucketWithOffsetAndUnit(490, 500, 10));
-  EXPECT_EQ(480, internal::BucketWithOffsetAndUnit(475, 500, 10));
-  EXPECT_EQ(460, internal::BucketWithOffsetAndUnit(450, 500, 10));
-  EXPECT_EQ(180, internal::BucketWithOffsetAndUnit(100, 500, 10));
-  EXPECT_EQ(180, internal::BucketWithOffsetAndUnit(0, 500, 10));
+  // Typical case initail-scale=1.0.
+  TestViewportInitialScale(10, 10);
 
-  // different offset
-  EXPECT_EQ(1000, internal::BucketWithOffsetAndUnit(1000, 1000, 10));
-  EXPECT_EQ(1010, internal::BucketWithOffsetAndUnit(1010, 1000, 10));
-  EXPECT_EQ(1080, internal::BucketWithOffsetAndUnit(1100, 1000, 10));
+  // Bigger number cases.
+  TestViewportInitialScale(12, 12);
+  TestViewportInitialScale(14, 15);
+  TestViewportInitialScale(14, 15);
+  TestViewportInitialScale(14, 17);
+  TestViewportInitialScale(18, 18);
+  TestViewportInitialScale(18, 25);
+  TestViewportInitialScale(26, 26);
 
-  // different unit
-  EXPECT_EQ(1000, internal::BucketWithOffsetAndUnit(1000, 1000, 100));
-  EXPECT_EQ(1000, internal::BucketWithOffsetAndUnit(1010, 1000, 100));
-  EXPECT_EQ(1100, internal::BucketWithOffsetAndUnit(1100, 1000, 100));
+  // Smaller number cases.
+  TestViewportInitialScale(10, 9);
+  TestViewportInitialScale(8, 8);
+  TestViewportInitialScale(8, 7);
+  TestViewportInitialScale(6, 6);
+  TestViewportInitialScale(6, 3);
+  TestViewportInitialScale(2, 1);
+  TestViewportInitialScale(2, 0);
+}
+
+void TestViewportHardcodedWidth(int expected, int input) {
+  blink::MobileFriendliness mf;
+  mf.viewport_hardcoded_width = input;
+  EXPECT_EQ(expected, page_load_metrics::GetBucketedViewportHardcodedWidth(mf));
+}
+
+TEST_F(UkmPageLoadMetricsObserverTest, BucketingViewportHardcodedWidth) {
+  // Default value to be ignored.
+  TestViewportHardcodedWidth(-1, -1);
+
+  // Middle case.
+  TestViewportHardcodedWidth(500, 500);
+
+  // Bigger number cases.
+  TestViewportHardcodedWidth(500, 509);
+
+  TestViewportHardcodedWidth(510, 510);
+  TestViewportHardcodedWidth(510, 519);
+  TestViewportHardcodedWidth(520, 520);
+  TestViewportHardcodedWidth(520, 539);
+  TestViewportHardcodedWidth(540, 540);
+  TestViewportHardcodedWidth(540, 579);
+  TestViewportHardcodedWidth(580, 580);
+  TestViewportHardcodedWidth(580, 640);
+  TestViewportHardcodedWidth(820, 1000);
+  TestViewportHardcodedWidth(1780, 2000);
+
+  // Smaller number cases.
+  TestViewportHardcodedWidth(500, 491);
+  TestViewportHardcodedWidth(490, 490);
+  TestViewportHardcodedWidth(480, 480);
+  TestViewportHardcodedWidth(460, 421);
+  TestViewportHardcodedWidth(180, 180);
 }
diff --git a/chrome/browser/policy/android/java/src/org/chromium/chrome/browser/policy/CloudManagementAndroidConnection.java b/chrome/browser/policy/android/java/src/org/chromium/chrome/browser/policy/CloudManagementAndroidConnection.java
index 0cbe861..a643336 100644
--- a/chrome/browser/policy/android/java/src/org/chromium/chrome/browser/policy/CloudManagementAndroidConnection.java
+++ b/chrome/browser/policy/android/java/src/org/chromium/chrome/browser/policy/CloudManagementAndroidConnection.java
@@ -8,6 +8,8 @@
 
 import org.chromium.base.annotations.CalledByNative;
 
+import java.util.UUID;
+
 /**
  * Allows access to cloud management functionalities implemented downstream.
  */
@@ -27,7 +29,19 @@
     private final CloudManagementAndroidConnectionDelegate mDelegate;
 
     private CloudManagementAndroidConnection() {
-        mDelegate = new CloudManagementAndroidConnectionDelegate();
+        mDelegate = new CloudManagementAndroidConnectionDelegate() {
+            /* Returns the client ID to be used in the DM token generation. By default a random UUID
+             * is generated for development and testing purposes. Any device that uses randomly
+             * generated UUID as client id for CBCM might be wiped out from the server without
+             * notice.
+             *
+             * TODO(http://crbug.com/1210116): Move this implementation to its own class once the
+             * Google Chrome-specific implementation has been landed internally. */
+            @Override
+            public String generateClientId() {
+                return UUID.randomUUID().toString();
+            }
+        };
     }
 
     /* Returns the client ID to be used in the DM token generation. Once generated, the ID is saved
diff --git a/chrome/browser/policy/android/java/src/org/chromium/chrome/browser/policy/CloudManagementAndroidConnectionDelegate.java b/chrome/browser/policy/android/java/src/org/chromium/chrome/browser/policy/CloudManagementAndroidConnectionDelegate.java
index 026c87a..09aa0c9 100644
--- a/chrome/browser/policy/android/java/src/org/chromium/chrome/browser/policy/CloudManagementAndroidConnectionDelegate.java
+++ b/chrome/browser/policy/android/java/src/org/chromium/chrome/browser/policy/CloudManagementAndroidConnectionDelegate.java
@@ -4,16 +4,10 @@
 
 package org.chromium.chrome.browser.policy;
 
-import java.util.UUID;
-
 /**
  * Delegate for cloud management functions implemented downstream for Google Chrome.
  */
-public class CloudManagementAndroidConnectionDelegate {
-    /* Returns the client ID to be used in the DM token generation. By default a random UUID is
-     * generated for development and testing purposes. Any device that uses randomly generated UUID
-     * as client id for CBCM might be wiped out from the server without notice. */
-    public String generateClientId() {
-        return UUID.randomUUID().toString();
-    }
+public interface CloudManagementAndroidConnectionDelegate {
+    /* Returns the client ID to be used in the DM token generation. */
+    public String generateClientId();
 }
diff --git a/chrome/browser/policy/android/java/src/org/chromium/chrome/browser/policy/CloudManagementAndroidConnectionTest.java b/chrome/browser/policy/android/java/src/org/chromium/chrome/browser/policy/CloudManagementAndroidConnectionTest.java
index 3b8e877b..9f6063b 100644
--- a/chrome/browser/policy/android/java/src/org/chromium/chrome/browser/policy/CloudManagementAndroidConnectionTest.java
+++ b/chrome/browser/policy/android/java/src/org/chromium/chrome/browser/policy/CloudManagementAndroidConnectionTest.java
@@ -26,7 +26,7 @@
     /* Simple implementation of {@link CloudManagementAndroidConnection} that overrides {@link
      * generateClientIdInternal} for easier testing. */
     private static class FakeCloudManagementAndroidConnectionDelegate
-            extends CloudManagementAndroidConnectionDelegate {
+            implements CloudManagementAndroidConnectionDelegate {
         @Override
         public String generateClientId() {
             return CLIENT_ID;
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 26723c3..6946d30 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -1151,7 +1151,7 @@
   ash::FamilyUserSessionMetrics::RegisterProfilePrefs(registry);
   chromeos::InlineLoginHandlerChromeOS::RegisterProfilePrefs(registry);
   chromeos::first_run::RegisterProfilePrefs(registry);
-  chromeos::file_system_provider::RegisterProfilePrefs(registry);
+  ash::file_system_provider::RegisterProfilePrefs(registry);
   chromeos::full_restore::RegisterProfilePrefs(registry);
   ash::KerberosCredentialsManager::RegisterProfilePrefs(registry);
   chromeos::login::SecurityTokenSessionController::RegisterProfilePrefs(
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/focus_ring_manager_test.js b/chrome/browser/resources/chromeos/accessibility/switch_access/focus_ring_manager_test.js
index 337a1a9..af1fd69 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/focus_ring_manager_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/focus_ring_manager_test.js
@@ -46,4 +46,25 @@
     assertEquals(0, primary.rects.length);
     assertEquals(1, preview.rects.length);
   });
-});
\ No newline at end of file
+});
+
+TEST_F('FocusRingManagerTest', 'ButtonFocus', function() {
+  const site = '<button>Test</button>';
+  this.runWithLoadedTree(site, async (root) => {
+    const button =
+        root.find({attributes: {role: chrome.automation.RoleType.BUTTON}});
+    Navigator.byItem.moveTo_(button);
+    const rings = FocusRingManager.instance.rings_;
+    const primary = rings.get(SAConstants.Focus.ID.PRIMARY);
+    const preview = rings.get(SAConstants.Focus.ID.PREVIEW);
+    assertEquals(1, primary.rects.length);
+    assertEquals(0, preview.rects.length);
+    // Primary focus should be on the button.
+    const focusLocation = primary.rects[0];
+    const buttonLocation = button.location;
+    assertEquals(buttonLocation.top, focusLocation.top);
+    assertEquals(buttonLocation.left, focusLocation.left);
+    assertEquals(buttonLocation.width, focusLocation.width);
+    assertEquals(buttonLocation.height, focusLocation.height);
+  });
+});
diff --git a/chrome/browser/resources/new_tab_page/realbox/realbox.js b/chrome/browser/resources/new_tab_page/realbox/realbox.js
index 3c3bbd6..5d38f13 100644
--- a/chrome/browser/resources/new_tab_page/realbox/realbox.js
+++ b/chrome/browser/resources/new_tab_page/realbox/realbox.js
@@ -364,7 +364,8 @@
           'text/plain', this.selectedMatch_.destinationUrl.url);
       e.preventDefault();
       if (e.type === 'cut') {
-        this.$.input.value = '';
+        this.updateInput_({text: '', inline: ''});
+        this.clearAutocompleteMatches_();
       }
     }
   }
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_page.html b/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_page.html
index 1cb7c91..0ad0a15 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_page.html
+++ b/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_page.html
@@ -67,14 +67,22 @@
         color: var(--google-red-500);
       }
 
+      #preferNetworkToggleContainer:hover {
+        background-color: var(--cr-hover-background-color);
+      }
+
+      #preferNetworkToggleContainer:active {
+        background-color: var(--cr-active-background-color);
+      }
+
       paper-spinner-lite {
         height: var(--cr-icon-size);
         width: var(--cr-icon-size);
       }
 
       .warning {
-          color: var(--cr-secondary-text-color);
-          margin-inline-start: var(--settings-controlled-by-spacing);
+        color: var(--cr-secondary-text-color);
+        margin-inline-start: var(--settings-controlled-by-spacing);
       }
 
       #mac-address-container {
@@ -209,7 +217,8 @@
         <template is="dom-if"
             if="[[showPreferNetwork_(managedProperties_, globalPolicy,
                 managedNetworkAvailable)]]">
-          <div class="settings-box"  on-click="onPreferNetworkRowClicked_"
+          <div id="preferNetworkToggleContainer" class="settings-box"
+              on-click="onPreferNetworkRowClicked_"
               actionable$="[[!isNetworkPolicyEnforced(
                   managedProperties_.priority)]]">
             <div id="preferNetworkToggleLabel" class="start settings-box-text">
@@ -378,7 +387,7 @@
             globalPolicy, managedNetworkAvailable)]]">
           <!-- Network toggle -->
           <cr-expand-button
-              id="configurableSetions"
+              id="configurableSections"
               aria-label="$i18n{networkSectionNetworkExpandA11yLabel}"
               class="settings-box"
               expanded="{{networkExpanded_}}">
@@ -467,4 +476,4 @@
     </template>
   </template>
   <script src="internet_detail_page.js"></script>
-</dom-module>
\ No newline at end of file
+</dom-module>
diff --git a/chrome/browser/resources/settings/images/privacy_sandbox_floc_banner.svg b/chrome/browser/resources/settings/images/privacy_sandbox_floc_banner.svg
index 7af203e71..8944f5b 100644
--- a/chrome/browser/resources/settings/images/privacy_sandbox_floc_banner.svg
+++ b/chrome/browser/resources/settings/images/privacy_sandbox_floc_banner.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 140"><style>.st0{fill:#8ab4f8}.st1{fill:#a8dab5}.st2{fill:#34a853}</style><circle class="st0" cx="154.4" cy="39.7" r="10.8"/><path class="st0" d="M176.1 69.2c0-4.4-2.6-8.3-6.6-10-9.6-4.2-20.5-4.2-30.1 0-4 1.7-6.6 5.7-6.6 10v3.1h43.4l-.1-3.1z"/><circle class="st0" cx="277.9" cy="77.6" r="7.3"/><path class="st0" d="M292.4 97.3c0-2.9-1.7-5.6-4.4-6.7-6.4-2.8-13.8-2.8-20.2 0-2.7 1.1-4.4 3.8-4.4 6.7v2.1h29v-2.1z"/><circle class="st1" cx="105.2" cy="34" r="7.3"/><path class="st1" d="M119.7 53.8c0-2.9-1.7-5.6-4.4-6.7-6.4-2.8-13.8-2.8-20.2 0-2.7 1.1-4.4 3.8-4.4 6.7v2.1h29v-2.1z"/><circle class="st1" cx="319" cy="56.9" r="7.3"/><path class="st1" d="M333.6 76.6c0-2.9-1.7-5.6-4.4-6.7-6.4-2.8-13.8-2.8-20.2 0-2.7 1.1-4.4 3.8-4.4 6.7v2.1h29v-2.1z"/><circle class="st0" cx="81.2" cy="73.5" r="7.3"/><path class="st0" d="M95.7 93.2c0-2.9-1.7-5.6-4.4-6.7-6.4-2.8-13.8-2.8-20.2 0-2.7 1.1-4.4 3.8-4.4 6.7v2.1h29v-2.1z"/><circle class="st0" cx="241.4" cy="92" r="10.8"/><path class="st0" d="M263.1 121.5c0-4.4-2.6-8.3-6.6-10-9.6-4.2-20.5-4.2-30.1 0-4 1.7-6.6 5.7-6.6 10v3.1h43.4l-.1-3.1z"/><circle class="st1" cx="125.4" cy="84.9" r="9.1"/><path class="st1" d="M143.6 109.5c0-3.7-2.2-7-5.5-8.4-8.1-3.5-17.2-3.5-25.2 0-3.4 1.4-5.5 4.7-5.5 8.4v2.6h36.3l-.1-2.6z"/><circle class="st1" cx="257.7" cy="26.5" r="9.1"/><path class="st1" d="M275.9 51.2c0-3.7-2.2-7-5.5-8.4-8.1-3.5-17.2-3.5-25.2 0-3.4 1.4-5.5 4.7-5.5 8.4v2.6H276l-.1-2.6z"/><circle class="st2" cx="200" cy="60.3" r="10.8"/><path class="st2" d="M221.7 89.7c0-4.4-2.6-8.3-6.6-10-9.6-4.2-20.5-4.2-30.1 0-4 1.7-6.6 5.7-6.6 10v3.1h43.4l-.1-3.1z"/><path d="M223.3 104.9h-46.6c-5.8 0-10.5-4.7-10.5-10.5V45.6c0-5.8 4.7-10.5 10.5-10.5h46.6c5.8 0 10.5 4.7 10.5 10.5v48.8c0 5.8-4.7 10.5-10.5 10.5zm-46.6-68.8c-5.2 0-9.5 4.3-9.5 9.5v48.8c0 5.2 4.3 9.5 9.5 9.5h46.6c5.2 0 9.5-4.3 9.5-9.5V45.6c0-5.2-4.3-9.5-9.5-9.5h-46.6zm46.6 63.1h-46.6c-2.6 0-4.8-2.2-4.8-4.8V45.6c0-2.6 2.2-4.8 4.8-4.8h46.6c2.6 0 4.8 2.2 4.8 4.8v48.8c0 2.6-2.2 4.8-4.8 4.8zm0-57.4h-46.6c-2.1 0-3.8 1.7-3.8 3.8v48.8c0 2.1 1.7 3.8 3.8 3.8h46.6c2.1 0 3.8-1.7 3.8-3.8V45.6c0-2.1-1.7-3.8-3.8-3.8z" fill="#ceead6"/><path fill="none" d="M0 0h400v140H0z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 140"><circle cx="199.871" cy="53.632" r="16.293" fill="#81c995"/><path d="M232.608 97.985a16.325 16.325 0 0 0-9.957-15.086 56.615 56.615 0 0 0-45.409 0 16.482 16.482 0 0 0-9.956 15.086v4.676h65.473z" fill="#81c995"/><circle cx="146.033" cy="58.535" r="14.664" fill="#8ab4f8"/><path d="M175.496 98.452a14.692 14.692 0 0 0-8.961-13.577 50.953 50.953 0 0 0-40.869 0 14.834 14.834 0 0 0-8.96 13.577v4.21h58.925z" fill="#8ab4f8"/><path d="M170.904 87.74a16.474 16.474 0 0 0-3.618 10.245v4.676h8.345l-.135-4.209a14.708 14.708 0 0 0-4.592-10.712z" fill="#4285f4"/><circle cx="98.741" cy="63.438" r="13.034" fill="#81c995"/><path d="M124.93 98.92a13.06 13.06 0 0 0-7.965-12.069 45.292 45.292 0 0 0-36.327 0 13.186 13.186 0 0 0-7.966 12.069v3.741h52.38z" fill="#81c995"/><path d="M120.26 88.869a14.838 14.838 0 0 0-3.555 9.583v4.21h8.346l-.12-3.742a13.07 13.07 0 0 0-4.67-10.051z" fill="#4285f4"/><circle cx="57.997" cy="68.341" r="11.405" fill="#aecbfa"/><path d="M80.913 99.388a11.427 11.427 0 0 0-6.97-10.56 39.63 39.63 0 0 0-31.787 0 11.537 11.537 0 0 0-6.97 10.56v3.273h45.832z" fill="#aecbfa"/><path d="M76.114 90.079a13.198 13.198 0 0 0-3.442 8.841v3.741h8.346l-.105-3.273a11.407 11.407 0 0 0-4.799-9.31z" fill="#4285f4"/><circle cx="253.71" cy="58.535" r="14.664" fill="#8ab4f8"/><path d="M224.247 98.452a14.692 14.692 0 0 1 8.961-13.577 50.953 50.953 0 0 1 40.869 0 14.834 14.834 0 0 1 8.96 13.577v4.21h-58.925z" fill="#8ab4f8"/><path d="M228.947 87.642a14.704 14.704 0 0 0-4.7 10.81l-.135 4.21h8.647l-.15-4.677a16.36 16.36 0 0 0-3.662-10.343z" fill="#4285f4"/><circle cx="301.002" cy="63.438" r="13.034" fill="#81c995"/><path d="M274.813 98.92a13.06 13.06 0 0 1 7.965-12.069 45.292 45.292 0 0 1 36.327 0 13.186 13.186 0 0 1 7.966 12.069v3.741h-52.38z" fill="#81c995"/><path d="M279.482 88.869a13.071 13.071 0 0 0-4.67 10.051l-.12 3.741h8.346v-4.209a14.837 14.837 0 0 0-3.556-9.583z" fill="#4285f4"/><circle cx="341.746" cy="68.341" r="11.405" fill="#aecbfa"/><path d="M318.83 99.388a11.427 11.427 0 0 1 6.97-10.56 39.63 39.63 0 0 1 31.786 0 11.537 11.537 0 0 1 6.97 10.56v3.273h-45.831z" fill="#aecbfa"/><path d="M323.629 90.079a11.407 11.407 0 0 0-4.799 9.309l-.105 3.273h8.346V98.92a13.198 13.198 0 0 0-3.442-8.841z" fill="#4285f4"/></svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/settings/images/privacy_sandbox_floc_banner_dark.svg b/chrome/browser/resources/settings/images/privacy_sandbox_floc_banner_dark.svg
index 7d1c422d..fd7525a 100644
--- a/chrome/browser/resources/settings/images/privacy_sandbox_floc_banner_dark.svg
+++ b/chrome/browser/resources/settings/images/privacy_sandbox_floc_banner_dark.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 140"><circle cx="154.4" cy="39.7" r="10.8" fill="#185abc"/><path d="M176.1 69.2a10.821 10.821 0 0 0-6.6-10 37.528 37.528 0 0 0-30.1 0 10.925 10.925 0 0 0-6.6 10v3.1h43.4z" fill="#185abc"/><circle cx="277.9" cy="77.6" r="7.3" fill="#185abc"/><path d="M292.4 97.3a7.208 7.208 0 0 0-4.4-6.7 25.338 25.338 0 0 0-20.2 0 7.208 7.208 0 0 0-4.4 6.7v2.1h29z" fill="#185abc"/><circle cx="105.2" cy="34" r="7.3" fill="#34a853"/><path d="M119.7 53.8a7.208 7.208 0 0 0-4.4-6.7 25.338 25.338 0 0 0-20.2 0 7.208 7.208 0 0 0-4.4 6.7v2.1h29z" fill="#34a853"/><circle cx="319" cy="56.9" r="7.3" fill="#34a853"/><path d="M333.6 76.6a7.208 7.208 0 0 0-4.4-6.7 25.338 25.338 0 0 0-20.2 0 7.208 7.208 0 0 0-4.4 6.7v2.1h29z" fill="#34a853"/><circle cx="81.2" cy="73.5" r="7.3" fill="#185abc"/><path d="M95.7 93.2a7.208 7.208 0 0 0-4.4-6.7 25.338 25.338 0 0 0-20.2 0 7.208 7.208 0 0 0-4.4 6.7v2.1h29z" fill="#185abc"/><circle cx="241.4" cy="92" r="10.8" fill="#185abc"/><path d="M263.1 121.5a10.821 10.821 0 0 0-6.6-10 37.528 37.528 0 0 0-30.1 0 10.925 10.925 0 0 0-6.6 10v3.1h43.4z" fill="#185abc"/><circle cx="125.4" cy="84.9" r="9.1" fill="#34a853"/><path d="M143.6 109.5a9.11 9.11 0 0 0-5.5-8.4 31.552 31.552 0 0 0-25.2 0 8.957 8.957 0 0 0-5.5 8.4v2.6h36.3z" fill="#34a853"/><circle cx="257.7" cy="26.5" r="9.1" fill="#34a853"/><path d="M275.9 51.2a9.11 9.11 0 0 0-5.5-8.4 31.552 31.552 0 0 0-25.2 0 8.957 8.957 0 0 0-5.5 8.4v2.6H276z" fill="#34a853"/><circle cx="200" cy="60.3" r="10.8" fill="#34a853"/><path d="M221.7 89.7a10.821 10.821 0 0 0-6.6-10 37.528 37.528 0 0 0-30.1 0 10.925 10.925 0 0 0-6.6 10v3.1h43.4z" fill="#34a853"/><path d="M223.3 104.9h-46.6a10.499 10.499 0 0 1-10.5-10.5V45.6a10.499 10.499 0 0 1 10.5-10.5h46.6a10.499 10.499 0 0 1 10.5 10.5v48.8a10.499 10.499 0 0 1-10.5 10.5zm-46.6-68.8a9.56 9.56 0 0 0-9.5 9.5v48.8a9.56 9.56 0 0 0 9.5 9.5h46.6a9.56 9.56 0 0 0 9.5-9.5V45.6a9.56 9.56 0 0 0-9.5-9.5zm46.6 63.1h-46.6a4.867 4.867 0 0 1-4.8-4.8V45.6a4.867 4.867 0 0 1 4.8-4.8h46.6a4.867 4.867 0 0 1 4.8 4.8v48.8a4.867 4.867 0 0 1-4.8 4.8zm0-57.4h-46.6a3.798 3.798 0 0 0-3.8 3.8v48.8a3.798 3.798 0 0 0 3.8 3.8h46.6a3.798 3.798 0 0 0 3.8-3.8V45.6a3.798 3.798 0 0 0-3.8-3.8z" fill="#137333"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 140"><circle cx="199.871" cy="53.632" r="16.293" fill="#34a853"/><path d="M232.608 97.985a16.325 16.325 0 0 0-9.957-15.086 56.615 56.615 0 0 0-45.409 0 16.482 16.482 0 0 0-9.956 15.086v4.676h65.473z" fill="#34a853"/><circle cx="146.033" cy="58.535" r="14.664" fill="#4285f4"/><path d="M175.496 98.452a14.692 14.692 0 0 0-8.961-13.577 50.953 50.953 0 0 0-40.869 0 14.834 14.834 0 0 0-8.96 13.577v4.21h58.925z" fill="#4285f4"/><path d="M170.904 87.74a16.474 16.474 0 0 0-3.618 10.245v4.676h8.345l-.135-4.209a14.708 14.708 0 0 0-4.592-10.712z" fill="#185abc"/><circle cx="98.741" cy="63.438" r="13.034" fill="#34a853"/><path d="M124.93 98.92a13.06 13.06 0 0 0-7.965-12.069 45.292 45.292 0 0 0-36.327 0 13.186 13.186 0 0 0-7.966 12.069v3.741h52.38z" fill="#34a853"/><path d="M120.26 88.869a14.838 14.838 0 0 0-3.555 9.583v4.21h8.346l-.12-3.742a13.07 13.07 0 0 0-4.67-10.051z" fill="#185abc"/><circle cx="57.997" cy="68.341" r="11.405" fill="#4285f4"/><path d="M80.913 99.388a11.427 11.427 0 0 0-6.97-10.56 39.63 39.63 0 0 0-31.787 0 11.537 11.537 0 0 0-6.97 10.56v3.273h45.832z" fill="#4285f4"/><path d="M76.114 90.079a13.198 13.198 0 0 0-3.442 8.841v3.741h8.346l-.105-3.273a11.407 11.407 0 0 0-4.799-9.31z" fill="#185abc"/><circle cx="253.71" cy="58.535" r="14.664" fill="#4285f4"/><path d="M224.247 98.452a14.692 14.692 0 0 1 8.961-13.577 50.953 50.953 0 0 1 40.869 0 14.834 14.834 0 0 1 8.96 13.577v4.21h-58.925z" fill="#4285f4"/><path d="M228.947 87.642a14.704 14.704 0 0 0-4.7 10.81l-.135 4.21h8.647l-.15-4.677a16.36 16.36 0 0 0-3.662-10.343z" fill="#185abc"/><circle cx="301.002" cy="63.438" r="13.034" fill="#34a853"/><path d="M274.813 98.92a13.06 13.06 0 0 1 7.965-12.069 45.292 45.292 0 0 1 36.327 0 13.186 13.186 0 0 1 7.966 12.069v3.741h-52.38z" fill="#34a853"/><path d="M279.482 88.869a13.071 13.071 0 0 0-4.67 10.051l-.12 3.741h8.346v-4.209a14.837 14.837 0 0 0-3.556-9.583z" fill="#185abc"/><circle cx="341.746" cy="68.341" r="11.405" fill="#4285f4"/><path d="M318.83 99.388a11.427 11.427 0 0 1 6.97-10.56 39.63 39.63 0 0 1 31.786 0 11.537 11.537 0 0 1 6.97 10.56v3.273h-45.831z" fill="#4285f4"/><path d="M323.629 90.079a11.407 11.407 0 0 0-4.799 9.309l-.105 3.273h8.346V98.92a13.198 13.198 0 0 0-3.442-8.841z" fill="#185abc"/></svg>
\ No newline at end of file
diff --git a/chrome/browser/sessions/session_restore_browsertest.cc b/chrome/browser/sessions/session_restore_browsertest.cc
index ca910ec..9aa3c1d 100644
--- a/chrome/browser/sessions/session_restore_browsertest.cc
+++ b/chrome/browser/sessions/session_restore_browsertest.cc
@@ -102,6 +102,10 @@
 #include "ui/base/ui_base_features.h"
 #include "ui/gfx/color_palette.h"
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "components/full_restore/features.h"
+#endif
+
 #if BUILDFLAG(ENABLE_APP_SESSION_SERVICE)
 #include "chrome/browser/sessions/app_session_service.h"
 #include "chrome/browser/sessions/app_session_service_factory.h"
@@ -502,6 +506,16 @@
 // not do session restore if an incognito window is already open.
 // (http://crbug.com/120927)
 IN_PROC_BROWSER_TEST_F(SessionRestoreTest, NoSessionRestoreNewWindowChromeOS) {
+  // When the full restore feature is enabled, session restore does occur when a
+  // user opens a browser window. So set the pref as default, open the New Tab
+  // page for this test, to verify that session restore does not occur.
+  Profile* profile = browser()->profile();
+  SessionStartupPref current_pref = SessionStartupPref::GetStartupPref(profile);
+  if (full_restore::features::IsFullRestoreEnabled()) {
+    SessionStartupPref pref(SessionStartupPref::DEFAULT);
+    SessionStartupPref::SetStartupPref(profile, pref);
+  }
+
   GURL url(ui_test_utils::GetTestUrl(
       base::FilePath(base::FilePath::kCurrentDirectory),
       base::FilePath(FILE_PATH_LITERAL("title1.html"))));
diff --git a/chrome/browser/sharing_hub/sharing_hub_model.cc b/chrome/browser/sharing_hub/sharing_hub_model.cc
index 0436df32..248b07f8 100644
--- a/chrome/browser/sharing_hub/sharing_hub_model.cc
+++ b/chrome/browser/sharing_hub/sharing_hub_model.cc
@@ -73,19 +73,14 @@
        kCopyIcon, true});
 
   first_party_action_list_.push_back(
-      {IDC_QRCODE_GENERATOR,
-       l10n_util::GetStringUTF16(IDS_OMNIBOX_QRCODE_GENERATOR_ICON_LABEL),
-       kQrcodeGeneratorIcon, true});
-
-  first_party_action_list_.push_back(
       {IDC_SEND_TAB_TO_SELF,
        l10n_util::GetStringUTF16(IDS_CONTEXT_MENU_SEND_TAB_TO_SELF),
        kSendTabToSelfIcon, true});
 
   first_party_action_list_.push_back(
-      {IDC_SAVE_PAGE,
-       l10n_util::GetStringUTF16(IDS_SHARING_HUB_SAVE_PAGE_LABEL),
-       kSavePageIcon, true});
+      {IDC_QRCODE_GENERATOR,
+       l10n_util::GetStringUTF16(IDS_OMNIBOX_QRCODE_GENERATOR_ICON_LABEL),
+       kQrcodeGeneratorIcon, true});
 
   if (media_router::MediaRouterEnabled(context_)) {
     first_party_action_list_.push_back(
@@ -93,6 +88,11 @@
          l10n_util::GetStringUTF16(IDS_SHARING_HUB_MEDIA_ROUTER_LABEL),
          vector_icons::kMediaRouterIdleIcon, true});
   }
+
+  first_party_action_list_.push_back(
+      {IDC_SAVE_PAGE,
+       l10n_util::GetStringUTF16(IDS_SHARING_HUB_SAVE_PAGE_LABEL),
+       kSavePageIcon, true});
 }
 
 void SharingHubModel::PopulateThirdPartyActions() {
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index cee0ec75..4f2a4f0a 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -636,6 +636,8 @@
     sources += [
       "android/android_about_app_info.cc",
       "android/android_about_app_info.h",
+      "android/autofill/autofill_error_dialog_view_android.cc",
+      "android/autofill/autofill_error_dialog_view_android.h",
       "android/autofill/autofill_keyboard_accessory_view.cc",
       "android/autofill/autofill_keyboard_accessory_view.h",
       "android/autofill/autofill_logger_android.cc",
@@ -751,6 +753,10 @@
       "android/webid/account_selection_view_android.cc",
       "android/webid/account_selection_view_android.h",
       "android/webid/webid_dialog_android.cc",
+      "autofill/payments/autofill_error_dialog_controller.h",
+      "autofill/payments/autofill_error_dialog_controller_impl.cc",
+      "autofill/payments/autofill_error_dialog_controller_impl.h",
+      "autofill/payments/autofill_error_dialog_view.h",
       "autofill/payments/autofill_snackbar_controller.h",
       "autofill/payments/autofill_snackbar_controller_impl.cc",
       "autofill/payments/autofill_snackbar_controller_impl.h",
@@ -799,6 +805,7 @@
       "//chrome/browser/image_decoder",
       "//chrome/browser/notifications/scheduler/public",
       "//chrome/browser/resources/webapks:resources",
+      "//chrome/browser/ui/android/autofill/internal:jni_headers",
       "//chrome/browser/ui/webui/explore_sites_internals:mojo_bindings",
       "//chrome/browser/ui/webui/feed_internals:mojo_bindings",
       "//components/autofill_assistant/browser",
diff --git a/chrome/browser/ui/android/autofill/autofill_error_dialog_view_android.cc b/chrome/browser/ui/android/autofill/autofill_error_dialog_view_android.cc
new file mode 100644
index 0000000..fe85723d
--- /dev/null
+++ b/chrome/browser/ui/android/autofill/autofill_error_dialog_view_android.cc
@@ -0,0 +1,71 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/android/autofill/autofill_error_dialog_view_android.h"
+
+#include <jni.h>
+#include <stddef.h>
+#include "chrome/browser/android/resource_mapper.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "chrome/browser/ui/android/autofill/internal/jni_headers/AutofillErrorDialogBridge_jni.h"
+#include "components/grit/components_scaled_resources.h"
+#include "ui/android/view_android.h"
+#include "ui/android/window_android.h"
+#include "ui/base/resource/resource_bundle.h"
+
+using base::android::ConvertUTF16ToJavaString;
+
+namespace autofill {
+
+AutofillErrorDialogViewAndroid::AutofillErrorDialogViewAndroid(
+    AutofillErrorDialogController* controller)
+    : controller_(controller) {}
+
+AutofillErrorDialogViewAndroid::~AutofillErrorDialogViewAndroid() = default;
+
+// static
+std::unique_ptr<AutofillErrorDialogView> AutofillErrorDialogView::Create(
+    AutofillErrorDialogController* controller) {
+  return std::make_unique<AutofillErrorDialogViewAndroid>(controller);
+}
+
+void AutofillErrorDialogViewAndroid::Show() {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  ui::ViewAndroid* view_android =
+      controller_->GetWebContents()->GetNativeView();
+  DCHECK(view_android);
+  ui::WindowAndroid* window_android = view_android->GetWindowAndroid();
+  if (!window_android)
+    return;
+
+  java_object_.Reset(Java_AutofillErrorDialogBridge_create(
+      env, reinterpret_cast<intptr_t>(this), window_android->GetJavaObject()));
+
+  Java_AutofillErrorDialogBridge_show(
+      env, java_object_, ConvertUTF16ToJavaString(env, controller_->GetTitle()),
+      ConvertUTF16ToJavaString(env, controller_->GetDescription()),
+      ConvertUTF16ToJavaString(env, controller_->GetButtonLabel()),
+      ResourceMapper::MapToJavaDrawableId(
+          IDR_AUTOFILL_GOOGLE_PAY_WITH_DIVIDER));
+}
+
+void AutofillErrorDialogViewAndroid::Dismiss() {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  if (!java_object_.is_null()) {
+    Java_AutofillErrorDialogBridge_dismiss(env, java_object_);
+  } else {
+    OnDismissed(env);
+  }
+}
+
+void AutofillErrorDialogViewAndroid::OnDismissed(JNIEnv* env) {
+  controller_->OnDismissed();
+}
+
+}  // namespace autofill
diff --git a/chrome/browser/ui/android/autofill/autofill_error_dialog_view_android.h b/chrome/browser/ui/android/autofill/autofill_error_dialog_view_android.h
new file mode 100644
index 0000000..f16b635
--- /dev/null
+++ b/chrome/browser/ui/android/autofill/autofill_error_dialog_view_android.h
@@ -0,0 +1,41 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_ANDROID_AUTOFILL_AUTOFILL_ERROR_DIALOG_VIEW_ANDROID_H_
+#define CHROME_BROWSER_UI_ANDROID_AUTOFILL_AUTOFILL_ERROR_DIALOG_VIEW_ANDROID_H_
+
+#include <jni.h>
+#include <stddef.h>
+
+#include "base/android/scoped_java_ref.h"
+#include "chrome/browser/ui/autofill/payments/autofill_error_dialog_controller.h"
+#include "chrome/browser/ui/autofill/payments/autofill_error_dialog_view.h"
+
+namespace autofill {
+
+// Android implementation of the AutofillErrorDialogView. This view is owned by
+// the `AutofillErrorDialogControllerImpl` which lives for the duration of
+// the tab.
+class AutofillErrorDialogViewAndroid : public AutofillErrorDialogView {
+ public:
+  explicit AutofillErrorDialogViewAndroid(
+      AutofillErrorDialogController* controller);
+  ~AutofillErrorDialogViewAndroid() override;
+
+  // AutofillErrorDialogView.
+  void Show() override;
+  void Dismiss() override;
+
+  // Called by the Java code when the error dialog is dismissed.
+  void OnDismissed(JNIEnv* env);
+
+ private:
+  AutofillErrorDialogController* controller_;
+  // The corresponding java object.
+  base::android::ScopedJavaGlobalRef<jobject> java_object_;
+};
+
+}  // namespace autofill
+
+#endif  // CHROME_BROWSER_UI_ANDROID_AUTOFILL_AUTOFILL_ERROR_DIALOG_VIEW_ANDROID_H_
diff --git a/chrome/browser/ui/android/autofill/internal/BUILD.gn b/chrome/browser/ui/android/autofill/internal/BUILD.gn
new file mode 100644
index 0000000..d16857d3
--- /dev/null
+++ b/chrome/browser/ui/android/autofill/internal/BUILD.gn
@@ -0,0 +1,47 @@
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/android/rules.gni")
+
+android_library("java") {
+  visibility = [
+    ":*",
+    "//chrome/android:chrome_all_java",
+  ]
+  sources = [ "java/src/org/chromium/chrome/browser/ui/autofill/AutofillErrorDialogBridge.java" ]
+  deps = [
+    ":jni_headers",
+    "//base:base_java",
+    "//base:jni_java",
+    "//third_party/androidx:androidx_core_core_java",
+    "//ui/android:ui_java",
+  ]
+  annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
+}
+
+generate_jni("jni_headers") {
+  visibility = [
+    ":*",
+    "//chrome/browser/ui:ui",
+  ]
+  sources = [ "java/src/org/chromium/chrome/browser/ui/autofill/AutofillErrorDialogBridge.java" ]
+}
+
+android_library("junit") {
+  # Skip platform checks since Robolectric depends on requires_android targets.
+  bypass_platform_checks = true
+  testonly = true
+  sources = [ "java/src/org/chromium/chrome/browser/ui/autofill/AutofillErrorDialogBridgeTest.java" ]
+  deps = [
+    ":java",
+    "//base:base_java",
+    "//base:base_java_test_support",
+    "//base:base_junit_test_support",
+    "//base/test:test_support_java",
+    "//third_party/androidx:androidx_test_runner_java",
+    "//third_party/junit:junit",
+    "//third_party/mockito:mockito_java",
+    "//ui/android:ui_full_java",
+  ]
+}
diff --git a/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/AutofillErrorDialogBridge.java b/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/AutofillErrorDialogBridge.java
new file mode 100644
index 0000000..e61c09b
--- /dev/null
+++ b/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/AutofillErrorDialogBridge.java
@@ -0,0 +1,101 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.ui.autofill;
+
+import android.content.Context;
+
+import androidx.core.content.res.ResourcesCompat;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.NativeMethods;
+import org.chromium.ui.base.WindowAndroid;
+import org.chromium.ui.modaldialog.DialogDismissalCause;
+import org.chromium.ui.modaldialog.ModalDialogManager;
+import org.chromium.ui.modaldialog.ModalDialogProperties;
+import org.chromium.ui.modelutil.PropertyModel;
+
+/**
+ * Controller that allows the native autofill code to show an error dialog.
+ * For example: When unmasking a virtual card returns an error, we show an error dialog with more
+ * information about the error.
+ *
+ * Note: The error dialog only shows a positive button which dismisses the dialog.
+ */
+@JNINamespace("autofill")
+public class AutofillErrorDialogBridge {
+    private final long mNativeAutofillErrorDialogView;
+    private final ModalDialogManager mModalDialogManager;
+    private final Context mContext;
+    private PropertyModel mDialogModel;
+
+    private final ModalDialogProperties.Controller mModalDialogController =
+
+            new ModalDialogProperties.Controller() {
+                @Override
+                public void onClick(PropertyModel model, int buttonType) {
+                    mModalDialogManager.dismissDialog(
+                            mDialogModel, DialogDismissalCause.POSITIVE_BUTTON_CLICKED);
+                }
+
+                @Override
+                public void onDismiss(PropertyModel model, int dismissalCause) {
+                    AutofillErrorDialogBridgeJni.get().onDismissed(mNativeAutofillErrorDialogView);
+                }
+            };
+
+    public AutofillErrorDialogBridge(long nativeAutofillErrorDialogView,
+            ModalDialogManager modalDialogManager, Context context) {
+        this.mNativeAutofillErrorDialogView = nativeAutofillErrorDialogView;
+        this.mModalDialogManager = modalDialogManager;
+        this.mContext = context;
+    }
+
+    @CalledByNative
+    public static AutofillErrorDialogBridge create(
+            long nativeAutofillErrorDialogView, WindowAndroid windowAndroid) {
+        return new AutofillErrorDialogBridge(nativeAutofillErrorDialogView,
+                windowAndroid.getModalDialogManager(), windowAndroid.getActivity().get());
+    }
+
+    /**
+     * Shows an error dialog.
+     *
+     * @param title Title for the error dialog.
+     * @param description Description for the error dialog which shows below the title.
+     * @param buttonLabel Label for the positive button which acts as a cancel button.
+     * @param titleIconId The resource id for the icon to be displayed to the left of the title.
+     */
+    @CalledByNative
+    public void show(String title, String description, String buttonLabel, int titleIconId) {
+        PropertyModel.Builder builder =
+                new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS)
+                        .with(ModalDialogProperties.CONTROLLER, mModalDialogController)
+                        .with(ModalDialogProperties.TITLE, title)
+                        .with(ModalDialogProperties.MESSAGE, description)
+                        .with(ModalDialogProperties.NEGATIVE_BUTTON_DISABLED, true)
+                        .with(ModalDialogProperties.POSITIVE_BUTTON_TEXT, buttonLabel);
+        if (titleIconId != 0) {
+            builder.with(ModalDialogProperties.TITLE_ICON,
+                    ResourcesCompat.getDrawable(
+                            mContext.getResources(), titleIconId, mContext.getTheme()));
+        }
+        mDialogModel = builder.build();
+        mModalDialogManager.showDialog(mDialogModel, ModalDialogManager.ModalDialogType.TAB);
+    }
+
+    /**
+     * Dismisses the currently showing dialog.
+     */
+    @CalledByNative
+    public void dismiss() {
+        mModalDialogManager.dismissDialog(mDialogModel, DialogDismissalCause.DISMISSED_BY_NATIVE);
+    }
+
+    @NativeMethods
+    interface Natives {
+        void onDismissed(long nativeAutofillErrorDialogViewAndroid);
+    }
+}
diff --git a/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/AutofillErrorDialogBridgeTest.java b/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/AutofillErrorDialogBridgeTest.java
new file mode 100644
index 0000000..96fab39c
--- /dev/null
+++ b/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/AutofillErrorDialogBridgeTest.java
@@ -0,0 +1,118 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.ui.autofill;
+
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+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.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.JniMocker;
+import org.chromium.ui.modaldialog.ModalDialogManager;
+import org.chromium.ui.modaldialog.ModalDialogProperties;
+import org.chromium.ui.modelutil.PropertyModel;
+
+/**
+ * Unit tests for {@link AutofillErrorDialogBridge}
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+public class AutofillErrorDialogBridgeTest {
+    @Rule
+    public MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Rule
+    public JniMocker mMocker = new JniMocker();
+
+    private static final String ERROR_DIALOG_TITLE = "title";
+    private static final String ERROR_DIALOG_DESCRIPTION = "description";
+    private static final String ERROR_DIALOG_BUTTON_LABEL = "Close";
+    private static final long NATIVE_AUTOFILL_ERROR_DIALOG_VIEW = 100L;
+
+    @Mock
+    private AutofillErrorDialogBridge.Natives mNativeMock;
+    @Mock
+    private Context mContext;
+
+    private AutofillErrorDialogBridge mAutofillErrorDialogBridge;
+    private FakeModalDialogManager mModalDialogManager;
+
+    private class FakeModalDialogManager extends ModalDialogManager {
+        private PropertyModel mShownDialogModel;
+
+        public FakeModalDialogManager() {
+            super(Mockito.mock(Presenter.class), 0);
+        }
+
+        @Override
+        public void showDialog(PropertyModel model, int dialogType) {
+            mShownDialogModel = model;
+        }
+
+        @Override
+        public void dismissDialog(PropertyModel model, int dismissalCause) {
+            model.get(ModalDialogProperties.CONTROLLER).onDismiss(model, dismissalCause);
+            mShownDialogModel = null;
+        }
+
+        public void clickPositiveButton() {
+            mShownDialogModel.get(ModalDialogProperties.CONTROLLER)
+                    .onClick(mShownDialogModel, ModalDialogProperties.ButtonType.POSITIVE);
+        }
+
+        public PropertyModel getShownDialogModel() {
+            return mShownDialogModel;
+        }
+    }
+
+    @Before
+    public void setUp() {
+        reset(mNativeMock);
+        mModalDialogManager = new FakeModalDialogManager();
+        mAutofillErrorDialogBridge = new AutofillErrorDialogBridge(
+                NATIVE_AUTOFILL_ERROR_DIALOG_VIEW, mModalDialogManager, mContext);
+        mMocker.mock(AutofillErrorDialogBridgeJni.TEST_HOOKS, mNativeMock);
+    }
+
+    @Test
+    @SmallTest
+    public void testBasic() throws Exception {
+        showErrorDialog();
+        Assert.assertNotNull(mModalDialogManager.getShownDialogModel());
+
+        mAutofillErrorDialogBridge.dismiss();
+        // Verify that no dialog is shown and that the callback is triggered on dismissal.
+        Assert.assertNull(mModalDialogManager.getShownDialogModel());
+        verify(mNativeMock, times(1)).onDismissed(NATIVE_AUTOFILL_ERROR_DIALOG_VIEW);
+    }
+
+    @Test
+    @SmallTest
+    public void testDismissedCalledOnButtonClick() throws Exception {
+        showErrorDialog();
+
+        mModalDialogManager.clickPositiveButton();
+
+        verify(mNativeMock, times(1)).onDismissed(NATIVE_AUTOFILL_ERROR_DIALOG_VIEW);
+    }
+
+    private void showErrorDialog() {
+        mAutofillErrorDialogBridge.show(ERROR_DIALOG_TITLE, ERROR_DIALOG_DESCRIPTION,
+                ERROR_DIALOG_BUTTON_LABEL,
+                /* iconId= */ 0);
+    }
+}
diff --git a/chrome/browser/ui/app_list/app_service/app_service_context_menu.cc b/chrome/browser/ui/app_list/app_service/app_service_context_menu.cc
index a59907e..1d21cdd 100644
--- a/chrome/browser/ui/app_list/app_service/app_service_context_menu.cc
+++ b/chrome/browser/ui/app_list/app_service/app_service_context_menu.cc
@@ -27,8 +27,6 @@
 #include "chrome/browser/ui/app_list/extension_app_utils.h"
 #include "chrome/browser/ui/chrome_pages.h"
 #include "chrome/browser/ui/webui/settings/chromeos/app_management/app_management_uma.h"
-#include "chrome/browser/web_applications/components/app_registry_controller.h"
-#include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/services/app_service/public/cpp/types_util.h"
 #include "content/public/browser/context_menu_params.h"
@@ -50,9 +48,10 @@
       return apps::mojom::WindowMode::kBrowser;
     case ash::USE_LAUNCH_TYPE_WINDOW:
       return apps::mojom::WindowMode::kWindow;
+    case ash::USE_LAUNCH_TYPE_TABBED_WINDOW:
+      return apps::mojom::WindowMode::kTabbedWindow;
     case ash::USE_LAUNCH_TYPE_PINNED:
     case ash::USE_LAUNCH_TYPE_FULLSCREEN:
-    case ash::USE_LAUNCH_TYPE_TABBED_WINDOW:
     default:
       return apps::mojom::WindowMode::kUnknown;
   }
@@ -148,10 +147,8 @@
           command_id < ash::USE_LAUNCH_TYPE_COMMAND_END) {
         if (app_type_ == apps::mojom::AppType::kWeb &&
             command_id == ash::USE_LAUNCH_TYPE_TABBED_WINDOW) {
-          auto* provider = web_app::WebAppProvider::Get(profile());
-          DCHECK(provider);
-          provider->registry_controller().SetExperimentalTabbedWindowMode(
-              app_id(), true, /*is_user_action=*/true);
+          apps::AppServiceProxyFactory::GetForProfile(profile())->SetWindowMode(
+              app_id(), apps::mojom::WindowMode::kTabbedWindow);
           return;
         }
 
@@ -181,12 +178,6 @@
     case apps::mojom::AppType::kWeb:
       if (command_id >= ash::USE_LAUNCH_TYPE_COMMAND_START &&
           command_id < ash::USE_LAUNCH_TYPE_COMMAND_END) {
-        auto* provider = web_app::WebAppProvider::Get(profile());
-        DCHECK(provider);
-        if (provider->registrar().IsInExperimentalTabbedWindowMode(app_id())) {
-          return command_id == ash::USE_LAUNCH_TYPE_TABBED_WINDOW;
-        }
-
         auto user_window_mode = apps::mojom::WindowMode::kUnknown;
         apps::AppServiceProxyFactory::GetForProfile(profile())
             ->AppRegistryCache()
@@ -321,10 +312,6 @@
       apps::mojom::WindowMode user_window_mode =
           ConvertUseLaunchTypeCommandToWindowMode(command_id);
       if (user_window_mode != apps::mojom::WindowMode::kUnknown) {
-        auto* provider = web_app::WebAppProvider::Get(profile());
-        DCHECK(provider);
-        provider->registry_controller().SetExperimentalTabbedWindowMode(
-            app_id(), false, /*is_user_action=*/true);
         apps::AppServiceProxyFactory::GetForProfile(profile())->SetWindowMode(
             app_id(), user_window_mode);
       }
diff --git a/chrome/browser/ui/ash/shelf/app_service/app_service_shelf_context_menu.cc b/chrome/browser/ui/ash/shelf/app_service/app_service_shelf_context_menu.cc
index 3e75f4d..b8f3298 100644
--- a/chrome/browser/ui/ash/shelf/app_service/app_service_shelf_context_menu.cc
+++ b/chrome/browser/ui/ash/shelf/app_service/app_service_shelf_context_menu.cc
@@ -39,9 +39,6 @@
 #include "chrome/browser/ui/chrome_pages.h"
 #include "chrome/browser/ui/views/crostini/crostini_app_restart_dialog.h"
 #include "chrome/browser/ui/webui/settings/chromeos/app_management/app_management_uma.h"
-#include "chrome/browser/web_applications/components/app_registrar.h"
-#include "chrome/browser/web_applications/components/app_registry_controller.h"
-#include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/grit/generated_resources.h"
 #include "content/public/browser/context_menu_params.h"
 #include "extensions/browser/extension_prefs.h"
@@ -60,6 +57,8 @@
       return apps::mojom::WindowMode::kBrowser;
     case ash::LAUNCH_TYPE_WINDOW:
       return apps::mojom::WindowMode::kWindow;
+    case ash::LAUNCH_TYPE_TABBED_WINDOW:
+      return apps::mojom::WindowMode::kTabbedWindow;
     default:
       return apps::mojom::WindowMode::kUnknown;
   }
@@ -160,13 +159,7 @@
       break;
 
     case ash::LAUNCH_TYPE_TABBED_WINDOW:
-      if (app_type_ == apps::mojom::AppType::kWeb) {
-        auto* provider = web_app::WebAppProvider::Get(controller()->profile());
-        DCHECK(provider);
-        provider->registry_controller().SetExperimentalTabbedWindowMode(
-            item().id.app_id, true, /*is_user_action=*/true);
-      }
-      return;
+      FALLTHROUGH;
     case ash::LAUNCH_TYPE_PINNED_TAB:
       FALLTHROUGH;
     case ash::LAUNCH_TYPE_REGULAR_TAB:
@@ -216,15 +209,9 @@
   switch (app_type_) {
     case apps::mojom::AppType::kWeb:
     case apps::mojom::AppType::kSystemWeb: {
-      auto* provider = web_app::WebAppProvider::Get(controller()->profile());
-      DCHECK(provider);
       if ((command_id >= ash::LAUNCH_TYPE_PINNED_TAB &&
            command_id <= ash::LAUNCH_TYPE_WINDOW) ||
           command_id == ash::LAUNCH_TYPE_TABBED_WINDOW) {
-        if (provider->registrar().IsInExperimentalTabbedWindowMode(
-                item().id.app_id)) {
-          return command_id == ash::LAUNCH_TYPE_TABBED_WINDOW;
-        }
         auto user_window_mode = apps::mojom::WindowMode::kUnknown;
         apps::AppServiceProxyFactory::GetForProfile(controller()->profile())
             ->AppRegistryCache()
@@ -461,10 +448,6 @@
       apps::mojom::WindowMode user_window_mode =
           ConvertLaunchTypeCommandToWindowMode(command_id);
       if (user_window_mode != apps::mojom::WindowMode::kUnknown) {
-        auto* provider = web_app::WebAppProvider::Get(controller()->profile());
-        DCHECK(provider);
-        provider->registry_controller().SetExperimentalTabbedWindowMode(
-            item().id.app_id, false, /*is_user_action=*/true);
         apps::AppServiceProxyFactory::GetForProfile(controller()->profile())
             ->SetWindowMode(item().id.app_id, user_window_mode);
       }
diff --git a/chrome/browser/ui/ash/shelf/app_service/app_service_shelf_context_menu_browsertest.cc b/chrome/browser/ui/ash/shelf/app_service/app_service_shelf_context_menu_browsertest.cc
index 1d8266f..94eb237 100644
--- a/chrome/browser/ui/ash/shelf/app_service/app_service_shelf_context_menu_browsertest.cc
+++ b/chrome/browser/ui/ash/shelf/app_service/app_service_shelf_context_menu_browsertest.cc
@@ -123,6 +123,10 @@
       app_id, ash::LAUNCH_TYPE_TABBED_WINDOW);
   ASSERT_TRUE(menu_section);
   menu_section->sub_model->ActivatedAt(menu_section->command_index);
+
+  apps::AppServiceProxyFactory::GetForProfile(profile)
+      ->FlushMojoCallsForTesting();
+
   EXPECT_EQ(user_action_tester.GetActionCount("WebApp.SetWindowMode.Window"),
             1);
 
diff --git a/chrome/browser/ui/autofill/payments/autofill_error_dialog_controller.h b/chrome/browser/ui/autofill/payments/autofill_error_dialog_controller.h
new file mode 100644
index 0000000..dec7fe98
--- /dev/null
+++ b/chrome/browser/ui/autofill/payments/autofill_error_dialog_controller.h
@@ -0,0 +1,51 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_AUTOFILL_ERROR_DIALOG_CONTROLLER_H_
+#define CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_AUTOFILL_ERROR_DIALOG_CONTROLLER_H_
+#include <string>
+
+#include "content/public/browser/web_contents.h"
+
+namespace autofill {
+
+// Interface that exposes controller functionality to AutofillErrorDialogView.
+// The interface exposes the title, description and the button label to the view
+// to help show an error dialog with a single button that acts as a cancel
+// button. For example: We show an error dialog when unmasking a virtual card
+// fails.
+//
+// Note: This is only used for virtual card related errors.
+class AutofillErrorDialogController {
+ public:
+  enum AutofillErrorDialogType {
+    // Error shown when the server returns a temporary error for unmasking a
+    // virtual card.
+    VIRTUAL_CARD_TEMPORARY_ERROR,
+    // Error shown when the server returns a permanent error for unmasking a
+    // virtual card.
+    VIRTUAL_CARD_PERMANENT_ERROR,
+    // Error shown when the server says that the virtual card being unmasked is
+    // not eligible for the virtual card feature.
+    VIRTUAL_CARD_NOT_ELIGIBLE_ERROR
+  };
+
+  // Callback received when the error dialog is dismissed.
+  virtual void OnDismissed() = 0;
+
+  // Title to displayed on the error dialog.
+  virtual const std::u16string GetTitle() = 0;
+  // Description of the error to be displayed below the title.
+  virtual const std::u16string GetDescription() = 0;
+  // Text for the positive button which cancels the dialog.
+  virtual const std::u16string GetButtonLabel() = 0;
+  virtual content::WebContents* GetWebContents() = 0;
+
+ protected:
+  virtual ~AutofillErrorDialogController() = default;
+};
+
+}  // namespace autofill
+
+#endif  // CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_AUTOFILL_ERROR_DIALOG_CONTROLLER_H_
diff --git a/chrome/browser/ui/autofill/payments/autofill_error_dialog_controller_impl.cc b/chrome/browser/ui/autofill/payments/autofill_error_dialog_controller_impl.cc
new file mode 100644
index 0000000..96398c2
--- /dev/null
+++ b/chrome/browser/ui/autofill/payments/autofill_error_dialog_controller_impl.cc
@@ -0,0 +1,94 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/autofill/payments/autofill_error_dialog_controller_impl.h"
+
+#include "components/strings/grit/components_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace autofill {
+
+AutofillErrorDialogControllerImpl::AutofillErrorDialogControllerImpl(
+    content::WebContents* web_contents)
+    : web_contents_(web_contents) {}
+
+AutofillErrorDialogControllerImpl::~AutofillErrorDialogControllerImpl() {
+  Dismiss();
+}
+
+void AutofillErrorDialogControllerImpl::Show(
+    std::unique_ptr<AutofillErrorDialogView> autofill_error_dialog_view,
+    AutofillErrorDialogController::AutofillErrorDialogType error_dialog_type) {
+  if (autofill_error_dialog_view_)
+    Dismiss();
+
+  DCHECK(autofill_error_dialog_view_ == nullptr);
+  autofill_error_dialog_view_ = std::move(autofill_error_dialog_view);
+  error_dialog_type_ = error_dialog_type;
+  autofill_error_dialog_view_->Show();
+}
+
+void AutofillErrorDialogControllerImpl::OnDismissed() {
+  // TODO(crbug.com/1196021): Log the dismiss action along with the type of the
+  // error dialog.
+  autofill_error_dialog_view_.reset();
+}
+
+const std::u16string AutofillErrorDialogControllerImpl::GetTitle() {
+  int title_string_resource_id = 0;
+  switch (error_dialog_type_) {
+    case VIRTUAL_CARD_TEMPORARY_ERROR:
+      title_string_resource_id =
+          IDS_AUTOFILL_VIRTUAL_CARD_TEMPORARY_ERROR_TITLE;
+      break;
+    case VIRTUAL_CARD_PERMANENT_ERROR:
+      title_string_resource_id =
+          IDS_AUTOFILL_VIRTUAL_CARD_PERMANENT_ERROR_TITLE;
+      break;
+    case VIRTUAL_CARD_NOT_ELIGIBLE_ERROR:
+      title_string_resource_id =
+          IDS_AUTOFILL_VIRTUAL_CARD_NOT_ELIGIBLE_ERROR_TITLE;
+      break;
+  }
+  return title_string_resource_id != 0
+             ? l10n_util::GetStringUTF16(title_string_resource_id)
+             : std::u16string();
+}
+
+const std::u16string AutofillErrorDialogControllerImpl::GetDescription() {
+  int description_string_resource_id = 0;
+  switch (error_dialog_type_) {
+    case VIRTUAL_CARD_TEMPORARY_ERROR:
+      description_string_resource_id =
+          IDS_AUTOFILL_VIRTUAL_CARD_TEMPORARY_ERROR_DESCRIPTION;
+      break;
+    case VIRTUAL_CARD_PERMANENT_ERROR:
+      description_string_resource_id =
+          IDS_AUTOFILL_VIRTUAL_CARD_PERMANENT_ERROR_DESCRIPTION;
+      break;
+    case VIRTUAL_CARD_NOT_ELIGIBLE_ERROR:
+      description_string_resource_id =
+          IDS_AUTOFILL_VIRTUAL_CARD_NOT_ELIGIBLE_ERROR_DESCRIPTION;
+      break;
+  }
+  return description_string_resource_id != 0
+             ? l10n_util::GetStringUTF16(description_string_resource_id)
+             : std::u16string();
+}
+
+const std::u16string AutofillErrorDialogControllerImpl::GetButtonLabel() {
+  return l10n_util::GetStringUTF16(
+      IDS_AUTOFILL_ERROR_DIALOG_NEGATIVE_BUTTON_LABEL);
+}
+
+content::WebContents* AutofillErrorDialogControllerImpl::GetWebContents() {
+  return web_contents_;
+}
+
+void AutofillErrorDialogControllerImpl::Dismiss() {
+  if (autofill_error_dialog_view_)
+    autofill_error_dialog_view_->Dismiss();
+}
+
+}  // namespace autofill
diff --git a/chrome/browser/ui/autofill/payments/autofill_error_dialog_controller_impl.h b/chrome/browser/ui/autofill/payments/autofill_error_dialog_controller_impl.h
new file mode 100644
index 0000000..b0a8128
--- /dev/null
+++ b/chrome/browser/ui/autofill/payments/autofill_error_dialog_controller_impl.h
@@ -0,0 +1,55 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_AUTOFILL_ERROR_DIALOG_CONTROLLER_IMPL_H_
+#define CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_AUTOFILL_ERROR_DIALOG_CONTROLLER_IMPL_H_
+
+#include <string>
+
+#include "chrome/browser/ui/autofill/payments/autofill_error_dialog_controller.h"
+#include "chrome/browser/ui/autofill/payments/autofill_error_dialog_view.h"
+#include "content/public/browser/web_contents.h"
+
+namespace autofill {
+
+// Implementation of the AutofillErrorDialogController. This class allows error
+// dialog to be shown or dismissed.
+// The controller is destroyed once the view is dismissed.
+class AutofillErrorDialogControllerImpl : public AutofillErrorDialogController {
+ public:
+  explicit AutofillErrorDialogControllerImpl(
+      content::WebContents* web_contents);
+  ~AutofillErrorDialogControllerImpl() override;
+
+  AutofillErrorDialogControllerImpl(const AutofillErrorDialogControllerImpl&) =
+      delete;
+  AutofillErrorDialogControllerImpl& operator=(
+      const AutofillErrorDialogControllerImpl&) = delete;
+
+  // Show the error dialog for the given `AutofillErrorDialogType`
+  void Show(
+      std::unique_ptr<AutofillErrorDialogView> view,
+      AutofillErrorDialogController::AutofillErrorDialogType error_dialog_type);
+
+  // AutofillErrorDialogController.
+  void OnDismissed() override;
+  const std::u16string GetTitle() override;
+  const std::u16string GetDescription() override;
+  const std::u16string GetButtonLabel() override;
+  content::WebContents* GetWebContents() override;
+
+ private:
+  // Dismiss the error dialog if showing.
+  void Dismiss();
+
+  content::WebContents* web_contents_;
+  // The type of the error dialog that is being displayed.
+  AutofillErrorDialogController::AutofillErrorDialogType error_dialog_type_;
+  // View that displays the error dialog.
+  std::unique_ptr<AutofillErrorDialogView> autofill_error_dialog_view_;
+};
+
+}  // namespace autofill
+
+#endif  // CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_AUTOFILL_ERROR_DIALOG_CONTROLLER_IMPL_H_
diff --git a/chrome/browser/ui/autofill/payments/autofill_error_dialog_view.h b/chrome/browser/ui/autofill/payments/autofill_error_dialog_view.h
new file mode 100644
index 0000000..f99f7c00
--- /dev/null
+++ b/chrome/browser/ui/autofill/payments/autofill_error_dialog_view.h
@@ -0,0 +1,28 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_AUTOFILL_ERROR_DIALOG_VIEW_H_
+#define CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_AUTOFILL_ERROR_DIALOG_VIEW_H_
+
+namespace autofill {
+
+// The cross-platform view interface which helps show an error dialog for
+// autofill flows.
+//
+// Note: This is only used for virtual card related errors.
+class AutofillErrorDialogView {
+ public:
+  virtual ~AutofillErrorDialogView() = default;
+
+  virtual void Show() = 0;
+  virtual void Dismiss() = 0;
+
+  // Factory function for creating the view.
+  static std::unique_ptr<AutofillErrorDialogView> Create(
+      AutofillErrorDialogController* controller);
+};
+
+}  // namespace autofill
+
+#endif  // CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_AUTOFILL_ERROR_DIALOG_VIEW_H_
diff --git a/chrome/browser/ui/commander/commander_controller_unittest.cc b/chrome/browser/ui/commander/commander_controller_unittest.cc
index 016fb2d..a6b27ebc 100644
--- a/chrome/browser/ui/commander/commander_controller_unittest.cc
+++ b/chrome/browser/ui/commander/commander_controller_unittest.cc
@@ -51,7 +51,7 @@
 
 std::unique_ptr<CommandItem> CreateNoOpCommandItem(const std::u16string& title,
                                                    double score) {
-  std::vector<gfx::Range> ranges{{0, title.size()}};
+  std::vector<gfx::Range> ranges{{0, static_cast<uint32_t>(title.size())}};
   auto item = std::make_unique<CommandItem>(title, score, ranges);
   item->command = base::DoNothing::Once();
   return item;
diff --git a/chrome/browser/ui/commander/entity_match_unittest.cc b/chrome/browser/ui/commander/entity_match_unittest.cc
index 9bdcb97..bec631a 100644
--- a/chrome/browser/ui/commander/entity_match_unittest.cc
+++ b/chrome/browser/ui/commander/entity_match_unittest.cc
@@ -50,7 +50,8 @@
     TabStripModel* tab_strip_model = browser()->tab_strip_model();
     TabGroupModel* group_model = tab_strip_model->group_model();
     for (size_t i = 0; i < titles.size(); ++i) {
-      tab_groups::TabGroupId group = tab_strip_model->AddToNewGroup({i});
+      tab_groups::TabGroupId group =
+          tab_strip_model->AddToNewGroup({static_cast<int>(i)});
       tab_groups::TabGroupVisualData data(
           titles.at(i), tab_groups::TabGroupColorId::kGrey, false);
       group_model->GetTabGroup(group)->SetVisualData(data);
diff --git a/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc b/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc
index acb00e7..3e7db465 100644
--- a/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc
@@ -278,8 +278,7 @@
     std::move(quit_closure_).Run();
 }
 
-#if defined(OS_WIN) || defined(OS_MAC) || \
-    (defined(OS_LINUX) && !BUILDFLAG(IS_CHROMEOS_LACROS))
+#if defined(OS_WIN) || (defined(OS_LINUX) && !BUILDFLAG(IS_CHROMEOS_LACROS))
 void AutoCloseDialog(views::Widget* widget) {
   // Call CancelDialog to close the dialog, but the actual behavior will be
   // determined by the ScopedTestDialogAutoConfirm configs.
@@ -1633,8 +1632,9 @@
 #endif  // BUILDFLAG(ENABLE_APP_SESSION_SERVICE)
 #endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 
-#if defined(OS_WIN) || defined(OS_MAC) || \
-    (defined(OS_LINUX) && !BUILDFLAG(IS_CHROMEOS_LACROS))
+// TODO(crbug.com/1217869): Test is disabled on Mac since it causes failures
+// in many bots.
+#if defined(OS_WIN) || (defined(OS_LINUX) && !BUILDFLAG(IS_CHROMEOS_LACROS))
 class StartupBrowserWebAppUrlHandlingTest : public InProcessBrowserTest {
  protected:
   StartupBrowserWebAppUrlHandlingTest() {
diff --git a/chrome/browser/ui/views/desktop_capture/desktop_media_picker_views_test_api.cc b/chrome/browser/ui/views/desktop_capture/desktop_media_picker_views_test_api.cc
index 45a3bd1c..f0303bc 100644
--- a/chrome/browser/ui/views/desktop_capture/desktop_media_picker_views_test_api.cc
+++ b/chrome/browser/ui/views/desktop_capture/desktop_media_picker_views_test_api.cc
@@ -111,7 +111,7 @@
   const views::TableView* table = GetTableView();
   if (table)
     return base::checked_cast<size_t>(table->GetRowCount()) > index;
-  return bool{GetSourceAtIndex(index)};
+  return !!GetSourceAtIndex(index);
 }
 
 views::View* DesktopMediaPickerViewsTestApi::GetSelectedListView() {
diff --git a/chrome/browser/ui/views/webid/webid_permission_view.cc b/chrome/browser/ui/views/webid/webid_permission_view.cc
index da3a0c6..039dd34e 100644
--- a/chrome/browser/ui/views/webid/webid_permission_view.cc
+++ b/chrome/browser/ui/views/webid/webid_permission_view.cc
@@ -83,9 +83,13 @@
 
   secondary_label->SetText(secondary_text);
   secondary_label->AddStyleRange(
-      gfx::Range{offsets[0], offsets[0] + idp_hostname.length()}, bold_style);
+      gfx::Range{static_cast<uint32_t>(offsets[0]),
+                 static_cast<uint32_t>(offsets[0] + idp_hostname.length())},
+      bold_style);
   secondary_label->AddStyleRange(
-      gfx::Range{offsets[1], offsets[1] + rp_hostname.length()}, bold_style);
+      gfx::Range{static_cast<uint32_t>(offsets[1]),
+                 static_cast<uint32_t>(offsets[1] + rp_hostname.length())},
+      bold_style);
   view->AddChildView(std::move(secondary_label));
   return view;
 }
@@ -133,9 +137,13 @@
   views::StyledLabel::RangeStyleInfo bold_style;
   bold_style.text_style = STYLE_EMPHASIZED_SECONDARY;
   secondary_label->AddStyleRange(
-      gfx::Range{offsets[0], offsets[0] + rp_hostname.length()}, bold_style);
+      gfx::Range{static_cast<uint32_t>(offsets[0]),
+                 static_cast<uint32_t>(offsets[0] + rp_hostname.length())},
+      bold_style);
   secondary_label->AddStyleRange(
-      gfx::Range{offsets[1], offsets[1] + idp_hostname.length()}, bold_style);
+      gfx::Range{static_cast<uint32_t>(offsets[1]),
+                 static_cast<uint32_t>(offsets[1] + idp_hostname.length())},
+      bold_style);
   view->AddChildView(std::move(secondary_label));
   return view;
 }
diff --git a/chrome/browser/ui/web_applications/test/system_web_app_interactive_uitest.cc b/chrome/browser/ui/web_applications/test/system_web_app_interactive_uitest.cc
index 2c1be6e..0f4363da 100644
--- a/chrome/browser/ui/web_applications/test/system_web_app_interactive_uitest.cc
+++ b/chrome/browser/ui/web_applications/test/system_web_app_interactive_uitest.cc
@@ -123,14 +123,14 @@
 }
 
 // This test is flaky on MacOS with ASAN or DBG. https://crbug.com/1173317
-// Also flaky on MacOS in general, see https://crbug.com/1216076
 #if defined(OS_MAC)
+#if defined(ADDRESS_SANITIZER) || !defined(NDEBUG)
 #define MAYBE_AnchorLinkClick DISABLED_AnchorLinkClick
 #else
 #define MAYBE_AnchorLinkClick AnchorLinkClick
+#endif  // ADDRESS_SANITIZER || !NDEBUG
 #endif  // OS_MAC
-IN_PROC_BROWSER_TEST_P(SystemWebAppLinkCaptureBrowserTest,
-                       MAYBE_AnchorLinkClick) {
+IN_PROC_BROWSER_TEST_P(SystemWebAppLinkCaptureBrowserTest, AnchorLinkClick) {
   WaitForTestSystemAppInstall();
 
   GURL kInitiatingChromeUrl = GURL(chrome::kChromeUIAboutURL);
@@ -303,13 +303,7 @@
                                       ->GetLastCommittedURL());
 }
 
-// Flaky on Mac, see https://crbug.com/1216076
-#if defined(OS_MAC)
-#define MAYBE_WindowOpen DISABLED_WindowOpen
-#else
-#define MAYBE_WindowOpen WindowOpen
-#endif  // OS_MAC
-IN_PROC_BROWSER_TEST_P(SystemWebAppLinkCaptureBrowserTest, MAYBE_WindowOpen) {
+IN_PROC_BROWSER_TEST_P(SystemWebAppLinkCaptureBrowserTest, WindowOpen) {
   WaitForTestSystemAppInstall();
 
   GURL kInitiatingChromeUrl = GURL(chrome::kChromeUIAboutURL);
@@ -356,14 +350,8 @@
   }
 }
 
-// Flaky on Mac, see https://crbug.com/1216076
-#if defined(OS_MAC)
-#define MAYBE_WindowOpenFromOtherSWA DISABLED_WindowOpenFromOtherSWA
-#else
-#define MAYBE_WindowOpenFromOtherSWA WindowOpenFromOtherSWA
-#endif  // OS_MAC
 IN_PROC_BROWSER_TEST_P(SystemWebAppLinkCaptureBrowserTest,
-                       MAYBE_WindowOpenFromOtherSWA) {
+                       WindowOpenFromOtherSWA) {
   WaitForTestSystemAppInstall();
 
   content::WebContents* initiating_web_contents = LaunchApp(kInitiatingAppType);
diff --git a/chrome/browser/ui/webui/chromeos/smb_shares/smb_handler.cc b/chrome/browser/ui/webui/chromeos/smb_shares/smb_handler.cc
index 7ccee0b9..de61cf5 100644
--- a/chrome/browser/ui/webui/chromeos/smb_shares/smb_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/smb_shares/smb_handler.cc
@@ -81,7 +81,7 @@
     return;
   }
 
-  chromeos::file_system_provider::MountOptions mo;
+  file_system_provider::MountOptions mo;
   mo.display_name = mount_name.empty() ? mount_url : mount_name;
   mo.writable = true;
 
diff --git a/chrome/browser/ui/webui/conflicts/conflicts_data_fetcher.cc b/chrome/browser/ui/webui/conflicts/conflicts_data_fetcher.cc
index 9fc6374..e1bb7b8e 100644
--- a/chrome/browser/ui/webui/conflicts/conflicts_data_fetcher.cc
+++ b/chrome/browser/ui/webui/conflicts/conflicts_data_fetcher.cc
@@ -424,7 +424,7 @@
   module_list_ = std::make_unique<base::ListValue>();
 
   auto* module_database = ModuleDatabase::GetInstance();
-  module_database->ForceStartInspection();
+  module_database->IncreaseInspectionPriority();
   module_database->AddObserver(this);
 }
 
diff --git a/chrome/browser/web_applications/app_service/web_app_publisher_helper.cc b/chrome/browser/web_applications/app_service/web_app_publisher_helper.cc
index 30f86ae..b1a0815 100644
--- a/chrome/browser/web_applications/app_service/web_app_publisher_helper.cc
+++ b/chrome/browser/web_applications/app_service/web_app_publisher_helper.cc
@@ -564,13 +564,23 @@
     case apps::mojom::WindowMode::kWindow:
       display_mode = blink::mojom::DisplayMode::kStandalone;
       break;
+    case apps::mojom::WindowMode::kTabbedWindow:
+      provider_->registry_controller().SetExperimentalTabbedWindowMode(
+          app_id, /*enabled=*/true, /*is_user_action=*/true);
+      return;
   }
+  provider_->registry_controller().SetExperimentalTabbedWindowMode(
+      app_id, /*enabled=*/false, /*is_user_action=*/true);
   provider_->registry_controller().SetAppUserDisplayMode(
       app_id, display_mode, /*is_user_action=*/true);
 }
 
 apps::mojom::WindowMode WebAppPublisherHelper::ConvertDisplayModeToWindowMode(
-    blink::mojom::DisplayMode display_mode) {
+    blink::mojom::DisplayMode display_mode,
+    bool in_experimental_tabbed_window) {
+  if (in_experimental_tabbed_window) {
+    return apps::mojom::WindowMode::kTabbedWindow;
+  }
   switch (display_mode) {
     case blink::mojom::DisplayMode::kUndefined:
       return apps::mojom::WindowMode::kUnknown;
@@ -584,6 +594,20 @@
   }
 }
 
+void WebAppPublisherHelper::PublishWindowModeUpdate(
+    const std::string& app_id,
+    blink::mojom::DisplayMode display_mode,
+    bool in_experimental_tabbed_window) {
+  if (GetWebApp(app_id) && Accepts(app_id)) {
+    apps::mojom::AppPtr app = apps::mojom::App::New();
+    app->app_type = app_type();
+    app->app_id = app_id;
+    app->window_mode = ConvertDisplayModeToWindowMode(
+        display_mode, in_experimental_tabbed_window);
+    delegate_->PublishWebApp(std::move(app));
+  }
+}
+
 WebAppRegistrar& WebAppPublisherHelper::registrar() const {
   return *provider_->registrar().AsWebAppRegistrar();
 }
diff --git a/chrome/browser/web_applications/app_service/web_app_publisher_helper.h b/chrome/browser/web_applications/app_service/web_app_publisher_helper.h
index 03c960e..bfb7194f 100644
--- a/chrome/browser/web_applications/app_service/web_app_publisher_helper.h
+++ b/chrome/browser/web_applications/app_service/web_app_publisher_helper.h
@@ -153,7 +153,12 @@
 
   // Converts |display_mode| to a |window_mode|.
   apps::mojom::WindowMode ConvertDisplayModeToWindowMode(
-      blink::mojom::DisplayMode display_mode);
+      blink::mojom::DisplayMode display_mode,
+      bool in_experimental_tabbed_window);
+
+  void PublishWindowModeUpdate(const std::string& app_id,
+                               blink::mojom::DisplayMode display_mode,
+                               bool in_experimental_tabbed_window);
 
   Profile* profile() { return profile_; }
 
diff --git a/chrome/browser/web_applications/app_service/web_apps_chromeos.cc b/chrome/browser/web_applications/app_service/web_apps_chromeos.cc
index 9e7a1c9..4401cd5f 100644
--- a/chrome/browser/web_applications/app_service/web_apps_chromeos.cc
+++ b/chrome/browser/web_applications/app_service/web_apps_chromeos.cc
@@ -191,9 +191,9 @@
 
   if (!is_system_web_app) {
     apps::CreateOpenNewSubmenu(menu_type,
-                               display_mode == apps::mojom::WindowMode::kWindow
-                                   ? IDS_APP_LIST_CONTEXT_MENU_NEW_WINDOW
-                                   : IDS_APP_LIST_CONTEXT_MENU_NEW_TAB,
+                               display_mode == apps::mojom::WindowMode::kBrowser
+                                   ? IDS_APP_LIST_CONTEXT_MENU_NEW_TAB
+                                   : IDS_APP_LIST_CONTEXT_MENU_NEW_WINDOW,
                                &menu_items);
   }
 
@@ -435,14 +435,16 @@
 void WebAppsChromeOs::OnWebAppUserDisplayModeChanged(
     const AppId& app_id,
     DisplayMode user_display_mode) {
-  if (GetWebApp(app_id) && Accepts(app_id)) {
-    apps::mojom::AppPtr app = apps::mojom::App::New();
-    app->app_type = app_type();
-    app->app_id = app_id;
-    app->window_mode =
-        publisher_helper().ConvertDisplayModeToWindowMode(user_display_mode);
-    Publish(std::move(app), subscribers());
-  }
+  publisher_helper().PublishWindowModeUpdate(
+      app_id, user_display_mode,
+      GetRegistrar()->IsInExperimentalTabbedWindowMode(app_id));
+}
+
+void WebAppsChromeOs::OnWebAppExperimentalTabbedWindowModeChanged(
+    const AppId& app_id,
+    bool enabled) {
+  auto display_mode = GetRegistrar()->GetAppUserDisplayMode(app_id);
+  publisher_helper().PublishWindowModeUpdate(app_id, display_mode, enabled);
 }
 
 void WebAppsChromeOs::UpdateAppDisabledMode(apps::mojom::AppPtr& app) {
@@ -654,8 +656,9 @@
   app->icon_key = publisher_helper().MakeIconKey(web_app);
 
   auto display_mode = GetRegistrar()->GetAppUserDisplayMode(web_app->app_id());
-  app->window_mode =
-      publisher_helper().ConvertDisplayModeToWindowMode(display_mode);
+  app->window_mode = publisher_helper().ConvertDisplayModeToWindowMode(
+      display_mode,
+      GetRegistrar()->IsInExperimentalTabbedWindowMode(web_app->app_id()));
 
   apps::mojom::OptionalBool has_notification =
       app_notifications_.HasNotification(web_app->app_id())
diff --git a/chrome/browser/web_applications/app_service/web_apps_chromeos.h b/chrome/browser/web_applications/app_service/web_apps_chromeos.h
index 4176bc9..1be2f0c 100644
--- a/chrome/browser/web_applications/app_service/web_apps_chromeos.h
+++ b/chrome/browser/web_applications/app_service/web_apps_chromeos.h
@@ -103,6 +103,8 @@
   void OnWebAppsDisabledModeChanged() override;
   void OnWebAppUserDisplayModeChanged(const AppId& app_id,
                                       DisplayMode user_display_mode) override;
+  void OnWebAppExperimentalTabbedWindowModeChanged(const AppId& app_id,
+                                                   bool enabled) override;
 
   // Updates app visibility.
   void UpdateAppDisabledMode(apps::mojom::AppPtr& app);
diff --git a/chrome/browser/web_applications/app_service/web_apps_publisher_host.cc b/chrome/browser/web_applications/app_service/web_apps_publisher_host.cc
index c24dc4f2..7f6b9a07 100644
--- a/chrome/browser/web_applications/app_service/web_apps_publisher_host.cc
+++ b/chrome/browser/web_applications/app_service/web_apps_publisher_host.cc
@@ -234,14 +234,16 @@
 void WebAppsPublisherHost::OnWebAppUserDisplayModeChanged(
     const AppId& app_id,
     DisplayMode user_display_mode) {
-  if (GetWebApp(app_id)) {
-    apps::mojom::AppPtr app = apps::mojom::App::New();
-    app->app_type = apps::mojom::AppType::kWeb;
-    app->app_id = app_id;
-    app->window_mode =
-        publisher_helper().ConvertDisplayModeToWindowMode(user_display_mode);
-    PublishWebApp(std::move(app));
-  }
+  publisher_helper().PublishWindowModeUpdate(
+      app_id, user_display_mode,
+      registrar().IsInExperimentalTabbedWindowMode(app_id));
+}
+
+void WebAppsPublisherHost::OnWebAppExperimentalTabbedWindowModeChanged(
+    const AppId& app_id,
+    bool enabled) {
+  auto display_mode = registrar().GetAppUserDisplayMode(app_id);
+  publisher_helper().PublishWindowModeUpdate(app_id, display_mode, enabled);
 }
 
 void WebAppsPublisherHost::OnRequestUpdate(
diff --git a/chrome/browser/web_applications/app_service/web_apps_publisher_host.h b/chrome/browser/web_applications/app_service/web_apps_publisher_host.h
index 3820d0e..d2ff2fb 100644
--- a/chrome/browser/web_applications/app_service/web_apps_publisher_host.h
+++ b/chrome/browser/web_applications/app_service/web_apps_publisher_host.h
@@ -123,7 +123,8 @@
       const base::Time& last_launch_time) override;
   void OnWebAppUserDisplayModeChanged(const AppId& app_id,
                                       DisplayMode user_display_mode) override;
-
+  void OnWebAppExperimentalTabbedWindowModeChanged(const AppId& app_id,
+                                                   bool enabled) override;
   // TODO(crbug.com/1194709): Add more overrides, guided by WebAppsChromeOs.
 
   // MediaCaptureDevicesDispatcher::Observer:
diff --git a/chrome/browser/web_applications/components/app_registrar.cc b/chrome/browser/web_applications/components/app_registrar.cc
index d82afd1..5b62bbc 100644
--- a/chrome/browser/web_applications/components/app_registrar.cc
+++ b/chrome/browser/web_applications/components/app_registrar.cc
@@ -122,6 +122,13 @@
     observer.OnWebAppUserDisplayModeChanged(app_id, user_display_mode);
 }
 
+void AppRegistrar::NotifyWebAppExperimentalTabbedWindowModeChanged(
+    const AppId& app_id,
+    bool enabled) {
+  for (AppRegistrarObserver& observer : observers_)
+    observer.OnWebAppExperimentalTabbedWindowModeChanged(app_id, enabled);
+}
+
 void AppRegistrar::NotifyAppRegistrarShutdown() {
   for (AppRegistrarObserver& observer : observers_)
     observer.OnAppRegistrarShutdown();
diff --git a/chrome/browser/web_applications/components/app_registrar.h b/chrome/browser/web_applications/components/app_registrar.h
index 65a2b75..b604a58 100644
--- a/chrome/browser/web_applications/components/app_registrar.h
+++ b/chrome/browser/web_applications/components/app_registrar.h
@@ -238,6 +238,8 @@
   void NotifyWebAppInstalledWithOsHooks(const AppId& app_id);
   void NotifyWebAppUserDisplayModeChanged(const AppId& app_id,
                                           DisplayMode user_display_mode);
+  void NotifyWebAppExperimentalTabbedWindowModeChanged(const AppId& app_id,
+                                                       bool enabled);
 
  protected:
   Profile* profile() const { return profile_; }
diff --git a/chrome/browser/web_applications/components/app_registrar_observer.h b/chrome/browser/web_applications/components/app_registrar_observer.h
index 57164c35..9307fa99 100644
--- a/chrome/browser/web_applications/components/app_registrar_observer.h
+++ b/chrome/browser/web_applications/components/app_registrar_observer.h
@@ -64,9 +64,10 @@
                                              const base::Time& time) {}
   virtual void OnWebAppInstallTimeChanged(const AppId& app_id,
                                           const base::Time& time) {}
-
   virtual void OnWebAppUserDisplayModeChanged(const AppId& app_id,
                                               DisplayMode user_display_mode) {}
+  virtual void OnWebAppExperimentalTabbedWindowModeChanged(const AppId& app_id,
+                                                           bool enabled) {}
 };
 
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/components/app_registry_controller.cc b/chrome/browser/web_applications/components/app_registry_controller.cc
index 4f8abc9..9358cab 100644
--- a/chrome/browser/web_applications/components/app_registry_controller.cc
+++ b/chrome/browser/web_applications/components/app_registry_controller.cc
@@ -4,10 +4,7 @@
 
 #include "chrome/browser/web_applications/components/app_registry_controller.h"
 
-#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/web_applications/components/os_integration_manager.h"
-#include "chrome/browser/web_applications/components/web_app_prefs_utils.h"
-#include "content/public/common/content_features.h"
 
 namespace web_app {
 
@@ -21,22 +18,4 @@
   os_integration_manager_ = os_integration_manager;
 }
 
-void AppRegistryController::SetExperimentalTabbedWindowMode(
-    const AppId& app_id,
-    bool enabled,
-    bool is_user_action) {
-  if (enabled) {
-    DCHECK(base::FeatureList::IsEnabled(features::kDesktopPWAsTabStrip));
-    UpdateBoolWebAppPref(profile()->GetPrefs(), app_id,
-                         kExperimentalTabbedWindowMode, true);
-
-    // Set non-experimental window mode to standalone for when the user disables
-    // this flag.
-    SetAppUserDisplayMode(app_id, DisplayMode::kStandalone, is_user_action);
-  } else {
-    RemoveWebAppPref(profile()->GetPrefs(), app_id,
-                     kExperimentalTabbedWindowMode);
-  }
-}
-
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/components/app_registry_controller.h b/chrome/browser/web_applications/components/app_registry_controller.h
index 227a4fb..f2a4117 100644
--- a/chrome/browser/web_applications/components/app_registry_controller.h
+++ b/chrome/browser/web_applications/components/app_registry_controller.h
@@ -50,9 +50,9 @@
 
   // TODO(crbug.com/897314): Finish experiment by legitimising it as a
   // DisplayMode or removing entirely.
-  void SetExperimentalTabbedWindowMode(const AppId& app_id,
-                                       bool enabled,
-                                       bool is_user_action);
+  virtual void SetExperimentalTabbedWindowMode(const AppId& app_id,
+                                               bool enabled,
+                                               bool is_user_action) = 0;
 
   virtual void SetAppIsLocallyInstalled(const AppId& app_id,
                                         bool is_locally_installed) = 0;
diff --git a/chrome/browser/web_applications/test/test_app_registry_controller.cc b/chrome/browser/web_applications/test/test_app_registry_controller.cc
index 2df35b1..5ee314b 100644
--- a/chrome/browser/web_applications/test/test_app_registry_controller.cc
+++ b/chrome/browser/web_applications/test/test_app_registry_controller.cc
@@ -22,6 +22,11 @@
                                                  bool is_disabled) {}
 void TestAppRegistryController::UpdateAppsDisableMode() {}
 
+void TestAppRegistryController::SetExperimentalTabbedWindowMode(
+    const AppId& app_id,
+    bool enabled,
+    bool is_user_action) {}
+
 void TestAppRegistryController::SetAppIsLocallyInstalled(
     const AppId& app_id,
     bool is_locally_installed) {}
diff --git a/chrome/browser/web_applications/test/test_app_registry_controller.h b/chrome/browser/web_applications/test/test_app_registry_controller.h
index 927f72c..dd27f30 100644
--- a/chrome/browser/web_applications/test/test_app_registry_controller.h
+++ b/chrome/browser/web_applications/test/test_app_registry_controller.h
@@ -21,6 +21,9 @@
                              bool is_user_action) override;
   void SetAppIsDisabled(const AppId& app_id, bool is_disabled) override;
   void UpdateAppsDisableMode() override;
+  void SetExperimentalTabbedWindowMode(const AppId& app_id,
+                                       bool enabled,
+                                       bool is_user_action) override;
   void SetAppIsLocallyInstalled(const AppId& app_id,
                                 bool is_locally_installed) override;
   void SetAppLastBadgingTime(const AppId& app_id,
diff --git a/chrome/browser/web_applications/web_app_sync_bridge.cc b/chrome/browser/web_applications/web_app_sync_bridge.cc
index 4abb4fd..13e8f6ad 100644
--- a/chrome/browser/web_applications/web_app_sync_bridge.cc
+++ b/chrome/browser/web_applications/web_app_sync_bridge.cc
@@ -14,9 +14,11 @@
 #include "base/logging.h"
 #include "base/metrics/user_metrics.h"
 #include "base/types/pass_key.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/web_applications/components/app_registry_controller.h"
 #include "chrome/browser/web_applications/components/os_integration_manager.h"
 #include "chrome/browser/web_applications/components/web_app_helpers.h"
+#include "chrome/browser/web_applications/components/web_app_prefs_utils.h"
 #include "chrome/browser/web_applications/components/web_app_provider_base.h"
 #include "chrome/browser/web_applications/components/web_app_utils.h"
 #include "chrome/browser/web_applications/web_app.h"
@@ -34,6 +36,7 @@
 #include "components/sync/model/model_type_store.h"
 #include "components/sync/model/mutable_data_batch.h"
 #include "components/sync/protocol/web_app_specifics.pb.h"
+#include "content/public/common/content_features.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/gurl.h"
 
@@ -254,6 +257,24 @@
   registrar_->NotifyWebAppsDisabledModeChanged();
 }
 
+void WebAppSyncBridge::SetExperimentalTabbedWindowMode(const AppId& app_id,
+                                                       bool enabled,
+                                                       bool is_user_action) {
+  if (enabled) {
+    DCHECK(base::FeatureList::IsEnabled(features::kDesktopPWAsTabStrip));
+    UpdateBoolWebAppPref(profile()->GetPrefs(), app_id,
+                         kExperimentalTabbedWindowMode, true);
+
+    // Set non-experimental window mode to standalone for when the user disables
+    // this flag.
+    SetAppUserDisplayMode(app_id, DisplayMode::kStandalone, is_user_action);
+  } else {
+    RemoveWebAppPref(profile()->GetPrefs(), app_id,
+                     kExperimentalTabbedWindowMode);
+  }
+  registrar_->NotifyWebAppExperimentalTabbedWindowModeChanged(app_id, enabled);
+}
+
 void WebAppSyncBridge::SetAppIsLocallyInstalled(const AppId& app_id,
                                                 bool is_locally_installed) {
   {
diff --git a/chrome/browser/web_applications/web_app_sync_bridge.h b/chrome/browser/web_applications/web_app_sync_bridge.h
index 27a9177..ea3a8ab 100644
--- a/chrome/browser/web_applications/web_app_sync_bridge.h
+++ b/chrome/browser/web_applications/web_app_sync_bridge.h
@@ -75,6 +75,9 @@
                              bool is_user_action) override;
   void SetAppIsDisabled(const AppId& app_id, bool is_disabled) override;
   void UpdateAppsDisableMode() override;
+  void SetExperimentalTabbedWindowMode(const AppId& app_id,
+                                       bool enabled,
+                                       bool is_user_action) override;
   void SetAppIsLocallyInstalled(const AppId& app_id,
                                 bool is_locally_installed) override;
   void SetAppLastBadgingTime(const AppId& app_id,
diff --git a/chrome/browser/win/conflicts/module_database.cc b/chrome/browser/win/conflicts/module_database.cc
index 17c2b1f..83024dbd 100644
--- a/chrome/browser/win/conflicts/module_database.cc
+++ b/chrome/browser/win/conflicts/module_database.cc
@@ -293,8 +293,8 @@
   observer_list_.RemoveObserver(observer);
 }
 
-void ModuleDatabase::ForceStartInspection() {
-  module_inspector_.ForceStartInspection();
+void ModuleDatabase::IncreaseInspectionPriority() {
+  module_inspector_.IncreaseInspectionPriority();
 }
 
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
diff --git a/chrome/browser/win/conflicts/module_database.h b/chrome/browser/win/conflicts/module_database.h
index b831a0f1..0cc076d 100644
--- a/chrome/browser/win/conflicts/module_database.h
+++ b/chrome/browser/win/conflicts/module_database.h
@@ -142,8 +142,9 @@
   void AddObserver(ModuleDatabaseObserver* observer) override;
   void RemoveObserver(ModuleDatabaseObserver* observer) override;
 
-  // Skips waiting for startup to be finished to start inspecting modules.
-  void ForceStartInspection();
+  // Raises the priority of module inspection tasks to ensure the
+  // ModuleDatabase becomes idle ASAP.
+  void IncreaseInspectionPriority();
 
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
   static void RegisterLocalStatePrefs(PrefRegistrySimple* registry);
diff --git a/chrome/browser/win/conflicts/module_inspector.cc b/chrome/browser/win/conflicts/module_inspector.cc
index 204a6e5..974f2af5 100644
--- a/chrome/browser/win/conflicts/module_inspector.cc
+++ b/chrome/browser/win/conflicts/module_inspector.cc
@@ -72,12 +72,18 @@
 }  // namespace
 
 // static
+constexpr base::Feature ModuleInspector::kWinOOPInspectModuleFeature;
+
+// static
 constexpr base::TimeDelta ModuleInspector::kFlushInspectionResultsTimerTimeout;
 
 ModuleInspector::ModuleInspector(
     const OnModuleInspectedCallback& on_module_inspected_callback)
     : on_module_inspected_callback_(on_module_inspected_callback),
       is_after_startup_(false),
+      inspection_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
+          {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+           base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})),
       path_mapping_(GetPathMapping()),
       cache_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
           {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
@@ -113,8 +119,14 @@
     StartInspectingModule();
 }
 
-void ModuleInspector::ForceStartInspection() {
+void ModuleInspector::IncreaseInspectionPriority() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // Create a task runner with higher priority so that future inspections are
+  // done faster.
+  inspection_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
+      {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
+       base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN});
+
   // Assume startup is finished to immediately begin inspecting modules.
   OnStartupFinished();
 }
@@ -139,6 +151,8 @@
 }
 
 void ModuleInspector::EnsureUtilWinServiceBound() {
+  DCHECK(base::FeatureList::IsEnabled(kWinOOPInspectModuleFeature));
+
   if (test_remote_util_win_ || remote_util_win_)
     return;
 
@@ -156,7 +170,8 @@
 void ModuleInspector::OnStartupFinished() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  // This function will be invoked twice if ForceStartInspection() is called.
+  // This function will be invoked twice if IncreaseInspectionPriority() is
+  // called.
   if (is_after_startup_)
     return;
 
@@ -217,17 +232,36 @@
     return;
   }
 
-  EnsureUtilWinServiceBound();
+  if (base::FeatureList::IsEnabled(kWinOOPInspectModuleFeature)) {
+    EnsureUtilWinServiceBound();
 
-  // Use the test UtilWin remote if it exists.
-  chrome::mojom::UtilWin* util_win = test_remote_util_win_
-                                         ? test_remote_util_win_.get()
-                                         : remote_util_win_.get();
+    // Use the test UtilWin remote if it exists.
+    chrome::mojom::UtilWin* util_win = test_remote_util_win_
+                                           ? test_remote_util_win_.get()
+                                           : remote_util_win_.get();
 
-  util_win->InspectModule(
-      module_key.module_path,
-      base::BindOnce(&ModuleInspector::OnModuleNewlyInspected,
-                     weak_ptr_factory_.GetWeakPtr(), module_key));
+    util_win->InspectModule(
+        module_key.module_path,
+        base::BindOnce(&ModuleInspector::OnModuleNewlyInspected,
+                       weak_ptr_factory_.GetWeakPtr(), module_key));
+  } else {
+    // There is a small priority inversion that happens when
+    // IncreaseInspectionPriority() is called while a module is currently being
+    // inspected.
+    //
+    // This is because all the subsequent tasks on |inspection_task_runner_|
+    // will be posted at a higher priority, but they are waiting on the current
+    // task that is currently running at a lower priority.
+    //
+    // In practice, this is not an issue because the only caller of
+    // IncreaseInspectionPriority() (chrome://conflicts) does not depend on the
+    // inspection to finish synchronously and is not blocking anything else.
+    base::PostTaskAndReplyWithResult(
+        inspection_task_runner_.get(), FROM_HERE,
+        base::BindOnce(&InspectModule, module_key.module_path),
+        base::BindOnce(&ModuleInspector::OnModuleNewlyInspected,
+                       weak_ptr_factory_.GetWeakPtr(), module_key));
+  }
 }
 
 void ModuleInspector::OnModuleNewlyInspected(
diff --git a/chrome/browser/win/conflicts/module_inspector.h b/chrome/browser/win/conflicts/module_inspector.h
index ec4eb8b..d6ed0d9 100644
--- a/chrome/browser/win/conflicts/module_inspector.h
+++ b/chrome/browser/win/conflicts/module_inspector.h
@@ -10,6 +10,7 @@
 
 #include "base/callback.h"
 #include "base/containers/queue.h"
+#include "base/feature_list.h"
 #include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
@@ -31,13 +32,17 @@
 // the SequencedTaskRunner where it was created.
 //
 // The inspection of all modules is quite expensive in terms of resources, so it
-// is done after startup, one by one, in a utility process. If needed, it is
-// possible to skip waiting for startup to be finished by calling
-// ForceStartInspection().
+// is done one by one, in a task with a background priority level. If needed, it
+// is possible to increase the priority level of these tasks by calling
+// IncreaseInspectionPriority().
 //
 // This class is not thread safe and it enforces safety via a SEQUENCE_CHECKER.
 class ModuleInspector : public ModuleDatabaseObserver {
  public:
+  // Controls whether or not module inspection is done out of process.
+  static constexpr base::Feature kWinOOPInspectModuleFeature = {
+      "WinOOPInspectModule", base::FEATURE_ENABLED_BY_DEFAULT};
+
   // The amount of time before the |inspection_results_cache_| is flushed to
   // disk while the ModuleDatabase is not idle.
   static constexpr base::TimeDelta kFlushInspectionResultsTimerTimeout =
@@ -55,8 +60,8 @@
   // process if the |queue_| is empty.
   void AddModule(const ModuleInfoKey& module_key);
 
-  // Skips waiting for startup to be finished.
-  void ForceStartInspection();
+  // Removes the throttling.
+  void IncreaseInspectionPriority();
 
   // Returns true if ModuleInspector is not doing anything right now.
   bool IsIdle();
@@ -114,7 +119,8 @@
   // inspection tasks in order to not negatively impact startup performance.
   bool is_after_startup_;
 
-  // A remote interface to the UtilWin service. It is created when inspection is
+  // A remote interface to the UtilWin service. Only used if the
+  // WinOOPInspectModule feature is enabled. It is created when inspection is
   // ongoing, and freed when no longer needed.
   mojo::Remote<chrome::mojom::UtilWin> remote_util_win_;
 
@@ -122,6 +128,11 @@
   // the duration of this instance's lifetime.
   mojo::Remote<chrome::mojom::UtilWin> test_remote_util_win_;
 
+  // The task runner where module inspections takes place. It originally starts
+  // at BEST_EFFORT priority, but is changed to USER_VISIBLE when
+  // IncreaseInspectionPriority() is called.
+  scoped_refptr<base::SequencedTaskRunner> inspection_task_runner_;
+
   // The vector of paths to %env_var%, used to account for differences in
   // localization and where people keep their files.
   // e.g. c:\windows vs d:\windows
diff --git a/chrome/common/extensions/api/tabs.json b/chrome/common/extensions/api/tabs.json
index 67f179b..b789686 100644
--- a/chrome/common/extensions/api/tabs.json
+++ b/chrome/common/extensions/api/tabs.json
@@ -231,20 +231,19 @@
           {
             "type": "any",
             "name": "request"
-          },
-          {
-            "type": "function",
-            "name": "responseCallback",
-            "optional": true,
-            "parameters": [
-              {
-                "name": "response",
-                "type": "any",
-                "description": "The JSON response object sent by the handler of the request. If an error occurs while connecting to the specified tab, the callback is called with no arguments and $(ref:runtime.lastError) is set to the error message."
-              }
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "optional": true,
+          "parameters": [
+            {
+              "name": "response",
+              "type": "any",
+              "description": "The JSON response object sent by the handler of the request. If an error occurs while connecting to the specified tab, the promise is rejected with the error message or the callback is called with no arguments and $(ref:runtime.lastError) is set to the error message."
+            }
+          ]
+        }
       },
       {
         "name": "sendMessage",
@@ -274,20 +273,19 @@
               }
             },
             "optional": true
-          },
-          {
-            "type": "function",
-            "name": "responseCallback",
-            "optional": true,
-            "parameters": [
-              {
-                "name": "response",
-                "type": "any",
-                "description": "The JSON response object sent by the handler of the message. If an error occurs while connecting to the specified tab, the callback is called with no arguments and $(ref:runtime.lastError) is set to the error message."
-              }
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "optional": true,
+          "parameters": [
+            {
+              "name": "response",
+              "type": "any",
+              "description": "The JSON response object sent by the handler of the message. If an error occurs while connecting to the specified tab, the promise is rejected with the error message or the callback is called with no arguments and $(ref:runtime.lastError) is set to the error message."
+            }
+          ]
+        }
       },
       {
         "name": "getSelected",
diff --git a/chrome/common/extensions/api/web_navigation.json b/chrome/common/extensions/api/web_navigation.json
index 89ba867..2df5fc32 100644
--- a/chrome/common/extensions/api/web_navigation.json
+++ b/chrome/common/extensions/api/web_navigation.json
@@ -39,32 +39,33 @@
               },
               "frameId": { "type": "integer", "minimum": 0, "description": "The ID of the frame in the given tab." }
             }
-          },
-          {
-            "type": "function", "name": "callback", "parameters": [
-              {
-                "type": "object",
-                "name": "details",
-                "optional": true,
-                "description": "Information about the requested frame, null if the specified frame ID and/or tab ID are invalid.",
-                "properties": {
-                  "errorOccurred": {
-                    "type": "boolean",
-                    "description": "True if the last navigation in this frame was interrupted by an error, i.e. the onErrorOccurred event fired."
-                  },
-                  "url": {
-                    "type": "string",
-                    "description": "The URL currently associated with this frame, if the frame identified by the frameId existed at one point in the given tab. The fact that an URL is associated with a given frameId does not imply that the corresponding frame still exists."
-                  },
-                  "parentFrameId": {
-                    "type": "integer",
-                    "description": "The ID of the parent frame, or <code>-1</code> if this is the main frame."
-                  }
+          }
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "type": "object",
+              "name": "details",
+              "optional": true,
+              "description": "Information about the requested frame, null if the specified frame ID and/or tab ID are invalid.",
+              "properties": {
+                "errorOccurred": {
+                  "type": "boolean",
+                  "description": "True if the last navigation in this frame was interrupted by an error, i.e. the onErrorOccurred event fired."
+                },
+                "url": {
+                  "type": "string",
+                  "description": "The URL currently associated with this frame, if the frame identified by the frameId existed at one point in the given tab. The fact that an URL is associated with a given frameId does not imply that the corresponding frame still exists."
+                },
+                "parentFrameId": {
+                  "type": "integer",
+                  "description": "The ID of the parent frame, or <code>-1</code> if this is the main frame."
                 }
               }
-            ]
-          }
-        ]
+            }
+          ]
+        }
       },
       {
         "name": "getAllFrames",
@@ -78,43 +79,44 @@
             "properties": {
               "tabId": { "type": "integer", "minimum": 0, "description": "The ID of the tab." }
             }
-          },
-          {
-            "type": "function", "name": "callback", "parameters": [
-              {
-                "name": "details",
-                "type": "array",
-                "description": "A list of frames in the given tab, null if the specified tab ID is invalid.",
-                "optional": true,
-                "items": {
-                  "type": "object",
-                  "properties": {
-                    "errorOccurred": {
-                      "type": "boolean",
-                      "description": "True if the last navigation in this frame was interrupted by an error, i.e. the onErrorOccurred event fired."
-                    },
-                    "processId": {
-                      "type": "integer",
-                      "description": "The ID of the process that runs the renderer for this frame."
-                    },
-                    "frameId": {
-                      "type": "integer",
-                      "description": "The ID of the frame. 0 indicates that this is the main frame; a positive value indicates the ID of a subframe."
-                    },
-                    "parentFrameId": {
-                      "type": "integer",
-                      "description": "The ID of the parent frame, or <code>-1</code> if this is the main frame."
-                    },
-                    "url": {
-                      "type": "string",
-                      "description": "The URL currently associated with this frame."
-                    }
+          }
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "name": "details",
+              "type": "array",
+              "description": "A list of frames in the given tab, null if the specified tab ID is invalid.",
+              "optional": true,
+              "items": {
+                "type": "object",
+                "properties": {
+                  "errorOccurred": {
+                    "type": "boolean",
+                    "description": "True if the last navigation in this frame was interrupted by an error, i.e. the onErrorOccurred event fired."
+                  },
+                  "processId": {
+                    "type": "integer",
+                    "description": "The ID of the process that runs the renderer for this frame."
+                  },
+                  "frameId": {
+                    "type": "integer",
+                    "description": "The ID of the frame. 0 indicates that this is the main frame; a positive value indicates the ID of a subframe."
+                  },
+                  "parentFrameId": {
+                    "type": "integer",
+                    "description": "The ID of the parent frame, or <code>-1</code> if this is the main frame."
+                  },
+                  "url": {
+                    "type": "string",
+                    "description": "The URL currently associated with this frame."
                   }
                 }
               }
-            ]
-          }
-        ]
+            }
+          ]
+        }
       }
     ],
     "events": [
diff --git a/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc b/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc
index 0d418c54..5ccdef8 100644
--- a/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc
+++ b/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc
@@ -3412,7 +3412,10 @@
   std::vector<FakeEventLogsUploadManager::EventLogEntry> logs;
   for (size_t i = 0; i < num_events_in_log; i++) {
     std::wstring data(1024, '0');  // 1KB payload.
-    logs.push_back({i + 1, {1000 + i, 200 + i}, data, 1 + i % 4});
+    logs.push_back({i + 1,
+                    {1000 + i, static_cast<uint32_t>(200 + i)},
+                    data,
+                    static_cast<uint32_t>(1 + i % 4)});
   }
 
   FakeEventLogsUploadManager fake_event_logs_upload_manager(logs);
diff --git a/chrome/installer/setup/setup_util.cc b/chrome/installer/setup/setup_util.cc
index a73b909..9ce54a3 100644
--- a/chrome/installer/setup/setup_util.cc
+++ b/chrome/installer/setup/setup_util.cc
@@ -726,7 +726,7 @@
 
   wts_info = reinterpret_cast<WTSINFO*>(buffer);
   FILETIME filetime = {wts_info->LogonTime.u.LowPart,
-                       wts_info->LogonTime.u.HighPart};
+                       static_cast<DWORD>(wts_info->LogonTime.u.HighPart)};
   return base::Time::FromFileTime(filetime);
 }
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 8f8a6d7a..02758eb 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2268,6 +2268,7 @@
 
       if (!is_official_build) {
         deps += [
+          "//ash/content/file_manager:constants",
           "//chromeos/components/demo_mode_app_ui",
           "//chromeos/components/sample_system_web_app_ui",
           "//chromeos/components/telemetry_extension_ui",
@@ -3935,7 +3936,7 @@
 
     # For tests in tools/perf/process_perf_results_unittest.py
     "//build/android/pylib/",
-    "//tools/swarming_client/",
+    "//third_party/logdog/logdog",
 
     # For representative perf testing run_rendering_benchmark_with_gated_performance.py
     "//testing/scripts/run_rendering_benchmark_with_gated_performance.py",
diff --git a/chrome/test/base/devtools_listener.cc b/chrome/test/base/devtools_listener.cc
index 526c9b17..482f6eac 100644
--- a/chrome/test/base/devtools_listener.cc
+++ b/chrome/test/base/devtools_listener.cc
@@ -35,7 +35,7 @@
 std::string EncodeURIComponent(const std::string& component) {
   url::RawCanonOutputT<char> encoded;
   url::EncodeURIComponent(component.c_str(), component.size(), &encoded);
-  return {encoded.data(), encoded.length()};
+  return {encoded.data(), static_cast<size_t>(encoded.length())};
 }
 
 }  // namespace
diff --git a/chrome/test/data/webui/chromeos/personalization_app/test_mojo_interface_provider.js b/chrome/test/data/webui/chromeos/personalization_app/test_mojo_interface_provider.js
index 3e3360b..3e31019 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/test_mojo_interface_provider.js
+++ b/chrome/test/data/webui/chromeos/personalization_app/test_mojo_interface_provider.js
@@ -14,6 +14,8 @@
     super([
       'fetchCollections',
       'fetchImagesForCollection',
+      'getLocalImages',
+      'getLocalImageThumbnail',
       'getCurrentWallpaper',
       'selectWallpaper',
     ]);
@@ -94,6 +96,19 @@
     return Promise.resolve({images: this.images_});
   }
 
+  /** @override */
+  getLocalImages() {
+    this.methodCalled('getLocalImages');
+    return Promise.resolve({images: []});
+  }
+
+  /** @override */
+  getLocalImageThumbnail(id) {
+    this.methodCalled('getLocalImageThumbnail', id);
+    return Promise.resolve({data: ''});
+  }
+
+  /** @override */
   getCurrentWallpaper() {
     this.methodCalled('getCurrentWallpaper');
     return Promise.resolve({image: this.currentWallpaper});
diff --git a/chrome/test/data/webui/new_tab_page/realbox/realbox_test.js b/chrome/test/data/webui/new_tab_page/realbox/realbox_test.js
index e6a56bd..1f59a131 100644
--- a/chrome/test/data/webui/new_tab_page/realbox/realbox_test.js
+++ b/chrome/test/data/webui/new_tab_page/realbox/realbox_test.js
@@ -1966,6 +1966,33 @@
       });
 
   test(
+      'realbox icons is updated when url match is cut from realbox',
+      async () => {
+        realbox.$.input.value = 'www.test.com';
+        realbox.$.input.dispatchEvent(new InputEvent('input'));
+
+        const matches = [createUrlMatch(
+            {allowedToBeDefaultMatch: true, iconUrl: 'page.svg'})];
+
+        testProxy.callbackRouterRemote.autocompleteResultChanged({
+          input: mojoString16(realbox.$.input.value.trimLeft()),
+          matches,
+          suggestionGroupsMap: {},
+        });
+        await testProxy.callbackRouterRemote.$.flushForTesting();
+
+        assertIconMaskImageUrl(realbox.$.icon, 'page.svg');
+        // Select the entire input.
+        realbox.$.input.setSelectionRange(0, realbox.$.input.value.length);
+
+        let cutEvent = createClipboardEvent('cut');
+        realbox.$.input.dispatchEvent(cutEvent);
+        assertTrue(cutEvent.defaultPrevented);
+
+        assertIconMaskImageUrl(realbox.$.icon, 'search.svg');
+      });
+
+  test(
       'match icons are updated when entity images become available',
       async () => {
         const imageData = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC=';
diff --git a/chrome/test/data/webui/settings/chromeos/internet_detail_page_tests.js b/chrome/test/data/webui/settings/chromeos/internet_detail_page_tests.js
index e632273..cf43581c 100644
--- a/chrome/test/data/webui/settings/chromeos/internet_detail_page_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/internet_detail_page_tests.js
@@ -419,9 +419,10 @@
 
   suite('DetailsPageCellular', function() {
     async function expandConfigurableSection() {
-      const configurableSetions = internetDetailPage.$$('#configurableSetions');
-      assertTrue(!!configurableSetions);
-      configurableSetions.click();
+      const configurableSections =
+          internetDetailPage.$$('#configurableSections');
+      assertTrue(!!configurableSections);
+      configurableSections.click();
       await flushAsync();
       assertTrue(internetDetailPage.showConfigurableSections_);
     }
diff --git a/chromeos/components/camera_app_ui/resources/js/state.js b/chromeos/components/camera_app_ui/resources/js/state.js
index 9fe0565..c39ddcdb1 100644
--- a/chromeos/components/camera_app_ui/resources/js/state.js
+++ b/chromeos/components/camera_app_ui/resources/js/state.js
@@ -65,6 +65,7 @@
   TIMER_10SEC: 'timer-10s',
   TIMER_3SEC: 'timer-3s',
   TIMER: 'timer',
+  USE_FAKE_CAMERA: 'use-fake-camera',
 };
 
 /**
diff --git a/chromeos/components/camera_app_ui/resources/js/views/camera.js b/chromeos/components/camera_app_ui/resources/js/views/camera.js
index 4e8b905..9fb1010 100644
--- a/chromeos/components/camera_app_ui/resources/js/views/camera.js
+++ b/chromeos/components/camera_app_ui/resources/js/views/camera.js
@@ -683,6 +683,7 @@
    */
   async startWithMode_(deviceId, mode) {
     const deviceOperator = await DeviceOperator.getInstance();
+    state.set(state.State.USE_FAKE_CAMERA, deviceOperator === null);
     let resolCandidates;
     if (deviceOperator) {
       resolCandidates =
diff --git a/chromeos/components/camera_app_ui/resources/js/views/ptz_panel.js b/chromeos/components/camera_app_ui/resources/js/views/ptz_panel.js
index 5530e91..415e89b 100644
--- a/chromeos/components/camera_app_ui/resources/js/views/ptz_panel.js
+++ b/chromeos/components/camera_app_ui/resources/js/views/ptz_panel.js
@@ -19,7 +19,7 @@
 /**
  * A set of vid:pid of digital zoom cameras whose PT control is disabled when
  * all zooming out.
- * @const
+ * @const {!Set<string>}
  */
 const digitalZoomCameras = new Set([
   '046d:0809',
@@ -462,7 +462,8 @@
     this.panel_.style.left = `${right + 6}px`;
     const oldTrack = this.track_;
     this.track_ = assertInstanceof(stream, MediaStream).getVideoTracks()[0];
-    this.isDigitalZoom_ = digitalZoomCameras.has(vidPid);
+    this.isDigitalZoom_ = state.get(state.State.USE_FAKE_CAMERA) ||
+        (vidPid !== null && digitalZoomCameras.has(vidPid));
 
     let updatingDefault = Promise.resolve();
     if (oldTrack === null ||
diff --git a/chromeos/components/eche_app_ui/eche_app_manager.cc b/chromeos/components/eche_app_ui/eche_app_manager.cc
index 4491823..e8502f0b 100644
--- a/chromeos/components/eche_app_ui/eche_app_manager.cc
+++ b/chromeos/components/eche_app_ui/eche_app_manager.cc
@@ -40,8 +40,8 @@
               secure_channel_client,
               kSecureChannelFeatureName,
               kMetricNameResult,
-              kMetricNameDuration,
-              kMetricNameLatency)),
+              kMetricNameLatency,
+              kMetricNameDuration)),
       feature_status_provider_(std::make_unique<EcheFeatureStatusProvider>(
           phone_hub_manager,
           device_sync_client,
diff --git a/chromeos/components/personalization_app/mojom/personalization_app.mojom b/chromeos/components/personalization_app/mojom/personalization_app.mojom
index 0b3a47e..9defe4c8 100644
--- a/chromeos/components/personalization_app/mojom/personalization_app.mojom
+++ b/chromeos/components/personalization_app/mojom/personalization_app.mojom
@@ -4,6 +4,7 @@
 
 module chromeos.personalization_app.mojom;
 
+import "mojo/public/mojom/base/unguessable_token.mojom";
 import "url/mojom/url.mojom";
 
 // WallpaperCollection contains a list of |WallpaperImage| objects.
@@ -30,6 +31,14 @@
     uint64 asset_id;
 };
 
+struct LocalImage {
+  // Id to be used for fetching the image thumbnail data.
+  mojo_base.mojom.UnguessableToken id;
+
+  // Name of this local asset to be displayed to the user.
+  string name;
+};
+
 // Provides APIs to retrieve Wallpaper information. This API is exposed only to
 // the Personalization App (chrome://personalization). It is a mojom wrapper for
 // APIs found in |backdrop_wallpaper.proto|.
@@ -45,6 +54,15 @@
     FetchImagesForCollection(string collection_id) => (
         array<WallpaperImage>? images);
 
+    // Fetch a list of LocalImage objects from the local file system. |images|
+    // will be null on failure.
+    GetLocalImages() => (array<LocalImage>? images);
+
+    // Fetch a thumbnail data url for the given LocalImage |id|. |data| will be
+    // empty string on failure.
+    GetLocalImageThumbnail(mojo_base.mojom.UnguessableToken id) =>
+        (string data);
+
     // Get information about the currently set wallpaper. |image| will be null
     // on failure.
     GetCurrentWallpaper() => (WallpaperImage? image);
diff --git a/chromeos/components/personalization_app/resources/trusted/mojo_interface_provider.js b/chromeos/components/personalization_app/resources/trusted/mojo_interface_provider.js
index ecf96bdc..6fbae27 100644
--- a/chromeos/components/personalization_app/resources/trusted/mojo_interface_provider.js
+++ b/chromeos/components/personalization_app/resources/trusted/mojo_interface_provider.js
@@ -9,6 +9,7 @@
  */
 
 import 'chrome://resources/mojo/mojo/public/js/mojo_bindings_lite.js';
+import 'chrome://resources/mojo/mojo/public/mojom/base/unguessable_token.mojom-lite.js';
 import 'chrome://resources/mojo/url/mojom/url.mojom-lite.js';
 import './personalization_app.mojom-lite.js';
 import {assert} from '/assert.m.js';
diff --git a/chromeos/components/personalization_app/resources/trusted/wallpaper_collections_element.html b/chromeos/components/personalization_app/resources/trusted/wallpaper_collections_element.html
index 71b6e49..1f28f72 100644
--- a/chromeos/components/personalization_app/resources/trusted/wallpaper_collections_element.html
+++ b/chromeos/components/personalization_app/resources/trusted/wallpaper_collections_element.html
@@ -1,7 +1,12 @@
 <style include="shared-style"></style>
 <paper-spinner-lite active="[[isLoading_]]"></paper-spinner-lite>
+<dom-repeat items="[[localImages_]]">
+  <template>
+    <img src="[[item]]">
+  </template>
+</dom-repeat>
 <!-- TODO(b/181697575) handle error cases and update error string to UI spec -->
 <p hidden$="[[!hasError_]]" id="error">error</p>
 <iframe id="collections-iframe" frameBorder="0" hidden$="[[!showCollections_]]"
     src="chrome-untrusted://personalization/untrusted/collections.html">
-</iframe>
+</iframe>
\ No newline at end of file
diff --git a/chromeos/components/personalization_app/resources/trusted/wallpaper_collections_element.js b/chromeos/components/personalization_app/resources/trusted/wallpaper_collections_element.js
index 4685d30..7a674216 100644
--- a/chromeos/components/personalization_app/resources/trusted/wallpaper_collections_element.js
+++ b/chromeos/components/personalization_app/resources/trusted/wallpaper_collections_element.js
@@ -60,6 +60,15 @@
         value: null,
       },
 
+      /**
+       * @private
+       * @type {!Array<string>}
+       */
+      localImages_: {
+        type: Array,
+        value: [],
+      },
+
       /** @private */
       isLoading_: {
         type: Boolean,
@@ -93,6 +102,7 @@
   ready() {
     super.ready();
     this.fetchCollections_();
+    this.fetchLocalImages_();
   }
 
   /** @override */
@@ -129,6 +139,30 @@
   }
 
   /**
+   * TODO(b/189968254) clean up data fetching and move display into untrusted
+   * container.
+   * @private
+   */
+  async fetchLocalImages_() {
+    const {images} = await this.wallpaperProvider_.getLocalImages();
+    if (!Array.isArray(images)) {
+      console.warn('Error fetching local images');
+      return;
+    }
+    // TODO(b/189968254) only show first ten until follow-up CL.
+    images.length = Math.min(10, images.length);
+    for (const image of images) {
+      const {data} =
+          await this.wallpaperProvider_.getLocalImageThumbnail(image.id);
+      if (!data) {
+        console.warn('Error fetching image data');
+        continue;
+      }
+      this.push('localImages_', data);
+    }
+  }
+
+  /**
    * @private
    * @param {?Array<!chromeos.personalizationApp.mojom.WallpaperCollection>}
    *     collections
diff --git a/chromeos/components/personalization_app/test/fake_personalization_app_ui_delegate.cc b/chromeos/components/personalization_app/test/fake_personalization_app_ui_delegate.cc
index 7e6d19b..401d57a6 100644
--- a/chromeos/components/personalization_app/test/fake_personalization_app_ui_delegate.cc
+++ b/chromeos/components/personalization_app/test/fake_personalization_app_ui_delegate.cc
@@ -7,6 +7,7 @@
 #include <stdint.h>
 
 #include "base/check_op.h"
+#include "base/unguessable_token.h"
 #include "chromeos/components/personalization_app/mojom/personalization_app.mojom-forward.h"
 #include "chromeos/components/personalization_app/mojom/personalization_app.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -48,6 +49,17 @@
   std::move(callback).Run(std::move(images));
 }
 
+void FakePersonalizationAppUiDelegate::GetLocalImages(
+    GetLocalImagesCallback callback) {
+  std::move(callback).Run({});
+}
+
+void FakePersonalizationAppUiDelegate::GetLocalImageThumbnail(
+    const base::UnguessableToken& id,
+    GetLocalImageThumbnailCallback callback) {
+  std::move(callback).Run(std::string());
+}
+
 void FakePersonalizationAppUiDelegate::GetCurrentWallpaper(
     GetCurrentWallpaperCallback callback) {
   std::move(callback).Run(
diff --git a/chromeos/components/personalization_app/test/fake_personalization_app_ui_delegate.h b/chromeos/components/personalization_app/test/fake_personalization_app_ui_delegate.h
index 133441a..c7cb864 100644
--- a/chromeos/components/personalization_app/test/fake_personalization_app_ui_delegate.h
+++ b/chromeos/components/personalization_app/test/fake_personalization_app_ui_delegate.h
@@ -9,6 +9,7 @@
 
 #include <stdint.h>
 
+#include "base/unguessable_token.h"
 #include "chromeos/components/personalization_app/mojom/personalization_app.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
@@ -39,6 +40,11 @@
       const std::string& collection_id,
       FetchImagesForCollectionCallback callback) override;
 
+  void GetLocalImages(GetLocalImagesCallback callback) override;
+
+  void GetLocalImageThumbnail(const base::UnguessableToken& id,
+                              GetLocalImageThumbnailCallback callback) override;
+
   void GetCurrentWallpaper(GetCurrentWallpaperCallback callback) override;
 
   void SelectWallpaper(uint64_t image_asset_id,
diff --git a/chromeos/components/phonehub/phone_hub_manager_impl.cc b/chromeos/components/phonehub/phone_hub_manager_impl.cc
index 6f649565..ced7a4f 100644
--- a/chromeos/components/phonehub/phone_hub_manager_impl.cc
+++ b/chromeos/components/phonehub/phone_hub_manager_impl.cc
@@ -54,8 +54,8 @@
               secure_channel_client,
               kSecureChannelFeatureName,
               kConnectionResultMetricName,
-              kConnectionDurationMetricName,
-              kConnectionLatencyMetricName)),
+              kConnectionLatencyMetricName,
+              kConnectionDurationMetricName)),
       feature_status_provider_(std::make_unique<FeatureStatusProviderImpl>(
           device_sync_client,
           multidevice_setup_client,
diff --git a/chromeos/dbus/power/power_manager_client.cc b/chromeos/dbus/power/power_manager_client.cc
index ed9c9062..bc11fef5 100644
--- a/chromeos/dbus/power/power_manager_client.cc
+++ b/chromeos/dbus/power/power_manager_client.cc
@@ -659,7 +659,7 @@
     }
   }
 
-  void NotifiyIntiailization() {
+  void NotifyInitialization() {
     for (auto& observer : observers_)
       observer.PowerManagerInitialized();
   }
@@ -798,9 +798,8 @@
   void OnGetPowerSupplyPropertiesMethod(dbus::Response* response) {
     // This is the last callback to run after all the initialization in |Init|.
     // Notify all observers that the initialization is complete.
-    base::ScopedClosureRunner(
-        base::BindOnce(&PowerManagerClientImpl::NotifiyIntiailization,
-                       base::Unretained(this)));
+    base::ScopedClosureRunner notify_runner(base::BindOnce(
+        &PowerManagerClientImpl::NotifyInitialization, base::Unretained(this)));
 
     if (!response) {
       POWER_LOG(ERROR) << "Error calling "
diff --git a/chromeos/services/ime/rule_based_engine.cc b/chromeos/services/ime/rule_based_engine.cc
index eccd16e..c16be2cd 100644
--- a/chromeos/services/ime/rule_based_engine.cc
+++ b/chromeos/services/ime/rule_based_engine.cc
@@ -8,7 +8,6 @@
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
-#include "chromeos/services/ime/public/cpp/rulebased/engine.h"
 
 namespace chromeos {
 namespace ime {
@@ -24,11 +23,11 @@
 
 uint8_t GenerateModifierValueForRulebased(
     const mojom::ModifierStatePtr& modifier_state,
-    bool isAltRightDown) {
+    bool is_alt_right_key_down) {
   uint8_t modifiers = 0;
   if (modifier_state->shift)
     modifiers |= rulebased::MODIFIER_SHIFT;
-  if (modifier_state->alt_graph || isAltRightDown)
+  if (modifier_state->alt_graph || is_alt_right_key_down)
     modifiers |= rulebased::MODIFIER_ALTGR;
   if (modifier_state->caps_lock)
     modifiers |= rulebased::MODIFIER_CAPSLOCK;
@@ -85,37 +84,9 @@
 
 RuleBasedEngine::~RuleBasedEngine() = default;
 
-void RuleBasedEngine::ProcessMessage(const std::vector<uint8_t>& message,
-                                     ProcessMessageCallback callback) {
-  NOTIMPLEMENTED();  // Protobuf message is not used in the rulebased engine.
-}
-
-void RuleBasedEngine::OnInputMethodChanged(const std::string& engine_id) {
-  NOTIMPLEMENTED();  // Not used in the rulebased engine.
-}
-
-void RuleBasedEngine::OnFocus(mojom::InputFieldInfoPtr input_field_info) {
-  NOTIMPLEMENTED();  // Not used in the rulebased engine.
-}
-
-void RuleBasedEngine::OnBlur() {
-  NOTIMPLEMENTED();  // Not used in the rulebased engine.
-}
-
-void RuleBasedEngine::OnSurroundingTextChanged(
-    const std::string& text,
-    uint32_t offset,
-    mojom::SelectionRangePtr selection_range) {
-  NOTIMPLEMENTED();  // Not used in the rulebased engine.
-}
-
 void RuleBasedEngine::OnCompositionCanceledBySystem() {
-  // TODO(https://crbug.com/1633694) Handle the case when the engine is not
-  // defined
-  if (engine_) {
-    engine_->Reset();
-  }
-  isAltRightDown_ = false;
+  engine_.Reset();
+  is_alt_right_key_down_ = false;
 }
 
 void RuleBasedEngine::ProcessKeypressForRulebased(
@@ -131,16 +102,16 @@
   // TODO(https://crbug.com/1014778): Change the base layouts for the
   // rule-based input methods so that |altKey| is false when AltGr is pressed.
   if (event->code == "AltRight") {
-    isAltRightDown_ = event->type == mojom::KeyEventType::kKeyDown;
+    is_alt_right_key_down_ = event->type == mojom::KeyEventType::kKeyDown;
   }
 
-  const bool isAltDown = event->modifier_state->alt && !isAltRightDown_;
+  const bool isAltDown = event->modifier_state->alt && !is_alt_right_key_down_;
 
   // - Shift/AltRight/Caps/Ctrl are modifier keys for the characters which the
   // Mojo service may accept, but don't send the keys themselves to Mojo.
   // - Ctrl+? and Alt+? are shortcut keys, so don't send them to the rule based
   // engine.
-  if (!engine_ || event->type != mojom::KeyEventType::kKeyDown ||
+  if (event->type != mojom::KeyEventType::kKeyDown ||
       (IsModifierKey(event->code) || event->modifier_state->control ||
        isAltDown)) {
     std::move(callback).Run(mojom::KeypressResponseForRulebased::New(
@@ -148,70 +119,22 @@
     return;
   }
 
-  rulebased::ProcessKeyResult process_key_result = engine_->ProcessKey(
+  rulebased::ProcessKeyResult process_key_result = engine_.ProcessKey(
       event->code, GenerateModifierValueForRulebased(event->modifier_state,
-                                                     isAltRightDown_));
+                                                     is_alt_right_key_down_));
   mojom::KeypressResponseForRulebasedPtr keypress_response =
       GenerateKeypressResponseForRulebased(process_key_result);
 
   std::move(callback).Run(std::move(keypress_response));
 }
 
-void RuleBasedEngine::OnKeyEvent(mojom::PhysicalKeyEventPtr event,
-                                 OnKeyEventCallback callback) {
-  NOTIMPLEMENTED();  // Not used in the rulebased engine.
-}
-
-void RuleBasedEngine::CommitText(
-    const std::string& text,
-    mojom::CommitTextCursorBehavior cursor_behavior) {
-  NOTIMPLEMENTED();  // Not used in the rulebased engine.
-}
-
-void RuleBasedEngine::SetComposition(const std::string& text) {
-  NOTIMPLEMENTED();  // Not used in the rulebased engine.
-}
-
-void RuleBasedEngine::SetCompositionRange(uint32_t start, uint32_t end) {
-  NOTIMPLEMENTED();  // Not used in the rulebased engine.
-}
-
-void RuleBasedEngine::FinishComposition() {
-  NOTIMPLEMENTED();  // Not used in the rulebased engine.
-}
-
-void RuleBasedEngine::DeleteSurroundingText(uint32_t num_bytes_before_cursor,
-                                            uint32_t num_bytes_after_cursor) {
-  NOTIMPLEMENTED();  // Not used in the rulebased engine.
-}
-
-void RuleBasedEngine::HandleAutocorrect(
-    mojom::AutocorrectSpanPtr autocorrect_span) {
-  NOTIMPLEMENTED();  // Not used in the rulebased engine.
-}
-
-void RuleBasedEngine::RequestSuggestions(mojom::SuggestionsRequestPtr request,
-                                         RequestSuggestionsCallback callback) {
-  NOTIMPLEMENTED();  // Not used in the rulebased engine.
-}
-
-void RuleBasedEngine::DisplaySuggestions(
-    const std::vector<TextSuggestion>& suggestions) {
-  NOTIMPLEMENTED();  // Not used in the rulebased engine.
-}
-
-void RuleBasedEngine::RecordUkm(mojom::UkmEntryPtr entry) {
-  NOTIMPLEMENTED();  // Not used in the rulebased engine.
-}
-
 RuleBasedEngine::RuleBasedEngine(
     const std::string& ime_spec,
     mojo::PendingReceiver<mojom::InputChannel> receiver)
-    : receiver_(this, std::move(receiver)),
-      engine_(std::make_unique<rulebased::Engine>()) {
+    : receiver_(this, std::move(receiver)) {
   DCHECK(IsImeSupportedByRulebased(ime_spec));
 
-  engine_->Activate(GetIdFromImeSpec(ime_spec));
+  engine_.Activate(GetIdFromImeSpec(ime_spec));
 
   // TODO(https://crbug.com/837156): Registry connection error handler.
 }
diff --git a/chromeos/services/ime/rule_based_engine.h b/chromeos/services/ime/rule_based_engine.h
index 8fb4a977..1a3b4ab 100644
--- a/chromeos/services/ime/rule_based_engine.h
+++ b/chromeos/services/ime/rule_based_engine.h
@@ -6,6 +6,7 @@
 #define CHROMEOS_SERVICES_IME_RULE_BASED_ENGINE_H_
 
 #include "chromeos/services/ime/input_engine.h"
+#include "chromeos/services/ime/public/cpp/rulebased/engine.h"
 #include "chromeos/services/ime/public/cpp/suggestions.h"
 #include "chromeos/services/ime/public/mojom/input_engine.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -15,10 +16,6 @@
 namespace chromeos {
 namespace ime {
 
-namespace rulebased {
-class Engine;
-}
-
 // Handles rule-based input methods such as Arabic and Vietnamese.
 // Rule-based input methods are based off deterministic rules and do not
 // provide features such as suggestions.
@@ -34,35 +31,37 @@
   ~RuleBasedEngine() override;
 
   // mojom::InputChannel overrides:
+  // Most of these methods are deliberately empty because rule-based input
+  // methods do not need to listen to these events.
   void ProcessMessage(const std::vector<uint8_t>& message,
-                      ProcessMessageCallback callback) override;
-  void OnInputMethodChanged(const std::string& engine_id) override;
-  void OnFocus(mojom::InputFieldInfoPtr input_field_info) override;
-  void OnBlur() override;
+                      ProcessMessageCallback callback) override {}
+  void OnInputMethodChanged(const std::string& engine_id) override {}
+  void OnFocus(mojom::InputFieldInfoPtr input_field_info) override {}
+  void OnBlur() override {}
   void OnSurroundingTextChanged(
       const std::string& text,
       uint32_t offset,
-      mojom::SelectionRangePtr selection_range) override;
+      mojom::SelectionRangePtr selection_range) override {}
   void OnCompositionCanceledBySystem() override;
   void ProcessKeypressForRulebased(
       mojom::PhysicalKeyEventPtr event,
       ProcessKeypressForRulebasedCallback callback) override;
   void OnKeyEvent(mojom::PhysicalKeyEventPtr event,
-                  OnKeyEventCallback callback) override;
+                  OnKeyEventCallback callback) override {}
   void CommitText(const std::string& text,
-                  mojom::CommitTextCursorBehavior cursor_behavior) override;
-  void SetComposition(const std::string& text) override;
+                  mojom::CommitTextCursorBehavior cursor_behavior) override {}
+  void SetComposition(const std::string& text) override {}
   void SetCompositionRange(uint32_t start_byte_index,
-                           uint32_t end_byte_index) override;
-  void FinishComposition() override;
+                           uint32_t end_byte_index) override {}
+  void FinishComposition() override {}
   void DeleteSurroundingText(uint32_t num_bytes_before_cursor,
-                             uint32_t num_bytes_after_cursor) override;
-  void HandleAutocorrect(mojom::AutocorrectSpanPtr autocorrect_span) override;
+                             uint32_t num_bytes_after_cursor) override {}
+  void HandleAutocorrect(mojom::AutocorrectSpanPtr autocorrect_span) override {}
   void RequestSuggestions(mojom::SuggestionsRequestPtr request,
-                          RequestSuggestionsCallback callback) override;
+                          RequestSuggestionsCallback callback) override {}
   void DisplaySuggestions(
-      const std::vector<TextSuggestion>& suggestions) override;
-  void RecordUkm(mojom::UkmEntryPtr entry) override;
+      const std::vector<TextSuggestion>& suggestions) override {}
+  void RecordUkm(mojom::UkmEntryPtr entry) override {}
 
   // TODO(https://crbug.com/837156): Implement a state for the interface.
 
@@ -71,10 +70,10 @@
                   mojo::PendingReceiver<mojom::InputChannel> receiver);
 
   mojo::Receiver<mojom::InputChannel> receiver_;
-  std::unique_ptr<rulebased::Engine> engine_;
+  rulebased::Engine engine_;
 
-  // Whether the AltRight key is held down or not. Only used for rule-based.
-  bool isAltRightDown_ = false;
+  // Whether the AltRight key is held down or not.
+  bool is_alt_right_key_down_ = false;
 };
 
 }  // namespace ime
diff --git a/components/arc/BUILD.gn b/components/arc/BUILD.gn
index d57af0d..19c8f8a4 100644
--- a/components/arc/BUILD.gn
+++ b/components/arc/BUILD.gn
@@ -376,6 +376,8 @@
     "test/fake_intent_helper_instance.h",
     "test/fake_lock_screen_instance.cc",
     "test/fake_lock_screen_instance.h",
+    "test/fake_nearby_share_instance.cc",
+    "test/fake_nearby_share_instance.h",
     "test/fake_pip_instance.cc",
     "test/fake_pip_instance.h",
     "test/fake_policy_instance.cc",
diff --git a/components/arc/mojom/BUILD.gn b/components/arc/mojom/BUILD.gn
index 76eedfd..4efa77f 100644
--- a/components/arc/mojom/BUILD.gn
+++ b/components/arc/mojom/BUILD.gn
@@ -45,6 +45,7 @@
       "media_session.mojom",
       "metrics.mojom",
       "midis.mojom",
+      "nearby_share.mojom",
       "net.mojom",
       "obb_mounter.mojom",
       "payment_app.mojom",
diff --git a/components/arc/mojom/arc_bridge.mojom b/components/arc/mojom/arc_bridge.mojom
index bddf7d1..bb89db04 100644
--- a/components/arc/mojom/arc_bridge.mojom
+++ b/components/arc/mojom/arc_bridge.mojom
@@ -35,6 +35,7 @@
 import "components/arc/mojom/media_session.mojom";
 import "components/arc/mojom/metrics.mojom";
 import "components/arc/mojom/midis.mojom";
+import "components/arc/mojom/nearby_share.mojom";
 import "components/arc/mojom/net.mojom";
 import "components/arc/mojom/notifications.mojom";
 import "components/arc/mojom/obb_mounter.mojom";
@@ -63,9 +64,9 @@
 import "components/arc/mojom/wallpaper.mojom";
 import "components/arc/mojom/webapk.mojom";
 
-// Next MinVersion: 58
+// Next MinVersion: 59
 // Deprecated method IDs: 101, 105, 121
-// Next method ID: 163
+// Next method ID: 164
 interface ArcBridgeHost {
   // Keep the entries alphabetical. In order to do so without breaking
   // compatibility with the ARC instance, explicitly assign each interface a
@@ -194,6 +195,10 @@
   [MinVersion=30] OnMidisInstanceReady@135(
       pending_remote<MidisInstance> instance_remote);
 
+  // Notifies Chrome that the NearbyShareInstance interface is ready.
+  [MinVersion=58] OnNearbyShareInstanceReady@163(
+      pending_remote<NearbyShareInstance> instance_remote);
+
   // Notifies Chrome that the NetInstance interface is ready.
   [MinVersion=5] OnNetInstanceReady@108(
       pending_remote<NetInstance> instance_remote);
diff --git a/components/arc/mojom/nearby_share.mojom b/components/arc/mojom/nearby_share.mojom
new file mode 100644
index 0000000..3b6085a
--- /dev/null
+++ b/components/arc/mojom/nearby_share.mojom
@@ -0,0 +1,65 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Next MinVersion: 1
+
+module arc.mojom;
+
+import "url/mojom/url.mojom";
+
+// This mojo file defines the interface that Android apps on ChromeOS will use
+// to allow users to share data (text/files) to nearby share targets, using
+// ChromeOS's NearbyShare view. Nearby share is the ability for a user to share
+// data from a ChromeOS device to another ChromeOS or Android device.
+
+// Represents details about a shared file.
+struct FileInfo {
+  url.mojom.Url content_uri;  // Content URI for the shared file
+  string mime_type;           // MIME type for the file
+  string name;                // File name
+  uint32 size;                // The number of bytes in the file
+};
+
+// Represents the share information received from an Android app.
+struct ShareIntentInfo {
+  string title;                 // Title of share intent
+  string mime_type;             // MIME type for the data.
+                                // (e.g. text/*, text/plain, image/*, etc)
+  map<string, string>? extras;  // (optional) Metadata from the Android intent
+  array<FileInfo>? files;       // (optional) Shared file information
+};
+
+// Represents the Chrome side of NearbyShare session. Used by ARC to send
+// NearbyShare messages to Chrome and interact with the ARC Custom Tab used
+// for NearbyShare.
+// Closing the interface will close the ARC Custom Tab.
+// Next method ID: 0
+interface NearbyShareSessionHost {
+};
+
+// Represents the ARC side of a NearbyShare session. Used by Chrome to send
+// share-related messages to ARC.
+// Next method ID: 1
+interface NearbyShareSessionInstance {
+  // Called when Chrome NearbyShare view is closed.
+  OnNearbyShareViewClosed@0();
+};
+
+// Interface for Android to communicate with Chrome.
+// Next method ID: 1
+interface NearbyShareHost {
+  // Used to create a NearbyShare session.
+  StartNearbyShare@0(
+      int32 task_id,
+      ShareIntentInfo info,
+      pending_remote<NearbyShareSessionInstance> instance)
+      => (pending_remote<NearbyShareSessionHost> host);
+};
+
+// Interface for Chrome to communicate with Android.
+// Next method ID: 1
+interface NearbyShareInstance {
+  // Establishes full-duplex communication with the host.
+  Init@0(pending_remote<NearbyShareHost> host_remote) => ();
+};
diff --git a/components/arc/session/arc_bridge_host_impl.cc b/components/arc/session/arc_bridge_host_impl.cc
index 9cec61a..557010f 100644
--- a/components/arc/session/arc_bridge_host_impl.cc
+++ b/components/arc/session/arc_bridge_host_impl.cc
@@ -43,6 +43,7 @@
 #include "components/arc/mojom/media_session.mojom.h"
 #include "components/arc/mojom/metrics.mojom.h"
 #include "components/arc/mojom/midis.mojom.h"
+#include "components/arc/mojom/nearby_share.mojom.h"
 #include "components/arc/mojom/net.mojom.h"
 #include "components/arc/mojom/notifications.mojom.h"
 #include "components/arc/mojom/obb_mounter.mojom.h"
@@ -271,6 +272,12 @@
   OnInstanceReady(arc_bridge_service_->midis(), std::move(midis_remote));
 }
 
+void ArcBridgeHostImpl::OnNearbyShareInstanceReady(
+    mojo::PendingRemote<mojom::NearbyShareInstance> nearby_share_remote) {
+  OnInstanceReady(arc_bridge_service_->nearby_share(),
+                  std::move(nearby_share_remote));
+}
+
 void ArcBridgeHostImpl::OnNetInstanceReady(
     mojo::PendingRemote<mojom::NetInstance> net_remote) {
   OnInstanceReady(arc_bridge_service_->net(), std::move(net_remote));
diff --git a/components/arc/session/arc_bridge_host_impl.h b/components/arc/session/arc_bridge_host_impl.h
index ea63b0e..4394c00a 100644
--- a/components/arc/session/arc_bridge_host_impl.h
+++ b/components/arc/session/arc_bridge_host_impl.h
@@ -114,6 +114,9 @@
       mojo::PendingRemote<mojom::MetricsInstance> metrics_remote) override;
   void OnMidisInstanceReady(
       mojo::PendingRemote<mojom::MidisInstance> midis_remote) override;
+  void OnNearbyShareInstanceReady(
+      mojo::PendingRemote<mojom::NearbyShareInstance> nearby_share_remote)
+      override;
   void OnNetInstanceReady(
       mojo::PendingRemote<mojom::NetInstance> net_remote) override;
   void OnNotificationsInstanceReady(
diff --git a/components/arc/session/arc_bridge_host_impl_unittest.cc b/components/arc/session/arc_bridge_host_impl_unittest.cc
index 70419ff..18c5bb4 100644
--- a/components/arc/session/arc_bridge_host_impl_unittest.cc
+++ b/components/arc/session/arc_bridge_host_impl_unittest.cc
@@ -138,6 +138,7 @@
     MAKE_INSTANCE_READY(MediaSession);
     MAKE_INSTANCE_READY(Metrics);
     MAKE_INSTANCE_READY(Midis);
+    MAKE_INSTANCE_READY(NearbyShare);
     MAKE_INSTANCE_READY(Net);
     // TODO(yusukes): Test mojom::NotificationsInstance. Unlike others, the
     // notification instance is not managed by ArcBridgeHostImpl. Since the
diff --git a/components/arc/session/arc_bridge_service.cc b/components/arc/session/arc_bridge_service.cc
index 5f41961..9f3db88 100644
--- a/components/arc/session/arc_bridge_service.cc
+++ b/components/arc/session/arc_bridge_service.cc
@@ -35,6 +35,7 @@
 #include "components/arc/mojom/media_session.mojom.h"
 #include "components/arc/mojom/metrics.mojom.h"
 #include "components/arc/mojom/midis.mojom.h"
+#include "components/arc/mojom/nearby_share.mojom.h"
 #include "components/arc/mojom/net.mojom.h"
 #include "components/arc/mojom/obb_mounter.mojom.h"
 #include "components/arc/mojom/oemcrypto.mojom.h"
diff --git a/components/arc/session/arc_bridge_service.h b/components/arc/session/arc_bridge_service.h
index 5172090..b34ea5a 100644
--- a/components/arc/session/arc_bridge_service.h
+++ b/components/arc/session/arc_bridge_service.h
@@ -69,6 +69,8 @@
 class MetricsInstance;
 class MidisHost;
 class MidisInstance;
+class NearbyShareHost;
+class NearbyShareInstance;
 class NetHost;
 class NetInstance;
 class ObbMounterHost;
@@ -249,6 +251,10 @@
   ConnectionHolder<mojom::MidisInstance, mojom::MidisHost>* midis() {
     return &midis_;
   }
+  ConnectionHolder<mojom::NearbyShareInstance, mojom::NearbyShareHost>*
+  nearby_share() {
+    return &nearby_share_;
+  }
   ConnectionHolder<mojom::NetInstance, mojom::NetHost>* net() { return &net_; }
   ConnectionHolder<mojom::ObbMounterInstance, mojom::ObbMounterHost>*
   obb_mounter() {
@@ -366,6 +372,8 @@
   ConnectionHolder<mojom::MediaSessionInstance> media_session_;
   ConnectionHolder<mojom::MetricsInstance, mojom::MetricsHost> metrics_;
   ConnectionHolder<mojom::MidisInstance, mojom::MidisHost> midis_;
+  ConnectionHolder<mojom::NearbyShareInstance, mojom::NearbyShareHost>
+      nearby_share_;
   ConnectionHolder<mojom::NetInstance, mojom::NetHost> net_;
   ConnectionHolder<mojom::ObbMounterInstance, mojom::ObbMounterHost>
       obb_mounter_;
diff --git a/components/arc/test/fake_arc_bridge_host.cc b/components/arc/test/fake_arc_bridge_host.cc
index 15af1fc..d9bd249 100644
--- a/components/arc/test/fake_arc_bridge_host.cc
+++ b/components/arc/test/fake_arc_bridge_host.cc
@@ -34,6 +34,7 @@
 #include "components/arc/mojom/media_session.mojom.h"
 #include "components/arc/mojom/metrics.mojom.h"
 #include "components/arc/mojom/midis.mojom.h"
+#include "components/arc/mojom/nearby_share.mojom.h"
 #include "components/arc/mojom/net.mojom.h"
 #include "components/arc/mojom/notifications.mojom.h"
 #include "components/arc/mojom/obb_mounter.mojom.h"
@@ -167,6 +168,9 @@
 void FakeArcBridgeHost::OnMidisInstanceReady(
     mojo::PendingRemote<mojom::MidisInstance> midis_remote) {}
 
+void FakeArcBridgeHost::OnNearbyShareInstanceReady(
+    mojo::PendingRemote<mojom::NearbyShareInstance> nearby_share_remote) {}
+
 void FakeArcBridgeHost::OnNetInstanceReady(
     mojo::PendingRemote<mojom::NetInstance> net_remote) {}
 
diff --git a/components/arc/test/fake_arc_bridge_host.h b/components/arc/test/fake_arc_bridge_host.h
index bbf42a4..75864071 100644
--- a/components/arc/test/fake_arc_bridge_host.h
+++ b/components/arc/test/fake_arc_bridge_host.h
@@ -92,6 +92,9 @@
       mojo::PendingRemote<mojom::MetricsInstance> metrics_remote) override;
   void OnMidisInstanceReady(
       mojo::PendingRemote<mojom::MidisInstance> midis_remote) override;
+  void OnNearbyShareInstanceReady(
+      mojo::PendingRemote<mojom::NearbyShareInstance> nearby_share_remote)
+      override;
   void OnNetInstanceReady(
       mojo::PendingRemote<mojom::NetInstance> net_remote) override;
   void OnNotificationsInstanceReady(
diff --git a/components/arc/test/fake_nearby_share_instance.cc b/components/arc/test/fake_nearby_share_instance.cc
new file mode 100644
index 0000000..5969d9a7
--- /dev/null
+++ b/components/arc/test/fake_nearby_share_instance.cc
@@ -0,0 +1,26 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/arc/test/fake_nearby_share_instance.h"
+
+#include "base/callback_helpers.h"
+#include "components/arc/mojom/nearby_share.mojom.h"
+
+namespace arc {
+
+FakeNearbyShareInstance::FakeNearbyShareInstance() = default;
+
+FakeNearbyShareInstance::~FakeNearbyShareInstance() = default;
+
+void FakeNearbyShareInstance::Init(
+    mojo::PendingRemote<mojom::NearbyShareHost> host_remote,
+    InitCallback callback) {
+  ++num_init_called_;
+  // For every change in a connection bind latest remote.
+  host_remote_.reset();
+  host_remote_.Bind(std::move(host_remote));
+  std::move(callback).Run();
+}
+
+}  // namespace arc
diff --git a/components/arc/test/fake_nearby_share_instance.h b/components/arc/test/fake_nearby_share_instance.h
new file mode 100644
index 0000000..c85e47a
--- /dev/null
+++ b/components/arc/test/fake_nearby_share_instance.h
@@ -0,0 +1,35 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_ARC_TEST_FAKE_NEARBY_SHARE_INSTANCE_H_
+#define COMPONENTS_ARC_TEST_FAKE_NEARBY_SHARE_INSTANCE_H_
+
+#include "components/arc/mojom/nearby_share.mojom.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+namespace arc {
+
+class FakeNearbyShareInstance : public mojom::NearbyShareInstance {
+ public:
+  FakeNearbyShareInstance();
+  FakeNearbyShareInstance(const FakeNearbyShareInstance&) = delete;
+  FakeNearbyShareInstance& operator=(const FakeNearbyShareInstance&) = delete;
+  ~FakeNearbyShareInstance() override;
+
+  // mojom::NearbyShareInstance overrides:
+  void Init(mojo::PendingRemote<mojom::NearbyShareHost> host_remote,
+            InitCallback callback) override;
+
+  size_t num_init_called() const { return num_init_called_; }
+
+ private:
+  // Keeps the binding alive so that calls to this class can be correctly
+  // routed.
+  mojo::Remote<mojom::NearbyShareHost> host_remote_;
+  size_t num_init_called_ = 0;
+};
+
+}  // namespace arc
+
+#endif  // COMPONENTS_ARC_TEST_FAKE_NEARBY_SHARE_INSTANCE_H_
diff --git a/components/autofill/core/browser/autofill_suggestion_generator.cc b/components/autofill/core/browser/autofill_suggestion_generator.cc
index ebfc20e..7b167c2 100644
--- a/components/autofill/core/browser/autofill_suggestion_generator.cc
+++ b/components/autofill/core/browser/autofill_suggestion_generator.cc
@@ -95,8 +95,8 @@
               features::kAutofillEnableMerchantBoundVirtualCards) &&
           credit_card->virtual_card_enrollment_state() ==
               CreditCard::ENROLLED &&
-          (!base::FeatureList::IsEnabled(
-               features::kAutofillSuggestVirtualCardsOnlyOnFullFormDetection) ||
+          (base::FeatureList::IsEnabled(
+               features::kAutofillSuggestVirtualCardsOnIncompleteForm) ||
            IsCompleteCreditCardFormIncludingCvcField(form_structure))) {
         suggestions.push_back(CreateCreditCardSuggestion(
             *credit_card, type, prefix_matched_suggestion,
diff --git a/components/autofill/core/browser/browser_autofill_manager_unittest.cc b/components/autofill/core/browser/browser_autofill_manager_unittest.cc
index 2dc4aad..f65e30b 100644
--- a/components/autofill/core/browser/browser_autofill_manager_unittest.cc
+++ b/components/autofill/core/browser/browser_autofill_manager_unittest.cc
@@ -8386,9 +8386,8 @@
        GetCreditCardSuggestions_VirtualCard) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures(
-      {features::kAutofillSuggestVirtualCardsOnlyOnFullFormDetection,
-       features::kAutofillEnableMerchantBoundVirtualCards},
-      {});
+      {features::kAutofillEnableMerchantBoundVirtualCards},
+      {features::kAutofillSuggestVirtualCardsOnIncompleteForm});
 
   personal_data_.ClearCreditCards();
   CreditCard masked_server_card(CreditCard::MASKED_SERVER_CARD,
diff --git a/components/autofill/core/common/autofill_payments_features.cc b/components/autofill/core/common/autofill_payments_features.cc
index 440d8c8..56aa464b 100644
--- a/components/autofill/core/common/autofill_payments_features.cc
+++ b/components/autofill/core/common/autofill_payments_features.cc
@@ -137,10 +137,10 @@
     "AutofillSortSuggestionsBasedOnOfferPresence",
     base::FEATURE_ENABLED_BY_DEFAULT};
 
-// When enabled, merchant bound virtual cards will be suggested only if we
+// When enabled, merchant bound virtual cards will be suggested even if we don't
 // detect all of the card number, exp date and CVC fields in the payment form.
-const base::Feature kAutofillSuggestVirtualCardsOnlyOnFullFormDetection{
-    "AutofillSuggestVirtualCardsOnlyOnFullFormDetection",
+const base::Feature kAutofillSuggestVirtualCardsOnIncompleteForm{
+    "AutofillSuggestVirtualCardsOnIncompleteForm",
     base::FEATURE_DISABLED_BY_DEFAULT};
 
 // When enabled, if the Autofill Assistant is running, credit card save (both
diff --git a/components/autofill/core/common/autofill_payments_features.h b/components/autofill/core/common/autofill_payments_features.h
index 7a227ca9..ce7a274a 100644
--- a/components/autofill/core/common/autofill_payments_features.h
+++ b/components/autofill/core/common/autofill_payments_features.h
@@ -37,7 +37,7 @@
 extern const base::Feature kAutofillSaveCardInfobarEditSupport;
 extern const base::Feature kAutofillShowUnmaskedCachedCardInManualFillingView;
 extern const base::Feature kAutofillSortSuggestionsBasedOnOfferPresence;
-extern const base::Feature kAutofillSuggestVirtualCardsOnlyOnFullFormDetection;
+extern const base::Feature kAutofillSuggestVirtualCardsOnIncompleteForm;
 extern const base::Feature kAutofillSuppressCreditCardSaveForAssistant;
 extern const base::Feature kAutofillUpstream;
 extern const base::Feature kAutofillUpstreamAllowAllEmailDomains;
diff --git a/components/autofill_payments_strings.grdp b/components/autofill_payments_strings.grdp
index 30cc081..421aef1 100644
--- a/components/autofill_payments_strings.grdp
+++ b/components/autofill_payments_strings.grdp
@@ -530,6 +530,15 @@
   <message name="IDS_AUTOFILL_VIRTUAL_CARD_PERMANENT_ERROR_DESCRIPTION" desc="Text to be displayed as the description of the error dialog for virtual card permanent error.">
     Virtual card is not available right now, please contact your bank
   </message>
+  <message name="IDS_AUTOFILL_VIRTUAL_CARD_NOT_ELIGIBLE_ERROR_TITLE" desc="Text to be displayed as the title of the error dialog for virtual card permanent error">
+    Not eligible for virtual card
+  </message>
+  <message name="IDS_AUTOFILL_VIRTUAL_CARD_NOT_ELIGIBLE_ERROR_DESCRIPTION" desc="Text to be displayed as the description of the error dialog for virtual card permanent error">
+    This card is not eligible for virtual card number.
+  </message>
+  <message name="IDS_AUTOFILL_ERROR_DIALOG_NEGATIVE_BUTTON_LABEL" desc="Label for the negative button for the error dialog.">
+    Close
+  </message>
   <if expr="is_android">
     <message name="IDS_AUTOFILL_VIRTUAL_CARD_NUMBER_SNACKBAR_MESSAGE_TEXT" desc="Text to be displayed in the snackbar shown after a virtual card number id autofilled.">
       Your virtual card number is applied.
diff --git a/components/autofill_payments_strings_grdp/IDS_AUTOFILL_ERROR_DIALOG_NEGATIVE_BUTTON_LABEL.png.sha1 b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_ERROR_DIALOG_NEGATIVE_BUTTON_LABEL.png.sha1
new file mode 100644
index 0000000..548617a
--- /dev/null
+++ b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_ERROR_DIALOG_NEGATIVE_BUTTON_LABEL.png.sha1
@@ -0,0 +1 @@
+77b857ad20a7af5afc893b56693cf791a54e9d2a
\ No newline at end of file
diff --git a/components/autofill_payments_strings_grdp/IDS_AUTOFILL_VIRTUAL_CARD_NOT_ELIGIBLE_ERROR_DESCRIPTION.png.sha1 b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_VIRTUAL_CARD_NOT_ELIGIBLE_ERROR_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..548617a
--- /dev/null
+++ b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_VIRTUAL_CARD_NOT_ELIGIBLE_ERROR_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+77b857ad20a7af5afc893b56693cf791a54e9d2a
\ No newline at end of file
diff --git a/components/autofill_payments_strings_grdp/IDS_AUTOFILL_VIRTUAL_CARD_NOT_ELIGIBLE_ERROR_TITLE.png.sha1 b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_VIRTUAL_CARD_NOT_ELIGIBLE_ERROR_TITLE.png.sha1
new file mode 100644
index 0000000..548617a
--- /dev/null
+++ b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_VIRTUAL_CARD_NOT_ELIGIBLE_ERROR_TITLE.png.sha1
@@ -0,0 +1 @@
+77b857ad20a7af5afc893b56693cf791a54e9d2a
\ No newline at end of file
diff --git a/components/exo/wayland/zcr_remote_shell.cc b/components/exo/wayland/zcr_remote_shell.cc
index 8224c31..07ecbf1 100644
--- a/components/exo/wayland/zcr_remote_shell.cc
+++ b/components/exo/wayland/zcr_remote_shell.cc
@@ -221,6 +221,8 @@
     caption_button_icon_mask |= 1 << views::CAPTION_BUTTON_ICON_CLOSE;
   if (mask & ZCR_REMOTE_SURFACE_V1_FRAME_BUTTON_TYPE_ZOOM)
     caption_button_icon_mask |= 1 << views::CAPTION_BUTTON_ICON_ZOOM;
+  if (mask & ZCR_REMOTE_SURFACE_V1_FRAME_BUTTON_TYPE_CENTER)
+    caption_button_icon_mask |= 1 << views::CAPTION_BUTTON_ICON_CENTER;
   return caption_button_icon_mask;
 }
 
diff --git a/components/history_clusters/core/BUILD.gn b/components/history_clusters/core/BUILD.gn
index ef5a1aa..5c826b3 100644
--- a/components/history_clusters/core/BUILD.gn
+++ b/components/history_clusters/core/BUILD.gn
@@ -55,6 +55,8 @@
   sources = [ "history_clusters_service_test_api.h" ]
   deps = [
     ":core",
+    "//base/test:test_support",
     "//components/history/core/browser",
+    "//components/history/core/test",
   ]
 }
diff --git a/components/history_clusters/core/history_clusters_service.cc b/components/history_clusters/core/history_clusters_service.cc
index 2d6f81f..d5b2528 100644
--- a/components/history_clusters/core/history_clusters_service.cc
+++ b/components/history_clusters/core/history_clusters_service.cc
@@ -172,16 +172,13 @@
       visit_context_annotations.status.navigation_end_signals &&
       (visit_context_annotations.status.ukm_page_end_signals ||
        !visit_context_annotations.status.expect_ukm_page_end_signals)) {
-    if (base::FeatureList::IsEnabled(kMemories)) {
-      if (kPersistContextAnnotationsInHistoryDb.Get())
-        history_service_->AddContextAnnotationsForVisit(
-            visit_context_annotations.visit_row.visit_id,
-            visit_context_annotations.context_annotations);
-      else
-        visits_.push_back({visit_context_annotations.url_row,
-                           visit_context_annotations.visit_row,
-                           visit_context_annotations.context_annotations,
-                           {}});
+    // If the main kMemories feature is enabled, we want to persist visits.
+    // And if the persist-only switch is enabled, we also want to persist them.
+    if (base::FeatureList::IsEnabled(kMemories) ||
+        base::FeatureList::IsEnabled(kPersistContextAnnotationsInHistoryDb)) {
+      history_service_->AddContextAnnotationsForVisit(
+          visit_context_annotations.visit_row.visit_id,
+          visit_context_annotations.context_annotations);
     }
     incomplete_visit_context_annotations_.erase(nav_id);
   }
@@ -208,17 +205,14 @@
                                               std::move(query_params)))
                          .Then(std::move(callback)));
 
-  if (kPersistContextAnnotationsInHistoryDb.Get()) {
-    history_service_->GetAnnotatedVisits(
-        kMaxVisitsToCluster.Get(),
-        base::BindOnce(
-            // This echo callback is necessary to copy the `AnnotatedVisit`
-            // refs.
-            [](std::vector<history::AnnotatedVisit> visits) { return visits; })
-            .Then(std::move(on_visits_callback)),
-        task_tracker);
-  } else
-    std::move(on_visits_callback).Run(visits_);
+  history_service_->GetAnnotatedVisits(
+      kMaxVisitsToCluster.Get(),
+      base::BindOnce(
+          // This echo callback is necessary to copy the `AnnotatedVisit`
+          // refs.
+          [](std::vector<history::AnnotatedVisit> visits) { return visits; })
+          .Then(std::move(on_visits_callback)),
+      task_tracker);
 }
 
 void HistoryClustersService::RemoveVisits(
@@ -259,7 +253,6 @@
 void HistoryClustersService::PopulateClusterKeywordCache(
     QueryMemoriesResponse response) {
   all_keywords_cache_.clear();
-
   for (auto& cluster : response.clusters) {
     for (auto& keyword : cluster->keywords) {
       // Each `keyword` may itself have multiple terms that we need to extract.
diff --git a/components/history_clusters/core/history_clusters_service.h b/components/history_clusters/core/history_clusters_service.h
index 98e0de23..10a3dc5 100644
--- a/components/history_clusters/core/history_clusters_service.h
+++ b/components/history_clusters/core/history_clusters_service.h
@@ -85,7 +85,7 @@
   // along with continuation query params meant to be used in the follow-up
   // request to load older Memories.
   // Note: At the moment, this method asks `remote_model_helper_` to construct
-  // Memories from `visits_`.
+  // Memories.
   void QueryMemories(mojom::QueryParamsPtr query_params,
                      base::OnceCallback<void(QueryMemoriesResponse)> callback,
                      base::CancelableTaskTracker* task_tracker);
@@ -111,15 +111,9 @@
   //  we refactor the `QueryMemories()` interface.
   void PopulateClusterKeywordCache(QueryMemoriesResponse response);
 
-  // If the Memories flag is enabled, this contains all the visits in-memory
-  // during the Profile lifetime. If the `kPersistContextAnnotationsInHistoryDb`
-  // param is true, this will be empty.
-  // TODO(tommycli): Hide this better behind a new debug flag.
-  std::vector<history::AnnotatedVisit> visits_;
-
   // `VisitContextAnnotations`s are constructed stepwise; they're initially
-  // placed in `incomplete_visit_context_annotations_` and moved either to
-  // `visits_` or the history database once completed.
+  // placed in `incomplete_visit_context_annotations_` and saved to the history
+  // database once completed (if persistence is enabled).
   std::map<int64_t, IncompleteVisitContextAnnotations>
       incomplete_visit_context_annotations_;
 
diff --git a/components/history_clusters/core/history_clusters_service_test_api.h b/components/history_clusters/core/history_clusters_service_test_api.h
index 23b930af..83cb35882 100644
--- a/components/history_clusters/core/history_clusters_service_test_api.h
+++ b/components/history_clusters/core/history_clusters_service_test_api.h
@@ -7,7 +7,10 @@
 
 #include <vector>
 
+#include "base/test/bind.h"
+#include "components/history/core/browser/history_service.h"
 #include "components/history/core/browser/history_types.h"
+#include "components/history/core/test/history_service_test_util.h"
 #include "components/history_clusters/core/history_clusters_service.h"
 
 namespace history_clusters {
@@ -15,18 +18,30 @@
 class HistoryClustersServiceTestApi {
  public:
   explicit HistoryClustersServiceTestApi(
-      HistoryClustersService* history_clusters_service)
-      : history_clusters_service_(history_clusters_service) {}
+      HistoryClustersService* history_clusters_service,
+      history::HistoryService* history_service)
+      : history_clusters_service_(history_clusters_service),
+        history_service_(history_service) {}
 
-  std::vector<history::AnnotatedVisit> GetVisits() const {
-    return history_clusters_service_->visits_;
+  // Gets the annotated visits from HistoryService synchronously for testing.
+  std::vector<history::AnnotatedVisit> GetVisits() {
+    std::vector<history::AnnotatedVisit> result;
+
+    base::CancelableTaskTracker tracker;
+    history_service_->GetAnnotatedVisits(
+        1000,  // Getting 1000 clusters for testing is a reasonable fake value.
+        base::BindLambdaForTesting(
+            [&](std::vector<history::AnnotatedVisit> visits) {
+              result = std::move(visits);
+            }),
+        &tracker);
+    history::BlockUntilHistoryProcessesPendingRequests(history_service_);
+
+    return result;
   }
 
-  base::CancelableTaskTracker& GetCacheQueryTaskTracker() {
-    return history_clusters_service_->cache_query_task_tracker_;
-  }
-
-  HistoryClustersService* history_clusters_service_;
+  HistoryClustersService* const history_clusters_service_;
+  history::HistoryService* const history_service_;
 };
 
 }  // namespace history_clusters
diff --git a/components/history_clusters/core/history_clusters_service_unittest.cc b/components/history_clusters/core/history_clusters_service_unittest.cc
index 5372d093..b9a0891 100644
--- a/components/history_clusters/core/history_clusters_service_unittest.cc
+++ b/components/history_clusters/core/history_clusters_service_unittest.cc
@@ -10,6 +10,7 @@
 #include "base/base64.h"
 #include "base/cxx17_backports.h"
 #include "base/files/scoped_temp_dir.h"
+#include "base/json/json_reader.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/run_loop.h"
 #include "base/strings/stringprintf.h"
@@ -19,6 +20,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
+#include "base/values.h"
 #include "components/history/core/browser/history_types.h"
 #include "components/history/core/browser/url_row.h"
 #include "components/history/core/test/history_service_test_util.h"
@@ -38,12 +40,6 @@
 
 namespace {
 
-// Returns a Time that's `milliseconds` milliseconds after Windows epoch.
-base::Time IntToTime(int milliseconds) {
-  return base::Time::FromDeltaSinceWindowsEpoch(
-      base::TimeDelta::FromMilliseconds(milliseconds));
-}
-
 class HistoryClustersServiceTest : public testing::Test {
  public:
   HistoryClustersServiceTest()
@@ -60,7 +56,7 @@
         history_service_.get(), shared_url_loader_factory_);
     history_clusters_service_test_api_ =
         std::make_unique<HistoryClustersServiceTestApi>(
-            history_clusters_service_.get());
+            history_clusters_service_.get(), history_service_.get());
   }
 
   HistoryClustersServiceTest(const HistoryClustersServiceTest&) = delete;
@@ -89,13 +85,6 @@
         {});
   }
 
-  void AddVisit(int time, const GURL& url) {
-    history::AnnotatedVisit visit;
-    visit.url_row.set_url(url);
-    visit.visit_row.visit_time = IntToTime(time);
-    AddVisit(visit);
-  }
-
   void AddVisit(history::URLID url_id,
                 const GURL& url,
                 const std::u16string title,
@@ -144,33 +133,47 @@
     return std::string(element.As<network::DataElementBytes>().AsStringPiece());
   }
 
+  void AddHardcodedTestDataToHistoryService() {
+    AddVisit(1, GURL{"https://google.com/"}, u"Google title", 1, visit_1_time_,
+             3, absl::nullopt);
+    AddVisit(2, GURL{"https://github.com/"}, u"Github title", 2, visit_2_time_,
+             5, 1);
+  }
+
   // Verifies that that a particular hardcoded request is in a pending request
   // within the URL loader.
   void VerifyHardcodedTestDataInUrlLoaderRequest(
       const std::string& expected_experiment_name = "") {
     EXPECT_TRUE(test_url_loader_factory_.IsPending(kFakeEndpoint));
+
+    absl::optional<base::Value> value =
+        base::JSONReader::Read(GetPendingRequestBody());
+    ASSERT_TRUE(value);
+    std::string* encoded = value->FindStringKey("data");
+    ASSERT_TRUE(encoded);
+
+    std::string decoded;
+    ASSERT_TRUE(base::Base64Decode(*encoded, &decoded));
+
     proto::GetClustersRequest request;
-    request.set_experiment_name(expected_experiment_name);
-    auto* visit = request.add_visits();
-    visit->set_visit_id(2);
-    visit->set_navigation_time_ms(2);
-    visit->set_origin("https://google.com/");
-    visit->set_page_end_reason(3);
-    visit->set_url("https://google.com/");
-    visit = request.add_visits();
-    visit->set_visit_id(4);
-    visit->set_navigation_time_ms(4);
-    visit->set_origin("https://github.com/");
-    visit->set_page_end_reason(5);
-    visit->set_url("https://github.com/");
-    visit->set_referring_visit_id(2);
+    ASSERT_TRUE(request.ParseFromString(decoded));
 
-    std::string encoded;
-    base::Base64Encode(request.SerializeAsString(), &encoded);
-    std::string expected_request_body =
-        base::StringPrintf("{\"data\":\"%s\"}", encoded.c_str());
-
-    EXPECT_EQ(GetPendingRequestBody(), expected_request_body);
+    EXPECT_EQ(request.experiment_name(), expected_experiment_name);
+    ASSERT_EQ(request.visits_size(), 2);
+    auto visit = request.visits().at(0);
+    EXPECT_EQ(visit.visit_id(), 2);
+    EXPECT_EQ(visit.navigation_time_ms(),
+              visit_2_time_.ToDeltaSinceWindowsEpoch().InMilliseconds());
+    EXPECT_EQ(visit.url(), "https://github.com/");
+    EXPECT_EQ(visit.page_end_reason(), 5);
+    visit = request.visits().at(1);
+    EXPECT_EQ(visit.visit_id(), 1);
+    EXPECT_EQ(visit.navigation_time_ms(),
+              visit_1_time_.ToDeltaSinceWindowsEpoch().InMilliseconds());
+    EXPECT_EQ(visit.url(), "https://google.com/");
+    EXPECT_EQ(visit.page_end_reason(), 3);
+    // TODO(tommycli): Add back visit.referring_visit_id() check after updating
+    //  the HistoryService test methods to support that field.
   }
 
   // Fakes a particular partly hardcoded response from the URL loader.
@@ -214,6 +217,10 @@
   base::RunLoop run_loop_;
   base::RepeatingClosure run_loop_quit_;
 
+  // Must not be too old otherwise the history layer will ignore the visit.
+  base::Time visit_1_time_ = base::Time::Now() - base::TimeDelta::FromDays(2);
+  base::Time visit_2_time_ = base::Time::Now() - base::TimeDelta::FromDays(1);
+
   // Tracks the next available navigation ID to be associated with visits.
   int64_t next_navigation_id_ = 0;
 };
@@ -224,11 +231,7 @@
 TEST_F(HistoryClustersServiceTest, QueryMemoriesVariousQueries) {
   std::string experiment_name = "someExperiment";
   EnableMemoriesWithEndpoint(kFakeEndpoint, experiment_name);
-
-  AddVisit(0, GURL{"https://google.com"}, u"Google title", 2, IntToTime(2), 3,
-           absl::nullopt);
-  AddVisit(0, GURL{"https://github.com"}, u"Github title", 4, IntToTime(4), 5,
-           2);
+  AddHardcodedTestDataToHistoryService();
 
   struct TestData {
     std::string query;
@@ -280,13 +283,13 @@
                 const auto& cluster = response.clusters[0];
                 EXPECT_FALSE(cluster->id.is_empty());
                 ASSERT_EQ(cluster->top_visits.size(), 2u);
-                EXPECT_EQ(cluster->top_visits[0]->id, 2);
+                EXPECT_EQ(cluster->top_visits[0]->id, 1);
                 EXPECT_EQ(cluster->top_visits[0]->url, "https://google.com/");
-                EXPECT_EQ(cluster->top_visits[0]->time, IntToTime(2));
+                EXPECT_EQ(cluster->top_visits[0]->time, visit_1_time_);
                 EXPECT_EQ(cluster->top_visits[0]->page_title, "Google title");
-                EXPECT_EQ(cluster->top_visits[1]->id, 4);
+                EXPECT_EQ(cluster->top_visits[1]->id, 2);
                 EXPECT_EQ(cluster->top_visits[1]->url, "https://github.com/");
-                EXPECT_EQ(cluster->top_visits[1]->time, IntToTime(4));
+                EXPECT_EQ(cluster->top_visits[1]->time, visit_2_time_);
                 EXPECT_EQ(cluster->top_visits[1]->page_title, "Github title");
                 ASSERT_EQ(cluster->keywords.size(), 2u);
                 EXPECT_EQ(cluster->keywords[0], u"apples");
@@ -299,9 +302,9 @@
                                           : response.clusters[0];
                 EXPECT_FALSE(cluster->id.is_empty());
                 ASSERT_EQ(cluster->top_visits.size(), 1u);
-                EXPECT_EQ(cluster->top_visits[0]->id, 4);
+                EXPECT_EQ(cluster->top_visits[0]->id, 2);
                 EXPECT_EQ(cluster->top_visits[0]->url, "https://github.com/");
-                EXPECT_EQ(cluster->top_visits[0]->time, IntToTime(4));
+                EXPECT_EQ(cluster->top_visits[0]->time, visit_2_time_);
                 EXPECT_EQ(cluster->top_visits[0]->page_title, "Github title");
                 EXPECT_TRUE(cluster->keywords.empty());
               }
@@ -310,8 +313,9 @@
             }),
         &task_tracker_);
 
+    history::BlockUntilHistoryProcessesPendingRequests(history_service_.get());
     VerifyHardcodedTestDataInUrlLoaderRequest(experiment_name);
-    InjectHardcodedTestDataToUrlLoaderResponse({{2, 4}, {4}});
+    InjectHardcodedTestDataToUrlLoaderResponse({{1, 2}, {2}});
 
     // Verify the callback is invoked.
     run_loop.Run();
@@ -343,9 +347,7 @@
 
 TEST_F(HistoryClustersServiceTest, QueryMemoriesWithEmptyEndpoint) {
   EnableMemoriesWithEndpoint("");
-
-  AddVisit(1, GURL{"google.com"});
-  AddVisit(2, GURL{"github.com"});
+  AddHardcodedTestDataToHistoryService();
 
   EXPECT_EQ(test_url_loader_factory_.NumPending(), 0);
   history_clusters_service_->QueryMemories(
@@ -369,9 +371,7 @@
 
 TEST_F(HistoryClustersServiceTest, QueryMemoriesWithEmptyResponse) {
   EnableMemoriesWithEndpoint();
-
-  AddVisit(1, GURL{"google.com"});
-  AddVisit(2, GURL{"github.com"});
+  AddHardcodedTestDataToHistoryService();
 
   EXPECT_FALSE(test_url_loader_factory_.IsPending(kFakeEndpoint));
   history_clusters_service_->QueryMemories(
@@ -387,6 +387,7 @@
       &task_tracker_);
 
   // Verify a request is made.
+  history::BlockUntilHistoryProcessesPendingRequests(history_service_.get());
   EXPECT_TRUE(test_url_loader_factory_.IsPending(kFakeEndpoint));
 
   // Fake an empty but valid response from the endpoint.
@@ -400,9 +401,7 @@
 
 TEST_F(HistoryClustersServiceTest, QueryMemoriesWithInvalidJsonResponse) {
   EnableMemoriesWithEndpoint();
-
-  AddVisit(1, GURL{"google.com"});
-  AddVisit(2, GURL{"github.com"});
+  AddHardcodedTestDataToHistoryService();
 
   EXPECT_FALSE(test_url_loader_factory_.IsPending(kFakeEndpoint));
   history_clusters_service_->QueryMemories(
@@ -418,6 +417,7 @@
       &task_tracker_);
 
   // Verify a request is made.
+  history::BlockUntilHistoryProcessesPendingRequests(history_service_.get());
   EXPECT_TRUE(test_url_loader_factory_.IsPending(kFakeEndpoint));
 
   // Fake a junk response from the endpoint.
@@ -431,9 +431,7 @@
 
 TEST_F(HistoryClustersServiceTest, QueryMemoriesWithEmptyJsonResponse) {
   EnableMemoriesWithEndpoint();
-
-  AddVisit(1, GURL{"google.com"});
-  AddVisit(2, GURL{"github.com"});
+  AddHardcodedTestDataToHistoryService();
 
   EXPECT_FALSE(test_url_loader_factory_.IsPending(kFakeEndpoint));
   history_clusters_service_->QueryMemories(
@@ -449,6 +447,7 @@
       &task_tracker_);
 
   // Verify a request is made.
+  history::BlockUntilHistoryProcessesPendingRequests(history_service_.get());
   EXPECT_TRUE(test_url_loader_factory_.IsPending(kFakeEndpoint));
 
   // Fake an empty but valid response from the endpoint.
@@ -462,9 +461,7 @@
 
 TEST_F(HistoryClustersServiceTest, QueryMemoriesWithPendingRequest) {
   EnableMemoriesWithEndpoint();
-
-  AddVisit(1, GURL{"google.com"});
-  AddVisit(2, GURL{"github.com"});
+  AddHardcodedTestDataToHistoryService();
 
   EXPECT_FALSE(test_url_loader_factory_.IsPending(kFakeEndpoint));
   history_clusters_service_->QueryMemories(
@@ -479,10 +476,10 @@
       &task_tracker_);
 
   // Verify there's a single request to the endpoint.
+  history::BlockUntilHistoryProcessesPendingRequests(history_service_.get());
   EXPECT_TRUE(test_url_loader_factory_.IsPending(kFakeEndpoint));
   EXPECT_EQ(test_url_loader_factory_.NumPending(), 1);
 
-  EXPECT_TRUE(test_url_loader_factory_.IsPending(kFakeEndpoint));
   history_clusters_service_->QueryMemories(
       mojom::QueryParams::New(),
       base::BindLambdaForTesting(
@@ -496,6 +493,7 @@
       &task_tracker_);
 
   // Verify there are two requests to the endpoint.
+  history::BlockUntilHistoryProcessesPendingRequests(history_service_.get());
   EXPECT_TRUE(test_url_loader_factory_.IsPending(kFakeEndpoint));
   EXPECT_EQ(test_url_loader_factory_.NumPending(), 2);
 
@@ -511,137 +509,6 @@
   run_loop_.Run();
 }
 
-TEST_F(HistoryClustersServiceTest, QueryMemoriesWithHistoryDb) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeaturesAndParameters(
-      {
-          {
-              kMemories,
-              {{"MemoriesPersistContextAnnotationsInHistoryDb", "true"}},
-          },
-          {
-              kRemoteModelForDebugging,
-              {{"MemoriesRemoteModelEndpoint", kFakeEndpoint}},
-          },
-      },
-      {});
-
-  // Must not be too old otherwise the history layer will ignore the visit.
-  const auto visit_time = base::Time::Now() - base::TimeDelta::FromDays(1);
-  AddVisit(1, GURL{"https://google.com"}, u"Google title", 1, visit_time, 3,
-           absl::nullopt);
-  AddVisit(2, GURL{"https://github.com"}, u"Github title", 2, visit_time, 5, 2);
-
-  EXPECT_FALSE(test_url_loader_factory_.IsPending(kFakeEndpoint));
-
-  history_clusters_service_->QueryMemories(
-      mojom::QueryParams::New(),
-      // This "expect" block is not run until after the fake response is sent
-      // further down in this method.
-      base::BindLambdaForTesting(
-          [&](HistoryClustersService::QueryMemoriesResponse response) {
-            // Verify the parsed response.
-            ASSERT_EQ(response.clusters.size(), 2u);
-            EXPECT_FALSE(response.clusters[0]->id.is_empty());
-            ASSERT_EQ(response.clusters[0]->top_visits.size(), 2u);
-            EXPECT_EQ(response.clusters[0]->top_visits[0]->id, 1);
-            EXPECT_EQ(response.clusters[0]->top_visits[0]->url,
-                      "https://google.com/");
-            EXPECT_EQ(response.clusters[0]->top_visits[0]->time, visit_time);
-            EXPECT_EQ(response.clusters[0]->top_visits[0]->page_title,
-                      "Google title");
-            EXPECT_EQ(response.clusters[0]->top_visits[1]->id, 2);
-            EXPECT_EQ(response.clusters[0]->top_visits[1]->url,
-                      "https://github.com/");
-            EXPECT_EQ(response.clusters[0]->top_visits[1]->time, visit_time);
-            EXPECT_EQ(response.clusters[0]->top_visits[1]->page_title,
-                      "Github title");
-            ASSERT_EQ(response.clusters[1]->top_visits.size(), 1u);
-            EXPECT_FALSE(response.clusters[1]->id.is_empty());
-            EXPECT_EQ(response.clusters[1]->top_visits[0]->id, 2);
-            EXPECT_EQ(response.clusters[1]->top_visits[0]->url,
-                      "https://github.com/");
-            EXPECT_EQ(response.clusters[1]->top_visits[0]->time, visit_time);
-            EXPECT_EQ(response.clusters[1]->top_visits[0]->page_title,
-                      "Github title");
-            run_loop_quit_.Run();
-          }),
-      &task_tracker_);
-
-  history::BlockUntilHistoryProcessesPendingRequests(history_service_.get());
-
-  // Verify there's a single request to the endpoint.
-  EXPECT_TRUE(test_url_loader_factory_.IsPending(kFakeEndpoint));
-  EXPECT_EQ(test_url_loader_factory_.NumPending(), 1);
-
-  // Fake a response from the endpoint with two clusters.
-  InjectHardcodedTestDataToUrlLoaderResponse({{1, 2}, {2}});
-
-  // Verify the callback is invoked.
-  run_loop_.Run();
-}
-
-TEST_F(HistoryClustersServiceTest,
-       QueryMemoriesWithHistoryDbWithPendingRequest) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeaturesAndParameters(
-      {
-          {
-              kMemories,
-              {{"MemoriesPersistContextAnnotationsInHistoryDb", "true"}},
-          },
-          {
-              kRemoteModelForDebugging,
-              {{"MemoriesRemoteModelEndpoint", kFakeEndpoint}},
-          },
-      },
-      {});
-
-  // Must not be too old otherwise the history layer will ignore the visit.
-  const auto visit_time = base::Time::Now() - base::TimeDelta::FromDays(1);
-  AddVisit(1, GURL{"https://google.com"}, u"Google title", 1, visit_time, 3,
-           absl::nullopt);
-  AddVisit(2, GURL{"https://github.com"}, u"Github title", 2, visit_time, 5, 2);
-
-  EXPECT_FALSE(test_url_loader_factory_.IsPending(kFakeEndpoint));
-  history_clusters_service_->QueryMemories(
-      mojom::QueryParams::New(),
-      base::BindLambdaForTesting(
-          [&](HistoryClustersService::QueryMemoriesResponse response) {
-            ADD_FAILURE() << "This should not be reached.";
-          }),
-      &task_tracker_);
-
-  // Verify there are no requests to the endpoint just yet.
-  EXPECT_FALSE(test_url_loader_factory_.IsPending(kFakeEndpoint));
-
-  // Cancel pending queries, if any.
-  task_tracker_.TryCancelAll();
-
-  EXPECT_FALSE(test_url_loader_factory_.IsPending(kFakeEndpoint));
-  history_clusters_service_->QueryMemories(
-      mojom::QueryParams::New(),
-      base::BindLambdaForTesting(
-          [&](HistoryClustersService::QueryMemoriesResponse response) {
-            // Verify the parsed response.
-            EXPECT_EQ(response.clusters.size(), 2u);
-            run_loop_quit_.Run();
-          }),
-      &task_tracker_);
-
-  history::BlockUntilHistoryProcessesPendingRequests(history_service_.get());
-
-  // Verify there's a single request to the endpoint.
-  EXPECT_TRUE(test_url_loader_factory_.IsPending(kFakeEndpoint));
-  EXPECT_EQ(test_url_loader_factory_.NumPending(), 1);
-
-  // Fake a response from the endpoint with two clusters.
-  InjectHardcodedTestDataToUrlLoaderResponse({{1, 2}, {2}});
-
-  // Verify the last callback is invoked.
-  run_loop_.Run();
-}
-
 TEST_F(HistoryClustersServiceTest, CompleteVisitContextAnnotationsIfReady) {
   auto test = [&](RecordingStatus status, bool expected_complete) {
     auto& incomplete_visit_context_annotations =
@@ -726,56 +593,64 @@
 
 TEST_F(HistoryClustersServiceTest,
        CompleteVisitContextAnnotationsIfReadyWhenFeatureDisabled) {
-  {
-    // When the feature is disabled, the `IncompleteVisitContextAnnotations`
-    // should be removed but not added to visits.
-    base::test::ScopedFeatureList feature_list;
-    feature_list.InitAndDisableFeature(kMemories);
-    auto& incomplete_visit_context_annotations =
-        history_clusters_service_->GetOrCreateIncompleteVisitContextAnnotations(
-            0);
-    incomplete_visit_context_annotations.status = {true, true, true};
-    history_clusters_service_->CompleteVisitContextAnnotationsIfReady(0);
-    EXPECT_FALSE(
-        history_clusters_service_->HasIncompleteVisitContextAnnotations(0));
-    EXPECT_TRUE(history_clusters_service_test_api_->GetVisits().empty());
-  }
+  history_service_->AddPageWithDetails(GURL("https://fake.com"), u"Test 1", 1,
+                                       1, base::Time::Now(), false,
+                                       history::SOURCE_BROWSED);
 
-  {
-    // When the feature is enabled, the `IncompleteVisitContextAnnotations`
-    // should be removed and added to visits.
-    base::test::ScopedFeatureList feature_list;
-    feature_list.InitAndEnableFeature(kMemories);
-    auto& incomplete_visit_context_annotations =
-        history_clusters_service_->GetOrCreateIncompleteVisitContextAnnotations(
-            0);
-    incomplete_visit_context_annotations.status = {true, true, true};
-    history_clusters_service_->CompleteVisitContextAnnotationsIfReady(0);
-    EXPECT_FALSE(
-        history_clusters_service_->HasIncompleteVisitContextAnnotations(0));
-    EXPECT_EQ(history_clusters_service_test_api_->GetVisits().size(), 1u);
-  }
+  // When the feature is disabled, the `IncompleteVisitContextAnnotations`
+  // should be removed but not added to visits.
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{}, /*disabled_features=*/{
+          kMemories,
+          kPersistContextAnnotationsInHistoryDb,
+      });
+  auto& incomplete_visit_context_annotations =
+      history_clusters_service_->GetOrCreateIncompleteVisitContextAnnotations(
+          0);
+  incomplete_visit_context_annotations.url_row.set_id(1);
+  incomplete_visit_context_annotations.visit_row.visit_id = 1;
+  incomplete_visit_context_annotations.status = {true, true, true};
+  history_clusters_service_->CompleteVisitContextAnnotationsIfReady(0);
+  EXPECT_FALSE(
+      history_clusters_service_->HasIncompleteVisitContextAnnotations(0));
+  EXPECT_TRUE(history_clusters_service_test_api_->GetVisits().empty());
+}
+
+TEST_F(HistoryClustersServiceTest,
+       CompleteVisitContextAnnotationsIfReadyWhenFeatureEnabled) {
+  history_service_->AddPageWithDetails(GURL("https://fake.com"), u"Test 1", 1,
+                                       1, base::Time::Now(), false,
+                                       history::SOURCE_BROWSED);
+
+  // When the feature is enabled, the `IncompleteVisitContextAnnotations`
+  // should be removed and added to visits.
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(kMemories);
+  auto& incomplete_visit_context_annotations =
+      history_clusters_service_->GetOrCreateIncompleteVisitContextAnnotations(
+          0);
+  incomplete_visit_context_annotations.url_row.set_id(1);
+  incomplete_visit_context_annotations.visit_row.visit_id = 1;
+  incomplete_visit_context_annotations.status = {true, true, true};
+  history_clusters_service_->CompleteVisitContextAnnotationsIfReady(0);
+  EXPECT_FALSE(
+      history_clusters_service_->HasIncompleteVisitContextAnnotations(0));
+  EXPECT_EQ(history_clusters_service_test_api_->GetVisits().size(), 1u);
 }
 
 TEST_F(HistoryClustersServiceTest, DoesQueryMatchAnyCluster) {
   EnableMemoriesWithEndpoint();
-
-  AddVisit(0, GURL{"https://google.com"}, u"Google title", 2, IntToTime(2), 3,
-           absl::nullopt);
-  AddVisit(0, GURL{"https://github.com"}, u"Github title", 4, IntToTime(4), 5,
-           2);
+  AddHardcodedTestDataToHistoryService();
 
   // Verify that initially, the test keyword doesn't match anything, but this
   // query should have kicked off a cache population request.
   EXPECT_FALSE(history_clusters_service_->DoesQueryMatchAnyCluster("appl"));
 
   // Providing the response and running the task loop should populate the cache.
+  history::BlockUntilHistoryProcessesPendingRequests(history_service_.get());
   VerifyHardcodedTestDataInUrlLoaderRequest("");
-  InjectHardcodedTestDataToUrlLoaderResponse({{2, 4}, {4}});
-  history_clusters_service_test_api_->GetCacheQueryTaskTracker().PostTask(
-      task_environment_.GetMainThreadTaskRunner().get(), FROM_HERE,
-      run_loop_quit_);
-  run_loop_.Run();
+  InjectHardcodedTestDataToUrlLoaderResponse({{1, 2}, {2}});
 
   // Now the query should match the populated cache.
   EXPECT_TRUE(history_clusters_service_->DoesQueryMatchAnyCluster("appl"));
diff --git a/components/history_clusters/core/memories_features.cc b/components/history_clusters/core/memories_features.cc
index 2cacde3a..a15f8cc0 100644
--- a/components/history_clusters/core/memories_features.cc
+++ b/components/history_clusters/core/memories_features.cc
@@ -22,9 +22,6 @@
 const base::FeatureParam<std::string> kRemoteModelEndpointExperimentName{
     &kRemoteModelForDebugging, "MemoriesRemoteModelEndpointExperimentName", ""};
 
-const base::FeatureParam<bool> kPersistContextAnnotationsInHistoryDb{
-    &kMemories, "MemoriesPersistContextAnnotationsInHistoryDb", false};
-
 const base::FeatureParam<int> kMaxVisitsToCluster{
     &kMemories, "MemoriesMaxVisitsToCluster", 1000};
 
@@ -41,4 +38,8 @@
 const base::Feature kRemoteModelForDebugging{"MemoriesRemoteModelForDebugging",
                                              base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kPersistContextAnnotationsInHistoryDb{
+    "MemoriesPersistContextAnnotationsInHistoryDb",
+    base::FEATURE_DISABLED_BY_DEFAULT};
+
 }  // namespace history_clusters
diff --git a/components/history_clusters/core/memories_features.h b/components/history_clusters/core/memories_features.h
index 9b82e781..6a7db37f 100644
--- a/components/history_clusters/core/memories_features.h
+++ b/components/history_clusters/core/memories_features.h
@@ -23,12 +23,6 @@
 // remote model debug endpoint is disabled.
 extern const base::FeatureParam<std::string> kRemoteModelEndpointExperimentName;
 
-// If enabled, completed visits context annotations are persisted to the history
-// DB and read back when clustering. If disabled, completed visit context
-// annotations are kept in-memory and used these in-memory visits are used when
-// clustering.
-extern const base::FeatureParam<bool> kPersistContextAnnotationsInHistoryDb;
-
 // The max number of visits to use for each clustering iteration. When using the
 // remote model, this limits the number of visits sent.  Only applies when using
 // persisted visit context annotations; i.e.
@@ -59,6 +53,15 @@
 // purposes. This should not be ever enabled in production.
 extern const base::Feature kRemoteModelForDebugging;
 
+// Enables persisting context annotations in the History DB. They are always
+// calculated anyways. This just enables storing them. This is expected to be
+// enabled for all users shortly. This just provides a killswitch.
+
+// This flag is to enable us to turn on persisting context annotations WITHOUT
+// exposing the Memories UI in general. If EITHER this flag or `kMemories` is
+// enabled, users will have context annotations persisted into their History DB.
+extern const base::Feature kPersistContextAnnotationsInHistoryDb;
+
 }  // namespace history_clusters
 
 #endif  // COMPONENTS_HISTORY_CLUSTERS_CORE_MEMORIES_FEATURES_H_
diff --git a/components/history_clusters/core/memories_remote_model_helper.cc b/components/history_clusters/core/memories_remote_model_helper.cc
index 05022b6..6fb791a2 100644
--- a/components/history_clusters/core/memories_remote_model_helper.cc
+++ b/components/history_clusters/core/memories_remote_model_helper.cc
@@ -49,6 +49,7 @@
       debug_visit.SetStringKey("visitId",
                                base::NumberToString(request_visit->visit_id()));
       debug_visit.SetStringKey("url", request_visit->url());
+      debug_visit.SetStringKey("origin", request_visit->origin());
       debug_visit.SetStringKey(
           "navigationTimeMs",
           base::NumberToString(request_visit->navigation_time_ms()));
diff --git a/components/omnibox/browser/vector_icons/send.icon b/components/omnibox/browser/vector_icons/send.icon
index 1ed8a96f..a34e810 100644
--- a/components/omnibox/browser/vector_icons/send.icon
+++ b/components/omnibox/browser/vector_icons/send.icon
@@ -3,16 +3,10 @@
 // found in the LICENSE file.
 
 CANVAS_DIMENSIONS, 16,
-MOVE_TO, 1.33f, 2,
-LINE_TO, 1.33f, 14,
-LINE_TO, 14.67f, 8,
-LINE_TO, 1.33f, 2,
-CLOSE,
-MOVE_TO, 2.67f, 9.33f,
-LINE_TO, 8.67f, 8,
-LINE_TO, 2.67f, 6.67f,
-LINE_TO, 2.67f, 4.06f,
-LINE_TO, 11.42f, 8,
-LINE_TO, 2.67f, 11.94f,
-LINE_TO, 2.67f, 9.33f,
+MOVE_TO, 1.34f, 14,
+R_LINE_TO, 13.99f, -6,
+LINE_TO, 1.34f, 2,
+R_LINE_TO, -0.01f, 4.67f,
+R_LINE_TO, 10, 1.33f,
+R_LINE_TO, -10, 1.33f,
 CLOSE
diff --git a/components/omnibox/browser/vector_icons/share.icon b/components/omnibox/browser/vector_icons/share.icon
index c6cd7f2..6037da5 100644
--- a/components/omnibox/browser/vector_icons/share.icon
+++ b/components/omnibox/browser/vector_icons/share.icon
@@ -3,13 +3,13 @@
 // found in the LICENSE file.
 
 CANVAS_DIMENSIONS, 16,
-MOVE_TO, 12, 10.67f,
-CUBIC_TO, 11.47f, 10.67f, 11, 10.87f, 10.65f, 11.21f,
+MOVE_TO, 12, 10.72f,
+CUBIC_TO, 11.49f, 10.72f, 11.04f, 10.92f, 10.69f, 11.23f,
 LINE_TO, 5.94f, 8.47f,
 CUBIC_TO, 5.97f, 8.31f, 6, 8.16f, 6, 8,
 CUBIC_TO, 6, 7.84f, 5.97f, 7.69f, 5.94f, 7.53f,
 LINE_TO, 10.64f, 4.79f,
-CUBIC_TO, 10.99f, 5.13f, 11.47f, 5.33f, 12, 5.33f,
+CUBIC_TO, 11, 5.13f, 11.47f, 5.33f, 12, 5.33f,
 CUBIC_TO, 13.11f, 5.33f, 14, 4.44f, 14, 3.33f,
 CUBIC_TO, 14, 2.23f, 13.11f, 1.33f, 12, 1.33f,
 CUBIC_TO, 10.89f, 1.33f, 10, 2.23f, 10, 3.33f,
@@ -19,27 +19,10 @@
 CUBIC_TO, 2.89f, 6, 2, 6.89f, 2, 8,
 CUBIC_TO, 2, 9.11f, 2.89f, 10, 4, 10,
 CUBIC_TO, 4.53f, 10, 5, 9.79f, 5.36f, 9.46f,
-LINE_TO, 10.06f, 12.21f,
-CUBIC_TO, 10.03f, 12.35f, 10, 12.51f, 10, 12.67f,
-CUBIC_TO, 10, 13.77f, 10.89f, 14.67f, 12, 14.67f,
-CUBIC_TO, 13.11f, 14.67f, 14, 13.77f, 14, 12.67f,
-CUBIC_TO, 14, 11.56f, 13.11f, 10.67f, 12, 10.67f,
-CLOSE,
-MOVE_TO, 12, 2.67f,
-CUBIC_TO, 12.37f, 2.67f, 12.67f, 2.97f, 12.67f, 3.33f,
-CUBIC_TO, 12.67f, 3.7f, 12.37f, 4, 12, 4,
-CUBIC_TO, 11.63f, 4, 11.33f, 3.7f, 11.33f, 3.33f,
-CUBIC_TO, 11.33f, 2.97f, 11.63f, 2.67f, 12, 2.67f,
-CLOSE,
-MOVE_TO, 4, 8.67f,
-CUBIC_TO, 3.63f, 8.67f, 3.33f, 8.37f, 3.33f, 8,
-CUBIC_TO, 3.33f, 7.63f, 3.63f, 7.33f, 4, 7.33f,
-CUBIC_TO, 4.37f, 7.33f, 4.67f, 7.63f, 4.67f, 8,
-CUBIC_TO, 4.67f, 8.37f, 4.37f, 8.67f, 4, 8.67f,
-CLOSE,
-MOVE_TO, 12, 13.33f,
-CUBIC_TO, 11.63f, 13.33f, 11.33f, 13.03f, 11.33f, 12.67f,
-CUBIC_TO, 11.33f, 12.3f, 11.63f, 12, 12, 12,
-CUBIC_TO, 12.37f, 12, 12.67f, 12.3f, 12.67f, 12.67f,
-CUBIC_TO, 12.67f, 13.03f, 12.37f, 13.33f, 12, 13.33f,
+LINE_TO, 10.11f, 12.23f,
+CUBIC_TO, 10.07f, 12.37f, 10.05f, 12.52f, 10.05f, 12.67f,
+CUBIC_TO, 10.05f, 13.74f, 10.93f, 14.61f, 12, 14.61f,
+CUBIC_TO, 13.07f, 14.61f, 13.95f, 13.74f, 13.95f, 12.67f,
+CUBIC_TO, 13.95f, 11.59f, 13.07f, 10.72f, 12, 10.72f,
+LINE_TO, 12, 10.72f,
 CLOSE
diff --git a/components/page_load_metrics/browser/observers/page_load_metrics_observer_tester.cc b/components/page_load_metrics/browser/observers/page_load_metrics_observer_tester.cc
index 5c11fcb37..88f0a74 100644
--- a/components/page_load_metrics/browser/observers/page_load_metrics_observer_tester.cc
+++ b/components/page_load_metrics/browser/observers/page_load_metrics_observer_tester.cc
@@ -151,6 +151,18 @@
   SimulateInputTimingUpdate(input_timing, web_contents()->GetMainFrame());
 }
 
+void PageLoadMetricsObserverTester::SimulateMobileFriendlinessUpdate(
+    const blink::MobileFriendliness& mobile_friendliness,
+    content::RenderFrameHost* rfh) {
+  mojom::PageLoadTiming timing;
+  page_load_metrics::InitPageLoadTimingForTest(&timing);
+  SimulatePageLoadTimingUpdate(
+      timing, mojom::FrameMetadata(),
+      /* new_features= */ {}, mojom::FrameRenderDataUpdate(),
+      mojom::CpuTiming(), mojom::DeferredResourceCounts(), mojom::InputTiming(),
+      mobile_friendliness, rfh);
+}
+
 void PageLoadMetricsObserverTester::SimulateInputTimingUpdate(
     const mojom::InputTiming& input_timing,
     content::RenderFrameHost* rfh) {
diff --git a/components/page_load_metrics/browser/observers/page_load_metrics_observer_tester.h b/components/page_load_metrics/browser/observers/page_load_metrics_observer_tester.h
index ef07776..2f0fd53 100644
--- a/components/page_load_metrics/browser/observers/page_load_metrics_observer_tester.h
+++ b/components/page_load_metrics/browser/observers/page_load_metrics_observer_tester.h
@@ -94,6 +94,9 @@
   void SimulateInputTimingUpdate(const mojom::InputTiming& input_timing);
   void SimulateInputTimingUpdate(const mojom::InputTiming& input_timing,
                                  content::RenderFrameHost* rfh);
+  void SimulateMobileFriendlinessUpdate(
+      const blink::MobileFriendliness& mobile_friendliness,
+      content::RenderFrameHost* rfh);
   void SimulateTimingAndMetadataUpdate(const mojom::PageLoadTiming& timing,
                                        const mojom::FrameMetadata& metadata);
   void SimulateMetadataUpdate(const mojom::FrameMetadata& metadata,
diff --git a/components/page_load_metrics/browser/page_load_metrics_observer.cc b/components/page_load_metrics/browser/page_load_metrics_observer.cc
index 749895c..b88e2fa 100644
--- a/components/page_load_metrics/browser/page_load_metrics_observer.cc
+++ b/components/page_load_metrics/browser/page_load_metrics_observer.cc
@@ -6,8 +6,34 @@
 
 #include <utility>
 
+namespace {
+
+int BucketWithOffsetAndUnit(int num, int offset, int unit) {
+  // Bucketing raw number with `offset` centered.
+  const int grid = (num - offset) / unit;
+  const int bucketed =
+      grid == 0 ? 0
+                : grid > 0 ? std::pow(2, static_cast<int>(std::log2(grid)))
+                           : -std::pow(2, static_cast<int>(std::log2(-grid)));
+  return bucketed * unit + offset;
+}
+
+}  // namespace
+
 namespace page_load_metrics {
 
+int GetBucketedViewportInitialScale(const blink::MobileFriendliness& mf) {
+  return mf.viewport_initial_scale_x10 <= -1
+             ? -1
+             : BucketWithOffsetAndUnit(mf.viewport_initial_scale_x10, 10, 2);
+}
+
+int GetBucketedViewportHardcodedWidth(const blink::MobileFriendliness& mf) {
+  return mf.viewport_hardcoded_width <= -1
+             ? -1
+             : BucketWithOffsetAndUnit(mf.viewport_hardcoded_width, 500, 10);
+}
+
 MemoryUpdate::MemoryUpdate(content::GlobalFrameRoutingId id, int64_t delta)
     : routing_id(id), delta_bytes(delta) {}
 
diff --git a/components/page_load_metrics/browser/page_load_metrics_observer.h b/components/page_load_metrics/browser/page_load_metrics_observer.h
index 385c171..7844e41 100644
--- a/components/page_load_metrics/browser/page_load_metrics_observer.h
+++ b/components/page_load_metrics/browser/page_load_metrics_observer.h
@@ -31,6 +31,14 @@
 
 namespace page_load_metrics {
 
+// Get bucketed value of viewport initial scale from given MobileFriendliness
+// metrics.
+int GetBucketedViewportInitialScale(const blink::MobileFriendliness& mf);
+
+// Get bucketed value of hardcoded viewport width from given MobileFriendliness
+// metrics.
+int GetBucketedViewportHardcodedWidth(const blink::MobileFriendliness& mf);
+
 // Struct for storing per-frame memory update data.
 struct MemoryUpdate {
   content::GlobalFrameRoutingId routing_id;
@@ -370,6 +378,9 @@
   virtual void OnTimingUpdate(content::RenderFrameHost* subframe_rfh,
                               const mojom::PageLoadTiming& timing) {}
 
+  virtual void OnMobileFriendlinessUpdate(
+      const blink::MobileFriendliness& mobile_friendliness) {}
+
   // OnRenderDataUpdate is triggered when an updated PageRenderData is available
   // at the subframe level. This method may be called multiple times over the
   // course of the page load.
diff --git a/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.cc b/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.cc
index 5f02125..fbc1e4fa 100644
--- a/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.cc
+++ b/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.cc
@@ -497,12 +497,13 @@
     UpdateMainFrameMetadata(render_frame_host, std::move(new_metadata));
     UpdateMainFrameTiming(std::move(new_timing));
     UpdateMainFrameRenderData(*render_data);
+    UpdateMainFrameMobileFriendliness(mobile_friendliness);
   } else {
     UpdateSubFrameMetadata(render_frame_host, std::move(new_metadata));
     UpdateSubFrameTiming(render_frame_host, std::move(new_timing));
+    UpdateSubFrameMobileFriendliness(mobile_friendliness);
   }
   UpdatePageInputTiming(*input_timing_delta);
-  UpdateMobileFriendliness(mobile_friendliness);
   UpdatePageRenderData(*render_data);
   if (!is_main_frame) {
     // This path is just for the AMP metrics.
@@ -621,6 +622,17 @@
   MaybeUpdateFrameIntersection(render_frame_host, subframe_metadata);
 }
 
+void PageLoadMetricsUpdateDispatcher::UpdateMainFrameMobileFriendliness(
+    const blink::MobileFriendliness& mobile_friendliness) {
+  mobile_friendliness_ = mobile_friendliness;
+}
+
+void PageLoadMetricsUpdateDispatcher::UpdateSubFrameMobileFriendliness(
+    const blink::MobileFriendliness& mobile_friendliness) {
+  mobile_friendliness_ = mobile_friendliness;
+  client_->OnSubFrameMobileFriendlinessChanged(mobile_friendliness);
+}
+
 void PageLoadMetricsUpdateDispatcher::MaybeUpdateFrameIntersection(
     content::RenderFrameHost* render_frame_host,
     const mojom::FrameMetadataPtr& frame_metadata) {
@@ -728,11 +740,6 @@
       input_timing_delta.total_adjusted_input_delay;
 }
 
-void PageLoadMetricsUpdateDispatcher::UpdateMobileFriendliness(
-    const blink::MobileFriendliness& mobile_friendliness) {
-  mobile_friendliness_ = mobile_friendliness;
-}
-
 void PageLoadMetricsUpdateDispatcher::UpdatePageRenderData(
     const mojom::FrameRenderDataUpdate& render_data) {
   page_render_data_.layout_shift_score += render_data.layout_shift_delta;
diff --git a/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h b/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h
index fdb9e2fb..77469cff 100644
--- a/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h
+++ b/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h
@@ -124,6 +124,8 @@
     virtual void OnSubFrameRenderDataChanged(
         content::RenderFrameHost* rfh,
         const mojom::FrameRenderDataUpdate& render_data) = 0;
+    virtual void OnSubFrameMobileFriendlinessChanged(
+        const blink::MobileFriendliness& mobile_friendliness) = 0;
     virtual void UpdateFeaturesUsage(
         content::RenderFrameHost* rfh,
         const std::vector<blink::UseCounterFeature>& new_features) = 0;
@@ -227,14 +229,17 @@
   void UpdateSubFrameMetadata(content::RenderFrameHost* render_frame_host,
                               mojom::FrameMetadataPtr subframe_metadata);
 
+  void UpdateMainFrameMobileFriendliness(
+      const blink::MobileFriendliness& mobile_friendliness);
+  void UpdateSubFrameMobileFriendliness(
+      const blink::MobileFriendliness& mobile_friendliness);
+
   void UpdatePageInputTiming(const mojom::InputTiming& input_timing_delta);
   void MaybeUpdateFrameIntersection(
       content::RenderFrameHost* render_frame_host,
       const mojom::FrameMetadataPtr& frame_metadata);
 
   void UpdatePageRenderData(const mojom::FrameRenderDataUpdate& render_data);
-  void UpdateMobileFriendliness(
-      const blink::MobileFriendliness& mobile_friendliness);
   void UpdateMainFrameRenderData(
       const mojom::FrameRenderDataUpdate& render_data);
   void OnSubFrameRenderDataChanged(
diff --git a/components/page_load_metrics/browser/page_load_tracker.cc b/components/page_load_metrics/browser/page_load_tracker.cc
index bcc6c27..462fbf8 100644
--- a/components/page_load_metrics/browser/page_load_tracker.cc
+++ b/components/page_load_metrics/browser/page_load_tracker.cc
@@ -787,6 +787,13 @@
   }
 }
 
+void PageLoadTracker::OnSubFrameMobileFriendlinessChanged(
+    const blink::MobileFriendliness& mobile_friendliness) {
+  for (const auto& observer : observers_) {
+    observer->OnMobileFriendlinessUpdate(mobile_friendliness);
+  }
+}
+
 void PageLoadTracker::BroadcastEventToObservers(PageLoadMetricsEvent event) {
   for (const auto& observer : observers_) {
     observer->OnEventOccurred(event);
diff --git a/components/page_load_metrics/browser/page_load_tracker.h b/components/page_load_metrics/browser/page_load_tracker.h
index 0d87aa0..5428633 100644
--- a/components/page_load_metrics/browser/page_load_tracker.h
+++ b/components/page_load_metrics/browser/page_load_tracker.h
@@ -193,6 +193,8 @@
   void OnMainFrameMetadataChanged() override;
   void OnSubframeMetadataChanged(content::RenderFrameHost* rfh,
                                  const mojom::FrameMetadata& metadata) override;
+  void OnSubFrameMobileFriendlinessChanged(
+      const blink::MobileFriendliness&) override;
   void UpdateFeaturesUsage(
       content::RenderFrameHost* rfh,
       const std::vector<blink::UseCounterFeature>& new_features) override;
diff --git a/components/services/app_service/public/mojom/types.mojom b/components/services/app_service/public/mojom/types.mojom
index c4e8855..edd659ac 100644
--- a/components/services/app_service/public/mojom/types.mojom
+++ b/components/services/app_service/public/mojom/types.mojom
@@ -462,4 +462,6 @@
   kWindow,
   // Opens in the default web browser
   kBrowser,
+  // Opens in a tabbed app window
+  kTabbedWindow,
 };
\ No newline at end of file
diff --git a/components/soda/soda_installer.cc b/components/soda/soda_installer.cc
index e7401fb..bc36f864 100644
--- a/components/soda/soda_installer.cc
+++ b/components/soda/soda_installer.cc
@@ -114,6 +114,11 @@
   NotifyOnSodaInstalled();
 }
 
+void SodaInstaller::UninstallSodaForTesting() {
+  soda_binary_installed_ = false;
+  language_installed_ = false;
+}
+
 void SodaInstaller::RegisterRegisteredLanguagePackPref(
     PrefRegistrySimple* registry) {
   // TODO: Default to one of the user's languages.
diff --git a/components/soda/soda_installer.h b/components/soda/soda_installer.h
index 5a9d6fd..19d39bc 100644
--- a/components/soda/soda_installer.h
+++ b/components/soda/soda_installer.h
@@ -107,6 +107,8 @@
 
   void NotifySodaInstalledForTesting();
 
+  void UninstallSodaForTesting();
+
  protected:
   // Registers the preference tracking the installed SODA language packs.
   static void RegisterRegisteredLanguagePackPref(PrefRegistrySimple* registry);
diff --git a/content/browser/browser_interface_binders.cc b/content/browser/browser_interface_binders.cc
index 81bf983f..089ef34 100644
--- a/content/browser/browser_interface_binders.cc
+++ b/content/browser/browser_interface_binders.cc
@@ -840,11 +840,6 @@
                             base::Unretained(host)));
   }
 
-  if (blink::features::IsPrerender2Enabled()) {
-    map->Add<blink::mojom::PrerenderProcessor>(base::BindRepeating(
-        &RenderFrameHostImpl::BindPrerenderProcessor, base::Unretained(host)));
-  }
-
 #if defined(OS_ANDROID)
   if (base::FeatureList::IsEnabled(features::kWebNfc)) {
     map->Add<device::mojom::NFC>(base::BindRepeating(
diff --git a/content/browser/prerender/prerender_host.h b/content/browser/prerender/prerender_host.h
index d13914d4..13352ff 100644
--- a/content/browser/prerender/prerender_host.h
+++ b/content/browser/prerender/prerender_host.h
@@ -136,6 +136,8 @@
 
   void CreatePageHolder(WebContentsImpl& web_contents);
 
+  // TODO(https://crbug.com/1217045): Flatten the params and do not rely on
+  // PrerenderAttributesPtr.
   const blink::mojom::PrerenderAttributesPtr attributes_;
   const url::Origin initiator_origin_;
   const int initiator_process_id_;
diff --git a/content/browser/prerender/prerender_host_registry.h b/content/browser/prerender/prerender_host_registry.h
index 3cdacc4..c4ca0dab 100644
--- a/content/browser/prerender/prerender_host_registry.h
+++ b/content/browser/prerender/prerender_host_registry.h
@@ -66,6 +66,8 @@
   // For triggers.
   // Creates and starts a host. Returns the root frame tree node id of the
   // prerendered page, which can be used as the id of the host.
+  // TODO(https://crbug.com/1217045): Flatten the params and do not rely on
+  // PrerenderAttributesPtr.
   int CreateAndStartHost(blink::mojom::PrerenderAttributesPtr attributes,
                          RenderFrameHostImpl& initiator_render_frame_host);
 
diff --git a/content/browser/prerender/prerender_processor.cc b/content/browser/prerender/prerender_processor.cc
index bcc42c9..41376d20 100644
--- a/content/browser/prerender/prerender_processor.cc
+++ b/content/browser/prerender/prerender_processor.cc
@@ -40,68 +40,24 @@
 void PrerenderProcessor::Start(
     blink::mojom::PrerenderAttributesPtr attributes) {
   // Start() must be called only one time.
-  if (state_ != State::kInitial) {
-    mojo::ReportBadMessage("PP_START_TWICE");
-    return;
-  }
+  DCHECK_EQ(state_, State::kInitial);
   state_ = State::kStarted;
 
-  // Abort cross-origin prerendering.
-  // TODO(https://crbug.com/1176054): This is a tentative behavior. We plan to
-  // support cross-origin prerendering later.
-  if (!initiator_origin_.IsSameOriginWith(
-          url::Origin::Create(attributes->url))) {
-    mojo::ReportBadMessage("PP_CROSS_ORIGIN");
-    return;
-  }
-
-  // Prerendering is only supported for <link rel=prerender> and
-  // SpeculationRules.
-  switch (attributes->trigger_type) {
-    case blink::mojom::PrerenderTriggerType::kLinkRelPrerender:
-    case blink::mojom::PrerenderTriggerType::kSpeculationRule:
-      break;
-    case blink::mojom::PrerenderTriggerType::kLinkRelNext:
-      return;
-  }
-
-  // TODO(https://crbug.com/1132746): Validate the origin, etc and send
-  // mojo::ReportBadMessage() if necessary like
-  // `NoStatePrefetchProcessorImpl::Start()`.
-
-  // TODO(https://crbug.com/1138711, https://crbug.com/1138723): Abort if the
-  // initiator frame is not the main frame (i.e., iframe or pop-up window).
-
-  // The origin may have changed if a same-site navigation occurred in the frame
-  // after the PrerenderProcessor was created.
-  if (initiator_render_frame_host_.GetLastCommittedOrigin() !=
-      initiator_origin_) {
-    return;
-  }
-
-  // Report bad message if asked to prerender webUI.
-  std::string scheme = attributes->url.scheme();
-  const auto& webui_schemes = URLDataManagerBackend::GetWebUISchemes();
-  if (base::Contains(webui_schemes, scheme)) {
-    mojo::ReportBadMessage("PP_WEBUI");
-    return;
-  }
-
   if (!registry_)
     return;
+
+  // TODO(https://crbug.com/1176054): This is a tentative behavior. We plan to
+  // support cross-origin prerendering later.
+  CHECK(
+      initiator_origin_.IsSameOriginWith(url::Origin::Create(attributes->url)));
+  CHECK(attributes->url.SchemeIsHTTPOrHTTPS());
+  CHECK_EQ(attributes->trigger_type,
+           blink::mojom::PrerenderTriggerType::kSpeculationRule);
+
   prerender_frame_tree_node_id_ = registry_->CreateAndStartHost(
       std::move(attributes), initiator_render_frame_host_);
 }
 
-void PrerenderProcessor::Cancel() {
-  // Cancel() must be called after Start().
-  if (state_ != State::kStarted) {
-    mojo::ReportBadMessage("PP_CANCEL_BEFORE_START");
-    return;
-  }
-  CancelPrerendering();
-}
-
 void PrerenderProcessor::OnRegistryDestroyed() {
   DCHECK(registry_);
   registry_ = nullptr;
diff --git a/content/browser/prerender/prerender_processor.h b/content/browser/prerender/prerender_processor.h
index bd5000b..59885cc7c 100644
--- a/content/browser/prerender/prerender_processor.h
+++ b/content/browser/prerender/prerender_processor.h
@@ -19,16 +19,13 @@
 class RenderFrameHostImpl;
 
 // Prerender2:
-// PrerenderProcessor implements blink::mojom::PrerenderProcessor and works as
-// the browser-side entry point of prerendering. This is created per prerender
-// request (see comments on the mojom interface for details) and owned by the
-// initiator RenderFrameHostImpl's mojo::UniqueReceiverSet.
+// PrerenderProcessor works as the browser-side entry point of prerendering.
+// This is created per prerender request and owned by SpeculationHostImpl.
 //
-// When Start() is called from a renderer process, this asks
-// PrerenderHostRegistry to create a PrerenderHost and start prerendering.
+// When Start() is called, this asks PrerenderHostRegistry to create a
+// PrerenderHost and start prerendering.
 class CONTENT_EXPORT PrerenderProcessor final
-    : public blink::mojom::PrerenderProcessor,
-      public PrerenderHostRegistry::Observer {
+    : public PrerenderHostRegistry::Observer {
  public:
   explicit PrerenderProcessor(RenderFrameHostImpl& initiator_render_frame_host);
   ~PrerenderProcessor() override;
@@ -38,9 +35,9 @@
   PrerenderProcessor(PrerenderProcessor&&) = delete;
   PrerenderProcessor& operator=(PrerenderProcessor&&) = delete;
 
-  // blink::mojom::PrerenderProcessor implementation:
-  void Start(blink::mojom::PrerenderAttributesPtr attributes) override;
-  void Cancel() override;
+  // TODO(https://crbug.com/1217045): Flatten the params and do not rely on
+  // PrerenderAttributesPtr.
+  void Start(blink::mojom::PrerenderAttributesPtr attributes);
 
   // PrerenderHostRegistry::Observer implementation:
   void OnRegistryDestroyed() override;
@@ -48,7 +45,9 @@
  private:
   void CancelPrerendering();
 
-  // The initiator render frame host owns `this`, so the reference is safe.
+  // SpeculationHostImpl, which inherits DocumentServiceBase and is tied with
+  // the lifetime of the initiator RenderFrameHostImpl, owns `this`, so the
+  // reference is safe.
   RenderFrameHostImpl& initiator_render_frame_host_;
 
   // The origin of the initiator render frame host. This could be different from
diff --git a/content/browser/prerender/prerender_processor_unittest.cc b/content/browser/prerender/prerender_processor_unittest.cc
index f6602f8..a1d1824 100644
--- a/content/browser/prerender/prerender_processor_unittest.cc
+++ b/content/browser/prerender/prerender_processor_unittest.cc
@@ -4,8 +4,7 @@
 
 #include "content/browser/prerender/prerender_processor.h"
 
-#include "base/run_loop.h"
-#include "base/test/bind.h"
+#include "base/test/gtest_util.h"
 #include "base/test/scoped_feature_list.h"
 #include "content/browser/site_instance_impl.h"
 #include "content/public/browser/back_forward_cache.h"
@@ -15,7 +14,6 @@
 #include "content/public/test/test_utils.h"
 #include "content/test/test_render_view_host.h"
 #include "content/test/test_web_contents.h"
-#include "mojo/public/cpp/system/functions.h"
 #include "third_party/blink/public/common/features.h"
 
 namespace content {
@@ -47,8 +45,6 @@
     return web_contents_->GetMainFrame();
   }
 
-  TestWebContents* GetWebContents() { return web_contents_.get(); }
-
   GURL GetSameOriginUrl(const std::string& path) {
     return GURL("https://example.com" + path);
   }
@@ -57,18 +53,10 @@
     return GURL("https://other.example.com" + path);
   }
 
-  GURL GetCrossSiteUrl(const std::string& path) {
-    return GURL("https://example.test" + path);
-  }
-
   PrerenderHostRegistry* GetPrerenderHostRegistry() const {
     return web_contents_->GetPrerenderHostRegistry();
   }
 
-  void NavigateAndCommit(const GURL& url) {
-    web_contents_->NavigateAndCommit(url);
-  }
-
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
 
@@ -76,160 +64,29 @@
   std::unique_ptr<TestWebContents> web_contents_;
 };
 
-TEST_F(PrerenderProcessorTest, StartCancel) {
+TEST_F(PrerenderProcessorTest, StartDestruct) {
   RenderFrameHostImpl* render_frame_host = GetRenderFrameHost();
   PrerenderHostRegistry* registry = GetPrerenderHostRegistry();
 
-  mojo::Remote<blink::mojom::PrerenderProcessor> remote;
-  render_frame_host->BindPrerenderProcessor(
-      remote.BindNewPipeAndPassReceiver());
+  auto prerender_processor =
+      std::make_unique<PrerenderProcessor>(*render_frame_host);
 
   const GURL kPrerenderingUrl = GetSameOriginUrl("/next");
   auto attributes = blink::mojom::PrerenderAttributes::New();
   attributes->url = kPrerenderingUrl;
   attributes->referrer = blink::mojom::Referrer::New();
-
+  attributes->trigger_type =
+      blink::mojom::PrerenderTriggerType::kSpeculationRule;
   // Start() call should register a new prerender host.
   EXPECT_FALSE(registry->FindHostByUrlForTesting(kPrerenderingUrl));
-  remote->Start(std::move(attributes));
-  remote.FlushForTesting();
+  prerender_processor->Start(std::move(attributes));
   EXPECT_TRUE(registry->FindHostByUrlForTesting(kPrerenderingUrl));
 
-  // Cancel() call should abandon the prerender host.
-  remote->Cancel();
-  remote.FlushForTesting();
+  // Destroying `prerender_processor` should abandon the prerender host.
+  prerender_processor.reset();
   EXPECT_FALSE(registry->FindHostByUrlForTesting(kPrerenderingUrl));
 }
 
-TEST_F(PrerenderProcessorTest, StartDisconnect) {
-  RenderFrameHostImpl* render_frame_host = GetRenderFrameHost();
-  PrerenderHostRegistry* registry = GetPrerenderHostRegistry();
-
-  mojo::Remote<blink::mojom::PrerenderProcessor> remote;
-  render_frame_host->BindPrerenderProcessor(
-      remote.BindNewPipeAndPassReceiver());
-
-  const GURL kPrerenderingUrl = GetSameOriginUrl("/next");
-  auto attributes = blink::mojom::PrerenderAttributes::New();
-  attributes->url = kPrerenderingUrl;
-  attributes->referrer = blink::mojom::Referrer::New();
-
-  // Start() call should register a new prerender host.
-  EXPECT_FALSE(registry->FindHostByUrlForTesting(kPrerenderingUrl));
-  remote->Start(std::move(attributes));
-  remote.FlushForTesting();
-  EXPECT_TRUE(registry->FindHostByUrlForTesting(kPrerenderingUrl));
-
-  // Connection lost should abandon the prerender host.
-  remote.reset();
-  // FlushForTesting() is no longer available. Instead, use base::RunLoop.
-  base::RunLoop().RunUntilIdle();
-  EXPECT_FALSE(registry->FindHostByUrlForTesting(kPrerenderingUrl));
-}
-
-TEST_F(PrerenderProcessorTest, CancelOnDestruction) {
-  RenderFrameHostImpl* render_frame_host = GetRenderFrameHost();
-  PrerenderHostRegistry* registry = GetPrerenderHostRegistry();
-
-  mojo::Remote<blink::mojom::PrerenderProcessor> remote;
-  render_frame_host->BindPrerenderProcessor(
-      remote.BindNewPipeAndPassReceiver());
-
-  const GURL kPrerenderingUrl = GetSameOriginUrl("/next");
-  auto attributes = blink::mojom::PrerenderAttributes::New();
-  attributes->url = kPrerenderingUrl;
-  attributes->referrer = blink::mojom::Referrer::New();
-
-  // Start() call should register a new prerender host.
-  EXPECT_FALSE(registry->FindHostByUrlForTesting(kPrerenderingUrl));
-  remote->Start(std::move(attributes));
-  remote.FlushForTesting();
-  EXPECT_TRUE(registry->FindHostByUrlForTesting(kPrerenderingUrl));
-
-  // The test assumes `render_frame_host` to be deleted. Disable the
-  // back-forward cache to ensure that it doesn't get preserved in the cache.
-  DisableBackForwardCacheForTesting(GetWebContents(),
-                                    BackForwardCache::TEST_ASSUMES_NO_CACHING);
-
-  // Navigate the primary page to a cross-site URL that induces destruction of
-  // the render frame host.
-  RenderFrameDeletedObserver observer(render_frame_host);
-  const GURL kCrossSiteUrl = GetCrossSiteUrl("/cross-site");
-  NavigationSimulator::NavigateAndCommitFromBrowser(GetWebContents(),
-                                                    kCrossSiteUrl);
-  observer.WaitUntilDeleted();
-
-  // The destruction of the render frame host should destroy PrerenderProcessor
-  // and cancel prerendering.
-  EXPECT_FALSE(registry->FindHostByUrlForTesting(kPrerenderingUrl));
-}
-
-TEST_F(PrerenderProcessorTest, StartTwice) {
-  RenderFrameHostImpl* render_frame_host = GetRenderFrameHost();
-  PrerenderHostRegistry* registry = GetPrerenderHostRegistry();
-
-  mojo::Remote<blink::mojom::PrerenderProcessor> remote;
-  render_frame_host->BindPrerenderProcessor(
-      remote.BindNewPipeAndPassReceiver());
-
-  // Set up the error handler for bad mojo messages.
-  std::string bad_message_error;
-  mojo::SetDefaultProcessErrorHandler(
-      base::BindLambdaForTesting([&](const std::string& error) {
-        EXPECT_TRUE(bad_message_error.empty());
-        bad_message_error = error;
-      }));
-
-  const GURL kPrerenderingUrl = GetSameOriginUrl("/next");
-  auto attributes1 = blink::mojom::PrerenderAttributes::New();
-  attributes1->url = kPrerenderingUrl;
-  attributes1->referrer = blink::mojom::Referrer::New();
-
-  // Start() call should register a new prerender host.
-  EXPECT_FALSE(registry->FindHostByUrlForTesting(kPrerenderingUrl));
-  remote->Start(std::move(attributes1));
-  remote.FlushForTesting();
-  EXPECT_TRUE(registry->FindHostByUrlForTesting(kPrerenderingUrl));
-
-  auto attributes2 = blink::mojom::PrerenderAttributes::New();
-  attributes2->url = kPrerenderingUrl;
-  attributes2->referrer = blink::mojom::Referrer::New();
-
-  // Call Start() again. This should be reported as a bad mojo message.
-  ASSERT_TRUE(bad_message_error.empty());
-  remote->Start(std::move(attributes2));
-  remote.FlushForTesting();
-  EXPECT_EQ(bad_message_error, "PP_START_TWICE");
-}
-
-TEST_F(PrerenderProcessorTest, CancelBeforeStart) {
-  RenderFrameHostImpl* render_frame_host = GetRenderFrameHost();
-
-  mojo::Remote<blink::mojom::PrerenderProcessor> remote;
-  render_frame_host->BindPrerenderProcessor(
-      remote.BindNewPipeAndPassReceiver());
-
-  // Set up the error handler for bad mojo messages.
-  std::string bad_message_error;
-  mojo::SetDefaultProcessErrorHandler(
-      base::BindLambdaForTesting([&](const std::string& error) {
-        EXPECT_TRUE(bad_message_error.empty());
-        bad_message_error = error;
-      }));
-
-  const GURL kPrerenderingUrl = GetSameOriginUrl("/next");
-  auto attributes1 = blink::mojom::PrerenderAttributes::New();
-  attributes1->url = kPrerenderingUrl;
-  attributes1->referrer = blink::mojom::Referrer::New();
-
-  // Call Cancel() before Start(). This should be reported as a bad mojo
-  // message.
-  ASSERT_TRUE(bad_message_error.empty());
-  remote->Cancel();
-  remote.FlushForTesting();
-  EXPECT_EQ(bad_message_error, "PP_CANCEL_BEFORE_START");
-}
-
 // Tests that prerendering a cross-origin URL is aborted. Cross-origin
 // prerendering is not supported for now, but we plan to support it later
 // (https://crbug.com/1176054).
@@ -237,94 +94,69 @@
   RenderFrameHostImpl* render_frame_host = GetRenderFrameHost();
   PrerenderHostRegistry* registry = GetPrerenderHostRegistry();
 
-  mojo::Remote<blink::mojom::PrerenderProcessor> remote;
-  render_frame_host->BindPrerenderProcessor(
-      remote.BindNewPipeAndPassReceiver());
-
-  // Set up the error handler for bad mojo messages.
-  std::string bad_message_error;
-  mojo::SetDefaultProcessErrorHandler(
-      base::BindLambdaForTesting([&](const std::string& error) {
-        EXPECT_FALSE(error.empty());
-        EXPECT_TRUE(bad_message_error.empty());
-        bad_message_error = error;
-      }));
+  auto prerender_processor =
+      std::make_unique<PrerenderProcessor>(*render_frame_host);
 
   const GURL kPrerenderingUrl = GetCrossOriginUrl("/next");
   auto attributes = blink::mojom::PrerenderAttributes::New();
   attributes->url = kPrerenderingUrl;
   attributes->referrer = blink::mojom::Referrer::New();
+  attributes->trigger_type =
+      blink::mojom::PrerenderTriggerType::kSpeculationRule;
 
-  // Start() call with the cross-origin URL should be reported as a bad message.
+  // Start() call with the cross-origin URL should hit a DCHECK.
   EXPECT_FALSE(registry->FindHostByUrlForTesting(kPrerenderingUrl));
-  remote->Start(std::move(attributes));
-  remote.FlushForTesting();
-  EXPECT_EQ(bad_message_error, "PP_CROSS_ORIGIN");
+  EXPECT_CHECK_DEATH(prerender_processor->Start(std::move(attributes)));
 }
 
 // Tests that prerendering triggered by <link rel=next> is aborted. This trigger
 // is not supported for now, but we may want to support it if NoStatePrefetch
 // re-enables it again. See https://crbug.com/1161545.
+// TODO(https://crbug.com/1217045): Remove this test after Prerender2 does not
+// rely on PrerenderAttributesPtr.
 TEST_F(PrerenderProcessorTest, RelTypeNext) {
   RenderFrameHostImpl* render_frame_host = GetRenderFrameHost();
   PrerenderHostRegistry* registry = GetPrerenderHostRegistry();
 
-  mojo::Remote<blink::mojom::PrerenderProcessor> remote;
-  render_frame_host->BindPrerenderProcessor(
-      remote.BindNewPipeAndPassReceiver());
-
-  // Set up the error handler for bad mojo messages.
-  std::string bad_message_error;
-  mojo::SetDefaultProcessErrorHandler(
-      base::BindLambdaForTesting([&](const std::string& error) {
-        EXPECT_FALSE(error.empty());
-        EXPECT_TRUE(bad_message_error.empty());
-        bad_message_error = error;
-      }));
+  auto prerender_processor =
+      std::make_unique<PrerenderProcessor>(*render_frame_host);
 
   const GURL kPrerenderingUrl = GetSameOriginUrl("/next");
   auto attributes = blink::mojom::PrerenderAttributes::New();
   attributes->url = kPrerenderingUrl;
-  // Set kLinkRelNext instead of the default kLinkRelPrerender.
+  // Set kLinkRelNext instead of the default kSpeculationRule.
   attributes->trigger_type = blink::mojom::PrerenderTriggerType::kLinkRelNext;
   attributes->referrer = blink::mojom::Referrer::New();
 
   // Start() call with kNext should be aborted.
   EXPECT_FALSE(registry->FindHostByUrlForTesting(kPrerenderingUrl));
-  remote->Start(std::move(attributes));
-  remote.FlushForTesting();
+  ASSERT_DCHECK_DEATH(prerender_processor->Start(std::move(attributes)));
   EXPECT_FALSE(registry->FindHostByUrlForTesting(kPrerenderingUrl));
-
-  // Start() call with kNext is a valid request, currently it's not supported
-  // though. The request shouldn't result in a bad message failure.
-  EXPECT_TRUE(bad_message_error.empty());
-
-  // Cancel() call should not be reported as a bad mojo message as well.
-  remote->Cancel();
-  remote.FlushForTesting();
-  EXPECT_TRUE(bad_message_error.empty());
 }
 
-TEST_F(PrerenderProcessorTest, StartAfterNavigation) {
+// Tests that prerendering triggered by <link rel=prerender> is aborted. This
+// trigger is not supported for now, but we want to support it after Prerender2
+// works well.
+// TODO(https://crbug.com/1217045): Remove this test after Prerender2 does not
+// rely on PrerenderAttributesPtr.
+TEST_F(PrerenderProcessorTest, RelTypePrerender) {
   RenderFrameHostImpl* render_frame_host = GetRenderFrameHost();
   PrerenderHostRegistry* registry = GetPrerenderHostRegistry();
 
-  mojo::Remote<blink::mojom::PrerenderProcessor> remote;
-  render_frame_host->BindPrerenderProcessor(
-      remote.BindNewPipeAndPassReceiver());
+  auto prerender_processor =
+      std::make_unique<PrerenderProcessor>(*render_frame_host);
 
   const GURL kPrerenderingUrl = GetSameOriginUrl("/next");
   auto attributes = blink::mojom::PrerenderAttributes::New();
   attributes->url = kPrerenderingUrl;
+  // Set kLinkRelPrerender instead of the default kSpeculationRule.
+  attributes->trigger_type =
+      blink::mojom::PrerenderTriggerType::kLinkRelPrerender;
   attributes->referrer = blink::mojom::Referrer::New();
 
-  // Navigate to a same-site, but different origin URL.
-  NavigateAndCommit(GetCrossOriginUrl("/navigate"));
-
-  // Start() call should not register a new prerender host.
+  // Start() call with kLinkRelPrerender should be aborted.
   EXPECT_FALSE(registry->FindHostByUrlForTesting(kPrerenderingUrl));
-  remote->Start(std::move(attributes));
-  remote.FlushForTesting();
+  ASSERT_DCHECK_DEATH(prerender_processor->Start(std::move(attributes)));
   EXPECT_FALSE(registry->FindHostByUrlForTesting(kPrerenderingUrl));
 }
 
diff --git a/content/browser/renderer_host/back_forward_cache_impl.cc b/content/browser/renderer_host/back_forward_cache_impl.cc
index e3fa93f..07ff27e 100644
--- a/content/browser/renderer_host/back_forward_cache_impl.cc
+++ b/content/browser/renderer_host/back_forward_cache_impl.cc
@@ -56,7 +56,7 @@
 static constexpr size_t kDefaultForegroundBackForwardCacheSize = 0;
 
 // The default time to live in seconds for documents in BackForwardCache.
-static constexpr int kDefaultTimeToLiveInBackForwardCacheInSeconds = 15;
+static constexpr int kDefaultTimeToLiveInBackForwardCacheInSeconds = 180;
 
 #if defined(OS_ANDROID)
 bool IsProcessBindingEnabled() {
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 4c7558e8..5bb8c66 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -90,8 +90,8 @@
 #include "content/browser/permissions/permission_service_context.h"
 #include "content/browser/permissions/permission_service_impl.h"
 #include "content/browser/portal/portal.h"
+#include "content/browser/prerender/prerender_host_registry.h"
 #include "content/browser/prerender/prerender_metrics.h"
-#include "content/browser/prerender/prerender_processor.h"
 #include "content/browser/presentation/presentation_service_impl.h"
 #include "content/browser/push_messaging/push_messaging_manager.h"
 #include "content/browser/renderer_host/agent_scheduling_group_host.h"
@@ -8523,13 +8523,6 @@
   screen_enumeration_impl_->Bind(std::move(receiver));
 }
 
-void RenderFrameHostImpl::BindPrerenderProcessor(
-    mojo::PendingReceiver<blink::mojom::PrerenderProcessor> pending_receiver) {
-  DCHECK(blink::features::IsPrerender2Enabled());
-  prerender_processor_receivers_.Add(
-      std::make_unique<PrerenderProcessor>(*this), std::move(pending_receiver));
-}
-
 void RenderFrameHostImpl::CancelPrerendering(
     PrerenderHost::FinalStatus status) {
   DCHECK(blink::features::IsPrerender2Enabled());
diff --git a/content/browser/renderer_host/render_frame_host_impl.h b/content/browser/renderer_host/render_frame_host_impl.h
index 6210f6b..6d85bf4 100644
--- a/content/browser/renderer_host/render_frame_host_impl.h
+++ b/content/browser/renderer_host/render_frame_host_impl.h
@@ -1604,9 +1604,6 @@
   void BindScreenEnumerationReceiver(
       mojo::PendingReceiver<blink::mojom::ScreenEnumeration> receiver);
 
-  void BindPrerenderProcessor(
-      mojo::PendingReceiver<blink::mojom::PrerenderProcessor> pending_receiver);
-
   // Prerender2:
   // Tells PrerenderHostRegistry to cancel the prerendering of the page this
   // frame is in, which destroys this frame.
@@ -3705,18 +3702,6 @@
   scoped_refptr<PolicyContainerHost> policy_container_host_;
 
   // Prerender2:
-  // Receivers for PrerenderProcessor that handle prerendering requests from a
-  // renderer process. These receivers are disconnected when the document
-  // explicitly cancels prerendering or the document gets destroyed.
-  // There are two types of RenderFrameHosts during prerendering.
-  // - Initiator RenderFrameHost: (if any) it triggers a prerendering
-  // navigation, e.g., by <link rel="prerender">.
-  // - Prerendered RenderFrameHost: it is for a prerendering page.
-  // Note that it is the initiator RenderFrameHost that stores these receivers.
-  mojo::UniqueReceiverSet<blink::mojom::PrerenderProcessor>
-      prerender_processor_receivers_;
-
-  // Prerender2:
   // This is true while notifying the frame in the renderer of activation for
   // prerendering.
   bool is_notifying_activation_for_prerendering_ = false;
diff --git a/content/browser/speculation_rules/speculation_host_impl.cc b/content/browser/speculation_rules/speculation_host_impl.cc
index e93a180..33b56c5 100644
--- a/content/browser/speculation_rules/speculation_host_impl.cc
+++ b/content/browser/speculation_rules/speculation_host_impl.cc
@@ -16,10 +16,9 @@
 
 namespace {
 
-// Returns true if all candidates pass the inspection.
-bool InspectCandidates(
+bool CandidatesAreValid(
     std::vector<blink::mojom::SpeculationCandidatePtr>& candidates) {
-  for (auto& candidate : candidates) {
+  for (const auto& candidate : candidates) {
     // These non-http candidates should be filtered out in Blink and
     // SpeculationHostImpl should not see them. If SpeculationHostImpl receives
     // non-http candidates, it may mean the renderer process has a bug
@@ -68,7 +67,7 @@
 void SpeculationHostImpl::UpdateSpeculationCandidates(
     std::vector<blink::mojom::SpeculationCandidatePtr> candidates) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  if (!InspectCandidates(candidates))
+  if (!CandidatesAreValid(candidates))
     return;
 
   // Only handle messages from an active main frame.
diff --git a/content/child/OWNERS b/content/child/OWNERS
index 6ef1e6f..c4bf690 100644
--- a/content/child/OWNERS
+++ b/content/child/OWNERS
@@ -1,3 +1,4 @@
+dom@chromium.org
 haraken@chromium.org
 
 per-file browser_exposed_child_interfaces.cc=set noparent
diff --git a/content/public/common/referrer.h b/content/public/common/referrer.h
index 512fbfc..d0af362 100644
--- a/content/public/common/referrer.h
+++ b/content/public/common/referrer.h
@@ -50,13 +50,6 @@
   static net::ReferrerPolicy ReferrerPolicyForUrlRequest(
       network::mojom::ReferrerPolicy referrer_policy);
 
-  // Configures retaining the pre-M80 default referrer
-  // policy of no-referrer-when-downgrade.
-  // TODO(crbug.com/1016541): After M82, remove when the corresponding
-  // enterprise policy has been deleted.
-  static void SetForceLegacyDefaultReferrerPolicy(bool force);
-  static bool ShouldForceLegacyDefaultReferrerPolicy();
-
   // Validates |policy| to make sure it represents one of the valid
   // net::mojom::ReferrerPolicy enum values and returns it.  The relatively safe
   // |kNever| value is returned if |policy| is not a valid value.
diff --git a/extensions/browser/blob_reader.cc b/extensions/browser/blob_reader.cc
index 791422d8..0c41d3e 100644
--- a/extensions/browser/blob_reader.cc
+++ b/extensions/browser/blob_reader.cc
@@ -22,7 +22,8 @@
   CHECK_GT(length, 0);
   CHECK_LE(offset, std::numeric_limits<int64_t>::max() - length);
 
-  absl::optional<Range> range = Range{offset, length};
+  absl::optional<Range> range =
+      Range{static_cast<uint64_t>(offset), static_cast<uint64_t>(length)};
   Read(browser_context, blob_uuid, std::move(callback), std::move(range));
 }
 
diff --git a/extensions/browser/event_router.h b/extensions/browser/event_router.h
index db95ca7..0bfcd592 100644
--- a/extensions/browser/event_router.h
+++ b/extensions/browser/event_router.h
@@ -41,13 +41,13 @@
 namespace content {
 class BrowserContext;
 class RenderProcessHost;
-}
+}  // namespace content
 
-namespace chromeos {
+namespace ash {
 namespace file_system_provider {
 class FileSystemProviderProvidedFileSystemTest;
-}
-}  // namespace chromeos
+}  // namespace file_system_provider
+}  // namespace ash
 
 namespace extensions {
 class Extension;
@@ -325,7 +325,7 @@
  private:
   friend class EventRouterFilterTest;
   friend class EventRouterTest;
-  friend class chromeos::file_system_provider::
+  friend class ash::file_system_provider::
       FileSystemProviderProvidedFileSystemTest;
   friend class UpdateInstallGateTest;
   friend class DownloadExtensionTest;
diff --git a/gpu/command_buffer/common/gpu_memory_buffer_support.cc b/gpu/command_buffer/common/gpu_memory_buffer_support.cc
index 3989d99..3e38afc5 100644
--- a/gpu/command_buffer/common/gpu_memory_buffer_support.cc
+++ b/gpu/command_buffer/common/gpu_memory_buffer_support.cc
@@ -44,8 +44,13 @@
     case gfx::BufferFormat::YVU_420:
     case gfx::BufferFormat::YUV_420_BIPLANAR:
     case gfx::BufferFormat::P010:
+#if defined(OS_CHROMEOS)
+      // Allow odd size for CrOS. https://crbug.com/1208788
+      return true;
+#else
       // U and V planes are subsampled by a factor of 2.
       return size.width() % 2 == 0 && size.height() % 2 == 0;
+#endif  // defined(OS_CHROMEOS)
   }
 
   NOTREACHED();
diff --git a/gpu/ipc/client/shared_image_interface_proxy.cc b/gpu/ipc/client/shared_image_interface_proxy.cc
index b2505564..5e0e646 100644
--- a/gpu/ipc/client/shared_image_interface_proxy.cc
+++ b/gpu/ipc/client/shared_image_interface_proxy.cc
@@ -173,34 +173,33 @@
 
   auto mailbox = Mailbox::GenerateForSharedImage();
 
-  GpuChannelMsg_CreateGMBSharedImage_Params params;
-  params.mailbox = mailbox;
-  params.handle = gpu_memory_buffer->CloneHandle();
-  params.size = gpu_memory_buffer->GetSizeOfPlane(plane);
-  params.format = gpu_memory_buffer->GetFormat();
-  params.plane = plane;
-  params.color_space = color_space;
-  params.usage = usage;
-  params.surface_origin = surface_origin;
-  params.alpha_type = alpha_type;
+  auto params = mojom::CreateGMBSharedImageParams::New();
+  params->mailbox = mailbox;
+  params->buffer_handle = gpu_memory_buffer->CloneHandle();
+  params->size = gpu_memory_buffer->GetSizeOfPlane(plane);
+  params->format = gpu_memory_buffer->GetFormat();
+  params->plane = plane;
+  params->color_space = color_space;
+  params->usage = usage;
+  params->surface_origin = surface_origin;
+  params->alpha_type = alpha_type;
 
   // TODO(piman): DCHECK GMB format support.
-  DCHECK(gpu::IsImageSizeValidForGpuMemoryBufferFormat(params.size,
-                                                       params.format));
+  DCHECK(gpu::IsImageSizeValidForGpuMemoryBufferFormat(params->size,
+                                                       params->format));
 
-  bool requires_sync_token = params.handle.type == gfx::IO_SURFACE_BUFFER;
+  bool requires_sync_token =
+      params->buffer_handle.type == gfx::IO_SURFACE_BUFFER;
   {
     base::AutoLock lock(lock_);
-    params.release_id = ++next_release_id_;
-    // Note: we send the IPC under the lock, after flushing previous work (if
-    // any) to guarantee monotonicity of the release ids as seen by the service.
-    // Although we don't strictly need to for correctness, we also flush
-    // DestroySharedImage messages, so that we get a chance to delete resources
-    // before creating new ones.
-    // TODO(piman): support messages with handles in EnqueueDeferredMessage.
+    params->release_id = ++next_release_id_;
+    // Note: we enqueue and send the IPC under the lock to guarantee
+    // monotonicity of the release ids as seen by the service.
+    last_flush_id_ = host_->EnqueueDeferredMessage(
+        mojom::DeferredRequestParams::NewSharedImageRequest(
+            mojom::DeferredSharedImageRequest::NewCreateGmbSharedImage(
+                std::move(params))));
     host_->EnsureFlush(last_flush_id_);
-    host_->Send(
-        new GpuChannelMsg_CreateGMBSharedImage(route_id_, std::move(params)));
   }
   if (requires_sync_token) {
     gpu::SyncToken sync_token = GenVerifiedSyncToken();
@@ -338,16 +337,18 @@
       return false;
 
     // Duplicate the buffer for sharing to the GPU process.
-    base::ReadOnlySharedMemoryRegion shared_shm = shm.region.Duplicate();
-    if (!shared_shm.IsValid())
+    base::ReadOnlySharedMemoryRegion readonly_shm = shm.region.Duplicate();
+    if (!readonly_shm.IsValid())
       return false;
 
     // Share the SHM to the GPU process. In order to ensure that any deferred
     // messages which rely on the previous SHM have a chance to execute before
-    // it is replaced, flush before sending.
+    // it is replaced, send this message in the deferred queue.
+    last_flush_id_ = host_->EnqueueDeferredMessage(
+        mojom::DeferredRequestParams::NewSharedImageRequest(
+            mojom::DeferredSharedImageRequest::NewRegisterUploadBuffer(
+                std::move(readonly_shm))));
     host_->EnsureFlush(last_flush_id_);
-    host_->Send(new GpuChannelMsg_RegisterSharedImageUploadBuffer(
-        route_id_, std::move(shared_shm)));
 
     upload_buffer_ = std::move(shm);
     upload_buffer_offset_ = 0;
@@ -448,13 +449,14 @@
     gfx::BufferFormat format,
     gfx::BufferUsage usage,
     bool register_with_image_pipe) {
-  host_->Send(new GpuChannelMsg_RegisterSysmemBufferCollection(
-      route_id_, id, token, format, usage, register_with_image_pipe));
+  host_->GetGpuChannel().RegisterSysmemBufferCollection(
+      id, mojo::PlatformHandle(std::move(token)), format, usage,
+      register_with_image_pipe);
 }
 
 void SharedImageInterfaceProxy::ReleaseSysmemBufferCollection(
     gfx::SysmemBufferCollectionId id) {
-  host_->Send(new GpuChannelMsg_ReleaseSysmemBufferCollection(route_id_, id));
+  host_->GetGpuChannel().ReleaseSysmemBufferCollection(id);
 }
 #endif  // defined(OS_FUCHSIA)
 
diff --git a/gpu/ipc/common/gpu_channel.mojom b/gpu/ipc/common/gpu_channel.mojom
index d53e014..692ee02f 100644
--- a/gpu/ipc/common/gpu_channel.mojom
+++ b/gpu/ipc/common/gpu_channel.mojom
@@ -10,6 +10,7 @@
 import "gpu/ipc/common/surface_handle.mojom";
 import "gpu/ipc/common/sync_token.mojom";
 import "mojo/public/mojom/base/shared_memory.mojom";
+import "mojo/public/mojom/base/unguessable_token.mojom";
 import "services/viz/public/mojom/compositing/resource_format.mojom";
 import "skia/public/mojom/image_info.mojom";
 import "skia/public/mojom/surface_origin.mojom";
@@ -200,6 +201,15 @@
   [Sync, NoInterrupt] WaitForGetOffsetInRange(
       int32 routing_id, uint32 set_get_buffer_count, int32 start, int32 end)
       => (CommandBufferState state);
+
+  [EnableIf=is_fuchsia]
+  RegisterSysmemBufferCollection(
+      mojo_base.mojom.UnguessableToken id, handle<platform> token,
+      gfx.mojom.BufferFormat format, gfx.mojom.BufferUsage usage,
+      bool register_with_image_pipe);
+
+  [EnableIf=is_fuchsia]
+  ReleaseSysmemBufferCollection(mojo_base.mojom.UnguessableToken id);
 };
 
 struct CreateImageParams {
@@ -352,10 +362,17 @@
   // See CreateSharedImageWithDataParams.
   CreateSharedImageWithDataParams create_shared_image_with_data;
 
+  // See CreateGMBSharedImageParams.
+  CreateGMBSharedImageParams create_gmb_shared_image;
+
   // See CreateSharedImageWithAHBParams.
   [EnableIf=is_android]
   CreateSharedImageWithAHBParams create_shared_image_with_ahb;
 
+  // Registers a new shared memory region to share with the GPU process for
+  // uploading shared image data.
+  mojo_base.mojom.ReadOnlySharedMemoryRegion register_upload_buffer;
+
   // See UpdateSharedImageParams.
   UpdateSharedImageParams update_shared_image;
 
@@ -462,6 +479,40 @@
   skia.mojom.AlphaType alpha_type;
 };
 
+// Creates a new shared image from an existing GPU memory buffer.
+struct CreateGMBSharedImageParams {
+  // The mailbox used to identify the shared image.
+  Mailbox mailbox;
+
+  // A handle to the existing GPU memory buffer.
+  gfx.mojom.GpuMemoryBufferHandle buffer_handle;
+
+  // Size in pixels.
+  gfx.mojom.Size size;
+
+  // Format of pixels in the buffer.
+  gfx.mojom.BufferFormat format;
+
+  // Image plane represented by the buffer.
+  gfx.mojom.BufferPlane plane;
+
+  // Color space.
+  gfx.mojom.ColorSpace color_space;
+
+  // Usage flags corresponding to values defined in
+  // gpu/command_buffer/common/shared_image_usage.h.
+  uint32 usage;
+
+  // The fence to release once the image is created.
+  uint32 release_id;
+
+  // Which corner is considered the origin of the new image.
+  skia.mojom.SurfaceOrigin surface_origin;
+
+  // Indicates how the alpha component of each pixel is interpreted.
+  skia.mojom.AlphaType alpha_type;
+};
+
 // Creates a new shared image from an existing one backed by an AHardwareBuffer.
 [EnableIf=is_android]
 struct CreateSharedImageWithAHBParams {
diff --git a/gpu/ipc/common/gpu_messages.h b/gpu/ipc/common/gpu_messages.h
index 9580aa67..a31c1cfa 100644
--- a/gpu/ipc/common/gpu_messages.h
+++ b/gpu/ipc/common/gpu_messages.h
@@ -55,41 +55,10 @@
 
 #define IPC_MESSAGE_START GpuChannelMsgStart
 
-IPC_ENUM_TRAITS_MAX_VALUE(GrSurfaceOrigin, kBottomLeft_GrSurfaceOrigin)
-
-IPC_STRUCT_BEGIN(GpuChannelMsg_CreateGMBSharedImage_Params)
-  IPC_STRUCT_MEMBER(gpu::Mailbox, mailbox)
-  IPC_STRUCT_MEMBER(gfx::GpuMemoryBufferHandle, handle)
-  IPC_STRUCT_MEMBER(gfx::Size, size)
-  IPC_STRUCT_MEMBER(gfx::BufferFormat, format)
-  IPC_STRUCT_MEMBER(gfx::BufferPlane, plane)
-  IPC_STRUCT_MEMBER(gfx::ColorSpace, color_space)
-  IPC_STRUCT_MEMBER(uint32_t, usage)
-  IPC_STRUCT_MEMBER(uint32_t, release_id)
-  IPC_STRUCT_MEMBER(GrSurfaceOrigin, surface_origin)
-  IPC_STRUCT_MEMBER(SkAlphaType, alpha_type)
-IPC_STRUCT_END()
-
 //------------------------------------------------------------------------------
 // GPU Channel Messages
 // These are messages from a renderer process to the GPU process.
 
-IPC_MESSAGE_ROUTED1(GpuChannelMsg_CreateGMBSharedImage,
-                    GpuChannelMsg_CreateGMBSharedImage_Params /* params */)
-
-#if defined(OS_FUCHSIA)
-IPC_MESSAGE_ROUTED5(GpuChannelMsg_RegisterSysmemBufferCollection,
-                    gfx::SysmemBufferCollectionId /* id */,
-                    zx::channel /* token */,
-                    gfx::BufferFormat /* format */,
-                    gfx::BufferUsage /* usage */,
-                    bool /* register_with_image_pipe */)
-IPC_MESSAGE_ROUTED1(GpuChannelMsg_ReleaseSysmemBufferCollection,
-                    gfx::SysmemBufferCollectionId /* id */)
-#endif  // OS_FUCHSIA
-IPC_MESSAGE_ROUTED1(GpuChannelMsg_RegisterSharedImageUploadBuffer,
-                    base::ReadOnlySharedMemoryRegion /* shm */)
-
 #if defined(OS_ANDROID)
 //------------------------------------------------------------------------------
 // Tells the StreamTexture to send its SurfaceTexture to the browser process,
diff --git a/gpu/ipc/common/mock_gpu_channel.h b/gpu/ipc/common/mock_gpu_channel.h
index 7d0620b..a41f1227c 100644
--- a/gpu/ipc/common/mock_gpu_channel.h
+++ b/gpu/ipc/common/mock_gpu_channel.h
@@ -5,6 +5,7 @@
 #ifndef GPU_IPC_COMMON_MOCK_GPU_CHANNEL_H_
 #define GPU_IPC_COMMON_MOCK_GPU_CHANNEL_H_
 
+#include "build/build_config.h"
 #include "gpu/ipc/common/gpu_channel.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
@@ -57,6 +58,16 @@
                     int32_t,
                     int32_t,
                     WaitForGetOffsetInRangeCallback));
+#if defined(OS_FUCHSIA)
+  MOCK_METHOD5(RegisterSysmemBufferCollection,
+               void(const base::UnguessableToken&,
+                    mojo::PlatformHandle,
+                    gfx::BufferFormat,
+                    gfx::BufferUsage,
+                    bool));
+  MOCK_METHOD1(ReleaseSysmemBufferCollection,
+               void(const base::UnguessableToken&));
+#endif  // defined(OS_FUCHSIA)
 };
 
 }  // namespace gpu
diff --git a/gpu/ipc/service/gpu_channel.cc b/gpu/ipc/service/gpu_channel.cc
index 11a57530..dbbd718b 100644
--- a/gpu/ipc/service/gpu_channel.cc
+++ b/gpu/ipc/service/gpu_channel.cc
@@ -170,6 +170,37 @@
       int32_t start,
       int32_t end,
       WaitForGetOffsetInRangeCallback callback) override;
+#if defined(OS_FUCHSIA)
+  void RegisterSysmemBufferCollection(const base::UnguessableToken& id,
+                                      mojo::PlatformHandle token,
+                                      gfx::BufferFormat format,
+                                      gfx::BufferUsage usage,
+                                      bool register_with_image_pipe) override {
+    base::AutoLock lock(gpu_channel_lock_);
+    if (!gpu_channel_)
+      return;
+
+    scheduler_->ScheduleTask(Scheduler::Task(
+        gpu_channel_->shared_image_stub()->sequence(),
+        base::BindOnce(&gpu::GpuChannel::RegisterSysmemBufferCollection,
+                       gpu_channel_->AsWeakPtr(), id, std::move(token), format,
+                       usage, register_with_image_pipe),
+        std::vector<SyncToken>()));
+  }
+
+  void ReleaseSysmemBufferCollection(
+      const base::UnguessableToken& id) override {
+    base::AutoLock lock(gpu_channel_lock_);
+    if (!gpu_channel_)
+      return;
+
+    scheduler_->ScheduleTask(Scheduler::Task(
+        gpu_channel_->shared_image_stub()->sequence(),
+        base::BindOnce(&gpu::GpuChannel::ReleaseSysmemBufferCollection,
+                       gpu_channel_->AsWeakPtr(), id),
+        std::vector<SyncToken>()));
+  }
+#endif  // defined(OS_FUCHSIA)
 
   IPC::Channel* ipc_channel_ = nullptr;
   base::ProcessId peer_pid_ = base::kNullProcessId;
@@ -785,7 +816,6 @@
     return false;
 
   filter_->AddRoute(shared_image_route_id, shared_image_stub_->sequence());
-  router_.AddRoute(shared_image_route_id, shared_image_stub_.get());
   return true;
 }
 
@@ -999,6 +1029,24 @@
 #endif
 }
 
+#if defined(OS_FUCHSIA)
+void GpuChannel::RegisterSysmemBufferCollection(
+    const base::UnguessableToken& id,
+    mojo::PlatformHandle token,
+    gfx::BufferFormat format,
+    gfx::BufferUsage usage,
+    bool register_with_image_pipe) {
+  shared_image_stub_->RegisterSysmemBufferCollection(
+      id, zx::channel(token.TakeHandle()), format, usage,
+      register_with_image_pipe);
+}
+
+void GpuChannel::ReleaseSysmemBufferCollection(
+    const base::UnguessableToken& id) {
+  shared_image_stub_->ReleaseSysmemBufferCollection(id);
+}
+#endif  // defined(OS_FUCHSIA)
+
 void GpuChannel::CacheShader(const std::string& key,
                              const std::string& shader) {
   gpu_channel_manager_->delegate()->StoreShaderToDisk(client_id_, key, shader);
diff --git a/gpu/ipc/service/gpu_channel.h b/gpu/ipc/service/gpu_channel.h
index 002b64c7..2adf69e 100644
--- a/gpu/ipc/service/gpu_channel.h
+++ b/gpu/ipc/service/gpu_channel.h
@@ -198,6 +198,15 @@
   void DestroyCommandBuffer(int32_t routing_id);
   bool CreateStreamTexture(int32_t stream_id);
 
+#if defined(OS_FUCHSIA)
+  void RegisterSysmemBufferCollection(const base::UnguessableToken& id,
+                                      mojo::PlatformHandle token,
+                                      gfx::BufferFormat format,
+                                      gfx::BufferUsage usage,
+                                      bool register_with_image_pipe);
+  void ReleaseSysmemBufferCollection(const base::UnguessableToken& id);
+#endif  // defined(OS_FUCHSIA)
+
  private:
   // Takes ownership of the renderer process handle.
   GpuChannel(GpuChannelManager* gpu_channel_manager,
diff --git a/gpu/ipc/service/shared_image_stub.cc b/gpu/ipc/service/shared_image_stub.cc
index 0ebfe75..a26e8d8 100644
--- a/gpu/ipc/service/shared_image_stub.cc
+++ b/gpu/ipc/service/shared_image_stub.cc
@@ -86,6 +86,10 @@
           std::move(request->get_create_shared_image_with_data()));
       break;
 
+    case mojom::DeferredSharedImageRequest::Tag::kCreateGmbSharedImage:
+      OnCreateGMBSharedImage(std::move(request->get_create_gmb_shared_image()));
+      break;
+
 #if defined(OS_ANDROID)
     case mojom::DeferredSharedImageRequest::Tag::kCreateSharedImageWithAhb: {
       auto& create_request = *request->get_create_shared_image_with_ahb();
@@ -96,6 +100,11 @@
     }
 #endif  // defined(OS_ANDROID)
 
+    case mojom::DeferredSharedImageRequest::Tag::kRegisterUploadBuffer:
+      OnRegisterSharedImageUploadBuffer(
+          std::move(request->get_register_upload_buffer()));
+      break;
+
     case mojom::DeferredSharedImageRequest::Tag::kUpdateSharedImage: {
       auto& update = *request->get_update_shared_image();
       OnUpdateSharedImage(update.mailbox, update.release_id,
@@ -120,24 +129,6 @@
   }
 }
 
-bool SharedImageStub::OnMessageReceived(const IPC::Message& msg) {
-  bool handled = true;
-  IPC_BEGIN_MESSAGE_MAP(SharedImageStub, msg)
-    IPC_MESSAGE_HANDLER(GpuChannelMsg_CreateGMBSharedImage,
-                        OnCreateGMBSharedImage)
-    IPC_MESSAGE_HANDLER(GpuChannelMsg_RegisterSharedImageUploadBuffer,
-                        OnRegisterSharedImageUploadBuffer)
-#if defined(OS_FUCHSIA)
-    IPC_MESSAGE_HANDLER(GpuChannelMsg_RegisterSysmemBufferCollection,
-                        OnRegisterSysmemBufferCollection)
-    IPC_MESSAGE_HANDLER(GpuChannelMsg_ReleaseSysmemBufferCollection,
-                        OnReleaseSysmemBufferCollection)
-#endif  // OS_FUCHSIA
-    IPC_MESSAGE_UNHANDLED(handled = false)
-  IPC_END_MESSAGE_MAP()
-  return handled;
-}
-
 bool SharedImageStub::CreateSharedImage(const Mailbox& mailbox,
                                         int client_id,
                                         gfx::GpuMemoryBufferHandle handle,
@@ -307,23 +298,23 @@
 }
 
 void SharedImageStub::OnCreateGMBSharedImage(
-    GpuChannelMsg_CreateGMBSharedImage_Params params) {
+    mojom::CreateGMBSharedImageParamsPtr params) {
   TRACE_EVENT2("gpu", "SharedImageStub::OnCreateGMBSharedImage", "width",
-               params.size.width(), "height", params.size.height());
+               params->size.width(), "height", params->size.height());
   // TODO(piman): add support for SurfaceHandle (for backbuffers for ozone/drm).
   constexpr SurfaceHandle surface_handle = kNullSurfaceHandle;
-  if (!CreateSharedImage(params.mailbox, channel_->client_id(),
-                         std::move(params.handle), params.format, params.plane,
-                         surface_handle, params.size, params.color_space,
-                         params.surface_origin, params.alpha_type,
-                         params.usage)) {
+  if (!CreateSharedImage(params->mailbox, channel_->client_id(),
+                         std::move(params->buffer_handle), params->format,
+                         params->plane, surface_handle, params->size,
+                         params->color_space, params->surface_origin,
+                         params->alpha_type, params->usage)) {
     return;
   }
 
   SyncToken sync_token(sync_point_client_state_->namespace_id(),
                        sync_point_client_state_->command_buffer_id(),
-                       params.release_id);
-  sync_point_client_state_->ReleaseFenceSync(params.release_id);
+                       params->release_id);
+  sync_point_client_state_->ReleaseFenceSync(params->release_id);
 }
 
 void SharedImageStub::OnUpdateSharedImage(const Mailbox& mailbox,
@@ -435,7 +426,7 @@
 #endif  // OS_WIN
 
 #if defined(OS_FUCHSIA)
-void SharedImageStub::OnRegisterSysmemBufferCollection(
+void SharedImageStub::RegisterSysmemBufferCollection(
     gfx::SysmemBufferCollectionId id,
     zx::channel token,
     gfx::BufferFormat format,
@@ -452,7 +443,7 @@
   }
 }
 
-void SharedImageStub::OnReleaseSysmemBufferCollection(
+void SharedImageStub::ReleaseSysmemBufferCollection(
     gfx::SysmemBufferCollectionId id) {
   if (!factory_->ReleaseSysmemBufferCollection(id)) {
     DLOG(ERROR) << "SharedImageStub: Trying to release unknown "
diff --git a/gpu/ipc/service/shared_image_stub.h b/gpu/ipc/service/shared_image_stub.h
index f307e2e..93415fd 100644
--- a/gpu/ipc/service/shared_image_stub.h
+++ b/gpu/ipc/service/shared_image_stub.h
@@ -25,8 +25,7 @@
 class SharedImageFactory;
 
 class GPU_IPC_SERVICE_EXPORT SharedImageStub
-    : public IPC::Listener,
-      public MemoryTracker,
+    : public MemoryTracker,
       public base::trace_event::MemoryDumpProvider {
  public:
   ~SharedImageStub() override;
@@ -40,9 +39,6 @@
   // Executes a DeferredRequest routed to this stub by a GpuChannel.
   void ExecuteDeferredRequest(mojom::DeferredSharedImageRequestPtr request);
 
-  // IPC::Listener implementation:
-  bool OnMessageReceived(const IPC::Message& msg) override;
-
   // MemoryTracker implementation:
   void TrackMemoryAllocatedChange(int64_t delta) override;
   uint64_t GetSize() const override;
@@ -82,13 +78,22 @@
   bool UpdateSharedImage(const Mailbox& mailbox,
                          gfx::GpuFenceHandle in_fence_handle);
 
+#if defined(OS_FUCHSIA)
+  void RegisterSysmemBufferCollection(gfx::SysmemBufferCollectionId id,
+                                      zx::channel token,
+                                      gfx::BufferFormat format,
+                                      gfx::BufferUsage usage,
+                                      bool register_with_image_pipe);
+  void ReleaseSysmemBufferCollection(gfx::SysmemBufferCollectionId id);
+#endif  // OS_FUCHSIA
+
  private:
   SharedImageStub(GpuChannel* channel, int32_t route_id);
 
   void OnCreateSharedImage(mojom::CreateSharedImageParamsPtr params);
   void OnCreateSharedImageWithData(
       mojom::CreateSharedImageWithDataParamsPtr params);
-  void OnCreateGMBSharedImage(GpuChannelMsg_CreateGMBSharedImage_Params params);
+  void OnCreateGMBSharedImage(mojom::CreateGMBSharedImageParamsPtr params);
   void OnUpdateSharedImage(const Mailbox& mailbox,
                            uint32_t release_id,
                            gfx::GpuFenceHandle in_fence_handle);
@@ -104,14 +109,6 @@
   void OnCreateSwapChain(mojom::CreateSwapChainParamsPtr params);
   void OnPresentSwapChain(const Mailbox& mailbox, uint32_t release_id);
 #endif  // OS_WIN
-#if defined(OS_FUCHSIA)
-  void OnRegisterSysmemBufferCollection(gfx::SysmemBufferCollectionId id,
-                                        zx::channel token,
-                                        gfx::BufferFormat format,
-                                        gfx::BufferUsage usage,
-                                        bool register_with_image_pipe);
-  void OnReleaseSysmemBufferCollection(gfx::SysmemBufferCollectionId id);
-#endif  // OS_FUCHSIA
 
   bool MakeContextCurrent(bool needs_gl = false);
   ContextResult MakeContextCurrentAndCreateFactory();
@@ -127,7 +124,7 @@
   // TODO(jonross): Look into a rename of CommandBufferId to reflect that it can
   // be a unique identifier for numerous gpu constructs.
   CommandBufferId command_buffer_id_;
-  SequenceId sequence_;
+  const SequenceId sequence_;
   scoped_refptr<gpu::SyncPointClientState> sync_point_client_state_;
   scoped_refptr<SharedContextState> context_state_;
   std::unique_ptr<SharedImageFactory> factory_;
diff --git a/infra/config/generated/commit-queue.cfg b/infra/config/generated/commit-queue.cfg
index ad94ee8..2afd42e 100644
--- a/infra/config/generated/commit-queue.cfg
+++ b/infra/config/generated/commit-queue.cfg
@@ -182,6 +182,14 @@
         includable_only: true
       }
       builders {
+        name: "chromium/try/android-angle-chromium-try"
+        includable_only: true
+      }
+      builders {
+        name: "chromium/try/android-angle-try"
+        includable_only: true
+      }
+      builders {
         name: "chromium/try/android-asan"
         includable_only: true
       }
diff --git a/infra/config/generated/cr-buildbucket.cfg b/infra/config/generated/cr-buildbucket.cfg
index bb8f3721..14cbe88ee 100644
--- a/infra/config/generated/cr-buildbucket.cfg
+++ b/infra/config/generated/cr-buildbucket.cfg
@@ -22119,7 +22119,7 @@
         properties_j: "$recipe_engine/isolated:{\"server\":\"https://isolateserver.appspot.com\"}"
         properties_j: "$recipe_engine/resultdb/test_presentation:{\"column_keys\":[],\"grouping_keys\":[\"status\",\"v.test_suite\"]}"
         properties_j: "builder_group:\"chromium.packager\""
-        properties_j: "packages:[{\"cipd_yaml\":\"third_party/android_sdk/cipd/build-tools/25.0.2.yaml\",\"sdk_package_name\":\"build-tools;25.0.2\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/build-tools/29.0.2.yaml\",\"sdk_package_name\":\"build-tools;29.0.2\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/build-tools/30.0.1.yaml\",\"sdk_package_name\":\"build-tools;30.0.1\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/cmdline-tools.yaml\",\"sdk_package_name\":\"cmdline-tools;latest\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/emulator.yaml\",\"sdk_package_name\":\"emulator\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/patcher/v4.yaml\",\"sdk_package_name\":\"patcher;v4\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/platforms/android-29.yaml\",\"sdk_package_name\":\"platforms;android-29\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/platforms/android-30.yaml\",\"sdk_package_name\":\"platforms;android-30\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/platform-tools.yaml\",\"sdk_package_name\":\"platform-tools\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/sources/android-29.yaml\",\"sdk_package_name\":\"sources;android-29\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/system_images/android-27/google_apis/x86.yaml\",\"sdk_package_name\":\"system-images;android-27;google_apis;x86\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/system_images/android-27/google_apis_playstore/x86.yaml\",\"sdk_package_name\":\"system-images;android-27;google_apis_playstore;x86\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/system_images/android-29/google_apis/x86.yaml\",\"sdk_package_name\":\"system-images;android-29;google_apis;x86\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/system_images/android-29/google_apis_playstore/x86.yaml\",\"sdk_package_name\":\"system-images;android-29;google_apis_playstore;x86\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/system_images/android-30/google_apis/x86.yaml\",\"sdk_package_name\":\"system-images;android-30;google_apis;x86\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/system_images/android-30/google_apis_playstore/x86.yaml\",\"sdk_package_name\":\"system-images;android-30;google_apis_playstore;x86\"}]"
+        properties_j: "packages:[{\"cipd_yaml\":\"third_party/android_sdk/cipd/build-tools/25.0.2.yaml\",\"sdk_package_name\":\"build-tools;25.0.2\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/build-tools/29.0.2.yaml\",\"sdk_package_name\":\"build-tools;29.0.2\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/build-tools/30.0.1.yaml\",\"sdk_package_name\":\"build-tools;30.0.1\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/cmdline-tools.yaml\",\"sdk_package_name\":\"cmdline-tools;latest\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/emulator.yaml\",\"sdk_package_name\":\"emulator\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/patcher/v4.yaml\",\"sdk_package_name\":\"patcher;v4\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/platforms/android-29.yaml\",\"sdk_package_name\":\"platforms;android-29\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/platforms/android-30.yaml\",\"sdk_package_name\":\"platforms;android-30\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/platform-tools.yaml\",\"sdk_package_name\":\"platform-tools\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/sources/android-29.yaml\",\"sdk_package_name\":\"sources;android-29\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/system_images/android-23/google_apis/x86.yaml\",\"sdk_package_name\":\"system-images;android-23;google_apis;x86\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/system_images/android-27/google_apis/x86.yaml\",\"sdk_package_name\":\"system-images;android-27;google_apis;x86\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/system_images/android-27/google_apis_playstore/x86.yaml\",\"sdk_package_name\":\"system-images;android-27;google_apis_playstore;x86\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/system_images/android-28/google_apis/x86.yaml\",\"sdk_package_name\":\"system-images;android-28;google_apis;x86\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/system_images/android-28/google_apis_playstore/x86.yaml\",\"sdk_package_name\":\"system-images;android-28;google_apis_playstore;x86\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/system_images/android-29/google_apis/x86.yaml\",\"sdk_package_name\":\"system-images;android-29;google_apis;x86\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/system_images/android-29/google_apis_playstore/x86.yaml\",\"sdk_package_name\":\"system-images;android-29;google_apis_playstore;x86\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/system_images/android-30/google_apis/x86.yaml\",\"sdk_package_name\":\"system-images;android-30;google_apis;x86\"},{\"cipd_yaml\":\"third_party/android_sdk/cipd/system_images/android-30/google_apis_playstore/x86.yaml\",\"sdk_package_name\":\"system-images;android-30;google_apis_playstore;x86\"}]"
       }
       execution_timeout_secs: 10800
       build_numbers: YES
@@ -37275,6 +37275,158 @@
       }
     }
     builders {
+      name: "android-angle-chromium-try"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:android-angle-chromium-try"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-18.04"
+      dimensions: "pool:luci.chromium.try"
+      recipe {
+        name: "angle_chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\",\"use_luci_auth\":true}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "$recipe_engine/isolated:{\"server\":\"https://isolateserver.appspot.com\"}"
+        properties_j: "$recipe_engine/resultdb/test_presentation:{\"column_keys\":[],\"grouping_keys\":[\"status\",\"v.test_suite\"]}"
+        properties_j: "builder_group:\"tryserver.chromium.angle\""
+      }
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      grace_period {
+        seconds: 120
+      }
+      caches {
+        name: "win_toolchain"
+        path: "win_toolchain"
+      }
+      build_numbers: YES
+      service_account: "chromium-try-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage {
+        value: 5
+      }
+      experiments {
+        key: "chromium.resultdb.result_sink"
+        value: 100
+      }
+      experiments {
+        key: "chromium.resultdb.result_sink.junit_tests"
+        value: 100
+      }
+      experiments {
+        key: "luci.buildbucket.use_bbagent"
+        value: 10
+      }
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "luci-resultdb"
+          dataset: "chromium"
+          table: "try_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "luci-resultdb"
+          dataset: "chromium"
+          table: "gpu_try_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://(chrome/test:|content/test:fuchsia_)telemetry_gpu_integration_test/.+"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+    }
+    builders {
+      name: "android-angle-try"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:android-angle-try"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-18.04"
+      dimensions: "pool:luci.chromium.try"
+      recipe {
+        name: "angle_chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\",\"use_luci_auth\":true}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "$recipe_engine/isolated:{\"server\":\"https://isolateserver.appspot.com\"}"
+        properties_j: "$recipe_engine/resultdb/test_presentation:{\"column_keys\":[],\"grouping_keys\":[\"status\",\"v.test_suite\"]}"
+        properties_j: "builder_group:\"tryserver.chromium.angle\""
+      }
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      grace_period {
+        seconds: 120
+      }
+      caches {
+        name: "win_toolchain"
+        path: "win_toolchain"
+      }
+      build_numbers: YES
+      service_account: "chromium-try-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage {
+        value: 5
+      }
+      experiments {
+        key: "chromium.resultdb.result_sink"
+        value: 100
+      }
+      experiments {
+        key: "chromium.resultdb.result_sink.junit_tests"
+        value: 100
+      }
+      experiments {
+        key: "luci.buildbucket.use_bbagent"
+        value: 10
+      }
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "luci-resultdb"
+          dataset: "chromium"
+          table: "try_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "luci-resultdb"
+          dataset: "chromium"
+          table: "gpu_try_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://(chrome/test:|content/test:fuchsia_)telemetry_gpu_integration_test/.+"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+    }
+    builders {
       name: "android-asan"
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
diff --git a/infra/config/generated/luci-milo.cfg b/infra/config/generated/luci-milo.cfg
index dad89c3..b324e439 100644
--- a/infra/config/generated/luci-milo.cfg
+++ b/infra/config/generated/luci-milo.cfg
@@ -13178,6 +13178,12 @@
     name: "buildbucket/luci.chromium.try/android-11-x86-fyi-rel"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/android-angle-chromium-try"
+  }
+  builders {
+    name: "buildbucket/luci.chromium.try/android-angle-try"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/android-asan"
   }
   builders {
@@ -14393,6 +14399,12 @@
   id: "tryserver.chromium.angle"
   name: "tryserver.chromium.angle"
   builders {
+    name: "buildbucket/luci.chromium.try/android-angle-chromium-try"
+  }
+  builders {
+    name: "buildbucket/luci.chromium.try/android-angle-try"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/android_angle_deqp_rel_ng"
   }
   builders {
diff --git a/infra/config/subprojects/chromium/ci.star b/infra/config/subprojects/chromium/ci.star
index e41f969..993d303 100644
--- a/infra/config/subprojects/chromium/ci.star
+++ b/infra/config/subprojects/chromium/ci.star
@@ -2015,6 +2015,10 @@
             #    'cipd_yaml': 'third_party/android_sdk/cipd/sources/android-30.yaml'
             #},
             {
+                "sdk_package_name": "system-images;android-23;google_apis;x86",
+                "cipd_yaml": "third_party/android_sdk/cipd/system_images/android-23/google_apis/x86.yaml",
+            },
+            {
                 "sdk_package_name": "system-images;android-27;google_apis;x86",
                 "cipd_yaml": "third_party/android_sdk/cipd/system_images/android-27/google_apis/x86.yaml",
             },
@@ -2023,6 +2027,14 @@
                 "cipd_yaml": "third_party/android_sdk/cipd/system_images/android-27/google_apis_playstore/x86.yaml",
             },
             {
+                "sdk_package_name": "system-images;android-28;google_apis;x86",
+                "cipd_yaml": "third_party/android_sdk/cipd/system_images/android-28/google_apis/x86.yaml",
+            },
+            {
+                "sdk_package_name": "system-images;android-28;google_apis_playstore;x86",
+                "cipd_yaml": "third_party/android_sdk/cipd/system_images/android-28/google_apis_playstore/x86.yaml",
+            },
+            {
                 "sdk_package_name": "system-images;android-29;google_apis;x86",
                 "cipd_yaml": "third_party/android_sdk/cipd/system_images/android-29/google_apis/x86.yaml",
             },
diff --git a/infra/config/subprojects/chromium/try.star b/infra/config/subprojects/chromium/try.star
index 1b57f11..53e02bb8 100644
--- a/infra/config/subprojects/chromium/try.star
+++ b/infra/config/subprojects/chromium/try.star
@@ -669,6 +669,18 @@
 )
 
 try_.chromium_angle_builder(
+    name = "android-angle-chromium-try",
+    os = os.LINUX_BIONIC_REMOVE,
+    executable = "recipe:angle_chromium_trybot",
+)
+
+try_.chromium_angle_builder(
+    name = "android-angle-try",
+    os = os.LINUX_BIONIC_REMOVE,
+    executable = "recipe:angle_chromium_trybot",
+)
+
+try_.chromium_angle_builder(
     name = "android_angle_deqp_rel_ng",
     os = os.LINUX_BIONIC_REMOVE,
 )
diff --git a/media/gpu/vaapi/BUILD.gn b/media/gpu/vaapi/BUILD.gn
index 019eec23..53b3df7 100644
--- a/media/gpu/vaapi/BUILD.gn
+++ b/media/gpu/vaapi/BUILD.gn
@@ -65,10 +65,10 @@
     "vaapi_video_encoder_delegate.h",
     "vaapi_webp_decoder.cc",
     "vaapi_webp_decoder.h",
-    "vp8_encoder.cc",
-    "vp8_encoder.h",
     "vp8_vaapi_video_decoder_delegate.cc",
     "vp8_vaapi_video_decoder_delegate.h",
+    "vp8_vaapi_video_encoder_delegate.cc",
+    "vp8_vaapi_video_encoder_delegate.h",
     "vp9_encoder.cc",
     "vp9_encoder.h",
     "vp9_rate_control.cc",
diff --git a/media/gpu/vaapi/vaapi_video_encode_accelerator.cc b/media/gpu/vaapi/vaapi_video_encode_accelerator.cc
index 9c3658e..fb90404 100644
--- a/media/gpu/vaapi/vaapi_video_encode_accelerator.cc
+++ b/media/gpu/vaapi/vaapi_video_encode_accelerator.cc
@@ -40,7 +40,7 @@
 #include "media/gpu/vaapi/vaapi_common.h"
 #include "media/gpu/vaapi/vaapi_utils.h"
 #include "media/gpu/vaapi/vaapi_wrapper.h"
-#include "media/gpu/vaapi/vp8_encoder.h"
+#include "media/gpu/vaapi/vp8_vaapi_video_encoder_delegate.h"
 #include "media/gpu/vaapi/vp9_encoder.h"
 #include "media/gpu/vaapi/vp9_temporal_layers.h"
 #include "media/gpu/vp8_reference_frame_vector.h"
@@ -289,7 +289,8 @@
       break;
     case kCodecVP8:
       if (!IsConfiguredForTesting())
-        encoder_ = std::make_unique<VP8Encoder>(vaapi_wrapper_, error_cb);
+        encoder_ = std::make_unique<VP8VaapiVideoEncoderDelegate>(
+            vaapi_wrapper_, error_cb);
 
       DCHECK_EQ(ave_config.bitrate_control,
                 VaapiVideoEncoderDelegate::BitrateControl::kConstantBitrate);
diff --git a/media/gpu/vaapi/vp8_encoder.cc b/media/gpu/vaapi/vp8_vaapi_video_encoder_delegate.cc
similarity index 92%
rename from media/gpu/vaapi/vp8_encoder.cc
rename to media/gpu/vaapi/vp8_vaapi_video_encoder_delegate.cc
index b69c40c..86d0e5b 100644
--- a/media/gpu/vaapi/vp8_encoder.cc
+++ b/media/gpu/vaapi/vp8_vaapi_video_encoder_delegate.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 "media/gpu/vaapi/vp8_encoder.h"
+#include "media/gpu/vaapi/vp8_vaapi_video_encoder_delegate.h"
 
 #include <va/va.h>
 #include <va/va_enc_vp8.h>
@@ -66,7 +66,7 @@
 
 }  // namespace
 
-VP8Encoder::EncodeParams::EncodeParams()
+VP8VaapiVideoEncoderDelegate::EncodeParams::EncodeParams()
     : kf_period_frames(kKFPeriod),
       framerate(0),
       cpb_window_size_ms(kCPBWindowSizeMs),
@@ -76,7 +76,7 @@
       max_qp(kMaxQP),
       error_resilient_mode(false) {}
 
-void VP8Encoder::Reset() {
+void VP8VaapiVideoEncoderDelegate::Reset() {
   current_params_ = EncodeParams();
   reference_frames_.Clear();
   frame_num_ = 0;
@@ -84,15 +84,16 @@
   InitializeFrameHeader();
 }
 
-VP8Encoder::VP8Encoder(const scoped_refptr<VaapiWrapper>& vaapi_wrapper,
-                       base::RepeatingClosure error_cb)
+VP8VaapiVideoEncoderDelegate::VP8VaapiVideoEncoderDelegate(
+    const scoped_refptr<VaapiWrapper>& vaapi_wrapper,
+    base::RepeatingClosure error_cb)
     : VaapiVideoEncoderDelegate(vaapi_wrapper, error_cb) {}
 
-VP8Encoder::~VP8Encoder() {
-  // VP8Encoder can be destroyed on any thread.
+VP8VaapiVideoEncoderDelegate::~VP8VaapiVideoEncoderDelegate() {
+  // VP8VaapiVideoEncoderDelegate can be destroyed on any thread.
 }
 
-bool VP8Encoder::Initialize(
+bool VP8VaapiVideoEncoderDelegate::Initialize(
     const VideoEncodeAccelerator::Config& config,
     const VaapiVideoEncoderDelegate::Config& ave_config) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -120,20 +121,20 @@
                          VideoEncodeAccelerator::kDefaultFramerate));
 }
 
-gfx::Size VP8Encoder::GetCodedSize() const {
+gfx::Size VP8VaapiVideoEncoderDelegate::GetCodedSize() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!coded_size_.IsEmpty());
 
   return coded_size_;
 }
 
-size_t VP8Encoder::GetMaxNumOfRefFrames() const {
+size_t VP8VaapiVideoEncoderDelegate::GetMaxNumOfRefFrames() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   return kNumVp8ReferenceBuffers;
 }
 
-bool VP8Encoder::PrepareEncodeJob(EncodeJob* encode_job) {
+bool VP8VaapiVideoEncoderDelegate::PrepareEncodeJob(EncodeJob* encode_job) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (encode_job->IsKeyframeRequested())
@@ -171,8 +172,9 @@
   return true;
 }
 
-bool VP8Encoder::UpdateRates(const VideoBitrateAllocation& bitrate_allocation,
-                             uint32_t framerate) {
+bool VP8VaapiVideoEncoderDelegate::UpdateRates(
+    const VideoBitrateAllocation& bitrate_allocation,
+    uint32_t framerate) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (bitrate_allocation.GetSumBps() == 0 || framerate == 0)
@@ -195,7 +197,7 @@
   return true;
 }
 
-void VP8Encoder::InitializeFrameHeader() {
+void VP8VaapiVideoEncoderDelegate::InitializeFrameHeader() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   current_frame_hdr_ = {};
@@ -218,7 +220,7 @@
   current_frame_hdr_.mb_no_skip_coeff = 1;
 }
 
-void VP8Encoder::UpdateFrameHeader(bool keyframe) {
+void VP8VaapiVideoEncoderDelegate::UpdateFrameHeader(bool keyframe) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (keyframe) {
@@ -243,13 +245,14 @@
   }
 }
 
-void VP8Encoder::UpdateReferenceFrames(scoped_refptr<VP8Picture> picture) {
+void VP8VaapiVideoEncoderDelegate::UpdateReferenceFrames(
+    scoped_refptr<VP8Picture> picture) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   reference_frames_.Refresh(picture);
 }
 
-scoped_refptr<VP8Picture> VP8Encoder::GetPicture(
+scoped_refptr<VP8Picture> VP8VaapiVideoEncoderDelegate::GetPicture(
     VaapiVideoEncoderDelegate::EncodeJob* job) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
@@ -257,7 +260,7 @@
       reinterpret_cast<VP8Picture*>(job->picture().get()));
 }
 
-bool VP8Encoder::SubmitFrameParameters(
+bool VP8VaapiVideoEncoderDelegate::SubmitFrameParameters(
     VaapiVideoEncoderDelegate::EncodeJob* job,
     const EncodeParams& encode_params,
     scoped_refptr<VP8Picture> pic,
diff --git a/media/gpu/vaapi/vp8_encoder.h b/media/gpu/vaapi/vp8_vaapi_video_encoder_delegate.h
similarity index 82%
rename from media/gpu/vaapi/vp8_encoder.h
rename to media/gpu/vaapi/vp8_vaapi_video_encoder_delegate.h
index fec53d6a..c40b87d3 100644
--- a/media/gpu/vaapi/vp8_encoder.h
+++ b/media/gpu/vaapi/vp8_vaapi_video_encoder_delegate.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 MEDIA_GPU_VAAPI_VP8_ENCODER_H_
-#define MEDIA_GPU_VAAPI_VP8_ENCODER_H_
+#ifndef MEDIA_GPU_VAAPI_VP8_VAAPI_VIDEO_ENCODER_DELEGATE_H_
+#define MEDIA_GPU_VAAPI_VP8_VAAPI_VIDEO_ENCODER_DELEGATE_H_
 
 #include <list>
 #include <vector>
@@ -18,7 +18,7 @@
 namespace media {
 class VaapiWrapper;
 
-class VP8Encoder : public VaapiVideoEncoderDelegate {
+class VP8VaapiVideoEncoderDelegate : public VaapiVideoEncoderDelegate {
  public:
   struct EncodeParams {
     EncodeParams();
@@ -47,9 +47,9 @@
     bool error_resilient_mode;
   };
 
-  VP8Encoder(const scoped_refptr<VaapiWrapper>& vaapi_wrapper,
-             base::RepeatingClosure error_cb);
-  ~VP8Encoder() override;
+  VP8VaapiVideoEncoderDelegate(const scoped_refptr<VaapiWrapper>& vaapi_wrapper,
+                               base::RepeatingClosure error_cb);
+  ~VP8VaapiVideoEncoderDelegate() override;
 
   // VaapiVideoEncoderDelegate implementation.
   bool Initialize(const VideoEncodeAccelerator::Config& config,
@@ -86,9 +86,9 @@
   Vp8FrameHeader current_frame_hdr_;
   Vp8ReferenceFrameVector reference_frames_;
 
-  DISALLOW_COPY_AND_ASSIGN(VP8Encoder);
+  DISALLOW_COPY_AND_ASSIGN(VP8VaapiVideoEncoderDelegate);
 };
 
 }  // namespace media
 
-#endif  // MEDIA_GPU_VAAPI_VP8_ENCODER_H_
+#endif  // MEDIA_GPU_VAAPI_VP8_VAAPI_VIDEO_ENCODER_DELEGATE_H_
diff --git a/media/gpu/windows/d3d11_texture_selector.cc b/media/gpu/windows/d3d11_texture_selector.cc
index 8cab420f..ea2abf94 100644
--- a/media/gpu/windows/d3d11_texture_selector.cc
+++ b/media/gpu/windows/d3d11_texture_selector.cc
@@ -56,12 +56,14 @@
       !SupportsZeroCopy(gpu_preferences, workarounds) ||
       base::FeatureList::IsEnabled(kD3D11VideoDecoderAlwaysCopy);
 
-#define SUPPORTS(fmt) format_checker->CheckOutputFormatSupport(fmt)
+  auto supports_fmt = [format_checker](auto fmt) {
+    return format_checker->CheckOutputFormatSupport(fmt);
+  };
   // TODO(liberato): add other options here, like "copy to rgb" for NV12.
   switch (decoder_output_format) {
     case DXGI_FORMAT_NV12: {
       MEDIA_LOG(INFO, media_log) << "D3D11VideoDecoder producing NV12";
-      if (!needs_texture_copy || SUPPORTS(DXGI_FORMAT_NV12)) {
+      if (!needs_texture_copy || supports_fmt(DXGI_FORMAT_NV12)) {
         output_pixel_format = PIXEL_FORMAT_NV12;
         output_dxgi_format = DXGI_FORMAT_NV12;
         // Leave |output_color_space| the same, since we'll bind either the
@@ -69,7 +71,7 @@
         // shaders or in the overlay, if needed.
         output_color_space.reset();
         MEDIA_LOG(INFO, media_log) << "D3D11VideoDecoder: Selected NV12";
-      } else if (SUPPORTS(DXGI_FORMAT_B8G8R8A8_UNORM)) {
+      } else if (supports_fmt(DXGI_FORMAT_B8G8R8A8_UNORM)) {
         output_pixel_format = PIXEL_FORMAT_ARGB;
         output_dxgi_format = DXGI_FORMAT_B8G8R8A8_UNORM;
         output_color_space.reset();
@@ -83,22 +85,22 @@
     case DXGI_FORMAT_P010: {
       MEDIA_LOG(INFO, media_log) << "D3D11VideoDecoder producing P010";
       if (hdr_output_mode == HDRMode::kSDROnly &&
-          SUPPORTS(DXGI_FORMAT_B8G8R8A8_UNORM)) {
+          supports_fmt(DXGI_FORMAT_B8G8R8A8_UNORM)) {
         output_dxgi_format = DXGI_FORMAT_B8G8R8A8_UNORM;
         output_pixel_format = PIXEL_FORMAT_ARGB;
         output_color_space = gfx::ColorSpace::CreateSRGB();
         MEDIA_LOG(INFO, media_log) << "D3D11VideoDecoder: Selected ARGB";
-      } else if (!needs_texture_copy || SUPPORTS(DXGI_FORMAT_P010)) {
+      } else if (!needs_texture_copy || supports_fmt(DXGI_FORMAT_P010)) {
         output_dxgi_format = DXGI_FORMAT_P010;
         output_pixel_format = PIXEL_FORMAT_P016LE;
         output_color_space.reset();
         MEDIA_LOG(INFO, media_log) << "D3D11VideoDecoder: Selected P010";
-      } else if (SUPPORTS(DXGI_FORMAT_R16G16B16A16_FLOAT)) {
+      } else if (supports_fmt(DXGI_FORMAT_R16G16B16A16_FLOAT)) {
         output_dxgi_format = DXGI_FORMAT_R16G16B16A16_FLOAT;
         output_pixel_format = PIXEL_FORMAT_RGBAF16;
         output_color_space = gfx::ColorSpace::CreateSCRGBLinear();
         MEDIA_LOG(INFO, media_log) << "D3D11VideoDecoder: Selected RGBAF16";
-      } else if (SUPPORTS(DXGI_FORMAT_R10G10B10A2_UNORM)) {
+      } else if (supports_fmt(DXGI_FORMAT_R10G10B10A2_UNORM)) {
         output_dxgi_format = DXGI_FORMAT_R10G10B10A2_UNORM;
         output_pixel_format = PIXEL_FORMAT_XB30;
         output_color_space = gfx::ColorSpace::CreateHDR10();
@@ -116,7 +118,6 @@
       return nullptr;
     }
   }
-#undef SUPPORTS
 
   // If we're trying to produce an output texture that's different from what
   // the decoder is providing, then we need to copy it. If sharing decoder
diff --git a/pdf/out_of_process_instance.cc b/pdf/out_of_process_instance.cc
index 82d84153..735631e 100644
--- a/pdf/out_of_process_instance.cc
+++ b/pdf/out_of_process_instance.cc
@@ -546,7 +546,7 @@
   if (!stream_url)
     stream_url = original_url;
 
-  InitializeEngine(script_option);
+  InitializeEngine(std::make_unique<PDFiumEngine>(this, script_option));
 
   // If we're in print preview mode we don't need to load the document yet.
   // A `kJSResetPrintPreviewModeType` message will be sent to the plugin letting
@@ -1033,7 +1033,8 @@
   set_document_load_state(DocumentLoadState::kLoading);
   LoadUrl(GetURL(), /*is_print_preview=*/false);
   preview_engine_.reset();
-  InitializeEngine(PDFiumFormFiller::ScriptOption::kNoJavaScript);
+  InitializeEngine(std::make_unique<PDFiumEngine>(
+      this, PDFiumFormFiller::ScriptOption::kNoJavaScript));
   engine()->SetGrayscale(dict.Get(pp::Var(kJSPrintPreviewGrayscale)).AsBool());
   engine()->New(GetURL().c_str(), /*headers=*/nullptr);
 
diff --git a/pdf/pdf_view_plugin_base.cc b/pdf/pdf_view_plugin_base.cc
index 9f5add27..47fb1111 100644
--- a/pdf/pdf_view_plugin_base.cc
+++ b/pdf/pdf_view_plugin_base.cc
@@ -518,9 +518,9 @@
   engine_->HandleAccessibilityAction(action_data);
 }
 
-void PdfViewPluginBase::InitializeEngine(
-    PDFiumFormFiller::ScriptOption script_option) {
-  engine_ = std::make_unique<PDFiumEngine>(this, script_option);
+void PdfViewPluginBase::InitializeEngine(std::unique_ptr<PDFiumEngine> engine) {
+  DCHECK(engine);
+  engine_ = std::move(engine);
 }
 
 void PdfViewPluginBase::DestroyEngine() {
diff --git a/pdf/pdf_view_plugin_base.h b/pdf/pdf_view_plugin_base.h
index 98175ae..1d0dbc1 100644
--- a/pdf/pdf_view_plugin_base.h
+++ b/pdf/pdf_view_plugin_base.h
@@ -132,8 +132,9 @@
   PdfViewPluginBase();
   ~PdfViewPluginBase() override;
 
-  // Initializes the main `PDFiumEngine`. Any existing engine will be replaced.
-  void InitializeEngine(PDFiumFormFiller::ScriptOption script_option);
+  // Initializes the main `PDFiumEngine` with `engine`. Any existing engine will
+  // be replaced. `engine` must not be nullptr.
+  void InitializeEngine(std::unique_ptr<PDFiumEngine> engine);
 
   // Destroys the main `PDFiumEngine`. Subclasses should call this method in
   // their destructor to ensure the engine is destroyed first.
diff --git a/pdf/pdf_view_web_plugin.cc b/pdf/pdf_view_web_plugin.cc
index fb38605..5ba0d395 100644
--- a/pdf/pdf_view_web_plugin.cc
+++ b/pdf/pdf_view_web_plugin.cc
@@ -220,7 +220,8 @@
     stream_url = original_url;
 
   PerProcessInitializer::GetInstance().Acquire();
-  InitializeEngine(PDFiumFormFiller::ScriptOption::kNoJavaScript);
+  InitializeEngine(std::make_unique<PDFiumEngine>(
+      this, PDFiumFormFiller::ScriptOption::kNoJavaScript));
   LoadUrl(stream_url, /*is_print_preview=*/false);
   set_url(original_url);
   post_message_sender_.set_container(Container());
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index bd2bdac3..217553c 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -4689,7 +4689,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M91",
-              "revision": "version:91.0.4472.101"
+              "revision": "version:91.0.4472.102"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -4768,7 +4768,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.47"
+              "revision": "version:92.0.4515.48"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -4926,7 +4926,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M91",
-              "revision": "version:91.0.4472.101"
+              "revision": "version:91.0.4472.102"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -5005,7 +5005,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.47"
+              "revision": "version:92.0.4515.48"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index 3d9ef00..acfbf8d 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -53852,7 +53852,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M91",
-              "revision": "version:91.0.4472.101"
+              "revision": "version:91.0.4472.102"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -53932,7 +53932,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.47"
+              "revision": "version:92.0.4515.48"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -54092,7 +54092,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M91",
-              "revision": "version:91.0.4472.101"
+              "revision": "version:91.0.4472.102"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -54172,7 +54172,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.47"
+              "revision": "version:92.0.4515.48"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -54397,7 +54397,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M91",
-              "revision": "version:91.0.4472.101"
+              "revision": "version:91.0.4472.102"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -54476,7 +54476,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.47"
+              "revision": "version:92.0.4515.48"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -54634,7 +54634,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M91",
-              "revision": "version:91.0.4472.101"
+              "revision": "version:91.0.4472.102"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -54713,7 +54713,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.47"
+              "revision": "version:92.0.4515.48"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -54938,7 +54938,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M91",
-              "revision": "version:91.0.4472.101"
+              "revision": "version:91.0.4472.102"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -55017,7 +55017,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.47"
+              "revision": "version:92.0.4515.48"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -55175,7 +55175,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M91",
-              "revision": "version:91.0.4472.101"
+              "revision": "version:91.0.4472.102"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -55254,7 +55254,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.47"
+              "revision": "version:92.0.4515.48"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index 33d532e7..4f6cb436 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -5169,7 +5169,6 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform:blink_platform_unittests/"
       },
       {
-        "ci_only": true,
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index be39ed4..7e5f86d1 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -3235,10 +3235,6 @@
           'shards': 2,
         },
       },
-      # TODO(crbug.com/1217434) Test failure on CQ.
-      'linux-lacros-tester-rel': {
-        'ci_only': True,
-      },
     },
   },
   'weblayer_browsertests': {
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index bb5a762..4e8bc857 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -472,7 +472,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M92',
-          'revision': 'version:92.0.4515.47',
+          'revision': 'version:92.0.4515.48',
         }
       ],
     },
@@ -496,7 +496,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M91',
-          'revision': 'version:91.0.4472.101',
+          'revision': 'version:91.0.4472.102',
         }
       ],
     },
@@ -544,7 +544,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M92',
-          'revision': 'version:92.0.4515.47',
+          'revision': 'version:92.0.4515.48',
         }
       ],
     },
@@ -568,7 +568,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M91',
-          'revision': 'version:91.0.4472.101',
+          'revision': 'version:91.0.4472.102',
         }
       ],
     },
@@ -616,7 +616,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M92',
-          'revision': 'version:92.0.4515.47',
+          'revision': 'version:92.0.4515.48',
         }
       ],
     },
@@ -640,7 +640,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M91',
-          'revision': 'version:91.0.4472.101',
+          'revision': 'version:91.0.4472.102',
         }
       ],
     },
diff --git a/testing/merge_scripts/code_coverage/OWNERS b/testing/merge_scripts/code_coverage/OWNERS
index 6078887..1ec09db 100644
--- a/testing/merge_scripts/code_coverage/OWNERS
+++ b/testing/merge_scripts/code_coverage/OWNERS
@@ -1,4 +1,4 @@
 pasthana@google.com
 zhaoyangli@chromium.org
 liaoyuke@chromium.org
-per-file merge_js_lib.py,merge_js_lib_test.py,convert_to_istanbul.js=benreich@chromium.org
+per-file merge_js_lib.py,merge_js_lib_test.py,convert_to_istanbul.js,convert_to_istanbul_test.py=benreich@chromium.org
diff --git a/testing/merge_scripts/code_coverage/convert_to_istanbul.js b/testing/merge_scripts/code_coverage/convert_to_istanbul.js
index 94442f9..fcce5d6 100644
--- a/testing/merge_scripts/code_coverage/convert_to_istanbul.js
+++ b/testing/merge_scripts/code_coverage/convert_to_istanbul.js
@@ -36,6 +36,8 @@
     const contents = await readFile(filePath, 'utf-8');
 
     const {result: scriptCoverages} = JSON.parse(contents);
+    if (!scriptCoverages)
+      throw new Error(`result key missing for file: ${filePath}`);
 
     for (const coverage of scriptCoverages) {
       // URLs with a "// #sourceURL=..." comment are rewritten by DevTools and
@@ -82,13 +84,15 @@
 /**
  * The entry point to the function to enable the async functionality throughout.
  * @param {Object} args The parsed CLI arguments.
+ * @return {!Promise<string>} Directory containing istanbul reports.
  */
 async function main(args) {
-  for (const coverageDir of args.raw_coverage_dirs) {
-    const outputDir = join(args.output_dir, 'istanbul')
-        await mkdir(outputDir, {recursive: true});
+  const outputDir = join(args.output_dir, 'istanbul')
+  await mkdir(outputDir, {recursive: true});
+  for (const coverageDir of args.raw_coverage_dirs)
     await extractCoverage(coverageDir, args.source_dir, outputDir);
-  }
+
+  return outputDir;
 }
 
 const parser = new ArgumentParser({
@@ -115,8 +119,8 @@
 
 const args = parser.parseArgs();
 main(args)
-    .then(() => {
-      console.log('Successfully converted from v8 to IstanbulJS');
+    .then(outputDir => {
+      console.log(`Successfully converted from v8 to IstanbulJS: ${outputDir}`);
     })
     .catch(error => {
       console.error(error);
diff --git a/testing/merge_scripts/code_coverage/convert_to_istanbul_test.py b/testing/merge_scripts/code_coverage/convert_to_istanbul_test.py
new file mode 100755
index 0000000..31706c2d
--- /dev/null
+++ b/testing/merge_scripts/code_coverage/convert_to_istanbul_test.py
@@ -0,0 +1,379 @@
+#!/usr/bin/env vpython
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import mock
+import os
+import shutil
+import tempfile
+import unittest
+
+import merge_js_lib as merger
+
+
+class ConvertToIstanbulTest(unittest.TestCase):
+  _TEST_SOURCE_A = """function add(a, b) {
+  return a + b;
+}
+
+function subtract(a, b) {
+  return a - b;
+}
+
+subtract(5, 2);
+"""
+
+  _TEST_COVERAGE_A = """{
+  "result": [
+    {
+      "scriptId":"72",
+      "url":"//file.js",
+      "functions":[
+        {
+          "functionName":"",
+          "ranges":[
+            {"startOffset":0,"endOffset":101,"count":1}
+          ],
+          "isBlockCoverage":true
+        },
+        {
+          "functionName":"add",
+          "ranges":[
+            {"startOffset":0,"endOffset":38,"count":0}
+          ],
+          "isBlockCoverage":false
+        },
+        {
+          "functionName":"subtract",
+          "ranges":[
+            {"startOffset":40,"endOffset":83,"count":1}
+          ],
+          "isBlockCoverage":true
+        }
+      ]
+    }
+  ]
+}
+"""
+
+  _TEST_COVERAGE_INVALID = """{
+  "scriptId":"72",
+  "url":"//file.js",
+  "functions":[
+    {
+      "functionName":"",
+      "ranges":[
+        {"startOffset":0,"endOffset":101,"count":1}
+      ],
+      "isBlockCoverage":true
+    },
+    {
+      "functionName":"add",
+      "ranges":[
+        {"startOffset":0,"endOffset":38,"count":0}
+      ],
+      "isBlockCoverage":false
+    },
+    {
+      "functionName":"subtract",
+      "ranges":[
+        {"startOffset":40,"endOffset":83,"count":1}
+      ],
+      "isBlockCoverage":true
+    }
+  ]
+}
+"""
+
+  _TEST_SOURCE_B = """const {subtract} = require('./test1.js');
+
+function add(a, b) {
+  return a + b;
+}
+
+subtract(5, 2);
+
+"""
+
+  _TEST_SOURCE_C = """exports.subtract = function(a, b) {
+  return a - b;
+}
+"""
+
+  _TEST_COVERAGE_B = """{
+  "result":[
+    {
+      "scriptId":"72",
+      "url":"//test.js",
+      "functions":[
+        {
+          "functionName":"",
+          "ranges":[
+            {"startOffset":0,"endOffset":99,"count":1}
+          ],
+          "isBlockCoverage":true
+        },
+        {
+          "functionName":"add",
+          "ranges":[
+            {"startOffset":43,"endOffset":81,"count":0}
+          ],
+          "isBlockCoverage":false
+        }
+      ]
+    },
+    {
+      "scriptId":"73",
+      "url":"//test1.js",
+      "functions":[
+        {
+          "functionName":"",
+          "ranges":[
+            {"startOffset":0,"endOffset":54,"count":1}
+          ],
+          "isBlockCoverage":true
+        },
+        {
+          "functionName":"exports.subtract",
+          "ranges":[
+            {"startOffset":19,"endOffset":53,"count":1}
+          ],
+          "isBlockCoverage":true
+        }
+      ]
+    }
+  ]
+}
+"""
+
+  _TEST_COVERAGE_NO_LEADING_SLASH = """{
+  "result":[
+    {
+      "scriptId":"72",
+      "url":"file:///usr/local/google/home/benreich/v8-to-istanbul/test.js",
+      "functions":[
+        {
+          "functionName":"",
+          "ranges":[
+            {"startOffset":0,"endOffset":99,"count":1}
+          ],
+          "isBlockCoverage":true
+        },
+        {
+          "functionName":"add",
+          "ranges":[
+            {"startOffset":43,"endOffset":81,"count":0}
+          ],
+          "isBlockCoverage":false
+        }
+      ]
+    },
+    {
+      "scriptId":"73",
+      "url":"//test1.js",
+      "functions":[
+        {
+          "functionName":"",
+          "ranges":[
+            {"startOffset":0,"endOffset":54,"count":1}
+          ],
+          "isBlockCoverage":true
+        },
+        {
+          "functionName":"exports.subtract",
+          "ranges":[
+            {"startOffset":19,"endOffset":53,"count":1}
+          ],
+          "isBlockCoverage":true
+        }
+      ]
+    }
+  ]
+}
+"""
+
+  _TEST_COVERAGE_DUPLICATE_SINGLE = """{
+  "result":[
+    {
+      "scriptId":"73",
+      "url":"//test1.js",
+      "functions":[
+        {
+          "functionName":"",
+          "ranges":[
+            {"startOffset":0,"endOffset":54,"count":1}
+          ],
+          "isBlockCoverage":true
+        },
+        {
+          "functionName":"exports.subtract",
+          "ranges":[
+            {"startOffset":19,"endOffset":53,"count":1}
+          ],
+          "isBlockCoverage":true
+        }
+      ]
+    }
+  ]
+}
+"""
+
+  _TEST_COVERAGE_DUPLICATE_DOUBLE = """{
+  "result":[
+    {
+      "scriptId":"72",
+      "url":"//test.js",
+      "functions":[
+        {
+          "functionName":"",
+          "ranges":[
+            {"startOffset":0,"endOffset":99,"count":1}
+          ],
+          "isBlockCoverage":true
+        },
+        {
+          "functionName":"add",
+          "ranges":[
+            {"startOffset":43,"endOffset":81,"count":0}
+          ],
+          "isBlockCoverage":false
+        }
+      ]
+    },
+    {
+      "scriptId":"73",
+      "url":"//test1.js",
+      "functions":[
+        {
+          "functionName":"",
+          "ranges":[
+            {"startOffset":0,"endOffset":54,"count":1}
+          ],
+          "isBlockCoverage":true
+        },
+        {
+          "functionName":"exports.subtract",
+          "ranges":[
+            {"startOffset":19,"endOffset":53,"count":1}
+          ],
+          "isBlockCoverage":true
+        }
+      ]
+    }
+  ]
+}
+"""
+
+  def setUp(self):
+    self.task_output_dir = tempfile.mkdtemp()
+    self.coverage_dir = os.path.join(self.task_output_dir, 'coverages')
+    self.source_dir = os.path.join(self.task_output_dir, 'source')
+
+    os.makedirs(self.coverage_dir)
+    os.makedirs(self.source_dir)
+
+  def tearDown(self):
+    shutil.rmtree(self.task_output_dir)
+
+  def list_files(self, absolute_path):
+    actual_files = []
+    for root, _, files in os.walk(absolute_path):
+      actual_files.extend([
+          os.path.join(root, file_name) for file_name in files
+      ])
+
+    return actual_files
+
+  def _write_files(self, root_dir, *file_path_contents):
+    for data in file_path_contents:
+      file_path, contents = data
+      with open(os.path.join(root_dir, file_path), 'w') as f:
+        f.write(contents)
+
+  def write_sources(self, *file_path_contents):
+    self._write_files(self.source_dir, *file_path_contents)
+
+  def write_coverages(self, *file_path_contents):
+    self._write_files(self.coverage_dir, *file_path_contents)
+
+  def test_happy_path(self):
+    self.write_sources(('file.js', self._TEST_SOURCE_A))
+    self.write_coverages(('test_coverage.cov.json', self._TEST_COVERAGE_A))
+
+    merger.convert_raw_coverage_to_istanbul(
+        [self.coverage_dir], self.source_dir, self.task_output_dir)
+
+    istanbul_files = self.list_files(
+        os.path.join(self.task_output_dir, 'istanbul'))
+    self.assertEqual(len(istanbul_files), 1)
+
+  def test_no_coverages_in_file(self):
+    coverage_file = """{
+  "result": []
+}
+"""
+
+    self.write_sources(('file.js', self._TEST_SOURCE_A))
+    self.write_coverages(('test_coverage.cov.json', coverage_file))
+
+    merger.convert_raw_coverage_to_istanbul(
+        [self.coverage_dir], self.source_dir, self.task_output_dir)
+
+    istanbul_files = self.list_files(
+        os.path.join(self.task_output_dir, 'istanbul'))
+    self.assertEqual(len(istanbul_files), 0)
+
+  def test_invalid_coverage_file(self):
+    self.write_sources(('file.js', self._TEST_SOURCE_A))
+    self.write_coverages(
+      ('test_coverage.cov.json', self._TEST_COVERAGE_INVALID))
+
+    self.assertRaises(merger.convert_raw_coverage_to_istanbul(
+        [self.coverage_dir], self.source_dir, self.task_output_dir))
+
+  def test_multiple_coverages_single_file(self):
+    self.write_sources(('test.js', self._TEST_SOURCE_B))
+    self.write_sources(('test1.js', self._TEST_SOURCE_C))
+    self.write_coverages(('test_coverage.cov.json', self._TEST_COVERAGE_B))
+
+    merger.convert_raw_coverage_to_istanbul(
+        [self.coverage_dir], self.source_dir, self.task_output_dir)
+
+    istanbul_files = self.list_files(
+        os.path.join(self.task_output_dir, 'istanbul'))
+    self.assertEqual(len(istanbul_files), 2)
+
+
+  def test_multiple_coverages_no_leading_double_slash(self):
+    self.write_sources(('test.js', self._TEST_SOURCE_B))
+    self.write_sources(('test1.js', self._TEST_SOURCE_C))
+    self.write_coverages(
+        ('test_coverage.cov.json', self._TEST_COVERAGE_NO_LEADING_SLASH))
+
+    merger.convert_raw_coverage_to_istanbul(
+        [self.coverage_dir], self.source_dir, self.task_output_dir)
+
+    istanbul_files = self.list_files(
+        os.path.join(self.task_output_dir, 'istanbul'))
+    self.assertEqual(len(istanbul_files), 1)
+
+
+  def test_multiple_duplicate_coverages_flattened(self):
+    self.write_sources(('test.js', self._TEST_SOURCE_B))
+    self.write_sources(('test1.js', self._TEST_SOURCE_C))
+    self.write_coverages(
+        ('test_coverage_1.cov.json', self._TEST_COVERAGE_B))
+    self.write_coverages(
+        ('test_coverage_2.cov.json', self._TEST_COVERAGE_DUPLICATE_DOUBLE))
+
+    merger.convert_raw_coverage_to_istanbul(
+        [self.coverage_dir], self.source_dir, self.task_output_dir)
+
+    istanbul_files = self.list_files(
+        os.path.join(self.task_output_dir, 'istanbul'))
+    self.assertEqual(len(istanbul_files), 2)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/testing/merge_scripts/code_coverage/merge_js_lib.py b/testing/merge_scripts/code_coverage/merge_js_lib.py
index 75421e1..9f3a35d 100644
--- a/testing/merge_scripts/code_coverage/merge_js_lib.py
+++ b/testing/merge_scripts/code_coverage/merge_js_lib.py
@@ -8,6 +8,12 @@
 import os
 import sys
 
+_HERE_PATH = os.path.dirname(__file__)
+_SRC_PATH = os.path.normpath(os.path.join(_HERE_PATH, '..', '..', '..'))
+
+sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'node'))
+import node
+
 logging.basicConfig(format='[%(asctime)s %(levelname)s] %(message)s',
                     level=logging.DEBUG)
 
@@ -265,18 +271,18 @@
 
   Args:
     task_output_dir (str): The output directory for the sharded task. This will
-        contain the raw JavaScript v8 coverage files that are identified by
+        contain the raw JavaScript v8 parsed files that are identified by
         their ".js.json" suffix.
 
   Returns:
-    The absolute file path to the raw parsed scripts.
+    The absolute file path to the raw parsed scripts or None if no parsed
+    scripts were identified.
   """
   scripts = _get_paths_with_suffix(task_output_dir, '.js.json')
   output_dir = os.path.join(task_output_dir, 'parsed_scripts')
 
   if not scripts:
-    logging.info('No raw scripts found in %s', task_output_dir)
-    return
+    return None
 
   for file_path in scripts:
     script_data = _parse_json_file(file_path)
@@ -296,5 +302,42 @@
     with open(os.path.join(output_dir, source_path), 'w') as f:
       f.write(script_data['text'].encode('utf8'))
 
-  logging.info('Raw parsed scripts written out to %s', output_dir)
   return output_dir
+
+
+def get_raw_coverage_dirs(task_output_dir):
+  """Returns a list of directories containing raw v8 coverage.
+
+  Args:
+    task_output_dir (str): The output directory for the sharded task. This will
+        contain the raw JavaScript v8 coverage files that are identified by
+        their ".cov.json" suffix.
+  """
+  coverage_directories = set()
+  for dir_path, _sub_dirs, file_names in os.walk(task_output_dir):
+    for name in file_names:
+      if name.endswith('.cov.json'):
+        coverage_directories.add(dir_path)
+        continue
+
+  return coverage_directories
+
+
+def convert_raw_coverage_to_istanbul(
+    raw_coverage_dirs, source_dir, task_output_dir):
+  """Calls the node helper script convert_to_istanbul.js
+
+  Args:
+    raw_coverage_dirs (list): Directory that contains raw v8 code coverage.
+    source_dir (str): Root directory containing the instrumented source.
+  """
+  try:
+    output = node.RunNode(
+        [os.path.join(_HERE_PATH, 'convert_to_istanbul.js'),
+            '--source-dir', source_dir,
+            '--output-dir', task_output_dir,
+            '--raw-coverage-dirs', ' '.join(raw_coverage_dirs),
+        ])
+    logging.info(output)
+  except RuntimeError as e:
+    logging.warn(e.message)
diff --git a/testing/merge_scripts/code_coverage/merge_results.py b/testing/merge_scripts/code_coverage/merge_results.py
index be1cb21..34c8fa0 100755
--- a/testing/merge_scripts/code_coverage/merge_results.py
+++ b/testing/merge_scripts/code_coverage/merge_results.py
@@ -101,8 +101,16 @@
       parser.error('--merged-js-cov-filename required when merging '
                    'JavaScript coverage')
 
-    javascript_merger.write_parsed_scripts(
-        os.path.join(params.task_output_dir))
+    parsed_scripts = javascript_merger.write_parsed_scripts(
+        params.task_output_dir)
+    if parsed_scripts:
+      logging.info('Raw parsed scripts written out to %s', parsed_scripts)
+      coverage_dirs = javascript_merger.get_raw_coverage_dirs(
+          params.task_output_dir)
+      logging.info(
+          'Identified directories containing coverage %s', coverage_dirs)
+      javascript_merger.convert_raw_coverage_to_istanbul(
+          coverage_dirs, parsed_scripts, params.task_output_dir)
 
     # Ensure JavaScript coverage dir exists.
     if not os.path.exists(params.javascript_coverage_dir):
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 01bb215..a596be1 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -4826,6 +4826,26 @@
             ]
         }
     ],
+    "MemoriesHistoryClusters": [
+        {
+            "platforms": [
+                "android",
+                "chromeos",
+                "chromeos_lacros",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "MemoriesPersistContextAnnotationsInHistoryDb"
+                    ]
+                }
+            ]
+        }
+    ],
     "MigrateDefaultChromeAppToWebAppsGSuite": [
         {
             "platforms": [
diff --git a/third_party/android_sdk/cipd/system_images/android-23/google_apis/x86.yaml b/third_party/android_sdk/cipd/system_images/android-23/google_apis/x86.yaml
new file mode 100644
index 0000000..b03e632
--- /dev/null
+++ b/third_party/android_sdk/cipd/system_images/android-23/google_apis/x86.yaml
@@ -0,0 +1,9 @@
+# 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.
+
+package: chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86
+description: system_images;android-23;google_apis;x86
+root: ../../../../public/
+data:
+  - dir: system-images/android-23/google_apis/x86
diff --git a/third_party/android_sdk/cipd/system_images/android-28/google_apis/x86.yaml b/third_party/android_sdk/cipd/system_images/android-28/google_apis/x86.yaml
new file mode 100644
index 0000000..fafba16
--- /dev/null
+++ b/third_party/android_sdk/cipd/system_images/android-28/google_apis/x86.yaml
@@ -0,0 +1,9 @@
+# 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.
+
+package: chromium/third_party/android_sdk/public/system-images/android-28/google_apis/x86
+description: system_images;android-28;google_apis;x86
+root: ../../../../public/
+data:
+  - dir: system-images/android-28/google_apis/x86
diff --git a/third_party/android_sdk/cipd/system_images/android-28/google_apis_playstore/x86.yaml b/third_party/android_sdk/cipd/system_images/android-28/google_apis_playstore/x86.yaml
new file mode 100644
index 0000000..7923ec5d
--- /dev/null
+++ b/third_party/android_sdk/cipd/system_images/android-28/google_apis_playstore/x86.yaml
@@ -0,0 +1,9 @@
+# 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.
+
+package: chromium/third_party/android_sdk/public/system-images/android-28/google_apis_playstore/x86
+description: system_images;android-28;google_apis_playstore;x86
+root: ../../../../public/
+data:
+  - dir: system-images/android-28/google_apis_playstore/x86
diff --git a/third_party/blink/public/mojom/prerender/prerender.mojom b/third_party/blink/public/mojom/prerender/prerender.mojom
index 125562e..3ad4ac25 100644
--- a/third_party/blink/public/mojom/prerender/prerender.mojom
+++ b/third_party/blink/public/mojom/prerender/prerender.mojom
@@ -41,22 +41,3 @@
   // etc. This must be called after Start().
   Cancel();
 };
-
-// Prerender2 (https://crbug.com/1132746):
-// This interface is used only when blink::features::kPrerender2 is enabled.
-//
-// This interface is used to request prerendering from a renderer process to the
-// browser process. This is created per prerender request, for example, when a
-// new <link rel=prerender> element is added, when the element's href is
-// changed, etc.
-interface PrerenderProcessor {
-  // Requests the browesr process to start prerendering with
-  // |prerender_attribute|. This must be called only one time before any other
-  // functions.
-  Start(PrerenderAttributes prerender_attribute);
-
-  // Cancels the ongoing prerendering. This is supposed to be called when the
-  // <link rel=prerender> element is removed, the element's href is changed,
-  // etc. This must be called after Start().
-  Cancel();
-};
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 73af2d49..6026eab 100644
--- a/third_party/blink/public/mojom/web_feature/web_feature.mojom
+++ b/third_party/blink/public/mojom/web_feature/web_feature.mojom
@@ -3245,6 +3245,7 @@
   kHTMLMediaElementControlsListNoPlaybackRate = 3930,
   kDocumentTransition = 3931,
   kSpeculationRules = 3932,
+  kV8AbortSignal_Abort_Method = 3933,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots.
diff --git a/third_party/blink/renderer/core/dom/abort_signal.cc b/third_party/blink/renderer/core/dom/abort_signal.cc
index 9796868..1cb50c2 100644
--- a/third_party/blink/renderer/core/dom/abort_signal.cc
+++ b/third_party/blink/renderer/core/dom/abort_signal.cc
@@ -11,6 +11,7 @@
 #include "third_party/blink/renderer/core/event_target_names.h"
 #include "third_party/blink/renderer/core/event_type_names.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
+#include "third_party/blink/renderer/platform/bindings/script_state.h"
 #include "third_party/blink/renderer/platform/heap/persistent.h"
 #include "third_party/blink/renderer/platform/heap/visitor.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
@@ -22,6 +23,14 @@
     : execution_context_(execution_context) {}
 AbortSignal::~AbortSignal() = default;
 
+// static
+AbortSignal* AbortSignal::abort(ScriptState* script_state) {
+  AbortSignal* signal =
+      MakeGarbageCollected<AbortSignal>(ExecutionContext::From(script_state));
+  signal->aborted_flag_ = true;
+  return signal;
+}
+
 const AtomicString& AbortSignal::InterfaceName() const {
   return event_target_names::kAbortSignal;
 }
diff --git a/third_party/blink/renderer/core/dom/abort_signal.h b/third_party/blink/renderer/core/dom/abort_signal.h
index 22efc1e..a9c77a34 100644
--- a/third_party/blink/renderer/core/dom/abort_signal.h
+++ b/third_party/blink/renderer/core/dom/abort_signal.h
@@ -15,6 +15,7 @@
 namespace blink {
 
 class ExecutionContext;
+class ScriptState;
 
 // Implementation of https://dom.spec.whatwg.org/#interface-AbortSignal
 class CORE_EXPORT AbortSignal : public EventTargetWithInlineData {
@@ -25,6 +26,7 @@
   ~AbortSignal() override;
 
   // abort_signal.idl
+  static AbortSignal* abort(ScriptState*);
   bool aborted() const { return aborted_flag_; }
   DEFINE_ATTRIBUTE_EVENT_LISTENER(abort, kAbort)
 
diff --git a/third_party/blink/renderer/core/dom/abort_signal.idl b/third_party/blink/renderer/core/dom/abort_signal.idl
index 41836a8..d2675b6 100644
--- a/third_party/blink/renderer/core/dom/abort_signal.idl
+++ b/third_party/blink/renderer/core/dom/abort_signal.idl
@@ -7,6 +7,12 @@
 [
     Exposed=(Window,Worker)
 ] interface AbortSignal : EventTarget {
+    [
+        CallWith=ScriptState,
+        Measure,
+        NewObject
+    ] static AbortSignal abort();
+
     readonly attribute boolean aborted;
 
     attribute EventHandler onabort;
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index e8d12d5..126079e 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -3745,7 +3745,7 @@
 
   GetFrame()->Loader().SaveScrollAnchor();
   if (auto* mf_checker = View()->GetMobileFriendlinessChecker())
-    mf_checker->NotifyDocumentUnload();
+    mf_checker->EvaluateNow();
 
   // TODO(crbug.com/1161996): Remove this VLOG once the investigation is done.
   VLOG(1) << "Actually dispatching an UnloadEvent: URL = " << Url();
diff --git a/third_party/blink/renderer/core/dom/node.cc b/third_party/blink/renderer/core/dom/node.cc
index 23ca697..a7821af 100644
--- a/third_party/blink/renderer/core/dom/node.cc
+++ b/third_party/blink/renderer/core/dom/node.cc
@@ -3086,8 +3086,15 @@
   // dirty. RecalcAssignment() is almost no-op if we don't need to recalc.
   root->GetSlotAssignment().RecalcAssignment();
   if (FlatTreeNodeData* data = GetFlatTreeNodeData()) {
-    DCHECK_EQ(root->AssignedSlotFor(*this), data->AssignedSlot())
-        << "Assigned slot mismatch for node " << this;
+#if DCHECK_IS_ON()
+    // User agent shadow slot assignment (FindSlotInUserAgentShadow()) will
+    // re-check the DOM tree, and if we're in the process of removing nodes
+    // from the tree, there could be a mismatch here.
+    if (root->SupportsNameBasedSlotAssignment()) {
+      DCHECK_EQ(root->AssignedSlotFor(*this), data->AssignedSlot())
+          << "Assigned slot mismatch for node " << this;
+    }
+#endif
     return data->AssignedSlot();
   }
   return nullptr;
diff --git a/third_party/blink/renderer/core/frame/local_frame_client_impl.cc b/third_party/blink/renderer/core/frame/local_frame_client_impl.cc
index a3f1507..9b510daf 100644
--- a/third_party/blink/renderer/core/frame/local_frame_client_impl.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_client_impl.cc
@@ -88,6 +88,7 @@
 #include "third_party/blink/renderer/core/loader/frame_load_request.h"
 #include "third_party/blink/renderer/core/loader/frame_loader.h"
 #include "third_party/blink/renderer/core/loader/history_item.h"
+#include "third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.h"
 #include "third_party/blink/renderer/core/origin_trials/origin_trial_context.h"
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/page/plugin_data.h"
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 1256882c..1e75446 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -270,9 +270,7 @@
       layout_shift_tracker_(MakeGarbageCollected<LayoutShiftTracker>(this)),
       paint_timing_detector_(MakeGarbageCollected<PaintTimingDetector>(this)),
       mobile_friendliness_checker_(
-          frame_->IsMainFrame()
-              ? MakeGarbageCollected<MobileFriendlinessChecker>(*this)
-              : nullptr)
+          MakeGarbageCollected<MobileFriendlinessChecker>(*this))
 #if DCHECK_IS_ON()
       ,
       is_updating_descendant_dependent_flags_(false),
diff --git a/third_party/blink/renderer/core/html/forms/text_field_input_type.cc b/third_party/blink/renderer/core/html/forms/text_field_input_type.cc
index bf44e5b..f217138 100644
--- a/third_party/blink/renderer/core/html/forms/text_field_input_type.cc
+++ b/third_party/blink/renderer/core/html/forms/text_field_input_type.cc
@@ -93,7 +93,9 @@
     SetShadowPseudoId(AtomicString("-webkit-calendar-picker-indicator"));
     setAttribute(html_names::kIdAttr, shadow_element_names::kIdPickerIndicator);
     setAttribute(html_names::kStyleAttr,
-                 "display:list-item; list-style:disclosure-open inside; "
+                 "display:list-item; "
+                 "list-style:disclosure-open inside; "
+                 "counter-increment: list-item 0;"
                  "block-size:1em;");
     // Do not expose list-item role.
     setAttribute(html_names::kAriaHiddenAttr, "true");
diff --git a/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers.cc b/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers.cc
index b7d3b59..23e0a28 100644
--- a/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers.cc
+++ b/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers.cc
@@ -1080,160 +1080,222 @@
   if (sections->IsEmpty())
     return;
   // Redistribute table block size over sections algorithm:
-  // 1. Compute section groups:
-  //   Group 0: sections with 0-block size
-  //   Group 1: sections with %-age block size not in Group 0
-  //   Group 2: unconstrained tbody sections not in Group 0
-  //   Group 3: all tbody sections not in Group 0
-  //   Group 4: all sections not in Group 0
+  // Compute section size guesses:
+  // min_guess_sum is sum of section sizes
+  // percentage_guess_sum is sum of kMinGuess + percentage guesses
+
+  // if table_block_size <= min_guess_sum, there is nothing to distribute.
+
+  // 1. if table_block_size > min_guess_sum distribute size to
+  //    percentage sections.
+  //    Sections grow in proportion to difference between their percentage
+  //    size and min size.
   //
-  // 2. Percentage redistribution:
-  //   Grow sections in group 1 up to their %ge block size
+  // 2. if table_block_size > percentage_guess_sum distribute size to
+  //    eligible sections.
+  //    Eligible sections:
+  //      if TBODY sections exist, only TBODY sections are eligible.
+  //      otherwise, all sections are eligible.
   //
-  // 3. Final redistribution
-  //   Pick first non-empty group between groups 4, 3, 2, and 0.
-  //   Grow sections in picked group.
-  //   Groups 4, 3, 2 grow proportiononaly to their block size.
-  //   Group 0 grows evenly.
+  //    - grow auto eligible sections in proportion to their size
+  //    - grow fixed eligible sections in proportion to their size
+  //    - grow percentage eligible sections in proportion to their size
+
   unsigned block_space_count = sections->size() + 1;
   LayoutUnit undistributable_space = block_space_count * border_block_spacing;
 
   LayoutUnit distributable_table_block_size =
       std::max(LayoutUnit(), table_block_size - undistributable_space);
-  bool has_growable_percent_sections = false;
-  LayoutUnit desired_percentage_block_size_deficit;
-  LayoutUnit total_group_block_sizes[5];
-  unsigned number_of_empty_groups = 0;
 
-  auto is_group_0 = [](auto& section) {
-    return section.block_size == LayoutUnit();
-  };
-  auto is_group_1 = [](auto& section) {
-    return section.percent.has_value() && section.percent != 0.0 &&
-           section.block_size != LayoutUnit();
-  };
-  auto is_group_2 = [](auto& section) {
-    return section.is_tbody && !section.is_constrained &&
-           section.block_size != LayoutUnit();
-  };
-  auto is_group_3 = [](auto& section) {
-    return section.is_tbody && section.block_size != LayoutUnit();
-  };
-  auto is_group_4 = [](auto& section) {
-    return section.block_size != LayoutUnit();
+  auto ComputePercentageSize = [&distributable_table_block_size](
+                                   auto& section) {
+    DCHECK(section.percent.has_value());
+    return std::max(
+        section.block_size,
+        LayoutUnit(*section.percent * distributable_table_block_size / 100));
   };
 
-  auto update_block_sizes = [&total_group_block_sizes, &number_of_empty_groups,
-                             &is_group_0, &is_group_2, &is_group_3,
-                             &is_group_4](auto& section) {
-    if (is_group_2(section))
-      total_group_block_sizes[2] += section.block_size;
-    if (is_group_3(section))
-      total_group_block_sizes[3] += section.block_size;
-    if (is_group_4(section))
-      total_group_block_sizes[4] += section.block_size;
-    if (is_group_0(section))
-      number_of_empty_groups++;
-  };
+  LayoutUnit auto_sections_size;
+  LayoutUnit fixed_sections_size;
+  LayoutUnit percent_sections_size;
+  LayoutUnit tbody_auto_sections_size;
+  LayoutUnit tbody_fixed_sections_size;
+  LayoutUnit tbody_percent_sections_size;
+  LayoutUnit minimum_size_guess;
+  LayoutUnit percent_size_guess;
 
-  for (NGTableTypes::Section& section : *sections) {
-    section.needs_redistribution = false;
-    update_block_sizes(section);
-    if (is_group_1(section)) {
-      has_growable_percent_sections = true;
-      desired_percentage_block_size_deficit +=
-          (LayoutUnit(*section.percent * distributable_table_block_size / 100) -
-           section.block_size)
-              .ClampNegativeToZero();
+  unsigned auto_sections_count = 0;
+  unsigned fixed_sections_count = 0;
+  unsigned percent_sections_count = 0;
+  unsigned tbody_auto_sections_count = 0;
+  unsigned tbody_fixed_sections_count = 0;
+  unsigned tbody_percent_sections_count = 0;
+
+  for (const NGTableTypes::Section& section : *sections) {
+    minimum_size_guess += section.block_size;
+    if (section.percent.has_value())
+      percent_size_guess += ComputePercentageSize(section);
+    else
+      percent_size_guess += section.block_size;
+
+    if (section.is_constrained) {
+      if (section.percent.has_value()) {
+        percent_sections_count++;
+        if (section.is_tbody)
+          tbody_percent_sections_count++;
+      } else {
+        fixed_sections_count++;
+        fixed_sections_size += section.block_size;
+        if (section.is_tbody) {
+          tbody_fixed_sections_size += section.block_size;
+          tbody_fixed_sections_count++;
+        }
+      }
+    } else {
+      auto_sections_count++;
+      auto_sections_size += section.block_size;
+      if (section.is_tbody) {
+        tbody_auto_sections_count++;
+        tbody_auto_sections_size += section.block_size;
+      }
     }
   }
-  LayoutUnit excess_block_size =
-      distributable_table_block_size - total_group_block_sizes[4];
-  if (excess_block_size <= LayoutUnit())
+
+  if (distributable_table_block_size <= minimum_size_guess)
     return;
 
-  // Step 1: Percentage redistribution: grow percentages to their maximum.
-  if (has_growable_percent_sections) {
-    // Because percentages will grow, need to recompute all the totals.
-    total_group_block_sizes[2] = LayoutUnit();
-    total_group_block_sizes[3] = LayoutUnit();
-    total_group_block_sizes[4] = LayoutUnit();
-    number_of_empty_groups = 0;
-    float ratio = std::min(
-        excess_block_size / desired_percentage_block_size_deficit.ToFloat(),
-        1.0f);
-    LayoutUnit remaining_deficit =
-        LayoutUnit(ratio * desired_percentage_block_size_deficit);
+  LayoutUnit current_sections_size = minimum_size_guess;
+
+  // Distribute to percent sections.
+  if (percent_sections_count > 0 && percent_size_guess > minimum_size_guess) {
+    LayoutUnit distributable_size =
+        std::min(percent_size_guess, distributable_table_block_size) -
+        minimum_size_guess;
+    DCHECK_GE(distributable_size, LayoutUnit());
+    LayoutUnit percent_minimum_difference =
+        percent_size_guess - minimum_size_guess;
+
+    LayoutUnit rounding_error_tally = distributable_size;
     NGTableTypes::Section* last_section = nullptr;
     for (NGTableTypes::Section& section : *sections) {
-      if (!is_group_1(section)) {
-        update_block_sizes(section);
+      if (!section.percent)
         continue;
-      }
-      LayoutUnit desired_block_size =
-          LayoutUnit(*section.percent * distributable_table_block_size / 100);
-      LayoutUnit block_size_deficit =
-          (desired_block_size - section.block_size).ClampNegativeToZero();
-      LayoutUnit grow_by = LayoutUnit(block_size_deficit * ratio);
-      if (grow_by != LayoutUnit()) {
-        section.block_size += grow_by;
-        section.needs_redistribution = true;
-        last_section = &section;
-        remaining_deficit -= grow_by;
-      }
-      update_block_sizes(section);
+      LayoutUnit delta = LayoutUnit(
+          distributable_size *
+          (ComputePercentageSize(section).ToFloat() - section.block_size) /
+          percent_minimum_difference);
+      section.block_size += delta;
+      section.needs_redistribution = true;
+      rounding_error_tally -= delta;
+      current_sections_size += delta;
+      last_section = &section;
+      percent_sections_size += section.block_size;
+      if (section.is_tbody)
+        tbody_percent_sections_size += section.block_size;
     }
-    if (last_section && remaining_deficit != LayoutUnit()) {
-      last_section->block_size += remaining_deficit;
-      if (is_group_4(*last_section))
-        total_group_block_sizes[4] += remaining_deficit;
-    }
-  }
-  excess_block_size =
-      distributable_table_block_size - total_group_block_sizes[4];
-
-  if (excess_block_size > LayoutUnit()) {
-    // Step 2: distribute remaining block sizes to group 0, 2, 3, or 4.
-    unsigned group_index;
-    if (total_group_block_sizes[2] > LayoutUnit())
-      group_index = 2;
-    else if (total_group_block_sizes[3] > LayoutUnit())
-      group_index = 3;
-    else if (total_group_block_sizes[4] > LayoutUnit())
-      group_index = 4;
-    else
-      group_index = 0;
-
-    LayoutUnit remaining_deficit = excess_block_size;
-    NGTableTypes::Section* last_section = nullptr;
-    for (NGTableTypes::Section& section : *sections) {
-      if (group_index == 2 && !is_group_2(section))
-        continue;
-      if (group_index == 3 && !is_group_3(section))
-        continue;
-      if (group_index == 4 && !is_group_4(section))
-        continue;
-      LayoutUnit grow_by;
-      if (group_index == 0) {
-        grow_by =
-            LayoutUnit(excess_block_size.ToFloat() / number_of_empty_groups);
-      } else {
-        grow_by = LayoutUnit(section.block_size.ToFloat() * excess_block_size /
-                             total_group_block_sizes[group_index]);
-      }
-      if (grow_by > LayoutUnit()) {
-        section.block_size += grow_by;
-        section.needs_redistribution = true;
-        remaining_deficit -= grow_by;
-        last_section = &section;
-      }
-    }
-    if (last_section && remaining_deficit != LayoutUnit()) {
-      last_section->block_size += remaining_deficit;
-    }
+    DCHECK_LT(rounding_error_tally,
+              LayoutUnit(1));  // DO NOT CHECK IN, cluster fuzz magnet
+    DCHECK(last_section);
+    last_section->block_size += rounding_error_tally;
+    percent_sections_size += rounding_error_tally;
+    current_sections_size += rounding_error_tally;
+    if (last_section->is_tbody)
+      percent_sections_size += rounding_error_tally;
   }
 
-  // Step 3: Propagate section expansion to rows.
+  // Distribute remaining sizes.
+  bool has_tbody = tbody_auto_sections_count > 0 ||
+                   tbody_fixed_sections_count > 0 ||
+                   tbody_percent_sections_count > 0;
+  LayoutUnit distributable_size =
+      distributable_table_block_size - current_sections_size;
+  if (distributable_size > LayoutUnit()) {
+    LayoutUnit rounding_error_tally = distributable_size;
+    if ((tbody_auto_sections_count > 0) ||
+        (!has_tbody && auto_sections_count > 0)) {
+      // Distribute to auto sections.
+      // Sections grow by ratio of their size / total auto sizes.
+      NGTableTypes::Section* last_section = nullptr;
+      LayoutUnit total_auto_size =
+          has_tbody ? tbody_auto_sections_size : auto_sections_size;
+      for (NGTableTypes::Section& section : *sections) {
+        if (section.is_constrained || (section.is_tbody != has_tbody))
+          continue;
+        LayoutUnit delta;
+        if (total_auto_size > LayoutUnit()) {
+          delta = LayoutUnit(distributable_size.ToFloat() * section.block_size /
+                             total_auto_size);
+        } else {
+          delta = LayoutUnit(
+              distributable_size.ToFloat() /
+              (has_tbody ? tbody_auto_sections_count : auto_sections_count));
+        }
+        section.block_size += delta;
+        section.needs_redistribution = true;
+        rounding_error_tally -= delta;
+        last_section = &section;
+      }
+      DCHECK(last_section);
+      last_section->block_size += rounding_error_tally;
+    } else if ((tbody_fixed_sections_count > 0) ||
+               (!has_tbody && fixed_sections_count > 0)) {
+      // Distribute to fixed sections.
+      // Sections grow  by ration of their size / total fixed sizes.
+      NGTableTypes::Section* last_section = nullptr;
+      LayoutUnit total_fixed_size =
+          has_tbody ? tbody_fixed_sections_size : fixed_sections_size;
+      for (NGTableTypes::Section& section : *sections) {
+        if (!section.is_constrained || section.percent.has_value())
+          continue;
+        if (section.is_tbody != has_tbody)
+          continue;
+        LayoutUnit delta;
+        if (total_fixed_size > LayoutUnit()) {
+          delta = LayoutUnit(distributable_size.ToFloat() * section.block_size /
+                             total_fixed_size);
+        } else {
+          delta = LayoutUnit(
+              distributable_size.ToFloat() /
+              (has_tbody ? tbody_fixed_sections_count : fixed_sections_count));
+        }
+        section.block_size += delta;
+        section.needs_redistribution = true;
+        rounding_error_tally -= delta;
+        last_section = &section;
+      }
+      DCHECK(last_section);
+      last_section->block_size += rounding_error_tally;
+    } else {
+      DCHECK((tbody_percent_sections_count > 0) ||
+             (!has_tbody && percent_sections_count > 0));
+      // Distribute to percentage sections.
+      NGTableTypes::Section* last_section = nullptr;
+      LayoutUnit total_percent_size =
+          has_tbody ? tbody_percent_sections_size : percent_sections_size;
+      for (NGTableTypes::Section& section : *sections) {
+        if (!section.percent.has_value())
+          continue;
+        if (section.is_tbody != has_tbody)
+          continue;
+        LayoutUnit delta;
+        if (total_percent_size > LayoutUnit()) {
+          delta = LayoutUnit(distributable_size.ToFloat() * section.block_size /
+                             total_percent_size);
+        } else {
+          delta = LayoutUnit(distributable_size.ToFloat() /
+                             (has_tbody ? tbody_percent_sections_count
+                                        : percent_sections_count));
+        }
+        section.block_size += delta;
+        section.needs_redistribution = true;
+        rounding_error_tally -= delta;
+        last_section = &section;
+      }
+      DCHECK(last_section);
+      last_section->block_size += rounding_error_tally;
+    }
+  }
+  // Propagate new section sizes to rows.
   for (NGTableTypes::Section& section : *sections) {
     if (!section.needs_redistribution)
       continue;
diff --git a/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers_test.cc b/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers_test.cc
index 0098ab6..5b58c98 100644
--- a/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers_test.cc
+++ b/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers_test.cc
@@ -304,7 +304,7 @@
   sections.push_back(MakeSection(&rows, 100));
   NGTableAlgorithmHelpers::DistributeTableBlockSizeToSections(
       LayoutUnit(), LayoutUnit(500), &sections, &rows);
-  EXPECT_EQ(sections[0].block_size, LayoutUnit(0));
+  EXPECT_EQ(sections[0].block_size, LayoutUnit(400));
 
   // Sections with % block size grow to percentage.
   sections.Shrink(0);
@@ -328,8 +328,8 @@
   // TODO(atotic) Is this what we want? FF/Edge/Legacy all disagree.
   NGTableAlgorithmHelpers::DistributeTableBlockSizeToSections(
       LayoutUnit(), LayoutUnit(1000), &sections, &rows);
-  EXPECT_EQ(sections[0].block_size, LayoutUnit(750));
-  EXPECT_EQ(sections[1].block_size, LayoutUnit(250));
+  EXPECT_EQ(sections[0].block_size, LayoutUnit(300));
+  EXPECT_EQ(sections[1].block_size, LayoutUnit(700));
 
   // If there is a constrained section, and an unconstrained section,
   // unconstrained section grows.
diff --git a/third_party/blink/renderer/core/layout/ng/table/ng_table_section_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/table/ng_table_section_layout_algorithm.cc
index b0d90a0..61d7a8f 100644
--- a/third_party/blink/renderer/core/layout/ng/table/ng_table_section_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/table/ng_table_section_layout_algorithm.cc
@@ -66,7 +66,14 @@
     is_first_row = false;
     row_index++;
   }
-  container_builder_.SetFragmentBlockSize(offset.block_offset);
+  if (table_data.sections[section_index].rowspan == 0) {
+    // Sections without rows can get redistributed height from table.
+    DCHECK(ConstraintSpace().IsFixedBlockSize());
+    container_builder_.SetFragmentBlockSize(
+        ConstraintSpace().AvailableSize().block_size);
+  } else {
+    container_builder_.SetFragmentBlockSize(offset.block_offset);
+  }
   if (section_baseline)
     container_builder_.SetBaseline(*section_baseline);
   container_builder_.SetIsTableNGPart();
diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_party/blink/renderer/core/loader/document_loader.cc
index 5fe4eab..b77ec2ea 100644
--- a/third_party/blink/renderer/core/loader/document_loader.cc
+++ b/third_party/blink/renderer/core/loader/document_loader.cc
@@ -84,6 +84,7 @@
 #include "third_party/blink/renderer/core/loader/preload_helper.h"
 #include "third_party/blink/renderer/core/loader/progress_tracker.h"
 #include "third_party/blink/renderer/core/loader/subresource_filter.h"
+#include "third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.h"
 #include "third_party/blink/renderer/core/origin_trials/origin_trial_context.h"
 #include "third_party/blink/renderer/core/page/frame_tree.h"
 #include "third_party/blink/renderer/core/page/page.h"
@@ -791,6 +792,9 @@
       commit_type == kWebHistoryInertCommit,
       FrameScheduler::NavigationType::kSameDocument);
 
+  if (auto* mf_checker = frame_->View()->GetMobileFriendlinessChecker())
+    mf_checker->EvaluateNow();
+
   GetLocalFrameClient().DidFinishSameDocumentNavigation(
       history_item_.Get(), commit_type, is_synchronously_committed,
       is_history_api_navigation, is_client_redirect_);
diff --git a/third_party/blink/renderer/core/loader/modulescript/module_script_loader_test.cc b/third_party/blink/renderer/core/loader/modulescript/module_script_loader_test.cc
index 933391b..f1d340ca 100644
--- a/third_party/blink/renderer/core/loader/modulescript/module_script_loader_test.cc
+++ b/third_party/blink/renderer/core/loader/modulescript/module_script_loader_test.cc
@@ -87,18 +87,6 @@
 
   ScriptState* GetScriptState() override { return script_state_; }
 
-  void SetModuleRequests(const Vector<String>& requests) {
-    requests_.clear();
-    for (const String& request : requests) {
-      requests_.emplace_back(request, TextPosition::MinimumPosition(),
-                             Vector<ImportAssertion>());
-    }
-  }
-  Vector<ModuleRequest> ModuleRequestsFromModuleRecord(
-      v8::Local<v8::Module>) override {
-    return requests_;
-  }
-
   ModuleScriptFetcher* CreateModuleScriptFetcher(
       ModuleScriptCustomFetchType custom_fetch_type,
       base::PassKey<ModuleScriptLoader> pass_key) override {
@@ -117,7 +105,6 @@
 
  private:
   Member<ScriptState> script_state_;
-  Vector<ModuleRequest> requests_;
 };
 
 void ModuleScriptLoaderTestModulator::Trace(Visitor* visitor) const {
@@ -424,7 +411,6 @@
     TestModuleScriptLoaderClient* client) {
   auto* registry = MakeGarbageCollected<ModuleScriptLoaderRegistry>();
   KURL url("data:text/javascript,import 'invalid';export default 'grapes';");
-  GetModulator()->SetModuleRequests({"invalid"});
   ModuleScriptLoader::Fetch(
       ModuleScriptFetchRequest::CreateForTest(url, ModuleType::kJavaScript),
       fetcher_, ModuleGraphLevel::kTopLevelModuleFetch, GetModulator(),
diff --git a/third_party/blink/renderer/core/loader/modulescript/module_tree_linker.cc b/third_party/blink/renderer/core/loader/modulescript/module_tree_linker.cc
index 024aa5c..acb12f3 100644
--- a/third_party/blink/renderer/core/loader/modulescript/module_tree_linker.cc
+++ b/third_party/blink/renderer/core/loader/modulescript/module_tree_linker.cc
@@ -369,14 +369,14 @@
 void ModuleTreeLinker::FetchDescendants(const ModuleScript* module_script) {
   DCHECK(module_script);
 
-  v8::Isolate* isolate = modulator_->GetScriptState()->GetIsolate();
-  v8::HandleScope scope(isolate);
   // [nospec] Abort the steps if the browsing context is discarded.
   if (!modulator_->HasValidContext()) {
     result_ = nullptr;
     AdvanceState(State::kFinished);
     return;
   }
+  ScriptState* script_state = modulator_->GetScriptState();
+  v8::HandleScope scope(script_state->GetIsolate());
 
   // <spec step="2">Let record be module script's record.</spec>
   v8::Local<v8::Module> record = module_script->V8Module();
@@ -409,7 +409,7 @@
   // <spec step="5">For each ModuleRequest Record requested of
   // record.[[RequestedModules]],</spec>
   Vector<ModuleRequest> record_requested_modules =
-      modulator_->ModuleRequestsFromModuleRecord(record);
+      ModuleRecord::ModuleRequests(script_state, record);
 
   for (const auto& requested : record_requested_modules) {
     // <spec step="5.1">Let url be the result of resolving a module specifier
@@ -606,7 +606,7 @@
   // <spec step="5.1">Let childSpecifiers be the value of moduleScript's
   // record's [[RequestedModules]] internal slot.</spec>
   Vector<ModuleRequest> child_specifiers =
-      modulator_->ModuleRequestsFromModuleRecord(record);
+      ModuleRecord::ModuleRequests(modulator_->GetScriptState(), record);
 
   for (const auto& module_request : child_specifiers) {
     // <spec step="5.2">Let childURLs be the list obtained by calling resolve a
diff --git a/third_party/blink/renderer/core/loader/modulescript/module_tree_linker_test.cc b/third_party/blink/renderer/core/loader/modulescript/module_tree_linker_test.cc
index b0c1f9a..eafd48a 100644
--- a/third_party/blink/renderer/core/loader/modulescript/module_tree_linker_test.cc
+++ b/third_party/blink/renderer/core/loader/modulescript/module_tree_linker_test.cc
@@ -162,12 +162,6 @@
     return ScriptValue();
   }
 
-  Vector<ModuleRequest> ModuleRequestsFromModuleRecord(
-      v8::Local<v8::Module> module_record) override {
-    ScriptState::Scope scope(script_state_);
-    return ModuleRecord::ModuleRequests(script_state_, module_record);
-  }
-
   Member<ScriptState> script_state_;
   HeapHashMap<KURL, Member<SingleModuleClient>> pending_clients_;
   HeapHashMap<KURL, Member<ModuleScript>> module_map_;
diff --git a/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.cc b/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.cc
index 654036b..dfb7473d 100644
--- a/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.cc
+++ b/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.cc
@@ -322,9 +322,10 @@
   }
 }
 
-void MobileFriendlinessChecker::NotifyDocumentUnload() {
+void MobileFriendlinessChecker::EvaluateNow() {
   // If detached, there's no need to calculate any metrics.
-  if (!frame_view_->GetChromeClient())
+  // Or if this is before FCP, there's nothing to evaluate.
+  if (!frame_view_->GetChromeClient() || !fcp_detected_)
     return;
 
   if (tap_target_check_enabled_)
@@ -340,8 +341,7 @@
   mobile_friendliness_.text_content_outside_viewport_percentage = std::max(
       0, mobile_friendliness_.text_content_outside_viewport_percentage);
 
-  if (fcp_detected_)
-    frame_view_->DidChangeMobileFriendliness(mobile_friendliness_);
+  frame_view_->DidChangeMobileFriendliness(mobile_friendliness_);
 }
 
 void MobileFriendlinessChecker::NotifyViewportUpdated(
@@ -431,9 +431,6 @@
 
 void MobileFriendlinessChecker::ComputeTextContentOutsideViewport(
     const LayoutObject& object) {
-  if (!frame_view_->GetFrame().IsMainFrame())
-    return;
-
   int frame_width = frame_view_->GetPage()->GetVisualViewport().Size().Width();
   if (frame_width == 0) {
     return;
diff --git a/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.h b/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.h
index 64749eeb..ad8d7b7 100644
--- a/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.h
+++ b/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.h
@@ -33,7 +33,7 @@
   const blink::MobileFriendliness& GetMobileFriendliness() const {
     return mobile_friendliness_;
   }
-  void NotifyDocumentUnload();
+  void EvaluateNow();
 
   void Trace(Visitor* visitor) const;
   struct TextAreaWithFontSize {
diff --git a/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker_test.cc b/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker_test.cc
index 4b824be8..dcb54aa 100644
--- a/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker_test.cc
+++ b/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker_test.cc
@@ -15,6 +15,7 @@
 
 namespace blink {
 
+using mobile_metrics_test_helpers::MobileFriendlinessTree;
 using mojom::ViewportStatus;
 
 static constexpr char kBaseUrl[] = "http://www.test.com/";
@@ -33,48 +34,37 @@
     settings->SetViewportMetaEnabled(true);
   }
 
-  MobileFriendliness CalculateMetricsForHTMLString(const std::string& html) {
-    mobile_metrics_test_helpers::TestWebFrameClient web_frame_client;
-    {
-      // This scope is required to make sure that ~WebViewHelper() is called
-      // before the return value of this function is determined. Because
-      // MobileFriendlinessChecker::NotifyDocumentDestroying is called in
-      // destruction sequence of WebView.
-      frame_test_helpers::WebViewHelper helper;
-      helper.Initialize(&web_frame_client, nullptr, ConfigureAndroidSettings);
-      helper.Resize(gfx::Size(480, 800));
-      frame_test_helpers::LoadHTMLString(
-          helper.GetWebView()->MainFrameImpl(), html,
-          url_test_helpers::ToKURL("about:blank"));
-      LocalFrameView& frame_view =
-          *helper.GetWebView()->MainFrameImpl()->GetFrameView();
-      frame_view.UpdateLifecycleToPrePaintClean(DocumentUpdateReason::kTest);
-      frame_view.GetMobileFriendlinessChecker()->NotifyFirstContentfulPaint();
-    }
-    return web_frame_client.GetMobileFriendliness();
+  MobileFriendlinessTree CalculateMetricsForHTMLString(
+      const std::string& html) {
+    frame_test_helpers::WebViewHelper helper;
+    helper.Initialize(nullptr, nullptr, ConfigureAndroidSettings);
+    helper.Resize(gfx::Size(480, 800));
+    frame_test_helpers::LoadHTMLString(helper.GetWebView()->MainFrameImpl(),
+                                       html,
+                                       url_test_helpers::ToKURL("about:blank"));
+    return MobileFriendlinessTree::GetMobileFriendlinessTree(
+        helper.GetWebView()->MainFrameImpl()->GetFrameView());
   }
 
-  MobileFriendliness CalculateMetricsForFile(const std::string& path) {
-    mobile_metrics_test_helpers::TestWebFrameClient web_frame_client;
-    {
-      // This scope is required to make sure that ~WebViewHelper() is called
-      // before the return value of this function is determined. Because
-      // MobileFriendlinessChecker::NotifyDocumentDestroying is called in
-      // destruction sequence of WebView.
-      frame_test_helpers::WebViewHelper helper;
-      helper.Initialize(&web_frame_client, nullptr, ConfigureAndroidSettings);
-      helper.Resize(gfx::Size(480, 800));
-      url_test_helpers::RegisterMockedURLLoadFromBase(
-          WebString::FromUTF8(kBaseUrl), blink::test::CoreTestDataPath(),
-          WebString::FromUTF8(path));
-      frame_test_helpers::LoadFrame(helper.GetWebView()->MainFrameImpl(),
-                                    kBaseUrl + path);
-      LocalFrameView& frame_view =
-          *helper.GetWebView()->MainFrameImpl()->GetFrameView();
-      frame_view.UpdateLifecycleToPrePaintClean(DocumentUpdateReason::kTest);
-      frame_view.GetMobileFriendlinessChecker()->NotifyFirstContentfulPaint();
-    }
-    return web_frame_client.GetMobileFriendliness();
+  MobileFriendlinessTree CalculateMetricsForFile(const std::string& path) {
+    frame_test_helpers::WebViewHelper helper;
+    helper.Initialize(nullptr, nullptr, ConfigureAndroidSettings);
+    helper.Resize(gfx::Size(480, 800));
+    url_test_helpers::RegisterMockedURLLoadFromBase(
+        WebString::FromUTF8(kBaseUrl), blink::test::CoreTestDataPath(),
+        WebString::FromUTF8(path));
+    frame_test_helpers::LoadFrame(helper.GetWebView()->MainFrameImpl(),
+                                  kBaseUrl + path);
+    return MobileFriendlinessTree::GetMobileFriendlinessTree(
+        helper.GetWebView()->MainFrameImpl()->GetFrameView());
+  }
+
+  MobileFriendliness CalculateMainFrameMetricsForHTMLString(
+      const std::string& html) {
+    return CalculateMetricsForHTMLString(html).mf;
+  }
+  MobileFriendliness CalculateMainFrameMetricsForFile(const std::string& path) {
+    return CalculateMetricsForFile(path).mf;
   }
 
  private:
@@ -83,7 +73,7 @@
 
 TEST_F(MobileFriendlinessCheckerTest, NoViewportSetting) {
   MobileFriendliness actual_mf =
-      CalculateMetricsForHTMLString("<body>bar</body>");
+      CalculateMainFrameMetricsForHTMLString("<body>bar</body>");
   EXPECT_EQ(actual_mf.viewport_device_width, mojom::ViewportStatus::kNo);
   EXPECT_EQ(actual_mf.allow_user_zoom, mojom::ViewportStatus::kYes);
   EXPECT_EQ(actual_mf.small_text_ratio, 100);
@@ -91,14 +81,14 @@
 
 TEST_F(MobileFriendlinessCheckerTest, DeviceWidth) {
   MobileFriendliness actual_mf =
-      CalculateMetricsForFile("viewport/viewport-1.html");
+      CalculateMainFrameMetricsForFile("viewport/viewport-1.html");
   EXPECT_EQ(actual_mf.viewport_device_width, mojom::ViewportStatus::kYes);
   EXPECT_EQ(actual_mf.allow_user_zoom, mojom::ViewportStatus::kYes);
 }
 
 TEST_F(MobileFriendlinessCheckerTest, HardcodedViewport) {
   MobileFriendliness actual_mf =
-      CalculateMetricsForFile("viewport/viewport-30.html");
+      CalculateMainFrameMetricsForFile("viewport/viewport-30.html");
   EXPECT_EQ(actual_mf.viewport_device_width, blink::mojom::ViewportStatus::kNo);
   EXPECT_EQ(actual_mf.allow_user_zoom, mojom::ViewportStatus::kYes);
   EXPECT_EQ(actual_mf.viewport_hardcoded_width, 200);
@@ -108,14 +98,14 @@
   // Specifying initial-scale=0.5 is usually not the best choice for most web
   // pages. But we cannot determine that such page must not be mobile friendly.
   MobileFriendliness actual_mf =
-      CalculateMetricsForFile("viewport/viewport-34.html");
+      CalculateMainFrameMetricsForFile("viewport/viewport-34.html");
   EXPECT_EQ(actual_mf.viewport_device_width, mojom::ViewportStatus::kYes);
   EXPECT_EQ(actual_mf.allow_user_zoom, mojom::ViewportStatus::kYes);
   EXPECT_EQ(actual_mf.viewport_initial_scale_x10, 5);
 }
 
 TEST_F(MobileFriendlinessCheckerTest, UserZoom) {
-  MobileFriendliness actual_mf = CalculateMetricsForFile(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForFile(
       "viewport-initial-scale-and-user-scalable-no.html");
   EXPECT_EQ(actual_mf.viewport_device_width, mojom::ViewportStatus::kYes);
   EXPECT_EQ(actual_mf.viewport_initial_scale_x10, 20);
@@ -125,7 +115,7 @@
 
 TEST_F(MobileFriendlinessCheckerTest, NoText) {
   MobileFriendliness actual_mf =
-      CalculateMetricsForHTMLString(R"(<body></body>)");
+      CalculateMainFrameMetricsForHTMLString(R"(<body></body>)");
   EXPECT_EQ(actual_mf.viewport_device_width, mojom::ViewportStatus::kNo);
   EXPECT_EQ(actual_mf.allow_user_zoom, mojom::ViewportStatus::kYes);
   EXPECT_EQ(actual_mf.small_text_ratio, 0);
@@ -133,7 +123,7 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, NoSmallFonts) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
 <div style="font-size: 12px">
   This is legible font size example.
 </div>
@@ -144,7 +134,7 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, OnlySmallFonts) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
 <div style="font-size:7px">
   Small font text.
 </div>
@@ -155,7 +145,7 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, MostlySmallFont) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
 <div style="font-size:12px">
   legible text.
   <div style="font-size:8px">
@@ -190,7 +180,7 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, MostlySmallInSpan) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
 <div style="font-size: 12px">
   x
   <span style="font-size:8px">
@@ -215,13 +205,14 @@
   </div>
   y
 </div>
-)");
+)")
+                                     .mf;
   EXPECT_LT(actual_mf.small_text_ratio, 100);
   EXPECT_GT(actual_mf.small_text_ratio, 68);
 }
 
 TEST_F(MobileFriendlinessCheckerTest, DontCountInvisibleSmallFontArea) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
 <html>
   <body>
     <div style="font-size: 12px">
@@ -239,7 +230,7 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, ScaleZoomedLegibleFont) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
 <html>
   <head>
     <meta name="viewport" content="width=device-width, initial-scale=10">
@@ -256,7 +247,7 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, ViewportZoomedOutIllegibleFont) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
 <html>
   <head>
     <meta name="viewport" content="width=480, initial-scale=0.5">
@@ -274,7 +265,7 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, TooWideViewportWidthIllegibleFont) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
 <html>
   <head>
     <meta name="viewport" content="width=960">
@@ -291,7 +282,7 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, CSSZoomedIllegibleFont) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
 <html>
   <body style="font-size: 12px; zoom:50%">
     Illegible text in 6px.
@@ -304,7 +295,7 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, TextNarrow) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
 <html>
   <body>
     <pre>foo foo foo foo foo</pre>
@@ -315,12 +306,13 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, TextTooWide) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(
       R"(
 <html>
   <body>
     <pre>)" +
-      std::string(10000, 'a') + R"(</pre>
+      std::string(10000, 'a') +
+      R"(</pre>
   </body>
 </html>
 )");
@@ -328,12 +320,13 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, TextTooWideOpacityZero) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(
       R"(
 <html>
   <body>
     <pre style="opacity:0">)" +
-      std::string(10000, 'a') + R"(</pre>
+      std::string(10000, 'a') +
+      R"(</pre>
   </body>
 </html>
 )");
@@ -341,7 +334,7 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, TextTooWideVisibilityHidden) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(
       R"(
 <html>
   <body>
@@ -354,12 +347,13 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, TextTooWideHidden) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(
       R"(
 <html>
   <body>
     <pre style="overflow:hidden">)" +
-      std::string(10000, 'a') + R"(</pre>
+      std::string(10000, 'a') +
+      R"(</pre>
   </body>
 </html>
 )");
@@ -367,13 +361,14 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, TextTooWideHiddenInDiv) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(
       R"(
 <html>
   <body>
     <div style="overflow:hidden">
       <pre>)" +
-      std::string(10000, 'a') + R"(
+      std::string(10000, 'a') +
+      R"(
       </pre>
     </div>
   </body>
@@ -383,14 +378,15 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, TextTooWideHiddenInDivDiv) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(
       R"(
 <html>
   <body>
     <div style="overflow:hidden">
       <div>
         <pre>)" +
-      std::string(10000, 'a') + R"(
+      std::string(10000, 'a') +
+      R"(
         </pre>
       <div>
     </div>
@@ -401,7 +397,7 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, ImageNarrow) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
 <html>
   <body>
     <img style="width:200px; height:50px">
@@ -412,7 +408,7 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, ImageTooWide) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
 <html>
   <body>
     <img style="width:2000px; height:50px">
@@ -423,7 +419,7 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, ImageAbsolutePosition) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
 <html>
   <body>
     <img style="width:100px; height:100px; position:absolute; left:2000px">
@@ -434,7 +430,7 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, ImageTooWideDisplayNone) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
 <html>
   <body>
     <img style="width:2000px; height:50px; display:none">
@@ -445,7 +441,7 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, ScaleTextOutsideViewport) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
 <html>
   <head>
     <meta name="viewport" content="width=480, minimum-scale=1, initial-scale=3">
@@ -469,7 +465,7 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, SingleTapTarget) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
   <head>
     <meta name="viewport" content="width=480, initial-scale=1">
   </head>
@@ -483,7 +479,7 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, NoBadTapTarget) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
   <head>
     <meta name="viewport" content="width=480, initial-scale=1">
   </head>
@@ -500,7 +496,7 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, TooCloseTapTargetsVertical) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
   <head>
     <meta name="viewport" content="width=480, initial-scale=1">
   </head>
@@ -521,7 +517,7 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, TooCloseTapTargetsVerticalSamePoint) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
   <head>
     <meta name="viewport" content="width=480, initial-scale=1">
   </head>
@@ -547,7 +543,7 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, TooCloseTapTargetsHorizontal) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
   <head>
     <meta name="viewport" content="width=480, initial-scale=1">
   </head>
@@ -568,7 +564,7 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, TooCloseTapTargetsHorizontalSamePoint) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
   <head>
     <meta name="viewport" content="width=480, initial-scale=1">
   </head>
@@ -594,7 +590,7 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, GridGoodTargets3X3) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
   <head>
     <meta name="viewport" content="width=480, initial-scale=1">
   </head>
@@ -656,7 +652,7 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, GridBadTargets3X3) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
   <head>
     <meta name="viewport" content="width=480, initial-scale=1">
   </head>
@@ -718,7 +714,7 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, FormTapTargets) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
   <head>
     <meta name="viewport" content="width=480, initial-scale=1">
   </head>
@@ -733,8 +729,8 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, InvisibleTapTargetWillBeIgnored) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
-  <head>
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
+vv  <head>
     <meta name="viewport" content="width=480, initial-scale=1">
   </head>
   <body style="font-size: 18px">
@@ -750,7 +746,7 @@
 }
 
 TEST_F(MobileFriendlinessCheckerTest, BadTapTargetWithPositionAbsolute) {
-  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  MobileFriendliness actual_mf = CalculateMainFrameMetricsForHTMLString(R"(
   <head>
     <meta name="viewport" content="width=480, initial-scale=1">
   </head>
@@ -766,4 +762,20 @@
   EXPECT_EQ(actual_mf.bad_tap_targets_ratio, 100);
 }
 
+TEST_F(MobileFriendlinessCheckerTest, IFrameTest) {
+  url_test_helpers::RegisterMockedURLLoadFromBase(
+      WebString::FromUTF8(kBaseUrl), blink::test::CoreTestDataPath(),
+      WebString::FromUTF8("visible_iframe.html"));
+  MobileFriendlinessTree actual_mf_tree =
+      CalculateMetricsForFile("single_iframe.html");
+  const MobileFriendliness& mainframe_mf = actual_mf_tree.mf;
+  EXPECT_EQ(mainframe_mf.viewport_device_width, mojom::ViewportStatus::kNo);
+  EXPECT_EQ(mainframe_mf.allow_user_zoom, mojom::ViewportStatus::kYes);
+  EXPECT_EQ(mainframe_mf.bad_tap_targets_ratio, 0);
+
+  EXPECT_EQ(actual_mf_tree.children.size(), 1u);
+  const MobileFriendliness& subframe_mf = actual_mf_tree.children[0].mf;
+  EXPECT_EQ(subframe_mf.bad_tap_targets_ratio, 0);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/mobile_metrics/mobile_metrics_test_helpers.h b/third_party/blink/renderer/core/mobile_metrics/mobile_metrics_test_helpers.h
index 1601f8b..e5848984 100644
--- a/third_party/blink/renderer/core/mobile_metrics/mobile_metrics_test_helpers.h
+++ b/third_party/blink/renderer/core/mobile_metrics/mobile_metrics_test_helpers.h
@@ -7,21 +7,33 @@
 
 #include "third_party/blink/public/common/mobile_metrics/mobile_friendliness.h"
 #include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
 
 namespace blink {
 namespace mobile_metrics_test_helpers {
 
-class TestWebFrameClient : public frame_test_helpers::TestWebFrameClient {
- public:
-  void DidChangeMobileFriendliness(const MobileFriendliness& mf) override {
-    mobile_friendliness_ = mf;
-  }
-  const MobileFriendliness& GetMobileFriendliness() const {
-    return mobile_friendliness_;
-  }
+// Collect MobileFriendliness metrics with tree structure which reflects
+// tree structure of subframe.
+struct MobileFriendlinessTree {
+  MobileFriendliness mf;
+  WTF::Vector<MobileFriendlinessTree> children;
 
- private:
-  MobileFriendliness mobile_friendliness_;
+  static MobileFriendlinessTree GetMobileFriendlinessTree(
+      LocalFrameView* view) {
+    mobile_metrics_test_helpers::MobileFriendlinessTree result;
+    view->UpdateLifecycleToPrePaintClean(DocumentUpdateReason::kTest);
+    view->GetMobileFriendlinessChecker()->NotifyFirstContentfulPaint();
+    view->GetMobileFriendlinessChecker()->EvaluateNow();
+    result.mf = view->GetMobileFriendlinessChecker()->GetMobileFriendliness();
+    for (Frame* child = view->GetFrame().FirstChild(); child;
+         child = child->NextSibling()) {
+      if (LocalFrame* local_frame = DynamicTo<LocalFrame>(child)) {
+        result.children.push_back(
+            GetMobileFriendlinessTree(local_frame->View()));
+      }
+    }
+    return result;
+  }
 };
 
 }  // namespace mobile_metrics_test_helpers
diff --git a/third_party/blink/renderer/core/script/js_module_script.cc b/third_party/blink/renderer/core/script/js_module_script.cc
index ecd5ee7..f100f4c 100644
--- a/third_party/blink/renderer/core/script/js_module_script.cc
+++ b/third_party/blink/renderer/core/script/js_module_script.cc
@@ -76,7 +76,7 @@
   // <spec step="9">For each string requested of
   // result.[[RequestedModules]]:</spec>
   for (const auto& requested :
-       modulator->ModuleRequestsFromModuleRecord(result)) {
+       ModuleRecord::ModuleRequests(script_state, result)) {
     // <spec step="9.1">Let url be the result of resolving a module specifier
     // given script's base URL and requested.</spec>
     //
diff --git a/third_party/blink/renderer/core/script/modulator.h b/third_party/blink/renderer/core/script/modulator.h
index 4a06bcac..d3c4feef 100644
--- a/third_party/blink/renderer/core/script/modulator.h
+++ b/third_party/blink/renderer/core/script/modulator.h
@@ -210,9 +210,6 @@
 
   virtual ScriptValue InstantiateModule(v8::Local<v8::Module>, const KURL&) = 0;
 
-  virtual Vector<ModuleRequest> ModuleRequestsFromModuleRecord(
-      v8::Local<v8::Module>) = 0;
-
   virtual ModuleType ModuleTypeFromRequest(
       const ModuleRequest& module_request) const = 0;
 
diff --git a/third_party/blink/renderer/core/script/modulator_impl_base.cc b/third_party/blink/renderer/core/script/modulator_impl_base.cc
index f8c5c8d..4098fcb 100644
--- a/third_party/blink/renderer/core/script/modulator_impl_base.cc
+++ b/third_party/blink/renderer/core/script/modulator_impl_base.cc
@@ -265,12 +265,6 @@
   return ModuleRecord::Instantiate(script_state_, module_record, source_url);
 }
 
-Vector<ModuleRequest> ModulatorImplBase::ModuleRequestsFromModuleRecord(
-    v8::Local<v8::Module> module_record) {
-  ScriptState::Scope scope(script_state_);
-  return ModuleRecord::ModuleRequests(script_state_, module_record);
-}
-
 ModuleType ModulatorImplBase::ModuleTypeFromRequest(
     const ModuleRequest& module_request) const {
   String module_type_string = module_request.GetModuleTypeString();
@@ -323,7 +317,7 @@
   module_script->ProduceCache();
 
   Vector<ModuleRequest> child_specifiers =
-      ModuleRequestsFromModuleRecord(record);
+      ModuleRecord::ModuleRequests(GetScriptState(), record);
 
   for (const auto& module_request : child_specifiers) {
     KURL child_url =
diff --git a/third_party/blink/renderer/core/script/modulator_impl_base.h b/third_party/blink/renderer/core/script/modulator_impl_base.h
index f8f977b..4ef26e62 100644
--- a/third_party/blink/renderer/core/script/modulator_impl_base.h
+++ b/third_party/blink/renderer/core/script/modulator_impl_base.h
@@ -93,8 +93,6 @@
   ModuleImportMeta HostGetImportMetaProperties(
       v8::Local<v8::Module>) const override;
   ScriptValue InstantiateModule(v8::Local<v8::Module>, const KURL&) override;
-  Vector<ModuleRequest> ModuleRequestsFromModuleRecord(
-      v8::Local<v8::Module>) override;
   ModuleType ModuleTypeFromRequest(
       const ModuleRequest& module_request) const override;
 
diff --git a/third_party/blink/renderer/core/script/module_map_test.cc b/third_party/blink/renderer/core/script/module_map_test.cc
index 73d2313..8d3659a 100644
--- a/third_party/blink/renderer/core/script/module_map_test.cc
+++ b/third_party/blink/renderer/core/script/module_map_test.cc
@@ -138,11 +138,6 @@
     return MakeGarbageCollected<TestModuleScriptFetcher>(this, pass_key);
   }
 
-  Vector<ModuleRequest> ModuleRequestsFromModuleRecord(
-      v8::Local<v8::Module>) override {
-    return Vector<ModuleRequest>();
-  }
-
   base::SingleThreadTaskRunner* TaskRunner() override {
     return Thread::Current()->GetTaskRunner().get();
   }
diff --git a/third_party/blink/renderer/core/script/module_script_test.cc b/third_party/blink/renderer/core/script/module_script_test.cc
index 69ac133..fd20842 100644
--- a/third_party/blink/renderer/core/script/module_script_test.cc
+++ b/third_party/blink/renderer/core/script/module_script_test.cc
@@ -32,15 +32,10 @@
 
 class ModuleScriptTestModulator final : public DummyModulator {
  public:
-  ModuleScriptTestModulator(ScriptState* script_state)
+  explicit ModuleScriptTestModulator(ScriptState* script_state)
       : script_state_(script_state) {}
   ~ModuleScriptTestModulator() override = default;
 
-  Vector<ModuleRequest> ModuleRequestsFromModuleRecord(
-      v8::Local<v8::Module>) override {
-    return Vector<ModuleRequest>();
-  }
-
   void Trace(Visitor* visitor) const override {
     visitor->Trace(script_state_);
     DummyModulator::Trace(visitor);
diff --git a/third_party/blink/renderer/core/testing/dummy_modulator.cc b/third_party/blink/renderer/core/testing/dummy_modulator.cc
index 4fb46bed..cc99f2b6 100644
--- a/third_party/blink/renderer/core/testing/dummy_modulator.cc
+++ b/third_party/blink/renderer/core/testing/dummy_modulator.cc
@@ -162,12 +162,6 @@
   return ScriptValue();
 }
 
-Vector<ModuleRequest> DummyModulator::ModuleRequestsFromModuleRecord(
-    v8::Local<v8::Module>) {
-  NOTREACHED();
-  return Vector<ModuleRequest>();
-}
-
 ModuleType DummyModulator::ModuleTypeFromRequest(
     const ModuleRequest& module_request) const {
   String module_type_string = module_request.GetModuleTypeString();
diff --git a/third_party/blink/renderer/core/testing/dummy_modulator.h b/third_party/blink/renderer/core/testing/dummy_modulator.h
index 1086896..47fc8677 100644
--- a/third_party/blink/renderer/core/testing/dummy_modulator.h
+++ b/third_party/blink/renderer/core/testing/dummy_modulator.h
@@ -73,8 +73,6 @@
       v8::Local<v8::Module>) const override;
   const ImportMap* GetImportMapForTest() const override;
   ScriptValue InstantiateModule(v8::Local<v8::Module>, const KURL&) override;
-  Vector<ModuleRequest> ModuleRequestsFromModuleRecord(
-      v8::Local<v8::Module>) override;
   ModuleType ModuleTypeFromRequest(
       const ModuleRequest& module_request) const override;
   ModuleScriptFetcher* CreateModuleScriptFetcher(
diff --git a/third_party/blink/renderer/modules/speech/speech_synthesis.cc b/third_party/blink/renderer/modules/speech/speech_synthesis.cc
index b2e6006ab..06220437 100644
--- a/third_party/blink/renderer/modules/speech/speech_synthesis.cc
+++ b/third_party/blink/renderer/modules/speech/speech_synthesis.cc
@@ -236,8 +236,7 @@
 }
 
 void SpeechSynthesis::VoicesDidChange() {
-  if (GetSupplementable()->GetFrame())
-    DispatchEvent(*Event::Create(event_type_names::kVoiceschanged));
+  DispatchEvent(*Event::Create(event_type_names::kVoiceschanged));
 }
 
 void SpeechSynthesis::StartSpeakingImmediately() {
diff --git a/third_party/blink/renderer/modules/url_pattern/url_pattern.cc b/third_party/blink/renderer/modules/url_pattern/url_pattern.cc
index 948ad2c..4f568168 100644
--- a/third_party/blink/renderer/modules/url_pattern/url_pattern.cc
+++ b/third_party/blink/renderer/modules/url_pattern/url_pattern.cc
@@ -111,9 +111,9 @@
     port =
         base_url.Port() > 0 ? String::Number(base_url.Port()) : g_empty_string;
     pathname = base_url.GetPath() ? base_url.GetPath() : g_empty_string;
-
-    // Do no propagate search or hash from the base URL.  This matches the
-    // behavior when resolving a relative URL against a base URL.
+    search = base_url.Query() ? base_url.Query() : g_empty_string;
+    hash = base_url.HasFragmentIdentifier() ? base_url.FragmentIdentifier()
+                                            : g_empty_string;
   }
 
   // Apply the URLPatternInit component values on top of the default and
diff --git a/third_party/blink/renderer/modules/url_pattern/url_pattern_canon.cc b/third_party/blink/renderer/modules/url_pattern/url_pattern_canon.cc
index 15a7bae..d05ff06 100644
--- a/third_party/blink/renderer/modules/url_pattern/url_pattern_canon.cc
+++ b/third_party/blink/renderer/modules/url_pattern/url_pattern_canon.cc
@@ -16,6 +16,18 @@
 
 namespace {
 
+String MaybeStripPrefix(const String& value, StringView prefix) {
+  if (value.StartsWith(prefix))
+    return value.Substring(1, value.length() - 1);
+  return value;
+}
+
+String MaybeStripSuffix(const String& value, StringView suffix) {
+  if (value.EndsWith(suffix))
+    return value.Substring(0, value.length() - 1);
+  return value;
+}
+
 String StringFromCanonOutput(const url::CanonOutput& output,
                              const url::Component& component) {
   return String::FromUTF8(output.data() + component.begin, component.len);
@@ -193,27 +205,31 @@
 String CanonicalizeProtocol(const String& input,
                             ValueType type,
                             ExceptionState& exception_state) {
+  // We allow the protocol input to optionally contain a ":" suffix.  Strip
+  // this for both URL and pattern protocols.
+  String stripped = MaybeStripSuffix(input, ":");
+
   if (type == ValueType::kPattern) {
     // Canonicalization for patterns is handled during compilation via
     // encoding callbacks.
-    return input;
+    return stripped;
   }
 
   bool result = false;
   url::RawCanonOutputT<char> canon_output;
   url::Component component;
-  if (input.Is8Bit()) {
-    StringUTF8Adaptor utf8(input);
+  if (stripped.Is8Bit()) {
+    StringUTF8Adaptor utf8(stripped);
     result = url::CanonicalizeScheme(
         utf8.data(), url::Component(0, utf8.size()), &canon_output, &component);
   } else {
-    result = url::CanonicalizeScheme(input.Characters16(),
-                                     url::Component(0, input.length()),
+    result = url::CanonicalizeScheme(stripped.Characters16(),
+                                     url::Component(0, stripped.length()),
                                      &canon_output, &component);
   }
 
   if (!result) {
-    exception_state.ThrowTypeError("Invalid protocol '" + input + "'.");
+    exception_state.ThrowTypeError("Invalid protocol '" + stripped + "'.");
     return String();
   }
 
@@ -391,21 +407,25 @@
 String CanonicalizeSearch(const String& input,
                           ValueType type,
                           ExceptionState& exception_state) {
+  // We allow the search input to optionally contain a "?" prefix.  Strip
+  // this for both URL and pattern protocols.
+  String stripped = MaybeStripPrefix(input, "?");
+
   if (type == ValueType::kPattern) {
     // Canonicalization for patterns is handled during compilation via
     // encoding callbacks.
-    return input;
+    return stripped;
   }
 
   url::RawCanonOutputT<char> canon_output;
   url::Component component;
-  if (input.Is8Bit()) {
-    StringUTF8Adaptor utf8(input);
+  if (stripped.Is8Bit()) {
+    StringUTF8Adaptor utf8(stripped);
     url::CanonicalizeQuery(utf8.data(), url::Component(0, utf8.size()),
                            /*converter=*/nullptr, &canon_output, &component);
   } else {
-    url::CanonicalizeQuery(input.Characters16(),
-                           url::Component(0, input.length()),
+    url::CanonicalizeQuery(stripped.Characters16(),
+                           url::Component(0, stripped.length()),
                            /*converter=*/nullptr, &canon_output, &component);
   }
 
@@ -415,21 +435,25 @@
 String CanonicalizeHash(const String& input,
                         ValueType type,
                         ExceptionState& exception_state) {
+  // We allow the hash input to optionally contain a "#" prefix.  Strip
+  // this for both URL and pattern protocols.
+  String stripped = MaybeStripPrefix(input, "#");
+
   if (type == ValueType::kPattern) {
     // Canonicalization for patterns is handled during compilation via
     // encoding callbacks.
-    return input;
+    return stripped;
   }
 
   url::RawCanonOutputT<char> canon_output;
   url::Component component;
-  if (input.Is8Bit()) {
-    StringUTF8Adaptor utf8(input);
+  if (stripped.Is8Bit()) {
+    StringUTF8Adaptor utf8(stripped);
     url::CanonicalizeRef(utf8.data(), url::Component(0, utf8.size()),
                          &canon_output, &component);
   } else {
-    url::CanonicalizeRef(input.Characters16(),
-                         url::Component(0, input.length()), &canon_output,
+    url::CanonicalizeRef(stripped.Characters16(),
+                         url::Component(0, stripped.length()), &canon_output,
                          &component);
   }
 
diff --git a/third_party/blink/renderer/modules/url_pattern/url_pattern_parser.cc b/third_party/blink/renderer/modules/url_pattern/url_pattern_parser.cc
index acb89aa..fc558682 100644
--- a/third_party/blink/renderer/modules/url_pattern/url_pattern_parser.cc
+++ b/third_party/blink/renderer/modules/url_pattern/url_pattern_parser.cc
@@ -27,27 +27,29 @@
     return;
   }
 
+  DCHECK_EQ(token_index_, 0u);
   token_list_ = std::move(tokenize_result.value());
   result_ = MakeGarbageCollected<URLPatternInit>();
 
+  // When constructing a pattern using structured input like
+  // `new URLPattern({ pathname: 'foo' })` any missing components will be
+  // defaulted to wildcards.  In the constructor string case, however, all
+  // components are precisely defined as either empty string or a longer
+  // value.  This is due to there being no way to simply "leave out" a
+  // component when writing a URL.  The behavior also matches the URL
+  // constructor.
+  //
+  // To implement this we initialize components to the empty string in advance.
+  //
+  // We can't, however, do this immediately for all components.  We want to
+  // allow the baseURL to provide information for relative URLs, so we only
+  // want to set the default empty string values for components following the
+  // first component in the relative URL.
+
   // We start in relative mode by default.  If we find a protocol `:` later,
   // we will update the starting state to expect an absolute URL pattern.
   DCHECK_EQ(state_, StringParseState::kPathname);
 
-  // When constructing a pattern using structured input like
-  // `new URLPattern({ pathname: 'foo' })` any missing components will be
-  // defaulted to wildcards.  In this case, however, we default any missing
-  // components to the empty string.  This is due to there being no way to
-  // simply "leave out" a component when writing a URL.  The behavior also
-  // matches the URL constructor.
-  //
-  // To that end we initialize components that would be set for a relative
-  // pattern to the empty string default.  We don't do this for other
-  // components right now so that any base URL value can set those components.
-  result_->setPathname(g_empty_string);
-  result_->setSearch(g_empty_string);
-  result_->setHash(g_empty_string);
-
   // Scan for protocol `:` terminator.  This should be an invalid pattern
   // character.  This automatically works for "https://" because a name
   // cannot start with a `/`.  For URLs that do not include "//", however,
@@ -60,17 +62,39 @@
       // Now that we are in absolute mode we know values will not be inherited
       // from a base URL.  Therefore initialize the rest of the components to
       // the empty string.
-      result_->setProtocol(g_empty_string);
       result_->setUsername(g_empty_string);
       result_->setPassword(g_empty_string);
       result_->setHostname(g_empty_string);
       result_->setPort(g_empty_string);
+      result_->setPathname(g_empty_string);
+      result_->setSearch(g_empty_string);
+      result_->setHash(g_empty_string);
       break;
     }
   }
 
+  // If we failed to find a protocol terminator then we are still in relative
+  // mode.  We now need to determine the first component of the relative URL.
+  if (state_ == StringParseState::kPathname) {
+    // If the string begins with `?` then its a relative search component.  If
+    // it starts with `#` then its a relative hash component.  Otherwise its
+    // a relative pathname.
+    //
+    // In each case we initialize any components following the initial
+    // component to be empty string.
+    if (IsHashPrefix()) {
+      ChangeStateWithoutSettingComponent(StringParseState::kHash, Skip(1));
+    } else if (IsSearchPrefix()) {
+      ChangeStateWithoutSettingComponent(StringParseState::kSearch, Skip(1));
+      result_->setHash(g_empty_string);
+    } else {
+      result_->setSearch(g_empty_string);
+      result_->setHash(g_empty_string);
+    }
+  }
+
   // Iterate through the list of tokens and update our state machine as we go.
-  for (token_index_ = 0; token_index_ < token_list_.size(); ++token_index_) {
+  for (; token_index_ < token_list_.size(); ++token_index_) {
     // All states must respect the end of the token list.  The liburlpattern
     // tokenizer guarantees that the last token will have the type `kEnd`.
     if (token_list_[token_index_].type == liburlpattern::TokenType::kEnd) {
@@ -102,13 +126,15 @@
           // First we eagerly compile the protocol pattern and use it to
           // compute if this entire URLPattern should be treated as a
           // "standard" URL.  If any of the special schemes, like `https`,
-          // match the protocol pattern then we treat it as standard.  This
-          // also forces the default pathname to be `/` instead of the empty
-          // string.
+          // match the protocol pattern then we treat it as standard.
           ComputeShouldTreatAsStandardURL(exception_state);
           if (exception_state.HadException())
             return;
 
+          // Standard URLs default to `/` for the pathname.
+          if (should_treat_as_standard_url_)
+            result_->setPathname("/");
+
           // Next, if there are authority slashes, like `https://`, then
           // we must transition to the authority section of the URLPattern.
           // We explicitly don't support username and password here.  We
@@ -214,7 +240,11 @@
       break;
   }
 
-  // Next move to the new state.
+  ChangeStateWithoutSettingComponent(new_state, skip);
+}
+
+void Parser::ChangeStateWithoutSettingComponent(StringParseState new_state,
+                                                Skip skip) {
   state_ = new_state;
 
   // Now update `component_start_` to point to the new component.  The `skip`
@@ -336,10 +366,8 @@
   protocol_component_ =
       Component::Compile(MakeComponentString(), Component::Type::kProtocol,
                          /*protocol_component=*/nullptr, exception_state);
-  if (protocol_component_ && protocol_component_->ShouldTreatAsStandardURL()) {
+  if (protocol_component_ && protocol_component_->ShouldTreatAsStandardURL())
     should_treat_as_standard_url_ = true;
-    result_->setPathname("/");
-  }
 }
 
 }  // namespace url_pattern
diff --git a/third_party/blink/renderer/modules/url_pattern/url_pattern_parser.h b/third_party/blink/renderer/modules/url_pattern/url_pattern_parser.h
index 02cbe51..190df7c 100644
--- a/third_party/blink/renderer/modules/url_pattern/url_pattern_parser.h
+++ b/third_party/blink/renderer/modules/url_pattern/url_pattern_parser.h
@@ -68,6 +68,11 @@
   // many tokens the `skip` argument indicates should be ignored.
   void ChangeState(StringParseState new_state, Skip skip);
 
+  // A utility function to move to `new_state`.  This is like `ChangeState()`,
+  // but does not automatically set the component string for the current state.
+  void ChangeStateWithoutSettingComponent(StringParseState new_state,
+                                          Skip skip);
+
   // Attempt to access the Token at the given `index`.  If the `index` is out
   // of bounds for the `token_list_`, then the last Token in the list is
   // returned.  This will always be a `TokenType::kEnd` token.
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_adapter.h b/third_party/blink/renderer/modules/webgpu/gpu_adapter.h
index e6057ff..e488c13 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_adapter.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_adapter.h
@@ -41,12 +41,6 @@
   ScriptPromise requestDevice(ScriptState* script_state,
                               GPUDeviceDescriptor* descriptor);
 
- private:
-  void OnRequestDeviceCallback(ScriptPromiseResolver* resolver,
-                               const GPUDeviceDescriptor* descriptor,
-                               WGPUDevice dawn_device);
-  void InitializeFeatureNameList();
-
   // Console warnings should generally be attributed to a GPUDevice, but in
   // cases where there is no device warnings can be surfaced here. It's expected
   // that very few warning will need to be shown for a given adapter, and as a
@@ -54,6 +48,12 @@
   void AddConsoleWarning(ExecutionContext* execution_context,
                          const char* message);
 
+ private:
+  void OnRequestDeviceCallback(ScriptPromiseResolver* resolver,
+                               const GPUDeviceDescriptor* descriptor,
+                               WGPUDevice dawn_device);
+  void InitializeFeatureNameList();
+
   String name_;
   uint32_t adapter_service_id_;
   WGPUDeviceProperties adapter_properties_;
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.cc b/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.cc
index 58a962e6..f067687 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.cc
@@ -106,22 +106,72 @@
       swapchain_->TransferToStaticBitmapImage());
 }
 
-// gpu_canvas_context.idl
+// gpu_presentation_context.idl
+void GPUCanvasContext::configure(const GPUSwapChainDescriptor* descriptor,
+                                 ExceptionState& exception_state) {
+  ConfigureInternal(descriptor, exception_state);
+}
+
+String GPUCanvasContext::getPreferredFormat(const GPUAdapter* adapter) {
+  // TODO(crbug.com/1007166): Return actual preferred format for the swap chain.
+  return "bgra8unorm";
+}
+
+GPUTexture* GPUCanvasContext::getCurrentTexture(
+    ExceptionState& exception_state) {
+  if (!swapchain_) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kOperationError,
+                                      "context is not configured");
+    return nullptr;
+  }
+  return swapchain_->getCurrentTexture();
+}
+
+// gpu_canvas_context.idl (Deprecated)
 GPUSwapChain* GPUCanvasContext::configureSwapChain(
     const GPUSwapChainDescriptor* descriptor,
     ExceptionState& exception_state) {
+  descriptor->device()->AddConsoleWarning(
+      "configureSwapChain() is deprecated. Use configure() instead and call "
+      "getCurrentTexture() directly on the context. Note that configure() must "
+      "also be called if you want to change the size of the textures returned "
+      "by getCurrentTexture()");
+  ConfigureInternal(descriptor, exception_state, true);
+  return swapchain_;
+}
+
+String GPUCanvasContext::getSwapChainPreferredFormat(
+    ExecutionContext* execution_context,
+    GPUAdapter* adapter) {
+  adapter->AddConsoleWarning(
+      execution_context,
+      "getSwapChainPreferredFormat() is deprecated. Use getPreferredFormat() "
+      "instead.");
+  return getPreferredFormat(adapter);
+}
+
+void GPUCanvasContext::ConfigureInternal(
+    const GPUSwapChainDescriptor* descriptor,
+    ExceptionState& exception_state,
+    bool deprecated_resize_behavior) {
   if (stopped_) {
     // This is probably not possible, or at least would only happen during page
     // shutdown.
     exception_state.ThrowDOMException(DOMExceptionCode::kUnknownError,
                                       "canvas has been destroyed");
-    return nullptr;
+    return;
   }
 
   if (swapchain_) {
     // Tell any previous swapchain that it will no longer be used and can
     // destroy all its resources (and produce errors when used).
     swapchain_->Neuter();
+    swapchain_ = nullptr;
+  }
+
+  // Passing a null descriptor explicitly clears the configuration.
+  if (!descriptor) {
+    return;
   }
 
   WGPUTextureUsage usage = AsDawnEnum<WGPUTextureUsage>(descriptor->usage());
@@ -134,29 +184,42 @@
       exception_state.ThrowDOMException(
           DOMExceptionCode::kUnknownError,
           "rgba16float swap chain is not yet supported");
-      return nullptr;
+      return;
     default:
       exception_state.ThrowDOMException(DOMExceptionCode::kOperationError,
                                         "unsupported swap chain format");
-      return nullptr;
+      return;
+  }
+
+  // Set the default size.
+  IntSize size;
+  if (deprecated_resize_behavior) {
+    // A negative size will indicate to the swap chain that it should follow the
+    // deprecated behavior of resizing to match the canvas size each frame.
+    size = IntSize(-1, -1);
+  } else if (descriptor->hasSize()) {
+    WGPUExtent3D dawn_extent =
+        AsDawnType(&descriptor->size(), descriptor->device());
+    size = IntSize(dawn_extent.width, dawn_extent.height);
+
+    if (dawn_extent.depthOrArrayLayers != 1) {
+      exception_state.ThrowDOMException(
+          DOMExceptionCode::kOperationError,
+          "swap chain size must have depthOrArrayLayers set to 1");
+      return;
+    }
+  } else {
+    size = CanvasSize();
   }
 
   swapchain_ = MakeGarbageCollected<GPUSwapChain>(
-      this, descriptor->device(), usage, format, filter_quality_);
+      this, descriptor->device(), usage, format, filter_quality_, size);
   swapchain_->CcLayer()->SetContentsOpaque(!CreationAttributes().alpha);
   swapchain_->setLabel(descriptor->label());
 
   // If we don't notify the host that something has changed it may never check
   // for the new cc::Layer.
   Host()->SetNeedsCompositingUpdate();
-
-  return swapchain_;
-}
-
-String GPUCanvasContext::getSwapChainPreferredFormat(
-    const GPUAdapter* adapter) {
-  // TODO(crbug.com/1007166): Return actual preferred format for the swap chain.
-  return "bgra8unorm";
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.h b/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.h
index c23a0cd..1c3712d 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.h
@@ -17,6 +17,7 @@
 class GPUAdapter;
 class GPUSwapChain;
 class GPUSwapChainDescriptor;
+class GPUTexture;
 
 // A GPUCanvasContext does little by itself and basically just binds a canvas
 // and a GPUSwapChain together and forwards calls from one to the other.
@@ -72,13 +73,24 @@
     return false;
   }
 
-  // gpu_canvas_context.idl
+  // gpu_presentation_context.idl
+  void configure(const GPUSwapChainDescriptor* descriptor, ExceptionState&);
+  String getPreferredFormat(const GPUAdapter* adapter);
+  GPUTexture* getCurrentTexture(ExceptionState&);
+
+  // gpu_canvas_context.idl (Deprecated)
   GPUSwapChain* configureSwapChain(const GPUSwapChainDescriptor* descriptor,
                                    ExceptionState&);
-  String getSwapChainPreferredFormat(const GPUAdapter* adapter);
+  String getSwapChainPreferredFormat(ExecutionContext* execution_context,
+                                     GPUAdapter* adapter);
 
  private:
   DISALLOW_COPY_AND_ASSIGN(GPUCanvasContext);
+
+  void ConfigureInternal(const GPUSwapChainDescriptor* descriptor,
+                         ExceptionState&,
+                         bool deprecated_resize_behavior = false);
+
   SkFilterQuality filter_quality_ = kLow_SkFilterQuality;
   Member<GPUSwapChain> swapchain_;
   bool stopped_ = false;
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.idl b/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.idl
index 45edc38..a4ff2ed4 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.idl
+++ b/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.idl
@@ -8,6 +8,11 @@
     ActiveScriptWrappable,
     Exposed(Window WebGPU, Worker WebGPU)
 ] interface GPUCanvasContext {
+    GPUTextureFormat getPreferredFormat(GPUAdapter adapter);
+    [RaisesException] void configure(GPUSwapChainDescriptor descriptor);
+    [RaisesException] GPUTexture getCurrentTexture();
+
+    // Deprecated
     [RaisesException] GPUSwapChain configureSwapChain(GPUSwapChainDescriptor descriptor);
-    GPUTextureFormat getSwapChainPreferredFormat(GPUAdapter adapter);
+    [CallWith=ExecutionContext] GPUTextureFormat getSwapChainPreferredFormat(GPUAdapter adapter);
 };
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_swap_chain.cc b/third_party/blink/renderer/modules/webgpu/gpu_swap_chain.cc
index 580e1cd..8c971f2 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_swap_chain.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_swap_chain.cc
@@ -19,11 +19,13 @@
                            GPUDevice* device,
                            WGPUTextureUsage usage,
                            WGPUTextureFormat format,
-                           SkFilterQuality filter_quality)
+                           SkFilterQuality filter_quality,
+                           IntSize size)
     : DawnObjectImpl(device),
       context_(context),
       usage_(usage),
-      format_(format) {
+      format_(format),
+      size_(size) {
   // TODO: Use label from GPUObjectDescriptorBase.
   swap_buffers_ = base::AdoptRef(new WebGPUSwapBufferProvider(
       this, GetDawnControlClient(), device->GetHandle(), usage_, format));
@@ -143,8 +145,11 @@
     return texture_;
   }
 
-  WGPUTexture dawn_client_texture =
-      swap_buffers_->GetNewTexture(context_->CanvasSize());
+  // A negative size indicates we're on the deprecated path which automatically
+  // adjusts to the canvas width/height attributes.
+  // TODO(bajones@chromium.org): Remove automatic path after deprecation period.
+  IntSize texture_size = size_.Width() >= 0 ? size_ : context_->CanvasSize();
+  WGPUTexture dawn_client_texture = swap_buffers_->GetNewTexture(texture_size);
   DCHECK(dawn_client_texture);
   texture_ = MakeGarbageCollected<GPUTexture>(device_, dawn_client_texture,
                                               format_, usage_);
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_swap_chain.h b/third_party/blink/renderer/modules/webgpu/gpu_swap_chain.h
index 54a2a40..db08a2d 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_swap_chain.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_swap_chain.h
@@ -29,7 +29,8 @@
                         GPUDevice*,
                         WGPUTextureUsage,
                         WGPUTextureFormat,
-                        SkFilterQuality);
+                        SkFilterQuality,
+                        IntSize);
   ~GPUSwapChain() override;
 
   void Trace(Visitor* visitor) const override;
@@ -62,6 +63,7 @@
   Member<GPUCanvasContext> context_;
   WGPUTextureUsage usage_;
   WGPUTextureFormat format_;
+  const IntSize size_;
 
   Member<GPUTexture> texture_;
 };
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_swap_chain_descriptor.idl b/third_party/blink/renderer/modules/webgpu/gpu_swap_chain_descriptor.idl
index a80a739..ef7774f5 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_swap_chain_descriptor.idl
+++ b/third_party/blink/renderer/modules/webgpu/gpu_swap_chain_descriptor.idl
@@ -8,4 +8,5 @@
     required GPUDevice device;
     required GPUTextureFormat format;
     GPUTextureUsageFlags usage = 16;  // GPUTextureUsage.RENDER_ATTACHMENT
+    GPUExtent3D size;
 };
diff --git a/third_party/blink/renderer/modules/websockets/dom_websocket.cc b/third_party/blink/renderer/modules/websockets/dom_websocket.cc
index 91a54ae..8f57ea5a4 100644
--- a/third_party/blink/renderer/modules/websockets/dom_websocket.cc
+++ b/third_party/blink/renderer/modules/websockets/dom_websocket.cc
@@ -82,7 +82,6 @@
   switch (state_) {
     case kActive:
       DCHECK(events_.IsEmpty());
-      DCHECK(target_->GetExecutionContext());
       target_->DispatchEvent(*event);
       break;
     case kPaused:
@@ -140,7 +139,6 @@
     if (state_ == kStopped || state_ == kPaused || state_ == kUnpausePosted)
       break;
     DCHECK_EQ(state_, kActive);
-    DCHECK(target_->GetExecutionContext());
     target_->DispatchEvent(*events.TakeFirst());
     // |this| can be stopped here.
   }
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index abc88ef..ece434b 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -1082,6 +1082,8 @@
     "graphics/paint/paint_record_builder.h",
     "graphics/paint/paint_recorder.h",
     "graphics/paint/paint_shader.h",
+    "graphics/paint/paint_under_invalidation_checker.cc",
+    "graphics/paint/paint_under_invalidation_checker.h",
     "graphics/paint/painted_selection_bound.h",
     "graphics/paint/property_tree_state.cc",
     "graphics/paint/property_tree_state.h",
@@ -2087,6 +2089,7 @@
     "graphics/paint/paint_controller_test.cc",
     "graphics/paint/paint_property_node_test.cc",
     "graphics/paint/paint_record_builder_test.cc",
+    "graphics/paint/paint_under_invalidation_checker_test.cc",
     "graphics/paint/raster_invalidator_test.cc",
     "graphics/paint/scrollbar_display_item_test.cc",
     "graphics/paint_invalidation_reason_test.cc",
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_chunk.cc b/third_party/blink/renderer/platform/graphics/paint/paint_chunk.cc
index 7ad9c06..9078aff 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_chunk.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_chunk.cc
@@ -35,9 +35,10 @@
          ((!hit_test_data && !other.hit_test_data) ||
           (hit_test_data && other.hit_test_data &&
            *hit_test_data == *other.hit_test_data));
-  // known_to_be_opaque is not checked ]because it's updated when we create the
-  // next chunk or release chunks. We ensure its correctness with unit tests and
-  // under-invalidation checking of display items.
+  // Derived fields like known_to_be_opaque are not checked because they are
+  // updated when we create the next chunk or release chunks. We ensure their
+  // correctness with unit tests and under-invalidation checking of display
+  // items.
 }
 
 size_t PaintChunk::MemoryUsageInBytes() const {
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc b/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc
index a5f0651..f49e027 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc
@@ -9,10 +9,10 @@
 #include "base/auto_reset.h"
 #include "base/logging.h"
 #include "base/metrics/histogram_macros.h"
-#include "third_party/blink/renderer/platform/graphics/logging_canvas.h"
 #include "third_party/blink/renderer/platform/graphics/paint/drawing_display_item.h"
 #include "third_party/blink/renderer/platform/graphics/paint/ignore_paint_timing_scope.h"
 #include "third_party/blink/renderer/platform/graphics/paint/paint_chunk_subset.h"
+#include "third_party/blink/renderer/platform/graphics/paint/paint_under_invalidation_checker.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
 
 namespace blink {
@@ -111,8 +111,7 @@
   if (!ClientCacheIsValid(client))
     return false;
 
-  if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled() &&
-      IsCheckingUnderInvalidation()) {
+  if (IsCheckingUnderInvalidation()) {
     // We are checking under-invalidation of a subsequence enclosing this
     // display item. Let the client continue to actually paint the display item.
     return false;
@@ -133,11 +132,7 @@
     next_item_to_index_ = next_item_to_match_;
 
   if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled()) {
-    if (!IsCheckingUnderInvalidation()) {
-      under_invalidation_checking_begin_ = cached_item;
-      under_invalidation_checking_end_ = cached_item + 1;
-      under_invalidation_message_prefix_ = "";
-    }
+    EnsureUnderInvalidationChecker().WouldUseCachedItem(cached_item);
     // Return false to let the painter actually paint. We will check if the new
     // painting is the same as the cached one.
     return false;
@@ -160,8 +155,7 @@
   if (!ClientCacheIsValid(client))
     return false;
 
-  if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled() &&
-      IsCheckingUnderInvalidation()) {
+  if (IsCheckingUnderInvalidation()) {
     // We are checking under-invalidation of an ancestor subsequence enclosing
     // this one. The ancestor subsequence is supposed to have already "copied",
     // so we should let the client continue to actually paint the descendant
@@ -207,11 +201,7 @@
   ++num_cached_new_subsequences_;
 
   if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled()) {
-    DCHECK(!IsCheckingUnderInvalidation());
-    under_invalidation_checking_begin_ = start_item_index;
-    under_invalidation_checking_end_ = end_item_index;
-    under_invalidation_message_prefix_ =
-        "(In cached subsequence for " + client.DebugName() + ")";
+    EnsureUnderInvalidationChecker().WouldUseCachedSubsequence(client);
     // Return false to let the painter actually paint. We will check if the new
     // painting is the same as the cached one.
     return false;
@@ -239,58 +229,27 @@
   return &current_subsequences_.tree[index];
 }
 
-void PaintController::BeginSubsequence(wtf_size_t& subsequence_index,
-                                       wtf_size_t& start_chunk_index) {
+wtf_size_t PaintController::BeginSubsequence(const DisplayItemClient& client) {
   // Force new paint chunk which is required for subsequence caching.
   SetWillForceNewChunk(true);
-  subsequence_index = new_subsequences_.tree.size();
-  new_subsequences_.tree.emplace_back();
-  start_chunk_index = NumNewChunks();
+  new_subsequences_.tree.push_back(SubsequenceMarkers{&client, NumNewChunks()});
+  return new_subsequences_.tree.size() - 1;
 }
 
-void PaintController::EndSubsequence(const DisplayItemClient& client,
-                                     wtf_size_t subsequence_index,
-                                     wtf_size_t start_chunk_index) {
-  wtf_size_t end_chunk_index = NumNewChunks();
+void PaintController::EndSubsequence(wtf_size_t subsequence_index) {
+  auto& markers = new_subsequences_.tree[subsequence_index];
 
-  if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled() &&
-      IsCheckingUnderInvalidation()) {
-    const SubsequenceMarkers* markers = GetSubsequenceMarkers(client);
-    if (!markers) {
-      if (start_chunk_index != end_chunk_index) {
-        ShowSequenceUnderInvalidationError(
-            "under-invalidation : unexpected subsequence", client);
-        CHECK(false);
-      }
-    } else {
-      if (markers->end_chunk_index - markers->start_chunk_index !=
-          end_chunk_index - start_chunk_index) {
-        ShowSequenceUnderInvalidationError(
-            "under-invalidation: new subsequence wrong length", client);
-        CHECK(false);
-      }
-      auto old_chunk_index = markers->start_chunk_index;
-      for (auto new_chunk_index = start_chunk_index;
-           new_chunk_index < end_chunk_index;
-           ++new_chunk_index, ++old_chunk_index) {
-        const auto& old_chunk =
-            current_paint_artifact_->PaintChunks()[old_chunk_index];
-        const auto& new_chunk =
-            new_paint_artifact_->PaintChunks()[new_chunk_index];
-        if (!old_chunk.EqualsForUnderInvalidationChecking(new_chunk)) {
-          ShowSequenceUnderInvalidationError(
-              "under-invalidation: chunk changed", client);
-          CHECK(false) << "Changed chunk: " << new_chunk;
-        }
-      }
-    }
+  if (IsCheckingUnderInvalidation()) {
+    under_invalidation_checker_->WillEndSubsequence(*markers.client,
+                                                    markers.start_chunk_index);
   }
 
-  if (start_chunk_index == end_chunk_index) {
-    // Omit the empty subsequence. The forcing-new-chunk flag set by
-    // BeginSubsequence() still applies, but this not a big deal because empty
-    // subsequences are not common. Also we should not clear the flag because
-    // there might be unhandled flag that was set before this empty subsequence.
+  wtf_size_t end_chunk_index = NumNewChunks();
+  if (markers.start_chunk_index == end_chunk_index) {
+    // Omit the empty subsequence. The WillForceNewChunk flag set in
+    // BeginSubsequence() still applies, but it's useful to reduce churns of
+    // raster invalidation and compositing when the subsequence switches between
+    // empty and non-empty.
     new_subsequences_.tree.pop_back();
     return;
   }
@@ -299,24 +258,23 @@
   SetWillForceNewChunk(true);
 
 #if DCHECK_IS_ON()
-  DCHECK(!new_subsequences_.map.Contains(&client))
-      << "Multiple subsequences for client: " << client.DebugName();
+  DCHECK(!new_subsequences_.map.Contains(markers.client))
+      << "Multiple subsequences for client: " << markers.client->DebugName();
 
   // Check tree integrity.
   if (subsequence_index > 0) {
-    DCHECK_GE(start_chunk_index,
+    DCHECK_GE(markers.start_chunk_index,
               new_subsequences_.tree[subsequence_index - 1].end_chunk_index);
   }
   for (auto i = subsequence_index + 1; i < new_subsequences_.tree.size(); i++) {
     auto& child_markers = new_subsequences_.tree[i];
-    DCHECK_GE(child_markers.start_chunk_index, start_chunk_index);
+    DCHECK_GE(child_markers.start_chunk_index, markers.start_chunk_index);
     DCHECK_LE(child_markers.end_chunk_index, end_chunk_index);
   }
 #endif
 
-  new_subsequences_.map.insert(&client, subsequence_index);
-  new_subsequences_.tree[subsequence_index] =
-      SubsequenceMarkers{&client, start_chunk_index, end_chunk_index};
+  new_subsequences_.map.insert(markers.client, subsequence_index);
+  markers.end_chunk_index = end_chunk_index;
 }
 
 void PaintController::CheckNewItem(DisplayItem& display_item) {
@@ -341,8 +299,8 @@
   }
 #endif
 
-  if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled())
-    CheckUnderInvalidation();
+  if (IsCheckingUnderInvalidation())
+    under_invalidation_checker_->CheckNewItem();
 }
 
 void PaintController::ProcessNewItem(DisplayItem& display_item) {
@@ -375,6 +333,9 @@
                     new_paint_chunk_id_index_map_);
   }
 #endif
+
+  if (IsCheckingUnderInvalidation())
+    under_invalidation_checker_->CheckNewChunk();
 }
 
 void PaintController::InvalidateAllForTesting() {
@@ -488,26 +449,22 @@
     }
   }
 
+#if DCHECK_IS_ON()
   // The display item newly appears while the client is not invalidated. The
   // situation alone (without other kinds of under-invalidations) won't corrupt
   // rendering, but causes AddItemToIndexIfNeeded() for all remaining display
   // item, which is not the best for performance. In this case, the caller
   // should fall back to repaint the display item.
   if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled()) {
-#if DCHECK_IS_ON()
-    ShowDebugData();
-#endif
     // Ensure our paint invalidation tests don't trigger the less performant
     // situation which should be rare.
     DLOG(WARNING) << "Can't find cached display item: " << id;
+    ShowDebugData();
   }
+#endif
   return kNotFound;
 }
 
-// Moves a cached subsequence from current list to the new list.
-// When PaintUnderInvaldiationCheckingEnabled() we'll not actually
-// move the subsequence, but mark the begin and end of the subsequence for
-// under-invalidation checking.
 void PaintController::AppendSubsequenceByMoving(const DisplayItemClient& client,
                                                 wtf_size_t subsequence_index,
                                                 wtf_size_t start_chunk_index,
@@ -519,9 +476,8 @@
   auto properties_before_subsequence = CurrentPaintChunkProperties();
 #endif
 
-  wtf_size_t new_start_chunk_index;
-  wtf_size_t new_subsequence_index;
-  BeginSubsequence(new_subsequence_index, new_start_chunk_index);
+  auto new_start_chunk_index = NumNewChunks();
+  auto new_subsequence_index = BeginSubsequence(client);
 
   auto& current_chunks = current_paint_artifact_->PaintChunks();
   for (auto chunk_index = start_chunk_index; chunk_index < end_chunk_index;
@@ -566,20 +522,13 @@
     ++num_cached_new_subsequences_;
   }
 
-  EndSubsequence(client, new_subsequence_index, new_start_chunk_index);
+  EndSubsequence(new_subsequence_index);
 
 #if DCHECK_IS_ON()
   DCHECK_EQ(properties_before_subsequence, CurrentPaintChunkProperties());
 #endif
 }
 
-void PaintController::ResetCurrentListIndices() {
-  next_item_to_match_ = 0;
-  next_item_to_index_ = 0;
-  under_invalidation_checking_begin_ = 0;
-  under_invalidation_checking_end_ = 0;
-}
-
 DISABLE_CFI_PERF
 void PaintController::CommitNewDisplayItems() {
   TRACE_EVENT2(
@@ -604,6 +553,8 @@
   cache_is_all_invalid_ = false;
   committed_ = true;
 
+  under_invalidation_checker_.reset();
+
   DCHECK_EQ(new_subsequences_.map.size(), new_subsequences_.tree.size());
   current_subsequences_.map.clear();
   current_subsequences_.tree.clear();
@@ -620,7 +571,8 @@
     paint_chunker_.ResetChunks(nullptr);
   }
 
-  ResetCurrentListIndices();
+  next_item_to_match_ = 0;
+  next_item_to_index_ = 0;
   out_of_order_item_id_index_map_.clear();
 
 #if DCHECK_IS_ON()
@@ -709,89 +661,22 @@
   return memory_usage;
 }
 
-void PaintController::ShowUnderInvalidationError(
-    const char* reason,
-    const DisplayItem& new_item,
-    const DisplayItem* old_item) const {
-  LOG(ERROR) << under_invalidation_message_prefix_ << " " << reason;
-#if DCHECK_IS_ON()
-  LOG(ERROR) << "New display item: " << new_item.AsDebugString();
-  LOG(ERROR) << "Old display item: "
-             << (old_item ? old_item->AsDebugString() : "None");
-  LOG(ERROR) << "See http://crbug.com/619103.";
-
-  const PaintRecord* new_record = nullptr;
-  if (auto* new_drawing = DynamicTo<DrawingDisplayItem>(new_item))
-    new_record = new_drawing->GetPaintRecord().get();
-  const PaintRecord* old_record = nullptr;
-  if (auto* old_drawing = DynamicTo<DrawingDisplayItem>(old_item))
-    old_record = old_drawing->GetPaintRecord().get();
-  LOG(INFO) << "new record:\n"
-            << (new_record ? RecordAsDebugString(*new_record).Utf8() : "None");
-  LOG(INFO) << "old record:\n"
-            << (old_record ? RecordAsDebugString(*old_record).Utf8() : "None");
-
-  ShowDebugData();
-#else
-  LOG(ERROR) << "Run a build with DCHECK on to get more details.";
-  LOG(ERROR) << "See http://crbug.com/619103.";
-#endif
-}
-
-void PaintController::ShowSequenceUnderInvalidationError(
-    const char* reason,
-    const DisplayItemClient& client) {
-  LOG(ERROR) << under_invalidation_message_prefix_ << " " << reason;
-  LOG(ERROR) << "Subsequence client: " << client.DebugName();
-#if DCHECK_IS_ON()
-  ShowDebugData();
-#else
-  LOG(ERROR) << "Run a build with DCHECK on to get more details.";
-#endif
-  LOG(ERROR) << "See http://crbug.com/619103.";
-}
-
-void PaintController::CheckUnderInvalidation() {
-  DCHECK_EQ(usage_, kMultiplePaints);
+PaintUnderInvalidationChecker&
+PaintController::EnsureUnderInvalidationChecker() {
   DCHECK(RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled());
-
-  if (!IsCheckingUnderInvalidation())
-    return;
-
-  if (IsSkippingCache()) {
-    // We allow cache skipping and temporary under-invalidation in cached
-    // subsequences. See the usage of DisplayItemCacheSkipper in BoxPainter.
-    under_invalidation_checking_end_ = 0;
-    // Match the remaining display items in the subsequence normally.
-    next_item_to_match_ = next_item_to_index_ =
-        under_invalidation_checking_begin_;
-    return;
+  if (!under_invalidation_checker_) {
+    under_invalidation_checker_ =
+        std::make_unique<PaintUnderInvalidationChecker>(*this);
   }
+  return *under_invalidation_checker_;
+}
 
-  DisplayItem& new_item = new_paint_artifact_->GetDisplayItemList().back();
-  auto old_item_index = under_invalidation_checking_begin_;
-  DisplayItem* old_item =
-      old_item_index < current_paint_artifact_->GetDisplayItemList().size()
-          ? &current_paint_artifact_->GetDisplayItemList()[old_item_index]
-          : nullptr;
-
-  if (!old_item || !new_item.EqualsForUnderInvalidation(*old_item)) {
-    // If we ever skipped reporting any under-invalidations, report the earliest
-    // one.
-    ShowUnderInvalidationError("under-invalidation: display item changed",
-                               new_item, old_item);
-    CHECK(false);
+bool PaintController::IsCheckingUnderInvalidation() const {
+  if (under_invalidation_checker_) {
+    DCHECK(RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled());
+    return under_invalidation_checker_->IsChecking();
   }
-
-  // Discard the forced repainted display item and move the cached item into
-  // new_display_item_list_. This is to align with the
-  // non-under-invalidation-checking path to empty the original cached slot,
-  // leaving only disappeared or invalidated display items in the old list after
-  // painting.
-  new_paint_artifact_->GetDisplayItemList().ReplaceLastByMoving(
-      current_paint_artifact_->GetDisplayItemList()[old_item_index]);
-
-  ++under_invalidation_checking_begin_;
+  return false;
 }
 
 void PaintController::SetFirstPainted() {
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_controller.h b/third_party/blink/renderer/platform/graphics/paint/paint_controller.h
index f0d5b54..e56adae 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_controller.h
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_controller.h
@@ -31,6 +31,8 @@
 
 namespace blink {
 
+class PaintUnderInvalidationChecker;
+
 enum class PaintBenchmarkMode {
   kNormal,
   kForceRasterInvalidationAndConvert,
@@ -103,6 +105,7 @@
   void EnsureChunk();
 
   void SetShouldComputeContentsOpaque(bool should_compute) {
+    DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled());
     paint_chunker_.SetShouldComputeContentsOpaque(should_compute);
   }
 
@@ -151,13 +154,11 @@
   // true. Otherwise returns false.
   bool UseCachedSubsequenceIfPossible(const DisplayItemClient&);
 
-  void BeginSubsequence(wtf_size_t& subsequence_index,
-                        wtf_size_t& start_chunk_index);
+  // Returns the index of the new subsequence.
+  wtf_size_t BeginSubsequence(const DisplayItemClient&);
   // The |start| parameter should be the return value of the corresponding
   // BeginSubsequence().
-  void EndSubsequence(const DisplayItemClient&,
-                      wtf_size_t subsequence_index,
-                      wtf_size_t start_chunk_index);
+  void EndSubsequence(wtf_size_t subsequence_index);
 
   void BeginSkippingCache() {
     if (usage_ == kTransient)
@@ -275,6 +276,7 @@
  private:
   friend class PaintControllerTestBase;
   friend class PaintControllerPaintTestBase;
+  friend class PaintUnderInvalidationChecker;
   friend class GraphicsLayer;  // Temporary for ClientCacheIsValid().
 
   // True if all display items associated with the client are validly cached.
@@ -352,27 +354,6 @@
                                  wtf_size_t start_chunk_index,
                                  wtf_size_t end_chunk_index);
 
-  // Resets the indices (e.g. next_item_to_match_) of
-  // current_paint_artifact_.GetDisplayItemList() to their initial values. This
-  // should be called when the DisplayItemList in current_paint_artifact_ is
-  // newly created, or is changed causing the previous indices to be invalid.
-  void ResetCurrentListIndices();
-
-  // The following two methods are for checking under-invalidations
-  // (when RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled).
-  void ShowUnderInvalidationError(const char* reason,
-                                  const DisplayItem& new_item,
-                                  const DisplayItem* old_item) const;
-
-  void ShowSequenceUnderInvalidationError(const char* reason,
-                                          const DisplayItemClient&);
-
-  void CheckUnderInvalidation();
-  bool IsCheckingUnderInvalidation() const {
-    return under_invalidation_checking_end_ >
-           under_invalidation_checking_begin_;
-  }
-
   struct SubsequenceMarkers {
     const DisplayItemClient* client = nullptr;
     // The start and end (not included) index of paint chunks in this
@@ -387,6 +368,9 @@
 
   void ValidateNewChunkId(const PaintChunk::Id&);
 
+  PaintUnderInvalidationChecker& EnsureUnderInvalidationChecker();
+  ALWAYS_INLINE bool IsCheckingUnderInvalidation() const;
+
 #if DCHECK_IS_ON()
   void ShowDebugDataInternal(DisplayItemList::JsonFlags) const;
 #endif
@@ -459,16 +443,7 @@
   IdIndexMap new_paint_chunk_id_index_map_;
 #endif
 
-  // These are set in UseCachedItemIfPossible() and
-  // UseCachedSubsequenceIfPossible() when we could use cached drawing or
-  // subsequence and under-invalidation checking is on, indicating the begin and
-  // end of the cached drawing or subsequence in the current list. The functions
-  // return false to let the client do actual painting, and PaintController will
-  // check if the actual painting results are the same as the cached.
-  wtf_size_t under_invalidation_checking_begin_ = 0;
-  wtf_size_t under_invalidation_checking_end_ = 0;
-
-  String under_invalidation_message_prefix_;
+  std::unique_ptr<PaintUnderInvalidationChecker> under_invalidation_checker_;
 
   struct SubsequencesData {
     // Map a client to the index into |tree|.
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_controller_test.cc b/third_party/blink/renderer/platform/graphics/paint/paint_controller_test.cc
index 014d63b..4049044 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_controller_test.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_controller_test.cc
@@ -1868,278 +1868,6 @@
 #endif
 }
 
-class PaintControllerUnderInvalidationTest
-    : public PaintControllerTestBase,
-      private ScopedPaintUnderInvalidationCheckingForTest {
- public:
-  PaintControllerUnderInvalidationTest()
-      : PaintControllerTestBase(),
-        ScopedPaintUnderInvalidationCheckingForTest(true) {}
-
- protected:
-  void SetUp() override {
-    testing::FLAGS_gtest_death_test_style = "threadsafe";
-  }
-
-  void TestChangeDrawing() {
-    FakeDisplayItemClient first("first");
-    GraphicsContext context(GetPaintController());
-
-    InitRootChunk();
-    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 300, 300));
-    DrawRect(context, first, kForegroundType, IntRect(100, 100, 300, 300));
-    CommitAndFinishCycle();
-
-    InitRootChunk();
-    DrawRect(context, first, kBackgroundType, IntRect(200, 200, 300, 300));
-    DrawRect(context, first, kForegroundType, IntRect(100, 100, 300, 300));
-    CommitAndFinishCycle();
-  }
-
-  void TestMoreDrawing() {
-    FakeDisplayItemClient first("first");
-    GraphicsContext context(GetPaintController());
-
-    InitRootChunk();
-    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 300, 300));
-    CommitAndFinishCycle();
-
-    InitRootChunk();
-    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 300, 300));
-    DrawRect(context, first, kForegroundType, IntRect(100, 100, 300, 300));
-    CommitAndFinishCycle();
-  }
-
-  void TestLessDrawing() {
-    FakeDisplayItemClient first("first");
-    GraphicsContext context(GetPaintController());
-
-    InitRootChunk();
-    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 300, 300));
-    DrawRect(context, first, kForegroundType, IntRect(100, 100, 300, 300));
-    CommitAndFinishCycle();
-
-    InitRootChunk();
-    DrawRect(context, first, kBackgroundType, IntRect(100, 100, 300, 300));
-    CommitAndFinishCycle();
-  }
-
-  void TestChangeDrawingInSubsequence() {
-    FakeDisplayItemClient first("first");
-    GraphicsContext context(GetPaintController());
-    InitRootChunk();
-    {
-      SubsequenceRecorder r(context, first);
-      DrawRect(context, first, kBackgroundType, IntRect(100, 100, 300, 300));
-      DrawRect(context, first, kForegroundType, IntRect(100, 100, 300, 300));
-    }
-    CommitAndFinishCycle();
-
-    InitRootChunk();
-    {
-      EXPECT_FALSE(
-          SubsequenceRecorder::UseCachedSubsequenceIfPossible(context, first));
-      SubsequenceRecorder r(context, first);
-      DrawRect(context, first, kBackgroundType, IntRect(200, 200, 300, 300));
-      DrawRect(context, first, kForegroundType, IntRect(100, 100, 300, 300));
-    }
-    CommitAndFinishCycle();
-  }
-
-  void TestMoreDrawingInSubsequence() {
-    FakeDisplayItemClient first("first");
-    GraphicsContext context(GetPaintController());
-
-    InitRootChunk();
-    {
-      SubsequenceRecorder r(context, first);
-      DrawRect(context, first, kBackgroundType, IntRect(100, 100, 300, 300));
-    }
-    CommitAndFinishCycle();
-
-    InitRootChunk();
-    {
-      EXPECT_FALSE(
-          SubsequenceRecorder::UseCachedSubsequenceIfPossible(context, first));
-      SubsequenceRecorder r(context, first);
-      DrawRect(context, first, kBackgroundType, IntRect(100, 100, 300, 300));
-      DrawRect(context, first, kForegroundType, IntRect(100, 100, 300, 300));
-    }
-    CommitAndFinishCycle();
-  }
-
-  void TestLessDrawingInSubsequence() {
-    FakeDisplayItemClient first("first");
-    GraphicsContext context(GetPaintController());
-
-    InitRootChunk();
-    {
-      SubsequenceRecorder r(context, first);
-      DrawRect(context, first, kBackgroundType, IntRect(100, 100, 300, 300));
-      DrawRect(context, first, kForegroundType, IntRect(100, 100, 300, 300));
-    }
-    CommitAndFinishCycle();
-
-    InitRootChunk();
-    {
-      EXPECT_FALSE(
-          SubsequenceRecorder::UseCachedSubsequenceIfPossible(context, first));
-      SubsequenceRecorder r(context, first);
-      DrawRect(context, first, kBackgroundType, IntRect(100, 100, 300, 300));
-    }
-    CommitAndFinishCycle();
-  }
-
-  void TestInvalidationInSubsequence() {
-    FakeDisplayItemClient container("container");
-    FakeDisplayItemClient content("content");
-    GraphicsContext context(GetPaintController());
-
-    InitRootChunk();
-    {
-      SubsequenceRecorder r(context, container);
-      DrawRect(context, content, kBackgroundType, IntRect(100, 100, 300, 300));
-    }
-    CommitAndFinishCycle();
-
-    content.Invalidate();
-    InitRootChunk();
-    // Leave container not invalidated.
-    {
-      EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
-          context, container));
-      SubsequenceRecorder r(context, container);
-      DrawRect(context, content, kBackgroundType, IntRect(100, 100, 300, 300));
-    }
-    CommitAndFinishCycle();
-  }
-
-  void TestSubsequenceBecomesEmpty() {
-    FakeDisplayItemClient target("target");
-    GraphicsContext context(GetPaintController());
-
-    InitRootChunk();
-    {
-      SubsequenceRecorder r(context, target);
-      DrawRect(context, target, kBackgroundType, IntRect(100, 100, 300, 300));
-    }
-    CommitAndFinishCycle();
-
-    InitRootChunk();
-    {
-      EXPECT_FALSE(
-          SubsequenceRecorder::UseCachedSubsequenceIfPossible(context, target));
-      SubsequenceRecorder r(context, target);
-    }
-    CommitAndFinishCycle();
-  }
-};
-
-TEST_F(PaintControllerUnderInvalidationTest, ChangeDrawing) {
-  EXPECT_DEATH(TestChangeDrawing(), "under-invalidation: display item changed");
-}
-
-TEST_F(PaintControllerUnderInvalidationTest, MoreDrawing) {
-  // We don't detect under-invalidation in this case, and PaintController can
-  // also handle the case gracefully.
-  TestMoreDrawing();
-}
-
-TEST_F(PaintControllerUnderInvalidationTest, LessDrawing) {
-  // We don't detect under-invalidation in this case, and PaintController can
-  // also handle the case gracefully.
-  TestLessDrawing();
-}
-
-TEST_F(PaintControllerUnderInvalidationTest, ChangeDrawingInSubsequence) {
-  EXPECT_DEATH(TestChangeDrawingInSubsequence(),
-               "In cached subsequence for first.*"
-               "under-invalidation: display item changed");
-}
-
-TEST_F(PaintControllerUnderInvalidationTest, MoreDrawingInSubsequence) {
-  // TODO(wangxianzhu): Detect more drawings at the end of a subsequence.
-  TestMoreDrawingInSubsequence();
-}
-
-TEST_F(PaintControllerUnderInvalidationTest, LessDrawingInSubsequence) {
-  EXPECT_DEATH(TestLessDrawingInSubsequence(),
-               "In cached subsequence for first.*"
-               "under-invalidation: chunk changed");
-}
-
-TEST_F(PaintControllerUnderInvalidationTest, InvalidationInSubsequence) {
-  // We allow invalidated display item clients as long as they would produce the
-  // same display items. The cases of changed display items are tested by other
-  // test cases.
-  TestInvalidationInSubsequence();
-}
-
-TEST_F(PaintControllerUnderInvalidationTest, SubsequenceBecomesEmpty) {
-  EXPECT_DEATH(TestSubsequenceBecomesEmpty(),
-               "In cached subsequence for target.*"
-               "under-invalidation: new subsequence wrong length");
-}
-
-TEST_F(PaintControllerUnderInvalidationTest, SkipCacheInSubsequence) {
-  FakeDisplayItemClient container("container");
-  FakeDisplayItemClient content("content");
-  GraphicsContext context(GetPaintController());
-
-  InitRootChunk();
-  {
-    SubsequenceRecorder r(context, container);
-    {
-      DisplayItemCacheSkipper cache_skipper(context);
-      DrawRect(context, content, kBackgroundType, IntRect(100, 100, 300, 300));
-    }
-    DrawRect(context, content, kForegroundType, IntRect(200, 200, 400, 400));
-  }
-  CommitAndFinishCycle();
-
-  InitRootChunk();
-  {
-    EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
-        context, container));
-    SubsequenceRecorder r(context, container);
-    {
-      DisplayItemCacheSkipper cache_skipper(context);
-      DrawRect(context, content, kBackgroundType, IntRect(200, 200, 400, 400));
-    }
-    DrawRect(context, content, kForegroundType, IntRect(200, 200, 400, 400));
-  }
-  CommitAndFinishCycle();
-}
-
-TEST_F(PaintControllerUnderInvalidationTest,
-       EmptySubsequenceInCachedSubsequence) {
-  FakeDisplayItemClient container("container");
-  FakeDisplayItemClient content("content");
-  GraphicsContext context(GetPaintController());
-
-  InitRootChunk();
-  {
-    SubsequenceRecorder r(context, container);
-    DrawRect(context, container, kBackgroundType, IntRect(100, 100, 300, 300));
-    { SubsequenceRecorder r1(context, content); }
-    DrawRect(context, container, kForegroundType, IntRect(100, 100, 300, 300));
-  }
-  CommitAndFinishCycle();
-
-  InitRootChunk();
-  {
-    EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
-        context, container));
-    SubsequenceRecorder r(context, container);
-    DrawRect(context, container, kBackgroundType, IntRect(100, 100, 300, 300));
-    EXPECT_FALSE(
-        SubsequenceRecorder::UseCachedSubsequenceIfPossible(context, content));
-    { SubsequenceRecorder r1(context, content); }
-    DrawRect(context, container, kForegroundType, IntRect(100, 100, 300, 300));
-  }
-  CommitAndFinishCycle();
-}
-
 #endif  // defined(GTEST_HAS_DEATH_TEST) && !defined(OS_ANDROID)
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_controller_test.h b/third_party/blink/renderer/platform/graphics/paint/paint_controller_test.h
index e162837..ca7223d5 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_controller_test.h
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_controller_test.h
@@ -45,6 +45,10 @@
                              DisplayItem::kUninitializedType),
         paint_controller_(std::make_unique<PaintController>()) {}
 
+  void SetUp() override {
+    testing::FLAGS_gtest_death_test_style = "threadsafe";
+  }
+
   void InitRootChunk() { InitRootChunk(GetPaintController()); }
   void InitRootChunk(PaintController& paint_controller) {
     paint_controller.UpdateCurrentPaintChunkProperties(
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_under_invalidation_checker.cc b/third_party/blink/renderer/platform/graphics/paint/paint_under_invalidation_checker.cc
new file mode 100644
index 0000000..f4eff14
--- /dev/null
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_under_invalidation_checker.cc
@@ -0,0 +1,249 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/platform/graphics/paint/paint_under_invalidation_checker.h"
+
+#include "base/logging.h"
+#include "third_party/blink/renderer/platform/graphics/logging_canvas.h"
+#include "third_party/blink/renderer/platform/graphics/paint/drawing_display_item.h"
+#include "third_party/blink/renderer/platform/graphics/paint/paint_controller.h"
+
+namespace blink {
+
+PaintUnderInvalidationChecker::PaintUnderInvalidationChecker(
+    PaintController& paint_controller)
+    : paint_controller_(paint_controller) {
+#if DCHECK_IS_ON()
+  DCHECK(RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled());
+  DCHECK_EQ(paint_controller_.GetUsage(), PaintController::kMultiplePaints);
+#endif
+}
+
+PaintUnderInvalidationChecker::~PaintUnderInvalidationChecker() {
+  DCHECK(!IsChecking());
+}
+
+bool PaintUnderInvalidationChecker::IsChecking() const {
+  if (old_item_index_ != kNotFound) {
+    DCHECK(RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled());
+    DCHECK(!subsequence_client_ ||
+           (old_chunk_index_ != kNotFound && new_chunk_index_ != kNotFound));
+    return true;
+  }
+
+  DCHECK(!subsequence_client_);
+  DCHECK_EQ(old_chunk_index_, kNotFound);
+  DCHECK_EQ(new_chunk_index_, kNotFound);
+  return false;
+}
+
+bool PaintUnderInvalidationChecker::IsCheckingSubsequence() const {
+  if (subsequence_client_) {
+    DCHECK(IsChecking());
+    return true;
+  }
+  return false;
+}
+
+void PaintUnderInvalidationChecker::Stop() {
+  DCHECK(IsChecking());
+  old_chunk_index_ = kNotFound;
+  new_chunk_index_ = kNotFound;
+  old_item_index_ = kNotFound;
+  subsequence_client_ = nullptr;
+}
+
+void PaintUnderInvalidationChecker::WouldUseCachedItem(
+    wtf_size_t old_item_index) {
+  DCHECK(!IsChecking());
+  old_item_index_ = old_item_index;
+}
+
+void PaintUnderInvalidationChecker::CheckNewItem() {
+  DCHECK(IsChecking());
+
+  if (paint_controller_.IsSkippingCache()) {
+    // We allow cache skipping and temporary under-invalidation in cached
+    // subsequences. See the usage of DisplayItemCacheSkipper in BoxPainter.
+    Stop();
+    // Match the remaining display items in the subsequence normally.
+    paint_controller_.next_item_to_match_ = old_item_index_;
+    paint_controller_.next_item_to_index_ = old_item_index_;
+    return;
+  }
+
+  const auto& new_item = NewDisplayItemList().back();
+  if (old_item_index_ >= OldDisplayItemList().size())
+    ShowItemError("extra display item", new_item);
+
+  auto& old_item = OldDisplayItemList()[old_item_index_];
+  if (!new_item.EqualsForUnderInvalidation(old_item))
+    ShowItemError("display item changed", new_item, &old_item);
+
+  // Discard the forced repainted display item and move the cached item into
+  // new_display_item_list_. This is to align with the
+  // non-under-invalidation-checking path to empty the original cached slot,
+  // leaving only disappeared or invalidated display items in the old list after
+  // painting.
+  NewDisplayItemList().ReplaceLastByMoving(old_item);
+
+  if (subsequence_client_) {
+    // We are checking under-invalidation of a cached subsequence.
+    ++old_item_index_;
+  } else {
+    // We have checked the single item for under-invalidation.
+    Stop();
+  }
+}
+
+void PaintUnderInvalidationChecker::WouldUseCachedSubsequence(
+    const DisplayItemClient& client) {
+  DCHECK(!IsChecking());
+
+  const auto* markers = paint_controller_.GetSubsequenceMarkers(client);
+  DCHECK(markers);
+  old_chunk_index_ = markers->start_chunk_index;
+  new_chunk_index_ = NewPaintChunks().size();
+  old_item_index_ = OldPaintChunks()[markers->start_chunk_index].begin_index;
+  subsequence_client_ = &client;
+}
+
+void PaintUnderInvalidationChecker::CheckNewChunk() {
+  DCHECK(IsChecking());
+  if (!IsCheckingSubsequence())
+    return;
+
+  if (NewPaintChunks().size() > new_chunk_index_ + 1) {
+    // Check the previous new chunk (pointed by new_chunk_index_, before the
+    // just added chunk) which is now complete. The just added chunk will be
+    // checked when it's complete later in CheckNewChunk() or
+    // WillEndSubsequence().
+    CheckNewChunkInternal();
+  }
+}
+
+void PaintUnderInvalidationChecker::WillEndSubsequence(
+    const DisplayItemClient& client,
+    wtf_size_t start_chunk_index) {
+  DCHECK(IsChecking());
+  if (!IsCheckingSubsequence())
+    return;
+
+  const auto* markers = paint_controller_.GetSubsequenceMarkers(client);
+  if (!markers) {
+    if (start_chunk_index != NewPaintChunks().size())
+      ShowSubsequenceError("unexpected subsequence", &client);
+  } else if (markers->end_chunk_index - markers->start_chunk_index !=
+             NewPaintChunks().size() - start_chunk_index) {
+    ShowSubsequenceError("new subsequence wrong length", &client);
+  } else {
+    // Now we know that the last chunk in the subsequence is complete. See also
+    // CheckNewChunk().
+    auto end_chunk_index = NewPaintChunks().size();
+    if (new_chunk_index_ < end_chunk_index) {
+      DCHECK_EQ(new_chunk_index_ + 1, end_chunk_index);
+      CheckNewChunkInternal();
+      DCHECK_EQ(new_chunk_index_, end_chunk_index);
+    }
+  }
+
+  if (subsequence_client_ == &client)
+    Stop();
+}
+
+void PaintUnderInvalidationChecker::CheckNewChunkInternal() {
+  DCHECK(subsequence_client_);
+  const auto* markers =
+      paint_controller_.GetSubsequenceMarkers(*subsequence_client_);
+  DCHECK(markers);
+  const auto& new_chunk = NewPaintChunks()[new_chunk_index_];
+  if (old_chunk_index_ >= markers->end_chunk_index) {
+    ShowSubsequenceError("extra chunk", nullptr, &new_chunk);
+  } else {
+    const auto& old_chunk = OldPaintChunks()[old_chunk_index_];
+    if (!old_chunk.EqualsForUnderInvalidationChecking(new_chunk)) {
+      ShowSubsequenceError("chunk changed", nullptr, &new_chunk, &old_chunk);
+    }
+  }
+  new_chunk_index_++;
+  old_chunk_index_++;
+}
+
+void PaintUnderInvalidationChecker::ShowItemError(
+    const char* reason,
+    const DisplayItem& new_item,
+    const DisplayItem* old_item) const {
+  if (subsequence_client_) {
+    LOG(ERROR) << "(In cached subsequence for " << subsequence_client_ << ")";
+  }
+  LOG(ERROR) << "Under-invalidation: " << reason;
+#if DCHECK_IS_ON()
+  LOG(ERROR) << "New display item: " << new_item.AsDebugString();
+  if (old_item)
+    LOG(ERROR) << "Old display item: " << old_item->AsDebugString();
+  LOG(ERROR) << "See http://crbug.com/619103.";
+
+  if (auto* new_drawing = DynamicTo<DrawingDisplayItem>(new_item)) {
+    auto* new_record = new_drawing->GetPaintRecord().get();
+    LOG(INFO) << "new record:\n"
+              << (new_record ? RecordAsDebugString(*new_record).Utf8() : "{}");
+  }
+  if (auto* old_drawing = DynamicTo<DrawingDisplayItem>(old_item)) {
+    auto* old_record = old_drawing->GetPaintRecord().get();
+    LOG(INFO) << "old record:\n"
+              << (old_record ? RecordAsDebugString(*old_record).Utf8() : "{}");
+  }
+
+  paint_controller_.ShowDebugData();
+#else
+  LOG(ERROR) << "Run a build with DCHECK on to get more details.";
+#endif
+  LOG(FATAL) << "See https://crbug.com/619103.";
+}
+
+void PaintUnderInvalidationChecker::ShowSubsequenceError(
+    const char* reason,
+    const DisplayItemClient* client,
+    const PaintChunk* new_chunk,
+    const PaintChunk* old_chunk) {
+  if (subsequence_client_) {
+    LOG(ERROR) << "(In cached subsequence for " << subsequence_client_ << ")";
+  }
+  LOG(ERROR) << "Under-invalidation: " << reason;
+  if (client) {
+    // |client| may be different from |subsequence_client_| if the error occurs
+    // in a descendant subsequence of the cached subsequence.
+    LOG(ERROR) << "Subsequence client: " << client;
+  }
+  if (new_chunk)
+    LOG(ERROR) << "New paint chunk: " << *new_chunk;
+  if (old_chunk)
+    LOG(ERROR) << "Old paint chunk: " << *old_chunk;
+#if DCHECK_IS_ON()
+  paint_controller_.ShowDebugData();
+#else
+  LOG(ERROR) << "Run a build with DCHECK on to get more details.";
+#endif
+  LOG(FATAL) << "See https://crbug.com/619103.";
+}
+
+const Vector<PaintChunk>& PaintUnderInvalidationChecker::OldPaintChunks()
+    const {
+  return paint_controller_.current_paint_artifact_->PaintChunks();
+}
+
+const Vector<PaintChunk>& PaintUnderInvalidationChecker::NewPaintChunks()
+    const {
+  return paint_controller_.new_paint_artifact_->PaintChunks();
+}
+
+DisplayItemList& PaintUnderInvalidationChecker::OldDisplayItemList() {
+  return paint_controller_.current_paint_artifact_->GetDisplayItemList();
+}
+
+DisplayItemList& PaintUnderInvalidationChecker::NewDisplayItemList() {
+  return paint_controller_.new_paint_artifact_->GetDisplayItemList();
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_under_invalidation_checker.h b/third_party/blink/renderer/platform/graphics/paint/paint_under_invalidation_checker.h
new file mode 100644
index 0000000..aac34a47
--- /dev/null
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_under_invalidation_checker.h
@@ -0,0 +1,77 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_PAINT_PAINT_UNDER_INVALIDATION_CHECKER_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_PAINT_PAINT_UNDER_INVALIDATION_CHECKER_H_
+
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
+
+namespace blink {
+
+class DisplayItem;
+class DisplayItemClient;
+class DisplayItemList;
+class PaintController;
+struct PaintChunk;
+
+// If RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled(),
+// when PaintController can use a cached display item or a cached subsequence,
+// it lets the client paint instead of using the cache, and this class checks
+// whether the painting is the same as the cache.
+class PaintUnderInvalidationChecker {
+ public:
+  explicit PaintUnderInvalidationChecker(PaintController& paint_controller);
+  ~PaintUnderInvalidationChecker();
+
+  bool IsChecking() const;
+
+  // Called from PaintController::UseCachedItemIfPossible() to inform that
+  // PaintController would use a cached display item if we were not checking
+  // under-invalidations.
+  void WouldUseCachedItem(wtf_size_t old_item_index);
+  void CheckNewItem();
+
+  // Called from PaintController::UseCachedSubsequenceIfPossible() to inform
+  // that PaintController would use a cached subsequence if we were not checking
+  // under-invalidations.
+  void WouldUseCachedSubsequence(const DisplayItemClient&);
+  void CheckNewChunk();
+  void WillEndSubsequence(const DisplayItemClient& client,
+                          wtf_size_t start_chunk_index);
+
+ private:
+  bool IsCheckingSubsequence() const;
+  void Stop();
+  void CheckNewChunkInternal();
+  void ShowItemError(const char* reason,
+                     const DisplayItem& new_item,
+                     const DisplayItem* old_item = nullptr) const;
+  void ShowSubsequenceError(const char* reason,
+                            const DisplayItemClient* = nullptr,
+                            const PaintChunk* new_chunk = nullptr,
+                            const PaintChunk* old_chunk = nullptr);
+
+  const Vector<PaintChunk>& OldPaintChunks() const;
+  const Vector<PaintChunk>& NewPaintChunks() const;
+  DisplayItemList& OldDisplayItemList();
+  DisplayItemList& NewDisplayItemList();
+
+  PaintController& paint_controller_;
+
+  // Points to the cached display item which is expected to match the nextnew
+  // display item.
+  wtf_size_t old_item_index_ = kNotFound;
+  // Points to the cached paint chunk which is expected to match the next
+  // complete new paint chunk.
+  wtf_size_t old_chunk_index_ = kNotFound;
+  // Points to the next new paint chunk which will be checked when it's
+  // complete.
+  wtf_size_t new_chunk_index_ = kNotFound;
+  const DisplayItemClient* subsequence_client_ = nullptr;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_PAINT_PAINT_UNDER_INVALIDATION_CHECKER_H_
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_under_invalidation_checker_test.cc b/third_party/blink/renderer/platform/graphics/paint/paint_under_invalidation_checker_test.cc
new file mode 100644
index 0000000..1b22a95
--- /dev/null
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_under_invalidation_checker_test.cc
@@ -0,0 +1,291 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "build/build_config.h"
+#include "third_party/blink/renderer/platform/graphics/graphics_context.h"
+#include "third_party/blink/renderer/platform/graphics/paint/display_item_cache_skipper.h"
+#include "third_party/blink/renderer/platform/graphics/paint/paint_controller_test.h"
+#include "third_party/blink/renderer/platform/graphics/paint/subsequence_recorder.h"
+#include "third_party/blink/renderer/platform/testing/paint_test_configurations.h"
+
+using testing::ElementsAre;
+
+namespace blink {
+
+// Death tests don't work properly on Android.
+#if defined(GTEST_HAS_DEATH_TEST) && !defined(OS_ANDROID)
+
+class PaintControllerUnderInvalidationTest
+    : private ScopedPaintUnderInvalidationCheckingForTest,
+      public PaintControllerTestBase {
+ public:
+  PaintControllerUnderInvalidationTest()
+      : ScopedPaintUnderInvalidationCheckingForTest(true) {}
+};
+
+TEST_F(PaintControllerUnderInvalidationTest, ChangeDrawing) {
+  auto test = [&]() {
+    FakeDisplayItemClient first("first");
+    GraphicsContext context(GetPaintController());
+
+    InitRootChunk();
+    DrawRect(context, first, kBackgroundType, IntRect(1, 1, 1, 1));
+    DrawRect(context, first, kForegroundType, IntRect(1, 1, 3, 3));
+    CommitAndFinishCycle();
+
+    InitRootChunk();
+    DrawRect(context, first, kBackgroundType, IntRect(2, 2, 3, 3));
+    DrawRect(context, first, kForegroundType, IntRect(1, 1, 3, 3));
+    CommitAndFinishCycle();
+  };
+
+  EXPECT_DEATH(test(),
+               "Under-invalidation: display item changed\n"
+#if DCHECK_IS_ON()
+               ".*New display item:.*2,2 3x3.*\n"
+               ".*Old display item:.*1,1 1x1"
+#endif
+  );
+}
+
+TEST_F(PaintControllerUnderInvalidationTest, MoreDrawing) {
+  // We don't detect under-invalidation in this case, and PaintController can
+  // also handle the case gracefully.
+  FakeDisplayItemClient first("first");
+  GraphicsContext context(GetPaintController());
+
+  InitRootChunk();
+  DrawRect(context, first, kBackgroundType, IntRect(1, 1, 1, 1));
+  CommitAndFinishCycle();
+
+  InitRootChunk();
+  DrawRect(context, first, kBackgroundType, IntRect(1, 1, 1, 1));
+  DrawRect(context, first, kForegroundType, IntRect(1, 1, 3, 3));
+  CommitAndFinishCycle();
+}
+
+TEST_F(PaintControllerUnderInvalidationTest, LessDrawing) {
+  // We don't detect under-invalidation in this case, and PaintController can
+  // also handle the case gracefully.
+  FakeDisplayItemClient first("first");
+  GraphicsContext context(GetPaintController());
+
+  InitRootChunk();
+  DrawRect(context, first, kBackgroundType, IntRect(1, 1, 1, 1));
+  DrawRect(context, first, kForegroundType, IntRect(1, 1, 3, 3));
+  CommitAndFinishCycle();
+
+  InitRootChunk();
+  DrawRect(context, first, kBackgroundType, IntRect(1, 1, 1, 1));
+  CommitAndFinishCycle();
+}
+
+TEST_F(PaintControllerUnderInvalidationTest, ChangeDrawingInSubsequence) {
+  auto test = [&]() {
+    FakeDisplayItemClient first("first");
+    GraphicsContext context(GetPaintController());
+    InitRootChunk();
+    {
+      SubsequenceRecorder r(context, first);
+      DrawRect(context, first, kBackgroundType, IntRect(1, 1, 1, 1));
+      DrawRect(context, first, kForegroundType, IntRect(1, 1, 3, 3));
+    }
+    CommitAndFinishCycle();
+
+    InitRootChunk();
+    {
+      EXPECT_FALSE(
+          SubsequenceRecorder::UseCachedSubsequenceIfPossible(context, first));
+      SubsequenceRecorder r(context, first);
+      DrawRect(context, first, kBackgroundType, IntRect(2, 2, 1, 1));
+      DrawRect(context, first, kForegroundType, IntRect(1, 1, 3, 3));
+    }
+    CommitAndFinishCycle();
+  };
+
+  EXPECT_DEATH(test(),
+               "In cached subsequence for .*first.*\n"
+               ".*Under-invalidation: display item changed\n"
+#if DCHECK_IS_ON()
+               ".*New display item:.*2,2 1x1.*\n"
+               ".*Old display item:.*1,1 1x1"
+#endif
+  );
+}
+
+TEST_F(PaintControllerUnderInvalidationTest, MoreDrawingInSubsequence) {
+  auto test = [&]() {
+    FakeDisplayItemClient first("first");
+    GraphicsContext context(GetPaintController());
+
+    InitRootChunk();
+    {
+      SubsequenceRecorder r(context, first);
+      DrawRect(context, first, kBackgroundType, IntRect(1, 1, 1, 1));
+    }
+    CommitAndFinishCycle();
+
+    InitRootChunk();
+    {
+      EXPECT_FALSE(
+          SubsequenceRecorder::UseCachedSubsequenceIfPossible(context, first));
+      SubsequenceRecorder r(context, first);
+      DrawRect(context, first, kBackgroundType, IntRect(1, 1, 1, 1));
+      DrawRect(context, first, kForegroundType, IntRect(1, 1, 3, 3));
+    }
+    CommitAndFinishCycle();
+  };
+
+  EXPECT_DEATH(test(),
+               "In cached subsequence for .*first.*\n"
+               ".*Under-invalidation: extra display item\n"
+#if DCHECK_IS_ON()
+               ".*New display item:.*1,1 3x3"
+#endif
+  );
+}
+
+TEST_F(PaintControllerUnderInvalidationTest, LessDrawingInSubsequence) {
+  auto test = [&]() {
+    FakeDisplayItemClient first("first");
+    GraphicsContext context(GetPaintController());
+
+    InitRootChunk();
+    {
+      SubsequenceRecorder r(context, first);
+      DrawRect(context, first, kBackgroundType, IntRect(1, 1, 3, 3));
+      DrawRect(context, first, kForegroundType, IntRect(1, 1, 3, 3));
+    }
+    CommitAndFinishCycle();
+
+    InitRootChunk();
+    {
+      EXPECT_FALSE(
+          SubsequenceRecorder::UseCachedSubsequenceIfPossible(context, first));
+      SubsequenceRecorder r(context, first);
+      DrawRect(context, first, kBackgroundType, IntRect(1, 1, 3, 3));
+    }
+    CommitAndFinishCycle();
+  };
+
+  EXPECT_DEATH(test(),
+               "In cached subsequence for .*first.*\n"
+               ".*Under-invalidation: chunk changed");
+}
+
+TEST_F(PaintControllerUnderInvalidationTest, InvalidationInSubsequence) {
+  // We allow invalidated display item clients as long as they would produce the
+  // same display items. The cases of changed display items are tested by other
+  // test cases.
+  FakeDisplayItemClient container("container");
+  FakeDisplayItemClient content("content");
+  GraphicsContext context(GetPaintController());
+
+  InitRootChunk();
+  {
+    SubsequenceRecorder r(context, container);
+    DrawRect(context, content, kBackgroundType, IntRect(1, 1, 3, 3));
+  }
+  CommitAndFinishCycle();
+
+  content.Invalidate();
+  InitRootChunk();
+  // Leave container not invalidated.
+  {
+    EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
+        context, container));
+    SubsequenceRecorder r(context, container);
+    DrawRect(context, content, kBackgroundType, IntRect(1, 1, 3, 3));
+  }
+  CommitAndFinishCycle();
+}
+
+TEST_F(PaintControllerUnderInvalidationTest, SubsequenceBecomesEmpty) {
+  auto test = [&]() {
+    FakeDisplayItemClient target("target");
+    GraphicsContext context(GetPaintController());
+
+    InitRootChunk();
+    {
+      SubsequenceRecorder r(context, target);
+      DrawRect(context, target, kBackgroundType, IntRect(1, 1, 3, 3));
+    }
+    CommitAndFinishCycle();
+
+    InitRootChunk();
+    {
+      EXPECT_FALSE(
+          SubsequenceRecorder::UseCachedSubsequenceIfPossible(context, target));
+      SubsequenceRecorder r(context, target);
+    }
+    CommitAndFinishCycle();
+  };
+
+  EXPECT_DEATH(test(),
+               "In cached subsequence for .*target.*\n"
+               ".*Under-invalidation: new subsequence wrong length");
+}
+
+TEST_F(PaintControllerUnderInvalidationTest, SkipCacheInSubsequence) {
+  FakeDisplayItemClient container("container");
+  FakeDisplayItemClient content("content");
+  GraphicsContext context(GetPaintController());
+
+  InitRootChunk();
+  {
+    SubsequenceRecorder r(context, container);
+    {
+      DisplayItemCacheSkipper cache_skipper(context);
+      DrawRect(context, content, kBackgroundType, IntRect(1, 1, 3, 3));
+    }
+    DrawRect(context, content, kForegroundType, IntRect(2, 2, 4, 4));
+  }
+  CommitAndFinishCycle();
+
+  InitRootChunk();
+  {
+    EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
+        context, container));
+    SubsequenceRecorder r(context, container);
+    {
+      DisplayItemCacheSkipper cache_skipper(context);
+      DrawRect(context, content, kBackgroundType, IntRect(2, 2, 4, 4));
+    }
+    DrawRect(context, content, kForegroundType, IntRect(2, 2, 4, 4));
+  }
+  CommitAndFinishCycle();
+}
+
+TEST_F(PaintControllerUnderInvalidationTest,
+       EmptySubsequenceInCachedSubsequence) {
+  FakeDisplayItemClient container("container");
+  FakeDisplayItemClient content("content");
+  GraphicsContext context(GetPaintController());
+
+  InitRootChunk();
+  {
+    SubsequenceRecorder r(context, container);
+    DrawRect(context, container, kBackgroundType, IntRect(1, 1, 3, 3));
+    { SubsequenceRecorder r1(context, content); }
+    DrawRect(context, container, kForegroundType, IntRect(1, 1, 3, 3));
+  }
+  CommitAndFinishCycle();
+
+  InitRootChunk();
+  {
+    EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
+        context, container));
+    SubsequenceRecorder r(context, container);
+    DrawRect(context, container, kBackgroundType, IntRect(1, 1, 3, 3));
+    EXPECT_FALSE(
+        SubsequenceRecorder::UseCachedSubsequenceIfPossible(context, content));
+    { SubsequenceRecorder r1(context, content); }
+    DrawRect(context, container, kForegroundType, IntRect(1, 1, 3, 3));
+  }
+  CommitAndFinishCycle();
+}
+
+#endif  // defined(GTEST_HAS_DEATH_TEST) && !defined(OS_ANDROID)
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/paint/subsequence_recorder.h b/third_party/blink/renderer/platform/graphics/paint/subsequence_recorder.h
index d85a143d3..5100696e 100644
--- a/third_party/blink/renderer/platform/graphics/paint/subsequence_recorder.h
+++ b/third_party/blink/renderer/platform/graphics/paint/subsequence_recorder.h
@@ -36,20 +36,17 @@
   }
 
   SubsequenceRecorder(GraphicsContext& context, const DisplayItemClient& client)
-      : paint_controller_(context.GetPaintController()), client_(client) {
-    paint_controller_.BeginSubsequence(subsequence_index_, start_chunk_index_);
+      : paint_controller_(context.GetPaintController()) {
+    subsequence_index_ = paint_controller_.BeginSubsequence(client);
   }
 
   ~SubsequenceRecorder() {
-    paint_controller_.EndSubsequence(client_, subsequence_index_,
-                                     start_chunk_index_);
+    paint_controller_.EndSubsequence(subsequence_index_);
   }
 
  private:
   PaintController& paint_controller_;
-  const DisplayItemClient& client_;
   wtf_size_t subsequence_index_;
-  wtf_size_t start_chunk_index_;
 
   DISALLOW_COPY_AND_ASSIGN(SubsequenceRecorder);
 };
diff --git a/third_party/blink/renderer/platform/testing/testing_platform_support.h b/third_party/blink/renderer/platform/testing/testing_platform_support.h
index 945118f..c48e932 100644
--- a/third_party/blink/renderer/platform/testing/testing_platform_support.h
+++ b/third_party/blink/renderer/platform/testing/testing_platform_support.h
@@ -71,7 +71,7 @@
 
   virtual void RunUntilIdle();
   void SetThreadedAnimationEnabled(bool enabled);
-  void SetUseZoomForDSF(bool enabeld);
+  void SetUseZoomForDSF(bool enabled);
 
   // Overrides the handling of GetInterface on the platform's associated
   // interface provider.
diff --git a/third_party/blink/web_tests/LeakExpectations b/third_party/blink/web_tests/LeakExpectations
index bdff475..27b158d1 100644
--- a/third_party/blink/web_tests/LeakExpectations
+++ b/third_party/blink/web_tests/LeakExpectations
@@ -170,6 +170,15 @@
 # Sheriff 2021-05-03
 crbug.com/1197465 [ Linux ] virtual/scroll-unification/fast/events/mouse-cursor-no-mousemove.html [ Pass Failure ]
 
+# Sheriff 2021-06-09
+crbug.com/1217831 [ Linux ] virtual/plz-service-worker/http/tests/devtools/console/console-format-collections.js [ Pass Failure ]
+crbug.com/1217831 [ Linux ] virtual/plz-service-worker/http/tests/devtools/console/console-repeat-count.js [ Pass Failure ]
+crbug.com/1217831 [ Linux ] virtual/shared_array_buffer_on_desktop/http/tests/devtools/console/console-format-collections.js [ Pass Failure ]
+crbug.com/1217831 [ Linux ] virtual/shared_array_buffer_on_desktop/http/tests/devtools/sources/debugger-async/async-callstack-middle-run.js [ Pass Failure ]
+crbug.com/1217831 [ Linux ] virtual/shared_array_buffer_on_desktop/http/tests/devtools/sources/debugger-breakpoints/event-listener-breakpoints-script-first-stmt.js [ Pass Failure ]
+crbug.com/1217831 [ Linux ] virtual/shared_array_buffer_on_desktop/http/tests/devtools/sources/debugger-frameworks/frameworks-ignore-list-by-source-code.js [ Pass Failure ]
+crbug.com/1217831 [ Linux ] virtual/synchronous_html_parser/http/tests/devtools/sources/debugger-breakpoints/event-listener-breakpoints-script-first-stmt.js [ Pass Failure ]
+
 ###########################################################################
 # WARNING: Memory leaks must be fixed asap. Sheriff is expected to revert #
 # culprit CLs instead of suppressing the leaks. If you have any question, #
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 57974b8..b0bcd9e4 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -6156,6 +6156,9 @@
 # No support for key combinations like Alt + c in testdriver.Actions for content_shell
 crbug.com/893480 external/wpt/uievents/interface/keyboard-accesskey-click-event.html [ Timeout ]
 
+# Unblock https://crrev.com/c/2944898
+crbug.com/1193250 http/tests/devtools/console/console-format.js [ Pass Failure ]
+
 # Timing out on Fuchsia
 crbug.com/1171283 [ Fuchsia ] virtual/threaded/synthetic_gestures/synthetic-pinch-zoom-gesture-touchscreen-zoom-out-slow.html [ Pass Timeout ]
 
@@ -6846,10 +6849,5 @@
 crbug.com/1215949 external/wpt/pointerevents/pointerevent_iframe-touch-action-none_touch.html [ Pass Timeout ]
 crbug.com/1216139 virtual/bfcache/http/tests/devtools/bfcache/bfcache-elements-update.js [ Pass Failure ]
 
-# TODO(csmartalton): Tessellation improves these tests. Rebaseline after Skia roll.
-skbug.com/10419 [ Fuchsia ] tables/mozilla/bugs/bug2479-3.html [ Pass Failure ]
-skbug.com/10419 [ Fuchsia ] tables/mozilla/bugs/bug7342.html [ Pass Failure ]
-skbug.com/10419 [ Fuchsia ] tables/mozilla/bugs/bug59354.html [ Pass Failure ]
-
 # Sheriff 2021-06-07
 crbug.com/1217342 [ Linux ] http/tests/devtools/sources/debugger-async/async-callstack-set-interval.js [ Pass Failure ]
diff --git a/third_party/blink/web_tests/external/wpt/css/css-tables/tentative/table-height-redistribution.html b/third_party/blink/web_tests/external/wpt/css/css-tables/tentative/table-height-redistribution.html
index 9c587e31..c0f48e4 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-tables/tentative/table-height-redistribution.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-tables/tentative/table-height-redistribution.html
@@ -4,13 +4,15 @@
 <script src='/resources/testharnessreport.js'></script>
 <script src="/resources/check-layout-th.js"></script>
 <link rel='stylesheet' href='../support/base.css' />
+<link rel='stylesheet' href='./support/table-tentative.css' />
 <link rel="author" title="Aleks Totic" href="atotic@chromium.org" />
 <link rel="help" href="https://drafts.csswg.org/css-tables-3/#row-layout" />
 <style>
   main table {
-    border-collapse: collapse;
-    background: #DDD;
+    border-spacing: 0px;
+    background: rgba(255,0,0,0.3);
     height: 100px;
+    width: 100px;
   }
   main td {
     padding: 0;
@@ -30,178 +32,286 @@
   main table tfoot {
     background: rgba(255,255,0,0.3);
   }
-  main .test {
-    margin-top:24px;
+
+  .debug {
+    width:100px;
+    height:1px;
+    position:relative;
   }
+  .debug pre {
+    position:absolute;
+    font: 30px/30px monospace;
+    left: 120px;
+    top:-30px;
+  }
+
 </style>
 <main>
  <h1>Tests for redistribution of table height to row group height.</h1>
- <p>This algorithm has not been standardized. Browsers implementations disagree</p>
-<p class="test">Empty sections and rows do grow when table height forces it.</p>
-<table>
-  <tbody data-expected-height="100">
-    <tr data-expected-height="100">
-      <td style="width:0;height:0" data-expected-height="100"></td>
-    </tr>
-  </tbody>
+ <p>This algorithm has not been standardized. Browsers implementations disagree a lot.
+ Legacy Chrome used to always distribute all the height to the first tbody.</p>
+
+<pre class=error>Major incompatibility: Legacy:
+- ignores any height set on sections.
+- does not size the table unless section has a TR
+- does not grow sections without TDs
+- only distributes height to the 1st tbody
+FF
+- does not prioritize thead for height distribution most of the time.
+- y offset of multiple tbodies can be incorrect.
+</pre>
+
+<h2>Empty table</h2>
+<p>Empty tables always grow to specified height in all browsers.</p>
+<p class="testdesc">no sections</p>
+<table data-expected-height=100></table>
+
+<p class="testdesc">no sections, no border spacing</p>
+<table style="border-spacing: 0" data-expected-height=100></table>
+
+<p class="testdesc">collapsed table  no sections</p>
+<table style="border-collapse:collapse" data-expected-height=100></table>
+
+<p class="testdesc">fixed table  no sections</p>
+<table style="table-layout:fixed" data-expected-height=100></table>
+
+<h2>One TBODY</h2>
+<p>The big difference here is between empty TBODY, and a body with an empty TR</p>
+<li>FF: always sizes the table, only sizes TBODY if it has TR.</li>
+<li>Legacy: does not size the table unless TBODY has TR</li>
+<p>FF: sizes the table, but tbody size remains 0 unless tbody is not empty.</p>
+<p>Legacy table size remains 0, or border-spacing</p>
+
+<p class="testdesc">single empty tbody</p>
+<table data-expected-height=100>
+  <tbody data-expected-height=100></tbody>
 </table>
 
-<p class="test">Unconstrained sections grow proportionally.</p>
-<p>Chrome only grows first TBODY. This test expects only TBODYs to grow.</p>
-<table >
-  <thead data-expected-height="15">
-    <tr><td><div style="height:15px">0,0</div></td></tr>
+<p class="testdesc">single tbody+tr</p>
+<table data-expected-height=100>
+  <tbody data-expected-height=100><tr></tr></tbody>
+</table>
+
+<p class="testdesc">border spacing</p>
+<p class="error">FF/Legacy do not apply border-spacing</p>
+<table style="border-spacing: 10px" data-expected-height=100>
+  <tbody data-expected-height=80><tr></tr></tbody>
+</table>
+
+<p class="testdesc">collapsed table</p>
+<table style="border-collapse:collapse" data-expected-height=100>
+    <tbody data-expected-height=100><tr></tr></tbody>
+</table>
+
+<p class="testdesc">fixed table</p>
+<table style="table-layout:fixed" data-expected-height=100>
+    <tbody data-expected-height=100><tr></tr></tbody>
+</table>
+
+<p class="testdesc">tbody has fixed height</p>
+<p class="error">FF adds tbody height to table?</p>
+<table data-expected-height=100>
+  <tbody style="height:50px" data-expected-height=100><tr></tr></tbody>
+</table>
+
+<p class="testdesc">tbody has fixed height > table</p>
+<p class="error">Legacy: table size wins. FF: table size wins, but body grows to 300px?</p>
+<table data-expected-height=200>
+  <tbody style="height:200px" data-expected-height=200><tr></tr></tbody>
+</table>
+
+<p class="testdesc">tr has fixed height > table</p>
+<p class="error">FF: table size wins, but body is 300.</p>
+<table data-expected-height=200>
+  <tbody data-expected-height=200><tr style="height:200px"></tr></tbody>
+</table>
+
+<p class="testdesc">tbody has percentage height > table</p>
+<table data-expected-height=100>
+  <tbody style="height:200%" data-expected-height=100><tr></tr></tbody>
+</table>
+
+<p class="testdesc">tr has percentage height > table</p>
+<p class=error>FF/Legacy: table wins. FF: body is 200px</p>
+<table data-expected-height=100>
+  <tbody data-expected-height=100><tr style="height:200%"></tr></tbody>
+</table>
+
+<p class="testdesc">non-empty tbody</p>
+<table data-expected-height=100>
+  <tbody data-expected-height=100><tr></tr></tbody>
+</table>
+
+<p class="testdesc">non-empty thead</p>
+<table data-expected-height=100>
+  <thead data-expected-height=100><tr></tr></thead>
+</table>
+
+<h2>THEAD TBODY</h2>
+
+<p class="testdesc">empty thead, empty tbody</p>
+<p class="error">FF thead/tbody both grow</p>
+<table data-expected-height=100>
+  <thead data-expected-height=0><tr></tr></thead>
+  <tbody data-expected-height=100><tr></tr></tbody>
+</table>
+
+<p class="testdesc">sized thead, empty tbody</p>
+<table data-expected-height=100>
+  <thead data-expected-height=20><td style="height:20px">thead</td></thead>
+  <tbody data-expected-height=80><tr></tr></tbody>
+</table>
+
+<p class="testdesc">table layout fixed, thead with td, tbody</p>
+<table style="table-layout:fixed" data-expected-height=100>
+  <thead data-expected-height=20>
+    <tr>
+      <td style="height:20px">thead</td>
+    </tr>
   </thead>
-  <tbody data-expected-height="35">
-    <tr><td>1,0</td></tr>
+  <tbody data-expected-height=80><tr><td>x</td></tr></tbody>
+</table>
+
+<p class="testdesc">table layout fixed, thead+td, tbody+td</p>
+<table style="table-layout:fixed" data-expected-height=100>
+  <thead data-expected-height=20>
+    <tr>
+      <td style="height:20px">thead</td>
+    </tr>
+  </thead>
+  <tbody data-expected-height=80><tr><td>x</td></tr></tbody>
+</table>
+
+<p class="testdesc">thead with td</p>
+<table data-expected-height=100>
+  <thead data-expected-height=20>
+    <tr>
+      <td style="height:20px">thead</td>
+    </tr>
+  </thead>
+  <tbody data-expected-height=80>
+    <tr>
+    </tr>
   </tbody>
-  <tbody data-expected-height="35">
-    <tr><td>2,0</td></tr>
-  </tbody>
-  <tfoot data-expected-height="15">
-    <tr><td><div style="height:15px">3,0</div></td></tr>
+</table>
+
+<p class="testdesc">tfoot with td</p>
+<table data-expected-height=100>
+  <tfoot data-expected-height=20>
+    <tr>
+      <td style="height:20px">tfoot</td>
+    </tr>
   </tfoot>
-</table>
-
-<p class="test">Unconstrained sections grow proportionally to their size</p>
-<table >
-  <tbody data-expected-height="75">
-    <tr><td>0,0</td></tr>
-    <tr><td>1,0</td></tr>
-    <tr><td>2,0</td></tr>
-  </tbody>
-  <tbody data-expected-height="25">
-    <tr><td>3,0</td></tr>
-  </tbody>
-</table>
-
-<p class="test">Fixed sections do not grow if there are unconstrained sections.</p>
-<p>FF gets the right sizes, but gets locations wrong.</p>
-<table>
-  <tbody style="height:30px" data-expected-height="30" data-offset-y="0">
-    <tr><td>0,0</td></tr>
-  </tbody>
-  <tbody data-expected-height="35" data-offset-y="30">
-    <tr><td>1,0</td></tr>
-  </tbody>
-  <tbody data-expected-height="35" data-offset-y="65">
-    <tr><td>2,0</td></tr>
-  </tbody>
-</table>
-
-<p class="test">Fixed sections grow proportionally to their height.</p>
-<p>FF grows sections equally, not proportionally to height</p>
-<table>
-  <tbody style="height:20px" data-expected-height="25">
-    <tr><td>0,0</td></tr>
-  </tbody>
-  <tbody style="height:60px" data-expected-height="75">
-    <tr><td>1,0</td></tr>
-  </tbody>
-</table>
-
-<p class="test">Percentage resolution size. Initial percentage resolution size should be undefined.
-<table >
-  <tbody style="height:30%" data-expected-height="20">
-    <tr><td><div style="height:20px">0,0</div></td></tr>
-  </tbody>
-  <tbody style="height:70%" data-expected-height="20">
-    <tr><td><div style="height:20px">1,0</div></td></tr>
-  </tbody>
-  <tbody style="height:200px" data-expected-height="200">
-    <tr><td>1,0</td></tr>
-  </tbody>
-</table>
-
-<p class="test">Percentage resolution size. During table height redistribution, use table height as
-  percentage resolution height..
-<table>
-  <tbody style="height:30%" data-expected-height="30">
-    <tr><td><div style="height:20px">0,0</div></td></tr>
-  </tbody>
-  <tbody style="height:70%" data-expected-height="70">
-    <tr><td><div style="height:20px">1,0</div></td></tr>
-  </tbody>
-</table>
-
-<p class="test">Redistribute percentage bodies proportional to their initial height
-<table>
-  <tbody style="height:20%" data-expected-height="25">
-    <tr><td><div style="height:20px">0,0</div></td></tr>
-  </tbody>
-  <tbody style="height:60%" data-expected-height="75">
-    <tr><td><div style="height:20px">1,0</div></td></tr>
-  </tbody>
-</table>
-
-<p class="test">Table min-height is not ignored.</p>
-<table style="min-height:200px;height:auto" data-expected-height="200">
-  <tbody>
-    <tr><td><div style="height:50px">0,0</div></td></tr>
-  </tbody>
-</table>
-
-<p class="test">Table max-height is ignored.</p>
-<table style="max-height:90px;height:auto" data-expected-height="100">
-  <tbody>
-    <tr><td><div style="height:100px">0,0</div></td></tr>
-  </tbody>
-</table>
-
-<p class="test">Table height does not include caption</p>
-<table style="height:100px;" >
-  <caption>not included</caption>
-  <tbody>
-    <tr data-expected-height="100"><td>0,0</td></tr>
-  </tbody>
-</table>
-
-<p class="test">Excess row percentage height is distributed greedily.</p>
-<table style="height:160px">
-  <tbody>
-    <tr style="height:50%" data-expected-height="80">
-      <td >
-        <div style="height:20px">0,0</div>
-      </td>
-    </tr>
-    <tr style="height:30%" data-expected-height="48">
-      <td >
-        <div style="height:20px">1,0</div>
-      </td>
-    </tr>
-    <tr style="height:80%" data-expected-height="32">
-      <td >
-        <div style="height:20px">2,0</div>
-      </td>
+  <tbody data-expected-height=80>
+    <tr>
     </tr>
   </tbody>
 </table>
 
-<p class="test">Row percentage height is influenced by percentage cells.</p>
-<table style="height:100px">
-  <tbody>
-    <tr style="height:5%" data-expected-height="50">
-      <td style="height:50%">0,0</td>
-    </tr>
-    <tr style="height:5%" data-expected-height="30">
-      <td style="height:30%">1,0</td>
-    </tr>
-    <tr style="height:5%" data-expected-height="20">
-      <td style="height:20%">2,0</td>
-    </tr>
+<h2>Multiple TBODY</h2>
+<p class="error">Legacy does not distribute any heights when tr is empty.</p>
+<p class="error">Legacy always distributes everything to 1st tbody.</p>
+
+<p class="testdesc">2 tbody</p>
+<table data-expected-height=100>
+  <tbody data-expected-height=50><tr></tr></tbody>
+  <tbody data-expected-height=50><tr></tr></tbody>
+</table>
+
+<p class="testdesc">2 tbody, with non-empty tds</p>
+<p class="error">Legacy distributes everything to 1st tbody</p>
+<table data-expected-height=100>
+  <tbody data-expected-height=50><tr><td>x</td></tr></tbody>
+  <tbody data-expected-height=50><tr><td>x</td></tr></tbody>
+</table>
+
+
+<p class="testdesc">2 tbody, 40%, auto, no td</p>
+<p class="error">FF: distributes everything to auto when empty. Legacy does not distribute</p>
+<table data-expected-height=100>
+  <tbody style="height:40%" data-expected-height=40><tr></tr></tbody>
+  <tbody data-expected-height=60><tr></tr></tbody>
+</table>
+
+<p class="testdesc">2 tbody, 40%, auto, non-empty td</p>
+<table data-expected-height=100>
+  <tbody style="height:40%" data-expected-height=40><tr><td>x</td></tr></tbody>
+  <tbody data-expected-height=60><tr><td>x</td></tr></tbody>
+</table>
+
+<p class="testdesc">2 tbody, 40px, auto, non-empty td</p>
+<p class="error">FF gets confused with 2nd body placement</p>
+<table data-expected-height=100>
+  <tbody style="height:40px" data-expected-height=40><tr><td>x</td></tr></tbody>
+  <tbody data-expected-height=60 data-offset-y=40><tr><td>x</td></tr></tbody>
+</table>
+
+<p class="testdesc">2 tbody, 40%, 40px, non-empty td</p>
+<p class="error">FF splits evenly</p>
+<table data-expected-height=100>
+  <tbody style="height:40%" data-expected-height=40><tr><td>x</td></tr></tbody>
+  <tbody style="height:40px" data-expected-height=60 data-offset-y=40><tr><td>x</td></tr></tbody>
+</table>
+
+<h2>Sized THEAD/TBODY</h2>
+<p class=error>FF does not prioritize TBODY for distribution</p>
+<p class="testdesc">20px thead, 30px tbody</p>
+<table data-expected-height=100>
+  <thead data-expected-height=20 style="height:20px">
+    <tr><td>x</td></tr>
+  </thead>
+  <tbody data-expected-height=80 style="height:30px">
+    <tr><td>x</td></tr>
   </tbody>
 </table>
 
-<p class="test">Mixed constrained and unconstrained empty rows: only unconstrained rows grow.</p>
-  <table style="width:50px;height:50px">
-  <tbody data-expected-height="50">
-    <tr style="height:0" data-expected-height="0"><td></td></tr>
-    <tr data-expected-height="25"><td></td></tr>
-    <tr data-expected-height="25"><td></td></tr>
+<p class="testdesc">20px thead's tr, 30px tbody's tr</p>
+<table data-expected-height=100>
+  <thead data-expected-height=20 >
+    <tr style="height:20px"><td>x</td></tr>
+  </thead>
+  <tbody data-expected-height=80 >
+    <tr style="height:30px"><td>x</td></tr>
+  </tbody>
+</table>
+
+<p class="testdesc">20px thead's td, 30px tbody's td</p>
+<table data-expected-height=100>
+  <thead data-expected-height=20 >
+    <tr><td  style="height:20px">x</td></tr>
+  </thead>
+  <tbody data-expected-height=80 >
+    <tr><td  style="height:30px">x</td></tr>
   </tbody>
 </table>
 
 </main>
+
 <script>
   checkLayout("table");
+
+  // Display body sizes next to the table.
+  for (let table of Array.from(document.querySelectorAll("table"))) {
+    let d = document.createElement("div");
+    d.classList.add("debug");
+    let log = document.createElement("pre");
+    d.appendChild(log);
+
+    let text = "";
+    let x;
+    x = table.querySelector("thead");
+    if (x)
+      text += `h:${x.offsetHeight}\n`;
+    x = table.querySelectorAll("tbody");
+    if (x.length > 0)
+      for (body of Array.from(x))
+        text += `b:${body.offsetHeight}\n`;
+    x = table.querySelector("tfoot");
+    if (x)
+      text += `f:${x.offsetHeight}\n`;
+    log.innerText = text;
+
+    table.parentNode.insertBefore(d, table);
+  }
 </script>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-tables/tentative/tbody-height-redistribution.html b/third_party/blink/web_tests/external/wpt/css/css-tables/tentative/tbody-height-redistribution.html
index 61b304ff..bfd47fa 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-tables/tentative/tbody-height-redistribution.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-tables/tentative/tbody-height-redistribution.html
@@ -116,9 +116,9 @@
     <tr style="height:30px" data-expected-height="30"><td>0,2</td></tr>
   </tbody>
 </table>
-<p>9) Empty section's height is ignored.</p>
+<p>9) Empty section height is not ignored</p>
 <table id="nine" >
-  <tbody style="height:75px"  data-expected-height="0">
+  <tbody style="height:75px"  data-expected-height="75">
   </tbody>
 </table>
 <p>10) Section with empty rows is not considered empty.</p>
diff --git a/third_party/blink/web_tests/external/wpt/dom/abort/AbortSignal.any.js b/third_party/blink/web_tests/external/wpt/dom/abort/AbortSignal.any.js
new file mode 100644
index 0000000..1d7d767
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/dom/abort/AbortSignal.any.js
@@ -0,0 +1,12 @@
+test(t => {
+  const signal = AbortSignal.abort();
+  assert_true(signal instanceof AbortSignal, "returned object is an AbortSignal");
+  assert_true(signal.aborted, "returned signal is already aborted");
+}, "the AbortSignal.abort() static returns an already aborted signal");
+
+async_test(t => {
+  const s = AbortSignal.abort();
+  s.addEventListener("abort", t.unreached_func("abort event listener called"));
+  s.onabort = t.unreached_func("abort event handler called");
+  t.step_timeout(() => { t.done(); }, 2000);
+}, "signal returned by AbortSignal.abort() should not fire abort event");
diff --git a/third_party/blink/web_tests/external/wpt/dom/abort/event.any-expected.txt b/third_party/blink/web_tests/external/wpt/dom/abort/event.any-expected.txt
deleted file mode 100644
index 459b664..0000000
--- a/third_party/blink/web_tests/external/wpt/dom/abort/event.any-expected.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-This is a testharness.js-based test.
-PASS AbortController abort() should fire event synchronously
-PASS controller.signal should always return the same object
-PASS controller.abort() should do nothing the second time it is called
-PASS event handler should not be called if added after controller.abort()
-PASS the abort event should have the right properties
-FAIL the AbortSignal.abort() static returns an already aborted signal AbortSignal.abort is not a function
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/dom/abort/event.any.js b/third_party/blink/web_tests/external/wpt/dom/abort/event.any.js
index 2589ba1c..a67e6f4 100644
--- a/third_party/blink/web_tests/external/wpt/dom/abort/event.any.js
+++ b/third_party/blink/web_tests/external/wpt/dom/abort/event.any.js
@@ -64,9 +64,4 @@
   controller.abort();
 }, "the abort event should have the right properties");
 
-test(t => {
-  const signal = AbortSignal.abort();
-  assert_true(signal.aborted);
-}, "the AbortSignal.abort() static returns an already aborted signal");
-
 done();
diff --git a/third_party/blink/web_tests/external/wpt/dom/abort/event.any.worker-expected.txt b/third_party/blink/web_tests/external/wpt/dom/abort/event.any.worker-expected.txt
deleted file mode 100644
index 459b664..0000000
--- a/third_party/blink/web_tests/external/wpt/dom/abort/event.any.worker-expected.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-This is a testharness.js-based test.
-PASS AbortController abort() should fire event synchronously
-PASS controller.signal should always return the same object
-PASS controller.abort() should do nothing the second time it is called
-PASS event handler should not be called if added after controller.abort()
-PASS the abort event should have the right properties
-FAIL the AbortSignal.abort() static returns an already aborted signal AbortSignal.abort is not a function
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/dom/idlharness.any.serviceworker-expected.txt b/third_party/blink/web_tests/external/wpt/dom/idlharness.any.serviceworker-expected.txt
deleted file mode 100644
index 7b59d468a..0000000
--- a/third_party/blink/web_tests/external/wpt/dom/idlharness.any.serviceworker-expected.txt
+++ /dev/null
@@ -1,210 +0,0 @@
-This is a testharness.js-based test.
-Found 206 tests; 205 PASS, 1 FAIL, 0 TIMEOUT, 0 NOTRUN.
-PASS idl_test setup
-PASS idl_test validation
-PASS Partial interface Window: original interface defined
-PASS Partial interface Window: member names are unique
-PASS Partial interface Document: member names are unique
-PASS Partial interface Document[2]: member names are unique
-PASS Partial interface Window[2]: member names are unique
-PASS Document includes NonElementParentNode: member names are unique
-PASS DocumentFragment includes NonElementParentNode: member names are unique
-PASS Document includes ParentNode: member names are unique
-PASS DocumentFragment includes ParentNode: member names are unique
-PASS Element includes ParentNode: member names are unique
-PASS Element includes NonDocumentTypeChildNode: member names are unique
-PASS CharacterData includes NonDocumentTypeChildNode: member names are unique
-PASS DocumentType includes ChildNode: member names are unique
-PASS Element includes ChildNode: member names are unique
-PASS CharacterData includes ChildNode: member names are unique
-PASS Element includes Slottable: member names are unique
-PASS Text includes Slottable: member names are unique
-PASS Document includes XPathEvaluatorBase: member names are unique
-PASS XPathEvaluator includes XPathEvaluatorBase: member names are unique
-PASS Document includes GlobalEventHandlers: member names are unique
-PASS Document includes DocumentAndElementEventHandlers: member names are unique
-PASS HTMLElement includes GlobalEventHandlers: member names are unique
-PASS HTMLElement includes DocumentAndElementEventHandlers: member names are unique
-PASS HTMLElement includes ElementContentEditable: member names are unique
-PASS HTMLElement includes HTMLOrSVGElement: member names are unique
-PASS Window includes GlobalEventHandlers: member names are unique
-PASS Window includes WindowEventHandlers: member names are unique
-PASS Window includes WindowOrWorkerGlobalScope: member names are unique
-PASS Window includes AnimationFrameProvider: member names are unique
-PASS Window includes WindowSessionStorage: member names are unique
-PASS Window includes WindowLocalStorage: member names are unique
-PASS Event interface: existence and properties of interface object
-PASS Event interface object length
-PASS Event interface object name
-PASS Event interface: existence and properties of interface prototype object
-PASS Event interface: existence and properties of interface prototype object's "constructor" property
-PASS Event interface: existence and properties of interface prototype object's @@unscopables property
-PASS Event interface: attribute type
-PASS Event interface: attribute target
-PASS Event interface: attribute srcElement
-PASS Event interface: attribute currentTarget
-PASS Event interface: operation composedPath()
-PASS Event interface: constant NONE on interface object
-PASS Event interface: constant NONE on interface prototype object
-PASS Event interface: constant CAPTURING_PHASE on interface object
-PASS Event interface: constant CAPTURING_PHASE on interface prototype object
-PASS Event interface: constant AT_TARGET on interface object
-PASS Event interface: constant AT_TARGET on interface prototype object
-PASS Event interface: constant BUBBLING_PHASE on interface object
-PASS Event interface: constant BUBBLING_PHASE on interface prototype object
-PASS Event interface: attribute eventPhase
-PASS Event interface: operation stopPropagation()
-PASS Event interface: attribute cancelBubble
-PASS Event interface: operation stopImmediatePropagation()
-PASS Event interface: attribute bubbles
-PASS Event interface: attribute cancelable
-PASS Event interface: attribute returnValue
-PASS Event interface: operation preventDefault()
-PASS Event interface: attribute defaultPrevented
-PASS Event interface: attribute composed
-PASS Event interface: attribute timeStamp
-PASS Event interface: operation initEvent(DOMString, optional boolean, optional boolean)
-PASS Event must be primary interface of new Event("foo")
-PASS Stringification of new Event("foo")
-PASS Event interface: new Event("foo") must inherit property "type" with the proper type
-PASS Event interface: new Event("foo") must inherit property "target" with the proper type
-PASS Event interface: new Event("foo") must inherit property "srcElement" with the proper type
-PASS Event interface: new Event("foo") must inherit property "currentTarget" with the proper type
-PASS Event interface: new Event("foo") must inherit property "composedPath()" with the proper type
-PASS Event interface: new Event("foo") must inherit property "NONE" with the proper type
-PASS Event interface: new Event("foo") must inherit property "CAPTURING_PHASE" with the proper type
-PASS Event interface: new Event("foo") must inherit property "AT_TARGET" with the proper type
-PASS Event interface: new Event("foo") must inherit property "BUBBLING_PHASE" with the proper type
-PASS Event interface: new Event("foo") must inherit property "eventPhase" with the proper type
-PASS Event interface: new Event("foo") must inherit property "stopPropagation()" with the proper type
-PASS Event interface: new Event("foo") must inherit property "cancelBubble" with the proper type
-PASS Event interface: new Event("foo") must inherit property "stopImmediatePropagation()" with the proper type
-PASS Event interface: new Event("foo") must inherit property "bubbles" with the proper type
-PASS Event interface: new Event("foo") must inherit property "cancelable" with the proper type
-PASS Event interface: new Event("foo") must inherit property "returnValue" with the proper type
-PASS Event interface: new Event("foo") must inherit property "preventDefault()" with the proper type
-PASS Event interface: new Event("foo") must inherit property "defaultPrevented" with the proper type
-PASS Event interface: new Event("foo") must inherit property "composed" with the proper type
-PASS Event interface: new Event("foo") must have own property "isTrusted"
-PASS Event interface: new Event("foo") must inherit property "timeStamp" with the proper type
-PASS Event interface: new Event("foo") must inherit property "initEvent(DOMString, optional boolean, optional boolean)" with the proper type
-PASS Event interface: calling initEvent(DOMString, optional boolean, optional boolean) on new Event("foo") with too few arguments must throw TypeError
-PASS CustomEvent interface: existence and properties of interface object
-PASS CustomEvent interface object length
-PASS CustomEvent interface object name
-PASS CustomEvent interface: existence and properties of interface prototype object
-PASS CustomEvent interface: existence and properties of interface prototype object's "constructor" property
-PASS CustomEvent interface: existence and properties of interface prototype object's @@unscopables property
-PASS CustomEvent interface: attribute detail
-PASS CustomEvent interface: operation initCustomEvent(DOMString, optional boolean, optional boolean, optional any)
-PASS CustomEvent must be primary interface of new CustomEvent("foo")
-PASS Stringification of new CustomEvent("foo")
-PASS CustomEvent interface: new CustomEvent("foo") must inherit property "detail" with the proper type
-PASS CustomEvent interface: new CustomEvent("foo") must inherit property "initCustomEvent(DOMString, optional boolean, optional boolean, optional any)" with the proper type
-PASS CustomEvent interface: calling initCustomEvent(DOMString, optional boolean, optional boolean, optional any) on new CustomEvent("foo") with too few arguments must throw TypeError
-PASS Event interface: new CustomEvent("foo") must inherit property "type" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "target" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "srcElement" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "currentTarget" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "composedPath()" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "NONE" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "CAPTURING_PHASE" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "AT_TARGET" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "BUBBLING_PHASE" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "eventPhase" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "stopPropagation()" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "cancelBubble" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "stopImmediatePropagation()" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "bubbles" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "cancelable" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "returnValue" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "preventDefault()" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "defaultPrevented" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "composed" with the proper type
-PASS Event interface: new CustomEvent("foo") must have own property "isTrusted"
-PASS Event interface: new CustomEvent("foo") must inherit property "timeStamp" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "initEvent(DOMString, optional boolean, optional boolean)" with the proper type
-PASS Event interface: calling initEvent(DOMString, optional boolean, optional boolean) on new CustomEvent("foo") with too few arguments must throw TypeError
-PASS EventTarget interface: existence and properties of interface object
-PASS EventTarget interface object length
-PASS EventTarget interface object name
-PASS EventTarget interface: existence and properties of interface prototype object
-PASS EventTarget interface: existence and properties of interface prototype object's "constructor" property
-PASS EventTarget interface: existence and properties of interface prototype object's @@unscopables property
-PASS EventTarget interface: operation addEventListener(DOMString, EventListener?, optional (AddEventListenerOptions or boolean))
-PASS EventTarget interface: operation removeEventListener(DOMString, EventListener?, optional (EventListenerOptions or boolean))
-PASS EventTarget interface: operation dispatchEvent(Event)
-PASS EventTarget must be primary interface of new EventTarget()
-PASS Stringification of new EventTarget()
-PASS EventTarget interface: new EventTarget() must inherit property "addEventListener(DOMString, EventListener?, optional (AddEventListenerOptions or boolean))" with the proper type
-PASS EventTarget interface: calling addEventListener(DOMString, EventListener?, optional (AddEventListenerOptions or boolean)) on new EventTarget() with too few arguments must throw TypeError
-PASS EventTarget interface: new EventTarget() must inherit property "removeEventListener(DOMString, EventListener?, optional (EventListenerOptions or boolean))" with the proper type
-PASS EventTarget interface: calling removeEventListener(DOMString, EventListener?, optional (EventListenerOptions or boolean)) on new EventTarget() with too few arguments must throw TypeError
-PASS EventTarget interface: new EventTarget() must inherit property "dispatchEvent(Event)" with the proper type
-PASS EventTarget interface: calling dispatchEvent(Event) on new EventTarget() with too few arguments must throw TypeError
-PASS EventListener interface: existence and properties of interface object
-PASS AbortController interface: existence and properties of interface object
-PASS AbortController interface object length
-PASS AbortController interface object name
-PASS AbortController interface: existence and properties of interface prototype object
-PASS AbortController interface: existence and properties of interface prototype object's "constructor" property
-PASS AbortController interface: existence and properties of interface prototype object's @@unscopables property
-PASS AbortController interface: attribute signal
-PASS AbortController interface: operation abort()
-PASS AbortController must be primary interface of new AbortController()
-PASS Stringification of new AbortController()
-PASS AbortController interface: new AbortController() must inherit property "signal" with the proper type
-PASS AbortController interface: new AbortController() must inherit property "abort()" with the proper type
-PASS AbortSignal interface: existence and properties of interface object
-PASS AbortSignal interface object length
-PASS AbortSignal interface object name
-PASS AbortSignal interface: existence and properties of interface prototype object
-PASS AbortSignal interface: existence and properties of interface prototype object's "constructor" property
-PASS AbortSignal interface: existence and properties of interface prototype object's @@unscopables property
-FAIL AbortSignal interface: operation abort() assert_own_property: interface object missing static operation expected property "abort" missing
-PASS AbortSignal interface: attribute aborted
-PASS AbortSignal interface: attribute onabort
-PASS AbortSignal must be primary interface of new AbortController().signal
-PASS Stringification of new AbortController().signal
-PASS AbortSignal interface: new AbortController().signal must inherit property "abort()" with the proper type
-PASS AbortSignal interface: new AbortController().signal must inherit property "aborted" with the proper type
-PASS AbortSignal interface: new AbortController().signal must inherit property "onabort" with the proper type
-PASS EventTarget interface: new AbortController().signal must inherit property "addEventListener(DOMString, EventListener?, optional (AddEventListenerOptions or boolean))" with the proper type
-PASS EventTarget interface: calling addEventListener(DOMString, EventListener?, optional (AddEventListenerOptions or boolean)) on new AbortController().signal with too few arguments must throw TypeError
-PASS EventTarget interface: new AbortController().signal must inherit property "removeEventListener(DOMString, EventListener?, optional (EventListenerOptions or boolean))" with the proper type
-PASS EventTarget interface: calling removeEventListener(DOMString, EventListener?, optional (EventListenerOptions or boolean)) on new AbortController().signal with too few arguments must throw TypeError
-PASS EventTarget interface: new AbortController().signal must inherit property "dispatchEvent(Event)" with the proper type
-PASS EventTarget interface: calling dispatchEvent(Event) on new AbortController().signal with too few arguments must throw TypeError
-PASS NodeList interface: existence and properties of interface object
-PASS HTMLCollection interface: existence and properties of interface object
-PASS MutationObserver interface: existence and properties of interface object
-PASS MutationRecord interface: existence and properties of interface object
-PASS Node interface: existence and properties of interface object
-PASS Document interface: existence and properties of interface object
-PASS XMLDocument interface: existence and properties of interface object
-PASS DOMImplementation interface: existence and properties of interface object
-PASS DocumentType interface: existence and properties of interface object
-PASS DocumentFragment interface: existence and properties of interface object
-PASS ShadowRoot interface: existence and properties of interface object
-PASS Element interface: existence and properties of interface object
-PASS NamedNodeMap interface: existence and properties of interface object
-PASS Attr interface: existence and properties of interface object
-PASS CharacterData interface: existence and properties of interface object
-PASS Text interface: existence and properties of interface object
-PASS CDATASection interface: existence and properties of interface object
-PASS ProcessingInstruction interface: existence and properties of interface object
-PASS Comment interface: existence and properties of interface object
-PASS AbstractRange interface: existence and properties of interface object
-PASS StaticRange interface: existence and properties of interface object
-PASS Range interface: existence and properties of interface object
-PASS NodeIterator interface: existence and properties of interface object
-PASS TreeWalker interface: existence and properties of interface object
-PASS NodeFilter interface: existence and properties of interface object
-PASS DOMTokenList interface: existence and properties of interface object
-PASS XPathResult interface: existence and properties of interface object
-PASS XPathExpression interface: existence and properties of interface object
-PASS XPathNSResolver interface: existence and properties of interface object
-PASS XPathEvaluator interface: existence and properties of interface object
-PASS XSLTProcessor interface: existence and properties of interface object
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/dom/idlharness.any.sharedworker-expected.txt b/third_party/blink/web_tests/external/wpt/dom/idlharness.any.sharedworker-expected.txt
deleted file mode 100644
index 7b59d468a..0000000
--- a/third_party/blink/web_tests/external/wpt/dom/idlharness.any.sharedworker-expected.txt
+++ /dev/null
@@ -1,210 +0,0 @@
-This is a testharness.js-based test.
-Found 206 tests; 205 PASS, 1 FAIL, 0 TIMEOUT, 0 NOTRUN.
-PASS idl_test setup
-PASS idl_test validation
-PASS Partial interface Window: original interface defined
-PASS Partial interface Window: member names are unique
-PASS Partial interface Document: member names are unique
-PASS Partial interface Document[2]: member names are unique
-PASS Partial interface Window[2]: member names are unique
-PASS Document includes NonElementParentNode: member names are unique
-PASS DocumentFragment includes NonElementParentNode: member names are unique
-PASS Document includes ParentNode: member names are unique
-PASS DocumentFragment includes ParentNode: member names are unique
-PASS Element includes ParentNode: member names are unique
-PASS Element includes NonDocumentTypeChildNode: member names are unique
-PASS CharacterData includes NonDocumentTypeChildNode: member names are unique
-PASS DocumentType includes ChildNode: member names are unique
-PASS Element includes ChildNode: member names are unique
-PASS CharacterData includes ChildNode: member names are unique
-PASS Element includes Slottable: member names are unique
-PASS Text includes Slottable: member names are unique
-PASS Document includes XPathEvaluatorBase: member names are unique
-PASS XPathEvaluator includes XPathEvaluatorBase: member names are unique
-PASS Document includes GlobalEventHandlers: member names are unique
-PASS Document includes DocumentAndElementEventHandlers: member names are unique
-PASS HTMLElement includes GlobalEventHandlers: member names are unique
-PASS HTMLElement includes DocumentAndElementEventHandlers: member names are unique
-PASS HTMLElement includes ElementContentEditable: member names are unique
-PASS HTMLElement includes HTMLOrSVGElement: member names are unique
-PASS Window includes GlobalEventHandlers: member names are unique
-PASS Window includes WindowEventHandlers: member names are unique
-PASS Window includes WindowOrWorkerGlobalScope: member names are unique
-PASS Window includes AnimationFrameProvider: member names are unique
-PASS Window includes WindowSessionStorage: member names are unique
-PASS Window includes WindowLocalStorage: member names are unique
-PASS Event interface: existence and properties of interface object
-PASS Event interface object length
-PASS Event interface object name
-PASS Event interface: existence and properties of interface prototype object
-PASS Event interface: existence and properties of interface prototype object's "constructor" property
-PASS Event interface: existence and properties of interface prototype object's @@unscopables property
-PASS Event interface: attribute type
-PASS Event interface: attribute target
-PASS Event interface: attribute srcElement
-PASS Event interface: attribute currentTarget
-PASS Event interface: operation composedPath()
-PASS Event interface: constant NONE on interface object
-PASS Event interface: constant NONE on interface prototype object
-PASS Event interface: constant CAPTURING_PHASE on interface object
-PASS Event interface: constant CAPTURING_PHASE on interface prototype object
-PASS Event interface: constant AT_TARGET on interface object
-PASS Event interface: constant AT_TARGET on interface prototype object
-PASS Event interface: constant BUBBLING_PHASE on interface object
-PASS Event interface: constant BUBBLING_PHASE on interface prototype object
-PASS Event interface: attribute eventPhase
-PASS Event interface: operation stopPropagation()
-PASS Event interface: attribute cancelBubble
-PASS Event interface: operation stopImmediatePropagation()
-PASS Event interface: attribute bubbles
-PASS Event interface: attribute cancelable
-PASS Event interface: attribute returnValue
-PASS Event interface: operation preventDefault()
-PASS Event interface: attribute defaultPrevented
-PASS Event interface: attribute composed
-PASS Event interface: attribute timeStamp
-PASS Event interface: operation initEvent(DOMString, optional boolean, optional boolean)
-PASS Event must be primary interface of new Event("foo")
-PASS Stringification of new Event("foo")
-PASS Event interface: new Event("foo") must inherit property "type" with the proper type
-PASS Event interface: new Event("foo") must inherit property "target" with the proper type
-PASS Event interface: new Event("foo") must inherit property "srcElement" with the proper type
-PASS Event interface: new Event("foo") must inherit property "currentTarget" with the proper type
-PASS Event interface: new Event("foo") must inherit property "composedPath()" with the proper type
-PASS Event interface: new Event("foo") must inherit property "NONE" with the proper type
-PASS Event interface: new Event("foo") must inherit property "CAPTURING_PHASE" with the proper type
-PASS Event interface: new Event("foo") must inherit property "AT_TARGET" with the proper type
-PASS Event interface: new Event("foo") must inherit property "BUBBLING_PHASE" with the proper type
-PASS Event interface: new Event("foo") must inherit property "eventPhase" with the proper type
-PASS Event interface: new Event("foo") must inherit property "stopPropagation()" with the proper type
-PASS Event interface: new Event("foo") must inherit property "cancelBubble" with the proper type
-PASS Event interface: new Event("foo") must inherit property "stopImmediatePropagation()" with the proper type
-PASS Event interface: new Event("foo") must inherit property "bubbles" with the proper type
-PASS Event interface: new Event("foo") must inherit property "cancelable" with the proper type
-PASS Event interface: new Event("foo") must inherit property "returnValue" with the proper type
-PASS Event interface: new Event("foo") must inherit property "preventDefault()" with the proper type
-PASS Event interface: new Event("foo") must inherit property "defaultPrevented" with the proper type
-PASS Event interface: new Event("foo") must inherit property "composed" with the proper type
-PASS Event interface: new Event("foo") must have own property "isTrusted"
-PASS Event interface: new Event("foo") must inherit property "timeStamp" with the proper type
-PASS Event interface: new Event("foo") must inherit property "initEvent(DOMString, optional boolean, optional boolean)" with the proper type
-PASS Event interface: calling initEvent(DOMString, optional boolean, optional boolean) on new Event("foo") with too few arguments must throw TypeError
-PASS CustomEvent interface: existence and properties of interface object
-PASS CustomEvent interface object length
-PASS CustomEvent interface object name
-PASS CustomEvent interface: existence and properties of interface prototype object
-PASS CustomEvent interface: existence and properties of interface prototype object's "constructor" property
-PASS CustomEvent interface: existence and properties of interface prototype object's @@unscopables property
-PASS CustomEvent interface: attribute detail
-PASS CustomEvent interface: operation initCustomEvent(DOMString, optional boolean, optional boolean, optional any)
-PASS CustomEvent must be primary interface of new CustomEvent("foo")
-PASS Stringification of new CustomEvent("foo")
-PASS CustomEvent interface: new CustomEvent("foo") must inherit property "detail" with the proper type
-PASS CustomEvent interface: new CustomEvent("foo") must inherit property "initCustomEvent(DOMString, optional boolean, optional boolean, optional any)" with the proper type
-PASS CustomEvent interface: calling initCustomEvent(DOMString, optional boolean, optional boolean, optional any) on new CustomEvent("foo") with too few arguments must throw TypeError
-PASS Event interface: new CustomEvent("foo") must inherit property "type" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "target" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "srcElement" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "currentTarget" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "composedPath()" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "NONE" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "CAPTURING_PHASE" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "AT_TARGET" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "BUBBLING_PHASE" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "eventPhase" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "stopPropagation()" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "cancelBubble" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "stopImmediatePropagation()" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "bubbles" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "cancelable" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "returnValue" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "preventDefault()" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "defaultPrevented" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "composed" with the proper type
-PASS Event interface: new CustomEvent("foo") must have own property "isTrusted"
-PASS Event interface: new CustomEvent("foo") must inherit property "timeStamp" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "initEvent(DOMString, optional boolean, optional boolean)" with the proper type
-PASS Event interface: calling initEvent(DOMString, optional boolean, optional boolean) on new CustomEvent("foo") with too few arguments must throw TypeError
-PASS EventTarget interface: existence and properties of interface object
-PASS EventTarget interface object length
-PASS EventTarget interface object name
-PASS EventTarget interface: existence and properties of interface prototype object
-PASS EventTarget interface: existence and properties of interface prototype object's "constructor" property
-PASS EventTarget interface: existence and properties of interface prototype object's @@unscopables property
-PASS EventTarget interface: operation addEventListener(DOMString, EventListener?, optional (AddEventListenerOptions or boolean))
-PASS EventTarget interface: operation removeEventListener(DOMString, EventListener?, optional (EventListenerOptions or boolean))
-PASS EventTarget interface: operation dispatchEvent(Event)
-PASS EventTarget must be primary interface of new EventTarget()
-PASS Stringification of new EventTarget()
-PASS EventTarget interface: new EventTarget() must inherit property "addEventListener(DOMString, EventListener?, optional (AddEventListenerOptions or boolean))" with the proper type
-PASS EventTarget interface: calling addEventListener(DOMString, EventListener?, optional (AddEventListenerOptions or boolean)) on new EventTarget() with too few arguments must throw TypeError
-PASS EventTarget interface: new EventTarget() must inherit property "removeEventListener(DOMString, EventListener?, optional (EventListenerOptions or boolean))" with the proper type
-PASS EventTarget interface: calling removeEventListener(DOMString, EventListener?, optional (EventListenerOptions or boolean)) on new EventTarget() with too few arguments must throw TypeError
-PASS EventTarget interface: new EventTarget() must inherit property "dispatchEvent(Event)" with the proper type
-PASS EventTarget interface: calling dispatchEvent(Event) on new EventTarget() with too few arguments must throw TypeError
-PASS EventListener interface: existence and properties of interface object
-PASS AbortController interface: existence and properties of interface object
-PASS AbortController interface object length
-PASS AbortController interface object name
-PASS AbortController interface: existence and properties of interface prototype object
-PASS AbortController interface: existence and properties of interface prototype object's "constructor" property
-PASS AbortController interface: existence and properties of interface prototype object's @@unscopables property
-PASS AbortController interface: attribute signal
-PASS AbortController interface: operation abort()
-PASS AbortController must be primary interface of new AbortController()
-PASS Stringification of new AbortController()
-PASS AbortController interface: new AbortController() must inherit property "signal" with the proper type
-PASS AbortController interface: new AbortController() must inherit property "abort()" with the proper type
-PASS AbortSignal interface: existence and properties of interface object
-PASS AbortSignal interface object length
-PASS AbortSignal interface object name
-PASS AbortSignal interface: existence and properties of interface prototype object
-PASS AbortSignal interface: existence and properties of interface prototype object's "constructor" property
-PASS AbortSignal interface: existence and properties of interface prototype object's @@unscopables property
-FAIL AbortSignal interface: operation abort() assert_own_property: interface object missing static operation expected property "abort" missing
-PASS AbortSignal interface: attribute aborted
-PASS AbortSignal interface: attribute onabort
-PASS AbortSignal must be primary interface of new AbortController().signal
-PASS Stringification of new AbortController().signal
-PASS AbortSignal interface: new AbortController().signal must inherit property "abort()" with the proper type
-PASS AbortSignal interface: new AbortController().signal must inherit property "aborted" with the proper type
-PASS AbortSignal interface: new AbortController().signal must inherit property "onabort" with the proper type
-PASS EventTarget interface: new AbortController().signal must inherit property "addEventListener(DOMString, EventListener?, optional (AddEventListenerOptions or boolean))" with the proper type
-PASS EventTarget interface: calling addEventListener(DOMString, EventListener?, optional (AddEventListenerOptions or boolean)) on new AbortController().signal with too few arguments must throw TypeError
-PASS EventTarget interface: new AbortController().signal must inherit property "removeEventListener(DOMString, EventListener?, optional (EventListenerOptions or boolean))" with the proper type
-PASS EventTarget interface: calling removeEventListener(DOMString, EventListener?, optional (EventListenerOptions or boolean)) on new AbortController().signal with too few arguments must throw TypeError
-PASS EventTarget interface: new AbortController().signal must inherit property "dispatchEvent(Event)" with the proper type
-PASS EventTarget interface: calling dispatchEvent(Event) on new AbortController().signal with too few arguments must throw TypeError
-PASS NodeList interface: existence and properties of interface object
-PASS HTMLCollection interface: existence and properties of interface object
-PASS MutationObserver interface: existence and properties of interface object
-PASS MutationRecord interface: existence and properties of interface object
-PASS Node interface: existence and properties of interface object
-PASS Document interface: existence and properties of interface object
-PASS XMLDocument interface: existence and properties of interface object
-PASS DOMImplementation interface: existence and properties of interface object
-PASS DocumentType interface: existence and properties of interface object
-PASS DocumentFragment interface: existence and properties of interface object
-PASS ShadowRoot interface: existence and properties of interface object
-PASS Element interface: existence and properties of interface object
-PASS NamedNodeMap interface: existence and properties of interface object
-PASS Attr interface: existence and properties of interface object
-PASS CharacterData interface: existence and properties of interface object
-PASS Text interface: existence and properties of interface object
-PASS CDATASection interface: existence and properties of interface object
-PASS ProcessingInstruction interface: existence and properties of interface object
-PASS Comment interface: existence and properties of interface object
-PASS AbstractRange interface: existence and properties of interface object
-PASS StaticRange interface: existence and properties of interface object
-PASS Range interface: existence and properties of interface object
-PASS NodeIterator interface: existence and properties of interface object
-PASS TreeWalker interface: existence and properties of interface object
-PASS NodeFilter interface: existence and properties of interface object
-PASS DOMTokenList interface: existence and properties of interface object
-PASS XPathResult interface: existence and properties of interface object
-PASS XPathExpression interface: existence and properties of interface object
-PASS XPathNSResolver interface: existence and properties of interface object
-PASS XPathEvaluator interface: existence and properties of interface object
-PASS XSLTProcessor interface: existence and properties of interface object
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/dom/idlharness.any.worker-expected.txt b/third_party/blink/web_tests/external/wpt/dom/idlharness.any.worker-expected.txt
deleted file mode 100644
index 7b59d468a..0000000
--- a/third_party/blink/web_tests/external/wpt/dom/idlharness.any.worker-expected.txt
+++ /dev/null
@@ -1,210 +0,0 @@
-This is a testharness.js-based test.
-Found 206 tests; 205 PASS, 1 FAIL, 0 TIMEOUT, 0 NOTRUN.
-PASS idl_test setup
-PASS idl_test validation
-PASS Partial interface Window: original interface defined
-PASS Partial interface Window: member names are unique
-PASS Partial interface Document: member names are unique
-PASS Partial interface Document[2]: member names are unique
-PASS Partial interface Window[2]: member names are unique
-PASS Document includes NonElementParentNode: member names are unique
-PASS DocumentFragment includes NonElementParentNode: member names are unique
-PASS Document includes ParentNode: member names are unique
-PASS DocumentFragment includes ParentNode: member names are unique
-PASS Element includes ParentNode: member names are unique
-PASS Element includes NonDocumentTypeChildNode: member names are unique
-PASS CharacterData includes NonDocumentTypeChildNode: member names are unique
-PASS DocumentType includes ChildNode: member names are unique
-PASS Element includes ChildNode: member names are unique
-PASS CharacterData includes ChildNode: member names are unique
-PASS Element includes Slottable: member names are unique
-PASS Text includes Slottable: member names are unique
-PASS Document includes XPathEvaluatorBase: member names are unique
-PASS XPathEvaluator includes XPathEvaluatorBase: member names are unique
-PASS Document includes GlobalEventHandlers: member names are unique
-PASS Document includes DocumentAndElementEventHandlers: member names are unique
-PASS HTMLElement includes GlobalEventHandlers: member names are unique
-PASS HTMLElement includes DocumentAndElementEventHandlers: member names are unique
-PASS HTMLElement includes ElementContentEditable: member names are unique
-PASS HTMLElement includes HTMLOrSVGElement: member names are unique
-PASS Window includes GlobalEventHandlers: member names are unique
-PASS Window includes WindowEventHandlers: member names are unique
-PASS Window includes WindowOrWorkerGlobalScope: member names are unique
-PASS Window includes AnimationFrameProvider: member names are unique
-PASS Window includes WindowSessionStorage: member names are unique
-PASS Window includes WindowLocalStorage: member names are unique
-PASS Event interface: existence and properties of interface object
-PASS Event interface object length
-PASS Event interface object name
-PASS Event interface: existence and properties of interface prototype object
-PASS Event interface: existence and properties of interface prototype object's "constructor" property
-PASS Event interface: existence and properties of interface prototype object's @@unscopables property
-PASS Event interface: attribute type
-PASS Event interface: attribute target
-PASS Event interface: attribute srcElement
-PASS Event interface: attribute currentTarget
-PASS Event interface: operation composedPath()
-PASS Event interface: constant NONE on interface object
-PASS Event interface: constant NONE on interface prototype object
-PASS Event interface: constant CAPTURING_PHASE on interface object
-PASS Event interface: constant CAPTURING_PHASE on interface prototype object
-PASS Event interface: constant AT_TARGET on interface object
-PASS Event interface: constant AT_TARGET on interface prototype object
-PASS Event interface: constant BUBBLING_PHASE on interface object
-PASS Event interface: constant BUBBLING_PHASE on interface prototype object
-PASS Event interface: attribute eventPhase
-PASS Event interface: operation stopPropagation()
-PASS Event interface: attribute cancelBubble
-PASS Event interface: operation stopImmediatePropagation()
-PASS Event interface: attribute bubbles
-PASS Event interface: attribute cancelable
-PASS Event interface: attribute returnValue
-PASS Event interface: operation preventDefault()
-PASS Event interface: attribute defaultPrevented
-PASS Event interface: attribute composed
-PASS Event interface: attribute timeStamp
-PASS Event interface: operation initEvent(DOMString, optional boolean, optional boolean)
-PASS Event must be primary interface of new Event("foo")
-PASS Stringification of new Event("foo")
-PASS Event interface: new Event("foo") must inherit property "type" with the proper type
-PASS Event interface: new Event("foo") must inherit property "target" with the proper type
-PASS Event interface: new Event("foo") must inherit property "srcElement" with the proper type
-PASS Event interface: new Event("foo") must inherit property "currentTarget" with the proper type
-PASS Event interface: new Event("foo") must inherit property "composedPath()" with the proper type
-PASS Event interface: new Event("foo") must inherit property "NONE" with the proper type
-PASS Event interface: new Event("foo") must inherit property "CAPTURING_PHASE" with the proper type
-PASS Event interface: new Event("foo") must inherit property "AT_TARGET" with the proper type
-PASS Event interface: new Event("foo") must inherit property "BUBBLING_PHASE" with the proper type
-PASS Event interface: new Event("foo") must inherit property "eventPhase" with the proper type
-PASS Event interface: new Event("foo") must inherit property "stopPropagation()" with the proper type
-PASS Event interface: new Event("foo") must inherit property "cancelBubble" with the proper type
-PASS Event interface: new Event("foo") must inherit property "stopImmediatePropagation()" with the proper type
-PASS Event interface: new Event("foo") must inherit property "bubbles" with the proper type
-PASS Event interface: new Event("foo") must inherit property "cancelable" with the proper type
-PASS Event interface: new Event("foo") must inherit property "returnValue" with the proper type
-PASS Event interface: new Event("foo") must inherit property "preventDefault()" with the proper type
-PASS Event interface: new Event("foo") must inherit property "defaultPrevented" with the proper type
-PASS Event interface: new Event("foo") must inherit property "composed" with the proper type
-PASS Event interface: new Event("foo") must have own property "isTrusted"
-PASS Event interface: new Event("foo") must inherit property "timeStamp" with the proper type
-PASS Event interface: new Event("foo") must inherit property "initEvent(DOMString, optional boolean, optional boolean)" with the proper type
-PASS Event interface: calling initEvent(DOMString, optional boolean, optional boolean) on new Event("foo") with too few arguments must throw TypeError
-PASS CustomEvent interface: existence and properties of interface object
-PASS CustomEvent interface object length
-PASS CustomEvent interface object name
-PASS CustomEvent interface: existence and properties of interface prototype object
-PASS CustomEvent interface: existence and properties of interface prototype object's "constructor" property
-PASS CustomEvent interface: existence and properties of interface prototype object's @@unscopables property
-PASS CustomEvent interface: attribute detail
-PASS CustomEvent interface: operation initCustomEvent(DOMString, optional boolean, optional boolean, optional any)
-PASS CustomEvent must be primary interface of new CustomEvent("foo")
-PASS Stringification of new CustomEvent("foo")
-PASS CustomEvent interface: new CustomEvent("foo") must inherit property "detail" with the proper type
-PASS CustomEvent interface: new CustomEvent("foo") must inherit property "initCustomEvent(DOMString, optional boolean, optional boolean, optional any)" with the proper type
-PASS CustomEvent interface: calling initCustomEvent(DOMString, optional boolean, optional boolean, optional any) on new CustomEvent("foo") with too few arguments must throw TypeError
-PASS Event interface: new CustomEvent("foo") must inherit property "type" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "target" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "srcElement" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "currentTarget" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "composedPath()" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "NONE" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "CAPTURING_PHASE" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "AT_TARGET" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "BUBBLING_PHASE" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "eventPhase" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "stopPropagation()" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "cancelBubble" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "stopImmediatePropagation()" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "bubbles" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "cancelable" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "returnValue" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "preventDefault()" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "defaultPrevented" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "composed" with the proper type
-PASS Event interface: new CustomEvent("foo") must have own property "isTrusted"
-PASS Event interface: new CustomEvent("foo") must inherit property "timeStamp" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "initEvent(DOMString, optional boolean, optional boolean)" with the proper type
-PASS Event interface: calling initEvent(DOMString, optional boolean, optional boolean) on new CustomEvent("foo") with too few arguments must throw TypeError
-PASS EventTarget interface: existence and properties of interface object
-PASS EventTarget interface object length
-PASS EventTarget interface object name
-PASS EventTarget interface: existence and properties of interface prototype object
-PASS EventTarget interface: existence and properties of interface prototype object's "constructor" property
-PASS EventTarget interface: existence and properties of interface prototype object's @@unscopables property
-PASS EventTarget interface: operation addEventListener(DOMString, EventListener?, optional (AddEventListenerOptions or boolean))
-PASS EventTarget interface: operation removeEventListener(DOMString, EventListener?, optional (EventListenerOptions or boolean))
-PASS EventTarget interface: operation dispatchEvent(Event)
-PASS EventTarget must be primary interface of new EventTarget()
-PASS Stringification of new EventTarget()
-PASS EventTarget interface: new EventTarget() must inherit property "addEventListener(DOMString, EventListener?, optional (AddEventListenerOptions or boolean))" with the proper type
-PASS EventTarget interface: calling addEventListener(DOMString, EventListener?, optional (AddEventListenerOptions or boolean)) on new EventTarget() with too few arguments must throw TypeError
-PASS EventTarget interface: new EventTarget() must inherit property "removeEventListener(DOMString, EventListener?, optional (EventListenerOptions or boolean))" with the proper type
-PASS EventTarget interface: calling removeEventListener(DOMString, EventListener?, optional (EventListenerOptions or boolean)) on new EventTarget() with too few arguments must throw TypeError
-PASS EventTarget interface: new EventTarget() must inherit property "dispatchEvent(Event)" with the proper type
-PASS EventTarget interface: calling dispatchEvent(Event) on new EventTarget() with too few arguments must throw TypeError
-PASS EventListener interface: existence and properties of interface object
-PASS AbortController interface: existence and properties of interface object
-PASS AbortController interface object length
-PASS AbortController interface object name
-PASS AbortController interface: existence and properties of interface prototype object
-PASS AbortController interface: existence and properties of interface prototype object's "constructor" property
-PASS AbortController interface: existence and properties of interface prototype object's @@unscopables property
-PASS AbortController interface: attribute signal
-PASS AbortController interface: operation abort()
-PASS AbortController must be primary interface of new AbortController()
-PASS Stringification of new AbortController()
-PASS AbortController interface: new AbortController() must inherit property "signal" with the proper type
-PASS AbortController interface: new AbortController() must inherit property "abort()" with the proper type
-PASS AbortSignal interface: existence and properties of interface object
-PASS AbortSignal interface object length
-PASS AbortSignal interface object name
-PASS AbortSignal interface: existence and properties of interface prototype object
-PASS AbortSignal interface: existence and properties of interface prototype object's "constructor" property
-PASS AbortSignal interface: existence and properties of interface prototype object's @@unscopables property
-FAIL AbortSignal interface: operation abort() assert_own_property: interface object missing static operation expected property "abort" missing
-PASS AbortSignal interface: attribute aborted
-PASS AbortSignal interface: attribute onabort
-PASS AbortSignal must be primary interface of new AbortController().signal
-PASS Stringification of new AbortController().signal
-PASS AbortSignal interface: new AbortController().signal must inherit property "abort()" with the proper type
-PASS AbortSignal interface: new AbortController().signal must inherit property "aborted" with the proper type
-PASS AbortSignal interface: new AbortController().signal must inherit property "onabort" with the proper type
-PASS EventTarget interface: new AbortController().signal must inherit property "addEventListener(DOMString, EventListener?, optional (AddEventListenerOptions or boolean))" with the proper type
-PASS EventTarget interface: calling addEventListener(DOMString, EventListener?, optional (AddEventListenerOptions or boolean)) on new AbortController().signal with too few arguments must throw TypeError
-PASS EventTarget interface: new AbortController().signal must inherit property "removeEventListener(DOMString, EventListener?, optional (EventListenerOptions or boolean))" with the proper type
-PASS EventTarget interface: calling removeEventListener(DOMString, EventListener?, optional (EventListenerOptions or boolean)) on new AbortController().signal with too few arguments must throw TypeError
-PASS EventTarget interface: new AbortController().signal must inherit property "dispatchEvent(Event)" with the proper type
-PASS EventTarget interface: calling dispatchEvent(Event) on new AbortController().signal with too few arguments must throw TypeError
-PASS NodeList interface: existence and properties of interface object
-PASS HTMLCollection interface: existence and properties of interface object
-PASS MutationObserver interface: existence and properties of interface object
-PASS MutationRecord interface: existence and properties of interface object
-PASS Node interface: existence and properties of interface object
-PASS Document interface: existence and properties of interface object
-PASS XMLDocument interface: existence and properties of interface object
-PASS DOMImplementation interface: existence and properties of interface object
-PASS DocumentType interface: existence and properties of interface object
-PASS DocumentFragment interface: existence and properties of interface object
-PASS ShadowRoot interface: existence and properties of interface object
-PASS Element interface: existence and properties of interface object
-PASS NamedNodeMap interface: existence and properties of interface object
-PASS Attr interface: existence and properties of interface object
-PASS CharacterData interface: existence and properties of interface object
-PASS Text interface: existence and properties of interface object
-PASS CDATASection interface: existence and properties of interface object
-PASS ProcessingInstruction interface: existence and properties of interface object
-PASS Comment interface: existence and properties of interface object
-PASS AbstractRange interface: existence and properties of interface object
-PASS StaticRange interface: existence and properties of interface object
-PASS Range interface: existence and properties of interface object
-PASS NodeIterator interface: existence and properties of interface object
-PASS TreeWalker interface: existence and properties of interface object
-PASS NodeFilter interface: existence and properties of interface object
-PASS DOMTokenList interface: existence and properties of interface object
-PASS XPathResult interface: existence and properties of interface object
-PASS XPathExpression interface: existence and properties of interface object
-PASS XPathNSResolver interface: existence and properties of interface object
-PASS XPathEvaluator interface: existence and properties of interface object
-PASS XSLTProcessor interface: existence and properties of interface object
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/dom/idlharness.window_exclude=Node-expected.txt b/third_party/blink/web_tests/external/wpt/dom/idlharness.window_exclude=Node-expected.txt
index 853c679..3f7ccb1 100644
--- a/third_party/blink/web_tests/external/wpt/dom/idlharness.window_exclude=Node-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/dom/idlharness.window_exclude=Node-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 1286 tests; 1281 PASS, 5 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 1286 tests; 1282 PASS, 4 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS idl_test setup
 PASS idl_test validation
 PASS Partial interface Window: original interface defined
@@ -190,7 +190,7 @@
 PASS AbortSignal interface: existence and properties of interface prototype object
 PASS AbortSignal interface: existence and properties of interface prototype object's "constructor" property
 PASS AbortSignal interface: existence and properties of interface prototype object's @@unscopables property
-FAIL AbortSignal interface: operation abort() assert_own_property: interface object missing static operation expected property "abort" missing
+PASS AbortSignal interface: operation abort()
 PASS AbortSignal interface: attribute aborted
 PASS AbortSignal interface: attribute onabort
 PASS AbortSignal must be primary interface of new AbortController().signal
diff --git a/third_party/blink/web_tests/external/wpt/html/rendering/the-details-element/two-summaries-removal-crash.html b/third_party/blink/web_tests/external/wpt/html/rendering/the-details-element/two-summaries-removal-crash.html
new file mode 100644
index 0000000..164d3f1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/rendering/the-details-element/two-summaries-removal-crash.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel="help" href="https://crbug.com/1216804">
+<meta name="assert" content="The renderer should not crash.">
+
+<details id="details" open>
+  <summary></summary>
+  <summary>
+    <details></details>
+  </summary>
+</details>
+
+<script>
+details.appendChild(document.createElement("frame"));
+details.innerText="";
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/no-dynamic-import-in-module.any.js b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/no-dynamic-import-in-module.any.js
index 84b17629..f7c2ef3 100644
--- a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/no-dynamic-import-in-module.any.js
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/no-dynamic-import-in-module.any.js
@@ -2,6 +2,6 @@
 
 // This is imported to ensure import('./basic-module-2.js') fails even if
 // it has been previously statically imported.
-import './basic-module-2.js';
+import './resources/basic-module-2.js';
 
 import './resources/no-dynamic-import.js';
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/no-dynamic-import-in-module.any.serviceworker-module-expected.txt b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/no-dynamic-import-in-module.any.serviceworker-module-expected.txt
deleted file mode 100644
index 1d26dba..0000000
--- a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/no-dynamic-import-in-module.any.serviceworker-module-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-Harness Error. harness_status.status = 1 , harness_status.message = Unhandled rejection: Failed to register a ServiceWorker for scope ('https://web-platform.test:8444/service-workers/service-worker/does/not/exist') with script ('https://web-platform.test:8444/service-workers/service-worker/no-dynamic-import-in-module.any.worker-module.js'): ServiceWorker cannot be started
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/urlpattern/resources/urlpatterntestdata.json b/third_party/blink/web_tests/external/wpt/urlpattern/resources/urlpatterntestdata.json
index 7ca6ca9..be78ec21 100644
--- a/third_party/blink/web_tests/external/wpt/urlpattern/resources/urlpatterntestdata.json
+++ b/third_party/blink/web_tests/external/wpt/urlpattern/resources/urlpatterntestdata.json
@@ -81,6 +81,15 @@
     "inputs": [{ "protocol": "https", "hostname": "example.com",
                  "pathname": "/foo/bar" }],
     "exactly_empty_components": [ "username", "password", "port" ],
+    "expected_match": null
+  },
+  {
+    "pattern": [{ "pathname": "/foo/bar",
+                 "baseURL": "https://example.com" }],
+    "inputs": [{ "protocol": "https", "hostname": "example.com",
+                 "pathname": "/foo/bar" }],
+    "exactly_empty_components": [ "username", "password", "port", "search",
+                                  "hash" ],
     "expected_match": {
       "hostname": { "input": "example.com", "groups": {} },
       "pathname": { "input": "/foo/bar", "groups": {} },
@@ -89,7 +98,7 @@
   },
   {
     "pattern": [{ "pathname": "/foo/bar",
-                 "baseURL": "https://example.com?query#hash" }],
+                 "baseURL": "https://example.com" }],
     "inputs": [{ "protocol": "https", "hostname": "example.com",
                  "pathname": "/foo/bar/baz" }],
     "expected_match": null
@@ -101,12 +110,31 @@
                  "pathname": "/foo/bar", "search": "otherquery",
                  "hash": "otherhash" }],
     "exactly_empty_components": [ "username", "password", "port" ],
+    "expected_match": null
+  },
+  {
+    "pattern": [{ "pathname": "/foo/bar",
+                 "baseURL": "https://example.com" }],
+    "inputs": [{ "protocol": "https", "hostname": "example.com",
+                 "pathname": "/foo/bar", "search": "otherquery",
+                 "hash": "otherhash" }],
+    "exactly_empty_components": [ "username", "password", "port", "search",
+                                  "hash" ],
+    "expected_match": null
+  },
+  {
+    "pattern": [{ "pathname": "/foo/bar",
+                 "baseURL": "https://example.com?otherquery#otherhash" }],
+    "inputs": [{ "protocol": "https", "hostname": "example.com",
+                 "pathname": "/foo/bar", "search": "otherquery",
+                 "hash": "otherhash" }],
+    "exactly_empty_components": [ "username", "password", "port" ],
     "expected_match": {
-      "hash": { "input": "otherhash", "groups": { "0": "otherhash" } },
+      "hash": { "input": "otherhash", "groups": {} },
       "hostname": { "input": "example.com", "groups": {} },
       "pathname": { "input": "/foo/bar", "groups": {} },
       "protocol": { "input": "https", "groups": {} },
-      "search": { "input": "otherquery", "groups": { "0": "otherquery" } }
+      "search": { "input": "otherquery", "groups": {} }
     }
   },
   {
@@ -114,23 +142,26 @@
                  "baseURL": "https://example.com?query#hash" }],
     "inputs": [ "https://example.com/foo/bar" ],
     "exactly_empty_components": [ "username", "password", "port" ],
-    "expected_match": {
-      "hostname": { "input": "example.com", "groups": {} },
-      "pathname": { "input": "/foo/bar", "groups": {} },
-      "protocol": { "input": "https", "groups": {} }
-    }
+    "expected_match": null
   },
   {
     "pattern": [{ "pathname": "/foo/bar",
                  "baseURL": "https://example.com?query#hash" }],
     "inputs": [ "https://example.com/foo/bar?otherquery#otherhash" ],
     "exactly_empty_components": [ "username", "password", "port" ],
+    "expected_match": null
+  },
+  {
+    "pattern": [{ "pathname": "/foo/bar",
+                 "baseURL": "https://example.com?query#hash" }],
+    "inputs": [ "https://example.com/foo/bar?query#hash" ],
+    "exactly_empty_components": [ "username", "password", "port" ],
     "expected_match": {
-      "hash": { "input": "otherhash", "groups": { "0": "otherhash" } },
+      "hash": { "input": "hash", "groups": {} },
       "hostname": { "input": "example.com", "groups": {} },
       "pathname": { "input": "/foo/bar", "groups": {} },
       "protocol": { "input": "https", "groups": {} },
-      "search": { "input": "otherquery", "groups": { "0": "otherquery" } }
+      "search": { "input": "query", "groups": {} }
     }
   },
   {
@@ -156,10 +187,20 @@
                  "baseURL": "https://example.com?query#hash" }],
     "inputs": [{ "pathname": "/foo/bar", "baseURL": "https://example.com" }],
     "exactly_empty_components": [ "username", "password", "port" ],
+    "expected_match": null
+  },
+  {
+    "pattern": [{ "pathname": "/foo/bar",
+                 "baseURL": "https://example.com?query#hash" }],
+    "inputs": [{ "pathname": "/foo/bar",
+                 "baseURL": "https://example.com?query#hash" }],
+    "exactly_empty_components": [ "username", "password", "port" ],
     "expected_match": {
+      "hash": { "input": "hash", "groups": {} },
       "hostname": { "input": "example.com", "groups": {} },
       "pathname": { "input": "/foo/bar", "groups": {} },
-      "protocol": { "input": "https", "groups": {} }
+      "protocol": { "input": "https", "groups": {} },
+      "search": { "input": "query", "groups": {} }
     }
   },
   {
@@ -1155,10 +1196,11 @@
   {
     "pattern": [{ "pathname": "./foo/bar", "baseURL": "https://example.com" }],
     "inputs": [{ "pathname": "foo/bar", "baseURL": "https://example.com" }],
+    "exactly_empty_components": [ "username", "password", "port", "search",
+                                  "hash" ],
     "expected_obj": {
       "pathname": "/foo/bar"
     },
-    "exactly_empty_components": [ "username", "password", "port" ],
     "expected_match": {
       "protocol": { "input": "https", "groups": {}},
       "hostname": { "input": "example.com", "groups": {}},
@@ -1173,10 +1215,11 @@
   {
     "pattern": [{ "pathname": "foo/bar", "baseURL": "https://example.com" }],
     "inputs": [ "https://example.com/foo/bar" ],
+    "exactly_empty_components": [ "username", "password", "port", "search",
+                                  "hash" ],
     "expected_obj": {
       "pathname": "/foo/bar"
     },
-    "exactly_empty_components": [ "username", "password", "port" ],
     "expected_match": {
       "protocol": { "input": "https", "groups": {}},
       "hostname": { "input": "example.com", "groups": {}},
@@ -1186,10 +1229,11 @@
   {
     "pattern": [{ "pathname": ":name.html", "baseURL": "https://example.com" }],
     "inputs": [ "https://example.com/foo.html"] ,
+    "exactly_empty_components": [ "username", "password", "port", "search",
+                                  "hash" ],
     "expected_obj": {
       "pathname": "/:name.html"
     },
-    "exactly_empty_components": [ "username", "password", "port" ],
     "expected_match": {
       "protocol": { "input": "https", "groups": {}},
       "hostname": { "input": "example.com", "groups": {}},
@@ -1795,5 +1839,117 @@
   {
     "pattern": [ "(café)://foo" ],
     "expected_obj": "error"
+  },
+  {
+    "pattern": [ "https://example.com/foo?bar#baz" ],
+    "inputs": [{ "protocol": "https:",
+                 "search": "?bar",
+                 "hash": "#baz",
+                 "baseURL": "http://example.com/foo" }],
+    "exactly_empty_components": [ "username", "password", "port" ],
+    "expected_obj": {
+      "protocol": "https",
+      "hostname": "example.com",
+      "pathname": "/foo",
+      "search": "bar",
+      "hash": "baz"
+    },
+    "expected_match": {
+      "protocol": { "input": "https", "groups": {} },
+      "hostname": { "input": "example.com", "groups": {} },
+      "pathname": { "input": "/foo", "groups": {} },
+      "search": { "input": "bar", "groups": {} },
+      "hash": { "input": "baz", "groups": {} }
+    }
+  },
+  {
+    "pattern": [{ "protocol": "http{s}?:",
+                  "search": "?bar",
+                  "hash": "#baz" }],
+    "inputs": [ "http://example.com/foo?bar#baz" ],
+    "expected_obj": {
+      "protocol": "http{s}?",
+      "search": "bar",
+      "hash": "baz"
+    },
+    "expected_match": {
+      "protocol": { "input": "http", "groups": {} },
+      "hostname": { "input": "example.com", "groups": { "0": "example.com" }},
+      "pathname": { "input": "/foo", "groups": { "0": "/foo" }},
+      "search": { "input": "bar", "groups": {} },
+      "hash": { "input": "baz", "groups": {} }
+    }
+  },
+  {
+    "pattern": [ "?bar#baz", "https://example.com/foo" ],
+    "inputs": [ "?bar#baz", "https://example.com/foo" ],
+    "exactly_empty_components": [ "username", "password", "port" ],
+    "expected_obj": {
+      "protocol": "https",
+      "hostname": "example.com",
+      "pathname": "/foo",
+      "search": "bar",
+      "hash": "baz"
+    },
+    "expected_match": {
+      "protocol": { "input": "https", "groups": {} },
+      "hostname": { "input": "example.com", "groups": {} },
+      "pathname": { "input": "/foo", "groups": {} },
+      "search": { "input": "bar", "groups": {} },
+      "hash": { "input": "baz", "groups": {} }
+    }
+  },
+  {
+    "pattern": [ "?bar", "https://example.com/foo#baz" ],
+    "inputs": [ "?bar", "https://example.com/foo#snafu" ],
+    "exactly_empty_components": [ "username", "password", "port", "hash" ],
+    "expected_obj": {
+      "protocol": "https",
+      "hostname": "example.com",
+      "pathname": "/foo",
+      "search": "bar"
+    },
+    "expected_match": {
+      "protocol": { "input": "https", "groups": {} },
+      "hostname": { "input": "example.com", "groups": {} },
+      "pathname": { "input": "/foo", "groups": {} },
+      "search": { "input": "bar", "groups": {} }
+    }
+  },
+  {
+    "pattern": [ "#baz", "https://example.com/foo?bar" ],
+    "inputs": [ "#baz", "https://example.com/foo?bar" ],
+    "exactly_empty_components": [ "username", "password", "port" ],
+    "expected_obj": {
+      "protocol": "https",
+      "hostname": "example.com",
+      "pathname": "/foo",
+      "search": "bar",
+      "hash": "baz"
+    },
+    "expected_match": {
+      "protocol": { "input": "https", "groups": {} },
+      "hostname": { "input": "example.com", "groups": {} },
+      "pathname": { "input": "/foo", "groups": {} },
+      "search": { "input": "bar", "groups": {} },
+      "hash": { "input": "baz", "groups": {} }
+    }
+  },
+  {
+    "pattern": [ "#baz", "https://example.com/foo" ],
+    "inputs": [ "#baz", "https://example.com/foo" ],
+    "exactly_empty_components": [ "username", "password", "port", "search" ],
+    "expected_obj": {
+      "protocol": "https",
+      "hostname": "example.com",
+      "pathname": "/foo",
+      "hash": "baz"
+    },
+    "expected_match": {
+      "protocol": { "input": "https", "groups": {} },
+      "hostname": { "input": "example.com", "groups": {} },
+      "pathname": { "input": "/foo", "groups": {} },
+      "hash": { "input": "baz", "groups": {} }
+    }
   }
 ]
diff --git a/third_party/blink/web_tests/external/wpt/urlpattern/urlpattern.https.any.js b/third_party/blink/web_tests/external/wpt/urlpattern/urlpattern.https.any.js
index ec8ff1c9..1fa535a 100644
--- a/third_party/blink/web_tests/external/wpt/urlpattern/urlpattern.https.any.js
+++ b/third_party/blink/web_tests/external/wpt/urlpattern/urlpattern.https.any.js
@@ -54,8 +54,7 @@
           //  1. If the original input explicitly provided a pattern, then
           //     echo that back as the expected value.
           //  2. If the baseURL exists and provides a component value then
-          //     use that for the expected pattern.  Note, the baseURL
-          //     does not provide search/hash component values.
+          //     use that for the expected pattern.
           //  3. Otherwise fall back on the default pattern of `*` for an
           //     empty component pattern.
           if (entry.exactly_empty_components &&
@@ -64,13 +63,14 @@
           } else if (typeof entry.pattern[0] === 'object' &&
               entry.pattern[0][component]) {
             expected = entry.pattern[0][component];
-          } else if (baseURL &&
-                     component !== 'search' && component !== 'hash') {
+          } else if (baseURL) {
             let base_value = baseURL[component];
-            // Unfortunately the URL() protocol getter includes a trailing `:`
-            // that is not used by URLPattern.  Strip that off in necessary.
+            // Unfortunately some URL() getters include separator chars; e.g.
+            // the trailing `:` for the protocol.  Strip those off if necessary.
             if (component === 'protocol')
               base_value = base_value.substring(0, base_value.length - 1);
+            else if (component === 'search' || component === 'hash')
+              base_value = base_value.substring(1, base_value.length);
             expected = base_value;
           } else {
             expected = '*';
diff --git a/third_party/blink/web_tests/fast/forms/datalist/text-with-datalist-in-ol-expected.html b/third_party/blink/web_tests/fast/forms/datalist/text-with-datalist-in-ol-expected.html
new file mode 100644
index 0000000..4b4464b
--- /dev/null
+++ b/third_party/blink/web_tests/fast/forms/datalist/text-with-datalist-in-ol-expected.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+
+<datalist id="dl1"><option>a</option></datalist>
+<ol>
+<li>First
+<li>Second: <input list="dl1">
+<li value="3">Third
+</ol>
diff --git a/third_party/blink/web_tests/fast/forms/datalist/text-with-datalist-in-ol.html b/third_party/blink/web_tests/fast/forms/datalist/text-with-datalist-in-ol.html
new file mode 100644
index 0000000..3e792998
--- /dev/null
+++ b/third_party/blink/web_tests/fast/forms/datalist/text-with-datalist-in-ol.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+
+<!-- crbug.com/1216923 Popup indicator triangle should not affect LI -->
+<datalist id="dl1"><option>a</option></datalist>
+<ol>
+<li>First
+<li>Second: <input list="dl1">
+<li>Third
+</ol>
diff --git a/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index 1482d7c..26f68b0 100644
--- a/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -9,6 +9,7 @@
     method constructor
     method respondWith
 interface AbortSignal : EventTarget
+    static method abort
     attribute @@toStringTag
     getter aborted
     getter onabort
diff --git a/third_party/blink/web_tests/platform/fuchsia/tables/mozilla/bugs/bug2479-3-expected.png b/third_party/blink/web_tests/platform/fuchsia/tables/mozilla/bugs/bug2479-3-expected.png
index c7050e7..bf4d50c 100644
--- a/third_party/blink/web_tests/platform/fuchsia/tables/mozilla/bugs/bug2479-3-expected.png
+++ b/third_party/blink/web_tests/platform/fuchsia/tables/mozilla/bugs/bug2479-3-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/tables/mozilla/bugs/bug59354-expected.png b/third_party/blink/web_tests/platform/fuchsia/tables/mozilla/bugs/bug59354-expected.png
index c24600e..b387987 100644
--- a/third_party/blink/web_tests/platform/fuchsia/tables/mozilla/bugs/bug59354-expected.png
+++ b/third_party/blink/web_tests/platform/fuchsia/tables/mozilla/bugs/bug59354-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/tables/mozilla/bugs/bug7342-expected.png b/third_party/blink/web_tests/platform/fuchsia/tables/mozilla/bugs/bug7342-expected.png
index 936bb249..5ec44f41 100644
--- a/third_party/blink/web_tests/platform/fuchsia/tables/mozilla/bugs/bug7342-expected.png
+++ b/third_party/blink/web_tests/platform/fuchsia/tables/mozilla/bugs/bug7342-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/scroll-unification-synchronous_html_parser/external/wpt/dom/idlharness.any.sharedworker-expected.txt b/third_party/blink/web_tests/virtual/scroll-unification-synchronous_html_parser/external/wpt/dom/idlharness.any.sharedworker-expected.txt
deleted file mode 100644
index 7b59d468a..0000000
--- a/third_party/blink/web_tests/virtual/scroll-unification-synchronous_html_parser/external/wpt/dom/idlharness.any.sharedworker-expected.txt
+++ /dev/null
@@ -1,210 +0,0 @@
-This is a testharness.js-based test.
-Found 206 tests; 205 PASS, 1 FAIL, 0 TIMEOUT, 0 NOTRUN.
-PASS idl_test setup
-PASS idl_test validation
-PASS Partial interface Window: original interface defined
-PASS Partial interface Window: member names are unique
-PASS Partial interface Document: member names are unique
-PASS Partial interface Document[2]: member names are unique
-PASS Partial interface Window[2]: member names are unique
-PASS Document includes NonElementParentNode: member names are unique
-PASS DocumentFragment includes NonElementParentNode: member names are unique
-PASS Document includes ParentNode: member names are unique
-PASS DocumentFragment includes ParentNode: member names are unique
-PASS Element includes ParentNode: member names are unique
-PASS Element includes NonDocumentTypeChildNode: member names are unique
-PASS CharacterData includes NonDocumentTypeChildNode: member names are unique
-PASS DocumentType includes ChildNode: member names are unique
-PASS Element includes ChildNode: member names are unique
-PASS CharacterData includes ChildNode: member names are unique
-PASS Element includes Slottable: member names are unique
-PASS Text includes Slottable: member names are unique
-PASS Document includes XPathEvaluatorBase: member names are unique
-PASS XPathEvaluator includes XPathEvaluatorBase: member names are unique
-PASS Document includes GlobalEventHandlers: member names are unique
-PASS Document includes DocumentAndElementEventHandlers: member names are unique
-PASS HTMLElement includes GlobalEventHandlers: member names are unique
-PASS HTMLElement includes DocumentAndElementEventHandlers: member names are unique
-PASS HTMLElement includes ElementContentEditable: member names are unique
-PASS HTMLElement includes HTMLOrSVGElement: member names are unique
-PASS Window includes GlobalEventHandlers: member names are unique
-PASS Window includes WindowEventHandlers: member names are unique
-PASS Window includes WindowOrWorkerGlobalScope: member names are unique
-PASS Window includes AnimationFrameProvider: member names are unique
-PASS Window includes WindowSessionStorage: member names are unique
-PASS Window includes WindowLocalStorage: member names are unique
-PASS Event interface: existence and properties of interface object
-PASS Event interface object length
-PASS Event interface object name
-PASS Event interface: existence and properties of interface prototype object
-PASS Event interface: existence and properties of interface prototype object's "constructor" property
-PASS Event interface: existence and properties of interface prototype object's @@unscopables property
-PASS Event interface: attribute type
-PASS Event interface: attribute target
-PASS Event interface: attribute srcElement
-PASS Event interface: attribute currentTarget
-PASS Event interface: operation composedPath()
-PASS Event interface: constant NONE on interface object
-PASS Event interface: constant NONE on interface prototype object
-PASS Event interface: constant CAPTURING_PHASE on interface object
-PASS Event interface: constant CAPTURING_PHASE on interface prototype object
-PASS Event interface: constant AT_TARGET on interface object
-PASS Event interface: constant AT_TARGET on interface prototype object
-PASS Event interface: constant BUBBLING_PHASE on interface object
-PASS Event interface: constant BUBBLING_PHASE on interface prototype object
-PASS Event interface: attribute eventPhase
-PASS Event interface: operation stopPropagation()
-PASS Event interface: attribute cancelBubble
-PASS Event interface: operation stopImmediatePropagation()
-PASS Event interface: attribute bubbles
-PASS Event interface: attribute cancelable
-PASS Event interface: attribute returnValue
-PASS Event interface: operation preventDefault()
-PASS Event interface: attribute defaultPrevented
-PASS Event interface: attribute composed
-PASS Event interface: attribute timeStamp
-PASS Event interface: operation initEvent(DOMString, optional boolean, optional boolean)
-PASS Event must be primary interface of new Event("foo")
-PASS Stringification of new Event("foo")
-PASS Event interface: new Event("foo") must inherit property "type" with the proper type
-PASS Event interface: new Event("foo") must inherit property "target" with the proper type
-PASS Event interface: new Event("foo") must inherit property "srcElement" with the proper type
-PASS Event interface: new Event("foo") must inherit property "currentTarget" with the proper type
-PASS Event interface: new Event("foo") must inherit property "composedPath()" with the proper type
-PASS Event interface: new Event("foo") must inherit property "NONE" with the proper type
-PASS Event interface: new Event("foo") must inherit property "CAPTURING_PHASE" with the proper type
-PASS Event interface: new Event("foo") must inherit property "AT_TARGET" with the proper type
-PASS Event interface: new Event("foo") must inherit property "BUBBLING_PHASE" with the proper type
-PASS Event interface: new Event("foo") must inherit property "eventPhase" with the proper type
-PASS Event interface: new Event("foo") must inherit property "stopPropagation()" with the proper type
-PASS Event interface: new Event("foo") must inherit property "cancelBubble" with the proper type
-PASS Event interface: new Event("foo") must inherit property "stopImmediatePropagation()" with the proper type
-PASS Event interface: new Event("foo") must inherit property "bubbles" with the proper type
-PASS Event interface: new Event("foo") must inherit property "cancelable" with the proper type
-PASS Event interface: new Event("foo") must inherit property "returnValue" with the proper type
-PASS Event interface: new Event("foo") must inherit property "preventDefault()" with the proper type
-PASS Event interface: new Event("foo") must inherit property "defaultPrevented" with the proper type
-PASS Event interface: new Event("foo") must inherit property "composed" with the proper type
-PASS Event interface: new Event("foo") must have own property "isTrusted"
-PASS Event interface: new Event("foo") must inherit property "timeStamp" with the proper type
-PASS Event interface: new Event("foo") must inherit property "initEvent(DOMString, optional boolean, optional boolean)" with the proper type
-PASS Event interface: calling initEvent(DOMString, optional boolean, optional boolean) on new Event("foo") with too few arguments must throw TypeError
-PASS CustomEvent interface: existence and properties of interface object
-PASS CustomEvent interface object length
-PASS CustomEvent interface object name
-PASS CustomEvent interface: existence and properties of interface prototype object
-PASS CustomEvent interface: existence and properties of interface prototype object's "constructor" property
-PASS CustomEvent interface: existence and properties of interface prototype object's @@unscopables property
-PASS CustomEvent interface: attribute detail
-PASS CustomEvent interface: operation initCustomEvent(DOMString, optional boolean, optional boolean, optional any)
-PASS CustomEvent must be primary interface of new CustomEvent("foo")
-PASS Stringification of new CustomEvent("foo")
-PASS CustomEvent interface: new CustomEvent("foo") must inherit property "detail" with the proper type
-PASS CustomEvent interface: new CustomEvent("foo") must inherit property "initCustomEvent(DOMString, optional boolean, optional boolean, optional any)" with the proper type
-PASS CustomEvent interface: calling initCustomEvent(DOMString, optional boolean, optional boolean, optional any) on new CustomEvent("foo") with too few arguments must throw TypeError
-PASS Event interface: new CustomEvent("foo") must inherit property "type" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "target" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "srcElement" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "currentTarget" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "composedPath()" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "NONE" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "CAPTURING_PHASE" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "AT_TARGET" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "BUBBLING_PHASE" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "eventPhase" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "stopPropagation()" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "cancelBubble" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "stopImmediatePropagation()" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "bubbles" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "cancelable" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "returnValue" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "preventDefault()" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "defaultPrevented" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "composed" with the proper type
-PASS Event interface: new CustomEvent("foo") must have own property "isTrusted"
-PASS Event interface: new CustomEvent("foo") must inherit property "timeStamp" with the proper type
-PASS Event interface: new CustomEvent("foo") must inherit property "initEvent(DOMString, optional boolean, optional boolean)" with the proper type
-PASS Event interface: calling initEvent(DOMString, optional boolean, optional boolean) on new CustomEvent("foo") with too few arguments must throw TypeError
-PASS EventTarget interface: existence and properties of interface object
-PASS EventTarget interface object length
-PASS EventTarget interface object name
-PASS EventTarget interface: existence and properties of interface prototype object
-PASS EventTarget interface: existence and properties of interface prototype object's "constructor" property
-PASS EventTarget interface: existence and properties of interface prototype object's @@unscopables property
-PASS EventTarget interface: operation addEventListener(DOMString, EventListener?, optional (AddEventListenerOptions or boolean))
-PASS EventTarget interface: operation removeEventListener(DOMString, EventListener?, optional (EventListenerOptions or boolean))
-PASS EventTarget interface: operation dispatchEvent(Event)
-PASS EventTarget must be primary interface of new EventTarget()
-PASS Stringification of new EventTarget()
-PASS EventTarget interface: new EventTarget() must inherit property "addEventListener(DOMString, EventListener?, optional (AddEventListenerOptions or boolean))" with the proper type
-PASS EventTarget interface: calling addEventListener(DOMString, EventListener?, optional (AddEventListenerOptions or boolean)) on new EventTarget() with too few arguments must throw TypeError
-PASS EventTarget interface: new EventTarget() must inherit property "removeEventListener(DOMString, EventListener?, optional (EventListenerOptions or boolean))" with the proper type
-PASS EventTarget interface: calling removeEventListener(DOMString, EventListener?, optional (EventListenerOptions or boolean)) on new EventTarget() with too few arguments must throw TypeError
-PASS EventTarget interface: new EventTarget() must inherit property "dispatchEvent(Event)" with the proper type
-PASS EventTarget interface: calling dispatchEvent(Event) on new EventTarget() with too few arguments must throw TypeError
-PASS EventListener interface: existence and properties of interface object
-PASS AbortController interface: existence and properties of interface object
-PASS AbortController interface object length
-PASS AbortController interface object name
-PASS AbortController interface: existence and properties of interface prototype object
-PASS AbortController interface: existence and properties of interface prototype object's "constructor" property
-PASS AbortController interface: existence and properties of interface prototype object's @@unscopables property
-PASS AbortController interface: attribute signal
-PASS AbortController interface: operation abort()
-PASS AbortController must be primary interface of new AbortController()
-PASS Stringification of new AbortController()
-PASS AbortController interface: new AbortController() must inherit property "signal" with the proper type
-PASS AbortController interface: new AbortController() must inherit property "abort()" with the proper type
-PASS AbortSignal interface: existence and properties of interface object
-PASS AbortSignal interface object length
-PASS AbortSignal interface object name
-PASS AbortSignal interface: existence and properties of interface prototype object
-PASS AbortSignal interface: existence and properties of interface prototype object's "constructor" property
-PASS AbortSignal interface: existence and properties of interface prototype object's @@unscopables property
-FAIL AbortSignal interface: operation abort() assert_own_property: interface object missing static operation expected property "abort" missing
-PASS AbortSignal interface: attribute aborted
-PASS AbortSignal interface: attribute onabort
-PASS AbortSignal must be primary interface of new AbortController().signal
-PASS Stringification of new AbortController().signal
-PASS AbortSignal interface: new AbortController().signal must inherit property "abort()" with the proper type
-PASS AbortSignal interface: new AbortController().signal must inherit property "aborted" with the proper type
-PASS AbortSignal interface: new AbortController().signal must inherit property "onabort" with the proper type
-PASS EventTarget interface: new AbortController().signal must inherit property "addEventListener(DOMString, EventListener?, optional (AddEventListenerOptions or boolean))" with the proper type
-PASS EventTarget interface: calling addEventListener(DOMString, EventListener?, optional (AddEventListenerOptions or boolean)) on new AbortController().signal with too few arguments must throw TypeError
-PASS EventTarget interface: new AbortController().signal must inherit property "removeEventListener(DOMString, EventListener?, optional (EventListenerOptions or boolean))" with the proper type
-PASS EventTarget interface: calling removeEventListener(DOMString, EventListener?, optional (EventListenerOptions or boolean)) on new AbortController().signal with too few arguments must throw TypeError
-PASS EventTarget interface: new AbortController().signal must inherit property "dispatchEvent(Event)" with the proper type
-PASS EventTarget interface: calling dispatchEvent(Event) on new AbortController().signal with too few arguments must throw TypeError
-PASS NodeList interface: existence and properties of interface object
-PASS HTMLCollection interface: existence and properties of interface object
-PASS MutationObserver interface: existence and properties of interface object
-PASS MutationRecord interface: existence and properties of interface object
-PASS Node interface: existence and properties of interface object
-PASS Document interface: existence and properties of interface object
-PASS XMLDocument interface: existence and properties of interface object
-PASS DOMImplementation interface: existence and properties of interface object
-PASS DocumentType interface: existence and properties of interface object
-PASS DocumentFragment interface: existence and properties of interface object
-PASS ShadowRoot interface: existence and properties of interface object
-PASS Element interface: existence and properties of interface object
-PASS NamedNodeMap interface: existence and properties of interface object
-PASS Attr interface: existence and properties of interface object
-PASS CharacterData interface: existence and properties of interface object
-PASS Text interface: existence and properties of interface object
-PASS CDATASection interface: existence and properties of interface object
-PASS ProcessingInstruction interface: existence and properties of interface object
-PASS Comment interface: existence and properties of interface object
-PASS AbstractRange interface: existence and properties of interface object
-PASS StaticRange interface: existence and properties of interface object
-PASS Range interface: existence and properties of interface object
-PASS NodeIterator interface: existence and properties of interface object
-PASS TreeWalker interface: existence and properties of interface object
-PASS NodeFilter interface: existence and properties of interface object
-PASS DOMTokenList interface: existence and properties of interface object
-PASS XPathResult interface: existence and properties of interface object
-PASS XPathExpression interface: existence and properties of interface object
-PASS XPathNSResolver interface: existence and properties of interface object
-PASS XPathEvaluator interface: existence and properties of interface object
-PASS XSLTProcessor interface: existence and properties of interface object
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index 424d806bd7..0079830 100644
--- a/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -9,6 +9,7 @@
     method constructor
     method respondWith
 interface AbortSignal : EventTarget
+    static method abort
     attribute @@toStringTag
     getter aborted
     getter onabort
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
index c76e118..e90e4d14e 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -10,6 +10,7 @@
 [Worker]     method abort
 [Worker]     method constructor
 [Worker] interface AbortSignal : EventTarget
+[Worker]     static method abort
 [Worker]     attribute @@toStringTag
 [Worker]     getter aborted
 [Worker]     getter onabort
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
index 371c015..b42a045 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
@@ -9,6 +9,7 @@
     method abort
     method constructor
 interface AbortSignal : EventTarget
+    static method abort
     attribute @@toStringTag
     getter aborted
     getter onabort
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
index a5a1d92..8951044 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
@@ -10,6 +10,7 @@
 [Worker]     method abort
 [Worker]     method constructor
 [Worker] interface AbortSignal : EventTarget
+[Worker]     static method abort
 [Worker]     attribute @@toStringTag
 [Worker]     getter aborted
 [Worker]     getter onabort
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
index a193532..f01caf4d 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -10,6 +10,7 @@
 [Worker]     method abort
 [Worker]     method constructor
 [Worker] interface AbortSignal : EventTarget
+[Worker]     static method abort
 [Worker]     attribute @@toStringTag
 [Worker]     getter aborted
 [Worker]     getter onabort
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index a918f1c..d70a0d2e 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -9,6 +9,7 @@
     method abort
     method constructor
 interface AbortSignal : EventTarget
+    static method abort
     attribute @@toStringTag
     getter aborted
     getter onabort
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt
index bedb310..47e71384 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt
@@ -10,6 +10,7 @@
 [Worker]     method abort
 [Worker]     method constructor
 [Worker] interface AbortSignal : EventTarget
+[Worker]     static method abort
 [Worker]     attribute @@toStringTag
 [Worker]     getter aborted
 [Worker]     getter onabort
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/cts.html b/third_party/blink/web_tests/wpt_internal/webgpu/cts.html
index 9ab9620..1df62ee 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/cts.html
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/cts.html
@@ -157,11 +157,14 @@
 <meta name=variant content='?q=webgpu:api,operation,command_buffer,copyTextureToTexture:color_textures,compressed,array:format="bc7-rgba-unorm";*'>
 <meta name=variant content='?q=webgpu:api,operation,command_buffer,copyTextureToTexture:color_textures,compressed,array:format="bc7-rgba-unorm-srgb";*'>
 <meta name=variant content='?q=webgpu:api,operation,command_buffer,copyTextureToTexture:zero_sized:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,copyTextureToTexture:copy_stencil_aspect:*'>
 <meta name=variant content='?q=webgpu:api,operation,command_buffer,image_copy:rowsPerImage_and_bytesPerRow:*'>
 <meta name=variant content='?q=webgpu:api,operation,command_buffer,image_copy:offsets_and_sizes:*'>
 <meta name=variant content='?q=webgpu:api,operation,command_buffer,image_copy:origins_and_extents:*'>
 <meta name=variant content='?q=webgpu:api,operation,command_buffer,image_copy:mip_levels:*'>
 <meta name=variant content='?q=webgpu:api,operation,command_buffer,image_copy:undefined_params:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,image_copy:copy_from_stencil_aspect:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,image_copy:copy_into_stencil_aspect:*'>
 <meta name=variant content='?q=webgpu:api,operation,compute,basic:memcpy:*'>
 <meta name=variant content='?q=webgpu:api,operation,compute,basic:large_dispatch:*'>
 <meta name=variant content='?q=webgpu:api,operation,device,lost:not_lost_on_gc:*'>
@@ -649,9 +652,12 @@
 <meta name=variant content='?q=webgpu:examples:not_implemented_yet,with_plan:*'>
 <meta name=variant content='?q=webgpu:examples:basic:*'>
 <meta name=variant content='?q=webgpu:examples:basic,async:*'>
-<meta name=variant content='?q=webgpu:examples:basic,cases:*'>
-<meta name=variant content='?q=webgpu:examples:basic,subcases:*'>
-<meta name=variant content='?q=webgpu:examples:basic,params_builder:*'>
+<meta name=variant content='?q=webgpu:examples:basic,plain_cases:*'>
+<meta name=variant content='?q=webgpu:examples:basic,plain_cases_private:*'>
+<meta name=variant content='?q=webgpu:examples:basic,builder_cases:*'>
+<meta name=variant content='?q=webgpu:examples:basic,builder_cases_subcases:*'>
+<meta name=variant content='?q=webgpu:examples:basic,builder_subcases:*'>
+<meta name=variant content='?q=webgpu:examples:basic,builder_subcases_short:*'>
 <meta name=variant content='?q=webgpu:examples:gpu,async:*'>
 <meta name=variant content='?q=webgpu:examples:gpu,buffers:*'>
 <meta name=variant content='?q=webgpu:examples:gpu,with_texture_compression,bc:*'>
diff --git a/third_party/boringssl/err_data.c b/third_party/boringssl/err_data.c
index 6a7ee066..2c914df 100644
--- a/third_party/boringssl/err_data.c
+++ b/third_party/boringssl/err_data.c
@@ -193,47 +193,47 @@
     0x283480b9,
     0x283500f7,
     0x28358c79,
-    0x2c3231de,
+    0x2c3231f7,
     0x2c329313,
-    0x2c3331ec,
-    0x2c33b1fe,
-    0x2c343212,
-    0x2c34b224,
-    0x2c35323f,
-    0x2c35b251,
-    0x2c363281,
+    0x2c333205,
+    0x2c33b217,
+    0x2c34322b,
+    0x2c34b23d,
+    0x2c353258,
+    0x2c35b26a,
+    0x2c36329a,
     0x2c36833a,
-    0x2c37328e,
-    0x2c37b2ba,
-    0x2c3832df,
-    0x2c38b2f6,
-    0x2c393314,
-    0x2c39b324,
-    0x2c3a3336,
-    0x2c3ab34a,
-    0x2c3b335b,
-    0x2c3bb37a,
+    0x2c3732a7,
+    0x2c37b2d3,
+    0x2c3832f8,
+    0x2c38b30f,
+    0x2c39332d,
+    0x2c39b33d,
+    0x2c3a334f,
+    0x2c3ab363,
+    0x2c3b3374,
+    0x2c3bb393,
     0x2c3c1325,
     0x2c3c933b,
-    0x2c3d338e,
+    0x2c3d33a7,
     0x2c3d9354,
-    0x2c3e33ab,
-    0x2c3eb3b9,
-    0x2c3f33d1,
-    0x2c3fb3e9,
-    0x2c403413,
+    0x2c3e33c4,
+    0x2c3eb3d2,
+    0x2c3f33ea,
+    0x2c3fb402,
+    0x2c40342c,
     0x2c409226,
-    0x2c413424,
-    0x2c41b437,
+    0x2c41343d,
+    0x2c41b450,
     0x2c4211ec,
-    0x2c42b448,
+    0x2c42b461,
     0x2c43072f,
-    0x2c43b36c,
-    0x2c4432cd,
-    0x2c44b3f6,
-    0x2c453264,
-    0x2c45b2a0,
-    0x2c463304,
+    0x2c43b385,
+    0x2c4432e6,
+    0x2c44b40f,
+    0x2c45327d,
+    0x2c45b2b9,
+    0x2c46331d,
     0x30320000,
     0x30328015,
     0x3033001f,
@@ -485,42 +485,42 @@
     0x406b29d9,
     0x406ba9fc,
     0x406c2a12,
-    0x406cad03,
-    0x406d2d32,
-    0x406dad5a,
-    0x406e2d88,
-    0x406eadd5,
-    0x406f2e2e,
-    0x406fae66,
-    0x40702e79,
-    0x4070ae96,
+    0x406cad1c,
+    0x406d2d4b,
+    0x406dad73,
+    0x406e2da1,
+    0x406eadee,
+    0x406f2e47,
+    0x406fae7f,
+    0x40702e92,
+    0x4070aeaf,
     0x4071080f,
-    0x4071aea8,
-    0x40722ebb,
-    0x4072aef1,
-    0x40732f09,
+    0x4071aec1,
+    0x40722ed4,
+    0x4072af0a,
+    0x40732f22,
     0x40739525,
-    0x40742f1d,
-    0x4074af37,
-    0x40752f48,
-    0x4075af5c,
-    0x40762f6a,
+    0x40742f36,
+    0x4074af50,
+    0x40752f61,
+    0x4075af75,
+    0x40762f83,
     0x407692e9,
-    0x40772f8f,
-    0x4077afcf,
-    0x40782fea,
-    0x4078b023,
-    0x4079303a,
-    0x4079b050,
-    0x407a307c,
-    0x407ab08f,
-    0x407b30a4,
-    0x407bb0b6,
-    0x407c30e7,
-    0x407cb0f0,
+    0x40772fa8,
+    0x4077afe8,
+    0x40783003,
+    0x4078b03c,
+    0x40793053,
+    0x4079b069,
+    0x407a3095,
+    0x407ab0a8,
+    0x407b30bd,
+    0x407bb0cf,
+    0x407c3100,
+    0x407cb109,
     0x407d27cb,
     0x407da102,
-    0x407e2fff,
+    0x407e3018,
     0x407ea344,
     0x407f1db0,
     0x407f9f76,
@@ -528,7 +528,7 @@
     0x40809dd8,
     0x408121b2,
     0x4081a060,
-    0x40822d73,
+    0x40822d8c,
     0x40829b2b,
     0x4083231f,
     0x4083a648,
@@ -538,7 +538,7 @@
     0x4085a525,
     0x40862481,
     0x4086a11c,
-    0x40872db9,
+    0x40872dd2,
     0x4087a573,
     0x40881b69,
     0x4088a75a,
@@ -546,8 +546,8 @@
     0x40899b45,
     0x408a2a4a,
     0x408a993d,
-    0x408b30cb,
-    0x408bae43,
+    0x408b30e4,
+    0x408bae5c,
     0x408c2411,
     0x408c9975,
     0x408d1ec7,
@@ -561,26 +561,26 @@
     0x40912a32,
     0x4091999b,
     0x40921c05,
-    0x4092adf4,
-    0x40932ed4,
+    0x4092ae0d,
+    0x40932eed,
     0x4093a12d,
     0x40941e00,
     0x4094aa63,
     0x409525cf,
-    0x4095b05c,
-    0x40962da0,
+    0x4095b075,
+    0x40962db9,
     0x4096a0d5,
     0x40972178,
     0x4097a046,
     0x40981c65,
     0x4098a5e3,
-    0x40992e10,
+    0x40992e29,
     0x4099a29c,
     0x409a2235,
     0x409a9959,
     0x409b1e4d,
     0x409b9e78,
-    0x409c2fb1,
+    0x409c2fca,
     0x409c9ea0,
     0x409d2091,
     0x409da076,
@@ -589,7 +589,7 @@
     0x41f92996,
     0x41fe2889,
     0x41feab3f,
-    0x41ff2c54,
+    0x41ff2c6d,
     0x4203291d,
     0x4208293f,
     0x4208a97b,
@@ -599,25 +599,26 @@
     0x420aa8a4,
     0x420b28e4,
     0x420ba95d,
-    0x420c2c70,
+    0x420c2c89,
     0x420caa73,
     0x420d2b26,
     0x420dab5d,
-    0x42122b77,
-    0x42172c37,
-    0x4217abb9,
-    0x421c2bdb,
-    0x421f2b96,
-    0x42212ce8,
-    0x42262c1a,
-    0x422b2cc6,
+    0x42122b90,
+    0x42172c50,
+    0x4217abd2,
+    0x421c2bf4,
+    0x421f2baf,
+    0x42212d01,
+    0x42262c33,
+    0x422b2cdf,
     0x422bab01,
-    0x422c2ca8,
+    0x422c2cc1,
     0x422caab4,
     0x422d2a8d,
-    0x422dac87,
+    0x422daca0,
     0x422e2ae0,
-    0x42302bf6,
+    0x42302c0f,
+    0x4230ab77,
     0x4432073a,
     0x44328749,
     0x44330755,
@@ -672,69 +673,69 @@
     0x4c411582,
     0x4c419405,
     0x4c42156e,
-    0x5032345a,
-    0x5032b469,
-    0x50333474,
-    0x5033b484,
-    0x5034349d,
-    0x5034b4b7,
-    0x503534c5,
-    0x5035b4db,
-    0x503634ed,
-    0x5036b503,
-    0x5037351c,
-    0x5037b52f,
-    0x50383547,
-    0x5038b558,
-    0x5039356d,
-    0x5039b581,
-    0x503a35a1,
-    0x503ab5b7,
-    0x503b35cf,
-    0x503bb5e1,
-    0x503c35fd,
-    0x503cb614,
-    0x503d362d,
-    0x503db643,
-    0x503e3650,
-    0x503eb666,
-    0x503f3678,
+    0x50323473,
+    0x5032b482,
+    0x5033348d,
+    0x5033b49d,
+    0x503434b6,
+    0x5034b4d0,
+    0x503534de,
+    0x5035b4f4,
+    0x50363506,
+    0x5036b51c,
+    0x50373535,
+    0x5037b548,
+    0x50383560,
+    0x5038b571,
+    0x50393586,
+    0x5039b59a,
+    0x503a35ba,
+    0x503ab5d0,
+    0x503b35e8,
+    0x503bb5fa,
+    0x503c3616,
+    0x503cb62d,
+    0x503d3646,
+    0x503db65c,
+    0x503e3669,
+    0x503eb67f,
+    0x503f3691,
     0x503f8388,
-    0x5040368b,
-    0x5040b69b,
-    0x504136b5,
-    0x5041b6c4,
-    0x504236de,
-    0x5042b6fb,
-    0x5043370b,
-    0x5043b71b,
-    0x5044372a,
+    0x504036a4,
+    0x5040b6b4,
+    0x504136ce,
+    0x5041b6dd,
+    0x504236f7,
+    0x5042b714,
+    0x50433724,
+    0x5043b734,
+    0x50443743,
     0x5044843e,
-    0x5045373e,
-    0x5045b75c,
-    0x5046376f,
-    0x5046b785,
-    0x50473797,
-    0x5047b7ac,
-    0x504837d2,
-    0x5048b7e0,
-    0x504937f3,
-    0x5049b808,
-    0x504a381e,
-    0x504ab82e,
-    0x504b384e,
-    0x504bb861,
-    0x504c3884,
-    0x504cb8b2,
-    0x504d38c4,
-    0x504db8e1,
-    0x504e38fc,
-    0x504eb918,
-    0x504f392a,
-    0x504fb941,
-    0x50503950,
+    0x50453757,
+    0x5045b775,
+    0x50463788,
+    0x5046b79e,
+    0x504737b0,
+    0x5047b7c5,
+    0x504837eb,
+    0x5048b7f9,
+    0x5049380c,
+    0x5049b821,
+    0x504a3837,
+    0x504ab847,
+    0x504b3867,
+    0x504bb87a,
+    0x504c389d,
+    0x504cb8cb,
+    0x504d38dd,
+    0x504db8fa,
+    0x504e3915,
+    0x504eb931,
+    0x504f3943,
+    0x504fb95a,
+    0x50503969,
     0x505086fe,
-    0x50513963,
+    0x5051397c,
     0x58320f72,
     0x68320f34,
     0x68328c8c,
@@ -778,19 +779,19 @@
     0x7c321202,
     0x80321418,
     0x80328090,
-    0x803331ad,
+    0x803331c6,
     0x803380b9,
-    0x803431bc,
-    0x8034b124,
-    0x80353142,
-    0x8035b1d0,
-    0x80363184,
-    0x8036b133,
-    0x80373176,
-    0x8037b111,
-    0x80383197,
-    0x8038b153,
-    0x80393168,
+    0x803431d5,
+    0x8034b13d,
+    0x8035315b,
+    0x8035b1e9,
+    0x8036319d,
+    0x8036b14c,
+    0x8037318f,
+    0x8037b12a,
+    0x803831b0,
+    0x8038b16c,
+    0x80393181,
 };
 
 const size_t kOpenSSLReasonValuesLen = sizeof(kOpenSSLReasonValues) / sizeof(kOpenSSLReasonValues[0]);
@@ -1312,6 +1313,7 @@
     "TLSV1_ALERT_DECODE_ERROR\0"
     "TLSV1_ALERT_DECRYPTION_FAILED\0"
     "TLSV1_ALERT_DECRYPT_ERROR\0"
+    "TLSV1_ALERT_ECH_REQUIRED\0"
     "TLSV1_ALERT_EXPORT_RESTRICTION\0"
     "TLSV1_ALERT_INAPPROPRIATE_FALLBACK\0"
     "TLSV1_ALERT_INSUFFICIENT_SECURITY\0"
diff --git a/third_party/logdog/OWNERS b/third_party/logdog/OWNERS
new file mode 100644
index 0000000..a74cfbe
--- /dev/null
+++ b/third_party/logdog/OWNERS
@@ -0,0 +1 @@
+file://build/android/OWNERS
diff --git a/third_party/logdog/README.chromium b/third_party/logdog/README.chromium
new file mode 100644
index 0000000..1e7bcd40
--- /dev/null
+++ b/third_party/logdog/README.chromium
@@ -0,0 +1,15 @@
+Name: logdog
+Short Name: logdog
+URL: https://chromium.googlesource.com/infra/luci/luci-py/client/libs/logdog
+Version: 9a84af84d3fa62b230569cf1d3abf69cc7c576e2
+Revision: 9a84af84d3fa62b230569cf1d3abf69cc7c576e2
+License: Apache 2.0
+License File: NOT_SHIPPED
+Security Critical: no
+
+Description:
+This is used from build/android/pylib/utils/logdog_helper.py
+
+Local Modifications:
+See get.sh and this files is also generated by the script.
+
diff --git a/third_party/logdog/get.sh b/third_party/logdog/get.sh
new file mode 100755
index 0000000..87e75ab
--- /dev/null
+++ b/third_party/logdog/get.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+set -eux
+
+revision=9a84af84d3fa62b230569cf1d3abf69cc7c576e2
+
+cd $(dirname $0)
+
+rm -rf logdog
+git clone https://chromium.googlesource.com/infra/luci/luci-py/client/libs/logdog
+(
+    cd logdog
+    git checkout $revision
+
+    # remove unnecessary files.
+    rm -rf .git tests
+)
+
+cat <<EOF > README.chromium
+Name: logdog
+Short Name: logdog
+URL: https://chromium.googlesource.com/infra/luci/luci-py/client/libs/logdog
+Version: $revision
+Revision: $revision
+License: Apache 2.0
+License File: NOT_SHIPPED
+Security Critical: no
+
+Description:
+This is used from build/android/pylib/utils/logdog_helper.py
+
+Local Modifications:
+See get.sh and this files is also generated by the script.
+
+EOF
diff --git a/third_party/logdog/logdog/OWNERS b/third_party/logdog/logdog/OWNERS
new file mode 100644
index 0000000..760558a3
--- /dev/null
+++ b/third_party/logdog/logdog/OWNERS
@@ -0,0 +1 @@
+iannucci@chromium.org
diff --git a/third_party/logdog/logdog/__init__.py b/third_party/logdog/logdog/__init__.py
new file mode 100644
index 0000000..5c8814d0
--- /dev/null
+++ b/third_party/logdog/logdog/__init__.py
@@ -0,0 +1,3 @@
+# Copyright 2016 The LUCI Authors. All rights reserved.
+# Use of this source code is governed under the Apache License, Version 2.0
+# that can be found in the LICENSE file.
diff --git a/third_party/logdog/logdog/bootstrap.py b/third_party/logdog/logdog/bootstrap.py
new file mode 100644
index 0000000..aa88cef7
--- /dev/null
+++ b/third_party/logdog/logdog/bootstrap.py
@@ -0,0 +1,90 @@
+# Copyright 2016 The LUCI Authors. All rights reserved.
+# Use of this source code is governed under the Apache License, Version 2.0
+# that can be found in the LICENSE file.
+
+import collections
+import os
+
+from . import stream, streamname
+
+
+class NotBootstrappedError(RuntimeError):
+  """Raised when the current environment is missing Butler bootstrap variables.
+  """
+
+
+_ButlerBootstrapBase = collections.namedtuple('_ButlerBootstrapBase',
+    ('project', 'prefix', 'streamserver_uri', 'coordinator_host',
+     'namespace'))
+
+
+class ButlerBootstrap(_ButlerBootstrapBase):
+  """Loads LogDog Butler bootstrap parameters from the environment.
+
+  LogDog Butler adds variables describing the LogDog stream parameters to the
+  environment when it bootstraps an application. This class probes the
+  environment and identifies those parameters.
+  """
+
+  # TODO(iannucci): move all of these to LUCI_CONTEXT
+  _ENV_PROJECT = 'LOGDOG_STREAM_PROJECT'
+  _ENV_PREFIX = 'LOGDOG_STREAM_PREFIX'
+  _ENV_STREAM_SERVER_PATH = 'LOGDOG_STREAM_SERVER_PATH'
+  _ENV_COORDINATOR_HOST = 'LOGDOG_COORDINATOR_HOST'
+  _ENV_NAMESPACE = 'LOGDOG_NAMESPACE'
+
+  @classmethod
+  def probe(cls, env=None):
+    """Returns (ButlerBootstrap): The probed bootstrap environment.
+
+    Args:
+      env (dict): The environment to probe. If None, `os.getenv` will be used.
+
+    Raises:
+      NotBootstrappedError if the current environment is not boostrapped.
+    """
+    if env is None:
+      env = os.environ
+
+    def _check(kind, val):
+      if not val:
+        return val
+      try:
+        streamname.validate_stream_name(val)
+        return val
+      except ValueError as exp:
+        raise NotBootstrappedError('%s (%s) is invalid: %s' % (kind, val, exp))
+
+    streamserver_uri = env.get(cls._ENV_STREAM_SERVER_PATH)
+    if not streamserver_uri:
+      raise NotBootstrappedError('No streamserver in bootstrap environment.')
+
+    return cls(
+        project=env.get(cls._ENV_PROJECT, ''),
+        prefix=_check("Prefix", env.get(cls._ENV_PREFIX, '')),
+        streamserver_uri=streamserver_uri,
+        coordinator_host=env.get(cls._ENV_COORDINATOR_HOST, ''),
+        namespace=_check("Namespace", env.get(cls._ENV_NAMESPACE, '')))
+
+  def stream_client(self, reg=None):
+    """Returns: (StreamClient) stream client for the bootstrap streamserver URI.
+
+    If the Butler accepts external stream connections, it will export a
+    streamserver URI in the environment. This will create a StreamClient
+    instance to operate on the streamserver if one is defined.
+
+    Args:
+      reg (stream.StreamProtocolRegistry or None): The stream protocol registry
+          to use to create the stream. If None, the default global registry will
+          be used (recommended).
+
+    Raises:
+      ValueError: If no streamserver URI is present in the environment.
+    """
+    reg = reg or stream._default_registry
+    return reg.create(
+        self.streamserver_uri,
+        project=self.project,
+        prefix=self.prefix,
+        coordinator_host=self.coordinator_host,
+        namespace=self.namespace)
diff --git a/third_party/logdog/logdog/stream.py b/third_party/logdog/logdog/stream.py
new file mode 100644
index 0000000..6da02d4
--- /dev/null
+++ b/third_party/logdog/logdog/stream.py
@@ -0,0 +1,569 @@
+# Copyright 2016 The LUCI Authors. All rights reserved.
+# Use of this source code is governed under the Apache License, Version 2.0
+# that can be found in the LICENSE file.
+
+import collections
+import contextlib
+import json
+import os
+import posixpath
+import socket
+import sys
+import threading
+import time
+
+from . import streamname, varint
+
+
+if sys.platform == "win32":
+  from ctypes import GetLastError
+
+
+_StreamParamsBase = collections.namedtuple(
+    '_StreamParamsBase', ('name', 'type', 'content_type', 'tags'))
+
+
+# Magic number at the beginning of a Butler stream
+#
+# See "ProtocolFrameHeaderMagic" in:
+# <luci-go>/logdog/client/butlerlib/streamproto
+BUTLER_MAGIC = 'BTLR1\x1e'
+
+
+class StreamParams(_StreamParamsBase):
+  """Defines the set of parameters to apply to a new stream."""
+
+  # A text content stream.
+  TEXT = 'text'
+  # A binary content stream.
+  BINARY = 'binary'
+  # A datagram content stream.
+  DATAGRAM = 'datagram'
+
+  @classmethod
+  def make(cls, **kwargs):
+    """Returns (StreamParams): A new StreamParams instance with supplied values.
+
+    Any parameter that isn't supplied will be set to None.
+
+    Args:
+      kwargs (dict): Named parameters to apply.
+    """
+    return cls(**{f: kwargs.get(f) for f in cls._fields})
+
+  def validate(self):
+    """Raises (ValueError): if the parameters are not valid."""
+    streamname.validate_stream_name(self.name)
+
+    if self.type not in (self.TEXT, self.BINARY, self.DATAGRAM):
+      raise ValueError('Invalid type (%s)' % (self.type,))
+
+    if self.tags is not None:
+      if not isinstance(self.tags, collections.Mapping):
+        raise ValueError('Invalid tags type (%s)' % (self.tags,))
+      for k, v in self.tags.items():
+        streamname.validate_tag(k, v)
+
+  def to_json(self):
+    """Returns (str): The JSON representation of the StreamParams.
+
+    Converts stream parameters to JSON for Butler consumption.
+
+    Raises:
+      ValueError: if these parameters are not valid.
+    """
+    self.validate()
+
+    obj = {
+        'name': self.name,
+        'type': self.type,
+    }
+
+    def _maybe_add(key, value):
+      if value is not None:
+        obj[key] = value
+
+    _maybe_add('contentType', self.content_type)
+    _maybe_add('tags', self.tags)
+
+    # Note that "dumps' will dump UTF-8 by default, which is what Butler wants.
+    return json.dumps(obj, sort_keys=True, ensure_ascii=True, indent=None)
+
+
+class StreamProtocolRegistry(object):
+  """Registry of streamserver URI protocols and their client classes.
+  """
+
+  def __init__(self):
+    self._registry = {}
+
+  def register_protocol(self, protocol, client_cls):
+    assert issubclass(client_cls, StreamClient)
+    if self._registry.get(protocol) is not None:
+      raise KeyError('Duplicate protocol registered.')
+    self._registry[protocol] = client_cls
+
+  def create(self, uri, **kwargs):
+    """Returns (StreamClient): A stream client for the specified URI.
+
+    This uses the default StreamProtocolRegistry to instantiate a StreamClient
+    for the specified URI.
+
+    Args:
+      uri (str): The streamserver URI.
+      kwargs: keyword arguments to forward to the stream. See
+          StreamClient.__init__.
+
+    Raises:
+      ValueError: if the supplied URI references an invalid or improperly
+          configured streamserver.
+    """
+    uri = uri.split(':', 1)
+    if len(uri) != 2:
+      raise ValueError('Invalid stream server URI [%s]' % (uri,))
+    protocol, value = uri
+
+    client_cls = self._registry.get(protocol)
+    if not client_cls:
+      raise ValueError('Unknown stream client protocol (%s)' % (protocol,))
+    return client_cls._create(value, **kwargs)
+
+
+# Default (global) registry.
+_default_registry = StreamProtocolRegistry()
+create = _default_registry.create
+
+
+class StreamClient(object):
+  """Abstract base class for a streamserver client.
+  """
+
+  class _StreamBase(object):
+    """ABC for StreamClient streams."""
+
+    def __init__(self, stream_client, params):
+      self._stream_client = stream_client
+      self._params = params
+
+    @property
+    def params(self):
+      """Returns (StreamParams): The stream parameters."""
+      return self._params
+
+    @property
+    def path(self):
+      """Returns (streamname.StreamPath): The stream path.
+
+      Raises:
+        ValueError: if the stream path is invalid, or if the stream prefix is
+            not defined in the client.
+      """
+      return self._stream_client.get_stream_path(self._params.name)
+
+    def get_viewer_url(self):
+      """Returns (str): The viewer URL for this stream.
+
+      Raises:
+        KeyError: if information needed to construct the URL is missing.
+        ValueError: if the stream prefix or name do not form a valid stream
+            path.
+      """
+      return self._stream_client.get_viewer_url(self._params.name)
+
+
+  class _BasicStream(_StreamBase):
+    """Wraps a basic file descriptor, offering "write" and "close"."""
+
+    def __init__(self, stream_client, params, fd):
+      super(StreamClient._BasicStream, self).__init__(stream_client, params)
+      self._fd = fd
+
+    @property
+    def fd(self):
+      return self._fd
+
+    def fileno(self):
+      return self._fd.fileno()
+
+    def write(self, data):
+      return self._fd.write(data)
+
+    def close(self):
+      return self._fd.close()
+
+
+  class _DatagramStream(_StreamBase):
+    """Wraps a stream object to write length-prefixed datagrams."""
+
+    def __init__(self, stream_client, params, fd):
+      super(StreamClient._DatagramStream, self).__init__(stream_client, params)
+      self._fd = fd
+
+    def send(self, data):
+      varint.write_uvarint(self._fd, len(data))
+      self._fd.write(data)
+
+    def close(self):
+      return self._fd.close()
+
+
+  def __init__(self, project=None, prefix=None, coordinator_host=None,
+               namespace=''):
+    """Constructs a new base StreamClient instance.
+
+    Args:
+      project (str or None): If not None, the name of the log stream project.
+      prefix (str or None): If not None, the log stream session prefix.
+      coordinator_host (str or None): If not None, the name of the Coordinator
+          host that this stream client is bound to. This will be used to
+          construct viewer URLs for generated streams.
+      namespace (str): The prefix to apply to all streams opened by this client.
+    """
+    self._project = project
+    self._prefix = prefix
+    self._coordinator_host = coordinator_host
+    self._namespace = namespace
+
+    self._name_lock = threading.Lock()
+    self._names = set()
+
+  @property
+  def project(self):
+    """Returns (str or None): The stream project, or None if not configured."""
+    return self._project
+
+  @property
+  def prefix(self):
+    """Returns (str or None): The stream prefix, or None if not configured."""
+    return self._prefix
+
+  @property
+  def coordinator_host(self):
+    """Returns (str or None): The coordinator host, or None if not configured.
+    """
+    return self._coordinator_host
+
+  @property
+  def namespace(self):
+    """Returns (str): The namespace for all streams opened by this client.
+    Empty if not configured.
+    """
+    return self._namespace
+
+  def get_stream_path(self, name):
+    """Returns (streamname.StreamPath): The stream path.
+
+    Args:
+      name (str): The name of the stream.
+
+    Raises:
+      KeyError: if information needed to construct the path is missing.
+      ValueError: if the stream path is invalid, or if the stream prefix is
+          not defined in the client.
+    """
+    if not self._prefix:
+      raise KeyError('Stream prefix is not configured')
+    return streamname.StreamPath.make(self._prefix, name)
+
+  def get_viewer_url(self, name):
+    """Returns (str): The LogDog viewer URL for the named stream.
+
+    Args:
+      name (str): The name of the stream. This can also be a query glob.
+
+    Raises:
+      KeyError: if information needed to construct the URL is missing.
+      ValueError: if the stream prefix or name do not form a valid stream
+          path.
+    """
+    if not self._coordinator_host:
+      raise KeyError('Coordinator host is not configured')
+    if not self._project:
+      raise KeyError('Stream project is not configured')
+
+    return streamname.get_logdog_viewer_url(
+        self._coordinator_host,
+        self._project,
+        self.get_stream_path(name))
+
+  def _register_new_stream(self, name):
+    """Registers a new stream name.
+
+    The Butler will internally reject any duplicate stream names. However, there
+    isn't really feedback when this happens except a closed stream client. This
+    is a client-side check to provide a more user-friendly experience in the
+    event that a user attempts to register a duplicate stream name.
+
+    Note that this is imperfect, as something else could register stream names
+    with the same Butler instance and this library has no means of tracking.
+    This is a best-effort experience, not a reliable check.
+
+    Args:
+      name (str): The name of the stream.
+
+    Raises:
+      ValueError if the stream name has already been registered.
+    """
+    with self._name_lock:
+      if name in self._names:
+        raise ValueError("Duplicate stream name [%s]" % (name,))
+      self._names.add(name)
+
+  @classmethod
+  def _create(cls, value, **kwargs):
+    """Returns (StreamClient): A new stream client instance.
+
+    Validates the streamserver parameters and creates a new StreamClient
+    instance that connects to them.
+
+    Implementing classes must override this.
+    """
+    raise NotImplementedError()
+
+  def _connect_raw(self):
+    """Returns (file): A new file-like stream.
+
+    Creates a new raw connection to the streamserver. This connection MUST not
+    have any data written to it past initialization (if needed) when it has been
+    returned.
+
+    The file-like object must implement `write`, `fileno`, `flush`, and `close`.
+
+    Implementing classes must override this.
+    """
+    raise NotImplementedError()
+
+  def new_connection(self, params):
+    """Returns (file): A new configured stream.
+
+    The returned object implements (minimally) `write` and `close`.
+
+    Creates a new LogDog stream with the specified parameters.
+
+    Args:
+      params (StreamParams): The parameters to use with the new connection.
+
+    Raises:
+      ValueError if the stream name has already been used, or if the parameters
+      are not valid.
+    """
+    self._register_new_stream(params.name)
+    params_json = params.to_json()
+
+    fobj = self._connect_raw()
+    fobj.write(BUTLER_MAGIC)
+    varint.write_uvarint(fobj, len(params_json))
+    fobj.write(params_json)
+    return fobj
+
+  @contextlib.contextmanager
+  def text(self, name, **kwargs):
+    """Context manager to create, use, and teardown a TEXT stream.
+
+    This context manager creates a new butler TEXT stream with the specified
+    parameters, yields it, and closes it on teardown.
+
+    Args:
+      name (str): the LogDog name of the stream.
+      kwargs (dict): Log stream parameters. These may be any keyword arguments
+          accepted by `open_text`.
+
+    Returns (file): A file-like object to a Butler UTF-8 text stream supporting
+        `write`.
+    """
+    fobj = None
+    try:
+      fobj = self.open_text(name, **kwargs)
+      yield fobj
+    finally:
+      if fobj is not None:
+        fobj.close()
+
+  def open_text(self, name, content_type=None, tags=None):
+    """Returns (file): A file-like object for a single text stream.
+
+    This creates a new butler TEXT stream with the specified parameters.
+
+    Args:
+      name (str): the LogDog name of the stream.
+      content_type (str): The optional content type of the stream. If None, a
+          default content type will be chosen by the Butler.
+      tags (dict): An optional key/value dictionary pair of LogDog stream tags.
+
+    Returns (file): A file-like object to a Butler text stream. This object can
+        have UTF-8 text content written to it with its `write` method, and must
+        be closed when finished using its `close` method.
+    """
+    params = StreamParams.make(
+        name=posixpath.join(self._namespace, name),
+        type=StreamParams.TEXT,
+        content_type=content_type,
+        tags=tags)
+    return self._BasicStream(self, params, self.new_connection(params))
+
+  @contextlib.contextmanager
+  def binary(self, name, **kwargs):
+    """Context manager to create, use, and teardown a BINARY stream.
+
+    This context manager creates a new butler BINARY stream with the specified
+    parameters, yields it, and closes it on teardown.
+
+    Args:
+      name (str): the LogDog name of the stream.
+      kwargs (dict): Log stream parameters. These may be any keyword arguments
+          accepted by `open_binary`.
+
+    Returns (file): A file-like object to a Butler binary stream supporting
+        `write`.
+    """
+    fobj = None
+    try:
+      fobj = self.open_binary(name, **kwargs)
+      yield fobj
+    finally:
+      if fobj is not None:
+        fobj.close()
+
+  def open_binary(self, name, content_type=None, tags=None):
+    """Returns (file): A file-like object for a single binary stream.
+
+    This creates a new butler BINARY stream with the specified parameters.
+
+    Args:
+      name (str): the LogDog name of the stream.
+      content_type (str): The optional content type of the stream. If None, a
+          default content type will be chosen by the Butler.
+      tags (dict): An optional key/value dictionary pair of LogDog stream tags.
+
+    Returns (file): A file-like object to a Butler binary stream. This object
+        can have UTF-8 content written to it with its `write` method, and must
+        be closed when finished using its `close` method.
+    """
+    params = StreamParams.make(
+        name=posixpath.join(self._namespace, name),
+        type=StreamParams.BINARY,
+        content_type=content_type,
+        tags=tags)
+    return self._BasicStream(self, params, self.new_connection(params))
+
+  @contextlib.contextmanager
+  def datagram(self, name, **kwargs):
+    """Context manager to create, use, and teardown a DATAGRAM stream.
+
+    This context manager creates a new butler DATAAGRAM stream with the
+    specified parameters, yields it, and closes it on teardown.
+
+    Args:
+      name (str): the LogDog name of the stream.
+      kwargs (dict): Log stream parameters. These may be any keyword arguments
+          accepted by `open_datagram`.
+
+    Returns (_DatagramStream): A datagram stream object. Datagrams can be
+        written to it using its `send` method.
+    """
+    fobj = None
+    try:
+      fobj = self.open_datagram(name, **kwargs)
+      yield fobj
+    finally:
+      if fobj is not None:
+        fobj.close()
+
+  def open_datagram(self, name, content_type=None, tags=None):
+    """Creates a new butler DATAGRAM stream with the specified parameters.
+
+    Args:
+      name (str): the LogDog name of the stream.
+      content_type (str): The optional content type of the stream. If None, a
+          default content type will be chosen by the Butler.
+      tags (dict): An optional key/value dictionary pair of LogDog stream tags.
+
+    Returns (_DatagramStream): A datagram stream object. Datagrams can be
+        written to it using its `send` method. This object must be closed when
+        finished by using its `close` method.
+    """
+    params = StreamParams.make(
+        name=posixpath.join(self._namespace, name),
+        type=StreamParams.DATAGRAM,
+        content_type=content_type,
+        tags=tags)
+    return self._DatagramStream(self, params, self.new_connection(params))
+
+
+class _NamedPipeStreamClient(StreamClient):
+  """A StreamClient implementation that connects to a Windows named pipe.
+  """
+
+  def __init__(self, name, **kwargs):
+    r"""Initializes a new Windows named pipe stream client.
+
+    Args:
+      name (str): The name of the Windows named pipe to use (e.g., "\\.\name")
+    """
+    super(_NamedPipeStreamClient, self).__init__(**kwargs)
+    self._name = '\\\\.\\pipe\\' + name
+
+  @classmethod
+  def _create(cls, value, **kwargs):
+    return cls(value, **kwargs)
+
+  ERROR_PIPE_BUSY = 231
+
+  def _connect_raw(self):
+    # This is a similar procedure to the one in
+    #   https://github.com/microsoft/go-winio/blob/master/pipe.go (tryDialPipe)
+    while True:
+      try:
+        return open(self._name, 'wb+', buffering=0)
+      except (OSError, IOError):
+        if GetLastError() != self.ERROR_PIPE_BUSY:
+          raise
+      time.sleep(0.001)  # 1ms
+
+
+_default_registry.register_protocol('net.pipe', _NamedPipeStreamClient)
+
+
+class _UnixDomainSocketStreamClient(StreamClient):
+  """A StreamClient implementation that uses a UNIX domain socket.
+  """
+
+  class SocketFile(object):
+    """A write-only file-like object that writes to a UNIX socket."""
+
+    def __init__(self, sock):
+      self._sock = sock
+
+    def fileno(self):
+      return self._sock
+
+    def write(self, data):
+      self._sock.sendall(data)
+
+    def flush(self):
+      pass
+
+    def close(self):
+      self._sock.close()
+
+  def __init__(self, path, **kwargs):
+    """Initializes a new UNIX domain socket stream client.
+
+    Args:
+      path (str): The path to the named UNIX domain socket.
+    """
+    super(_UnixDomainSocketStreamClient, self).__init__(**kwargs)
+    self._path = path
+
+  @classmethod
+  def _create(cls, value, **kwargs):
+    if not os.path.exists(value):
+      raise ValueError('UNIX domain socket [%s] does not exist.' % (value,))
+    return cls(value, **kwargs)
+
+  def _connect_raw(self):
+    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+    sock.connect(self._path)
+    return self.SocketFile(sock)
+
+_default_registry.register_protocol('unix', _UnixDomainSocketStreamClient)
diff --git a/third_party/logdog/logdog/streamname.py b/third_party/logdog/logdog/streamname.py
new file mode 100644
index 0000000..8e9bf33
--- /dev/null
+++ b/third_party/logdog/logdog/streamname.py
@@ -0,0 +1,195 @@
+# Copyright 2016 The LUCI Authors. All rights reserved.
+# Use of this source code is governed under the Apache License, Version 2.0
+# that can be found in the LICENSE file.
+
+import collections
+import re
+import string
+
+# third_party/
+from six.moves import urllib
+
+_STREAM_SEP = '/'
+_ALNUM_CHARS = string.ascii_letters + string.digits
+_VALID_SEG_CHARS = _ALNUM_CHARS + ':_-.'
+_SEGMENT_RE_BASE = r'[a-zA-Z0-9][a-zA-Z0-9:_\-.]*'
+_SEGMENT_RE = re.compile('^' + _SEGMENT_RE_BASE + '$')
+_STREAM_NAME_RE = re.compile('^(' + _SEGMENT_RE_BASE + ')(/' +
+                             _SEGMENT_RE_BASE + ')*$')
+_MAX_STREAM_NAME_LENGTH = 4096
+
+_MAX_TAG_KEY_LENGTH = 64
+_MAX_TAG_VALUE_LENGTH = 4096
+
+
+def validate_stream_name(v, maxlen=None):
+  """Verifies that a given stream name is valid.
+
+  Args:
+    v (str): The stream name string.
+
+
+  Raises:
+    ValueError if the stream name is invalid.
+  """
+  maxlen = maxlen or _MAX_STREAM_NAME_LENGTH
+  if len(v) > maxlen:
+    raise ValueError('Maximum length exceeded (%d > %d)' % (len(v), maxlen))
+  if _STREAM_NAME_RE.match(v) is None:
+    raise ValueError('Invalid stream name: %r' % v)
+
+
+def validate_tag(key, value):
+  """Verifies that a given tag key/value is valid.
+
+  Args:
+    k (str): The tag key.
+    v (str): The tag value.
+
+  Raises:
+    ValueError if the tag is not valid.
+  """
+  validate_stream_name(key, maxlen=_MAX_TAG_KEY_LENGTH)
+  validate_stream_name(value, maxlen=_MAX_TAG_VALUE_LENGTH)
+
+
+def normalize_segment(seg, prefix=None):
+  """Given a string (str|unicode), mutate it into a valid segment name (str).
+
+  This operates by replacing invalid segment name characters with underscores
+  (_) when encountered.
+
+  A special case is when "seg" begins with non-alphanumeric character. In this
+  case, we will prefix it with the "prefix", if one is supplied. Otherwise,
+  raises ValueError.
+
+  See _VALID_SEG_CHARS for all valid characters for a segment.
+
+  Raises:
+    ValueError: If normalization could not be successfully performed.
+  """
+  if not seg:
+    if prefix is None:
+      raise ValueError('Cannot normalize empty segment with no prefix.')
+    seg = prefix
+  else:
+
+    def replace_if_invalid(ch, first=False):
+      ret = ch if ch in _VALID_SEG_CHARS else '_'
+      if first and ch not in _ALNUM_CHARS:
+        if prefix is None:
+          raise ValueError('Segment has invalid beginning, and no prefix was '
+                           'provided.')
+        return prefix + ret
+      return ret
+
+    seg = ''.join(replace_if_invalid(ch, i == 0) for i, ch in enumerate(seg))
+
+  if _SEGMENT_RE.match(seg) is None:
+    raise AssertionError('Normalized segment is still invalid: %r' % seg)
+
+  # v could be of type unicode. As a valid stream name contains only ascii
+  # characters, it is safe to transcode v to ascii encoding (become str type).
+  if isinstance(seg, unicode):
+    return seg.encode('ascii')
+  return seg
+
+
+def normalize(v, prefix=None):
+  """Given a string (str|unicode), mutate it into a valid stream name (str).
+
+  This operates by replacing invalid stream name characters with underscores (_)
+  when encountered.
+
+  A special case is when any segment of "v" begins with an non-alphanumeric
+  character. In this case, we will prefix the segment with the "prefix", if one
+  is supplied. Otherwise, raises ValueError.
+
+  See _STREAM_NAME_RE for a description of a valid stream name.
+
+  Raises:
+    ValueError: If normalization could not be successfully performed.
+  """
+  normalized = _STREAM_SEP.join(
+      normalize_segment(seg, prefix=prefix) for seg in v.split(_STREAM_SEP))
+  # Validate the resulting string.
+  validate_stream_name(normalized)
+  return normalized
+
+
+class StreamPath(collections.namedtuple('_StreamPath', ('prefix', 'name'))):
+  """StreamPath is a full stream path.
+
+  This consists of both a stream prefix and a stream name.
+
+  When constructed with parse or make, the stream path must be completely valid.
+  However, invalid stream paths may be constructed by manually instantiation.
+  This can be useful for wildcard query values (e.g., "prefix='foo/*/bar/**'").
+  """
+
+  @classmethod
+  def make(cls, prefix, name):
+    """Returns (StreamPath): The validated StreamPath instance.
+
+    Args:
+      prefix (str): the prefix component
+      name (str): the name component
+
+    Raises:
+      ValueError: If path is not a full, valid stream path string.
+    """
+    inst = cls(prefix=prefix, name=name)
+    inst.validate()
+    return inst
+
+  @classmethod
+  def parse(cls, path):
+    """Returns (StreamPath): The parsed StreamPath instance.
+
+    Args:
+      path (str): the full stream path to parse.
+
+    Raises:
+      ValueError: If path is not a full, valid stream path string.
+    """
+    parts = path.split('/+/', 1)
+    if len(parts) != 2:
+      raise ValueError('Not a full stream path: [%s]' % (path,))
+    return cls.make(*parts)
+
+  def validate(self):
+    """Raises: ValueError if this is not a valid stream name."""
+    try:
+      validate_stream_name(self.prefix)
+    except ValueError as e:
+      raise ValueError('Invalid prefix component [%s]: %s' % (
+          self.prefix, e.message,))
+
+    try:
+      validate_stream_name(self.name)
+    except ValueError as e:
+      raise ValueError('Invalid name component [%s]: %s' % (
+          self.name, e.message,))
+
+  def __str__(self):
+    return '%s/+/%s' % (self.prefix, self.name)
+
+
+def get_logdog_viewer_url(host, project, *stream_paths):
+  """Returns (str): The LogDog viewer URL for the named stream(s).
+
+  Args:
+    host (str): The name of the Coordiantor host.
+    project (str): The project name.
+    stream_paths: A set of StreamPath instances for the stream paths to
+        generate the URL for.
+  """
+  return urllib.parse.urlunparse((
+      'https',  # Scheme
+      host,  # netloc
+      'v/',  # path
+      '',  # params
+      '&'.join(('s=%s' % (urllib.parse.quote('%s/%s' % (project, path), ''))
+                for path in stream_paths)),  # query
+      '',  # fragment
+  ))
diff --git a/third_party/logdog/logdog/varint.py b/third_party/logdog/logdog/varint.py
new file mode 100644
index 0000000..7bf3cca
--- /dev/null
+++ b/third_party/logdog/logdog/varint.py
@@ -0,0 +1,63 @@
+# Copyright 2016 The LUCI Authors. All rights reserved.
+# Use of this source code is governed under the Apache License, Version 2.0
+# that can be found in the LICENSE file.
+
+import os
+import sys
+
+
+def write_uvarint(w, val):
+  """Writes a varint value to the supplied file-like object.
+
+  Args:
+    w (object): A file-like object to write to. Must implement write.
+    val (number): The value to write. Must be >= 0.
+
+  Returns (int): The number of bytes that were written.
+
+  Raises:
+    ValueError if 'val' is < 0.
+  """
+  if val < 0:
+    raise ValueError('Cannot encode negative value, %d' % (val,))
+
+  count = 0
+  while val > 0 or count == 0:
+    byte = (val & 0b01111111)
+    val >>= 7
+    if val > 0:
+      byte |= 0b10000000
+
+    w.write(chr(byte))
+    count += 1
+  return count
+
+
+def read_uvarint(r):
+  """Reads a uvarint from a stream.
+
+  This is targeted towards testing, and will not be used in production code.
+
+  Args:
+    r (object): A file-like object to read from. Must implement read.
+
+  Returns: (value, count)
+    value (int): The decoded varint number.
+    count (int): The number of bytes that were read from 'r'.
+
+  Raises:
+    ValueError if the encoded varint is not terminated.
+  """
+  count = 0
+  result = 0
+  while True:
+    byte = r.read(1)
+    if len(byte) == 0:
+      raise ValueError('UVarint was not terminated')
+
+    byte = ord(byte)
+    result |= ((byte & 0b01111111) << (7 * count))
+    count += 1
+    if byte & 0b10000000 == 0:
+      break
+  return result, count
diff --git a/third_party/wayland-protocols/unstable/remote-shell/remote-shell-unstable-v1.xml b/third_party/wayland-protocols/unstable/remote-shell/remote-shell-unstable-v1.xml
index a4be45a..cfc9c85 100644
--- a/third_party/wayland-protocols/unstable/remote-shell/remote-shell-unstable-v1.xml
+++ b/third_party/wayland-protocols/unstable/remote-shell/remote-shell-unstable-v1.xml
@@ -945,6 +945,8 @@
       <entry name="close" value="16" summary="a button to close the window"/>
       <entry name="zoom" value="32"
 	     summary="a mask to turn the maximize_restore button to zoom button"/>
+      <entry name="center" value="64"
+	     summary="a customizable, center-aligned button"/>
     </enum>
 
     <request name="set_frame_buttons" since="13">
diff --git a/third_party/webgpu-cts/ts_sources.txt b/third_party/webgpu-cts/ts_sources.txt
index 7903194..6c42b2d 100644
--- a/third_party/webgpu-cts/ts_sources.txt
+++ b/third_party/webgpu-cts/ts_sources.txt
@@ -16,11 +16,11 @@
 src/common/framework/query/parseQuery.ts
 src/common/framework/logging/test_case_recorder.ts
 src/common/framework/fixture.ts
+src/common/framework/params_builder.ts
 src/common/framework/test_group.ts
 src/common/framework/test_suite_listing.ts
 src/common/framework/tree.ts
 src/common/framework/file_loader.ts
-src/common/framework/params_builder.ts
 src/common/framework/preprocessor.ts
 src/common/framework/version.ts
 src/common/framework/logging/logger.ts
diff --git a/third_party/widevine/cdm/BUILD.gn b/third_party/widevine/cdm/BUILD.gn
index 24668a4a..55c3b1b1 100644
--- a/third_party/widevine/cdm/BUILD.gn
+++ b/third_party/widevine/cdm/BUILD.gn
@@ -38,10 +38,12 @@
         "chromeos/$widevine_arch/widevine_cdm_version.h"
     widevine_cdm_binary_files = [ "chromeos/$widevine_arch/libwidevinecdm.so" ]
   } else if (is_linux || is_chromeos_lacros) {
-    widevine_cdm_version_h_file = "linux/$widevine_arch/widevine_cdm_version.h"
-    widevine_cdm_binary_files = [ "linux/$widevine_arch/libwidevinecdm.so" ]
+    widevine_cdm_version_h_file =
+        "$widevine_root/linux/$widevine_arch/widevine_cdm_version.h"
+    widevine_cdm_binary_files =
+        [ "$widevine_root/linux/$widevine_arch/libwidevinecdm.so" ]
     widevine_cdm_manifest_and_license_files = [
-      "linux/$widevine_arch/manifest.json",
+      "$widevine_root/linux/$widevine_arch/manifest.json",
       "../LICENSE",
     ]
   } else if (is_win) {
diff --git a/third_party/widevine/cdm/widevine.gni b/third_party/widevine/cdm/widevine.gni
index d676aee..102eea06 100644
--- a/third_party/widevine/cdm/widevine.gni
+++ b/third_party/widevine/cdm/widevine.gni
@@ -54,6 +54,10 @@
 declare_args() {
   # Widevine CDM is bundled as part of Google Chrome builds.
   bundle_widevine_cdm = enable_library_widevine_cdm && is_chrome_branded
+
+  # Customized relative root directory to //third_party/widevine/cdm for
+  # cdm files. It is only applied on "is_linux" or "is_chromeos_lacros"
+  widevine_root = "."
 }
 
 # Enable Widevine CDM host verification, which will sign additional binaries
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index cf06533..89093d17 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -842,6 +842,8 @@
 
     # TODO(jmadill/ynovikov): Migrate these to angle waterfall. http://anglebug.com/4483
     'tryserver.chromium.angle': {
+      'android-angle-chromium-try': 'gpu_tests_android_release_trybot_arm64_fastbuild',
+      'android-angle-try': 'angle_specific_no_trace_android_release_trybot_arm64',
       'android_angle_rel_ng': 'gpu_tests_android_release_trybot_arm64',
       'android_angle_deqp_rel_ng': 'angle_deqp_android_release_trybot_arm64',
       'fuchsia-angle-rel': 'gpu_fyi_tests_release_trybot_fuchsia',
diff --git a/tools/mb/mb_config_expectations/tryserver.chromium.angle.json b/tools/mb/mb_config_expectations/tryserver.chromium.angle.json
index 03f3cf57..930427e 100644
--- a/tools/mb/mb_config_expectations/tryserver.chromium.angle.json
+++ b/tools/mb/mb_config_expectations/tryserver.chromium.angle.json
@@ -1,4 +1,33 @@
 {
+  "android-angle-chromium-try": {
+    "gn_args": {
+      "dcheck_always_on": true,
+      "disable_android_lint": true,
+      "ffmpeg_branding": "Chrome",
+      "is_component_build": false,
+      "is_debug": false,
+      "proprietary_codecs": true,
+      "symbol_level": 1,
+      "target_cpu": "arm64",
+      "target_os": "android",
+      "use_errorprone_java_compiler": false,
+      "use_goma": true,
+      "use_static_angle": true
+    }
+  },
+  "android-angle-try": {
+    "gn_args": {
+      "build_angle_deqp_tests": true,
+      "build_angle_gles1_conform_tests": true,
+      "dcheck_always_on": true,
+      "is_component_build": true,
+      "is_debug": false,
+      "symbol_level": 1,
+      "target_cpu": "arm64",
+      "target_os": "android",
+      "use_goma": true
+    }
+  },
   "android_angle_deqp_rel_ng": {
     "gn_args": {
       "build_angle_deqp_tests": true,
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index aaa0369..234d070 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -32891,6 +32891,7 @@
   <int value="3930" label="HTMLMediaElementControlsListNoPlaybackRate"/>
   <int value="3931" label="DocumentTransition"/>
   <int value="3932" label="SpeculationRules"/>
+  <int value="3933" label="V8AbortSignal_Abort_Method"/>
 </enum>
 
 <enum name="FeaturePolicyAllowlistType">
@@ -46532,8 +46533,6 @@
   <int value="-853594220" label="disable-new-avatar-menu"/>
   <int value="-853455968"
       label="OmniboxDefaultTypedNavigationsToHttps:disabled"/>
-  <int value="-852828854"
-      label="AutofillSuggestVirtualCardsOnlyOnFullFormDetection:enabled"/>
   <int value="-850821337" label="WebContentsForceDark:enabled"/>
   <int value="-848691867" label="DesktopPWAWindowing:enabled"/>
   <int value="-847735582" label="NotificationsViaHelperApp:enabled"/>
@@ -46668,6 +46667,8 @@
   <int value="-728461030" label="AutofillPruneSuggestions:disabled"/>
   <int value="-727860269" label="WebAuthenticationBle:disabled"/>
   <int value="-726892130" label="AndroidMessagesIntegration:disabled"/>
+  <int value="-726844226"
+      label="AutofillSuggestVirtualCardsOnIncompleteForm:enabled"/>
   <int value="-726567328" label="disable-virtual-keyboard"/>
   <int value="-725264428" label="CompositingBasedThrottling:enabled"/>
   <int value="-723224470" label="enable-password-force-saving:enabled"/>
@@ -48082,8 +48083,6 @@
   <int value="517429103" label="AutofillImportDynamicForms:enabled"/>
   <int value="517568645" label="AnimatedAppMenuIcon:disabled"/>
   <int value="518419320" label="RemoteCopyProgressNotification:disabled"/>
-  <int value="519028964"
-      label="AutofillSuggestVirtualCardsOnlyOnFullFormDetection:disabled"/>
   <int value="519140642" label="SendWebUIJavaScriptErrorReports:enabled"/>
   <int value="520738365" label="OmniboxPedalsBatch2NonEnglish:enabled"/>
   <int value="520982116" label="BuiltInModuleAll:enabled"/>
@@ -49501,6 +49500,8 @@
   <int value="1739782037" label="LinkDoctorDeprecationAndroid:enabled"/>
   <int value="1741855651"
       label="OmniboxTabSwitchSuggestionsDedicatedRow:disabled"/>
+  <int value="1741958365"
+      label="AutofillSuggestVirtualCardsOnIncompleteForm:disabled"/>
   <int value="1742398600"
       label="OmniboxUIExperimentBoldUserTextOnSearchSuggestions:enabled"/>
   <int value="1745053254" label="ClickToCallOpenDialerDirectly:enabled"/>
diff --git a/tools/metrics/histograms/histograms_xml/METRIC_REVIEWER_OWNERS b/tools/metrics/histograms/histograms_xml/METRIC_REVIEWER_OWNERS
index 9ab9d251..16318df 100644
--- a/tools/metrics/histograms/histograms_xml/METRIC_REVIEWER_OWNERS
+++ b/tools/metrics/histograms/histograms_xml/METRIC_REVIEWER_OWNERS
@@ -49,6 +49,7 @@
 toyoshim@chromium.org
 vasilii@chromium.org
 vsemeniuk@google.com
+wanderview@chromium.org
 xidachen@chromium.org
 xinghuilu@chromium.org
 yashard@chromium.org
diff --git a/tools/metrics/histograms/histograms_xml/service/OWNERS b/tools/metrics/histograms/histograms_xml/service/OWNERS
index a7315828..5c58f4b5 100644
--- a/tools/metrics/histograms/histograms_xml/service/OWNERS
+++ b/tools/metrics/histograms/histograms_xml/service/OWNERS
@@ -4,4 +4,7 @@
 # Use chromium-metrics-reviews@google.com as a backup.
 
 # Primary
+wanderview@chromium.org
+
+# Secondary
 pwnall@chromium.org
diff --git a/tools/metrics/histograms/histograms_xml/simple/OWNERS b/tools/metrics/histograms/histograms_xml/simple/OWNERS
new file mode 100644
index 0000000..87ef9e52
--- /dev/null
+++ b/tools/metrics/histograms/histograms_xml/simple/OWNERS
@@ -0,0 +1,7 @@
+per-file OWNERS=file://tools/metrics/histograms/histograms_xml/METRIC_REVIEWER_OWNERS
+
+# Prefer sending CLs to the owners listed below.
+# Use chromium-metrics-reviews@google.com as a backup.
+
+# Primary
+wanderview@chromium.org
diff --git a/tools/metrics/histograms/histograms_xml/storage/OWNERS b/tools/metrics/histograms/histograms_xml/storage/OWNERS
index ae17e08..ea507862 100644
--- a/tools/metrics/histograms/histograms_xml/storage/OWNERS
+++ b/tools/metrics/histograms/histograms_xml/storage/OWNERS
@@ -8,3 +8,4 @@
 
 # Secondary
 pwnall@chromium.org
+wanderview@chromium.org
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index 5f60c62..5f0e9826 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -523,6 +523,49 @@
       was not prerendered.
     </summary>
   </metric>
+  <metric name="SubFrame.MobileFriendliness.AllowUserZoom" enum="Boolean">
+    <summary>
+      Whether the page allows the user to zoom in/out, in the AMP subframe.
+    </summary>
+  </metric>
+  <metric name="SubFrame.MobileFriendliness.BadTapTargetsRatio">
+    <summary>
+      Percentage of tap targets whose center position is within another tap
+      target (expanded by a margin), in the AMP subframe. The detail of the
+      algorithm is go/bad-tap-target-ukm. If evaluation time budget exceeded,
+      this will be is -2.
+    </summary>
+  </metric>
+  <metric name="SubFrame.MobileFriendliness.SmallTextRatio">
+    <summary>
+      Percentage of small font text area in total text area, in the AMP
+      subframe.
+    </summary>
+  </metric>
+  <metric
+      name="SubFrame.MobileFriendliness.TextContentOutsideViewportPercentage">
+    <summary>
+      Percentage of pixels of text and images horizontally outside the viewport,
+      relative to the frame width, in the AMP subframe.
+    </summary>
+  </metric>
+  <metric name="SubFrame.MobileFriendliness.ViewportDeviceWidth" enum="Boolean">
+    <summary>
+      Whether the width of the viewport is specified as device-width or not, in
+      the AMP subframe.
+    </summary>
+  </metric>
+  <metric name="SubFrame.MobileFriendliness.ViewportHardcodedWidth">
+    <summary>
+      Specified hardcoded viewport width in CSS pixels, in the AMP subframe.
+    </summary>
+  </metric>
+  <metric name="SubFrame.MobileFriendliness.ViewportInitialScaleX10">
+    <summary>
+      Specified initial viewport scaling, in the AMP subframe, multiplied by 10.
+      1 means 0.1, 100 means 10. [1-100].
+    </summary>
+  </metric>
   <metric
       name="SubFrame.PaintTiming.NavigationToExperimentalLargestContentfulPaint">
     <obsolete>
@@ -14053,14 +14096,14 @@
   <metric name="DayOfWeek">
     <summary>
       An enum representing the of the week that the data was logged, defined in
-      |chromeos::power::ml::ScreenBrightness::Features::ActivityData::
+      |ash::power::ml::ScreenBrightnessEvent::Features::ActivityData::
       DayOfWeek|.
     </summary>
   </metric>
   <metric name="DeviceMode">
     <summary>
       An enum representing the mode of the device, defined in
-      |chromeos::power::ml::ScreenBrightness::Feature::EnvData:DeviceMode|
+      |ash::power::ml::ScreenBrightnessEvent::Features::EnvData:DeviceMode|
     </summary>
   </metric>
   <metric name="HourOfDay">
@@ -14193,7 +14236,7 @@
   <metric name="Reason">
     <summary>
       The reason that the event is logged. Values are enumerated in
-      |chromeos::power::ml::ScreenBrightness::Event::Reason|.
+      |ash::power::ml::ScreenBrightnessEvent::Event::Reason|.
     </summary>
   </metric>
   <metric name="RecentTimeActiveSec">
@@ -16007,19 +16050,19 @@
   <metric name="DeviceManagement">
     <summary>
       An enum representing whether the device is managed, defined in
-      |chromeos::power::ml::UserActivityEvent::Features::ManagementType|.
+      |ash::power::ml::UserActivityEvent::Features::ManagementType|.
     </summary>
   </metric>
   <metric name="DeviceMode">
     <summary>
       An enum representing the mode of the device, defined in
-      |chromeos::power::ml::UserActivityEvent::Feature::DeviceMode|
+      |ash::power::ml::UserActivityEvent::Features::DeviceMode|
     </summary>
   </metric>
   <metric name="DeviceType">
     <summary>
       An enum representing the type of the device, defined in
-      |chromeos::power::ml::UserActivityEvent::Feature::DeviceType|
+      |ash::power::ml::UserActivityEvent::Features::DeviceType|
     </summary>
   </metric>
   <metric name="EventLogDuration">
@@ -16032,13 +16075,13 @@
   <metric name="EventReason">
     <summary>
       An enum representing the reason of the event, defined in
-      |chromeos::power::ml::UserActivityEvent::Event::Reason|.
+      |ash::power::ml::UserActivityEvent::Event::Reason|.
     </summary>
   </metric>
   <metric name="EventType">
     <summary>
       An enum representing the type of the event, defined in
-      |chromeos::power::ml::UserActivityEvent::Event::Type|.
+      |ash::power::ml::UserActivityEvent::Event::Type|.
     </summary>
   </metric>
   <metric name="KeyEventsInLastHour">
@@ -16052,7 +16095,7 @@
   <metric name="LastActivityDay">
     <summary>
       An enum representing the last activity day of the week, defined in
-      |chromeos::power::ml::UserActivityEvent::Feature::DayOfWeek|.
+      |ash::power::ml::UserActivityEvent::Features::DayOfWeek|.
     </summary>
   </metric>
   <metric name="LastActivityTime">
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index d3a9519..2b42c5e 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -9,8 +9,8 @@
             "remote_path": "perfetto_binaries/trace_processor_shell/mac/bae8193de6c017394901163b7817157342914679/trace_processor_shell"
         },
         "linux": {
-            "hash": "e9b45644623948daee7eab4005f3545e3bee08d8",
-            "remote_path": "perfetto_binaries/trace_processor_shell/linux/853a6d0426035a895bd99a1a89764b9d8d7fac20/trace_processor_shell"
+            "hash": "6ef008bb60e20fb52f91a240ab6ad1b03a309b8e",
+            "remote_path": "perfetto_binaries/trace_processor_shell/linux/b9f666ed45833fe05f0a3cb51b4676ca713d72a4/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/perf/page_sets/desktop_ui/download_shelf_story.py b/tools/perf/page_sets/desktop_ui/download_shelf_story.py
index d9c3f88..b7eb1985 100644
--- a/tools/perf/page_sets/desktop_ui/download_shelf_story.py
+++ b/tools/perf/page_sets/desktop_ui/download_shelf_story.py
@@ -39,7 +39,8 @@
         tabs.New(url=url)
       except Exception:
         pass
-    self._devtools = action_runner.tab.browser.GetUIDevtools()
+    if not IsMac():
+      self._devtools = action_runner.tab.browser.GetUIDevtools()
 
   def IsWebUI(self):
     return 'webui' in self.NAME
diff --git a/ui/compositor/layer.cc b/ui/compositor/layer.cc
index a869508..be3f4fc 100644
--- a/ui/compositor/layer.cc
+++ b/ui/compositor/layer.cc
@@ -30,6 +30,7 @@
 #include "components/viz/common/resources/transferable_resource.h"
 #include "ui/compositor/compositor_switches.h"
 #include "ui/compositor/layer_animator.h"
+#include "ui/compositor/layer_delegate.h"
 #include "ui/compositor/layer_observer.h"
 #include "ui/compositor/paint_context.h"
 #include "ui/gfx/animation/animation.h"
@@ -38,8 +39,10 @@
 #include "ui/gfx/geometry/point3_f.h"
 #include "ui/gfx/geometry/point_conversions.h"
 #include "ui/gfx/geometry/rect_conversions.h"
+#include "ui/gfx/geometry/rounded_corners_f.h"
 #include "ui/gfx/geometry/size_conversions.h"
 #include "ui/gfx/interpolated_transform.h"
+#include "ui/gfx/transform.h"
 
 namespace ui {
 namespace {
@@ -75,6 +78,9 @@
     dest->set_delegate(this);
   }
 
+  LayerMirror(const LayerMirror&) = delete;
+  LayerMirror& operator=(const LayerMirror&) = delete;
+
   ~LayerMirror() override {
     dest_->RemoveObserver(this);
     dest_->set_delegate(nullptr);
@@ -99,8 +105,6 @@
  private:
   Layer* const source_;
   Layer* const dest_;
-
-  DISALLOW_COPY_AND_ASSIGN(LayerMirror);
 };
 
 // Manages the subpixel offset data for a given set of parameters (device
@@ -108,6 +112,9 @@
 class Layer::SubpixelPositionOffsetCache {
  public:
   SubpixelPositionOffsetCache() = default;
+  SubpixelPositionOffsetCache(const SubpixelPositionOffsetCache&) = delete;
+  SubpixelPositionOffsetCache& operator=(const SubpixelPositionOffsetCache&) =
+      delete;
   ~SubpixelPositionOffsetCache() = default;
 
   gfx::Vector2dF GetSubpixelOffset(float device_scale_factor,
@@ -173,8 +180,6 @@
 
   // True if the subpixel offset was computed and set by an external source.
   bool has_explicit_subpixel_offset_ = false;
-
-  DISALLOW_COPY_AND_ASSIGN(SubpixelPositionOffsetCache);
 };
 
 Layer::Layer(LayerType type)
@@ -391,7 +396,7 @@
   // Stop (and complete) an ongoing animation to update the bounds immediately.
   LayerAnimator* child_animator = child->animator_.get();
   if (child_animator)
-    child_animator->StopAnimatingProperty(ui::LayerAnimationElement::BOUNDS);
+    child_animator->StopAnimatingProperty(LayerAnimationElement::BOUNDS);
 
   // Do not proceed if |this| or |child| is released by an animation observer
   // of |child|'s bounds animation.
@@ -836,7 +841,7 @@
 }
 
 // Note: The code that sets this flag would be responsible to unset it on that
-// ui::Layer. We do not want to clone this flag to a cloned layer by accident,
+// Layer. We do not want to clone this flag to a cloned layer by accident,
 // which could be a supprise. But we want to preserve it after switching to a
 // new cc::Layer. There could be a whole subtree and the root changed, but does
 // not mean we want to treat the cache all different.
@@ -879,7 +884,7 @@
 }
 
 // Note: The code that sets this flag would be responsible to unset it on that
-// ui::Layer. We do not want to clone this flag to a cloned layer by accident,
+// Layer. We do not want to clone this flag to a cloned layer by accident,
 // which could be a supprise. But we want to preserve it after switching to a
 // new cc::Layer. There could be a whole subtree and the root changed, but does
 // not mean we want to treat the trilinear filtering all different.
@@ -1582,7 +1587,7 @@
   return compositor ? compositor->refresh_rate() : 60.0;
 }
 
-ui::Layer* Layer::GetLayer() {
+Layer* Layer::GetLayer() {
   return this;
 }
 
diff --git a/ui/compositor/layer.h b/ui/compositor/layer.h
index f572b6f..3711fd7 100644
--- a/ui/compositor/layer.h
+++ b/ui/compositor/layer.h
@@ -12,10 +12,7 @@
 #include <string>
 #include <vector>
 
-#include "base/compiler_specific.h"
-#include "base/containers/flat_set.h"
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "cc/base/region.h"
@@ -27,12 +24,9 @@
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/compositor/compositor.h"
 #include "ui/compositor/layer_animation_delegate.h"
-#include "ui/compositor/layer_delegate.h"
 #include "ui/compositor/layer_type.h"
 #include "ui/gfx/geometry/rect.h"
-#include "ui/gfx/geometry/rounded_corners_f.h"
 #include "ui/gfx/image/image_skia.h"
-#include "ui/gfx/transform.h"
 
 namespace cc {
 class Layer;
@@ -43,6 +37,11 @@
 class TextureLayer;
 }
 
+namespace gfx {
+class RoundedCornersF;
+class Transform;
+}  // namespace gfx
+
 namespace viz {
 class CopyOutputRequest;
 struct TransferableResource;
@@ -52,6 +51,7 @@
 
 class Compositor;
 class LayerAnimator;
+class LayerDelegate;
 class LayerObserver;
 class LayerOwner;
 class LayerThreadedAnimationDelegate;
@@ -72,6 +72,8 @@
  public:
   using ShapeRects = std::vector<gfx::Rect>;
   explicit Layer(LayerType type = LAYER_TEXTURED);
+  Layer(const Layer&) = delete;
+  Layer& operator=(const Layer&) = delete;
   ~Layer() override;
 
   // Note that only solid color and surface content is copied.
@@ -191,8 +193,6 @@
   void SetTransform(const gfx::Transform& transform);
   const gfx::Transform& transform() const { return cc_layer_->transform(); }
 
-  gfx::PointF position() const { return cc_layer_->position(); }
-
   // Return the target transform if animator is running, or the current
   // transform otherwise.
   gfx::Transform GetTargetTransform() const;
@@ -588,7 +588,7 @@
   gfx::Rect GetClipRectForAnimation() const override;
   gfx::RoundedCornersF GetRoundedCornersForAnimation() const override;
   float GetDeviceScaleFactor() const override;
-  ui::Layer* GetLayer() override;
+  Layer* GetLayer() override;
   cc::Layer* GetCcLayer() const override;
   LayerThreadedAnimationDelegate* GetThreadedAnimationDelegate() override;
   LayerAnimatorCollection* GetLayerAnimatorCollection() override;
@@ -787,8 +787,6 @@
 #endif
 
   base::WeakPtrFactory<Layer> weak_ptr_factory_{this};
-
-  DISALLOW_COPY_AND_ASSIGN(Layer);
 };
 
 }  // namespace ui
diff --git a/ui/compositor/layer_owner_unittest.cc b/ui/compositor/layer_owner_unittest.cc
index 65ec668..feadd35 100644
--- a/ui/compositor/layer_owner_unittest.cc
+++ b/ui/compositor/layer_owner_unittest.cc
@@ -14,6 +14,7 @@
 #include "ui/compositor/layer.h"
 #include "ui/compositor/layer_animation_observer.h"
 #include "ui/compositor/layer_animator.h"
+#include "ui/compositor/layer_delegate.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
 #include "ui/compositor/scoped_layer_animation_settings.h"
 #include "ui/compositor/test/test_context_factories.h"
diff --git a/ui/compositor/layer_unittest.cc b/ui/compositor/layer_unittest.cc
index a35a9e1..62aa365 100644
--- a/ui/compositor/layer_unittest.cc
+++ b/ui/compositor/layer_unittest.cc
@@ -51,6 +51,7 @@
 #include "ui/compositor/layer_animation_observer.h"
 #include "ui/compositor/layer_animation_sequence.h"
 #include "ui/compositor/layer_animator.h"
+#include "ui/compositor/layer_delegate.h"
 #include "ui/compositor/paint_context.h"
 #include "ui/compositor/paint_recorder.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
diff --git a/ui/file_manager/file_manager/background/js/runtime_loaded_test_util.js b/ui/file_manager/file_manager/background/js/runtime_loaded_test_util.js
index 5ea65a80..f8e58551 100644
--- a/ui/file_manager/file_manager/background/js/runtime_loaded_test_util.js
+++ b/ui/file_manager/file_manager/background/js/runtime_loaded_test_util.js
@@ -50,6 +50,16 @@
  */
 let MetadataStatsType;
 
+if (!assert) {
+  function assert(condition, opt_message) {
+    if (!condition) {
+      const message =
+          'Assertion error' + (opt_message ? ' ' + opt_message : '');
+      throw new Error(message);
+    }
+  }
+}
+
 /**
  * Extract the information of the given element.
  * @param {Element} element Element to be extracted.
@@ -1062,7 +1072,15 @@
 
   const args = request.args.slice();  // shallow copy
   if (request.appId) {
-    if (window.appWindows[request.appId]) {
+    if (request.contentWindow) {
+      // request.contentWindow is present if this function was called via
+      // test.swaTestMessageListener, an alternative code path used by the test
+      // harness to send messages directly to Files SWA. Test code uses
+      // request.contentWindow only, thus by setting it, we avoid having to
+      // change the test.utils functions to check for contentWindow || window,
+      // just to support SWA files app.
+      args.unshift(request.contentWindow);
+    } else if (window.appWindows[request.appId]) {
       args.unshift(window.appWindows[request.appId].contentWindow);
     } else if (window.background.dialogs[request.appId]) {
       args.unshift(window.background.dialogs[request.appId]);
diff --git a/ui/file_manager/file_manager/background/js/test_util_swa.js b/ui/file_manager/file_manager/background/js/test_util_swa.js
new file mode 100644
index 0000000..abfd77f
--- /dev/null
+++ b/ui/file_manager/file_manager/background/js/test_util_swa.js
@@ -0,0 +1,72 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Skeleton test framework that adapts Files SWA to the testing requirement of
+// Files.app. It is the SWA equivalent of
+// ui/file_manager/file_manager/background/js/test_util_base.js. We cannot
+// directly load the Files.app's test_util_base.js due to its dependencies on
+// Chrome APIs not available in SWAs.
+
+/**
+ * Namespace for test system code.
+ */
+window.test = window.test || {};
+
+/**
+ * Namespace for test utility functions.
+ *
+ * Public functions in the test.util.sync and the test.util.async namespaces are
+ * published to test cases and can be called by using callRemoteTestUtil. The
+ * arguments are serialized as JSON internally. If application ID is passed to
+ * callRemoteTestUtil, the content window of the application is added as the
+ * first argument. The functions in the test.util.async namespace are passed the
+ * callback function as the last argument.
+ */
+test.util = {};
+
+/**
+ * Namespace for synchronous utility functions.
+ */
+test.util.sync = {};
+
+/**
+ * Namespace for asynchronous utility functions.
+ */
+test.util.async = {};
+
+/**
+ * Remote call API handler. This function handles messages coming from the test
+ * harness to execute known functions and return results. This is a dummy
+ * implementation that is replaced by a real one once the test harness is fully
+ * loaded.
+ * @type {function(*, function(*): void)}
+ */
+test.util.executeTestMessage = (request, callback) => {
+  throw new Error('executeTestMessage not implemented');
+};
+
+/**
+ * Called when all test utility functions (in Files.app's test_util.js) have
+ * been installed in the test.util namespace. It exists here just to get handle
+ * the call from test_utils.
+ */
+test.util.registerRemoteTestUtils = () => {
+  console.assert(window.IN_TEST);
+  console.assert(
+      window.domAutomationController, 'domAutomationController not present');
+};
+
+/**
+ * Handles a direct call from the integration test harness. We execute
+ * swaTestMessageListener call directly from the FileManagerBrowserTest.
+ * This method avoids enabling external callers to Files SWA. We forward
+ * the response back to the caller, as a serialized JSON string.
+ * @param {!Object} request
+ */
+test.swaTestMessageListener = (request) => {
+  request.contentWindow = window.contentWindow || window;
+  test.util.executeTestMessage(request, (response) => {
+    window.domAutomationController.send(JSON.stringify(response));
+  });
+};
diff --git a/ui/file_manager/file_manager/common/js/util.js b/ui/file_manager/file_manager/common/js/util.js
index 4d26a810..f7b6c87 100644
--- a/ui/file_manager/file_manager/common/js/util.js
+++ b/ui/file_manager/file_manager/common/js/util.js
@@ -380,7 +380,12 @@
  * @return {string} The translated string.
  */
 /* #export */ function str(id) {
-  return loadTimeData.getString(id);
+  try {
+    return loadTimeData.getString(id);
+  } catch (e) {
+    console.warn('Failed to get string for ', id);
+    return id;
+  }
 }
 
 /**
diff --git a/ui/file_manager/file_manager/manifest.json b/ui/file_manager/file_manager/manifest.json
index 15bd188..e749658 100644
--- a/ui/file_manager/file_manager/manifest.json
+++ b/ui/file_manager/file_manager/manifest.json
@@ -145,25 +145,41 @@
   "web_accessible_resources": [
     "common/js/app_util.m.js",
     "common/js/async_util.m.js",
+    "common/js/file_operation_common.m.js",
     "common/js/file_type.m.js",
     "common/js/files_app_entry_types.m.js",
     "common/js/filtered_volume_manager.m.js",
     "common/js/lru_cache.m.js",
     "common/js/metrics.m.js",
+    "common/js/importer_common.m.js",
     "common/js/metrics_base.m.js",
     "common/js/mock_chrome.m.js",
     "common/js/power.m.js",
+    "common/js/progress_center_common.m.js",
     "common/js/storage_adapter.m.js",
     "common/js/test_error_reporting.m.js",
+    "common/js/trash.m.js",
     "common/js/util.m.js",
     "common/js/volume_manager_types.m.js",
     "common/js/xfm.m.js",
     "externs/background/background_base.m.js",
+    "externs/drive_dialog_controller.m.js",
     "externs/entry_location.m.js",
     "externs/files_app_entry_interfaces.m.js",
+    "externs/progress_center_panel.m.js",
     "externs/volume_info.m.js",
     "externs/volume_info_list.m.js",
     "externs/volume_manager.m.js",
+    "externs/background/crostini.m.js",
+    "externs/background/drive_sync_handler.m.js",
+    "externs/background/duplicate_finder.m.js",
+    "externs/background/file_browser_background_full.m.js",
+    "externs/background/file_operation_manager.m.js",
+    "externs/background/import_history.m.js",
+    "externs/background/media_import_handler.m.js",
+    "externs/background/media_scanner.m.js",
+    "externs/background/progress_center.m.js",
+    "externs/background/task_queue.m.js",
     "foreground/elements/files_icon_button.js",
     "foreground/elements/files_ripple.js",
     "foreground/elements/files_toast.js",
@@ -172,10 +188,28 @@
     "background/js/app_window_wrapper.m.js",
     "background/js/app_windows.m.js",
     "background/js/background_base.m.js",
+    "background/js/background.m.js",
     "background/js/background_common_scripts.js",
+    "background/js/crostini.m.js",
+    "background/js/device_handler.m.js",
+    "background/js/drive_sync_handler.m.js",
+    "background/js/duplicate_finder.m.js",
     "background/js/entry_location_impl.m.js",
+    "background/js/file_operation_handler.m.js",
+    "background/js/file_operation_manager.m.js",
+    "background/js/file_operation_util.m.js",
+    "background/js/import_history.m.js",
+    "background/js/launcher.m.js",
+    "background/js/media_scanner.m.js",
+    "background/js/media_import_handler.m.js",
+    "background/js/metadata_proxy.m.js",
+    "background/js/metrics_start.m.js",
+    "background/js/mount_metrics.m.js",
+    "background/js/progress_center.m.js",
     "background/js/runtime_loaded_test_util.js",
+    "background/js/task_queue.m.js",
     "background/js/test_util_base.m.js",
+    "background/js/trash.m.js",
     "background/js/volume_info_impl.m.js",
     "background/js/volume_info_list_impl.m.js",
     "background/js/volume_manager_factory.m.js",
diff --git a/ui/file_manager/integration_tests/file_manager/background.js b/ui/file_manager/integration_tests/file_manager/background.js
index bf906d13b..d383683 100644
--- a/ui/file_manager/integration_tests/file_manager/background.js
+++ b/ui/file_manager/integration_tests/file_manager/background.js
@@ -11,7 +11,14 @@
  */
 const FILE_MANAGER_EXTENSIONS_ID = 'hhaomjibdihmijegdhdafkllkbggdgoj';
 
-const remoteCall = new RemoteCallFilesApp(FILE_MANAGER_EXTENSIONS_ID);
+/**
+ * Application ID (URL) for File Manager System Web App (SWA).
+ * @type {string}
+ * @const
+ */
+const FILE_MANAGER_SWA_ID = 'chrome://file-manager';
+
+let remoteCall = new RemoteCallFilesApp(FILE_MANAGER_EXTENSIONS_ID);
 
 /**
  * Extension ID of Audio Player.
@@ -273,16 +280,28 @@
  *     app.
  * @return {Promise} Promise to be fulfilled after window creating.
  */
-function openNewWindow(initialRoot, appState = {}) {
+async function openNewWindow(initialRoot, appState = {}) {
   // TODO(mtomasz): Migrate from full paths to a pair of a volumeId and a
   // relative path. To compose the URL communicate via messages with
   // file_manager_browser_test.cc.
   if (initialRoot) {
-    appState.currentDirectoryURL = 'filesystem:chrome-extension://' +
-        FILE_MANAGER_EXTENSIONS_ID + '/external' + initialRoot;
+    const tail = `external${initialRoot}`;
+    if (remoteCall.isSwaMode()) {
+      appState.currentDirectoryURL =
+          `filesystem:${FILE_MANAGER_SWA_ID}/${tail}`;
+    } else {
+      appState.currentDirectoryURL =
+          `filesystem:chrome-extension://${FILE_MANAGER_EXTENSIONS_ID}/${tail}`;
+    }
   }
 
-  return remoteCall.callRemoteTestUtil('openMainWindow', null, [appState]);
+  const appId = remoteCall.isSwaMode() ?
+      await sendTestMessage({
+        name: 'launchFileManagerSwa',
+        launchDir: appState.currentDirectoryURL,
+      }) :
+      await remoteCall.callRemoteTestUtil('openMainWindow', null, [appState]);
+  return appId;
 }
 
 /**
@@ -452,8 +471,15 @@
  */
 window.addEventListener('load', () => {
   const steps = [
-    // Request the guest mode state.
+    // Check if we are running in Files SWA mode.
     () => {
+      sendBrowserTestCommand({name: 'isFilesAppSwa'}, steps.shift());
+    },
+    // Request the guest mode state.
+    (swaMode) => {
+      if (swaMode === 'true') {
+        remoteCall = new RemoteCallFilesApp(FILE_MANAGER_SWA_ID);
+      }
       sendBrowserTestCommand({name: 'isInGuestMode'}, steps.shift());
     },
     // Request the root entry paths.
diff --git a/ui/file_manager/integration_tests/file_manager/file_display.js b/ui/file_manager/integration_tests/file_manager/file_display.js
index f80925f..9afac1e8 100644
--- a/ui/file_manager/integration_tests/file_manager/file_display.js
+++ b/ui/file_manager/integration_tests/file_manager/file_display.js
@@ -11,12 +11,11 @@
  * @param {Array<TestEntryInfo>} defaultEntries Default file entries.
  */
 async function fileDisplay(path, defaultEntries) {
-  const defaultList = TestEntryInfo.getExpectedRows(defaultEntries).sort();
-
   // Open Files app on the given |path| with default file entries.
   const appId = await setupAndWaitUntilReady(path);
 
   // Verify the default file list is present in |result|.
+  const defaultList = TestEntryInfo.getExpectedRows(defaultEntries).sort();
   await remoteCall.waitForFiles(appId, defaultList);
 
   // Add new file entries.
diff --git a/ui/file_manager/integration_tests/file_manager/launch_files_app_swa.js b/ui/file_manager/integration_tests/file_manager/launch_files_app_swa.js
index 846c5ac4..b5f3fec 100644
--- a/ui/file_manager/integration_tests/file_manager/launch_files_app_swa.js
+++ b/ui/file_manager/integration_tests/file_manager/launch_files_app_swa.js
@@ -17,17 +17,9 @@
 
     console.log('file_manager_swa_id: ' + swaAppId);
 
-    await repeatUntil(async () => {
-      const launched = await sendTestMessage({
-        name: 'hasSwaStarted',
-        swaAppId: swaAppId,
-      });
-
-      if (launched !== 'true') {
-        return pending(caller, 'Waiting for files app SWA launch');
-      }
-    });
-
+    const element = await remoteCall.waitForElement(swaAppId, 'body');
+    chrome.test.assertEq('Files', element.attributes['aria-label']);
+    chrome.test.assertEq('files-ng', element.attributes['class']);
     return IGNORE_APP_ERRORS;
   };
 })();
diff --git a/ui/file_manager/integration_tests/remote_call.js b/ui/file_manager/integration_tests/remote_call.js
index d598d051..b9221e8 100644
--- a/ui/file_manager/integration_tests/remote_call.js
+++ b/ui/file_manager/integration_tests/remote_call.js
@@ -24,10 +24,10 @@
  */
 class RemoteCall {
   /**
-   * @param {string} extensionId ID of extension to be manipulated.
+   * @param {string} origin ID of the app to be manipulated.
    */
-  constructor(extensionId) {
-    this.extensionId_ = extensionId;
+  constructor(origin) {
+    this.origin_ = origin;
 
     /**
      * Tristate holding the cached result of isStepByStepEnabled_().
@@ -52,6 +52,18 @@
   }
 
   /**
+   * Delivers the given message to the test code running in the File Manager.
+   * @param {!Object} message
+   * @return {!Promise<!Object>} A promise which when fulfilled returns the
+   *     result of executing test code with the given message.
+   */
+  sendMessage(message) {
+    return new Promise((onFulfilled) => {
+      chrome.runtime.sendMessage(this.origin_, message, {}, onFulfilled);
+    });
+  }
+
+  /**
    * Calls a remote test util in the Files app's extension. See:
    * registerRemoteTestUtils in test_util_base.js.
    *
@@ -91,11 +103,7 @@
         console.info('Auto calling step() ...');
       }
     }
-    const response = await new Promise((onFulfilled) => {
-      chrome.runtime.sendMessage(
-          this.extensionId_, {func: func, appId: appId, args: args}, {},
-          onFulfilled);
-    });
+    const response = await this.sendMessage({func, appId, args});
 
     if (stepByStep) {
       console.info('Returned value:');
@@ -421,6 +429,34 @@
  */
 class RemoteCallFilesApp extends RemoteCall {
   /**
+   * @return {boolean} Returns whether the code is running in SWA mode.
+   */
+  isSwaMode() {
+    return this.origin_.startsWith('chrome://');
+  }
+
+  /**
+   * @param {!Object} message
+   * @return {!Promise<!Object>}
+   * @override
+   */
+  sendMessage(message) {
+    if (this.isSwaMode()) {
+      return new Promise((fulfill) => {
+        const command = {
+          name: 'callSwaTestMessageListener',
+          appId: message.appId,
+          data: JSON.stringify(message),
+        };
+        chrome.test.sendMessage(JSON.stringify(command), (response) => {
+          fulfill(/** @type {!Object} */ (JSON.parse(response)));
+        });
+      });
+    }
+    return super.sendMessage(message);
+  }
+
+  /**
    * Waits for the file list turns to the given contents.
    * @param {string} appId App window Id.
    * @param {Array<Array<string>>} expected Expected contents of file list.
diff --git a/ui/views/controls/button/toggle_button.cc b/ui/views/controls/button/toggle_button.cc
index 797f26a7..7e013b5 100644
--- a/ui/views/controls/button/toggle_button.cc
+++ b/ui/views/controls/button/toggle_button.cc
@@ -272,6 +272,12 @@
 void ToggleButton::UpdateThumb() {
   thumb_view_->Update(GetThumbBounds(),
                       static_cast<float>(slide_animation_.GetCurrentValue()));
+  if (focus_ring()) {
+    // Updating the thumb changes the result of GetFocusRingPath(), make sure
+    // the focus ring gets updated to match this new state.
+    focus_ring()->InvalidateLayout();
+    focus_ring()->SchedulePaint();
+  }
 }
 
 SkColor ToggleButton::GetTrackColor(bool is_on) const {