diff --git a/DEPS b/DEPS
index a4d50cd..bde87d6 100644
--- a/DEPS
+++ b/DEPS
@@ -195,7 +195,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': '530fc3bb4cbaef1f72f02cf4df64873d96c92090',
+  'skia_revision': '039f681a841a5507db74458329e5be64f9edf984',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -207,7 +207,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': '48ba75ac6845428971ea2a12bfb69eb85e608409',
+  'angle_revision': '8956bfb9c99d9d810022b6054486ed3811f99cb5',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -266,7 +266,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': '330914341bd4a018d4a9c0be0ad3e3ac2797d440',
+  'devtools_frontend_revision': '12bc13e26d28eecd2846bb424beede9d7095229c',
   # 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.
@@ -875,7 +875,7 @@
 
   # Build tools for Chrome OS.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'fb57bbd19358a5c2f1bb50e8417bd478f83588f9',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'a1d5ae72b241aea42dc26e65be8061b68981ce9d',
       'condition': 'checkout_chromeos',
   },
 
@@ -1248,7 +1248,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'a4cba6578f09d606d32fe32bb0a0c6283ea6d198',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '01e0ccdc9b8b4697a343b8ef49f53fd489d79a41',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1465,7 +1465,7 @@
   },
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '4c2f9c90f42591f0935b496d61ee256ebf53198f',
+    Var('webrtc_git') + '/src.git' + '@' + '582102c9b7f48f6ccad956fe17241668a90cb3fc',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1537,7 +1537,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@0fd4513a30ec4de041c9b39003243d9640b17345',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@b27f3f1980fd4f157efb8c1bb7891e6a95e654c2',
     'condition': 'checkout_src_internal',
   },
 
@@ -1545,7 +1545,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/help_app/app',
-        'version': 'CUU63VsXItsrBlNDrRhMgCrIBW8n5Lch_BLHRTfvmNUC',
+        'version': 'sQ-pH6rZbDHAmfK-kjBe53-R0rfRl9r_ObSKY2g81kUC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/android_webview/aapt2.config b/android_webview/aapt2.config
index c8b63db..b1398da 100644
--- a/android_webview/aapt2.config
+++ b/android_webview/aapt2.config
@@ -1,2 +1 @@
-string/license_activity_title#no_obfuscate
-drawable/shortcut_incognito#no_obfuscate
+string/license_activity_title#no_collapse
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwActivityTestRule.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwActivityTestRule.java
index e97acb3..538c5a0 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwActivityTestRule.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwActivityTestRule.java
@@ -60,6 +60,8 @@
 
     private static final Pattern MAYBE_QUOTED_STRING = Pattern.compile("^(\"?)(.*)\\1$");
 
+    private static boolean sBrowserProcessStarted;
+
     /**
      * An interface to call onCreateWindow(AwContents).
      */
@@ -98,6 +100,9 @@
         }
         if (needsBrowserProcessStarted()) {
             startBrowserProcess();
+        } else {
+            assert !sBrowserProcessStarted
+                : "needsBrowserProcessStarted false and @Batch are incompatible";
         }
     }
 
@@ -171,7 +176,10 @@
     public void startBrowserProcess() {
         // The Activity must be launched in order for proper webview statics to be setup.
         launchActivity();
-        TestThreadUtils.runOnUiThreadBlocking(() -> AwBrowserProcess.start());
+        if (!sBrowserProcessStarted) {
+            sBrowserProcessStarted = true;
+            TestThreadUtils.runOnUiThreadBlocking(() -> AwBrowserProcess.start());
+        }
         if (mBrowserContext != null) {
             TestThreadUtils.runOnUiThreadBlocking(
                     () -> mBrowserContext.setNativePointer(
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwSettingsTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwSettingsTest.java
index c765fdb9..e917620 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwSettingsTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwSettingsTest.java
@@ -39,10 +39,12 @@
 import org.chromium.android_webview.test.util.VideoTestWebServer;
 import org.chromium.base.Callback;
 import org.chromium.base.FileUtils;
+import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
+import org.chromium.base.test.util.RequiresRestart;
 import org.chromium.base.test.util.TestFileUtil;
 import org.chromium.base.test.util.UrlUtils;
 import org.chromium.content_public.browser.WebContents;
@@ -72,6 +74,7 @@
 @RunWith(AwJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ContentSwitches.HOST_RESOLVER_RULES + "=MAP * 127.0.0.1",
         "enable-features=WebViewOriginCheckForStreamReader"})
+@Batch(Batch.PER_CLASS)
 public class AwSettingsTest {
     @Rule
     public AwActivityTestRule mActivityTestRule =
@@ -2851,6 +2854,7 @@
         }
     }
 
+    @RequiresRestart("Enabling appcache is global and cannot be reversed.")
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Preferences", "AppCache"})
@@ -2884,6 +2888,7 @@
         }
     }
 
+    @RequiresRestart("Enabling appcache is global and cannot be reversed.")
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Preferences", "AppCache"})
diff --git a/ash/ambient/ambient_photo_controller.cc b/ash/ambient/ambient_photo_controller.cc
index 0f0e456e..0c3b4c5 100644
--- a/ash/ambient/ambient_photo_controller.cc
+++ b/ash/ambient/ambient_photo_controller.cc
@@ -323,7 +323,7 @@
   }
 
   ambient_backend_model_.AppendTopics(screen_update.next_topics);
-  StartDownloadingWeatherConditionIcon(screen_update);
+  StartDownloadingWeatherConditionIcon(screen_update.weather_info);
 }
 
 void AmbientPhotoController::TryReadPhotoRawData() {
@@ -416,41 +416,45 @@
 }
 
 void AmbientPhotoController::StartDownloadingWeatherConditionIcon(
-    const ash::ScreenUpdate& screen_update) {
-  if (!screen_update.weather_info.has_value()) {
+    const base::Optional<WeatherInfo>& weather_info) {
+  if (!weather_info) {
     LOG(WARNING) << "No weather info included in the response.";
     return;
   }
 
+  if (!weather_info->temp_f.has_value()) {
+    LOG(WARNING) << "No temperature included in weather info.";
+    return;
+  }
+
+  if (weather_info->condition_icon_url.value_or(std::string()).empty()) {
+    LOG(WARNING) << "No value found for condition icon url in the weather info "
+                    "response.";
+    return;
+  }
+
   // Ideally we should avoid downloading from the same url again to reduce the
   // overhead, as it's unlikely that the weather condition is changing
   // frequently during the day.
   // TODO(meilinw): avoid repeated downloading by caching the last N url hashes,
   // where N should depend on the icon image size.
-  const std::string& icon_url =
-      screen_update.weather_info->condition_icon_url.value_or(std::string());
-  if (icon_url.empty()) {
-    LOG(ERROR) << "No value found for condition icon url in the weather info "
-                  "response.";
-    return;
-  }
-
   DownloadImageFromUrl(
-      icon_url,
+      weather_info->condition_icon_url.value(),
       base::BindOnce(&AmbientPhotoController::OnWeatherConditionIconDownloaded,
-                     weak_factory_.GetWeakPtr(),
-                     screen_update.weather_info->temp_f));
+                     weak_factory_.GetWeakPtr(), weather_info->temp_f.value(),
+                     weather_info->show_celsius));
 }
 
 void AmbientPhotoController::OnWeatherConditionIconDownloaded(
-    base::Optional<float> temp_f,
+    float temp_f,
+    bool show_celsius,
     const gfx::ImageSkia& icon) {
   // For now we only show the weather card when both fields have values.
   // TODO(meilinw): optimize the behavior with more specific error handling.
-  if (icon.isNull() || !temp_f.has_value())
+  if (icon.isNull())
     return;
 
-  ambient_backend_model_.UpdateWeatherInfo(icon, temp_f.value());
+  ambient_backend_model_.UpdateWeatherInfo(icon, temp_f, show_celsius);
 }
 
 }  // namespace ash
diff --git a/ash/ambient/ambient_photo_controller.h b/ash/ambient/ambient_photo_controller.h
index cac890b4..2661bb5 100644
--- a/ash/ambient/ambient_photo_controller.h
+++ b/ash/ambient/ambient_photo_controller.h
@@ -129,11 +129,12 @@
   void OnPhotoDecoded(const gfx::ImageSkia& image);
 
   void StartDownloadingWeatherConditionIcon(
-      const ash::ScreenUpdate& screen_update);
+      const base::Optional<WeatherInfo>& weather_info);
 
   // Invoked upon completion of the weather icon download, |icon| can be a null
   // image if the download attempt from the url failed.
-  void OnWeatherConditionIconDownloaded(base::Optional<float> temp_f,
+  void OnWeatherConditionIconDownloaded(float temp_f,
+                                        bool show_celsius,
                                         const gfx::ImageSkia& icon);
 
   void set_url_loader_for_testing(
diff --git a/ash/ambient/backdrop/ambient_backend_controller_impl.cc b/ash/ambient/backdrop/ambient_backend_controller_impl.cc
index 07681d07..9e82b308 100644
--- a/ash/ambient/backdrop/ambient_backend_controller_impl.cc
+++ b/ash/ambient/backdrop/ambient_backend_controller_impl.cc
@@ -97,14 +97,20 @@
 
   // Parse |WeatherInfo|.
   if (backdrop_screen_update.has_weather_info()) {
-    backdrop::WeatherInfo backdrop_weather_info =
-        backdrop_screen_update.weather_info();
+    const auto& backdrop_weather_info = backdrop_screen_update.weather_info();
     ash::WeatherInfo weather_info;
-    if (backdrop_weather_info.has_condition_icon_url())
+
+    if (backdrop_weather_info.has_condition_icon_url()) {
       weather_info.condition_icon_url =
           backdrop_weather_info.condition_icon_url();
+    }
+
     if (backdrop_weather_info.has_temp_f())
       weather_info.temp_f = backdrop_weather_info.temp_f();
+
+    if (backdrop_weather_info.has_show_celsius())
+      weather_info.show_celsius = backdrop_weather_info.show_celsius();
+
     screen_update.weather_info = weather_info;
   }
   return screen_update;
diff --git a/ash/ambient/model/ambient_backend_model.cc b/ash/ambient/model/ambient_backend_model.cc
index 375d07a..505e2ac 100644
--- a/ash/ambient/model/ambient_backend_model.cc
+++ b/ash/ambient/model/ambient_backend_model.cc
@@ -74,11 +74,17 @@
   return current_image_;
 }
 
+float AmbientBackendModel::GetTemperatureInCelsius() const {
+  return (temperature_fahrenheit_ - 32) * 5 / 9;
+}
+
 void AmbientBackendModel::UpdateWeatherInfo(
     const gfx::ImageSkia& weather_condition_icon,
-    float temperature) {
+    float temperature_fahrenheit,
+    bool show_celsius) {
   weather_condition_icon_ = weather_condition_icon;
-  temperature_ = temperature;
+  temperature_fahrenheit_ = temperature_fahrenheit;
+  show_celsius_ = show_celsius;
 
   if (!weather_condition_icon.isNull())
     NotifyWeatherInfoUpdated();
diff --git a/ash/ambient/model/ambient_backend_model.h b/ash/ambient/model/ambient_backend_model.h
index 102549d5..35dafd9 100644
--- a/ash/ambient/model/ambient_backend_model.h
+++ b/ash/ambient/model/ambient_backend_model.h
@@ -53,7 +53,8 @@
   // Updates the weather information and notifies observers if the icon image is
   // not null.
   void UpdateWeatherInfo(const gfx::ImageSkia& weather_condition_icon,
-                         float temperature);
+                         float temperature_fahrenheit,
+                         bool show_celsius);
 
   // Returns the cached condition icon. Will return a null image if it has not
   // been set yet.
@@ -62,7 +63,12 @@
   }
 
   // Returns the cached temperature value in Fahrenheit.
-  float temperature() const { return temperature_; }
+  float temperature_fahrenheit() const { return temperature_fahrenheit_; }
+
+  // Calculate the temperature in celsius.
+  float GetTemperatureInCelsius() const;
+
+  bool show_celsius() const { return show_celsius_; }
 
  private:
   friend class AmbientBackendModelTest;
@@ -80,10 +86,10 @@
   // The index of currently shown image.
   int current_image_index_ = 0;
 
-  // Current weather information. The temperature is in Fahrenheit by default
-  // (b/154046129).
+  // Current weather information.
   gfx::ImageSkia weather_condition_icon_;
-  float temperature_ = 0.0f;
+  float temperature_fahrenheit_ = 0.0f;
+  bool show_celsius_ = false;
 
   // The interval to refresh photos.
   base::TimeDelta photo_refresh_interval_;
diff --git a/ash/ambient/ui/glanceable_info_view.cc b/ash/ambient/ui/glanceable_info_view.cc
index 0b18a4f..9e80e39 100644
--- a/ash/ambient/ui/glanceable_info_view.cc
+++ b/ash/ambient/ui/glanceable_info_view.cc
@@ -98,14 +98,25 @@
 }
 
 void GlanceableInfoView::Show() {
+  AmbientBackendModel* ambient_backend_model =
+      delegate_->GetAmbientBackendModel();
   weather_condition_icon_->SetImage(
-      delegate_->GetAmbientBackendModel()->weather_condition_icon());
+      ambient_backend_model->weather_condition_icon());
 
-  float temperature = delegate_->GetAmbientBackendModel()->temperature();
-  // TODO(b/154046129): handle Celsius format.
-  temperature_->SetText(l10n_util::GetStringFUTF16Int(
+  temperature_->SetText(GetTemperatureText());
+}
+
+base::string16 GlanceableInfoView::GetTemperatureText() const {
+  AmbientBackendModel* ambient_backend_model =
+      delegate_->GetAmbientBackendModel();
+  if (ambient_backend_model->show_celsius()) {
+    return l10n_util::GetStringFUTF16Int(
+        IDS_ASH_AMBIENT_MODE_WEATHER_TEMPERATURE_IN_CELSIUS,
+        static_cast<int>(ambient_backend_model->GetTemperatureInCelsius()));
+  }
+  return l10n_util::GetStringFUTF16Int(
       IDS_ASH_AMBIENT_MODE_WEATHER_TEMPERATURE_IN_FAHRENHEIT,
-      static_cast<int>(temperature)));
+      static_cast<int>(ambient_backend_model->temperature_fahrenheit()));
 }
 
 void GlanceableInfoView::InitLayout() {
diff --git a/ash/ambient/ui/glanceable_info_view.h b/ash/ambient/ui/glanceable_info_view.h
index 17d3917..d0d4593 100644
--- a/ash/ambient/ui/glanceable_info_view.h
+++ b/ash/ambient/ui/glanceable_info_view.h
@@ -42,6 +42,8 @@
  private:
   void InitLayout();
 
+  base::string16 GetTemperatureText() const;
+
   // View for the time info. Owned by the view hierarchy.
   ash::tray::TimeView* time_view_ = nullptr;
 
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 368dfffc..b0bab69 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -1936,6 +1936,12 @@
       <message name="IDS_ASH_LOGIN_POD_PASSWORD_PIN_PLACEHOLDER" desc="Text to display as placeholder in the password field when both password and pin are allowed.">
         PIN or password
       </message>
+      <message name="IDS_ASH_LOGIN_SWITCH_TO_PASSWORD" desc="Text to display on the button that is used for switching between PIN and password authentication">
+        Switch to password
+      </message>
+      <message name="IDS_ASH_LOGIN_SWITCH_TO_PIN" desc="Text to display on the button that is used for switching between PIN and password authentication">
+        Switch to PIN
+      </message>
       <message name="IDS_ASH_LOGIN_POD_PASSWORD_TAP_PLACEHOLDER" desc="Text to display as placeholder in the password field when the user can click/tap their profile picture to unlock.">
         Tap your profile image
       </message>
@@ -2585,6 +2591,9 @@
       <message name="IDS_ASH_AMBIENT_MODE_WEATHER_TEMPERATURE_IN_FAHRENHEIT" desc="Text of the weather temperature in Fahrenheit.">
         <ph name="TEMPERATURE_F">$1<ex>81</ex></ph>° F
       </message>
+      <message name="IDS_ASH_AMBIENT_MODE_WEATHER_TEMPERATURE_IN_CELSIUS" desc="Text of the weather temperature in Celsius.">
+        <ph name="TEMPERATURE_C">$1<ex>25</ex></ph>° C
+      </message>
     </messages>
   </release>
 </grit>
diff --git a/ash/ash_strings_grd/IDS_ASH_AMBIENT_MODE_WEATHER_TEMPERATURE_IN_CELSIUS.png.sha1 b/ash/ash_strings_grd/IDS_ASH_AMBIENT_MODE_WEATHER_TEMPERATURE_IN_CELSIUS.png.sha1
new file mode 100644
index 0000000..ea2ac1f
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_AMBIENT_MODE_WEATHER_TEMPERATURE_IN_CELSIUS.png.sha1
@@ -0,0 +1 @@
+36535d2bcea72bbb3cf04f90ef95817ef3da6483
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_AMBIENT_MODE_WEATHER_TEMPERATURE_IN_FAHRENHEIT.png.sha1 b/ash/ash_strings_grd/IDS_ASH_AMBIENT_MODE_WEATHER_TEMPERATURE_IN_FAHRENHEIT.png.sha1
new file mode 100644
index 0000000..f6687be
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_AMBIENT_MODE_WEATHER_TEMPERATURE_IN_FAHRENHEIT.png.sha1
@@ -0,0 +1 @@
+1929c8da4ff653162e79ec0a0593ac0dd88b43da
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_LOGIN_SWITCH_TO_PASSWORD.png.sha1 b/ash/ash_strings_grd/IDS_ASH_LOGIN_SWITCH_TO_PASSWORD.png.sha1
new file mode 100644
index 0000000..b3aa70a
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_LOGIN_SWITCH_TO_PASSWORD.png.sha1
@@ -0,0 +1 @@
+11d0c44aa250b377a2c9a0aefb69c80a4d1e1db2
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_LOGIN_SWITCH_TO_PIN.png.sha1 b/ash/ash_strings_grd/IDS_ASH_LOGIN_SWITCH_TO_PIN.png.sha1
new file mode 100644
index 0000000..50f4bb8
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_LOGIN_SWITCH_TO_PIN.png.sha1
@@ -0,0 +1 @@
+a6fdc8379efd1adf0d1c4487d0b48d4ae1903e22
\ No newline at end of file
diff --git a/ash/public/cpp/ambient/ambient_backend_controller.h b/ash/public/cpp/ambient/ambient_backend_controller.h
index 45761d2b3..7dbf3e9ea 100644
--- a/ash/public/cpp/ambient/ambient_backend_controller.h
+++ b/ash/public/cpp/ambient/ambient_backend_controller.h
@@ -49,6 +49,10 @@
 
   // Weather temperature in Fahrenheit.
   base::Optional<float> temp_f;
+
+  // If the temperature should be displayed in celsius. Conversion must happen
+  // before the value in temp_f is displayed.
+  bool show_celsius = false;
 };
 
 // Trimmed-down version of |backdrop::ScreenUpdate| proto from the backdrop
diff --git a/ash/public/cpp/ambient/fake_ambient_backend_controller_impl.cc b/ash/public/cpp/ambient/fake_ambient_backend_controller_impl.cc
index 4831b12..6c3ebca 100644
--- a/ash/public/cpp/ambient/fake_ambient_backend_controller_impl.cc
+++ b/ash/public/cpp/ambient/fake_ambient_backend_controller_impl.cc
@@ -17,11 +17,15 @@
 constexpr AmbientModeTopicSource kTopicSource =
     AmbientModeTopicSource::kGooglePhotos;
 
+constexpr AmbientModeTemperatureUnit kTemperatureUnit =
+    AmbientModeTemperatureUnit::kCelsius;
+
 constexpr char kFakeUrl[] = "chrome://ambient";
 
 AmbientSettings CreateFakeSettings() {
   AmbientSettings settings;
   settings.topic_source = kTopicSource;
+  settings.temperature_unit = kTemperatureUnit;
 
   ArtSetting art_setting0;
   art_setting0.album_id = "0";
@@ -68,6 +72,7 @@
   ash::WeatherInfo weather_info;
   weather_info.temp_f = .0f;
   weather_info.condition_icon_url = kFakeUrl;
+  weather_info.show_celsius = true;
 
   ash::ScreenUpdate update;
   update.next_topics.emplace_back(topic);
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 448c528..3e190aef 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -3775,6 +3775,7 @@
       "test/android/javatests/src/org/chromium/base/test/util/MetricsUtils.java",
       "test/android/javatests/src/org/chromium/base/test/util/MinAndroidSdkLevel.java",
       "test/android/javatests/src/org/chromium/base/test/util/MinAndroidSdkLevelSkipCheck.java",
+      "test/android/javatests/src/org/chromium/base/test/util/RequiresRestart.java",
       "test/android/javatests/src/org/chromium/base/test/util/Restriction.java",
       "test/android/javatests/src/org/chromium/base/test/util/RestrictionSkipCheck.java",
       "test/android/javatests/src/org/chromium/base/test/util/ScalableTimeout.java",
diff --git a/base/cpu_affinity_posix.h b/base/cpu_affinity_posix.h
index db9d7049..938a64c 100644
--- a/base/cpu_affinity_posix.h
+++ b/base/cpu_affinity_posix.h
@@ -10,7 +10,7 @@
 
 namespace base {
 
-enum class BASE_EXPORT CpuAffinityMode {
+enum class CpuAffinityMode {
   // No restrictions on affinity.
   kDefault,
   // Restrict execution to LITTLE cores only. Only has an effect on platforms
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/RequiresRestart.java b/base/test/android/javatests/src/org/chromium/base/test/util/RequiresRestart.java
new file mode 100644
index 0000000..6c9a8b8
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/RequiresRestart.java
@@ -0,0 +1,21 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Overrides a Batch annotation on a class and still require process restart for a particular test
+ * method. This should be used on individual methods only. See comments on Batch for more details.
+ * Optionally supply a message explaining why restart is needed as the value.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RequiresRestart {
+    String value() default "";
+}
diff --git a/build/android/BUILD.gn b/build/android/BUILD.gn
index a3290e1..e9eccf26 100644
--- a/build/android/BUILD.gn
+++ b/build/android/BUILD.gn
@@ -2,7 +2,6 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//build/config/android/build_vars.gni")
 import("//build/config/android/config.gni")
 import("//build/config/android/rules.gni")
 import("//build/config/python.gni")
@@ -22,6 +21,28 @@
       min_sdk_version = default_min_sdk_version
     }
   }
+
+  # Write to a file some GN vars that are useful to scripts that use the output
+  # directory. Format is chosen as easliy importable by both python and bash.
+  _lines = [
+    "android_sdk_build_tools=" +
+        rebase_path(android_sdk_build_tools, root_build_dir),
+    "android_sdk_build_tools_version=$android_sdk_build_tools_version",
+    "android_sdk_root=" + rebase_path(android_sdk_root, root_build_dir),
+    "android_sdk_version=$android_sdk_version",
+    "android_ndk_root=" + rebase_path(android_ndk_root, root_build_dir),
+    "android_tool_prefix=" + rebase_path(android_tool_prefix, root_build_dir),
+    "android_configuration_failure_dir=" +
+        rebase_path(android_configuration_failure_dir, root_build_dir),
+    "final_android_sdk=$final_android_sdk"
+  ]
+  if (defined(android_secondary_abi_cpu)) {
+    _secondary_label_info =
+        get_label_info(":foo($android_secondary_abi_toolchain)", "root_out_dir")
+    _lines += [ "android_secondary_abi_toolchain=" +
+                rebase_path(_secondary_label_info, root_build_dir) ]
+  }
+  write_file(android_build_vars, _lines)
 }
 
 python_library("devil_chromium_py") {
@@ -77,7 +98,7 @@
     "//third_party/catapult/tracing:convert_chart_json",
   ]
   data = [
-    build_vars_file,
+    android_build_vars,
     android_readelf,
   ]
 }
@@ -105,19 +126,3 @@
         [ "//third_party/android_platform/development/scripts:stack_py" ]
   }
 }
-
-# GN evaluates each .gn file once per toolchain, so restricting to default
-# toolchain will ensure write_file() is called only once.
-assert(current_toolchain == default_toolchain)
-
-# NOTE: If other platforms would benefit from exporting variables, we should
-# move this to a more top-level place.
-# It is currently here (instead of //BUILD.gn) to ensure that the file is
-# written even for non-chromium embedders of //build.
-_build_vars_json = {
-  # Underscore prefix so that it appears at the top.
-  _HEADER = "Generated during 'gn gen' by //build/android/BUILD.gn."
-  forward_variables_from(android_build_vars_json, "*")
-}
-
-write_file(build_vars_file, _build_vars_json, "json")
diff --git a/build/android/docs/life_of_a_resource.md b/build/android/docs/life_of_a_resource.md
index 1e542c87..4a30d27 100644
--- a/build/android/docs/life_of_a_resource.md
+++ b/build/android/docs/life_of_a_resource.md
@@ -174,7 +174,7 @@
 ```
 
 The aapt2 config file is passed to the ninja target through the
-`resources_config_path` variable. To add a resource to the whitelist, check
+`resources_config_paths` variable. To add a resource to the whitelist, check
 where the config is for your target and add a new line for your resource. If
 none exist, create a new config file and pass its path in your target.
 
diff --git a/build/android/gradle/generate_gradle.py b/build/android/gradle/generate_gradle.py
index affa6c64..603d130 100755
--- a/build/android/gradle/generate_gradle.py
+++ b/build/android/gradle/generate_gradle.py
@@ -29,9 +29,6 @@
 from util import build_utils
 from util import resource_utils
 
-sys.path.append(os.path.dirname(_BUILD_ANDROID))
-import gn_helpers
-
 _DEPOT_TOOLS_PATH = os.path.join(host_paths.DIR_SOURCE_ROOT, 'third_party',
                                  'depot_tools')
 _DEFAULT_ANDROID_MANIFEST_PATH = os.path.join(
@@ -109,6 +106,11 @@
     output_file.write(data)
 
 
+def _ReadPropertiesFile(path):
+  with open(path) as f:
+    return dict(l.rstrip().split('=', 1) for l in f if '=' in l)
+
+
 def _RunGnGen(output_dir, args=None):
   cmd = [os.path.join(_DEPOT_TOOLS_PATH, 'gn'), 'gen', output_dir]
   if args:
@@ -823,11 +825,10 @@
         for t in targets_from_args
     ]
     # Necessary after "gn clean"
-    if not os.path.exists(
-        os.path.join(output_dir, gn_helpers.BUILD_VARS_FILENAME)):
+    if not os.path.exists(os.path.join(output_dir, 'build_vars.txt')):
       _RunGnGen(output_dir)
 
-  build_vars = gn_helpers.ReadBuildVars(output_dir)
+  build_vars = _ReadPropertiesFile(os.path.join(output_dir, 'build_vars.txt'))
   jinja_processor = jinja_template.JinjaProcessor(_FILE_DIR)
   if args.beta:
     channel = 'beta'
diff --git a/build/android/gyp/compile_resources.py b/build/android/gyp/compile_resources.py
index fe64ca9..d9f6f02d 100755
--- a/build/android/gyp/compile_resources.py
+++ b/build/android/gyp/compile_resources.py
@@ -197,7 +197,9 @@
       '--optimized-proto-path',
       help='Output for `aapt2 optimize` for proto format (enables the step).')
   input_opts.add_argument(
-      '--resources-config-path', help='Path to aapt2 resources config file.')
+      '--resources-config-paths',
+      default='[]',
+      help='GN list of paths to aapt2 resources config files.')
 
   output_opts.add_argument(
       '--info-path', help='Path to output info file for the partial apk.')
@@ -244,6 +246,8 @@
       options.values_filter_rules)
   options.extra_main_r_text_files = build_utils.ParseGnList(
       options.extra_main_r_text_files)
+  options.resources_config_paths = build_utils.ParseGnList(
+      options.resources_config_paths)
 
   if options.optimized_proto_path and not options.proto_path:
     # We could write to a temp file, but it's simpler to require it.
@@ -879,6 +883,14 @@
   return desired_manifest_package_name
 
 
+def _CombineResourceConfigs(resources_config_paths, out_config_path):
+  with open(out_config_path, 'w') as out_config:
+    for config_path in resources_config_paths:
+      with open(config_path) as config:
+        out_config.write(config.read())
+        out_config.write('\n')
+
+
 def _OptimizeApk(output, options, temp_dir, unoptimized_path, r_txt_path):
   """Optimize intermediate .ap_ file with aapt2.
 
@@ -900,17 +912,13 @@
   # Optimize the resources.arsc file by obfuscating resource names and only
   # allow usage via R.java constant.
   if options.strip_resource_names:
-    # Resources of type ID are references to UI elements/views. They are used by
-    # UI automation testing frameworks. They are kept in so that they dont break
-    # tests, even though they may not actually be used during runtime. See
-    # https://crbug.com/900993
-    id_resources = _ExtractIdResources(r_txt_path)
+    no_collapse_resources = _ExtractNonCollapsableResources(r_txt_path)
     gen_config_path = os.path.join(temp_dir, 'aapt2.config')
-    if options.resources_config_path:
-      shutil.copyfile(options.resources_config_path, gen_config_path)
-    with open(gen_config_path, 'a+') as config:
-      for resource in id_resources:
-        config.write('{}#no_obfuscate\n'.format(resource))
+    if options.resources_config_paths:
+      _CombineResourceConfigs(options.resources_config_paths, gen_config_path)
+    with open(gen_config_path, 'a') as config:
+      for resource in no_collapse_resources:
+        config.write('{}#no_collapse\n'.format(resource))
 
     optimize_command += [
         '--collapse-resource-names',
@@ -930,21 +938,30 @@
       optimize_command, print_stdout=False, print_stderr=False)
 
 
-def _ExtractIdResources(rtxt_path):
-  """Extract resources of type ID from the R.txt file
+def _ExtractNonCollapsableResources(rtxt_path):
+  """Extract resources that should not be collapsed from the R.txt file
+
+  Resources of type ID are references to UI elements/views. They are used by
+  UI automation testing frameworks. They are kept in so that they don't break
+  tests, even though they may not actually be used during runtime. See
+  https://crbug.com/900993
+  App icons (aka mipmaps) are sometimes referenced by other apps by name so must
+  be keps as well. See https://b/161564466
 
   Args:
     rtxt_path: Path to R.txt file with all the resources
   Returns:
-    List of id resources in the form of id/<resource_name>
+    List of resources in the form of <resource_type>/<resource_name>
   """
-  id_resources = []
+  resources = []
+  _NO_COLLAPSE_TYPES = ['id', 'mipmap']
   with open(rtxt_path) as rtxt:
     for line in rtxt:
-      if ' id ' in line:
-        resource_name = line.split()[2]
-        id_resources.append('id/{}'.format(resource_name))
-  return id_resources
+      for resource_type in _NO_COLLAPSE_TYPES:
+        if ' {} '.format(resource_type) in line:
+          resource_name = line.split()[2]
+          resources.append('{}/{}'.format(resource_type, resource_name))
+  return resources
 
 
 @contextlib.contextmanager
@@ -1098,12 +1115,11 @@
   depfile_deps = (options.dependencies_res_zips +
                   options.extra_main_r_text_files + options.include_resources)
 
-  possible_input_paths = depfile_deps + [
+  possible_input_paths = depfile_deps + options.resources_config_paths + [
       options.aapt2_path,
       options.android_manifest,
       options.expected_file,
       options.expected_file_base,
-      options.resources_config_path,
       options.shared_resources_allowlist,
       options.use_resource_ids_path,
       options.webp_binary,
diff --git a/build/android/gyp/java_cpp_strings_tests.py b/build/android/gyp/java_cpp_strings_tests.py
index 3b7d5ca..64f0f79 100755
--- a/build/android/gyp/java_cpp_strings_tests.py
+++ b/build/android/gyp/java_cpp_strings_tests.py
@@ -73,7 +73,7 @@
     "invalid-line-break";
 """.split('\n')
     strings = java_cpp_strings.StringFileParser(test_data).Parse()
-    self.assertEqual(5, len(strings))
+    self.assertEqual(6, len(strings))
     self.assertEqual('A_STRING', strings[0].name)
     self.assertEqual('"a-value"', strings[0].value)
     self.assertEqual('NO_COMMENT', strings[1].name)
@@ -91,6 +91,20 @@
     self.assertEqual('"a-string-with-a-very-long-name-that-will-have-to-wrap2"',
                      strings[5].value)
 
+  def testTreatWebViewLikeOneWord(self):
+    test_data = """
+const char kSomeWebViewSwitch[] = "some-webview-switch";
+const char kWebViewOtherSwitch[] = "webview-other-switch";
+const char kSwitchWithPluralWebViews[] = "switch-with-plural-webviews";
+""".split('\n')
+    strings = java_cpp_strings.StringFileParser(test_data).Parse()
+    self.assertEqual('SOME_WEBVIEW_SWITCH', strings[0].name)
+    self.assertEqual('"some-webview-switch"', strings[0].value)
+    self.assertEqual('WEBVIEW_OTHER_SWITCH', strings[1].name)
+    self.assertEqual('"webview-other-switch"', strings[1].value)
+    self.assertEqual('SWITCH_WITH_PLURAL_WEBVIEWS', strings[2].name)
+    self.assertEqual('"switch-with-plural-webviews"', strings[2].value)
+
   def testTemplateParsing(self):
     test_data = """
 // Copyright {YEAR} The Chromium Authors. All rights reserved.
diff --git a/build/android/gyp/lint.py b/build/android/gyp/lint.py
index b70e00f4..a133545e 100755
--- a/build/android/gyp/lint.py
+++ b/build/android/gyp/lint.py
@@ -98,10 +98,8 @@
   return project
 
 
-def _GenerateAndroidManifest(original_manifest_path,
-                             min_sdk_version,
-                             manifest_package=None):
-  # Set minSdkVersion and package in the manifest to the correct values.
+def _GenerateAndroidManifest(original_manifest_path, min_sdk_version):
+  # Set minSdkVersion in the manifest to the correct value.
   doc, manifest, _ = manifest_utils.ParseManifest(original_manifest_path)
   uses_sdk = manifest.find('./uses-sdk')
   if uses_sdk is None:
@@ -109,8 +107,6 @@
     manifest.insert(0, uses_sdk)
   uses_sdk.set('{%s}minSdkVersion' % manifest_utils.ANDROID_NAMESPACE,
                min_sdk_version)
-  if manifest_package:
-    manifest.set('package', manifest_package)
   return doc
 
 
@@ -133,7 +129,6 @@
              android_sdk_version,
              srcjars,
              min_sdk_version,
-             manifest_package,
              resource_sources,
              resource_zips,
              android_sdk_root,
@@ -166,8 +161,7 @@
 
   logging.info('Generating Android manifest file')
   android_manifest_tree = _GenerateAndroidManifest(manifest_path,
-                                                   min_sdk_version,
-                                                   manifest_package)
+                                                   min_sdk_version)
   # Include the rebased manifest_path in the lint generated path so that it is
   # clear in error messages where the original AndroidManifest.xml came from.
   lint_android_manifest_path = os.path.join(lint_gen_dir,
@@ -282,8 +276,6 @@
                       help='If set, some checks like UnusedResources will be '
                       'disabled since they are not helpful for test '
                       'targets.')
-  parser.add_argument('--manifest-package',
-                      help='Package name of the AndroidManifest.xml.')
   parser.add_argument('--warnings-as-errors',
                       action='store_true',
                       help='Treat all warnings as errors.')
@@ -347,7 +339,6 @@
            args.android_sdk_version,
            args.srcjars,
            args.min_sdk_version,
-           args.manifest_package,
            resource_sources,
            args.resource_zips,
            args.android_sdk_root,
diff --git a/build/android/gyp/util/build_utils.py b/build/android/gyp/util/build_utils.py
index 902c9de7..56ec117a 100644
--- a/build/android/gyp/util/build_utils.py
+++ b/build/android/gyp/util/build_utils.py
@@ -83,6 +83,12 @@
   return files
 
 
+def ReadBuildVars(path):
+  """Parses a build_vars.txt into a dict."""
+  with open(path) as f:
+    return dict(l.rstrip().split('=', 1) for l in f)
+
+
 def ParseGnList(value):
   """Converts a "GN-list" command-line parameter into a list.
 
diff --git a/build/android/gyp/util/java_cpp_utils.py b/build/android/gyp/util/java_cpp_utils.py
index 0b97486..855b35a 100755
--- a/build/android/gyp/util/java_cpp_utils.py
+++ b/build/android/gyp/util/java_cpp_utils.py
@@ -25,6 +25,8 @@
     return s
   # Strip the leading k.
   s = re.sub(r'^k', '', s)
+  # Treat "WebView" like one word.
+  s = re.sub(r'WebView', r'Webview', s)
   # Add _ between title words and anything else.
   s = re.sub(r'([^_])([A-Z][^A-Z_0-9]+)', r'\1_\2', s)
   # Add _ between lower -> upper transitions.
diff --git a/build/android/lint/OWNERS b/build/android/lint/OWNERS
deleted file mode 100644
index b758a829..0000000
--- a/build/android/lint/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-per-file baseline.xml=*
diff --git a/build/android/lint/suppressions.xml b/build/android/lint/suppressions.xml
index e202115..95c5b86 100644
--- a/build/android/lint/suppressions.xml
+++ b/build/android/lint/suppressions.xml
@@ -201,9 +201,6 @@
     <!-- We no longer supply class files to lint. -->
     <ignore regexp="No `.class` files were found in project"/>
   </issue>
-  <!-- We use the same baseline for all our targets. Since some targets run less
-       code than other targets, there will always be unused suppressions. -->
-  <issue id="LintBaseline" severity="ignore"/>
   <issue id="LogConditional" severity="ignore"/>
   <issue id="LongLogTag" severity="ignore"/>
   <issue id="MergeRootFrame" severity="Error">
@@ -370,6 +367,9 @@
     <ignore regexp="The resource `R.plurals.accessibility_tab_selection_editor_group_button` appears to be unused"/>
     <ignore regexp="The resource `R.string.accessibility_tab_suggestion_group_tabs_message` appears to be unused"/>
     <ignore regexp="The resource `R.string.tab_suggestion_group_tabs_message` appears to be unused"/>
+    <!-- crbug.com/1114311 remove this line and the following line after the bug is resolved -->
+    <ignore regexp="The resource `R.string.languages_set_application_language_prompt` appears to be unused"/>
+    <ignore regexp="The resource `R.string.languages_set_as_application_language` appears to be unused"/>
     <!-- Old-style and new-style WebAPKs use same resources for simplicity. Old-style WebAPKs do
          not use R.style.SplashTheme but new-style WebAPKs do.
          TODO(crbug.com/971254): Remove suppression once old-style WebAPKs are deprecated. -->
diff --git a/build/android/pylib/local/device/local_device_instrumentation_test_run.py b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
index 9caf47d..5f39b3a 100644
--- a/build/android/pylib/local/device/local_device_instrumentation_test_run.py
+++ b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
@@ -471,7 +471,8 @@
     batched_tests = dict()
     other_tests = []
     for test in tests:
-      if 'Batch' in test['annotations']:
+      if 'Batch' in test['annotations'] and 'RequiresRestart' not in test[
+          'annotations']:
         batch_name = test['annotations']['Batch']['value']
         if not batch_name:
           batch_name = test['class']
diff --git a/build/android/resource_sizes.py b/build/android/resource_sizes.py
index 7f4f7ee..cd80694 100755
--- a/build/android/resource_sizes.py
+++ b/build/android/resource_sizes.py
@@ -35,17 +35,14 @@
 _BUILD_UTILS_PATH = os.path.join(
     host_paths.DIR_SOURCE_ROOT, 'build', 'android', 'gyp')
 
-with host_paths.SysPath(os.path.join(host_paths.DIR_SOURCE_ROOT, 'build')):
-  import gn_helpers  # pylint: disable=import-error
-
 with host_paths.SysPath(host_paths.BUILD_COMMON_PATH):
-  import perf_tests_results_helper  # pylint: disable=import-error
+  import perf_tests_results_helper # pylint: disable=import-error
 
 with host_paths.SysPath(host_paths.TRACING_PATH):
-  from tracing.value import convert_chart_json  # pylint: disable=import-error
+  from tracing.value import convert_chart_json # pylint: disable=import-error
 
 with host_paths.SysPath(_BUILD_UTILS_PATH, 0):
-  from util import build_utils  # pylint: disable=import-error
+  from util import build_utils # pylint: disable=import-error
   from util import zipalign  # pylint: disable=import-error
 
 
@@ -576,7 +573,8 @@
       out_dir = constants.GetOutDirectory()
     except Exception:  # pylint: disable=broad-except
       return out_dir, ''
-  build_vars = gn_helpers.ReadBuildVars(out_dir)
+  build_vars = build_utils.ReadBuildVars(
+      os.path.join(out_dir, "build_vars.txt"))
   tool_prefix = os.path.join(out_dir, build_vars['android_tool_prefix'])
   return out_dir, tool_prefix
 
diff --git a/build/config/android/build_vars.gni b/build/config/android/build_vars.gni
deleted file mode 100644
index a47607dc..0000000
--- a/build/config/android/build_vars.gni
+++ /dev/null
@@ -1,29 +0,0 @@
-# Copyright 2020 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//build/config/android/config.gni")
-
-# Contains useful GN variables that may be used by scripts that take
-# --output-directory as an arg.
-build_vars_file = "$root_build_dir/build_vars.json"
-
-android_build_vars_json = {
-  if (enable_java_templates) {
-    android_ndk_root = rebase_path(android_ndk_root, root_build_dir)
-    android_sdk_build_tools =
-        rebase_path(android_sdk_build_tools, root_build_dir)
-    android_sdk_build_tools_version = android_sdk_build_tools_version
-    android_sdk_root = rebase_path(android_sdk_root, root_build_dir)
-    android_sdk_version = android_sdk_version
-    android_tool_prefix = rebase_path(android_tool_prefix, root_build_dir)
-    final_android_sdk = final_android_sdk
-
-    if (defined(android_secondary_abi_cpu)) {
-      android_secondary_abi_toolchain =
-          rebase_path(get_label_info(":foo($android_secondary_abi_toolchain)",
-                                     "root_out_dir"),
-                      root_build_dir)
-    }
-  }
-}
diff --git a/build/config/android/config.gni b/build/config/android/config.gni
index 9be0dc1..87afc44 100644
--- a/build/config/android/config.gni
+++ b/build/config/android/config.gni
@@ -84,7 +84,7 @@
   public_android_sdk_root = "//third_party/android_sdk/public"
   if (android_sdk_release == "r") {
     default_android_sdk_root = public_android_sdk_root
-    default_android_sdk_version = "30"
+    default_android_sdk_version = 30
     default_android_sdk_build_tools_version = "30.0.1"
     public_android_sdk = true
   }
@@ -138,6 +138,9 @@
   assert(defined(default_android_sdk_root),
          "SDK release " + android_sdk_release + " not recognized.")
 
+  # Where to write failed expectations for bots to read.
+  android_configuration_failure_dir = "$root_build_dir/failed_expectations"
+
   declare_args() {
     android_ndk_root = default_android_ndk_root
     android_ndk_version = default_android_ndk_version
@@ -247,6 +250,9 @@
     enable_jdk_library_desugaring = false
   }
 
+  # Path to where selected build variables are written to.
+  android_build_vars = "$root_build_dir/build_vars.txt"
+
   # Host stuff -----------------------------------------------------------------
 
   # Defines the name the Android build gives to the current host CPU
diff --git a/build/config/android/internal_rules.gni b/build/config/android/internal_rules.gni
index 24d239c..8b23041 100644
--- a/build/config/android/internal_rules.gni
+++ b/build/config/android/internal_rules.gni
@@ -82,9 +82,6 @@
 _desugar_jdk_libs_jar = "//third_party/android_deps/libs/com_android_tools_desugar_jdk_libs/desugar_jdk_libs-1.0.5.jar"
 _desugar_runtime_jar = "$root_build_dir/obj/third_party/bazel/desugar/Desugar_runtime.processed.jar"
 
-# Where to write failed expectations for bots to read.
-_expectations_failure_dir = "$root_build_dir/failed_expectations"
-
 _dexdump_path = "$android_sdk_build_tools/dexdump"
 _dexlayout_path = "//third_party/android_build_tools/art/dexlayout"
 _profman_path = "//third_party/android_build_tools/art/profman"
@@ -1047,14 +1044,13 @@
         args += [ "--warnings-as-errors" ]
       }
 
-      _stamp_path = "$target_out_dir/$target_name/build.lint.stamp"
       if (defined(invoker.create_cache) && invoker.create_cache) {
-        args += [ "--silent" ]
-
         # Putting the stamp file in the cache dir allows us to depend on ninja
         # to create the cache dir for us.
         _stamp_path = "$_cache_dir/build.lint.stamp"
+        args += [ "--silent" ]
       } else {
+        _stamp_path = "$target_out_dir/$target_name/build.lint.stamp"
         deps += [
           "//build/android:prepare_android_lint_cache",
           invoker.build_config_dep,
@@ -1063,17 +1059,6 @@
         _rebased_build_config =
             rebase_path(invoker.build_config, root_build_dir)
 
-        # TODO(wnwen): Remove this baseline once it is empty.
-        _baseline = "//build/android/lint/baseline.xml"
-        _rebased_baseline = rebase_path(_baseline, root_build_dir)
-
-        if (compute_inputs_for_analyze) {
-          # The baseline file is included in lint.py as a depfile dep. Since
-          # removing it regenerates the file, it is useful to not have this as
-          # a gn input during local development. Add it only for bots' analyze.
-          inputs += [ _baseline ]
-        }
-
         args += [
           "--manifest-path=@FileArg($_rebased_build_config:deps_info:lint_android_manifest)",
 
@@ -1084,14 +1069,25 @@
           "--resource-sources=@FileArg($_rebased_build_config:deps_info:lint_resource_sources)",
           "--resource-zips=@FileArg($_rebased_build_config:deps_info:lint_resource_zips)",
 
-          # Baseline allows us to turn on lint warnings without fixing all the
-          # pre-existing issues. This stops the flood of new issues while the
-          # existing ones are being fixed.
-          "--baseline=$_rebased_baseline",
-
           # The full classpath is required for annotation checks like @IntDef.
           "--classpath=@FileArg($_rebased_build_config:deps_info:javac_full_interface_classpath)",
         ]
+
+        if (defined(invoker.lint_baseline_file)) {
+          if (compute_inputs_for_analyze) {
+            # The baseline file is included in lint.py as a depfile dep. Since
+            # removing it regenerates the file, it is useful to not have this as
+            # a gn input during local development. Add it only for bots' analyze.
+            inputs += [ invoker.lint_baseline_file ]
+          }
+          args += [
+            # Baseline allows us to turn on lint warnings without fixing all the
+            # pre-existing issues. This stops the flood of new issues while the
+            # existing ones are being fixed.
+            "--baseline",
+            rebase_path(invoker.lint_baseline_file, root_build_dir),
+          ]
+        }
       }
 
       outputs = [ _stamp_path ]
@@ -1265,7 +1261,7 @@
         ]
         _actual_file = "$target_gen_dir/$target_name.proguard_configs"
         _failure_file =
-            "$_expectations_failure_dir/" +
+            "$android_configuration_failure_dir/" +
             string_replace(invoker.expected_proguard_config, "/", "_")
         outputs = [ _actual_file ]
         args = _args + [
@@ -2460,12 +2456,11 @@
         rebase_path(invoker.optimized_proto_output, root_build_dir),
       ]
     }
-    if (defined(invoker.resources_config_path)) {
-      _inputs += [ invoker.resources_config_path ]
-      _args += [
-        "--resources-config-path",
-        rebase_path(invoker.resources_config_path, root_build_dir),
-      ]
+    if (defined(invoker.resources_config_paths)) {
+      _inputs += invoker.resources_config_paths
+      _rebased_resource_configs =
+          rebase_path(invoker.resources_config_paths, root_build_dir)
+      _args += [ "--resources-config-paths=${_rebased_resource_configs}" ]
     }
     if (defined(invoker.short_resource_paths) && invoker.short_resource_paths) {
       _args += [ "--short-resource-paths" ]
@@ -2644,7 +2639,7 @@
       action_with_pydeps(_expectations_target) {
         _actual_file = "${invoker.android_manifest}.normalized"
         _failure_file =
-            "$_expectations_failure_dir/" +
+            "$android_configuration_failure_dir/" +
             string_replace(invoker.expected_android_manifest, "/", "_")
         inputs = [
           invoker.android_manifest,
@@ -2923,7 +2918,7 @@
       action_with_pydeps(_expectations_target) {
         _actual_file = "$target_gen_dir/$target_name.libs_and_assets"
         _failure_file =
-            "$_expectations_failure_dir/" +
+            "$android_configuration_failure_dir/" +
             string_replace(invoker.expected_libs_and_assets, "/", "_")
         inputs = [
           invoker.build_config,
@@ -4143,7 +4138,7 @@
     _expectations_target = "${invoker.top_target_name}_validate_libs_and_assets"
     action_with_pydeps(_expectations_target) {
       _actual_file = "$target_gen_dir/$target_name.libs_and_assets"
-      _failure_file = "$_expectations_failure_dir/" +
+      _failure_file = "$android_configuration_failure_dir/" +
                       string_replace(invoker.expected_libs_and_assets, "/", "_")
       inputs = [
         invoker.expected_libs_and_assets,
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index a4a7b90..dd74568 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -2088,8 +2088,8 @@
   #     resources.arsc file in the apk or module.
   #   short_resource_paths: True if resource paths should be shortened in the
   #     apk or module.
-  #   resources_config_path: Path to the aapt2 optimize config file that tags
-  #     resources with acceptable/non-acceptable optimizations.
+  #   resources_config_paths: List of paths to the aapt2 optimize config files
+  #     that tags resources with acceptable/non-acceptable optimizations.
   #   expected_android_manifest: Enables verification of expected merged
   #     manifest based on a golden file.
   #   resource_ids_provider_dep: If passed, this target will use the resource
@@ -2482,7 +2482,7 @@
                                "resource_exclusion_exceptions",
                                "resource_exclusion_regex",
                                "resource_values_filter_rules",
-                               "resources_config_path",
+                               "resources_config_paths",
                                "shared_resources",
                                "shared_resources_allowlist_locales",
                                "support_zh_hk",
@@ -3330,9 +3330,9 @@
       android_lint("${target_name}__lint") {
         forward_variables_from(invoker,
                                [
+                                 "lint_baseline_file",
                                  "lint_suppressions_dep",
                                  "lint_suppressions_file",
-                                 "manifest_package",
                                  "min_sdk_version",
                                ])
         if (defined(invoker.lint_min_sdk_version)) {
@@ -3343,10 +3343,9 @@
         deps = [ ":$_java_target" ]
       }
     } else {
-      # TODO(wnwen): Remove manifest_package when all usages are removed.
       not_needed(invoker,
                  [
-                   "manifest_package",
+                   "lint_baseline_file",
                    "lint_min_sdk_version",
                  ])
     }
@@ -3456,6 +3455,7 @@
                                "keystore_name",
                                "keystore_password",
                                "keystore_path",
+                               "lint_baseline_file",
                                "lint_min_sdk_version",
                                "lint_suppressions_dep",
                                "lint_suppressions_file",
@@ -3483,7 +3483,7 @@
                                "resource_exclusion_regex",
                                "resource_ids_provider_dep",
                                "resource_values_filter_rules",
-                               "resources_config_path",
+                               "resources_config_paths",
                                "require_native_mocks",
                                "secondary_abi_loadable_modules",
                                "secondary_abi_shared_libraries",
@@ -3610,7 +3610,7 @@
                                "resource_exclusion_regex",
                                "resource_ids_provider_dep",
                                "resource_values_filter_rules",
-                               "resources_config_path",
+                               "resources_config_paths",
                                "secondary_abi_loadable_modules",
                                "secondary_abi_shared_libraries",
                                "secondary_native_lib_placeholders",
@@ -4996,9 +4996,9 @@
       android_lint("${target_name}__lint") {
         forward_variables_from(invoker,
                                [
+                                 "lint_baseline_file",
                                  "lint_suppressions_dep",
                                  "lint_suppressions_file",
-                                 "manifest_package",
                                  "min_sdk_version",
                                ])
         if (defined(invoker.lint_min_sdk_version)) {
@@ -5009,10 +5009,9 @@
         deps = _module_java_targets
       }
     } else {
-      # TODO(wnwen): Remove manifest_package when all usages are removed.
       not_needed(invoker,
                  [
-                   "manifest_package",
+                   "lint_baseline_file",
                    "lint_min_sdk_version",
                  ])
     }
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index c9dd4f31..33059c97 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-0.20200810.0.1
+0.20200810.1.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index c9dd4f31..33059c97 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-0.20200810.0.1
+0.20200810.1.1
diff --git a/build/gn_helpers.py b/build/gn_helpers.py
index 8258658..a240d80 100644
--- a/build/gn_helpers.py
+++ b/build/gn_helpers.py
@@ -20,7 +20,6 @@
 file to the build directory.
 """
 
-import json
 import os
 import re
 import sys
@@ -28,7 +27,6 @@
 
 _CHROMIUM_ROOT = os.path.join(os.path.dirname(__file__), os.pardir)
 
-BUILD_VARS_FILENAME = 'build_vars.json'
 IMPORT_RE = re.compile(r'^import\("//(\S+)"\)')
 
 
@@ -498,9 +496,3 @@
       self.cur = end
       return True
     return False
-
-
-def ReadBuildVars(output_directory):
-  """Parses $output_directory/build_vars.json into a dict."""
-  with open(os.path.join(output_directory, BUILD_VARS_FILENAME)) as f:
-    return json.load(f)
diff --git a/cc/layers/picture_layer_impl_unittest.cc b/cc/layers/picture_layer_impl_unittest.cc
index ec505ac..4e1d2392 100644
--- a/cc/layers/picture_layer_impl_unittest.cc
+++ b/cc/layers/picture_layer_impl_unittest.cc
@@ -8,6 +8,7 @@
 
 #include <algorithm>
 #include <limits>
+#include <memory>
 #include <set>
 #include <utility>
 
@@ -6008,5 +6009,188 @@
             pending_layer()->ComputeLCDTextDisallowedReasonForTesting());
 }
 
+enum {
+  kCanUseLCDText = 1 << 0,
+  kLayersAlwaysAllowedLCDText = 1 << 1,
+};
+
+class LCDTextTest : public PictureLayerImplTest,
+                    public testing::WithParamInterface<unsigned> {
+ protected:
+  LayerTreeSettings CreateSettings() override {
+    auto settings = PictureLayerImplTest::CreateSettings();
+    settings.can_use_lcd_text = GetParam() & kCanUseLCDText;
+    settings.layers_always_allowed_lcd_text =
+        GetParam() & kLayersAlwaysAllowedLCDText;
+    return settings;
+  }
+
+  void SetUp() override {
+    PictureLayerImplTest::SetUp();
+
+    SetupDefaultTreesWithInvalidation(gfx::Size(200, 200), Region());
+    tree_ = host_impl()->pending_tree();
+    root_ = tree_->root_layer();
+    child_ = AddLayer<PictureLayerImpl>(tree_);
+    grand_child_ = AddLayer<PictureLayerImpl>(tree_);
+    tree_->SetElementIdsForTesting();
+
+    root_->SetContentsOpaque(true);
+    child_->SetContentsOpaque(true);
+    grand_child_->SetContentsOpaque(true);
+
+    root_->SetDrawsContent(true);
+    child_->SetDrawsContent(true);
+    grand_child_->SetDrawsContent(true);
+
+    root_->SetBounds(gfx::Size(1, 1));
+    child_->SetBounds(gfx::Size(1, 1));
+    grand_child_->SetBounds(gfx::Size(1, 1));
+
+    CopyProperties(root_, child_);
+    CreateTransformNode(child_);
+    CreateEffectNode(child_).render_surface_reason = RenderSurfaceReason::kTest;
+    CopyProperties(child_, grand_child_);
+    CreateTransformNode(grand_child_);
+    CreateEffectNode(grand_child_);
+  }
+
+  void CheckCanUseLCDText(LCDTextDisallowedReason expected_disallowed_reason,
+                          PictureLayerImpl* layer = nullptr) {
+    UpdateDrawProperties(tree_);
+
+    if (GetParam() & kLayersAlwaysAllowedLCDText)
+      expected_disallowed_reason = LCDTextDisallowedReason::kNone;
+    else if (!(GetParam() & kCanUseLCDText))
+      expected_disallowed_reason = LCDTextDisallowedReason::kSetting;
+
+    if (layer) {
+      EXPECT_EQ(expected_disallowed_reason,
+                layer->ComputeLCDTextDisallowedReasonForTesting());
+    } else {
+      EXPECT_EQ(expected_disallowed_reason,
+                child_->ComputeLCDTextDisallowedReasonForTesting());
+      EXPECT_EQ(expected_disallowed_reason,
+                grand_child_->ComputeLCDTextDisallowedReasonForTesting());
+    }
+  }
+
+  LayerTreeImpl* tree_ = nullptr;
+  LayerImpl* root_ = nullptr;
+  PictureLayerImpl* child_ = nullptr;
+  PictureLayerImpl* grand_child_ = nullptr;
+};
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         LCDTextTest,
+                         testing::Values(0,
+                                         kCanUseLCDText,
+                                         kLayersAlwaysAllowedLCDText,
+                                         kCanUseLCDText |
+                                             kLayersAlwaysAllowedLCDText));
+
+TEST_P(LCDTextTest, IdentityTransform) {
+  CheckCanUseLCDText(LCDTextDisallowedReason::kNone);
+}
+
+TEST_P(LCDTextTest, IntegralTransform) {
+  gfx::Transform integral_translation;
+  integral_translation.Translate(1.0, 2.0);
+  SetTransform(child_, integral_translation);
+
+  CheckCanUseLCDText(LCDTextDisallowedReason::kNone);
+}
+
+TEST_P(LCDTextTest, NonIntegralTranslation) {
+  // Non-integral translation.
+  gfx::Transform non_integral_translation;
+  non_integral_translation.Translate(1.5, 2.5);
+  SetTransform(child_, non_integral_translation);
+  CheckCanUseLCDText(LCDTextDisallowedReason::kNonIntegralTranslation);
+
+  SetTransform(child_, gfx::Transform());
+  CheckCanUseLCDText(LCDTextDisallowedReason::kNone);
+}
+
+TEST_P(LCDTextTest, NonTranslation) {
+  // Rotation.
+  gfx::Transform rotation;
+  rotation.Rotate(10.0);
+  SetTransform(child_, rotation);
+  CheckCanUseLCDText(LCDTextDisallowedReason::kNonIntegralTranslation);
+
+  // Scale.
+  gfx::Transform scale;
+  scale.Scale(2.0, 2.0);
+  SetTransform(child_, scale);
+  CheckCanUseLCDText(LCDTextDisallowedReason::kNonIntegralTranslation);
+
+  // Skew.
+  gfx::Transform skew;
+  skew.Skew(10.0, 0.0);
+  SetTransform(child_, skew);
+  CheckCanUseLCDText(LCDTextDisallowedReason::kNonIntegralTranslation);
+
+  SetTransform(child_, gfx::Transform());
+  CheckCanUseLCDText(LCDTextDisallowedReason::kNone);
+}
+
+TEST_P(LCDTextTest, Opacity) {
+  // LCD-text is allowed with opacity paint property.
+  SetOpacity(child_, 0.5f);
+  CheckCanUseLCDText(LCDTextDisallowedReason::kNone);
+  SetOpacity(child_, 1.f);
+  CheckCanUseLCDText(LCDTextDisallowedReason::kNone);
+}
+
+TEST_P(LCDTextTest, ContentsNotOpaque) {
+  // Non-opaque content and opaque background.
+  child_->SetContentsOpaque(false);
+  child_->SetBackgroundColor(SK_ColorGREEN);
+  CheckCanUseLCDText(LCDTextDisallowedReason::kContentsNotOpaque, child_);
+  CheckCanUseLCDText(LCDTextDisallowedReason::kNone, grand_child_);
+
+  // Non-opaque content and non-opaque background.
+  child_->SetBackgroundColor(SkColorSetARGB(128, 255, 255, 255));
+  CheckCanUseLCDText(LCDTextDisallowedReason::kBackgroundColorNotOpaque,
+                     child_);
+  CheckCanUseLCDText(LCDTextDisallowedReason::kNone, grand_child_);
+
+  child_->SetContentsOpaque(true);
+  CheckCanUseLCDText(LCDTextDisallowedReason::kNone);
+}
+
+TEST_P(LCDTextTest, WillChangeTransform) {
+  child_->SetHasWillChangeTransformHint(true);
+  CheckCanUseLCDText(LCDTextDisallowedReason::kWillChangeTransform, child_);
+  // TODO(crbug.com/1114504): will-change:transform should apply to descendants.
+  CheckCanUseLCDText(LCDTextDisallowedReason::kNone, grand_child_);
+
+  child_->SetHasWillChangeTransformHint(false);
+  CheckCanUseLCDText(LCDTextDisallowedReason::kNone);
+}
+
+TEST_P(LCDTextTest, Filter) {
+  FilterOperations blur_filter;
+  blur_filter.Append(FilterOperation::CreateBlurFilter(4.0f));
+  SetFilter(child_, blur_filter);
+  CheckCanUseLCDText(LCDTextDisallowedReason::kLayerHasFilterEffect, child_);
+  // TODO(crbug.com/1114504): will-change:transform should apply to descendants.
+  CheckCanUseLCDText(LCDTextDisallowedReason::kNone, grand_child_);
+
+  SetFilter(child_, FilterOperations());
+  CheckCanUseLCDText(LCDTextDisallowedReason::kNone);
+}
+
+TEST_P(LCDTextTest, ContentsOpaqueForText) {
+  child_->SetContentsOpaque(false);
+  child_->SetBackgroundColor(SK_ColorGREEN);
+  child_->SetContentsOpaqueForText(true);
+  CheckCanUseLCDText(LCDTextDisallowedReason::kNone, child_);
+
+  child_->SetContentsOpaqueForText(false);
+  CheckCanUseLCDText(LCDTextDisallowedReason::kContentsNotOpaque, child_);
+}
+
 }  // namespace
 }  // namespace cc
diff --git a/cc/test/pixel_test.h b/cc/test/pixel_test.h
index 9c1de03..66d31cb4 100644
--- a/cc/test/pixel_test.h
+++ b/cc/test/pixel_test.h
@@ -13,6 +13,7 @@
 #include "base/memory/shared_memory_mapping.h"
 #include "base/single_thread_task_runner.h"
 #include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
 #include "cc/test/pixel_comparator.h"
 #include "cc/trees/layer_tree_settings.h"
 #include "components/viz/client/client_resource_provider.h"
@@ -26,6 +27,7 @@
 #include "gpu/command_buffer/client/gpu_memory_buffer_manager.h"
 #include "gpu/ipc/in_process_command_buffer.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/ui_base_features.h"
 #include "ui/gfx/geometry/size.h"
 
 namespace viz {
@@ -170,8 +172,20 @@
 
   bool use_gpu() const { return !!child_context_provider_; }
   GraphicsBackend backend() const {
-    if (std::is_base_of<VulkanSkiaRenderer, RendererType>::value)
-      return kSkiaVulkan;
+    if (std::is_base_of<VulkanSkiaRenderer, RendererType>::value) {
+#if defined(USE_OZONE) && defined(OS_LINUX) && !defined(OS_CHROMEOS)
+      // TODO(https://crbug.com/1113577): Enable SkiaVulkan backend for
+      // PixelTests. For example, RendererPixelTest* hadn't been using
+      // SkiaVulkanRenderer until USE_X11 was defined for the OS_LINUX
+      // configuration that uses USE_OZONE. Thus, given the lack of test
+      // coverage, we must fix this test variant so that we do not loose
+      // important test coverage when USE_X11 goes away.
+      if (!features::IsUsingOzonePlatform())
+#endif
+      {
+        return kSkiaVulkan;
+      }
+    }
     if (std::is_base_of<DawnSkiaRenderer, RendererType>::value)
       return kSkiaDawn;
     return kDefault;
diff --git a/cc/trees/draw_properties_unittest.cc b/cc/trees/draw_properties_unittest.cc
index 666b6a2..7e2c465 100644
--- a/cc/trees/draw_properties_unittest.cc
+++ b/cc/trees/draw_properties_unittest.cc
@@ -3646,211 +3646,6 @@
           front_facing, host_impl()->active_tree()->property_trees()));
 }
 
-using LCDTextTestParam = std::tuple<bool, bool>;
-class LCDTextTest : public DrawPropertiesTestBase,
-                    public testing::TestWithParam<LCDTextTestParam> {
- public:
-  LCDTextTest() : DrawPropertiesTestBase(LCDTextTestLayerTreeSettings()) {}
-
- protected:
-  LayerTreeSettings LCDTextTestLayerTreeSettings() {
-    LayerListSettings settings;
-
-    can_use_lcd_text_ = std::get<0>(GetParam());
-    layers_always_allowed_lcd_text_ = std::get<1>(GetParam());
-    settings.can_use_lcd_text = can_use_lcd_text_;
-    settings.layers_always_allowed_lcd_text = layers_always_allowed_lcd_text_;
-    return settings;
-  }
-
-  void SetUp() override {
-    root_ = root_layer();
-    child_ = AddLayer<PictureLayerImpl>();
-    grand_child_ = AddLayer<PictureLayerImpl>();
-    SetElementIdsForTesting();
-
-    root_->SetContentsOpaque(true);
-    child_->SetContentsOpaque(true);
-    grand_child_->SetContentsOpaque(true);
-
-    root_->SetDrawsContent(true);
-    child_->SetDrawsContent(true);
-    grand_child_->SetDrawsContent(true);
-
-    root_->SetBounds(gfx::Size(1, 1));
-    child_->SetBounds(gfx::Size(1, 1));
-    grand_child_->SetBounds(gfx::Size(1, 1));
-
-    CopyProperties(root_, child_);
-    CreateTransformNode(child_);
-    CreateEffectNode(child_).render_surface_reason = RenderSurfaceReason::kTest;
-    CopyProperties(child_, grand_child_);
-  }
-
-  void CheckCanUseLCDText(LCDTextDisallowedReason expected_disallowed_reason,
-                          PictureLayerImpl* layer = nullptr) {
-    if (layers_always_allowed_lcd_text_)
-      expected_disallowed_reason = LCDTextDisallowedReason::kNone;
-    else if (!can_use_lcd_text_)
-      expected_disallowed_reason = LCDTextDisallowedReason::kSetting;
-
-    if (layer) {
-      EXPECT_EQ(expected_disallowed_reason,
-                layer->ComputeLCDTextDisallowedReasonForTesting());
-    } else {
-      EXPECT_EQ(expected_disallowed_reason,
-                child_->ComputeLCDTextDisallowedReasonForTesting());
-      EXPECT_EQ(expected_disallowed_reason,
-                grand_child_->ComputeLCDTextDisallowedReasonForTesting());
-    }
-  }
-
-  bool can_use_lcd_text_;
-  bool layers_always_allowed_lcd_text_;
-
-  LayerImpl* root_ = nullptr;
-  PictureLayerImpl* child_ = nullptr;
-  PictureLayerImpl* grand_child_ = nullptr;
-};
-
-TEST_P(LCDTextTest, CanUseLCDText) {
-  // Case 1: Identity transform.
-  UpdateActiveTreeDrawProperties();
-  CheckCanUseLCDText(LCDTextDisallowedReason::kNone);
-
-  // Case 2: Integral translation.
-  gfx::Transform integral_translation;
-  integral_translation.Translate(1.0, 2.0);
-  SetTransform(child_, integral_translation);
-
-  UpdateActiveTreeDrawProperties();
-  CheckCanUseLCDText(LCDTextDisallowedReason::kNone);
-
-  // Case 3: Non-integral translation.
-  gfx::Transform non_integral_translation;
-  non_integral_translation.Translate(1.5, 2.5);
-  SetTransform(child_, non_integral_translation);
-  UpdateActiveTreeDrawProperties();
-  CheckCanUseLCDText(LCDTextDisallowedReason::kNonIntegralTranslation);
-
-  // Case 4: Rotation.
-  gfx::Transform rotation;
-  rotation.Rotate(10.0);
-  SetTransform(child_, rotation);
-  UpdateActiveTreeDrawProperties();
-  CheckCanUseLCDText(LCDTextDisallowedReason::kNonIntegralTranslation);
-
-  // Case 5: Scale.
-  gfx::Transform scale;
-  scale.Scale(2.0, 2.0);
-  SetTransform(child_, scale);
-  UpdateActiveTreeDrawProperties();
-  CheckCanUseLCDText(LCDTextDisallowedReason::kNonIntegralTranslation);
-
-  // Case 6: Skew.
-  gfx::Transform skew;
-  skew.Skew(10.0, 0.0);
-  SetTransform(child_, skew);
-  UpdateActiveTreeDrawProperties();
-  CheckCanUseLCDText(LCDTextDisallowedReason::kNonIntegralTranslation);
-
-  // Case 7: Translucent: LCD-text is allowed.
-  SetTransform(child_, gfx::Transform());
-  SetOpacity(child_, 0.5f);
-  UpdateActiveTreeDrawProperties();
-  CheckCanUseLCDText(LCDTextDisallowedReason::kNone);
-
-  // Case 8: Sanity check: restore transform and opacity.
-  SetTransform(child_, gfx::Transform());
-  SetOpacity(child_, 1.f);
-  UpdateActiveTreeDrawProperties();
-  CheckCanUseLCDText(LCDTextDisallowedReason::kNone);
-
-  // Case 9a: Non-opaque content and opaque background.
-  child_->SetContentsOpaque(false);
-  child_->SetBackgroundColor(SK_ColorGREEN);
-  UpdateActiveTreeDrawProperties();
-  CheckCanUseLCDText(LCDTextDisallowedReason::kContentsNotOpaque, child_);
-  CheckCanUseLCDText(LCDTextDisallowedReason::kNone, grand_child_);
-
-  // Case 9b: Non-opaque content and non-opaque background.
-  child_->SetBackgroundColor(SkColorSetARGB(128, 255, 255, 255));
-  UpdateActiveTreeDrawProperties();
-  CheckCanUseLCDText(LCDTextDisallowedReason::kBackgroundColorNotOpaque,
-                     child_);
-  CheckCanUseLCDText(LCDTextDisallowedReason::kNone, grand_child_);
-
-  // Case 10: Sanity check: restore content opaqueness.
-  child_->SetContentsOpaque(true);
-  UpdateActiveTreeDrawProperties();
-  CheckCanUseLCDText(LCDTextDisallowedReason::kNone);
-
-  // Case 11: will-change: transform
-  child_->SetHasWillChangeTransformHint(true);
-  UpdateActiveTreeDrawProperties();
-  CheckCanUseLCDText(LCDTextDisallowedReason::kWillChangeTransform, child_);
-  // TODO(wangxianzhu): Is this correct?
-  CheckCanUseLCDText(LCDTextDisallowedReason::kNone, grand_child_);
-
-  // Case 12: Sanity check: restore will-change: transform.
-  child_->SetHasWillChangeTransformHint(false);
-  UpdateActiveTreeDrawProperties();
-  CheckCanUseLCDText(LCDTextDisallowedReason::kNone);
-
-  // Case 13: Normal filter.
-  FilterOperations blur_filter;
-  blur_filter.Append(FilterOperation::CreateBlurFilter(4.0f));
-  SetFilter(child_, blur_filter);
-  UpdateActiveTreeDrawProperties();
-  CheckCanUseLCDText(LCDTextDisallowedReason::kLayerHasFilterEffect, child_);
-}
-
-TEST_P(LCDTextTest, CanUseLCDTextWithContentsOpaqueForText) {
-  child_->SetContentsOpaque(false);
-  child_->SetBackgroundColor(SK_ColorGREEN);
-  child_->SetContentsOpaqueForText(true);
-  CheckCanUseLCDText(LCDTextDisallowedReason::kNone, child_);
-
-  child_->SetContentsOpaqueForText(false);
-  CheckCanUseLCDText(LCDTextDisallowedReason::kContentsNotOpaque, child_);
-}
-
-TEST_P(LCDTextTest, CanUseLCDTextWithAnimation) {
-  // Sanity check: Make sure can_use_lcd_text_ is set on each node.
-  UpdateActiveTreeDrawProperties();
-  CheckCanUseLCDText(LCDTextDisallowedReason::kNone);
-
-  // Add opacity animation.
-  gfx::Transform non_integral_translation;
-  non_integral_translation.Translate(1.5, 2.5);
-  SetTransform(child_, non_integral_translation);
-  AddAnimatedTransformToElementWithAnimation(child_->element_id(), timeline(),
-                                             10.0, 12, 34);
-  UpdateActiveTreeDrawProperties();
-  // Text LCD should be adjusted while animation is active.
-  CheckCanUseLCDText(LCDTextDisallowedReason::kNonIntegralTranslation);
-}
-
-TEST_P(LCDTextTest, CanUseLCDTextWithAnimationContentsOpaque) {
-  // Sanity check: Make sure can_use_lcd_text_ is set on each node.
-  UpdateActiveTreeDrawProperties();
-  CheckCanUseLCDText(LCDTextDisallowedReason::kNone);
-
-  // Mark contents non-opaque within the first animation frame.
-  child_->SetContentsOpaque(false);
-  child_->SetBackgroundColor(SK_ColorWHITE);
-  AddOpacityTransitionToElementWithAnimation(child_->element_id(), timeline(),
-                                             10.0, 0.9f, 0.1f, false);
-  UpdateActiveTreeDrawProperties();
-  // LCD text should be disabled for non-opaque layers even during animations.
-  CheckCanUseLCDText(LCDTextDisallowedReason::kContentsNotOpaque, child_);
-  CheckCanUseLCDText(LCDTextDisallowedReason::kNone, grand_child_);
-}
-
-INSTANTIATE_TEST_SUITE_P(DrawPropertiesTest,
-                         LCDTextTest,
-                         testing::Combine(testing::Bool(), testing::Bool()));
-
 // Needs layer tree mode: hide_layer_and_subtree.
 TEST_F(DrawPropertiesTestWithLayerTree, SubtreeHidden_SingleLayerImpl) {
   auto root = Layer::Create();
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index f569b1f..685e028 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -1939,7 +1939,6 @@
                              "jni_registration_header",
                              "target_type",
                              "enable_multidex",
-                             "lint_min_sdk_version",
                              "lint_suppressions_dep",
                              "lint_suppressions_file",
                            ])
diff --git a/chrome/android/aapt2.config b/chrome/android/aapt2.config
new file mode 100644
index 0000000..a1e0ee9
--- /dev/null
+++ b/chrome/android/aapt2.config
@@ -0,0 +1 @@
+drawable/shortcut_incognito#no_collapse
diff --git a/chrome/android/chrome_public_apk_tmpl.gni b/chrome/android/chrome_public_apk_tmpl.gni
index ebf5013..d029559 100644
--- a/chrome/android/chrome_public_apk_tmpl.gni
+++ b/chrome/android/chrome_public_apk_tmpl.gni
@@ -609,10 +609,12 @@
       }
 
       # Resources config for blocklisting resource names from obfuscation
-      if (defined(invoker.resources_config_path)) {
-        resources_config_path = invoker.resources_config_path
-      } else {
-        resources_config_path = "//android_webview/aapt2.config"
+      resources_config_paths = [
+        "//android_webview/aapt2.config",
+        "//chrome/android/aapt2.config",
+      ]
+      if (defined(invoker.resources_config_paths)) {
+        resources_config_paths += invoker.resources_config_paths
       }
 
       if (defined(invoker.never_incremental)) {
@@ -702,7 +704,7 @@
       "no_xml_namespaces",
       "product_config_java_packages",
       "proguard_configs",
-      "resources_config_path",
+      "resources_config_paths",
       "secondary_abi_loadable_modules",
       "secondary_abi_shared_libraries",
       "secondary_native_lib_placeholders",
diff --git a/build/android/lint/baseline.xml b/chrome/android/expectations/lint-baseline.xml
similarity index 94%
rename from build/android/lint/baseline.xml
rename to chrome/android/expectations/lint-baseline.xml
index 5e70e32..945e044 100644
--- a/build/android/lint/baseline.xml
+++ b/chrome/android/expectations/lint-baseline.xml
@@ -359,17 +359,6 @@
 
     <issue
         id="WrongConstant"
-        message="Must be one of: Type.NO_ADJUSTMENT, Type.FRAMERATE_ADJUSTMENT"
-        errorLine1="        return -1;"
-        errorLine2="               ~~">
-        <location
-            file="media/base/android/java/src/org/chromium/media/MediaCodecUtil.java"
-            line="556"
-            column="16"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
         message="Must be one of: MediaSessionActionSource.MEDIA_NOTIFICATION, MediaSessionActionSource.MEDIA_SESSION, MediaSessionActionSource.HEADSET_UNPLUG"
         errorLine1="        return MediaSessionUma.MediaSessionActionSource.NUM_ENTRIES;"
         errorLine2="               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -580,33 +569,11 @@
     <issue
         id="VisibleForTests"
         message="This method should only be accessed from tests or within private scope"
-        errorLine1="        setKeyboardDelegate(createKeyboardVisibilityDelegate());"
-        errorLine2="        ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="ui/android/java/src/org/chromium/ui/base/ActivityWindowAndroid.java"
-            line="58"
-            column="9"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
-        errorLine1="        setAndroidPermissionDelegate(createAndroidPermissionDelegate());"
-        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="ui/android/java/src/org/chromium/ui/base/ActivityWindowAndroid.java"
-            line="59"
-            column="9"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
         errorLine1="                                || !WebsitePreferenceBridgeJni.get().isContentSettingsPatternValid("
         errorLine2="                                                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/AddExceptionPreference.java"
-            line="189"
+            line="187"
             column="70"/>
     </issue>
 
@@ -1002,7 +969,7 @@
         errorLine2="                          ~~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java"
-            line="237"
+            line="240"
             column="27"/>
     </issue>
 
@@ -1013,7 +980,7 @@
         errorLine2="                              ~~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java"
-            line="238"
+            line="241"
             column="31"/>
     </issue>
 
@@ -1024,7 +991,7 @@
         errorLine2="                              ~~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java"
-            line="239"
+            line="242"
             column="31"/>
     </issue>
 
@@ -1035,7 +1002,7 @@
         errorLine2="                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java"
-            line="469"
+            line="475"
             column="30"/>
     </issue>
 
@@ -1046,7 +1013,7 @@
         errorLine2="                                                ~~~~~~~~~~~~~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java"
-            line="558"
+            line="564"
             column="49"/>
     </issue>
 
@@ -1057,7 +1024,7 @@
         errorLine2="                                       ~~~~~~~~~~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java"
-            line="559"
+            line="565"
             column="40"/>
     </issue>
 
@@ -1068,7 +1035,7 @@
         errorLine2="                                                                  ~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java"
-            line="574"
+            line="580"
             column="67"/>
     </issue>
 
@@ -1079,7 +1046,7 @@
         errorLine2="                        ~~~~~~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java"
-            line="612"
+            line="618"
             column="25"/>
     </issue>
 
@@ -1090,7 +1057,7 @@
         errorLine2="                                                                        ~~~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java"
-            line="629"
+            line="635"
             column="73"/>
     </issue>
 
@@ -1101,7 +1068,7 @@
         errorLine2="                                                                      ~~~~~~~~~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java"
-            line="650"
+            line="656"
             column="71"/>
     </issue>
 
@@ -1112,7 +1079,7 @@
         errorLine2="                                                   ~~~~~~~~~~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java"
-            line="668"
+            line="674"
             column="52"/>
     </issue>
 
@@ -1123,7 +1090,7 @@
         errorLine2="                       ~~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java"
-            line="687"
+            line="693"
             column="24"/>
     </issue>
 
@@ -1134,7 +1101,7 @@
         errorLine2="                              ~~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java"
-            line="688"
+            line="694"
             column="31"/>
     </issue>
 
@@ -1145,7 +1112,7 @@
         errorLine2="                              ~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java"
-            line="689"
+            line="695"
             column="31"/>
     </issue>
 
@@ -1156,7 +1123,7 @@
         errorLine2="                                                                     ~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java"
-            line="698"
+            line="704"
             column="70"/>
     </issue>
 
@@ -1167,7 +1134,7 @@
         errorLine2="                                          ~~~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java"
-            line="703"
+            line="709"
             column="43"/>
     </issue>
 
@@ -1178,7 +1145,7 @@
         errorLine2="                                               ~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java"
-            line="705"
+            line="711"
             column="48"/>
     </issue>
 
@@ -1189,7 +1156,7 @@
         errorLine2="                           ~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java"
-            line="728"
+            line="734"
             column="28"/>
     </issue>
 
@@ -1200,7 +1167,7 @@
         errorLine2="                                                        ~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java"
-            line="846"
+            line="852"
             column="57"/>
     </issue>
 
@@ -1211,7 +1178,7 @@
         errorLine2="                                    ~~~~~~~~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java"
-            line="933"
+            line="940"
             column="37"/>
     </issue>
 
@@ -1222,7 +1189,7 @@
         errorLine2="                              ~~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java"
-            line="934"
+            line="941"
             column="31"/>
     </issue>
 
@@ -1233,7 +1200,7 @@
         errorLine2="                                   ~~~~~~~~~~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java"
-            line="955"
+            line="962"
             column="36"/>
     </issue>
 
@@ -1244,7 +1211,7 @@
         errorLine2="                       ~~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java"
-            line="970"
+            line="977"
             column="24"/>
     </issue>
 
@@ -1255,7 +1222,7 @@
         errorLine2="                               ~~~~~~~~~~~~~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java"
-            line="972"
+            line="979"
             column="32"/>
     </issue>
 
@@ -1266,7 +1233,7 @@
         errorLine2="                                                                ~~~~~~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java"
-            line="972"
+            line="979"
             column="65"/>
     </issue>
 
@@ -1277,7 +1244,7 @@
         errorLine2="                                                                                  ~~~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java"
-            line="1150"
+            line="1157"
             column="83"/>
     </issue>
 
@@ -1288,7 +1255,7 @@
         errorLine2="                           ~~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java"
-            line="1151"
+            line="1158"
             column="28"/>
     </issue>
 
@@ -2707,7 +2674,7 @@
         errorLine2="                                       ~~~~~~~~~~~~~~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkPromoHeader.java"
-            line="239"
+            line="237"
             column="40"/>
     </issue>
 
@@ -3374,171 +3341,6 @@
     <issue
         id="VisibleForTests"
         message="This method should only be accessed from tests or within private scope"
-        errorLine1="        mPopup.setAdapter(adapter);"
-        errorLine2="               ~~~~~~~~~~">
-        <location
-            file="ui/android/java/src/org/chromium/ui/DropdownPopupWindow.java"
-            line="46"
-            column="16"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
-        errorLine1="        mPopup.setInitialSelection(initialSelection);"
-        errorLine2="               ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="ui/android/java/src/org/chromium/ui/DropdownPopupWindow.java"
-            line="50"
-            column="16"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
-        errorLine1="        mPopup.show();"
-        errorLine2="               ~~~~">
-        <location
-            file="ui/android/java/src/org/chromium/ui/DropdownPopupWindow.java"
-            line="57"
-            column="16"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
-        errorLine1="        mPopup.setOnDismissListener(listener);"
-        errorLine2="               ~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="ui/android/java/src/org/chromium/ui/DropdownPopupWindow.java"
-            line="66"
-            column="16"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
-        errorLine1="        mPopup.setRtl(isRtl);"
-        errorLine2="               ~~~~~~">
-        <location
-            file="ui/android/java/src/org/chromium/ui/DropdownPopupWindow.java"
-            line="74"
-            column="16"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
-        errorLine1="        mPopup.disableHideOnOutsideTap();"
-        errorLine2="               ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="ui/android/java/src/org/chromium/ui/DropdownPopupWindow.java"
-            line="82"
-            column="16"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
-        errorLine1="        mPopup.setContentDescriptionForAccessibility(description);"
-        errorLine2="               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="ui/android/java/src/org/chromium/ui/DropdownPopupWindow.java"
-            line="91"
-            column="16"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
-        errorLine1="        mPopup.setOnItemClickListener(clickListener);"
-        errorLine2="               ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="ui/android/java/src/org/chromium/ui/DropdownPopupWindow.java"
-            line="100"
-            column="16"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
-        errorLine1="        mPopup.postShow();"
-        errorLine2="               ~~~~~~~~">
-        <location
-            file="ui/android/java/src/org/chromium/ui/DropdownPopupWindow.java"
-            line="108"
-            column="16"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
-        errorLine1="        mPopup.dismiss();"
-        errorLine2="               ~~~~~~~">
-        <location
-            file="ui/android/java/src/org/chromium/ui/DropdownPopupWindow.java"
-            line="115"
-            column="16"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
-        errorLine1="        return mPopup.getListView();"
-        errorLine2="                      ~~~~~~~~~~~">
-        <location
-            file="ui/android/java/src/org/chromium/ui/DropdownPopupWindow.java"
-            line="122"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
-        errorLine1="        return mPopup.isShowing();"
-        errorLine2="                      ~~~~~~~~~">
-        <location
-            file="ui/android/java/src/org/chromium/ui/DropdownPopupWindow.java"
-            line="129"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
-        errorLine1="        mPopup.setFooterView(footerItem);"
-        errorLine2="               ~~~~~~~~~~~~~">
-        <location
-            file="ui/android/java/src/org/chromium/ui/DropdownPopupWindow.java"
-            line="136"
-            column="16"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
-        errorLine1="        implements AnchoredPopupWindow.LayoutObserver, DropdownPopupWindowInterface {"
-        errorLine2="                                                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="ui/android/java/src/org/chromium/ui/DropdownPopupWindowImpl.java"
-            line="32"
-            column="56"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
-        errorLine1="class DropdownPopupWindowJellyBean implements DropdownPopupWindowInterface {"
-        errorLine2="                                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="ui/android/java/src/org/chromium/ui/DropdownPopupWindowJellyBean.java"
-            line="33"
-            column="47"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
         errorLine1="        mLastProcessedSuggestionURL = suggestion.getUrl();"
         errorLine2="                                                 ~~~~~~">
         <location
@@ -3671,28 +3473,6 @@
     <issue
         id="VisibleForTests"
         message="This method should only be accessed from tests or within private scope"
-        errorLine1="        setKeyboardDelegate(new ActivityKeyboardVisibilityDelegate(getActivity()));"
-        errorLine2="        ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="weblayer/browser/java/org/chromium/weblayer_private/FragmentWindowAndroid.java"
-            line="37"
-            column="9"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
-        errorLine1="        setAndroidPermissionDelegate(new FragmentAndroidPermissionDelegate(mFragment));"
-        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="weblayer/browser/java/org/chromium/weblayer_private/FragmentWindowAndroid.java"
-            line="38"
-            column="9"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
         errorLine1="                    ? UrlUtilities.urlsMatchIgnoringFragments(speculatedUrl, url)"
         errorLine2="                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
@@ -3715,28 +3495,6 @@
     <issue
         id="VisibleForTests"
         message="This method should only be accessed from tests or within private scope"
-        errorLine1="    public static final int COMPOSITION_KEY_CODE = ImeAdapter.COMPOSITION_KEY_CODE;"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="content/public/android/java/src/org/chromium/content/browser/input/ImeAdapterImpl.java"
-            line="96"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
-        errorLine1="            mCursorAnchorInfoController.setInputMethodManagerWrapper(immw);"
-        errorLine2="                                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="content/public/android/java/src/org/chromium/content/browser/input/ImeAdapterImpl.java"
-            line="350"
-            column="41"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
         errorLine1="                                IncognitoNotificationService.getRemoveAllIncognitoTabsIntent("
         errorLine2="                                                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
@@ -3891,83 +3649,6 @@
     <issue
         id="VisibleForTests"
         message="This method should only be accessed from tests or within private scope"
-        errorLine1="            LocationProviderAdapter.onNewLocationAvailable(location);"
-        errorLine2="                                    ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="services/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderAndroid.java"
-            line="62"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
-        errorLine1="            LocationProviderAdapter.newErrorAvailable("
-        errorLine2="                                    ~~~~~~~~~~~~~~~~~">
-        <location
-            file="services/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderAndroid.java"
-            line="114"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
-        errorLine1="            LocationProviderAdapter.onNewLocationAvailable(location);"
-        errorLine2="                                    ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="services/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderAndroid.java"
-            line="144"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
-        errorLine1="            LocationProviderAdapter.onNewLocationAvailable(location);"
-        errorLine2="                                    ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="services/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderGmsCore.java"
-            line="92"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
-        errorLine1="            LocationProviderAdapter.newErrorAvailable("
-        errorLine2="                                    ~~~~~~~~~~~~~~~~~">
-        <location
-            file="services/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderGmsCore.java"
-            line="104"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
-        errorLine1="        LocationProviderAdapter.newErrorAvailable("
-        errorLine2="                                ~~~~~~~~~~~~~~~~~">
-        <location
-            file="services/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderGmsCore.java"
-            line="116"
-            column="33"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
-        errorLine1="        LocationProviderAdapter.onNewLocationAvailable(location);"
-        errorLine2="                                ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="services/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderGmsCore.java"
-            line="150"
-            column="33"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
         errorLine1="                sController.mThrottler.queueNotification(notificationInfo);"
         errorLine2="                            ~~~~~~~~~~">
         <location
@@ -4434,7 +4115,7 @@
         errorLine2="                                             ~~~~~~~~~~~~~~~~~~~">
         <location
             file="chrome/android/java/src/org/chromium/chrome/browser/ntp/RecentTabsManager.java"
-            line="382"
+            line="381"
             column="46"/>
     </issue>
 
@@ -4804,39 +4485,6 @@
     <issue
         id="VisibleForTests"
         message="This method should only be accessed from tests or within private scope"
-        errorLine1="        mImeAdapter.finishComposingText();"
-        errorLine2="                    ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnection.java"
-            line="583"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
-        errorLine1="            mFactory.setTriggerDelayedOnCreateInputConnection(false);"
-        errorLine2="                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnectionProxyView.java"
-            line="95"
-            column="22"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
-        errorLine1="            mFactory.setTriggerDelayedOnCreateInputConnection(true);"
-        errorLine2="                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnectionProxyView.java"
-            line="97"
-            column="22"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
         errorLine1="        mTileRenderer.renderTileSection(tiles, mSectionView, mTileGroup.getTileSetupDelegate());"
         errorLine2="                                                                        ~~~~~~~~~~~~~~~~~~~~">
         <location
@@ -5453,39 +5101,6 @@
     <issue
         id="VisibleForTests"
         message="This method should only be accessed from tests or within private scope"
-        errorLine1="        mVrBrowsingEnabled = mDelegate.isVrBrowsingEnabled();"
-        errorLine2="        ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrShell.java"
-            line="173"
-            column="9"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
-        errorLine1="        mVrBrowsingEnabled = mDelegate.isVrBrowsingEnabled();"
-        errorLine2="                                       ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrShell.java"
-            line="173"
-            column="40"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
-        errorLine1="        setAndroidPermissionDelegate(new ActivityAndroidPermissionDelegate());"
-        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrWindowAndroid.java"
-            line="37"
-            column="9"/>
-    </issue>
-
-    <issue
-        id="VisibleForTests"
-        message="This method should only be accessed from tests or within private scope"
         errorLine1="        mWebApkUpdateManager.get().updateIfNeeded(storage, mActivity.getIntentDataProvider());"
         errorLine2="                                                                     ~~~~~~~~~~~~~~~~~~~~~">
         <location
@@ -5589,7 +5204,7 @@
         errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreferenceBridge.java"
-            line="156"
+            line="155"
             column="18"/>
     </issue>
 
@@ -5600,7 +5215,7 @@
         errorLine2="                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreferenceBridge.java"
-            line="181"
+            line="180"
             column="30"/>
     </issue>
 
@@ -5611,7 +5226,7 @@
         errorLine2="                 ^">
         <location
             file="components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreferenceBridge.java"
-            line="236"
+            line="235"
             column="18"/>
     </issue>
 
diff --git a/chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_header.xml b/chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_header.xml
index 9689c17..9185a74 100644
--- a/chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_header.xml
+++ b/chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_header.xml
@@ -8,6 +8,7 @@
     android:layout_height="wrap_content">
 
   <LinearLayout
+      android:id="@+id/header_top_container"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:minHeight="56dp"
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomBarCoordinator.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomBarCoordinator.java
index b246ed73..9a6819b1 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomBarCoordinator.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomBarCoordinator.java
@@ -151,6 +151,8 @@
         // We don't want to animate the carousels children views as they are already animated by the
         // recyclers ItemAnimator, so we exclude them to avoid a clash between the animations.
         mLayoutTransition.excludeChildren(mActionsCoordinator.getView(), /* exclude= */ true);
+        mLayoutTransition.excludeChildren(
+                mHeaderCoordinator.getCarouselView(), /* exclude= */ true);
 
         // do not animate the contents of the payment method section inside the section choice list,
         // since the animation is not required and causes a rendering crash.
@@ -190,7 +192,11 @@
         controller.addObserver(new EmptyBottomSheetObserver() {
             @Override
             public void onSheetStateChanged(int newState) {
-                maybeShowHeaderChip();
+                // Note: recycler view updates while the bottom sheet is SCROLLING result in a
+                // BottomSheet assertion.
+                if (newState != BottomSheetController.SheetState.SCROLLING) {
+                    maybeShowHeaderChips();
+                }
             }
 
             @Override
@@ -256,10 +262,8 @@
     }
 
     private void setupAnimations(AssistantModel model, ViewGroup rootView) {
-        // Animate when the chip in the header changes.
         model.getHeaderModel().addObserver((source, propertyKey) -> {
-            if (propertyKey == AssistantHeaderModel.CHIP
-                    || propertyKey == AssistantHeaderModel.CHIP_VISIBLE) {
+            if (propertyKey == AssistantHeaderModel.CHIPS_VISIBLE) {
                 animateChildren(rootView);
             }
         });
@@ -295,12 +299,12 @@
         TransitionManager.beginDelayedTransition(rootView, mLayoutTransition);
     }
 
-    private void maybeShowHeaderChip() {
-        boolean showChip =
+    private void maybeShowHeaderChips() {
+        boolean showChips =
                 mBottomSheetController.getSheetState() == BottomSheetController.SheetState.PEEK
                 && mPeekHeightCoordinator.getPeekMode()
                         == AssistantPeekHeightCoordinator.PeekMode.HANDLE_HEADER;
-        mModel.getHeaderModel().set(AssistantHeaderModel.CHIP_VISIBLE, showChip);
+        mModel.getHeaderModel().set(AssistantHeaderModel.CHIPS_VISIBLE, showChips);
     }
 
     /**
@@ -350,7 +354,7 @@
     /** Set the peek mode. */
     void setPeekMode(@AssistantPeekHeightCoordinator.PeekMode int peekMode) {
         mPeekHeightCoordinator.setPeekMode(peekMode);
-        maybeShowHeaderChip();
+        maybeShowHeaderChips();
     }
 
     /** Expand the bottom sheet. */
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java
index 674fef83..dfc3750 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java
@@ -18,7 +18,6 @@
 import org.chromium.chrome.browser.autofill_assistant.carousel.AssistantCarouselModel;
 import org.chromium.chrome.browser.autofill_assistant.carousel.AssistantChip;
 import org.chromium.chrome.browser.autofill_assistant.carousel.AssistantChip.Type;
-import org.chromium.chrome.browser.autofill_assistant.header.AssistantHeaderModel;
 import org.chromium.chrome.browser.autofill_assistant.metrics.DropOutReason;
 import org.chromium.chrome.browser.customtabs.CustomTabActivity;
 import org.chromium.chrome.browser.profiles.Profile;
@@ -283,55 +282,56 @@
     }
 
     /**
-     * Adds an action button to the chip list, which executes the action {@code actionIndex}.
+     * Creates an action button which executes the action {@code actionIndex}.
      */
     @CalledByNative
-    private void addActionButton(List<AssistantChip> chips, int icon, String text, int actionIndex,
+    private AssistantChip createActionButton(int icon, String text, int actionIndex,
             boolean disabled, boolean sticky, String identifier) {
-        chips.add(new AssistantChip(AssistantChip.Type.BUTTON_HAIRLINE, icon, text, disabled,
-                sticky, identifier, () -> safeNativeOnUserActionSelected(actionIndex)));
+        return new AssistantChip(AssistantChip.Type.BUTTON_HAIRLINE, icon, text, disabled, sticky,
+                identifier, () -> safeNativeOnUserActionSelected(actionIndex));
     }
 
     /**
-     * Adds a highlighted action button to the chip list, which executes the action {@code
-     * actionIndex}.
+     * Creates a highlighted action button which executes the action {@code actionIndex}.
      */
     @CalledByNative
-    private void addHighlightedActionButton(List<AssistantChip> chips, int icon, String text,
-            int actionIndex, boolean disabled, boolean sticky, String identifier) {
-        chips.add(new AssistantChip(Type.BUTTON_FILLED_BLUE, icon, text, disabled, sticky,
-                identifier, () -> safeNativeOnUserActionSelected(actionIndex)));
+    private AssistantChip createHighlightedActionButton(int icon, String text, int actionIndex,
+            boolean disabled, boolean sticky, String identifier) {
+        return new AssistantChip(Type.BUTTON_FILLED_BLUE, icon, text, disabled, sticky, identifier,
+                () -> safeNativeOnUserActionSelected(actionIndex));
     }
 
     /**
-     * Adds a cancel action button to the chip list. If the keyboard is currently shown, it
-     * dismisses the keyboard. Otherwise, it shows the snackbar and then executes
-     * {@code actionIndex}, or shuts down Autofill Assistant if {@code actionIndex} is {@code -1}.
+     * Creates a cancel action button. If the keyboard is currently shown, it dismisses the
+     * keyboard. Otherwise, it shows the snackbar and then executes {@code actionIndex}, or shuts
+     * down Autofill Assistant if {@code actionIndex} is {@code -1}.
      */
     @CalledByNative
-    private void addCancelButton(List<AssistantChip> chips, int icon, String text, int actionIndex,
+    private AssistantChip createCancelButton(int icon, String text, int actionIndex,
             boolean disabled, boolean sticky, String identifier) {
-        chips.add(new AssistantChip(AssistantChip.Type.BUTTON_HAIRLINE, icon, text, disabled,
-                sticky, identifier, () -> safeNativeOnCancelButtonClicked(actionIndex)));
+        return new AssistantChip(AssistantChip.Type.BUTTON_HAIRLINE, icon, text, disabled, sticky,
+                identifier, () -> safeNativeOnCancelButtonClicked(actionIndex));
     }
 
     /**
      * Adds a close action button to the chip list, which shuts down Autofill Assistant.
      */
     @CalledByNative
-    private void addCloseButton(List<AssistantChip> chips, int icon, String text, boolean disabled,
-            boolean sticky, String identifier) {
-        chips.add(new AssistantChip(AssistantChip.Type.BUTTON_HAIRLINE, icon, text, disabled,
-                sticky, identifier, this::safeNativeOnCloseButtonClicked));
+    private AssistantChip createCloseButton(
+            int icon, String text, boolean disabled, boolean sticky, String identifier) {
+        return new AssistantChip(AssistantChip.Type.BUTTON_HAIRLINE, icon, text, disabled, sticky,
+                identifier, this::safeNativeOnCloseButtonClicked);
+    }
+
+    @CalledByNative
+    private static void appendChipToList(List<AssistantChip> chips, AssistantChip chip) {
+        chips.add(chip);
     }
 
     @CalledByNative
     private void setActions(List<AssistantChip> chips) {
-        // TODO(b/144075373): Move this to AssistantCarouselModel and AssistantHeaderModel. Move
-        // header chip logic to native.
-        AssistantCarouselModel model = getModel().getActionsModel();
-        model.setChips(chips);
-        setHeaderChip(chips);
+        // TODO(b/144075373): Move this to AssistantCarouselModel.
+        getModel().getActionsModel().setChips(chips);
     }
 
     @CalledByNative
@@ -358,19 +358,6 @@
         model.setChips(newChips);
     }
 
-    private void setHeaderChip(List<AssistantChip> chips) {
-        // The header chip is the first sticky chip found in the actions.
-        AssistantChip headerChip = null;
-        for (AssistantChip chip : chips) {
-            if (chip.isSticky()) {
-                headerChip = chip;
-                break;
-            }
-        }
-
-        getModel().getHeaderModel().set(AssistantHeaderModel.CHIP, headerChip);
-    }
-
     @CalledByNative
     private void setViewportMode(@AssistantViewportMode int mode) {
         mCoordinator.getBottomBarCoordinator().setViewportMode(mode);
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantChipAdapter.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantChipAdapter.java
index 4ce5656..f449346 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantChipAdapter.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantChipAdapter.java
@@ -22,7 +22,7 @@
 public class AssistantChipAdapter extends RecyclerView.Adapter<AssistantChipViewHolder> {
     private final List<AssistantChip> mChips = new ArrayList<>();
 
-    void setChips(List<AssistantChip> chips) {
+    public void setChips(List<AssistantChip> chips) {
         DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() {
             @Override
             public int getOldListSize() {
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderCoordinator.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderCoordinator.java
index 7653718e..7f97029f 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderCoordinator.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderCoordinator.java
@@ -5,13 +5,19 @@
 package org.chromium.chrome.browser.autofill_assistant.header;
 
 import android.content.Context;
+import android.graphics.Rect;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ImageView;
 
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
 import org.chromium.chrome.autofill_assistant.R;
 import org.chromium.chrome.browser.autofill_assistant.AutofillAssistantUiController;
+import org.chromium.chrome.browser.autofill_assistant.carousel.AssistantChipAdapter;
 import org.chromium.chrome.browser.autofill_assistant.header.AssistantHeaderViewBinder.ViewHolder;
 import org.chromium.chrome.browser.signin.DisplayableProfileData;
 import org.chromium.chrome.browser.signin.IdentityServicesProvider;
@@ -32,6 +38,7 @@
     private final ImageView mProfileView;
     private final String mSignedInAccountName;
     private final ViewHolder mViewHolder;
+    private final RecyclerView mChipsContainer;
 
     public AssistantHeaderCoordinator(Context context, AssistantHeaderModel model) {
         // Create the poodle and insert it before the status message. We have to create a view
@@ -56,8 +63,52 @@
                 identityManager.getPrimaryAccountInfo(ConsentLevel.SYNC));
         setupProfileImage();
 
+        mChipsContainer = new RecyclerView(context);
+        final int innerChipSpacing = context.getResources().getDimensionPixelSize(
+                R.dimen.autofill_assistant_actions_spacing);
+        mChipsContainer.addItemDecoration(new RecyclerView.ItemDecoration() {
+            @Override
+            public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
+                    @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
+                outRect.top = 0;
+                outRect.bottom = 0;
+
+                if (state.getItemCount() <= 1) {
+                    return;
+                }
+
+                // If old position != NO_POSITION, it means the carousel is being animated and we
+                // should use that position in our logic.
+                int position = parent.getChildAdapterPosition(view);
+                RecyclerView.ViewHolder viewHolder = parent.getChildViewHolder(view);
+                if (viewHolder != null && viewHolder.getOldPosition() != RecyclerView.NO_POSITION) {
+                    position = viewHolder.getOldPosition();
+                }
+
+                if (position == RecyclerView.NO_POSITION) {
+                    return;
+                }
+
+                outRect.left = position == 0 ? 0 : innerChipSpacing;
+                outRect.right = 0;
+            }
+        });
+
+        AssistantChipAdapter chipAdapter = new AssistantChipAdapter();
+        mChipsContainer.setAdapter(chipAdapter);
+        LinearLayoutManager layoutManager = new LinearLayoutManager(context);
+        layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
+        mChipsContainer.setLayoutManager(layoutManager);
+        mView.setPadding(mChipsContainer.getPaddingLeft(), mChipsContainer.getPaddingTop(),
+                context.getResources().getDimensionPixelSize(
+                        R.dimen.autofill_assistant_profile_icon_padding),
+                mChipsContainer.getPaddingBottom());
+        ViewGroup topContainer = mView.findViewById(R.id.header_top_container);
+        topContainer.addView(mChipsContainer);
+
         // Bind view and mediator through the model.
-        mViewHolder = new AssistantHeaderViewBinder.ViewHolder(context, mView, poodle);
+        mViewHolder =
+                new AssistantHeaderViewBinder.ViewHolder(context, mView, poodle, mChipsContainer);
         AssistantHeaderViewBinder viewBinder = new AssistantHeaderViewBinder();
         PropertyModelChangeProcessor.create(model, mViewHolder, viewBinder);
 
@@ -77,6 +128,11 @@
         return mView;
     }
 
+    /** Returns the view containing the chips. */
+    public View getCarouselView() {
+        return mChipsContainer;
+    }
+
     /**
      * Cleanup resources when this goes out of scope.
      */
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderModel.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderModel.java
index 1fc9fc8..7c86cc3 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderModel.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderModel.java
@@ -4,6 +4,8 @@
 
 package org.chromium.chrome.browser.autofill_assistant.header;
 
+import android.support.annotation.VisibleForTesting;
+
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.chrome.browser.autofill_assistant.carousel.AssistantChip;
@@ -18,6 +20,9 @@
  */
 @JNINamespace("autofill_assistant")
 public class AssistantHeaderModel extends PropertyModel {
+    public static final WritableObjectPropertyKey<List<AssistantChip>> CHIPS =
+            new WritableObjectPropertyKey<>();
+
     public static final WritableObjectPropertyKey<String> STATUS_MESSAGE =
             new WritableObjectPropertyKey<>();
 
@@ -45,10 +50,7 @@
     public static final WritableObjectPropertyKey<Runnable> FEEDBACK_BUTTON_CALLBACK =
             new WritableObjectPropertyKey<>();
 
-    public static final WritableObjectPropertyKey<AssistantChip> CHIP =
-            new WritableObjectPropertyKey<>();
-
-    public static final WritableBooleanPropertyKey CHIP_VISIBLE = new WritableBooleanPropertyKey();
+    public static final WritableBooleanPropertyKey CHIPS_VISIBLE = new WritableBooleanPropertyKey();
 
     public static final WritableBooleanPropertyKey DISABLE_ANIMATIONS_FOR_TESTING =
             new WritableBooleanPropertyKey();
@@ -56,7 +58,8 @@
     public AssistantHeaderModel() {
         super(STATUS_MESSAGE, BUBBLE_MESSAGE, PROGRESS, PROGRESS_ACTIVE_STEP, PROGRESS_BAR_ERROR,
                 PROGRESS_VISIBLE, USE_STEP_PROGRESS_BAR, STEP_PROGRESS_BAR_ICONS, SPIN_POODLE,
-                FEEDBACK_BUTTON_CALLBACK, CHIP, CHIP_VISIBLE, DISABLE_ANIMATIONS_FOR_TESTING);
+                FEEDBACK_BUTTON_CALLBACK, CHIPS, CHIPS_VISIBLE, DISABLE_ANIMATIONS_FOR_TESTING);
+        set(CHIPS, new ArrayList<>());
     }
 
     @CalledByNative
@@ -127,4 +130,16 @@
     private void setDisableAnimations(boolean disableAnimations) {
         set(DISABLE_ANIMATIONS_FOR_TESTING, disableAnimations);
     }
+
+    @CalledByNative
+    @VisibleForTesting
+    public void setChips(List<AssistantChip> chips) {
+        // Move last chip (cancel) to first position. For legacy reasons, native builds this list
+        // such that the cancel chip is last, but the regular carousel will show it in the left-most
+        // position and the header should mirror this.
+        if (chips.size() > 1) {
+            chips.add(0, chips.remove(chips.size() - 1));
+        }
+        set(CHIPS, chips);
+    }
 }
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderViewBinder.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderViewBinder.java
index c5941419..e3cc10c 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderViewBinder.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderViewBinder.java
@@ -11,11 +11,12 @@
 import android.widget.TextView;
 
 import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.DefaultItemAnimator;
+import androidx.recyclerview.widget.RecyclerView;
 
 import org.chromium.chrome.autofill_assistant.R;
 import org.chromium.chrome.browser.autofill_assistant.AssistantTextUtils;
-import org.chromium.chrome.browser.autofill_assistant.carousel.AssistantChip;
-import org.chromium.chrome.browser.autofill_assistant.carousel.AssistantChipViewHolder;
+import org.chromium.chrome.browser.autofill_assistant.carousel.AssistantChipAdapter;
 import org.chromium.chrome.browser.settings.SettingsLauncher;
 import org.chromium.chrome.browser.settings.SettingsLauncherImpl;
 import org.chromium.chrome.browser.sync.settings.SyncAndServicesSettings;
@@ -45,12 +46,12 @@
         final AssistantStepProgressBar mStepProgressBar;
         final View mProfileIconView;
         final PopupMenu mProfileIconMenu;
-        @Nullable
-        AssistantChipViewHolder mChip;
+        final RecyclerView mChipsContainer;
         @Nullable
         TextBubble mTextBubble;
 
-        ViewHolder(Context context, ViewGroup headerView, AnimatedPoodle poodle) {
+        ViewHolder(Context context, ViewGroup headerView, AnimatedPoodle poodle,
+                RecyclerView chipsContainer) {
             mContext = context;
             mPoodle = poodle;
             mHeader = headerView;
@@ -62,6 +63,7 @@
             mProfileIconMenu = new PopupMenu(context, mProfileIconView);
             mProfileIconMenu.inflate(R.menu.profile_icon_menu);
             mProfileIconView.setOnClickListener(unusedView -> mProfileIconMenu.show());
+            mChipsContainer = chipsContainer;
         }
 
         void disableAnimations(boolean disable) {
@@ -70,6 +72,8 @@
             // Hiding the animated poodle seems to be the easiest way to disable its animation since
             // {@link LogoView#setAnimationEnabled(boolean)} is private.
             mPoodle.getView().setVisibility(View.INVISIBLE);
+            ((DefaultItemAnimator) mChipsContainer.getItemAnimator())
+                    .setSupportsChangeAnimations(!disable);
         }
 
         void updateProgressBarVisibility(boolean visible, boolean useStepProgressBar) {
@@ -110,11 +114,13 @@
             view.mPoodle.setSpinEnabled(model.get(AssistantHeaderModel.SPIN_POODLE));
         } else if (AssistantHeaderModel.FEEDBACK_BUTTON_CALLBACK == propertyKey) {
             setProfileMenuListener(view, model.get(AssistantHeaderModel.FEEDBACK_BUTTON_CALLBACK));
-        } else if (AssistantHeaderModel.CHIP == propertyKey) {
-            bindChip(view, model.get(AssistantHeaderModel.CHIP));
-            maybeShowChip(model, view);
-        } else if (AssistantHeaderModel.CHIP_VISIBLE == propertyKey) {
-            maybeShowChip(model, view);
+        } else if (AssistantHeaderModel.CHIPS == propertyKey) {
+            view.mChipsContainer.invalidateItemDecorations();
+            ((AssistantChipAdapter) view.mChipsContainer.getAdapter())
+                    .setChips(model.get(AssistantHeaderModel.CHIPS));
+            maybeShowChips(model, view);
+        } else if (AssistantHeaderModel.CHIPS_VISIBLE == propertyKey) {
+            maybeShowChips(model, view);
         } else if (AssistantHeaderModel.BUBBLE_MESSAGE == propertyKey) {
             showOrDismissBubble(model, view);
         } else if (AssistantHeaderModel.DISABLE_ANIMATIONS_FOR_TESTING == propertyKey) {
@@ -124,45 +130,18 @@
         }
     }
 
-    private void maybeShowChip(AssistantHeaderModel model, ViewHolder view) {
-        if (model.get(AssistantHeaderModel.CHIP_VISIBLE)
-                && model.get(AssistantHeaderModel.CHIP) != null) {
-            view.mChip.getView().setVisibility(View.VISIBLE);
+    private void maybeShowChips(AssistantHeaderModel model, ViewHolder view) {
+        if (model.get(AssistantHeaderModel.CHIPS_VISIBLE)
+                && !model.get(AssistantHeaderModel.CHIPS).isEmpty()) {
+            view.mChipsContainer.setVisibility(View.VISIBLE);
             view.mProfileIconView.setVisibility(View.GONE);
         } else {
-            if (view.mChip != null) {
-                view.mChip.getView().setVisibility(View.GONE);
-            }
+            view.mChipsContainer.setVisibility(View.GONE);
 
             view.mProfileIconView.setVisibility(View.VISIBLE);
         }
     }
 
-    private void bindChip(ViewHolder view, @Nullable AssistantChip chip) {
-        if (chip == null) {
-            return;
-        }
-
-        int viewType = AssistantChipViewHolder.getViewType(chip);
-
-        // If there is already a chip in the header but with incompatible type, remove it.
-        ViewGroup parent = (ViewGroup) view.mStatusMessage.getParent();
-        if (view.mChip != null && view.mChip.getType() != viewType) {
-            parent.removeView(view.mChip.getView());
-            view.mChip = null;
-        }
-
-        // If there is no chip already in the header, create one and add it at the end of the
-        // header.
-        if (view.mChip == null) {
-            view.mChip = AssistantChipViewHolder.create(view.mHeader, viewType);
-            parent.addView(view.mChip.getView());
-        }
-
-        // Bind the chip to the view.
-        view.mChip.bind(chip);
-    }
-
     private void setProfileMenuListener(ViewHolder view, @Nullable Runnable feedbackCallback) {
         view.mProfileIconMenu.setOnMenuItemClickListener(item -> {
             int itemId = item.getItemId();
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantHeaderUiTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantHeaderUiTest.java
index 9a19708..cad8a5de 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantHeaderUiTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantHeaderUiTest.java
@@ -7,6 +7,7 @@
 import static androidx.test.espresso.Espresso.onView;
 import static androidx.test.espresso.action.ViewActions.click;
 import static androidx.test.espresso.assertion.PositionAssertions.isRightOf;
+import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
 import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA;
 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
@@ -52,6 +53,9 @@
 import org.chromium.components.browser_ui.widget.MaterialProgressBar;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Tests for the Autofill Assistant header.
  */
@@ -206,15 +210,17 @@
                 chipText, /* disabled= */ false, /* sticky= */ false, "", () -> {});
 
         // Set the header chip without displaying it.
-        TestThreadUtils.runOnUiThreadBlocking(() -> model.set(AssistantHeaderModel.CHIP, chip));
+        List<AssistantChip> chips = new ArrayList<>();
+        chips.add(chip);
+        TestThreadUtils.runOnUiThreadBlocking(() -> model.setChips(chips));
 
         Matcher<View> chipMatcher =
                 allOf(isDescendantOfA(is(coordinator.getView())), withText(chipText));
-        onView(chipMatcher).check(matches(not(isDisplayed())));
+        onView(chipMatcher).check(doesNotExist());
 
         // Show the chip
         TestThreadUtils.runOnUiThreadBlocking(
-                () -> model.set(AssistantHeaderModel.CHIP_VISIBLE, true));
+                () -> model.set(AssistantHeaderModel.CHIPS_VISIBLE, true));
         onView(chipMatcher)
                 .check(matches(isDisplayed()))
                 .check(isRightOf(withId(R.id.status_message)));
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
index 23e316f..d3f6edb 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
@@ -407,11 +407,6 @@
             fail("Failed to tap 'more tabs' " + e.toString());
         }
         onViewWaiting(withId(R.id.secondary_tasks_surface_view));
-        if (isInstantReturn()) {
-            // TODO(crbug.com/1076274): fix toolbar to avoid wrongly focusing on the toolbar
-            // omnibox.
-            return;
-        }
 
         pressBack();
         onViewWaiting(withId(R.id.primary_tasks_surface_view));
@@ -474,11 +469,6 @@
             fail("Failed to tap 'more tabs' " + e.toString());
         }
         onViewWaiting(withId(R.id.secondary_tasks_surface_view));
-        if (isInstantReturn()) {
-            // TODO(crbug.com/1076274): fix toolbar to avoid wrongly focusing on the toolbar
-            // omnibox.
-            return;
-        }
 
         pressBack();
         onViewWaiting(withId(R.id.primary_tasks_surface_view));
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/pseudotab/PseudoTab.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/pseudotab/PseudoTab.java
index 4e607a38..dafb35f 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/pseudotab/PseudoTab.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/pseudotab/PseudoTab.java
@@ -162,7 +162,7 @@
      * @return The title
      */
     public String getTitle() {
-        if (mTab != null && mTab.get() != null) {
+        if (mTab != null && mTab.get() != null && mTab.get().isInitialized()) {
             return mTab.get().getTitle();
         }
         assert mTabId != null;
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/pseudotab/PseudoTabUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/pseudotab/PseudoTabUnitTest.java
index 79f1014..17428bf 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/pseudotab/PseudoTabUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/pseudotab/PseudoTabUnitTest.java
@@ -427,7 +427,7 @@
     }
 
     @Test
-    public void testTabDestroyed() {
+    public void testTabDestroyedRootId() {
         Tab tab = new MockTab(TAB4_ID, false);
         PseudoTab pseudoTab = PseudoTab.fromTab(tab);
         tab.destroy();
@@ -436,4 +436,15 @@
         // UnsupportedOperationException
         Assert.assertEquals(Tab.INVALID_TAB_ID, pseudoTab.getRootId());
     }
+
+    @Test
+    public void testTabDestroyedTitle() {
+        Tab tab = new MockTab(TAB4_ID, false);
+        PseudoTab pseudoTab = PseudoTab.fromTab(tab);
+        tab.destroy();
+        // Title was not set. Without the isInitialized() check,
+        // pseudoTab.getTitle() would crash here with
+        // UnsupportedOperationException
+        Assert.assertEquals("", pseudoTab.getTitle());
+    }
 }
diff --git a/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrShellDelegate.java b/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrShellDelegate.java
index a26c8ae..b031505d 100644
--- a/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrShellDelegate.java
+++ b/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrShellDelegate.java
@@ -859,7 +859,6 @@
                 ContextUtils.getApplicationContext(), GVR_KEYBOARD_PACKAGE_ID);
     }
 
-    @VisibleForTesting
     protected boolean isVrBrowsingEnabled() {
         return isVrBrowsingEnabled(mActivity, VrCoreInstallUtils.getVrSupportLevel());
     }
diff --git a/chrome/android/java/res/layout/account_picker_bottom_sheet_view.xml b/chrome/android/java/res/layout/account_picker_bottom_sheet_view.xml
index f0a95424..a30cc10 100644
--- a/chrome/android/java/res/layout/account_picker_bottom_sheet_view.xml
+++ b/chrome/android/java/res/layout/account_picker_bottom_sheet_view.xml
@@ -27,6 +27,7 @@
         app:srcCompat="@drawable/chrome_sync_logo" />
 
     <TextView
+        android:id="@+id/account_picker_bottom_sheet_title"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginTop="12dp"
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/QualityEnforcer.java b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/QualityEnforcer.java
index ba899113..da8ab6e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/QualityEnforcer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/QualityEnforcer.java
@@ -8,6 +8,7 @@
 import android.content.pm.PackageManager;
 import android.os.Bundle;
 
+import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 import androidx.browser.customtabs.CustomTabsSessionToken;
@@ -25,8 +26,12 @@
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.content_public.browser.NavigationHandle;
+import org.chromium.net.NetError;
 import org.chromium.ui.widget.Toast;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 import javax.inject.Inject;
 
 /**
@@ -44,7 +49,8 @@
     static final String NOTIFY = "quality_enforcement.notify";
     @VisibleForTesting
     static final String CRASH = "quality_enforcement.crash";
-    private static final String KEY_CRASH_REASON = "crash_reason";
+    @VisibleForTesting
+    static final String KEY_CRASH_REASON = "crash_reason";
     private static final String KEY_SUCCESS = "success";
 
     private final ChromeActivity<?> mActivity;
@@ -55,6 +61,17 @@
 
     private boolean mOriginVerified;
 
+    @IntDef({ViolationType.ERROR_404, ViolationType.ERROR_5XX, ViolationType.UNAVAILABLE_OFFLINE,
+            ViolationType.DIGITAL_ASSERTLINKS})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ViolationType {
+        int ERROR_404 = 0;
+        int ERROR_5XX = 1;
+        int UNAVAILABLE_OFFLINE = 2;
+        int DIGITAL_ASSERTLINKS = 3;
+        int NUM_ENTRIES = 4;
+    }
+
     private final CustomTabTabObserver mTabObserver = new CustomTabTabObserver() {
         @Override
         public void onDidFinishNavigation(Tab tab, NavigationHandle navigation) {
@@ -64,11 +81,15 @@
             }
 
             String newUrl = tab.getOriginalUrl();
-            if (isNavigationInScope(newUrl) && navigation.httpStatusCode() == 404) {
-                String message = ContextUtils.getApplicationContext().getString(
-                        R.string.twa_quality_enforcement_violation_error,
-                        navigation.httpStatusCode(), newUrl);
-                trigger(message);
+            if (isNavigationInScope(newUrl)) {
+                if (navigation.httpStatusCode() == 404) {
+                    trigger(ViolationType.ERROR_404, newUrl, navigation.httpStatusCode());
+                } else if (navigation.httpStatusCode() >= 500
+                        && navigation.httpStatusCode() <= 599) {
+                    trigger(ViolationType.ERROR_5XX, newUrl, navigation.httpStatusCode());
+                } else if (navigation.errorCode() == NetError.ERR_INTERNET_DISCONNECTED) {
+                    trigger(ViolationType.UNAVAILABLE_OFFLINE, newUrl, navigation.httpStatusCode());
+                }
             }
         }
 
@@ -93,11 +114,12 @@
         tabObserverRegistrar.registerActivityTabObserver(mTabObserver);
     }
 
-    private void trigger(String message) {
-        showErrorToast(message);
+    private void trigger(@ViolationType int type, String url, int httpStatusCode) {
+        showErrorToast(getToastMessage(type, url, httpStatusCode));
 
+        // Notify the client app.
         Bundle args = new Bundle();
-        args.putString(KEY_CRASH_REASON, message);
+        args.putString(KEY_CRASH_REASON, toTwaCrashMessage(type, url, httpStatusCode));
         if (!ChromeFeatureList.isEnabled(
                     ChromeFeatureList.TRUSTED_WEB_ACTIVITY_QUALITY_ENFORCEMENT)) {
             mConnection.sendExtraCallbackWithResult(mSessionToken, NOTIFY, args);
@@ -129,4 +151,29 @@
         mOriginVerified = !result.isFulfilled() || result.getResult();
         return wasVerified && mOriginVerified;
     }
+
+    /* Get the localized string for toast message. */
+    private String getToastMessage(@ViolationType int type, String url, int httpStatusCode) {
+        if (type == ViolationType.ERROR_404 || type == ViolationType.ERROR_5XX) {
+            return ContextUtils.getApplicationContext().getString(
+                    R.string.twa_quality_enforcement_violation_error, httpStatusCode, url);
+        } else if (type == ViolationType.UNAVAILABLE_OFFLINE) {
+            return ContextUtils.getApplicationContext().getString(
+                    R.string.twa_quality_enforcement_violation_offline, url);
+        }
+        return "";
+    }
+
+    /*
+     * Get the string for sending message to TWA client app. We are not using the localized one as
+     * the toast because this is used in TWA's crash message.
+     */
+    private String toTwaCrashMessage(@ViolationType int type, String url, int httpStatusCode) {
+        if (type == ViolationType.ERROR_404 || type == ViolationType.ERROR_5XX) {
+            return httpStatusCode + " on " + url;
+        } else if (type == ViolationType.UNAVAILABLE_OFFLINE) {
+            return "Page unavailable offline: " + url;
+        }
+        return "";
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/HistoryNavigationCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/HistoryNavigationCoordinator.java
index 70b930e..59bc3f9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/HistoryNavigationCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/HistoryNavigationCoordinator.java
@@ -322,10 +322,8 @@
      * Starts preparing an edge swipe gesture.
      */
     public void startGesture() {
-        assert mNavigationHandler != null;
-
         // Simulates the initial onDown event to update the internal state.
-        mNavigationHandler.onDown();
+        if (mNavigationHandler != null) mNavigationHandler.onDown();
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/RequestGenerator.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/RequestGenerator.java
index 1b9ad62..4c0c40ee 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omaha/RequestGenerator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/RequestGenerator.java
@@ -221,14 +221,19 @@
      */
     @VisibleForTesting
     public int getNumSignedIn() {
-        return PostTask.runSynchronously(UiThreadTaskTraits.DEFAULT, () -> {
-            // The native needs to be loaded for the usage of IdentityManager.
-            ChromeBrowserInitializer.getInstance().handleSynchronousStartup();
-            // We only have a single account.
-            IdentityManager identityManager = IdentityServicesProvider.get().getIdentityManager(
-                    Profile.getLastUsedRegularProfile());
-            return identityManager.hasPrimaryAccount() ? 1 : 0;
-        });
+        try {
+            return PostTask.runSynchronously(UiThreadTaskTraits.DEFAULT, () -> {
+                // The native needs to be loaded for the usage of IdentityManager.
+                ChromeBrowserInitializer.getInstance().handleSynchronousStartup();
+                // We only have a single account.
+                IdentityManager identityManager = IdentityServicesProvider.get().getIdentityManager(
+                        Profile.getLastUsedRegularProfile());
+                return identityManager.hasPrimaryAccount() ? 1 : 0;
+            });
+        } catch (Exception e) {
+            Log.e(TAG, "Cannot get number of signed in accounts:", e);
+        }
+        return 0;
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestFactory.java
index 26f3e925..c7e321a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestFactory.java
@@ -186,6 +186,8 @@
             delegate = new PaymentRequestDelegateImpl(mRenderFrameHost);
         }
 
-        return new ComponentPaymentRequestImpl(new PaymentRequestImpl(mRenderFrameHost, delegate));
+        return new ComponentPaymentRequestImpl((componentPaymentRequest)
+                                                       -> new PaymentRequestImpl(mRenderFrameHost,
+                                                               componentPaymentRequest, delegate));
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
index 33776b9..ba2391b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
@@ -53,9 +53,9 @@
 import org.chromium.components.embedder_support.util.UrlConstants;
 import org.chromium.components.page_info.CertificateChainHelper;
 import org.chromium.components.payments.AbortReason;
+import org.chromium.components.payments.BrowserPaymentRequest;
 import org.chromium.components.payments.CanMakePaymentQuery;
 import org.chromium.components.payments.ComponentPaymentRequestImpl;
-import org.chromium.components.payments.ComponentPaymentRequestImpl.ComponentPaymentRequestDelegate;
 import org.chromium.components.payments.CurrencyFormatter;
 import org.chromium.components.payments.ErrorMessageUtil;
 import org.chromium.components.payments.ErrorStrings;
@@ -121,10 +121,9 @@
  * living in {@link ComponentPaymentRequestImpl}.
  */
 public class PaymentRequestImpl
-        implements ComponentPaymentRequestDelegate, PaymentRequestUI.Client,
-                   PaymentAppFactoryDelegate, PaymentAppFactoryParams,
-                   PaymentRequestUpdateEventListener, PaymentApp.AbortCallback,
-                   PaymentApp.InstrumentDetailsCallback,
+        implements BrowserPaymentRequest, PaymentRequestUI.Client, PaymentAppFactoryDelegate,
+                   PaymentAppFactoryParams, PaymentRequestUpdateEventListener,
+                   PaymentApp.AbortCallback, PaymentApp.InstrumentDetailsCallback,
                    PaymentResponseHelper.PaymentResponseRequesterDelegate,
                    NormalizedAddressRequestDelegate, PaymentDetailsConverter.MethodChecker,
                    PaymentUIsManager.Delegate {
@@ -231,24 +230,16 @@
     }
 
     private static final String TAG = "PaymentRequest";
-
-    private ComponentPaymentRequestImpl mComponentPaymentRequestImpl;
-
-    private PaymentOptions mPaymentOptions;
-    private boolean mRequestShipping;
-    private boolean mRequestPayerName;
-    private boolean mRequestPayerPhone;
-    private boolean mRequestPayerEmail;
-
     private static PaymentRequestServiceObserverForTest sObserverForTest;
     private static boolean sIsLocalCanMakePaymentQueryQuotaEnforcedForTest;
-
     /**
      * Hold the currently showing PaymentRequest. Used to prevent showing more than one
      * PaymentRequest UI per browser process.
      */
     private static PaymentRequestImpl sShowingPaymentRequest;
 
+    private final ComponentPaymentRequestImpl mComponentPaymentRequestImpl;
+
     /** Monitors changes in the TabModelSelector. */
     private final TabModelSelectorObserver mSelectorObserver = new EmptyTabModelSelectorObserver() {
         @Override
@@ -291,6 +282,14 @@
     private final JourneyLogger mJourneyLogger;
     private final boolean mIsOffTheRecord;
 
+    private final PaymentUIsManager mPaymentUIsManager;
+
+    private PaymentOptions mPaymentOptions;
+    private boolean mRequestShipping;
+    private boolean mRequestPayerName;
+    private boolean mRequestPayerPhone;
+    private boolean mRequestPayerEmail;
+
     private boolean mIsCanMakePaymentResponsePending;
     private boolean mIsHasEnrolledInstrumentResponsePending;
     private boolean mHasEnrolledInstrumentUsesPerMethodQuota;
@@ -328,7 +327,6 @@
     private int mShippingType;
     private boolean mIsFinishedQueryingPaymentApps;
     private List<PaymentApp> mPendingApps = new ArrayList<>();
-    private final PaymentUIsManager mPaymentUIsManager;
     private MinimalUICoordinator mMinimalUi;
     private PaymentApp mInvokedPaymentApp;
     private boolean mHideServerAutofillCards;
@@ -413,9 +411,13 @@
      * Builds the PaymentRequest service implementation.
      *
      * @param renderFrameHost The host of the frame that has invoked the PaymentRequest API.
+     * @param componentPaymentRequestImpl The component side of the PaymentRequest implementation.
      */
-    public PaymentRequestImpl(RenderFrameHost renderFrameHost, Delegate delegate) {
+    public PaymentRequestImpl(RenderFrameHost renderFrameHost,
+            ComponentPaymentRequestImpl componentPaymentRequestImpl, Delegate delegate) {
         assert renderFrameHost != null;
+        assert componentPaymentRequestImpl != null;
+        assert delegate != null;
 
         mRenderFrameHost = renderFrameHost;
         mDelegate = delegate;
@@ -440,18 +442,10 @@
         if (sObserverForTest != null) sObserverForTest.onPaymentRequestCreated(this);
         mPaymentUIsManager = new PaymentUIsManager(/*delegate=*/this,
                 /*params=*/this, mWebContents, mIsOffTheRecord, mJourneyLogger);
-    }
-
-    // Implement ComponentPaymentRequestDelegate:
-    @Override
-    public void setComponentPaymentRequestImpl(
-            ComponentPaymentRequestImpl componentPaymentRequestImpl) {
-        assert mComponentPaymentRequestImpl == null;
-        assert componentPaymentRequestImpl != null;
         mComponentPaymentRequestImpl = componentPaymentRequestImpl;
     }
 
-    // Implement ComponentPaymentRequestDelegate:
+    // Implement BrowserPaymentRequest:
     /**
      * Called by the merchant website to initialize the payment request data.
      */
@@ -712,7 +706,7 @@
         return true;
     }
 
-    // Implement ComponentPaymentRequestDelegate:
+    // Implement BrowserPaymentRequest:
     /**
      * Called by the merchant website to show the payment request to the user.
      */
@@ -1089,7 +1083,7 @@
                 && mInvokedPaymentApp.isValidForPaymentMethodData(methodName, null);
     }
 
-    // Implement ComponentPaymentRequestDelegate:
+    // Implement BrowserPaymentRequest:
     /**
      * Called by merchant to update the shipping options and line items after the user has selected
      * their shipping address or shipping option.
@@ -1193,7 +1187,7 @@
         }
     }
 
-    // Implement ComponentPaymentRequestDelegate:
+    // Implement BrowserPaymentRequest:
     /**
      * Called when the merchant received a new shipping address, shipping option, or payment method
      * info, but did not update the payment details in response.
@@ -1545,7 +1539,7 @@
         disconnectFromClientWithDebugMessage(ErrorStrings.USER_CANCELLED);
     }
 
-    // Implement ComponentPaymentRequestDelegate:
+    // Implement BrowserPaymentRequest:
     // This method is not supposed to be used outside this class and
     // ComponentPaymentRequestImpl.
     @Override
@@ -1564,7 +1558,7 @@
         }
     }
 
-    // Implement ComponentPaymentRequestDelegate:
+    // Implement BrowserPaymentRequest:
     /**
      * Called by the merchant website to abort the payment.
      */
@@ -1597,7 +1591,7 @@
         }
     }
 
-    // Implement ComponentPaymentRequestDelegate:
+    // Implement BrowserPaymentRequest:
     /**
      * Called when the merchant website has processed the payment.
      */
@@ -1639,7 +1633,7 @@
         closeUIAndDestroyNativeObjects();
     }
 
-    // Implement ComponentPaymentRequestDelegate:
+    // Implement BrowserPaymentRequest:
     @Override
     public void retry(PaymentValidationErrors errors) {
         if (getClient() == null) return;
@@ -1731,7 +1725,7 @@
         settingsLauncher.launchSettingsActivity(context);
     }
 
-    // Implement ComponentPaymentRequestDelegate:
+    // Implement BrowserPaymentRequest:
     /** Called by the merchant website to check if the user has complete payment apps. */
     @Override
     public void canMakePayment() {
@@ -1768,7 +1762,7 @@
         }
     }
 
-    // Implement ComponentPaymentRequestDelegate:
+    // Implement BrowserPaymentRequest:
     /** Called by the merchant website to check if the user has complete payment instruments. */
     @Override
     public void hasEnrolledInstrument(boolean perMethodQuota) {
@@ -1830,7 +1824,7 @@
                 || sIsLocalCanMakePaymentQueryQuotaEnforcedForTest;
     }
 
-    // Implement ComponentPaymentRequestDelegate:
+    // Implement BrowserPaymentRequest:
     /**
      * Called when the renderer closes the Mojo connection.
      */
@@ -1846,7 +1840,7 @@
         }
     }
 
-    // Implement ComponentPaymentRequestDelegate:
+    // Implement BrowserPaymentRequest:
     /**
      * Called when the Mojo connection encounters an error.
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/account_picker/AccountPickerBottomSheetView.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/account_picker/AccountPickerBottomSheetView.java
index f124108..e22630d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/account_picker/AccountPickerBottomSheetView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/account_picker/AccountPickerBottomSheetView.java
@@ -8,6 +8,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.ImageView;
+import android.widget.TextView;
 
 import androidx.annotation.Nullable;
 import androidx.recyclerview.widget.LinearLayoutManager;
@@ -28,6 +29,7 @@
 class AccountPickerBottomSheetView implements BottomSheetContent {
     private final Context mContext;
     private final View mContentView;
+    private final TextView mAccountPickerTitle;
     private final RecyclerView mAccountListView;
     private final View mSelectedAccountView;
     private final ButtonCompat mContinueAsButton;
@@ -36,6 +38,7 @@
         mContext = context;
         mContentView = LayoutInflater.from(mContext).inflate(
                 R.layout.account_picker_bottom_sheet_view, null);
+        mAccountPickerTitle = mContentView.findViewById(R.id.account_picker_bottom_sheet_title);
         mAccountListView = mContentView.findViewById(R.id.account_picker_account_list);
         mAccountListView.setLayoutManager(new LinearLayoutManager(
                 mAccountListView.getContext(), LinearLayoutManager.VERTICAL, false));
@@ -113,9 +116,7 @@
      * Sets up the sign-in in progress view.
      */
     void setUpSignInInProgressView() {
-        // TODO(https://crbug.com/1102784):
-        //  - Setup the sign-in string |Signing in...|
-        //  - Add signing in progress spinner
+        mAccountPickerTitle.setText(R.string.signin_account_picker_bottom_sheet_signin_title);
         mContentView.findViewById(R.id.account_picker_bottom_sheet_subtitle)
                 .setVisibility(View.GONE);
         mSelectedAccountView.setVisibility(View.GONE);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/QualityEnforcerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/QualityEnforcerTest.java
index 6fad9aa..476931b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/QualityEnforcerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/QualityEnforcerTest.java
@@ -4,6 +4,8 @@
 
 package org.chromium.chrome.browser.browserservices;
 
+import static org.junit.Assert.assertEquals;
+
 import static org.chromium.chrome.browser.browserservices.TrustedWebActivityTestUtil.createSession;
 import static org.chromium.chrome.browser.browserservices.TrustedWebActivityTestUtil.spoofVerification;
 
@@ -29,11 +31,14 @@
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.DisabledTest;
 import org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule;
 import org.chromium.chrome.browser.customtabs.CustomTabsTestUtils;
 import org.chromium.chrome.browser.document.ChromeLauncherActivity;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
 import org.chromium.net.test.EmbeddedTestServerRule;
 
 import java.util.concurrent.TimeoutException;
@@ -43,6 +48,7 @@
  */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
+@DisableFeatures(ChromeFeatureList.TRUSTED_WEB_ACTIVITY_QUALITY_ENFORCEMENT)
 public class QualityEnforcerTest {
     private static final String TEST_PAGE = "/chrome/test/data/android/google.html";
     // A not exist test page to triger 404.
@@ -62,6 +68,7 @@
     private String mTestPage;
     private String mTestPage404;
 
+    private String mErrorMessage;
     CallbackHelper mCallbackHelper = new CallbackHelper();
 
     CustomTabsCallback mCallback = new CustomTabsCallback() {
@@ -69,6 +76,7 @@
         public Bundle extraCallbackWithResult(String callbackName, Bundle args) {
             if (callbackName.equals(QualityEnforcer.NOTIFY)) {
                 mCallbackHelper.notifyCalled();
+                mErrorMessage = args.getString(QualityEnforcer.KEY_CRASH_REASON);
             }
             return Bundle.EMPTY;
         }
@@ -89,6 +97,7 @@
     public void notifiedWhenLaunch404() throws TimeoutException {
         launch(mTestPage404);
         mCallbackHelper.waitForFirst();
+        assertEquals(mErrorMessage, "404 on " + mTestPage404);
     }
 
     @Test
@@ -97,6 +106,17 @@
         launch(mTestPage);
         mCustomTabActivityTestRule.loadUrl(mTestPage404);
         mCallbackHelper.waitForFirst();
+        assertEquals(mErrorMessage, "404 on " + mTestPage404);
+    }
+
+    @Test
+    @MediumTest
+    @DisabledTest(message = "This test only works when device is running offline.")
+    // TODO(eirage): Figure out how to make it work on local device without changing network.
+    public void notifiedOffline() throws TimeoutException {
+        launch("https://example.com/");
+        mCallbackHelper.waitForFirst();
+        assertEquals(mErrorMessage, "Page unavailable offline: https://example.com/");
     }
 
     public void launch(String testPage) throws TimeoutException {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/digitalgoods/DigitalGoodsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/digitalgoods/DigitalGoodsTest.java
index 4ea57e4..db7a400f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/digitalgoods/DigitalGoodsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/digitalgoods/DigitalGoodsTest.java
@@ -28,7 +28,6 @@
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.DisabledTest;
 import org.chromium.chrome.browser.ChromeApplication;
 import org.chromium.chrome.browser.browserservices.TrustedWebActivityClient;
 import org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule;
@@ -144,7 +143,6 @@
      */
     @Test
     @MediumTest
-    @DisabledTest(message = "https://crbug.com/1111906")
     public void jsToTwaConnected() throws TimeoutException {
         DigitalGoodsFactory.setDigitalGoodsForTesting(createFixedDigitalGoods());
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountPickerBottomSheetTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountPickerBottomSheetTest.java
index 02c4db65..fcc90c79a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountPickerBottomSheetTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountPickerBottomSheetTest.java
@@ -146,6 +146,7 @@
         TestThreadUtils.runOnUiThreadBlocking(
                 () -> { ChromeNightModeTestUtils.setUpNightModeForChromeActivity(true); });
         mRenderTestRule.setNightModeEnabled(true);
+        mActivityTestRule.startMainActivityOnBlankPage();
         buildAndShowCollapsedBottomSheet();
         mRenderTestRule.render(
                 mCoordinator.getBottomSheetViewForTesting(), "collapsed_sheet_with_account_dark");
@@ -296,6 +297,8 @@
             return !bottomSheetView.findViewById(R.id.account_picker_continue_as_button).isShown();
         });
         verify(mAccountPickerDelegateMock).signIn(PROFILE_DATA1.getAccountName());
+        onView(withText(R.string.signin_account_picker_bottom_sheet_signin_title))
+                .check(matches(isDisplayed()));
         onView(withId(R.id.account_picker_bottom_sheet_subtitle))
                 .check(matches(not(isDisplayed())));
         onView(withId(R.id.account_picker_account_list)).check(matches(not(isDisplayed())));
@@ -308,9 +311,14 @@
     public void testSignInAnotherAccount() {
         buildAndShowExpandedBottomSheet();
         onView(withText(PROFILE_DATA2.getAccountName())).perform(click());
-        String continueAsText = mActivityTestRule.getActivity().getString(
-                R.string.signin_promo_continue_as, PROFILE_DATA2.getAccountName());
-        onView(withText(continueAsText)).perform(click());
+        View bottomSheetView = mCoordinator.getBottomSheetViewForTesting();
+        CriteriaHelper.pollUiThread(
+                bottomSheetView.findViewById(R.id.account_picker_continue_as_button)::isShown);
+        ThreadUtils.runOnUiThread(
+                bottomSheetView.findViewById(R.id.account_picker_continue_as_button)::performClick);
+        CriteriaHelper.pollUiThread(() -> {
+            return !bottomSheetView.findViewById(R.id.account_picker_continue_as_button).isShown();
+        });
         verify(mAccountPickerDelegateMock).signIn(PROFILE_DATA2.getAccountName());
     }
 
@@ -393,7 +401,8 @@
             mCoordinator = new AccountPickerBottomSheetCoordinator(mActivityTestRule.getActivity(),
                     getBottomSheetController(), mAccountPickerDelegateMock);
         });
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        CriteriaHelper.pollUiThread(mCoordinator.getBottomSheetViewForTesting().findViewById(
+                R.id.account_picker_continue_as_button)::isShown);
     }
 
     private void buildAndShowExpandedBottomSheet() {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkIntegrationTest.java
index 7d53ad6..b3b7532 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkIntegrationTest.java
@@ -22,6 +22,7 @@
 import org.chromium.base.CommandLine;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
+import org.chromium.base.test.util.FlakyTest;
 import org.chromium.chrome.browser.flags.ActivityType;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.tab.Tab;
@@ -85,6 +86,7 @@
     @Test
     @LargeTest
     @Feature({"Webapps"})
+    @FlakyTest(message = "https://crbug.com/1112352")
     public void testShare() throws TimeoutException {
         final String sharedSubject = "Fun tea parties";
         final String sharedText = "Boston";
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/QualityEnforcerUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/QualityEnforcerUnitTest.java
index dc28dd6..12aa5ad7 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/QualityEnforcerUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/QualityEnforcerUnitTest.java
@@ -44,6 +44,7 @@
 import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
 import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 import org.chromium.content_public.browser.NavigationHandle;
+import org.chromium.net.NetError;
 
 /**
  * Tests for {@link QualityEnforcer}.
@@ -96,34 +97,34 @@
 
     @Test
     public void trigger_navigateTo404() {
-        navigateToUrl(TRUSTED_ORIGIN_PAGE, HTTP_ERROR_NOT_FOUND);
+        navigateToUrlNotFound(TRUSTED_ORIGIN_PAGE);
         verifyTriggered404();
     }
 
     @Test
     public void notTrigger_navigationSuccess() {
-        navigateToUrl(TRUSTED_ORIGIN_PAGE, HTTP_STATUS_SUCCESS);
+        navigateToUrlNoError(TRUSTED_ORIGIN_PAGE);
         verifyNotTriggered();
     }
 
     @Test
     public void notTrigger_navigateTo404NotVerifiedSite() {
-        navigateToUrl(UNTRUSTED_PAGE, HTTP_ERROR_NOT_FOUND);
+        navigateToUrlNotFound(UNTRUSTED_PAGE);
         verifyNotTriggered();
     }
 
     @Test
     public void notTrigger_navigateFromNotVerifiedToVerified404() {
-        navigateToUrl(UNTRUSTED_PAGE, HTTP_STATUS_SUCCESS);
-        navigateToUrl(TRUSTED_ORIGIN_PAGE, HTTP_ERROR_NOT_FOUND);
+        navigateToUrlNoError(UNTRUSTED_PAGE);
+        navigateToUrlNotFound(TRUSTED_ORIGIN_PAGE);
         verifyNotTriggered();
     }
 
     @Test
     public void trigger_notVerifiedToVerifiedThen404() {
-        navigateToUrl(UNTRUSTED_PAGE, HTTP_STATUS_SUCCESS);
-        navigateToUrl(TRUSTED_ORIGIN_PAGE, HTTP_STATUS_SUCCESS);
-        navigateToUrl(TRUSTED_ORIGIN_PAGE, HTTP_ERROR_NOT_FOUND);
+        navigateToUrlNoError(UNTRUSTED_PAGE);
+        navigateToUrlNoError(TRUSTED_ORIGIN_PAGE);
+        navigateToUrlNotFound(TRUSTED_ORIGIN_PAGE);
         verifyTriggered404();
     }
 
@@ -136,7 +137,7 @@
                      any(), eq(QualityEnforcer.CRASH), any()))
                 .thenReturn(result);
 
-        navigateToUrl(TRUSTED_ORIGIN_PAGE, HTTP_ERROR_NOT_FOUND);
+        navigateToUrlNotFound(TRUSTED_ORIGIN_PAGE);
         verify(mActivity).finish();
     }
 
@@ -149,10 +150,16 @@
                      any(), eq(QualityEnforcer.CRASH), any()))
                 .thenReturn(result);
 
-        navigateToUrl(TRUSTED_ORIGIN_PAGE, HTTP_ERROR_NOT_FOUND);
+        navigateToUrlNotFound(TRUSTED_ORIGIN_PAGE);
         verify(mActivity, never()).finish();
     }
 
+    @Test
+    public void trigger_offline() {
+        navigateToUrlInternet(TRUSTED_ORIGIN_PAGE);
+        verifyTriggeredOffline();
+    }
+
     private void verifyTriggered404() {
         Assert.assertEquals(ContextUtils.getApplicationContext().getString(
                                     R.string.twa_quality_enforcement_violation_error,
@@ -162,12 +169,33 @@
                 .sendExtraCallbackWithResult(any(), eq(QualityEnforcer.NOTIFY), any());
     }
 
+    private void verifyTriggeredOffline() {
+        Assert.assertEquals(
+                ContextUtils.getApplicationContext().getString(
+                        R.string.twa_quality_enforcement_violation_offline, TRUSTED_ORIGIN_PAGE),
+                ShadowToast.getTextOfLatestToast());
+        verify(mCustomTabsConnection)
+                .sendExtraCallbackWithResult(any(), eq(QualityEnforcer.NOTIFY), any());
+    }
+
     private void verifyNotTriggered() {
         verify(mCustomTabsConnection, never())
                 .sendExtraCallbackWithResult(any(), eq(QualityEnforcer.NOTIFY), any());
     }
 
-    private void navigateToUrl(String url, int httpStatusCode) {
+    private void navigateToUrlNoError(String url) {
+        navigateToUrl(url, HTTP_STATUS_SUCCESS, NetError.OK);
+    }
+
+    private void navigateToUrlNotFound(String url) {
+        navigateToUrl(url, HTTP_ERROR_NOT_FOUND, NetError.OK);
+    }
+
+    private void navigateToUrlInternet(String url) {
+        navigateToUrl(url, HTTP_STATUS_SUCCESS, NetError.ERR_INTERNET_DISCONNECTED);
+    }
+
+    private void navigateToUrl(String url, int httpStatusCode, @NetError int errorCode) {
         when(mTab.getOriginalUrl()).thenReturn(url);
 
         NavigationHandle navigation =
@@ -175,7 +203,7 @@
                         false /* isSameDocument */, false /* isRendererInitiated */);
         navigation.didFinish(url, false /* isErrorPage */, true /* hasCommitted */,
                 false /* isFragmentNavigation */, false /* isDownload */,
-                false /* isValidSearchFormUrl */, 0 /* pageTransition */, 0 /* errorCode*/,
+                false /* isValidSearchFormUrl */, 0 /* pageTransition */, errorCode,
                 httpStatusCode);
         for (CustomTabTabObserver tabObserver : mTabObserverCaptor.getAllValues()) {
             tabObserver.onDidFinishNavigation(mTab, navigation);
diff --git a/chrome/android/modules/chrome_bundle_tmpl.gni b/chrome/android/modules/chrome_bundle_tmpl.gni
index 19673cb24..d9e4d08a 100644
--- a/chrome/android/modules/chrome_bundle_tmpl.gni
+++ b/chrome/android/modules/chrome_bundle_tmpl.gni
@@ -88,7 +88,6 @@
                              "lint_min_sdk_version",
                              "lint_suppressions_dep",
                              "lint_suppressions_file",
-                             "manifest_package",
                              "min_sdk_version",
                              "proguard_android_sdk_dep",
                              "sign_bundle",
@@ -103,6 +102,12 @@
     system_image_locale_allowlist = android_apk_locales
     is_multi_abi = _is_multi_abi
 
+    # Use a consistent baseline so that it is easy to regenerate by deleting the
+    # file and re-building the "android_lint" target.
+    if (defined(invoker.enable_lint) && invoker.enable_lint) {
+      lint_baseline_file = "//chrome/android/expectations/lint-baseline.xml"
+    }
+
     # List of DFMs that are installed by default by wrapper scripts, to make
     # testing easier. This removes the need to manually specify, e.g.,
     # "-m dev_ui" on every install or run.
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index 44028c2..a08df89 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-86.0.4208.0_rc-r1-merged.afdo.bz2
+chromeos-chrome-amd64-86.0.4228.0_rc-r1-merged.afdo.bz2
diff --git a/chrome/app/chrome_main_delegate.cc b/chrome/app/chrome_main_delegate.cc
index e923be4..520570c9 100644
--- a/chrome/app/chrome_main_delegate.cc
+++ b/chrome/app/chrome_main_delegate.cc
@@ -128,11 +128,12 @@
 #endif
 
 #if defined(OS_ANDROID)
-#include "base/android/library_loader/library_loader_hooks.h"
 #include "base/android/java_exception_reporter.h"
+#include "base/android/library_loader/library_loader_hooks.h"
 #include "chrome/browser/android/crash/pure_java_exception_handler.h"
 #include "chrome/browser/android/metrics/uma_session_stats.h"
 #include "chrome/browser/flags/android/chrome_feature_list.h"
+#include "chrome/common/android/cpu_affinity_experiments.h"
 #include "chrome/common/chrome_descriptors.h"
 #include "net/android/network_change_notifier_factory_android.h"
 #else  // defined(OS_ANDROID)
@@ -579,6 +580,13 @@
           switches::kProcessType);
   bool is_browser_process = process_type.empty();
 
+#if defined(OS_ANDROID)
+  // For child processes, this requires whitelisting of the sched_setaffinity()
+  // syscall in the sandbox (baseline_policy_android.cc). When this call is
+  // removed, the sandbox whitelist should be updated too.
+  chrome::InitializeCpuAffinityExperiments();
+#endif
+
 #if BUILDFLAG(ENABLE_GWP_ASAN_MALLOC)
   {
     version_info::Channel channel = chrome::GetChannel();
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index 7a632dc..1263d6aa 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -516,6 +516,15 @@
   <message name="IDS_OS_SETTINGS_AMBIENT_MODE_ALBUMS_SUBPAGE_PHOTOS_NUM_ONE" desc="1 photo in an album in ambient mode.">
     1 photo
   </message>
+  <message name="IDS_OS_SETTINGS_AMBIENT_MODE_WEATHER_TITLE" desc="Label for the radio button group of weather.">
+    Weather
+  </message>
+  <message name="IDS_OS_SETTINGS_AMBIENT_MODE_TEMPERATURE_UNIT_FAHRENHEIT" desc="Label for the radio button to choose fahrenheit as the preferred temperature unit.">
+    Fahrenheit
+  </message>
+  <message name="IDS_OS_SETTINGS_AMBIENT_MODE_TEMPERATURE_UNIT_CELSIUS" desc="Label for the radio button to choose celsius as the preferred temperature unit.">
+    Celsius
+  </message>
 
   <!-- Search and Assistant section. -->
   <message name="IDS_OS_SETTINGS_SEARCH_ENGINE_LABEL" desc="Label in OS settings describing search engine behavior.">
diff --git a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_AMBIENT_MODE_TEMPERATURE_UNIT_CELSIUS.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_AMBIENT_MODE_TEMPERATURE_UNIT_CELSIUS.png.sha1
new file mode 100644
index 0000000..97a98ff
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_AMBIENT_MODE_TEMPERATURE_UNIT_CELSIUS.png.sha1
@@ -0,0 +1 @@
+ed42dfdad76f060f1e03cba3b5ff901838c6da1c
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_AMBIENT_MODE_TEMPERATURE_UNIT_FAHRENHEIT.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_AMBIENT_MODE_TEMPERATURE_UNIT_FAHRENHEIT.png.sha1
new file mode 100644
index 0000000..97a98ff
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_AMBIENT_MODE_TEMPERATURE_UNIT_FAHRENHEIT.png.sha1
@@ -0,0 +1 @@
+ed42dfdad76f060f1e03cba3b5ff901838c6da1c
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_AMBIENT_MODE_WEATHER_TITLE.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_AMBIENT_MODE_WEATHER_TITLE.png.sha1
new file mode 100644
index 0000000..97a98ff
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_AMBIENT_MODE_WEATHER_TITLE.png.sha1
@@ -0,0 +1 @@
+ed42dfdad76f060f1e03cba3b5ff901838c6da1c
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 0771499..803ec3b 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -347,10 +347,10 @@
     "content_settings/host_content_settings_map_factory.h",
     "content_settings/mixed_content_settings_tab_helper.cc",
     "content_settings/mixed_content_settings_tab_helper.h",
+    "content_settings/page_specific_content_settings_delegate.cc",
+    "content_settings/page_specific_content_settings_delegate.h",
     "content_settings/sound_content_setting_observer.cc",
     "content_settings/sound_content_setting_observer.h",
-    "content_settings/tab_specific_content_settings_delegate.cc",
-    "content_settings/tab_specific_content_settings_delegate.h",
     "crash_upload_list/crash_upload_list.cc",
     "crash_upload_list/crash_upload_list.h",
     "custom_handlers/protocol_handler_registry.cc",
diff --git a/chrome/browser/android/autofill_assistant/ui_controller_android.cc b/chrome/browser/android/autofill_assistant/ui_controller_android.cc
index a01ed3e..ed485f8 100644
--- a/chrome/browser/android/autofill_assistant/ui_controller_android.cc
+++ b/chrome/browser/android/autofill_assistant/ui_controller_android.cc
@@ -678,11 +678,13 @@
   JNIEnv* env = AttachCurrentThread();
 
   bool has_close_or_cancel = false;
-  auto chips = Java_AutofillAssistantUiController_createChipList(env);
+  auto jchips = Java_AutofillAssistantUiController_createChipList(env);
+  auto jsticky_chips = Java_AutofillAssistantUiController_createChipList(env);
   int user_action_count = static_cast<int>(user_actions.size());
   for (int i = 0; i < user_action_count; i++) {
     const auto& action = user_actions[i];
     const Chip& chip = action.chip();
+    base::android::ScopedJavaLocalRef<jobject> jchip;
     switch (chip.type) {
       default:  // Ignore actions with other chip types or with no chips.
         break;
@@ -692,16 +694,17 @@
         // can hide all the chips except for the cancel chip when the keyboard
         // is showing.
         // TODO(b/149543425): Find a better way to do this.
-        Java_AutofillAssistantUiController_addHighlightedActionButton(
-            env, java_object_, chips, chip.icon,
-            base::android::ConvertUTF8ToJavaString(env, chip.text), i,
-            !action.enabled(), chip.sticky,
-            base::android::ConvertUTF8ToJavaString(env, ""));
+        jchip =
+            Java_AutofillAssistantUiController_createHighlightedActionButton(
+                env, java_object_, chip.icon,
+                base::android::ConvertUTF8ToJavaString(env, chip.text), i,
+                !action.enabled(), chip.sticky,
+                base::android::ConvertUTF8ToJavaString(env, ""));
         break;
 
       case NORMAL_ACTION:
-        Java_AutofillAssistantUiController_addActionButton(
-            env, java_object_, chips, chip.icon,
+        jchip = Java_AutofillAssistantUiController_createActionButton(
+            env, java_object_, chip.icon,
             base::android::ConvertUTF8ToJavaString(env, chip.text), i,
             !action.enabled(), chip.sticky,
             base::android::ConvertUTF8ToJavaString(env, ""));
@@ -710,8 +713,8 @@
       case CANCEL_ACTION:
         // A Cancel button sneaks in an UNDO snackbar before executing the
         // action, while a close button behaves like a normal button.
-        Java_AutofillAssistantUiController_addCancelButton(
-            env, java_object_, chips, chip.icon,
+        jchip = Java_AutofillAssistantUiController_createCancelButton(
+            env, java_object_, chip.icon,
             base::android::ConvertUTF8ToJavaString(env, chip.text), i,
             !action.enabled(), chip.sticky,
             base::android::ConvertUTF8ToJavaString(env, kCancelChipIdentifier));
@@ -719,8 +722,8 @@
         break;
 
       case CLOSE_ACTION:
-        Java_AutofillAssistantUiController_addActionButton(
-            env, java_object_, chips, chip.icon,
+        jchip = Java_AutofillAssistantUiController_createActionButton(
+            env, java_object_, chip.icon,
             base::android::ConvertUTF8ToJavaString(env, chip.text), i,
             !action.enabled(), chip.sticky,
             base::android::ConvertUTF8ToJavaString(env, ""));
@@ -728,33 +731,50 @@
         break;
 
       case DONE_ACTION:
-        Java_AutofillAssistantUiController_addHighlightedActionButton(
-            env, java_object_, chips, chip.icon,
-            base::android::ConvertUTF8ToJavaString(env, chip.text), i,
-            !action.enabled(), chip.sticky,
-            base::android::ConvertUTF8ToJavaString(env, ""));
+        jchip =
+            Java_AutofillAssistantUiController_createHighlightedActionButton(
+                env, java_object_, chip.icon,
+                base::android::ConvertUTF8ToJavaString(env, chip.text), i,
+                !action.enabled(), chip.sticky,
+                base::android::ConvertUTF8ToJavaString(env, ""));
         has_close_or_cancel = true;
         break;
     }
+    if (jchip) {
+      Java_AutofillAssistantUiController_appendChipToList(env, jchips, jchip);
+      if (chip.sticky) {
+        Java_AutofillAssistantUiController_appendChipToList(env, jsticky_chips,
+                                                            jchip);
+      }
+    }
   }
 
   if (!has_close_or_cancel) {
+    base::android::ScopedJavaLocalRef<jobject> jcancel_chip;
     if (ui_delegate_->GetState() == AutofillAssistantState::STOPPED) {
-      Java_AutofillAssistantUiController_addCloseButton(
-          env, java_object_, chips, ICON_CLEAR,
+      jcancel_chip = Java_AutofillAssistantUiController_createCloseButton(
+          env, java_object_, ICON_CLEAR,
           base::android::ConvertUTF8ToJavaString(env, ""),
           /* disabled= */ false, /* sticky= */ true,
           base::android::ConvertUTF8ToJavaString(env, ""));
     } else if (ui_delegate_->GetState() != AutofillAssistantState::INACTIVE) {
-      Java_AutofillAssistantUiController_addCancelButton(
-          env, java_object_, chips, ICON_CLEAR,
+      jcancel_chip = Java_AutofillAssistantUiController_createCancelButton(
+          env, java_object_, ICON_CLEAR,
           base::android::ConvertUTF8ToJavaString(env, ""), -1,
           /* disabled= */ false, /* sticky= */ true,
           base::android::ConvertUTF8ToJavaString(env, kCancelChipIdentifier));
     }
+    if (jcancel_chip) {
+      Java_AutofillAssistantUiController_appendChipToList(env, jchips,
+                                                          jcancel_chip);
+      Java_AutofillAssistantUiController_appendChipToList(env, jsticky_chips,
+                                                          jcancel_chip);
+    }
   }
 
-  Java_AutofillAssistantUiController_setActions(env, java_object_, chips);
+  Java_AutofillAssistantUiController_setActions(env, java_object_, jchips);
+  Java_AssistantHeaderModel_setChips(AttachCurrentThread(), GetHeaderModel(),
+                                     jsticky_chips);
 }
 
 void UiControllerAndroid::OnUserActionsChanged(
diff --git a/chrome/browser/apps/app_service/app_icon_factory.cc b/chrome/browser/apps/app_service/app_icon_factory.cc
index 1c3c55bb..354c959c 100644
--- a/chrome/browser/apps/app_service/app_icon_factory.cc
+++ b/chrome/browser/apps/app_service/app_icon_factory.cc
@@ -391,6 +391,9 @@
   // The scale factor the icon is intended for. See gfx::ImageSkiaRep::scale
   // comments.
   float icon_scale_ = 0.0f;
+  // A scale factor to take as input for the IconType::kCompressed response. See
+  // gfx::ImageSkia::GetRepresentation() comments.
+  float icon_scale_for_compressed_response_ = 1.0f;
 
   bool is_placeholder_icon_;
   apps::IconEffects icon_effects_;
@@ -441,6 +444,16 @@
 
   fallback_favicon_url_ = launch_url;
   profile_ = profile;
+
+  // In all other callpaths MaybeApplyEffectsAndComplete() uses
+  // |icon_scale_for_compressed_response_| to apps::EncodeImageToPngBytes(). In
+  // most cases IconLoadingPipeline always uses the 1.0 intended icon scale
+  // factor as an intermediate representation to be compressed and returned.
+  // TODO(crbug.com/1112737): Investigate how to unify it and set
+  // |icon_scale_for_compressed_response_| value in IconLoadingPipeline()
+  // constructor.
+  icon_scale_for_compressed_response_ = icon_scale_;
+
   if (icon_manager.HasSmallestIcon(web_app_id, icon_size_in_px_)) {
     switch (icon_type_) {
       case apps::mojom::IconType::kCompressed:
@@ -509,11 +522,8 @@
         // LoadImageAtEveryScaleFactorAsync here, because the caller has asked
         // for compressed icons (i.e. PNG-formatted data), not uncompressed
         // (i.e. a gfx::ImageSkia).
-        constexpr bool quantize_to_supported_scale_factor = true;
-        int size_hint_in_px = apps_util::ConvertDipToPx(
-            size_hint_in_dip_, quantize_to_supported_scale_factor);
         LoadCompressedDataFromExtension(
-            extension, size_hint_in_px,
+            extension, icon_size_in_px_,
             base::BindOnce(&IconLoadingPipeline::CompleteWithCompressed,
                            base::WrapRefCounted(this)));
         return;
@@ -774,7 +784,8 @@
   processed_image.MakeThreadSafe();
   base::ThreadPool::PostTaskAndReplyWithResult(
       FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
-      base::BindOnce(&apps::EncodeImageToPngBytes, processed_image),
+      base::BindOnce(&apps::EncodeImageToPngBytes, processed_image,
+                     icon_scale_for_compressed_response_),
       base::BindOnce(&IconLoadingPipeline::CompleteWithCompressed,
                      base::WrapRefCounted(this)));
 }
@@ -807,10 +818,8 @@
 }
 
 void IconLoadingPipeline::MaybeLoadFallbackOrCompleteEmpty() {
-  const int icon_size_in_px = apps_util::ConvertDipToPx(
-      size_hint_in_dip_, /*quantize_to_supported_scale_factor=*/true);
   if (fallback_favicon_url_.is_valid() &&
-      icon_size_in_px == kFaviconFallbackImagePx) {
+      icon_size_in_px_ == kFaviconFallbackImagePx) {
     GURL favicon_url = fallback_favicon_url_;
     // Reset to avoid infinite loops.
     fallback_favicon_url_ = GURL();
@@ -899,14 +908,16 @@
       std::move(callback), icon_scale);
 }
 
-std::vector<uint8_t> EncodeImageToPngBytes(const gfx::ImageSkia image) {
+std::vector<uint8_t> EncodeImageToPngBytes(const gfx::ImageSkia image,
+                                           float rep_icon_scale) {
   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                 base::BlockingType::MAY_BLOCK);
 
   std::vector<uint8_t> image_data;
 
-  const gfx::ImageSkiaRep& image_skia_rep = image.GetRepresentation(1.0f);
-  if (image_skia_rep.scale() != 1.0f) {
+  const gfx::ImageSkiaRep& image_skia_rep =
+      image.GetRepresentation(rep_icon_scale);
+  if (image_skia_rep.scale() != rep_icon_scale) {
     return image_data;
   }
 
diff --git a/chrome/browser/apps/app_service/app_icon_factory.h b/chrome/browser/apps/app_service/app_icon_factory.h
index 1238d4ce..475d38a 100644
--- a/chrome/browser/apps/app_service/app_icon_factory.h
+++ b/chrome/browser/apps/app_service/app_icon_factory.h
@@ -60,10 +60,12 @@
     base::OnceCallback<void(gfx::ImageSkia)> callback,
     float icon_scale);
 
-// Encode the ImageSkia to the compressed PNG data with the image's 1.0f scale
-// factor representation. Return the encoded PNG data.
-// This function should not be called on the UI thread.
-std::vector<uint8_t> EncodeImageToPngBytes(const gfx::ImageSkia image);
+// Encodes a single SkBitmap representation from the given ImageSkia to the
+// compressed PNG data. |rep_icon_scale| argument denotes, which ImageSkiaRep to
+// take as input. See ImageSkia::GetRepresentation() comments. Returns the
+// encoded PNG data. This function should not be called on the UI thread.
+std::vector<uint8_t> EncodeImageToPngBytes(const gfx::ImageSkia image,
+                                           float rep_icon_scale);
 
 #if defined(OS_CHROMEOS)
 void ArcRawIconPngDataToImageSkia(
diff --git a/chrome/browser/apps/app_service/arc_apps.cc b/chrome/browser/apps/app_service/arc_apps.cc
index 55cc8d9..c4607a1de 100644
--- a/chrome/browser/apps/app_service/arc_apps.cc
+++ b/chrome/browser/apps/app_service/arc_apps.cc
@@ -130,7 +130,8 @@
     iv->uncompressed.MakeThreadSafe();
     base::ThreadPool::PostTaskAndReplyWithResult(
         FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
-        base::BindOnce(&apps::EncodeImageToPngBytes, iv->uncompressed),
+        base::BindOnce(&apps::EncodeImageToPngBytes, iv->uncompressed,
+                       /*rep_icon_scale=*/1.0f),
         base::BindOnce(&CompleteWithCompressed, std::move(callback)));
     return;
   }
diff --git a/chrome/browser/browsing_data/README.md b/chrome/browser/browsing_data/README.md
index b48040e..3493a2a 100644
--- a/chrome/browser/browsing_data/README.md
+++ b/chrome/browser/browsing_data/README.md
@@ -27,7 +27,7 @@
 some details (e.g. full details for cookies, but only the usage of
 other storage typess).
 
-* TabSpecificContentSettings is notified on storage access/blocked.
+* PageSpecificContentSettings is notified on storage access/blocked.
 * It calls into the "canned" helper instance for the storage type.
 * The "canned" instance records necessary "pending" info about the access.
 * On demand, the "pending" info is used to populate a CookiesTreeModel.
diff --git a/chrome/browser/browsing_data/access_context_audit_database.cc b/chrome/browser/browsing_data/access_context_audit_database.cc
index a15cd87f..e7997bc 100644
--- a/chrome/browser/browsing_data/access_context_audit_database.cc
+++ b/chrome/browser/browsing_data/access_context_audit_database.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/browsing_data/access_context_audit_database.h"
 
 #include "base/logging.h"
+#include "net/cookies/cookie_util.h"
 #include "sql/database.h"
 #include "sql/meta_table.h"
 #include "sql/recovery.h"
@@ -72,6 +73,25 @@
   return db->Execute(remove.c_str());
 }
 
+bool IsContentSettingSessionOnly(
+    const GURL& url,
+    const ContentSettingsForOneType& content_settings) {
+  // ContentSettingsForOneType are in order of decreasing specificity, such
+  // that the first matching entry defines the effective content setting.
+  for (const auto& setting : content_settings) {
+    // A match is performed against both primary and secondary patterns. This
+    // aligns with the behavior in CookieSettingsBase::ShouldDeleteCookieOnExit,
+    // which is used by the cookie store.
+    if (setting.primary_pattern.Matches(url) &&
+        setting.secondary_pattern.Matches(url)) {
+      return setting.GetContentSetting() ==
+             ContentSetting::CONTENT_SETTING_SESSION_ONLY;
+    }
+  }
+  NOTREACHED();
+  return false;
+}
+
 }  // namespace
 
 AccessContextAuditDatabase::AccessRecord::AccessRecord(
@@ -339,51 +359,70 @@
 }
 
 void AccessContextAuditDatabase::RemoveSessionOnlyRecords(
-    scoped_refptr<content_settings::CookieSettings> cookie_settings,
     const ContentSettingsForOneType& content_settings) {
+  // ContentSettingsForOneType is a list of settings in decreasing specificity
+  // for origins, ending with a setting that matches all and is the default.
+  DCHECK(content_settings.size());
+  if (content_settings.size() == 1) {
+    DCHECK_EQ(content_settings[0].primary_pattern,
+              ContentSettingsPattern::Wildcard());
+    DCHECK_EQ(content_settings[0].secondary_pattern,
+              ContentSettingsPattern::Wildcard());
+    if (content_settings[0].GetContentSetting() ==
+        ContentSetting::CONTENT_SETTING_SESSION_ONLY) {
+      RemoveAllRecords();
+    }
+    // As only the default content setting is set, there is no need to inspect
+    // any individual records.
+    return;
+  }
+
   sql::Transaction transaction(&db_);
   if (!transaction.Begin())
     return;
 
-  // Extract the set of all domains from the cookies table.
+  // Extract the set of all domains from the cookies table, determine the
+  // effective content setting, and store for removal if appropriate.
   std::string select = "SELECT DISTINCT domain FROM ";
   select.append(kCookieTableName);
   sql::Statement select_cookie_domains(
       db_.GetCachedStatement(SQL_FROM_HERE, select.c_str()));
 
-  std::vector<std::string> cookie_domains;
+  std::vector<std::string> cookie_domains_for_removal;
   while (select_cookie_domains.Step()) {
-    cookie_domains.emplace_back(select_cookie_domains.ColumnString(0));
+    auto domain = select_cookie_domains.ColumnString(0);
+    GURL url = net::cookie_util::CookieOriginToURL(domain,
+                                                   /* is_https */ false);
+    GURL secure_url = net::cookie_util::CookieOriginToURL(domain,
+                                                          /* is_https */ true);
+    if (IsContentSettingSessionOnly(url, content_settings) ||
+        IsContentSettingSessionOnly(secure_url, content_settings)) {
+      cookie_domains_for_removal.emplace_back(std::move(domain));
+    }
   }
 
-  // Extract the set of all origins from the storage API table.
+  // Repeat the above, but for the origin keyed storage API table.
   select = "SELECT DISTINCT origin FROM ";
   select.append(kStorageAPITableName);
   sql::Statement select_storage_origins(
       db_.GetCachedStatement(SQL_FROM_HERE, select.c_str()));
 
-  std::vector<url::Origin> storage_origins;
+  std::vector<std::string> storage_origins_for_removal;
   while (select_storage_origins.Step()) {
-    storage_origins.emplace_back(
-        url::Origin::Create(GURL(select_storage_origins.ColumnString(0))));
+    auto origin = select_storage_origins.ColumnString(0);
+    if (IsContentSettingSessionOnly(GURL(origin), content_settings))
+      storage_origins_for_removal.emplace_back(origin);
   }
 
-  // Remove records for all cookie domains and storage origins for which the
-  // provided settings indicate should be cleared on exit.
+  // Remove entries belonging to cookie domains and origins identified as having
+  // a SESSION_ONLY content setting.
   std::string remove = "DELETE FROM ";
   remove.append(kCookieTableName);
   remove.append(" WHERE domain = ?");
   sql::Statement remove_cookies(
       db_.GetCachedStatement(SQL_FROM_HERE, remove.c_str()));
 
-  for (const auto& domain : cookie_domains) {
-    if (!cookie_settings->ShouldDeleteCookieOnExit(content_settings, domain,
-                                                   true) &&
-        !cookie_settings->ShouldDeleteCookieOnExit(content_settings, domain,
-                                                   false)) {
-      continue;
-    }
-
+  for (const auto& domain : cookie_domains_for_removal) {
     remove_cookies.BindString(0, domain);
     if (!remove_cookies.Run())
       return;
@@ -396,13 +435,8 @@
   sql::Statement remove_storage_apis(
       db_.GetCachedStatement(SQL_FROM_HERE, remove.c_str()));
 
-  for (const auto& origin : storage_origins) {
-    // TODO(crbug.com/1099164): Rename IsCookieSessionOnly to better convey
-    //                          its actual functionality.
-    if (!cookie_settings->IsCookieSessionOnly(origin.GetURL()))
-      continue;
-
-    remove_storage_apis.BindString(0, origin.Serialize());
+  for (const auto& origin : storage_origins_for_removal) {
+    remove_storage_apis.BindString(0, origin);
     if (!remove_storage_apis.Run())
       return;
     remove_storage_apis.Reset(true);
diff --git a/chrome/browser/browsing_data/access_context_audit_database.h b/chrome/browser/browsing_data/access_context_audit_database.h
index b80b796..e4e631d 100644
--- a/chrome/browser/browsing_data/access_context_audit_database.h
+++ b/chrome/browser/browsing_data/access_context_audit_database.h
@@ -104,10 +104,10 @@
   void RemoveAllRecordsForTopFrameOrigins(
       const std::vector<url::Origin>& origins);
 
-  // Removes all records for cookie domains and API origins that match session
-  // only entries in |settings|
+  // Removes all records for which the result of inspecting |content_settings|
+  // for the storage origin or cookie domain is a content setting of
+  // CLEAR_ON_EXIT.
   void RemoveSessionOnlyRecords(
-      scoped_refptr<content_settings::CookieSettings> cookie_settings,
       const ContentSettingsForOneType& content_settings);
 
  protected:
diff --git a/chrome/browser/browsing_data/access_context_audit_database_unittest.cc b/chrome/browser/browsing_data/access_context_audit_database_unittest.cc
index 8eca3b50..98c06c94 100644
--- a/chrome/browser/browsing_data/access_context_audit_database_unittest.cc
+++ b/chrome/browser/browsing_data/access_context_audit_database_unittest.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/browsing_data/access_context_audit_database.h"
 
 #include "base/files/scoped_temp_dir.h"
+#include "components/content_settings/core/common/content_settings_utils.h"
 #include "sql/database.h"
 #include "sql/test/scoped_error_expecter.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -459,3 +460,63 @@
   EXPECT_EQ(num_test_cookie_entries, cookie_rows);
   EXPECT_EQ(num_test_storage_entries, storage_api_rows);
 }
+
+TEST_F(AccessContextAuditDatabaseTest, RemoveSessionOnlyRecords) {
+  // Check that records are cleared appropriately by RemoveSessionOnlyRecords
+  // when the provided content settings indicate they should.
+  auto test_records = GetTestRecords();
+  OpenDatabase();
+  database()->AddRecords(test_records);
+  ValidateDatabaseRecords(database(), test_records);
+
+  // Test that default content setting of SESSION_ONLY results in all records
+  // being removed.
+  ContentSettingsForOneType content_settings;
+  content_settings.emplace_back(
+      ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
+      base::Value::FromUniquePtrValue(content_settings::ContentSettingToValue(
+          CONTENT_SETTING_SESSION_ONLY)),
+      std::string(), /* incognito */ false);
+
+  database()->RemoveSessionOnlyRecords(content_settings);
+  ValidateDatabaseRecords(database(), {});
+
+  // Check that a more targeted content setting also removes the appropriate
+  // records.
+  content_settings.clear();
+  database()->AddRecords(test_records);
+  ValidateDatabaseRecords(database(), test_records);
+
+  content_settings.emplace_back(
+      ContentSettingsPattern::FromString(kManyContextsCookieDomain),
+      ContentSettingsPattern::Wildcard(),
+      base::Value::FromUniquePtrValue(content_settings::ContentSettingToValue(
+          CONTENT_SETTING_SESSION_ONLY)),
+      std::string(), /* incognito */ false);
+  content_settings.emplace_back(
+      ContentSettingsPattern::FromString(kManyContextsStorageAPIOrigin),
+      ContentSettingsPattern::Wildcard(),
+      base::Value::FromUniquePtrValue(content_settings::ContentSettingToValue(
+          CONTENT_SETTING_SESSION_ONLY)),
+      std::string(), /* incognito */ false);
+  content_settings.emplace_back(
+      ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
+      base::Value::FromUniquePtrValue(
+          content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW)),
+      std::string(), /* incognito */ false);
+  database()->RemoveSessionOnlyRecords(content_settings);
+
+  test_records.erase(
+      std::remove_if(
+          test_records.begin(), test_records.end(),
+          [=](const AccessContextAuditDatabase::AccessRecord& record) {
+            if (record.type ==
+                AccessContextAuditDatabase::StorageAPIType::kCookie) {
+              return record.domain == kManyContextsCookieDomain;
+            }
+            return record.origin ==
+                   url::Origin::Create(GURL(kManyContextsStorageAPIOrigin));
+          }),
+      test_records.end());
+  ValidateDatabaseRecords(database(), test_records);
+}
diff --git a/chrome/browser/browsing_data/access_context_audit_service.cc b/chrome/browser/browsing_data/access_context_audit_service.cc
index ced41ab..4a55922 100644
--- a/chrome/browser/browsing_data/access_context_audit_service.cc
+++ b/chrome/browser/browsing_data/access_context_audit_service.cc
@@ -192,6 +192,5 @@
   database_task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(&AccessContextAuditDatabase::RemoveSessionOnlyRecords,
-                     database_, CookieSettingsFactory::GetForProfile(profile_),
-                     std::move(settings)));
+                     database_, std::move(settings)));
 }
diff --git a/chrome/browser/browsing_data/access_context_audit_service_unittest.cc b/chrome/browser/browsing_data/access_context_audit_service_unittest.cc
index eae050481..d5a6799 100644
--- a/chrome/browser/browsing_data/access_context_audit_service_unittest.cc
+++ b/chrome/browser/browsing_data/access_context_audit_service_unittest.cc
@@ -448,12 +448,7 @@
   CheckContainsStorageAPIRecord(kOrigin1, kTestStorageType2, kOrigin1, records);
 }
 
-#if defined(THREAD_SANITIZER)
-#define MAYBE_SessionOnlyRecords DISABLED_SessionOnlyRecords
-#else
-#define MAYBE_SessionOnlyRecords SessionOnlyRecords
-#endif
-TEST_F(AccessContextAuditServiceTest, MAYBE_SessionOnlyRecords) {
+TEST_F(AccessContextAuditServiceTest, SessionOnlyRecords) {
   // Check that data for cookie domains and storage origins are cleared on
   // service shutdown when the associated content settings indicate they should.
   const GURL kTestPersistentURL("https://persistent.com");
@@ -516,4 +511,13 @@
                             kTopFrameOrigin, records);
   CheckContainsStorageAPIRecord(url::Origin::Create(GURL(kTestPersistentURL)),
                                 kTestStorageType, kTopFrameOrigin, records);
+
+  // Update the default content setting to SESSION_ONLY and ensure that all
+  // records are cleared.
+  HostContentSettingsMapFactory::GetForProfile(profile())
+      ->SetDefaultContentSetting(ContentSettingsType::COOKIES,
+                                 ContentSetting::CONTENT_SETTING_SESSION_ONLY);
+  service()->ClearSessionOnlyRecords();
+  records = GetAllAccessRecords();
+  ASSERT_EQ(0u, records.size());
 }
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 68b973e..91b9459 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -192,7 +192,7 @@
 #include "components/cdm/browser/cdm_message_filter_android.h"
 #include "components/certificate_matching/certificate_principal_pattern.h"
 #include "components/cloud_devices/common/cloud_devices_switches.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/browser/content_settings_utils.h"
 #include "components/content_settings/core/browser/cookie_settings.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
@@ -2515,7 +2515,7 @@
   // Check if this is an extension-related service worker, and, if so, if it's
   // allowed (this can return false if, e.g., the extension is disabled).
   // If it's not allowed, return immediately. We deliberately do *not* report
-  // to the TabSpecificContentSettings, since the service worker is blocked
+  // to the PageSpecificContentSettings, since the service worker is blocked
   // because of the extension, rather than because of the user's content
   // settings.
   if (!ChromeContentBrowserClientExtensionsPart::AllowServiceWorkerOnIO(
@@ -2557,7 +2557,7 @@
   // Check if this is an extension-related service worker, and, if so, if it's
   // allowed (this can return false if, e.g., the extension is disabled).
   // If it's not allowed, return immediately. We deliberately do *not* report
-  // to the TabSpecificContentSettings, since the service worker is blocked
+  // to the PageSpecificContentSettings, since the service worker is blocked
   // because of the extension, rather than because of the user's content
   // settings.
   if (!ChromeContentBrowserClientExtensionsPart::AllowServiceWorkerOnUI(
@@ -2603,7 +2603,7 @@
           ->IsCookieAccessAllowed(worker_url, site_for_cookies,
                                   top_frame_origin);
 
-  content_settings::TabSpecificContentSettings::SharedWorkerAccessed(
+  content_settings::PageSpecificContentSettings::SharedWorkerAccessed(
       render_process_id, render_frame_id, worker_url, name, constructor_origin,
       !allow);
   return allow;
@@ -2687,7 +2687,7 @@
     bool allow) {
   // Record access to file system for potential display in UI.
   for (const auto& it : render_frames) {
-    content_settings::TabSpecificContentSettings::FileSystemAccessed(
+    content_settings::PageSpecificContentSettings::FileSystemAccessed(
         it.child_id, it.frame_routing_id, url, !allow);
   }
   std::move(callback).Run(allow);
@@ -2704,7 +2704,7 @@
 
   // Record access to IndexedDB for potential display in UI.
   for (const auto& it : render_frames) {
-    content_settings::TabSpecificContentSettings::IndexedDBAccessed(
+    content_settings::PageSpecificContentSettings::IndexedDBAccessed(
         it.child_id, it.frame_routing_id, url, !allow);
   }
 
@@ -2721,7 +2721,7 @@
 
   // Record access to CacheStorage for potential display in UI.
   for (const auto& it : render_frames) {
-    content_settings::TabSpecificContentSettings::CacheStorageAccessed(
+    content_settings::PageSpecificContentSettings::CacheStorageAccessed(
         it.child_id, it.frame_routing_id, url, !allow);
   }
 
diff --git a/chrome/browser/chrome_service_worker_browsertest.cc b/chrome/browser/chrome_service_worker_browsertest.cc
index 2af369c..2399b571 100644
--- a/chrome/browser/chrome_service_worker_browsertest.cc
+++ b/chrome/browser/chrome_service_worker_browsertest.cc
@@ -27,7 +27,7 @@
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/test_chrome_web_ui_controller_factory.h"
 #include "chrome/test/base/ui_test_utils.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/content_settings/core/common/pref_names.h"
 #include "components/favicon/content/content_favicon_driver.h"
@@ -222,7 +222,7 @@
   content::RenderFrameHost* main_frame =
       browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
   EXPECT_TRUE(
-      content_settings::TabSpecificContentSettings::GetForFrame(main_frame)
+      content_settings::PageSpecificContentSettings::GetForFrame(main_frame)
           ->IsContentBlocked(ContentSettingsType::JAVASCRIPT));
 }
 
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 157dcac..ea61dc36 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -989,6 +989,8 @@
     "crostini/crosvm_process_list.h",
     "crostini/fake_crostini_installer_ui_delegate.cc",
     "crostini/fake_crostini_installer_ui_delegate.h",
+    "crostini/termina_installer.cc",
+    "crostini/termina_installer.h",
     "crostini/throttle/crostini_active_window_throttle_observer.cc",
     "crostini/throttle/crostini_active_window_throttle_observer.h",
     "crostini/throttle/crostini_throttle.cc",
@@ -1873,10 +1875,10 @@
     "platform_keys/extension_platform_keys_service.h",
     "platform_keys/extension_platform_keys_service_factory.cc",
     "platform_keys/extension_platform_keys_service_factory.h",
-    "platform_keys/key_permissions.cc",
-    "platform_keys/key_permissions.h",
-    "platform_keys/key_permissions_policy_handler.cc",
-    "platform_keys/key_permissions_policy_handler.h",
+    "platform_keys/key_permissions/key_permissions.cc",
+    "platform_keys/key_permissions/key_permissions.h",
+    "platform_keys/key_permissions/key_permissions_policy_handler.cc",
+    "platform_keys/key_permissions/key_permissions_policy_handler.h",
     "platform_keys/platform_keys_service.cc",
     "platform_keys/platform_keys_service.h",
     "platform_keys/platform_keys_service_factory.cc",
@@ -3085,6 +3087,7 @@
     "crostini/crostini_util_unittest.cc",
     "crostini/crosvm_metrics_unittest.cc",
     "crostini/crosvm_process_list_unittest.cc",
+    "crostini/termina_installer_unittest.cc",
     "crostini/throttle/crostini_active_window_throttle_observer_unittest.cc",
     "crostini/throttle/crostini_throttle_unittest.cc",
     "cryptauth/client_app_metadata_provider_service_unittest.cc",
diff --git a/chrome/browser/chromeos/arc/enterprise/cert_store/arc_cert_store_bridge.cc b/chrome/browser/chromeos/arc/enterprise/cert_store/arc_cert_store_bridge.cc
index 725f0b0..4ff9ffc 100644
--- a/chrome/browser/chromeos/arc/enterprise/cert_store/arc_cert_store_bridge.cc
+++ b/chrome/browser/chromeos/arc/enterprise/cert_store/arc_cert_store_bridge.cc
@@ -13,7 +13,7 @@
 #include "base/callback.h"
 #include "base/logging.h"
 #include "base/memory/singleton.h"
-#include "chrome/browser/chromeos/platform_keys/key_permissions.h"
+#include "chrome/browser/chromeos/platform_keys/key_permissions/key_permissions.h"
 #include "chrome/browser/chromeos/platform_keys/platform_keys_service.h"
 #include "chrome/browser/net/nss_context.h"
 #include "chrome/browser/policy/profile_policy_connector.h"
@@ -82,7 +82,7 @@
   std::string spki_der = chromeos::platform_keys::GetSubjectPublicKeyInfo(cert);
   std::string public_key_spki_der_b64;
   base::Base64Encode(spki_der, &public_key_spki_der_b64);
-  if (!chromeos::KeyPermissions::IsCorporateKeyForProfile(
+  if (!chromeos::platform_keys::KeyPermissions::IsCorporateKeyForProfile(
           public_key_spki_der_b64, prefs)) {
     DVLOG(1) << "Certificate is not allowed to be used by ARC.";
     return false;
@@ -303,9 +303,8 @@
 void ArcCertStoreBridge::UpdateFromKeyPermissionsPolicy() {
   DVLOG(1) << "ArcCertStoreBridge::UpdateFromKeyPermissionsPolicy";
 
-  std::vector<std::string> app_ids =
-      chromeos::KeyPermissions::GetCorporateKeyUsageAllowedAppIds(
-          policy_service_);
+  std::vector<std::string> app_ids = chromeos::platform_keys::KeyPermissions::
+      GetCorporateKeyUsageAllowedAppIds(policy_service_);
   std::vector<std::string> permissions;
   for (const auto& app_id : app_ids) {
     if (LooksLikeAndroidPackageName(app_id))
diff --git a/chrome/browser/chromeos/arc/enterprise/cert_store/arc_cert_store_bridge_browsertest.cc b/chrome/browser/chromeos/arc/enterprise/cert_store/arc_cert_store_bridge_browsertest.cc
index b54cef5..2f7742c7 100644
--- a/chrome/browser/chromeos/arc/enterprise/cert_store/arc_cert_store_bridge_browsertest.cc
+++ b/chrome/browser/chromeos/arc/enterprise/cert_store/arc_cert_store_bridge_browsertest.cc
@@ -15,7 +15,7 @@
 #include "chrome/browser/chromeos/arc/enterprise/cert_store/arc_cert_store_bridge.h"
 #include "chrome/browser/chromeos/arc/session/arc_service_launcher.h"
 #include "chrome/browser/chromeos/login/test/local_policy_test_server_mixin.h"
-#include "chrome/browser/chromeos/platform_keys/key_permissions.h"
+#include "chrome/browser/chromeos/platform_keys/key_permissions/key_permissions.h"
 #include "chrome/browser/chromeos/platform_keys/platform_keys_service.h"
 #include "chrome/browser/chromeos/policy/user_policy_test_helper.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
@@ -210,7 +210,7 @@
     extensions::StateStore* const state_store =
         extensions::ExtensionSystem::Get(browser()->profile())->state_store();
 
-    chromeos::KeyPermissions permissions(
+    chromeos::platform_keys::KeyPermissions permissions(
         policy_connector->IsManaged(), browser()->profile()->GetPrefs(),
         policy_connector->policy_service(), state_store);
 
@@ -262,13 +262,15 @@
   // client_cert2_ is not allowed.
   void GotPermissionsForExtension(
       const base::Closure& done_callback,
-      std::unique_ptr<chromeos::KeyPermissions::PermissionsForExtension>
+      std::unique_ptr<
+          chromeos::platform_keys::KeyPermissions::PermissionsForExtension>
           permissions_for_ext) {
     std::string client_cert1_spki(
         client_cert1_->derPublicKey.data,
         client_cert1_->derPublicKey.data + client_cert1_->derPublicKey.len);
     permissions_for_ext->RegisterKeyForCorporateUsage(
-        client_cert1_spki, {chromeos::KeyPermissions::KeyLocation::kUserSlot});
+        client_cert1_spki,
+        {chromeos::platform_keys::KeyPermissions::KeyLocation::kUserSlot});
     done_callback.Run();
   }
 
diff --git a/chrome/browser/chromeos/crostini/crostini_installer_unittest.cc b/chrome/browser/chromeos/crostini/crostini_installer_unittest.cc
index c5bef593..47d66350 100644
--- a/chrome/browser/chromeos/crostini/crostini_installer_unittest.cc
+++ b/chrome/browser/chromeos/crostini/crostini_installer_unittest.cc
@@ -16,11 +16,14 @@
 #include "chrome/browser/chromeos/crostini/crostini_installer_ui_delegate.h"
 #include "chrome/browser/chromeos/crostini/crostini_test_helper.h"
 #include "chrome/browser/chromeos/crostini/crostini_types.mojom.h"
+#include "chrome/browser/component_updater/fake_cros_component_manager.h"
+#include "chrome/test/base/browser_process_platform_part_test_api_chromeos.h"
 #include "chrome/test/base/scoped_testing_local_state.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chromeos/dbus/concierge/concierge_service.pb.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/dlcservice/dlcservice_client.h"
 #include "chromeos/dbus/fake_concierge_client.h"
 #include "chromeos/disks/disk_mount_manager.h"
 #include "chromeos/disks/mock_disk_mount_manager.h"
@@ -114,9 +117,21 @@
 
   CrostiniInstallerTest()
       : local_state_(std::make_unique<ScopedTestingLocalState>(
-            TestingBrowserProcess::GetGlobal())) {}
+            TestingBrowserProcess::GetGlobal())),
+        browser_part_(g_browser_process->platform_part()) {}
 
   void SetUp() override {
+    component_manager_ =
+        base::MakeRefCounted<component_updater::FakeCrOSComponentManager>();
+    component_manager_->set_supported_components({"cros-termina"});
+    component_manager_->ResetComponentState(
+        "cros-termina",
+        component_updater::FakeCrOSComponentManager::ComponentInfo(
+            component_updater::CrOSComponentManager::Error::NONE,
+            base::FilePath("/install/path"), base::FilePath("/mount/path")));
+    browser_part_.InitializeCrosComponentManager(component_manager_);
+
+    chromeos::DlcserviceClient::InitializeFake();
     waiting_fake_concierge_client_ = new WaitingFakeConciergeClient;
     chromeos::DBusThreadManager::GetSetterForTesting()->SetConciergeClient(
         base::WrapUnique(waiting_fake_concierge_client_));
@@ -147,6 +162,10 @@
 
     chromeos::disks::MockDiskMountManager::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
+    chromeos::DlcserviceClient::Shutdown();
+
+    browser_part_.ShutdownCrosComponentManager();
+    component_manager_.reset();
   }
 
   void Install() {
@@ -182,6 +201,8 @@
 
  private:
   std::unique_ptr<ScopedTestingLocalState> local_state_;
+  scoped_refptr<component_updater::FakeCrOSComponentManager> component_manager_;
+  BrowserProcessPlatformPartTestApi browser_part_;
 
   DISALLOW_COPY_AND_ASSIGN(CrostiniInstallerTest);
 };
diff --git a/chrome/browser/chromeos/crostini/crostini_manager.cc b/chrome/browser/chromeos/crostini/crostini_manager.cc
index dc3d23f..dfd2e1a 100644
--- a/chrome/browser/chromeos/crostini/crostini_manager.cc
+++ b/chrome/browser/chromeos/crostini/crostini_manager.cc
@@ -311,7 +311,7 @@
     }
 
     StartStage(mojom::InstallerState::kInstallImageLoader);
-    crostini_manager_->InstallTerminaComponent(
+    crostini_manager_->InstallTermina(
         base::BindOnce(&CrostiniRestarter::LoadComponentFinished,
                        weak_ptr_factory_.GetWeakPtr()));
   }
@@ -675,7 +675,6 @@
 
 CrostiniManager::RestartId
     CrostiniManager::CrostiniRestarter::next_restart_id_ = 0;
-bool CrostiniManager::is_cros_termina_registered_ = false;
 // Unit tests need this initialized to true. In Browser tests and real life,
 // it is updated via MaybeUpdateCrostini.
 bool CrostiniManager::is_dev_kvm_present_ = true;
@@ -977,29 +976,16 @@
 }
 
 // static
-bool CrostiniManager::IsCrosTerminaInstalled() {
-  return is_cros_termina_registered_;
-}
-
-// static
 bool CrostiniManager::IsDevKvmPresent() {
   return is_dev_kvm_present_;
 }
 
 void CrostiniManager::MaybeUpdateCrostini() {
-  scoped_refptr<component_updater::CrOSComponentManager> component_manager =
-      g_browser_process->platform_part()->cros_component_manager();
-  if (!component_manager) {
-    // |component_manager| may be nullptr in unit tests.
-    return;
-  }
   // This is a new user session, perhaps using an old CrostiniManager.
   container_upgrade_prompt_shown_.clear();
   base::ThreadPool::PostTaskAndReply(
       FROM_HERE, {base::MayBlock()},
-      base::BindOnce(
-          CrostiniManager::CheckPathsAndComponents,
-          g_browser_process->platform_part()->cros_component_manager()),
+      base::BindOnce(&CrostiniManager::CheckPaths),
       base::BindOnce(&CrostiniManager::MaybeUpdateCrostiniAfterChecks,
                      weak_ptr_factory_.GetWeakPtr()));
   // Probe Concierge - if it's still running after an unclean shutdown, a
@@ -1030,167 +1016,51 @@
 }
 
 // static
-void CrostiniManager::CheckPathsAndComponents(
-    scoped_refptr<component_updater::CrOSComponentManager> component_manager) {
-  DCHECK(component_manager);
+void CrostiniManager::CheckPaths() {
   is_dev_kvm_present_ = base::PathExists(base::FilePath("/dev/kvm"));
-  is_cros_termina_registered_ = component_manager->IsRegisteredMayBlock(
-      imageloader::kTerminaComponentName);
 }
 
 void CrostiniManager::MaybeUpdateCrostiniAfterChecks() {
   if (!is_dev_kvm_present_) {
     return;
   }
-  if (!is_cros_termina_registered_) {
+  if (!CrostiniFeatures::Get()->IsEnabled(profile_)) {
     return;
   }
   if (!CrostiniFeatures::Get()->IsAllowed(profile_)) {
     return;
   }
-  termina_update_check_needed_ = true;
-  if (content::GetNetworkConnectionTracker()->IsOffline()) {
-    // Can't do a component Load with kForce when offline.
-    VLOG(1) << "Not online, so can't check now for cros-termina upgrade.";
-    return;
-  }
   if (ShouldPromptContainerUpgrade(DefaultContainerId())) {
     upgrade_available_notification_ =
         CrostiniUpgradeAvailableNotification::Show(profile_, base::DoNothing());
   }
-  InstallTerminaComponent(base::DoNothing());
+  // TODO(crbug/953544) Remove this once we have transitioned completely to DLC
+  InstallTermina(base::DoNothing());
 }
 
-using UpdatePolicy = component_updater::CrOSComponentManager::UpdatePolicy;
-
-void CrostiniManager::InstallTerminaComponent(CrostiniResultCallback callback) {
-  scoped_refptr<component_updater::CrOSComponentManager> component_manager =
-      g_browser_process->platform_part()->cros_component_manager();
-  if (!component_manager) {
-    // Running in a unit test. We still PostTask to prevent races.
-    content::GetUIThreadTaskRunner({})->PostTask(
-        FROM_HERE,
-        base::BindOnce(&CrostiniManager::OnInstallTerminaComponent,
-                       weak_ptr_factory_.GetWeakPtr(), std::move(callback),
-                       true, component_manager_load_error_for_testing_,
-                       base::FilePath()));
-    return;
-  }
-
-  DCHECK(component_manager);
-
-  bool major_update_required =
-      is_cros_termina_registered_ &&
-      component_manager->GetCompatiblePath(imageloader::kTerminaComponentName)
-          .empty();
-  bool is_offline = content::GetNetworkConnectionTracker()->IsOffline();
-
-  if (major_update_required) {
-    termina_update_check_needed_ = false;
-    if (is_offline) {
-      LOG(ERROR) << "Need to load a major component update, but we're offline.";
-      // TODO(nverne): Show a dialog/notification here for online upgrade
-      // required.
-      std::move(callback).Run(CrostiniResult::OFFLINE_WHEN_UPGRADE_REQUIRED);
-      return;
-    }
-  }
-
-  UpdatePolicy update_policy;
-  if (termina_update_check_needed_ && !is_offline) {
-    // Don't use kForce all the time because it generates traffic to
-    // ComponentUpdaterService. Also, it's only appropriate for minor version
-    // updates. Not major version incompatiblility.
-    update_policy = UpdatePolicy::kForce;
-  } else {
-    update_policy = UpdatePolicy::kDontForce;
-  }
-
-  component_manager->Load(
-      imageloader::kTerminaComponentName,
-      component_updater::CrOSComponentManager::MountPolicy::kMount,
-      update_policy,
-      base::BindOnce(&CrostiniManager::OnInstallTerminaComponent,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(callback),
-                     update_policy == UpdatePolicy::kForce));
+void CrostiniManager::InstallTermina(CrostiniResultCallback callback) {
+  termina_installer_.Install(base::BindOnce(
+      [](CrostiniResultCallback callback,
+         TerminaInstaller::InstallResult result) {
+        CrostiniResult res;
+        if (result == TerminaInstaller::InstallResult::Success) {
+          res = CrostiniResult::SUCCESS;
+        } else if (result == TerminaInstaller::InstallResult::Offline) {
+          res = CrostiniResult::OFFLINE_WHEN_UPGRADE_REQUIRED;
+        } else if (result == TerminaInstaller::InstallResult::Failure) {
+          res = CrostiniResult::LOAD_COMPONENT_FAILED;
+        } else {
+          CHECK(false)
+              << "Got unexpected value of TerminaInstaller::InstallResult";
+          res = CrostiniResult::LOAD_COMPONENT_FAILED;
+        }
+        std::move(callback).Run(res);
+      },
+      std::move(callback)));
 }
 
-void CrostiniManager::OnInstallTerminaComponent(
-    CrostiniResultCallback callback,
-    bool is_update_checked,
-    component_updater::CrOSComponentManager::Error error,
-    const base::FilePath& path) {
-  bool is_successful =
-      error == component_updater::CrOSComponentManager::Error::NONE;
-
-  if (is_successful) {
-    is_cros_termina_registered_ = true;
-  } else {
-    LOG(ERROR)
-        << "Failed to install the cros-termina component with error code: "
-        << static_cast<int>(error);
-    if (is_cros_termina_registered_ && is_update_checked) {
-      scoped_refptr<component_updater::CrOSComponentManager> component_manager =
-          g_browser_process->platform_part()->cros_component_manager();
-      if (component_manager) {
-        // Try again, this time with no update checking. The reason we do this
-        // is that we may still be offline even when is_offline above was
-        // false. It's notoriously difficult to know when you're really
-        // connected to the Internet, and it's also possible to be unable to
-        // connect to a service like ComponentUpdaterService even when you are
-        // connected to the rest of the Internet.
-        UpdatePolicy update_policy = UpdatePolicy::kDontForce;
-
-        LOG(ERROR) << "Retrying cros-termina component load, no update check";
-        // Load the existing component on disk.
-        component_manager->Load(
-            imageloader::kTerminaComponentName,
-            component_updater::CrOSComponentManager::MountPolicy::kMount,
-            update_policy,
-            base::BindOnce(&CrostiniManager::OnInstallTerminaComponent,
-                           weak_ptr_factory_.GetWeakPtr(), std::move(callback),
-                           false));
-        return;
-      }
-    }
-  }
-
-  if (is_successful && is_update_checked) {
-    VLOG(1) << "cros-termina update check successful.";
-    termina_update_check_needed_ = false;
-  }
-  CrostiniResult result = CrostiniResult::SUCCESS;
-  if (!is_successful) {
-    if (error ==
-        component_updater::CrOSComponentManager::Error::UPDATE_IN_PROGRESS) {
-      // Something else triggered an update that we have to wait on. We don't
-      // know what, or when they will be finished, so just retry every 5 seconds
-      // until we get a different result.
-      content::GetUIThreadTaskRunner({})->PostDelayedTask(
-          FROM_HERE,
-          base::BindOnce(&CrostiniManager::InstallTerminaComponent,
-                         weak_ptr_factory_.GetWeakPtr(), std::move(callback)),
-          base::TimeDelta::FromSeconds(5));
-      return;
-    } else {
-      result = CrostiniResult::LOAD_COMPONENT_FAILED;
-    }
-  }
-
-  std::move(callback).Run(result);
-}
-
-bool CrostiniManager::UninstallTerminaComponent() {
-  bool success = true;
-  scoped_refptr<component_updater::CrOSComponentManager> component_manager =
-      g_browser_process->platform_part()->cros_component_manager();
-  if (component_manager) {
-    success = component_manager->Unload(imageloader::kTerminaComponentName);
-  }
-  if (success) {
-    is_cros_termina_registered_ = false;
-  }
-  return success;
+void CrostiniManager::UninstallTermina(BoolCallback callback) {
+  termina_installer_.Uninstall(std::move(callback));
 }
 
 void CrostiniManager::StartConcierge(BoolCallback callback) {
@@ -1330,6 +1200,11 @@
   }
 
   vm_tools::concierge::StartVmRequest request;
+  base::FilePath base_path = termina_installer_.GetInstallLocation();
+  request.mutable_vm()->set_kernel(
+      base_path.Append(kCrostiniKernel).AsUTF8Unsafe());
+  request.mutable_vm()->set_rootfs(
+      base_path.Append(kCrostiniRootfs).AsUTF8Unsafe());
   request.set_name(std::move(name));
   request.set_start_termina(true);
   request.set_owner_id(owner_id_);
@@ -1344,11 +1219,24 @@
   DCHECK_LT(0, cpus);
   request.set_cpus(cpus);
 
-  vm_tools::concierge::DiskImage* disk_image = request.add_disks();
-  disk_image->set_path(std::move(disk_path_string));
-  disk_image->set_image_type(vm_tools::concierge::DISK_IMAGE_AUTO);
-  disk_image->set_writable(true);
-  disk_image->set_do_mount(false);
+  // The stateful disk must be added first because concierge treats it
+  // specially.
+  {
+    vm_tools::concierge::DiskImage* disk_image = request.add_disks();
+    disk_image->set_path(std::move(disk_path_string));
+    disk_image->set_image_type(vm_tools::concierge::DISK_IMAGE_AUTO);
+    disk_image->set_writable(true);
+    disk_image->set_do_mount(false);
+  }
+
+  {
+    vm_tools::concierge::DiskImage* disk_image = request.add_disks();
+    disk_image->set_path(base_path.Append(kCrostiniToolfs).AsUTF8Unsafe());
+    disk_image->set_mount_point(kCrostiniToolfsMountPath);
+    disk_image->set_fstype(kCrostiniToolfsType);
+    disk_image->set_writable(false);
+    disk_image->set_do_mount(true);
+  }
 
   GetConciergeClient()->StartTerminaVm(
       request, base::BindOnce(&CrostiniManager::OnStartTerminaVm,
diff --git a/chrome/browser/chromeos/crostini/crostini_manager.h b/chrome/browser/chromeos/crostini/crostini_manager.h
index 007ed735..e866d3e 100644
--- a/chrome/browser/chromeos/crostini/crostini_manager.h
+++ b/chrome/browser/chromeos/crostini/crostini_manager.h
@@ -19,6 +19,7 @@
 #include "chrome/browser/chromeos/crostini/crostini_simple_types.h"
 #include "chrome/browser/chromeos/crostini/crostini_types.mojom-forward.h"
 #include "chrome/browser/chromeos/crostini/crostini_util.h"
+#include "chrome/browser/chromeos/crostini/termina_installer.h"
 #include "chrome/browser/chromeos/vm_starting_observer.h"
 #include "chrome/browser/component_updater/cros_component_installer_chromeos.h"
 #include "chrome/browser/ui/browser.h"
@@ -227,21 +228,20 @@
 
   base::WeakPtr<CrostiniManager> GetWeakPtr();
 
-  // Returns true if the cros-termina component is installed.
-  static bool IsCrosTerminaInstalled();
-
   // Returns true if the /dev/kvm directory is present.
   static bool IsDevKvmPresent();
 
-  // Upgrades cros-termina component if the current version is not compatible.
+  // Upgrades cros-termina component if the current version is not
+  // compatible. This is a no-op if chromeos::features::kCrostiniUseDlc is
+  // enabled.
   void MaybeUpdateCrostini();
 
-  // Installs the current version of cros-termina component. Attempts to apply
-  // pending upgrades if a MaybeUpdateCrostini failed.
-  void InstallTerminaComponent(CrostiniResultCallback callback);
+  // Installs termina using either component updater or the DLC service
+  // depending on the value of chromeos::features::kCrostiniUseDlc
+  void InstallTermina(CrostiniResultCallback callback);
 
-  // Unloads and removes the cros-termina component. Returns success/failure.
-  bool UninstallTerminaComponent();
+  // Unloads and removes termina.
+  void UninstallTermina(BoolCallback callback);
 
   // Starts the Concierge service. |callback| is called after the method call
   // finishes.
@@ -710,14 +710,6 @@
       base::Optional<vm_tools::concierge::GetVmEnterpriseReportingInfoResponse>
           response);
 
-  // Callback for CrostiniManager::InstallCrostiniComponent. Must be called on
-  // the UI thread.
-  void OnInstallTerminaComponent(
-      CrostiniResultCallback callback,
-      bool is_update_checked,
-      component_updater::CrOSComponentManager::Error error,
-      const base::FilePath& path);
-
   // Callback for CrostiniClient::StartConcierge. Called after the
   // DebugDaemon service method finishes.
   void OnStartConcierge(BoolCallback callback, bool success);
@@ -846,9 +838,8 @@
   void OnDefaultContainerConfigured(bool success);
 
   // Helper for CrostiniManager::MaybeUpdateCrostini. Makes blocking calls to
-  // check for file paths and registered components.
-  static void CheckPathsAndComponents(
-      scoped_refptr<component_updater::CrOSComponentManager> component_manager);
+  // check for /dev/kvm.
+  static void CheckPaths();
 
   // Helper for CrostiniManager::MaybeUpdateCrostini. Separated because the
   // checking component registration code may block.
@@ -879,8 +870,6 @@
       component_manager_load_error_for_testing_ =
           component_updater::CrOSComponentManager::Error::NONE;
 
-  static bool is_cros_termina_registered_;
-  bool termina_update_check_needed_ = false;
   static bool is_dev_kvm_present_;
 
   // |is_unclean_startup_| is true when we detect Concierge still running at
@@ -974,6 +963,8 @@
   std::unique_ptr<CrostiniUpgradeAvailableNotification>
       upgrade_available_notification_;
 
+  TerminaInstaller termina_installer_{};
+
   // 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<CrostiniManager> weak_ptr_factory_{this};
diff --git a/chrome/browser/chromeos/crostini/crostini_manager_unittest.cc b/chrome/browser/chromeos/crostini/crostini_manager_unittest.cc
index 4ea6fe4f..ecdf41d8 100644
--- a/chrome/browser/chromeos/crostini/crostini_manager_unittest.cc
+++ b/chrome/browser/chromeos/crostini/crostini_manager_unittest.cc
@@ -25,8 +25,10 @@
 #include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/chromeos/policy/powerwash_requirements_checker.h"
 #include "chrome/browser/chromeos/settings/scoped_cros_settings_test_helper.h"
+#include "chrome/browser/component_updater/fake_cros_component_manager.h"
 #include "chrome/browser/notifications/notification_display_service_tester.h"
 #include "chrome/common/chrome_features.h"
+#include "chrome/test/base/browser_process_platform_part_test_api_chromeos.h"
 #include "chrome/test/base/scoped_testing_local_state.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
@@ -34,6 +36,7 @@
 #include "chromeos/dbus/concierge/concierge_service.pb.h"
 #include "chromeos/dbus/cryptohome/fake_cryptohome_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/dlcservice/dlcservice_client.h"
 #include "chromeos/dbus/fake_anomaly_detector_client.h"
 #include "chromeos/dbus/fake_cicerone_client.h"
 #include "chromeos/dbus/fake_concierge_client.h"
@@ -158,10 +161,20 @@
     std::move(closure).Run();
   }
 
+  void EnsureTerminaInstalled() {
+    base::RunLoop run_loop;
+    crostini_manager()->InstallTermina(
+        base::BindOnce([](base::OnceClosure callback,
+                          CrostiniResult) { std::move(callback).Run(); },
+                       run_loop.QuitClosure()));
+    run_loop.Run();
+  }
+
   CrostiniManagerTest()
       : task_environment_(content::BrowserTaskEnvironment::REAL_IO_THREAD),
         local_state_(std::make_unique<ScopedTestingLocalState>(
-            TestingBrowserProcess::GetGlobal())) {
+            TestingBrowserProcess::GetGlobal())),
+        browser_part_(g_browser_process->platform_part()) {
     chromeos::DBusThreadManager::Initialize();
     fake_cicerone_client_ = static_cast<chromeos::FakeCiceroneClient*>(
         chromeos::DBusThreadManager::Get()->GetCiceroneClient());
@@ -175,6 +188,17 @@
   ~CrostiniManagerTest() override { chromeos::DBusThreadManager::Shutdown(); }
 
   void SetUp() override {
+    component_manager_ =
+        base::MakeRefCounted<component_updater::FakeCrOSComponentManager>();
+    component_manager_->set_supported_components({"cros-termina"});
+    component_manager_->ResetComponentState(
+        "cros-termina",
+        component_updater::FakeCrOSComponentManager::ComponentInfo(
+            component_updater::CrOSComponentManager::Error::NONE,
+            base::FilePath("/install/path"), base::FilePath("/mount/path")));
+    browser_part_.InitializeCrosComponentManager(component_manager_);
+    chromeos::DlcserviceClient::InitializeFake();
+
     scoped_feature_list_.InitWithFeatures(
         {features::kCrostini, features::kCrostiniArcSideload}, {});
     run_loop_ = std::make_unique<base::RunLoop>();
@@ -209,6 +233,9 @@
     crostini_manager_->Shutdown();
     profile_.reset();
     run_loop_.reset();
+    chromeos::DlcserviceClient::Shutdown();
+    browser_part_.ShutdownCrosComponentManager();
+    component_manager_.reset();
   }
 
  protected:
@@ -239,6 +266,8 @@
   content::BrowserTaskEnvironment task_environment_;
   std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
   std::unique_ptr<ScopedTestingLocalState> local_state_;
+  scoped_refptr<component_updater::FakeCrOSComponentManager> component_manager_;
+  BrowserProcessPlatformPartTestApi browser_part_;
 
   DISALLOW_COPY_AND_ASSIGN(CrostiniManagerTest);
 };
@@ -413,6 +442,7 @@
   response.set_mount_result(vm_tools::concierge::StartVmResponse::FAILURE);
   fake_concierge_client_->set_start_vm_response(response);
 
+  EnsureTerminaInstalled();
   crostini_manager()->StartTerminaVm(
       kVmName, disk_path, 0,
       base::BindOnce(&ExpectFailure, run_loop()->QuitClosure()));
@@ -432,6 +462,7 @@
       vm_tools::concierge::StartVmResponse::PARTIAL_DATA_LOSS);
   fake_concierge_client_->set_start_vm_response(response);
 
+  EnsureTerminaInstalled();
   crostini_manager()->StartTerminaVm(
       kVmName, disk_path, 0,
       base::BindOnce(&ExpectSuccess, run_loop()->QuitClosure()));
@@ -445,6 +476,7 @@
   base::HistogramTester histogram_tester{};
   const base::FilePath& disk_path = base::FilePath(kVmName);
 
+  EnsureTerminaInstalled();
   crostini_manager()->StartTerminaVm(
       kVmName, disk_path, 0,
       base::BindOnce(&ExpectSuccess, run_loop()->QuitClosure()));
@@ -458,6 +490,7 @@
   const std::string owner_id = CryptohomeIdForProfile(profile());
 
   // Start the Vm.
+  EnsureTerminaInstalled();
   crostini_manager()->StartTerminaVm(
       kVmName, disk_path, 0,
       base::BindOnce(&ExpectSuccess, run_loop()->QuitClosure()));
diff --git a/chrome/browser/chromeos/crostini/crostini_remover.cc b/chrome/browser/chromeos/crostini/crostini_remover.cc
index b0d7563..e3133ab 100644
--- a/chrome/browser/chromeos/crostini/crostini_remover.cc
+++ b/chrome/browser/chromeos/crostini/crostini_remover.cc
@@ -34,21 +34,6 @@
 
 void CrostiniRemover::RemoveCrostini() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  if (CrostiniManager::GetForProfile(profile_)->IsCrosTerminaInstalled()) {
-    CrostiniManager::GetForProfile(profile_)->InstallTerminaComponent(
-        base::BindOnce(&CrostiniRemover::OnComponentLoaded, this));
-  } else {
-    // Crostini installation didn't install the component. Concierge should not
-    // be running, nor should there be any VMs.
-    CrostiniRemover::DestroyDiskImageFinished(true);
-  }
-}
-
-void CrostiniRemover::OnComponentLoaded(CrostiniResult result) {
-  if (result != CrostiniResult::SUCCESS) {
-    std::move(callback_).Run(result);
-    return;
-  }
   CrostiniManager::GetForProfile(profile_)->StartConcierge(
       base::BindOnce(&CrostiniRemover::OnConciergeStarted, this));
 }
@@ -87,17 +72,21 @@
     return;
   }
 
-  // UninstallTerminaComponent returns false both if Termina wasn't installed
-  // and if the uninstall failed, so we explicitly reset the relevant
-  // preferences even if it's already uninstalled
-  if (!CrostiniManager::GetForProfile(profile_)->IsCrosTerminaInstalled() ||
-      CrostiniManager::GetForProfile(profile_)->UninstallTerminaComponent()) {
-    profile_->GetPrefs()->SetBoolean(prefs::kCrostiniEnabled, false);
-    profile_->GetPrefs()->ClearPref(prefs::kCrostiniLastDiskSize);
-    profile_->GetPrefs()->Set(prefs::kCrostiniContainers,
-                              base::Value(base::Value::Type::LIST));
-    profile_->GetPrefs()->ClearPref(prefs::kCrostiniDefaultContainerConfigured);
+  CrostiniManager::GetForProfile(profile_)->UninstallTermina(
+      base::BindOnce(&CrostiniRemover::UninstallTerminaFinished, this));
+}
+
+void CrostiniRemover::UninstallTerminaFinished(bool success) {
+  if (!success) {
+    std::move(callback_).Run(CrostiniResult::UNKNOWN_ERROR);
+    return;
   }
+
+  profile_->GetPrefs()->SetBoolean(prefs::kCrostiniEnabled, false);
+  profile_->GetPrefs()->ClearPref(prefs::kCrostiniLastDiskSize);
+  profile_->GetPrefs()->Set(prefs::kCrostiniContainers,
+                            base::Value(base::Value::Type::LIST));
+  profile_->GetPrefs()->ClearPref(prefs::kCrostiniDefaultContainerConfigured);
   std::move(callback_).Run(CrostiniResult::SUCCESS);
 }
 
diff --git a/chrome/browser/chromeos/crostini/crostini_remover.h b/chrome/browser/chromeos/crostini/crostini_remover.h
index 9e4b6181..afc43327 100644
--- a/chrome/browser/chromeos/crostini/crostini_remover.h
+++ b/chrome/browser/chromeos/crostini/crostini_remover.h
@@ -22,10 +22,10 @@
 
   ~CrostiniRemover();
 
-  void OnComponentLoaded(crostini::CrostiniResult result);
   void OnConciergeStarted(bool is_successful);
   void StopVmFinished(crostini::CrostiniResult result);
   void DestroyDiskImageFinished(bool success);
+  void UninstallTerminaFinished(bool success);
 
   Profile* profile_;
   std::string vm_name_;
diff --git a/chrome/browser/chromeos/crostini/crostini_util.cc b/chrome/browser/chromeos/crostini/crostini_util.cc
index c12521e2..f48067b0 100644
--- a/chrome/browser/chromeos/crostini/crostini_util.cc
+++ b/chrome/browser/chromeos/crostini/crostini_util.cc
@@ -66,6 +66,13 @@
     "https://storage.googleapis.com/cros-containers/%d";
 const char kCrostiniStretchImageAlias[] = "debian/stretch";
 const char kCrostiniBusterImageAlias[] = "debian/buster";
+const char kCrostiniDlcName[] = "termina-dlc";
+
+const char kCrostiniKernel[] = "vm_kernel";
+const char kCrostiniRootfs[] = "vm_rootfs.img";
+const char kCrostiniToolfs[] = "vm_tools.img";
+const char kCrostiniToolfsMountPath[] = "/opt/google/cros-containers";
+const char kCrostiniToolfsType[] = "ext4";
 
 const base::FilePath::CharType kHomeDirectory[] = FILE_PATH_LITERAL("/home");
 
@@ -383,8 +390,7 @@
 
   // At this point, we know that Crostini UI is allowed.
   if (app_id == kCrostiniTerminalSystemAppId &&
-      (!crostini_manager->IsCrosTerminaInstalled() ||
-       !CrostiniFeatures::Get()->IsEnabled(profile))) {
+      !CrostiniFeatures::Get()->IsEnabled(profile)) {
     crostini::CrostiniInstaller::GetForProfile(profile)->ShowDialog(
         CrostiniUISurface::kAppList);
     return std::move(callback).Run(false, "Crostini not installed");
diff --git a/chrome/browser/chromeos/crostini/crostini_util.h b/chrome/browser/chromeos/crostini/crostini_util.h
index 0694878c..eb5a679 100644
--- a/chrome/browser/chromeos/crostini/crostini_util.h
+++ b/chrome/browser/chromeos/crostini/crostini_util.h
@@ -46,6 +46,13 @@
 extern const char kCrostiniDefaultImageServerUrl[];
 extern const char kCrostiniStretchImageAlias[];
 extern const char kCrostiniBusterImageAlias[];
+extern const char kCrostiniDlcName[];
+
+extern const char kCrostiniKernel[];
+extern const char kCrostiniRootfs[];
+extern const char kCrostiniToolfs[];
+extern const char kCrostiniToolfsMountPath[];
+extern const char kCrostiniToolfsType[];
 
 extern const base::FilePath::CharType kHomeDirectory[];
 
diff --git a/chrome/browser/chromeos/crostini/crostini_util_unittest.cc b/chrome/browser/chromeos/crostini/crostini_util_unittest.cc
index b4236fa..640d69ad 100644
--- a/chrome/browser/chromeos/crostini/crostini_util_unittest.cc
+++ b/chrome/browser/chromeos/crostini/crostini_util_unittest.cc
@@ -8,10 +8,13 @@
 #include "base/run_loop.h"
 #include "chrome/browser/browser_process_platform_part_base.h"
 #include "chrome/browser/chromeos/crostini/crostini_test_helper.h"
+#include "chrome/browser/component_updater/fake_cros_component_manager.h"
+#include "chrome/test/base/browser_process_platform_part_test_api_chromeos.h"
 #include "chrome/test/base/scoped_testing_local_state.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/dlcservice/dlcservice_client.h"
 #include "chromeos/dbus/fake_concierge_client.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -36,7 +39,8 @@
       : app_id_(crostini::CrostiniTestHelper::GenerateAppId(kDesktopFileId)),
         task_environment_(content::BrowserTaskEnvironment::REAL_IO_THREAD),
         local_state_(std::make_unique<ScopedTestingLocalState>(
-            TestingBrowserProcess::GetGlobal())) {
+            TestingBrowserProcess::GetGlobal())),
+        browser_part_(g_browser_process->platform_part()) {
     chromeos::DBusThreadManager::Initialize();
 
     fake_concierge_client_ = static_cast<chromeos::FakeConciergeClient*>(
@@ -47,6 +51,18 @@
   CrostiniUtilTest& operator=(const CrostiniUtilTest&) = delete;
 
   void SetUp() override {
+    chromeos::DlcserviceClient::InitializeFake();
+
+    component_manager_ =
+        base::MakeRefCounted<component_updater::FakeCrOSComponentManager>();
+    component_manager_->set_supported_components({"cros-termina"});
+    component_manager_->ResetComponentState(
+        "cros-termina",
+        component_updater::FakeCrOSComponentManager::ComponentInfo(
+            component_updater::CrOSComponentManager::Error::NONE,
+            base::FilePath("/install/path"), base::FilePath("/mount/path")));
+    browser_part_.InitializeCrosComponentManager(component_manager_);
+
     run_loop_ = std::make_unique<base::RunLoop>();
     profile_ = std::make_unique<TestingProfile>();
     test_helper_ = std::make_unique<CrostiniTestHelper>(profile_.get());
@@ -60,6 +76,9 @@
     test_helper_.reset();
     run_loop_.reset();
     profile_.reset();
+    browser_part_.ShutdownCrosComponentManager();
+    component_manager_.reset();
+    chromeos::DlcserviceClient::Shutdown();
   }
 
  protected:
@@ -73,6 +92,8 @@
  private:
   content::BrowserTaskEnvironment task_environment_;
   std::unique_ptr<ScopedTestingLocalState> local_state_;
+  scoped_refptr<component_updater::FakeCrOSComponentManager> component_manager_;
+  BrowserProcessPlatformPartTestApi browser_part_;
 };
 
 TEST_F(CrostiniUtilTest, ContainerIdEquality) {
diff --git a/chrome/browser/chromeos/crostini/termina_installer.cc b/chrome/browser/chromeos/crostini/termina_installer.cc
new file mode 100644
index 0000000..8935d6a
--- /dev/null
+++ b/chrome/browser/chromeos/crostini/termina_installer.cc
@@ -0,0 +1,311 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/crostini/termina_installer.h"
+
+#include <memory>
+
+#include "base/barrier_closure.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/feature_list.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/chromeos/crostini/crostini_util.h"
+#include "chromeos/constants/chromeos_features.h"
+#include "content/public/browser/network_service_instance.h"
+#include "services/network/public/cpp/network_connection_tracker.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+
+namespace crostini {
+
+TerminaInstaller::TerminaInstaller() {}
+TerminaInstaller::~TerminaInstaller() {}
+
+void TerminaInstaller::Install(
+    base::OnceCallback<void(InstallResult)> callback) {
+  // The Remove*IfPresent methods require an unowned UninstallResult pointer to
+  // record their success/failure state. This has to be unowned so that in
+  // Uninstall it can be accessed further down the callback chain, but here we
+  // don't care about it, so we assign ownership of the pointer to a
+  // base::DoNothing that will delete it once called.
+  auto ptr = std::make_unique<UninstallResult>();
+  auto* uninstall_result_ptr = ptr.get();
+  auto remove_callback = base::BindOnce(
+      [](std::unique_ptr<UninstallResult> ptr) {}, std::move(ptr));
+
+  // Remove whichever version of termina we're *not* using and install the right
+  // one.
+  if (base::FeatureList::IsEnabled(chromeos::features::kCrostiniUseDlc)) {
+    RemoveComponentIfPresent(std::move(remove_callback), uninstall_result_ptr);
+    InstallDlc(std::move(callback));
+  } else {
+    RemoveDlcIfPresent(std::move(remove_callback), uninstall_result_ptr);
+    InstallComponent(std::move(callback));
+  }
+}
+
+void TerminaInstaller::InstallDlc(
+    base::OnceCallback<void(InstallResult)> callback) {
+  chromeos::DlcserviceClient::Get()->Install(
+      kCrostiniDlcName,
+      base::BindOnce(&TerminaInstaller::OnInstallDlc,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)),
+      base::DoNothing());
+}
+
+void TerminaInstaller::OnInstallDlc(
+    base::OnceCallback<void(InstallResult)> callback,
+    const chromeos::DlcserviceClient::InstallResult& result) {
+  CHECK(result.dlc_id == kCrostiniDlcName);
+  InstallResult response;
+  if (result.error == dlcservice::kErrorNone) {
+    response = InstallResult::Success;
+    termina_location_ = base::FilePath(result.root_path);
+  } else {
+    if (content::GetNetworkConnectionTracker()->IsOffline()) {
+      LOG(ERROR) << "Failed to install termina-dlc while offline, assuming "
+                    "network issue: "
+                 << result.error;
+      response = InstallResult::Offline;
+    } else {
+      LOG(ERROR) << "Failed to install termina-dlc: " << result.error;
+      response = InstallResult::Failure;
+    }
+  }
+  std::move(callback).Run(response);
+}
+
+using UpdatePolicy = component_updater::CrOSComponentManager::UpdatePolicy;
+
+void TerminaInstaller::InstallComponent(
+    base::OnceCallback<void(InstallResult)> callback) {
+  scoped_refptr<component_updater::CrOSComponentManager> component_manager =
+      g_browser_process->platform_part()->cros_component_manager();
+
+  bool major_update_required =
+      component_manager->GetCompatiblePath(imageloader::kTerminaComponentName)
+          .empty();
+  bool is_offline = content::GetNetworkConnectionTracker()->IsOffline();
+
+  if (major_update_required) {
+    component_update_check_needed_ = false;
+    if (is_offline) {
+      LOG(ERROR) << "Need to load a major component update, but we're offline.";
+      std::move(callback).Run(InstallResult::Offline);
+      return;
+    }
+  }
+
+  UpdatePolicy update_policy;
+  if (component_update_check_needed_ && !is_offline) {
+    // Don't use kForce all the time because it generates traffic to
+    // ComponentUpdaterService. Also, it's only appropriate for minor version
+    // updates. Not major version incompatiblility.
+    update_policy = UpdatePolicy::kForce;
+  } else {
+    update_policy = UpdatePolicy::kDontForce;
+  }
+
+  component_manager->Load(
+      imageloader::kTerminaComponentName,
+      component_updater::CrOSComponentManager::MountPolicy::kMount,
+      update_policy,
+      base::BindOnce(&TerminaInstaller::OnInstallComponent,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback),
+                     update_policy == UpdatePolicy::kForce));
+}
+
+void TerminaInstaller::OnInstallComponent(
+    base::OnceCallback<void(InstallResult)> callback,
+    bool is_update_checked,
+    component_updater::CrOSComponentManager::Error error,
+    const base::FilePath& path) {
+  bool is_successful =
+      error == component_updater::CrOSComponentManager::Error::NONE;
+
+  if (is_successful) {
+    termina_location_ = path;
+  } else {
+    LOG(ERROR)
+        << "Failed to install the cros-termina component with error code: "
+        << static_cast<int>(error);
+    if (is_update_checked) {
+      scoped_refptr<component_updater::CrOSComponentManager> component_manager =
+          g_browser_process->platform_part()->cros_component_manager();
+      if (component_manager) {
+        // Try again, this time with no update checking. The reason we do this
+        // is that we may still be offline even when is_offline above was
+        // false. It's notoriously difficult to know when you're really
+        // connected to the Internet, and it's also possible to be unable to
+        // connect to a service like ComponentUpdaterService even when you are
+        // connected to the rest of the Internet.
+        UpdatePolicy update_policy = UpdatePolicy::kDontForce;
+
+        LOG(ERROR) << "Retrying cros-termina component load, no update check";
+        // Load the existing component on disk.
+        component_manager->Load(
+            imageloader::kTerminaComponentName,
+            component_updater::CrOSComponentManager::MountPolicy::kMount,
+            update_policy,
+            base::BindOnce(&TerminaInstaller::OnInstallComponent,
+                           weak_ptr_factory_.GetWeakPtr(), std::move(callback),
+                           false));
+        return;
+      }
+    }
+  }
+
+  if (is_successful && is_update_checked) {
+    VLOG(1) << "cros-termina update check successful.";
+    component_update_check_needed_ = false;
+  }
+  InstallResult result = InstallResult::Success;
+  if (!is_successful) {
+    if (error ==
+        component_updater::CrOSComponentManager::Error::UPDATE_IN_PROGRESS) {
+      // Something else triggered an update that we have to wait on. We don't
+      // know what, or when they will be finished, so just retry every 5 seconds
+      // until we get a different result.
+      base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+          FROM_HERE,
+          base::BindOnce(&TerminaInstaller::InstallComponent,
+                         weak_ptr_factory_.GetWeakPtr(), std::move(callback)),
+          base::TimeDelta::FromSeconds(5));
+      return;
+    } else {
+      result = InstallResult::Failure;
+    }
+  }
+
+  std::move(callback).Run(result);
+}
+
+void TerminaInstaller::Uninstall(base::OnceCallback<void(bool)> callback) {
+  // Unset |termina_location_| now since it will become invalid at some point
+  // soon.
+  termina_location_ = base::nullopt;
+
+  // This is really a vector of bool, but std::vector<bool> has weird properties
+  // that stop us from using it in this way.
+  std::vector<UninstallResult> partial_results{0, 0};
+  UninstallResult* component_result = &partial_results[0];
+  UninstallResult* dlc_result = &partial_results[1];
+
+  // We want to get the results from both uninstall calls and combine them, and
+  // the asynchronous nature of this process means we can't use return values,
+  // so we need to pass a pointer into those calls to store their results and
+  // pass ownership of that memory into the result callback.
+  auto b_closure = BarrierClosure(
+      2, base::BindOnce(&TerminaInstaller::OnUninstallFinished,
+                        weak_ptr_factory_.GetWeakPtr(), std::move(callback),
+                        std::move(partial_results)));
+
+  RemoveComponentIfPresent(b_closure, component_result);
+  RemoveDlcIfPresent(b_closure, dlc_result);
+}
+
+void TerminaInstaller::RemoveComponentIfPresent(
+    base::OnceCallback<void()> callback,
+    UninstallResult* result) {
+  scoped_refptr<component_updater::CrOSComponentManager> component_manager =
+      g_browser_process->platform_part()->cros_component_manager();
+
+  base::ThreadPool::PostTaskAndReplyWithResult(
+      FROM_HERE, {base::MayBlock()},
+      base::BindOnce(
+          [](scoped_refptr<component_updater::CrOSComponentManager>
+                 component_manager) {
+            return component_manager->IsRegisteredMayBlock(
+                imageloader::kTerminaComponentName);
+          },
+          std::move(component_manager)),
+      base::BindOnce(
+          [](base::OnceCallback<void()> callback, UninstallResult* result,
+             bool is_present) {
+            scoped_refptr<component_updater::CrOSComponentManager>
+                component_manager = g_browser_process->platform_part()
+                                        ->cros_component_manager();
+            if (is_present) {
+              *result =
+                  component_manager->Unload(imageloader::kTerminaComponentName);
+              if (!*result) {
+                LOG(ERROR) << "Failed to remove cros-termina component";
+              }
+            } else {
+              *result = true;
+            }
+            std::move(callback).Run();
+          },
+          std::move(callback), result));
+}
+
+void TerminaInstaller::RemoveDlcIfPresent(base::OnceCallback<void()> callback,
+                                          UninstallResult* result) {
+  chromeos::DlcserviceClient::Get()->GetExistingDlcs(base::BindOnce(
+      [](base::WeakPtr<TerminaInstaller> weak_this,
+         base::OnceCallback<void()> callback, UninstallResult* result,
+         const std::string& err,
+         const dlcservice::DlcsWithContent& dlcs_with_content) {
+        if (!weak_this)
+          return;
+
+        if (err != dlcservice::kErrorNone) {
+          LOG(ERROR) << "Failed to list installed DLCs: " << err;
+          *result = false;
+          std::move(callback).Run();
+          return;
+        }
+        for (const auto& dlc : dlcs_with_content.dlc_infos()) {
+          if (dlc.id() == kCrostiniDlcName) {
+            weak_this->RemoveDlc(std::move(callback), result);
+            return;
+          }
+        }
+        *result = true;
+        std::move(callback).Run();
+      },
+      weak_ptr_factory_.GetWeakPtr(), std::move(callback), result));
+}
+
+void TerminaInstaller::RemoveDlc(base::OnceCallback<void()> callback,
+                                 UninstallResult* result) {
+  chromeos::DlcserviceClient::Get()->Uninstall(
+      kCrostiniDlcName,
+      base::BindOnce(
+          [](base::OnceCallback<void()> callback, UninstallResult* result,
+             const std::string& err) {
+            if (err == dlcservice::kErrorNone) {
+              *result = true;
+            } else {
+              LOG(ERROR) << "Failed to remove termina-dlc: " << err;
+              *result = false;
+            }
+            std::move(callback).Run();
+          },
+          std::move(callback), result));
+}
+
+void TerminaInstaller::OnUninstallFinished(
+    base::OnceCallback<void(bool)> callback,
+    std::vector<UninstallResult> partial_results) {
+  for (auto i : partial_results) {
+    if (!i) {
+      std::move(callback).Run(false);
+      return;
+    }
+  }
+  std::move(callback).Run(true);
+}
+
+base::FilePath TerminaInstaller::GetInstallLocation() {
+  CHECK(termina_location_)
+      << "GetInstallLocation() called while termina not installed";
+  return *termina_location_;
+}
+
+}  // namespace crostini
diff --git a/chrome/browser/chromeos/crostini/termina_installer.h b/chrome/browser/chromeos/crostini/termina_installer.h
new file mode 100644
index 0000000..9696bb46
--- /dev/null
+++ b/chrome/browser/chromeos/crostini/termina_installer.h
@@ -0,0 +1,88 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_CROSTINI_TERMINA_INSTALLER_H_
+#define CHROME_BROWSER_CHROMEOS_CROSTINI_TERMINA_INSTALLER_H_
+
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "chrome/browser/component_updater/cros_component_manager.h"
+#include "chromeos/dbus/dlcservice/dlcservice_client.h"
+
+namespace crostini {
+
+// This class is responsible for tracking which source the termina VM is
+// installed from (component updater, dlc service etc.) and ensuring that any
+// unneeded versions are cleaned up.
+class TerminaInstaller {
+ public:
+  TerminaInstaller();
+  ~TerminaInstaller();
+
+  TerminaInstaller(const TerminaInstaller&) = delete;
+  TerminaInstaller& operator=(const TerminaInstaller&) = delete;
+
+  enum class InstallResult {
+    // The install succeeded.
+    Success,
+    // The install failed for an unspecified reason.
+    Failure,
+    // The install failed because it needed to download an image and the device
+    // is offline.
+    Offline,
+  };
+
+  // This is really a bool, but std::vector<bool> has weird properties that stop
+  // us from using bool here.
+  using UninstallResult = int;
+
+  // Ensure that termina is installed. This will also attempt to remove any
+  // other instances of termina that may be installed, but will not block on or
+  // check the result of this.
+  void Install(base::OnceCallback<void(InstallResult)> callback);
+
+  // Remove termina entirely. This will also attempt to remove any
+  // other instances of termina that may be installed.
+  void Uninstall(base::OnceCallback<void(bool)> callback);
+
+  // Get a path to the install location of termina. You must call Install and
+  // get a Success response back before calling this method.
+  base::FilePath GetInstallLocation();
+
+ private:
+  void InstallDlc(base::OnceCallback<void(InstallResult)> callback);
+  void OnInstallDlc(base::OnceCallback<void(InstallResult)> callback,
+                    const chromeos::DlcserviceClient::InstallResult& result);
+
+  void InstallComponent(base::OnceCallback<void(InstallResult)> callback);
+  void OnInstallComponent(base::OnceCallback<void(InstallResult)> callback,
+                          bool is_update_checked,
+                          component_updater::CrOSComponentManager::Error error,
+                          const base::FilePath& path);
+
+  void RemoveComponentIfPresent(base::OnceCallback<void()> callback,
+                                UninstallResult* result);
+  void RemoveDlcIfPresent(base::OnceCallback<void()> callback,
+                          UninstallResult* result);
+  void RemoveDlc(base::OnceCallback<void()> callback, UninstallResult* result);
+
+  void OnUninstallFinished(base::OnceCallback<void(bool)> callback,
+                           std::vector<UninstallResult> partial_results);
+
+  // Do we need to try to force a component update? We want to do this at most
+  // once per session, so this is initialized to true and unset whenever we
+  // successfully check for an update or we need to install a new major version.
+  bool component_update_check_needed_{true};
+
+  base::Optional<base::FilePath> termina_location_{base::nullopt};
+  base::WeakPtrFactory<TerminaInstaller> weak_ptr_factory_{this};
+};
+
+}  // namespace crostini
+
+#endif  // CHROME_BROWSER_CHROMEOS_CROSTINI_TERMINA_INSTALLER_H_
diff --git a/chrome/browser/chromeos/crostini/termina_installer_unittest.cc b/chrome/browser/chromeos/crostini/termina_installer_unittest.cc
new file mode 100644
index 0000000..ab9a8c3
--- /dev/null
+++ b/chrome/browser/chromeos/crostini/termina_installer_unittest.cc
@@ -0,0 +1,538 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/crostini/termina_installer.h"
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chromeos/crostini/crostini_util.h"
+#include "chrome/browser/component_updater/fake_cros_component_manager.h"
+#include "chrome/test/base/browser_process_platform_part_test_api_chromeos.h"
+#include "chromeos/constants/chromeos_features.h"
+#include "chromeos/dbus/dlcservice/dlcservice_client.h"
+#include "chromeos/dbus/dlcservice/fake_dlcservice_client.h"
+#include "services/network/test/test_network_connection_tracker.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+
+namespace crostini {
+
+class TerminaInstallTest : public testing::Test {
+ public:
+  TerminaInstallTest() : browser_part_(g_browser_process->platform_part()) {}
+
+  void SetUp() override {
+    component_manager_ =
+        base::MakeRefCounted<component_updater::FakeCrOSComponentManager>();
+    browser_part_.InitializeCrosComponentManager(component_manager_);
+    chromeos::DlcserviceClient::InitializeFake();
+    fake_dlc_client_ = static_cast<chromeos::FakeDlcserviceClient*>(
+        chromeos::DlcserviceClient::Get());
+    fake_dlc_client_->set_install_root_path(dlc_root_path_);
+  }
+
+  void TearDown() override {
+    chromeos::DlcserviceClient::Shutdown();
+    browser_part_.ShutdownCrosComponentManager();
+    component_manager_.reset();
+  }
+
+  void ExpectTrue(bool result) {
+    EXPECT_TRUE(result);
+    run_loop_.Quit();
+  }
+
+  void ExpectFalse(bool result) {
+    EXPECT_FALSE(result);
+    run_loop_.Quit();
+  }
+
+  void ExpectSuccess(TerminaInstaller::InstallResult result) {
+    EXPECT_EQ(result, TerminaInstaller::InstallResult::Success);
+    run_loop_.Quit();
+  }
+
+  void ExpectSuccess2(TerminaInstaller::InstallResult result) {
+    EXPECT_EQ(result, TerminaInstaller::InstallResult::Success);
+    run_loop_2_.Quit();
+  }
+
+  void ExpectFailure(TerminaInstaller::InstallResult result) {
+    EXPECT_EQ(result, TerminaInstaller::InstallResult::Failure);
+    run_loop_.Quit();
+  }
+
+  void ExpectOffline(TerminaInstaller::InstallResult result) {
+    EXPECT_EQ(result, TerminaInstaller::InstallResult::Offline);
+    run_loop_.Quit();
+  }
+
+  void InjectDlc() {
+    dlcservice::DlcsWithContent dlcs;
+    auto* dlc_info = dlcs.add_dlc_infos();
+    dlc_info->set_id(kCrostiniDlcName);
+    fake_dlc_client_->set_dlcs_with_content(dlcs);
+  }
+
+  const base::FilePath component_install_path_ =
+      base::FilePath("/install/path");
+  const base::FilePath component_mount_path_ = base::FilePath("/mount/path");
+  using ComponentError = component_updater::CrOSComponentManager::Error;
+  using ComponentInfo =
+      component_updater::FakeCrOSComponentManager::ComponentInfo;
+
+  void PrepareComponentForLoad() {
+    component_manager_->set_supported_components(
+        {imageloader::kTerminaComponentName});
+    component_manager_->ResetComponentState(
+        imageloader::kTerminaComponentName,
+        ComponentInfo(ComponentError::NONE, component_install_path_,
+                      component_mount_path_));
+  }
+
+  const std::string dlc_root_path_ = "/dlc/root/path";
+
+  void CheckDlcInstalled() {
+    base::RunLoop run_loop;
+
+    fake_dlc_client_->GetExistingDlcs(base::BindOnce(
+        [](base::OnceClosure quit, const std::string& err,
+           const dlcservice::DlcsWithContent& dlcs_with_content) {
+          std::move(quit).Run();
+          ASSERT_EQ(dlcs_with_content.dlc_infos_size(), 1);
+          EXPECT_EQ(dlcs_with_content.dlc_infos(0).id(), kCrostiniDlcName);
+        },
+        run_loop.QuitClosure()));
+
+    EXPECT_EQ(termina_installer_.GetInstallLocation(),
+              base::FilePath(dlc_root_path_));
+
+    run_loop.Run();
+  }
+
+  void CheckDlcNotInstalled() {
+    base::RunLoop run_loop;
+
+    fake_dlc_client_->GetExistingDlcs(base::BindOnce(
+        [](base::OnceClosure quit, const std::string& err,
+           const dlcservice::DlcsWithContent& dlcs_with_content) {
+          std::move(quit).Run();
+          EXPECT_EQ(dlcs_with_content.dlc_infos_size(), 0);
+        },
+        run_loop.QuitClosure()));
+
+    run_loop.Run();
+  }
+
+ protected:
+  scoped_refptr<component_updater::FakeCrOSComponentManager> component_manager_;
+  BrowserProcessPlatformPartTestApi browser_part_;
+  chromeos::FakeDlcserviceClient* fake_dlc_client_;
+  TerminaInstaller termina_installer_;
+  base::test::TaskEnvironment task_env_{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  base::RunLoop run_loop_;
+  base::RunLoop run_loop_2_;
+};
+
+// Specialization of TerminaInstallTest that force-enables installing via DLC
+class TerminaDlcInstallTest : public TerminaInstallTest {
+ public:
+  TerminaDlcInstallTest() {
+    feature_list_.InitWithFeatures(
+        /*enabled_features=*/{chromeos::features::kCrostiniUseDlc},
+        /*disabled_features=*/{});
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+// Specialization of TerminaInstallTest that force-disables installing via DLC
+class TerminaComponentInstallTest : public TerminaInstallTest {
+ public:
+  TerminaComponentInstallTest() {
+    feature_list_.InitWithFeatures(
+        /*enabled_features=*/{},
+        /*disabled_features=*/{chromeos::features::kCrostiniUseDlc});
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+TEST_F(TerminaInstallTest, UninstallWithNothingInstalled) {
+  termina_installer_.Uninstall(
+      base::BindOnce(&TerminaInstallTest::ExpectTrue, base::Unretained(this)));
+  run_loop_.Run();
+}
+
+TEST_F(TerminaInstallTest, UninstallWithNothingInstalledListError) {
+  fake_dlc_client_->set_get_existing_dlcs_error("An error");
+
+  termina_installer_.Uninstall(
+      base::BindOnce(&TerminaInstallTest::ExpectFalse, base::Unretained(this)));
+  run_loop_.Run();
+}
+
+TEST_F(TerminaInstallTest, UninstallWithNothingInstalledUninstallError) {
+  // These should be ignored because nothing needs to be uninstalled
+  component_manager_->set_unload_component_result(false);
+  fake_dlc_client_->set_uninstall_error("An error");
+
+  termina_installer_.Uninstall(
+      base::BindOnce(&TerminaInstallTest::ExpectTrue, base::Unretained(this)));
+  run_loop_.Run();
+}
+
+TEST_F(TerminaInstallTest, UninstallWithComponentInstalled) {
+  component_manager_->SetRegisteredComponents(
+      {imageloader::kTerminaComponentName});
+
+  termina_installer_.Uninstall(
+      base::BindOnce(&TerminaInstallTest::ExpectTrue, base::Unretained(this)));
+  run_loop_.Run();
+
+  EXPECT_FALSE(component_manager_->IsRegisteredMayBlock(
+      imageloader::kTerminaComponentName));
+}
+
+TEST_F(TerminaInstallTest, UninstallWithComponentInstalledError) {
+  component_manager_->SetRegisteredComponents(
+      {imageloader::kTerminaComponentName});
+  component_manager_->set_unload_component_result(false);
+
+  termina_installer_.Uninstall(
+      base::BindOnce(&TerminaInstallTest::ExpectFalse, base::Unretained(this)));
+  run_loop_.Run();
+}
+
+TEST_F(TerminaInstallTest, UninstallWithDlcInstalled) {
+  InjectDlc();
+
+  termina_installer_.Uninstall(
+      base::BindOnce(&TerminaInstallTest::ExpectTrue, base::Unretained(this)));
+  run_loop_.Run();
+
+  CheckDlcNotInstalled();
+}
+
+TEST_F(TerminaInstallTest, UninstallWithDlcInstalledUninstallError) {
+  InjectDlc();
+  fake_dlc_client_->set_uninstall_error("An error");
+
+  termina_installer_.Uninstall(
+      base::BindOnce(&TerminaInstallTest::ExpectFalse, base::Unretained(this)));
+  run_loop_.Run();
+}
+
+TEST_F(TerminaInstallTest, UninstallWithBothInstalled) {
+  component_manager_->SetRegisteredComponents(
+      {imageloader::kTerminaComponentName});
+  InjectDlc();
+
+  termina_installer_.Uninstall(
+      base::BindOnce(&TerminaInstallTest::ExpectTrue, base::Unretained(this)));
+  run_loop_.Run();
+
+  EXPECT_FALSE(component_manager_->IsRegisteredMayBlock(
+      imageloader::kTerminaComponentName));
+  CheckDlcNotInstalled();
+}
+
+TEST_F(TerminaDlcInstallTest, InstallDlc) {
+  termina_installer_.Install(base::BindOnce(&TerminaInstallTest::ExpectSuccess,
+                                            base::Unretained(this)));
+  run_loop_.Run();
+
+  CheckDlcInstalled();
+}
+
+TEST_F(TerminaDlcInstallTest, InstallDlcError) {
+  fake_dlc_client_->set_install_error("An error");
+
+  termina_installer_.Install(base::BindOnce(&TerminaInstallTest::ExpectFailure,
+                                            base::Unretained(this)));
+  run_loop_.Run();
+}
+
+TEST_F(TerminaDlcInstallTest, InstallDlcOffline) {
+  fake_dlc_client_->set_install_error("An error");
+
+  auto* network_connection_tracker =
+      network::TestNetworkConnectionTracker::GetInstance();
+  network_connection_tracker->SetConnectionType(
+      network::mojom::ConnectionType::CONNECTION_NONE);
+
+  termina_installer_.Install(base::BindOnce(&TerminaInstallTest::ExpectOffline,
+                                            base::Unretained(this)));
+  run_loop_.Run();
+}
+
+TEST_F(TerminaDlcInstallTest, InstallDlcWithComponentInstalled) {
+  component_manager_->SetRegisteredComponents(
+      {imageloader::kTerminaComponentName});
+
+  termina_installer_.Install(base::BindOnce(&TerminaInstallTest::ExpectSuccess,
+                                            base::Unretained(this)));
+  run_loop_.Run();
+
+  CheckDlcInstalled();
+
+  task_env_.RunUntilIdle();
+  EXPECT_FALSE(component_manager_->IsRegisteredMayBlock(
+      imageloader::kTerminaComponentName));
+}
+
+TEST_F(TerminaDlcInstallTest, InstallDlcWithComponentInstalledUninstallError) {
+  component_manager_->SetRegisteredComponents(
+      {imageloader::kTerminaComponentName});
+  component_manager_->set_unload_component_result(false);
+
+  termina_installer_.Install(base::BindOnce(&TerminaInstallTest::ExpectSuccess,
+                                            base::Unretained(this)));
+  run_loop_.Run();
+
+  CheckDlcInstalled();
+}
+
+TEST_F(TerminaComponentInstallTest, InstallComponent) {
+  PrepareComponentForLoad();
+
+  termina_installer_.Install(base::BindOnce(&TerminaInstallTest::ExpectSuccess,
+                                            base::Unretained(this)));
+  run_loop_.Run();
+
+  EXPECT_TRUE(component_manager_->IsRegisteredMayBlock(
+      imageloader::kTerminaComponentName));
+  EXPECT_EQ(termina_installer_.GetInstallLocation(), component_mount_path_);
+}
+
+TEST_F(TerminaComponentInstallTest, InstallComponentOffline) {
+  PrepareComponentForLoad();
+  auto* network_connection_tracker =
+      network::TestNetworkConnectionTracker::GetInstance();
+  network_connection_tracker->SetConnectionType(
+      network::mojom::ConnectionType::CONNECTION_NONE);
+
+  termina_installer_.Install(base::BindOnce(&TerminaInstallTest::ExpectOffline,
+                                            base::Unretained(this)));
+  run_loop_.Run();
+}
+
+TEST_F(TerminaComponentInstallTest, InstallComponentWithDlcInstalled) {
+  PrepareComponentForLoad();
+  InjectDlc();
+
+  termina_installer_.Install(base::BindOnce(&TerminaInstallTest::ExpectSuccess,
+                                            base::Unretained(this)));
+  run_loop_.Run();
+
+  EXPECT_TRUE(component_manager_->IsRegisteredMayBlock(
+      imageloader::kTerminaComponentName));
+  CheckDlcNotInstalled();
+  EXPECT_EQ(termina_installer_.GetInstallLocation(), component_mount_path_);
+}
+
+TEST_F(TerminaComponentInstallTest, InstallComponentWithDlcInstalledError) {
+  PrepareComponentForLoad();
+  InjectDlc();
+  fake_dlc_client_->set_uninstall_error("An error");
+
+  termina_installer_.Install(base::BindOnce(&TerminaInstallTest::ExpectSuccess,
+                                            base::Unretained(this)));
+  run_loop_.Run();
+
+  EXPECT_TRUE(component_manager_->IsRegisteredMayBlock(
+      imageloader::kTerminaComponentName));
+  EXPECT_EQ(termina_installer_.GetInstallLocation(), component_mount_path_);
+}
+
+TEST_F(TerminaComponentInstallTest, LoadComponentAlreadyInstalled) {
+  component_manager_->set_supported_components(
+      {imageloader::kTerminaComponentName});
+  component_manager_->set_queue_load_requests(true);
+  component_manager_->RegisterCompatiblePath(imageloader::kTerminaComponentName,
+                                             component_install_path_);
+
+  termina_installer_.Install(base::BindOnce(&TerminaInstallTest::ExpectSuccess,
+                                            base::Unretained(this)));
+  EXPECT_TRUE(component_manager_->HasPendingInstall(
+      imageloader::kTerminaComponentName));
+  EXPECT_TRUE(
+      component_manager_->UpdateRequested(imageloader::kTerminaComponentName));
+  component_manager_->FinishLoadRequest(
+      imageloader::kTerminaComponentName,
+      ComponentInfo(ComponentError::NONE, component_install_path_,
+                    component_mount_path_));
+  run_loop_.Run();
+}
+
+TEST_F(TerminaComponentInstallTest, LoadComponentInitiallyOffline) {
+  component_manager_->set_supported_components(
+      {imageloader::kTerminaComponentName});
+  component_manager_->set_queue_load_requests(true);
+  component_manager_->RegisterCompatiblePath(imageloader::kTerminaComponentName,
+                                             component_install_path_);
+  auto* network_connection_tracker =
+      network::TestNetworkConnectionTracker::GetInstance();
+  network_connection_tracker->SetConnectionType(
+      network::mojom::ConnectionType::CONNECTION_NONE);
+
+  termina_installer_.Install(base::BindOnce(&TerminaInstallTest::ExpectSuccess,
+                                            base::Unretained(this)));
+  EXPECT_TRUE(component_manager_->HasPendingInstall(
+      imageloader::kTerminaComponentName));
+  EXPECT_FALSE(
+      component_manager_->UpdateRequested(imageloader::kTerminaComponentName));
+  component_manager_->FinishLoadRequest(
+      imageloader::kTerminaComponentName,
+      ComponentInfo(ComponentError::NONE, component_install_path_,
+                    component_mount_path_));
+
+  network_connection_tracker->SetConnectionType(
+      network::mojom::ConnectionType::CONNECTION_ETHERNET);
+
+  termina_installer_.Install(base::BindOnce(&TerminaInstallTest::ExpectSuccess2,
+                                            base::Unretained(this)));
+  EXPECT_TRUE(component_manager_->HasPendingInstall(
+      imageloader::kTerminaComponentName));
+  EXPECT_TRUE(
+      component_manager_->UpdateRequested(imageloader::kTerminaComponentName));
+  component_manager_->FinishLoadRequest(
+      imageloader::kTerminaComponentName,
+      ComponentInfo(ComponentError::NONE, component_install_path_,
+                    component_mount_path_));
+  run_loop_.Run();
+  run_loop_2_.Run();
+}
+
+TEST_F(TerminaComponentInstallTest, ComponentUpdatesOnlyOnce) {
+  component_manager_->set_supported_components(
+      {imageloader::kTerminaComponentName});
+  component_manager_->set_queue_load_requests(true);
+
+  termina_installer_.Install(base::BindOnce(&TerminaInstallTest::ExpectSuccess,
+                                            base::Unretained(this)));
+  EXPECT_TRUE(component_manager_->HasPendingInstall(
+      imageloader::kTerminaComponentName));
+  EXPECT_TRUE(
+      component_manager_->UpdateRequested(imageloader::kTerminaComponentName));
+  component_manager_->FinishLoadRequest(
+      imageloader::kTerminaComponentName,
+      ComponentInfo(ComponentError::NONE, component_install_path_,
+                    component_mount_path_));
+  run_loop_.Run();
+
+  termina_installer_.Install(base::DoNothing());
+  EXPECT_FALSE(
+      component_manager_->UpdateRequested(imageloader::kTerminaComponentName));
+}
+
+TEST_F(TerminaComponentInstallTest, UpdateComponentErrorRetry) {
+  component_manager_->set_supported_components(
+      {imageloader::kTerminaComponentName});
+  component_manager_->set_queue_load_requests(true);
+  component_manager_->RegisterCompatiblePath(imageloader::kTerminaComponentName,
+                                             component_install_path_);
+
+  termina_installer_.Install(base::BindOnce(&TerminaInstallTest::ExpectSuccess,
+                                            base::Unretained(this)));
+  EXPECT_TRUE(component_manager_->HasPendingInstall(
+      imageloader::kTerminaComponentName));
+  EXPECT_TRUE(
+      component_manager_->UpdateRequested(imageloader::kTerminaComponentName));
+  component_manager_->FinishLoadRequest(
+      imageloader::kTerminaComponentName,
+      ComponentInfo(ComponentError::INSTALL_FAILURE, base::FilePath(),
+                    base::FilePath()));
+
+  EXPECT_TRUE(component_manager_->HasPendingInstall(
+      imageloader::kTerminaComponentName));
+  EXPECT_FALSE(
+      component_manager_->UpdateRequested(imageloader::kTerminaComponentName));
+  component_manager_->FinishLoadRequest(
+      imageloader::kTerminaComponentName,
+      ComponentInfo(ComponentError::NONE, component_install_path_,
+                    component_mount_path_));
+
+  termina_installer_.Install(base::BindOnce(&TerminaInstallTest::ExpectSuccess2,
+                                            base::Unretained(this)));
+  EXPECT_TRUE(component_manager_->HasPendingInstall(
+      imageloader::kTerminaComponentName));
+  EXPECT_TRUE(
+      component_manager_->UpdateRequested(imageloader::kTerminaComponentName));
+  component_manager_->FinishLoadRequest(
+      imageloader::kTerminaComponentName,
+      ComponentInfo(ComponentError::NONE, component_install_path_,
+                    component_mount_path_));
+
+  run_loop_.Run();
+  run_loop_2_.Run();
+}
+
+TEST_F(TerminaComponentInstallTest, InstallComponentErrorNoRetry) {
+  component_manager_->set_supported_components(
+      {imageloader::kTerminaComponentName});
+  component_manager_->set_queue_load_requests(true);
+
+  termina_installer_.Install(base::BindOnce(&TerminaInstallTest::ExpectFailure,
+                                            base::Unretained(this)));
+  EXPECT_TRUE(component_manager_->HasPendingInstall(
+      imageloader::kTerminaComponentName));
+  EXPECT_TRUE(
+      component_manager_->UpdateRequested(imageloader::kTerminaComponentName));
+  component_manager_->FinishLoadRequest(
+      imageloader::kTerminaComponentName,
+      ComponentInfo(ComponentError::INSTALL_FAILURE, base::FilePath(),
+                    base::FilePath()));
+
+  termina_installer_.Install(base::BindOnce(&TerminaInstallTest::ExpectSuccess2,
+                                            base::Unretained(this)));
+  EXPECT_TRUE(component_manager_->HasPendingInstall(
+      imageloader::kTerminaComponentName));
+  EXPECT_TRUE(
+      component_manager_->UpdateRequested(imageloader::kTerminaComponentName));
+  component_manager_->FinishLoadRequest(
+      imageloader::kTerminaComponentName,
+      ComponentInfo(ComponentError::NONE, component_install_path_,
+                    component_mount_path_));
+
+  run_loop_.Run();
+  run_loop_2_.Run();
+}
+
+TEST_F(TerminaComponentInstallTest, UpdateInProgressTriggersRetry) {
+  component_manager_->set_supported_components(
+      {imageloader::kTerminaComponentName});
+  component_manager_->set_queue_load_requests(true);
+
+  termina_installer_.Install(base::BindOnce(&TerminaInstallTest::ExpectSuccess,
+                                            base::Unretained(this)));
+  EXPECT_TRUE(component_manager_->HasPendingInstall(
+      imageloader::kTerminaComponentName));
+  EXPECT_TRUE(
+      component_manager_->UpdateRequested(imageloader::kTerminaComponentName));
+  component_manager_->FinishLoadRequest(
+      imageloader::kTerminaComponentName,
+      ComponentInfo(ComponentError::UPDATE_IN_PROGRESS, base::FilePath(),
+                    base::FilePath()));
+
+  task_env_.FastForwardBy(base::TimeDelta::FromSeconds(6));
+
+  EXPECT_TRUE(component_manager_->HasPendingInstall(
+      imageloader::kTerminaComponentName));
+  EXPECT_TRUE(
+      component_manager_->UpdateRequested(imageloader::kTerminaComponentName));
+  component_manager_->FinishLoadRequest(
+      imageloader::kTerminaComponentName,
+      ComponentInfo(ComponentError::NONE, component_install_path_,
+                    component_mount_path_));
+  run_loop_.Run();
+}
+
+}  // namespace crostini
diff --git a/chrome/browser/chromeos/guest_os/guest_os_share_path_unittest.cc b/chrome/browser/chromeos/guest_os/guest_os_share_path_unittest.cc
index 0e15c174..08c9d30 100644
--- a/chrome/browser/chromeos/guest_os/guest_os_share_path_unittest.cc
+++ b/chrome/browser/chromeos/guest_os/guest_os_share_path_unittest.cc
@@ -21,12 +21,15 @@
 #include "chrome/browser/chromeos/file_system_provider/service_factory.h"
 #include "chrome/browser/chromeos/guest_os/guest_os_pref_names.h"
 #include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h"
+#include "chrome/browser/component_updater/fake_cros_component_manager.h"
 #include "chrome/common/chrome_features.h"
+#include "chrome/test/base/browser_process_platform_part_test_api_chromeos.h"
 #include "chrome/test/base/scoped_testing_local_state.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/dlcservice/dlcservice_client.h"
 #include "chromeos/dbus/fake_cicerone_client.h"
 #include "chromeos/dbus/fake_concierge_client.h"
 #include "chromeos/dbus/fake_seneschal_client.h"
@@ -209,7 +212,8 @@
 
   GuestOsSharePathTest()
       : local_state_(std::make_unique<ScopedTestingLocalState>(
-            TestingBrowserProcess::GetGlobal())) {
+            TestingBrowserProcess::GetGlobal())),
+        browser_part_(g_browser_process->platform_part()) {
     chromeos::DBusThreadManager::Initialize();
     fake_concierge_client_ = static_cast<chromeos::FakeConciergeClient*>(
         chromeos::DBusThreadManager::Get()->GetConciergeClient());
@@ -246,6 +250,17 @@
   }
 
   void SetUp() override {
+    component_manager_ =
+        base::MakeRefCounted<component_updater::FakeCrOSComponentManager>();
+    component_manager_->set_supported_components({"cros-termina"});
+    component_manager_->ResetComponentState(
+        "cros-termina",
+        component_updater::FakeCrOSComponentManager::ComponentInfo(
+            component_updater::CrOSComponentManager::Error::NONE,
+            base::FilePath("/install/path"), base::FilePath("/mount/path")));
+    browser_part_.InitializeCrosComponentManager(component_manager_);
+    chromeos::DlcserviceClient::InitializeFake();
+
     run_loop_ = std::make_unique<base::RunLoop>();
     profile_ = std::make_unique<TestingProfile>();
     guest_os_share_path_ = GuestOsSharePath::GetForProfile(profile());
@@ -283,6 +298,9 @@
     run_loop_.reset();
     scoped_user_manager_.reset();
     profile_.reset();
+    chromeos::DlcserviceClient::Shutdown();
+    browser_part_.ShutdownCrosComponentManager();
+    component_manager_.reset();
   }
 
   chromeos::FakeChromeUserManager* GetFakeUserManager() const {
@@ -314,6 +332,8 @@
 
  private:
   std::unique_ptr<ScopedTestingLocalState> local_state_;
+  scoped_refptr<component_updater::FakeCrOSComponentManager> component_manager_;
+  BrowserProcessPlatformPartTestApi browser_part_;
 
   DISALLOW_COPY_AND_ASSIGN(GuestOsSharePathTest);
 };
diff --git a/chrome/browser/chromeos/platform_keys/extension_platform_keys_service.cc b/chrome/browser/chromeos/platform_keys/extension_platform_keys_service.cc
index 8cba1a0c..ee9037d 100644
--- a/chrome/browser/chromeos/platform_keys/extension_platform_keys_service.cc
+++ b/chrome/browser/chromeos/platform_keys/extension_platform_keys_service.cc
@@ -55,16 +55,18 @@
 
 // Converts |token_ids| (string-based token identifiers used in the
 // platformKeys API) to a vector of KeyPermissions::KeyLocation.
-std::vector<KeyPermissions::KeyLocation> TokenIdsToKeyLocations(
+std::vector<platform_keys::KeyPermissions::KeyLocation> TokenIdsToKeyLocations(
     const std::vector<platform_keys::TokenId>& token_ids) {
-  std::vector<KeyPermissions::KeyLocation> key_locations;
+  std::vector<platform_keys::KeyPermissions::KeyLocation> key_locations;
   for (const auto& token_id : token_ids) {
     switch (token_id) {
       case platform_keys::TokenId::kUser:
-        key_locations.push_back(KeyPermissions::KeyLocation::kUserSlot);
+        key_locations.push_back(
+            platform_keys::KeyPermissions::KeyLocation::kUserSlot);
         break;
       case platform_keys::TokenId::kSystem:
-        key_locations.push_back(KeyPermissions::KeyLocation::kSystemSlot);
+        key_locations.push_back(
+            platform_keys::KeyPermissions::KeyLocation::kSystemSlot);
         break;
     }
   }
@@ -96,7 +98,7 @@
   GenerateKeyTask(platform_keys::TokenId token_id,
                   const std::string& extension_id,
                   const GenerateKeyCallback& callback,
-                  KeyPermissions* key_permissions,
+                  platform_keys::KeyPermissions* key_permissions,
                   ExtensionPlatformKeysService* service)
       : token_id_(token_id),
         extension_id_(extension_id),
@@ -120,9 +122,9 @@
   std::string public_key_spki_der_;
   const std::string extension_id_;
   GenerateKeyCallback callback_;
-  std::unique_ptr<KeyPermissions::PermissionsForExtension>
+  std::unique_ptr<platform_keys::KeyPermissions::PermissionsForExtension>
       extension_permissions_;
-  KeyPermissions* const key_permissions_;
+  platform_keys::KeyPermissions* const key_permissions_;
   ExtensionPlatformKeysService* const service_;
 
  private:
@@ -170,7 +172,7 @@
   }
 
   void UpdatePermissionsAndCallBack() {
-    std::vector<KeyPermissions::KeyLocation> key_locations =
+    std::vector<platform_keys::KeyPermissions::KeyLocation> key_locations =
         TokenIdsToKeyLocations({token_id_});
     extension_permissions_->RegisterKeyForCorporateUsage(public_key_spki_der_,
                                                          key_locations);
@@ -179,8 +181,9 @@
     return;
   }
 
-  void GotPermissions(std::unique_ptr<KeyPermissions::PermissionsForExtension>
-                          extension_permissions) {
+  void GotPermissions(
+      std::unique_ptr<platform_keys::KeyPermissions::PermissionsForExtension>
+          extension_permissions) {
     extension_permissions_ = std::move(extension_permissions);
     DoStep();
   }
@@ -202,7 +205,7 @@
                      unsigned int modulus_length,
                      const std::string& extension_id,
                      const GenerateKeyCallback& callback,
-                     KeyPermissions* key_permissions,
+                     platform_keys::KeyPermissions* key_permissions,
                      ExtensionPlatformKeysService* service)
       : GenerateKeyTask(token_id,
                         extension_id,
@@ -232,7 +235,7 @@
                     const std::string& named_curve,
                     const std::string& extension_id,
                     const GenerateKeyCallback& callback,
-                    KeyPermissions* key_permissions,
+                    platform_keys::KeyPermissions* key_permissions,
                     ExtensionPlatformKeysService* service)
       : GenerateKeyTask(token_id,
                         extension_id,
@@ -277,7 +280,7 @@
            platform_keys::HashAlgorithm hash_algorithm,
            const std::string& extension_id,
            const SignCallback& callback,
-           KeyPermissions* key_permissions,
+           platform_keys::KeyPermissions* key_permissions,
            ExtensionPlatformKeysService* service)
       : token_id_(token_id),
         data_(data),
@@ -343,8 +346,9 @@
         base::Bind(&SignTask::GotPermissions, base::Unretained(this)));
   }
 
-  void GotPermissions(std::unique_ptr<KeyPermissions::PermissionsForExtension>
-                          extension_permissions) {
+  void GotPermissions(
+      std::unique_ptr<platform_keys::KeyPermissions::PermissionsForExtension>
+          extension_permissions) {
     extension_permissions_ = std::move(extension_permissions);
     DoStep();
   }
@@ -417,10 +421,10 @@
   const platform_keys::HashAlgorithm hash_algorithm_;
   const std::string extension_id_;
   const SignCallback callback_;
-  std::unique_ptr<KeyPermissions::PermissionsForExtension>
+  std::unique_ptr<platform_keys::KeyPermissions::PermissionsForExtension>
       extension_permissions_;
-  KeyPermissions* const key_permissions_;
-  std::vector<KeyPermissions::KeyLocation> key_locations_;
+  platform_keys::KeyPermissions* const key_permissions_;
+  std::vector<platform_keys::KeyPermissions::KeyLocation> key_locations_;
   ExtensionPlatformKeysService* const service_;
   base::WeakPtrFactory<SignTask> weak_factory_{this};
 
@@ -453,7 +457,7 @@
              const std::string& extension_id,
              const SelectCertificatesCallback& callback,
              content::WebContents* web_contents,
-             KeyPermissions* key_permissions,
+             platform_keys::KeyPermissions* key_permissions,
              ExtensionPlatformKeysService* service)
       : request_(request),
         input_client_certificates_(std::move(input_client_certificates)),
@@ -521,8 +525,9 @@
         base::Bind(&SelectTask::GotPermissions, base::Unretained(this)));
   }
 
-  void GotPermissions(std::unique_ptr<KeyPermissions::PermissionsForExtension>
-                          extension_permissions) {
+  void GotPermissions(
+      std::unique_ptr<platform_keys::KeyPermissions::PermissionsForExtension>
+          extension_permissions) {
     extension_permissions_ = std::move(extension_permissions);
     DoStep();
   }
@@ -610,7 +615,7 @@
     const std::string public_key_spki_der(
         platform_keys::GetSubjectPublicKeyInfo(certificate));
 
-    std::vector<KeyPermissions::KeyLocation> key_locations =
+    std::vector<platform_keys::KeyPermissions::KeyLocation> key_locations =
         TokenIdsToKeyLocations(token_ids);
 
     // Use this key if the user can use it for signing or can grant permission
@@ -726,7 +731,8 @@
   net::CertificateList matches_;
   // Mapping of DER-encoded Subject Public Key Info to the KeyLocations
   // determined for the corresponding private key.
-  base::flat_map<std::string, std::vector<KeyPermissions::KeyLocation>>
+  base::flat_map<std::string,
+                 std::vector<platform_keys::KeyPermissions::KeyLocation>>
       key_locations_for_matches_;
   scoped_refptr<net::X509Certificate> selected_cert_;
   platform_keys::ClientCertificateRequest request_;
@@ -735,9 +741,9 @@
   const std::string extension_id_;
   const SelectCertificatesCallback callback_;
   content::WebContents* const web_contents_;
-  std::unique_ptr<KeyPermissions::PermissionsForExtension>
+  std::unique_ptr<platform_keys::KeyPermissions::PermissionsForExtension>
       extension_permissions_;
-  KeyPermissions* const key_permissions_;
+  platform_keys::KeyPermissions* const key_permissions_;
   ExtensionPlatformKeysService* const service_;
   base::WeakPtrFactory<SelectTask> weak_factory_{this};
 
diff --git a/chrome/browser/chromeos/platform_keys/extension_platform_keys_service.h b/chrome/browser/chromeos/platform_keys/extension_platform_keys_service.h
index ddb3f58..47f7be1 100644
--- a/chrome/browser/chromeos/platform_keys/extension_platform_keys_service.h
+++ b/chrome/browser/chromeos/platform_keys/extension_platform_keys_service.h
@@ -13,7 +13,7 @@
 #include "base/containers/queue.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
-#include "chrome/browser/chromeos/platform_keys/key_permissions.h"
+#include "chrome/browser/chromeos/platform_keys/key_permissions/key_permissions.h"
 #include "chrome/browser/chromeos/platform_keys/platform_keys_service.h"
 #include "components/keyed_service/core/keyed_service.h"
 
@@ -225,7 +225,7 @@
 
   content::BrowserContext* const browser_context_ = nullptr;
   platform_keys::PlatformKeysService* const platform_keys_service_ = nullptr;
-  KeyPermissions key_permissions_;
+  platform_keys::KeyPermissions key_permissions_;
   std::unique_ptr<SelectDelegate> select_delegate_;
   base::queue<std::unique_ptr<Task>> tasks_;
   base::WeakPtrFactory<ExtensionPlatformKeysService> weak_factory_{this};
diff --git a/chrome/browser/chromeos/platform_keys/key_permissions/README.md b/chrome/browser/chromeos/platform_keys/key_permissions/README.md
new file mode 100644
index 0000000..b6e905c
--- /dev/null
+++ b/chrome/browser/chromeos/platform_keys/key_permissions/README.md
@@ -0,0 +1,37 @@
+# Key Permissions
+
+This directory contains code managing platform key permissions.
+
+## Key Usages
+
+This can only be “corporate” or undefined. If a key is marked for “corporate”
+usage, only extensions listed in
+[KeyPermissions](https://cloud.google.com/docs/chrome-enterprise/policies/?policy=KeyPermissions)
+policy will be allowed to access this key via chrome.platformKeys and
+chrome.enterprise.platformKeys APIs. Key Usages are considered to be
+properties / metadata attached to keys themselves. This metadata was
+historically persisted in a chromium Preference, but is now being migrated to
+the backing key store (which is implemented by the chaps daemon on Chrome OS).
+
+Usage of keys/certificates for network authentication and TLS client
+authentication is currently not restricted by key usages, but this may change in
+the future.
+
+## Signing Permissions for Extensions
+
+A (key, extension id) pair can have one of the following signing permissions:
+
+* The key can be used once for signing. This permission is granted if an
+extension generated the key using the enterprise.platformKeys API, so that it
+can build a certification request.
+
+* The key can not be used for signing. That will happen after an extension
+generates a key using the enterprise.platformKeys API, and signs using it for
+the first time to build a certification request.
+
+* The key can be used for signing unlimited number of times. This permission is
+granted by the user (only when the key is non-corporate and the profile is
+non-managed) or the KeyPermissions policy to allow the extension to use the
+key for signing through the
+[enterprise.platformKeys](https://developer.chrome.com/extensions/enterprise_platformKeys)
+or [platformKeys](https://developer.chrome.com/extensions/platformKeys) API.
diff --git a/chrome/browser/chromeos/platform_keys/key_permissions.cc b/chrome/browser/chromeos/platform_keys/key_permissions/key_permissions.cc
similarity index 98%
rename from chrome/browser/chromeos/platform_keys/key_permissions.cc
rename to chrome/browser/chromeos/platform_keys/key_permissions/key_permissions.cc
index ff9b4c1c..705b6a4 100644
--- a/chrome/browser/chromeos/platform_keys/key_permissions.cc
+++ b/chrome/browser/chromeos/platform_keys/key_permissions/key_permissions.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/chromeos/platform_keys/key_permissions.h"
+#include "chrome/browser/chromeos/platform_keys/key_permissions/key_permissions.h"
 
 #include <utility>
 
@@ -23,9 +23,9 @@
 #include "extensions/browser/state_store.h"
 
 namespace chromeos {
+namespace platform_keys {
 
 namespace {
-
 // The key at which platform key specific data is stored in each extension's
 // state store.
 //
@@ -190,8 +190,7 @@
     KeyEntriesFromState(*state_store_value);
 }
 
-KeyPermissions::PermissionsForExtension::~PermissionsForExtension() {
-}
+KeyPermissions::PermissionsForExtension::~PermissionsForExtension() {}
 
 bool KeyPermissions::PermissionsForExtension::CanUseKeyForSigning(
     const std::string& public_key_spki_der,
@@ -410,8 +409,7 @@
   DCHECK(!profile_is_managed_ || profile_policies_);
 }
 
-KeyPermissions::~KeyPermissions() {
-}
+KeyPermissions::~KeyPermissions() {}
 
 void KeyPermissions::GetPermissionsForExtension(
     const std::string& extension_id,
@@ -519,4 +517,5 @@
       extension_id, kStateStorePlatformKeys, std::move(value));
 }
 
+}  // namespace platform_keys
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/platform_keys/key_permissions.h b/chrome/browser/chromeos/platform_keys/key_permissions/key_permissions.h
similarity index 97%
rename from chrome/browser/chromeos/platform_keys/key_permissions.h
rename to chrome/browser/chromeos/platform_keys/key_permissions/key_permissions.h
index dbc40f5..e2c3874f 100644
--- a/chrome/browser/chromeos/platform_keys/key_permissions.h
+++ b/chrome/browser/chromeos/platform_keys/key_permissions/key_permissions.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_CHROMEOS_PLATFORM_KEYS_KEY_PERMISSIONS_H_
-#define CHROME_BROWSER_CHROMEOS_PLATFORM_KEYS_KEY_PERMISSIONS_H_
+#ifndef CHROME_BROWSER_CHROMEOS_PLATFORM_KEYS_KEY_PERMISSIONS_KEY_PERMISSIONS_H_
+#define CHROME_BROWSER_CHROMEOS_PLATFORM_KEYS_KEY_PERMISSIONS_KEY_PERMISSIONS_H_
 
 #include <memory>
 #include <string>
@@ -32,6 +32,7 @@
 }
 
 namespace chromeos {
+namespace platform_keys {
 
 // This class manages permissions for extensions to use private keys through
 // chrome.platformKeys or chrome.enterprise.platformKeys .
@@ -234,6 +235,7 @@
   DISALLOW_COPY_AND_ASSIGN(KeyPermissions);
 };
 
+}  // namespace platform_keys
 }  // namespace chromeos
 
-#endif  // CHROME_BROWSER_CHROMEOS_PLATFORM_KEYS_KEY_PERMISSIONS_H_
+#endif  // CHROME_BROWSER_CHROMEOS_PLATFORM_KEYS_KEY_PERMISSIONS_KEY_PERMISSIONS_H_
diff --git a/chrome/browser/chromeos/platform_keys/key_permissions_policy_handler.cc b/chrome/browser/chromeos/platform_keys/key_permissions/key_permissions_policy_handler.cc
similarity index 74%
rename from chrome/browser/chromeos/platform_keys/key_permissions_policy_handler.cc
rename to chrome/browser/chromeos/platform_keys/key_permissions/key_permissions_policy_handler.cc
index 79db821a..e24bfea 100644
--- a/chrome/browser/chromeos/platform_keys/key_permissions_policy_handler.cc
+++ b/chrome/browser/chromeos/platform_keys/key_permissions/key_permissions_policy_handler.cc
@@ -2,24 +2,24 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/chromeos/platform_keys/key_permissions_policy_handler.h"
+#include "chrome/browser/chromeos/platform_keys/key_permissions/key_permissions_policy_handler.h"
 
 #include "components/policy/core/common/schema.h"
 #include "components/policy/policy_constants.h"
 
 namespace chromeos {
+namespace platform_keys {
 
 KeyPermissionsPolicyHandler::KeyPermissionsPolicyHandler(
     const policy::Schema& chrome_schema)
     : policy::SchemaValidatingPolicyHandler(
           policy::key::kKeyPermissions,
           chrome_schema.GetKnownProperty(policy::key::kKeyPermissions),
-          policy::SCHEMA_ALLOW_UNKNOWN) {
-}
+          policy::SCHEMA_ALLOW_UNKNOWN) {}
 
 void KeyPermissionsPolicyHandler::ApplyPolicySettings(
     const policy::PolicyMap& /* policies */,
-    PrefValueMap* /* prefs */) {
-}
+    PrefValueMap* /* prefs */) {}
 
+}  // namespace platform_keys
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/platform_keys/key_permissions_policy_handler.h b/chrome/browser/chromeos/platform_keys/key_permissions/key_permissions_policy_handler.h
similarity index 74%
rename from chrome/browser/chromeos/platform_keys/key_permissions_policy_handler.h
rename to chrome/browser/chromeos/platform_keys/key_permissions/key_permissions_policy_handler.h
index 67d6aac..27c8389 100644
--- a/chrome/browser/chromeos/platform_keys/key_permissions_policy_handler.h
+++ b/chrome/browser/chromeos/platform_keys/key_permissions/key_permissions_policy_handler.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_CHROMEOS_PLATFORM_KEYS_KEY_PERMISSIONS_POLICY_HANDLER_H_
-#define CHROME_BROWSER_CHROMEOS_PLATFORM_KEYS_KEY_PERMISSIONS_POLICY_HANDLER_H_
+#ifndef CHROME_BROWSER_CHROMEOS_PLATFORM_KEYS_KEY_PERMISSIONS_KEY_PERMISSIONS_POLICY_HANDLER_H_
+#define CHROME_BROWSER_CHROMEOS_PLATFORM_KEYS_KEY_PERMISSIONS_KEY_PERMISSIONS_POLICY_HANDLER_H_
 
 #include "base/macros.h"
 #include "components/policy/core/browser/configuration_policy_handler.h"
@@ -13,6 +13,7 @@
 }
 
 namespace chromeos {
+namespace platform_keys {
 
 class KeyPermissionsPolicyHandler
     : public policy::SchemaValidatingPolicyHandler {
@@ -27,6 +28,7 @@
   DISALLOW_COPY_AND_ASSIGN(KeyPermissionsPolicyHandler);
 };
 
+}  // namespace platform_keys
 }  // namespace chromeos
 
-#endif  // CHROME_BROWSER_CHROMEOS_PLATFORM_KEYS_KEY_PERMISSIONS_POLICY_HANDLER_H_
+#endif  // CHROME_BROWSER_CHROMEOS_PLATFORM_KEYS_KEY_PERMISSIONS_KEY_PERMISSIONS_POLICY_HANDLER_H_
diff --git a/chrome/browser/chromeos/plugin_vm/plugin_vm_installer_unittest.cc b/chrome/browser/chromeos/plugin_vm/plugin_vm_installer_unittest.cc
index 85cacbd5..ef29685 100644
--- a/chrome/browser/chromeos/plugin_vm/plugin_vm_installer_unittest.cc
+++ b/chrome/browser/chromeos/plugin_vm/plugin_vm_installer_unittest.cc
@@ -697,7 +697,7 @@
 
 TEST_F(PluginVmInstallerDriveTest, SuccessfulDriveDownloadTest) {
   SetPluginVmImagePref(kDriveUrl, kHash);
-  fake_dlcservice_client_->SetInstallError(dlcservice::kErrorNone);
+  fake_dlcservice_client_->set_install_error(dlcservice::kErrorNone);
 
   ExpectObserverEventsUntil(InstallingState::kImporting);
   EXPECT_CALL(*observer_, OnDownloadProgressUpdated(_, std::strlen(kContent)))
@@ -711,7 +711,7 @@
 
 TEST_F(PluginVmInstallerDriveTest, InstallingPluingVmDlcInternal) {
   SetPluginVmImagePref(kDriveUrl, kHash);
-  fake_dlcservice_client_->SetInstallError(dlcservice::kErrorInternal);
+  fake_dlcservice_client_->set_install_error(dlcservice::kErrorInternal);
 
   ExpectObserverEventsUntil(InstallingState::kDownloadingDlc);
   EXPECT_CALL(*observer_, OnError(FailureReason::DLC_INTERNAL));
@@ -724,7 +724,7 @@
 
 TEST_F(PluginVmInstallerDriveTest, InstallingPluingVmDlcBusy) {
   SetPluginVmImagePref(kDriveUrl, kHash);
-  fake_dlcservice_client_->SetInstallError(dlcservice::kErrorBusy);
+  fake_dlcservice_client_->set_install_error(dlcservice::kErrorBusy);
 
   ExpectObserverEventsUntil(InstallingState::kDownloadingDlc);
   EXPECT_CALL(*observer_, OnError(FailureReason::DLC_BUSY));
@@ -736,7 +736,7 @@
 
 TEST_F(PluginVmInstallerDriveTest, InstallingPluginVmDlcNeedReboot) {
   SetPluginVmImagePref(kDriveUrl, kHash);
-  fake_dlcservice_client_->SetInstallError(dlcservice::kErrorNeedReboot);
+  fake_dlcservice_client_->set_install_error(dlcservice::kErrorNeedReboot);
 
   ExpectObserverEventsUntil(InstallingState::kDownloadingDlc);
   EXPECT_CALL(*observer_, OnError(FailureReason::DLC_NEED_REBOOT));
@@ -749,7 +749,7 @@
 
 TEST_F(PluginVmInstallerDriveTest, InstallingPluginVmDlcNeedSpace) {
   SetPluginVmImagePref(kDriveUrl, kHash);
-  fake_dlcservice_client_->SetInstallError(dlcservice::kErrorAllocation);
+  fake_dlcservice_client_->set_install_error(dlcservice::kErrorAllocation);
 
   ExpectObserverEventsUntil(InstallingState::kDownloadingDlc);
   EXPECT_CALL(*observer_, OnError(FailureReason::DLC_NEED_SPACE));
@@ -762,7 +762,7 @@
 
 TEST_F(PluginVmInstallerDriveTest, InstallingPluginVmDlcWhenUnsupported) {
   SetPluginVmImagePref(kDriveUrl, kHash);
-  fake_dlcservice_client_->SetInstallError(dlcservice::kErrorInvalidDlc);
+  fake_dlcservice_client_->set_install_error(dlcservice::kErrorInvalidDlc);
 
   ExpectObserverEventsUntil(InstallingState::kDownloadingDlc);
   EXPECT_CALL(*observer_, OnError(FailureReason::DLC_UNSUPPORTED));
diff --git a/chrome/browser/chromeos/web_applications/telemetry_extension_integration_browsertest.cc b/chrome/browser/chromeos/web_applications/telemetry_extension_integration_browsertest.cc
index c717ad5..4552c3d 100644
--- a/chrome/browser/chromeos/web_applications/telemetry_extension_integration_browsertest.cc
+++ b/chrome/browser/chromeos/web_applications/telemetry_extension_integration_browsertest.cc
@@ -23,7 +23,7 @@
 namespace {
 constexpr char kNonExistentUrlPath[] = "non-existent-url.html";
 constexpr char kLoadFromDiskUrlPath[] = "telemetry_extension_test.html";
-constexpr char kRegisteredUrlPath[] = "untrusted.html";
+constexpr char kRegisteredUrlPath[] = "dpsl.js";
 }  // namespace
 
 class TelemetryExtensionIntegrationTest : public SystemWebAppIntegrationTest {
@@ -73,10 +73,6 @@
   // The |registered_resource_gurl| is a file that is included in the
   // TelemteryExtensionUntrustedSource's list of registered resources.
   EXPECT_TRUE(content::NavigateToURL(web_contents, registered_resource_gurl));
-
-  // Verify that the file loaded from disk has the expected title.
-  EXPECT_EQ(base::UTF8ToUTF16("Untrusted Telemetry Extension"),
-            web_contents->GetTitle());
 }
 
 INSTANTIATE_TEST_SUITE_P(All,
diff --git a/chrome/browser/client_hints/client_hints_browsertest.cc b/chrome/browser/client_hints/client_hints_browsertest.cc
index 74673a05..3ea2395 100644
--- a/chrome/browser/client_hints/client_hints_browsertest.cc
+++ b/chrome/browser/client_hints/client_hints_browsertest.cc
@@ -23,7 +23,7 @@
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/browser/cookie_settings.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/content_settings/core/common/pref_names.h"
@@ -300,10 +300,10 @@
   // Verify that the user is not notified that cookies or JavaScript were
   // blocked on the webpage due to the checks done by client hints.
   void VerifyContentSettingsNotNotified() const {
-    auto* tscs = content_settings::TabSpecificContentSettings::GetForFrame(
+    auto* pscs = content_settings::PageSpecificContentSettings::GetForFrame(
         browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame());
-    EXPECT_FALSE(tscs->IsContentBlocked(ContentSettingsType::COOKIES));
-    EXPECT_FALSE(tscs->IsContentBlocked(ContentSettingsType::JAVASCRIPT));
+    EXPECT_FALSE(pscs->IsContentBlocked(ContentSettingsType::COOKIES));
+    EXPECT_FALSE(pscs->IsContentBlocked(ContentSettingsType::JAVASCRIPT));
   }
 
   void SetExpectedEffectiveConnectionType(
diff --git a/chrome/browser/clipboard/clipboard_read_write_permission_context.cc b/chrome/browser/clipboard/clipboard_read_write_permission_context.cc
index 8cdd8dc..b659241 100644
--- a/chrome/browser/clipboard/clipboard_read_write_permission_context.cc
+++ b/chrome/browser/clipboard/clipboard_read_write_permission_context.cc
@@ -5,7 +5,7 @@
 #include "chrome/browser/clipboard/clipboard_read_write_permission_context.h"
 
 #include "chrome/common/chrome_features.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "components/permissions/permission_request_id.h"
 #include "third_party/blink/public/mojom/feature_policy/feature_policy.mojom.h"
@@ -23,8 +23,8 @@
     const permissions::PermissionRequestID& id,
     const GURL& requesting_frame,
     bool allowed) {
-  content_settings::TabSpecificContentSettings* content_settings =
-      content_settings::TabSpecificContentSettings::GetForFrame(
+  content_settings::PageSpecificContentSettings* content_settings =
+      content_settings::PageSpecificContentSettings::GetForFrame(
           id.render_process_id(), id.render_frame_id());
   if (!content_settings)
     return;
diff --git a/chrome/browser/component_updater/fake_cros_component_manager.cc b/chrome/browser/component_updater/fake_cros_component_manager.cc
index 2578e13..f4659ca 100644
--- a/chrome/browser/component_updater/fake_cros_component_manager.cc
+++ b/chrome/browser/component_updater/fake_cros_component_manager.cc
@@ -137,7 +137,7 @@
   }
   mounted_components_.erase(name);
   installed_components_.erase(name);
-  return true;
+  return unload_component_result_;
 }
 
 void FakeCrOSComponentManager::RegisterCompatiblePath(
diff --git a/chrome/browser/component_updater/fake_cros_component_manager.h b/chrome/browser/component_updater/fake_cros_component_manager.h
index 04956b8..6465aa23 100644
--- a/chrome/browser/component_updater/fake_cros_component_manager.h
+++ b/chrome/browser/component_updater/fake_cros_component_manager.h
@@ -51,6 +51,9 @@
   void set_supported_components(const std::set<std::string>& components) {
     supported_components_ = components;
   }
+  void set_unload_component_result(bool result) {
+    unload_component_result_ = result;
+  }
   void SetRegisteredComponents(const std::set<std::string>& components);
 
   // Finishes a queued component load request. Should be used only if
@@ -130,6 +133,8 @@
   // FinishLoadRequest().
   bool queue_load_requests_ = false;
 
+  bool unload_component_result_ = true;
+
   // Set of components that can be handled by this component manager.
   std::set<std::string> supported_components_;
 
diff --git a/chrome/browser/content_settings/content_settings_browsertest.cc b/chrome/browser/content_settings/content_settings_browsertest.cc
index 47b4ecb..56b3468 100644
--- a/chrome/browser/content_settings/content_settings_browsertest.cc
+++ b/chrome/browser/content_settings/content_settings_browsertest.cc
@@ -27,7 +27,7 @@
 #include "chrome/test/base/test_launcher_utils.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/browsing_data/content/cookie_helper.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/browser/cookie_settings.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/content_settings/core/common/content_settings.h"
@@ -67,23 +67,23 @@
 #endif
 
 using content::BrowserThread;
-using content_settings::TabSpecificContentSettings;
+using content_settings::PageSpecificContentSettings;
 using net::URLRequestMockHTTPJob;
 
 namespace {
 
 browsing_data::CannedCookieHelper* GetSiteSettingsCookieContainer(
     Browser* browser) {
-  TabSpecificContentSettings* settings =
-      TabSpecificContentSettings::GetForFrame(
+  PageSpecificContentSettings* settings =
+      PageSpecificContentSettings::GetForFrame(
           browser->tab_strip_model()->GetActiveWebContents()->GetMainFrame());
   return settings->allowed_local_shared_objects().cookies();
 }
 
 browsing_data::CannedCookieHelper* GetSiteSettingsBlockedCookieContainer(
     Browser* browser) {
-  TabSpecificContentSettings* settings =
-      TabSpecificContentSettings::GetForFrame(
+  PageSpecificContentSettings* settings =
+      PageSpecificContentSettings::GetForFrame(
           browser->tab_strip_model()->GetActiveWebContents()->GetMainFrame());
   return settings->blocked_local_shared_objects().cookies();
 }
@@ -731,13 +731,13 @@
   ASSERT_TRUE(::testing::Mock::VerifyAndClearExpectations(&observer));
 
   EXPECT_TRUE(
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
           ->IsContentBlocked(ContentSettingsType::COOKIES));
 }
 
 // Any cookie access during a navigation does not end up in a new document (e.g.
 // due to the request returning HTTP 204) should not be tracked by the
-// TabSpecificContentSettings.
+// PageSpecificContentSettings.
 IN_PROC_BROWSER_TEST_F(ContentSettingsTest, CookiesIgnoredFor204) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
@@ -753,7 +753,7 @@
   ui_test_utils::NavigateToURL(browser(), test_url);
 
   EXPECT_FALSE(
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
           ->IsContentBlocked(ContentSettingsType::COOKIES));
 }
 
@@ -792,20 +792,20 @@
   content::RenderFrameHost* main_frame = web_contents->GetMainFrame();
 
   EXPECT_TRUE(
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
           ->IsContentBlocked(ContentSettingsType::COOKIES));
 
   ui_test_utils::NavigateToURL(browser(), other_url);
   EXPECT_TRUE(main_frame->IsInBackForwardCache());
   EXPECT_FALSE(
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
           ->IsContentBlocked(ContentSettingsType::COOKIES));
 
   web_contents->GetController().GoBack();
   EXPECT_TRUE(WaitForLoadStop(web_contents));
   EXPECT_EQ(main_frame, web_contents->GetMainFrame());
   EXPECT_TRUE(
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
           ->IsContentBlocked(ContentSettingsType::COOKIES));
 }
 
@@ -823,12 +823,12 @@
 
   ui_test_utils::NavigateToURL(browser(), test_url);
   EXPECT_TRUE(
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
           ->IsContentBlocked(ContentSettingsType::COOKIES));
 
   ui_test_utils::NavigateToURL(browser(), other_url);
   EXPECT_FALSE(
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
           ->IsContentBlocked(ContentSettingsType::COOKIES));
 
   // This triggers a OnContentSettingChanged notification that should be
@@ -839,7 +839,7 @@
   web_contents->GetController().GoBack();
   EXPECT_TRUE(WaitForLoadStop(web_contents));
   EXPECT_FALSE(
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
           ->IsContentBlocked(ContentSettingsType::COOKIES));
 }
 
@@ -896,7 +896,7 @@
   ASSERT_EQ(base::UTF8ToUTF16("Data URL"), web_contents->GetTitle());
 
   EXPECT_TRUE(
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
           ->IsContentBlocked(ContentSettingsType::JAVASCRIPT));
 }
 
@@ -922,7 +922,7 @@
       browser()->tab_strip_model()->GetActiveWebContents();
 
   EXPECT_TRUE(
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
           ->IsContentBlocked(ContentSettingsType::COOKIES));
 }
 
@@ -1062,7 +1062,7 @@
   ui_test_utils::WaitForViewVisibility(
       browser(), VIEW_ID_CONTENT_SETTING_JAVASCRIPT, true);
   EXPECT_TRUE(
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
           ->IsContentBlocked(ContentSettingsType::JAVASCRIPT));
   EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
 }
@@ -1211,10 +1211,10 @@
     ui_test_utils::NavigateToURL(browser(), url);
 
     EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
-    auto* tscs =
-        TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame());
+    auto* pscs =
+        PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame());
     EXPECT_EQ(!expect_loaded,
-              tscs && tscs->IsContentBlocked(ContentSettingsType::PLUGINS));
+              pscs && pscs->IsContentBlocked(ContentSettingsType::PLUGINS));
   }
 
   void RunJavaScriptBlockedTest(const char* path,
@@ -1253,11 +1253,11 @@
       // the TitleWatcher adding sufficient delay most of the time.
     }
 
-    TabSpecificContentSettings* tab_settings =
-        TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame());
+    PageSpecificContentSettings* settings =
+        PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame());
     EXPECT_EQ(expect_is_javascript_content_blocked,
-              tab_settings->IsContentBlocked(ContentSettingsType::JAVASCRIPT));
-    EXPECT_FALSE(tab_settings->IsContentBlocked(ContentSettingsType::PLUGINS));
+              settings->IsContentBlocked(ContentSettingsType::JAVASCRIPT));
+    EXPECT_FALSE(settings->IsContentBlocked(ContentSettingsType::PLUGINS));
   }
 
  private:
diff --git a/chrome/browser/content_settings/content_settings_manager_delegate.cc b/chrome/browser/content_settings/content_settings_manager_delegate.cc
index cb0287a..7fd77de 100644
--- a/chrome/browser/content_settings/content_settings_manager_delegate.cc
+++ b/chrome/browser/content_settings/content_settings_manager_delegate.cc
@@ -6,7 +6,7 @@
 
 #include "chrome/browser/content_settings/cookie_settings_factory.h"
 #include "chrome/browser/profiles/profile.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/browser/cookie_settings.h"
 #include "extensions/buildflags/buildflags.h"
 
@@ -25,7 +25,7 @@
     const GURL& url,
     base::OnceCallback<void(bool)> callback,
     bool allowed) {
-  content_settings::TabSpecificContentSettings::FileSystemAccessed(
+  content_settings::PageSpecificContentSettings::FileSystemAccessed(
       render_process_id, render_frame_id, url, !allowed);
   std::move(callback).Run(allowed);
 }
diff --git a/chrome/browser/content_settings/content_settings_usages_state_unittest.cc b/chrome/browser/content_settings/content_settings_usages_state_unittest.cc
index f35b3ac7..f7f1d08 100644
--- a/chrome/browser/content_settings/content_settings_usages_state_unittest.cc
+++ b/chrome/browser/content_settings/content_settings_usages_state_unittest.cc
@@ -7,7 +7,7 @@
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/permissions/permission_decision_auto_blocker_factory.h"
 #include "chrome/test/base/testing_profile.h"
-#include "components/content_settings/browser/test_tab_specific_content_settings_delegate.h"
+#include "components/content_settings/browser/test_page_specific_content_settings_delegate.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/permissions/permission_decision_auto_blocker.h"
 #include "components/permissions/permission_result.h"
@@ -17,16 +17,16 @@
 namespace {
 
 class TestDelegate
-    : public content_settings::TestTabSpecificContentSettingsDelegate {
+    : public content_settings::TestPageSpecificContentSettingsDelegate {
  public:
   explicit TestDelegate(
       HostContentSettingsMap* map,
       permissions::PermissionDecisionAutoBlocker* auto_blocker)
-      : TestTabSpecificContentSettingsDelegate(/*prefs=*/nullptr, map),
+      : TestPageSpecificContentSettingsDelegate(/*prefs=*/nullptr, map),
         auto_blocker_(auto_blocker) {}
 
  private:
-  // content_settings::TabSpecificContentSettings::Delegate:
+  // content_settings::PageSpecificContentSettings::Delegate:
   ContentSetting GetEmbargoSetting(const GURL& request_origin,
                                    ContentSettingsType permission) override {
     return auto_blocker_->GetEmbargoResult(request_origin, permission)
diff --git a/chrome/browser/content_settings/generated_cookie_prefs.cc b/chrome/browser/content_settings/generated_cookie_prefs.cc
index 1a5ee96c..4ad82c8f 100644
--- a/chrome/browser/content_settings/generated_cookie_prefs.cc
+++ b/chrome/browser/content_settings/generated_cookie_prefs.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/content_settings/generated_cookie_prefs.h"
 
+#include "base/notreached.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/extensions/api/settings_private.h"
@@ -93,6 +94,19 @@
              : extensions::settings_private::SetPrefResult::PREF_NOT_MODIFIABLE;
 }
 
+CookiePrimarySetting ToCookiePrimarySetting(
+    CookieControlsMode cookie_controls_mode) {
+  switch (cookie_controls_mode) {
+    case CookieControlsMode::kBlockThirdParty:
+      return CookiePrimarySetting::BLOCK_THIRD_PARTY;
+    case CookieControlsMode::kIncognitoOnly:
+      return CookiePrimarySetting::BLOCK_THIRD_PARTY_INCOGNITO;
+    case CookieControlsMode::kOff:
+      return CookiePrimarySetting::ALLOW_ALL;
+  }
+  NOTREACHED();
+}
+
 }  // namespace
 
 const char kCookiePrimarySetting[] = "generated.cookie_primary_setting";
@@ -107,10 +121,6 @@
 
   user_prefs_registrar_.Init(profile->GetPrefs());
   user_prefs_registrar_.Add(
-      prefs::kBlockThirdPartyCookies,
-      base::BindRepeating(&GeneratedCookiePrefBase::OnCookiePreferencesChanged,
-                          base::Unretained(this)));
-  user_prefs_registrar_.Add(
       prefs::kCookieControlsMode,
       base::BindRepeating(&GeneratedCookiePrefBase::OnCookiePreferencesChanged,
                           base::Unretained(this)));
@@ -180,23 +190,15 @@
   auto content_setting = host_content_settings_map_->GetDefaultContentSetting(
       ContentSettingsType::COOKIES, nullptr);
 
-  auto block_third_party_pref_enabled =
-      profile_->GetPrefs()->GetBoolean(prefs::kBlockThirdPartyCookies);
-  auto cookie_controls_pref_value = static_cast<CookieControlsMode>(
+  auto cookie_controls_mode = static_cast<CookieControlsMode>(
       profile_->GetPrefs()->GetInteger(prefs::kCookieControlsMode));
 
   if (content_setting == ContentSetting::CONTENT_SETTING_BLOCK) {
     pref_object->value = std::make_unique<base::Value>(
         static_cast<int>(CookiePrimarySetting::BLOCK_ALL));
-  } else if (block_third_party_pref_enabled) {
-    pref_object->value = std::make_unique<base::Value>(
-        static_cast<int>(CookiePrimarySetting::BLOCK_THIRD_PARTY));
-  } else if (cookie_controls_pref_value == CookieControlsMode::kIncognitoOnly) {
-    pref_object->value = std::make_unique<base::Value>(
-        static_cast<int>(CookiePrimarySetting::BLOCK_THIRD_PARTY_INCOGNITO));
   } else {
     pref_object->value = std::make_unique<base::Value>(
-        static_cast<int>(CookiePrimarySetting::ALLOW_ALL));
+        static_cast<int>(ToCookiePrimarySetting(cookie_controls_mode)));
   }
 
   ApplyPrimaryCookieSettingManagedState(pref_object.get(), profile_);
@@ -238,20 +240,17 @@
 
   // Both the content setting and the block_third_party preference can
   // be controlled via policy.
-  const PrefService::Preference* block_third_party_pref =
-      profile->GetPrefs()->FindPreference(prefs::kBlockThirdPartyCookies);
-  bool block_third_party_on = block_third_party_pref->GetValue()->GetBool();
-  bool block_third_party_enforced = !block_third_party_pref->IsUserModifiable();
+  const PrefService::Preference* cookie_controls_mode_pref =
+      profile->GetPrefs()->FindPreference(prefs::kCookieControlsMode);
+  bool cookie_controls_mode_enforced =
+      !cookie_controls_mode_pref->IsUserModifiable();
   // IsRecommended() cannot be used as we care if a recommended value exists at
   // all, even if a user has overwritten it.
-  bool block_third_party_recommended =
-      (block_third_party_pref && block_third_party_pref->GetRecommendedValue());
-  bool block_third_party_recommended_on =
-      block_third_party_recommended &&
-      block_third_party_pref->GetRecommendedValue()->GetBool();
+  bool cookie_controls_mode_recommended =
+      cookie_controls_mode_pref->GetRecommendedValue();
 
-  if (!content_setting_enforced && !block_third_party_enforced &&
-      !block_third_party_recommended) {
+  if (!content_setting_enforced && !cookie_controls_mode_enforced &&
+      !cookie_controls_mode_recommended) {
     // No cookie controls are managed or recommended.
     return;
   }
@@ -264,70 +263,58 @@
     return;
   }
 
-  if (content_setting_enforced && block_third_party_enforced) {
+  if (content_setting_enforced && cookie_controls_mode_enforced) {
     // Preference is considered fully managed by the third party preference.
     pref_object->enforcement = settings_api::Enforcement::ENFORCEMENT_ENFORCED;
     extensions::settings_private::GeneratedPref::ApplyControlledByFromPref(
-        pref_object, block_third_party_pref);
+        pref_object, cookie_controls_mode_pref);
     return;
   }
 
-  DCHECK(!content_setting_enforced ||
-         content_setting == CONTENT_SETTING_ALLOW ||
-         content_setting == CONTENT_SETTING_SESSION_ONLY);
-  DCHECK(!content_setting_enforced || !block_third_party_enforced);
-
   // At this stage the content setting is not enforcing a BLOCK state. Given
   // this, allow and block_third_party are still valid choices that do not
   // contradict the content setting. They can thus be controlled or recommended
   // by the block_third_party preference.
-  if (block_third_party_recommended) {
+  DCHECK(!content_setting_enforced || !cookie_controls_mode_enforced);
+
+  if (cookie_controls_mode_recommended) {
+    auto recommended_value = static_cast<CookieControlsMode>(
+        cookie_controls_mode_pref->GetRecommendedValue()->GetInt());
     pref_object->recommended_value = std::make_unique<base::Value>(
-        static_cast<int>(block_third_party_recommended_on
-                             ? CookiePrimarySetting::BLOCK_THIRD_PARTY
-                             : CookiePrimarySetting::ALLOW_ALL));
+        static_cast<int>(ToCookiePrimarySetting(recommended_value)));
 
     // Based on state assessed so far the enforcement is only recommended. This
     // may be changed to ENFORCED later in this function.
     pref_object->enforcement =
         settings_api::Enforcement::ENFORCEMENT_RECOMMENDED;
-    if (!content_setting_enforced)
-      return;
   }
 
-  if (!content_setting_enforced) {
-    AddUserSelectableValue(pref_object, CookiePrimarySetting::BLOCK_ALL);
-  } else {
-    pref_object->enforcement = settings_api::Enforcement::ENFORCEMENT_ENFORCED;
-    // This may overwritten later in the function by the third party preference,
-    // if it too is enforced.
-    pref_object->controlled_by =
-        GetControlledByForContentSettingSource(content_setting_source);
-  }
-
-  if (block_third_party_enforced) {
-    DCHECK(!content_setting_enforced);
+  // If cookie controls are enforced and the content settings is not enforced,
+  // you can choose between the selected cookie controls setting and "BLOCK"
+  if (cookie_controls_mode_enforced) {
     pref_object->enforcement = settings_api::Enforcement::ENFORCEMENT_ENFORCED;
     extensions::settings_private::GeneratedPref::ApplyControlledByFromPref(
-        pref_object, block_third_party_pref);
-    if (block_third_party_on && !content_setting_enforced) {
-      AddUserSelectableValue(pref_object,
-                             CookiePrimarySetting::BLOCK_THIRD_PARTY);
-    } else {
-      AddUserSelectableValue(pref_object, CookiePrimarySetting::ALLOW_ALL);
-    }
+        pref_object, cookie_controls_mode_pref);
+    auto value = static_cast<CookieControlsMode>(
+        cookie_controls_mode_pref->GetValue()->GetInt());
+    AddUserSelectableValue(pref_object, ToCookiePrimarySetting(value));
+    AddUserSelectableValue(pref_object, CookiePrimarySetting::BLOCK_ALL);
     return;
   }
 
-  AddUserSelectableValue(pref_object, CookiePrimarySetting::ALLOW_ALL);
-  AddUserSelectableValue(pref_object, CookiePrimarySetting::BLOCK_THIRD_PARTY);
-  AddUserSelectableValue(pref_object,
-                         CookiePrimarySetting::BLOCK_THIRD_PARTY_INCOGNITO);
-  if (block_third_party_recommended) {
-    pref_object->recommended_value = std::make_unique<base::Value>(
-        static_cast<int>(block_third_party_recommended_on
-                             ? CookiePrimarySetting::BLOCK_THIRD_PARTY
-                             : CookiePrimarySetting::ALLOW_ALL));
+  // The content setting is enforced to either ALLOW OR SESSION_ONLY
+  if (content_setting_enforced) {
+    DCHECK(content_setting == CONTENT_SETTING_ALLOW ||
+           content_setting == CONTENT_SETTING_SESSION_ONLY);
+    pref_object->enforcement = settings_api::Enforcement::ENFORCEMENT_ENFORCED;
+    pref_object->controlled_by =
+        GetControlledByForContentSettingSource(content_setting_source);
+
+    AddUserSelectableValue(pref_object, CookiePrimarySetting::ALLOW_ALL);
+    AddUserSelectableValue(pref_object,
+                           CookiePrimarySetting::BLOCK_THIRD_PARTY);
+    AddUserSelectableValue(pref_object,
+                           CookiePrimarySetting::BLOCK_THIRD_PARTY_INCOGNITO);
   }
 }
 
diff --git a/chrome/browser/content_settings/generated_cookie_prefs_unittest.cc b/chrome/browser/content_settings/generated_cookie_prefs_unittest.cc
index 0d2411f4..feaff0a3 100644
--- a/chrome/browser/content_settings/generated_cookie_prefs_unittest.cc
+++ b/chrome/browser/content_settings/generated_cookie_prefs_unittest.cc
@@ -35,9 +35,7 @@
     GeneratedCookiePrimarySettingPref* generated_pref,
     CookiePrimarySetting pref_value,
     ContentSetting expected_content_setting,
-    bool expected_block_third_party,
-    CookieControlsMode expected_cookie_controls_mode,
-    CookiePrimarySetting expected_pref_value) {
+    CookieControlsMode expected_cookie_controls_mode) {
   EXPECT_EQ(
       generated_pref->SetPref(
           std::make_unique<base::Value>(static_cast<int>(pref_value)).get()),
@@ -45,14 +43,12 @@
   EXPECT_EQ(
       map->GetDefaultContentSetting(ContentSettingsType::COOKIES, nullptr),
       expected_content_setting);
-  EXPECT_EQ(prefs->GetUserPref(prefs::kBlockThirdPartyCookies)->GetBool(),
-            expected_block_third_party);
   EXPECT_EQ(static_cast<CookieControlsMode>(
                 prefs->GetUserPref(prefs::kCookieControlsMode)->GetInt()),
             expected_cookie_controls_mode);
   EXPECT_EQ(static_cast<CookiePrimarySetting>(
                 generated_pref->GetPrefObject()->value->GetInt()),
-            expected_pref_value);
+            pref_value);
 }
 
 // Define additional unused values of Enforcement, ControlledBy and
@@ -288,24 +284,28 @@
                                                   provider_type);
   }
   if (test_case.block_third_party != settings_private::PrefSetting::kNotSet) {
-    bool third_party_value =
-        test_case.block_third_party ==
+    CookieControlsMode cookie_controls_mode = CookieControlsMode::kOff;
+    if (test_case.block_third_party ==
             settings_private::PrefSetting::kRecommendedOn ||
         test_case.block_third_party ==
-            settings_private::PrefSetting::kEnforcedOn;
+            settings_private::PrefSetting::kEnforcedOn) {
+      cookie_controls_mode = CookieControlsMode::kBlockThirdParty;
+    }
+    auto cookie_controls_mode_value =
+        std::make_unique<base::Value>(static_cast<int>(cookie_controls_mode));
+
     if (test_case.block_third_party_source ==
         settings_private::PrefSource::kExtension) {
-      prefs->SetExtensionPref(prefs::kBlockThirdPartyCookies,
-                              std::make_unique<base::Value>(third_party_value));
+      prefs->SetExtensionPref(prefs::kCookieControlsMode,
+                              std::move(cookie_controls_mode_value));
     } else if (test_case.block_third_party_source ==
                settings_private::PrefSource::kDevicePolicy) {
-      prefs->SetManagedPref(prefs::kBlockThirdPartyCookies,
-                            std::make_unique<base::Value>(third_party_value));
+      prefs->SetManagedPref(prefs::kCookieControlsMode,
+                            std::move(cookie_controls_mode_value));
     } else if (test_case.block_third_party_source ==
                settings_private::PrefSource::kRecommended) {
-      prefs->SetRecommendedPref(
-          prefs::kBlockThirdPartyCookies,
-          std::make_unique<base::Value>(third_party_value));
+      prefs->SetRecommendedPref(prefs::kCookieControlsMode,
+                                std::move(cookie_controls_mode_value));
     }
   }
 }
@@ -361,8 +361,6 @@
   // Setup a baseline content setting and preference state.
   map->SetDefaultContentSetting(ContentSettingsType::COOKIES,
                                 ContentSetting::CONTENT_SETTING_ALLOW);
-  prefs()->SetDefaultPrefValue(prefs::kBlockThirdPartyCookies,
-                               base::Value(false));
   prefs()->SetDefaultPrefValue(
       prefs::kCookieControlsMode,
       base::Value(static_cast<int>(CookieControlsMode::kOff)));
@@ -370,25 +368,22 @@
   // Check that each of the four possible preference values sets the correct
   // state and is correctly reflected in a newly returned PrefObject.
   // First test this without the improved cookie controls enabled.
-  ValidatePrimarySettingPrefValue(
-      map, prefs(), pref.get(), CookiePrimarySetting::BLOCK_ALL,
-      ContentSetting::CONTENT_SETTING_BLOCK, /* block 3P */ true,
-      CookieControlsMode::kBlockThirdParty, CookiePrimarySetting::BLOCK_ALL);
-  ValidatePrimarySettingPrefValue(
-      map, prefs(), pref.get(), CookiePrimarySetting::BLOCK_THIRD_PARTY,
-      ContentSetting::CONTENT_SETTING_ALLOW, /* block 3P */ true,
-      CookieControlsMode::kBlockThirdParty,
-      CookiePrimarySetting::BLOCK_THIRD_PARTY);
+  ValidatePrimarySettingPrefValue(map, prefs(), pref.get(),
+                                  CookiePrimarySetting::BLOCK_ALL,
+                                  ContentSetting::CONTENT_SETTING_BLOCK,
+                                  CookieControlsMode::kBlockThirdParty);
+  ValidatePrimarySettingPrefValue(map, prefs(), pref.get(),
+                                  CookiePrimarySetting::BLOCK_THIRD_PARTY,
+                                  ContentSetting::CONTENT_SETTING_ALLOW,
+                                  CookieControlsMode::kBlockThirdParty);
   ValidatePrimarySettingPrefValue(
       map, prefs(), pref.get(), CookiePrimarySetting::ALLOW_ALL,
-      ContentSetting::CONTENT_SETTING_ALLOW, /* block 3P */ false,
-      CookieControlsMode::kOff, CookiePrimarySetting::ALLOW_ALL);
+      ContentSetting::CONTENT_SETTING_ALLOW, CookieControlsMode::kOff);
   ValidatePrimarySettingPrefValue(
       map, prefs(), pref.get(),
       CookiePrimarySetting::BLOCK_THIRD_PARTY_INCOGNITO,
-      ContentSetting::CONTENT_SETTING_ALLOW, /* block 3P */ false,
-      CookieControlsMode::kIncognitoOnly,
-      CookiePrimarySetting::BLOCK_THIRD_PARTY_INCOGNITO);
+      ContentSetting::CONTENT_SETTING_ALLOW,
+      CookieControlsMode::kIncognitoOnly);
 
   // Confirm that a type mismatch is reported as such.
   EXPECT_EQ(pref->SetPref(std::make_unique<base::Value>(true).get()),
@@ -409,17 +404,17 @@
       std::make_unique<base::Value>(ContentSetting::CONTENT_SETTING_ALLOW));
   content_settings::TestUtils::OverrideProvider(
       map, std::move(provider), HostContentSettingsMap::POLICY_PROVIDER);
-  ValidatePrimarySettingPrefValue(
-      map, prefs(), pref.get(), CookiePrimarySetting::BLOCK_THIRD_PARTY,
-      ContentSetting::CONTENT_SETTING_ALLOW, /* block 3P */ true,
-      CookieControlsMode::kBlockThirdParty,
-      CookiePrimarySetting::BLOCK_THIRD_PARTY);
+  ValidatePrimarySettingPrefValue(map, prefs(), pref.get(),
+                                  CookiePrimarySetting::BLOCK_THIRD_PARTY,
+                                  ContentSetting::CONTENT_SETTING_ALLOW,
+                                  CookieControlsMode::kBlockThirdParty);
 
   // Update source preferences and ensure that an observer is fired.
   settings_private::TestGeneratedPrefObserver test_observer;
   pref->AddObserver(&test_observer);
-  prefs()->SetUserPref(prefs::kBlockThirdPartyCookies,
-                       std::make_unique<base::Value>(false));
+  prefs()->SetUserPref(prefs::kCookieControlsMode,
+                       std::make_unique<base::Value>(static_cast<int>(
+                           CookieControlsMode::kIncognitoOnly)));
   EXPECT_EQ(test_observer.GetUpdatedPrefName(), kCookiePrimarySetting);
 
   test_observer.Reset();
diff --git a/chrome/browser/content_settings/tab_specific_content_settings_delegate.cc b/chrome/browser/content_settings/page_specific_content_settings_delegate.cc
similarity index 73%
rename from chrome/browser/content_settings/tab_specific_content_settings_delegate.cc
rename to chrome/browser/content_settings/page_specific_content_settings_delegate.cc
index b4afc9c..bc2f9e37 100644
--- a/chrome/browser/content_settings/tab_specific_content_settings_delegate.cc
+++ b/chrome/browser/content_settings/page_specific_content_settings_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 "chrome/browser/content_settings/tab_specific_content_settings_delegate.h"
+#include "chrome/browser/content_settings/page_specific_content_settings_delegate.h"
 
 #include "build/build_config.h"
 #include "chrome/browser/browsing_data/browsing_data_file_system_util.h"
@@ -17,7 +17,7 @@
 #include "chrome/common/pref_names.h"
 #include "chrome/common/render_messages.h"
 #include "chrome/common/renderer_configuration.mojom.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/permissions/permission_decision_auto_blocker.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/navigation_handle.h"
@@ -28,30 +28,30 @@
 #include "chrome/browser/browsing_data/access_context_audit_service_factory.h"
 #endif  // !defined(OS_ANDROID)
 
-using content_settings::TabSpecificContentSettings;
+using content_settings::PageSpecificContentSettings;
 
 namespace chrome {
 
-TabSpecificContentSettingsDelegate::TabSpecificContentSettingsDelegate(
+PageSpecificContentSettingsDelegate::PageSpecificContentSettingsDelegate(
     content::WebContents* web_contents)
     : WebContentsObserver(web_contents) {}
 
-TabSpecificContentSettingsDelegate::~TabSpecificContentSettingsDelegate() =
+PageSpecificContentSettingsDelegate::~PageSpecificContentSettingsDelegate() =
     default;
 
 // static
-TabSpecificContentSettingsDelegate*
-TabSpecificContentSettingsDelegate::FromWebContents(
+PageSpecificContentSettingsDelegate*
+PageSpecificContentSettingsDelegate::FromWebContents(
     content::WebContents* web_contents) {
-  return static_cast<TabSpecificContentSettingsDelegate*>(
-      TabSpecificContentSettings::GetDelegateForWebContents(web_contents));
+  return static_cast<PageSpecificContentSettingsDelegate*>(
+      PageSpecificContentSettings::GetDelegateForWebContents(web_contents));
 }
 
-void TabSpecificContentSettingsDelegate::UpdateLocationBar() {
+void PageSpecificContentSettingsDelegate::UpdateLocationBar() {
   content_settings::UpdateLocationBarUiForWebContents(web_contents());
 }
 
-void TabSpecificContentSettingsDelegate::SetContentSettingRules(
+void PageSpecificContentSettingsDelegate::SetContentSettingRules(
     content::RenderProcessHost* process,
     const RendererContentSettingRules& rules) {
   // |channel| may be null in tests.
@@ -64,7 +64,7 @@
   rc_interface->SetContentSettingRules(rules);
 }
 
-PrefService* TabSpecificContentSettingsDelegate::GetPrefs() {
+PrefService* PageSpecificContentSettingsDelegate::GetPrefs() {
   Profile* profile =
       Profile::FromBrowserContext(web_contents()->GetBrowserContext());
   if (!profile)
@@ -73,12 +73,12 @@
   return profile->GetPrefs();
 }
 
-HostContentSettingsMap* TabSpecificContentSettingsDelegate::GetSettingsMap() {
+HostContentSettingsMap* PageSpecificContentSettingsDelegate::GetSettingsMap() {
   return HostContentSettingsMapFactory::GetForProfile(
       Profile::FromBrowserContext(web_contents()->GetBrowserContext()));
 }
 
-ContentSetting TabSpecificContentSettingsDelegate::GetEmbargoSetting(
+ContentSetting PageSpecificContentSettingsDelegate::GetEmbargoSetting(
     const GURL& request_origin,
     ContentSettingsType permission) {
   return PermissionDecisionAutoBlockerFactory::GetForProfile(
@@ -88,18 +88,18 @@
 }
 
 std::vector<storage::FileSystemType>
-TabSpecificContentSettingsDelegate::GetAdditionalFileSystemTypes() {
+PageSpecificContentSettingsDelegate::GetAdditionalFileSystemTypes() {
   return browsing_data_file_system_util::GetAdditionalFileSystemTypes();
 }
 
 browsing_data::CookieHelper::IsDeletionDisabledCallback
-TabSpecificContentSettingsDelegate::GetIsDeletionDisabledCallback() {
+PageSpecificContentSettingsDelegate::GetIsDeletionDisabledCallback() {
   return CookiesTreeModel::GetCookieDeletionDisabledCallback(
       Profile::FromBrowserContext(web_contents()->GetBrowserContext()));
 }
 
-bool TabSpecificContentSettingsDelegate::IsMicrophoneCameraStateChanged(
-    TabSpecificContentSettings::MicrophoneCameraState microphone_camera_state,
+bool PageSpecificContentSettingsDelegate::IsMicrophoneCameraStateChanged(
+    PageSpecificContentSettings::MicrophoneCameraState microphone_camera_state,
     const std::string& media_stream_selected_audio_device,
     const std::string& media_stream_selected_video_device) {
   PrefService* prefs = GetPrefs();
@@ -108,13 +108,14 @@
           ->GetMediaStreamCaptureIndicator();
 
   if ((microphone_camera_state &
-       TabSpecificContentSettings::MICROPHONE_ACCESSED) &&
+       PageSpecificContentSettings::MICROPHONE_ACCESSED) &&
       prefs->GetString(prefs::kDefaultAudioCaptureDevice) !=
           media_stream_selected_audio_device &&
       media_indicator->IsCapturingAudio(web_contents()))
     return true;
 
-  if ((microphone_camera_state & TabSpecificContentSettings::CAMERA_ACCESSED) &&
+  if ((microphone_camera_state &
+       PageSpecificContentSettings::CAMERA_ACCESSED) &&
       prefs->GetString(prefs::kDefaultVideoCaptureDevice) !=
           media_stream_selected_video_device &&
       media_indicator->IsCapturingVideo(web_contents()))
@@ -123,10 +124,10 @@
   return false;
 }
 
-TabSpecificContentSettings::MicrophoneCameraState
-TabSpecificContentSettingsDelegate::GetMicrophoneCameraState() {
-  TabSpecificContentSettings::MicrophoneCameraState state =
-      TabSpecificContentSettings::MICROPHONE_CAMERA_NOT_ACCESSED;
+PageSpecificContentSettings::MicrophoneCameraState
+PageSpecificContentSettingsDelegate::GetMicrophoneCameraState() {
+  PageSpecificContentSettings::MicrophoneCameraState state =
+      PageSpecificContentSettings::MICROPHONE_CAMERA_NOT_ACCESSED;
 
   // Include capture devices in the state if there are still consumers of the
   // approved media stream.
@@ -134,14 +135,14 @@
       MediaCaptureDevicesDispatcher::GetInstance()
           ->GetMediaStreamCaptureIndicator();
   if (media_indicator->IsCapturingAudio(web_contents()))
-    state |= TabSpecificContentSettings::MICROPHONE_ACCESSED;
+    state |= PageSpecificContentSettings::MICROPHONE_ACCESSED;
   if (media_indicator->IsCapturingVideo(web_contents()))
-    state |= TabSpecificContentSettings::CAMERA_ACCESSED;
+    state |= PageSpecificContentSettings::CAMERA_ACCESSED;
 
   return state;
 }
 
-void TabSpecificContentSettingsDelegate::OnContentBlocked(
+void PageSpecificContentSettingsDelegate::OnContentBlocked(
     ContentSettingsType type) {
   if (type == ContentSettingsType::PLUGINS) {
     content_settings::RecordPluginsAction(
@@ -152,7 +153,7 @@
   }
 }
 
-void TabSpecificContentSettingsDelegate::OnCookieAccessAllowed(
+void PageSpecificContentSettingsDelegate::OnCookieAccessAllowed(
     const net::CookieList& accessed_cookies) {
 #if !defined(OS_ANDROID)
   auto* access_context_audit_service =
@@ -165,7 +166,7 @@
 #endif  // !defined(OS_ANDROID)
 }
 
-void TabSpecificContentSettingsDelegate::DidFinishNavigation(
+void PageSpecificContentSettingsDelegate::DidFinishNavigation(
     content::NavigationHandle* navigation_handle) {
   if (!navigation_handle->IsInMainFrame() ||
       !navigation_handle->HasCommitted() ||
diff --git a/chrome/browser/content_settings/tab_specific_content_settings_delegate.h b/chrome/browser/content_settings/page_specific_content_settings_delegate.h
similarity index 77%
rename from chrome/browser/content_settings/tab_specific_content_settings_delegate.h
rename to chrome/browser/content_settings/page_specific_content_settings_delegate.h
index 6eab8dbd..93998a0 100644
--- a/chrome/browser/content_settings/tab_specific_content_settings_delegate.h
+++ b/chrome/browser/content_settings/page_specific_content_settings_delegate.h
@@ -2,27 +2,27 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_CONTENT_SETTINGS_TAB_SPECIFIC_CONTENT_SETTINGS_DELEGATE_H_
-#define CHROME_BROWSER_CONTENT_SETTINGS_TAB_SPECIFIC_CONTENT_SETTINGS_DELEGATE_H_
+#ifndef CHROME_BROWSER_CONTENT_SETTINGS_PAGE_SPECIFIC_CONTENT_SETTINGS_DELEGATE_H_
+#define CHROME_BROWSER_CONTENT_SETTINGS_PAGE_SPECIFIC_CONTENT_SETTINGS_DELEGATE_H_
 
 #include "chrome/common/custom_handlers/protocol_handler.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 
 namespace chrome {
 
-class TabSpecificContentSettingsDelegate
-    : public content_settings::TabSpecificContentSettings::Delegate,
+class PageSpecificContentSettingsDelegate
+    : public content_settings::PageSpecificContentSettings::Delegate,
       public content::WebContentsObserver {
  public:
-  explicit TabSpecificContentSettingsDelegate(
+  explicit PageSpecificContentSettingsDelegate(
       content::WebContents* web_contents);
-  ~TabSpecificContentSettingsDelegate() override;
-  TabSpecificContentSettingsDelegate(
-      const TabSpecificContentSettingsDelegate&) = delete;
-  TabSpecificContentSettingsDelegate& operator=(
-      const TabSpecificContentSettingsDelegate&) = delete;
+  ~PageSpecificContentSettingsDelegate() override;
+  PageSpecificContentSettingsDelegate(
+      const PageSpecificContentSettingsDelegate&) = delete;
+  PageSpecificContentSettingsDelegate& operator=(
+      const PageSpecificContentSettingsDelegate&) = delete;
 
-  static TabSpecificContentSettingsDelegate* FromWebContents(
+  static PageSpecificContentSettingsDelegate* FromWebContents(
       content::WebContents* web_contents);
 
   // Call to indicate that there is a protocol handler pending user approval.
@@ -59,7 +59,7 @@
   }
 
  private:
-  // TabSpecificContentSettings::Delegate:
+  // PageSpecificContentSettings::Delegate:
   void UpdateLocationBar() override;
   void SetContentSettingRules(
       content::RenderProcessHost* process,
@@ -72,11 +72,11 @@
   browsing_data::CookieHelper::IsDeletionDisabledCallback
   GetIsDeletionDisabledCallback() override;
   bool IsMicrophoneCameraStateChanged(
-      content_settings::TabSpecificContentSettings::MicrophoneCameraState
+      content_settings::PageSpecificContentSettings::MicrophoneCameraState
           microphone_camera_state,
       const std::string& media_stream_selected_audio_device,
       const std::string& media_stream_selected_video_device) override;
-  content_settings::TabSpecificContentSettings::MicrophoneCameraState
+  content_settings::PageSpecificContentSettings::MicrophoneCameraState
   GetMicrophoneCameraState() override;
   void OnContentBlocked(ContentSettingsType type) override;
   void OnCookieAccessAllowed(const net::CookieList& accessed_cookies) override;
@@ -105,4 +105,4 @@
 
 }  // namespace chrome
 
-#endif  // CHROME_BROWSER_CONTENT_SETTINGS_TAB_SPECIFIC_CONTENT_SETTINGS_DELEGATE_H_
+#endif  // CHROME_BROWSER_CONTENT_SETTINGS_PAGE_SPECIFIC_CONTENT_SETTINGS_DELEGATE_H_
diff --git a/chrome/browser/content_settings/sound_content_setting_observer.cc b/chrome/browser/content_settings/sound_content_setting_observer.cc
index a8e905b..13ad04c 100644
--- a/chrome/browser/content_settings/sound_content_setting_observer.cc
+++ b/chrome/browser/content_settings/sound_content_setting_observer.cc
@@ -9,7 +9,7 @@
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/pref_names.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/common/content_settings_utils.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/pref_service.h"
@@ -161,8 +161,8 @@
     // This is a page level event so it is OK to get the main frame here.
     // TODO(https://crbug.com/1103176): We should figure a way of not having to
     // use GetMainFrame here. (pass the source frame somehow)
-    content_settings::TabSpecificContentSettings* settings =
-        content_settings::TabSpecificContentSettings::GetForFrame(
+    content_settings::PageSpecificContentSettings* settings =
+        content_settings::PageSpecificContentSettings::GetForFrame(
             web_contents()->GetMainFrame());
     if (settings)
       settings->OnAudioBlocked();
diff --git a/chrome/browser/devtools/devtools_ui_bindings.cc b/chrome/browser/devtools/devtools_ui_bindings.cc
index 91e9867..ddd8f65 100644
--- a/chrome/browser/devtools/devtools_ui_bindings.cc
+++ b/chrome/browser/devtools/devtools_ui_bindings.cc
@@ -114,6 +114,12 @@
     "DevTools.GridSettingChanged";
 static const char kDevtoolsCSSGridSettingsHistogram[] =
     "DevTools.CSSGridSettings";
+static const char kDevtoolsExperimentEnabledHistogram[] =
+    "DevTools.ExperimentEnabled";
+static const char kDevtoolsExperimentDisabledHistogram[] =
+    "DevTools.ExperimentDisabled";
+static const char kDevtoolsExperimentEnabledAtLaunchHistogram[] =
+    "DevTools.ExperimentEnabledAtLaunch";
 
 static const char kRemotePageActionInspect[] = "inspect";
 static const char kRemotePageActionReload[] = "reload";
@@ -1275,6 +1281,12 @@
     base::UmaHistogramExactLinear(name, sample, boundary_value);
   else if (name == kDevtoolsCSSGridSettingsHistogram)
     base::UmaHistogramExactLinear(name, sample, boundary_value);
+  else if (name == kDevtoolsExperimentEnabledHistogram)
+    base::UmaHistogramExactLinear(name, sample, boundary_value);
+  else if (name == kDevtoolsExperimentDisabledHistogram)
+    base::UmaHistogramExactLinear(name, sample, boundary_value);
+  else if (name == kDevtoolsExperimentEnabledAtLaunchHistogram)
+    base::UmaHistogramExactLinear(name, sample, boundary_value);
   else
     frontend_host_->BadMessageRecieved();
 }
diff --git a/chrome/browser/download/download_request_limiter.cc b/chrome/browser/download/download_request_limiter.cc
index d76244e..e0e1323 100644
--- a/chrome/browser/download/download_request_limiter.cc
+++ b/chrome/browser/download/download_request_limiter.cc
@@ -346,7 +346,7 @@
     return;
 
   GURL origin = origin_.GetURL();
-  // Analogous to TabSpecificContentSettings::OnContentSettingChanged:
+  // Analogous to PageSpecificContentSettings::OnContentSettingChanged:
   const ContentSettingsDetails details(primary_pattern, secondary_pattern,
                                        content_type, resource_identifier);
 
diff --git a/chrome/browser/extensions/api/platform_keys/platform_keys_apitest_nss.cc b/chrome/browser/extensions/api/platform_keys/platform_keys_apitest_nss.cc
index 0f82a4d..3c46570 100644
--- a/chrome/browser/extensions/api/platform_keys/platform_keys_apitest_nss.cc
+++ b/chrome/browser/extensions/api/platform_keys/platform_keys_apitest_nss.cc
@@ -137,7 +137,7 @@
     extensions::StateStore* const state_store =
         extensions::ExtensionSystem::Get(profile())->state_store();
 
-    chromeos::KeyPermissions permissions(
+    chromeos::platform_keys::KeyPermissions permissions(
         policy_connector->IsManaged(), profile()->GetPrefs(),
         policy_connector->policy_service(), state_store);
 
@@ -168,12 +168,14 @@
 
   void GotPermissionsForExtension(
       const base::Closure& done_callback,
-      std::unique_ptr<chromeos::KeyPermissions::PermissionsForExtension>
+      std::unique_ptr<
+          chromeos::platform_keys::KeyPermissions::PermissionsForExtension>
           permissions_for_ext) {
     std::string client_cert1_spki =
         chromeos::platform_keys::GetSubjectPublicKeyInfo(client_cert1_);
     permissions_for_ext->RegisterKeyForCorporateUsage(
-        client_cert1_spki, {chromeos::KeyPermissions::KeyLocation::kUserSlot});
+        client_cert1_spki,
+        {chromeos::platform_keys::KeyPermissions::KeyLocation::kUserSlot});
     done_callback.Run();
   }
 
diff --git a/chrome/browser/generic_sensor/sensor_permission_context.cc b/chrome/browser/generic_sensor/sensor_permission_context.cc
index a68a5c8..810d8bb 100644
--- a/chrome/browser/generic_sensor/sensor_permission_context.cc
+++ b/chrome/browser/generic_sensor/sensor_permission_context.cc
@@ -5,7 +5,7 @@
 #include "chrome/browser/generic_sensor/sensor_permission_context.h"
 
 #include "base/feature_list.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/common/content_settings.h"
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "components/permissions/permission_request_id.h"
@@ -26,7 +26,7 @@
     const GURL& requesting_frame,
     bool allowed) {
   auto* content_settings =
-      content_settings::TabSpecificContentSettings::GetForFrame(
+      content_settings::PageSpecificContentSettings::GetForFrame(
           id.render_process_id(), id.render_frame_id());
   if (!content_settings)
     return;
diff --git a/chrome/browser/geolocation/geolocation_browsertest.cc b/chrome/browser/geolocation/geolocation_browsertest.cc
index 8ba14cc..612dc1a 100644
--- a/chrome/browser/geolocation/geolocation_browsertest.cc
+++ b/chrome/browser/geolocation/geolocation_browsertest.cc
@@ -27,7 +27,7 @@
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/content_settings/browser/content_settings_usages_state.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/permissions/features.h"
 #include "components/permissions/permission_request_manager.h"
diff --git a/chrome/browser/geolocation/geolocation_permission_context_delegate_unittest.cc b/chrome/browser/geolocation/geolocation_permission_context_delegate_unittest.cc
index d09a3a6..3bce946 100644
--- a/chrome/browser/geolocation/geolocation_permission_context_delegate_unittest.cc
+++ b/chrome/browser/geolocation/geolocation_permission_context_delegate_unittest.cc
@@ -3,12 +3,12 @@
 // found in the LICENSE file.
 
 #include "build/build_config.h"
-#include "chrome/browser/content_settings/tab_specific_content_settings_delegate.h"
+#include "chrome/browser/content_settings/page_specific_content_settings_delegate.h"
 #include "chrome/browser/permissions/permission_manager_factory.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/content_settings/browser/content_settings_usages_state.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "components/permissions/permission_manager.h"
 #include "components/permissions/permission_request_manager.h"
@@ -56,9 +56,9 @@
     ChromeRenderViewHostTestHarness::SetUp();
 
     permissions::PermissionRequestManager::CreateForWebContents(web_contents());
-    content_settings::TabSpecificContentSettings::CreateForWebContents(
+    content_settings::PageSpecificContentSettings::CreateForWebContents(
         web_contents(),
-        std::make_unique<chrome::TabSpecificContentSettingsDelegate>(
+        std::make_unique<chrome::PageSpecificContentSettingsDelegate>(
             web_contents()));
 #if defined(OS_ANDROID)
     static_cast<permissions::GeolocationPermissionContextAndroid*>(
@@ -76,8 +76,8 @@
 
   void CheckTabContentsState(const GURL& requesting_frame,
                              ContentSetting expected_content_setting) {
-    content_settings::TabSpecificContentSettings* content_settings =
-        content_settings::TabSpecificContentSettings::GetForFrame(
+    content_settings::PageSpecificContentSettings* content_settings =
+        content_settings::PageSpecificContentSettings::GetForFrame(
             web_contents()->GetMainFrame());
     const ContentSettingsUsagesState::StateMap& state_map =
         content_settings->geolocation_usages_state().state_map();
diff --git a/chrome/browser/idle/idle_detection_permission_context.cc b/chrome/browser/idle/idle_detection_permission_context.cc
index 6c78d5b..cc3039b7 100644
--- a/chrome/browser/idle/idle_detection_permission_context.cc
+++ b/chrome/browser/idle/idle_detection_permission_context.cc
@@ -4,7 +4,7 @@
 
 #include "chrome/browser/idle/idle_detection_permission_context.h"
 
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/permissions/permission_request_id.h"
 #include "url/gurl.h"
 
@@ -20,8 +20,8 @@
     const permissions::PermissionRequestID& id,
     const GURL& requesting_frame,
     bool allowed) {
-  content_settings::TabSpecificContentSettings* content_settings =
-      content_settings::TabSpecificContentSettings::GetForFrame(
+  content_settings::PageSpecificContentSettings* content_settings =
+      content_settings::PageSpecificContentSettings::GetForFrame(
           id.render_process_id(), id.render_frame_id());
   if (!content_settings)
     return;
diff --git a/chrome/browser/media/midi_sysex_permission_context.cc b/chrome/browser/media/midi_sysex_permission_context.cc
index c14e8677..020d5ea 100644
--- a/chrome/browser/media/midi_sysex_permission_context.cc
+++ b/chrome/browser/media/midi_sysex_permission_context.cc
@@ -4,7 +4,7 @@
 
 #include "chrome/browser/media/midi_sysex_permission_context.h"
 
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/permissions/permission_request_id.h"
 #include "content/public/browser/child_process_security_policy.h"
 #include "url/gurl.h"
@@ -21,8 +21,8 @@
     const permissions::PermissionRequestID& id,
     const GURL& requesting_frame,
     bool allowed) {
-  content_settings::TabSpecificContentSettings* content_settings =
-      content_settings::TabSpecificContentSettings::GetForFrame(
+  content_settings::PageSpecificContentSettings* content_settings =
+      content_settings::PageSpecificContentSettings::GetForFrame(
           id.render_process_id(), id.render_frame_id());
   if (!content_settings)
     return;
diff --git a/chrome/browser/media/protected_media_identifier_permission_context.cc b/chrome/browser/media/protected_media_identifier_permission_context.cc
index 45d98b90..da41e0a 100644
--- a/chrome/browser/media/protected_media_identifier_permission_context.cc
+++ b/chrome/browser/media/protected_media_identifier_permission_context.cc
@@ -13,7 +13,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/pref_names.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/permissions/permission_util.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/browser_thread.h"
@@ -157,8 +157,8 @@
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   // WebContents may have gone away.
-  content_settings::TabSpecificContentSettings* content_settings =
-      content_settings::TabSpecificContentSettings::GetForFrame(
+  content_settings::PageSpecificContentSettings* content_settings =
+      content_settings::PageSpecificContentSettings::GetForFrame(
           id.render_process_id(), id.render_frame_id());
   if (content_settings) {
     content_settings->OnProtectedMediaIdentifierPermissionSet(
diff --git a/chrome/browser/media/webrtc/media_stream_devices_controller_browsertest.cc b/chrome/browser/media/webrtc/media_stream_devices_controller_browsertest.cc
index 3914c71..29560e1 100644
--- a/chrome/browser/media/webrtc/media_stream_devices_controller_browsertest.cc
+++ b/chrome/browser/media/webrtc/media_stream_devices_controller_browsertest.cc
@@ -23,7 +23,7 @@
 #include "chrome/common/pref_names.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/test/base/ui_test_utils.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/permissions/permission_context_base.h"
 #include "components/permissions/permission_manager.h"
@@ -41,7 +41,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/mediastream/media_stream_request.h"
 
-using content_settings::TabSpecificContentSettings;
+using content_settings::PageSpecificContentSettings;
 
 class MediaStreamDevicesControllerTest : public WebRtcTestBase {
  public:
@@ -67,8 +67,8 @@
 
   const GURL& example_url() const { return example_url_; }
 
-  TabSpecificContentSettings* GetContentSettings() {
-    return TabSpecificContentSettings::GetForFrame(
+  PageSpecificContentSettings* GetContentSettings() {
+    return PageSpecificContentSettings::GetForFrame(
         GetWebContents()->GetMainFrame());
   }
 
@@ -179,7 +179,7 @@
     DCHECK(example_url_.is_empty());
     example_url_ = url;
     ui_test_utils::NavigateToURL(browser(), example_url_);
-    EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_CAMERA_NOT_ACCESSED,
+    EXPECT_EQ(PageSpecificContentSettings::MICROPHONE_CAMERA_NOT_ACCESSED,
               GetContentSettings()->GetMicrophoneCameraState());
   }
 
@@ -282,7 +282,7 @@
       ContentSettingsType::MEDIASTREAM_MIC));
   EXPECT_FALSE(GetContentSettings()->IsContentBlocked(
       ContentSettingsType::MEDIASTREAM_MIC));
-  EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_ACCESSED,
+  EXPECT_EQ(PageSpecificContentSettings::MICROPHONE_ACCESSED,
             GetContentSettings()->GetMicrophoneCameraState());
   EXPECT_EQ(example_audio_id(),
             GetContentSettings()->media_stream_requested_audio_device());
@@ -309,7 +309,7 @@
       ContentSettingsType::MEDIASTREAM_CAMERA));
   EXPECT_FALSE(GetContentSettings()->IsContentBlocked(
       ContentSettingsType::MEDIASTREAM_CAMERA));
-  EXPECT_EQ(TabSpecificContentSettings::CAMERA_ACCESSED,
+  EXPECT_EQ(PageSpecificContentSettings::CAMERA_ACCESSED,
             GetContentSettings()->GetMicrophoneCameraState());
   EXPECT_EQ(std::string(),
             GetContentSettings()->media_stream_requested_audio_device());
@@ -336,8 +336,8 @@
       ContentSettingsType::MEDIASTREAM_MIC));
   EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
       ContentSettingsType::MEDIASTREAM_MIC));
-  EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_ACCESSED |
-                TabSpecificContentSettings::MICROPHONE_BLOCKED,
+  EXPECT_EQ(PageSpecificContentSettings::MICROPHONE_ACCESSED |
+                PageSpecificContentSettings::MICROPHONE_BLOCKED,
             GetContentSettings()->GetMicrophoneCameraState());
   EXPECT_EQ(example_audio_id(),
             GetContentSettings()->media_stream_requested_audio_device());
@@ -364,8 +364,8 @@
       ContentSettingsType::MEDIASTREAM_CAMERA));
   EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
       ContentSettingsType::MEDIASTREAM_CAMERA));
-  EXPECT_EQ(TabSpecificContentSettings::CAMERA_ACCESSED |
-                TabSpecificContentSettings::CAMERA_BLOCKED,
+  EXPECT_EQ(PageSpecificContentSettings::CAMERA_ACCESSED |
+                PageSpecificContentSettings::CAMERA_BLOCKED,
             GetContentSettings()->GetMicrophoneCameraState());
   EXPECT_EQ(std::string(),
             GetContentSettings()->media_stream_requested_audio_device());
@@ -399,8 +399,8 @@
       ContentSettingsType::MEDIASTREAM_CAMERA));
   EXPECT_FALSE(GetContentSettings()->IsContentBlocked(
       ContentSettingsType::MEDIASTREAM_CAMERA));
-  EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_ACCESSED |
-                TabSpecificContentSettings::CAMERA_ACCESSED,
+  EXPECT_EQ(PageSpecificContentSettings::MICROPHONE_ACCESSED |
+                PageSpecificContentSettings::CAMERA_ACCESSED,
             GetContentSettings()->GetMicrophoneCameraState());
   EXPECT_EQ(example_audio_id(),
             GetContentSettings()->media_stream_requested_audio_device());
@@ -434,10 +434,10 @@
       ContentSettingsType::MEDIASTREAM_CAMERA));
   EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
       ContentSettingsType::MEDIASTREAM_CAMERA));
-  EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_ACCESSED |
-                TabSpecificContentSettings::MICROPHONE_BLOCKED |
-                TabSpecificContentSettings::CAMERA_ACCESSED |
-                TabSpecificContentSettings::CAMERA_BLOCKED,
+  EXPECT_EQ(PageSpecificContentSettings::MICROPHONE_ACCESSED |
+                PageSpecificContentSettings::MICROPHONE_BLOCKED |
+                PageSpecificContentSettings::CAMERA_ACCESSED |
+                PageSpecificContentSettings::CAMERA_BLOCKED,
             GetContentSettings()->GetMicrophoneCameraState());
   EXPECT_EQ(example_audio_id(),
             GetContentSettings()->media_stream_requested_audio_device());
@@ -472,10 +472,10 @@
       ContentSettingsType::MEDIASTREAM_CAMERA));
   EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
       ContentSettingsType::MEDIASTREAM_CAMERA));
-  EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_ACCESSED |
-                TabSpecificContentSettings::MICROPHONE_BLOCKED |
-                TabSpecificContentSettings::CAMERA_ACCESSED |
-                TabSpecificContentSettings::CAMERA_BLOCKED,
+  EXPECT_EQ(PageSpecificContentSettings::MICROPHONE_ACCESSED |
+                PageSpecificContentSettings::MICROPHONE_BLOCKED |
+                PageSpecificContentSettings::CAMERA_ACCESSED |
+                PageSpecificContentSettings::CAMERA_BLOCKED,
             GetContentSettings()->GetMicrophoneCameraState());
   EXPECT_EQ(example_audio_id(),
             GetContentSettings()->media_stream_requested_audio_device());
@@ -510,10 +510,10 @@
       ContentSettingsType::MEDIASTREAM_CAMERA));
   EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
       ContentSettingsType::MEDIASTREAM_CAMERA));
-  EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_ACCESSED |
-                TabSpecificContentSettings::MICROPHONE_BLOCKED |
-                TabSpecificContentSettings::CAMERA_ACCESSED |
-                TabSpecificContentSettings::CAMERA_BLOCKED,
+  EXPECT_EQ(PageSpecificContentSettings::MICROPHONE_ACCESSED |
+                PageSpecificContentSettings::MICROPHONE_BLOCKED |
+                PageSpecificContentSettings::CAMERA_ACCESSED |
+                PageSpecificContentSettings::CAMERA_BLOCKED,
             GetContentSettings()->GetMicrophoneCameraState());
   EXPECT_EQ(example_audio_id(),
             GetContentSettings()->media_stream_requested_audio_device());
@@ -591,7 +591,7 @@
             GetContentSettings()->media_stream_requested_video_device());
   EXPECT_EQ(example_video_id(),
             GetContentSettings()->media_stream_selected_video_device());
-  EXPECT_EQ(TabSpecificContentSettings::CAMERA_ACCESSED,
+  EXPECT_EQ(PageSpecificContentSettings::CAMERA_ACCESSED,
             GetContentSettings()->GetMicrophoneCameraState());
 
   // Simulate that an a video stream is now being captured.
@@ -634,16 +634,16 @@
             GetContentSettings()->media_stream_requested_video_device());
   EXPECT_EQ(example_video_id(),
             GetContentSettings()->media_stream_selected_video_device());
-  EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_ACCESSED |
-                TabSpecificContentSettings::MICROPHONE_BLOCKED |
-                TabSpecificContentSettings::CAMERA_ACCESSED,
+  EXPECT_EQ(PageSpecificContentSettings::MICROPHONE_ACCESSED |
+                PageSpecificContentSettings::MICROPHONE_BLOCKED |
+                PageSpecificContentSettings::CAMERA_ACCESSED,
             GetContentSettings()->GetMicrophoneCameraState());
 
   // After ending the camera capture, the camera permission is no longer
   // relevant, so it should no be included in the mic/cam state.
   video_stream_ui.reset();
-  EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_ACCESSED |
-                TabSpecificContentSettings::MICROPHONE_BLOCKED,
+  EXPECT_EQ(PageSpecificContentSettings::MICROPHONE_ACCESSED |
+                PageSpecificContentSettings::MICROPHONE_BLOCKED,
             GetContentSettings()->GetMicrophoneCameraState());
 }
 
@@ -988,7 +988,7 @@
 
   VerifyResultState(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
                     false, false);
-  EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_CAMERA_NOT_ACCESSED,
+  EXPECT_EQ(PageSpecificContentSettings::MICROPHONE_CAMERA_NOT_ACCESSED,
             GetContentSettings()->GetMicrophoneCameraState());
 }
 
@@ -1018,7 +1018,7 @@
 
   VerifyResultState(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
                     false, false);
-  EXPECT_EQ(TabSpecificContentSettings::MICROPHONE_CAMERA_NOT_ACCESSED,
+  EXPECT_EQ(PageSpecificContentSettings::MICROPHONE_CAMERA_NOT_ACCESSED,
             GetContentSettings()->GetMicrophoneCameraState());
 }
 
diff --git a/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc b/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc
index 883e426..fe81c159 100644
--- a/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc
+++ b/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc
@@ -17,7 +17,7 @@
 #include "chrome/browser/permissions/permission_manager_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/pref_names.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/permissions/permission_manager.h"
 #include "components/permissions/permission_result.h"
@@ -59,7 +59,7 @@
 
 namespace {
 
-void UpdateTabSpecificContentSettings(
+void UpdatePageSpecificContentSettings(
     content::WebContents* web_contents,
     const content::MediaStreamRequest& request,
     ContentSetting audio_setting,
@@ -69,13 +69,13 @@
 
   // TODO(https://crbug.com/1103176): We should extract the frame from |request|
   auto* content_settings =
-      content_settings::TabSpecificContentSettings::GetForFrame(
+      content_settings::PageSpecificContentSettings::GetForFrame(
           web_contents->GetMainFrame());
   if (!content_settings)
     return;
 
-  content_settings::TabSpecificContentSettings::MicrophoneCameraState
-      microphone_camera_state = content_settings::TabSpecificContentSettings::
+  content_settings::PageSpecificContentSettings::MicrophoneCameraState
+      microphone_camera_state = content_settings::PageSpecificContentSettings::
           MICROPHONE_CAMERA_NOT_ACCESSED;
   std::string selected_audio_device;
   std::string selected_video_device;
@@ -92,10 +92,10 @@
             ? profile->GetPrefs()->GetString(prefs::kDefaultAudioCaptureDevice)
             : requested_audio_device;
     microphone_camera_state |=
-        content_settings::TabSpecificContentSettings::MICROPHONE_ACCESSED |
+        content_settings::PageSpecificContentSettings::MICROPHONE_ACCESSED |
         (audio_setting == CONTENT_SETTING_ALLOW
              ? 0
-             : content_settings::TabSpecificContentSettings::
+             : content_settings::PageSpecificContentSettings::
                    MICROPHONE_BLOCKED);
   }
 
@@ -105,10 +105,10 @@
             ? profile->GetPrefs()->GetString(prefs::kDefaultVideoCaptureDevice)
             : requested_video_device;
     microphone_camera_state |=
-        content_settings::TabSpecificContentSettings::CAMERA_ACCESSED |
+        content_settings::PageSpecificContentSettings::CAMERA_ACCESSED |
         (video_setting == CONTENT_SETTING_ALLOW
              ? 0
-             : content_settings::TabSpecificContentSettings::CAMERA_BLOCKED);
+             : content_settings::PageSpecificContentSettings::CAMERA_BLOCKED);
   }
 
   content_settings->OnMediaStreamPermissionSet(
@@ -306,8 +306,8 @@
   // policy we don't update the tab context.
   if (result != blink::mojom::MediaStreamRequestResult::KILL_SWITCH_ON &&
       !blocked_by_feature_policy) {
-    UpdateTabSpecificContentSettings(web_contents, request, audio_setting,
-                                     video_setting);
+    UpdatePageSpecificContentSettings(web_contents, request, audio_setting,
+                                      video_setting);
   }
 
   std::unique_ptr<content::MediaStreamUI> ui;
diff --git a/chrome/browser/nearby_sharing/fake_nearby_connections_manager.cc b/chrome/browser/nearby_sharing/fake_nearby_connections_manager.cc
index 01debf03..b00598a 100644
--- a/chrome/browser/nearby_sharing/fake_nearby_connections_manager.cc
+++ b/chrome/browser/nearby_sharing/fake_nearby_connections_manager.cc
@@ -110,6 +110,23 @@
   upgrade_bandwidth_endpoint_ids_.insert(endpoint_id);
 }
 
+void FakeNearbyConnectionsManager::OnEndpointFound(
+    const std::string& endpoint_id,
+    location::nearby::connections::mojom::DiscoveredEndpointInfoPtr info) {
+  if (!discovery_listener_)
+    return;
+
+  discovery_listener_->OnEndpointDiscovered(endpoint_id, info->endpoint_info);
+}
+
+void FakeNearbyConnectionsManager::OnEndpointLost(
+    const std::string& endpoint_id) {
+  if (!discovery_listener_)
+    return;
+
+  discovery_listener_->OnEndpointLost(endpoint_id);
+}
+
 bool FakeNearbyConnectionsManager::IsAdvertising() {
   return advertising_listener_ != nullptr;
 }
diff --git a/chrome/browser/nearby_sharing/fake_nearby_connections_manager.h b/chrome/browser/nearby_sharing/fake_nearby_connections_manager.h
index fbebd68..2bfbc6cd 100644
--- a/chrome/browser/nearby_sharing/fake_nearby_connections_manager.h
+++ b/chrome/browser/nearby_sharing/fake_nearby_connections_manager.h
@@ -12,11 +12,13 @@
 #include <vector>
 
 #include "chrome/browser/nearby_sharing/nearby_connections_manager.h"
+#include "chrome/services/sharing/public/mojom/nearby_connections.mojom.h"
 
 // Fake NearbyConnectionsManager for testing.
-class FakeNearbyConnectionsManager : public NearbyConnectionsManager {
+class FakeNearbyConnectionsManager
+    : public NearbyConnectionsManager,
+      public location::nearby::connections::mojom::EndpointDiscoveryListener {
  public:
-
   FakeNearbyConnectionsManager();
   ~FakeNearbyConnectionsManager() override;
 
@@ -50,6 +52,13 @@
       const std::string& endpoint_id) override;
   void UpgradeBandwidth(const std::string& endpoint_id) override;
 
+  // mojom::EndpointDiscoveryListener:
+  void OnEndpointFound(
+      const std::string& endpoint_id,
+      location::nearby::connections::mojom::DiscoveredEndpointInfoPtr info)
+      override;
+  void OnEndpointLost(const std::string& endpoint_id) override;
+
   // Testing methods
   bool IsAdvertising();
   bool IsDiscovering();
diff --git a/chrome/browser/nearby_sharing/mock_nearby_connections.h b/chrome/browser/nearby_sharing/mock_nearby_connections.h
index 7a7170de..4dae0730 100644
--- a/chrome/browser/nearby_sharing/mock_nearby_connections.h
+++ b/chrome/browser/nearby_sharing/mock_nearby_connections.h
@@ -11,6 +11,8 @@
 
 using NearbyConnectionsMojom =
     location::nearby::connections::mojom::NearbyConnections;
+using AdvertisingOptionsPtr =
+    location::nearby::connections::mojom::AdvertisingOptionsPtr;
 using DiscoveryOptionsPtr =
     location::nearby::connections::mojom::DiscoveryOptionsPtr;
 using EndpointDiscoveryListener =
@@ -26,27 +28,32 @@
   ~MockNearbyConnections() override;
 
   MOCK_METHOD(void,
+              StartAdvertising,
+              (const std::vector<uint8_t>& endpoint_info,
+               const std::string& service_id,
+               AdvertisingOptionsPtr,
+               mojo::PendingRemote<ConnectionLifecycleListener>,
+               StartDiscoveryCallback),
+              (override));
+  MOCK_METHOD(void, StopAdvertising, (StopAdvertisingCallback), (override));
+  MOCK_METHOD(void,
               StartDiscovery,
               (const std::string& service_id,
                DiscoveryOptionsPtr,
                mojo::PendingRemote<EndpointDiscoveryListener>,
-               StartDiscoveryCallback callback),
+               StartDiscoveryCallback),
               (override));
-  MOCK_METHOD(void,
-              StopDiscovery,
-              (StopDiscoveryCallback callback),
-              (override));
+  MOCK_METHOD(void, StopDiscovery, (StopDiscoveryCallback), (override));
   MOCK_METHOD(void,
               RequestConnection,
               (const std::vector<uint8_t>& endpoint_info,
                const std::string& endpoint_id,
-               mojo::PendingRemote<ConnectionLifecycleListener> listener,
-               RequestConnectionCallback callback),
+               mojo::PendingRemote<ConnectionLifecycleListener>,
+               RequestConnectionCallback),
               (override));
   MOCK_METHOD(void,
               DisconnectFromEndpoint,
-              (const std::string& endpoint_id,
-               DisconnectFromEndpointCallback callback),
+              (const std::string& endpoint_id, DisconnectFromEndpointCallback),
               (override));
 };
 
diff --git a/chrome/browser/nearby_sharing/nearby_connections_manager_impl.cc b/chrome/browser/nearby_sharing/nearby_connections_manager_impl.cc
index 275964c..b93ecf956 100644
--- a/chrome/browser/nearby_sharing/nearby_connections_manager_impl.cc
+++ b/chrome/browser/nearby_sharing/nearby_connections_manager_impl.cc
@@ -9,6 +9,8 @@
 #include "chrome/browser/nearby_sharing/logging/logging.h"
 #include "chrome/services/sharing/public/mojom/nearby_connections_types.mojom.h"
 #include "crypto/random.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "net/base/network_change_notifier.h"
 
 namespace {
 
@@ -16,6 +18,32 @@
 const location::nearby::connections::mojom::Strategy kStrategy =
     location::nearby::connections::mojom::Strategy::kP2pPointToPoint;
 
+bool ShouldEnableWebRtc(bool is_advertising,
+                        DataUsage data_usage,
+                        PowerLevel power_level) {
+  // We won't use internet if the user requested we don't.
+  if (data_usage == DataUsage::kOffline)
+    return false;
+
+  // We won't use internet in a low power mode.
+  if (power_level == PowerLevel::kLowPower)
+    return false;
+
+  net::NetworkChangeNotifier::ConnectionType connection_type =
+      net::NetworkChangeNotifier::GetConnectionType();
+
+  // Verify that this network has an internet connection.
+  if (connection_type == net::NetworkChangeNotifier::CONNECTION_NONE)
+    return false;
+
+  // If the user wants to limit WebRTC, then only use it on unmetered networks.
+  if (data_usage == DataUsage::kWifiOnly)
+    return !net::NetworkChangeNotifier::IsConnectionCellular(connection_type);
+
+  // We're online, the user hasn't disabled WebRTC, let's use it!
+  return true;
+}
+
 }  // namespace
 
 NearbyConnectionsManagerImpl::NearbyConnectionsManagerImpl(
@@ -39,17 +67,38 @@
     PowerLevel power_level,
     DataUsage data_usage,
     ConnectionsCallback callback) {
+  DCHECK(listener);
+  DCHECK(!incoming_connection_listener_);
+
   if (!BindNearbyConnections()) {
     std::move(callback).Run(ConnectionsStatus::kError);
     return;
   }
 
-  // TOOD(crbug/1076008): nearby_connections_->StartAdvertising
+  bool is_high_power = power_level == PowerLevel::kHighPower;
+  auto allowed_mediums = MediumSelection::New(
+      /*bluetooth=*/is_high_power,
+      ShouldEnableWebRtc(/*is_advertising=*/true, data_usage, power_level),
+      /*wifi_lan=*/is_high_power);
+
+  mojo::PendingRemote<ConnectionLifecycleListener> lifecycle_listener;
+  connection_lifecycle_listeners_.Add(
+      this, lifecycle_listener.InitWithNewPipeAndPassReceiver());
+
+  incoming_connection_listener_ = listener;
+  nearby_connections_->StartAdvertising(
+      endpoint_info, kServiceId,
+      AdvertisingOptions::New(kStrategy, std::move(allowed_mediums),
+                              /*auto_upgrade_bandwidth=*/is_high_power,
+                              /*enforce_topology_constraints=*/true),
+      std::move(lifecycle_listener), std::move(callback));
 }
 
 void NearbyConnectionsManagerImpl::StopAdvertising() {
-  if (!nearby_connections_)
-    return;
+  if (nearby_connections_)
+    nearby_connections_->StopAdvertising(base::DoNothing());
+
+  incoming_connection_listener_ = nullptr;
 }
 
 void NearbyConnectionsManagerImpl::StartDiscovery(
@@ -91,10 +140,13 @@
     return;
   }
 
+  mojo::PendingRemote<ConnectionLifecycleListener> lifecycle_listener;
+  connection_lifecycle_listeners_.Add(
+      this, lifecycle_listener.InitWithNewPipeAndPassReceiver());
+
   // TODO(crbug/10706008): Add MediumSelector and bluetooth_mac_address.
   nearby_connections_->RequestConnection(
-      endpoint_info, endpoint_id,
-      connection_lifecycle_listener_.BindNewPipeAndPassRemote(),
+      endpoint_info, endpoint_id, std::move(lifecycle_listener),
       base::BindOnce(&NearbyConnectionsManagerImpl::OnConnectionRequested,
                      weak_ptr_factory_.GetWeakPtr(), endpoint_id,
                      std::move(callback)));
@@ -260,7 +312,17 @@
     return;
 
   if (it->second->is_incoming_connection) {
-    // TOOD(crbug/1076008): Handle incoming connection.
+    if (!incoming_connection_listener_) {
+      // Not in advertising mode.
+      Disconnect(endpoint_id);
+      return;
+    }
+
+    auto result = connections_.emplace(
+        endpoint_id, std::make_unique<NearbyConnectionImpl>(this, endpoint_id));
+    DCHECK(result.second);
+    incoming_connection_listener_->OnIncomingConnection(
+        endpoint_id, it->second->endpoint_info, result.first->second.get());
   } else {
     auto it = pending_outgoing_connections_.find(endpoint_id);
     if (it == pending_outgoing_connections_.end()) {
@@ -324,5 +386,6 @@
   nearby_connections_ = nullptr;
   discovered_endpoints_.clear();
   discovery_listener_ = nullptr;
+  incoming_connection_listener_ = nullptr;
   endpoint_discovery_listener_.reset();
 }
diff --git a/chrome/browser/nearby_sharing/nearby_connections_manager_impl.h b/chrome/browser/nearby_sharing/nearby_connections_manager_impl.h
index 1c4e3745..0d824bf 100644
--- a/chrome/browser/nearby_sharing/nearby_connections_manager_impl.h
+++ b/chrome/browser/nearby_sharing/nearby_connections_manager_impl.h
@@ -15,6 +15,7 @@
 #include "chrome/browser/nearby_sharing/nearby_process_manager.h"
 #include "chrome/services/sharing/public/mojom/nearby_connections.mojom.h"
 #include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
 
 class Profile;
 
@@ -63,6 +64,9 @@
   void UpgradeBandwidth(const std::string& endpoint_id) override;
 
  private:
+  using AdvertisingOptions =
+      location::nearby::connections::mojom::AdvertisingOptions;
+  using MediumSelection = location::nearby::connections::mojom::MediumSelection;
   using DiscoveryOptions =
       location::nearby::connections::mojom::DiscoveryOptions;
   using EndpointDiscoveryListener =
@@ -106,6 +110,7 @@
 
   NearbyProcessManager* process_manager_;
   Profile* profile_;
+  IncomingConnectionListener* incoming_connection_listener_ = nullptr;
   DiscoveryListener* discovery_listener_ = nullptr;
   base::flat_set<std::string> discovered_endpoints_;
   // A map of endpoint_id to NearbyConnectionCallback.
@@ -120,8 +125,8 @@
   ScopedObserver<NearbyProcessManager, NearbyProcessManager::Observer>
       nearby_process_observer_{this};
   mojo::Receiver<EndpointDiscoveryListener> endpoint_discovery_listener_{this};
-  mojo::Receiver<ConnectionLifecycleListener> connection_lifecycle_listener_{
-      this};
+  mojo::ReceiverSet<ConnectionLifecycleListener>
+      connection_lifecycle_listeners_;
 
   location::nearby::connections::mojom::NearbyConnections* nearby_connections_ =
       nullptr;
diff --git a/chrome/browser/nearby_sharing/nearby_connections_manager_impl_unittest.cc b/chrome/browser/nearby_sharing/nearby_connections_manager_impl_unittest.cc
index 31d6f3c..867a734 100644
--- a/chrome/browser/nearby_sharing/nearby_connections_manager_impl_unittest.cc
+++ b/chrome/browser/nearby_sharing/nearby_connections_manager_impl_unittest.cc
@@ -17,6 +17,7 @@
 #include "chrome/test/base/testing_profile.h"
 #include "content/public/test/browser_task_environment.h"
 #include "mojo/public/cpp/bindings/remote.h"
+#include "net/base/mock_network_change_notifier.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -40,6 +41,7 @@
 using DiscoveredEndpointInfo =
     location::nearby::connections::mojom::DiscoveredEndpointInfo;
 using ConnectionInfo = location::nearby::connections::mojom::ConnectionInfo;
+using MediumSelection = location::nearby::connections::mojom::MediumSelection;
 
 class MockDiscoveryListener
     : public NearbyConnectionsManager::DiscoveryListener {
@@ -55,6 +57,17 @@
               (override));
 };
 
+class MockIncomingConnectionListener
+    : public NearbyConnectionsManager::IncomingConnectionListener {
+ public:
+  MOCK_METHOD(void,
+              OnIncomingConnection,
+              (const std::string& endpoint_id,
+               const std::vector<uint8_t>& endpoint_info,
+               NearbyConnection* connection),
+              (override));
+};
+
 class NearbyConnectionsManagerImplTest : public testing::Test {
  public:
   void SetUp() override {
@@ -85,6 +98,33 @@
                                                callback.Get());
   }
 
+  void StartAdvertising(
+      mojo::Remote<ConnectionLifecycleListener>& listener_remote,
+      testing::NiceMock<MockIncomingConnectionListener>&
+          incoming_connection_listener) {
+    const std::vector<uint8_t> local_endpoint_info(std::begin(kEndpointInfo),
+                                                   std::end(kEndpointInfo));
+    EXPECT_CALL(nearby_connections_, StartAdvertising)
+        .WillOnce(
+            [&](const std::vector<uint8_t>& endpoint_info,
+                const std::string& service_id, AdvertisingOptionsPtr options,
+                mojo::PendingRemote<ConnectionLifecycleListener> listener,
+                NearbyConnectionsMojom::StartAdvertisingCallback callback) {
+              EXPECT_EQ(local_endpoint_info, endpoint_info);
+              EXPECT_EQ(kServiceId, service_id);
+              EXPECT_EQ(kStrategy, options->strategy);
+              EXPECT_TRUE(options->enforce_topology_constraints);
+
+              listener_remote.Bind(std::move(listener));
+              std::move(callback).Run(Status::kSuccess);
+            });
+    base::MockCallback<NearbyConnectionsManager::ConnectionsCallback> callback;
+    EXPECT_CALL(callback, Run(testing::Eq(Status::kSuccess)));
+    nearby_connections_manager_.StartAdvertising(
+        local_endpoint_info, &incoming_connection_listener,
+        PowerLevel::kHighPower, DataUsage::kOnline, callback.Get());
+  }
+
   enum class ConnectionResponse { kAccepted, kRejceted, kDisconnected };
 
   NearbyConnection* Connect(
@@ -145,6 +185,8 @@
 
   content::BrowserTaskEnvironment task_environment_;
   TestingProfile profile_;
+  std::unique_ptr<net::test::MockNetworkChangeNotifier> network_notifier_ =
+      net::test::MockNetworkChangeNotifier::Create();
   testing::NiceMock<MockNearbyConnections> nearby_connections_;
   testing::NiceMock<MockNearbyProcessManager> nearby_process_manager_;
   NearbyConnectionsManagerImpl nearby_connections_manager_{
@@ -254,7 +296,7 @@
       nearby_connections_manager_.GetRawAuthenticationToken(kRemoteEndpointId));
 }
 
-TEST_F(NearbyConnectionsManagerImplTest, ConnectDisconnted) {
+TEST_F(NearbyConnectionsManagerImplTest, ConnectDisconnected) {
   // StartDiscovery will succeed.
   mojo::Remote<EndpointDiscoveryListener> discovery_listener_remote;
   testing::NiceMock<MockDiscoveryListener> discovery_listener;
@@ -481,3 +523,117 @@
   EXPECT_FALSE(
       nearby_connections_manager_.GetRawAuthenticationToken(kRemoteEndpointId));
 }
+
+TEST_F(NearbyConnectionsManagerImplTest, StartAdvertising) {
+  mojo::Remote<ConnectionLifecycleListener> listener_remote;
+  testing::NiceMock<MockIncomingConnectionListener>
+      incoming_connection_listener;
+  StartAdvertising(listener_remote, incoming_connection_listener);
+
+  const std::vector<uint8_t> remote_endpoint_info(
+      std::begin(kRemoteEndpointInfo), std::end(kRemoteEndpointInfo));
+  const std::vector<uint8_t> raw_authentication_token(
+      std::begin(kRawAuthenticationToken), std::end(kRawAuthenticationToken));
+
+  base::RunLoop run_loop;
+  EXPECT_CALL(
+      incoming_connection_listener,
+      OnIncomingConnection(kRemoteEndpointId, remote_endpoint_info, testing::_))
+      .WillOnce([&](const std::string&, const std::vector<uint8_t>&,
+                    NearbyConnection* connection) {
+        EXPECT_TRUE(connection);
+        run_loop.Quit();
+      });
+
+  listener_remote->OnConnectionInitiated(
+      kRemoteEndpointId,
+      ConnectionInfo::New(kAuthenticationToken, raw_authentication_token,
+                          remote_endpoint_info,
+                          /*is_incoming_connection=*/true));
+
+  listener_remote->OnConnectionAccepted(kRemoteEndpointId);
+
+  run_loop.Run();
+
+  EXPECT_EQ(
+      raw_authentication_token,
+      nearby_connections_manager_.GetRawAuthenticationToken(kRemoteEndpointId));
+}
+
+using MediumsTestParam = std::
+    tuple<PowerLevel, DataUsage, net::NetworkChangeNotifier::ConnectionType>;
+class NearbyConnectionsManagerImplTestMediums
+    : public NearbyConnectionsManagerImplTest,
+      public testing::WithParamInterface<MediumsTestParam> {};
+
+TEST_P(NearbyConnectionsManagerImplTestMediums,
+       StartAdvertising_MediumSelection) {
+  const MediumsTestParam& param = GetParam();
+  PowerLevel power_level = std::get<0>(param);
+  DataUsage data_usage = std::get<1>(param);
+  net::NetworkChangeNotifier::ConnectionType connection_type =
+      std::get<2>(param);
+
+  network_notifier_->SetConnectionType(connection_type);
+  bool should_use_web_rtc =
+      data_usage != DataUsage::kOffline &&
+      power_level != PowerLevel::kLowPower &&
+      connection_type != net::NetworkChangeNotifier::CONNECTION_NONE &&
+      (data_usage != DataUsage::kWifiOnly ||
+       !net::NetworkChangeNotifier::IsConnectionCellular(connection_type));
+
+  bool is_high_power = power_level == PowerLevel::kHighPower;
+  auto expected_mediums = MediumSelection::New(
+      /*bluetooth=*/is_high_power,
+      /*web_rtc=*/should_use_web_rtc,
+      /*wifi_lan=*/is_high_power);
+
+  const std::vector<uint8_t> local_endpoint_info(std::begin(kEndpointInfo),
+                                                 std::end(kEndpointInfo));
+  base::MockCallback<NearbyConnectionsManager::ConnectionsCallback> callback;
+  testing::NiceMock<MockIncomingConnectionListener>
+      incoming_connection_listener;
+
+  EXPECT_CALL(nearby_connections_, StartAdvertising)
+      .WillOnce([&](const std::vector<uint8_t>& endpoint_info,
+                    const std::string& service_id,
+                    AdvertisingOptionsPtr options,
+                    mojo::PendingRemote<ConnectionLifecycleListener> listener,
+                    NearbyConnectionsMojom::StartAdvertisingCallback callback) {
+        EXPECT_EQ(is_high_power, options->auto_upgrade_bandwidth);
+        EXPECT_EQ(expected_mediums, options->allowed_mediums);
+        std::move(callback).Run(Status::kSuccess);
+      });
+  EXPECT_CALL(callback, Run(testing::Eq(Status::kSuccess)));
+
+  nearby_connections_manager_.StartAdvertising(
+      local_endpoint_info, &incoming_connection_listener, power_level,
+      data_usage, callback.Get());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    NearbyConnectionsManagerImplTestMediums,
+    NearbyConnectionsManagerImplTestMediums,
+    testing::Combine(
+        testing::Values(PowerLevel::kLowPower, PowerLevel::kHighPower),
+        testing::Values(DataUsage::kWifiOnly,
+                        DataUsage::kOffline,
+                        DataUsage::kOnline),
+        testing::Values(net::NetworkChangeNotifier::CONNECTION_NONE,
+                        net::NetworkChangeNotifier::CONNECTION_WIFI,
+                        net::NetworkChangeNotifier::CONNECTION_3G)));
+
+TEST_F(NearbyConnectionsManagerImplTest, StopAdvertising_BeforeStart) {
+  EXPECT_CALL(nearby_connections_, StopAdvertising).Times(0);
+  nearby_connections_manager_.StopAdvertising();
+}
+
+TEST_F(NearbyConnectionsManagerImplTest, StopAdvertising) {
+  mojo::Remote<ConnectionLifecycleListener> listener_remote;
+  testing::NiceMock<MockIncomingConnectionListener>
+      incoming_connection_listener;
+  StartAdvertising(listener_remote, incoming_connection_listener);
+
+  EXPECT_CALL(nearby_connections_, StopAdvertising);
+  nearby_connections_manager_.StopAdvertising();
+}
diff --git a/chrome/browser/nearby_sharing/nearby_sharing_service.h b/chrome/browser/nearby_sharing/nearby_sharing_service.h
index e1807fc..f897f4819 100644
--- a/chrome/browser/nearby_sharing/nearby_sharing_service.h
+++ b/chrome/browser/nearby_sharing/nearby_sharing_service.h
@@ -33,6 +33,14 @@
     kOk,
     // The operation failed since it was called in an invalid order.
     kOutOfOrderApiCall,
+    // Tried to stop something that was already stopped.
+    kStatusAlreadyStopped,
+    // Tried to register an opposite foreground surface in the midst of a
+    // transfer or connection.
+    // (Tried to register Send Surface when receiving a file or tried to
+    // register Receive Surface when
+    // sending a file.)
+    kTransferAlreadyInProgress,
   };
 
   enum class ReceiveSurfaceState {
diff --git a/chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc b/chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc
index 057bb77..911f61f 100644
--- a/chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc
+++ b/chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc
@@ -7,8 +7,10 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/logging.h"
+#include "base/task/post_task.h"
 #include "base/task_runner_util.h"
-#include "base/threading/thread_task_runner_handle.h"
+#include "base/threading/sequenced_task_runner_handle.h"
 #include "chrome/browser/nearby_sharing/certificates/nearby_share_certificate_manager_impl.h"
 #include "chrome/browser/nearby_sharing/client/nearby_share_client_impl.h"
 #include "chrome/browser/nearby_sharing/common/nearby_share_prefs.h"
@@ -34,6 +36,9 @@
     base::TimeDelta::FromSeconds(60);
 constexpr base::TimeDelta kIncomingRejectionDelay =
     base::TimeDelta::FromSeconds(2);
+// Time to delay running the task to invalidate send and receive surfaces.
+constexpr base::TimeDelta kInvalidateDelay =
+    base::TimeDelta::FromMilliseconds(500);
 
 std::string ReceiveSurfaceStateToString(
     NearbySharingService::ReceiveSurfaceState state) {
@@ -130,8 +135,7 @@
     Profile* profile,
     std::unique_ptr<NearbyConnectionsManager> nearby_connections_manager,
     NearbyProcessManager* process_manager)
-    : prefs_(prefs),
-      profile_(profile),
+    : profile_(profile),
       settings_(prefs),
       nearby_connections_manager_(std::move(nearby_connections_manager)),
       process_manager_(process_manager),
@@ -146,7 +150,6 @@
       contact_manager_(NearbyShareContactManagerImpl::Factory::Create()),
       certificate_manager_(
           NearbyShareCertificateManagerImpl::Factory::Create()) {
-  DCHECK(prefs_);
   DCHECK(profile_);
   DCHECK(nearby_connections_manager_);
 
@@ -178,7 +181,61 @@
     ShareTargetDiscoveredCallback* discovery_callback,
     SendSurfaceState state) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  // TODO(crrbug.com/1084644): Implement send surface logic.
+  DCHECK(transfer_callback);
+  DCHECK(discovery_callback);
+  DCHECK_NE(state, SendSurfaceState::kUnknown);
+
+  if (!process_manager_->IsActiveProfile(profile_)) {
+    NS_LOG(VERBOSE) << __func__
+                    << ": RegisterSendSurface failed, since profile not active";
+    return StatusCodes::kError;
+  }
+
+  if (foreground_send_transfer_callbacks_.HasObserver(transfer_callback) ||
+      background_send_transfer_callbacks_.HasObserver(transfer_callback)) {
+    NS_LOG(VERBOSE) << __func__
+                    << ": RegisterSendSurface failed. Already registered.";
+    return StatusCodes::kError;
+  }
+
+  if (state == SendSurfaceState::kForeground) {
+    foreground_send_transfer_callbacks_.AddObserver(transfer_callback);
+    foreground_send_discovery_callbacks_.AddObserver(discovery_callback);
+  } else {
+    background_send_transfer_callbacks_.AddObserver(transfer_callback);
+    background_send_discovery_callbacks_.AddObserver(discovery_callback);
+  }
+
+  NS_LOG(VERBOSE) << __func__ << ": RegisterSendSurface";
+
+  if (is_receiving_files_) {
+    UnregisterSendSurface(transfer_callback, discovery_callback);
+    NS_LOG(VERBOSE)
+        << __func__
+        << ": Ignore registering (and unregistering if registered) send "
+           "surface because we're currently receiving files.";
+    return StatusCodes::kTransferAlreadyInProgress;
+  }
+
+  // If the share sheet to be registered is a foreground surface, let it catch
+  // up with most recent transfer metadata immediately.
+  if (state == SendSurfaceState::kForeground && last_outgoing_metadata_) {
+    // When a new share sheet is registered, we want to immediately show the
+    // in-progress bar.
+    discovery_callback->OnShareTargetDiscovered(last_outgoing_metadata_->first);
+    transfer_callback->OnTransferUpdate(last_outgoing_metadata_->first,
+                                        last_outgoing_metadata_->second);
+  }
+
+  // Let newly registered send surface catch up with discovered share targets
+  // from current scanning session.
+  for (const std::pair<std::string, ShareTarget>& item :
+       outgoing_share_target_map_) {
+    discovery_callback->OnShareTargetDiscovered(item.second);
+  }
+
+  NS_LOG(VERBOSE) << __func__ << ": A SendSurface has been registered.";
+  InvalidateSendSurfaceState();
   if (state == SendSurfaceState::kForeground)
     StartFastInitiationAdvertising();
   return StatusCodes::kOk;
@@ -189,6 +246,46 @@
     TransferUpdateCallback* transfer_callback,
     ShareTargetDiscoveredCallback* discovery_callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(transfer_callback);
+  DCHECK(discovery_callback);
+  if (!foreground_send_transfer_callbacks_.HasObserver(transfer_callback) &&
+      !background_send_transfer_callbacks_.HasObserver(transfer_callback)) {
+    NS_LOG(VERBOSE)
+        << __func__
+        << ": unregisterSendSurface failed. Unknown TransferUpdateCallback";
+    return StatusCodes::kError;
+  }
+
+  if (foreground_send_transfer_callbacks_.might_have_observers() &&
+      last_outgoing_metadata_ &&
+      last_outgoing_metadata_->second.is_final_status()) {
+    // We already saw the final status in the foreground
+    // Nullify it so the next time the user opens sharing, it starts the UI from
+    // the beginning
+    last_outgoing_metadata_.reset();
+  }
+
+  if (foreground_send_transfer_callbacks_.HasObserver(transfer_callback)) {
+    foreground_send_transfer_callbacks_.RemoveObserver(transfer_callback);
+    foreground_send_discovery_callbacks_.RemoveObserver(discovery_callback);
+  } else {
+    background_send_transfer_callbacks_.RemoveObserver(transfer_callback);
+    background_send_discovery_callbacks_.RemoveObserver(discovery_callback);
+  }
+
+  // Displays the most recent payload status processed by foreground surfaces on
+  // background surfaces.
+  if (!foreground_send_transfer_callbacks_.might_have_observers() &&
+      last_outgoing_metadata_) {
+    for (TransferUpdateCallback& background_transfer_callback :
+         background_send_transfer_callbacks_) {
+      background_transfer_callback.OnTransferUpdate(
+          last_outgoing_metadata_->first, last_outgoing_metadata_->second);
+    }
+  }
+
+  NS_LOG(VERBOSE) << __func__ << ": A SendSurface has been unregistered";
+  InvalidateSurfaceState();
   StopFastInitiationAdvertising();
   return StatusCodes::kOk;
 }
@@ -200,6 +297,14 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(transfer_callback);
   DCHECK_NE(state, ReceiveSurfaceState::kUnknown);
+
+  if (!process_manager_->IsActiveProfile(profile_)) {
+    NS_LOG(VERBOSE)
+        << __func__
+        << ": registerReceiveSurface failed, since profile not active";
+    return StatusCodes::kError;
+  }
+
   if (foreground_receive_callbacks_.HasObserver(transfer_callback) ||
       background_receive_callbacks_.HasObserver(transfer_callback)) {
     NS_LOG(VERBOSE) << __func__
@@ -272,8 +377,7 @@
   NS_LOG(VERBOSE) << __func__ << ": A ReceiveSurface("
                   << (is_foreground ? "foreground" : "background")
                   << ") has been unregistered";
-
-  InvalidateReceiveSurfaceState();
+  InvalidateSurfaceState();
   return StatusCodes::kOk;
 }
 
@@ -363,6 +467,8 @@
 
 void NearbySharingServiceImpl::OnNearbyProfileChanged(Profile* profile) {
   // TODO(crbug.com/1084576): Notify UI about the new active profile.
+  NS_LOG(VERBOSE) << __func__ << ": Nearby profile changed to "
+                  << process_manager_->IsActiveProfile(profile_);
 }
 
 void NearbySharingServiceImpl::OnNearbyProcessStarted() {
@@ -384,7 +490,6 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(connection);
   // TODO(crbug/1085068): Handle incoming connection; use CertificateManager
-
   // TODO(himanshujaju) - Update placeholder implementation
   ShareTarget share_target;
   share_target.is_incoming = true;
@@ -398,15 +503,128 @@
   ReceiveIntroduction(std::move(share_target), /*token=*/base::nullopt);
 }
 
+void NearbySharingServiceImpl::OnEndpointDiscovered(
+    const std::string& endpoint_id,
+    const std::vector<uint8_t>& endpoint_info) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!is_scanning_) {
+    NS_LOG(VERBOSE)
+        << __func__
+        << ": Ignoring discovered endpoint because we're no longer scanning";
+    return;
+  }
+
+  process_manager_->GetOrStartNearbySharingDecoder(profile_)
+      ->DecodeAdvertisement(
+          endpoint_info,
+          base::BindOnce(
+              &NearbySharingServiceImpl::OnOutgoingAdvertisementDecoded,
+              weak_ptr_factory_.GetWeakPtr(), endpoint_id));
+}
+
+void NearbySharingServiceImpl::OnOutgoingAdvertisementDecoded(
+    const std::string& endpoint_id,
+    sharing::mojom::AdvertisementPtr advertisement) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!advertisement) {
+    NS_LOG(VERBOSE) << __func__
+                    << ": Failed to parse discovered advertisement.";
+    return;
+  }
+
+  // Now we will report endpoints met before in NearbyConnectionsManager.
+  // Check outgoingShareTargetInfoMap first and pass the same shareTarget if we
+  // found one.
+
+  // Looking for the ShareTarget based on endpoint id.
+  if (outgoing_share_target_map_.find(endpoint_id) !=
+      outgoing_share_target_map_.end()) {
+    return;
+  }
+
+  // Once get the advertisement, first thing to do is to decrypt its device name
+  // based on its visibility and create a ShareTarget to represent this remote
+  // device.
+  base::Optional<ShareTarget> share_target =
+      CreateShareTarget(endpoint_id, std::move(advertisement),
+                        /*is_incoming=*/false);
+  if (!share_target) {
+    NS_LOG(VERBOSE) << __func__
+                    << ": Failed to convert advertisement to share target from "
+                       "discovered advertisement. Ignoring endpoint.";
+    return;
+  }
+
+  // Update the endpoint id for the share target.
+  NS_LOG(VERBOSE) << __func__
+                  << ": An endpoint has been discovered, with an advertisement "
+                     "containing a valid share target.";
+  GetOrCreateOutgoingShareTargetInfo(*share_target, endpoint_id)
+      .set_endpoint_id(endpoint_id);
+
+  // Notifies the user that we discovered a device.
+  for (ShareTargetDiscoveredCallback& discovery_callback :
+       foreground_send_discovery_callbacks_) {
+    discovery_callback.OnShareTargetDiscovered(*share_target);
+  }
+  for (ShareTargetDiscoveredCallback& discovery_callback :
+       background_send_discovery_callbacks_) {
+    discovery_callback.OnShareTargetDiscovered(*share_target);
+  }
+
+  NS_LOG(VERBOSE) << __func__ << ": Reported OnShareTargetDiscovered "
+                  << (base::Time::Now() - scanning_start_timestamp_);
+
+  // TODO(crbug/1108348) CachingManager should cache known and non-external
+  // share targets.
+}
+
+void NearbySharingServiceImpl::OnEndpointLost(const std::string& endpoint_id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!is_scanning_) {
+    NS_LOG(VERBOSE)
+        << __func__
+        << ": Ignoring lost endpoint because we're no longer scanning";
+    return;
+  }
+
+  // Remove the share target with this endpoint id.
+  auto it = outgoing_share_target_map_.find(endpoint_id);
+  if (it == outgoing_share_target_map_.end()) {
+    NS_LOG(VERBOSE) << __func__
+                    << ": Ignoring lost endpoint because we don't have an "
+                       "associated ShareTarget";
+    return;
+  }
+
+  ShareTarget share_target = std::move(it->second);
+  outgoing_share_target_info_map_.erase(share_target.id);
+  outgoing_share_target_map_.erase(it);
+
+  for (ShareTargetDiscoveredCallback& discovery_callback :
+       foreground_send_discovery_callbacks_) {
+    discovery_callback.OnShareTargetLost(share_target);
+  }
+  for (ShareTargetDiscoveredCallback& discovery_callback :
+       background_send_discovery_callbacks_) {
+    discovery_callback.OnShareTargetLost(share_target);
+  }
+
+  NS_LOG(VERBOSE) << __func__ << ": Reported onShareTargetLost";
+}
+
 void NearbySharingServiceImpl::OnEnabledChanged(bool enabled) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (enabled) {
     NS_LOG(VERBOSE) << __func__ << ": Nearby sharing enabled!";
   } else {
     NS_LOG(VERBOSE) << __func__ << ": Nearby sharing disabled!";
     StopAdvertising();
+    StopScanning();
     // TODO(crbug/1085067): Stop discovery.
     nearby_connections_manager_->Shutdown();
   }
+  InvalidateSurfaceState();
 }
 
 void NearbySharingServiceImpl::FlushMojoForTesting() {
@@ -437,25 +655,43 @@
 }
 
 void NearbySharingServiceImpl::OnVisibilityChanged(Visibility new_visibility) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   NS_LOG(VERBOSE) << __func__ << ": Nearby sharing visibility changed to "
                   << VisibilityToString(new_visibility);
-
-  if (advertising_power_level_ != PowerLevel::kUnknown) {
+  if (advertising_power_level_ != PowerLevel::kUnknown)
     StopAdvertising();
-  }
 
-  InvalidateReceiveSurfaceState();
+  InvalidateSurfaceState();
 }
 
 void NearbySharingServiceImpl::OnDataUsageChanged(DataUsage data_usage) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   NS_LOG(VERBOSE) << __func__ << ": Nearby sharing data usage changed to "
                   << DataUsageToString(data_usage);
 
-  if (advertising_power_level_ != PowerLevel::kUnknown) {
+  if (advertising_power_level_ != PowerLevel::kUnknown)
     StopAdvertising();
-  }
 
-  InvalidateReceiveSurfaceState();
+  InvalidateSurfaceState();
+}
+
+const base::Optional<std::vector<uint8_t>>
+NearbySharingServiceImpl::CreateEndpointInfo(
+    const base::Optional<std::string>& device_name) {
+  // TODO(nmusgrave) fill values from CertificateManager
+  std::vector<uint8_t> salt(sharing::Advertisement::kSaltSize, 0);
+  std::vector<uint8_t> encrypted_metadata_key(
+      sharing::Advertisement::kMetadataEncryptionKeyHashByteSize, 0);
+
+  // TODO(nmusgrave) fill value from local device data manager
+  std::unique_ptr<sharing::Advertisement> advertisement =
+      sharing::Advertisement::NewInstance(
+          std::move(salt), std::move(encrypted_metadata_key), device_name);
+  if (advertisement) {
+    return advertisement->ToEndpointInfo();
+  } else {
+    return base::nullopt;
+  }
 }
 
 void NearbySharingServiceImpl::OnDeviceNameChanged(
@@ -557,6 +793,19 @@
   return IsBluetoothPresent() && bluetooth_adapter_->IsPowered();
 }
 
+bool NearbySharingServiceImpl::HasAvailableConnectionMediums() {
+  // Check if Wifi or Ethernet LAN is off.  Advertisements won't work, so
+  // disable them, unless bluetooth is known to be enabled. Not all platforms
+  // have bluetooth, so wifi LAN is a platform-agnostic check.
+  net::NetworkChangeNotifier::ConnectionType connection_type =
+      net::NetworkChangeNotifier::GetConnectionType();
+  return IsBluetoothPresent() ||
+         (connection_type ==
+              net::NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI ||
+          connection_type ==
+              net::NetworkChangeNotifier::ConnectionType::CONNECTION_ETHERNET);
+}
+
 void NearbySharingServiceImpl::AdapterPresentChanged(
     device::BluetoothAdapter* adapter,
     bool present) {
@@ -571,9 +820,14 @@
     StopFastInitiationAdvertising();
 }
 
+void NearbySharingServiceImpl::InvalidateSurfaceState() {
+  InvalidateSendSurfaceState();
+  InvalidateReceiveSurfaceState();
+}
+
 void NearbySharingServiceImpl::InvalidateReceiveSurfaceState() {
   InvalidateAdvertisingState();
-  // TODO(crbug/154846208) InvalidateFastInitScan();
+  // TODO(b/161889067) InvalidateFastInitScan();
 }
 
 void NearbySharingServiceImpl::InvalidateAdvertisingState() {
@@ -585,16 +839,7 @@
     return;
   }
 
-  // Check if Wifi or Ethernet LAN is off.  Advertisements won't work, so
-  // disable them, unless bluetooth is known to be enabled. Not all platforms
-  // have bluetooth, so wifi LAN is a platform-agnostic check.
-  net::NetworkChangeNotifier::ConnectionType connection_type =
-      net::NetworkChangeNotifier::GetConnectionType();
-  if (!IsBluetoothPresent() &&
-      !(connection_type ==
-            net::NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI ||
-        connection_type ==
-            net::NetworkChangeNotifier::ConnectionType::CONNECTION_ETHERNET)) {
+  if (!HasAvailableConnectionMediums()) {
     StopAdvertising();
     NS_LOG(VERBOSE)
         << __func__
@@ -621,7 +866,7 @@
     return;
   }
 
-  if (is_transferring_files_) {
+  if (is_transferring_) {
     StopAdvertising();
     NS_LOG(VERBOSE)
         << __func__
@@ -664,10 +909,10 @@
     if (power_level == advertising_power_level_) {
       NS_LOG(VERBOSE)
           << __func__
-          << "Failed to advertise because we're already advertising with power"
-          << " level " << PowerLevelToString(advertising_power_level_)
-          << " and data usage preference "
-          << DataUsageToString(settings_.GetDataUsage());
+          << ": Failed to advertise because we're already advertising with "
+             "power level "
+          << PowerLevelToString(advertising_power_level_)
+          << " and data usage preference " << DataUsageToString(data_usage);
       return;
     }
 
@@ -678,23 +923,23 @@
                     << DataUsageToString(data_usage);
   }
 
+  base::Optional<std::string> device_name;
+  if (foreground_receive_callbacks_.might_have_observers())
+    device_name = local_device_data_manager_->GetDeviceName();
+
   // Starts advertising through Nearby Connections. Caller is expected to ensure
   // |listener| remains valid until StopAdvertising is called.
+  base::Optional<std::vector<uint8_t>> endpoint_info =
+      CreateEndpointInfo(device_name);
+  if (!endpoint_info) {
+    NS_LOG(VERBOSE) << __func__
+                    << ": Unable to advertise since could not parse the "
+                       "endpoint info from the advertisement.";
+    return;
+  }
 
-  // TODO(nmusgrave) fill values from CertificateManager
-  std::vector<uint8_t> salt(sharing::Advertisement::kSaltSize, 0);
-  std::vector<uint8_t> encrypted_metadata_key(
-      sharing::Advertisement::kMetadataEncryptionKeyHashByteSize, 0);
-
-  // TODO(nmusgrave) fill value from local device data manager
-  base::Optional<std::string> device_name = "todo_device_name";
-  std::vector<uint8_t> endpoint_info =
-      sharing::Advertisement::NewInstance(std::move(salt),
-                                          std::move(encrypted_metadata_key),
-                                          std::move(device_name))
-          ->ToEndpointInfo();
   nearby_connections_manager_->StartAdvertising(
-      std::move(endpoint_info),
+      *endpoint_info,
       /* listener= */ this, power_level, data_usage,
       base::BindOnce([](NearbyConnectionsManager::ConnectionsStatus status) {
         NS_LOG(VERBOSE)
@@ -722,11 +967,139 @@
   }
 
   nearby_connections_manager_->StopAdvertising();
-
   advertising_power_level_ = PowerLevel::kUnknown;
   NS_LOG(VERBOSE) << __func__ << ": Advertising has stopped";
 }
 
+void NearbySharingServiceImpl::InvalidateSendSurfaceState() {
+  InvalidateScanningState();
+  // TODO(b/161889067) InvalidateFastInitAdvertisement();
+}
+
+void NearbySharingServiceImpl::InvalidateScanningState() {
+  // Screen is off. Do no work.
+  if (ui::CheckIdleStateIsLocked()) {
+    StopScanning();
+    NS_LOG(VERBOSE) << __func__
+                    << ": Stopping discovery because the screen is locked.";
+    return;
+  }
+
+  if (!HasAvailableConnectionMediums()) {
+    StopScanning();
+    NS_LOG(VERBOSE)
+        << __func__
+        << ": Stopping scanning because both bluetooth and wifi LAN are "
+           "disabled.";
+    return;
+  }
+
+  // Nearby Sharing is disabled. Don't advertise.
+  if (!settings_.GetEnabled()) {
+    StopScanning();
+    NS_LOG(VERBOSE)
+        << __func__
+        << ": Stopping discovery because Nearby Sharing is disabled.";
+    return;
+  }
+
+  if (is_transferring_ || is_connecting_) {
+    StopScanning();
+    NS_LOG(VERBOSE)
+        << __func__
+        << ": Stopping discovery because we're currently in the midst of a "
+           "transfer.";
+    return;
+  }
+
+  if (!foreground_send_transfer_callbacks_.might_have_observers()) {
+    StopScanning();
+    NS_LOG(VERBOSE)
+        << __func__
+        << ": Stopping discovery because no scanning surface has been "
+           "registered.";
+    return;
+  }
+
+  // Screen is on, Bluetooth is enabled, and Nearby Sharing is enabled! Start
+  // discovery.
+  StartScanning();
+}
+
+void NearbySharingServiceImpl::StartScanning() {
+  if (!settings_.GetEnabled()) {
+    NS_LOG(VERBOSE) << __func__
+                    << ": Failed to scan because we're not enabled.";
+    return;
+  }
+
+  if (ui::CheckIdleStateIsLocked()) {
+    NS_LOG(VERBOSE) << __func__
+                    << ": Failed to scan because the user's screen is locked.";
+    return;
+  }
+
+  if (!HasAvailableConnectionMediums()) {
+    NS_LOG(VERBOSE) << __func__ << ": Failed to scan because Bluetooth is off.";
+    return;
+  }
+
+  if (is_scanning_) {
+    NS_LOG(VERBOSE) << __func__
+                    << ": Failed to scan because we're currently scanning.";
+    return;
+  }
+
+  if (!foreground_send_transfer_callbacks_.might_have_observers()) {
+    NS_LOG(VERBOSE)
+        << __func__
+        << ": Failed to scan because there's no scanning send surface "
+           "registered.";
+    return;
+  }
+
+  scanning_start_timestamp_ = base::Time::Now();
+  is_scanning_ = true;
+  InvalidateReceiveSurfaceState();
+
+  ClearOutgoingShareTargetInfoMap();
+
+  nearby_connections_manager_->StartDiscovery(
+      /* listener= */ this,
+      base::BindOnce([](NearbyConnectionsManager::ConnectionsStatus status) {
+        NS_LOG(VERBOSE) << __func__
+                        << ": Scanning start attempted over Nearby Connections "
+                           "with result "
+                        << ConnectionsStatusToString(status);
+      }));
+
+  InvalidateSendSurfaceState();
+  NS_LOG(VERBOSE) << __func__ << ": Scanning has started";
+}
+
+NearbySharingService::StatusCodes NearbySharingServiceImpl::StopScanning() {
+  if (!is_scanning_) {
+    NS_LOG(VERBOSE) << __func__
+                    << ": Failed to stop scanning because weren't scanning.";
+    return StatusCodes::kStatusAlreadyStopped;
+  }
+
+  nearby_connections_manager_->StopDiscovery();
+  is_scanning_ = false;
+
+  // Note: We don't know if we stopped scanning in preparation to send a file,
+  // or we stopped because the user left the page. We'll invalidate after a
+  // short delay.
+  base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(&NearbySharingServiceImpl::InvalidateSurfaceState,
+                     weak_ptr_factory_.GetWeakPtr()),
+      kInvalidateDelay);
+
+  NS_LOG(VERBOSE) << __func__ << ": Scanning has stopped.";
+  return StatusCodes::kOk;
+}
+
 void NearbySharingServiceImpl::OnIncomingTransferUpdate(
     const ShareTarget& share_target,
     TransferMetadata metadata) {
@@ -1074,17 +1447,6 @@
   UnregisterShareTarget(share_target);
 }
 
-void NearbySharingServiceImpl::UnregisterShareTarget(
-    const ShareTarget& share_target) {
-  if (share_target.is_incoming) {
-    incoming_share_target_info_map_.erase(share_target.id);
-    nearby_connections_manager_->ClearIncomingPayloads();
-  } else {
-    // TODO(crbug.com/1084644) - Clear from outgoing map.
-  }
-  mutual_acceptance_timeout_alarm_.Cancel();
-}
-
 bool NearbySharingServiceImpl::IsOutOfStorage(const ShareTarget& share_target) {
   // TODO(himanshujaju) - Check storage space based on file path.
   return false;
@@ -1116,13 +1478,68 @@
   return GetIncomingShareTargetInfo(share_target).connection();
 }
 
-OutgoingShareTargetInfo& NearbySharingServiceImpl::GetOutgoingShareTargetInfo(
-    const ShareTarget& share_target) {
+OutgoingShareTargetInfo&
+NearbySharingServiceImpl::GetOrCreateOutgoingShareTargetInfo(
+    const ShareTarget& share_target,
+    const std::string& endpoint_id) {
+  // Default initialize outgoing_share_target_map_ as well, since Share Target
+  // needs to have fields explicitly set.
+  outgoing_share_target_map_.emplace(endpoint_id, share_target);
   return outgoing_share_target_info_map_[share_target.id];
 }
 
 void NearbySharingServiceImpl::ClearOutgoingShareTargetInfoMap() {
+  // TODO(crbug.com/1085068) close file payloads
   outgoing_share_target_info_map_.clear();
+  outgoing_share_target_map_.clear();
+}
+
+base::Optional<ShareTarget> NearbySharingServiceImpl::CreateShareTarget(
+    const std::string& endpoint_id,
+    sharing::mojom::AdvertisementPtr advertisement,
+    bool is_incoming) {
+  if (!advertisement->device_name) {
+    // TODO(crbug/1085068): Handle incoming connection; relies upon
+    // CertificateManager
+    return base::nullopt;
+  }
+
+  return ShareTarget(*advertisement->device_name, /* image_url= */ GURL(),
+                     nearby_share::mojom::ShareTargetType::kUnknown,
+                     std::vector<TextAttachment>(),
+                     std::vector<FileAttachment>(), is_incoming,
+                     /* full_name= */ base::nullopt, /* is_known= */ false);
+}
+
+void NearbySharingServiceImpl::UnregisterShareTarget(
+    const ShareTarget& share_target) {
+  if (share_target.is_incoming) {
+    incoming_share_target_info_map_.erase(share_target.id);
+    // Clear legacy incoming payloads to release resource
+    nearby_connections_manager_->ClearIncomingPayloads();
+  } else {
+    // Find the endpoint id that matches the given share target.
+    base::Optional<std::string> endpoint_id;
+    auto it = outgoing_share_target_info_map_.find(share_target.id);
+    if (it != outgoing_share_target_info_map_.end())
+      endpoint_id = it->second.endpoint_id();
+
+    // Remove info except for this endpoint id, if present.
+    ClearOutgoingShareTargetInfoMap();
+
+    if (endpoint_id) {
+      NS_LOG(VERBOSE) << __func__ << ": Unregister share target: "
+                      << share_target.device_name;
+      GetOrCreateOutgoingShareTargetInfo(share_target, *endpoint_id)
+          .set_endpoint_id(*endpoint_id);
+    } else {
+      NS_LOG(VERBOSE)
+          << __func__
+          << ": Cannot unregister share target since none registered: "
+          << share_target.device_name;
+    }
+  }
+  mutual_acceptance_timeout_alarm_.Cancel();
 }
 
 void NearbySharingServiceImpl::SetAttachmentPayloadId(
diff --git a/chrome/browser/nearby_sharing/nearby_sharing_service_impl.h b/chrome/browser/nearby_sharing/nearby_sharing_service_impl.h
index 5089cd2..de8cbc8 100644
--- a/chrome/browser/nearby_sharing/nearby_sharing_service_impl.h
+++ b/chrome/browser/nearby_sharing/nearby_sharing_service_impl.h
@@ -16,6 +16,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/scoped_observer.h"
 #include "base/sequence_checker.h"
+#include "base/time/time.h"
 #include "base/unguessable_token.h"
 #include "chrome/browser/nearby_sharing/attachment.h"
 #include "chrome/browser/nearby_sharing/attachment_info.h"
@@ -23,6 +24,7 @@
 #include "chrome/browser/nearby_sharing/common/nearby_share_enums.h"
 #include "chrome/browser/nearby_sharing/incoming_frames_reader.h"
 #include "chrome/browser/nearby_sharing/incoming_share_target_info.h"
+#include "chrome/browser/nearby_sharing/local_device_data/nearby_share_local_device_data_manager.h"
 #include "chrome/browser/nearby_sharing/nearby_connections_manager.h"
 #include "chrome/browser/nearby_sharing/nearby_notification_manager.h"
 #include "chrome/browser/nearby_sharing/nearby_process_manager.h"
@@ -52,7 +54,8 @@
       public nearby_share::mojom::NearbyShareSettingsObserver,
       public NearbyProcessManager::Observer,
       public device::BluetoothAdapter::Observer,
-      public NearbyConnectionsManager::IncomingConnectionListener {
+      public NearbyConnectionsManager::IncomingConnectionListener,
+      public NearbyConnectionsManager::DiscoveryListener {
  public:
   explicit NearbySharingServiceImpl(
       PrefService* prefs,
@@ -117,8 +120,15 @@
   NearbyShareContactManager* GetContactManager() override;
   NearbyShareCertificateManager* GetCertificateManager() override;
 
+  // NearbyConnectionsManager::DiscoveryListener:
+  void OnEndpointDiscovered(const std::string& endpoint_id,
+                            const std::vector<uint8_t>& endpoint_info) override;
+  void OnEndpointLost(const std::string& endpoint_id) override;
+
  private:
   bool IsVisibleInBackground(Visibility visibility);
+  const base::Optional<std::vector<uint8_t>> CreateEndpointInfo(
+      const base::Optional<std::string>& device_name);
   void StartFastInitiationAdvertising();
   void StopFastInitiationAdvertising();
   void GetBluetoothAdapter();
@@ -126,12 +136,19 @@
   void OnStartFastInitiationAdvertising();
   void OnStartFastInitiationAdvertisingError();
   void OnStopFastInitiationAdvertising();
+  void OnOutgoingAdvertisementDecoded(
+      const std::string& endpoint_id,
+      sharing::mojom::AdvertisementPtr advertisement);
   bool IsBluetoothPresent() const;
   bool IsBluetoothPowered() const;
+  bool HasAvailableConnectionMediums();
   void AdapterPresentChanged(device::BluetoothAdapter* adapter,
                              bool present) override;
   void AdapterPoweredChanged(device::BluetoothAdapter* adapter,
                              bool powered) override;
+  void InvalidateSurfaceState();
+  void InvalidateSendSurfaceState();
+  void InvalidateScanningState();
   void InvalidateReceiveSurfaceState();
   void InvalidateAdvertisingState();
   void StopAdvertising();
@@ -143,6 +160,10 @@
       NearbyConnection& connection,
       sharing::nearby::ConnectionResponseFrame::Status reponse_status);
   void Fail(const ShareTarget& share_target, TransferMetadata::Status status);
+  void StartScanning(
+      base::Optional<ShareTargetDiscoveredCallback*> discovery_callback);
+  void StartScanning();
+  StatusCodes StopScanning();
   void OnIncomingTransferUpdate(const ShareTarget& share_target,
                                 TransferMetadata metadata);
   void CloseConnection(const ShareTarget& share_target);
@@ -158,7 +179,6 @@
       const sharing::mojom::CertificateInfoFramePtr& certificate_frame);
 
   void OnIncomingConnectionDisconnected(const ShareTarget& share_target);
-  void UnregisterShareTarget(const ShareTarget& share_target);
   bool IsOutOfStorage(const ShareTarget& share_target);
 
   void OnIncomingMutualAcceptanceTimeout(const ShareTarget& share_target);
@@ -166,14 +186,19 @@
   IncomingShareTargetInfo& GetIncomingShareTargetInfo(
       const ShareTarget& share_target);
   NearbyConnection* GetIncomingConnection(const ShareTarget& share_target);
-  OutgoingShareTargetInfo& GetOutgoingShareTargetInfo(
-      const ShareTarget& share_target);
+  OutgoingShareTargetInfo& GetOrCreateOutgoingShareTargetInfo(
+      const ShareTarget& share_target,
+      const std::string& endpoint_id);
   void ClearOutgoingShareTargetInfoMap();
   void SetAttachmentPayloadId(const Attachment& attachment, int64_t payload_id);
   base::Optional<int64_t> GetAttachmentPayloadId(
       const base::UnguessableToken& attachment_id);
+  base::Optional<ShareTarget> CreateShareTarget(
+      const std::string& endpoint_id,
+      sharing::mojom::AdvertisementPtr advertisement,
+      bool is_incoming);
+  void UnregisterShareTarget(const ShareTarget& share_target);
 
-  PrefService* prefs_;
   Profile* profile_;
   NearbyShareSettings settings_;
   std::unique_ptr<NearbyConnectionsManager> nearby_connections_manager_;
@@ -193,6 +218,20 @@
   base::ObserverList<TransferUpdateCallback> foreground_receive_callbacks_;
   // A list of foreground receivers.
   base::ObserverList<TransferUpdateCallback> background_receive_callbacks_;
+  // A list of foreground receivers for transfer updates on the send surface.
+  base::ObserverList<TransferUpdateCallback>
+      foreground_send_transfer_callbacks_;
+  // A list of foreground receivers for discovered device updates on the send
+  // surface.
+  base::ObserverList<ShareTargetDiscoveredCallback>
+      foreground_send_discovery_callbacks_;
+  // A list of background receivers for transfer updates on the send surface.
+  base::ObserverList<TransferUpdateCallback>
+      background_send_transfer_callbacks_;
+  // A list of background receivers for discovered device updates on the send
+  // surface.
+  base::ObserverList<ShareTargetDiscoveredCallback>
+      background_send_discovery_callbacks_;
 
   // Registers the most recent TransferMetadata and ShareTarget used for
   // transitioning notifications between foreground surfaces and background
@@ -207,6 +246,10 @@
   // incoming share target.
   base::flat_map<base::UnguessableToken, IncomingShareTargetInfo>
       incoming_share_target_info_map_;
+  // A map of endpoint id to ShareTarget, where each ShareTarget entry
+  // directly corresponds to a OutgoingShareTargetInfo entry in
+  // outgoing_share_target_info_map_;
+  base::flat_map<std::string, ShareTarget> outgoing_share_target_map_;
   // A map of ShareTarget id to OutgoingShareTargetInfo. This lets us know which
   // endpoint and public certificate are related to the outgoing share target.
   // TODO(crbug/1085068) update this map when handling payloads
@@ -227,7 +270,13 @@
   // True if we are currently scanning for remote devices.
   bool is_scanning_ = false;
   // True if we're currently sending or receiving a file.
-  bool is_transferring_files_ = false;
+  bool is_transferring_ = false;
+  // True if we're currently receiving a file.
+  bool is_receiving_files_ = false;
+  // True if we're currently attempting to connect to a remote device.
+  bool is_connecting_ = false;
+  // The time scanning began.
+  base::Time scanning_start_timestamp_;
 
   mojo::Receiver<nearby_share::mojom::NearbyShareSettingsObserver>
       settings_receiver_{this};
diff --git a/chrome/browser/nearby_sharing/nearby_sharing_service_impl_unittest.cc b/chrome/browser/nearby_sharing/nearby_sharing_service_impl_unittest.cc
index 026e5af..70699f66 100644
--- a/chrome/browser/nearby_sharing/nearby_sharing_service_impl_unittest.cc
+++ b/chrome/browser/nearby_sharing/nearby_sharing_service_impl_unittest.cc
@@ -23,6 +23,7 @@
 #include "chrome/browser/nearby_sharing/nearby_connections_manager.h"
 #include "chrome/browser/notifications/notification_display_service_factory.h"
 #include "chrome/browser/notifications/notification_display_service_tester.h"
+#include "chrome/services/sharing/public/cpp/advertisement.h"
 #include "chrome/services/sharing/public/proto/wire_format.pb.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
@@ -43,6 +44,8 @@
 
 using NetConnectionType = net::NetworkChangeNotifier::ConnectionType;
 
+using SendSurfaceState = NearbySharingService::SendSurfaceState;
+
 class FakeFastInitiationManager : public FastInitiationManager {
  public:
   explicit FakeFastInitiationManager(
@@ -112,7 +115,10 @@
   }
 
   size_t StartAdvertisingCount() {
-    return last_fake_fast_initiation_manager_->start_advertising_call_count();
+    return last_fake_fast_initiation_manager_
+               ? last_fake_fast_initiation_manager_
+                     ->start_advertising_call_count()
+               : 0;
   }
 
   bool StopAdvertisingCalledAndManagerDestroyed() {
@@ -139,9 +145,22 @@
               (override));
 };
 
+class MockShareTargetDiscoveredCallback : public ShareTargetDiscoveredCallback {
+ public:
+  ~MockShareTargetDiscoveredCallback() override = default;
+
+  MOCK_METHOD(void,
+              OnShareTargetDiscovered,
+              (ShareTarget shareTarget),
+              (override));
+  MOCK_METHOD(void, OnShareTargetLost, (ShareTarget shareTarget), (override));
+};
+
 namespace {
 
+const char kServiceId[] = "NearbySharing";
 const char kEndpointId[] = "endpoint_id";
+const char kDeviceName[] = "device_name";
 
 sharing::mojom::FramePtr GetValidIntroductionFrame() {
   std::vector<sharing::mojom::TextMetadataPtr> mojo_text_metadatas;
@@ -162,6 +181,19 @@
   return mojo_frame;
 }
 
+// Length sharing::Advertisement::kSaltSize
+const uint8_t kSalt[] = {0x0c, 0x08};
+// Length sharing::Advertisement::kMetadataEncryptionKeyHashByteSize
+const uint8_t kMetadataEncryptionKeyHashByte[] = {0x07, 0x01, 0x08, 0x08, 0x04,
+                                                  0x09, 0x06, 0x00, 0x0b, 0x0f,
+                                                  0x0c, 0x09, 0x03, 0x0d};
+const int kEndpointInfoSize =
+    1 + sharing::Advertisement::kSaltSize +
+    sharing::Advertisement::kMetadataEncryptionKeyHashByteSize;
+const uint8_t kEndpointInfo[] = {0x00, 0x0c, 0x08, 0x07, 0x01, 0x08,
+                                 0x08, 0x04, 0x09, 0x06, 0x00, 0x0b,
+                                 0x0f, 0x0c, 0x09, 0x03, 0x0d};
+
 class NearbySharingServiceImplTest : public testing::Test {
  public:
   NearbySharingServiceImplTest() {
@@ -206,6 +238,8 @@
         &prefs_, notification_display_service, profile,
         base::WrapUnique(fake_nearby_connections_manager_),
         &mock_nearby_process_manager_);
+    NearbyProcessManager& process_manager = NearbyProcessManager::GetInstance();
+    process_manager.SetActiveProfile(profile);
 
     // Allow the posted task to fetch the BluetoothAdapter to finish.
     base::RunLoop().RunUntilIdle();
@@ -259,6 +293,47 @@
       net::test::MockNetworkChangeNotifier::Create();
 };
 
+struct ValidSendSurfaceTestData {
+  ui::IdleState idle_state;
+  bool bluetooth_enabled;
+  net::NetworkChangeNotifier::ConnectionType connection_type;
+} kValidSendSurfaceTestData[] = {
+    // No network connection, only bluetooth available
+    {ui::IDLE_STATE_IDLE, true, net::NetworkChangeNotifier::CONNECTION_NONE},
+    // Wifi available
+    {ui::IDLE_STATE_IDLE, true, net::NetworkChangeNotifier::CONNECTION_WIFI},
+    // Ethernet available
+    {ui::IDLE_STATE_IDLE, true,
+     net::NetworkChangeNotifier::CONNECTION_ETHERNET},
+    // 3G available
+    {ui::IDLE_STATE_IDLE, true, net::NetworkChangeNotifier::CONNECTION_3G},
+    // Wifi available and no bluetooth
+    {ui::IDLE_STATE_IDLE, false, net::NetworkChangeNotifier::CONNECTION_WIFI},
+    // Ethernet available and no bluetooth
+    {ui::IDLE_STATE_IDLE, false,
+     net::NetworkChangeNotifier::CONNECTION_ETHERNET}};
+
+class NearbySharingServiceImplValidSendTest
+    : public NearbySharingServiceImplTest,
+      public testing::WithParamInterface<ValidSendSurfaceTestData> {};
+
+struct InvalidSendSurfaceTestData {
+  ui::IdleState idle_state;
+  bool bluetooth_enabled;
+  net::NetworkChangeNotifier::ConnectionType connection_type;
+} kInvalidSendSurfaceTestData[] = {
+    // Screen locked
+    {ui::IDLE_STATE_LOCKED, true, net::NetworkChangeNotifier::CONNECTION_WIFI},
+    // No network connection and no bluetooth
+    {ui::IDLE_STATE_IDLE, false, net::NetworkChangeNotifier::CONNECTION_NONE},
+    // 3G available and no bluetooth
+    {ui::IDLE_STATE_IDLE, false, net::NetworkChangeNotifier::CONNECTION_3G},
+};
+
+class NearbySharingServiceImplInvalidSendTest
+    : public NearbySharingServiceImplTest,
+      public testing::WithParamInterface<InvalidSendSurfaceTestData> {};
+
 }  // namespace
 
 TEST_F(NearbySharingServiceImplTest, AddsNearbyProcessObserver) {
@@ -272,79 +347,111 @@
 }
 
 TEST_F(NearbySharingServiceImplTest, DisableNearbyShutdownConnections) {
+  ui::ScopedSetIdleState unlocked(ui::IDLE_STATE_IDLE);
+  SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
   prefs_.SetBoolean(prefs::kNearbySharingEnabledPrefName, false);
   service_->FlushMojoForTesting();
   EXPECT_TRUE(fake_nearby_connections_manager_->IsShutdown());
 }
 
 TEST_F(NearbySharingServiceImplTest, StartFastInitiationAdvertising) {
-  EXPECT_EQ(NearbySharingService::StatusCodes::kOk,
-            service_->RegisterSendSurface(
-                /*transfer_callback=*/nullptr,
-                /*discovery_callback=*/nullptr,
-                NearbySharingService::SendSurfaceState::kForeground));
+  ui::ScopedSetIdleState unlocked(ui::IDLE_STATE_IDLE);
+  SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
+  MockTransferUpdateCallback transfer_callback;
+  MockShareTargetDiscoveredCallback discovery_callback;
+  EXPECT_EQ(
+      NearbySharingService::StatusCodes::kOk,
+      service_->RegisterSendSurface(&transfer_callback, &discovery_callback,
+                                    SendSurfaceState::kForeground));
   EXPECT_EQ(1u, fast_initiation_manager_factory_->StartAdvertisingCount());
 
-  // Call RegisterSendSurface() a second time and make sure StartAdvertising is
+  // Call RegisterSendSurface a second time and make sure StartAdvertising is
   // not called again.
-  EXPECT_EQ(NearbySharingService::StatusCodes::kOk,
-            service_->RegisterSendSurface(
-                /*transfer_callback=*/nullptr,
-                /*discovery_callback=*/nullptr,
-                NearbySharingService::SendSurfaceState::kForeground));
+  EXPECT_EQ(
+      NearbySharingService::StatusCodes::kError,
+      service_->RegisterSendSurface(&transfer_callback, &discovery_callback,
+                                    SendSurfaceState::kForeground));
   EXPECT_EQ(1u, fast_initiation_manager_factory_->StartAdvertisingCount());
 }
 
 TEST_F(NearbySharingServiceImplTest, StartFastInitiationAdvertisingError) {
+  ui::ScopedSetIdleState unlocked(ui::IDLE_STATE_IDLE);
+  SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
   SetFakeFastInitiationManagerFactory(/*should_succeed_on_start=*/false);
-  EXPECT_EQ(NearbySharingService::StatusCodes::kOk,
-            service_->RegisterSendSurface(
-                /*transfer_callback=*/nullptr,
-                /*discovery_callback=*/nullptr,
-                NearbySharingService::SendSurfaceState::kForeground));
+  MockTransferUpdateCallback transfer_callback;
+  MockShareTargetDiscoveredCallback discovery_callback;
+  EXPECT_EQ(
+      NearbySharingService::StatusCodes::kOk,
+      service_->RegisterSendSurface(&transfer_callback, &discovery_callback,
+                                    SendSurfaceState::kForeground));
+}
+
+TEST_F(NearbySharingServiceImplTest,
+       BackgroundStartFastInitiationAdvertisingError) {
+  ui::ScopedSetIdleState unlocked(ui::IDLE_STATE_IDLE);
+  SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
+  MockTransferUpdateCallback transfer_callback;
+  MockShareTargetDiscoveredCallback discovery_callback;
+  EXPECT_EQ(
+      NearbySharingService::StatusCodes::kOk,
+      service_->RegisterSendSurface(&transfer_callback, &discovery_callback,
+                                    SendSurfaceState::kBackground));
+  EXPECT_EQ(0u, fast_initiation_manager_factory_->StartAdvertisingCount());
 }
 
 TEST_F(NearbySharingServiceImplTest,
        StartFastInitiationAdvertising_BluetoothNotPresent) {
+  ui::ScopedSetIdleState unlocked(ui::IDLE_STATE_IDLE);
+  SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
   is_bluetooth_present_ = false;
-  EXPECT_EQ(NearbySharingService::StatusCodes::kOk,
-            service_->RegisterSendSurface(
-                /*transfer_callback=*/nullptr,
-                /*discovery_callback=*/nullptr,
-                NearbySharingService::SendSurfaceState::kForeground));
+  MockTransferUpdateCallback transfer_callback;
+  MockShareTargetDiscoveredCallback discovery_callback;
+  EXPECT_EQ(
+      NearbySharingService::StatusCodes::kOk,
+      service_->RegisterSendSurface(&transfer_callback, &discovery_callback,
+                                    SendSurfaceState::kForeground));
 }
 
 TEST_F(NearbySharingServiceImplTest,
        StartFastInitiationAdvertising_BluetoothNotPowered) {
+  ui::ScopedSetIdleState unlocked(ui::IDLE_STATE_IDLE);
+  SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
   is_bluetooth_powered_ = false;
-  EXPECT_EQ(NearbySharingService::StatusCodes::kOk,
-            service_->RegisterSendSurface(
-                /*transfer_callback=*/nullptr,
-                /*discovery_callback=*/nullptr,
-                NearbySharingService::SendSurfaceState::kForeground));
+  MockTransferUpdateCallback transfer_callback;
+  MockShareTargetDiscoveredCallback discovery_callback;
+  EXPECT_EQ(
+      NearbySharingService::StatusCodes::kOk,
+      service_->RegisterSendSurface(&transfer_callback, &discovery_callback,
+                                    SendSurfaceState::kForeground));
 }
 
 TEST_F(NearbySharingServiceImplTest, StopFastInitiationAdvertising) {
-  EXPECT_EQ(NearbySharingService::StatusCodes::kOk,
-            service_->RegisterSendSurface(
-                /*transfer_callback=*/nullptr,
-                /*discovery_callback=*/nullptr,
-                NearbySharingService::SendSurfaceState::kForeground));
+  ui::ScopedSetIdleState unlocked(ui::IDLE_STATE_IDLE);
+  SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
+  MockTransferUpdateCallback transfer_callback;
+  MockShareTargetDiscoveredCallback discovery_callback;
+  EXPECT_EQ(
+      NearbySharingService::StatusCodes::kOk,
+      service_->RegisterSendSurface(&transfer_callback, &discovery_callback,
+                                    SendSurfaceState::kForeground));
   EXPECT_EQ(1u, fast_initiation_manager_factory_->StartAdvertisingCount());
-  EXPECT_EQ(NearbySharingService::StatusCodes::kOk,
-            service_->UnregisterSendSurface(/*transfer_callback=*/nullptr,
-                                            /*discovery_callback=*/nullptr));
+  EXPECT_EQ(
+      NearbySharingService::StatusCodes::kOk,
+      service_->UnregisterSendSurface(&transfer_callback, &discovery_callback));
   EXPECT_TRUE(fast_initiation_manager_factory_
                   ->StopAdvertisingCalledAndManagerDestroyed());
 }
 
 TEST_F(NearbySharingServiceImplTest,
        StopFastInitiationAdvertising_BluetoothBecomesNotPresent) {
-  EXPECT_EQ(NearbySharingService::StatusCodes::kOk,
-            service_->RegisterSendSurface(
-                /*transfer_callback=*/nullptr,
-                /*discovery_callback=*/nullptr,
-                NearbySharingService::SendSurfaceState::kForeground));
+  ui::ScopedSetIdleState unlocked(ui::IDLE_STATE_IDLE);
+  SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
+  MockTransferUpdateCallback transfer_callback;
+  MockShareTargetDiscoveredCallback discovery_callback;
+  EXPECT_EQ(
+      NearbySharingService::StatusCodes::kOk,
+      service_->RegisterSendSurface(&transfer_callback, &discovery_callback,
+                                    SendSurfaceState::kForeground));
   adapter_observer_->AdapterPresentChanged(mock_bluetooth_adapter_.get(),
                                            false);
   EXPECT_TRUE(fast_initiation_manager_factory_
@@ -353,11 +460,14 @@
 
 TEST_F(NearbySharingServiceImplTest,
        StopFastInitiationAdvertising_BluetoothBecomesNotPowered) {
-  EXPECT_EQ(NearbySharingService::StatusCodes::kOk,
-            service_->RegisterSendSurface(
-                /*transfer_callback=*/nullptr,
-                /*discovery_callback=*/nullptr,
-                NearbySharingService::SendSurfaceState::kForeground));
+  ui::ScopedSetIdleState unlocked(ui::IDLE_STATE_IDLE);
+  SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
+  MockTransferUpdateCallback transfer_callback;
+  MockShareTargetDiscoveredCallback discovery_callback;
+  EXPECT_EQ(
+      NearbySharingService::StatusCodes::kOk,
+      service_->RegisterSendSurface(&transfer_callback, &discovery_callback,
+                                    SendSurfaceState::kForeground));
   adapter_observer_->AdapterPoweredChanged(mock_bluetooth_adapter_.get(),
                                            false);
   EXPECT_TRUE(fast_initiation_manager_factory_
@@ -365,6 +475,286 @@
 }
 
 TEST_F(NearbySharingServiceImplTest,
+       RegisterSendSurfaceNoActiveProfilesNotDiscovering) {
+  ui::ScopedSetIdleState unlocked(ui::IDLE_STATE_IDLE);
+  SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
+  NearbyProcessManager& process_manager = NearbyProcessManager::GetInstance();
+  process_manager.ClearActiveProfile();
+  MockTransferUpdateCallback transfer_callback;
+  MockShareTargetDiscoveredCallback discovery_callback;
+  EXPECT_EQ(
+      NearbySharingService::StatusCodes::kError,
+      service_->RegisterSendSurface(&transfer_callback, &discovery_callback,
+                                    SendSurfaceState::kForeground));
+}
+
+TEST_F(NearbySharingServiceImplTest,
+       ForegroundRegisterSendSurfaceStartsDiscovering) {
+  ui::ScopedSetIdleState unlocked(ui::IDLE_STATE_IDLE);
+  SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
+  MockTransferUpdateCallback transfer_callback;
+  MockShareTargetDiscoveredCallback discovery_callback;
+  EXPECT_EQ(
+      NearbySharingService::StatusCodes::kOk,
+      service_->RegisterSendSurface(&transfer_callback, &discovery_callback,
+                                    SendSurfaceState::kForeground));
+  EXPECT_TRUE(fake_nearby_connections_manager_->IsDiscovering());
+}
+
+TEST_F(NearbySharingServiceImplTest,
+       ForegroundRegisterSendSurfaceTwiceKeepsDiscovering) {
+  ui::ScopedSetIdleState unlocked(ui::IDLE_STATE_IDLE);
+  SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
+  MockTransferUpdateCallback transfer_callback;
+  MockShareTargetDiscoveredCallback discovery_callback;
+  EXPECT_EQ(
+      NearbySharingService::StatusCodes::kOk,
+      service_->RegisterSendSurface(&transfer_callback, &discovery_callback,
+                                    SendSurfaceState::kForeground));
+  EXPECT_TRUE(fake_nearby_connections_manager_->IsDiscovering());
+
+  EXPECT_EQ(
+      NearbySharingService::StatusCodes::kError,
+      service_->RegisterSendSurface(&transfer_callback, &discovery_callback,
+                                    SendSurfaceState::kForeground));
+  EXPECT_TRUE(fake_nearby_connections_manager_->IsDiscovering());
+}
+
+TEST_F(NearbySharingServiceImplTest,
+       RegisterSendSurfaceAlreadyReceivingNotDiscovering) {
+  ui::ScopedSetIdleState unlocked(ui::IDLE_STATE_IDLE);
+  SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
+  // TODO(himanshujaju) is_receiving_files_ should be set to true when
+  // receiving. Test that WHEN receiving files, THEN below passes.
+  // EXPECT_EQ(NearbySharingService::StatusCodes::kTransferAlreadyInProgress,
+  //           RegisterSendSurface(SendSurfaceState::kForeground));
+  // EXPECT_FALSE(fake_nearby_connections_manager_->IsDiscovering());
+  // EXPECT_FALSE(fake_nearby_connections_manager_->IsShutdown());
+}
+
+TEST_F(NearbySharingServiceImplTest,
+       BackgroundRegisterSendSurfaceNotDiscovering) {
+  ui::ScopedSetIdleState unlocked(ui::IDLE_STATE_IDLE);
+  SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
+  MockTransferUpdateCallback transfer_callback;
+  MockShareTargetDiscoveredCallback discovery_callback;
+  EXPECT_EQ(
+      NearbySharingService::StatusCodes::kOk,
+      service_->RegisterSendSurface(&transfer_callback, &discovery_callback,
+                                    SendSurfaceState::kBackground));
+  EXPECT_FALSE(fake_nearby_connections_manager_->IsDiscovering());
+  EXPECT_FALSE(fake_nearby_connections_manager_->IsShutdown());
+}
+
+TEST_F(NearbySharingServiceImplTest,
+       DifferentSurfaceRegisterSendSurfaceTwiceKeepsDiscovering) {
+  ui::ScopedSetIdleState unlocked(ui::IDLE_STATE_IDLE);
+  SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
+  MockTransferUpdateCallback transfer_callback;
+  MockShareTargetDiscoveredCallback discovery_callback;
+  EXPECT_EQ(
+      NearbySharingService::StatusCodes::kOk,
+      service_->RegisterSendSurface(&transfer_callback, &discovery_callback,
+                                    SendSurfaceState::kForeground));
+  EXPECT_TRUE(fake_nearby_connections_manager_->IsDiscovering());
+
+  EXPECT_EQ(
+      NearbySharingService::StatusCodes::kError,
+      service_->RegisterSendSurface(&transfer_callback, &discovery_callback,
+                                    SendSurfaceState::kBackground));
+  EXPECT_TRUE(fake_nearby_connections_manager_->IsDiscovering());
+}
+
+TEST_F(NearbySharingServiceImplTest,
+       RegisterSendSurfaceEndpointFoundDiscoveryCallbackNotified) {
+  ui::ScopedSetIdleState unlocked(ui::IDLE_STATE_IDLE);
+  SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
+
+  // Ensure decoder parses a valid endpoint advertisement.
+  NiceMock<MockNearbySharingDecoder> mock_decoder;
+  std::vector<uint8_t> endpoint_info(kEndpointInfo,
+                                     kEndpointInfo + kEndpointInfoSize);
+  std::vector<uint8_t> metadata_encryption_key(
+      kMetadataEncryptionKeyHashByte,
+      kMetadataEncryptionKeyHashByte +
+          sharing::Advertisement::kMetadataEncryptionKeyHashByteSize);
+  std::vector<uint8_t> salt(kSalt, kSalt + sharing::Advertisement::kSaltSize);
+  EXPECT_CALL(mock_decoder,
+              DecodeAdvertisement(testing::Eq(endpoint_info), testing::_))
+      .WillOnce(testing::Invoke(
+          [&salt, &metadata_encryption_key](
+              const std::vector<uint8_t>& data,
+              MockNearbySharingDecoder::DecodeAdvertisementCallback callback) {
+            sharing::mojom::AdvertisementPtr mojo_adv =
+                sharing::mojom::Advertisement::New(
+                    salt, metadata_encryption_key, kDeviceName);
+            std::move(callback).Run(std::move(mojo_adv));
+          }));
+  EXPECT_CALL(mock_nearby_process_manager(),
+              GetOrStartNearbySharingDecoder(testing::_))
+      .WillRepeatedly(testing::Return(&mock_decoder));
+
+  // Start discovering, to ensure a discovery listener is registered.
+  base::RunLoop run_loop;
+  MockTransferUpdateCallback transfer_callback;
+  NiceMock<MockShareTargetDiscoveredCallback> discovery_callback;
+  EXPECT_EQ(
+      NearbySharingService::StatusCodes::kOk,
+      service_->RegisterSendSurface(&transfer_callback, &discovery_callback,
+                                    SendSurfaceState::kForeground));
+  EXPECT_TRUE(fake_nearby_connections_manager_->IsDiscovering());
+
+  // Discover a new endpoint.
+  EXPECT_CALL(discovery_callback, OnShareTargetDiscovered)
+      .WillOnce([&run_loop](ShareTarget share_target) {
+        EXPECT_EQ(kDeviceName, share_target.device_name);
+        run_loop.Quit();
+      });
+  fake_nearby_connections_manager_->OnEndpointFound(
+      kEndpointId,
+      location::nearby::connections::mojom::DiscoveredEndpointInfo::New(
+          endpoint_info, kServiceId));
+  run_loop.Run();
+
+  // Register another send surface, which will automatically catch up discovered
+  // endpoints.
+  base::RunLoop run_loop2;
+  MockTransferUpdateCallback transfer_callback2;
+  NiceMock<MockShareTargetDiscoveredCallback> discovery_callback2;
+  EXPECT_CALL(discovery_callback2, OnShareTargetDiscovered)
+      .WillOnce([&run_loop2](ShareTarget share_target) {
+        EXPECT_EQ(kDeviceName, share_target.device_name);
+        run_loop2.Quit();
+      });
+
+  EXPECT_EQ(
+      NearbySharingService::StatusCodes::kOk,
+      service_->RegisterSendSurface(&transfer_callback2, &discovery_callback2,
+                                    SendSurfaceState::kForeground));
+  run_loop2.Run();
+}
+
+TEST_P(NearbySharingServiceImplValidSendTest,
+       RegisterSendSurfaceIsDiscovering) {
+  ui::ScopedSetIdleState idle_state(GetParam().idle_state);
+  is_bluetooth_present_ = GetParam().bluetooth_enabled;
+  SetConnectionType(GetParam().connection_type);
+  MockTransferUpdateCallback transfer_callback;
+  MockShareTargetDiscoveredCallback discovery_callback;
+  EXPECT_EQ(
+      NearbySharingService::StatusCodes::kOk,
+      service_->RegisterSendSurface(&transfer_callback, &discovery_callback,
+                                    SendSurfaceState::kForeground));
+  EXPECT_TRUE(fake_nearby_connections_manager_->IsDiscovering());
+}
+
+INSTANTIATE_TEST_SUITE_P(NearbySharingServiceImplTest,
+                         NearbySharingServiceImplValidSendTest,
+                         testing::ValuesIn(kValidSendSurfaceTestData));
+
+TEST_P(NearbySharingServiceImplInvalidSendTest,
+       RegisterSendSurfaceNotDiscovering) {
+  ui::ScopedSetIdleState idle_state(GetParam().idle_state);
+  is_bluetooth_present_ = GetParam().bluetooth_enabled;
+  SetConnectionType(GetParam().connection_type);
+  MockTransferUpdateCallback transfer_callback;
+  MockShareTargetDiscoveredCallback discovery_callback;
+  EXPECT_FALSE(fake_nearby_connections_manager_->IsDiscovering());
+  EXPECT_EQ(
+      NearbySharingService::StatusCodes::kOk,
+      service_->RegisterSendSurface(&transfer_callback, &discovery_callback,
+                                    SendSurfaceState::kForeground));
+  EXPECT_FALSE(fake_nearby_connections_manager_->IsDiscovering());
+  EXPECT_FALSE(fake_nearby_connections_manager_->IsShutdown());
+}
+
+INSTANTIATE_TEST_SUITE_P(NearbySharingServiceImplTest,
+                         NearbySharingServiceImplInvalidSendTest,
+                         testing::ValuesIn(kInvalidSendSurfaceTestData));
+
+TEST_F(NearbySharingServiceImplTest, DisableFeatureSendSurfaceNotDiscovering) {
+  ui::ScopedSetIdleState unlocked(ui::IDLE_STATE_IDLE);
+  prefs_.SetBoolean(prefs::kNearbySharingEnabledPrefName, false);
+  service_->FlushMojoForTesting();
+  SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
+  MockTransferUpdateCallback transfer_callback;
+  MockShareTargetDiscoveredCallback discovery_callback;
+  EXPECT_EQ(
+      NearbySharingService::StatusCodes::kOk,
+      service_->RegisterSendSurface(&transfer_callback, &discovery_callback,
+                                    SendSurfaceState::kForeground));
+  EXPECT_FALSE(fake_nearby_connections_manager_->IsDiscovering());
+  EXPECT_TRUE(fake_nearby_connections_manager_->IsShutdown());
+}
+
+TEST_F(NearbySharingServiceImplTest,
+       DisableFeatureSendSurfaceStopsDiscovering) {
+  ui::ScopedSetIdleState unlocked(ui::IDLE_STATE_IDLE);
+  SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
+  MockTransferUpdateCallback transfer_callback;
+  MockShareTargetDiscoveredCallback discovery_callback;
+  EXPECT_EQ(
+      NearbySharingService::StatusCodes::kOk,
+      service_->RegisterSendSurface(&transfer_callback, &discovery_callback,
+                                    SendSurfaceState::kForeground));
+  EXPECT_TRUE(fake_nearby_connections_manager_->IsDiscovering());
+
+  prefs_.SetBoolean(prefs::kNearbySharingEnabledPrefName, false);
+  service_->FlushMojoForTesting();
+  EXPECT_FALSE(fake_nearby_connections_manager_->IsDiscovering());
+  EXPECT_TRUE(fake_nearby_connections_manager_->IsShutdown());
+}
+
+TEST_F(NearbySharingServiceImplTest, UnregisterSendSurfaceStopsDiscovering) {
+  ui::ScopedSetIdleState unlocked(ui::IDLE_STATE_IDLE);
+  SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
+  MockTransferUpdateCallback transfer_callback;
+  MockShareTargetDiscoveredCallback discovery_callback;
+  EXPECT_EQ(
+      NearbySharingService::StatusCodes::kOk,
+      service_->RegisterSendSurface(&transfer_callback, &discovery_callback,
+                                    SendSurfaceState::kForeground));
+  EXPECT_TRUE(fake_nearby_connections_manager_->IsDiscovering());
+
+  EXPECT_EQ(
+      NearbySharingService::StatusCodes::kOk,
+      service_->UnregisterSendSurface(&transfer_callback, &discovery_callback));
+  EXPECT_FALSE(fake_nearby_connections_manager_->IsDiscovering());
+  EXPECT_FALSE(fake_nearby_connections_manager_->IsShutdown());
+}
+
+TEST_F(NearbySharingServiceImplTest,
+       UnregisterSendSurfaceDifferentCallbackKeepDiscovering) {
+  ui::ScopedSetIdleState unlocked(ui::IDLE_STATE_IDLE);
+  SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
+  MockTransferUpdateCallback transfer_callback;
+  MockShareTargetDiscoveredCallback discovery_callback;
+  EXPECT_EQ(
+      NearbySharingService::StatusCodes::kOk,
+      service_->RegisterSendSurface(&transfer_callback, &discovery_callback,
+                                    SendSurfaceState::kForeground));
+  EXPECT_TRUE(fake_nearby_connections_manager_->IsDiscovering());
+
+  MockTransferUpdateCallback transfer_callback2;
+  MockShareTargetDiscoveredCallback discovery_callback2;
+  EXPECT_EQ(NearbySharingService::StatusCodes::kError,
+            service_->UnregisterSendSurface(&transfer_callback2,
+                                            &discovery_callback2));
+  EXPECT_TRUE(fake_nearby_connections_manager_->IsDiscovering());
+}
+
+TEST_F(NearbySharingServiceImplTest, UnregisterSendSurfaceNeverRegistered) {
+  ui::ScopedSetIdleState unlocked(ui::IDLE_STATE_IDLE);
+  SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
+  MockTransferUpdateCallback transfer_callback;
+  MockShareTargetDiscoveredCallback discovery_callback;
+  EXPECT_EQ(
+      NearbySharingService::StatusCodes::kError,
+      service_->UnregisterSendSurface(&transfer_callback, &discovery_callback));
+  EXPECT_FALSE(fake_nearby_connections_manager_->IsDiscovering());
+}
+
+TEST_F(NearbySharingServiceImplTest,
        ForegroundRegisterReceiveSurfaceIsAdvertising) {
   ui::ScopedSetIdleState unlocked(ui::IDLE_STATE_IDLE);
   SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
diff --git a/chrome/browser/nfc/nfc_permission_context.cc b/chrome/browser/nfc/nfc_permission_context.cc
index 672805a..f72f597 100644
--- a/chrome/browser/nfc/nfc_permission_context.cc
+++ b/chrome/browser/nfc/nfc_permission_context.cc
@@ -4,7 +4,7 @@
 
 #include "chrome/browser/nfc/nfc_permission_context.h"
 
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/permissions/permission_request_id.h"
 
 NfcPermissionContext::NfcPermissionContext(
@@ -45,7 +45,7 @@
     const GURL& requesting_frame,
     bool allowed) {
   auto* content_settings =
-      content_settings::TabSpecificContentSettings::GetForFrame(
+      content_settings::PageSpecificContentSettings::GetForFrame(
           id.render_process_id(), id.render_frame_id());
   if (!content_settings)
     return;
diff --git a/chrome/browser/nfc/nfc_permission_context_unittest.cc b/chrome/browser/nfc/nfc_permission_context_unittest.cc
index 165ba47..510d8ff1 100644
--- a/chrome/browser/nfc/nfc_permission_context_unittest.cc
+++ b/chrome/browser/nfc/nfc_permission_context_unittest.cc
@@ -6,10 +6,10 @@
 
 #include "build/build_config.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
-#include "chrome/browser/content_settings/tab_specific_content_settings_delegate.h"
+#include "chrome/browser/content_settings/page_specific_content_settings_delegate.h"
 #include "chrome/browser/permissions/permission_manager_factory.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/permissions/permission_manager.h"
 #include "components/permissions/permission_request_id.h"
 #include "components/permissions/permission_request_manager.h"
@@ -115,9 +115,9 @@
 void NfcPermissionContextTests::SetUp() {
   ChromeRenderViewHostTestHarness::SetUp();
 
-  content_settings::TabSpecificContentSettings::CreateForWebContents(
+  content_settings::PageSpecificContentSettings::CreateForWebContents(
       web_contents(),
-      std::make_unique<chrome::TabSpecificContentSettingsDelegate>(
+      std::make_unique<chrome::PageSpecificContentSettingsDelegate>(
           web_contents()));
   nfc_permission_context_ = static_cast<NfcPermissionContext*>(
       PermissionManagerFactory::GetForProfile(profile())
diff --git a/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckBridge.java b/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckBridge.java
index 54613d6b..be71c982 100644
--- a/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckBridge.java
+++ b/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckBridge.java
@@ -142,8 +142,8 @@
      */
     void destroy() {
         if (mNativePasswordCheckBridge != 0) {
-            mNativePasswordCheckBridge = 0;
             PasswordCheckBridgeJni.get().destroy(mNativePasswordCheckBridge);
+            mNativePasswordCheckBridge = 0;
         }
     }
 
diff --git a/chrome/browser/payments/BUILD.gn b/chrome/browser/payments/BUILD.gn
index 29cc6a1ea..210ab7e1 100644
--- a/chrome/browser/payments/BUILD.gn
+++ b/chrome/browser/payments/BUILD.gn
@@ -20,6 +20,7 @@
     "payment_handler_enforce_full_delegation_browsertest.cc",
     "payment_handler_exploit_browsertest.cc",
     "payment_handler_just_in_time_installation_browsertest.cc",
+    "payment_handler_uninstall_browsertest.cc",
     "payment_request_app_store_billing_browsertest.cc",
     "payment_request_can_make_payment_browsertest.cc",
     "payment_request_can_make_payment_event_browsertest.cc",
diff --git a/chrome/browser/payments/payment_handler_uninstall_browsertest.cc b/chrome/browser/payments/payment_handler_uninstall_browsertest.cc
new file mode 100644
index 0000000..e66c316
--- /dev/null
+++ b/chrome/browser/payments/payment_handler_uninstall_browsertest.cc
@@ -0,0 +1,67 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/test/payments/payment_request_platform_browsertest_base.h"
+#include "content/public/test/browser_test.h"
+
+namespace payments {
+
+namespace {
+
+class PaymentHandlerUninstallTest
+    : public PaymentRequestPlatformBrowserTestBase {
+ protected:
+  PaymentHandlerUninstallTest() = default;
+  ~PaymentHandlerUninstallTest() override = default;
+
+  void SetUpOnMainThread() override {
+    PaymentRequestPlatformBrowserTestBase::SetUpOnMainThread();
+    NavigateTo("/payment_handler.html");
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(PaymentHandlerUninstallTest, URLBasedPaymentMethod) {
+  EXPECT_EQ("success", content::EvalJs(GetActiveWebContents(), "install()"));
+
+  // Launch the payment request and confirm checkout completion.
+  ResetEventWaiterForSingleEvent(TestEvent::kPaymentCompleted);
+  EXPECT_EQ("success", content::EvalJs(GetActiveWebContents(), "launch()"));
+  WaitForObservedEvent();
+
+  // Uninstall the payment app and verify that a new request.show() gets
+  // rejected after the app uninstallation.
+  EXPECT_EQ("success", content::EvalJs(GetActiveWebContents(), "uninstall()"));
+  ResetEventWaiterForSingleEvent(TestEvent::kNotSupportedError);
+  EXPECT_EQ("success", content::EvalJs(GetActiveWebContents(),
+                                       "launchWithoutWaitForResponse()"));
+  WaitForObservedEvent();
+}
+
+IN_PROC_BROWSER_TEST_F(PaymentHandlerUninstallTest, BasicCard) {
+  EXPECT_EQ("success",
+            content::EvalJs(GetActiveWebContents(), "install('basic-card')"));
+
+  // Launch the payment request and validate that one app is available.
+  ResetEventWaiterForSingleEvent(TestEvent::kAppListReady);
+  EXPECT_EQ("success", content::EvalJs(GetActiveWebContents(),
+                                       "launchWithoutWaitForResponse()"));
+  WaitForObservedEvent();
+  EXPECT_EQ(1u, test_controller()->app_descriptions().size());
+
+  EXPECT_EQ("success", content::EvalJs(GetActiveWebContents(), "abort()"));
+
+  // Uninstall the payment app and verify that there is no payment app
+  // available. A new request.show() will not get rejected though since the user
+  // will still have the option to add a credit card.
+  EXPECT_EQ("success", content::EvalJs(GetActiveWebContents(), "uninstall()"));
+  ResetEventWaiterForSingleEvent(TestEvent::kAppListReady);
+  EXPECT_EQ("success", content::EvalJs(GetActiveWebContents(),
+                                       "launchWithoutWaitForResponse()"));
+  WaitForObservedEvent();
+  EXPECT_EQ(0u, test_controller()->app_descriptions().size());
+}
+
+}  // namespace
+
+}  // namespace payments
diff --git a/chrome/browser/pepper_broker_infobar_delegate.cc b/chrome/browser/pepper_broker_infobar_delegate.cc
index 9f5e8cd..2a88ab3f 100644
--- a/chrome/browser/pepper_broker_infobar_delegate.cc
+++ b/chrome/browser/pepper_broker_infobar_delegate.cc
@@ -9,7 +9,7 @@
 #include "chrome/browser/infobars/infobar_service.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/grit/generated_resources.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/infobars/core/infobar.h"
 #include "components/strings/grit/components_strings.h"
@@ -26,11 +26,11 @@
     const GURL& url,
     const base::string16& plugin_name,
     HostContentSettingsMap* content_settings,
-    content_settings::TabSpecificContentSettings* tab_content_settings,
+    content_settings::PageSpecificContentSettings* page_content_settings,
     base::OnceCallback<void(bool)> callback) {
   infobar_service->AddInfoBar(infobar_service->CreateConfirmInfoBar(
       base::WrapUnique(new PepperBrokerInfoBarDelegate(
-          url, plugin_name, content_settings, tab_content_settings,
+          url, plugin_name, content_settings, page_content_settings,
           std::move(callback)))));
 }
 
@@ -38,13 +38,13 @@
     const GURL& url,
     const base::string16& plugin_name,
     HostContentSettingsMap* content_settings,
-    content_settings::TabSpecificContentSettings* tab_content_settings,
+    content_settings::PageSpecificContentSettings* page_content_settings,
     base::OnceCallback<void(bool)> callback)
     : url_(url),
       plugin_name_(plugin_name),
       content_settings_(content_settings),
-      tab_content_settings_(
-          tab_content_settings ? tab_content_settings->AsWeakPtr() : nullptr),
+      page_content_settings_(
+          page_content_settings ? page_content_settings->AsWeakPtr() : nullptr),
       callback_(std::move(callback)) {}
 
 PepperBrokerInfoBarDelegate::~PepperBrokerInfoBarDelegate() {
@@ -100,7 +100,7 @@
   content_settings_->SetContentSettingDefaultScope(
       url_, GURL(), ContentSettingsType::PPAPI_BROKER, std::string(),
       result ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK);
-  if (tab_content_settings_) {
-    tab_content_settings_->SetPepperBrokerAllowed(result);
+  if (page_content_settings_) {
+    page_content_settings_->SetPepperBrokerAllowed(result);
   }
 }
diff --git a/chrome/browser/pepper_broker_infobar_delegate.h b/chrome/browser/pepper_broker_infobar_delegate.h
index dbea1b0..a379542 100644
--- a/chrome/browser/pepper_broker_infobar_delegate.h
+++ b/chrome/browser/pepper_broker_infobar_delegate.h
@@ -15,7 +15,7 @@
 class InfoBarService;
 
 namespace content_settings {
-class TabSpecificContentSettings;
+class PageSpecificContentSettings;
 }
 
 // Shows an infobar that asks the user whether a Pepper plugin is allowed
@@ -30,7 +30,7 @@
       const GURL& url,
       const base::string16& plugin_name,
       HostContentSettingsMap* content_settings,
-      content_settings::TabSpecificContentSettings* tab_content_settings,
+      content_settings::PageSpecificContentSettings* page_content_settings,
       base::OnceCallback<void(bool)> callback);
   ~PepperBrokerInfoBarDelegate() override;
 
@@ -39,7 +39,7 @@
       const GURL& url,
       const base::string16& plugin_name,
       HostContentSettingsMap* content_settings,
-      content_settings::TabSpecificContentSettings* tab_content_settings,
+      content_settings::PageSpecificContentSettings* page_content_settings,
       base::OnceCallback<void(bool)> callback);
 
   // ConfirmInfoBarDelegate:
@@ -57,8 +57,8 @@
   const GURL url_;
   const base::string16 plugin_name_;
   HostContentSettingsMap* content_settings_;
-  base::WeakPtr<content_settings::TabSpecificContentSettings>
-      tab_content_settings_;
+  base::WeakPtr<content_settings::PageSpecificContentSettings>
+      page_content_settings_;
   base::OnceCallback<void(bool)> callback_;
 
   DISALLOW_COPY_AND_ASSIGN(PepperBrokerInfoBarDelegate);
diff --git a/chrome/browser/plugins/flash_download_interception.cc b/chrome/browser/plugins/flash_download_interception.cc
index 689c74c..1eb0e399 100644
--- a/chrome/browser/plugins/flash_download_interception.cc
+++ b/chrome/browser/plugins/flash_download_interception.cc
@@ -12,7 +12,7 @@
 #include "chrome/browser/plugins/plugin_utils.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/chrome_features.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "components/navigation_interception/intercept_navigation_throttle.h"
 #include "components/navigation_interception/navigation_params.h"
@@ -90,7 +90,7 @@
         ContentSettingsType::PLUGINS, web_contents->GetMainFrame(),
         web_contents->GetLastCommittedURL(), true, base::DoNothing());
   } else if (flash_setting == CONTENT_SETTING_BLOCK) {
-    auto* settings = content_settings::TabSpecificContentSettings::GetForFrame(
+    auto* settings = content_settings::PageSpecificContentSettings::GetForFrame(
         web_contents->GetMainFrame());
     if (settings)
       settings->FlashDownloadBlocked();
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index 910322e..991a13c 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -114,7 +114,7 @@
 #include "ash/public/cpp/ash_pref_names.h"
 #include "chrome/browser/chromeos/accessibility/magnifier_type.h"
 #include "chrome/browser/chromeos/crostini/crostini_pref_names.h"
-#include "chrome/browser/chromeos/platform_keys/key_permissions_policy_handler.h"
+#include "chrome/browser/chromeos/platform_keys/key_permissions/key_permissions_policy_handler.h"
 #include "chrome/browser/chromeos/plugin_vm/plugin_vm_pref_names.h"
 #include "chrome/browser/chromeos/policy/configuration_policy_handler_chromeos.h"
 #include "chrome/browser/chromeos/policy/secondary_google_account_signin_policy_handler.h"
@@ -1725,7 +1725,8 @@
       SimpleSchemaValidatingPolicyHandler::RECOMMENDED_ALLOWED,
       SimpleSchemaValidatingPolicyHandler::MANDATORY_PROHIBITED));
   handlers->AddHandler(
-      std::make_unique<chromeos::KeyPermissionsPolicyHandler>(chrome_schema));
+      std::make_unique<chromeos::platform_keys::KeyPermissionsPolicyHandler>(
+          chrome_schema));
   handlers->AddHandler(std::make_unique<DefaultGeolocationPolicyHandler>());
   handlers->AddHandler(std::make_unique<extensions::ExtensionListPolicyHandler>(
       key::kNoteTakingAppsLockScreenAllowlist,
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index ab4ae7979..5344b93 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -300,7 +300,7 @@
 #include "chrome/browser/chromeos/login/users/chrome_user_manager_impl.h"
 #include "chrome/browser/chromeos/login/users/multi_profile_user_controller.h"
 #include "chrome/browser/chromeos/net/network_throttling_observer.h"
-#include "chrome/browser/chromeos/platform_keys/key_permissions.h"
+#include "chrome/browser/chromeos/platform_keys/key_permissions/key_permissions.h"
 #include "chrome/browser/chromeos/plugin_vm/plugin_vm_pref_names.h"
 #include "chrome/browser/chromeos/policy/app_install_event_log_manager_wrapper.h"
 #include "chrome/browser/chromeos/policy/app_install_event_logger.h"
@@ -1059,7 +1059,7 @@
   chromeos::first_run::RegisterProfilePrefs(registry);
   chromeos::file_system_provider::RegisterProfilePrefs(registry);
   chromeos::KerberosCredentialsManager::RegisterProfilePrefs(registry);
-  chromeos::KeyPermissions::RegisterProfilePrefs(registry);
+  chromeos::platform_keys::KeyPermissions::RegisterProfilePrefs(registry);
   chromeos::multidevice_setup::MultiDeviceSetupService::RegisterProfilePrefs(
       registry);
   chromeos::MultiProfileUserController::RegisterProfilePrefs(registry);
diff --git a/chrome/browser/resources/pdf/pdf_viewer.js b/chrome/browser/resources/pdf/pdf_viewer.js
index d8677fd..d2300291 100644
--- a/chrome/browser/resources/pdf/pdf_viewer.js
+++ b/chrome/browser/resources/pdf/pdf_viewer.js
@@ -944,7 +944,7 @@
     chrome.fileSystem.chooseEntry(
         {
           type: 'saveFile',
-          accepts: [{extensions: ['pdf']}],
+          accepts: [{description: '*.pdf', extensions: ['pdf']}],
           suggestedName: fileName
         },
         entry => {
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_browser_proxy.js b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_browser_proxy.js
index eb92cf1..8001c8b 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_browser_proxy.js
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_browser_proxy.js
@@ -2,8 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
 // #import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
-// #import {AmbientModeTopicSource, AmbientModeSettings} from './constants.m.js';
+// #import {AmbientModeTopicSource, AmbientModeTemperatureUnit, AmbientModeSettings} from './constants.m.js';
+// clang-format on
 
 /**
  * @fileoverview A helper object used from the ambient mode section to interact
@@ -14,10 +16,11 @@
   /** @interface */
   /* #export */ class AmbientModeBrowserProxy {
     /**
-     * Retrieves the AmbientModeTopicSource from server. As a response, the C++
-     * sends the 'topic-source-changed' event.
+     * Retrieves the AmbientModeTopicSource and AmbientModeTemperatureUnit from
+     * server. As a response, the C++ sends the 'topic-source-changed' and
+     * 'temperature-unit-changed' events.
      */
-    requestTopicSource() {}
+    requestSettings() {}
 
     /**
      * Retrieves the albums from server. As a response, the C++ sends either the
@@ -28,6 +31,12 @@
     requestAlbums(topicSource) {}
 
     /**
+     * Updates the selected temperature unit to server.
+     * @param {!AmbientModeTemperatureUnit} temperatureUnit
+     */
+    setSelectedTemperatureUnit(temperatureUnit) {}
+
+    /**
      * Updates the selected topic source to server.
      * @param {!AmbientModeTopicSource} topicSource the selected topic source.
      */
@@ -43,8 +52,8 @@
   /** @implements {settings.AmbientModeBrowserProxy} */
   /* #export */ class AmbientModeBrowserProxyImpl {
     /** @override */
-    requestTopicSource() {
-      chrome.send('requestTopicSource');
+    requestSettings() {
+      chrome.send('requestSettings');
     }
 
     /** @override */
@@ -53,6 +62,11 @@
     }
 
     /** @override */
+    setSelectedTemperatureUnit(temperatureUnit) {
+      chrome.send('setSelectedTemperatureUnit', [temperatureUnit]);
+    }
+
+    /** @override */
     setSelectedTopicSource(topicSource) {
       chrome.send('setSelectedTopicSource', [topicSource]);
     }
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.html b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.html
index 90e6e622..49a9d1f 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.html
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.html
@@ -35,12 +35,23 @@
 
       /* Set padding on children instead of the container itself to ensure that
        * separator lines can fill the entire width of the page. */
-      #topicSourceListDiv > * {
+      #topicSourceListDiv > *,
+      #weatherDiv > * {
         /* Padded to the right to allow space for a ripple */
         padding-inline-end: calc(var(--cr-section-padding) -
             var(--cr-icon-ripple-padding));
         padding-inline-start: var(--cr-section-padding);
       }
+
+      #weatherTitle {
+        padding-bottom: 16px;
+        padding-top: 16px;
+      }
+
+      .list-item {
+        padding-inline-start: var(--cr-section-padding);
+      }
+
     </style>
     <h2 id="pageDescription">
       $i18n{ambientModePageDescription}
@@ -60,6 +71,29 @@
             disabled="[[!isValidTopicSource_(selectedTopicSource_)]]">
         </topic-source-list>
       </div>
+      <div id="weatherDiv" class="layout vertical flex">
+        <h2 id="weatherTitle" aria-hidden="true">
+          $i18n{ambientModeWeatherTitle}
+        </h2>
+        <div class="list-frame">
+          <cr-radio-group
+              id="ambientTemperatureUnit"
+              selected="{{selectedTemperatureUnit_}}"
+              disabled$="[[!isValidTemperatureUnit_(selectedTemperatureUnit_)]]"
+              aria-labelledby="weatherTitle">
+            <cr-radio-button
+                name="[[AmbientModeTemperatureUnit_.FAHRENHEIT]]"
+                class="list-item underbar"
+                label="$i18n{ambientModeTemperatureUnitFahrenheit}">
+            </cr-radio-button>
+            <cr-radio-button
+                name="[[AmbientModeTemperatureUnit_.CELSIUS]]"
+                class="list-item"
+                label="$i18n{ambientModeTemperatureUnitCelsius}">
+            </cr-radio-button>
+          </cr-radio-group>
+        </div>
+      </div>
     </template>
   </template>
   <script src="ambient_mode_page.js"></script>
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.js b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.js
index 116cbeea..12bef93 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.js
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.js
@@ -27,6 +27,15 @@
       value: AmbientModeTopicSource,
     },
 
+    /**
+     * Used to refer to the enum values in the HTML.
+     * @private {!Object<string, AmbientModeTemperatureUnit>}
+     */
+    AmbientModeTemperatureUnit_: {
+      type: Object,
+      value: AmbientModeTemperatureUnit,
+    },
+
     // TODO(b/160632748): Dynamically generate topic source of Google Photos.
     /** @private {!Array<!AmbientModeTopicSource>} */
     topicSources_: {
@@ -41,6 +50,13 @@
       type: AmbientModeTopicSource,
       value: AmbientModeTopicSource.UNKNOWN,
     },
+
+    /** @private {!AmbientModeTemperatureUnit} */
+    selectedTemperatureUnit_: {
+      type: AmbientModeTemperatureUnit,
+      value: AmbientModeTemperatureUnit.UNKNOWN,
+      observer: 'onSelectedTemperatureUnitChanged_'
+    },
   },
 
   listeners: {
@@ -58,9 +74,18 @@
 
   /** @override */
   ready() {
-    this.addWebUIListener('topic-source-changed', (topicSource) => {
-      this.selectedTopicSource_ = topicSource;
-    });
+    this.addWebUIListener(
+        'topic-source-changed',
+        (/** @type {!AmbientModeTopicSource} */ topicSource) => {
+          this.selectedTopicSource_ = topicSource;
+        },
+    );
+    this.addWebUIListener(
+        'temperature-unit-changed',
+        (/** @type {!AmbientModeTemperatureUnit} */ temperatureUnit) => {
+          this.selectedTemperatureUnit_ = temperatureUnit;
+        },
+    );
   },
 
   /**
@@ -73,7 +98,7 @@
       return;
     }
 
-    this.browserProxy_.requestTopicSource();
+    this.browserProxy_.requestSettings();
   },
 
   /**
@@ -86,6 +111,16 @@
   },
 
   /**
+   * @param {!AmbientModeTemperatureUnit} temperatureUnit
+   * @return {boolean}
+   * @private
+   */
+  isValidTemperatureUnit_(temperatureUnit) {
+    return temperatureUnit === AmbientModeTemperatureUnit.FAHRENHEIT ||
+        temperatureUnit === AmbientModeTemperatureUnit.CELSIUS;
+  },
+
+  /**
    * @param {number} topicSource
    * @return {boolean}
    * @private
@@ -95,6 +130,18 @@
   },
 
   /**
+   * @param {!AmbientModeTemperatureUnit} newValue
+   * @param {!AmbientModeTemperatureUnit} oldValue
+   * @private
+   */
+  onSelectedTemperatureUnitChanged_(newValue, oldValue) {
+    if (newValue && newValue !== AmbientModeTemperatureUnit.UNKNOWN &&
+        newValue !== oldValue) {
+      this.browserProxy_.setSelectedTemperatureUnit(newValue);
+    }
+  },
+
+  /**
    * @param {!CustomEvent<{item: !AmbientModeTopicSource}>} event
    * @private
    */
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/constants.js b/chrome/browser/resources/settings/chromeos/ambient_mode_page/constants.js
index 81272cb..c41138848 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/constants.js
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/constants.js
@@ -13,6 +13,13 @@
   ART_GALLERY: 1,
 };
 
+/** @enum {string} */
+/* #export */ const AmbientModeTemperatureUnit = {
+  UNKNOWN: 'unknown',
+  FAHRENHEIT: 'fahrenheit',
+  CELSIUS: 'celsius',
+};
+
 /**
  * Album metadata for UI.
  *
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.gni b/chrome/browser/resources/settings/chromeos/os_settings.gni
index f6bef2a4..054c597 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.gni
+++ b/chrome/browser/resources/settings/chromeos/os_settings.gni
@@ -66,7 +66,7 @@
                            cr_components_chromeos_auto_imports +
                            cr_elements_chromeos_auto_imports + [
                              "chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_browser_proxy.html|AmbientModeBrowserProxy,AmbientModeBrowserProxyImpl",
-                             "chrome/browser/resources/settings/chromeos/ambient_mode_page/constants.html|AmbientModeTopicSource,AmbientModeAlbum,AmbientModeSettings",
+                             "chrome/browser/resources/settings/chromeos/ambient_mode_page/constants.html|AmbientModeTopicSource,AmbientModeTemperatureUnit,AmbientModeAlbum,AmbientModeSettings",
                              "chrome/browser/resources/settings/chromeos/deep_linking_behavior.html|DeepLinkingBehavior",
                              "chrome/browser/resources/settings/chromeos/metrics_recorder.html|recordSettingChange",
                              "chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_browser_proxy.html|MultiDeviceBrowserProxy,MultiDeviceBrowserProxyImpl",
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.js b/chrome/browser/resources/settings/chromeos/os_settings.js
index 4e38396..66ea8fb 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings.js
@@ -26,6 +26,7 @@
 export {CrSettingsPrefs} from '../prefs/prefs_types.m.js';
 export {Route, Router} from '../router.m.js';
 export {AmbientModeBrowserProxyImpl} from './ambient_mode_page/ambient_mode_browser_proxy.m.js';
+export {AmbientModeTemperatureUnit, AmbientModeTopicSource} from './ambient_mode_page/constants.m.js';
 export {bluetoothApis} from './bluetooth_page/bluetooth_page.m.js';
 export {MultiDeviceBrowserProxy, MultiDeviceBrowserProxyImpl} from './multidevice_page/multidevice_browser_proxy.m.js';
 export {MultiDeviceFeature, MultiDeviceFeatureState, MultiDevicePageContentData, MultiDeviceSettingsMode, SmartLockSignInEnabledState} from './multidevice_page/multidevice_constants.m.js';
diff --git a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.cc b/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.cc
index 43e3a9aa..d838d1a 100644
--- a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.cc
+++ b/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.cc
@@ -77,7 +77,8 @@
   return ::safe_browsing::GetDMToken(profile_);
 }
 
-std::string ChromeEnterpriseRealTimeUrlLookupService::GetDMTokenString() const {
+base::Optional<std::string>
+ChromeEnterpriseRealTimeUrlLookupService::GetDMTokenString() const {
   DCHECK(GetDMToken().is_valid())
       << "Get a dm token string only if the dm token is valid.";
   return GetDMToken().value();
diff --git a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.h b/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.h
index 53824ef..8215f8f 100644
--- a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.h
+++ b/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.h
@@ -64,7 +64,7 @@
   void GetAccessToken(const GURL& url,
                       RTLookupRequestCallback request_callback,
                       RTLookupResponseCallback response_callback) override;
-  std::string GetDMTokenString() const override;
+  base::Optional<std::string> GetDMTokenString() const override;
   std::string GetMetricSuffix() const override;
 
   policy::DMToken GetDMToken() const;
diff --git a/chrome/browser/safe_browsing/download_protection/deep_scanning_browsertest.cc b/chrome/browser/safe_browsing/download_protection/deep_scanning_browsertest.cc
index 831f353..83891d48 100644
--- a/chrome/browser/safe_browsing/download_protection/deep_scanning_browsertest.cc
+++ b/chrome/browser/safe_browsing/download_protection/deep_scanning_browsertest.cc
@@ -6,14 +6,18 @@
 
 #include "base/base64.h"
 #include "base/path_service.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/values.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h"
+#include "chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/safe_browsing/cloud_content_scanning/binary_fcm_service.h"
 #include "chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h"
 #include "chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service_factory.h"
 #include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_browsertest_base.h"
+#include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.h"
 #include "chrome/browser/safe_browsing/dm_token_utils.h"
 #include "chrome/browser/safe_browsing/download_protection/ppapi_download_request.h"
 #include "chrome/browser/safe_browsing/test_safe_browsing_service.h"
@@ -24,6 +28,7 @@
 #include "components/autofill/core/common/mojom/autofill_types.mojom-shared.h"
 #include "components/download/public/common/download_danger_type.h"
 #include "components/enterprise/common/proto/connectors.pb.h"
+#include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
 #include "components/prefs/pref_service.h"
 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
 #include "components/safe_browsing/core/db/test_database_manager.h"
@@ -110,6 +115,16 @@
     download_items_.erase(item);
   }
 
+  void SetUpReporting() {
+    SetUnsafeEventsReportingPolicy(true);
+    client_ = std::make_unique<policy::MockCloudPolicyClient>();
+    extensions::SafeBrowsingPrivateEventRouterFactory::GetForProfile(
+        browser()->profile())
+        ->SetCloudPolicyClientForTesting(client_.get());
+  }
+
+  policy::MockCloudPolicyClient* client() { return client_.get(); }
+
  protected:
   void SetUp() override {
     test_sb_factory_ = std::make_unique<TestSafeBrowsingServiceFactory>();
@@ -340,6 +355,8 @@
   base::OnceClosure waiting_for_upload_closure_;
 
   base::flat_set<download::DownloadItem*> download_items_;
+
+  std::unique_ptr<policy::MockCloudPolicyClient> client_;
 };
 
 INSTANTIATE_TEST_SUITE_P(, DownloadDeepScanningBrowserTest, testing::Bool());
@@ -657,6 +674,119 @@
   EXPECT_EQ(item->GetState(), download::DownloadItem::INTERRUPTED);
 }
 
+IN_PROC_BROWSER_TEST_P(DownloadDeepScanningBrowserTest, MultipleFCMResponses) {
+  SetUpReporting();
+  base::HistogramTester histograms;
+
+  // The file is SAFE according to the metadata check
+  ClientDownloadResponse metadata_response;
+  metadata_response.set_verdict(ClientDownloadResponse::SAFE);
+  ExpectMetadataResponse(metadata_response);
+
+  // No scan runs synchronously.
+  if (use_legacy_policies()) {
+    DeepScanningClientResponse sync_response;
+    ExpectDeepScanSynchronousResponse(/*is_advanced_protection=*/false,
+                                      sync_response);
+  } else {
+    enterprise_connectors::ContentAnalysisResponse sync_response;
+    ExpectContentAnalysisSynchronousResponse(/*is_advanced_protection=*/false,
+                                             sync_response, {"dlp", "malware"});
+  }
+
+  GURL url = embedded_test_server()->GetURL(
+      "/safe_browsing/download_protection/zipfile_two_archives.zip");
+  ui_test_utils::NavigateToURLWithDisposition(
+      browser(), url, WindowOpenDisposition::CURRENT_TAB,
+      ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
+
+  WaitForDeepScanRequest(/*is_advanced_protection=*/false);
+
+  // The malware scan finishes asynchronously, and finds malware.
+  if (use_legacy_policies()) {
+    DeepScanningClientResponse async_response;
+    async_response.set_token(last_enterprise_request().request_token());
+    async_response.mutable_malware_scan_verdict()->set_verdict(
+        MalwareDeepScanningVerdict::MALWARE);
+    SendFcmMessage(async_response);
+  } else {
+    enterprise_connectors::ContentAnalysisResponse async_response;
+    async_response.set_request_token(
+        last_enterprise_content_analysis_request().request_token());
+    auto* result = async_response.add_results();
+    result->set_tag("malware");
+    result->set_status(
+        enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
+    auto* malware_rule = result->add_triggered_rules();
+    malware_rule->set_action(enterprise_connectors::TriggeredRule::BLOCK);
+    malware_rule->set_rule_name("malware");
+    SendFcmMessage(async_response);
+  }
+
+  // A single unsafe event should be recorded for this request.
+  std::set<std::string> zip_types = {"application/zip",
+                                     "application/x-zip-compressed"};
+  EventReportValidator validator(client());
+  validator.ExpectDangerousDeepScanningResult(
+      /*url*/ url.spec(),
+      /*filename*/
+      (*download_items().begin())->GetTargetFilePath().AsUTF8Unsafe(),
+      // sha256sum chrome/test/data/safe_browsing/download_protection/\
+      // zipfile_two_archives.zip |  tr '[:lower:]' '[:upper:]'
+      /*sha*/
+      "339C8FFDAE735C4F1846D0E6FF07FBD85CAEE6D96045AAEF5B30F3220836643C",
+      /*threat_type*/ "DANGEROUS",
+      /*trigger*/
+      extensions::SafeBrowsingPrivateEventRouter::kTriggerFileDownload,
+      /*mimetypes*/ &zip_types,
+      /*size*/ 276,
+      /*result*/ EventResultToString(EventResult::WARNED));
+
+  // The DLP scan finishes asynchronously, and finds nothing. The malware result
+  // is attached to the response again.
+  if (use_legacy_policies()) {
+    DeepScanningClientResponse async_response;
+    async_response.set_token(last_enterprise_request().request_token());
+    async_response.mutable_malware_scan_verdict()->set_verdict(
+        MalwareDeepScanningVerdict::MALWARE);
+    async_response.mutable_dlp_scan_verdict()->set_status(
+        DlpDeepScanningVerdict::SUCCESS);
+    SendFcmMessage(async_response);
+  } else {
+    enterprise_connectors::ContentAnalysisResponse async_response;
+    async_response.set_request_token(
+        last_enterprise_content_analysis_request().request_token());
+    auto* malware_result = async_response.add_results();
+    malware_result->set_tag("malware");
+    malware_result->set_status(
+        enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
+    auto* malware_rule = malware_result->add_triggered_rules();
+    malware_rule->set_action(enterprise_connectors::TriggeredRule::BLOCK);
+    malware_rule->set_rule_name("malware");
+    auto* dlp_result = async_response.add_results();
+    dlp_result->set_tag("dlp");
+    dlp_result->set_status(
+        enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
+    SendFcmMessage(async_response);
+  }
+
+  // The file should be blocked.
+  ASSERT_EQ(download_items().size(), 1u);
+  download::DownloadItem* item = *download_items().begin();
+  EXPECT_EQ(
+      item->GetDangerType(),
+      download::DownloadDangerType::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT);
+  EXPECT_EQ(item->GetState(), download::DownloadItem::IN_PROGRESS);
+
+  // UMAs for this request should only be recorded once.
+  histograms.ExpectUniqueSample("SafeBrowsingBinaryUploadRequest.Result",
+                                BinaryUploadService::Result::SUCCESS, 1);
+  histograms.ExpectUniqueSample("SafeBrowsingBinaryUploadRequest.DlpResult",
+                                true, 1);
+  histograms.ExpectUniqueSample("SafeBrowsingBinaryUploadRequest.MalwareResult",
+                                true, 1);
+}
+
 class WhitelistedUrlDeepScanningBrowserTest
     : public DownloadDeepScanningBrowserTest {
  public:
diff --git a/chrome/browser/safe_browsing/download_protection/download_reporter.cc b/chrome/browser/safe_browsing/download_protection/download_reporter.cc
index bdcaac9..9588410a 100644
--- a/chrome/browser/safe_browsing/download_protection/download_reporter.cc
+++ b/chrome/browser/safe_browsing/download_protection/download_reporter.cc
@@ -154,6 +154,7 @@
   download::DownloadDangerType current_danger_type = download->GetDangerType();
 
   if (!DangerTypeIsDangerous(old_danger_type) &&
+      old_danger_type != download::DOWNLOAD_DANGER_TYPE_ASYNC_SCANNING &&
       DangerTypeIsDangerous(current_danger_type)) {
     ReportDangerousDownloadWarning(download);
   }
diff --git a/chrome/browser/signin/e2e_tests/live_sign_in_test.cc b/chrome/browser/signin/e2e_tests/live_sign_in_test.cc
index 58a7b9e..67bb6ca 100644
--- a/chrome/browser/signin/e2e_tests/live_sign_in_test.cc
+++ b/chrome/browser/signin/e2e_tests/live_sign_in_test.cc
@@ -119,14 +119,13 @@
       return;
     }
 
-    bool has_valid_primary_sync_account = HasValidPrimarySyncAccount();
     switch (primary_sync_account_wait_) {
       case PrimarySyncAccountWait::kWaitForAdded:
-        if (!has_valid_primary_sync_account)
+        if (!HasValidPrimarySyncAccount())
           return;
         break;
       case PrimarySyncAccountWait::kWaitForCleared:
-        if (has_valid_primary_sync_account)
+        if (identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSync))
           return;
         break;
       case PrimarySyncAccountWait::kNotWait:
@@ -179,6 +178,8 @@
 };
 
 // Live tests for SignIn.
+// These tests can be run with:
+// browser_tests --gtest_filter=LiveSignInTest.* --run-live-tests --run-manual
 class LiveSignInTest : public signin::test::LiveTest {
  public:
   LiveSignInTest() = default;
@@ -409,6 +410,31 @@
   EXPECT_FALSE(identity_manager()->HasPrimaryAccount());
 }
 
+// In "Sync paused" state, when the primary account is invalid, turns off sync
+// from settings. Checks that the account is removed from Chrome.
+// Regression test for https://crbug.com/1114646
+IN_PROC_BROWSER_TEST_F(LiveSignInTest, MANUAL_TurnOffSyncWhenPaused) {
+  TestAccount test_account_1;
+  CHECK(GetTestAccountsUtil()->GetAccount("TEST_ACCOUNT_1", test_account_1));
+  TurnOnSync(test_account_1, 0);
+
+  // Get in sync paused state.
+  SignOutFromWeb();
+
+  const CoreAccountInfo& primary_account =
+      identity_manager()->GetPrimaryAccountInfo();
+  EXPECT_FALSE(primary_account.IsEmpty());
+  EXPECT_TRUE(gaia::AreEmailsSame(test_account_1.user, primary_account.email));
+  EXPECT_TRUE(sync_service()->IsSyncFeatureEnabled());
+  EXPECT_TRUE(
+      identity_manager()->HasAccountWithRefreshTokenInPersistentErrorState(
+          primary_account.account_id));
+
+  TurnOffSync();
+  EXPECT_TRUE(identity_manager()->GetAccountsWithRefreshTokens().empty());
+  EXPECT_FALSE(identity_manager()->HasPrimaryAccount());
+}
+
 // This test can pass. Marked as manual because it TIMED_OUT on Win7.
 // See crbug.com/1025335.
 // Signs in an account on the web. Goes to the Chrome settings to enable Sync
diff --git a/chrome/browser/signin/signin_features.cc b/chrome/browser/signin/signin_features.cc
index 34a745b..04c9c495 100644
--- a/chrome/browser/signin/signin_features.cc
+++ b/chrome/browser/signin/signin_features.cc
@@ -8,6 +8,3 @@
 const base::Feature kDiceWebSigninInterceptionFeature{
     "DiceWebSigninInterception", base::FEATURE_DISABLED_BY_DEFAULT};
 #endif  // ENABLE_DICE_SUPPORT
-
-const base::Feature kSigninReauthPrompt = {"SigninReauthPrompt",
-                                           base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/chrome/browser/signin/signin_features.h b/chrome/browser/signin/signin_features.h
index 258cb70c..6b258b77 100644
--- a/chrome/browser/signin/signin_features.h
+++ b/chrome/browser/signin/signin_features.h
@@ -12,6 +12,4 @@
 extern const base::Feature kDiceWebSigninInterceptionFeature;
 #endif  // ENABLE_DICE_SUPPORT
 
-extern const base::Feature kSigninReauthPrompt;
-
 #endif  // CHROME_BROWSER_SIGNIN_SIGNIN_FEATURES_H_
diff --git a/chrome/browser/ssl/ssl_browsertest.cc b/chrome/browser/ssl/ssl_browsertest.cc
index 0fafb9b3..fcde182 100644
--- a/chrome/browser/ssl/ssl_browsertest.cc
+++ b/chrome/browser/ssl/ssl_browsertest.cc
@@ -75,7 +75,7 @@
 #include "chrome/test/base/test_launcher_utils.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/certificate_transparency/pref_names.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/common/content_settings_agent.mojom.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/content_settings/core/common/content_settings_types.h"
@@ -3508,7 +3508,7 @@
     }
 
     EXPECT_EQ(expected_show_blocked,
-              content_settings::TabSpecificContentSettings::GetForFrame(
+              content_settings::PageSpecificContentSettings::GetForFrame(
                   tab->GetMainFrame())
                   ->IsContentBlocked(ContentSettingsType::MIXEDSCRIPT));
     ssl_test_util::CheckSecurityState(
@@ -3532,7 +3532,7 @@
     }
 
     EXPECT_EQ(expected_show_blocked_after_allow,
-              content_settings::TabSpecificContentSettings::GetForFrame(
+              content_settings::PageSpecificContentSettings::GetForFrame(
                   tab->GetMainFrame())
                   ->IsContentBlocked(ContentSettingsType::MIXEDSCRIPT));
     ssl_test_util::CheckSecurityState(
@@ -3558,7 +3558,7 @@
 
   void CheckErrorStateIsCleared() {
     WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
-    EXPECT_FALSE(content_settings::TabSpecificContentSettings::GetForFrame(
+    EXPECT_FALSE(content_settings::PageSpecificContentSettings::GetForFrame(
                      tab->GetMainFrame())
                      ->IsContentBlocked(ContentSettingsType::MIXEDSCRIPT));
     ssl_test_util::CheckSecurityState(tab, CertError::NONE,
diff --git a/chrome/browser/subresource_filter/chrome_subresource_filter_client.cc b/chrome/browser/subresource_filter/chrome_subresource_filter_client.cc
index 4f395895..1c797e29e 100644
--- a/chrome/browser/subresource_filter/chrome_subresource_filter_client.cc
+++ b/chrome/browser/subresource_filter/chrome_subresource_filter_client.cc
@@ -17,7 +17,7 @@
 #include "chrome/browser/subresource_filter/subresource_filter_content_settings_manager.h"
 #include "chrome/browser/subresource_filter/subresource_filter_profile_context.h"
 #include "chrome/browser/subresource_filter/subresource_filter_profile_context_factory.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "components/safe_browsing/core/db/database_manager.h"
@@ -165,8 +165,8 @@
   // (it comes  from
   // ContentSubresourceFilterThrottleManager::DidDisallowFirstSubresource, which
   // comes from a specific frame).
-  content_settings::TabSpecificContentSettings* content_settings =
-      content_settings::TabSpecificContentSettings::GetForFrame(
+  content_settings::PageSpecificContentSettings* content_settings =
+      content_settings::PageSpecificContentSettings::GetForFrame(
           web_contents()->GetMainFrame());
   content_settings->OnContentBlocked(ContentSettingsType::ADS);
 
diff --git a/chrome/browser/subresource_filter/subresource_filter_popup_browsertest.cc b/chrome/browser/subresource_filter/subresource_filter_popup_browsertest.cc
index d5dda04..03030433 100644
--- a/chrome/browser/subresource_filter/subresource_filter_popup_browsertest.cc
+++ b/chrome/browser/subresource_filter/subresource_filter_popup_browsertest.cc
@@ -21,7 +21,7 @@
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/blocked_content/safe_browsing_triggered_popup_blocker.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "components/safe_browsing/core/db/util.h"
@@ -135,7 +135,7 @@
   EXPECT_TRUE(content::ExecuteScriptAndExtractBool(web_contents, "openWindow()",
                                                    &opened_window));
   EXPECT_TRUE(opened_window);
-  EXPECT_FALSE(content_settings::TabSpecificContentSettings::GetForFrame(
+  EXPECT_FALSE(content_settings::PageSpecificContentSettings::GetForFrame(
                    web_contents->GetMainFrame())
                    ->IsContentBlocked(ContentSettingsType::POPUPS));
 
@@ -184,7 +184,7 @@
   EXPECT_FALSE(opened_window);
   tester.ExpectTotalCount(kSubresourceFilterActionsHistogram, 0);
   // Make sure the popup UI was shown.
-  EXPECT_TRUE(content_settings::TabSpecificContentSettings::GetForFrame(
+  EXPECT_TRUE(content_settings::PageSpecificContentSettings::GetForFrame(
                   web_contents->GetMainFrame())
                   ->IsContentBlocked(ContentSettingsType::POPUPS));
 
@@ -203,7 +203,7 @@
                                                    &opened_window));
   EXPECT_TRUE(opened_window);
   // Popup UI should not be shown.
-  EXPECT_FALSE(content_settings::TabSpecificContentSettings::GetForFrame(
+  EXPECT_FALSE(content_settings::PageSpecificContentSettings::GetForFrame(
                    web_contents->GetMainFrame())
                    ->IsContentBlocked(ContentSettingsType::POPUPS));
 }
@@ -344,7 +344,7 @@
   EXPECT_TRUE(content::ExecuteScript(web_contents, "openWindow()"));
   tester.ExpectTotalCount(kSubresourceFilterActionsHistogram, 0);
 
-  EXPECT_TRUE(content_settings::TabSpecificContentSettings::GetForFrame(
+  EXPECT_TRUE(content_settings::PageSpecificContentSettings::GetForFrame(
                   web_contents->GetMainFrame())
                   ->IsContentBlocked(ContentSettingsType::POPUPS));
   const bool enable_adblock_on_abusive_sites = GetParam();
@@ -360,7 +360,7 @@
   navigation_observer.Wait();
 
   // Popup UI should not be shown.
-  EXPECT_FALSE(content_settings::TabSpecificContentSettings::GetForFrame(
+  EXPECT_FALSE(content_settings::PageSpecificContentSettings::GetForFrame(
                    web_contents->GetMainFrame())
                    ->IsContentBlocked(ContentSettingsType::POPUPS));
   EXPECT_FALSE(AreDisallowedRequestsBlocked());
@@ -382,7 +382,7 @@
   EXPECT_TRUE(content::ExecuteScriptAndExtractBool(web_contents, "openWindow()",
                                                    &sent_open));
   EXPECT_TRUE(sent_open);
-  EXPECT_TRUE(content_settings::TabSpecificContentSettings::GetForFrame(
+  EXPECT_TRUE(content_settings::PageSpecificContentSettings::GetForFrame(
                   web_contents->GetMainFrame())
                   ->IsContentBlocked(ContentSettingsType::POPUPS));
   const bool enable_adblock_on_abusive_sites = GetParam();
@@ -405,7 +405,7 @@
 
   content::WebContents* web_contents =
       browser()->tab_strip_model()->GetActiveWebContents();
-  EXPECT_FALSE(content_settings::TabSpecificContentSettings::GetForFrame(
+  EXPECT_FALSE(content_settings::PageSpecificContentSettings::GetForFrame(
                    web_contents->GetMainFrame())
                    ->IsContentBlocked(ContentSettingsType::POPUPS));
   const bool enable_adblock_on_abusive_sites = GetParam();
diff --git a/chrome/browser/subresource_filter/subresource_filter_test_harness.cc b/chrome/browser/subresource_filter/subresource_filter_test_harness.cc
index 496fa4b..eda4f880 100644
--- a/chrome/browser/subresource_filter/subresource_filter_test_harness.cc
+++ b/chrome/browser/subresource_filter/subresource_filter_test_harness.cc
@@ -10,7 +10,7 @@
 #include "base/task/post_task.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/threading/thread_task_runner_handle.h"
-#include "chrome/browser/content_settings/tab_specific_content_settings_delegate.h"
+#include "chrome/browser/content_settings/page_specific_content_settings_delegate.h"
 #include "chrome/browser/infobars/infobar_service.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/safe_browsing/test_safe_browsing_service.h"
@@ -22,7 +22,7 @@
 #include "chrome/browser/subresource_filter/test_ruleset_publisher.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/safe_browsing/core/db/v4_protocol_manager_util.h"
 #include "components/subresource_filter/content/browser/ruleset_service.h"
 #include "components/subresource_filter/content/browser/subresource_filter_observer_test_utils.h"
@@ -99,9 +99,9 @@
 
   // Set up the tab helpers.
   InfoBarService::CreateForWebContents(web_contents());
-  content_settings::TabSpecificContentSettings::CreateForWebContents(
+  content_settings::PageSpecificContentSettings::CreateForWebContents(
       web_contents(),
-      std::make_unique<chrome::TabSpecificContentSettingsDelegate>(
+      std::make_unique<chrome::PageSpecificContentSettingsDelegate>(
           web_contents()));
   ChromeSubresourceFilterClient::CreateForWebContents(web_contents());
 
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index 953788b..c460a5f 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -1089,6 +1089,12 @@
       <message name="IDS_LANGUAGES_ITEM_OPTION_OFFER_TO_TRANSLATE" desc="Option in language item menu. User can click the 'Offer to translate' option to toggle whether they want Chrome to translate pages in this language. [CHAR-LIMIT=32]">
         Offer to translate
       </message>
+      <message name="IDS_LANGUAGES_SET_APPLICATION_LANGUAGE_PROMPT" desc="Option in language item menu. User can click the 'App Language' option to toggle whether or not the Chrome user interface should be displayed in this language. [CHAR-LIMIT=32]">
+        Use as Chrome’s language
+      </message>
+      <message name="IDS_LANGUAGES_SET_AS_APPLICATION_LANGUAGE" desc="Toast to display after selecting language to use for Chrome's user interface.">
+        <ph name="APP_NAME">%1$s<ex>Chrome</ex></ph> will use <ph name="language">%2$s<ex>Hindi</ex></ph> on restart.
+      </message>
       <message name="IDS_LANGUAGES_EXPLICIT_ASK_TITLE" desc="Title of the dialog that explicitly asks the user which languages they can read.">
         What languages do you read?
       </message>
@@ -2416,6 +2422,9 @@
       <message name="IDS_SIGNIN_ACCOUNT_PICKER_BOTTOM_SHEET_SUBTITLE" desc="The subtitle for the account picker bottom sheet that tells the user what happens if the button 'Continue as John Doe' is clicked">
         You’ll be signed in with your Google Account\n(this won’t turn on Chrome Sync)
       </message>
+      <message name="IDS_SIGNIN_ACCOUNT_PICKER_BOTTOM_SHEET_SIGNIN_TITLE" desc="The title of the account picker bottom sheet tells that the user is in the process of signing in with their Google Account">
+        Signing in...
+      </message>
 
       <!-- Personalized Signin Promos Strings -->
       <message name="IDS_SIGNIN_PROMO_CONTINUE_AS" desc="Button that the user can press to login without asking the password and continue using Chrome with this acccount.">
@@ -3339,8 +3348,11 @@
       <message name="IDS_TWA_CLEAR_DATA_SITE_SELECTION_TITLE" desc="Title of screen showing the sites linked to TWA, allowing the user to clean data in any of them.">
         Linked sites
       </message>
-      <message name="IDS_TWA_QUALITY_ENFORCEMENT_VIOLATION_ERROR" desc="Text shown on a toast when TWA violation.">
-          TWA error: <ph name="ERROR_CODE">%1$s<ex>404</ex></ph> on <ph name="VIOLATED_URL">%2$s<ex>https://google.com/</ex></ph>
+      <message name="IDS_TWA_QUALITY_ENFORCEMENT_VIOLATION_ERROR" desc="Text shown on a toast when TWA violation 404.">
+        <ph name="ERROR_CODE">%1$s<ex>404</ex></ph> on <ph name="VIOLATED_URL">%2$s<ex>https://example.com/</ex></ph>
+      </message>
+      <message name="IDS_TWA_QUALITY_ENFORCEMENT_VIOLATION_OFFLINE" desc="Text shown on a toast when TWA violate the quality enforcement criteria: page unavailable offline.">
+        Page unavailable offline: <ph name="VIOLATED_URL">%1$s<ex>https://example.com/</ex></ph>
       </message>
 
       <message name="IDS_WEBAPP_TAP_TO_COPY_URL" desc="Message on the notification that indicates that taping it will copy a Web App's URL into the clipboard.">
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_LANGUAGES_SET_APPLICATION_LANGUAGE_PROMPT.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_LANGUAGES_SET_APPLICATION_LANGUAGE_PROMPT.png.sha1
new file mode 100644
index 0000000..c74c1dec
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_LANGUAGES_SET_APPLICATION_LANGUAGE_PROMPT.png.sha1
@@ -0,0 +1 @@
+a06a8a932e9e708294c314558f6e1ec468967cb4
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_LANGUAGES_SET_AS_APPLICATION_LANGUAGE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_LANGUAGES_SET_AS_APPLICATION_LANGUAGE.png.sha1
new file mode 100644
index 0000000..ee6ed33
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_LANGUAGES_SET_AS_APPLICATION_LANGUAGE.png.sha1
@@ -0,0 +1 @@
+1b9eb3ffd36ca1e5fcd298fdd008adee701e88bb
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SIGNIN_ACCOUNT_PICKER_BOTTOM_SHEET_SIGNIN_TITLE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SIGNIN_ACCOUNT_PICKER_BOTTOM_SHEET_SIGNIN_TITLE.png.sha1
new file mode 100644
index 0000000..cf0d86c
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SIGNIN_ACCOUNT_PICKER_BOTTOM_SHEET_SIGNIN_TITLE.png.sha1
@@ -0,0 +1 @@
+06b7cfe92a89ece7130c3fa62661dafc75ab32ce
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_TWA_QUALITY_ENFORCEMENT_VIOLATION_ERROR.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_TWA_QUALITY_ENFORCEMENT_VIOLATION_ERROR.png.sha1
index 6e89e7a..c6186f3 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_TWA_QUALITY_ENFORCEMENT_VIOLATION_ERROR.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_TWA_QUALITY_ENFORCEMENT_VIOLATION_ERROR.png.sha1
@@ -1 +1 @@
-42a7c0f54c737869a0c657bafeaada04a98d3f11
\ No newline at end of file
+ca08321ce7674bbe3013f114a68574bc50dd9494
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_TWA_QUALITY_ENFORCEMENT_VIOLATION_OFFLINE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_TWA_QUALITY_ENFORCEMENT_VIOLATION_OFFLINE.png.sha1
new file mode 100644
index 0000000..0cb3f0e08
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_TWA_QUALITY_ENFORCEMENT_VIOLATION_OFFLINE.png.sha1
@@ -0,0 +1 @@
+078ec0828bc1dab55a2c57950060e7838989398a
\ No newline at end of file
diff --git a/chrome/browser/ui/blocked_content/popup_opener_tab_helper_unittest.cc b/chrome/browser/ui/blocked_content/popup_opener_tab_helper_unittest.cc
index 624fa5f..5ab8350 100644
--- a/chrome/browser/ui/blocked_content/popup_opener_tab_helper_unittest.cc
+++ b/chrome/browser/ui/blocked_content/popup_opener_tab_helper_unittest.cc
@@ -18,7 +18,7 @@
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
-#include "chrome/browser/content_settings/tab_specific_content_settings_delegate.h"
+#include "chrome/browser/content_settings/page_specific_content_settings_delegate.h"
 #include "chrome/browser/infobars/infobar_service.h"
 #include "chrome/browser/ui/blocked_content/tab_under_navigation_throttle.h"
 #include "chrome/common/pref_names.h"
@@ -27,7 +27,7 @@
 #include "chrome/test/base/testing_profile.h"
 #include "components/blocked_content/list_item_position.h"
 #include "components/blocked_content/popup_tracker.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/ukm/content/source_url_recorder.h"
 #include "components/ukm/test_ukm_recorder.h"
 #include "content/public/browser/web_contents.h"
@@ -67,9 +67,9 @@
         web_contents(), &raw_clock_,
         HostContentSettingsMapFactory::GetForProfile(profile()));
     InfoBarService::CreateForWebContents(web_contents());
-    content_settings::TabSpecificContentSettings::CreateForWebContents(
+    content_settings::PageSpecificContentSettings::CreateForWebContents(
         web_contents(),
-        std::make_unique<chrome::TabSpecificContentSettingsDelegate>(
+        std::make_unique<chrome::PageSpecificContentSettingsDelegate>(
             web_contents()));
 #if !defined(OS_ANDROID)
     FramebustBlockTabHelper::CreateForWebContents(web_contents());
diff --git a/chrome/browser/ui/blocked_content/popup_tracker_browsertest.cc b/chrome/browser/ui/blocked_content/popup_tracker_browsertest.cc
index 9312c47e2..5854becb 100644
--- a/chrome/browser/ui/blocked_content/popup_tracker_browsertest.cc
+++ b/chrome/browser/ui/blocked_content/popup_tracker_browsertest.cc
@@ -21,7 +21,7 @@
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/blocked_content/popup_blocker_tab_helper.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/safe_browsing/core/db/v4_embedded_test_server_util.h"
 #include "components/safe_browsing/core/db/v4_test_util.h"
 #include "components/ukm/test_ukm_recorder.h"
@@ -290,7 +290,7 @@
 
   // Is blocked by the popup blocker.
   ui_test_utils::NavigateToURL(browser(), url);
-  EXPECT_TRUE(content_settings::TabSpecificContentSettings::GetForFrame(
+  EXPECT_TRUE(content_settings::PageSpecificContentSettings::GetForFrame(
                   web_contents->GetMainFrame())
                   ->IsContentBlocked(ContentSettingsType::POPUPS));
 
diff --git a/chrome/browser/ui/blocked_content/safe_browsing_triggered_popup_blocker_browsertest.cc b/chrome/browser/ui/blocked_content/safe_browsing_triggered_popup_blocker_browsertest.cc
index 1d3e081f..0ead9a4 100644
--- a/chrome/browser/ui/blocked_content/safe_browsing_triggered_popup_blocker_browsertest.cc
+++ b/chrome/browser/ui/blocked_content/safe_browsing_triggered_popup_blocker_browsertest.cc
@@ -28,7 +28,7 @@
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/blocked_content/popup_blocker_tab_helper.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "components/policy/core/browser/browser_policy_connector.h"
@@ -52,7 +52,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
-using content_settings::TabSpecificContentSettings;
+using content_settings::PageSpecificContentSettings;
 using safe_browsing::SubresourceFilterLevel;
 using safe_browsing::SubresourceFilterType;
 
@@ -248,7 +248,7 @@
                                                    &opened_window));
   EXPECT_TRUE(opened_window);
   EXPECT_FALSE(
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
           ->IsContentBlocked(ContentSettingsType::POPUPS));
 }
 
@@ -268,7 +268,7 @@
                                                    &opened_window));
   EXPECT_TRUE(opened_window);
   EXPECT_FALSE(
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
           ->IsContentBlocked(ContentSettingsType::POPUPS));
 
   RoundTripAndVerifyLogMessages(console_observer, web_contents, {},
@@ -299,7 +299,7 @@
                                                    &opened_window));
   EXPECT_TRUE(opened_window);
   EXPECT_FALSE(
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
           ->IsContentBlocked(ContentSettingsType::POPUPS));
 
   // Since the policy change can take effect without browser restart, verify
@@ -325,7 +325,7 @@
   EXPECT_FALSE(opened_window);
   // Make sure the popup UI was shown.
   EXPECT_TRUE(
-      TabSpecificContentSettings::GetForFrame(web_contents1->GetMainFrame())
+      PageSpecificContentSettings::GetForFrame(web_contents1->GetMainFrame())
           ->IsContentBlocked(ContentSettingsType::POPUPS));
 }
 
@@ -348,7 +348,7 @@
                                                    &opened_window));
   EXPECT_TRUE(opened_window);
   EXPECT_FALSE(
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
           ->IsContentBlocked(ContentSettingsType::POPUPS));
 }
 
@@ -366,7 +366,7 @@
                                                    &opened_window));
   EXPECT_TRUE(opened_window);
   EXPECT_FALSE(
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
           ->IsContentBlocked(ContentSettingsType::POPUPS));
 }
 
@@ -387,7 +387,7 @@
   EXPECT_FALSE(opened_window);
   // Make sure the popup UI was shown.
   EXPECT_TRUE(
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
           ->IsContentBlocked(ContentSettingsType::POPUPS));
 
   // Block again.
@@ -403,7 +403,7 @@
   EXPECT_TRUE(opened_window);
   // Popup UI should not be shown.
   EXPECT_FALSE(
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
           ->IsContentBlocked(ContentSettingsType::POPUPS));
 }
 
@@ -426,7 +426,7 @@
 
   // Make sure the popup UI was shown.
   EXPECT_TRUE(
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
           ->IsContentBlocked(ContentSettingsType::POPUPS));
 
   // Click through.
@@ -511,7 +511,7 @@
   EXPECT_TRUE(content::ExecuteScript(web_contents, "openWindow()"));
 
   EXPECT_TRUE(
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
           ->IsContentBlocked(ContentSettingsType::POPUPS));
 
   // Navigate to |b_url|, which should successfully open the popup.
@@ -525,7 +525,7 @@
 
   // Popup UI should not be shown.
   EXPECT_FALSE(
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
           ->IsContentBlocked(ContentSettingsType::POPUPS));
 }
 
@@ -544,7 +544,7 @@
                                                    &sent_open));
   EXPECT_TRUE(sent_open);
   EXPECT_TRUE(
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
           ->IsContentBlocked(ContentSettingsType::POPUPS));
 }
 
@@ -564,7 +564,7 @@
     EXPECT_EQ(expect_block, !opened_window);
     EXPECT_EQ(
         expect_block,
-        TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
+        PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
             ->IsContentBlocked(ContentSettingsType::POPUPS));
   };
 
diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc
index 66c4158..6689b358 100644
--- a/chrome/browser/ui/browser.cc
+++ b/chrome/browser/ui/browser.cc
@@ -41,8 +41,8 @@
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/content_settings/mixed_content_settings_tab_helper.h"
+#include "chrome/browser/content_settings/page_specific_content_settings_delegate.h"
 #include "chrome/browser/content_settings/sound_content_setting_observer.h"
-#include "chrome/browser/content_settings/tab_specific_content_settings_delegate.h"
 #include "chrome/browser/custom_handlers/protocol_handler_registry.h"
 #include "chrome/browser/custom_handlers/protocol_handler_registry_factory.h"
 #include "chrome/browser/custom_handlers/register_protocol_handler_permission_request.h"
@@ -163,7 +163,7 @@
 #include "components/bookmarks/browser/bookmark_utils.h"
 #include "components/bookmarks/common/bookmark_pref_names.h"
 #include "components/captive_portal/core/buildflags.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/favicon/content/content_favicon_driver.h"
 #include "components/find_in_page/find_tab_helper.h"
@@ -1440,11 +1440,11 @@
     // TODO(https://crbug.com/1103176): Plumb the actual frame reference here
     // (MixedContentNavigationThrottle::ShouldBlockNavigation has
     // |mixed_content_frame| reference)
-    content_settings::TabSpecificContentSettings* tab_settings =
-        content_settings::TabSpecificContentSettings::GetForFrame(
+    content_settings::PageSpecificContentSettings* page_settings =
+        content_settings::PageSpecificContentSettings::GetForFrame(
             web_contents->GetMainFrame());
-    DCHECK(tab_settings);
-    tab_settings->OnContentBlocked(ContentSettingsType::MIXEDSCRIPT);
+    DCHECK(page_settings);
+    page_settings->OnContentBlocked(ContentSettingsType::MIXEDSCRIPT);
   }
   return allowed;
 }
@@ -1996,11 +1996,12 @@
 
   // TODO(carlscab): This should probably be FromFrame() once it becomes
   // PageSpecificContentSettingsDelegate
-  auto* tab_content_settings_delegate =
-      chrome::TabSpecificContentSettingsDelegate::FromWebContents(web_contents);
+  auto* page_content_settings_delegate =
+      chrome::PageSpecificContentSettingsDelegate::FromWebContents(
+          web_contents);
   if (!user_gesture && window_) {
-    tab_content_settings_delegate->set_pending_protocol_handler(handler);
-    tab_content_settings_delegate->set_previous_protocol_handler(
+    page_content_settings_delegate->set_pending_protocol_handler(handler);
+    page_content_settings_delegate->set_previous_protocol_handler(
         registry->GetHandlerFor(handler.protocol()));
     window_->GetLocationBar()->UpdateContentSettingsIcons();
     return;
@@ -2009,7 +2010,7 @@
   // Make sure content-setting icon is turned off in case the page does
   // ungestured and gestured RPH calls.
   if (window_) {
-    tab_content_settings_delegate->ClearPendingProtocolHandler();
+    page_content_settings_delegate->ClearPendingProtocolHandler();
     window_->GetLocationBar()->UpdateContentSettingsIcons();
   }
 
@@ -2133,8 +2134,8 @@
   }
 
   // TODO(https://crbug.com/1103176): Plumb the actual frame reference here
-  content_settings::TabSpecificContentSettings* tab_content_settings =
-      content_settings::TabSpecificContentSettings::GetForFrame(
+  content_settings::PageSpecificContentSettings* tab_content_settings =
+      content_settings::PageSpecificContentSettings::GetForFrame(
           web_contents->GetMainFrame());
 
   HostContentSettingsMap* content_settings =
diff --git a/chrome/browser/ui/browser_browsertest.cc b/chrome/browser/ui/browser_browsertest.cc
index a779ecc..211a514 100644
--- a/chrome/browser/ui/browser_browsertest.cc
+++ b/chrome/browser/ui/browser_browsertest.cc
@@ -1360,13 +1360,7 @@
 }
 
 // Regression test for crbug.com/702505.
-// Fails occasionally on Mac. http://crbug.com/852697
-#if defined(OS_MAC)
-#define MAYBE_ReattachDevToolsWindow DISABLED_ReattachDevToolsWindow
-#else
-#define MAYBE_ReattachDevToolsWindow ReattachDevToolsWindow
-#endif
-IN_PROC_BROWSER_TEST_F(BrowserTest, MAYBE_ReattachDevToolsWindow) {
+IN_PROC_BROWSER_TEST_F(BrowserTest, ReattachDevToolsWindow) {
   ASSERT_TRUE(embedded_test_server()->Start());
   WebContents* web_contents =
       browser()->tab_strip_model()->GetActiveWebContents();
@@ -1394,7 +1388,7 @@
   // Re-attach the dev tools window. This resets its Browser*.
   devtools_delegate->SetIsDocked(true);
   // Wait until the browser actually gets closed.
-  content::RunAllPendingInMessageLoop();
+  ui_test_utils::WaitForBrowserToClose();
   ASSERT_EQ(1u, chrome::GetBrowserCount(browser()->profile()));
 
   // Do something that will make SearchTabHelper access its OmniboxView. This
diff --git a/chrome/browser/ui/content_settings/content_setting_bubble_model.cc b/chrome/browser/ui/content_settings/content_setting_bubble_model.cc
index 7180227..496a456 100644
--- a/chrome/browser/ui/content_settings/content_setting_bubble_model.cc
+++ b/chrome/browser/ui/content_settings/content_setting_bubble_model.cc
@@ -20,7 +20,7 @@
 #include "chrome/browser/content_settings/cookie_settings_factory.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/content_settings/mixed_content_settings_tab_helper.h"
-#include "chrome/browser/content_settings/tab_specific_content_settings_delegate.h"
+#include "chrome/browser/content_settings/page_specific_content_settings_delegate.h"
 #include "chrome/browser/custom_handlers/protocol_handler_registry.h"
 #include "chrome/browser/custom_handlers/protocol_handler_registry_factory.h"
 #include "chrome/browser/download/download_request_limiter.h"
@@ -45,7 +45,7 @@
 #include "chrome/grit/theme_resources.h"
 #include "components/blocked_content/popup_blocker_tab_helper.h"
 #include "components/content_settings/browser/content_settings_usages_state.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/common/content_settings_agent.mojom.h"
 #include "components/content_settings/core/browser/content_settings_utils.h"
 #include "components/content_settings/core/browser/cookie_settings.h"
@@ -83,11 +83,11 @@
 
 using base::UserMetricsAction;
 using content::WebContents;
+using content_settings::PageSpecificContentSettings;
 using content_settings::SETTING_SOURCE_NONE;
 using content_settings::SETTING_SOURCE_USER;
 using content_settings::SettingInfo;
 using content_settings::SettingSource;
-using content_settings::TabSpecificContentSettings;
 
 namespace {
 
@@ -209,8 +209,8 @@
 
 void ContentSettingSimpleBubbleModel::SetTitle() {
   // TODO(https://crbug.com/1103176): Plumb the actual frame reference here
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
 
   static const ContentSettingsTypeIdEntry kBlockedTitleIDs[] = {
       {ContentSettingsType::COOKIES, IDS_BLOCKED_COOKIES_TITLE},
@@ -244,8 +244,8 @@
 }
 
 void ContentSettingSimpleBubbleModel::SetMessage() {
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
 
   // TODO(https://crbug.com/978882): Make the two arrays below static again once
   // we no longer need to check base::FeatureList.
@@ -401,7 +401,8 @@
       pending_handler_(ProtocolHandler::EmptyProtocolHandler()),
       previous_handler_(ProtocolHandler::EmptyProtocolHandler()) {
   auto* content_settings =
-      chrome::TabSpecificContentSettingsDelegate::FromWebContents(web_contents);
+      chrome::PageSpecificContentSettingsDelegate::FromWebContents(
+          web_contents);
   pending_handler_ = content_settings->pending_protocol_handler();
   previous_handler_ = content_settings->previous_protocol_handler();
 
@@ -451,7 +452,7 @@
 
   // The user has one chance to deal with the RPH content setting UI,
   // then we remove it.
-  chrome::TabSpecificContentSettingsDelegate::FromWebContents(web_contents())
+  chrome::PageSpecificContentSettingsDelegate::FromWebContents(web_contents())
       ->ClearPendingProtocolHandler();
   content_settings::UpdateLocationBarUiForWebContents(web_contents());
 }
@@ -462,20 +463,20 @@
   registry_->RemoveIgnoredHandler(pending_handler_);
 
   registry_->OnAcceptRegisterProtocolHandler(pending_handler_);
-  chrome::TabSpecificContentSettingsDelegate::FromWebContents(web_contents())
+  chrome::PageSpecificContentSettingsDelegate::FromWebContents(web_contents())
       ->set_pending_protocol_handler_setting(CONTENT_SETTING_ALLOW);
 }
 
 void ContentSettingRPHBubbleModel::UnregisterProtocolHandler() {
   registry_->OnDenyRegisterProtocolHandler(pending_handler_);
-  chrome::TabSpecificContentSettingsDelegate::FromWebContents(web_contents())
+  chrome::PageSpecificContentSettingsDelegate::FromWebContents(web_contents())
       ->set_pending_protocol_handler_setting(CONTENT_SETTING_BLOCK);
   ClearOrSetPreviousHandler();
 }
 
 void ContentSettingRPHBubbleModel::IgnoreProtocolHandler() {
   registry_->OnIgnoreRegisterProtocolHandler(pending_handler_);
-  chrome::TabSpecificContentSettingsDelegate::FromWebContents(web_contents())
+  chrome::PageSpecificContentSettingsDelegate::FromWebContents(web_contents())
       ->set_pending_protocol_handler_setting(CONTENT_SETTING_DEFAULT);
   ClearOrSetPreviousHandler();
 }
@@ -537,8 +538,8 @@
 }
 
 void ContentSettingMidiSysExBubbleModel::SetDomainsAndCustomLink() {
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   const ContentSettingsUsagesState& usages_state =
       content_settings->midi_usages_state();
   ContentSettingsUsagesState::FormattedHostsPerState formatted_hosts_per_state;
@@ -567,8 +568,8 @@
   // Reset this embedder's entry to default for each of the requesting
   // origins currently on the page.
   const GURL& embedder_url = web_contents()->GetURL();
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   const ContentSettingsUsagesState::StateMap& state_map =
       content_settings->midi_usages_state().state_map();
   HostContentSettingsMap* map =
@@ -630,8 +631,8 @@
 }
 
 void ContentSettingDomainListBubbleModel::SetDomainsAndCustomLink() {
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   const ContentSettingsUsagesState& usages =
       content_settings->geolocation_usages_state();
   ContentSettingsUsagesState::FormattedHostsPerState formatted_hosts_per_state;
@@ -660,8 +661,8 @@
   // Reset this embedder's entry to default for each of the requesting
   // origins currently on the page.
   const GURL& embedder_url = web_contents()->GetURL();
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   const ContentSettingsUsagesState::StateMap& state_map =
       content_settings->geolocation_usages_state().state_map();
   HostContentSettingsMap* map =
@@ -721,7 +722,7 @@
     // Disable the "Run all plugins this time" link if the user already clicked
     // on the link and ran all plugins.
     set_custom_link_enabled(
-        TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
+        PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
             ->load_plugins_link_enabled());
   }
 
@@ -754,7 +755,7 @@
       web_contents(), true, std::string());
 #endif
   set_custom_link_enabled(false);
-  TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame())
+  PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame())
       ->set_load_plugins_link_enabled(false);
 }
 
@@ -792,12 +793,12 @@
   const GURL& url = web_contents()->GetURL();
   base::string16 display_host = url_formatter::FormatUrlForSecurityDisplay(url);
 
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   bool allowed = !content_settings->IsContentBlocked(content_type());
 
   // For the frame busting case the content is blocked but its content type is
-  // popup, and the popup TabSpecificContentSettings is unaware of the frame
+  // popup, and the popup PageSpecificContentSettings is unaware of the frame
   // busting block. Since the popup bubble won't happen without blocking, it's
   // safe to manually set this.
   if (content_type() == ContentSettingsType::POPUPS)
@@ -1039,7 +1040,7 @@
     Delegate* delegate,
     WebContents* web_contents)
     : ContentSettingBubbleModel(delegate, web_contents),
-      state_(TabSpecificContentSettings::MICROPHONE_CAMERA_NOT_ACCESSED) {
+      state_(PageSpecificContentSettings::MICROPHONE_CAMERA_NOT_ACCESSED) {
   // TODO(msramek): The media bubble has three states - mic only, camera only,
   // and both. There is a lot of duplicated code which does the same thing
   // for camera and microphone separately. Consider refactoring it to avoid
@@ -1050,8 +1051,8 @@
   radio_item_setting_[0] = CONTENT_SETTING_ASK;
   radio_item_setting_[1] = CONTENT_SETTING_BLOCK;
 
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame());
   state_ = content_settings->GetMicrophoneCameraState();
   DCHECK(CameraAccessed() || MicrophoneAccessed());
 
@@ -1130,19 +1131,19 @@
 }
 
 bool ContentSettingMediaStreamBubbleModel::MicrophoneAccessed() const {
-  return (state_ & TabSpecificContentSettings::MICROPHONE_ACCESSED) != 0;
+  return (state_ & PageSpecificContentSettings::MICROPHONE_ACCESSED) != 0;
 }
 
 bool ContentSettingMediaStreamBubbleModel::CameraAccessed() const {
-  return (state_ & TabSpecificContentSettings::CAMERA_ACCESSED) != 0;
+  return (state_ & PageSpecificContentSettings::CAMERA_ACCESSED) != 0;
 }
 
 bool ContentSettingMediaStreamBubbleModel::MicrophoneBlocked() const {
-  return (state_ & TabSpecificContentSettings::MICROPHONE_BLOCKED) != 0;
+  return (state_ & PageSpecificContentSettings::MICROPHONE_BLOCKED) != 0;
 }
 
 bool ContentSettingMediaStreamBubbleModel::CameraBlocked() const {
-  return (state_ & TabSpecificContentSettings::CAMERA_BLOCKED) != 0;
+  return (state_ & PageSpecificContentSettings::CAMERA_BLOCKED) != 0;
 }
 
 void ContentSettingMediaStreamBubbleModel::SetTitle() {
@@ -1186,8 +1187,8 @@
 }
 
 void ContentSettingMediaStreamBubbleModel::SetRadioGroup() {
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   GURL url = content_settings->media_stream_access_origin();
   RadioGroup radio_group;
   radio_group.url = url;
@@ -1197,8 +1198,8 @@
   DCHECK(CameraAccessed() || MicrophoneAccessed());
   int radio_allow_label_id = 0;
   int radio_block_label_id = 0;
-  if (state_ & (TabSpecificContentSettings::MICROPHONE_BLOCKED |
-                TabSpecificContentSettings::CAMERA_BLOCKED)) {
+  if (state_ & (PageSpecificContentSettings::MICROPHONE_BLOCKED |
+                PageSpecificContentSettings::CAMERA_BLOCKED)) {
     if (content::IsOriginSecure(url)) {
       radio_item_setting_[0] = CONTENT_SETTING_ALLOW;
       radio_allow_label_id = IDS_BLOCKED_MEDIASTREAM_CAMERA_ALLOW;
@@ -1265,8 +1266,8 @@
 
 void ContentSettingMediaStreamBubbleModel::UpdateSettings(
     ContentSetting setting) {
-  TabSpecificContentSettings* tab_content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* page_content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   // The same urls must be used as in other places (e.g. the infobar) in
   // order to override the existing rule. Otherwise a new rule is created.
   // TODO(markusheintz): Extract to a helper so that there is only a single
@@ -1276,21 +1277,21 @@
   if (MicrophoneAccessed()) {
     permissions::PermissionUmaUtil::ScopedRevocationReporter
         scoped_revocation_reporter(
-            GetProfile(), tab_content_settings->media_stream_access_origin(),
+            GetProfile(), page_content_settings->media_stream_access_origin(),
             GURL(), ContentSettingsType::MEDIASTREAM_MIC,
             permissions::PermissionSourceUI::PAGE_ACTION);
     map->SetContentSettingDefaultScope(
-        tab_content_settings->media_stream_access_origin(), GURL(),
+        page_content_settings->media_stream_access_origin(), GURL(),
         ContentSettingsType::MEDIASTREAM_MIC, std::string(), setting);
   }
   if (CameraAccessed()) {
     permissions::PermissionUmaUtil::ScopedRevocationReporter
         scoped_revocation_reporter(
-            GetProfile(), tab_content_settings->media_stream_access_origin(),
+            GetProfile(), page_content_settings->media_stream_access_origin(),
             GURL(), ContentSettingsType::MEDIASTREAM_CAMERA,
             permissions::PermissionSourceUI::PAGE_ACTION);
     map->SetContentSettingDefaultScope(
-        tab_content_settings->media_stream_access_origin(), GURL(),
+        page_content_settings->media_stream_access_origin(), GURL(),
         ContentSettingsType::MEDIASTREAM_CAMERA, std::string(), setting);
   }
 }
@@ -1367,8 +1368,8 @@
 }
 
 void ContentSettingMediaStreamBubbleModel::SetMediaMenus() {
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   const std::string& requested_microphone =
       content_settings->media_stream_requested_audio_device();
   const std::string& requested_camera =
@@ -1437,8 +1438,8 @@
 }
 
 void ContentSettingMediaStreamBubbleModel::SetCustomLink() {
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   if (content_settings->IsMicrophoneCameraStateChanged()) {
     set_custom_link(
         l10n_util::GetStringUTF16(IDS_MEDIASTREAM_SETTING_CHANGED_MESSAGE));
diff --git a/chrome/browser/ui/content_settings/content_setting_bubble_model.h b/chrome/browser/ui/content_settings/content_setting_bubble_model.h
index b590e11..afb07a2 100644
--- a/chrome/browser/ui/content_settings/content_setting_bubble_model.h
+++ b/chrome/browser/ui/content_settings/content_setting_bubble_model.h
@@ -21,7 +21,7 @@
 #include "chrome/browser/ui/blocked_content/framebust_block_tab_helper.h"
 #include "chrome/common/custom_handlers/protocol_handler.h"
 #include "components/blocked_content/url_list_manager.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/common/content_settings.h"
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "third_party/blink/public/common/mediastream/media_stream_request.h"
@@ -406,7 +406,7 @@
   // buttons.
   ContentSetting radio_item_setting_[2];
   // The state of the microphone and camera access.
-  content_settings::TabSpecificContentSettings::MicrophoneCameraState state_;
+  content_settings::PageSpecificContentSettings::MicrophoneCameraState state_;
 
   DISALLOW_COPY_AND_ASSIGN(ContentSettingMediaStreamBubbleModel);
 };
diff --git a/chrome/browser/ui/content_settings/content_setting_bubble_model_browsertest.cc b/chrome/browser/ui/content_settings/content_setting_bubble_model_browsertest.cc
index 324a40b..d128e537 100644
--- a/chrome/browser/ui/content_settings/content_setting_bubble_model_browsertest.cc
+++ b/chrome/browser/ui/content_settings/content_setting_bubble_model_browsertest.cc
@@ -22,7 +22,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "components/network_session_configurator/common/network_switches.h"
 #include "components/url_formatter/elide_url.h"
@@ -37,7 +37,7 @@
 #include "third_party/blink/public/common/features.h"
 #include "ui/events/event_constants.h"
 
-using content_settings::TabSpecificContentSettings;
+using content_settings::PageSpecificContentSettings;
 
 class ContentSettingBubbleModelMixedScriptTest : public InProcessBrowserTest {
  public:
@@ -56,8 +56,8 @@
     ASSERT_TRUE(https_server_->Start());
   }
 
-  TabSpecificContentSettings* GetActiveTabSpecificContentSettings() {
-    return TabSpecificContentSettings::GetForFrame(
+  PageSpecificContentSettings* GetActivePageSpecificContentSettings() {
+    return PageSpecificContentSettings::GetForFrame(
         browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame());
   }
 
@@ -75,7 +75,7 @@
   // the title string.
   ui_test_utils::NavigateToURL(browser(), url);
 
-  EXPECT_TRUE(GetActiveTabSpecificContentSettings()->IsContentBlocked(
+  EXPECT_TRUE(GetActivePageSpecificContentSettings()->IsContentBlocked(
       ContentSettingsType::MIXEDSCRIPT));
 
   // Emulate link clicking on the mixed script bubble.
@@ -91,7 +91,7 @@
       browser()->tab_strip_model()->GetActiveWebContents());
   observer.Wait();
 
-  EXPECT_FALSE(GetActiveTabSpecificContentSettings()->IsContentBlocked(
+  EXPECT_FALSE(GetActivePageSpecificContentSettings()->IsContentBlocked(
       ContentSettingsType::MIXEDSCRIPT));
 }
 
@@ -117,7 +117,7 @@
   // the title string.
   ui_test_utils::NavigateToURL(browser(), url);
 
-  EXPECT_TRUE(GetActiveTabSpecificContentSettings()->IsContentBlocked(
+  EXPECT_TRUE(GetActivePageSpecificContentSettings()->IsContentBlocked(
       ContentSettingsType::MIXEDSCRIPT));
 
   // Emulate link clicking on the mixed script bubble.
@@ -133,7 +133,7 @@
       browser()->tab_strip_model()->GetActiveWebContents());
   observer.Wait();
 
-  EXPECT_FALSE(GetActiveTabSpecificContentSettings()->IsContentBlocked(
+  EXPECT_FALSE(GetActivePageSpecificContentSettings()->IsContentBlocked(
       ContentSettingsType::MIXEDSCRIPT));
 }
 
@@ -149,14 +149,14 @@
   // Blink does not ask the browser to handle mixed content in the case
   // of active subresources in an iframe, so the content type should not
   // be marked as blocked.
-  EXPECT_FALSE(GetActiveTabSpecificContentSettings()->IsContentBlocked(
+  EXPECT_FALSE(GetActivePageSpecificContentSettings()->IsContentBlocked(
       ContentSettingsType::MIXEDSCRIPT));
 }
 
 class ContentSettingBubbleModelMediaStreamTest : public InProcessBrowserTest {
  public:
   void ManageMediaStreamSettings(
-      TabSpecificContentSettings::MicrophoneCameraState state) {
+      PageSpecificContentSettings::MicrophoneCameraState state) {
     content::WebContents* original_tab = OpenTab();
     std::unique_ptr<ContentSettingBubbleModel> bubble = ShowBubble(state);
 
@@ -169,11 +169,11 @@
   }
 
   std::unique_ptr<ContentSettingBubbleModel> ShowBubble(
-      TabSpecificContentSettings::MicrophoneCameraState state) {
+      PageSpecificContentSettings::MicrophoneCameraState state) {
     content::WebContents* web_contents = GetActiveTab();
 
     // Create a bubble with the given camera and microphone access state.
-    TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
+    PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame())
         ->OnMediaStreamPermissionSet(web_contents->GetLastCommittedURL(), state,
                                      std::string(), std::string(),
                                      std::string(), std::string());
@@ -217,19 +217,19 @@
   // active tab loads the correct internal url.
 
   // The microphone bubble links to microphone exceptions.
-  ManageMediaStreamSettings(TabSpecificContentSettings::MICROPHONE_ACCESSED);
+  ManageMediaStreamSettings(PageSpecificContentSettings::MICROPHONE_ACCESSED);
   EXPECT_EQ(GURL("chrome://settings/contentExceptions#media-stream-mic"),
             GetActiveTab()->GetLastCommittedURL());
 
   // The bubble for both media devices links to the the first section of the
   // default media content settings, which is the microphone section.
-  ManageMediaStreamSettings(TabSpecificContentSettings::MICROPHONE_ACCESSED |
-                            TabSpecificContentSettings::CAMERA_ACCESSED);
+  ManageMediaStreamSettings(PageSpecificContentSettings::MICROPHONE_ACCESSED |
+                            PageSpecificContentSettings::CAMERA_ACCESSED);
   EXPECT_EQ(GURL("chrome://settings/content#media-stream-mic"),
             GetActiveTab()->GetLastCommittedURL());
 
   // The camera bubble links to camera exceptions.
-  ManageMediaStreamSettings(TabSpecificContentSettings::CAMERA_ACCESSED);
+  ManageMediaStreamSettings(PageSpecificContentSettings::CAMERA_ACCESSED);
   EXPECT_EQ(GURL("chrome://settings/contentExceptions#media-stream-camera"),
             GetActiveTab()->GetLastCommittedURL());
 }
@@ -249,8 +249,8 @@
 
   // The mic & camera bubble content does not include camera PTZ.
   std::unique_ptr<ContentSettingBubbleModel> mic_and_camera_bubble =
-      ShowBubble(TabSpecificContentSettings::MICROPHONE_ACCESSED |
-                 TabSpecificContentSettings::CAMERA_ACCESSED);
+      ShowBubble(PageSpecificContentSettings::MICROPHONE_ACCESSED |
+                 PageSpecificContentSettings::CAMERA_ACCESSED);
   EXPECT_EQ(mic_and_camera_bubble->bubble_content().radio_group.radio_items[0],
             l10n_util::GetStringFUTF16(
                 IDS_ALLOWED_MEDIASTREAM_MIC_AND_CAMERA_NO_ACTION,
@@ -258,7 +258,7 @@
 
   // The camera bubble content does not include camera PTZ.
   std::unique_ptr<ContentSettingBubbleModel> camera_bubble =
-      ShowBubble(TabSpecificContentSettings::CAMERA_ACCESSED);
+      ShowBubble(PageSpecificContentSettings::CAMERA_ACCESSED);
   EXPECT_EQ(camera_bubble->bubble_content().radio_group.radio_items[0],
             l10n_util::GetStringFUTF16(
                 IDS_ALLOWED_MEDIASTREAM_CAMERA_NO_ACTION,
@@ -272,8 +272,8 @@
 
   // The mic & camera bubble content includes camera PTZ.
   std::unique_ptr<ContentSettingBubbleModel> mic_and_camera_ptz_bubble =
-      ShowBubble(TabSpecificContentSettings::MICROPHONE_ACCESSED |
-                 TabSpecificContentSettings::CAMERA_ACCESSED);
+      ShowBubble(PageSpecificContentSettings::MICROPHONE_ACCESSED |
+                 PageSpecificContentSettings::CAMERA_ACCESSED);
   EXPECT_EQ(
       mic_and_camera_ptz_bubble->bubble_content().radio_group.radio_items[0],
       l10n_util::GetStringFUTF16(
@@ -282,7 +282,7 @@
 
   // The camera bubble content includes camera PTZ.
   std::unique_ptr<ContentSettingBubbleModel> camera_ptz_bubble =
-      ShowBubble(TabSpecificContentSettings::CAMERA_ACCESSED);
+      ShowBubble(PageSpecificContentSettings::CAMERA_ACCESSED);
   EXPECT_EQ(camera_ptz_bubble->bubble_content().radio_group.radio_items[0],
             l10n_util::GetStringFUTF16(
                 IDS_ALLOWED_CAMERA_PAN_TILT_ZOOM_NO_ACTION,
@@ -382,7 +382,7 @@
   // Load a page with mixed content and verify that mixed content didn't get
   // executed.
   ui_test_utils::NavigateToURL(browser(), main_url);
-  EXPECT_TRUE(GetActiveTabSpecificContentSettings()->IsContentBlocked(
+  EXPECT_TRUE(GetActivePageSpecificContentSettings()->IsContentBlocked(
       ContentSettingsType::MIXEDSCRIPT));
 
   std::string title;
@@ -403,7 +403,7 @@
 
   // Wait for reload and verify that mixed content is allowed.
   observer.Wait();
-  EXPECT_FALSE(GetActiveTabSpecificContentSettings()->IsContentBlocked(
+  EXPECT_FALSE(GetActivePageSpecificContentSettings()->IsContentBlocked(
       ContentSettingsType::MIXEDSCRIPT));
 
   // Ensure that the script actually executed by checking the title of the
diff --git a/chrome/browser/ui/content_settings/content_setting_bubble_model_unittest.cc b/chrome/browser/ui/content_settings/content_setting_bubble_model_unittest.cc
index 27bbd02..2182ebe 100644
--- a/chrome/browser/ui/content_settings/content_setting_bubble_model_unittest.cc
+++ b/chrome/browser/ui/content_settings/content_setting_bubble_model_unittest.cc
@@ -11,7 +11,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
-#include "chrome/browser/content_settings/tab_specific_content_settings_delegate.h"
+#include "chrome/browser/content_settings/page_specific_content_settings_delegate.h"
 #include "chrome/browser/custom_handlers/protocol_handler_registry.h"
 #include "chrome/browser/custom_handlers/test_protocol_handler_registry_delegate.h"
 #include "chrome/browser/infobars/infobar_service.h"
@@ -31,7 +31,7 @@
 #include "chrome/test/base/testing_profile.h"
 #include "components/blocked_content/popup_blocker.h"
 #include "components/blocked_content/popup_blocker_tab_helper.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/content_settings/core/common/content_settings.h"
 #include "components/infobars/core/infobar_delegate.h"
@@ -47,16 +47,16 @@
 #include "ui/base/l10n/l10n_util.h"
 
 using content::WebContentsTester;
-using content_settings::TabSpecificContentSettings;
+using content_settings::PageSpecificContentSettings;
 
 class ContentSettingBubbleModelTest : public ChromeRenderViewHostTestHarness {
  protected:
   void SetUp() override {
     ChromeRenderViewHostTestHarness::SetUp();
 
-    TabSpecificContentSettings::CreateForWebContents(
+    PageSpecificContentSettings::CreateForWebContents(
         web_contents(),
-        std::make_unique<chrome::TabSpecificContentSettingsDelegate>(
+        std::make_unique<chrome::PageSpecificContentSettingsDelegate>(
             web_contents()));
     InfoBarService::CreateForWebContents(web_contents());
   }
@@ -93,8 +93,8 @@
 TEST_F(ContentSettingBubbleModelTest, ImageRadios) {
   WebContentsTester::For(web_contents())->
       NavigateAndCommit(GURL("https://www.example.com"));
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   content_settings->OnContentBlocked(ContentSettingsType::IMAGES);
 
   std::unique_ptr<ContentSettingBubbleModel> content_setting_bubble_model(
@@ -112,8 +112,8 @@
 TEST_F(ContentSettingBubbleModelTest, Cookies) {
   WebContentsTester::For(web_contents())->
       NavigateAndCommit(GURL("https://www.example.com"));
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   content_settings->OnContentBlocked(ContentSettingsType::COOKIES);
 
   std::unique_ptr<ContentSettingBubbleModel> content_setting_bubble_model(
@@ -131,7 +131,7 @@
   WebContentsTester::For(web_contents())
       ->NavigateAndCommit(GURL("https://www.example.com"));
   content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   content_settings->OnContentAllowed(ContentSettingsType::COOKIES);
   content_setting_bubble_model =
       ContentSettingBubbleModel::CreateContentSettingBubbleModel(
@@ -159,13 +159,13 @@
   MediaCaptureDevicesDispatcher::GetInstance()->
       DisableDeviceEnumerationForTesting();
 
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   std::string request_host = "google.com";
   GURL security_origin("http://" + request_host);
-  TabSpecificContentSettings::MicrophoneCameraState microphone_camera_state =
-      TabSpecificContentSettings::MICROPHONE_ACCESSED |
-      TabSpecificContentSettings::CAMERA_ACCESSED;
+  PageSpecificContentSettings::MicrophoneCameraState microphone_camera_state =
+      PageSpecificContentSettings::MICROPHONE_ACCESSED |
+      PageSpecificContentSettings::CAMERA_ACCESSED;
   content_settings->OnMediaStreamPermissionSet(security_origin,
                                                microphone_camera_state,
                                                GetDefaultAudioDevice(),
@@ -215,13 +215,13 @@
       url, GURL(), ContentSettingsType::MEDIASTREAM_CAMERA, std::string(),
       setting);
 
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
-  TabSpecificContentSettings::MicrophoneCameraState microphone_camera_state =
-      TabSpecificContentSettings::MICROPHONE_ACCESSED |
-      TabSpecificContentSettings::MICROPHONE_BLOCKED |
-      TabSpecificContentSettings::CAMERA_ACCESSED |
-      TabSpecificContentSettings::CAMERA_BLOCKED;
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings::MicrophoneCameraState microphone_camera_state =
+      PageSpecificContentSettings::MICROPHONE_ACCESSED |
+      PageSpecificContentSettings::MICROPHONE_BLOCKED |
+      PageSpecificContentSettings::CAMERA_ACCESSED |
+      PageSpecificContentSettings::CAMERA_BLOCKED;
   content_settings->OnMediaStreamPermissionSet(url,
                                                microphone_camera_state,
                                                GetDefaultAudioDevice(),
@@ -282,11 +282,11 @@
       url, GURL(), ContentSettingsType::MEDIASTREAM_MIC, std::string(),
       setting);
 
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
-  TabSpecificContentSettings::MicrophoneCameraState microphone_camera_state =
-      TabSpecificContentSettings::MICROPHONE_ACCESSED |
-      TabSpecificContentSettings::MICROPHONE_BLOCKED;
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings::MicrophoneCameraState microphone_camera_state =
+      PageSpecificContentSettings::MICROPHONE_ACCESSED |
+      PageSpecificContentSettings::MICROPHONE_BLOCKED;
   content_settings->OnMediaStreamPermissionSet(url,
                                                microphone_camera_state,
                                                GetDefaultAudioDevice(),
@@ -374,11 +374,11 @@
   MediaCaptureDevicesDispatcher::GetInstance()->SetTestAudioCaptureDevices(
       audio_devices);
 
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
-  TabSpecificContentSettings::MicrophoneCameraState microphone_camera_state =
-      TabSpecificContentSettings::MICROPHONE_ACCESSED |
-      TabSpecificContentSettings::MICROPHONE_BLOCKED;
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings::MicrophoneCameraState microphone_camera_state =
+      PageSpecificContentSettings::MICROPHONE_ACCESSED |
+      PageSpecificContentSettings::MICROPHONE_BLOCKED;
   content_settings->OnMediaStreamPermissionSet(url,
                                                microphone_camera_state,
                                                GetDefaultAudioDevice(),
@@ -437,7 +437,7 @@
       indicator->RegisterMediaStream(web_contents(), audio_devices);
   media_stream_ui->OnStarted(base::OnceClosure(),
                              content::MediaStreamUI::SourceCallback());
-  microphone_camera_state &= ~TabSpecificContentSettings::MICROPHONE_BLOCKED;
+  microphone_camera_state &= ~PageSpecificContentSettings::MICROPHONE_BLOCKED;
   content_settings->OnMediaStreamPermissionSet(url,
                                                microphone_camera_state,
                                                GetDefaultAudioDevice(),
@@ -485,7 +485,7 @@
   }
 
   // Simulate that yet another audio stream capture request was initiated.
-  microphone_camera_state |= TabSpecificContentSettings::MICROPHONE_BLOCKED;
+  microphone_camera_state |= PageSpecificContentSettings::MICROPHONE_BLOCKED;
   content_settings->OnMediaStreamPermissionSet(url,
                                                microphone_camera_state,
                                                GetDefaultAudioDevice(),
@@ -520,12 +520,12 @@
   MediaCaptureDevicesDispatcher::GetInstance()->
       DisableDeviceEnumerationForTesting();
 
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   std::string request_host = "google.com";
   GURL security_origin("http://" + request_host);
-  TabSpecificContentSettings::MicrophoneCameraState microphone_camera_state =
-      TabSpecificContentSettings::MICROPHONE_ACCESSED;
+  PageSpecificContentSettings::MicrophoneCameraState microphone_camera_state =
+      PageSpecificContentSettings::MICROPHONE_ACCESSED;
   content_settings->OnMediaStreamPermissionSet(security_origin,
                                                microphone_camera_state,
                                                GetDefaultAudioDevice(),
@@ -557,7 +557,7 @@
             bubble_content.media_menus.begin()->first);
 
   // Change the microphone access.
-  microphone_camera_state |= TabSpecificContentSettings::MICROPHONE_BLOCKED;
+  microphone_camera_state |= PageSpecificContentSettings::MICROPHONE_BLOCKED;
   content_settings->OnMediaStreamPermissionSet(security_origin,
                                                microphone_camera_state,
                                                GetDefaultAudioDevice(),
@@ -593,12 +593,12 @@
   MediaCaptureDevicesDispatcher::GetInstance()->
       DisableDeviceEnumerationForTesting();
 
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   std::string request_host = "google.com";
   GURL security_origin("http://" + request_host);
-  TabSpecificContentSettings::MicrophoneCameraState microphone_camera_state =
-      TabSpecificContentSettings::CAMERA_ACCESSED;
+  PageSpecificContentSettings::MicrophoneCameraState microphone_camera_state =
+      PageSpecificContentSettings::CAMERA_ACCESSED;
   content_settings->OnMediaStreamPermissionSet(security_origin,
                                                microphone_camera_state,
                                                std::string(),
@@ -630,7 +630,7 @@
             bubble_content.media_menus.begin()->first);
 
   // Change the camera access.
-  microphone_camera_state |= TabSpecificContentSettings::CAMERA_BLOCKED;
+  microphone_camera_state |= PageSpecificContentSettings::CAMERA_BLOCKED;
   content_settings->OnMediaStreamPermissionSet(security_origin,
                                                microphone_camera_state,
                                                std::string(),
@@ -667,14 +667,14 @@
   MediaCaptureDevicesDispatcher::GetInstance()->
       DisableDeviceEnumerationForTesting();
 
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   std::string request_host = "google.com";
   GURL security_origin("http://" + request_host);
 
   // Firstly, add microphone access.
-  TabSpecificContentSettings::MicrophoneCameraState microphone_camera_state =
-      TabSpecificContentSettings::MICROPHONE_ACCESSED;
+  PageSpecificContentSettings::MicrophoneCameraState microphone_camera_state =
+      PageSpecificContentSettings::MICROPHONE_ACCESSED;
   content_settings->OnMediaStreamPermissionSet(security_origin,
                                                microphone_camera_state,
                                                GetDefaultAudioDevice(),
@@ -703,7 +703,7 @@
             bubble_content.media_menus.begin()->first);
 
   // Then add camera access.
-  microphone_camera_state |= TabSpecificContentSettings::CAMERA_ACCESSED;
+  microphone_camera_state |= PageSpecificContentSettings::CAMERA_ACCESSED;
   content_settings->OnMediaStreamPermissionSet(security_origin,
                                                microphone_camera_state,
                                                GetDefaultAudioDevice(),
@@ -734,8 +734,8 @@
 TEST_F(ContentSettingBubbleModelTest, Plugins) {
   WebContentsTester::For(web_contents())->
       NavigateAndCommit(GURL("https://www.example.com"));
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   const base::string16 plugin_name = base::ASCIIToUTF16("plugin_name");
 
   content_settings->OnContentBlocked(ContentSettingsType::PLUGINS);
@@ -757,8 +757,8 @@
 TEST_F(ContentSettingBubbleModelTest, PepperBroker) {
   WebContentsTester::For(web_contents())->
       NavigateAndCommit(GURL("https://www.example.com"));
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   content_settings->OnContentBlocked(ContentSettingsType::PPAPI_BROKER);
 
   std::unique_ptr<ContentSettingBubbleModel> content_setting_bubble_model(
@@ -778,7 +778,7 @@
   WebContentsTester::For(web_contents())
       ->NavigateAndCommit(GURL("https://www.example.com"));
   content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   content_settings->OnContentAllowed(ContentSettingsType::PPAPI_BROKER);
   content_setting_bubble_model =
       ContentSettingBubbleModel::CreateContentSettingBubbleModel(
@@ -801,8 +801,8 @@
   const GURL frame2_url("http://host2.example:999/");
 
   NavigateAndCommit(page_url);
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
 
   // One permitted frame, but not in the content map: requires reload.
   content_settings->OnGeolocationPermissionSet(frame1_url, true);
@@ -847,8 +847,8 @@
   }
 
   NavigateAndCommit(origin_to_embargo);
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   content_settings->OnGeolocationPermissionSet(origin_to_embargo, false);
 
   // |origin_to_embargo| is not blocked or embargoed. Verify no clear link
@@ -896,7 +896,7 @@
 TEST_F(ContentSettingBubbleModelTest, FileURL) {
   std::string file_url("file:///tmp/test.html");
   NavigateAndCommit(GURL(file_url));
-  TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame())
+  PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame())
       ->OnContentBlocked(ContentSettingsType::IMAGES);
   std::unique_ptr<ContentSettingBubbleModel> content_setting_bubble_model(
       ContentSettingBubbleModel::CreateContentSettingBubbleModel(
@@ -909,7 +909,7 @@
 TEST_F(ContentSettingBubbleModelTest, RegisterProtocolHandler) {
   const GURL page_url("http://toplevel.example/");
   NavigateAndCommit(page_url);
-  chrome::TabSpecificContentSettingsDelegate::FromWebContents(web_contents())
+  chrome::PageSpecificContentSettingsDelegate::FromWebContents(web_contents())
       ->set_pending_protocol_handler(ProtocolHandler::CreateProtocolHandler(
           "mailto", GURL("http://www.toplevel.example/")));
 
@@ -935,7 +935,7 @@
   const GURL page_url("http://toplevel.example/");
   NavigateAndCommit(page_url);
   auto* content_settings =
-      chrome::TabSpecificContentSettingsDelegate::FromWebContents(
+      chrome::PageSpecificContentSettingsDelegate::FromWebContents(
           web_contents());
   ProtocolHandler test_handler = ProtocolHandler::CreateProtocolHandler(
       "mailto", GURL("http://www.toplevel.example/"));
@@ -1002,7 +1002,7 @@
   const GURL page_url("http://toplevel.example/");
   NavigateAndCommit(page_url);
   auto* content_settings =
-      chrome::TabSpecificContentSettingsDelegate::FromWebContents(
+      chrome::PageSpecificContentSettingsDelegate::FromWebContents(
           web_contents());
   ProtocolHandler test_handler = ProtocolHandler::CreateProtocolHandler(
       "mailto", GURL("http://www.toplevel.example/"));
@@ -1056,8 +1056,8 @@
 
   WebContentsTester::For(web_contents())
       ->NavigateAndCommit(GURL("https://www.example.com"));
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   HostContentSettingsMap* settings_map =
       HostContentSettingsMapFactory::GetForProfile(profile());
 
@@ -1135,7 +1135,7 @@
   WebContentsTester::For(web_contents())
       ->NavigateAndCommit(GURL("https://www.example.com"));
   content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
 
   // Go from block by default to allow by default to block by default.
   {
@@ -1211,7 +1211,7 @@
   WebContentsTester::For(web_contents())
       ->NavigateAndCommit(GURL("https://www.example.com"));
   content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
 
   // Block by default but allow a specific site.
   {
@@ -1244,7 +1244,7 @@
   WebContentsTester::For(web_contents())
       ->NavigateAndCommit(GURL("https://www.example.com"));
   content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   // Clear site-specific exceptions.
   settings_map->ClearSettingsForOneType(ContentSettingsType::SENSORS);
 
@@ -1280,8 +1280,8 @@
 TEST_F(ContentSettingBubbleModelTest, PopupBubbleModelListItems) {
   const GURL url("https://www.example.test/");
   WebContentsTester::For(web_contents())->NavigateAndCommit(url);
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   content_settings->OnContentBlocked(ContentSettingsType::POPUPS);
 
   blocked_content::PopupBlockerTabHelper::CreateForWebContents(web_contents());
@@ -1314,8 +1314,8 @@
   WebContentsTester::For(web_contents())->
       NavigateAndCommit(GURL("https://www.example.com"));
 
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   content_settings->OnContentBlocked(ContentSettingsType::COOKIES);
 
   std::unique_ptr<ContentSettingBubbleModel> content_setting_bubble_model(
@@ -1331,8 +1331,8 @@
   WebContentsTester::For(web_contents())->
       NavigateAndCommit(GURL("about:blank"));
 
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   content_settings->OnContentBlocked(ContentSettingsType::COOKIES);
 
   std::unique_ptr<ContentSettingBubbleModel> content_setting_bubble_model(
diff --git a/chrome/browser/ui/content_settings/content_setting_image_model.cc b/chrome/browser/ui/content_settings/content_setting_image_model.cc
index 84a6366..6d75d3d 100644
--- a/chrome/browser/ui/content_settings/content_setting_image_model.cc
+++ b/chrome/browser/ui/content_settings/content_setting_image_model.cc
@@ -16,7 +16,7 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/content_settings/cookie_settings_factory.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
-#include "chrome/browser/content_settings/tab_specific_content_settings_delegate.h"
+#include "chrome/browser/content_settings/page_specific_content_settings_delegate.h"
 #include "chrome/browser/download/download_request_limiter.h"
 #include "chrome/browser/permissions/quiet_notification_permission_ui_config.h"
 #include "chrome/browser/permissions/quiet_notification_permission_ui_state.h"
@@ -28,7 +28,7 @@
 #include "chrome/grit/chromium_strings.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/content_settings/browser/content_settings_usages_state.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/browser/cookie_settings.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/content_settings/core/common/content_settings_types.h"
@@ -52,7 +52,7 @@
 #endif
 
 using content::WebContents;
-using content_settings::TabSpecificContentSettings;
+using content_settings::PageSpecificContentSettings;
 
 // The image models hierarchy:
 //
@@ -156,7 +156,7 @@
       WebContents* web_contents) override;
 
  private:
-  TabSpecificContentSettings::MicrophoneCameraState state_;
+  PageSpecificContentSettings::MicrophoneCameraState state_;
 
   DISALLOW_COPY_AND_ASSIGN(ContentSettingMediaImageModel);
 };
@@ -419,8 +419,8 @@
 
   // If a content type is blocked by default and was accessed, display the
   // content blocked page action.
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame());
   if (!content_settings)
     return false;
 
@@ -483,8 +483,8 @@
 
 bool ContentSettingGeolocationImageModel::UpdateAndGetVisibility(
     WebContents* web_contents) {
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame());
   if (!content_settings)
     return false;
   const ContentSettingsUsagesState& usages_state =
@@ -518,7 +518,8 @@
 bool ContentSettingRPHImageModel::UpdateAndGetVisibility(
     WebContents* web_contents) {
   auto* content_settings_delegate =
-      chrome::TabSpecificContentSettingsDelegate::FromWebContents(web_contents);
+      chrome::PageSpecificContentSettingsDelegate::FromWebContents(
+          web_contents);
   if (!content_settings_delegate)
     return false;
   if (content_settings_delegate->pending_protocol_handler().IsEmpty())
@@ -535,8 +536,8 @@
 
 bool ContentSettingMIDISysExImageModel::UpdateAndGetVisibility(
     WebContents* web_contents) {
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame());
   if (!content_settings)
     return false;
   const ContentSettingsUsagesState& usages_state =
@@ -602,8 +603,8 @@
 
 bool ContentSettingClipboardReadWriteImageModel::UpdateAndGetVisibility(
     WebContents* web_contents) {
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame());
   if (!content_settings)
     return false;
   ContentSettingsType content_type = ContentSettingsType::CLIPBOARD_READ_WRITE;
@@ -627,15 +628,15 @@
 bool ContentSettingMediaImageModel::UpdateAndGetVisibility(
     WebContents* web_contents) {
   set_should_auto_open_bubble(false);
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame());
   if (!content_settings)
     return false;
   state_ = content_settings->GetMicrophoneCameraState();
 
   // If neither the microphone nor the camera stream was accessed then no icon
   // is displayed in the omnibox.
-  if (state_ == TabSpecificContentSettings::MICROPHONE_CAMERA_NOT_ACCESSED)
+  if (state_ == PageSpecificContentSettings::MICROPHONE_CAMERA_NOT_ACCESSED)
     return false;
 
 #if defined(OS_MAC)
@@ -735,19 +736,19 @@
 }
 
 bool ContentSettingMediaImageModel::IsMicAccessed() {
-  return ((state_ & TabSpecificContentSettings::MICROPHONE_ACCESSED) != 0);
+  return ((state_ & PageSpecificContentSettings::MICROPHONE_ACCESSED) != 0);
 }
 
 bool ContentSettingMediaImageModel::IsCamAccessed() {
-  return ((state_ & TabSpecificContentSettings::CAMERA_ACCESSED) != 0);
+  return ((state_ & PageSpecificContentSettings::CAMERA_ACCESSED) != 0);
 }
 
 bool ContentSettingMediaImageModel::IsMicBlockedOnSiteLevel() {
-  return ((state_ & TabSpecificContentSettings::MICROPHONE_BLOCKED) != 0);
+  return ((state_ & PageSpecificContentSettings::MICROPHONE_BLOCKED) != 0);
 }
 
 bool ContentSettingMediaImageModel::IsCameraBlockedOnSiteLevel() {
-  return ((state_ & TabSpecificContentSettings::CAMERA_BLOCKED) != 0);
+  return ((state_ & PageSpecificContentSettings::CAMERA_BLOCKED) != 0);
 }
 
 #if defined(OS_MAC)
@@ -820,7 +821,7 @@
 bool ContentSettingSensorsImageModel::UpdateAndGetVisibility(
     WebContents* web_contents) {
   auto* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame());
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame());
   if (!content_settings)
     return false;
 
@@ -860,8 +861,8 @@
 
 bool ContentSettingPopupImageModel::UpdateAndGetVisibility(
     WebContents* web_contents) {
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents->GetMainFrame());
   if (!content_settings || !content_settings->IsContentBlocked(content_type()))
     return false;
   set_icon(kWebIcon, vector_icons::kBlockedBadgeIcon);
diff --git a/chrome/browser/ui/content_settings/content_setting_image_model_browsertest.cc b/chrome/browser/ui/content_settings/content_setting_image_model_browsertest.cc
index 914d8add..df2b343 100644
--- a/chrome/browser/ui/content_settings/content_setting_image_model_browsertest.cc
+++ b/chrome/browser/ui/content_settings/content_setting_image_model_browsertest.cc
@@ -27,8 +27,8 @@
 IN_PROC_BROWSER_TEST_F(ContentSettingImageModelBrowserTest, CreateBubbleModel) {
   WebContents* web_contents =
       browser()->tab_strip_model()->GetActiveWebContents();
-  content_settings::TabSpecificContentSettings* content_settings =
-      content_settings::TabSpecificContentSettings::GetForFrame(
+  content_settings::PageSpecificContentSettings* content_settings =
+      content_settings::PageSpecificContentSettings::GetForFrame(
           web_contents->GetMainFrame());
   content_settings->BlockAllContentForTesting();
 
diff --git a/chrome/browser/ui/content_settings/content_setting_image_model_unittest.cc b/chrome/browser/ui/content_settings/content_setting_image_model_unittest.cc
index d871b538..58f7534 100644
--- a/chrome/browser/ui/content_settings/content_setting_image_model_unittest.cc
+++ b/chrome/browser/ui/content_settings/content_setting_image_model_unittest.cc
@@ -14,7 +14,7 @@
 #include "build/build_config.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
-#include "chrome/browser/content_settings/tab_specific_content_settings_delegate.h"
+#include "chrome/browser/content_settings/page_specific_content_settings_delegate.h"
 #include "chrome/browser/permissions/quiet_notification_permission_ui_state.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
@@ -24,7 +24,7 @@
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "chrome/test/base/testing_profile.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/content_settings/core/common/pref_names.h"
 #include "components/permissions/features.h"
@@ -47,7 +47,7 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/color_palette.h"
 
-using content_settings::TabSpecificContentSettings;
+using content_settings::PageSpecificContentSettings;
 
 namespace {
 
@@ -114,12 +114,12 @@
 }
 
 TEST_F(ContentSettingImageModelTest, Update) {
-  TabSpecificContentSettings::CreateForWebContents(
+  PageSpecificContentSettings::CreateForWebContents(
       web_contents(),
-      std::make_unique<chrome::TabSpecificContentSettingsDelegate>(
+      std::make_unique<chrome::PageSpecificContentSettingsDelegate>(
           web_contents()));
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   auto content_setting_image_model =
       ContentSettingImageModel::CreateForContentType(
           ContentSettingImageModel::ImageType::IMAGES);
@@ -135,9 +135,9 @@
 }
 
 TEST_F(ContentSettingImageModelTest, RPHUpdate) {
-  TabSpecificContentSettings::CreateForWebContents(
+  PageSpecificContentSettings::CreateForWebContents(
       web_contents(),
-      std::make_unique<chrome::TabSpecificContentSettingsDelegate>(
+      std::make_unique<chrome::PageSpecificContentSettingsDelegate>(
           web_contents()));
   auto content_setting_image_model =
       ContentSettingImageModel::CreateForContentType(
@@ -145,7 +145,7 @@
   content_setting_image_model->Update(web_contents());
   EXPECT_FALSE(content_setting_image_model->is_visible());
 
-  chrome::TabSpecificContentSettingsDelegate::FromWebContents(web_contents())
+  chrome::PageSpecificContentSettingsDelegate::FromWebContents(web_contents())
       ->set_pending_protocol_handler(ProtocolHandler::CreateProtocolHandler(
           "mailto", GURL("http://www.google.com/")));
   content_setting_image_model->Update(web_contents());
@@ -153,9 +153,9 @@
 }
 
 TEST_F(ContentSettingImageModelTest, CookieAccessed) {
-  TabSpecificContentSettings::CreateForWebContents(
+  PageSpecificContentSettings::CreateForWebContents(
       web_contents(),
-      std::make_unique<chrome::TabSpecificContentSettingsDelegate>(
+      std::make_unique<chrome::PageSpecificContentSettingsDelegate>(
           web_contents()));
   HostContentSettingsMapFactory::GetForProfile(profile())
       ->SetDefaultContentSetting(ContentSettingsType::COOKIES,
@@ -170,7 +170,7 @@
   std::unique_ptr<net::CanonicalCookie> cookie(net::CanonicalCookie::Create(
       origin, "A=B", base::Time::Now(), base::nullopt /* server_time */));
   ASSERT_TRUE(cookie);
-  TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame())
+  PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame())
       ->OnCookiesAccessed({content::CookieAccessDetails::Type::kChange,
                            origin,
                            origin,
@@ -188,12 +188,12 @@
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeature(features::kGenericSensorExtraClasses);
 
-  TabSpecificContentSettings::CreateForWebContents(
+  PageSpecificContentSettings::CreateForWebContents(
       web_contents(),
-      std::make_unique<chrome::TabSpecificContentSettingsDelegate>(
+      std::make_unique<chrome::PageSpecificContentSettingsDelegate>(
           web_contents()));
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
 
   auto content_setting_image_model =
       ContentSettingImageModel::CreateForContentType(
@@ -213,7 +213,7 @@
 
   NavigateAndCommit(controller_, GURL("http://www.google.com"));
   content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
 
   // Allowing by default but blocking (e.g. due to a feature policy) causes the
   // indicator to be shown.
@@ -230,7 +230,7 @@
 
   NavigateAndCommit(controller_, GURL("http://www.google.com"));
   content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
 
   // Blocking by default but allowing (e.g. via a site-specific exception)
   // causes the indicator to be shown.
@@ -247,7 +247,7 @@
 
   NavigateAndCommit(controller_, GURL("http://www.google.com"));
   content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
 
   // Blocking access by default also causes the indicator to be shown so users
   // can set an exception.
@@ -271,13 +271,13 @@
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeature(features::kGenericSensorExtraClasses);
 
-  TabSpecificContentSettings::CreateForWebContents(
+  PageSpecificContentSettings::CreateForWebContents(
       web_contents(),
-      std::make_unique<chrome::TabSpecificContentSettingsDelegate>(
+      std::make_unique<chrome::PageSpecificContentSettingsDelegate>(
           web_contents()));
   NavigateAndCommit(controller_, GURL("https://www.example.com"));
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   HostContentSettingsMap* settings_map =
       HostContentSettingsMapFactory::GetForProfile(profile());
 
@@ -317,7 +317,7 @@
 
   NavigateAndCommit(controller_, GURL("https://www.example.com"));
   content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
 
   // Go from block by default to allow by default to block by default.
   {
@@ -352,7 +352,7 @@
 
   NavigateAndCommit(controller_, GURL("https://www.example.com"));
   content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
 
   // Block by default but allow a specific site.
   {
@@ -373,7 +373,7 @@
 
   NavigateAndCommit(controller_, GURL("https://www.example.com"));
   content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   // Clear site-specific exceptions.
   settings_map->ClearSettingsForOneType(ContentSettingsType::SENSORS);
 
@@ -396,9 +396,9 @@
 }
 
 // Regression test for http://crbug.com/161854.
-TEST_F(ContentSettingImageModelTest, NULLTabSpecificContentSettings) {
-  TabSpecificContentSettings::DeleteForWebContentsForTest(web_contents());
-  EXPECT_EQ(nullptr, TabSpecificContentSettings::GetForFrame(
+TEST_F(ContentSettingImageModelTest, NULLPageSpecificContentSettings) {
+  PageSpecificContentSettings::DeleteForWebContentsForTest(web_contents());
+  EXPECT_EQ(nullptr, PageSpecificContentSettings::GetForFrame(
                          web_contents()->GetMainFrame()));
   // Should not crash.
   ContentSettingImageModel::CreateForContentType(
@@ -407,12 +407,12 @@
 }
 
 TEST_F(ContentSettingImageModelTest, SubresourceFilter) {
-  TabSpecificContentSettings::CreateForWebContents(
+  PageSpecificContentSettings::CreateForWebContents(
       web_contents(),
-      std::make_unique<chrome::TabSpecificContentSettingsDelegate>(
+      std::make_unique<chrome::PageSpecificContentSettingsDelegate>(
           web_contents()));
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   auto content_setting_image_model =
       ContentSettingImageModel::CreateForContentType(
           ContentSettingImageModel::ImageType::ADS);
@@ -428,12 +428,12 @@
 }
 
 TEST_F(ContentSettingImageModelTest, NotificationsIconVisibility) {
-  TabSpecificContentSettings::CreateForWebContents(
+  PageSpecificContentSettings::CreateForWebContents(
       web_contents(),
-      std::make_unique<chrome::TabSpecificContentSettingsDelegate>(
+      std::make_unique<chrome::PageSpecificContentSettingsDelegate>(
           web_contents()));
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   auto content_setting_image_model =
       ContentSettingImageModel::CreateForContentType(
           ContentSettingImageModel::ImageType::NOTIFICATIONS_QUIET_PROMPT);
diff --git a/chrome/browser/ui/content_settings/content_setting_media_image_model_unittest.mm b/chrome/browser/ui/content_settings/content_setting_media_image_model_unittest.mm
index 3198501..b541ff8 100644
--- a/chrome/browser/ui/content_settings/content_setting_media_image_model_unittest.mm
+++ b/chrome/browser/ui/content_settings/content_setting_media_image_model_unittest.mm
@@ -9,7 +9,7 @@
 #include "base/mac/mac_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
-#include "chrome/browser/content_settings/tab_specific_content_settings_delegate.h"
+#include "chrome/browser/content_settings/page_specific_content_settings_delegate.h"
 #include "chrome/browser/infobars/infobar_service.h"
 #include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"
 #include "chrome/browser/profiles/profile.h"
@@ -19,7 +19,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "chrome/test/base/testing_profile.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/prefs/pref_service.h"
 #include "components/prerender/browser/prerender_manager.h"
 #include "components/vector_icons/vector_icons.h"
@@ -34,7 +34,7 @@
 struct VectorIcon;
 }
 
-using content_settings::TabSpecificContentSettings;
+using content_settings::PageSpecificContentSettings;
 
 namespace {
 
@@ -61,9 +61,9 @@
   void SetUp() override {
     ChromeRenderViewHostTestHarness::SetUp();
 
-    TabSpecificContentSettings::CreateForWebContents(
+    PageSpecificContentSettings::CreateForWebContents(
         web_contents(),
-        std::make_unique<chrome::TabSpecificContentSettingsDelegate>(
+        std::make_unique<chrome::PageSpecificContentSettingsDelegate>(
             web_contents()));
     InfoBarService::CreateForWebContents(web_contents());
   }
@@ -86,12 +86,12 @@
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeature(features::kMacSystemMediaPermissionsInfoUi);
 
-  TabSpecificContentSettings::CreateForWebContents(
+  PageSpecificContentSettings::CreateForWebContents(
       web_contents(),
-      std::make_unique<chrome::TabSpecificContentSettingsDelegate>(
+      std::make_unique<chrome::PageSpecificContentSettingsDelegate>(
           web_contents()));
   auto* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   const GURL kTestOrigin("https://www.example.com");
   auto content_setting_image_model =
       ContentSettingImageModel::CreateForContentType(
@@ -103,8 +103,8 @@
   // Camera allowed per site: Test for system level permissions.
   {
     content_settings->OnMediaStreamPermissionSet(
-        kTestOrigin, TabSpecificContentSettings::CAMERA_ACCESSED, std::string(),
-        GetDefaultVideoDevice(), std::string(), std::string());
+        kTestOrigin, PageSpecificContentSettings::CAMERA_ACCESSED,
+        std::string(), GetDefaultVideoDevice(), std::string(), std::string());
     auth_wrapper.SetMockMediaPermissionStatus(kAllowed);
     content_setting_image_model->Update(web_contents());
     ExpectImageModelState(
@@ -124,7 +124,7 @@
   // Microphone allowed per site: Test for system level permissions.
   {
     content_settings->OnMediaStreamPermissionSet(
-        kTestOrigin, TabSpecificContentSettings::MICROPHONE_ACCESSED,
+        kTestOrigin, PageSpecificContentSettings::MICROPHONE_ACCESSED,
         std::string(), GetDefaultVideoDevice(), std::string(), std::string());
     auth_wrapper.SetMockMediaPermissionStatus(kAllowed);
     content_setting_image_model->Update(web_contents());
@@ -146,8 +146,8 @@
   {
     content_settings->OnMediaStreamPermissionSet(
         kTestOrigin,
-        (TabSpecificContentSettings::MICROPHONE_ACCESSED |
-         TabSpecificContentSettings::CAMERA_ACCESSED),
+        (PageSpecificContentSettings::MICROPHONE_ACCESSED |
+         PageSpecificContentSettings::CAMERA_ACCESSED),
         std::string(), GetDefaultVideoDevice(), std::string(), std::string());
     auth_wrapper.SetMockMediaPermissionStatus(kAllowed);
     auth_wrapper.SetMockMediaPermissionStatus(kAllowed);
@@ -180,8 +180,8 @@
     {
       content_settings->OnMediaStreamPermissionSet(
           kTestOrigin,
-          TabSpecificContentSettings::CAMERA_ACCESSED |
-              TabSpecificContentSettings::CAMERA_BLOCKED,
+          PageSpecificContentSettings::CAMERA_ACCESSED |
+              PageSpecificContentSettings::CAMERA_BLOCKED,
           GetDefaultAudioDevice(), GetDefaultVideoDevice(), std::string(),
           std::string());
       content_setting_image_model->Update(web_contents());
@@ -195,8 +195,8 @@
     {
       content_settings->OnMediaStreamPermissionSet(
           kTestOrigin,
-          TabSpecificContentSettings::MICROPHONE_ACCESSED |
-              TabSpecificContentSettings::MICROPHONE_BLOCKED,
+          PageSpecificContentSettings::MICROPHONE_ACCESSED |
+              PageSpecificContentSettings::MICROPHONE_BLOCKED,
           GetDefaultAudioDevice(), GetDefaultVideoDevice(), std::string(),
           std::string());
       content_setting_image_model->Update(web_contents());
@@ -210,10 +210,10 @@
     {
       content_settings->OnMediaStreamPermissionSet(
           kTestOrigin,
-          TabSpecificContentSettings::CAMERA_ACCESSED |
-              TabSpecificContentSettings::CAMERA_BLOCKED |
-              TabSpecificContentSettings::MICROPHONE_ACCESSED |
-              TabSpecificContentSettings::MICROPHONE_BLOCKED,
+          PageSpecificContentSettings::CAMERA_ACCESSED |
+              PageSpecificContentSettings::CAMERA_BLOCKED |
+              PageSpecificContentSettings::MICROPHONE_ACCESSED |
+              PageSpecificContentSettings::MICROPHONE_BLOCKED,
           GetDefaultAudioDevice(), GetDefaultVideoDevice(), std::string(),
           std::string());
       content_setting_image_model->Update(web_contents());
diff --git a/chrome/browser/ui/cookie_controls/cookie_controls_controller_unittest.cc b/chrome/browser/ui/cookie_controls/cookie_controls_controller_unittest.cc
index 554338f..507b4da 100644
--- a/chrome/browser/ui/cookie_controls/cookie_controls_controller_unittest.cc
+++ b/chrome/browser/ui/cookie_controls/cookie_controls_controller_unittest.cc
@@ -5,11 +5,11 @@
 #include "components/content_settings/browser/ui/cookie_controls_controller.h"
 
 #include "chrome/browser/content_settings/cookie_settings_factory.h"
-#include "chrome/browser/content_settings/tab_specific_content_settings_delegate.h"
+#include "chrome/browser/content_settings/page_specific_content_settings_delegate.h"
 #include "chrome/browser/ui/cookie_controls/cookie_controls_service.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "chrome/test/base/testing_profile.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/browser/ui/cookie_controls_view.h"
 #include "components/content_settings/core/browser/cookie_settings.h"
 #include "components/content_settings/core/common/cookie_controls_enforcement.h"
@@ -63,9 +63,9 @@
  protected:
   void SetUp() override {
     ChromeRenderViewHostTestHarness::SetUp();
-    content_settings::TabSpecificContentSettings::CreateForWebContents(
+    content_settings::PageSpecificContentSettings::CreateForWebContents(
         web_contents(),
-        std::make_unique<chrome::TabSpecificContentSettingsDelegate>(
+        std::make_unique<chrome::PageSpecificContentSettingsDelegate>(
             web_contents()));
     profile()->GetPrefs()->SetInteger(
         prefs::kCookieControlsMode,
@@ -92,9 +92,9 @@
 
   MockCookieControlsView* mock() { return &mock_; }
 
-  content_settings::TabSpecificContentSettings*
-  tab_specific_content_settings() {
-    return content_settings::TabSpecificContentSettings::GetForFrame(
+  content_settings::PageSpecificContentSettings*
+  page_specific_content_settings() {
+    return content_settings::PageSpecificContentSettings::GetForFrame(
         web_contents()->GetMainFrame());
   }
 
@@ -121,7 +121,7 @@
 
   // Accessing cookies should be notified.
   EXPECT_CALL(*mock(), OnCookiesCountChanged(1, 0));
-  tab_specific_content_settings()->OnWebDatabaseAccessed(
+  page_specific_content_settings()->OnWebDatabaseAccessed(
       GURL("https://example.com"), /*blocked=*/false);
   testing::Mock::VerifyAndClearExpectations(mock());
 
@@ -134,7 +134,7 @@
 
   // Blocking cookies should update the blocked cookie count.
   EXPECT_CALL(*mock(), OnCookiesCountChanged(1, 1));
-  tab_specific_content_settings()->OnWebDatabaseAccessed(
+  page_specific_content_settings()->OnWebDatabaseAccessed(
       GURL("https://thirdparty.com"), /*blocked=*/true);
   testing::Mock::VerifyAndClearExpectations(mock());
 
@@ -223,9 +223,9 @@
   std::unique_ptr<content::WebContents> incognito_web_contents =
       content::WebContentsTester::CreateTestWebContents(
           profile()->GetPrimaryOTRProfile(), nullptr);
-  content_settings::TabSpecificContentSettings::CreateForWebContents(
+  content_settings::PageSpecificContentSettings::CreateForWebContents(
       incognito_web_contents.get(),
-      std::make_unique<chrome::TabSpecificContentSettingsDelegate>(
+      std::make_unique<chrome::PageSpecificContentSettingsDelegate>(
           incognito_web_contents.get()));
   auto* tester = content::WebContentsTester::For(incognito_web_contents.get());
   MockCookieControlsView incognito_mock_;
diff --git a/chrome/browser/ui/page_info/chrome_page_info_delegate.cc b/chrome/browser/ui/page_info/chrome_page_info_delegate.cc
index 82cd90d..222e964 100644
--- a/chrome/browser/ui/page_info/chrome_page_info_delegate.cc
+++ b/chrome/browser/ui/page_info/chrome_page_info_delegate.cc
@@ -8,7 +8,7 @@
 #include "chrome/browser/bluetooth/bluetooth_chooser_context.h"
 #include "chrome/browser/bluetooth/bluetooth_chooser_context_factory.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
-#include "chrome/browser/content_settings/tab_specific_content_settings_delegate.h"
+#include "chrome/browser/content_settings/page_specific_content_settings_delegate.h"
 #include "chrome/browser/infobars/infobar_service.h"
 #include "chrome/browser/permissions/permission_decision_auto_blocker_factory.h"
 #include "chrome/browser/permissions/permission_manager_factory.h"
@@ -20,7 +20,7 @@
 #include "chrome/browser/usb/usb_chooser_context_factory.h"
 #include "chrome/browser/vr/vr_tab_helper.h"
 #include "chrome/common/url_constants.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/permissions/chooser_context_base.h"
 #include "components/permissions/permission_manager.h"
 #include "components/permissions/permission_result.h"
@@ -198,9 +198,9 @@
   return *helper->GetVisibleSecurityState();
 }
 
-std::unique_ptr<content_settings::TabSpecificContentSettings::Delegate>
-ChromePageInfoDelegate::GetTabSpecificContentSettingsDelegate() {
-  auto delegate = std::make_unique<chrome::TabSpecificContentSettingsDelegate>(
+std::unique_ptr<content_settings::PageSpecificContentSettings::Delegate>
+ChromePageInfoDelegate::GetPageSpecificContentSettingsDelegate() {
+  auto delegate = std::make_unique<chrome::PageSpecificContentSettingsDelegate>(
       web_contents_);
   return std::move(delegate);
 }
diff --git a/chrome/browser/ui/page_info/chrome_page_info_delegate.h b/chrome/browser/ui/page_info/chrome_page_info_delegate.h
index 970978b..81bbdd9 100644
--- a/chrome/browser/ui/page_info/chrome_page_info_delegate.h
+++ b/chrome/browser/ui/page_info/chrome_page_info_delegate.h
@@ -15,7 +15,7 @@
 class StatefulSSLHostStateDelegate;
 
 namespace content_settings {
-class TabSpecificContentSettings;
+class PageSpecificContentSettings;
 }
 
 namespace permissions {
@@ -63,8 +63,8 @@
   bool IsContentDisplayedInVrHeadset() override;
   security_state::SecurityLevel GetSecurityLevel() override;
   security_state::VisibleSecurityState GetVisibleSecurityState() override;
-  std::unique_ptr<content_settings::TabSpecificContentSettings::Delegate>
-  GetTabSpecificContentSettingsDelegate() override;
+  std::unique_ptr<content_settings::PageSpecificContentSettings::Delegate>
+  GetPageSpecificContentSettingsDelegate() override;
 
 #if defined(OS_ANDROID)
   const base::string16 GetClientApplicationName() override;
diff --git a/chrome/browser/ui/page_info/page_info_unittest.cc b/chrome/browser/ui/page_info/page_info_unittest.cc
index 2e3271a..5a83305 100644
--- a/chrome/browser/ui/page_info/page_info_unittest.cc
+++ b/chrome/browser/ui/page_info/page_info_unittest.cc
@@ -18,7 +18,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
-#include "chrome/browser/content_settings/tab_specific_content_settings_delegate.h"
+#include "chrome/browser/content_settings/page_specific_content_settings_delegate.h"
 #include "chrome/browser/infobars/mock_infobar_service.h"
 #include "chrome/browser/ssl/stateful_ssl_host_state_delegate_factory.h"
 #include "chrome/browser/ssl/tls_deprecation_test_utils.h"
@@ -149,9 +149,9 @@
     ASSERT_TRUE(cert_);
 
     MockInfoBarService::CreateForWebContents(web_contents());
-    content_settings::TabSpecificContentSettings::CreateForWebContents(
+    content_settings::PageSpecificContentSettings::CreateForWebContents(
         web_contents(),
-        std::make_unique<chrome::TabSpecificContentSettingsDelegate>(
+        std::make_unique<chrome::PageSpecificContentSettingsDelegate>(
             web_contents()));
 
     // Setup mock ui.
@@ -249,9 +249,9 @@
           content::WebContentsTester::CreateTestWebContents(
               profile()->GetPrimaryOTRProfile(), nullptr);
 
-      content_settings::TabSpecificContentSettings::CreateForWebContents(
+      content_settings::PageSpecificContentSettings::CreateForWebContents(
           incognito_web_contents_.get(),
-          std::make_unique<chrome::TabSpecificContentSettingsDelegate>(
+          std::make_unique<chrome::PageSpecificContentSettingsDelegate>(
               incognito_web_contents_.get()));
 
       incognito_mock_ui_ = std::make_unique<MockPageInfoUI>();
@@ -1252,7 +1252,13 @@
 
 // Tests that metrics are recorded on a PageInfo for pages with
 // various Safety Tip statuses.
-TEST_F(PageInfoTest, SafetyTipMetrics) {
+// See https://crbug.com/1114659 for why the test is disabled on Android.
+#if defined(OS_ANDROID)
+#define MAYBE_SafetyTipMetrics DISABLED_SafetyTipMetrics
+#else
+#define MAYBE_SafetyTipMetrics SafetyTipMetrics
+#endif
+TEST_F(PageInfoTest, MAYBE_SafetyTipMetrics) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
       security_state::features::kSafetyTipUI);
diff --git a/chrome/browser/ui/passwords/bubble_controllers/password_bubble_controller_base.h b/chrome/browser/ui/passwords/bubble_controllers/password_bubble_controller_base.h
index f1250f9..0536c58b 100644
--- a/chrome/browser/ui/passwords/bubble_controllers/password_bubble_controller_base.h
+++ b/chrome/browser/ui/passwords/bubble_controllers/password_bubble_controller_base.h
@@ -21,7 +21,7 @@
 
 // This is the base class for all bubble controllers. There should be a bubble
 // controller per view. Bubble controller provides the data and controls the
-// password management actions for the coressponding view.
+// password management actions for the corresponding view.
 class PasswordBubbleControllerBase {
  public:
   enum class PasswordAction { kRemovePassword, kAddPassword };
diff --git a/chrome/browser/ui/signin_reauth_view_controller.cc b/chrome/browser/ui/signin_reauth_view_controller.cc
index 4a466c7..b5d08bc 100644
--- a/chrome/browser/ui/signin_reauth_view_controller.cc
+++ b/chrome/browser/ui/signin_reauth_view_controller.cc
@@ -67,15 +67,6 @@
   // show it in some cases in the future.
   ShowReauthConfirmationDialog();
 
-  if (!base::FeatureList::IsEnabled(kSigninReauthPrompt)) {
-    // Approve reauth automatically.
-    gaia_reauth_type_ = GaiaReauthType::kAutoApproved;
-    gaia_reauth_page_state_ = GaiaReauthPageState::kDone;
-    gaia_reauth_page_result_ = signin::ReauthResult::kSuccess;
-    OnStateChanged();
-    return;
-  }
-
   // Navigate to the Gaia reauth challenge page in background.
   reauth_web_contents_ =
       content::WebContents::Create(content::WebContents::CreateParams(
diff --git a/chrome/browser/ui/signin_reauth_view_controller_browsertest.cc b/chrome/browser/ui/signin_reauth_view_controller_browsertest.cc
index 2b4c7b9..4d84afc 100644
--- a/chrome/browser/ui/signin_reauth_view_controller_browsertest.cc
+++ b/chrome/browser/ui/signin_reauth_view_controller_browsertest.cc
@@ -138,10 +138,6 @@
 // Browser tests for SigninReauthViewController.
 class SigninReauthViewControllerBrowserTest : public InProcessBrowserTest {
  public:
-  SigninReauthViewControllerBrowserTest() {
-    scoped_feature_list_.InitAndEnableFeature(kSigninReauthPrompt);
-  }
-
   void SetUp() override {
     ASSERT_TRUE(https_server()->InitializeAndListen());
     InProcessBrowserTest::SetUp();
@@ -225,7 +221,6 @@
   base::HistogramTester* histogram_tester() { return &histogram_tester_; }
 
  private:
-  base::test::ScopedFeatureList scoped_feature_list_;
   net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS};
   base::HistogramTester histogram_tester_;
   std::unique_ptr<net::test_server::ControllableHttpResponse>
diff --git a/chrome/browser/ui/signin_view_controller_interactive_uitest.cc b/chrome/browser/ui/signin_view_controller_interactive_uitest.cc
index 12819c9..b38d0f8 100644
--- a/chrome/browser/ui/signin_view_controller_interactive_uitest.cc
+++ b/chrome/browser/ui/signin_view_controller_interactive_uitest.cc
@@ -200,34 +200,3 @@
   dialog_observer.WaitForDialogClosed();
   EXPECT_FALSE(browser()->signin_view_controller()->ShowsModalDialog());
 }
-
-// Tests that the confirm button is focused by default in the reauth dialog.
-IN_PROC_BROWSER_TEST_F(SignInViewControllerBrowserTest, ReauthDefaultFocus) {
-  const auto kAccessPoint =
-      signin_metrics::ReauthAccessPoint::kAutofillDropdown;
-  content::TestNavigationObserver content_observer(
-      signin::GetReauthConfirmationURL(kAccessPoint));
-  content_observer.StartWatchingNewWebContents();
-  CoreAccountId account_id = signin::SetUnconsentedPrimaryAccount(
-                                 GetIdentityManager(), "alice@gmail.com")
-                                 .account_id;
-
-  signin::ReauthResult reauth_result;
-  base::RunLoop run_loop;
-  std::unique_ptr<SigninViewController::ReauthAbortHandle> abort_handle =
-      browser()->signin_view_controller()->ShowReauthPrompt(
-          account_id, kAccessPoint,
-          base::BindLambdaForTesting([&](signin::ReauthResult result) {
-            reauth_result = result;
-            run_loop.Quit();
-          }));
-  EXPECT_TRUE(browser()->signin_view_controller()->ShowsModalDialog());
-  content_observer.Wait();
-  ASSERT_TRUE(ui_test_utils::SendKeyPressSync(browser(), ui::VKEY_RETURN,
-                                              /*control=*/false,
-                                              /*shift=*/false, /*alt=*/false,
-                                              /*command=*/false));
-  run_loop.Run();
-  EXPECT_EQ(reauth_result, signin::ReauthResult::kSuccess);
-  EXPECT_FALSE(browser()->signin_view_controller()->ShowsModalDialog());
-}
diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc
index f888e94..f1837fb 100644
--- a/chrome/browser/ui/tab_helpers.cc
+++ b/chrome/browser/ui/tab_helpers.cc
@@ -20,8 +20,8 @@
 #include "chrome/browser/complex_tasks/task_tab_helper.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/content_settings/mixed_content_settings_tab_helper.h"
+#include "chrome/browser/content_settings/page_specific_content_settings_delegate.h"
 #include "chrome/browser/content_settings/sound_content_setting_observer.h"
-#include "chrome/browser/content_settings/tab_specific_content_settings_delegate.h"
 #include "chrome/browser/data_reduction_proxy/data_reduction_proxy_tab_helper.h"
 #include "chrome/browser/engagement/site_engagement_helper.h"
 #include "chrome/browser/engagement/site_engagement_service.h"
@@ -92,7 +92,7 @@
 #include "components/blocked_content/popup_blocker_tab_helper.h"
 #include "components/blocked_content/popup_opener_tab_helper.h"
 #include "components/captive_portal/core/buildflags.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/dom_distiller/core/dom_distiller_features.h"
 #include "components/download/content/factory/navigation_monitor_factory.h"
 #include "components/download/content/public/download_navigation_observer.h"
@@ -306,9 +306,9 @@
       web_contents,
       sync_sessions::SyncSessionsWebContentsRouterFactory::GetForProfile(
           profile));
-  content_settings::TabSpecificContentSettings::CreateForWebContents(
+  content_settings::PageSpecificContentSettings::CreateForWebContents(
       web_contents,
-      std::make_unique<chrome::TabSpecificContentSettingsDelegate>(
+      std::make_unique<chrome::PageSpecificContentSettingsDelegate>(
           web_contents));
   TabUIHelper::CreateForWebContents(web_contents);
   tasks::TaskTabHelper::CreateForWebContents(web_contents);
diff --git a/chrome/browser/ui/views/collected_cookies_views.cc b/chrome/browser/ui/views/collected_cookies_views.cc
index 41e5431..fbbc45c 100644
--- a/chrome/browser/ui/views/collected_cookies_views.cc
+++ b/chrome/browser/ui/views/collected_cookies_views.cc
@@ -27,7 +27,7 @@
 #include "components/browsing_data/content/local_shared_objects_container.h"
 #include "components/browsing_data/content/local_storage_helper.h"
 #include "components/constrained_window/constrained_window_views.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/browser/cookie_settings.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/vector_icons/vector_icons.h"
@@ -421,8 +421,8 @@
 std::unique_ptr<views::View> CollectedCookiesViews::CreateAllowedPane() {
   // This captures a snapshot of the allowed cookies of the current page so we
   // are fine using WebContents::GetMainFrame() here
-  content_settings::TabSpecificContentSettings* content_settings =
-      content_settings::TabSpecificContentSettings::GetForFrame(
+  content_settings::PageSpecificContentSettings* content_settings =
+      content_settings::PageSpecificContentSettings::GetForFrame(
           web_contents_->GetMainFrame());
 
   // Create the controls that go into the pane.
@@ -478,8 +478,8 @@
 }
 
 std::unique_ptr<views::View> CollectedCookiesViews::CreateBlockedPane() {
-  content_settings::TabSpecificContentSettings* content_settings =
-      content_settings::TabSpecificContentSettings::GetForFrame(
+  content_settings::PageSpecificContentSettings* content_settings =
+      content_settings::PageSpecificContentSettings::GetForFrame(
           web_contents_->GetMainFrame());
 
   Profile* profile =
diff --git a/chrome/browser/ui/views/crostini/crostini_uninstaller_view.h b/chrome/browser/ui/views/crostini/crostini_uninstaller_view.h
index 6031ed4..2ea12df 100644
--- a/chrome/browser/ui/views/crostini/crostini_uninstaller_view.h
+++ b/chrome/browser/ui/views/crostini/crostini_uninstaller_view.h
@@ -39,6 +39,9 @@
   gfx::Size CalculatePreferredSize() const override;
 
   static CrostiniUninstallerView* GetActiveViewForTesting();
+  void set_destructor_callback_for_testing(base::OnceClosure callback) {
+    destructor_callback_for_testing_.ReplaceClosure(std::move(callback));
+  }
 
  private:
   enum class State {
@@ -61,6 +64,8 @@
   bool has_logged_result_ = false;
   Profile* profile_;
 
+  base::ScopedClosureRunner destructor_callback_for_testing_;
+
   base::WeakPtrFactory<CrostiniUninstallerView> weak_ptr_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(CrostiniUninstallerView);
diff --git a/chrome/browser/ui/views/crostini/crostini_uninstaller_view_browsertest.cc b/chrome/browser/ui/views/crostini/crostini_uninstaller_view_browsertest.cc
index d3c7baa..eac030b 100644
--- a/chrome/browser/ui/views/crostini/crostini_uninstaller_view_browsertest.cc
+++ b/chrome/browser/ui/views/crostini/crostini_uninstaller_view_browsertest.cc
@@ -76,7 +76,9 @@
   bool HasCancelButton() { return ActiveView()->GetCancelButton() != nullptr; }
 
   void WaitForViewDestroyed() {
-    base::RunLoop().RunUntilIdle();
+    base::RunLoop run_loop;
+    ActiveView()->set_destructor_callback_for_testing(run_loop.QuitClosure());
+    run_loop.Run();
     EXPECT_EQ(nullptr, ActiveView());
   }
 
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc
index 721629a..4381608 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc
@@ -917,8 +917,8 @@
   ContentSettingImageView* GrantGeolocationPermission() {
     content::RenderFrameHost* frame =
         app_browser_->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
-    content_settings::TabSpecificContentSettings* content_settings =
-        content_settings::TabSpecificContentSettings::GetForFrame(
+    content_settings::PageSpecificContentSettings* content_settings =
+        content_settings::PageSpecificContentSettings::GetForFrame(
             frame->GetProcess()->GetID(), frame->GetRoutingID());
     content_settings->OnGeolocationPermissionSet(GetAppURL().GetOrigin(), true);
 
diff --git a/chrome/browser/ui/views/hats/hats_browsertest.cc b/chrome/browser/ui/views/hats/hats_browsertest.cc
index 20a27d1..76c7ce6 100644
--- a/chrome/browser/ui/views/hats/hats_browsertest.cc
+++ b/chrome/browser/ui/views/hats/hats_browsertest.cc
@@ -267,3 +267,26 @@
   });
   run_loop.Run();
 }
+
+IN_PROC_BROWSER_TEST_F(HatsNextWebDialogBrowserTest, NewWebContents) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  auto* dialog = new MockHatsNextWebDialog(
+      browser(), "open_new_web_contents_for_testing",
+      embedded_test_server()->GetURL("/hats/hats_next_mock.html"),
+      base::TimeDelta::FromSeconds(100));
+
+  // The mock hats dialog will push a loaded state after it has attempted to
+  // open another web contents.
+  base::RunLoop run_loop;
+  EXPECT_CALL(*dialog, ShowWidget).WillOnce(testing::Invoke([&run_loop]() {
+    run_loop.Quit();
+  }));
+  run_loop.Run();
+
+  // Check that a tab with http://foo.com (defined in hats_next_mock.html) has
+  // been opened in the regular browser and is active.
+  EXPECT_EQ(
+      GURL("http://foo.com"),
+      browser()->tab_strip_model()->GetActiveWebContents()->GetVisibleURL());
+}
diff --git a/chrome/browser/ui/views/hats/hats_next_web_dialog.cc b/chrome/browser/ui/views/hats/hats_next_web_dialog.cc
index fbdeb26b..d937fa3 100644
--- a/chrome/browser/ui/views/hats/hats_next_web_dialog.cc
+++ b/chrome/browser/ui/views/hats/hats_next_web_dialog.cc
@@ -26,6 +26,45 @@
 #include "ui/views/controls/webview/web_dialog_view.h"
 #include "ui/views/layout/fill_layout.h"
 
+// A delegate used to intercept the creation of new WebContents by the HaTS
+// Next dialog.
+class HatsNextWebDialog::WebContentsDelegate
+    : public content::WebContentsDelegate {
+ public:
+  explicit WebContentsDelegate(Browser* browser) : browser_(browser) {}
+
+  bool IsWebContentsCreationOverridden(
+      content::SiteInstance* source_site_instance,
+      content::mojom::WindowContainerType window_container_type,
+      const GURL& opener_url,
+      const std::string& frame_name,
+      const GURL& target_url) override {
+    return true;
+  }
+
+  content::WebContents* CreateCustomWebContents(
+      content::RenderFrameHost* opener,
+      content::SiteInstance* source_site_instance,
+      bool is_new_browsing_instance,
+      const GURL& opener_url,
+      const std::string& frame_name,
+      const GURL& target_url,
+      const std::string& partition_id,
+      content::SessionStorageNamespace* session_storage_namespace) override {
+    // The HaTS Next WebDialog runs with a non-primary OTR profile. This profile
+    // cannot open new browser windows, so they are instead opened in the
+    // regular browser that initiated the HaTS survey.
+    browser_->OpenURL(
+        content::OpenURLParams(target_url, content::Referrer(),
+                               WindowOpenDisposition::NEW_FOREGROUND_TAB,
+                               ui::PAGE_TRANSITION_LINK, false));
+    return nullptr;
+  }
+
+ private:
+  Browser* browser_;
+};
+
 // A thin wrapper that forwards the reference part of the URL associated with
 // navigation events to the enclosing web dialog.
 class HatsNextWebDialog::WebContentsObserver
@@ -130,10 +169,10 @@
                                views::BubbleBorder::TOP_RIGHT),
       otr_profile_(browser->profile()->GetOffTheRecordProfile(
           Profile::OTRProfileID::CreateUnique("HaTSNext:WebDialog"))),
+      browser_(browser),
       trigger_id_(trigger_id),
       hats_survey_url_(hats_survey_url),
-      timeout_(timeout),
-      close_bubble_helper_(this, browser) {
+      timeout_(timeout) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   otr_profile_->AddObserver(this);
   set_close_on_deactivate(false);
@@ -141,13 +180,16 @@
   SetButtons(ui::DIALOG_BUTTON_NONE);
 
   SetLayoutManager(std::make_unique<views::FillLayout>());
-  auto* web_view = AddChildView(std::make_unique<views::WebDialogView>(
+  web_view_ = AddChildView(std::make_unique<views::WebDialogView>(
       otr_profile_, this, std::make_unique<ChromeWebContentsHandler>(),
       /* use_dialog_frame */ true));
   widget_ = views::BubbleDialogDelegateView::CreateBubble(this);
 
   web_contents_observer_ =
-      std::make_unique<WebContentsObserver>(web_view->web_contents(), this);
+      std::make_unique<WebContentsObserver>(web_view_->web_contents(), this);
+
+  web_contents_delegate_ = std::make_unique<WebContentsDelegate>(browser_);
+  web_view_->web_contents()->SetDelegate(web_contents_delegate_.get());
 
   loading_timer_.Start(FROM_HERE, timeout_,
                        base::BindOnce(&HatsNextWebDialog::CloseWidget,
@@ -160,6 +202,9 @@
     otr_profile_->RemoveObserver(this);
     ProfileDestroyer::DestroyProfileWhenAppropriate(otr_profile_);
   }
+  // Explicitly clear the delegate to ensure it is not invalid between now and
+  // when the web contents is destroyed in the base class.
+  web_view_->web_contents()->SetDelegate(nullptr);
 }
 
 void HatsNextWebDialog::OnSurveyStateUpdateReceived(std::string state) {
diff --git a/chrome/browser/ui/views/hats/hats_next_web_dialog.h b/chrome/browser/ui/views/hats/hats_next_web_dialog.h
index dcb2babf..afd5ea0 100644
--- a/chrome/browser/ui/views/hats/hats_next_web_dialog.h
+++ b/chrome/browser/ui/views/hats/hats_next_web_dialog.h
@@ -6,7 +6,6 @@
 #define CHROME_BROWSER_UI_VIEWS_HATS_HATS_NEXT_WEB_DIALOG_H_
 
 #include "chrome/browser/profiles/profile_observer.h"
-#include "chrome/browser/ui/views/close_bubble_on_tab_activation_helper.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
 #include "ui/views/controls/webview/web_dialog_view.h"
@@ -67,6 +66,7 @@
                     const GURL& hats_survey_url_,
                     const base::TimeDelta& timeout);
 
+  class WebContentsDelegate;
   class WebContentsObserver;
 
   // Fired by the observer when the survey page has pushed state to the window
@@ -95,18 +95,20 @@
   // The off-the-record profile used for browsing to the Chrome HaTS webpage.
   Profile* otr_profile_;
 
+  Browser* browser_;
+
   // The HaTS Next survey trigger ID that is provided to the HaTS webpage.
   const std::string& trigger_id_;
 
+  views::WebDialogView* web_view_ = nullptr;
   views::Widget* widget_ = nullptr;
 
+  std::unique_ptr<WebContentsDelegate> web_contents_delegate_;
   std::unique_ptr<WebContentsObserver> web_contents_observer_;
   GURL hats_survey_url_;
 
   base::TimeDelta timeout_;
 
-  CloseBubbleOnTabActivationHelper close_bubble_helper_;
-
   base::WeakPtrFactory<HatsNextWebDialog> weak_factory_{this};
 };
 
diff --git a/chrome/browser/ui/views/location_bar/content_setting_bubble_dialog_browsertest.cc b/chrome/browser/ui/views/location_bar/content_setting_bubble_dialog_browsertest.cc
index abb8e0da..e68b1a6a 100644
--- a/chrome/browser/ui/views/location_bar/content_setting_bubble_dialog_browsertest.cc
+++ b/chrome/browser/ui/views/location_bar/content_setting_bubble_dialog_browsertest.cc
@@ -8,7 +8,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/content_settings/tab_specific_content_settings_delegate.h"
+#include "chrome/browser/content_settings/page_specific_content_settings_delegate.h"
 #include "chrome/browser/download/download_request_limiter.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
@@ -24,7 +24,7 @@
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/blocked_content/popup_blocker_tab_helper.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "components/permissions/notification_permission_ui_selector.h"
 #include "components/permissions/permission_request_manager.h"
@@ -99,14 +99,14 @@
     bool camera_accessed) {
   const int mic_setting =
       mic_accessed
-          ? content_settings::TabSpecificContentSettings::MICROPHONE_ACCESSED
+          ? content_settings::PageSpecificContentSettings::MICROPHONE_ACCESSED
           : 0;
   const int camera_setting =
       camera_accessed
-          ? content_settings::TabSpecificContentSettings::CAMERA_ACCESSED
+          ? content_settings::PageSpecificContentSettings::CAMERA_ACCESSED
           : 0;
-  content_settings::TabSpecificContentSettings* content_settings =
-      content_settings::TabSpecificContentSettings::GetForFrame(
+  content_settings::PageSpecificContentSettings* content_settings =
+      content_settings::PageSpecificContentSettings::GetForFrame(
           browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame());
   content_settings->OnMediaStreamPermissionSet(
       GURL("https://example.com/"), mic_setting | camera_setting, std::string(),
@@ -117,8 +117,8 @@
     ContentSettingsType content_type) {
   content::WebContents* web_contents =
       browser()->tab_strip_model()->GetActiveWebContents();
-  content_settings::TabSpecificContentSettings* content_settings =
-      content_settings::TabSpecificContentSettings::GetForFrame(
+  content_settings::PageSpecificContentSettings* content_settings =
+      content_settings::PageSpecificContentSettings::GetForFrame(
           web_contents->GetMainFrame());
   switch (content_type) {
     case ContentSettingsType::AUTOMATIC_DOWNLOADS: {
@@ -151,7 +151,7 @@
       break;
     }
     case ContentSettingsType::PROTOCOL_HANDLERS:
-      chrome::TabSpecificContentSettingsDelegate::FromWebContents(web_contents)
+      chrome::PageSpecificContentSettingsDelegate::FromWebContents(web_contents)
           ->set_pending_protocol_handler(ProtocolHandler::CreateProtocolHandler(
               "mailto", GURL("https://example.com/")));
       break;
diff --git a/chrome/browser/ui/views/media_router/presentation_receiver_window_view.cc b/chrome/browser/ui/views/media_router/presentation_receiver_window_view.cc
index 86ddab6..870b8bd 100644
--- a/chrome/browser/ui/views/media_router/presentation_receiver_window_view.cc
+++ b/chrome/browser/ui/views/media_router/presentation_receiver_window_view.cc
@@ -11,7 +11,7 @@
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/content_settings/mixed_content_settings_tab_helper.h"
-#include "chrome/browser/content_settings/tab_specific_content_settings_delegate.h"
+#include "chrome/browser/content_settings/page_specific_content_settings_delegate.h"
 #include "chrome/browser/infobars/infobar_service.h"
 #include "chrome/browser/password_manager/chrome_password_manager_client.h"
 #include "chrome/browser/profiles/profile.h"
@@ -30,7 +30,7 @@
 #include "components/autofill/content/browser/content_autofill_driver_factory.h"
 #include "components/autofill/core/browser/autofill_manager.h"
 #include "components/blocked_content/popup_blocker_tab_helper.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/omnibox/browser/location_bar_model_impl.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_constants.h"
@@ -164,9 +164,9 @@
   InfoBarService::CreateForWebContents(web_contents);
   MixedContentSettingsTabHelper::CreateForWebContents(web_contents);
   blocked_content::PopupBlockerTabHelper::CreateForWebContents(web_contents);
-  content_settings::TabSpecificContentSettings::CreateForWebContents(
+  content_settings::PageSpecificContentSettings::CreateForWebContents(
       web_contents,
-      std::make_unique<chrome::TabSpecificContentSettingsDelegate>(
+      std::make_unique<chrome::PageSpecificContentSettingsDelegate>(
           web_contents));
 
   auto* profile =
diff --git a/chrome/browser/ui/views/page_info/page_info_bubble_view.cc b/chrome/browser/ui/views/page_info/page_info_bubble_view.cc
index 0b2a107..cffc64f 100644
--- a/chrome/browser/ui/views/page_info/page_info_bubble_view.cc
+++ b/chrome/browser/ui/views/page_info/page_info_bubble_view.cc
@@ -18,7 +18,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
 #include "chrome/browser/certificate_viewer.h"
-#include "chrome/browser/content_settings/tab_specific_content_settings_delegate.h"
+#include "chrome/browser/content_settings/page_specific_content_settings_delegate.h"
 #include "chrome/browser/infobars/infobar_service.h"
 #include "chrome/browser/platform_util.h"
 #include "chrome/browser/profiles/profile.h"
diff --git a/chrome/browser/ui/views/page_info/page_info_bubble_view_unittest.cc b/chrome/browser/ui/views/page_info/page_info_bubble_view_unittest.cc
index 853e3b62..eaaf631 100644
--- a/chrome/browser/ui/views/page_info/page_info_bubble_view_unittest.cc
+++ b/chrome/browser/ui/views/page_info/page_info_bubble_view_unittest.cc
@@ -9,7 +9,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
-#include "chrome/browser/content_settings/tab_specific_content_settings_delegate.h"
+#include "chrome/browser/content_settings/page_specific_content_settings_delegate.h"
 #include "chrome/browser/history/history_service_factory.h"
 #include "chrome/browser/ssl/security_state_tab_helper.h"
 #include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
@@ -202,9 +202,9 @@
     parent_window_->Init(std::move(parent_params));
 
     content::WebContents* web_contents = web_contents_helper_.web_contents();
-    content_settings::TabSpecificContentSettings::CreateForWebContents(
+    content_settings::PageSpecificContentSettings::CreateForWebContents(
         web_contents,
-        std::make_unique<chrome::TabSpecificContentSettingsDelegate>(
+        std::make_unique<chrome::PageSpecificContentSettingsDelegate>(
             web_contents));
     api_ = std::make_unique<test::PageInfoBubbleViewTestApi>(
         parent_window_->GetNativeView(), web_contents_helper_.profile(),
diff --git a/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_unittest.cc b/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_unittest.cc
index f275d60..e53d766 100644
--- a/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_unittest.cc
+++ b/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_unittest.cc
@@ -5,10 +5,10 @@
 #include "chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view.h"
 
 #include "base/bind_helpers.h"
-#include "chrome/browser/content_settings/tab_specific_content_settings_delegate.h"
+#include "chrome/browser/content_settings/page_specific_content_settings_delegate.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chrome/test/views/chrome_test_views_delegate.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/security_state/core/security_state.h"
 #include "content/public/test/browser_task_environment.h"
 #include "content/public/test/test_web_contents_factory.h"
@@ -50,9 +50,9 @@
     parent_window_->Init(std::move(parent_params));
 
     content::WebContents* web_contents = web_contents_helper_.web_contents();
-    content_settings::TabSpecificContentSettings::CreateForWebContents(
+    content_settings::PageSpecificContentSettings::CreateForWebContents(
         web_contents,
-        std::make_unique<chrome::TabSpecificContentSettingsDelegate>(
+        std::make_unique<chrome::PageSpecificContentSettingsDelegate>(
             web_contents));
 
     bubble_ = CreateSafetyTipBubbleForTesting(
diff --git a/chrome/browser/ui/webui/settings/chromeos/ambient_mode_handler.cc b/chrome/browser/ui/webui/settings/chromeos/ambient_mode_handler.cc
index f37c3fcc..2606b34 100644
--- a/chrome/browser/ui/webui/settings/chromeos/ambient_mode_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/ambient_mode_handler.cc
@@ -25,6 +25,32 @@
 constexpr int kBannerWidth = 512;
 constexpr int kBannerHeight = 512;
 
+// Strings for converting to and from AmbientModeTemperatureUnit enum.
+constexpr char kCelsius[] = "celsius";
+constexpr char kFahrenheit[] = "fahrenheit";
+
+ash::AmbientModeTemperatureUnit ExtractTemperatureUnit(
+    const base::ListValue* args) {
+  auto temperature_unit = args->GetList()[0].GetString();
+  if (temperature_unit == kCelsius) {
+    return ash::AmbientModeTemperatureUnit::kCelsius;
+  } else if (temperature_unit == kFahrenheit) {
+    return ash::AmbientModeTemperatureUnit::kFahrenheit;
+  }
+  NOTREACHED() << "Unknown temperature unit";
+  return ash::AmbientModeTemperatureUnit::kFahrenheit;
+}
+
+std::string TemperatureUnitToString(
+    ash::AmbientModeTemperatureUnit temperature_unit) {
+  switch (temperature_unit) {
+    case ash::AmbientModeTemperatureUnit::kFahrenheit:
+      return kFahrenheit;
+    case ash::AmbientModeTemperatureUnit::kCelsius:
+      return kCelsius;
+  }
+}
+
 ash::AmbientModeTopicSource ExtractTopicSource(const base::Value& value) {
   ash::AmbientModeTopicSource topic_source =
       static_cast<ash::AmbientModeTopicSource>(value.GetInt());
@@ -47,8 +73,8 @@
 
 void AmbientModeHandler::RegisterMessages() {
   web_ui()->RegisterMessageCallback(
-      "requestTopicSource",
-      base::BindRepeating(&AmbientModeHandler::HandleRequestTopicSource,
+      "requestSettings",
+      base::BindRepeating(&AmbientModeHandler::HandleRequestSettings,
                           base::Unretained(this)));
 
   web_ui()->RegisterMessageCallback(
@@ -62,6 +88,11 @@
                           base::Unretained(this)));
 
   web_ui()->RegisterMessageCallback(
+      "setSelectedTemperatureUnit",
+      base::BindRepeating(&AmbientModeHandler::HandleSetSelectedTemperatureUnit,
+                          base::Unretained(this)));
+
+  web_ui()->RegisterMessageCallback(
       "setSelectedAlbums",
       base::BindRepeating(&AmbientModeHandler::HandleSetSelectedAlbums,
                           base::Unretained(this)));
@@ -72,7 +103,7 @@
   ui_update_weak_factory_.InvalidateWeakPtrs();
 }
 
-void AmbientModeHandler::HandleRequestTopicSource(const base::ListValue* args) {
+void AmbientModeHandler::HandleRequestSettings(const base::ListValue* args) {
   CHECK(args);
   CHECK(args->empty());
 
@@ -103,10 +134,21 @@
       ui_update_weak_factory_.GetWeakPtr(), ExtractTopicSource(args)));
 }
 
+void AmbientModeHandler::HandleSetSelectedTemperatureUnit(
+    const base::ListValue* args) {
+  DCHECK(settings_);
+  CHECK_EQ(1U, args->GetSize());
+
+  settings_->temperature_unit = ExtractTemperatureUnit(args);
+  UpdateSettings();
+}
+
 void AmbientModeHandler::HandleSetSelectedTopicSource(
     const base::ListValue* args) {
-  ash::AmbientModeTopicSource topic_source = ExtractTopicSource(args);
-  settings_->topic_source = topic_source;
+  DCHECK(settings_);
+  CHECK_EQ(1U, args->GetSize());
+
+  settings_->topic_source = ExtractTopicSource(args);
   UpdateSettings();
 }
 
@@ -156,6 +198,13 @@
   UpdateSettings();
 }
 
+void AmbientModeHandler::SendTemperatureUnit() {
+  DCHECK(settings_);
+  FireWebUIListener(
+      "temperature-unit-changed",
+      base::Value(TemperatureUnitToString(settings_->temperature_unit)));
+}
+
 void AmbientModeHandler::SendTopicSource() {
   DCHECK(settings_);
   FireWebUIListener("topic-source-changed",
@@ -237,6 +286,7 @@
   }
 
   SendTopicSource();
+  SendTemperatureUnit();
 }
 
 }  // namespace settings
diff --git a/chrome/browser/ui/webui/settings/chromeos/ambient_mode_handler.h b/chrome/browser/ui/webui/settings/chromeos/ambient_mode_handler.h
index 3e8308cc..b9047e2 100644
--- a/chrome/browser/ui/webui/settings/chromeos/ambient_mode_handler.h
+++ b/chrome/browser/ui/webui/settings/chromeos/ambient_mode_handler.h
@@ -41,20 +41,26 @@
  private:
   friend class AmbientModeHandlerTest;
 
-  // WebUI call to request topic source related data.
-  void HandleRequestTopicSource(const base::ListValue* args);
+  // WebUI call to request topic source and temperature unit related data.
+  void HandleRequestSettings(const base::ListValue* args);
 
   // WebUI call to request albums related data.
   void HandleRequestAlbums(const base::ListValue* args);
 
+  // WebUI call to sync temperature unit with server.
+  void HandleSetSelectedTemperatureUnit(const base::ListValue* args);
+
   // WebUI call to sync topic source with server.
   void HandleSetSelectedTopicSource(const base::ListValue* args);
 
   // WebUI call to sync albums with server.
   void HandleSetSelectedAlbums(const base::ListValue* args);
 
-  // Send the "topic-source-changed" WebUIListener event when the initial
-  // settings is retrieved.
+  // Send the "temperature-unit-changed" WebUIListener event to update the
+  // WebUI.
+  void SendTemperatureUnit();
+
+  // Send the "topic-source-changed" WebUIListener event to update the WebUI.
   void SendTopicSource();
 
   // Send the "albums-changed" WebUIListener event with albums info
diff --git a/chrome/browser/ui/webui/settings/chromeos/ambient_mode_handler_unittest.cc b/chrome/browser/ui/webui/settings/chromeos/ambient_mode_handler_unittest.cc
index d54803a..5591be1 100644
--- a/chrome/browser/ui/webui/settings/chromeos/ambient_mode_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/ambient_mode_handler_unittest.cc
@@ -47,9 +47,9 @@
         std::make_unique<ash::FakeAmbientBackendControllerImpl>();
   }
 
-  void RequestTopicSource() {
+  void RequestSettings() {
     base::ListValue args;
-    handler_->HandleRequestTopicSource(&args);
+    handler_->HandleRequestSettings(&args);
   }
 
   void RequestAlbums(int topic_source) {
@@ -58,19 +58,32 @@
     handler_->HandleRequestAlbums(&args);
   }
 
-  void VerifyTopicSourceSent(base::RunLoop* run_loop) {
-    EXPECT_EQ(1U, web_ui_->call_data().size());
+  std::string BoolToString(bool x) { return x ? "true" : "false"; }
 
-    const content::TestWebUI::CallData& call_data =
-        *web_ui_->call_data().back();
+  void VerifySettingsSent(base::RunLoop* run_loop) {
+    EXPECT_EQ(2U, web_ui_->call_data().size());
 
     // The call is structured such that the function name is the "web callback"
     // name and the first argument is the name of the message being sent.
-    EXPECT_EQ(kWebCallbackFunctionName, call_data.function_name());
-    EXPECT_EQ("topic-source-changed", call_data.arg1()->GetString());
+    const auto& topic_source_call_data = *web_ui_->call_data().front();
+    const auto& temperature_unit_call_data = *web_ui_->call_data().back();
+
+    // Topic Source
+    EXPECT_EQ(kWebCallbackFunctionName, topic_source_call_data.function_name());
+    EXPECT_EQ("topic-source-changed",
+              topic_source_call_data.arg1()->GetString());
     // In FakeAmbientBackendControllerImpl, the |topic_source| is
     // kGooglePhotos.
-    EXPECT_EQ(0, call_data.arg2()->GetInt());
+    EXPECT_EQ(0, topic_source_call_data.arg2()->GetInt());
+
+    // Temperature Unit
+    EXPECT_EQ(kWebCallbackFunctionName,
+              temperature_unit_call_data.function_name());
+    EXPECT_EQ("temperature-unit-changed",
+              temperature_unit_call_data.arg1()->GetString());
+    // In FakeAmbientBackendControllerImpl, the |temperature_unit| is kCelsius.
+    EXPECT_EQ("celsius", temperature_unit_call_data.arg2()->GetString());
+
     run_loop->Quit();
   }
 
@@ -128,13 +141,13 @@
   std::unique_ptr<TestAmbientModeHandler> handler_;
 };
 
-TEST_F(AmbientModeHandlerTest, TestSendTopicSource) {
-  RequestTopicSource();
+TEST_F(AmbientModeHandlerTest, TestSendTemperatureUnitAndTopicSource) {
+  RequestSettings();
 
   base::RunLoop run_loop;
   base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
       FROM_HERE,
-      base::BindOnce(&AmbientModeHandlerTest::VerifyTopicSourceSent,
+      base::BindOnce(&AmbientModeHandlerTest::VerifySettingsSent,
                      base::Unretained(this), &run_loop),
       base::TimeDelta::FromSeconds(1));
   run_loop.Run();
diff --git a/chrome/browser/ui/webui/settings/chromeos/personalization_section.cc b/chrome/browser/ui/webui/settings/chromeos/personalization_section.cc
index a49f06c..8db3fdc 100644
--- a/chrome/browser/ui/webui/settings/chromeos/personalization_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/personalization_section.cc
@@ -180,6 +180,11 @@
        IDS_OS_SETTINGS_AMBIENT_MODE_TOPIC_SOURCE_SUBPAGE},
       {"ambientModeAlbumsSubpageGooglePhotosNoAlbum",
        IDS_OS_SETTINGS_AMBIENT_MODE_ALBUMS_SUBPAGE_GOOGLE_PHOTOS_NO_ALBUM},
+      {"ambientModeWeatherTitle", IDS_OS_SETTINGS_AMBIENT_MODE_WEATHER_TITLE},
+      {"ambientModeTemperatureUnitFahrenheit",
+       IDS_OS_SETTINGS_AMBIENT_MODE_TEMPERATURE_UNIT_FAHRENHEIT},
+      {"ambientModeTemperatureUnitCelsius",
+       IDS_OS_SETTINGS_AMBIENT_MODE_TEMPERATURE_UNIT_CELSIUS},
       {"changePictureTitle", IDS_OS_SETTINGS_CHANGE_PICTURE_TITLE},
       {"openWallpaperApp", IDS_OS_SETTINGS_OPEN_WALLPAPER_APP},
       {"personalizationPageTitle", IDS_OS_SETTINGS_PERSONALIZATION},
diff --git a/chrome/browser/ui/webui/settings/people_handler.cc b/chrome/browser/ui/webui/settings/people_handler.cc
index 30844c9..702dce69 100644
--- a/chrome/browser/ui/webui/settings/people_handler.cc
+++ b/chrome/browser/ui/webui/settings/people_handler.cc
@@ -702,10 +702,13 @@
           delete_profile ? signin_metrics::SignoutDelete::DELETED
                          : signin_metrics::SignoutDelete::KEEPING;
 
-      // Do not remove the accounts: the Gaia logout tab will remove them in a
-      // better way (see http://crbug.com/1068978).
+      // Use ClearAccountsAction::kDefault: if the primary account is still
+      // valid, it will be removed by the Gaia logout tab
+      // (see http://crbug.com/1068978). If the account is already invalid, drop
+      // the token now (because it's already invalid on the web, so the Gaia
+      // logout tab won't affect it, see http://crbug.com/1114646).
       identity_manager->GetPrimaryAccountMutator()->ClearPrimaryAccount(
-          signin::PrimaryAccountMutator::ClearAccountsAction::kKeepAll,
+          signin::PrimaryAccountMutator::ClearAccountsAction::kDefault,
           signin_metrics::USER_CLICKED_SIGNOUT_SETTINGS, delete_metric);
     } else {
       DCHECK(!delete_profile)
diff --git a/chrome/browser/vr/ui_host/vr_ui_host_impl.cc b/chrome/browser/vr/ui_host/vr_ui_host_impl.cc
index 35dc73d..bb26020 100644
--- a/chrome/browser/vr/ui_host/vr_ui_host_impl.cc
+++ b/chrome/browser/vr/ui_host/vr_ui_host_impl.cc
@@ -17,7 +17,7 @@
 #include "chrome/browser/vr/vr_tab_helper.h"
 #include "chrome/browser/vr/win/vr_browser_renderer_thread_win.h"
 #include "components/content_settings/browser/content_settings_usages_state.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/permissions/permission_manager.h"
 #include "components/permissions/permission_result.h"
 #include "content/public/browser/device_service.h"
@@ -393,8 +393,8 @@
   CapturingStateModel active_capturing = active_capturing_;
   // TODO(https://crbug.com/1103176): Plumb the actual frame reference here (we
   // should get a RFH from VRServiceImpl instead of WebContents)
-  content_settings::TabSpecificContentSettings* settings =
-      content_settings::TabSpecificContentSettings::GetForFrame(
+  content_settings::PageSpecificContentSettings* settings =
+      content_settings::PageSpecificContentSettings::GetForFrame(
           web_contents_->GetMainFrame());
   if (settings) {
     const ContentSettingsUsagesState& usages_state =
@@ -408,15 +408,15 @@
 
     active_capturing.audio_capture_enabled =
         (settings->GetMicrophoneCameraState() &
-         content_settings::TabSpecificContentSettings::MICROPHONE_ACCESSED) &&
+         content_settings::PageSpecificContentSettings::MICROPHONE_ACCESSED) &&
         !(settings->GetMicrophoneCameraState() &
-          content_settings::TabSpecificContentSettings::MICROPHONE_BLOCKED);
+          content_settings::PageSpecificContentSettings::MICROPHONE_BLOCKED);
 
     active_capturing.video_capture_enabled =
         (settings->GetMicrophoneCameraState() &
-         content_settings::TabSpecificContentSettings::CAMERA_ACCESSED) &
+         content_settings::PageSpecificContentSettings::CAMERA_ACCESSED) &
         !(settings->GetMicrophoneCameraState() &
-          content_settings::TabSpecificContentSettings::CAMERA_BLOCKED);
+          content_settings::PageSpecificContentSettings::CAMERA_BLOCKED);
 
     active_capturing.midi_connected =
         settings->IsContentAllowed(ContentSettingsType::MIDI_SYSEX);
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 678128f..6b6c84d 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-master-1597037945-c748d93b7b3b8df201950e67d57a776238968dea.profdata
+chrome-mac-master-1597060771-94a7300bd1819f6d10eaf4c394bfbc10cebf8f71.profdata
diff --git a/chrome/common/BUILD.gn b/chrome/common/BUILD.gn
index fa4daa47..76204d1 100644
--- a/chrome/common/BUILD.gn
+++ b/chrome/common/BUILD.gn
@@ -404,6 +404,8 @@
   if (is_android) {
     sources -= [ "media_galleries/metadata_types.h" ]
     sources += [
+      "android/cpu_affinity_experiments.cc",
+      "android/cpu_affinity_experiments.h",
       "media/chrome_media_drm_bridge_client.cc",
       "media/chrome_media_drm_bridge_client.h",
     ]
diff --git a/chrome/common/android/cpu_affinity_experiments.cc b/chrome/common/android/cpu_affinity_experiments.cc
new file mode 100644
index 0000000..e3a3a048
--- /dev/null
+++ b/chrome/common/android/cpu_affinity_experiments.cc
@@ -0,0 +1,37 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/android/cpu_affinity_experiments.h"
+
+#include "base/cpu_affinity_posix.h"
+#include "base/feature_list.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/process/process_handle.h"
+
+namespace chrome {
+
+namespace {
+
+const base::Feature kCpuAffinityRestrictToLittleCores{
+    "CpuAffinityRestrictToLittleCores", base::FEATURE_DISABLED_BY_DEFAULT};
+
+}  // namespace
+
+void InitializeCpuAffinityExperiments() {
+  if (!base::FeatureList::IsEnabled(kCpuAffinityRestrictToLittleCores))
+    return;
+
+  // Restrict affinity of all existing threads of the current process. The
+  // affinity is inherited by any subsequently created thread. While
+  // InitializeThreadAffinityExperiments() is called early during startup, other
+  // threads (e.g. Java threads like the RenderThread) may already exist, so
+  // setting the affinity only for the current thread is not enough here.
+  bool success = base::SetProcessCpuAffinityMode(
+      base::GetCurrentProcessHandle(), base::CpuAffinityMode::kLittleCoresOnly);
+
+  base::UmaHistogramBoolean(
+      "Power.CpuAffinityExperiments.ProcessAffinityUpdateSuccess", success);
+}
+
+}  // namespace chrome
diff --git a/chrome/common/android/cpu_affinity_experiments.h b/chrome/common/android/cpu_affinity_experiments.h
new file mode 100644
index 0000000..bf43645
--- /dev/null
+++ b/chrome/common/android/cpu_affinity_experiments.h
@@ -0,0 +1,17 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_ANDROID_CPU_AFFINITY_EXPERIMENTS_H_
+#define CHROME_COMMON_ANDROID_CPU_AFFINITY_EXPERIMENTS_H_
+
+namespace chrome {
+
+// Setup CPU-affinity restriction experiments (e.g. to restrict execution to
+// little cores only) for the current process, based on the feature list. Should
+// be called during process startup after feature list initialization.
+void InitializeCpuAffinityExperiments();
+
+}  // namespace chrome
+
+#endif  // CHROME_COMMON_ANDROID_CPU_AFFINITY_EXPERIMENTS_H_
diff --git a/chrome/credential_provider/gaiacp/gaia_credential_base.cc b/chrome/credential_provider/gaiacp/gaia_credential_base.cc
index 9ad1166e..39336eb 100644
--- a/chrome/credential_provider/gaiacp/gaia_credential_base.cc
+++ b/chrome/credential_provider/gaiacp/gaia_credential_base.cc
@@ -308,6 +308,7 @@
     std::vector<base::string16> filtered_local_account_names_no_sn;
 
     for (auto local_account_name : local_account_names) {
+      LOGFN(VERBOSE) << "CD local account name : " << local_account_name;
       // The format for local_account_name custom attribute is
       // "un:abcd,sn:1234" where "un:abcd" would always exist and "sn:1234" is
       // optional.
@@ -315,12 +316,16 @@
       std::string serial_number;
       // Note: "?:" is used to signify non-capturing groups. For more details,
       // look at https://github.com/google/re2/wiki/Syntax link.
-      re2::RE2::FullMatch(local_account_name, "un:([^,]+)(?:,sn:(\\w+))?",
+      re2::RE2::FullMatch(local_account_name, "un:([^,]+)(?:,sn:([^,]+))?",
                           &username, &serial_number);
 
+      LOGFN(VERBOSE) << "RE2 username : " << username;
+      LOGFN(VERBOSE) << "RE2 serial_number : " << serial_number;
+
       if (!username.empty() && !serial_number.empty()) {
         std::string device_serial_number =
             base::UTF16ToUTF8(GetSerialNumber().c_str());
+        LOGFN(VERBOSE) << "Device serial_number : " << device_serial_number;
         if (base::EqualsCaseInsensitiveASCII(serial_number,
                                              device_serial_number))
           filtered_local_account_names.push_back(base::UTF8ToUTF16(username));
@@ -2325,6 +2330,14 @@
         if (FAILED(hr))
           LOGFN(ERROR) << "SetUserFullname hr=" << putHR(hr);
       }
+
+      // Set disable password change policy here as well. This flow would
+      // make sure password change is disabled even if any end user tries
+      // to enable it via registry after user create or association flow.
+      // Note: We donot fail the login flow if password policies were not
+      // applied for unknown reasons.
+      OSUserManager::Get()->SetDefaultPasswordChangePolicies(found_domain,
+                                                             found_username);
     } else {
       LOGFN(ERROR) << "GetUserFullname hr=" << putHR(hr);
     }
diff --git a/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc b/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc
index 2f0c3c5..9387b7d 100644
--- a/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc
+++ b/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc
@@ -2029,10 +2029,11 @@
 // logged in.
 class GaiaCredentialBaseCloudLocalAccountSuccessTest
     : public GcpGaiaCredentialBaseCloudLocalAccountTest,
-      public ::testing::WithParamInterface<bool> {};
+      public ::testing::WithParamInterface<std::tuple<bool, const wchar_t*>> {};
 
 TEST_P(GaiaCredentialBaseCloudLocalAccountSuccessTest, SerialNumber) {
-  bool set_serial_number = GetParam();
+  bool set_serial_number = std::get<0>(GetParam());
+  const wchar_t* serial_number = std::get<1>(GetParam());
 
   // Add the user as a local user.
   const wchar_t user_name[] = L"local_user";
@@ -2052,7 +2053,6 @@
 
   std::string admin_sdk_response;
   // Set a fake serial number.
-  base::string16 serial_number = L"1234";
   GoogleRegistrationDataForTesting g_registration_data(serial_number);
 
   if (set_serial_number) {
@@ -2061,7 +2061,7 @@
         "{\"customSchemas\": {\"Enhanced_desktop_security\": "
         "{\"Local_Windows_accounts\":"
         "[{ \"value\": \"un:%ls,sn:%ls\"}]}}}",
-        user_name, serial_number.c_str());
+        user_name, serial_number);
   } else {
     // Set valid response from admin sdk.
     admin_sdk_response = base::StringPrintf(
@@ -2108,9 +2108,15 @@
   ASSERT_TRUE(test->IsAuthenticationResultsEmpty());
 }
 
-INSTANTIATE_TEST_SUITE_P(All,
-                         GaiaCredentialBaseCloudLocalAccountSuccessTest,
-                         ::testing::Values(true, false));
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    GaiaCredentialBaseCloudLocalAccountSuccessTest,
+    ::testing::Combine(
+        ::testing::Bool(),
+        ::testing::Values(L"!@#!",        // All non alphanumeric characters
+                          L"serial#123",  // Contains non-alphanumeric chars.
+                          L"serial123!"   // Ends with non alphanumeric chars.
+                          )));
 
 // Existing cloud local account login scenario that was configured incorrectly.
 class GaiaCredentialBaseCDUsernameSuccessTest
@@ -2196,6 +2202,9 @@
   ASSERT_EQ(S_OK, hr);
   ASSERT_EQ(0u, error);
 
+  // Set fake serial number.
+  GoogleRegistrationDataForTesting g_registration_data(serial_number);
+
   // Set token result as a valid access token.
   fake_http_url_fetcher_factory()->SetFakeResponse(
       GURL(gaia_urls_->oauth2_token_url().spec().c_str()),
@@ -2232,10 +2241,9 @@
 INSTANTIATE_TEST_SUITE_P(
     All,
     GaiaCredentialBaseCDSerialNumberFailureTest,
-    ::testing::Values(L"!@#!",        // All non alphanumeric characters
-                      L"serial#123",  // Contains non-alphanumeric chars.
-                      L"serial123!",  // Ends with non alphanumeric chars.
-                      L""));
+    ::testing::Values(
+        L""  // Except for empty string all other characters are allowed chars.
+        ));
 
 // Tests various sign in scenarios with consumer and non-consumer domains.
 // Parameters are:
diff --git a/chrome/credential_provider/gaiacp/os_user_manager.cc b/chrome/credential_provider/gaiacp/os_user_manager.cc
index 07bc5ae..c7912e03 100644
--- a/chrome/credential_provider/gaiacp/os_user_manager.cc
+++ b/chrome/credential_provider/gaiacp/os_user_manager.cc
@@ -318,6 +318,22 @@
   return (nsts == NERR_Success ? S_OK : HRESULT_FROM_WIN32(nsts));
 }
 
+HRESULT OSUserManager::SetDefaultPasswordChangePolicies(
+    const wchar_t* domain,
+    const wchar_t* username) {
+  USER_INFO_1008 info1008;
+  DWORD error;
+  memset(&info1008, 0, sizeof(info1008));
+  info1008.usri1008_flags =
+      UF_PASSWD_CANT_CHANGE | UF_DONT_EXPIRE_PASSWD | UF_NORMAL_ACCOUNT;
+  NET_API_STATUS nsts = ::NetUserSetInfo(
+      domain, username, 1008, reinterpret_cast<LPBYTE>(&info1008), &error);
+  if (nsts != NERR_Success) {
+    LOGFN(ERROR) << "NetUserSetInfo(set password policies) nsts=" << nsts;
+  }
+  return HRESULT_FROM_WIN32(nsts);
+}
+
 HRESULT OSUserManager::ChangeUserPassword(const wchar_t* domain,
                                           const wchar_t* username,
                                           const wchar_t* old_password,
diff --git a/chrome/credential_provider/gaiacp/os_user_manager.h b/chrome/credential_provider/gaiacp/os_user_manager.h
index bfba5f9..d21239b 100644
--- a/chrome/credential_provider/gaiacp/os_user_manager.h
+++ b/chrome/credential_provider/gaiacp/os_user_manager.h
@@ -91,6 +91,10 @@
   virtual HRESULT GetUserFullname(
       const wchar_t* domain, const wchar_t* username, base::string16* fullname);
 
+  // Sets restrictive password change policies for the end user account.
+  virtual HRESULT SetDefaultPasswordChangePolicies(const wchar_t* domain,
+                                                   const wchar_t* username);
+
   // Changes the user's valid access hours to effectively allow or disallow them
   // from signing in to the system. If |allow| is false then the user is not
   // allowed to sign on at any hour of the day. If |allow| is true, then the
diff --git a/chrome/credential_provider/test/gcp_fakes.cc b/chrome/credential_provider/test/gcp_fakes.cc
index 5113a1d..67f3bb5 100644
--- a/chrome/credential_provider/test/gcp_fakes.cc
+++ b/chrome/credential_provider/test/gcp_fakes.cc
@@ -389,6 +389,12 @@
   return S_OK;
 }
 
+HRESULT FakeOSUserManager::SetDefaultPasswordChangePolicies(
+    const wchar_t* domain,
+    const wchar_t* username) {
+  return S_OK;
+}
+
 FakeOSUserManager::UserInfo::UserInfo(const wchar_t* domain,
                                       const wchar_t* password,
                                       const wchar_t* fullname,
diff --git a/chrome/credential_provider/test/gcp_fakes.h b/chrome/credential_provider/test/gcp_fakes.h
index c4ccbafd..eba5478 100644
--- a/chrome/credential_provider/test/gcp_fakes.h
+++ b/chrome/credential_provider/test/gcp_fakes.h
@@ -131,6 +131,9 @@
                                          const wchar_t* username,
                                          bool allow) override;
 
+  HRESULT SetDefaultPasswordChangePolicies(const wchar_t* domain,
+                                           const wchar_t* username) override;
+
   bool IsDeviceDomainJoined() override;
 
   void SetIsDeviceDomainJoined(bool is_device_domain_joined) {
diff --git a/chrome/installer/mini_installer/BUILD.gn b/chrome/installer/mini_installer/BUILD.gn
index 047b71e..7c7af8c 100644
--- a/chrome/installer/mini_installer/BUILD.gn
+++ b/chrome/installer/mini_installer/BUILD.gn
@@ -76,7 +76,6 @@
     "mini_file_test.cc",
     "mini_installer_unittest.cc",
     "mini_string_test.cc",
-    "pe_resource_test.cc",
   ]
 
   public_deps = [ ":lib" ]
diff --git a/chrome/installer/mini_installer/decompress.cc b/chrome/installer/mini_installer/decompress.cc
index 7ace7a94..de58600 100644
--- a/chrome/installer/mini_installer/decompress.cc
+++ b/chrome/installer/mini_installer/decompress.cc
@@ -22,7 +22,7 @@
   const wchar_t* const dest_path;
 
   // The destination file; valid once the destination is created.
-  mini_installer::MiniFile& dest_file;
+  mini_installer::MiniFile dest_file;
 
   // Set to true if the file was extracted to |dest_path|. Note that |dest_file|
   // may be valid even in case of failure.
@@ -247,7 +247,7 @@
 
 namespace mini_installer {
 
-bool Expand(const wchar_t* source, const wchar_t* destination, MiniFile& file) {
+bool Expand(const wchar_t* source, const wchar_t* destination) {
   if (!InitializeFdi())
     return false;
 
@@ -274,7 +274,7 @@
   if (!fdi)
     return false;
 
-  ExpandContext context = {destination, file, /*succeeded=*/false};
+  ExpandContext context = {destination, {}, /*succeeded=*/false};
   g_FDICopy(fdi, source_name_utf8, source_path_utf8, 0, &Notify, nullptr,
             &context);
   g_FDIDestroy(fdi);
@@ -282,7 +282,8 @@
     return true;
 
   // Delete the output file if it was created.
-  file.Close();
+  if (context.dest_file.IsValid())
+    context.dest_file.DeleteOnClose();
 
   return false;
 }
diff --git a/chrome/installer/mini_installer/decompress.h b/chrome/installer/mini_installer/decompress.h
index 1a57754..5e11af8d 100644
--- a/chrome/installer/mini_installer/decompress.h
+++ b/chrome/installer/mini_installer/decompress.h
@@ -7,14 +7,11 @@
 
 namespace mini_installer {
 
-class MiniFile;
-
-// Expands the first file in |source| to the file |destination| using
-// Microsoft's MSCF compression algorithm (a la expand.exe). Returns true on
-// success, in which case |file| holds an open handle to the destination file.
-// |file| will be opened with exclusive write access and shared read and delete
-// access, and will be marked as delete-on-close.
-bool Expand(const wchar_t* source, const wchar_t* destination, MiniFile& file);
+// Same as the tool, expand.exe.  Decompresses a file that was compressed
+// using Microsoft's MSCF compression algorithm.
+// |source| is the full path of the file to decompress and |destination|
+// is the full path of the target file.
+bool Expand(const wchar_t* source, const wchar_t* destination);
 
 }  // namespace mini_installer
 
diff --git a/chrome/installer/mini_installer/decompress_test.cc b/chrome/installer/mini_installer/decompress_test.cc
index ab77a29..3dd0c97d 100644
--- a/chrome/installer/mini_installer/decompress_test.cc
+++ b/chrome/installer/mini_installer/decompress_test.cc
@@ -5,11 +5,9 @@
 #include <windows.h>
 
 #include "base/files/file_path.h"
-#include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/path_service.h"
 #include "chrome/installer/mini_installer/decompress.h"
-#include "chrome/installer/mini_installer/mini_file.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 TEST(MiniDecompressTest, ExpandTest) {
@@ -29,21 +27,11 @@
       temp_dir.GetPath().Append(FILE_PATH_LITERAL("setup.exe")));
 
   // Decompress our test file.
-  mini_installer::MiniFile file(mini_installer::MiniFile::DeleteOnClose::kYes);
-  ASSERT_TRUE(mini_installer::Expand(source_path.value().c_str(),
-                                     dest_path.value().c_str(), file));
-  ASSERT_TRUE(file.IsValid());
-  ASSERT_PRED1(base::PathExists, dest_path);
-
-  // Drop write permission so that the call below can open the file.
-  ASSERT_TRUE(file.DropWritePermission());
+  EXPECT_TRUE(mini_installer::Expand(source_path.value().c_str(),
+                                     dest_path.value().c_str()));
 
   // Check if the expanded file is a valid executable.
   DWORD type = static_cast<DWORD>(-1);
   EXPECT_TRUE(GetBinaryType(dest_path.value().c_str(), &type));
   EXPECT_EQ(static_cast<DWORD>(SCS_32BIT_BINARY), type);
-
-  // Closing the handle should delete the file.
-  file.Close();
-  EXPECT_FALSE(base::PathExists(dest_path));
 }
diff --git a/chrome/installer/mini_installer/exit_code.h b/chrome/installer/mini_installer/exit_code.h
index 6d864e67..729c1b8 100644
--- a/chrome/installer/mini_installer/exit_code.h
+++ b/chrome/installer/mini_installer/exit_code.h
@@ -42,11 +42,6 @@
   RUN_SETUP_FAILED_FILE_NOT_FOUND = 122,            // ERROR_FILE_NOT_FOUND.
   RUN_SETUP_FAILED_PATH_NOT_FOUND = 123,            // ERROR_PATH_NOT_FOUND.
   RUN_SETUP_FAILED_COULD_NOT_CREATE_PROCESS = 124,  // All other errors.
-  UNABLE_TO_OPEN_PATCHED_SETUP = 125,
-  DROP_WRITE_ON_EXTRACTED_ARCHIVE_FAILED = 126,
-  DROP_WRITE_ON_EXTRACTED_SETUP_PATCH_FAILED = 127,
-  DROP_WRITE_ON_EXTRACTED_SETUP_FAILED = 128,
-  DROP_WRITE_ON_EXPANDED_SETUP_FAILED = 129,
 };
 
 }  // namespace mini_installer
diff --git a/chrome/installer/mini_installer/mini_file.cc b/chrome/installer/mini_installer/mini_file.cc
index 790d097..2e04f68 100644
--- a/chrome/installer/mini_installer/mini_file.cc
+++ b/chrome/installer/mini_installer/mini_file.cc
@@ -8,10 +8,7 @@
 
 namespace mini_installer {
 
-MiniFile::MiniFile(DeleteOnClose delete_on_close)
-    : delete_on_close_flag_(delete_on_close != DeleteOnClose::kNo
-                                ? FILE_FLAG_DELETE_ON_CLOSE
-                                : 0) {}
+MiniFile::MiniFile() = default;
 
 MiniFile::~MiniFile() {
   Close();
@@ -29,11 +26,9 @@
   Close();
   if (!path_.assign(path))
     return false;
-  handle_ = ::CreateFileW(path_.get(), GENERIC_WRITE,
-                          FILE_SHARE_DELETE | FILE_SHARE_READ,
-                          /*lpSecurityAttributes=*/nullptr, CREATE_ALWAYS,
-                          FILE_ATTRIBUTE_NORMAL | delete_on_close_flag_,
-                          /*hTemplateFile=*/nullptr);
+  handle_ =
+      ::CreateFileW(path_.get(), DELETE | GENERIC_WRITE, FILE_SHARE_DELETE,
+                    nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
   if (handle_ != INVALID_HANDLE_VALUE)
     return true;
   path_.clear();
@@ -44,76 +39,11 @@
   return handle_ != INVALID_HANDLE_VALUE;
 }
 
-bool MiniFile::DropWritePermission() {
-  // The original file was opened with write access (of course), so it will take
-  // a little hoop jumping to get a handle without it. First, get a new handle
-  // that doesn't have write access. This one must allow others to write on
-  // account of the fact that the original handle has write access.
-  HANDLE without_write =
-      ::ReOpenFile(handle_, GENERIC_READ,
-                   FILE_SHARE_DELETE | FILE_SHARE_WRITE | FILE_SHARE_READ,
-                   delete_on_close_flag_);
-  if (without_write == INVALID_HANDLE_VALUE) {
-    const auto error = ::GetLastError();
-    Close();
-    ::SetLastError(error);
-    return false;
-  }
-
-  // Next, close the original handle so that there are no longer any writers.
-  // This will mark the file for deletion if the original handle was opened with
-  // FILE_FLAG_DELETE_ON_CLOSE.
-  ::CloseHandle(std::exchange(handle_, INVALID_HANDLE_VALUE));
-
-  // Now unmark the file for deletion if needed.
-  if (delete_on_close_flag_) {
-    FILE_DISPOSITION_INFO disposition = {/*DeleteFile=*/FALSE};
-    if (!::SetFileInformationByHandle(without_write, FileDispositionInfo,
-                                      &disposition, sizeof(disposition))) {
-      const auto error = ::GetLastError();
-      ::CloseHandle(std::exchange(without_write, INVALID_HANDLE_VALUE));
-      Close();
-      ::SetLastError(error);
-      return false;
-    }
-  }
-
-  // Now open a read-only handle (with FILE_FLAG_DELETE_ON_CLOSE as needed) that
-  // doesn't allow others to write. Note that there is a potential race here:
-  // another party could open the file for shared write access at this precise
-  // moment, causing this ReOpenFile to fail. This would likely be an issue
-  // anyway, as one common thing to do with the file is to execute it, which
-  // will fail if there are writers.
-  handle_ =
-      ::ReOpenFile(without_write, GENERIC_READ,
-                   FILE_SHARE_DELETE | FILE_SHARE_READ, delete_on_close_flag_);
-  if (handle_ == INVALID_HANDLE_VALUE) {
-    const auto error = ::GetLastError();
-    ::CloseHandle(std::exchange(without_write, INVALID_HANDLE_VALUE));
-    Close();
-    ::SetLastError(error);
-    return false;
-  }
-
-  // Closing the handle that allowed shared writes may once again mark the file
-  // for deletion.
-  ::CloseHandle(std::exchange(without_write, INVALID_HANDLE_VALUE));
-
-  // Everything went according to plan; |handle_| is now lacking write access
-  // and does not allow other writers. The last step is to unmark the file for
-  // deletion once again, as the closure of |without_write| has re-marked it.
-  if (delete_on_close_flag_) {
-    FILE_DISPOSITION_INFO disposition = {/*DeleteFile=*/FALSE};
-    if (!::SetFileInformationByHandle(handle_, FileDispositionInfo,
-                                      &disposition, sizeof(disposition))) {
-      const auto error = ::GetLastError();
-      Close();
-      ::SetLastError(error);
-      return false;
-    }
-  }
-
-  return true;
+bool MiniFile::DeleteOnClose() {
+  FILE_DISPOSITION_INFO disposition = {/*DeleteFile=*/TRUE};
+  return IsValid() &&
+         ::SetFileInformationByHandle(handle_, FileDispositionInfo,
+                                      &disposition, sizeof(disposition));
 }
 
 void MiniFile::Close() {
@@ -134,18 +64,6 @@
              : INVALID_HANDLE_VALUE;
 }
 
-bool MiniFile::Open(const PathString& path) {
-  Close();
-  handle_ = ::CreateFileW(path.get(), GENERIC_READ,
-                          FILE_SHARE_DELETE | FILE_SHARE_READ,
-                          /*lpSecurityAttributes=*/nullptr, OPEN_EXISTING,
-                          delete_on_close_flag_, /*hTemplateFile=*/nullptr);
-  if (handle_ == INVALID_HANDLE_VALUE)
-    return false;
-  path_.assign(path);
-  return true;
-}
-
 HANDLE MiniFile::GetHandleUnsafe() const {
   return handle_;
 }
diff --git a/chrome/installer/mini_installer/mini_file.h b/chrome/installer/mini_installer/mini_file.h
index e0989002..16dd702 100644
--- a/chrome/installer/mini_installer/mini_file.h
+++ b/chrome/installer/mini_installer/mini_file.h
@@ -14,11 +14,10 @@
 // A simple abstraction over a path to a file and a Windows file handle to it.
 class MiniFile {
  public:
-  enum class DeleteOnClose : bool { kNo = false, kYes = true };
-  explicit MiniFile(DeleteOnClose delete_on_close);
+  MiniFile();
 
   // Closes the file if the instance holds a valid handle. The file will be
-  // deleted if the instance was constructed with |delete_on_close|.
+  // deleted if directed by a call to DeleteOnClose().
   ~MiniFile();
 
   MiniFile(const MiniFile&) = delete;
@@ -36,28 +35,17 @@
   // Returns true if this object has a path and a handle to an open file.
   bool IsValid() const;
 
-  // Drops write permission on the file handle so that other parties that
-  // require no writers may open the file. In particular, the Windows loader
-  // opens files for execution with shared read/delete access, as do the
-  // extraction operations in Chrome's mini_installer.exe and setup.exe. These
-  // would fail with sharing violations if mini_installer were to hold files
-  // open with write permissions. Returns false on failure, in which case the
-  // instance is no longer valid.  The file will have been deleted if the
-  // instance was created with DeleteOnClose.
-  bool DropWritePermission();
+  // Marks the file for deletion when the handle is closed via Close() or the
+  // instance's destructor. This state follows the handle when moved.
+  bool DeleteOnClose();
 
-  // Closes the handle and clears the path. The file will be deleted if the
-  // instance was constructed with |delete_on_close|. Following this, IsValid()
-  // will return false.
+  // Closes the handle and clears the path. Following this, IsValid() will
+  // return false.
   void Close();
 
   // Returns a new handle to the file, or INVALID_HANDLE_VALUE on error.
   HANDLE DuplicateHandle() const;
 
-  // Opens the file for read access, disallowing writers (as if Create followed
-  // by DropWritePermission).
-  bool Open(const PathString& path);
-
   // Returns the path to the open file, or a pointer to an empty string if
   // IsValid() is false.
   const wchar_t* path() const { return path_.get(); }
@@ -73,10 +61,6 @@
 
   // A handle to the open file, or INVALID_HANDLE_VALUE.
   HANDLE handle_ = INVALID_HANDLE_VALUE;
-
-  // Zero or FILE_FLAG_DELETE_ON_CLOSE, according to how the instance was
-  // constructed.
-  const DWORD delete_on_close_flag_;
 };
 
 }  // namespace mini_installer
diff --git a/chrome/installer/mini_installer/mini_file_test.cc b/chrome/installer/mini_installer/mini_file_test.cc
index 2ef4a6f..32f6ed40 100644
--- a/chrome/installer/mini_installer/mini_file_test.cc
+++ b/chrome/installer/mini_installer/mini_file_test.cc
@@ -8,25 +8,16 @@
 
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
-#include "chrome/installer/mini_installer/path_string.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace mini_installer {
 
-// A test harness for MiniFile. The MiniFile::DeleteOnClose parameter is passed
-// to the test instance's constructor.
-class MiniFileTest : public ::testing::TestWithParam<MiniFile::DeleteOnClose> {
+class MiniFileTest : public ::testing::Test {
  protected:
   void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }
 
   void TearDown() override { EXPECT_TRUE(temp_dir_.Delete()); }
 
-  static MiniFile::DeleteOnClose delete_on_close() { return GetParam(); }
-
-  static bool should_delete_on_close() {
-    return delete_on_close() != MiniFile::DeleteOnClose::kNo;
-  }
-
   const base::FilePath& temp_dir() const { return temp_dir_.GetPath(); }
 
  private:
@@ -34,73 +25,68 @@
 };
 
 // Create should create a file.
-TEST_P(MiniFileTest, Create) {
+TEST_F(MiniFileTest, Create) {
   const base::FilePath file_path = temp_dir().Append(FILE_PATH_LITERAL("HUM"));
 
-  MiniFile file(delete_on_close());
+  MiniFile file;
   ASSERT_TRUE(file.Create(file_path.value().c_str()));
-  EXPECT_TRUE(base::PathExists(file_path));
+  ASSERT_TRUE(base::PathExists(file_path));
 }
 
 // Created files should be deletable by others and should vanish when closed.
-TEST_P(MiniFileTest, CreateDeleteIsShared) {
+TEST_F(MiniFileTest, CreateDeleteIsShared) {
   const base::FilePath file_path = temp_dir().Append(FILE_PATH_LITERAL("HUM"));
 
-  MiniFile file(delete_on_close());
+  MiniFile file;
   ASSERT_TRUE(file.Create(file_path.value().c_str()));
   // DeleteFile uses POSIX semantics, so the file appears to vanish immediately.
   ASSERT_TRUE(base::DeleteFile(file_path));
   file.Close();
-  EXPECT_FALSE(base::PathExists(file_path));
+  ASSERT_FALSE(base::PathExists(file_path));
 }
 
-// Tests that a file can be opened without shared write access after write
-// permissions are dropped.
-TEST_P(MiniFileTest, DropWritePermission) {
+// DeleteOnClose should work as advertised.
+TEST_F(MiniFileTest, DeleteOnClose) {
   const base::FilePath file_path = temp_dir().Append(FILE_PATH_LITERAL("HUM"));
 
-  MiniFile file(delete_on_close());
+  MiniFile file;
   ASSERT_TRUE(file.Create(file_path.value().c_str()));
-  ASSERT_FALSE(base::File(file_path, base::File::FLAG_OPEN |
-                                         base::File::FLAG_READ |
-                                         base::File::FLAG_EXCLUSIVE_WRITE |
-                                         base::File::FLAG_SHARE_DELETE)
-                   .IsValid());
-  ASSERT_TRUE(file.DropWritePermission());
-  EXPECT_TRUE(base::File(file_path, base::File::FLAG_OPEN |
-                                        base::File::FLAG_READ |
-                                        base::File::FLAG_EXCLUSIVE_WRITE |
-                                        base::File::FLAG_SHARE_DELETE)
-                  .IsValid());
+  ASSERT_TRUE(file.DeleteOnClose());
+
+  // The file can no longer be opened now that it has been marked for deletion.
+  // Attempts to do so will fail with ERROR_ACCESS_DENIED. Under the covers, the
+  // NT status code is STATUS_DELETE_PENDING. Since base::PathExists will return
+  // false in this case, confirm the file's existence by trying to open it.
+  base::File the_file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ |
+                                     base::File::FLAG_SHARE_DELETE);
+  ASSERT_FALSE(the_file.IsValid());
+  ASSERT_EQ(the_file.error_details(), base::File::FILE_ERROR_ACCESS_DENIED);
+
+  file.Close();
+  ASSERT_FALSE(base::PathExists(file_path));
 }
 
 // Close should really close.
-TEST_P(MiniFileTest, CreateThenClose) {
+TEST_F(MiniFileTest, Close) {
   const base::FilePath file_path = temp_dir().Append(FILE_PATH_LITERAL("HUM"));
 
-  MiniFile file(delete_on_close());
+  MiniFile file;
   ASSERT_TRUE(file.Create(file_path.value().c_str()));
   file.Close();
   EXPECT_FALSE(file.IsValid());
   EXPECT_EQ(*file.path(), 0);
-
-  ASSERT_NE(base::PathExists(file_path), should_delete_on_close());
-  if (!should_delete_on_close()) {
-    // If closing should not have deleted the file, it should now be possible to
-    // open it with exclusive access.
-    EXPECT_TRUE(base::File(file_path, base::File::FLAG_OPEN |
-                                          base::File::FLAG_READ |
-                                          base::File::FLAG_EXCLUSIVE_READ |
-                                          base::File::FLAG_EXCLUSIVE_WRITE)
-                    .IsValid());
-  }
+  ASSERT_TRUE(base::PathExists(file_path));
+  base::File f(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ |
+                              base::File::FLAG_EXCLUSIVE_READ |
+                              base::File::FLAG_EXCLUSIVE_WRITE);
+  ASSERT_TRUE(f.IsValid());
 }
 
 // DuplicateHandle should work as advertized.
-TEST_P(MiniFileTest, DuplicateHandle) {
+TEST_F(MiniFileTest, DuplicateHandle) {
   const base::FilePath file_path = temp_dir().Append(FILE_PATH_LITERAL("HUM"));
 
-  MiniFile file(delete_on_close());
+  MiniFile file;
   ASSERT_TRUE(file.Create(file_path.value().c_str()));
   HANDLE dup = file.DuplicateHandle();
   ASSERT_NE(dup, INVALID_HANDLE_VALUE);
@@ -118,55 +104,10 @@
   ::CloseHandle(std::exchange(dup, INVALID_HANDLE_VALUE));
 }
 
-// Open should provide access to a file just like Create, but for a file that
-// already exists.
-TEST_P(MiniFileTest, Open) {
+TEST_F(MiniFileTest, Path) {
   const base::FilePath file_path = temp_dir().Append(FILE_PATH_LITERAL("HUM"));
 
-  ASSERT_TRUE(
-      base::File(file_path, base::File::FLAG_CREATE | base::File::FLAG_WRITE)
-          .IsValid());
-  ASSERT_TRUE(base::PathExists(file_path));
-
-  PathString path_string;
-  ASSERT_TRUE(path_string.assign(file_path.value().c_str()));
-  MiniFile file(delete_on_close());
-  ASSERT_TRUE(file.Open(path_string));
-}
-
-// Close should really close.
-TEST_P(MiniFileTest, OpenThenClose) {
-  const base::FilePath file_path = temp_dir().Append(FILE_PATH_LITERAL("HUM"));
-
-  ASSERT_TRUE(
-      base::File(file_path, base::File::FLAG_CREATE | base::File::FLAG_WRITE)
-          .IsValid());
-  ASSERT_TRUE(base::PathExists(file_path));
-
-  MiniFile file(delete_on_close());
-  PathString path_string;
-  ASSERT_TRUE(path_string.assign(file_path.value().c_str()));
-  ASSERT_TRUE(file.Open(path_string));
-  file.Close();
-  EXPECT_FALSE(file.IsValid());
-  EXPECT_EQ(*file.path(), 0);
-
-  ASSERT_NE(base::PathExists(file_path), should_delete_on_close());
-  if (!should_delete_on_close()) {
-    // If closing should not have deleted the file, it should now be possible to
-    // open it with exclusive access.
-    EXPECT_TRUE(base::File(file_path, base::File::FLAG_OPEN |
-                                          base::File::FLAG_READ |
-                                          base::File::FLAG_EXCLUSIVE_READ |
-                                          base::File::FLAG_EXCLUSIVE_WRITE)
-                    .IsValid());
-  }
-}
-
-TEST_P(MiniFileTest, Path) {
-  const base::FilePath file_path = temp_dir().Append(FILE_PATH_LITERAL("HUM"));
-
-  MiniFile file(delete_on_close());
+  MiniFile file;
   EXPECT_EQ(*file.path(), 0);
 
   ASSERT_TRUE(file.Create(file_path.value().c_str()));
@@ -176,10 +117,10 @@
   EXPECT_EQ(*file.path(), 0);
 }
 
-TEST_P(MiniFileTest, GetHandleUnsafe) {
+TEST_F(MiniFileTest, GetHandleUnsafe) {
   const base::FilePath file_path = temp_dir().Append(FILE_PATH_LITERAL("HUM"));
 
-  MiniFile file(delete_on_close());
+  MiniFile file;
   EXPECT_EQ(file.GetHandleUnsafe(), INVALID_HANDLE_VALUE);
 
   ASSERT_TRUE(file.Create(file_path.value().c_str()));
@@ -189,11 +130,4 @@
   EXPECT_EQ(file.GetHandleUnsafe(), INVALID_HANDLE_VALUE);
 }
 
-INSTANTIATE_TEST_SUITE_P(DoNotDeleteOnClose,
-                         MiniFileTest,
-                         ::testing::Values(MiniFile::DeleteOnClose::kNo));
-INSTANTIATE_TEST_SUITE_P(DeleteOnClose,
-                         MiniFileTest,
-                         ::testing::Values(MiniFile::DeleteOnClose::kYes));
-
 }  // namespace mini_installer
diff --git a/chrome/installer/mini_installer/mini_installer.cc b/chrome/installer/mini_installer/mini_installer.cc
index 1744608..a032e87 100644
--- a/chrome/installer/mini_installer/mini_installer.cc
+++ b/chrome/installer/mini_installer/mini_installer.cc
@@ -36,41 +36,32 @@
 #include <stdlib.h>
 
 #include <initializer_list>
-#include <utility>
 
 #include "build/branding_buildflags.h"
 #include "chrome/installer/mini_installer/appid.h"
 #include "chrome/installer/mini_installer/configuration.h"
 #include "chrome/installer/mini_installer/decompress.h"
-#include "chrome/installer/mini_installer/mini_file.h"
 #include "chrome/installer/mini_installer/mini_installer_constants.h"
 #include "chrome/installer/mini_installer/pe_resource.h"
 #include "chrome/installer/mini_installer/regkey.h"
 
 namespace mini_installer {
 
-namespace {
+typedef StackString<MAX_PATH> PathString;
 
 // This structure passes data back and forth for the processing
 // of resource callbacks.
 struct Context {
   // Input to the call back method. Specifies the dir to save resources.
   const wchar_t* base_path;
-  // First output from call back method; the Chrome archive.
-  MiniFile& archive;
-  // Second output from call back method; the Chrome installer.
-  MiniFile& setup;
+  // First output from call back method. Full path of Chrome archive.
+  PathString* chrome_resource_path;
+  // Second output from call back method. Full path of Setup archive/exe.
+  PathString* setup_resource_path;
   // A Windows error code corresponding to an extraction error.
   DWORD error_code;
 };
 
-MiniFile::DeleteOnClose GetDeleteOnCloseOption(
-    const Configuration& configuration) {
-  return configuration.should_delete_extracted_files()
-             ? MiniFile::DeleteOnClose::kYes
-             : MiniFile::DeleteOnClose::kNo;
-}
-
 // TODO(grt): Frame this in terms of whether or not the brand supports
 // integration with Omaha, where Google Update is the Google-specific fork of
 // the open-source Omaha project.
@@ -178,8 +169,6 @@
   return ProcessExitResult(SUCCESS_EXIT_CODE);
 }
 
-}  // namespace
-
 // Gets the path to setup.exe of the previous version. The overall path is found
 // in the Uninstall string in the registry. A previous version number specified
 // in |configuration| is used if available. |size| is measured in wchar_t units.
@@ -193,8 +182,6 @@
       configuration.previous_version(), path, size);
 }
 
-namespace {
-
 // Calls CreateProcess with good default parameters and waits for the process to
 // terminate returning the process exit code. In case of CreateProcess failure,
 // returns a results object with the provided codes as follows:
@@ -248,8 +235,6 @@
   return ProcessExitResult(exit_code);
 }
 
-}  // namespace
-
 void AppendCommandLineFlags(const wchar_t* command_line,
                             CommandString* buffer) {
   // The program name (the first argument parsed by CommandLineToArgvW) is
@@ -292,15 +277,13 @@
   buffer->append(command_line);
 }
 
-namespace {
-
 // Processes a resource of type |type| in |module| on behalf of a call to
 // EnumResourceNames. On each call, |name| contains the name of a resource. A
 // TRUE return value continues the enumeration, whereas FALSE stops it. This
 // function extracts the first resource starting with "chrome" and/or "setup",
 // populating |context| (which must be a pointer to a Context struct) with the
-// the extracted file(s). Enumeration stops early in case of error, which
-// includes any unexpected resources or duplicate matching resources.
+// path(s) of the extracted file(s). Enumeration stops early in case of error,
+// which includes any unexpected resources or duplicate matching resources.
 // |context|'s |error_code| member may be populated with a Windows error code
 // corresponding to an error condition.
 BOOL CALLBACK OnResourceFound(HMODULE module,
@@ -323,16 +306,20 @@
   if (!full_path.assign(context.base_path) || !full_path.append(name))
     return FALSE;  // Break: failed to form the output path.
 
-  if (StrStartsWith(name, kChromeArchivePrefix) && !context.archive.IsValid()) {
-    if (!resource.WriteToDisk(full_path.get(), context.archive)) {
+  if (StrStartsWith(name, kChromeArchivePrefix) &&
+      context.chrome_resource_path->empty()) {
+    if (!resource.WriteToDisk(full_path.get())) {
       context.error_code = ::GetLastError();
       return FALSE;  // Break: failed to write resource.
     }
-  } else if (StrStartsWith(name, kSetupPrefix) && !context.setup.IsValid()) {
-    if (!resource.WriteToDisk(full_path.get(), context.setup)) {
+    context.chrome_resource_path->assign(full_path);
+  } else if (StrStartsWith(name, kSetupPrefix) &&
+             context.setup_resource_path->empty()) {
+    if (!resource.WriteToDisk(full_path.get())) {
       context.error_code = ::GetLastError();
       return FALSE;  // Break: failed to write resource.
     }
+    context.setup_resource_path->assign(full_path);
   } else {
     // Break: unexpected resource names or multiple {chrome,setup}* resources
     // are unexpected.
@@ -343,41 +330,6 @@
 }
 
 #if defined(COMPONENT_BUILD)
-// Deletes all files in |base_path| (which must have a trailing path separator).
-void DeleteAllFilesInDir(const wchar_t* base_path) {
-  PathString path;
-  if (!path.assign(base_path))
-    return;
-
-  const size_t base_path_length = path.length();
-  if (!path.append(L"*"))
-    return;
-
-  WIN32_FIND_DATA find_data = {};
-  HANDLE find_handle =
-      ::FindFirstFileEx(path.get(), FindExInfoStandard, &find_data,
-                        FindExSearchNameMatch, /*lpSearchFilter=*/nullptr,
-                        /*dwAdditionalFlags=*/0);
-  if (find_handle == INVALID_HANDLE_VALUE)
-    return;
-
-  do {
-    if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
-      continue;
-
-    path.truncate_at(base_path_length);
-    if (!path.append(*find_data.cAlternateFileName
-                         ? find_data.cAlternateFileName
-                         : find_data.cFileName)) {
-      continue;
-    }
-
-    ::DeleteFile(path.get());
-  } while (::FindNextFile(find_handle, &find_data));
-
-  ::FindClose(find_handle);
-}
-
 // An EnumResNameProc callback that writes the resource |name| to disk in the
 // directory |base_path_ptr| (which must end with a path separator).
 BOOL CALLBACK WriteResourceToDirectory(HMODULE module,
@@ -388,10 +340,8 @@
   PathString full_path;
 
   PEResource resource(name, type, module);
-  MiniFile file(MiniFile::DeleteOnClose::kNo);
   return (resource.IsValid() && full_path.assign(base_path) &&
-          full_path.append(name) &&
-          resource.WriteToDisk(full_path.get(), file));
+          full_path.append(name) && resource.WriteToDisk(full_path.get()));
 }
 #endif
 
@@ -410,8 +360,8 @@
 ProcessExitResult UnpackBinaryResources(const Configuration& configuration,
                                         HMODULE module,
                                         const wchar_t* base_path,
-                                        MiniFile& archive,
-                                        MiniFile& setup) {
+                                        PathString* archive_path,
+                                        PathString* setup_path) {
   // Generate the setup.exe path where we patch/uncompress setup resource.
   PathString setup_dest_path;
   if (!setup_dest_path.assign(base_path) || !setup_dest_path.append(kSetupExe))
@@ -419,14 +369,19 @@
 
   // Prepare the input to OnResourceFound method that needs a location where
   // it will write all the resources.
-  Context context = {base_path, archive, setup, ERROR_SUCCESS};
+  Context context = {
+      base_path,
+      archive_path,
+      setup_path,
+      ERROR_SUCCESS,
+  };
 
   // Get the resources of type 'B7' (7zip archive).
   // We need a chrome archive to do the installation. So if there
   // is a problem in fetching B7 resource, just return an error.
   if (!::EnumResourceNames(module, kLZMAResourceType, OnResourceFound,
                            reinterpret_cast<LONG_PTR>(&context)) ||
-      !archive.IsValid()) {
+      archive_path->empty()) {
     const DWORD enum_error = ::GetLastError();
     return ProcessExitResult(UNABLE_TO_EXTRACT_CHROME_ARCHIVE,
                              enum_error == ERROR_RESOURCE_ENUM_USER_STOP
@@ -434,59 +389,45 @@
                                  : enum_error);
   }
 
-  if (!archive.DropWritePermission())
-    return {DROP_WRITE_ON_EXTRACTED_ARCHIVE_FAILED, ::GetLastError()};
+  ProcessExitResult exit_code = ProcessExitResult(SUCCESS_EXIT_CODE);
 
   // If we found setup 'B7' resource (used for differential updates), handle
   // it.  Note that this is only for Chrome; Chromium installs are always
   // "full" installs.
-  if (setup.IsValid()) {
-    if (!setup.DropWritePermission())
-      return {DROP_WRITE_ON_EXTRACTED_SETUP_PATCH_FAILED, ::GetLastError()};
-
+  if (!setup_path->empty()) {
     CommandString cmd_line;
     PathString exe_path;
     // Get the path to setup.exe first.
-    auto exit_code = GetPreviousSetupExePath(configuration, exe_path.get(),
-                                             exe_path.capacity());
-    if (!exit_code.IsSuccess())
-      return exit_code;
-
-    if (!cmd_line.append(L"\"") || !cmd_line.append(exe_path.get()) ||
-        !cmd_line.append(L"\" --") || !cmd_line.append(kCmdUpdateSetupExe) ||
-        !cmd_line.append(L"=\"") || !cmd_line.append(setup.path()) ||
-        !cmd_line.append(L"\" --") || !cmd_line.append(kCmdNewSetupExe) ||
-        !cmd_line.append(L"=\"") || !cmd_line.append(setup_dest_path.get()) ||
-        !cmd_line.append(L"\"")) {
-      return ProcessExitResult(COMMAND_STRING_OVERFLOW);
+    exit_code = GetPreviousSetupExePath(configuration, exe_path.get(),
+                                        exe_path.capacity());
+    if (exit_code.IsSuccess()) {
+      if (!cmd_line.append(L"\"") || !cmd_line.append(exe_path.get()) ||
+          !cmd_line.append(L"\" --") || !cmd_line.append(kCmdUpdateSetupExe) ||
+          !cmd_line.append(L"=\"") || !cmd_line.append(setup_path->get()) ||
+          !cmd_line.append(L"\" --") || !cmd_line.append(kCmdNewSetupExe) ||
+          !cmd_line.append(L"=\"") || !cmd_line.append(setup_dest_path.get()) ||
+          !cmd_line.append(L"\"")) {
+        exit_code = ProcessExitResult(COMMAND_STRING_OVERFLOW);
+      }
     }
 
     // Get any command line option specified for mini_installer and pass them
     // on to setup.exe.
     AppendCommandLineFlags(configuration.command_line(), &cmd_line);
 
-    exit_code = RunProcessAndWait(exe_path.get(), cmd_line.get(),
-                                  SETUP_PATCH_FAILED_FILE_NOT_FOUND,
-                                  SETUP_PATCH_FAILED_PATH_NOT_FOUND,
-                                  SETUP_PATCH_FAILED_COULD_NOT_CREATE_PROCESS);
-
-    // The setup patch file is no longer needed.
-    setup.Close();
-
-    if (!exit_code.IsSuccess())
-      return exit_code;
-
-    // Open the destination file (with delete-on-close) so that it will be
-    // deleted at process exit.
-    if (!setup.Open(setup_dest_path)) {
-      // Try to delete the file if it could not be opened.
-      const DWORD open_error = ::GetLastError();
-      if (configuration.should_delete_extracted_files())
-        ::DeleteFile(setup_dest_path.get());
-      return {UNABLE_TO_OPEN_PATCHED_SETUP, open_error};
+    if (exit_code.IsSuccess()) {
+      exit_code = RunProcessAndWait(
+          exe_path.get(), cmd_line.get(), SETUP_PATCH_FAILED_FILE_NOT_FOUND,
+          SETUP_PATCH_FAILED_PATH_NOT_FOUND,
+          SETUP_PATCH_FAILED_COULD_NOT_CREATE_PROCESS);
     }
 
-    return ProcessExitResult(SUCCESS_EXIT_CODE);
+    if (!exit_code.IsSuccess())
+      DeleteFile(setup_path->get());
+    else
+      setup_path->assign(setup_dest_path);
+
+    return exit_code;
   }
 
   // setup.exe wasn't sent as 'B7', lets see if it was sent as 'BL'
@@ -494,8 +435,7 @@
   context.error_code = ERROR_SUCCESS;
   if (!::EnumResourceNames(module, kLZCResourceType, OnResourceFound,
                            reinterpret_cast<LONG_PTR>(&context)) ||
-      !setup.IsValid()) {
-    // Neither setup_patch.packed.7z nor setup.ex_ could be extracted.
+      setup_path->empty()) {
     const DWORD enum_error = ::GetLastError();
     return ProcessExitResult(UNABLE_TO_EXTRACT_SETUP,
                              enum_error == ERROR_RESOURCE_ENUM_USER_STOP
@@ -503,29 +443,27 @@
                                  : enum_error);
   }
 
-  if (!setup.DropWritePermission())
-    return {DROP_WRITE_ON_EXTRACTED_SETUP_FAILED, ::GetLastError()};
-
   // Uncompress LZ compressed resource. Setup is packed with 'MSCF'
   // as opposed to old DOS way of 'SZDD'. Hence we don't use LZCopy.
-  MiniFile setup_dest(GetDeleteOnCloseOption(configuration));
-  mini_installer::Expand(setup.path(), setup_dest_path.get(), setup_dest);
-  setup = std::move(setup_dest);
-
-  if (!setup.IsValid())
-    return ProcessExitResult(UNABLE_TO_EXTRACT_SETUP_EXE);
-  if (!setup.DropWritePermission())
-    return {DROP_WRITE_ON_EXPANDED_SETUP_FAILED, ::GetLastError()};
+  bool success =
+      mini_installer::Expand(setup_path->get(), setup_dest_path.get());
+  ::DeleteFile(setup_path->get());
+  if (success)
+    setup_path->assign(setup_dest_path);
+  else
+    exit_code = ProcessExitResult(UNABLE_TO_EXTRACT_SETUP_EXE);
 
 #if defined(COMPONENT_BUILD)
-  // Extract the modules in component build required by setup.exe.
-  if (!::EnumResourceNames(module, kBinResourceType, WriteResourceToDirectory,
-                           reinterpret_cast<LONG_PTR>(base_path))) {
-    return ProcessExitResult(UNABLE_TO_EXTRACT_SETUP, ::GetLastError());
+  if (exit_code.IsSuccess()) {
+    // Extract the modules in component build required by setup.exe.
+    if (!::EnumResourceNames(module, kBinResourceType, WriteResourceToDirectory,
+                             reinterpret_cast<LONG_PTR>(base_path))) {
+      return ProcessExitResult(UNABLE_TO_EXTRACT_SETUP, ::GetLastError());
+    }
   }
 #endif
 
-  return ProcessExitResult(SUCCESS_EXIT_CODE);
+  return exit_code;
 }
 
 // Executes setup.exe, waits for it to finish and returns the exit code.
@@ -587,6 +525,16 @@
                            RUN_SETUP_FAILED_COULD_NOT_CREATE_PROCESS);
 }
 
+// Deletes given files and working dir.
+void DeleteExtractedFiles(const wchar_t* base_path,
+                          const wchar_t* archive_path,
+                          const wchar_t* setup_path) {
+  ::DeleteFile(archive_path);
+  ::DeleteFile(setup_path);
+  // Delete the temp dir (if it is empty, otherwise fail).
+  ::RemoveDirectory(base_path);
+}
+
 // Returns true if the supplied path supports ACLs.
 bool IsAclSupportedForPath(const wchar_t* path) {
   PathString volume;
@@ -881,8 +829,6 @@
   }
 }
 
-}  // namespace
-
 ProcessExitResult WMain(HMODULE module) {
   // Always start with deleting potential leftovers from previous installations.
   // This can make the difference between success and failure.  We've seen
@@ -921,10 +867,10 @@
   SetInstallerFlags(configuration);
 #endif
 
-  MiniFile archive(GetDeleteOnCloseOption(configuration));
-  MiniFile setup(GetDeleteOnCloseOption(configuration));
+  PathString archive_path;
+  PathString setup_path;
   exit_code = UnpackBinaryResources(configuration, module, base_path.get(),
-                                    archive, setup);
+                                    &archive_path, &setup_path);
 
   // While unpacking the binaries, we paged in a whole bunch of memory that
   // we don't need anymore.  Let's give it back to the pool before running
@@ -932,22 +878,10 @@
   ::SetProcessWorkingSetSize(::GetCurrentProcess(), (SIZE_T)-1, (SIZE_T)-1);
 
   if (exit_code.IsSuccess())
-    exit_code = RunSetup(configuration, archive.path(), setup.path());
+    exit_code = RunSetup(configuration, archive_path.get(), setup_path.get());
 
-  // Closing the files will delete them if should_delete_extracted_files is
-  // true (the normal case).
-  archive.Close();
-  setup.Close();
-
-#if defined(COMPONENT_BUILD)
-  // |base_path| is full of component DLLs in this case, which are not held
-  // open for delete-on-close. Manually delete everything in the directory.
   if (configuration.should_delete_extracted_files())
-    DeleteAllFilesInDir(base_path.get());
-#endif
-
-  // Delete the temp dir (if it is empty, otherwise fail).
-  ::RemoveDirectory(base_path.get());
+    DeleteExtractedFiles(base_path.get(), archive_path.get(), setup_path.get());
 
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
   WriteInstallResults(configuration, exit_code);
diff --git a/chrome/installer/mini_installer/pe_resource.cc b/chrome/installer/mini_installer/pe_resource.cc
index 65a06a0..7dcd02b5e 100644
--- a/chrome/installer/mini_installer/pe_resource.cc
+++ b/chrome/installer/mini_installer/pe_resource.cc
@@ -8,8 +8,6 @@
 
 #include "chrome/installer/mini_installer/mini_file.h"
 
-namespace mini_installer {
-
 PEResource::PEResource(HRSRC resource, HMODULE module)
     : resource_(resource), module_(module) {}
 
@@ -26,43 +24,42 @@
   return ::SizeofResource(module_, resource_);
 }
 
-bool PEResource::WriteToDisk(const wchar_t* full_path, MiniFile& file) {
+bool PEResource::WriteToDisk(const wchar_t* full_path) {
   // Resource handles are not real HGLOBALs so do not attempt to close them.
   // Resources are freed when the containing module is unloaded.
   HGLOBAL data_handle = ::LoadResource(module_, resource_);
-  if (!data_handle)
+  if (nullptr == data_handle)
     return false;
 
   const char* data = reinterpret_cast<const char*>(::LockResource(data_handle));
-  if (!data)
+  if (nullptr == data)
     return false;
 
+  mini_installer::MiniFile file;
   if (!file.Create(full_path))
     return false;
 
   // Don't write all of the data at once because this can lead to kernel
   // address-space exhaustion on 32-bit Windows (see https://crbug.com/1001022
   // for details).
-  constexpr DWORD kMaxWriteAmount = 8 * 1024 * 1024;
-  const DWORD resource_size = Size();
-  for (DWORD total_written = 0; total_written < resource_size; /**/) {
-    const DWORD write_amount =
+  constexpr size_t kMaxWriteAmount = 8 * 1024 * 1024;
+  const size_t resource_size = Size();
+  for (size_t total_written = 0; total_written < resource_size; /**/) {
+    const size_t write_amount =
         std::min(kMaxWriteAmount, resource_size - total_written);
     DWORD written = 0;
-    if (!::WriteFile(file.GetHandleUnsafe(), data + total_written, write_amount,
-                     &written, nullptr)) {
+    if (!::WriteFile(file.GetHandleUnsafe(), data + total_written,
+                     static_cast<DWORD>(write_amount), &written, nullptr)) {
       const auto write_error = ::GetLastError();
 
       // Delete the file since the write failed.
+      file.DeleteOnClose();
       file.Close();
 
       ::SetLastError(write_error);
       return false;
     }
-    total_written += written;
+    total_written += write_amount;
   }
-
   return true;
 }
-
-}  // namespace mini_installer
diff --git a/chrome/installer/mini_installer/pe_resource.h b/chrome/installer/mini_installer/pe_resource.h
index f5e5764..1e61195 100644
--- a/chrome/installer/mini_installer/pe_resource.h
+++ b/chrome/installer/mini_installer/pe_resource.h
@@ -9,10 +9,6 @@
 
 #include <stddef.h>
 
-namespace mini_installer {
-
-class MiniFile;
-
 // This class models a windows PE resource. It does not pretend to be a full
 // API wrapper and it is just concerned with loading it to memory and writing
 // it to disk. Each resource is unique only in the context of a loaded module,
@@ -34,17 +30,14 @@
   // not valid.
   size_t Size();
 
-  // Writes the resource to the file |path|. Returns true on success, in which
-  // case |file| holds an open handle to the destination file. |file| will be
-  // opened with exclusive write access and shared read and delete access, and
-  // will be marked as delete-on-close.
-  bool WriteToDisk(const wchar_t* path, MiniFile& file);
+  // Creates a file in 'path' with a copy of the resource. If the resource can
+  // not be loaded into memory or if it cannot be written to disk it returns
+  // false.
+  bool WriteToDisk(const wchar_t* path);
 
  private:
   HRSRC resource_;
   HMODULE module_;
 };
 
-}  // namespace mini_installer
-
 #endif  // CHROME_INSTALLER_MINI_INSTALLER_PE_RESOURCE_H_
diff --git a/chrome/installer/mini_installer/pe_resource_test.cc b/chrome/installer/mini_installer/pe_resource_test.cc
deleted file mode 100644
index b766308..0000000
--- a/chrome/installer/mini_installer/pe_resource_test.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/installer/mini_installer/pe_resource.h"
-
-#include <windows.h>
-
-#include "base/files/file.h"
-#include "base/files/file_path.h"
-#include "base/files/file_util.h"
-#include "base/files/scoped_temp_dir.h"
-#include "chrome/installer/mini_installer/mini_file.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace mini_installer {
-
-TEST(PEResourceTest, WriteToDisk) {
-  base::ScopedTempDir temp_dir;
-  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
-  const base::FilePath manifest_path =
-      temp_dir.GetPath().Append(FILE_PATH_LITERAL("manifest"));
-
-  PEResource manifest(MAKEINTRESOURCE(1), RT_MANIFEST,
-                      ::GetModuleHandle(nullptr));
-  ASSERT_TRUE(manifest.IsValid());
-
-  MiniFile manifest_file(MiniFile::DeleteOnClose::kYes);
-  ASSERT_TRUE(
-      manifest.WriteToDisk(manifest_path.value().c_str(), manifest_file));
-
-  ASSERT_TRUE(manifest_file.IsValid());
-  EXPECT_PRED1(base::PathExists, manifest_path);
-
-  manifest_file.Close();
-  EXPECT_FALSE(base::PathExists(manifest_path));
-
-  EXPECT_TRUE(temp_dir.Delete());
-}
-
-}  // namespace mini_installer
diff --git a/chrome/services/sharing/nearby/nearby_connections.cc b/chrome/services/sharing/nearby/nearby_connections.cc
index e008a4f..cbd019d 100644
--- a/chrome/services/sharing/nearby/nearby_connections.cc
+++ b/chrome/services/sharing/nearby/nearby_connections.cc
@@ -13,6 +13,65 @@
 namespace nearby {
 namespace connections {
 
+namespace {
+
+ConnectionRequestInfo CreateConnectionRequestInfo(
+    const std::vector<uint8_t>& endpoint_info,
+    mojo::PendingRemote<mojom::ConnectionLifecycleListener> listener) {
+  mojo::SharedRemote<mojom::ConnectionLifecycleListener> remote(
+      std::move(listener));
+  return ConnectionRequestInfo{
+      .name = std::string(endpoint_info.begin(), endpoint_info.end()),
+      .listener = {
+          .initiated_cb =
+              [remote](const std::string& endpoint_id,
+                       const ConnectionResponseInfo& info) {
+                if (!remote)
+                  return;
+
+                remote->OnConnectionInitiated(
+                    endpoint_id,
+                    mojom::ConnectionInfo::New(
+                        info.authentication_token,
+                        ByteArrayToMojom(info.raw_authentication_token),
+                        ByteArrayToMojom(info.endpoint_info),
+                        info.is_incoming_connection));
+              },
+          .accepted_cb =
+              [remote](const std::string& endpoint_id) {
+                if (!remote)
+                  return;
+
+                remote->OnConnectionAccepted(endpoint_id);
+              },
+          .rejected_cb =
+              [remote](const std::string& endpoint_id, Status status) {
+                if (!remote)
+                  return;
+
+                remote->OnConnectionRejected(endpoint_id,
+                                             StatusToMojom(status.value));
+              },
+          .disconnected_cb =
+              [remote](const std::string& endpoint_id) {
+                if (!remote)
+                  return;
+
+                remote->OnDisconnected(endpoint_id);
+              },
+          .bandwidth_changed_cb =
+              [remote](const std::string& endpoint_id, std::int32_t quality) {
+                if (!remote)
+                  return;
+
+                remote->OnBandwidthChanged(endpoint_id, quality);
+              },
+      },
+  };
+}
+
+}  // namespace
+
 // Should only be accessed by objects within lifetime of NearbyConnections.
 NearbyConnections* g_instance = nullptr;
 
@@ -128,6 +187,34 @@
   return webrtc_signaling_messenger_.get();
 }
 
+void NearbyConnections::StartAdvertising(
+    const std::vector<uint8_t>& endpoint_info,
+    const std::string& service_id,
+    mojom::AdvertisingOptionsPtr options,
+    mojo::PendingRemote<mojom::ConnectionLifecycleListener> listener,
+    StartAdvertisingCallback callback) {
+  BooleanMediumSelector allowed_mediums = {
+      .bluetooth = options->allowed_mediums->bluetooth,
+      .web_rtc = options->allowed_mediums->web_rtc,
+      .wifi_lan = options->allowed_mediums->wifi_lan,
+  };
+  ConnectionOptions connection_options{
+      .strategy = StrategyFromMojom(options->strategy),
+      .allowed = std::move(allowed_mediums),
+      .auto_upgrade_bandwidth = options->auto_upgrade_bandwidth,
+      .enforce_topology_constraints = options->enforce_topology_constraints,
+  };
+
+  core_->StartAdvertising(
+      service_id, std::move(connection_options),
+      CreateConnectionRequestInfo(endpoint_info, std::move(listener)),
+      ResultCallbackFromMojom(std::move(callback)));
+}
+
+void NearbyConnections::StopAdvertising(StopAdvertisingCallback callback) {
+  core_->StopAdvertising(ResultCallbackFromMojom(std::move(callback)));
+}
+
 void NearbyConnections::StartDiscovery(
     const std::string& service_id,
     mojom::DiscoveryOptionsPtr options,
@@ -176,59 +263,10 @@
     const std::string& endpoint_id,
     mojo::PendingRemote<mojom::ConnectionLifecycleListener> listener,
     RequestConnectionCallback callback) {
-  mojo::SharedRemote<mojom::ConnectionLifecycleListener> remote(
-      std::move(listener));
-  ConnectionRequestInfo connection_request_info{
-      .name = std::string(endpoint_info.begin(), endpoint_info.end()),
-      .listener = {
-          .initiated_cb =
-              [remote](const std::string& endpoint_id,
-                       const ConnectionResponseInfo& info) {
-                if (!remote)
-                  return;
-
-                remote->OnConnectionInitiated(
-                    endpoint_id,
-                    mojom::ConnectionInfo::New(
-                        info.authentication_token,
-                        ByteArrayToMojom(info.raw_authentication_token),
-                        ByteArrayToMojom(info.endpoint_info),
-                        info.is_incoming_connection));
-              },
-          .accepted_cb =
-              [remote](const std::string& endpoint_id) {
-                if (!remote)
-                  return;
-
-                remote->OnConnectionAccepted(endpoint_id);
-              },
-          .rejected_cb =
-              [remote](const std::string& endpoint_id, Status status) {
-                if (!remote)
-                  return;
-
-                remote->OnConnectionRejected(endpoint_id,
-                                             StatusToMojom(status.value));
-              },
-          .disconnected_cb =
-              [remote](const std::string& endpoint_id) {
-                if (!remote)
-                  return;
-
-                remote->OnDisconnected(endpoint_id);
-              },
-          .bandwidth_changed_cb =
-              [remote](const std::string& endpoint_id, std::int32_t quality) {
-                if (!remote)
-                  return;
-
-                remote->OnBandwidthChanged(endpoint_id, quality);
-              },
-      }};
-  ResultCallback result_callback = ResultCallbackFromMojom(std::move(callback));
-
-  core_->RequestConnection(endpoint_id, std::move(connection_request_info),
-                           std::move(result_callback));
+  core_->RequestConnection(
+      endpoint_id,
+      CreateConnectionRequestInfo(endpoint_info, std::move(listener)),
+      ResultCallbackFromMojom(std::move(callback)));
 }
 
 void NearbyConnections::DisconnectFromEndpoint(
diff --git a/chrome/services/sharing/nearby/nearby_connections.h b/chrome/services/sharing/nearby/nearby_connections.h
index ca16924..39fc82a 100644
--- a/chrome/services/sharing/nearby/nearby_connections.h
+++ b/chrome/services/sharing/nearby/nearby_connections.h
@@ -58,6 +58,13 @@
   sharing::mojom::WebRtcSignalingMessenger* GetWebRtcSignalingMessenger();
 
   // mojom::NearbyConnections:
+  void StartAdvertising(
+      const std::vector<uint8_t>& endpoint_info,
+      const std::string& service_id,
+      mojom::AdvertisingOptionsPtr options,
+      mojo::PendingRemote<mojom::ConnectionLifecycleListener> listener,
+      StartAdvertisingCallback callback) override;
+  void StopAdvertising(StopAdvertisingCallback callback) override;
   void StartDiscovery(
       const std::string& service_id,
       mojom::DiscoveryOptionsPtr options,
diff --git a/chrome/services/sharing/nearby/nearby_connections_unittest.cc b/chrome/services/sharing/nearby/nearby_connections_unittest.cc
index f868a22..36c1095 100644
--- a/chrome/services/sharing/nearby/nearby_connections_unittest.cc
+++ b/chrome/services/sharing/nearby/nearby_connections_unittest.cc
@@ -38,6 +38,16 @@
 const char kRawAuthenticationToken[] = {0x00, 0x05, 0x04, 0x03, 0x02};
 const int32_t kQuality = 5201314;
 
+mojom::AdvertisingOptionsPtr CreateAdvertisingOptions() {
+  auto allowed_mediums = mojom::MediumSelection::New(/*bluetooth=*/true,
+                                                     /*web_rtc=*/true,
+                                                     /*wifi_lan=*/true);
+  return mojom::AdvertisingOptions::New(mojom::Strategy::kP2pPointToPoint,
+                                        std::move(allowed_mediums),
+                                        /*auto_upgrade_bandwidth=*/true,
+                                        /*enforce_topology_constraints=*/true);
+}
+
 }  // namespace
 
 class FakeEndpointDiscoveryListener : public mojom::EndpointDiscoveryListener {
@@ -406,6 +416,139 @@
   EXPECT_CALL(*service_controller_ptr_, DisconnectFromEndpoint).Times(1);
 }
 
+TEST_F(NearbyConnectionsTest, StartAdvertising) {
+  FakeConnectionLifecycleListener fake_connection_life_cycle_listener;
+  ClientProxy* client_proxy;
+  ConnectionListener connections_listener;
+
+  EXPECT_CALL(*service_controller_ptr_, StartAdvertising)
+      .WillOnce([&](ClientProxy* client, const std::string& service_id,
+                    const ConnectionOptions& options,
+                    const ConnectionRequestInfo& info) {
+        EXPECT_EQ(kServiceId, service_id);
+        EXPECT_EQ(Strategy::kP2pPointToPoint, options.strategy);
+        EXPECT_TRUE(options.allowed.bluetooth);
+        EXPECT_TRUE(options.allowed.web_rtc);
+        EXPECT_TRUE(options.allowed.wifi_lan);
+        EXPECT_TRUE(options.auto_upgrade_bandwidth);
+        EXPECT_TRUE(options.enforce_topology_constraints);
+        EXPECT_EQ(
+            std::string(std::begin(kEndpointInfo), std::end(kEndpointInfo)),
+            info.name);
+
+        client_proxy = client;
+        connections_listener = info.listener;
+
+        return Status{Status::kSuccess};
+      });
+
+  base::RunLoop start_advertising_run_loop;
+  nearby_connections_->StartAdvertising(
+      std::vector<uint8_t>(std::begin(kEndpointInfo), std::end(kEndpointInfo)),
+      kServiceId, CreateAdvertisingOptions(),
+      fake_connection_life_cycle_listener.receiver.BindNewPipeAndPassRemote(),
+      base::BindLambdaForTesting([&](mojom::Status status) {
+        EXPECT_EQ(mojom::Status::kSuccess, status);
+        start_advertising_run_loop.Quit();
+      }));
+  start_advertising_run_loop.Run();
+
+  base::RunLoop initiated_run_loop;
+  fake_connection_life_cycle_listener.initiated_cb = base::BindLambdaForTesting(
+      [&](const std::string& endpoint_id, mojom::ConnectionInfoPtr info) {
+        EXPECT_EQ(kRemoteEndpointId, endpoint_id);
+        EXPECT_EQ(kAuthenticationToken, info->authentication_token);
+        EXPECT_EQ(std::vector<uint8_t>(std::begin(kRawAuthenticationToken),
+                                       std::end(kRawAuthenticationToken)),
+                  info->raw_authentication_token);
+        EXPECT_EQ(std::vector<uint8_t>(std::begin(kRemoteEndpointInfo),
+                                       std::end(kRemoteEndpointInfo)),
+                  info->endpoint_info);
+        EXPECT_FALSE(info->is_incoming_connection);
+        initiated_run_loop.Quit();
+      });
+  client_proxy->OnConnectionInitiated(
+      kRemoteEndpointId,
+      {.authentication_token = kAuthenticationToken,
+       .raw_authentication_token =
+           ByteArray(kRawAuthenticationToken, sizeof(kRawAuthenticationToken)),
+       .endpoint_info =
+           ByteArray(kRemoteEndpointInfo, sizeof(kRemoteEndpointInfo)),
+       .is_incoming_connection = false},
+      connections_listener);
+  initiated_run_loop.Run();
+
+  base::RunLoop rejected_run_loop;
+  fake_connection_life_cycle_listener.rejected_cb = base::BindLambdaForTesting(
+      [&](const std::string& endpoint_id, mojom::Status status) {
+        EXPECT_EQ(kRemoteEndpointId, endpoint_id);
+        EXPECT_EQ(mojom::Status::kConnectionRejected, status);
+        rejected_run_loop.Quit();
+      });
+  client_proxy->OnConnectionRejected(kRemoteEndpointId,
+                                     {Status::kConnectionRejected});
+  rejected_run_loop.Run();
+
+  // Initiate connection again to test accepted flow.
+  base::RunLoop initiated_run_loop_2;
+  fake_connection_life_cycle_listener.initiated_cb = base::BindLambdaForTesting(
+      [&](const std::string& endpoint_id, mojom::ConnectionInfoPtr info) {
+        EXPECT_EQ(kRemoteEndpointId, endpoint_id);
+        EXPECT_FALSE(info->is_incoming_connection);
+        initiated_run_loop_2.Quit();
+      });
+  client_proxy->OnConnectionInitiated(kRemoteEndpointId,
+                                      {.is_incoming_connection = false},
+                                      connections_listener);
+  initiated_run_loop_2.Run();
+
+  base::RunLoop accepted_run_loop;
+  fake_connection_life_cycle_listener.accepted_cb =
+      base::BindLambdaForTesting([&](const std::string& endpoint_id) {
+        EXPECT_EQ(kRemoteEndpointId, endpoint_id);
+        accepted_run_loop.Quit();
+      });
+  client_proxy->OnConnectionAccepted(kRemoteEndpointId);
+  accepted_run_loop.Run();
+}
+
+TEST_F(NearbyConnectionsTest, StopAdvertising) {
+  FakeConnectionLifecycleListener fake_connection_life_cycle_listener;
+
+  EXPECT_CALL(*service_controller_ptr_, StartAdvertising)
+      .WillOnce([](ClientProxy* client, const std::string& service_id,
+                   const ConnectionOptions& options,
+                   const ConnectionRequestInfo& info) {
+        client->StartedAdvertising(service_id, options.strategy, info.listener,
+                                   /*mediums=*/{});
+        return Status{Status::kSuccess};
+      });
+
+  base::RunLoop start_advertising_run_loop;
+  nearby_connections_->StartAdvertising(
+      std::vector<uint8_t>(std::begin(kEndpointInfo), std::end(kEndpointInfo)),
+      kServiceId, CreateAdvertisingOptions(), mojo::NullRemote(),
+      base::BindLambdaForTesting([&](mojom::Status status) {
+        EXPECT_EQ(mojom::Status::kSuccess, status);
+        start_advertising_run_loop.Quit();
+      }));
+  start_advertising_run_loop.Run();
+
+  EXPECT_CALL(*service_controller_ptr_, StopAdvertising)
+      .WillOnce([](ClientProxy* client) { client->StoppedAdvertising(); });
+
+  base::RunLoop stop_advertising_run_loop;
+  nearby_connections_->StopAdvertising(
+      base::BindLambdaForTesting([&](mojom::Status status) {
+        EXPECT_EQ(mojom::Status::kSuccess, status);
+        stop_advertising_run_loop.Quit();
+      }));
+  stop_advertising_run_loop.Run();
+
+  // Expect one more call during shutdown.
+  EXPECT_CALL(*service_controller_ptr_, StopAdvertising);
+}
+
 }  // namespace connections
 }  // namespace nearby
 }  // namespace location
diff --git a/chrome/services/sharing/public/mojom/nearby_connections.mojom b/chrome/services/sharing/public/mojom/nearby_connections.mojom
index ac9379d..dcc010e 100644
--- a/chrome/services/sharing/public/mojom/nearby_connections.mojom
+++ b/chrome/services/sharing/public/mojom/nearby_connections.mojom
@@ -97,12 +97,45 @@
 // packets is not part of the NearbyConnections library and is done in a
 // separate interface.
 interface NearbyConnections {
+  // Starts advertising an endpoint with the specified service ID. Only one
+  // advertisement can be active at a time and calling StartAdvertising() while
+  // advertising will fail and return Status::kAlreadyAdvertising.
+  //
+  // endpoint_info - The local info to be broadcasted. May contain a human
+  //                 readable name to appear on other devices if broadcasting
+  //                 in high visibility mode.
+  // service_id    - An identifier to advertise your app to other endpoints.
+  //                 This can be an arbitrary string, so long as it uniquely
+  //                 identifies your service. A good default is to use your
+  //                 app's package name.
+  // options       - The options for advertising.
+  // listener      - An interface notified when remote endpoints request a
+  //                 connection to this endpoint.
+  // Possible return values include:
+  //   Status::kSuccess if advertising started successfully.
+  //   Status::kAlreadyAdvertising if the app is already advertising.
+  //   Status::kOutOfOrderApiCall if the app is currently connected to remote
+  //       endpoints; call StopAllEndpoints first.
+  StartAdvertising(array<uint8> endpoint_info, string service_id,
+                   AdvertisingOptions options,
+                   pending_remote<ConnectionLifecycleListener> listener)
+      => (Status status);
+
+  // Stops advertising the local endpoint. Should be called after calling
+  // StartAdvertising, as soon as the application no longer needs to advertise
+  // itself or goes inactive. Payloads can still be sent to connected
+  // endpoints after advertising ends.
+  //
+  // Possible return values include:
+  //   Status::kSuccess returned after advertising got stopped.
+  StopAdvertising() => (Status status);
+
   // Starts discovery for remote endpoints with the specified service ID.
   //
   // service_id - The ID for the service to be discovered, as specified in
   //              the corresponding call to StartAdvertising.
   // options    - The options for discovery.
-  // listener   - A callback notified when a remote endpoint is discovered.
+  // listener   - An interface notified when a remote endpoint is discovered.
   // Possible status codes include:
   //   Status::kSuccess if discovery started successfully.
   //   Status::kAlreadyDiscovering if the app is already
@@ -130,7 +163,7 @@
   //                 connection request will be sent. Should match the value
   //                 provided in a call to
   //                 EndpointDiscoveryListener::OnEndpointFound().
-  // listener      - A callback notified when the remote endpoint sends a
+  // listener      - An interface notified when the remote endpoint sends a
   //                 response to the connection request.
   // Possible return values include:
   //   Status::kSuccess if the connection request was sent.
diff --git a/chrome/services/sharing/public/mojom/nearby_connections_types.mojom b/chrome/services/sharing/public/mojom/nearby_connections_types.mojom
index d8bf472..fbc4d66 100644
--- a/chrome/services/sharing/public/mojom/nearby_connections_types.mojom
+++ b/chrome/services/sharing/public/mojom/nearby_connections_types.mojom
@@ -99,11 +99,25 @@
   kP2pPointToPoint,
 };
 
+// A selection of on/off toggles to define a set of allowed mediums.
+struct MediumSelection {
+  // Whether Bluetooth should be allowed.
+  bool bluetooth;
+  // Whether WebRTC should be allowed.
+  bool web_rtc;
+  // Whether WiFi LAN should be allowed.
+  bool wifi_lan;
+};
+
 // Options for a call to NearbyConnections::StartAdvertising().
 struct AdvertisingOptions {
   // The strategy to use for advertising. Must match the strategy used in
   // DiscoveryOptions for remote devices to see this advertisement.
   Strategy strategy;
+  // Describes which mediums are allowed to be used for advertising. Note that
+  // allowing an otherwise unsupported medium is ok. Only the intersection of
+  // allowed and supported mediums will be used to advertise.
+  MediumSelection allowed_mediums;
   // By default, this option is true. If false, we will not attempt to upgrade
   // the bandwidth until a call to InitiateBandwidthUpgrade() is made.
   bool auto_upgrade_bandwidth = true;
diff --git a/chrome/test/data/hats/hats_next_mock.html b/chrome/test/data/hats/hats_next_mock.html
index fd60fe2..0883c853 100644
--- a/chrome/test/data/hats/hats_next_mock.html
+++ b/chrome/test/data/hats/hats_next_mock.html
@@ -15,6 +15,11 @@
       if (params.get('trigger_id') == "invalid_url_fragment_for_testing") {
         history.pushState('', '', '#foo');
       }
+
+      if (params.get('trigger_id') == "open_new_web_contents_for_testing"){
+        window.open('http://foo.com', '_blank');
+        history.pushState('', '', '#loaded');
+      }
     </script>
   </head>
   <body>
diff --git a/chrome/test/data/webui/cr_components/chromeos/cr_components_chromeos_browsertest.js b/chrome/test/data/webui/cr_components/chromeos/cr_components_chromeos_browsertest.js
index 15ce5b03..0a9058b2 100644
--- a/chrome/test/data/webui/cr_components/chromeos/cr_components_chromeos_browsertest.js
+++ b/chrome/test/data/webui/cr_components/chromeos/cr_components_chromeos_browsertest.js
@@ -9,6 +9,13 @@
 
 GEN('#include "content/public/test/browser_test.h"');
 
+// Tests are flaky on ChromeOS, debug (crbug.com/1114675).
+GEN('#if defined(OS_CHROMEOS) && !defined(NDEBUG)');
+GEN('#define MAYBE_All DISABLED_All');
+GEN('#else');
+GEN('#define MAYBE_All All');
+GEN('#endif');
+
 // Polymer 2 test list format:
 //
 // ['ModuleNameTest', 'module.js',
@@ -39,7 +46,7 @@
   this[className] = class extends PolymerTest {
     /** @override */
     get browsePreload() {
-      return `chrome://os-settings/test_loader.html?module=settings/chromeos/${module}`;
+      return `chrome://os-settings/test_loader.html?module=cr_components/chromeos/${module}`;
     }
 
     /** @override */
@@ -48,5 +55,5 @@
     }
   };
 
-  TEST_F(className, 'All', () => mocha.run());
+  TEST_F(className, 'MAYBE_All', () => mocha.run());
 }
diff --git a/chrome/test/data/webui/settings/chromeos/ambient_mode_page_test.js b/chrome/test/data/webui/settings/chromeos/ambient_mode_page_test.js
index 0a9875d..1d63e60a 100644
--- a/chrome/test/data/webui/settings/chromeos/ambient_mode_page_test.js
+++ b/chrome/test/data/webui/settings/chromeos/ambient_mode_page_test.js
@@ -5,7 +5,7 @@
 // clang-format off
 // #import 'chrome://os-settings/chromeos/os_settings.js';
 
-// #import {AmbientModeBrowserProxyImpl, CrSettingsPrefs} from 'chrome://os-settings/chromeos/os_settings.js';
+// #import {AmbientModeTopicSource, AmbientModeTemperatureUnit, AmbientModeBrowserProxyImpl, CrSettingsPrefs, Router} from 'chrome://os-settings/chromeos/os_settings.js';
 // #import {TestBrowserProxy} from '../../test_browser_proxy.m.js';
 // #import {assertEquals, assertFalse, assertTrue} from '../../chai_assert.js';
 // #import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
@@ -17,16 +17,17 @@
 class TestAmbientModeBrowserProxy extends TestBrowserProxy {
   constructor() {
     super([
-      'requestTopicSource',
+      'requestSettings',
       'requestAlbums',
+      'setSelectedTemperatureUnit',
       'setSelectedTopicSource',
       'setSelectedAlbums',
     ]);
   }
 
   /** @override */
-  requestTopicSource() {
-    this.methodCalled('requestTopicSource');
+  requestSettings() {
+    this.methodCalled('requestSettings');
   }
 
   /** @override */
@@ -35,6 +36,11 @@
   }
 
   /** @override */
+  setSelectedTemperatureUnit(temperatureUnit) {
+    this.methodCalled('setSelectedTemperatureUnit', [temperatureUnit]);
+  }
+
+  /** @override */
   setSelectedTopicSource(topicSource) {
     this.methodCalled('setSelectedTopicSource', [topicSource]);
   }
@@ -115,6 +121,34 @@
     assertEquals(enabled, enabled_toggled_twice);
   });
 
+  test('doubleClickTopicSource', () => {
+    // Select the google photos topic source.
+    cr.webUIListenerCallback(
+        'topic-source-changed', AmbientModeTopicSource.GOOGLE_PHOTOS);
+
+    const topicSourceList = ambientModePage.$$('topic-source-list');
+    const ironList = topicSourceList.$$('iron-list');
+    const topicSourceItem =
+        ironList.querySelector('topic-source-item[checked]');
+    const clickableDiv = topicSourceItem.$$('#rowContainer');
+
+    // Verify that the show-albums event is sent when the google photos radio
+    // button is clicked again.
+    let showAlbumEventCalls = 0;
+    topicSourceList.addEventListener('show-albums', (event) => {
+      assertEquals(AmbientModeTopicSource.GOOGLE_PHOTOS, event.detail);
+      showAlbumEventCalls++;
+    });
+
+    clickableDiv.click();
+    assertEquals(1, showAlbumEventCalls);
+
+    // Should navigate to the ambient-mode/photos?topic-source=0 subpage.
+    const router = settings.Router.getInstance();
+    assertEquals('/ambientMode/photos', router.getCurrentRoute().path);
+    assertEquals('topicSource=0', router.getQueryParameters().toString());
+  });
+
   test('hasTopicSourceItems', function() {
     const topicSourceListElement = ambientModePage.$$('topic-source-list');
     const ironList = topicSourceListElement.$$('iron-list');
@@ -142,4 +176,77 @@
     assertFalse(checkbox1.checked);
     assertEquals('album1', checkbox1.label);
   });
+
+  test('temperatureUnitRadioButtonsDisabled', () => {
+    // When |selectedTemperatureUnit_| is invalid the radio buttons should be
+    // disabled. This is the initial state.
+    const radioGroup = ambientModePage.$$('#weatherDiv cr-radio-group');
+
+    assertTrue(radioGroup.disabled);
+
+    // When |selectedTemperatureUnit_| is valid the radio buttons should be
+    // enabled.
+    cr.webUIListenerCallback(
+        'temperature-unit-changed', AmbientModeTemperatureUnit.CELSIUS);
+    assertFalse(radioGroup.disabled);
+
+    cr.webUIListenerCallback(
+        'temperature-unit-changed', AmbientModeTemperatureUnit.UNKNOWN);
+    assertTrue(radioGroup.disabled);
+  });
+
+  test('temperatureUnitRadioButtons', async () => {
+    // Simulate C++ setting celsius as the initial temperature unit.
+    cr.webUIListenerCallback(
+        'temperature-unit-changed', AmbientModeTemperatureUnit.CELSIUS);
+
+    const celsiusButton = ambientModePage.$$('cr-radio-button[name=celsius]');
+    const fahrenheitButton =
+        ambientModePage.$$('cr-radio-button[name=fahrenheit]');
+
+    assertTrue(celsiusButton.checked);
+    assertFalse(fahrenheitButton.checked);
+
+    browserProxy.resetResolver('setSelectedTemperatureUnit');
+
+    // Click fahrenheit and expect the fahrenheit radio button to be checked and
+    // the browser proxy to be called with the correct argument.
+    fahrenheitButton.click();
+
+    assertFalse(celsiusButton.checked);
+    assertTrue(fahrenheitButton.checked);
+
+    assertEquals(1, browserProxy.getCallCount('setSelectedTemperatureUnit'));
+    const fahrenheitArgs =
+        await browserProxy.whenCalled('setSelectedTemperatureUnit');
+    assertDeepEquals(['fahrenheit'], fahrenheitArgs);
+
+    browserProxy.resetResolver('setSelectedTemperatureUnit');
+
+    // Click celsius and expect the celsius radio button to be checked and the
+    // browser proxy to be called with the correct argument.
+    celsiusButton.click();
+
+    assertTrue(celsiusButton.checked);
+    assertFalse(fahrenheitButton.checked);
+
+    assertEquals(1, browserProxy.getCallCount('setSelectedTemperatureUnit'));
+    const celsiusArgs =
+        await browserProxy.whenCalled('setSelectedTemperatureUnit');
+    assertDeepEquals(['celsius'], celsiusArgs);
+  });
+
+  test('temperatureUnitRadioButtonsDoubleClick', async () => {
+    // Simulate C++ setting celsius as the default temperature unit.
+    cr.webUIListenerCallback(
+        'temperature-unit-changed', AmbientModeTemperatureUnit.CELSIUS);
+
+    const celsiusButton = ambientModePage.$$('cr-radio-button[name=celsius]');
+
+    browserProxy.resetResolver('setSelectedTemperatureUnit');
+
+    // Nothing should happen.
+    celsiusButton.click();
+    assertEquals(0, browserProxy.getCallCount('setSelectedTemperatureUnit'));
+  });
 });
diff --git a/chromecast/browser/BUILD.gn b/chromecast/browser/BUILD.gn
index c8c157d..2913bd2 100644
--- a/chromecast/browser/BUILD.gn
+++ b/chromecast/browser/BUILD.gn
@@ -127,13 +127,6 @@
     "service/cast_service_simple.h",
     "service_connector.cc",
     "service_connector.h",
-    "tts/tts_controller.h",
-    "tts/tts_controller_impl.cc",
-    "tts/tts_controller_impl.h",
-    "tts/tts_platform.cc",
-    "tts/tts_platform.h",
-    "tts/tts_platform_stub.cc",
-    "tts/tts_platform_stub.h",
     "webui/cast_resource_data_source.cc",
     "webui/cast_resource_data_source.h",
     "webui/cast_webui.cc",
diff --git a/chromecast/browser/DEPS b/chromecast/browser/DEPS
index 119c5d9..92a7011 100644
--- a/chromecast/browser/DEPS
+++ b/chromecast/browser/DEPS
@@ -65,7 +65,7 @@
   "+third_party/blink/public/mojom/loader/resource_load_info.mojom.h",
   "+third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h",
   "+third_party/blink/public/mojom/messaging",
-  "+third_party/blink/public/mojom/speech/speech_synthesis.mojom-forward.h",
+  "+third_party/blink/public/mojom/speech/speech_synthesis.mojom.h",
   "+third_party/skia/include/core/SkColor.h",
   "+ui/accessibility",
   "+ui/aura",
diff --git a/chromecast/browser/cast_browser_main_parts.cc b/chromecast/browser/cast_browser_main_parts.cc
index c3458d39..9df36b1d 100644
--- a/chromecast/browser/cast_browser_main_parts.cc
+++ b/chromecast/browser/cast_browser_main_parts.cc
@@ -44,8 +44,6 @@
 #include "chromecast/browser/media/media_caps_impl.h"
 #include "chromecast/browser/metrics/cast_browser_metrics.h"
 #include "chromecast/browser/service_connector.h"
-#include "chromecast/browser/tts/tts_controller_impl.h"
-#include "chromecast/browser/tts/tts_platform_stub.h"
 #include "chromecast/chromecast_buildflags.h"
 #include "chromecast/graphics/cast_window_manager.h"
 #include "chromecast/media/base/key_systems_common.h"
@@ -626,9 +624,6 @@
   ::media::InitializeMediaLibrary();
   media_caps_->Initialize();
 
-  cast_browser_process_->SetTtsController(std::make_unique<TtsControllerImpl>(
-      std::make_unique<TtsPlatformImplStub>()));
-
 #if BUILDFLAG(ENABLE_CHROMECAST_EXTENSIONS)
   user_pref_service_ = extensions::cast_prefs::CreateUserPrefService(
       cast_browser_process_->browser_context());
diff --git a/chromecast/browser/cast_browser_process.cc b/chromecast/browser/cast_browser_process.cc
index 6865e59c..1283fd6 100644
--- a/chromecast/browser/cast_browser_process.cc
+++ b/chromecast/browser/cast_browser_process.cc
@@ -13,7 +13,6 @@
 #include "chromecast/browser/cast_network_contexts.h"
 #include "chromecast/browser/devtools/remote_debugging_server.h"
 #include "chromecast/browser/metrics/cast_browser_metrics.h"
-#include "chromecast/browser/tts/tts_controller.h"
 #include "chromecast/metrics/cast_metrics_service_client.h"
 #include "chromecast/net/connectivity_checker.h"
 #include "chromecast/service/cast_service.h"
@@ -145,12 +144,6 @@
   net_log_ = net_log;
 }
 
-void CastBrowserProcess::SetTtsController(
-    std::unique_ptr<TtsController> tts_controller) {
-  DCHECK(!tts_controller_);
-  tts_controller_ = std::move(tts_controller);
-}
-
 void CastBrowserProcess::SetWebViewFactory(
     CastWebViewFactory* web_view_factory) {
   DCHECK(!web_view_factory_);
diff --git a/chromecast/browser/cast_browser_process.h b/chromecast/browser/cast_browser_process.h
index 67d9b617..7b50a25 100644
--- a/chromecast/browser/cast_browser_process.h
+++ b/chromecast/browser/cast_browser_process.h
@@ -12,7 +12,6 @@
 #include "build/build_config.h"
 #include "chromecast/chromecast_buildflags.h"
 
-class TtsController;
 class PrefService;
 
 namespace net {
@@ -76,7 +75,6 @@
   void SetConnectivityChecker(
       scoped_refptr<ConnectivityChecker> connectivity_checker);
   void SetNetLog(net::NetLog* net_log);
-  void SetTtsController(std::unique_ptr<TtsController> tts_controller);
   void SetWebViewFactory(CastWebViewFactory* web_view_factory);
 
   CastContentBrowserClient* browser_client() const {
@@ -108,7 +106,6 @@
     return remote_debugging_server_.get();
   }
   net::NetLog* net_log() const { return net_log_; }
-  TtsController* tts_controller() const { return tts_controller_.get(); }
   CastWebViewFactory* web_view_factory() const { return web_view_factory_; }
 
  private:
@@ -132,7 +129,6 @@
   CastWebViewFactory* web_view_factory_;
   CastContentBrowserClient* cast_content_browser_client_;
   net::NetLog* net_log_;
-  std::unique_ptr<TtsController> tts_controller_;
 
   // Note: CastService must be destroyed before others.
   std::unique_ptr<CastService> cast_service_;
diff --git a/chromecast/browser/cast_content_browser_client.cc b/chromecast/browser/cast_content_browser_client.cc
index d6fdf298..a8736241 100644
--- a/chromecast/browser/cast_content_browser_client.cc
+++ b/chromecast/browser/cast_content_browser_client.cc
@@ -49,7 +49,6 @@
 #include "chromecast/browser/media/media_caps_impl.h"
 #include "chromecast/browser/service/cast_service_simple.h"
 #include "chromecast/browser/service_connector.h"
-#include "chromecast/browser/tts/tts_controller.h"
 #include "chromecast/common/cast_content_client.h"
 #include "chromecast/common/global_descriptors.h"
 #include "chromecast/media/audio/cast_audio_manager.h"
diff --git a/chromecast/browser/extensions/api/tts/tts_extension_api.cc b/chromecast/browser/extensions/api/tts/tts_extension_api.cc
index 64ff390..80d2739 100644
--- a/chromecast/browser/extensions/api/tts/tts_extension_api.cc
+++ b/chromecast/browser/extensions/api/tts/tts_extension_api.cc
@@ -9,8 +9,7 @@
 
 #include "chromecast/browser/extensions/api/tts/tts_extension_api.h"
 
-#include <stddef.h>
-
+#include <cstddef>
 #include <memory>
 #include <string>
 #include <utility>
@@ -19,9 +18,10 @@
 #include "base/values.h"
 #include "chromecast/browser/cast_browser_process.h"
 #include "chromecast/browser/extensions/api/tts/tts_extension_api_constants.h"
-#include "chromecast/browser/tts/tts_controller.h"
+#include "content/public/browser/tts_controller.h"
 #include "extensions/browser/event_router.h"
 #include "extensions/browser/extension_function_registry.h"
+#include "third_party/blink/public/mojom/speech/speech_synthesis.mojom.h"
 #include "ui/base/l10n/l10n_util.h"
 
 namespace constants = tts_extension_api_constants;
@@ -30,27 +30,27 @@
 const char kOnEvent[] = "tts.onEvent";
 }  // namespace events
 
-const char* TtsEventTypeToString(TtsEventType event_type) {
+const char* TtsEventTypeToString(content::TtsEventType event_type) {
   switch (event_type) {
-    case TTS_EVENT_START:
+    case content::TTS_EVENT_START:
       return constants::kEventTypeStart;
-    case TTS_EVENT_END:
+    case content::TTS_EVENT_END:
       return constants::kEventTypeEnd;
-    case TTS_EVENT_WORD:
+    case content::TTS_EVENT_WORD:
       return constants::kEventTypeWord;
-    case TTS_EVENT_SENTENCE:
+    case content::TTS_EVENT_SENTENCE:
       return constants::kEventTypeSentence;
-    case TTS_EVENT_MARKER:
+    case content::TTS_EVENT_MARKER:
       return constants::kEventTypeMarker;
-    case TTS_EVENT_INTERRUPTED:
+    case content::TTS_EVENT_INTERRUPTED:
       return constants::kEventTypeInterrupted;
-    case TTS_EVENT_CANCELLED:
+    case content::TTS_EVENT_CANCELLED:
       return constants::kEventTypeCancelled;
-    case TTS_EVENT_ERROR:
+    case content::TTS_EVENT_ERROR:
       return constants::kEventTypeError;
-    case TTS_EVENT_PAUSE:
+    case content::TTS_EVENT_PAUSE:
       return constants::kEventTypePause;
-    case TTS_EVENT_RESUME:
+    case content::TTS_EVENT_RESUME:
       return constants::kEventTypeResume;
     default:
       NOTREACHED();
@@ -58,49 +58,44 @@
   }
 }
 
-TtsEventType TtsEventTypeFromString(const std::string& str) {
+content::TtsEventType TtsEventTypeFromString(const std::string& str) {
   if (str == constants::kEventTypeStart)
-    return TTS_EVENT_START;
+    return content::TTS_EVENT_START;
   if (str == constants::kEventTypeEnd)
-    return TTS_EVENT_END;
+    return content::TTS_EVENT_END;
   if (str == constants::kEventTypeWord)
-    return TTS_EVENT_WORD;
+    return content::TTS_EVENT_WORD;
   if (str == constants::kEventTypeSentence)
-    return TTS_EVENT_SENTENCE;
+    return content::TTS_EVENT_SENTENCE;
   if (str == constants::kEventTypeMarker)
-    return TTS_EVENT_MARKER;
+    return content::TTS_EVENT_MARKER;
   if (str == constants::kEventTypeInterrupted)
-    return TTS_EVENT_INTERRUPTED;
+    return content::TTS_EVENT_INTERRUPTED;
   if (str == constants::kEventTypeCancelled)
-    return TTS_EVENT_CANCELLED;
+    return content::TTS_EVENT_CANCELLED;
   if (str == constants::kEventTypeError)
-    return TTS_EVENT_ERROR;
+    return content::TTS_EVENT_ERROR;
   if (str == constants::kEventTypePause)
-    return TTS_EVENT_PAUSE;
+    return content::TTS_EVENT_PAUSE;
   if (str == constants::kEventTypeResume)
-    return TTS_EVENT_RESUME;
+    return content::TTS_EVENT_RESUME;
 
   NOTREACHED();
-  return TTS_EVENT_ERROR;
+  return content::TTS_EVENT_ERROR;
 }
 
-namespace {
-TtsController* GetTtsController() {
-  return chromecast::shell::CastBrowserProcess::GetInstance()->tts_controller();
-}
-}  // namespace
-
 namespace extensions {
 
 // One of these is constructed for each utterance, and deleted
 // when the utterance gets any final event.
-class TtsExtensionEventHandler : public UtteranceEventDelegate {
+class TtsExtensionEventHandler : public content::UtteranceEventDelegate {
  public:
   explicit TtsExtensionEventHandler(const std::string& src_extension_id);
 
-  void OnTtsEvent(Utterance* utterance,
-                  TtsEventType event_type,
+  void OnTtsEvent(content::TtsUtterance* utterance,
+                  content::TtsEventType event_type,
                   int char_index,
+                  int length,
                   const std::string& error_message) override;
 
  private:
@@ -113,21 +108,22 @@
     const std::string& src_extension_id)
     : src_extension_id_(src_extension_id) {}
 
-void TtsExtensionEventHandler::OnTtsEvent(Utterance* utterance,
-                                          TtsEventType event_type,
+void TtsExtensionEventHandler::OnTtsEvent(content::TtsUtterance* utterance,
+                                          content::TtsEventType event_type,
                                           int char_index,
+                                          int length,
                                           const std::string& error_message) {
-  if (utterance->src_id() < 0) {
-    if (utterance->finished())
+  if (utterance->GetSrcId() < 0) {
+    if (utterance->IsFinished())
       delete this;
     return;
   }
 
-  const std::set<TtsEventType>& desired_event_types =
-      utterance->desired_event_types();
-  if (desired_event_types.size() > 0 &&
+  const std::set<content::TtsEventType>& desired_event_types =
+      utterance->GetDesiredEventTypes();
+  if (!desired_event_types.empty() &&
       desired_event_types.find(event_type) == desired_event_types.end()) {
-    if (utterance->finished())
+    if (utterance->IsFinished())
       delete this;
     return;
   }
@@ -136,24 +132,26 @@
   std::unique_ptr<base::DictionaryValue> details(new base::DictionaryValue());
   if (char_index >= 0)
     details->SetInteger(constants::kCharIndexKey, char_index);
+  if (length >= 0)
+    details->SetInteger(constants::kLengthKey, length);
   details->SetString(constants::kEventTypeKey, event_type_string);
-  if (event_type == TTS_EVENT_ERROR) {
+  if (event_type == content::TTS_EVENT_ERROR) {
     details->SetString(constants::kErrorMessageKey, error_message);
   }
-  details->SetInteger(constants::kSrcIdKey, utterance->src_id());
-  details->SetBoolean(constants::kIsFinalEventKey, utterance->finished());
+  details->SetInteger(constants::kSrcIdKey, utterance->GetSrcId());
+  details->SetBoolean(constants::kIsFinalEventKey, utterance->IsFinished());
 
   std::unique_ptr<base::ListValue> arguments(new base::ListValue());
   arguments->Append(std::move(details));
 
   auto event = std::make_unique<extensions::Event>(
       ::extensions::events::TTS_ON_EVENT, ::events::kOnEvent,
-      std::move(arguments), utterance->browser_context());
-  event->event_url = utterance->src_url();
-  extensions::EventRouter::Get(utterance->browser_context())
+      std::move(arguments), utterance->GetBrowserContext());
+  event->event_url = utterance->GetSrcUrl();
+  extensions::EventRouter::Get(utterance->GetBrowserContext())
       ->DispatchEventToExtension(src_extension_id_, std::move(event));
 
-  if (utterance->finished())
+  if (utterance->IsFinished())
     delete this;
 }
 
@@ -184,22 +182,7 @@
     return RespondNow(Error(constants::kErrorInvalidLang));
   }
 
-  std::string gender_str;
-  TtsGenderType gender;
-  if (options->HasKey(constants::kGenderKey))
-    EXTENSION_FUNCTION_VALIDATE(
-        options->GetString(constants::kGenderKey, &gender_str));
-  if (gender_str == constants::kGenderMale) {
-    gender = TTS_GENDER_MALE;
-  } else if (gender_str == constants::kGenderFemale) {
-    gender = TTS_GENDER_FEMALE;
-  } else if (gender_str.empty()) {
-    gender = TTS_GENDER_NONE;
-  } else {
-    return RespondNow(Error(constants::kErrorInvalidGender));
-  }
-
-  double rate = 1.0;
+  double rate = blink::mojom::kSpeechSynthesisDoublePrefNotSet;
   if (options->HasKey(constants::kRateKey)) {
     EXTENSION_FUNCTION_VALIDATE(options->GetDouble(constants::kRateKey, &rate));
     if (rate < 0.1 || rate > 10.0) {
@@ -207,7 +190,7 @@
     }
   }
 
-  double pitch = 1.0;
+  double pitch = blink::mojom::kSpeechSynthesisDoublePrefNotSet;
   if (options->HasKey(constants::kPitchKey)) {
     EXTENSION_FUNCTION_VALIDATE(
         options->GetDouble(constants::kPitchKey, &pitch));
@@ -216,7 +199,7 @@
     }
   }
 
-  double volume = 1.0;
+  double volume = blink::mojom::kSpeechSynthesisDoublePrefNotSet;
   if (options->HasKey(constants::kVolumeKey)) {
     EXTENSION_FUNCTION_VALIDATE(
         options->GetDouble(constants::kVolumeKey, &volume));
@@ -231,7 +214,7 @@
         options->GetBoolean(constants::kEnqueueKey, &can_enqueue));
   }
 
-  std::set<TtsEventType> required_event_types;
+  std::set<content::TtsEventType> required_event_types;
   if (options->HasKey(constants::kRequiredEventTypesKey)) {
     base::ListValue* list;
     EXTENSION_FUNCTION_VALIDATE(
@@ -243,7 +226,7 @@
     }
   }
 
-  std::set<TtsEventType> desired_event_types;
+  std::set<content::TtsEventType> desired_event_types;
   if (options->HasKey(constants::kDesiredEventTypesKey)) {
     base::ListValue* list;
     EXTENSION_FUNCTION_VALIDATE(
@@ -271,71 +254,66 @@
   // send the success response to the callback now - this ensures that
   // the callback response always arrives before events, which makes
   // the behavior more predictable and easier to write unit tests for too.
+  Respond(NoArguments());
 
-  Respond(OneArgument(std::make_unique<base::Value>(true)));
+  std::unique_ptr<content::TtsUtterance> utterance =
+      content::TtsUtterance::Create(browser_context());
+  utterance->SetText(text);
+  utterance->SetVoiceName(voice_name);
+  utterance->SetSrcId(src_id);
+  utterance->SetSrcUrl(source_url());
+  utterance->SetLang(lang);
+  utterance->SetContinuousParameters(rate, pitch, volume);
+  utterance->SetCanEnqueue(can_enqueue);
+  utterance->SetRequiredEventTypes(required_event_types);
+  utterance->SetDesiredEventTypes(desired_event_types);
+  utterance->SetEngineId(voice_extension_id);
+  utterance->SetOptions(options.get());
+  utterance->SetEventDelegate(new TtsExtensionEventHandler(extension_id()));
 
-  Utterance* utterance = new Utterance(browser_context());
-  utterance->set_text(text);
-  utterance->set_voice_name(voice_name);
-  utterance->set_src_id(src_id);
-  utterance->set_src_url(source_url());
-  utterance->set_lang(lang);
-  utterance->set_gender(gender);
-  utterance->set_continuous_parameters(rate, pitch, volume);
-  utterance->set_can_enqueue(can_enqueue);
-  utterance->set_required_event_types(required_event_types);
-  utterance->set_desired_event_types(desired_event_types);
-  utterance->set_extension_id(voice_extension_id);
-  utterance->set_options(options.get());
-  utterance->set_event_delegate(new TtsExtensionEventHandler(extension_id()));
-
-  GetTtsController()->SpeakOrEnqueue(utterance);
-  return did_respond() ? AlreadyResponded() : RespondLater();
+  content::TtsController* controller = content::TtsController::GetInstance();
+  controller->SpeakOrEnqueue(std::move(utterance));
+  return AlreadyResponded();
 }
 
 ExtensionFunction::ResponseAction TtsStopSpeakingFunction::Run() {
-  GetTtsController()->Stop();
+  content::TtsController::GetInstance()->Stop(source_url());
   return RespondNow(NoArguments());
 }
 
 ExtensionFunction::ResponseAction TtsPauseFunction::Run() {
-  GetTtsController()->Pause();
+  content::TtsController::GetInstance()->Pause();
   return RespondNow(NoArguments());
 }
 
 ExtensionFunction::ResponseAction TtsResumeFunction::Run() {
-  GetTtsController()->Resume();
+  content::TtsController::GetInstance()->Resume();
   return RespondNow(NoArguments());
 }
 
 ExtensionFunction::ResponseAction TtsIsSpeakingFunction::Run() {
-  return RespondNow(OneArgument(
-      std::make_unique<base::Value>(GetTtsController()->IsSpeaking())));
+  return RespondNow(OneArgument(std::make_unique<base::Value>(
+      content::TtsController::GetInstance()->IsSpeaking())));
 }
 
 ExtensionFunction::ResponseAction TtsGetVoicesFunction::Run() {
-  std::vector<VoiceData> voices;
-  GetTtsController()->GetVoices(browser_context(), &voices);
+  std::vector<content::VoiceData> voices;
+  content::TtsController::GetInstance()->GetVoices(browser_context(), &voices);
 
   auto result_voices = std::make_unique<base::ListValue>();
   for (size_t i = 0; i < voices.size(); ++i) {
-    const VoiceData& voice = voices[i];
+    const content::VoiceData& voice = voices[i];
     std::unique_ptr<base::DictionaryValue> result_voice(
         new base::DictionaryValue());
     result_voice->SetString(constants::kVoiceNameKey, voice.name);
     result_voice->SetBoolean(constants::kRemoteKey, voice.remote);
     if (!voice.lang.empty())
       result_voice->SetString(constants::kLangKey, voice.lang);
-    if (voice.gender == TTS_GENDER_MALE)
-      result_voice->SetString(constants::kGenderKey, constants::kGenderMale);
-    else if (voice.gender == TTS_GENDER_FEMALE)
-      result_voice->SetString(constants::kGenderKey, constants::kGenderFemale);
-    if (!voice.extension_id.empty())
-      result_voice->SetString(constants::kExtensionIdKey, voice.extension_id);
+    if (!voice.engine_id.empty())
+      result_voice->SetString(constants::kExtensionIdKey, voice.engine_id);
 
     auto event_types = std::make_unique<base::ListValue>();
-    for (std::set<TtsEventType>::iterator iter = voice.events.begin();
-         iter != voice.events.end(); ++iter) {
+    for (auto iter = voice.events.begin(); iter != voice.events.end(); ++iter) {
       const char* event_name_constant = TtsEventTypeToString(*iter);
       event_types->AppendString(event_name_constant);
     }
diff --git a/chromecast/browser/extensions/api/tts/tts_extension_api.h b/chromecast/browser/extensions/api/tts/tts_extension_api.h
index 59f1d8f..7afd353c 100644
--- a/chromecast/browser/extensions/api/tts/tts_extension_api.h
+++ b/chromecast/browser/extensions/api/tts/tts_extension_api.h
@@ -12,7 +12,7 @@
 
 #include <string>
 
-#include "chromecast/browser/tts/tts_controller.h"
+#include "content/public/browser/tts_controller.h"
 #include "extensions/browser/browser_context_keyed_api_factory.h"
 #include "extensions/browser/extension_function.h"
 
@@ -20,8 +20,8 @@
 class BrowserContext;
 }
 
-const char* TtsEventTypeToString(TtsEventType event_type);
-TtsEventType TtsEventTypeFromString(const std::string& str);
+const char* TtsEventTypeToString(content::TtsEventType event_type);
+content::TtsEventType TtsEventTypeFromString(const std::string& str);
 
 namespace extensions {
 
diff --git a/chromecast/browser/extensions/api/tts/tts_extension_api_constants.cc b/chromecast/browser/extensions/api/tts/tts_extension_api_constants.cc
index b24dd97..c52e2d7 100644
--- a/chromecast/browser/extensions/api/tts/tts_extension_api_constants.cc
+++ b/chromecast/browser/extensions/api/tts/tts_extension_api_constants.cc
@@ -2,16 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// PLEASE NOTE: this is a copy with modifications from
-// /chrome/browser/speech/extension_api
-// It is temporary until a refactoring to move the chrome TTS implementation up
-// into components and extensions/components can be completed.
-
 #include "chromecast/browser/extensions/api/tts/tts_extension_api_constants.h"
 
 namespace tts_extension_api_constants {
 
 const char kCharIndexKey[] = "charIndex";
+const char kLengthKey[] = "length";
 const char kDesiredEventTypesKey[] = "desiredEventTypes";
 const char kEnqueueKey[] = "enqueue";
 const char kErrorMessageKey[] = "errorMessage";
@@ -30,9 +26,6 @@
 const char kVoiceNameKey[] = "voiceName";
 const char kVolumeKey[] = "volume";
 
-const char kGenderFemale[] = "female";
-const char kGenderMale[] = "male";
-
 const char kEventTypeCancelled[] = "cancelled";
 const char kEventTypeEnd[] = "end";
 const char kEventTypeError[] = "error";
@@ -45,7 +38,6 @@
 const char kEventTypeWord[] = "word";
 
 const char kErrorExtensionIdMismatch[] = "Extension id mismatch.";
-const char kErrorInvalidGender[] = "Invalid gender.";
 const char kErrorInvalidLang[] = "Invalid lang.";
 const char kErrorInvalidPitch[] = "Invalid pitch.";
 const char kErrorInvalidRate[] = "Invalid rate.";
diff --git a/chromecast/browser/extensions/api/tts/tts_extension_api_constants.h b/chromecast/browser/extensions/api/tts/tts_extension_api_constants.h
index 6e4f79f..74652ea 100644
--- a/chromecast/browser/extensions/api/tts/tts_extension_api_constants.h
+++ b/chromecast/browser/extensions/api/tts/tts_extension_api_constants.h
@@ -2,11 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// PLEASE NOTE: this is a copy with modifications from
-// /chrome/browser/speech/extension_api
-// It is temporary until a refactoring to move the chrome TTS implementation up
-// into components and extensions/components can be completed.
-
 #ifndef CHROMECAST_BROWSER_EXTENSIONS_API_TTS_TTS_EXTENSION_API_CONSTANTS_H_
 #define CHROMECAST_BROWSER_EXTENSIONS_API_TTS_TTS_EXTENSION_API_CONSTANTS_H_
 
@@ -17,6 +12,7 @@
 namespace tts_extension_api_constants {
 
 extern const char kCharIndexKey[];
+extern const char kLengthKey[];
 extern const char kDesiredEventTypesKey[];
 extern const char kEnqueueKey[];
 extern const char kErrorMessageKey[];
@@ -35,9 +31,6 @@
 extern const char kVoiceNameKey[];
 extern const char kVolumeKey[];
 
-extern const char kGenderFemale[];
-extern const char kGenderMale[];
-
 extern const char kEventTypeCancelled[];
 extern const char kEventTypeEnd[];
 extern const char kEventTypeError[];
@@ -50,7 +43,6 @@
 extern const char kEventTypeWord[];
 
 extern const char kErrorExtensionIdMismatch[];
-extern const char kErrorInvalidGender[];
 extern const char kErrorInvalidLang[];
 extern const char kErrorInvalidPitch[];
 extern const char kErrorInvalidRate[];
diff --git a/chromecast/browser/tts/tts_controller.h b/chromecast/browser/tts/tts_controller.h
deleted file mode 100644
index 30d7e51..0000000
--- a/chromecast/browser/tts/tts_controller.h
+++ /dev/null
@@ -1,274 +0,0 @@
-// Copyright (c) 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROMECAST_BROWSER_TTS_TTS_CONTROLLER_H_
-#define CHROMECAST_BROWSER_TTS_TTS_CONTROLLER_H_
-
-#include <memory>
-#include <queue>
-#include <set>
-#include <string>
-#include <vector>
-
-#include "base/memory/singleton.h"
-#include "base/memory/weak_ptr.h"
-#include "url/gurl.h"
-
-class Utterance;
-class TtsPlatformImpl;
-
-namespace base {
-class Value;
-}  // namespace base
-
-namespace content {
-class BrowserContext;
-}  // namespace content
-
-// Events sent back from the TTS engine indicating the progress.
-enum TtsEventType {
-  TTS_EVENT_START,
-  TTS_EVENT_END,
-  TTS_EVENT_WORD,
-  TTS_EVENT_SENTENCE,
-  TTS_EVENT_MARKER,
-  TTS_EVENT_INTERRUPTED,
-  TTS_EVENT_CANCELLED,
-  TTS_EVENT_ERROR,
-  TTS_EVENT_PAUSE,
-  TTS_EVENT_RESUME
-};
-
-enum TtsGenderType { TTS_GENDER_NONE, TTS_GENDER_MALE, TTS_GENDER_FEMALE };
-
-// Returns true if this event type is one that indicates an utterance
-// is finished and can be destroyed.
-bool IsFinalTtsEventType(TtsEventType event_type);
-
-// The continuous parameters that apply to a given utterance.
-struct UtteranceContinuousParameters {
-  UtteranceContinuousParameters();
-
-  double rate;
-  double pitch;
-  double volume;
-};
-
-// Information about one voice.
-struct VoiceData {
-  VoiceData();
-  VoiceData(const VoiceData& other);
-  ~VoiceData();
-
-  std::string name;
-  std::string lang;
-  TtsGenderType gender;
-  std::string extension_id;  // Not used in cast.
-  std::set<TtsEventType> events;
-
-  // If true, the synthesis engine is a remote network resource.
-  // It may be higher latency and may incur bandwidth costs.
-  bool remote;
-
-  // If true, this is implemented by this platform's subclass of
-  // TtsPlatformImpl. If false, this is implemented by an extension.
-  bool native;
-  std::string native_voice_identifier;
-};
-
-// Class that wants to receive events on utterances.
-class UtteranceEventDelegate {
- public:
-  virtual ~UtteranceEventDelegate() {}
-  virtual void OnTtsEvent(Utterance* utterance,
-                          TtsEventType event_type,
-                          int char_index,
-                          const std::string& error_message) = 0;
-};
-
-// One speech utterance.
-class Utterance {
- public:
-  // Construct an utterance given a profile and a completion task to call
-  // when the utterance is done speaking. Before speaking this utterance,
-  // its other parameters like text, rate, pitch, etc. should all be set.
-  explicit Utterance(content::BrowserContext* browser_context);
-  ~Utterance();
-
-  // Sends an event to the delegate. If the event type is TTS_EVENT_END
-  // or TTS_EVENT_ERROR, deletes the utterance. If |char_index| is -1,
-  // uses the last good value.
-  void OnTtsEvent(TtsEventType event_type,
-                  int char_index,
-                  const std::string& error_message);
-
-  // Finish an utterance without sending an event to the delegate.
-  void Finish();
-
-  // Getters and setters for the text to speak and other speech options.
-  void set_text(const std::string& text) { text_ = text; }
-  const std::string& text() const { return text_; }
-
-  void set_options(const base::Value* options);
-  const base::Value* options() const { return options_.get(); }
-
-  void set_src_id(int src_id) { src_id_ = src_id; }
-  int src_id() { return src_id_; }
-
-  void set_src_url(const GURL& src_url) { src_url_ = src_url; }
-  const GURL& src_url() { return src_url_; }
-
-  void set_voice_name(const std::string& voice_name) {
-    voice_name_ = voice_name;
-  }
-  const std::string& voice_name() const { return voice_name_; }
-
-  void set_lang(const std::string& lang) { lang_ = lang; }
-  const std::string& lang() const { return lang_; }
-
-  void set_gender(TtsGenderType gender) { gender_ = gender; }
-  TtsGenderType gender() const { return gender_; }
-
-  void set_continuous_parameters(const double rate,
-                                 const double pitch,
-                                 const double volume) {
-    continuous_parameters_.rate = rate;
-    continuous_parameters_.pitch = pitch;
-    continuous_parameters_.volume = volume;
-  }
-  const UtteranceContinuousParameters& continuous_parameters() {
-    return continuous_parameters_;
-  }
-
-  void set_can_enqueue(bool can_enqueue) { can_enqueue_ = can_enqueue; }
-  bool can_enqueue() const { return can_enqueue_; }
-
-  void set_required_event_types(const std::set<TtsEventType>& types) {
-    required_event_types_ = types;
-  }
-  const std::set<TtsEventType>& required_event_types() const {
-    return required_event_types_;
-  }
-
-  void set_desired_event_types(const std::set<TtsEventType>& types) {
-    desired_event_types_ = types;
-  }
-  const std::set<TtsEventType>& desired_event_types() const {
-    return desired_event_types_;
-  }
-
-  const std::string& extension_id() const { return extension_id_; }
-  void set_extension_id(const std::string& extension_id) {
-    extension_id_ = extension_id;
-  }
-
-  UtteranceEventDelegate* event_delegate() const { return event_delegate_; }
-  void set_event_delegate(UtteranceEventDelegate* event_delegate) {
-    event_delegate_ = event_delegate;
-  }
-
-  // Getters and setters for internal state.
-  content::BrowserContext* browser_context() const { return browser_context_; }
-  int id() const { return id_; }
-  bool finished() const { return finished_; }
-
- private:
-  // The BrowserContext that initiated this utterance.
-  content::BrowserContext* browser_context_;
-
-  // The extension ID of the extension providing TTS for this utterance, or
-  // empty if native TTS is being used.
-  std::string extension_id_;
-
-  // The unique ID of this utterance, used to associate callback functions
-  // with utterances.
-  int id_;
-
-  // The id of the next utterance, so we can associate requests with
-  // responses.
-  static int next_utterance_id_;
-
-  // The text to speak.
-  std::string text_;
-
-  // The full options arg passed to tts.speak, which may include fields
-  // other than the ones we explicitly parse, below.
-  std::unique_ptr<base::Value> options_;
-
-  // The source extension's ID of this utterance, so that it can associate
-  // events with the appropriate callback.
-  int src_id_;
-
-  // The URL of the page where the source extension called speak.
-  GURL src_url_;
-
-  // The delegate to be called when an utterance event is fired.
-  UtteranceEventDelegate* event_delegate_;
-
-  // The parsed options.
-  std::string voice_name_;
-  std::string lang_;
-  TtsGenderType gender_;
-  UtteranceContinuousParameters continuous_parameters_;
-  bool can_enqueue_;
-  std::set<TtsEventType> required_event_types_;
-  std::set<TtsEventType> desired_event_types_;
-
-  // The index of the current char being spoken.
-  int char_index_;
-
-  // True if this utterance received an event indicating it's done.
-  bool finished_;
-};
-
-// Singleton class that manages text-to-speech for the TTS extension APIs,
-// potentially maintaining a queue of pending utterances and keeping track of
-// all state.
-class TtsController {
- public:
-  virtual ~TtsController() = default;
-
-  // Set the TTS platform implementation to use.
-  virtual void SetPlatformImpl(
-      std::unique_ptr<TtsPlatformImpl> platform_impl) = 0;
-
-  // Returns true if we're currently speaking an utterance.
-  virtual bool IsSpeaking() = 0;
-
-  // Speak the given utterance. If the utterance's can_enqueue flag is true
-  // and another utterance is in progress, adds it to the end of the queue.
-  // Otherwise, interrupts any current utterance and speaks this one
-  // immediately.
-  virtual void SpeakOrEnqueue(Utterance* utterance) = 0;
-
-  // Stop all utterances and flush the queue. Implies leaving pause mode
-  // as well.
-  virtual void Stop() = 0;
-
-  // Pause the speech queue. Some engines may support pausing in the middle
-  // of an utterance.
-  virtual void Pause() = 0;
-
-  // Resume speaking.
-  virtual void Resume() = 0;
-
-  // Handle events received from the speech engine. Events are forwarded to
-  // the callback function, and in addition, completion and error events
-  // trigger finishing the current utterance and starting the next one, if
-  // any.
-  virtual void OnTtsEvent(int utterance_id,
-                          TtsEventType event_type,
-                          int char_index,
-                          const std::string& error_message) = 0;
-
-  // Return a list of all available voices, including the native voice,
-  // if supported, and all voices registered by extensions.
-  virtual void GetVoices(content::BrowserContext* browser_context,
-                         std::vector<VoiceData>* out_voices) = 0;
-
-  // For unit testing.
-  virtual int QueueSize() = 0;
-};
-
-#endif  // CHROMECAST_BROWSER_TTS_TTS_CONTROLLER_H_
diff --git a/chromecast/browser/tts/tts_controller_impl.cc b/chromecast/browser/tts/tts_controller_impl.cc
deleted file mode 100644
index 2bb8338e..0000000
--- a/chromecast/browser/tts/tts_controller_impl.cc
+++ /dev/null
@@ -1,508 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chromecast/browser/tts/tts_controller_impl.h"
-
-#include <stddef.h>
-
-#include <string>
-#include <vector>
-
-#include "base/containers/queue.h"
-#include "base/metrics/histogram_macros.h"
-#include "base/metrics/user_metrics.h"
-#include "base/values.h"
-#include "build/build_config.h"
-#include "chromecast/browser/tts/tts_platform.h"
-#include "chromecast/chromecast_buildflags.h"
-#include "third_party/blink/public/mojom/speech/speech_synthesis.mojom-forward.h"
-
-#if BUILDFLAG(ENABLE_CHROMECAST_EXTENSIONS)
-#include "extensions/browser/extensions_browser_client.h"
-#endif
-
-namespace {
-// A value to be used to indicate that there is no char index available.
-const int kInvalidCharIndex = -1;
-
-// Given a language/region code of the form 'fr-FR', returns just the basic
-// language portion, e.g. 'fr'.
-std::string TrimLanguageCode(const std::string& lang) {
-  if (lang.size() >= 5 && lang[2] == '-')
-    return lang.substr(0, 2);
-  else
-    return lang;
-}
-
-// IMPORTANT!
-// These values are written to logs.  Do not renumber or delete
-// existing items; add new entries to the end of the list.
-enum class UMATextToSpeechEvent {
-  START = 0,
-  END = 1,
-  WORD = 2,
-  SENTENCE = 3,
-  MARKER = 4,
-  INTERRUPTED = 5,
-  CANCELLED = 6,
-  SPEECH_ERROR = 7,
-  PAUSE = 8,
-  RESUME = 9,
-
-  // This must always be the last enum. It's okay for its value to
-  // increase, but none of the other enum values may change.
-  COUNT
-};
-
-}  // namespace
-
-bool IsFinalTtsEventType(TtsEventType event_type) {
-  return (event_type == TTS_EVENT_END || event_type == TTS_EVENT_INTERRUPTED ||
-          event_type == TTS_EVENT_CANCELLED || event_type == TTS_EVENT_ERROR);
-}
-
-//
-// UtteranceContinuousParameters
-//
-
-UtteranceContinuousParameters::UtteranceContinuousParameters()
-    : rate(blink::mojom::kSpeechSynthesisDoublePrefNotSet),
-      pitch(blink::mojom::kSpeechSynthesisDoublePrefNotSet),
-      volume(blink::mojom::kSpeechSynthesisDoublePrefNotSet) {}
-
-//
-// VoiceData
-//
-
-VoiceData::VoiceData()
-    : gender(TTS_GENDER_NONE), remote(false), native(false) {}
-
-VoiceData::VoiceData(const VoiceData& other) = default;
-
-VoiceData::~VoiceData() {}
-
-//
-// Utterance
-//
-
-// static
-int Utterance::next_utterance_id_ = 0;
-
-Utterance::Utterance(content::BrowserContext* browser_context)
-    : browser_context_(browser_context),
-      id_(next_utterance_id_++),
-      src_id_(-1),
-      gender_(TTS_GENDER_NONE),
-      can_enqueue_(false),
-      char_index_(0),
-      finished_(false) {
-  options_.reset(new base::DictionaryValue());
-}
-
-Utterance::~Utterance() {
-  // It's an error if an Utterance is destructed without being finished,
-  // unless |browser_context_| is nullptr because it's a unit test.
-  DCHECK(finished_ || !browser_context_);
-}
-
-void Utterance::OnTtsEvent(TtsEventType event_type,
-                           int char_index,
-                           const std::string& error_message) {
-  if (char_index >= 0)
-    char_index_ = char_index;
-  if (IsFinalTtsEventType(event_type))
-    finished_ = true;
-
-  if (event_delegate_)
-    event_delegate_->OnTtsEvent(this, event_type, char_index, error_message);
-  if (finished_)
-    event_delegate_ = nullptr;
-}
-
-void Utterance::Finish() {
-  finished_ = true;
-}
-
-void Utterance::set_options(const base::Value* options) {
-  options_.reset(options->DeepCopy());
-}
-
-//
-// TtsControllerImpl
-//
-
-TtsControllerImpl::TtsControllerImpl(
-    std::unique_ptr<TtsPlatformImpl> platform_impl)
-    : current_utterance_(nullptr),
-      paused_(false),
-      platform_impl_(std::move(platform_impl)) {}
-
-TtsControllerImpl::~TtsControllerImpl() {
-  if (current_utterance_) {
-    current_utterance_->Finish();
-    delete current_utterance_;
-  }
-
-  // Clear any queued utterances too.
-  ClearUtteranceQueue(false);  // Don't sent events.
-}
-
-void TtsControllerImpl::SpeakOrEnqueue(Utterance* utterance) {
-  // If we're paused and we get an utterance that can't be queued,
-  // flush the queue but stay in the paused state.
-  if (paused_ && !utterance->can_enqueue()) {
-    utterance_queue_.push(utterance);
-    Stop();
-    paused_ = true;
-    return;
-  }
-
-  if (paused_ || (IsSpeaking() && utterance->can_enqueue())) {
-    utterance_queue_.push(utterance);
-  } else {
-    Stop();
-    SpeakNow(utterance);
-  }
-}
-
-void TtsControllerImpl::SpeakNow(Utterance* utterance) {
-  // Get all available voices and try to find a matching voice.
-  std::vector<VoiceData> voices;
-  GetVoices(utterance->browser_context(), &voices);
-
-  // Get the best matching voice. If nothing matches, just set "native"
-  // to true because that might trigger deferred loading of native voices.
-  int index = GetMatchingVoice(utterance, voices);
-  VoiceData voice;
-  if (index >= 0)
-    voice = voices[index];
-  else
-    voice.native = true;
-
-  UpdateUtteranceDefaults(utterance);
-
-  GetPlatformImpl()->WillSpeakUtteranceWithVoice(utterance, voice);
-
-  base::RecordAction(base::UserMetricsAction("TextToSpeech.Speak"));
-  UMA_HISTOGRAM_COUNTS_100000("TextToSpeech.Utterance.TextLength",
-                              utterance->text().size());
-  UMA_HISTOGRAM_BOOLEAN("TextToSpeech.Utterance.FromExtensionAPI",
-                        !utterance->src_url().is_empty());
-  UMA_HISTOGRAM_BOOLEAN("TextToSpeech.Utterance.HasVoiceName",
-                        !utterance->voice_name().empty());
-  UMA_HISTOGRAM_BOOLEAN("TextToSpeech.Utterance.HasLang",
-                        !utterance->lang().empty());
-  UMA_HISTOGRAM_BOOLEAN("TextToSpeech.Utterance.HasGender",
-                        utterance->gender() != TTS_GENDER_NONE);
-  UMA_HISTOGRAM_BOOLEAN("TextToSpeech.Utterance.HasRate",
-                        utterance->continuous_parameters().rate != 1.0);
-  UMA_HISTOGRAM_BOOLEAN("TextToSpeech.Utterance.HasPitch",
-                        utterance->continuous_parameters().pitch != 1.0);
-  UMA_HISTOGRAM_BOOLEAN("TextToSpeech.Utterance.HasVolume",
-                        utterance->continuous_parameters().volume != 1.0);
-  UMA_HISTOGRAM_BOOLEAN("TextToSpeech.Utterance.Native", voice.native);
-
-  if (!voice.native) {
-#if !defined(OS_ANDROID)
-    DCHECK(!voice.extension_id.empty());
-    current_utterance_ = utterance;
-    utterance->set_extension_id(voice.extension_id);
-    bool sends_end_event =
-        voice.events.find(TTS_EVENT_END) != voice.events.end();
-    if (!sends_end_event) {
-      utterance->Finish();
-      delete utterance;
-      current_utterance_ = nullptr;
-      SpeakNextUtterance();
-    }
-#endif
-  } else {
-    // It's possible for certain platforms to send start events immediately
-    // during |speak|.
-    current_utterance_ = utterance;
-    GetPlatformImpl()->clear_error();
-    bool success = GetPlatformImpl()->Speak(utterance->id(), utterance->text(),
-                                            utterance->lang(), voice,
-                                            utterance->continuous_parameters());
-    if (!success) {
-      current_utterance_ = nullptr;
-      utterance->OnTtsEvent(TTS_EVENT_ERROR, kInvalidCharIndex,
-                            GetPlatformImpl()->error());
-      delete utterance;
-      return;
-    }
-  }
-}
-
-void TtsControllerImpl::Stop() {
-  base::RecordAction(base::UserMetricsAction("TextToSpeech.Stop"));
-
-  paused_ = false;
-  GetPlatformImpl()->clear_error();
-  GetPlatformImpl()->StopSpeaking();
-
-  if (current_utterance_)
-    current_utterance_->OnTtsEvent(TTS_EVENT_INTERRUPTED, kInvalidCharIndex,
-                                   std::string());
-  FinishCurrentUtterance();
-  ClearUtteranceQueue(true);  // Send events.
-}
-
-void TtsControllerImpl::Pause() {
-  base::RecordAction(base::UserMetricsAction("TextToSpeech.Pause"));
-
-  paused_ = true;
-  if (current_utterance_) {
-    GetPlatformImpl()->clear_error();
-    GetPlatformImpl()->Pause();
-  }
-}
-
-void TtsControllerImpl::Resume() {
-  base::RecordAction(base::UserMetricsAction("TextToSpeech.Resume"));
-
-  paused_ = false;
-  if (current_utterance_) {
-    GetPlatformImpl()->clear_error();
-    GetPlatformImpl()->Resume();
-  } else {
-    SpeakNextUtterance();
-  }
-}
-
-void TtsControllerImpl::OnTtsEvent(int utterance_id,
-                                   TtsEventType event_type,
-                                   int char_index,
-                                   const std::string& error_message) {
-  // We may sometimes receive completion callbacks "late", after we've
-  // already finished the utterance (for example because another utterance
-  // interrupted or we got a call to Stop). This is normal and we can
-  // safely just ignore these events.
-  if (!current_utterance_ || utterance_id != current_utterance_->id()) {
-    return;
-  }
-
-  UMATextToSpeechEvent metric;
-  switch (event_type) {
-    case TTS_EVENT_START:
-      metric = UMATextToSpeechEvent::START;
-      break;
-    case TTS_EVENT_END:
-      metric = UMATextToSpeechEvent::END;
-      break;
-    case TTS_EVENT_WORD:
-      metric = UMATextToSpeechEvent::WORD;
-      break;
-    case TTS_EVENT_SENTENCE:
-      metric = UMATextToSpeechEvent::SENTENCE;
-      break;
-    case TTS_EVENT_MARKER:
-      metric = UMATextToSpeechEvent::MARKER;
-      break;
-    case TTS_EVENT_INTERRUPTED:
-      metric = UMATextToSpeechEvent::INTERRUPTED;
-      break;
-    case TTS_EVENT_CANCELLED:
-      metric = UMATextToSpeechEvent::CANCELLED;
-      break;
-    case TTS_EVENT_ERROR:
-      metric = UMATextToSpeechEvent::SPEECH_ERROR;
-      break;
-    case TTS_EVENT_PAUSE:
-      metric = UMATextToSpeechEvent::PAUSE;
-      break;
-    case TTS_EVENT_RESUME:
-      metric = UMATextToSpeechEvent::RESUME;
-      break;
-    default:
-      NOTREACHED();
-      return;
-  }
-  UMA_HISTOGRAM_ENUMERATION("TextToSpeech.Event", metric,
-                            UMATextToSpeechEvent::COUNT);
-
-  current_utterance_->OnTtsEvent(event_type, char_index, error_message);
-  if (current_utterance_->finished()) {
-    FinishCurrentUtterance();
-    SpeakNextUtterance();
-  }
-}
-
-void TtsControllerImpl::GetVoices(content::BrowserContext* browser_context,
-                                  std::vector<VoiceData>* out_voices) {
-  TtsPlatformImpl* platform_impl = GetPlatformImpl();
-  if (platform_impl) {
-    // Ensure we have all built-in voices loaded. This is a no-op if already
-    // loaded.
-    if (platform_impl->PlatformImplAvailable())
-      platform_impl->GetVoices(out_voices);
-  }
-}
-
-bool TtsControllerImpl::IsSpeaking() {
-  return current_utterance_ != nullptr || GetPlatformImpl()->IsSpeaking();
-}
-
-void TtsControllerImpl::FinishCurrentUtterance() {
-  if (current_utterance_) {
-    if (!current_utterance_->finished())
-      current_utterance_->OnTtsEvent(TTS_EVENT_INTERRUPTED, kInvalidCharIndex,
-                                     std::string());
-    delete current_utterance_;
-    current_utterance_ = nullptr;
-  }
-}
-
-void TtsControllerImpl::SpeakNextUtterance() {
-  if (paused_)
-    return;
-
-  // Start speaking the next utterance in the queue.  Keep trying in case
-  // one fails but there are still more in the queue to try.
-  while (!utterance_queue_.empty() && !current_utterance_) {
-    Utterance* utterance = utterance_queue_.front();
-    utterance_queue_.pop();
-    SpeakNow(utterance);
-  }
-}
-
-void TtsControllerImpl::ClearUtteranceQueue(bool send_events) {
-  while (!utterance_queue_.empty()) {
-    Utterance* utterance = utterance_queue_.front();
-    utterance_queue_.pop();
-    if (send_events)
-      utterance->OnTtsEvent(TTS_EVENT_CANCELLED, kInvalidCharIndex,
-                            std::string());
-    else
-      utterance->Finish();
-    delete utterance;
-  }
-}
-
-void TtsControllerImpl::SetPlatformImpl(
-    std::unique_ptr<TtsPlatformImpl> platform_impl) {
-  platform_impl_ = std::move(platform_impl);
-}
-
-int TtsControllerImpl::QueueSize() {
-  return static_cast<int>(utterance_queue_.size());
-}
-
-TtsPlatformImpl* TtsControllerImpl::GetPlatformImpl() {
-  return platform_impl_.get();
-}
-
-std::string TtsControllerImpl::GetApplicationLocale() const {
-  // TODO(rdaum): Delegate back to the platform's mechaninism here.
-  // For Chrome, use g_browser_process as before. For cast, it's the system
-  // locale. Hardcoded to system locale for now
-#if BUILDFLAG(ENABLE_CHROMECAST_EXTENSIONS)
-  return extensions::ExtensionsBrowserClient::Get()->GetApplicationLocale();
-#else
-  return "en-US";
-#endif
-}
-
-int TtsControllerImpl::GetMatchingVoice(const Utterance* utterance,
-                                        std::vector<VoiceData>& voices) {
-  // Return the index of the voice that best match the utterance parameters.
-  //
-  // These criteria are considered mandatory - if they're specified, any voice
-  // that doesn't match is rejected.
-  //
-  //   Extension ID
-  //   Voice name
-  //
-  // The other criteria are scored based on how well they match, in
-  // this order of precedence:
-  //
-  //   Utterange language (exact region preferred, then general language)
-  //   App/system language (exact region preferred, then general language)
-  //   Required event types
-  //   Gender
-
-  std::string app_lang = GetApplicationLocale();
-
-  // Start with a best score of -1, that way even if none of the criteria
-  // match, something will be returned if there are any voices.
-  int best_score = -1;
-  int best_score_index = -1;
-  for (size_t i = 0; i < voices.size(); ++i) {
-    const VoiceData& voice = voices[i];
-    int score = 0;
-
-    // If the extension ID is specified, check for an exact match.
-    if (!utterance->extension_id().empty() &&
-        utterance->extension_id() != voice.extension_id)
-      continue;
-
-    // If the voice name is specified, check for an exact match.
-    if (!utterance->voice_name().empty() &&
-        voice.name != utterance->voice_name())
-      continue;
-
-    // Prefer the utterance language.
-    if (!voice.lang.empty() && !utterance->lang().empty()) {
-      // An exact language match is worth more than a partial match.
-      if (voice.lang == utterance->lang()) {
-        score += 32;
-      } else if (TrimLanguageCode(voice.lang) ==
-                 TrimLanguageCode(utterance->lang())) {
-        score += 16;
-      }
-    }
-
-    // Prefer the system language after that.
-    if (!voice.lang.empty()) {
-      if (voice.lang == app_lang)
-        score += 8;
-      else if (TrimLanguageCode(voice.lang) == TrimLanguageCode(app_lang))
-        score += 4;
-    }
-
-    // Next, prefer required event types.
-    if (utterance->required_event_types().size() > 0) {
-      bool has_all_required_event_types = true;
-      for (std::set<TtsEventType>::const_iterator iter =
-               utterance->required_event_types().begin();
-           iter != utterance->required_event_types().end(); ++iter) {
-        if (voice.events.find(*iter) == voice.events.end()) {
-          has_all_required_event_types = false;
-          break;
-        }
-      }
-      if (has_all_required_event_types)
-        score += 2;
-    }
-
-    // Finally prefer the requested gender last.
-    if (voice.gender != TTS_GENDER_NONE &&
-        utterance->gender() != TTS_GENDER_NONE &&
-        voice.gender == utterance->gender()) {
-      score += 1;
-    }
-
-    if (score > best_score) {
-      best_score = score;
-      best_score_index = i;
-    }
-  }
-
-  return best_score_index;
-}
-
-void TtsControllerImpl::UpdateUtteranceDefaults(Utterance* utterance) {
-  double rate = utterance->continuous_parameters().rate;
-  double pitch = utterance->continuous_parameters().pitch;
-  double volume = utterance->continuous_parameters().volume;
-  // Update pitch, rate and volume to defaults if not explicity set on
-  // this utterance.
-  if (rate == blink::mojom::kSpeechSynthesisDoublePrefNotSet)
-    rate = blink::mojom::kSpeechSynthesisDefaultRate;
-  if (pitch == blink::mojom::kSpeechSynthesisDoublePrefNotSet)
-    pitch = blink::mojom::kSpeechSynthesisDefaultPitch;
-  if (volume == blink::mojom::kSpeechSynthesisDoublePrefNotSet)
-    volume = blink::mojom::kSpeechSynthesisDefaultVolume;
-  utterance->set_continuous_parameters(rate, pitch, volume);
-}
diff --git a/chromecast/browser/tts/tts_controller_impl.h b/chromecast/browser/tts/tts_controller_impl.h
deleted file mode 100644
index 5282d9d8..0000000
--- a/chromecast/browser/tts/tts_controller_impl.h
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROMECAST_BROWSER_TTS_TTS_CONTROLLER_IMPL_H_
-#define CHROMECAST_BROWSER_TTS_TTS_CONTROLLER_IMPL_H_
-
-#include <memory>
-#include <set>
-#include <string>
-#include <vector>
-
-#include "base/containers/queue.h"
-#include "base/gtest_prod_util.h"
-#include "base/macros.h"
-#include "base/memory/singleton.h"
-#include "chromecast/browser/tts/tts_controller.h"
-#include "url/gurl.h"
-
-namespace content {
-class BrowserContext;
-}  // namespace content
-
-// Singleton class that manages text-to-speech for the TTS and TTS engine
-// extension APIs, maintaining a queue of pending utterances and keeping
-// track of all state.
-class TtsControllerImpl : public TtsController {
- public:
-  explicit TtsControllerImpl(std::unique_ptr<TtsPlatformImpl> platform_impl);
-  ~TtsControllerImpl() override;
-
-  // TtsController methods
-  bool IsSpeaking() override;
-  void SpeakOrEnqueue(Utterance* utterance) override;
-  void Stop() override;
-  void Pause() override;
-  void Resume() override;
-  void OnTtsEvent(int utterance_id,
-                  TtsEventType event_type,
-                  int char_index,
-                  const std::string& error_message) override;
-  void GetVoices(content::BrowserContext* browser_context,
-                 std::vector<VoiceData>* out_voices) override;
-  void SetPlatformImpl(std::unique_ptr<TtsPlatformImpl> platform_impl) override;
-  int QueueSize() override;
-
-  std::string GetApplicationLocale() const;
-
- private:
-  FRIEND_TEST_ALL_PREFIXES(TtsControllerTest, TestGetMatchingVoice);
-  FRIEND_TEST_ALL_PREFIXES(TtsControllerTest,
-                           TestTtsControllerUtteranceDefaults);
-
-  // Get the platform TTS implementation (or injected mock).
-  TtsPlatformImpl* GetPlatformImpl();
-
-  // Start speaking the given utterance. Will either take ownership of
-  // |utterance| or delete it if there's an error. Returns true on success.
-  void SpeakNow(Utterance* utterance);
-
-  // Clear the utterance queue. If send_events is true, will send
-  // TTS_EVENT_CANCELLED events on each one.
-  void ClearUtteranceQueue(bool send_events);
-
-  // Finalize and delete the current utterance.
-  void FinishCurrentUtterance();
-
-  // Start speaking the next utterance in the queue.
-  void SpeakNextUtterance();
-
-  // Given an utterance and a vector of voices, return the
-  // index of the voice that best matches the utterance.
-  int GetMatchingVoice(const Utterance* utterance,
-                       std::vector<VoiceData>& voices);
-
-  // Updates the utterance to have default values for rate, pitch, and
-  // volume if they have not yet been set. On Chrome OS, defaults are
-  // pulled from user prefs, and may not be the same as other platforms.
-  void UpdateUtteranceDefaults(Utterance* utterance);
-
-  // The current utterance being spoken.
-  Utterance* current_utterance_;
-
-  // Whether the queue is paused or not.
-  bool paused_;
-
-  // A queue of utterances to speak after the current one finishes.
-  base::queue<Utterance*> utterance_queue_;
-
-  // A pointer to the platform implementation of text-to-speech.
-  std::unique_ptr<TtsPlatformImpl> platform_impl_;
-
-  DISALLOW_COPY_AND_ASSIGN(TtsControllerImpl);
-};
-
-#endif  // CHROMECAST_BROWSER_TTS_TTS_CONTROLLER_IMPL_H_
diff --git a/chromecast/browser/tts/tts_controller_unittest.cc b/chromecast/browser/tts/tts_controller_unittest.cc
deleted file mode 100644
index 0e3a777a..0000000
--- a/chromecast/browser/tts/tts_controller_unittest.cc
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright (c) 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Unit tests for the TTS Controller.
-
-#include "base/values.h"
-#include "chromecast/browser/tts/tts_controller_impl.h"
-#include "chromecast/browser/tts/tts_platform.h"
-#include "components/prefs/pref_registry_simple.h"
-#include "components/prefs/testing_pref_service.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/public/platform/web_speech_synthesis_constants.h"
-
-namespace chromecast {
-
-class TtsControllerTest : public testing::Test {};
-
-// Platform Tts implementation that does nothing.
-class DummyTtsPlatformImpl : public TtsPlatformImpl {
- public:
-  DummyTtsPlatformImpl() {}
-  ~DummyTtsPlatformImpl() override {}
-  bool PlatformImplAvailable() override { return true; }
-  bool Speak(int utterance_id,
-             const std::string& utterance,
-             const std::string& lang,
-             const VoiceData& voice,
-             const UtteranceContinuousParameters& params) override {
-    return true;
-  }
-  bool IsSpeaking() override { return false; }
-  bool StopSpeaking() override { return true; }
-  void Pause() override {}
-  void Resume() override {}
-  void GetVoices(std::vector<VoiceData>* out_voices) override {}
-  std::string error() override { return std::string(); }
-  void clear_error() override {}
-  void set_error(const std::string& error) override {}
-};
-
-// Subclass of TtsController with a public ctor and dtor.
-class TestableTtsController : public TtsControllerImpl {
- public:
-  TestableTtsController() {}
-  ~TestableTtsController() override {}
-};
-
-TEST_F(TtsControllerTest, TestTtsControllerShutdown) {
-  DummyTtsPlatformImpl platform_impl;
-  TestableTtsController* controller = new TestableTtsController();
-  controller->SetPlatformImpl(&platform_impl);
-
-  Utterance* utterance1 = new Utterance(nullptr);
-  utterance1->set_can_enqueue(true);
-  utterance1->set_src_id(1);
-  controller->SpeakOrEnqueue(utterance1);
-
-  Utterance* utterance2 = new Utterance(nullptr);
-  utterance2->set_can_enqueue(true);
-  utterance2->set_src_id(2);
-  controller->SpeakOrEnqueue(utterance2);
-
-  // Make sure that deleting the controller when there are pending
-  // utterances doesn't cause a crash.
-  delete controller;
-}
-
-TEST_F(TtsControllerTest, TestGetMatchingVoice) {
-  TtsControllerImpl* tts_controller = TtsControllerImpl::GetInstance();
-
-  {
-    // Calling GetMatchingVoice with no voices returns -1.
-    Utterance utterance(nullptr);
-    std::vector<VoiceData> voices;
-    EXPECT_EQ(-1, tts_controller->GetMatchingVoice(&utterance, voices));
-  }
-
-  {
-    // Calling GetMatchingVoice with any voices returns the first one
-    // even if there are no criteria that match.
-    Utterance utterance(nullptr);
-    std::vector<VoiceData> voices;
-    voices.push_back(VoiceData());
-    voices.push_back(VoiceData());
-    EXPECT_EQ(0, tts_controller->GetMatchingVoice(&utterance, voices));
-  }
-
-  {
-    // If nothing else matches, the English voice is returned.
-    // (In tests the language will always be English.)
-    Utterance utterance(nullptr);
-    std::vector<VoiceData> voices;
-    VoiceData fr_voice;
-    fr_voice.lang = "fr";
-    voices.push_back(fr_voice);
-    VoiceData en_voice;
-    en_voice.lang = "en";
-    voices.push_back(en_voice);
-    VoiceData de_voice;
-    de_voice.lang = "de";
-    voices.push_back(de_voice);
-    EXPECT_EQ(1, tts_controller->GetMatchingVoice(&utterance, voices));
-  }
-
-  {
-    // Check precedence of various matching criteria.
-    std::vector<VoiceData> voices;
-    VoiceData voice0;
-    voices.push_back(voice0);
-    VoiceData voice1;
-    voice1.gender = TTS_GENDER_FEMALE;
-    voices.push_back(voice1);
-    VoiceData voice2;
-    voice2.events.insert(TTS_EVENT_WORD);
-    voices.push_back(voice2);
-    VoiceData voice3;
-    voice3.lang = "de-DE";
-    voices.push_back(voice3);
-    VoiceData voice4;
-    voice4.lang = "fr-CA";
-    voices.push_back(voice4);
-    VoiceData voice5;
-    voice5.name = "Voice5";
-    voices.push_back(voice5);
-    VoiceData voice6;
-    voice6.extension_id = "id6";
-    voices.push_back(voice6);
-
-    Utterance utterance(nullptr);
-    EXPECT_EQ(0, tts_controller->GetMatchingVoice(&utterance, voices));
-
-    utterance.set_gender(TTS_GENDER_FEMALE);
-    EXPECT_EQ(1, tts_controller->GetMatchingVoice(&utterance, voices));
-
-    std::set<TtsEventType> types;
-    types.insert(TTS_EVENT_WORD);
-    utterance.set_required_event_types(types);
-    EXPECT_EQ(2, tts_controller->GetMatchingVoice(&utterance, voices));
-
-    utterance.set_lang("de-DE");
-    EXPECT_EQ(3, tts_controller->GetMatchingVoice(&utterance, voices));
-
-    utterance.set_lang("fr-FR");
-    EXPECT_EQ(4, tts_controller->GetMatchingVoice(&utterance, voices));
-
-    utterance.set_voice_name("Voice5");
-    EXPECT_EQ(5, tts_controller->GetMatchingVoice(&utterance, voices));
-
-    utterance.set_voice_name("");
-    utterance.set_extension_id("id6");
-    EXPECT_EQ(6, tts_controller->GetMatchingVoice(&utterance, voices));
-  }
-}
-
-}  // namespace chromecast
diff --git a/chromecast/browser/tts/tts_platform.cc b/chromecast/browser/tts/tts_platform.cc
deleted file mode 100644
index 480574f..0000000
--- a/chromecast/browser/tts/tts_platform.cc
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright (c) 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chromecast/browser/tts/tts_platform.h"
-
-#include <string>
-
-std::string TtsPlatformImpl::error() {
-  return error_;
-}
-
-void TtsPlatformImpl::clear_error() {
-  error_ = std::string();
-}
-
-void TtsPlatformImpl::set_error(const std::string& error) {
-  error_ = error;
-}
-
-void TtsPlatformImpl::WillSpeakUtteranceWithVoice(const Utterance* utterance,
-                                                  const VoiceData& voice_data) {
-}
diff --git a/chromecast/browser/tts/tts_platform.h b/chromecast/browser/tts/tts_platform.h
deleted file mode 100644
index 5820b67..0000000
--- a/chromecast/browser/tts/tts_platform.h
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright (c) 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROMECAST_BROWSER_TTS_TTS_PLATFORM_H_
-#define CHROMECAST_BROWSER_TTS_TTS_PLATFORM_H_
-
-#include <string>
-
-#include "base/macros.h"
-#include "chromecast/browser/tts/tts_controller.h"
-
-// Abstract class that defines the native platform TTS interface,
-// subclassed by specific implementations on Win, Mac, etc.
-class TtsPlatformImpl {
- public:
-  TtsPlatformImpl() {}
-
-  virtual ~TtsPlatformImpl() {}
-
-  // Returns true if this platform implementation is supported and available.
-  virtual bool PlatformImplAvailable() = 0;
-
-  // Speak the given utterance with the given parameters if possible,
-  // and return true on success. Utterance will always be nonempty.
-  // If rate, pitch, or volume are -1.0, they will be ignored.
-  //
-  // The TtsController will only try to speak one utterance at
-  // a time. If it wants to interrupt speech, it will always call Stop
-  // before speaking again.
-  virtual bool Speak(int utterance_id,
-                     const std::string& utterance,
-                     const std::string& lang,
-                     const VoiceData& voice,
-                     const UtteranceContinuousParameters& params) = 0;
-
-  // Stop speaking immediately and return true on success.
-  virtual bool StopSpeaking() = 0;
-
-  // Returns whether any speech is on going.
-  virtual bool IsSpeaking() = 0;
-
-  // Append information about voices provided by this platform implementation
-  // to |out_voices|.
-  virtual void GetVoices(std::vector<VoiceData>* out_voices) = 0;
-
-  // Pause the current utterance, if any, until a call to Resume,
-  // Speak, or StopSpeaking.
-  virtual void Pause() = 0;
-
-  // Resume speaking the current utterance, if it was paused.
-  virtual void Resume() = 0;
-
-  // Allows the platform to monitor speech commands and the voices used
-  // for each one.
-  virtual void WillSpeakUtteranceWithVoice(const Utterance* utterance,
-                                           const VoiceData& voice_data);
-
-  virtual std::string error();
-  virtual void clear_error();
-  virtual void set_error(const std::string& error);
-
- protected:
-  std::string error_;
-
-  DISALLOW_COPY_AND_ASSIGN(TtsPlatformImpl);
-};
-
-#endif  // CHROMECAST_BROWSER_TTS_TTS_PLATFORM_H_
diff --git a/chromecast/browser/tts/tts_platform_stub.cc b/chromecast/browser/tts/tts_platform_stub.cc
deleted file mode 100644
index 3071fad..0000000
--- a/chromecast/browser/tts/tts_platform_stub.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (c) 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chromecast/browser/tts/tts_platform_stub.h"
-
-#include "base/logging.h"
-
-namespace chromecast {
-
-bool TtsPlatformImplStub::PlatformImplAvailable() {
-  return true;
-}
-
-bool TtsPlatformImplStub::Speak(int utterance_id,
-                                const std::string& utterance,
-                                const std::string& lang,
-                                const VoiceData& voice,
-                                const UtteranceContinuousParameters& params) {
-  LOG(INFO) << "Speak: " << utterance;
-  return true;
-}
-
-bool TtsPlatformImplStub::StopSpeaking() {
-  LOG(INFO) << "StopSpeaking";
-  return true;
-}
-
-void TtsPlatformImplStub::Pause() {
-  LOG(INFO) << "Pause";
-}
-
-void TtsPlatformImplStub::Resume() {
-  LOG(INFO) << "Resume";
-}
-
-bool TtsPlatformImplStub::IsSpeaking() {
-  LOG(INFO) << "IsSpeaking";
-  return false;
-}
-
-void TtsPlatformImplStub::GetVoices(std::vector<VoiceData>* out_voices) {
-  LOG(INFO) << "GetVoices";
-}
-
-}  // namespace chromecast
diff --git a/chromecast/browser/tts/tts_platform_stub.h b/chromecast/browser/tts/tts_platform_stub.h
deleted file mode 100644
index 4caa2fea..0000000
--- a/chromecast/browser/tts/tts_platform_stub.h
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright (c) 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chromecast/browser/tts/tts_controller.h"
-#include "chromecast/browser/tts/tts_platform.h"
-
-#ifndef CHROMECAST_BROWSER_TTS_TTS_PLATFORM_STUB_H_
-#define CHROMECAST_BROWSER_TTS_TTS_PLATFORM_STUB_H_
-
-namespace chromecast {
-
-// The default stub implementation of TtsPlaform for Cast that merely logs TTS
-// events.
-class TtsPlatformImplStub : public TtsPlatformImpl {
- public:
-  TtsPlatformImplStub() = default;
-  ~TtsPlatformImplStub() override = default;
-
-  bool PlatformImplAvailable() override;
-
-  bool Speak(int utterance_id,
-             const std::string& utterance,
-             const std::string& lang,
-             const VoiceData& voice,
-             const UtteranceContinuousParameters& params) override;
-
-  bool StopSpeaking() override;
-
-  void Pause() override;
-
-  void Resume() override;
-
-  bool IsSpeaking() override;
-
-  void GetVoices(std::vector<VoiceData>* out_voices) override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(TtsPlatformImplStub);
-};
-
-}  // namespace chromecast
-
-#endif  // CHROMECAST_BROWSER_TTS_TTS_PLATFORM_STUB_H_
diff --git a/chromeos/BUILD.gn b/chromeos/BUILD.gn
index 22c47430..2eaea00 100644
--- a/chromeos/BUILD.gn
+++ b/chromeos/BUILD.gn
@@ -312,6 +312,12 @@
 
       # crbug.com/1099695
       "platform.Drivefs",
+
+      # crbug.com/1114654
+      "crostini.Restart.artifact",
+
+      # crbug.com/1114752
+      "arc.ContainerMount",
     ]
   }
 
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index 54f9bd4..540a706 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-13395.0.0
\ No newline at end of file
+13398.0.0
\ No newline at end of file
diff --git a/chromeos/components/telemetry_extension_ui/resources/BUILD.gn b/chromeos/components/telemetry_extension_ui/resources/BUILD.gn
index 30b19d0..50763739 100644
--- a/chromeos/components/telemetry_extension_ui/resources/BUILD.gn
+++ b/chromeos/components/telemetry_extension_ui/resources/BUILD.gn
@@ -13,7 +13,6 @@
   deps = [
     ":trusted_closure_compile",
     ":untrusted_closure_compile",
-    ":untrusted_worker_closure_compile",
   ]
 }
 
@@ -27,10 +26,6 @@
   deps = [ ":untrusted" ]
 }
 
-js_type_check("untrusted_worker_closure_compile") {
-  deps = [ ":untrusted_worker" ]
-}
-
 js_library("trusted") {
   sources = [ "trusted.js" ]
   deps = [
@@ -50,6 +45,3 @@
 
 js_library("message_types") {
 }
-
-js_library("untrusted_worker") {
-}
diff --git a/chromeos/components/telemetry_extension_ui/resources/untrusted_scripts.js b/chromeos/components/telemetry_extension_ui/resources/dpsl.js
similarity index 71%
rename from chromeos/components/telemetry_extension_ui/resources/untrusted_scripts.js
rename to chromeos/components/telemetry_extension_ui/resources/dpsl.js
index d7f227d..ad702e70 100644
--- a/chromeos/components/telemetry_extension_ui/resources/untrusted_scripts.js
+++ b/chromeos/components/telemetry_extension_ui/resources/dpsl.js
@@ -2,7 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-/** @fileoverview Concatenation of the JS files we use in untrusted.html. */
+/**
+ * @fileoverview Concatenation of the JS files we use in
+ * chrome-untrusted://telemetry-extension/index.html.
+ */
 
 // <include src="../../system_apps/public/js/message_pipe.js">
 // <include src="message_types.js">
diff --git a/chromeos/components/telemetry_extension_ui/resources/index.html b/chromeos/components/telemetry_extension_ui/resources/index.html
index 5589737..8da03fa2 100644
--- a/chromeos/components/telemetry_extension_ui/resources/index.html
+++ b/chromeos/components/telemetry_extension_ui/resources/index.html
@@ -4,7 +4,7 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
 <title>Telemetry Extension</title>
-<iframe src="chrome-untrusted://telemetry-extension/untrusted.html"
+<iframe src="chrome-untrusted://telemetry-extension/index.html"
      style="position:fixed; top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0; overflow:hidden; z-index:999999;"></iframe>
 
 <script src="chrome://resources/mojo/mojo/public/js/mojo_bindings_lite.js"></script>
diff --git a/chromeos/components/telemetry_extension_ui/resources/telemetry_extension_resources.grd b/chromeos/components/telemetry_extension_ui/resources/telemetry_extension_resources.grd
index d8a32cb6..dc9f2f4c 100644
--- a/chromeos/components/telemetry_extension_ui/resources/telemetry_extension_resources.grd
+++ b/chromeos/components/telemetry_extension_ui/resources/telemetry_extension_resources.grd
@@ -23,9 +23,7 @@
         <include name="IDR_TELEMETRY_EXTENSION_PROBE_SERVICE_MOJO_LITE_JS" file="${root_gen_dir}/chromeos/components/telemetry_extension_ui/mojom/probe_service.mojom-lite.js" compress="gzip" use_base_dir="false" type="BINDATA" />
 
         <!-- Untrusted app host contents. -->
-        <include name="IDR_TELEMETRY_EXTENSION_UNTRUSTED_HTML" file="untrusted.html" type="BINDATA" compress="gzip" />
-        <include name="IDR_TELEMETRY_EXTENSION_UNTRUSTED_SCRIPTS_JS" file="untrusted_scripts.js" flattenhtml="true" type="BINDATA" compress="gzip" />
-        <include name="IDR_TELEMETRY_EXTENSION_UNTRUSTED_WORKER_JS" file="untrusted_worker.js" type="BINDATA" compress="gzip" />
+        <include name="IDR_TELEMETRY_EXTENSION_DPSL_JS" file="dpsl.js" flattenhtml="true" type="BINDATA" compress="gzip" />
       </if>
     </includes>
   </release>
diff --git a/chromeos/components/telemetry_extension_ui/telemetry_extension_ui.cc b/chromeos/components/telemetry_extension_ui/telemetry_extension_ui.cc
index f2cd474..54052a7f 100644
--- a/chromeos/components/telemetry_extension_ui/telemetry_extension_ui.cc
+++ b/chromeos/components/telemetry_extension_ui/telemetry_extension_ui.cc
@@ -66,12 +66,7 @@
   auto untrusted_source = TelemetryExtensionUntrustedSource::Create(
       chromeos::kChromeUIUntrustedTelemetryExtensionURL);
 
-  untrusted_source->AddResourcePath("untrusted.html",
-                                    IDR_TELEMETRY_EXTENSION_UNTRUSTED_HTML);
-  untrusted_source->AddResourcePath(
-      "untrusted_scripts.js", IDR_TELEMETRY_EXTENSION_UNTRUSTED_SCRIPTS_JS);
-  untrusted_source->AddResourcePath(
-      "untrusted_worker.js", IDR_TELEMETRY_EXTENSION_UNTRUSTED_WORKER_JS);
+  untrusted_source->AddResourcePath("dpsl.js", IDR_TELEMETRY_EXTENSION_DPSL_JS);
 
   untrusted_source->OverrideContentSecurityPolicy(
       network::mojom::CSPDirectiveName::FrameAncestors,
diff --git a/chromeos/components/telemetry_extension_ui/test/BUILD.gn b/chromeos/components/telemetry_extension_ui/test/BUILD.gn
index 8926a60..d981b18 100644
--- a/chromeos/components/telemetry_extension_ui/test/BUILD.gn
+++ b/chromeos/components/telemetry_extension_ui/test/BUILD.gn
@@ -33,11 +33,13 @@
     "../:telemetry_extension_ui",
     "//chrome/test:test_support_ui",
     "//chromeos/components/web_applications/test:test_support",
+    "//chromeos/constants",
     "//chromeos/dbus/cros_healthd:cros_healthd",
   ]
 
   data = [
     "trusted_test_requester.js",
+    "untrusted_app_resources/",
     "untrusted_browsertest.js",
     "untrusted_test_handlers.js",
   ]
@@ -48,6 +50,7 @@
   deps = [
     ":trusted_browsertest_closure_compile",
     ":untrusted_browsertest_closure_compile",
+    "untrusted_app_resources:closure_compile",
   ]
 }
 
diff --git a/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.cc b/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.cc
index a3f427a..329b528 100644
--- a/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.cc
+++ b/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.cc
@@ -4,9 +4,12 @@
 
 #include "chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.h"
 
+#include "base/base_paths.h"
 #include "base/files/file_path.h"
+#include "base/path_service.h"
 #include "chromeos/components/telemetry_extension_ui/url_constants.h"
 #include "chromeos/components/web_applications/test/sandboxed_web_ui_test_base.h"
+#include "chromeos/constants/chromeos_switches.h"
 #include "chromeos/dbus/cros_healthd/cros_healthd_client.h"
 #include "chromeos/dbus/cros_healthd/fake_cros_healthd_client.h"
 
@@ -20,6 +23,10 @@
 constexpr base::FilePath::CharType kCr[] =
     FILE_PATH_LITERAL("ui/webui/resources/js/cr.js");
 
+// Folder containing the resources for JS browser tests.
+constexpr base::FilePath::CharType kUntrustedAppResources[] = FILE_PATH_LITERAL(
+    "chromeos/components/telemetry_extension_ui/test/untrusted_app_resources");
+
 // File containing the query handlers for JS unit tests.
 constexpr base::FilePath::CharType kUntrustedTestHandlers[] = FILE_PATH_LITERAL(
     "chromeos/components/telemetry_extension_ui/test/"
@@ -41,6 +48,19 @@
 
 TelemetryExtensionUiBrowserTest::~TelemetryExtensionUiBrowserTest() = default;
 
+void TelemetryExtensionUiBrowserTest::SetUpCommandLine(
+    base::CommandLine* command_line) {
+  base::FilePath source_root;
+  base::PathService::Get(base::DIR_SOURCE_ROOT, &source_root);
+  base::FilePath file_path(kUntrustedAppResources);
+
+  command_line->AppendSwitchASCII(
+      chromeos::switches::kTelemetryExtensionDirectory,
+      source_root.Append(file_path).value());
+
+  SandboxedWebUiAppTestBase::SetUpCommandLine(command_line);
+}
+
 void TelemetryExtensionUiBrowserTest::SetUpOnMainThread() {
   {
     namespace cros_diagnostics = ::chromeos::cros_healthd::mojom;
diff --git a/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.h b/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.h
index cd1c9347..43597adcf 100644
--- a/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.h
+++ b/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.h
@@ -5,8 +5,7 @@
 #ifndef CHROMEOS_COMPONENTS_TELEMETRY_EXTENSION_UI_TEST_TELEMETRY_EXTENSION_UI_BROWSERTEST_H_
 #define CHROMEOS_COMPONENTS_TELEMETRY_EXTENSION_UI_TEST_TELEMETRY_EXTENSION_UI_BROWSERTEST_H_
 
-#include <string>
-
+#include "base/command_line.h"
 #include "chromeos/components/web_applications/test/sandboxed_web_ui_test_base.h"
 
 class TelemetryExtensionUiBrowserTest : public SandboxedWebUiAppTestBase {
@@ -19,6 +18,8 @@
   TelemetryExtensionUiBrowserTest& operator=(
       const TelemetryExtensionUiBrowserTest&) = delete;
 
+  // SandboxedWebUiAppTestBase overrides:
+  void SetUpCommandLine(base::CommandLine* command_line) override;
   void SetUpOnMainThread() override;
 };
 
diff --git a/chromeos/components/telemetry_extension_ui/test/untrusted_app_resources/BUILD.gn b/chromeos/components/telemetry_extension_ui/test/untrusted_app_resources/BUILD.gn
new file mode 100644
index 0000000..3ab0b1b
--- /dev/null
+++ b/chromeos/components/telemetry_extension_ui/test/untrusted_app_resources/BUILD.gn
@@ -0,0 +1,21 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/closure_compiler/compile_js.gni")
+
+assert(is_chromeos, "Telemetry Extension is Chrome OS only")
+assert(!is_official_build,
+       "Telemetry Extension is only built for unofficial builds")
+
+group("closure_compile") {
+  deps = [ ":worker_closure_compile" ]
+}
+
+js_type_check("worker_closure_compile") {
+  deps = [ ":worker" ]
+}
+
+js_library("worker") {
+  sources = [ "worker.js" ]
+}
diff --git a/chromeos/components/telemetry_extension_ui/resources/untrusted.html b/chromeos/components/telemetry_extension_ui/test/untrusted_app_resources/index.html
similarity index 62%
rename from chromeos/components/telemetry_extension_ui/resources/untrusted.html
rename to chromeos/components/telemetry_extension_ui/test/untrusted_app_resources/index.html
index 00291a22..fed740b 100644
--- a/chromeos/components/telemetry_extension_ui/resources/untrusted.html
+++ b/chromeos/components/telemetry_extension_ui/test/untrusted_app_resources/index.html
@@ -1,10 +1,12 @@
 <!-- Copyright 2020 The Chromium Authors. All rights reserved.
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
-
 <!DOCTYPE html>
 <meta charset="utf-8">
 <title>Untrusted Telemetry Extension</title>
 <h1 id='untrusted-title'>Telemetry Extension</h1>
 
-<script src="untrusted_scripts.js"></script>
+<!-- This support library is needed so that 3P can access telemetry APIs -->
+<script src="chrome-untrusted://telemetry-extension/dpsl.js"></script>
+
+<script src="index.js"></script>
diff --git a/chromeos/components/telemetry_extension_ui/test/untrusted_app_resources/index.js b/chromeos/components/telemetry_extension_ui/test/untrusted_app_resources/index.js
new file mode 100644
index 0000000..df32184
--- /dev/null
+++ b/chromeos/components/telemetry_extension_ui/test/untrusted_app_resources/index.js
@@ -0,0 +1,5 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+window.document.title = 'Third-party Telemetry Extension';
diff --git a/chromeos/components/telemetry_extension_ui/resources/untrusted_worker.js b/chromeos/components/telemetry_extension_ui/test/untrusted_app_resources/worker.js
similarity index 100%
rename from chromeos/components/telemetry_extension_ui/resources/untrusted_worker.js
rename to chromeos/components/telemetry_extension_ui/test/untrusted_app_resources/worker.js
diff --git a/chromeos/components/telemetry_extension_ui/test/untrusted_browsertest.js b/chromeos/components/telemetry_extension_ui/test/untrusted_browsertest.js
index 1ca550c..a5971c9 100644
--- a/chromeos/components/telemetry_extension_ui/test/untrusted_browsertest.js
+++ b/chromeos/components/telemetry_extension_ui/test/untrusted_browsertest.js
@@ -10,7 +10,7 @@
  */
 const workerUrlPolicy = trustedTypes.createPolicy(
     'telemetry-extension-static',
-    {createScriptURL: () => 'untrusted_worker.js'});
+    {createScriptURL: () => 'worker.js'});
 
 // Tests that web workers can be spawned from
 // chrome-untrusted://telemetry_extension.
diff --git a/chromeos/dbus/dlcservice/fake_dlcservice_client.cc b/chromeos/dbus/dlcservice/fake_dlcservice_client.cc
index 3ff3ff1..98a889f5 100644
--- a/chromeos/dbus/dlcservice/fake_dlcservice_client.cc
+++ b/chromeos/dbus/dlcservice/fake_dlcservice_client.cc
@@ -21,8 +21,9 @@
   InstallResult install_result{
       .error = install_err_,
       .dlc_id = dlc_id,
-      .root_path = "",
+      .root_path = install_root_path_,
   };
+  dlcs_with_content_.add_dlc_infos()->set_id(dlc_id);
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
       base::BindOnce(std::move(callback), std::move(install_result)));
@@ -31,6 +32,15 @@
 void FakeDlcserviceClient::Uninstall(const std::string& dlc_id,
                                      UninstallCallback callback) {
   VLOG(1) << "Requesting to uninstall DLC=" << dlc_id;
+  for (auto iter = dlcs_with_content_.dlc_infos().begin();
+       iter != dlcs_with_content_.dlc_infos().end();) {
+    if (iter->id() != dlc_id) {
+      iter++;
+      continue;
+    }
+    iter = dlcs_with_content_.mutable_dlc_infos()->erase(iter);
+  }
+
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::BindOnce(std::move(callback), uninstall_err_));
 }
diff --git a/chromeos/dbus/dlcservice/fake_dlcservice_client.h b/chromeos/dbus/dlcservice/fake_dlcservice_client.h
index efdb875..5c77892 100644
--- a/chromeos/dbus/dlcservice/fake_dlcservice_client.h
+++ b/chromeos/dbus/dlcservice/fake_dlcservice_client.h
@@ -37,12 +37,13 @@
   void NotifyObserversForTest(const dlcservice::DlcState& dlc_state);
 
   // Setters:
-  // TODO(hsuregan/kimjae): Convert setters and at tests that use them to
-  // underscore style instead of camel case.
-  void SetInstallError(const std::string& err) { install_err_ = err; }
-  void SetUninstallError(const std::string& err) { uninstall_err_ = err; }
-  void SetPurgeError(const std::string& err) { purge_err_ = err; }
-  void SetGetExistingDlcsError(const std::string& err) {
+  void set_install_error(const std::string& err) { install_err_ = err; }
+  void set_install_root_path(const std::string& path) {
+    install_root_path_ = path;
+  }
+  void set_uninstall_error(const std::string& err) { uninstall_err_ = err; }
+  void set_purge_error(const std::string& err) { purge_err_ = err; }
+  void set_get_existing_dlcs_error(const std::string& err) {
     get_existing_dlcs_err_ = err;
   }
   void set_dlcs_with_content(
@@ -56,6 +57,7 @@
   std::string purge_err_ = dlcservice::kErrorNone;
   std::string get_installed_err_ = dlcservice::kErrorNone;
   std::string get_existing_dlcs_err_ = dlcservice::kErrorNone;
+  std::string install_root_path_;
   dlcservice::DlcsWithContent dlcs_with_content_;
 
   // A list of observers that are listening on state changes, etc.
diff --git a/components/autofill/core/browser/autofill_data_util.cc b/components/autofill/core/browser/autofill_data_util.cc
index 6579de9..0f91c041 100644
--- a/components/autofill/core/browser/autofill_data_util.cc
+++ b/components/autofill/core/browser/autofill_data_util.cc
@@ -19,6 +19,7 @@
 #include "components/autofill/core/browser/data_model/autofill_profile.h"
 #include "components/autofill/core/browser/data_model/credit_card.h"
 #include "components/autofill/core/browser/field_types.h"
+#include "components/autofill/core/browser/form_structure.h"
 #include "components/autofill/core/browser/geo/autofill_country.h"
 #include "components/autofill/core/browser/webdata/autofill_table.h"
 #include "components/grit/components_scaled_resources.h"
@@ -246,6 +247,27 @@
   return false;
 }
 
+void AddGroupToBitmask(uint32_t* group_bitmask, ServerFieldType type) {
+  const FieldTypeGroup group =
+      AutofillType(AutofillType(type).GetStorableType()).group();
+  switch (group) {
+    case autofill::NAME:
+      *group_bitmask |= kName;
+      break;
+    case autofill::ADDRESS_HOME:
+      *group_bitmask |= kAddress;
+      break;
+    case autofill::EMAIL:
+      *group_bitmask |= kEmail;
+      break;
+    case autofill::PHONE_HOME:
+      *group_bitmask |= kPhone;
+      break;
+    default:
+      break;
+  }
+}
+
 }  // namespace
 
 bool ContainsName(uint32_t groups) {
@@ -264,27 +286,19 @@
   return groups & kPhone;
 }
 
+uint32_t DetermineGroups(const FormStructure& form) {
+  uint32_t group_bitmask = 0;
+  for (const auto& field : form) {
+    ServerFieldType type = field->Type().GetStorableType();
+    AddGroupToBitmask(&group_bitmask, type);
+  }
+  return group_bitmask;
+}
+
 uint32_t DetermineGroups(const std::vector<ServerFieldType>& types) {
   uint32_t group_bitmask = 0;
-  for (const ServerFieldType& type : types) {
-    const FieldTypeGroup group =
-        AutofillType(AutofillType(type).GetStorableType()).group();
-    switch (group) {
-      case autofill::NAME:
-        group_bitmask |= kName;
-        break;
-      case autofill::ADDRESS_HOME:
-        group_bitmask |= kAddress;
-        break;
-      case autofill::EMAIL:
-        group_bitmask |= kEmail;
-        break;
-      case autofill::PHONE_HOME:
-        group_bitmask |= kPhone;
-        break;
-      default:
-        break;
-    }
+  for (const auto& type : types) {
+    AddGroupToBitmask(&group_bitmask, type);
   }
   return group_bitmask;
 }
diff --git a/components/autofill/core/browser/autofill_data_util.h b/components/autofill/core/browser/autofill_data_util.h
index 644ab42..cef371d6 100644
--- a/components/autofill/core/browser/autofill_data_util.h
+++ b/components/autofill/core/browser/autofill_data_util.h
@@ -15,6 +15,7 @@
 namespace autofill {
 
 class AutofillProfile;
+class FormStructure;
 
 namespace data_util {
 
@@ -54,7 +55,9 @@
 bool ContainsPhone(uint32_t groups);
 
 // Returns a bitmask indicating which of the name, address, email address, and
-// phone number FieldTypeGroups are associated with the given |types|.
+// phone number FieldTypeGroups are associated with the given |form|'s storable
+// types or |types|, respectively.
+uint32_t DetermineGroups(const FormStructure& form);
 uint32_t DetermineGroups(const std::vector<ServerFieldType>& types);
 
 // Returns true if a form has address fields or has least two supported
diff --git a/components/autofill/core/browser/autofill_manager.cc b/components/autofill/core/browser/autofill_manager.cc
index 2093acec..f2ab07d 100644
--- a/components/autofill/core/browser/autofill_manager.cc
+++ b/components/autofill/core/browser/autofill_manager.cc
@@ -871,8 +871,7 @@
   if (!user_did_type_ || autofill_field->is_autofilled) {
     form_interactions_ukm_logger_->LogTextFieldDidChange(*form_structure,
                                                          *autofill_field);
-    profile_form_bitmask =
-        data_util::DetermineGroups(form_structure->GetServerFieldTypes());
+    profile_form_bitmask = data_util::DetermineGroups(*form_structure);
   }
 
   if (!autofill_field->is_autofilled) {
@@ -1198,9 +1197,7 @@
   }
 
   uint32_t profile_form_bitmask =
-      form_structure
-          ? data_util::DetermineGroups(form_structure->GetServerFieldTypes())
-          : 0;
+      form_structure ? data_util::DetermineGroups(*form_structure) : 0;
 
   AutofillMetrics::LogUserHappinessMetric(
       AutofillMetrics::USER_DID_AUTOFILL, form_types,
@@ -1233,8 +1230,7 @@
   if (!GetCachedFormAndField(form, field, &form_structure, &autofill_field))
     return;
 
-  uint32_t profile_form_bitmask =
-      data_util::DetermineGroups(form_structure->GetServerFieldTypes());
+  uint32_t profile_form_bitmask = data_util::DetermineGroups(*form_structure);
   AutofillMetrics::LogUserHappinessMetric(
       AutofillMetrics::SUGGESTIONS_SHOWN, autofill_field->Type().group(),
       client_->GetSecurityLevelForUmaHistograms(), profile_form_bitmask);
@@ -1890,11 +1886,10 @@
 
     // Fill the non-empty value from |data_model| into the result vector, which
     // will be sent to the renderer.
-    FillFieldWithValue(
-        cached_field, data_model, &result.fields[i], should_notify,
-        optional_cvc ? *optional_cvc : kEmptyCvc,
-        data_util::DetermineGroups(form_structure->GetServerFieldTypes()),
-        &failure_to_fill);
+    FillFieldWithValue(cached_field, data_model, &result.fields[i],
+                       should_notify, optional_cvc ? *optional_cvc : kEmptyCvc,
+                       data_util::DetermineGroups(*form_structure),
+                       &failure_to_fill);
 
     bool has_value_after = !result.fields[i].value.empty();
     bool is_autofilled_after = result.fields[i].is_autofilled;
@@ -2056,8 +2051,7 @@
       continue;
     }
 
-    if (data_util::ContainsPhone(data_util::DetermineGroups(
-            form_structure->GetServerFieldTypes()))) {
+    if (data_util::ContainsPhone(data_util::DetermineGroups(*form_structure))) {
       has_observed_phone_number_field_ = true;
     }
 
diff --git a/components/autofill/core/browser/form_structure.cc b/components/autofill/core/browser/form_structure.cc
index a268a80..85760cd 100644
--- a/components/autofill/core/browser/form_structure.cc
+++ b/components/autofill/core/browser/form_structure.cc
@@ -1184,7 +1184,7 @@
       AutofillMetrics::LogUserHappinessMetric(
           AutofillMetrics::USER_DID_ENTER_UPI_VPA, field->Type().group(),
           security_state::SecurityLevel::SECURITY_LEVEL_COUNT,
-          data_util::DetermineGroups(GetServerFieldTypes()));
+          data_util::DetermineGroups(*this));
     }
 
     form_interactions_ukm_logger->LogFieldFillStatus(*this, *field,
@@ -2287,14 +2287,6 @@
   return form_types;
 }
 
-std::vector<ServerFieldType> FormStructure::GetServerFieldTypes() const {
-  std::vector<ServerFieldType> types(field_count());
-  std::transform(begin(), end(), types.begin(), [&](const auto& field) {
-    return field->Type().GetStorableType();
-  });
-  return types;
-}
-
 base::string16 FormStructure::GetIdentifierForRefill() const {
   if (!form_name().empty())
     return form_name();
diff --git a/components/autofill/core/browser/form_structure.h b/components/autofill/core/browser/form_structure.h
index 7e0ceef..a0f583e 100644
--- a/components/autofill/core/browser/form_structure.h
+++ b/components/autofill/core/browser/form_structure.h
@@ -285,10 +285,6 @@
   // Returns the possible form types.
   std::set<FormType> GetFormTypes() const;
 
-  // Returns a collection of ServerFieldTypes corresponding to this
-  // FormStructure's fields.
-  std::vector<ServerFieldType> GetServerFieldTypes() const;
-
   bool passwords_were_revealed() const { return passwords_were_revealed_; }
   void set_passwords_were_revealed(bool passwords_were_revealed) {
     passwords_were_revealed_ = passwords_were_revealed;
diff --git a/components/autofill/core/browser/metrics/address_form_event_logger.cc b/components/autofill/core/browser/metrics/address_form_event_logger.cc
index a5cb728..9513cf2 100644
--- a/components/autofill/core/browser/metrics/address_form_event_logger.cc
+++ b/components/autofill/core/browser/metrics/address_form_event_logger.cc
@@ -84,14 +84,7 @@
 void AddressFormEventLogger::OnLog(const std::string& name,
                                    FormEvent event,
                                    const FormStructure& form) const {
-  std::vector<ServerFieldType> types;
-  std::transform(
-      form.begin(), form.end(), std::back_inserter(types),
-      [&](const std::unique_ptr<AutofillField>& field) -> ServerFieldType {
-        return field->Type().GetStorableType();
-      });
-
-  uint32_t groups = data_util::DetermineGroups(types);
+  uint32_t groups = data_util::DetermineGroups(form);
   base::UmaHistogramEnumeration(
       name + data_util::GetSuffixForProfileFormType(groups), event,
       NUM_FORM_EVENTS);
diff --git a/components/blocked_content/android/popup_blocked_infobar_delegate_unittest.cc b/components/blocked_content/android/popup_blocked_infobar_delegate_unittest.cc
index cd89e47..6361003 100644
--- a/components/blocked_content/android/popup_blocked_infobar_delegate_unittest.cc
+++ b/components/blocked_content/android/popup_blocked_infobar_delegate_unittest.cc
@@ -10,8 +10,8 @@
 #include "components/blocked_content/popup_blocker_tab_helper.h"
 #include "components/blocked_content/safe_browsing_triggered_popup_blocker.h"
 #include "components/blocked_content/test/test_popup_navigation_delegate.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
-#include "components/content_settings/browser/test_tab_specific_content_settings_delegate.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
+#include "components/content_settings/browser/test_page_specific_content_settings_delegate.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/infobars/content/content_infobar_manager.h"
 #include "components/infobars/core/infobar.h"
@@ -54,10 +54,10 @@
     HostContentSettingsMap::RegisterProfilePrefs(pref_service_.registry());
     settings_map_ = base::MakeRefCounted<HostContentSettingsMap>(
         &pref_service_, false, false, false, false);
-    content_settings::TabSpecificContentSettings::CreateForWebContents(
+    content_settings::PageSpecificContentSettings::CreateForWebContents(
         web_contents(),
         std::make_unique<
-            content_settings::TestTabSpecificContentSettingsDelegate>(
+            content_settings::TestPageSpecificContentSettingsDelegate>(
             /*prefs=*/nullptr, settings_map_.get()));
 
     PopupBlockerTabHelper::CreateForWebContents(web_contents());
diff --git a/components/blocked_content/popup_blocker_tab_helper.cc b/components/blocked_content/popup_blocker_tab_helper.cc
index 97509a4..ef6f2e7 100644
--- a/components/blocked_content/popup_blocker_tab_helper.cc
+++ b/components/blocked_content/popup_blocker_tab_helper.cc
@@ -13,7 +13,7 @@
 #include "components/blocked_content/popup_navigation_delegate.h"
 #include "components/blocked_content/popup_tracker.h"
 #include "components/blocked_content/safe_browsing_triggered_popup_blocker.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "content/public/browser/back_forward_cache.h"
 #include "content/public/browser/navigation_controller.h"
 #include "content/public/browser/navigation_handle.h"
@@ -75,10 +75,10 @@
 }
 
 void PopupBlockerTabHelper::HidePopupNotification() {
-  auto* tscs = content_settings::TabSpecificContentSettings::GetForFrame(
+  auto* pscs = content_settings::PageSpecificContentSettings::GetForFrame(
       web_contents()->GetMainFrame());
-  if (tscs)
-    tscs->ClearPopupsBlocked();
+  if (pscs)
+    pscs->ClearPopupsBlocked();
 }
 
 void PopupBlockerTabHelper::AddBlockedPopup(
@@ -95,11 +95,10 @@
   blocked_popups_[id] = std::make_unique<BlockedRequest>(
       std::move(delegate), window_features, block_type);
 
-  // TODO(carlscab): TabSpecificContentSettings is really
-  // PageSpecificContentSettings so it does not matter that we use the
-  // source_frame here and the main frame in HidePopupNotification
+  // PageSpecificContentSettings is per page so it does not matter that we use
+  // the source_frame here and the main frame in HidePopupNotification
   auto* content_settings =
-      content_settings::TabSpecificContentSettings::GetForFrame(source_frame);
+      content_settings::PageSpecificContentSettings::GetForFrame(source_frame);
   if (content_settings) {
     content_settings->OnContentBlocked(ContentSettingsType::POPUPS);
   }
diff --git a/components/blocked_content/popup_blocker_tab_helper_unittest.cc b/components/blocked_content/popup_blocker_tab_helper_unittest.cc
index 1720115..bd87de5 100644
--- a/components/blocked_content/popup_blocker_tab_helper_unittest.cc
+++ b/components/blocked_content/popup_blocker_tab_helper_unittest.cc
@@ -9,8 +9,8 @@
 #include "components/blocked_content/safe_browsing_triggered_popup_blocker.h"
 #include "components/blocked_content/test/test_popup_navigation_delegate.h"
 #include "components/blocked_content/url_list_manager.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
-#include "components/content_settings/browser/test_tab_specific_content_settings_delegate.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
+#include "components/content_settings/browser/test_page_specific_content_settings_delegate.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "content/public/test/test_renderer_host.h"
@@ -58,10 +58,10 @@
     HostContentSettingsMap::RegisterProfilePrefs(pref_service_.registry());
     settings_map_ = base::MakeRefCounted<HostContentSettingsMap>(
         &pref_service_, false, false, false, false);
-    content_settings::TabSpecificContentSettings::CreateForWebContents(
+    content_settings::PageSpecificContentSettings::CreateForWebContents(
         web_contents(),
         std::make_unique<
-            content_settings::TestTabSpecificContentSettingsDelegate>(
+            content_settings::TestPageSpecificContentSettingsDelegate>(
             /*prefs=*/nullptr, settings_map_.get()));
 
     PopupBlockerTabHelper::CreateForWebContents(web_contents());
@@ -153,7 +153,7 @@
 
 TEST_F(PopupBlockerTabHelperTest, SetsContentSettingsPopupState) {
   auto* content_settings =
-      content_settings::TabSpecificContentSettings::GetForFrame(
+      content_settings::PageSpecificContentSettings::GetForFrame(
           web_contents()->GetMainFrame());
   EXPECT_FALSE(content_settings->IsContentBlocked(ContentSettingsType::POPUPS));
 
@@ -183,12 +183,12 @@
       web_contents()->GetMainFrame(),
       std::make_unique<TestPopupNavigationDelegate>(GURL(kUrl1), &result),
       blink::mojom::WindowFeatures(), PopupBlockType::kNoGesture);
-  EXPECT_TRUE(content_settings::TabSpecificContentSettings::GetForFrame(
+  EXPECT_TRUE(content_settings::PageSpecificContentSettings::GetForFrame(
                   web_contents()->GetMainFrame())
                   ->IsContentBlocked(ContentSettingsType::POPUPS));
 
   NavigateAndCommit(GURL(kUrl2));
-  EXPECT_FALSE(content_settings::TabSpecificContentSettings::GetForFrame(
+  EXPECT_FALSE(content_settings::PageSpecificContentSettings::GetForFrame(
                    web_contents()->GetMainFrame())
                    ->IsContentBlocked(ContentSettingsType::POPUPS));
 }
diff --git a/components/blocked_content/safe_browsing_triggered_popup_blocker_unittest.cc b/components/blocked_content/safe_browsing_triggered_popup_blocker_unittest.cc
index 8277998..d91674a 100644
--- a/components/blocked_content/safe_browsing_triggered_popup_blocker_unittest.cc
+++ b/components/blocked_content/safe_browsing_triggered_popup_blocker_unittest.cc
@@ -18,8 +18,8 @@
 #include "components/blocked_content/popup_blocker_tab_helper.h"
 #include "components/blocked_content/popup_navigation_delegate.h"
 #include "components/blocked_content/test/test_popup_navigation_delegate.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
-#include "components/content_settings/browser/test_tab_specific_content_settings_delegate.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
+#include "components/content_settings/browser/test_page_specific_content_settings_delegate.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/subresource_filter/content/browser/fake_safe_browsing_database_manager.h"
 #include "components/subresource_filter/content/browser/subresource_filter_client.h"
@@ -83,10 +83,10 @@
     subresource_filter::SubresourceFilterObserverManager::CreateForWebContents(
         web_contents());
     PopupBlockerTabHelper::CreateForWebContents(web_contents());
-    content_settings::TabSpecificContentSettings::CreateForWebContents(
+    content_settings::PageSpecificContentSettings::CreateForWebContents(
         web_contents(),
         std::make_unique<
-            content_settings::TestTabSpecificContentSettingsDelegate>(
+            content_settings::TestPageSpecificContentSettingsDelegate>(
             /*prefs=*/nullptr, settings_map_.get()));
     popup_blocker_ =
         SafeBrowsingTriggeredPopupBlocker::FromWebContents(web_contents());
diff --git a/components/content_settings/browser/BUILD.gn b/components/content_settings/browser/BUILD.gn
index a4168a9..0670f9c1 100644
--- a/components/content_settings/browser/BUILD.gn
+++ b/components/content_settings/browser/BUILD.gn
@@ -8,8 +8,8 @@
     "content_settings_manager_impl.h",
     "content_settings_usages_state.cc",
     "content_settings_usages_state.h",
-    "tab_specific_content_settings.cc",
-    "tab_specific_content_settings.h",
+    "page_specific_content_settings.cc",
+    "page_specific_content_settings.h",
   ]
   deps = [
     "//base",
@@ -29,8 +29,8 @@
 source_set("test_support") {
   testonly = true
   sources = [
-    "test_tab_specific_content_settings_delegate.cc",
-    "test_tab_specific_content_settings_delegate.h",
+    "test_page_specific_content_settings_delegate.cc",
+    "test_page_specific_content_settings_delegate.h",
   ]
   deps = [
     ":browser",
@@ -40,7 +40,7 @@
 
 source_set("unit_tests") {
   testonly = true
-  sources = [ "tab_specific_content_settings_unittest.cc" ]
+  sources = [ "page_specific_content_settings_unittest.cc" ]
   deps = [
     ":browser",
     ":test_support",
diff --git a/components/content_settings/browser/content_settings_manager_impl.cc b/components/content_settings/browser/content_settings_manager_impl.cc
index 066dc48..59fb642 100644
--- a/components/content_settings/browser/content_settings_manager_impl.cc
+++ b/components/content_settings/browser/content_settings_manager_impl.cc
@@ -5,7 +5,7 @@
 #include "components/content_settings/browser/content_settings_manager_impl.h"
 
 #include "base/memory/ptr_util.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/browser/cookie_settings.h"
 #include "components/page_load_metrics/browser/metrics_web_contents_observer.h"
 #include "components/page_load_metrics/browser/page_load_metrics_observer.h"
@@ -13,7 +13,7 @@
 #include "content/public/browser/render_process_host.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
 
-using content_settings::TabSpecificContentSettings;
+using content_settings::PageSpecificContentSettings;
 
 namespace content_settings {
 namespace {
@@ -46,8 +46,8 @@
                           const GURL& top_origin_url,
                           bool local,
                           bool blocked_by_policy) {
-  TabSpecificContentSettings* settings =
-      TabSpecificContentSettings::GetForFrame(
+  PageSpecificContentSettings* settings =
+      PageSpecificContentSettings::GetForFrame(
           content::RenderFrameHost::FromID(process_id, frame_id));
   if (settings)
     settings->OnDomStorageAccessed(origin_url, local, blocked_by_policy);
@@ -95,7 +95,7 @@
 
   switch (storage_type) {
     case StorageType::DATABASE:
-      TabSpecificContentSettings::WebDatabaseAccessed(
+      PageSpecificContentSettings::WebDatabaseAccessed(
           render_process_id_, render_frame_id, url, !allowed);
       break;
     case StorageType::LOCAL_STORAGE:
@@ -114,21 +114,21 @@
 
       break;
     case StorageType::FILE_SYSTEM:
-      TabSpecificContentSettings::FileSystemAccessed(
+      PageSpecificContentSettings::FileSystemAccessed(
           render_process_id_, render_frame_id, url, !allowed);
       OnStorageAccessed(render_process_id_, render_frame_id, url,
                         top_frame_origin.GetURL(), !allowed,
                         page_load_metrics::StorageType::kFileSystem);
       break;
     case StorageType::INDEXED_DB:
-      TabSpecificContentSettings::IndexedDBAccessed(
+      PageSpecificContentSettings::IndexedDBAccessed(
           render_process_id_, render_frame_id, url, !allowed);
       OnStorageAccessed(render_process_id_, render_frame_id, url,
                         top_frame_origin.GetURL(), !allowed,
                         page_load_metrics::StorageType::kIndexedDb);
       break;
     case StorageType::CACHE:
-      TabSpecificContentSettings::CacheStorageAccessed(
+      PageSpecificContentSettings::CacheStorageAccessed(
           render_process_id_, render_frame_id, url, !allowed);
       OnStorageAccessed(render_process_id_, render_frame_id, url,
                         top_frame_origin.GetURL(), !allowed,
@@ -144,9 +144,9 @@
 
 void ContentSettingsManagerImpl::OnContentBlocked(int32_t render_frame_id,
                                                   ContentSettingsType type) {
-  TabSpecificContentSettings* settings =
-      TabSpecificContentSettings::GetForFrame(render_process_id_,
-                                              render_frame_id);
+  PageSpecificContentSettings* settings =
+      PageSpecificContentSettings::GetForFrame(render_process_id_,
+                                               render_frame_id);
   if (settings)
     settings->OnContentBlocked(type);
 }
diff --git a/components/content_settings/browser/content_settings_usages_state.cc b/components/content_settings/browser/content_settings_usages_state.cc
index 63e75f1..03203264 100644
--- a/components/content_settings/browser/content_settings_usages_state.cc
+++ b/components/content_settings/browser/content_settings_usages_state.cc
@@ -11,7 +11,7 @@
 #include "components/url_formatter/url_formatter.h"
 
 ContentSettingsUsagesState::ContentSettingsUsagesState(
-    content_settings::TabSpecificContentSettings::Delegate* delegate_,
+    content_settings::PageSpecificContentSettings::Delegate* delegate_,
     ContentSettingsType type,
     const GURL& embedder_url)
     : delegate_(delegate_), type_(type), embedder_url_(embedder_url) {}
diff --git a/components/content_settings/browser/content_settings_usages_state.h b/components/content_settings/browser/content_settings_usages_state.h
index 2e68ca9..11c4cdc 100644
--- a/components/content_settings/browser/content_settings_usages_state.h
+++ b/components/content_settings/browser/content_settings_usages_state.h
@@ -9,7 +9,7 @@
 #include <set>
 
 #include "base/macros.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/common/content_settings.h"
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "url/gurl.h"
@@ -22,7 +22,7 @@
 class ContentSettingsUsagesState {
  public:
   ContentSettingsUsagesState(
-      content_settings::TabSpecificContentSettings::Delegate* delegate_,
+      content_settings::PageSpecificContentSettings::Delegate* delegate_,
       ContentSettingsType type,
       const GURL& embedder_url);
 
@@ -55,7 +55,7 @@
 
  private:
   std::string GURLToFormattedHost(const GURL& url) const;
-  content_settings::TabSpecificContentSettings::Delegate* delegate_;
+  content_settings::PageSpecificContentSettings::Delegate* delegate_;
   ContentSettingsType type_;
   StateMap state_map_;
   GURL embedder_url_;
diff --git a/components/content_settings/browser/tab_specific_content_settings.cc b/components/content_settings/browser/page_specific_content_settings.cc
similarity index 79%
rename from components/content_settings/browser/tab_specific_content_settings.cc
rename to components/content_settings/browser/page_specific_content_settings.cc
index 2a667d6..b243d9f 100644
--- a/components/content_settings/browser/tab_specific_content_settings.cc
+++ b/components/content_settings/browser/page_specific_content_settings.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 "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 
 #include <list>
 #include <vector>
@@ -61,7 +61,7 @@
 void MaybeSendRendererContentSettingsRules(
     content::RenderFrameHost* rfh,
     const HostContentSettingsMap* map,
-    TabSpecificContentSettings::Delegate* delegate) {
+    PageSpecificContentSettings::Delegate* delegate) {
   DCHECK_EQ(rfh, rfh->GetMainFrame());
   // Only send a message to the renderer if it is initialised and not dead.
   // Otherwise, the IPC messages will be queued in the browser process,
@@ -75,7 +75,7 @@
   delegate->SetContentSettingRules(process, rules);
 }
 
-bool WillNavigationCreateNewTabSpecificContentSettingsOnCommit(
+bool WillNavigationCreateNewPageSpecificContentSettingsOnCommit(
     content::NavigationHandle* navigation_handle) {
   return navigation_handle->IsInMainFrame() &&
          !navigation_handle->IsSameDocument() &&
@@ -84,67 +84,68 @@
 
 }  // namespace
 
-TabSpecificContentSettings::SiteDataObserver::SiteDataObserver(
+PageSpecificContentSettings::SiteDataObserver::SiteDataObserver(
     content::WebContents* web_contents)
     : web_contents_(web_contents) {
   // Make sure the handler was attached to the WebContents as some UT might skip
   // this.
   auto* handler =
-      TabSpecificContentSettings::WebContentsHandler::FromWebContents(
+      PageSpecificContentSettings::WebContentsHandler::FromWebContents(
           web_contents_);
   if (handler)
     handler->AddSiteDataObserver(this);
 }
 
-TabSpecificContentSettings::SiteDataObserver::~SiteDataObserver() {
+PageSpecificContentSettings::SiteDataObserver::~SiteDataObserver() {
   if (!web_contents_)
     return;
   auto* handler =
-      TabSpecificContentSettings::WebContentsHandler::FromWebContents(
+      PageSpecificContentSettings::WebContentsHandler::FromWebContents(
           web_contents_);
   if (handler)
     handler->RemoveSiteDataObserver(this);
 }
 
-void TabSpecificContentSettings::SiteDataObserver::WebContentsDestroyed() {
+void PageSpecificContentSettings::SiteDataObserver::WebContentsDestroyed() {
   web_contents_ = nullptr;
 }
 
 // static
-void TabSpecificContentSettings::WebContentsHandler::CreateForWebContents(
+void PageSpecificContentSettings::WebContentsHandler::CreateForWebContents(
     content::WebContents* web_contents,
     std::unique_ptr<Delegate> delegate) {
   DCHECK(web_contents);
-  if (TabSpecificContentSettings::WebContentsHandler::FromWebContents(
+  if (PageSpecificContentSettings::WebContentsHandler::FromWebContents(
           web_contents)) {
     return;
   }
 
   web_contents->SetUserData(
-      TabSpecificContentSettings::WebContentsHandler::UserDataKey(),
-      base::WrapUnique(new TabSpecificContentSettings::WebContentsHandler(
+      PageSpecificContentSettings::WebContentsHandler::UserDataKey(),
+      base::WrapUnique(new PageSpecificContentSettings::WebContentsHandler(
           web_contents, std::move(delegate))));
 }
 
-TabSpecificContentSettings::WebContentsHandler::WebContentsHandler(
+PageSpecificContentSettings::WebContentsHandler::WebContentsHandler(
     content::WebContents* web_contents,
     std::unique_ptr<Delegate> delegate)
     : WebContentsObserver(web_contents),
       delegate_(std::move(delegate)),
       map_(delegate_->GetSettingsMap()) {
-  DCHECK(!TabSpecificContentSettings::GetForCurrentDocument(
+  DCHECK(!PageSpecificContentSettings::GetForCurrentDocument(
       web_contents->GetMainFrame()));
   content::SetRenderDocumentHostUserData(
-      web_contents->GetMainFrame(), TabSpecificContentSettings::UserDataKey(),
-      base::WrapUnique(new TabSpecificContentSettings(*this, delegate_.get())));
+      web_contents->GetMainFrame(), PageSpecificContentSettings::UserDataKey(),
+      base::WrapUnique(
+          new PageSpecificContentSettings(*this, delegate_.get())));
 }
 
-TabSpecificContentSettings::WebContentsHandler::~WebContentsHandler() {
+PageSpecificContentSettings::WebContentsHandler::~WebContentsHandler() {
   for (SiteDataObserver& observer : observer_list_)
     observer.WebContentsDestroyed();
 }
 
-void TabSpecificContentSettings::WebContentsHandler::
+void PageSpecificContentSettings::WebContentsHandler::
     TransferNavigationContentSettingsToCommittedDocument(
         const InflightNavigationContentSettings& navigation_settings,
         content::RenderFrameHost* rfh) {
@@ -158,7 +159,7 @@
   }
 }
 
-void TabSpecificContentSettings::WebContentsHandler::OnCookiesAccessed(
+void PageSpecificContentSettings::WebContentsHandler::OnCookiesAccessed(
     content::NavigationHandle* navigation,
     const content::CookieAccessDetails& details) {
   auto it = inflight_navigation_settings_.find(navigation);
@@ -167,23 +168,23 @@
     return;
   }
   // TODO(carlscab): We should be able to
-  // DHECK(!WillNavigationCreateNewTabSpecificContentSettingsOnCommit) here, but
-  // there is still code that starts a navigation before attaching the tab
+  // DHECK(!WillNavigationCreateNewPageSpecificContentSettingsOnCommit) here,
+  // but there is still code that starts a navigation before attaching the tab
   // helpers in DevConsole related code. So we miss the DidStartNavigation event
   // for those navigations. (https://crbug.com/1095576)
   OnCookiesAccessed(web_contents()->GetMainFrame(), details);
 }
 
-void TabSpecificContentSettings::WebContentsHandler::OnCookiesAccessed(
+void PageSpecificContentSettings::WebContentsHandler::OnCookiesAccessed(
     content::RenderFrameHost* rfh,
     const content::CookieAccessDetails& details) {
   auto* tscs =
-      TabSpecificContentSettings::GetForCurrentDocument(rfh->GetMainFrame());
+      PageSpecificContentSettings::GetForCurrentDocument(rfh->GetMainFrame());
   if (tscs)
     tscs->OnCookiesAccessed(details);
 }
 
-void TabSpecificContentSettings::WebContentsHandler::OnServiceWorkerAccessed(
+void PageSpecificContentSettings::WebContentsHandler::OnServiceWorkerAccessed(
     content::NavigationHandle* navigation,
     const GURL& scope,
     content::AllowServiceWorkerResult allowed) {
@@ -196,24 +197,24 @@
     return;
   }
   // TODO(carlscab): We should be able to
-  // DHECK(!WillNavigationCreateNewTabSpecificContentSettingsOnCommit) here, but
-  // there is still code that starts a navigation before attaching the tab
+  // DHECK(!WillNavigationCreateNewPageSpecificContentSettingsOnCommit) here,
+  // but there is still code that starts a navigation before attaching the tab
   // helpers in DevConsole related code. So we miss the DidStartNavigation event
   // for those navigations.
   OnServiceWorkerAccessed(web_contents()->GetMainFrame(), scope, allowed);
 }
 
-void TabSpecificContentSettings::WebContentsHandler::OnServiceWorkerAccessed(
+void PageSpecificContentSettings::WebContentsHandler::OnServiceWorkerAccessed(
     content::RenderFrameHost* frame,
     const GURL& scope,
     content::AllowServiceWorkerResult allowed) {
   auto* tscs =
-      TabSpecificContentSettings::GetForCurrentDocument(frame->GetMainFrame());
+      PageSpecificContentSettings::GetForCurrentDocument(frame->GetMainFrame());
   if (tscs)
     tscs->OnServiceWorkerAccessed(scope, allowed);
 }
 
-void TabSpecificContentSettings::WebContentsHandler::
+void PageSpecificContentSettings::WebContentsHandler::
     RenderFrameForInterstitialPageCreated(
         content::RenderFrameHost* render_frame_host) {
   // We want to tell the renderer-side code to ignore content settings for this
@@ -225,9 +226,9 @@
   content_settings_agent->SetAsInterstitial();
 }
 
-void TabSpecificContentSettings::WebContentsHandler::DidStartNavigation(
+void PageSpecificContentSettings::WebContentsHandler::DidStartNavigation(
     content::NavigationHandle* navigation_handle) {
-  if (!WillNavigationCreateNewTabSpecificContentSettingsOnCommit(
+  if (!WillNavigationCreateNewPageSpecificContentSettingsOnCommit(
           navigation_handle)) {
     return;
   }
@@ -236,9 +237,9 @@
       std::make_pair(navigation_handle, InflightNavigationContentSettings()));
 }
 
-void TabSpecificContentSettings::WebContentsHandler::ReadyToCommitNavigation(
+void PageSpecificContentSettings::WebContentsHandler::ReadyToCommitNavigation(
     content::NavigationHandle* navigation_handle) {
-  if (!WillNavigationCreateNewTabSpecificContentSettingsOnCommit(
+  if (!WillNavigationCreateNewPageSpecificContentSettingsOnCommit(
           navigation_handle)) {
     return;
   }
@@ -251,9 +252,9 @@
       delegate_.get());
 }
 
-void TabSpecificContentSettings::WebContentsHandler::DidFinishNavigation(
+void PageSpecificContentSettings::WebContentsHandler::DidFinishNavigation(
     content::NavigationHandle* navigation_handle) {
-  if (!WillNavigationCreateNewTabSpecificContentSettingsOnCommit(
+  if (!WillNavigationCreateNewPageSpecificContentSettingsOnCommit(
           navigation_handle)) {
     return;
   }
@@ -264,13 +265,13 @@
   }
 
   auto tscs =
-      base::WrapUnique(new TabSpecificContentSettings(*this, delegate_.get()));
+      base::WrapUnique(new PageSpecificContentSettings(*this, delegate_.get()));
 
   // TODO(carlscab): This sort of internal. Maybe add a
   // RenderDocumentHostUserData::Create(RenderFrameHost* rfh, Params...)
   content::SetRenderDocumentHostUserData(
       navigation_handle->GetRenderFrameHost(),
-      TabSpecificContentSettings::UserDataKey(), std::move(tscs));
+      PageSpecificContentSettings::UserDataKey(), std::move(tscs));
 
   auto it = inflight_navigation_settings_.find(navigation_handle);
   if (it != inflight_navigation_settings_.end()) {
@@ -282,54 +283,55 @@
   delegate_->UpdateLocationBar();
 }
 
-void TabSpecificContentSettings::WebContentsHandler::AppCacheAccessed(
+void PageSpecificContentSettings::WebContentsHandler::AppCacheAccessed(
     const GURL& manifest_url,
     bool blocked_by_policy) {
-  auto* tscs = TabSpecificContentSettings::GetForCurrentDocument(
+  auto* tscs = PageSpecificContentSettings::GetForCurrentDocument(
       web_contents()->GetMainFrame());
   if (tscs)
     tscs->AppCacheAccessed(manifest_url, blocked_by_policy);
 }
 
-void TabSpecificContentSettings::WebContentsHandler::AddSiteDataObserver(
+void PageSpecificContentSettings::WebContentsHandler::AddSiteDataObserver(
     SiteDataObserver* observer) {
   observer_list_.AddObserver(observer);
 }
 
-void TabSpecificContentSettings::WebContentsHandler::RemoveSiteDataObserver(
+void PageSpecificContentSettings::WebContentsHandler::RemoveSiteDataObserver(
     SiteDataObserver* observer) {
   observer_list_.RemoveObserver(observer);
 }
 
-void TabSpecificContentSettings::WebContentsHandler::NotifySiteDataObservers() {
+void PageSpecificContentSettings::WebContentsHandler::
+    NotifySiteDataObservers() {
   for (SiteDataObserver& observer : observer_list_)
     observer.OnSiteDataAccessed();
 }
 
-TabSpecificContentSettings::WebContentsHandler::
+PageSpecificContentSettings::WebContentsHandler::
     InflightNavigationContentSettings::InflightNavigationContentSettings() =
         default;
-TabSpecificContentSettings::WebContentsHandler::
+PageSpecificContentSettings::WebContentsHandler::
     InflightNavigationContentSettings::InflightNavigationContentSettings(
         const InflightNavigationContentSettings&) = default;
-TabSpecificContentSettings::WebContentsHandler::
+PageSpecificContentSettings::WebContentsHandler::
     InflightNavigationContentSettings::InflightNavigationContentSettings(
         InflightNavigationContentSettings&&) = default;
 
-TabSpecificContentSettings::WebContentsHandler::
+PageSpecificContentSettings::WebContentsHandler::
     InflightNavigationContentSettings::~InflightNavigationContentSettings() =
         default;
 
-TabSpecificContentSettings::WebContentsHandler::
+PageSpecificContentSettings::WebContentsHandler::
     InflightNavigationContentSettings&
-    TabSpecificContentSettings::WebContentsHandler::
+    PageSpecificContentSettings::WebContentsHandler::
         InflightNavigationContentSettings::operator=(
             InflightNavigationContentSettings&&) = default;
 
-WEB_CONTENTS_USER_DATA_KEY_IMPL(TabSpecificContentSettings::WebContentsHandler)
+WEB_CONTENTS_USER_DATA_KEY_IMPL(PageSpecificContentSettings::WebContentsHandler)
 
-TabSpecificContentSettings::TabSpecificContentSettings(
-    TabSpecificContentSettings::WebContentsHandler& handler,
+PageSpecificContentSettings::PageSpecificContentSettings(
+    PageSpecificContentSettings::WebContentsHandler& handler,
     Delegate* delegate)
     : handler_(handler),
       main_frame_(handler_.web_contents()->GetMainFrame()),
@@ -357,30 +359,30 @@
   observer_.Add(map_);
 }
 
-TabSpecificContentSettings::~TabSpecificContentSettings() = default;
+PageSpecificContentSettings::~PageSpecificContentSettings() = default;
 
 // static
-void TabSpecificContentSettings::CreateForWebContents(
+void PageSpecificContentSettings::CreateForWebContents(
     content::WebContents* web_contents,
     std::unique_ptr<Delegate> delegate) {
-  TabSpecificContentSettings::WebContentsHandler::CreateForWebContents(
+  PageSpecificContentSettings::WebContentsHandler::CreateForWebContents(
       web_contents, std::move(delegate));
 }
 
 // static
-void TabSpecificContentSettings::DeleteForWebContentsForTest(
+void PageSpecificContentSettings::DeleteForWebContentsForTest(
     content::WebContents* web_contents) {
   if (web_contents->GetMainFrame()) {
-    TabSpecificContentSettings::DeleteForCurrentDocument(
+    PageSpecificContentSettings::DeleteForCurrentDocument(
         web_contents->GetMainFrame());
   }
 
   web_contents->RemoveUserData(
-      TabSpecificContentSettings::WebContentsHandler::UserDataKey());
+      PageSpecificContentSettings::WebContentsHandler::UserDataKey());
 }
 
 // static
-TabSpecificContentSettings* TabSpecificContentSettings::GetForFrame(
+PageSpecificContentSettings* PageSpecificContentSettings::GetForFrame(
     int render_process_id,
     int render_frame_id) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
@@ -389,74 +391,74 @@
 }
 
 // static
-TabSpecificContentSettings* TabSpecificContentSettings::GetForFrame(
+PageSpecificContentSettings* PageSpecificContentSettings::GetForFrame(
     content::RenderFrameHost* rfh) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  return rfh ? TabSpecificContentSettings::GetForCurrentDocument(
+  return rfh ? PageSpecificContentSettings::GetForCurrentDocument(
                    rfh->GetMainFrame())
              : nullptr;
 }
 
 // static
-TabSpecificContentSettings::Delegate*
-TabSpecificContentSettings::GetDelegateForWebContents(
+PageSpecificContentSettings::Delegate*
+PageSpecificContentSettings::GetDelegateForWebContents(
     content::WebContents* web_contents) {
   auto* handler =
-      TabSpecificContentSettings::WebContentsHandler::FromWebContents(
+      PageSpecificContentSettings::WebContentsHandler::FromWebContents(
           web_contents);
   return handler ? handler->delegate() : nullptr;
 }
 
 // static
-void TabSpecificContentSettings::WebDatabaseAccessed(int render_process_id,
-                                                     int render_frame_id,
-                                                     const GURL& url,
-                                                     bool blocked_by_policy) {
+void PageSpecificContentSettings::WebDatabaseAccessed(int render_process_id,
+                                                      int render_frame_id,
+                                                      const GURL& url,
+                                                      bool blocked_by_policy) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  TabSpecificContentSettings* settings =
+  PageSpecificContentSettings* settings =
       GetForFrame(render_process_id, render_frame_id);
   if (settings)
     settings->OnWebDatabaseAccessed(url, blocked_by_policy);
 }
 
 // static
-void TabSpecificContentSettings::IndexedDBAccessed(int render_process_id,
-                                                   int render_frame_id,
-                                                   const GURL& url,
-                                                   bool blocked_by_policy) {
+void PageSpecificContentSettings::IndexedDBAccessed(int render_process_id,
+                                                    int render_frame_id,
+                                                    const GURL& url,
+                                                    bool blocked_by_policy) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  TabSpecificContentSettings* settings =
+  PageSpecificContentSettings* settings =
       GetForFrame(render_process_id, render_frame_id);
   if (settings)
     settings->OnIndexedDBAccessed(url, blocked_by_policy);
 }
 
 // static
-void TabSpecificContentSettings::CacheStorageAccessed(int render_process_id,
-                                                      int render_frame_id,
-                                                      const GURL& url,
-                                                      bool blocked_by_policy) {
+void PageSpecificContentSettings::CacheStorageAccessed(int render_process_id,
+                                                       int render_frame_id,
+                                                       const GURL& url,
+                                                       bool blocked_by_policy) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  TabSpecificContentSettings* settings =
+  PageSpecificContentSettings* settings =
       GetForFrame(render_process_id, render_frame_id);
   if (settings)
     settings->OnCacheStorageAccessed(url, blocked_by_policy);
 }
 
 // static
-void TabSpecificContentSettings::FileSystemAccessed(int render_process_id,
-                                                    int render_frame_id,
-                                                    const GURL& url,
-                                                    bool blocked_by_policy) {
+void PageSpecificContentSettings::FileSystemAccessed(int render_process_id,
+                                                     int render_frame_id,
+                                                     const GURL& url,
+                                                     bool blocked_by_policy) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  TabSpecificContentSettings* settings =
+  PageSpecificContentSettings* settings =
       GetForFrame(render_process_id, render_frame_id);
   if (settings)
     settings->OnFileSystemAccessed(url, blocked_by_policy);
 }
 
 // static
-void TabSpecificContentSettings::SharedWorkerAccessed(
+void PageSpecificContentSettings::SharedWorkerAccessed(
     int render_process_id,
     int render_frame_id,
     const GURL& worker_url,
@@ -464,7 +466,7 @@
     const url::Origin& constructor_origin,
     bool blocked_by_policy) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  TabSpecificContentSettings* settings =
+  PageSpecificContentSettings* settings =
       GetForFrame(render_process_id, render_frame_id);
   if (settings)
     settings->OnSharedWorkerAccessed(worker_url, name, constructor_origin,
@@ -473,13 +475,13 @@
 
 // static
 content::WebContentsObserver*
-TabSpecificContentSettings::GetWebContentsObserverForTest(
+PageSpecificContentSettings::GetWebContentsObserverForTest(
     content::WebContents* web_contents) {
-  return TabSpecificContentSettings::WebContentsHandler::FromWebContents(
+  return PageSpecificContentSettings::WebContentsHandler::FromWebContents(
       web_contents);
 }
 
-bool TabSpecificContentSettings::IsContentBlocked(
+bool PageSpecificContentSettings::IsContentBlocked(
     ContentSettingsType content_type) const {
   DCHECK_NE(ContentSettingsType::GEOLOCATION, content_type)
       << "Geolocation settings handled by ContentSettingGeolocationImageModel";
@@ -511,7 +513,7 @@
   return false;
 }
 
-bool TabSpecificContentSettings::IsContentAllowed(
+bool PageSpecificContentSettings::IsContentAllowed(
     ContentSettingsType content_type) const {
   DCHECK_NE(ContentSettingsType::AUTOMATIC_DOWNLOADS, content_type)
       << "Automatic downloads handled by DownloadRequestLimiter";
@@ -535,7 +537,7 @@
   return false;
 }
 
-void TabSpecificContentSettings::OnContentBlocked(ContentSettingsType type) {
+void PageSpecificContentSettings::OnContentBlocked(ContentSettingsType type) {
   DCHECK(type != ContentSettingsType::GEOLOCATION)
       << "Geolocation settings handled by OnGeolocationPermissionSet";
   DCHECK(type != ContentSettingsType::MEDIASTREAM_MIC &&
@@ -553,7 +555,7 @@
   }
 }
 
-void TabSpecificContentSettings::OnContentAllowed(ContentSettingsType type) {
+void PageSpecificContentSettings::OnContentAllowed(ContentSettingsType type) {
   DCHECK(type != ContentSettingsType::GEOLOCATION)
       << "Geolocation settings handled by OnGeolocationPermissionSet";
   DCHECK(type != ContentSettingsType::MEDIASTREAM_MIC &&
@@ -597,9 +599,9 @@
     delegate_->UpdateLocationBar();
 }
 
-void TabSpecificContentSettings::OnDomStorageAccessed(const GURL& url,
-                                                      bool local,
-                                                      bool blocked_by_policy) {
+void PageSpecificContentSettings::OnDomStorageAccessed(const GURL& url,
+                                                       bool local,
+                                                       bool blocked_by_policy) {
   browsing_data::LocalSharedObjectsContainer& container =
       blocked_by_policy ? blocked_local_shared_objects_
                         : allowed_local_shared_objects_;
@@ -615,7 +617,7 @@
   handler_.NotifySiteDataObservers();
 }
 
-void TabSpecificContentSettings::OnCookiesAccessed(
+void PageSpecificContentSettings::OnCookiesAccessed(
     const content::CookieAccessDetails& details) {
   if (details.cookie_list.empty())
     return;
@@ -631,8 +633,8 @@
   handler_.NotifySiteDataObservers();
 }
 
-void TabSpecificContentSettings::OnIndexedDBAccessed(const GURL& url,
-                                                     bool blocked_by_policy) {
+void PageSpecificContentSettings::OnIndexedDBAccessed(const GURL& url,
+                                                      bool blocked_by_policy) {
   if (blocked_by_policy) {
     blocked_local_shared_objects_.indexed_dbs()->Add(url::Origin::Create(url));
     OnContentBlocked(ContentSettingsType::COOKIES);
@@ -644,7 +646,7 @@
   handler_.NotifySiteDataObservers();
 }
 
-void TabSpecificContentSettings::OnCacheStorageAccessed(
+void PageSpecificContentSettings::OnCacheStorageAccessed(
     const GURL& url,
     bool blocked_by_policy) {
   if (blocked_by_policy) {
@@ -660,7 +662,7 @@
   handler_.NotifySiteDataObservers();
 }
 
-void TabSpecificContentSettings::OnServiceWorkerAccessed(
+void PageSpecificContentSettings::OnServiceWorkerAccessed(
     const GURL& scope,
     content::AllowServiceWorkerResult allowed) {
   DCHECK(scope.is_valid());
@@ -684,7 +686,7 @@
   }
 }
 
-void TabSpecificContentSettings::OnSharedWorkerAccessed(
+void PageSpecificContentSettings::OnSharedWorkerAccessed(
     const GURL& worker_url,
     const std::string& name,
     const url::Origin& constructor_origin,
@@ -701,8 +703,9 @@
   }
 }
 
-void TabSpecificContentSettings::OnWebDatabaseAccessed(const GURL& url,
-                                                       bool blocked_by_policy) {
+void PageSpecificContentSettings::OnWebDatabaseAccessed(
+    const GURL& url,
+    bool blocked_by_policy) {
   if (blocked_by_policy) {
     blocked_local_shared_objects_.databases()->Add(url::Origin::Create(url));
     OnContentBlocked(ContentSettingsType::COOKIES);
@@ -714,8 +717,8 @@
   handler_.NotifySiteDataObservers();
 }
 
-void TabSpecificContentSettings::OnFileSystemAccessed(const GURL& url,
-                                                      bool blocked_by_policy) {
+void PageSpecificContentSettings::OnFileSystemAccessed(const GURL& url,
+                                                       bool blocked_by_policy) {
   // Note that all sandboxed file system access is recorded here as
   // kTemporary; the distinction between temporary (default) and persistent
   // storage is not made in the UI that presents this data.
@@ -730,7 +733,7 @@
   handler_.NotifySiteDataObservers();
 }
 
-void TabSpecificContentSettings::OnGeolocationPermissionSet(
+void PageSpecificContentSettings::OnGeolocationPermissionSet(
     const GURL& requesting_origin,
     bool allowed) {
   geolocation_usages_state_->OnPermissionSet(requesting_origin, allowed);
@@ -738,7 +741,7 @@
 }
 
 #if defined(OS_ANDROID) || defined(OS_CHROMEOS)
-void TabSpecificContentSettings::OnProtectedMediaIdentifierPermissionSet(
+void PageSpecificContentSettings::OnProtectedMediaIdentifierPermissionSet(
     const GURL& requesting_origin,
     bool allowed) {
   if (allowed) {
@@ -749,12 +752,12 @@
 }
 #endif
 
-TabSpecificContentSettings::MicrophoneCameraState
-TabSpecificContentSettings::GetMicrophoneCameraState() const {
+PageSpecificContentSettings::MicrophoneCameraState
+PageSpecificContentSettings::GetMicrophoneCameraState() const {
   return microphone_camera_state_ | delegate_->GetMicrophoneCameraState();
 }
 
-bool TabSpecificContentSettings::IsMicrophoneCameraStateChanged() const {
+bool PageSpecificContentSettings::IsMicrophoneCameraStateChanged() const {
   if ((microphone_camera_state_ & MICROPHONE_ACCESSED) &&
       ((microphone_camera_state_ & MICROPHONE_BLOCKED)
            ? !IsContentBlocked(ContentSettingsType::MEDIASTREAM_MIC)
@@ -772,7 +775,7 @@
       media_stream_selected_video_device());
 }
 
-void TabSpecificContentSettings::OnMediaStreamPermissionSet(
+void PageSpecificContentSettings::OnMediaStreamPermissionSet(
     const GURL& request_origin,
     MicrophoneCameraState new_microphone_camera_state,
     const std::string& media_stream_selected_audio_device,
@@ -807,34 +810,34 @@
   }
 }
 
-void TabSpecificContentSettings::OnMidiSysExAccessed(
+void PageSpecificContentSettings::OnMidiSysExAccessed(
     const GURL& requesting_origin) {
   midi_usages_state_->OnPermissionSet(requesting_origin, true);
   OnContentAllowed(ContentSettingsType::MIDI_SYSEX);
 }
 
-void TabSpecificContentSettings::OnMidiSysExAccessBlocked(
+void PageSpecificContentSettings::OnMidiSysExAccessBlocked(
     const GURL& requesting_origin) {
   midi_usages_state_->OnPermissionSet(requesting_origin, false);
   OnContentBlocked(ContentSettingsType::MIDI_SYSEX);
 }
 
-void TabSpecificContentSettings::FlashDownloadBlocked() {
+void PageSpecificContentSettings::FlashDownloadBlocked() {
   OnContentBlocked(ContentSettingsType::PLUGINS);
 }
 
-void TabSpecificContentSettings::ClearPopupsBlocked() {
+void PageSpecificContentSettings::ClearPopupsBlocked() {
   ContentSettingsStatus& status =
       content_settings_status_[ContentSettingsType::POPUPS];
   status.blocked = false;
   delegate_->UpdateLocationBar();
 }
 
-void TabSpecificContentSettings::OnAudioBlocked() {
+void PageSpecificContentSettings::OnAudioBlocked() {
   OnContentBlocked(ContentSettingsType::SOUND);
 }
 
-void TabSpecificContentSettings::SetPepperBrokerAllowed(bool allowed) {
+void PageSpecificContentSettings::SetPepperBrokerAllowed(bool allowed) {
   if (allowed) {
     OnContentAllowed(ContentSettingsType::PPAPI_BROKER);
   } else {
@@ -842,7 +845,7 @@
   }
 }
 
-void TabSpecificContentSettings::OnContentSettingChanged(
+void PageSpecificContentSettings::OnContentSettingChanged(
     const ContentSettingsPattern& primary_pattern,
     const ContentSettingsPattern& secondary_pattern,
     ContentSettingsType content_type,
@@ -915,8 +918,8 @@
   MaybeSendRendererContentSettingsRules(main_frame_, map_, delegate_);
 }
 
-void TabSpecificContentSettings::AppCacheAccessed(const GURL& manifest_url,
-                                                  bool blocked_by_policy) {
+void PageSpecificContentSettings::AppCacheAccessed(const GURL& manifest_url,
+                                                   bool blocked_by_policy) {
   if (blocked_by_policy) {
     blocked_local_shared_objects_.appcaches()->Add(
         url::Origin::Create(manifest_url));
@@ -928,11 +931,11 @@
   }
 }
 
-void TabSpecificContentSettings::ClearContentSettingsChangedViaPageInfo() {
+void PageSpecificContentSettings::ClearContentSettingsChangedViaPageInfo() {
   content_settings_changed_via_page_info_.clear();
 }
 
-void TabSpecificContentSettings::BlockAllContentForTesting() {
+void PageSpecificContentSettings::BlockAllContentForTesting() {
   content_settings::ContentSettingsRegistry* registry =
       content_settings::ContentSettingsRegistry::GetInstance();
   for (const content_settings::ContentSettingsInfo* info : *registry) {
@@ -945,30 +948,30 @@
   }
 
   // Geolocation and media must be blocked separately, as the generic
-  // TabSpecificContentSettings::OnContentBlocked does not apply to them.
+  // PageSpecificContentSettings::OnContentBlocked does not apply to them.
   OnGeolocationPermissionSet(main_frame_->GetLastCommittedURL(), false);
   MicrophoneCameraStateFlags media_blocked =
       static_cast<MicrophoneCameraStateFlags>(
-          TabSpecificContentSettings::MICROPHONE_ACCESSED |
-          TabSpecificContentSettings::MICROPHONE_BLOCKED |
-          TabSpecificContentSettings::CAMERA_ACCESSED |
-          TabSpecificContentSettings::CAMERA_BLOCKED);
+          PageSpecificContentSettings::MICROPHONE_ACCESSED |
+          PageSpecificContentSettings::MICROPHONE_BLOCKED |
+          PageSpecificContentSettings::CAMERA_ACCESSED |
+          PageSpecificContentSettings::CAMERA_BLOCKED);
   OnMediaStreamPermissionSet(main_frame_->GetLastCommittedURL(), media_blocked,
                              std::string(), std::string(), std::string(),
                              std::string());
 }
 
-void TabSpecificContentSettings::ContentSettingChangedViaPageInfo(
+void PageSpecificContentSettings::ContentSettingChangedViaPageInfo(
     ContentSettingsType type) {
   content_settings_changed_via_page_info_.insert(type);
 }
 
-bool TabSpecificContentSettings::HasContentSettingChangedViaPageInfo(
+bool PageSpecificContentSettings::HasContentSettingChangedViaPageInfo(
     ContentSettingsType type) const {
   return content_settings_changed_via_page_info_.find(type) !=
          content_settings_changed_via_page_info_.end();
 }
 
-RENDER_DOCUMENT_HOST_USER_DATA_KEY_IMPL(TabSpecificContentSettings)
+RENDER_DOCUMENT_HOST_USER_DATA_KEY_IMPL(PageSpecificContentSettings)
 
 }  // namespace content_settings
diff --git a/components/content_settings/browser/tab_specific_content_settings.h b/components/content_settings/browser/page_specific_content_settings.h
similarity index 93%
rename from components/content_settings/browser/tab_specific_content_settings.h
rename to components/content_settings/browser/page_specific_content_settings.h
index 6c3d78e..e7b267e 100644
--- a/components/content_settings/browser/tab_specific_content_settings.h
+++ b/components/content_settings/browser/page_specific_content_settings.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 COMPONENTS_CONTENT_SETTINGS_BROWSER_TAB_SPECIFIC_CONTENT_SETTINGS_H_
-#define COMPONENTS_CONTENT_SETTINGS_BROWSER_TAB_SPECIFIC_CONTENT_SETTINGS_H_
+#ifndef COMPONENTS_CONTENT_SETTINGS_BROWSER_PAGE_SPECIFIC_CONTENT_SETTINGS_H_
+#define COMPONENTS_CONTENT_SETTINGS_BROWSER_PAGE_SPECIFIC_CONTENT_SETTINGS_H_
 
 #include <stdint.h>
 
@@ -42,7 +42,7 @@
 
 namespace content_settings {
 
-// TODO(msramek): Media is storing their state in TabSpecificContentSettings:
+// TODO(msramek): Media is storing their state in PageSpecificContentSettings:
 // |microphone_camera_state_| without being tied to a single content setting.
 // This state is not ideal, potential solution is to save this information via
 // content::WebContentsUserData
@@ -59,17 +59,15 @@
 // keep references to objects of this class.
 //
 // When a page enters the back-forward cache its associated
-// TabSpecificContentSettings are not cleared and will be restored along with
+// PageSpecificContentSettings are not cleared and will be restored along with
 // the document when navigating back. These stored instances still listen to
 // content settings updates and keep their internal state up to date.
 //
 // Events tied to a main frame navigation will be associated with the newly
 // loaded page once the navigation commits or discarded if it does not.
-//
-// TODO(carlscab): Rename this class to PageSpecificContentSettings
-class TabSpecificContentSettings
+class PageSpecificContentSettings
     : public content_settings::Observer,
-      public content::RenderDocumentHostUserData<TabSpecificContentSettings> {
+      public content::RenderDocumentHostUserData<PageSpecificContentSettings> {
  public:
   // Fields describing the current mic/camera state. If a page has attempted to
   // access a device, the XXX_ACCESSED bit will be set. If access was blocked,
@@ -138,7 +136,7 @@
 
   // Classes that want to be notified about site data events must implement
   // this abstract class and add themselves as observer to the
-  // |TabSpecificContentSettings|.
+  // |PageSpecificContentSettings|.
   class SiteDataObserver {
    public:
     explicit SiteDataObserver(content::WebContents* web_contents);
@@ -159,26 +157,27 @@
     DISALLOW_COPY_AND_ASSIGN(SiteDataObserver);
   };
 
-  ~TabSpecificContentSettings() override;
+  ~PageSpecificContentSettings() override;
 
   static void CreateForWebContents(content::WebContents* web_contents,
                                    std::unique_ptr<Delegate> delegate);
   static void DeleteForWebContentsForTest(content::WebContents* web_contents);
 
   // Returns the object given a RenderFrameHost ids. Returns nullptr if the
-  // frame no longer exist or there are no TabSpecificContentSettings attached
+  // frame no longer exist or there are no PageSpecificContentSettings attached
   // to the document.
-  static TabSpecificContentSettings* GetForFrame(int render_process_id,
-                                                 int render_frame_id);
+  static PageSpecificContentSettings* GetForFrame(int render_process_id,
+                                                  int render_frame_id);
   // Returns the object given a RenderFrameHost. Returns nullptr if the frame
-  // is nullptr or there are no TabSpecificContentSettings attached to the
+  // is nullptr or there are no PageSpecificContentSettings attached to the
   // document.
-  static TabSpecificContentSettings* GetForFrame(content::RenderFrameHost* rfh);
+  static PageSpecificContentSettings* GetForFrame(
+      content::RenderFrameHost* rfh);
 
   // Returns the Delegate that was associated to |web_contents| in
   // CreateForWebContents. Null if CreateForWebContents was not called for
   // |web_contents|.
-  static TabSpecificContentSettings::Delegate* GetDelegateForWebContents(
+  static PageSpecificContentSettings::Delegate* GetDelegateForWebContents(
       content::WebContents* web_contents);
 
   // Called when a specific Web database in the current page was accessed. If
@@ -228,10 +227,10 @@
   static content::WebContentsObserver* GetWebContentsObserverForTest(
       content::WebContents* web_contents);
 
-  // Returns a WeakPtr to this instance. Given that TabSpecificContentSettings
+  // Returns a WeakPtr to this instance. Given that PageSpecificContentSettings
   // instances are tied to a page it is generally unsafe to store these
   // references, instead a WeakPtr should be used instead.
-  base::WeakPtr<TabSpecificContentSettings> AsWeakPtr() {
+  base::WeakPtr<PageSpecificContentSettings> AsWeakPtr() {
     return weak_factory_.GetWeakPtr();
   }
 
@@ -379,10 +378,10 @@
   bool HasContentSettingChangedViaPageInfo(ContentSettingsType type) const;
 
  private:
-  friend class content::RenderDocumentHostUserData<TabSpecificContentSettings>;
+  friend class content::RenderDocumentHostUserData<PageSpecificContentSettings>;
 
   // This class attaches to WebContents to listen to events and route them to
-  // appropriate TabSpecificContentSettings, store navigation related events
+  // appropriate PageSpecificContentSettings, store navigation related events
   // until the navigation finishes and then transferring the
   // navigation-associated state to the newly-created page.
   class WebContentsHandler
@@ -488,8 +487,8 @@
     WEB_CONTENTS_USER_DATA_KEY_DECL();
   };
 
-  explicit TabSpecificContentSettings(
-      TabSpecificContentSettings::WebContentsHandler& handler,
+  explicit PageSpecificContentSettings(
+      PageSpecificContentSettings::WebContentsHandler& handler,
       Delegate* delegate);
 
   void AppCacheAccessed(const GURL& manifest_url, bool blocked_by_policy);
@@ -564,11 +563,11 @@
 
   RENDER_DOCUMENT_HOST_USER_DATA_KEY_DECL();
 
-  base::WeakPtrFactory<TabSpecificContentSettings> weak_factory_{this};
+  base::WeakPtrFactory<PageSpecificContentSettings> weak_factory_{this};
 
-  DISALLOW_COPY_AND_ASSIGN(TabSpecificContentSettings);
+  DISALLOW_COPY_AND_ASSIGN(PageSpecificContentSettings);
 };
 
 }  // namespace content_settings
 
-#endif  // COMPONENTS_CONTENT_SETTINGS_BROWSER_TAB_SPECIFIC_CONTENT_SETTINGS_H_
+#endif  // COMPONENTS_CONTENT_SETTINGS_BROWSER_PAGE_SPECIFIC_CONTENT_SETTINGS_H_
diff --git a/components/content_settings/browser/tab_specific_content_settings_unittest.cc b/components/content_settings/browser/page_specific_content_settings_unittest.cc
similarity index 87%
rename from components/content_settings/browser/tab_specific_content_settings_unittest.cc
rename to components/content_settings/browser/page_specific_content_settings_unittest.cc
index 806028e..0d3212f 100644
--- a/components/content_settings/browser/tab_specific_content_settings_unittest.cc
+++ b/components/content_settings/browser/page_specific_content_settings_unittest.cc
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 
 #include "base/macros.h"
 #include "base/optional.h"
 #include "base/strings/string16.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
-#include "components/content_settings/browser/test_tab_specific_content_settings_delegate.h"
+#include "components/content_settings/browser/test_page_specific_content_settings_delegate.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/security_state/core/security_state.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
@@ -26,7 +26,7 @@
 namespace {
 
 class MockSiteDataObserver
-    : public TabSpecificContentSettings::SiteDataObserver {
+    : public PageSpecificContentSettings::SiteDataObserver {
  public:
   explicit MockSiteDataObserver(content::WebContents* web_contents)
       : SiteDataObserver(web_contents) {}
@@ -41,7 +41,7 @@
 
 }  // namespace
 
-class TabSpecificContentSettingsTest
+class PageSpecificContentSettingsTest
     : public content::RenderViewHostTestHarness {
  public:
   void SetUp() override {
@@ -49,9 +49,9 @@
     HostContentSettingsMap::RegisterProfilePrefs(prefs_.registry());
     settings_map_ = base::MakeRefCounted<HostContentSettingsMap>(
         &prefs_, false, false, false, false);
-    TabSpecificContentSettings::CreateForWebContents(
+    PageSpecificContentSettings::CreateForWebContents(
         web_contents(),
-        std::make_unique<TestTabSpecificContentSettingsDelegate>(
+        std::make_unique<TestPageSpecificContentSettingsDelegate>(
             &prefs_, settings_map_.get()));
   }
 
@@ -63,7 +63,7 @@
   HostContentSettingsMap* settings_map() { return settings_map_.get(); }
 
   content::WebContentsObserver* GetHandle() {
-    return TabSpecificContentSettings::GetWebContentsObserverForTest(
+    return PageSpecificContentSettings::GetWebContentsObserverForTest(
         web_contents());
   }
 
@@ -72,10 +72,10 @@
   scoped_refptr<HostContentSettingsMap> settings_map_;
 };
 
-TEST_F(TabSpecificContentSettingsTest, BlockedContent) {
+TEST_F(PageSpecificContentSettingsTest, BlockedContent) {
   NavigateAndCommit(GURL("http://google.com"));
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
 
   // Check that after initializing, nothing is blocked.
 #if !defined(OS_ANDROID)
@@ -107,17 +107,17 @@
                                   {*cookie1},
                                   false});
   content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
 #if !defined(OS_ANDROID)
   content_settings->OnContentBlocked(ContentSettingsType::IMAGES);
 #endif
   content_settings->OnContentBlocked(ContentSettingsType::POPUPS);
-  TabSpecificContentSettings::MicrophoneCameraState
+  PageSpecificContentSettings::MicrophoneCameraState
       blocked_microphone_camera_state =
-          TabSpecificContentSettings::MICROPHONE_ACCESSED |
-          TabSpecificContentSettings::MICROPHONE_BLOCKED |
-          TabSpecificContentSettings::CAMERA_ACCESSED |
-          TabSpecificContentSettings::CAMERA_BLOCKED;
+          PageSpecificContentSettings::MICROPHONE_ACCESSED |
+          PageSpecificContentSettings::MICROPHONE_BLOCKED |
+          PageSpecificContentSettings::CAMERA_ACCESSED |
+          PageSpecificContentSettings::CAMERA_BLOCKED;
   content_settings->OnMediaStreamPermissionSet(
       GURL("http://google.com"), blocked_microphone_camera_state, std::string(),
       std::string(), std::string(), std::string());
@@ -168,12 +168,12 @@
       simulator->GetNavigationHandle(), GURL("http://google.com"),
       content::AllowServiceWorkerResult::FromPolicy(true, false));
   content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   EXPECT_FALSE(
       content_settings->IsContentBlocked(ContentSettingsType::JAVASCRIPT));
   simulator->Commit();
   content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
 
   // Block a javascript when page starts to start ServiceWorker.
   GetHandle()->OnServiceWorkerAccessed(
@@ -185,7 +185,7 @@
   // Reset blocked content settings.
   NavigateAndCommit(GURL("http://google.com"));
   content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
 #if !defined(OS_ANDROID)
   EXPECT_FALSE(content_settings->IsContentBlocked(ContentSettingsType::IMAGES));
   EXPECT_FALSE(
@@ -202,10 +202,10 @@
       ContentSettingsType::MEDIASTREAM_CAMERA));
 }
 
-TEST_F(TabSpecificContentSettingsTest, BlockedFileSystems) {
+TEST_F(PageSpecificContentSettingsTest, BlockedFileSystems) {
   NavigateAndCommit(GURL("http://google.com"));
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
 
   // Access a file system.
   content_settings->OnFileSystemAccessed(GURL("http://google.com"), false);
@@ -217,10 +217,10 @@
   EXPECT_TRUE(content_settings->IsContentBlocked(ContentSettingsType::COOKIES));
 }
 
-TEST_F(TabSpecificContentSettingsTest, AllowedContent) {
+TEST_F(PageSpecificContentSettingsTest, AllowedContent) {
   NavigateAndCommit(GURL("http://google.com"));
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
 
   // Test default settings.
   ASSERT_FALSE(content_settings->IsContentAllowed(ContentSettingsType::IMAGES));
@@ -262,10 +262,10 @@
   ASSERT_TRUE(content_settings->IsContentBlocked(ContentSettingsType::COOKIES));
 }
 
-TEST_F(TabSpecificContentSettingsTest, EmptyCookieList) {
+TEST_F(PageSpecificContentSettingsTest, EmptyCookieList) {
   NavigateAndCommit(GURL("http://google.com"));
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
 
   ASSERT_FALSE(
       content_settings->IsContentAllowed(ContentSettingsType::COOKIES));
@@ -281,10 +281,10 @@
       content_settings->IsContentBlocked(ContentSettingsType::COOKIES));
 }
 
-TEST_F(TabSpecificContentSettingsTest, SiteDataObserver) {
+TEST_F(PageSpecificContentSettingsTest, SiteDataObserver) {
   NavigateAndCommit(GURL("http://google.com"));
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   MockSiteDataObserver mock_observer(web_contents());
   EXPECT_CALL(mock_observer, OnSiteDataAccessed()).Times(6);
 
@@ -322,10 +322,10 @@
                                           blocked_by_policy);
 }
 
-TEST_F(TabSpecificContentSettingsTest, LocalSharedObjectsContainer) {
+TEST_F(PageSpecificContentSettingsTest, LocalSharedObjectsContainer) {
   NavigateAndCommit(GURL("http://google.com"));
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   bool blocked_by_policy = false;
   auto cookie = net::CanonicalCookie::Create(GURL("http://google.com"), "k=v",
                                              base::Time::Now(),
@@ -359,10 +359,10 @@
   EXPECT_EQ(4u, objects.GetDomainCount());
 }
 
-TEST_F(TabSpecificContentSettingsTest, LocalSharedObjectsContainerCookie) {
+TEST_F(PageSpecificContentSettingsTest, LocalSharedObjectsContainerCookie) {
   NavigateAndCommit(GURL("http://google.com"));
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
   bool blocked_by_policy = false;
   auto cookie1 = net::CanonicalCookie::Create(GURL("http://google.com"), "k1=v",
                                               base::Time::Now(),
@@ -399,11 +399,12 @@
   EXPECT_EQ(1u, objects.GetDomainCount());
 }
 
-TEST_F(TabSpecificContentSettingsTest, IndicatorChangedOnContentSettingChange) {
+TEST_F(PageSpecificContentSettingsTest,
+       IndicatorChangedOnContentSettingChange) {
   NavigateAndCommit(GURL("http://google.com"));
 
-  TabSpecificContentSettings* content_settings =
-      TabSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
+  PageSpecificContentSettings* content_settings =
+      PageSpecificContentSettings::GetForFrame(web_contents()->GetMainFrame());
 
   // First trigger OnContentBlocked.
   EXPECT_FALSE(content_settings->IsContentBlocked(
diff --git a/components/content_settings/browser/test_page_specific_content_settings_delegate.cc b/components/content_settings/browser/test_page_specific_content_settings_delegate.cc
new file mode 100644
index 0000000..7e22ac1c
--- /dev/null
+++ b/components/content_settings/browser/test_page_specific_content_settings_delegate.cc
@@ -0,0 +1,67 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/content_settings/browser/test_page_specific_content_settings_delegate.h"
+
+namespace content_settings {
+
+TestPageSpecificContentSettingsDelegate::
+    TestPageSpecificContentSettingsDelegate(
+        PrefService* prefs,
+        HostContentSettingsMap* settings_map)
+    : prefs_(prefs), settings_map_(settings_map) {}
+
+TestPageSpecificContentSettingsDelegate::
+    ~TestPageSpecificContentSettingsDelegate() = default;
+
+void TestPageSpecificContentSettingsDelegate::UpdateLocationBar() {}
+
+void TestPageSpecificContentSettingsDelegate::SetContentSettingRules(
+    content::RenderProcessHost* process,
+    const RendererContentSettingRules& rules) {}
+
+PrefService* TestPageSpecificContentSettingsDelegate::GetPrefs() {
+  return prefs_;
+}
+
+HostContentSettingsMap*
+TestPageSpecificContentSettingsDelegate::GetSettingsMap() {
+  return settings_map_.get();
+}
+
+ContentSetting TestPageSpecificContentSettingsDelegate::GetEmbargoSetting(
+    const GURL& request_origin,
+    ContentSettingsType permission) {
+  return ContentSetting::CONTENT_SETTING_ASK;
+}
+
+std::vector<storage::FileSystemType>
+TestPageSpecificContentSettingsDelegate::GetAdditionalFileSystemTypes() {
+  return {};
+}
+
+browsing_data::CookieHelper::IsDeletionDisabledCallback
+TestPageSpecificContentSettingsDelegate::GetIsDeletionDisabledCallback() {
+  return base::NullCallback();
+}
+
+bool TestPageSpecificContentSettingsDelegate::IsMicrophoneCameraStateChanged(
+    PageSpecificContentSettings::MicrophoneCameraState microphone_camera_state,
+    const std::string& media_stream_selected_audio_device,
+    const std::string& media_stream_selected_video_device) {
+  return false;
+}
+
+PageSpecificContentSettings::MicrophoneCameraState
+TestPageSpecificContentSettingsDelegate::GetMicrophoneCameraState() {
+  return PageSpecificContentSettings::MICROPHONE_CAMERA_NOT_ACCESSED;
+}
+
+void TestPageSpecificContentSettingsDelegate::OnContentBlocked(
+    ContentSettingsType type) {}
+
+void TestPageSpecificContentSettingsDelegate::OnCookieAccessAllowed(
+    const net::CookieList& accessed_cookies) {}
+
+}  // namespace content_settings
diff --git a/components/content_settings/browser/test_page_specific_content_settings_delegate.h b/components/content_settings/browser/test_page_specific_content_settings_delegate.h
new file mode 100644
index 0000000..9918d1c
--- /dev/null
+++ b/components/content_settings/browser/test_page_specific_content_settings_delegate.h
@@ -0,0 +1,49 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_CONTENT_SETTINGS_BROWSER_TEST_PAGE_SPECIFIC_CONTENT_SETTINGS_DELEGATE_H_
+#define COMPONENTS_CONTENT_SETTINGS_BROWSER_TEST_PAGE_SPECIFIC_CONTENT_SETTINGS_DELEGATE_H_
+
+#include "base/memory/scoped_refptr.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
+
+namespace content_settings {
+
+class TestPageSpecificContentSettingsDelegate
+    : public PageSpecificContentSettings::Delegate {
+ public:
+  TestPageSpecificContentSettingsDelegate(PrefService* prefs,
+                                          HostContentSettingsMap* settings_map);
+  ~TestPageSpecificContentSettingsDelegate() override;
+
+  // PageSpecificContentSettings::Delegate:
+  void UpdateLocationBar() override;
+  void SetContentSettingRules(
+      content::RenderProcessHost* process,
+      const RendererContentSettingRules& rules) override;
+  PrefService* GetPrefs() override;
+  HostContentSettingsMap* GetSettingsMap() override;
+  ContentSetting GetEmbargoSetting(const GURL& request_origin,
+                                   ContentSettingsType permission) override;
+  std::vector<storage::FileSystemType> GetAdditionalFileSystemTypes() override;
+  browsing_data::CookieHelper::IsDeletionDisabledCallback
+  GetIsDeletionDisabledCallback() override;
+  bool IsMicrophoneCameraStateChanged(
+      PageSpecificContentSettings::MicrophoneCameraState
+          microphone_camera_state,
+      const std::string& media_stream_selected_audio_device,
+      const std::string& media_stream_selected_video_device) override;
+  PageSpecificContentSettings::MicrophoneCameraState GetMicrophoneCameraState()
+      override;
+  void OnContentBlocked(ContentSettingsType type) override;
+  void OnCookieAccessAllowed(const net::CookieList& accessed_cookies) override;
+
+ private:
+  PrefService* prefs_;
+  scoped_refptr<HostContentSettingsMap> settings_map_;
+};
+
+}  // namespace content_settings
+
+#endif  // COMPONENTS_CONTENT_SETTINGS_BROWSER_TEST_PAGE_SPECIFIC_CONTENT_SETTINGS_DELEGATE_H_
diff --git a/components/content_settings/browser/test_tab_specific_content_settings_delegate.cc b/components/content_settings/browser/test_tab_specific_content_settings_delegate.cc
deleted file mode 100644
index 68e107a..0000000
--- a/components/content_settings/browser/test_tab_specific_content_settings_delegate.cc
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/content_settings/browser/test_tab_specific_content_settings_delegate.h"
-
-namespace content_settings {
-
-TestTabSpecificContentSettingsDelegate::TestTabSpecificContentSettingsDelegate(
-    PrefService* prefs,
-    HostContentSettingsMap* settings_map)
-    : prefs_(prefs), settings_map_(settings_map) {}
-
-TestTabSpecificContentSettingsDelegate::
-    ~TestTabSpecificContentSettingsDelegate() = default;
-
-void TestTabSpecificContentSettingsDelegate::UpdateLocationBar() {}
-
-void TestTabSpecificContentSettingsDelegate::SetContentSettingRules(
-    content::RenderProcessHost* process,
-    const RendererContentSettingRules& rules) {}
-
-PrefService* TestTabSpecificContentSettingsDelegate::GetPrefs() {
-  return prefs_;
-}
-
-HostContentSettingsMap*
-TestTabSpecificContentSettingsDelegate::GetSettingsMap() {
-  return settings_map_.get();
-}
-
-ContentSetting TestTabSpecificContentSettingsDelegate::GetEmbargoSetting(
-    const GURL& request_origin,
-    ContentSettingsType permission) {
-  return ContentSetting::CONTENT_SETTING_ASK;
-}
-
-std::vector<storage::FileSystemType>
-TestTabSpecificContentSettingsDelegate::GetAdditionalFileSystemTypes() {
-  return {};
-}
-
-browsing_data::CookieHelper::IsDeletionDisabledCallback
-TestTabSpecificContentSettingsDelegate::GetIsDeletionDisabledCallback() {
-  return base::NullCallback();
-}
-
-bool TestTabSpecificContentSettingsDelegate::IsMicrophoneCameraStateChanged(
-    TabSpecificContentSettings::MicrophoneCameraState microphone_camera_state,
-    const std::string& media_stream_selected_audio_device,
-    const std::string& media_stream_selected_video_device) {
-  return false;
-}
-
-TabSpecificContentSettings::MicrophoneCameraState
-TestTabSpecificContentSettingsDelegate::GetMicrophoneCameraState() {
-  return TabSpecificContentSettings::MICROPHONE_CAMERA_NOT_ACCESSED;
-}
-
-void TestTabSpecificContentSettingsDelegate::OnContentBlocked(
-    ContentSettingsType type) {}
-
-void TestTabSpecificContentSettingsDelegate::OnCookieAccessAllowed(
-    const net::CookieList& accessed_cookies) {}
-
-}  // namespace content_settings
diff --git a/components/content_settings/browser/test_tab_specific_content_settings_delegate.h b/components/content_settings/browser/test_tab_specific_content_settings_delegate.h
deleted file mode 100644
index 5060613..0000000
--- a/components/content_settings/browser/test_tab_specific_content_settings_delegate.h
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_CONTENT_SETTINGS_BROWSER_TEST_TAB_SPECIFIC_CONTENT_SETTINGS_DELEGATE_H_
-#define COMPONENTS_CONTENT_SETTINGS_BROWSER_TEST_TAB_SPECIFIC_CONTENT_SETTINGS_DELEGATE_H_
-
-#include "base/memory/scoped_refptr.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
-
-namespace content_settings {
-
-class TestTabSpecificContentSettingsDelegate
-    : public TabSpecificContentSettings::Delegate {
- public:
-  TestTabSpecificContentSettingsDelegate(PrefService* prefs,
-                                         HostContentSettingsMap* settings_map);
-  ~TestTabSpecificContentSettingsDelegate() override;
-
-  // TabSpecificContentSettings::Delegate:
-  void UpdateLocationBar() override;
-  void SetContentSettingRules(
-      content::RenderProcessHost* process,
-      const RendererContentSettingRules& rules) override;
-  PrefService* GetPrefs() override;
-  HostContentSettingsMap* GetSettingsMap() override;
-  ContentSetting GetEmbargoSetting(const GURL& request_origin,
-                                   ContentSettingsType permission) override;
-  std::vector<storage::FileSystemType> GetAdditionalFileSystemTypes() override;
-  browsing_data::CookieHelper::IsDeletionDisabledCallback
-  GetIsDeletionDisabledCallback() override;
-  bool IsMicrophoneCameraStateChanged(
-      TabSpecificContentSettings::MicrophoneCameraState microphone_camera_state,
-      const std::string& media_stream_selected_audio_device,
-      const std::string& media_stream_selected_video_device) override;
-  TabSpecificContentSettings::MicrophoneCameraState GetMicrophoneCameraState()
-      override;
-  void OnContentBlocked(ContentSettingsType type) override;
-  void OnCookieAccessAllowed(const net::CookieList& accessed_cookies) override;
-
- private:
-  PrefService* prefs_;
-  scoped_refptr<HostContentSettingsMap> settings_map_;
-};
-
-}  // namespace content_settings
-
-#endif  // COMPONENTS_CONTENT_SETTINGS_BROWSER_TEST_TAB_SPECIFIC_CONTENT_SETTINGS_DELEGATE_H_
diff --git a/components/content_settings/browser/ui/cookie_controls_controller.cc b/components/content_settings/browser/ui/cookie_controls_controller.cc
index 2b49667..69b81482 100644
--- a/components/content_settings/browser/ui/cookie_controls_controller.cc
+++ b/components/content_settings/browser/ui/cookie_controls_controller.cc
@@ -10,7 +10,7 @@
 #include "base/metrics/user_metrics.h"
 #include "base/metrics/user_metrics_action.h"
 #include "components/browsing_data/content/local_shared_objects_container.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/browser/ui/cookie_controls_view.h"
 #include "components/content_settings/core/browser/content_settings_utils.h"
 #include "components/content_settings/core/browser/cookie_settings.h"
@@ -108,21 +108,21 @@
 }
 
 int CookieControlsController::GetAllowedCookieCount() {
-  auto* tscs =
-      content_settings::TabSpecificContentSettings::GetForCurrentDocument(
+  auto* pscs =
+      content_settings::PageSpecificContentSettings::GetForCurrentDocument(
           tab_observer_->web_contents()->GetMainFrame());
-  if (tscs) {
-    return tscs->allowed_local_shared_objects().GetObjectCount();
+  if (pscs) {
+    return pscs->allowed_local_shared_objects().GetObjectCount();
   } else {
     return 0;
   }
 }
 int CookieControlsController::GetBlockedCookieCount() {
-  auto* tscs =
-      content_settings::TabSpecificContentSettings::GetForCurrentDocument(
+  auto* pscs =
+      content_settings::PageSpecificContentSettings::GetForCurrentDocument(
           tab_observer_->web_contents()->GetMainFrame());
-  if (tscs) {
-    return tscs->blocked_local_shared_objects().GetObjectCount();
+  if (pscs) {
+    return pscs->blocked_local_shared_objects().GetObjectCount();
   } else {
     return 0;
   }
@@ -163,7 +163,7 @@
 CookieControlsController::TabObserver::TabObserver(
     CookieControlsController* cookie_controls,
     content::WebContents* web_contents)
-    : content_settings::TabSpecificContentSettings::SiteDataObserver(
+    : content_settings::PageSpecificContentSettings::SiteDataObserver(
           web_contents),
       cookie_controls_(cookie_controls) {}
 
diff --git a/components/content_settings/browser/ui/cookie_controls_controller.h b/components/content_settings/browser/ui/cookie_controls_controller.h
index a049880..bdb1a78 100644
--- a/components/content_settings/browser/ui/cookie_controls_controller.h
+++ b/components/content_settings/browser/ui/cookie_controls_controller.h
@@ -7,7 +7,7 @@
 
 #include "base/memory/scoped_refptr.h"
 #include "base/observer_list.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/browser/cookie_settings.h"
 #include "components/content_settings/core/common/cookie_controls_enforcement.h"
 #include "components/content_settings/core/common/cookie_controls_status.h"
@@ -54,12 +54,12 @@
   // TODO(dullweber): Make it possible to change the observed class and maybe
   // convert SiteDataObserver to a pure virtual interface.
   class TabObserver
-      : public content_settings::TabSpecificContentSettings::SiteDataObserver {
+      : public content_settings::PageSpecificContentSettings::SiteDataObserver {
    public:
     TabObserver(CookieControlsController* cookie_controls,
                 content::WebContents* web_contents);
 
-    // TabSpecificContentSettings::SiteDataObserver:
+    // PageSpecificContentSettings::SiteDataObserver:
     void OnSiteDataAccessed() override;
 
    private:
diff --git a/components/favicon/core/favicon_driver_impl.cc b/components/favicon/core/favicon_driver_impl.cc
index 67ecbf2..730121a 100644
--- a/components/favicon/core/favicon_driver_impl.cc
+++ b/components/favicon/core/favicon_driver_impl.cc
@@ -27,9 +27,6 @@
 
 FaviconDriverImpl::FaviconDriverImpl(CoreFaviconService* favicon_service)
     : favicon_service_(favicon_service) {
-  if (!favicon_service_)
-    return;
-
   if (kEnableTouchIcon) {
     handlers_.push_back(std::make_unique<FaviconHandler>(
         favicon_service_, this, FaviconDriverObserver::NON_TOUCH_LARGEST));
diff --git a/components/favicon/core/favicon_driver_impl.h b/components/favicon/core/favicon_driver_impl.h
index a0338a3..c985be4 100644
--- a/components/favicon/core/favicon_driver_impl.h
+++ b/components/favicon/core/favicon_driver_impl.h
@@ -37,6 +37,7 @@
   bool HasPendingTasksForTest();
 
  protected:
+  // |favicon_service| may be null, which means favicons are not saved.
   explicit FaviconDriverImpl(CoreFaviconService* favicon_service);
   ~FaviconDriverImpl() override;
 
@@ -54,8 +55,8 @@
   CoreFaviconService* favicon_service() { return favicon_service_; }
 
  private:
-  // KeyedService used by FaviconDriverImpl. It may be null during testing,
-  // but if it is defined, it must outlive the FaviconDriverImpl.
+  // KeyedService used by FaviconDriverImpl. It may be null, if non-null, it
+  // must outlive FaviconDriverImpl.
   CoreFaviconService* favicon_service_;
 
   // FaviconHandlers used to download the different kind of favicons.
diff --git a/components/favicon/core/favicon_handler.cc b/components/favicon/core/favicon_handler.cc
index 0ab34fe..3d278c8 100644
--- a/components/favicon/core/favicon_handler.cc
+++ b/components/favicon/core/favicon_handler.cc
@@ -230,12 +230,16 @@
   // possible values in |page_urls_|) because we want to use the most
   // up-to-date / latest URL for DB lookups, which is the page URL for which
   // we get <link rel="icon"> candidates (FaviconHandler::OnUpdateCandidates()).
-  service_->GetFaviconForPageURL(
-      last_page_url_, icon_types_, preferred_icon_size(),
-      base::BindOnce(
-          &FaviconHandler::OnFaviconDataForInitialURLFromFaviconService,
-          base::Unretained(this)),
-      &cancelable_task_tracker_for_page_url_);
+  if (service_) {
+    service_->GetFaviconForPageURL(
+        last_page_url_, icon_types_, preferred_icon_size(),
+        base::BindOnce(
+            &FaviconHandler::OnFaviconDataForInitialURLFromFaviconService,
+            base::Unretained(this)),
+        &cancelable_task_tracker_for_page_url_);
+  } else {
+    OnFaviconDataForInitialURLFromFaviconService({});
+  }
 }
 
 bool FaviconHandler::ShouldDownloadNextCandidate() const {
@@ -263,7 +267,7 @@
   // Associate the icon to all URLs in |page_urls_|, which contains page URLs
   // within the same site/document that have been considered to reliably share
   // the same icon candidates.
-  if (!delegate_->IsOffTheRecord())
+  if (service_ && !delegate_->IsOffTheRecord())
     service_->SetFavicons(page_urls_, icon_url, icon_type, image);
 
   NotifyFaviconUpdated(icon_url, icon_type, image);
@@ -277,7 +281,7 @@
   // state to be checked at the very end.
   if (!error_other_than_404_found_ &&
       notification_icon_type_ != favicon_base::IconType::kInvalid) {
-    if (!delegate_->IsOffTheRecord())
+    if (service_ && !delegate_->IsOffTheRecord())
       service_->DeleteFaviconMappings(page_urls_, notification_icon_type_);
 
     delegate_->OnFaviconDeleted(last_page_url_, handler_type_);
@@ -349,9 +353,8 @@
   best_favicon_ = DownloadedFavicon();
   manifest_url_ = manifest_url;
 
-  // Check if the manifest was previously blacklisted (e.g. returned a 404) and
-  // ignore the manifest URL if that's the case.
-  if (!manifest_url_.is_empty() &&
+  // If the manifest couldn't be downloaded (or there is no service), ignore it.
+  if (!manifest_url_.is_empty() && service_ &&
       service_->WasUnableToDownloadFavicon(manifest_url_)) {
     DVLOG(1) << "Skip failed Manifest: " << manifest_url;
     manifest_url_ = GURL();
@@ -363,6 +366,11 @@
     return;
   }
 
+  if (!service_) {
+    OnFaviconDataForManifestFromFaviconService({});
+    return;
+  }
+
   // See if there is a cached favicon for the manifest. This will update the DB
   // mappings only if the manifest URL is cached.
   GetFaviconAndUpdateMappingsUnlessIncognito(
@@ -419,7 +427,9 @@
            << ", falling back to inlined ones, which are "
            << non_manifest_original_candidates_.size();
 
-  service_->UnableToDownloadFavicon(manifest_url_);
+  if (service_)
+    service_->UnableToDownloadFavicon(manifest_url_);
+
   manifest_url_ = GURL();
 
   OnGotFinalIconURLCandidates(non_manifest_original_candidates_);
@@ -503,7 +513,8 @@
   if (bitmaps.empty()) {
     if (http_status_code == 404) {
       DVLOG(1) << "Failed to Download Favicon:" << image_url;
-      service_->UnableToDownloadFavicon(image_url);
+      if (service_)
+        service_->UnableToDownloadFavicon(image_url);
     } else if (http_status_code != 0) {
       error_other_than_404_found_ = true;
     }
@@ -596,7 +607,7 @@
     // - The favicon in the database is expired.
     // AND
     // - Redownloading the favicon fails with a non-404 error code.
-    if (!delegate_->IsOffTheRecord()) {
+    if (service_ && !delegate_->IsOffTheRecord()) {
       service_->CloneFaviconMappingsForPages(last_page_url_, icon_types_,
                                              page_urls_);
     }
@@ -622,7 +633,7 @@
   // If the icons listed in a manifest are being processed, skip the cache
   // lookup for |icon_url| since the manifest's URL is used for caching, not the
   // icon URL, and this lookup has happened earlier.
-  if (redownload_icons_ || !manifest_url_.is_empty()) {
+  if (!service_ || redownload_icons_ || !manifest_url_.is_empty()) {
     // We have the mapping, but the favicon is out of date. Download it now.
     ScheduleImageDownload(icon_url, icon_type);
   } else {
@@ -636,6 +647,10 @@
     const GURL& icon_url,
     favicon_base::IconType icon_type,
     favicon_base::FaviconResultsCallback callback) {
+  // This should only be called if |service_| is available. If this is called
+  // with no |service_|, then |callback| is never run.
+  DCHECK(service_);
+
   // We don't know the favicon, but we may have previously downloaded the
   // favicon for another page that shares the same favicon. Ask for the
   // favicon given the favicon URL.
@@ -682,7 +697,7 @@
   // Note that CancelableCallback starts cancelled.
   DCHECK(image_download_request_.IsCancelled())
       << "More than one ongoing download";
-  if (service_->WasUnableToDownloadFavicon(image_url)) {
+  if (service_ && service_->WasUnableToDownloadFavicon(image_url)) {
     DVLOG(1) << "Skip Failed FavIcon: " << image_url;
     OnDidDownloadFavicon(icon_type, 0, 0, image_url, std::vector<SkBitmap>(),
                          std::vector<gfx::Size>());
diff --git a/components/favicon/core/favicon_handler.h b/components/favicon/core/favicon_handler.h
index 8b8d2a3..98706de 100644
--- a/components/favicon/core/favicon_handler.h
+++ b/components/favicon/core/favicon_handler.h
@@ -126,7 +126,9 @@
         FaviconDriverObserver::NotificationIconType notification_icon_type) = 0;
   };
 
-  // |service| and |delegate| must not be nullptr and must outlive this class.
+  // |service| may be null (which means favicons are not saved). If |service|
+  // is non-null it must outlive this class. |delegate| must not be nullptr and
+  // must outlive this class.
   FaviconHandler(CoreFaviconService* service,
                  Delegate* delegate,
                  FaviconDriverObserver::NotificationIconType handler_type);
@@ -358,8 +360,7 @@
   GURL notification_icon_url_;
   favicon_base::IconType notification_icon_type_;
 
-  // The CoreFaviconService which implements favicon operations. May be null
-  // during testing.
+  // The CoreFaviconService which implements favicon operations. May be null.
   CoreFaviconService* service_;
 
   // This handler's delegate.
diff --git a/components/page_info/page_info.cc b/components/page_info/page_info.cc
index 5c0e5bd..100a595c 100644
--- a/components/page_info/page_info.cc
+++ b/components/page_info/page_info.cc
@@ -25,7 +25,7 @@
 #include "build/build_config.h"
 #include "components/browser_ui/util/android/url_constants.h"
 #include "components/browsing_data/content/local_storage_helper.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/browser/content_settings_registry.h"
 #include "components/content_settings/core/browser/content_settings_utils.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
@@ -435,7 +435,7 @@
   ui_ = ui;
   DCHECK(ui_);
   // TabSpecificContentSetting needs to be created before page load.
-  DCHECK(GetTabSpecificContentSettings());
+  DCHECK(GetPageSpecificContentSettings());
 
   ComputeUIInputs(site_url_);
   PresentSitePermissions();
@@ -1001,7 +1001,7 @@
 
   // Add first party cookie and site data counts.
   // TODO(crbug.com/1058597): Remove the calls to the |delegate_| once
-  // TabSpecificContentSettings has been componentized.
+  // PageSpecificContentSettings has been componentized.
   PageInfoUI::CookieInfo cookie_info;
   cookie_info.allowed = GetFirstPartyAllowedCookiesCount(site_url_);
   cookie_info.blocked = GetFirstPartyBlockedCookiesCount(site_url_);
@@ -1132,16 +1132,16 @@
   }
 }
 
-content_settings::TabSpecificContentSettings*
-PageInfo::GetTabSpecificContentSettings() const {
+content_settings::PageSpecificContentSettings*
+PageInfo::GetPageSpecificContentSettings() const {
   // TODO(https://crbug.com/1103176): PageInfo should be per page. Why is it
   // a WebContentsObserver if it is not observing anything?
-  return content_settings::TabSpecificContentSettings::GetForFrame(
+  return content_settings::PageSpecificContentSettings::GetForFrame(
       web_contents()->GetMainFrame());
 }
 
 bool PageInfo::HasContentSettingChangedViaPageInfo(ContentSettingsType type) {
-  auto* settings = GetTabSpecificContentSettings();
+  auto* settings = GetPageSpecificContentSettings();
   if (!settings)
     return false;
 
@@ -1149,7 +1149,7 @@
 }
 
 void PageInfo::ContentSettingChangedViaPageInfo(ContentSettingsType type) {
-  auto* settings = GetTabSpecificContentSettings();
+  auto* settings = GetPageSpecificContentSettings();
   if (!settings)
     return;
 
@@ -1157,7 +1157,7 @@
 }
 
 int PageInfo::GetFirstPartyAllowedCookiesCount(const GURL& site_url) {
-  auto* settings = GetTabSpecificContentSettings();
+  auto* settings = GetPageSpecificContentSettings();
   if (!settings)
     return 0;
   return settings->allowed_local_shared_objects().GetObjectCountForDomain(
@@ -1165,7 +1165,7 @@
 }
 
 int PageInfo::GetFirstPartyBlockedCookiesCount(const GURL& site_url) {
-  auto* settings = GetTabSpecificContentSettings();
+  auto* settings = GetPageSpecificContentSettings();
   if (!settings)
     return 0;
 
@@ -1174,7 +1174,7 @@
 }
 
 int PageInfo::GetThirdPartyAllowedCookiesCount(const GURL& site_url) {
-  auto* settings = GetTabSpecificContentSettings();
+  auto* settings = GetPageSpecificContentSettings();
   if (!settings)
     return 0;
 
@@ -1183,7 +1183,7 @@
 }
 
 int PageInfo::GetThirdPartyBlockedCookiesCount(const GURL& site_url) {
-  auto* settings = GetTabSpecificContentSettings();
+  auto* settings = GetPageSpecificContentSettings();
   if (!settings)
     return 0;
 
diff --git a/components/page_info/page_info.h b/components/page_info/page_info.h
index 8acef48..cd7aa9c 100644
--- a/components/page_info/page_info.h
+++ b/components/page_info/page_info.h
@@ -25,7 +25,7 @@
 }
 
 namespace content_settings {
-class TabSpecificContentSettings;
+class PageSpecificContentSettings;
 }
 
 namespace net {
@@ -260,10 +260,10 @@
   // Exposed for testing.
   static std::vector<ContentSettingsType> GetAllPermissionsForTesting();
 
-  // Returns TabSpecificContentSettings for the observed WebContents if present,
-  // nullptr otherwise.
-  content_settings::TabSpecificContentSettings* GetTabSpecificContentSettings()
-      const;
+  // Returns PageSpecificContentSettings for the observed WebContents if
+  // present, nullptr otherwise.
+  content_settings::PageSpecificContentSettings*
+  GetPageSpecificContentSettings() const;
 
   // Whether the content setting of type |type| has changed via Page Info UI.
   bool HasContentSettingChangedViaPageInfo(ContentSettingsType type);
diff --git a/components/page_info/page_info_delegate.h b/components/page_info/page_info_delegate.h
index acd53050..a40745f 100644
--- a/components/page_info/page_info_delegate.h
+++ b/components/page_info/page_info_delegate.h
@@ -7,7 +7,7 @@
 
 #include "base/strings/string16.h"
 #include "build/build_config.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "components/permissions/permission_result.h"
 #include "components/permissions/permission_uma_util.h"
@@ -71,8 +71,8 @@
   virtual HostContentSettingsMap* GetContentSettings() = 0;
 
   virtual std::unique_ptr<
-      content_settings::TabSpecificContentSettings::Delegate>
-  GetTabSpecificContentSettingsDelegate() = 0;
+      content_settings::PageSpecificContentSettings::Delegate>
+  GetPageSpecificContentSettingsDelegate() = 0;
   virtual bool IsContentDisplayedInVrHeadset() = 0;
   virtual security_state::SecurityLevel GetSecurityLevel() = 0;
   virtual security_state::VisibleSecurityState GetVisibleSecurityState() = 0;
diff --git a/components/password_manager/core/browser/BUILD.gn b/components/password_manager/core/browser/BUILD.gn
index 70c77cfc..28b0b49 100644
--- a/components/password_manager/core/browser/BUILD.gn
+++ b/components/password_manager/core/browser/BUILD.gn
@@ -44,6 +44,7 @@
     "android_affiliation/affiliation_fetcher.cc",
     "android_affiliation/affiliation_fetcher.h",
     "android_affiliation/affiliation_fetcher_delegate.h",
+    "android_affiliation/affiliation_fetcher_interface.h",
     "android_affiliation/android_affiliation_service.cc",
     "android_affiliation/android_affiliation_service.h",
     "android_affiliation/facet_manager.cc",
@@ -451,6 +452,8 @@
     "android_affiliation/mock_affiliated_match_helper.h",
     "android_affiliation/mock_affiliation_consumer.cc",
     "android_affiliation/mock_affiliation_consumer.h",
+    "android_affiliation/mock_affiliation_fetcher.cc",
+    "android_affiliation/mock_affiliation_fetcher.h",
     "fake_form_fetcher.cc",
     "fake_form_fetcher.h",
     "mock_bulk_leak_check_service.cc",
diff --git a/components/password_manager/core/browser/android_affiliation/affiliation_backend.cc b/components/password_manager/core/browser/android_affiliation/affiliation_backend.cc
index be99b64..71ad3bb 100644
--- a/components/password_manager/core/browser/android_affiliation/affiliation_backend.cc
+++ b/components/password_manager/core/browser/android_affiliation/affiliation_backend.cc
@@ -256,9 +256,8 @@
   if (requested_facet_uris.empty())
     return false;
 
-  fetcher_.reset(AffiliationFetcher::Create(url_loader_factory_,
-                                            requested_facet_uris, this));
-  fetcher_->StartRequest();
+  fetcher_.reset(AffiliationFetcher::Create(url_loader_factory_, this));
+  fetcher_->StartRequest(requested_facet_uris);
   ReportStatistics(requested_facet_uris.size());
   return true;
 }
diff --git a/components/password_manager/core/browser/android_affiliation/affiliation_fetcher.cc b/components/password_manager/core/browser/android_affiliation/affiliation_fetcher.cc
index 404fba42..8827cfc 100644
--- a/components/password_manager/core/browser/android_affiliation/affiliation_fetcher.cc
+++ b/components/password_manager/core/browser/android_affiliation/affiliation_fetcher.cc
@@ -44,10 +44,8 @@
 
 AffiliationFetcher::AffiliationFetcher(
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-    const std::vector<FacetURI>& facet_uris,
     AffiliationFetcherDelegate* delegate)
     : url_loader_factory_(std::move(url_loader_factory)),
-      requested_facet_uris_(facet_uris),
       delegate_(delegate) {
   for (const FacetURI& uri : requested_facet_uris_) {
     DCHECK(uri.is_valid());
@@ -59,14 +57,12 @@
 // static
 AffiliationFetcher* AffiliationFetcher::Create(
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-    const std::vector<FacetURI>& facet_uris,
     AffiliationFetcherDelegate* delegate) {
   if (g_testing_factory) {
     return g_testing_factory->CreateInstance(std::move(url_loader_factory),
-                                             facet_uris, delegate);
+                                             delegate);
   }
-  return new AffiliationFetcher(std::move(url_loader_factory), facet_uris,
-                                delegate);
+  return new AffiliationFetcher(std::move(url_loader_factory), delegate);
 }
 
 // static
@@ -75,8 +71,9 @@
   g_testing_factory = factory;
 }
 
-void AffiliationFetcher::StartRequest() {
+void AffiliationFetcher::StartRequest(const std::vector<FacetURI>& facet_uris) {
   DCHECK(!simple_url_loader_);
+  requested_facet_uris_ = facet_uris;
 
   net::NetworkTrafficAnnotationTag traffic_annotation =
       net::DefineNetworkTrafficAnnotation("affiliation_lookup", R"(
@@ -125,6 +122,14 @@
                      base::Unretained(this)));
 }
 
+const std::vector<FacetURI>& AffiliationFetcher::GetRequestedFacetURIs() const {
+  return requested_facet_uris_;
+}
+
+AffiliationFetcherDelegate* AffiliationFetcher::delegate() const {
+  return delegate_;
+}
+
 // static
 GURL AffiliationFetcher::BuildQueryURL() {
   return net::AppendQueryParameter(
diff --git a/components/password_manager/core/browser/android_affiliation/affiliation_fetcher.h b/components/password_manager/core/browser/android_affiliation/affiliation_fetcher.h
index 2c220e0..9caf03a 100644
--- a/components/password_manager/core/browser/android_affiliation/affiliation_fetcher.h
+++ b/components/password_manager/core/browser/android_affiliation/affiliation_fetcher.h
@@ -11,8 +11,7 @@
 
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
-#include "components/password_manager/core/browser/android_affiliation/affiliation_fetcher_delegate.h"
-#include "components/password_manager/core/browser/android_affiliation/affiliation_utils.h"
+#include "components/password_manager/core/browser/android_affiliation/affiliation_fetcher_interface.h"
 
 class GURL;
 
@@ -31,16 +30,15 @@
 //
 // An instance is good for exactly one fetch, and may be used from any thread
 // that runs a message loop (i.e. not a worker pool thread).
-class AffiliationFetcher {
+class AffiliationFetcher : public AffiliationFetcherInterface {
  public:
-  ~AffiliationFetcher();
+  ~AffiliationFetcher() override;
 
-  // Constructs a fetcher to retrieve affiliations for each facet in |facet_ids|
-  // using the specified |url_loader_factory|, and will provide the results
-  // to the |delegate| on the same thread that creates the instance.
+  // Constructs a fetcher using the specified |url_loader_factory|, and will
+  // provide the results to the |delegate| on the same thread that creates the
+  // instance.
   static AffiliationFetcher* Create(
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-      const std::vector<FacetURI>& facet_uris,
       AffiliationFetcherDelegate* delegate);
 
   // Builds the URL for the Affiliation API's lookup method.
@@ -53,25 +51,22 @@
   // calls. The caller may pass in NULL to resume using the default factory.
   static void SetFactoryForTesting(TestAffiliationFetcherFactory* factory);
 
-  // Actually starts the request, and will call the delegate with the results on
-  // the same thread when done. If |this| is destroyed before completion, the
-  // in-flight request is cancelled, and the delegate will not be called.
-  // Further details:
+  // Actually starts the request to retrieve affiliations for each facet in
+  // |facet_uris|, and will call the delegate with the results on the same
+  // thread when done. If |this| is destroyed before completion, the in-flight
+  // request is cancelled, and the delegate will not be called. Further details:
   //   * No cookies are sent/saved with the request.
   //   * In case of network/server errors, the request will not be retried.
   //   * Results are guaranteed to be always fresh and will never be cached.
-  void StartRequest();
+  void StartRequest(const std::vector<FacetURI>& facet_uris) override;
 
-  const std::vector<FacetURI>& requested_facet_uris() const {
-    return requested_facet_uris_;
-  }
+  const std::vector<FacetURI>& GetRequestedFacetURIs() const override;
 
-  AffiliationFetcherDelegate* delegate() const { return delegate_; }
+  AffiliationFetcherDelegate* delegate() const;
 
  protected:
   AffiliationFetcher(
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-      const std::vector<FacetURI>& facet_uris,
       AffiliationFetcherDelegate* delegate);
 
  private:
@@ -91,7 +86,7 @@
   void OnSimpleLoaderComplete(std::unique_ptr<std::string> response_body);
 
   scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
-  const std::vector<FacetURI> requested_facet_uris_;
+  std::vector<FacetURI> requested_facet_uris_;
   AffiliationFetcherDelegate* const delegate_;
 
   std::unique_ptr<network::SimpleURLLoader> simple_url_loader_;
diff --git a/components/password_manager/core/browser/android_affiliation/affiliation_fetcher_interface.h b/components/password_manager/core/browser/android_affiliation/affiliation_fetcher_interface.h
new file mode 100644
index 0000000..dbeb4d5b
--- /dev/null
+++ b/components/password_manager/core/browser/android_affiliation/affiliation_fetcher_interface.h
@@ -0,0 +1,38 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_ANDROID_AFFILIATION_AFFILIATION_FETCHER_INTERFACE_H_
+#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_ANDROID_AFFILIATION_AFFILIATION_FETCHER_INTERFACE_H_
+
+#include <vector>
+
+#include "components/password_manager/core/browser/android_affiliation/affiliation_fetcher_delegate.h"
+#include "components/password_manager/core/browser/android_affiliation/affiliation_utils.h"
+
+namespace password_manager {
+
+class AffiliationFetcherInterface {
+ public:
+  AffiliationFetcherInterface() = default;
+  virtual ~AffiliationFetcherInterface() = default;
+
+  // Not copyable or movable
+  AffiliationFetcherInterface(const AffiliationFetcherInterface&) = delete;
+  AffiliationFetcherInterface& operator=(const AffiliationFetcherInterface&) =
+      delete;
+  AffiliationFetcherInterface(AffiliationFetcherInterface&&) = delete;
+  AffiliationFetcherInterface& operator=(AffiliationFetcherInterface&&) =
+      delete;
+
+  // Starts the request to retrieve affiliations for each facet in
+  // |facet_uris|.
+  virtual void StartRequest(const std::vector<FacetURI>& facet_uris) = 0;
+
+  // Returns requested facet uris.
+  virtual const std::vector<FacetURI>& GetRequestedFacetURIs() const = 0;
+};
+
+}  // namespace password_manager
+
+#endif  // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_ANDROID_AFFILIATION_AFFILIATION_FETCHER_INTERFACE_H_
diff --git a/components/password_manager/core/browser/android_affiliation/affiliation_fetcher_unittest.cc b/components/password_manager/core/browser/android_affiliation/affiliation_fetcher_unittest.cc
index b3bbda15..9e824e86 100644
--- a/components/password_manager/core/browser/android_affiliation/affiliation_fetcher_unittest.cc
+++ b/components/password_manager/core/browser/android_affiliation/affiliation_fetcher_unittest.cc
@@ -146,9 +146,9 @@
   SetupSuccessfulResponse(test_response.SerializeAsString());
   MockAffiliationFetcherDelegate mock_delegate;
   EXPECT_CALL(mock_delegate, OnFetchSucceededProxy());
-  std::unique_ptr<AffiliationFetcher> fetcher(AffiliationFetcher::Create(
-      test_shared_loader_factory(), requested_uris, &mock_delegate));
-  fetcher->StartRequest();
+  std::unique_ptr<AffiliationFetcher> fetcher(
+      AffiliationFetcher::Create(test_shared_loader_factory(), &mock_delegate));
+  fetcher->StartRequest(requested_uris);
   WaitForResponse();
 
   ASSERT_NO_FATAL_FAILURE(VerifyRequestPayload(requested_uris));
@@ -185,9 +185,9 @@
   SetupSuccessfulResponse(test_response.SerializeAsString());
   MockAffiliationFetcherDelegate mock_delegate;
   EXPECT_CALL(mock_delegate, OnFetchSucceededProxy());
-  std::unique_ptr<AffiliationFetcher> fetcher(AffiliationFetcher::Create(
-      test_shared_loader_factory(), requested_uris, &mock_delegate));
-  fetcher->StartRequest();
+  std::unique_ptr<AffiliationFetcher> fetcher(
+      AffiliationFetcher::Create(test_shared_loader_factory(), &mock_delegate));
+  fetcher->StartRequest(requested_uris);
   WaitForResponse();
 
   ASSERT_NO_FATAL_FAILURE(VerifyRequestPayload(requested_uris));
@@ -216,9 +216,9 @@
   SetupSuccessfulResponse(empty_test_response.SerializeAsString());
   MockAffiliationFetcherDelegate mock_delegate;
   EXPECT_CALL(mock_delegate, OnFetchSucceededProxy());
-  std::unique_ptr<AffiliationFetcher> fetcher(AffiliationFetcher::Create(
-      test_shared_loader_factory(), requested_uris, &mock_delegate));
-  fetcher->StartRequest();
+  std::unique_ptr<AffiliationFetcher> fetcher(
+      AffiliationFetcher::Create(test_shared_loader_factory(), &mock_delegate));
+  fetcher->StartRequest(requested_uris);
   WaitForResponse();
 
   ASSERT_NO_FATAL_FAILURE(VerifyRequestPayload(requested_uris));
@@ -247,9 +247,9 @@
   SetupSuccessfulResponse(test_response.SerializeAsString());
   MockAffiliationFetcherDelegate mock_delegate;
   EXPECT_CALL(mock_delegate, OnFetchSucceededProxy());
-  std::unique_ptr<AffiliationFetcher> fetcher(AffiliationFetcher::Create(
-      test_shared_loader_factory(), requested_uris, &mock_delegate));
-  fetcher->StartRequest();
+  std::unique_ptr<AffiliationFetcher> fetcher(
+      AffiliationFetcher::Create(test_shared_loader_factory(), &mock_delegate));
+  fetcher->StartRequest(requested_uris);
   WaitForResponse();
 
   ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(&mock_delegate));
@@ -275,9 +275,9 @@
   SetupSuccessfulResponse(test_response.SerializeAsString());
   MockAffiliationFetcherDelegate mock_delegate;
   EXPECT_CALL(mock_delegate, OnFetchSucceededProxy());
-  std::unique_ptr<AffiliationFetcher> fetcher(AffiliationFetcher::Create(
-      test_shared_loader_factory(), requested_uris, &mock_delegate));
-  fetcher->StartRequest();
+  std::unique_ptr<AffiliationFetcher> fetcher(
+      AffiliationFetcher::Create(test_shared_loader_factory(), &mock_delegate));
+  fetcher->StartRequest(requested_uris);
   WaitForResponse();
 
   ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(&mock_delegate));
@@ -307,9 +307,9 @@
   SetupSuccessfulResponse(test_response.SerializeAsString());
   MockAffiliationFetcherDelegate mock_delegate;
   EXPECT_CALL(mock_delegate, OnFetchSucceededProxy());
-  std::unique_ptr<AffiliationFetcher> fetcher(AffiliationFetcher::Create(
-      test_shared_loader_factory(), requested_uris, &mock_delegate));
-  fetcher->StartRequest();
+  std::unique_ptr<AffiliationFetcher> fetcher(
+      AffiliationFetcher::Create(test_shared_loader_factory(), &mock_delegate));
+  fetcher->StartRequest(requested_uris);
   WaitForResponse();
 
   ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(&mock_delegate));
@@ -331,9 +331,9 @@
   SetupSuccessfulResponse(kMalformedResponse);
   MockAffiliationFetcherDelegate mock_delegate;
   EXPECT_CALL(mock_delegate, OnMalformedResponse());
-  std::unique_ptr<AffiliationFetcher> fetcher(AffiliationFetcher::Create(
-      test_shared_loader_factory(), uris, &mock_delegate));
-  fetcher->StartRequest();
+  std::unique_ptr<AffiliationFetcher> fetcher(
+      AffiliationFetcher::Create(test_shared_loader_factory(), &mock_delegate));
+  fetcher->StartRequest(uris);
   WaitForResponse();
 }
 
@@ -355,9 +355,9 @@
   SetupSuccessfulResponse(test_response.SerializeAsString());
   MockAffiliationFetcherDelegate mock_delegate;
   EXPECT_CALL(mock_delegate, OnMalformedResponse());
-  std::unique_ptr<AffiliationFetcher> fetcher(AffiliationFetcher::Create(
-      test_shared_loader_factory(), uris, &mock_delegate));
-  fetcher->StartRequest();
+  std::unique_ptr<AffiliationFetcher> fetcher(
+      AffiliationFetcher::Create(test_shared_loader_factory(), &mock_delegate));
+  fetcher->StartRequest(uris);
   WaitForResponse();
 }
 
@@ -368,9 +368,9 @@
   SetupServerErrorResponse();
   MockAffiliationFetcherDelegate mock_delegate;
   EXPECT_CALL(mock_delegate, OnFetchFailed());
-  std::unique_ptr<AffiliationFetcher> fetcher(AffiliationFetcher::Create(
-      test_shared_loader_factory(), uris, &mock_delegate));
-  fetcher->StartRequest();
+  std::unique_ptr<AffiliationFetcher> fetcher(
+      AffiliationFetcher::Create(test_shared_loader_factory(), &mock_delegate));
+  fetcher->StartRequest(uris);
   WaitForResponse();
 }
 
@@ -381,9 +381,9 @@
   SetupNetworkErrorResponse();
   MockAffiliationFetcherDelegate mock_delegate;
   EXPECT_CALL(mock_delegate, OnFetchFailed());
-  std::unique_ptr<AffiliationFetcher> fetcher(AffiliationFetcher::Create(
-      test_shared_loader_factory(), uris, &mock_delegate));
-  fetcher->StartRequest();
+  std::unique_ptr<AffiliationFetcher> fetcher(
+      AffiliationFetcher::Create(test_shared_loader_factory(), &mock_delegate));
+  fetcher->StartRequest(uris);
   WaitForResponse();
 }
 
diff --git a/components/password_manager/core/browser/android_affiliation/fake_affiliation_api.cc b/components/password_manager/core/browser/android_affiliation/fake_affiliation_api.cc
index 672f55f6..1f07ed3 100644
--- a/components/password_manager/core/browser/android_affiliation/fake_affiliation_api.cc
+++ b/components/password_manager/core/browser/android_affiliation/fake_affiliation_api.cc
@@ -33,7 +33,7 @@
 
 std::vector<FacetURI> ScopedFakeAffiliationAPI::GetNextRequestedFacets() {
   if (fake_fetcher_factory_.has_pending_fetchers())
-    return fake_fetcher_factory_.PeekNextFetcher()->requested_facet_uris();
+    return fake_fetcher_factory_.PeekNextFetcher()->GetRequestedFacetURIs();
   return std::vector<FacetURI>();
 }
 
@@ -46,7 +46,7 @@
       new AffiliationFetcherDelegate::Result);
   for (const auto& preset_equivalence_class : preset_equivalence_relation_) {
     bool had_intersection_with_request = false;
-    for (const auto& requested_facet_uri : fetcher->requested_facet_uris()) {
+    for (const auto& requested_facet_uri : fetcher->GetRequestedFacetURIs()) {
       if (std::any_of(preset_equivalence_class.begin(),
                       preset_equivalence_class.end(),
                       [&requested_facet_uri](const Facet& facet) {
diff --git a/components/password_manager/core/browser/android_affiliation/fake_affiliation_fetcher.cc b/components/password_manager/core/browser/android_affiliation/fake_affiliation_fetcher.cc
index c723cf17..09eff4a 100644
--- a/components/password_manager/core/browser/android_affiliation/fake_affiliation_fetcher.cc
+++ b/components/password_manager/core/browser/android_affiliation/fake_affiliation_fetcher.cc
@@ -11,9 +11,8 @@
 
 password_manager::FakeAffiliationFetcher::FakeAffiliationFetcher(
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-    const std::vector<FacetURI>& facet_ids,
     AffiliationFetcherDelegate* delegate)
-    : AffiliationFetcher(std::move(url_loader_factory), facet_ids, delegate) {}
+    : AffiliationFetcher(std::move(url_loader_factory), delegate) {}
 
 password_manager::FakeAffiliationFetcher::~FakeAffiliationFetcher() = default;
 
@@ -50,10 +49,9 @@
 
 AffiliationFetcher* ScopedFakeAffiliationFetcherFactory::CreateInstance(
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-    const std::vector<FacetURI>& facet_ids,
     AffiliationFetcherDelegate* delegate) {
-  FakeAffiliationFetcher* fetcher = new FakeAffiliationFetcher(
-      std::move(url_loader_factory), facet_ids, delegate);
+  FakeAffiliationFetcher* fetcher =
+      new FakeAffiliationFetcher(std::move(url_loader_factory), delegate);
   pending_fetchers_.push(fetcher);
   return fetcher;
 }
diff --git a/components/password_manager/core/browser/android_affiliation/fake_affiliation_fetcher.h b/components/password_manager/core/browser/android_affiliation/fake_affiliation_fetcher.h
index 100de78..9343d833 100644
--- a/components/password_manager/core/browser/android_affiliation/fake_affiliation_fetcher.h
+++ b/components/password_manager/core/browser/android_affiliation/fake_affiliation_fetcher.h
@@ -21,9 +21,8 @@
  public:
   FakeAffiliationFetcher(
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-      const std::vector<FacetURI>& facet_ids,
       AffiliationFetcherDelegate* delegate);
-  ~FakeAffiliationFetcher();
+  ~FakeAffiliationFetcher() override;
 
   // Simulates successful completion of the request with |fake_result|. Note
   // that the consumer may choose to destroy |this| from within this call.
@@ -66,7 +65,6 @@
   // AffiliationFetcherFactory:
   AffiliationFetcher* CreateInstance(
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-      const std::vector<FacetURI>& facet_ids,
       AffiliationFetcherDelegate* delegate) override;
 
  private:
diff --git a/components/password_manager/core/browser/android_affiliation/mock_affiliation_fetcher.cc b/components/password_manager/core/browser/android_affiliation/mock_affiliation_fetcher.cc
new file mode 100644
index 0000000..a56cf81
--- /dev/null
+++ b/components/password_manager/core/browser/android_affiliation/mock_affiliation_fetcher.cc
@@ -0,0 +1,13 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/password_manager/core/browser/android_affiliation/mock_affiliation_fetcher.h"
+
+namespace password_manager {
+
+MockAffiliationFetcher::MockAffiliationFetcher() = default;
+
+MockAffiliationFetcher::~MockAffiliationFetcher() = default;
+
+}  // namespace password_manager
diff --git a/components/password_manager/core/browser/android_affiliation/mock_affiliation_fetcher.h b/components/password_manager/core/browser/android_affiliation/mock_affiliation_fetcher.h
new file mode 100644
index 0000000..75b8334
--- /dev/null
+++ b/components/password_manager/core/browser/android_affiliation/mock_affiliation_fetcher.h
@@ -0,0 +1,29 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_ANDROID_AFFILIATION_MOCK_AFFILIATION_FETCHER_H_
+#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_ANDROID_AFFILIATION_MOCK_AFFILIATION_FETCHER_H_
+
+#include <string>
+
+#include "components/password_manager/core/browser/android_affiliation/affiliation_fetcher_interface.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace password_manager {
+
+class MockAffiliationFetcher : public AffiliationFetcherInterface {
+ public:
+  MockAffiliationFetcher();
+  ~MockAffiliationFetcher() override;
+
+  MOCK_METHOD(void, StartRequest, (const std::vector<FacetURI>&), (override));
+  MOCK_METHOD(std::vector<FacetURI>&,
+              GetRequestedFacetURIs,
+              (),
+              (const, override));
+};
+
+}  // namespace password_manager
+
+#endif  // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_ANDROID_AFFILIATION_MOCK_AFFILIATION_FETCHER_H_
diff --git a/components/password_manager/core/browser/android_affiliation/test_affiliation_fetcher_factory.h b/components/password_manager/core/browser/android_affiliation/test_affiliation_fetcher_factory.h
index 8b43a20..e2de713 100644
--- a/components/password_manager/core/browser/android_affiliation/test_affiliation_fetcher_factory.h
+++ b/components/password_manager/core/browser/android_affiliation/test_affiliation_fetcher_factory.h
@@ -13,7 +13,6 @@
 
 namespace password_manager {
 
-class FacetURI;
 class AffiliationFetcherDelegate;
 
 // Interface for a factory to be used by AffiliationFetcher::Create() in tests
@@ -27,7 +26,6 @@
   // to the |delegate| on the same thread that creates the instance.
   virtual AffiliationFetcher* CreateInstance(
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-      const std::vector<FacetURI>& facet_ids,
       AffiliationFetcherDelegate* delegate) = 0;
 
  protected:
diff --git a/components/payments/content/android/BUILD.gn b/components/payments/content/android/BUILD.gn
index a7a89b65e4..6db3d80 100644
--- a/components/payments/content/android/BUILD.gn
+++ b/components/payments/content/android/BUILD.gn
@@ -78,6 +78,7 @@
   annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
   sources = [
     "java/src/org/chromium/components/payments/Address.java",
+    "java/src/org/chromium/components/payments/BrowserPaymentRequest.java",
     "java/src/org/chromium/components/payments/CanMakePaymentQuery.java",
     "java/src/org/chromium/components/payments/ComponentPaymentRequestImpl.java",
     "java/src/org/chromium/components/payments/CurrencyFormatter.java",
diff --git a/components/payments/content/android/java/src/org/chromium/components/payments/BrowserPaymentRequest.java b/components/payments/content/android/java/src/org/chromium/components/payments/BrowserPaymentRequest.java
new file mode 100644
index 0000000..fafcf4c8
--- /dev/null
+++ b/components/payments/content/android/java/src/org/chromium/components/payments/BrowserPaymentRequest.java
@@ -0,0 +1,85 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.components.payments;
+
+import org.chromium.mojo.system.MojoException;
+import org.chromium.payments.mojom.PaymentDetails;
+import org.chromium.payments.mojom.PaymentMethodData;
+import org.chromium.payments.mojom.PaymentOptions;
+import org.chromium.payments.mojom.PaymentValidationErrors;
+
+/**
+ * The browser part of the PaymentRequest implementation. The browser here can be either the
+ * Android Chrome browser or the WebLayer "browser".
+ */
+public interface BrowserPaymentRequest {
+    /**
+     * The browser part of the {@link PaymentRequest#init} implementation.
+     * @param methodData The supported methods specified by the merchant.
+     * @param details The payment details specified by the merchant.
+     * @param options The payment options specified by the merchant.
+     * @param googlePayBridgeEligible True when the renderer process deems the current request
+     *         eligible for the skip-to-GPay experimental flow. It is ultimately up to the browser
+     *         process to determine whether to trigger it
+     */
+    void init(PaymentMethodData[] methodData, PaymentDetails details, PaymentOptions options,
+            boolean googlePayBridgeEligible);
+
+    /**
+     * The browser part of the {@link PaymentRequest#show} implementation.
+     * @param isUserGesture Whether this method is triggered from a user gesture.
+     * @param waitForUpdatedDetails Whether to wait for updated details. It's true when merchant
+     *         passed in a promise into PaymentRequest.show(), so Chrome should disregard the
+     *         initial payment details and show a spinner until the promise resolves with the
+     *         correct payment details.
+     */
+    void show(boolean isUserGesture, boolean waitForUpdatedDetails);
+
+    /**
+     * The browser part of the {@link PaymentRequest#updateWith} implementation.
+     * @param details The details that the merchant provides to update the payment request.
+     */
+    void updateWith(PaymentDetails details);
+
+    /** The browser part of the {@link PaymentRequest#onPaymentDetailsNotUpdated} implementation. */
+    void onPaymentDetailsNotUpdated();
+
+    /** The browser part of the {@link PaymentRequest#abort} implementation. */
+    void abort();
+
+    /** The browser part of the {@link PaymentRequest#complete} implementation. */
+    void complete(int result);
+
+    /**
+     * The browser part of the {@link PaymentRequest#retry} implementation.
+     * @param errors The merchant-defined error message strings, which are used to indicate to the
+     *         end-user that something is wrong with the data of the payment response.
+     */
+    void retry(PaymentValidationErrors errors);
+
+    /**
+     * The browser part of the {@link PaymentRequest#hasEnrolledInstrument} implementation.
+     * @param perMethodQuota Whether to query with per-method quota.
+     */
+    void hasEnrolledInstrument(boolean perMethodQuota);
+
+    /** The browser part of the {@link PaymentRequest#canMakePayment} implementation. */
+    void canMakePayment();
+
+    /** The browser part of the {@link PaymentRequest#close} implementation. */
+    void close();
+
+    /**
+     * The browser part of the {@link PaymentRequest#onConnectionError} implementation.
+     * @param e The detail of the error.
+     */
+    void onConnectionError(MojoException e);
+
+    /** @return The JourneyLogger of PaymentRequestImpl. */
+    JourneyLogger getJourneyLogger();
+
+    /** Delegate to the same method of PaymentRequestImpl. */
+    void disconnectFromClientWithDebugMessage(String debugMessage);
+}
diff --git a/components/payments/content/android/java/src/org/chromium/components/payments/ComponentPaymentRequestImpl.java b/components/payments/content/android/java/src/org/chromium/components/payments/ComponentPaymentRequestImpl.java
index c667c3ca..0b7fb674 100644
--- a/components/payments/content/android/java/src/org/chromium/components/payments/ComponentPaymentRequestImpl.java
+++ b/components/payments/content/android/java/src/org/chromium/components/payments/ComponentPaymentRequestImpl.java
@@ -10,6 +10,7 @@
 import org.chromium.components.autofill.EditableOption;
 import org.chromium.mojo.system.MojoException;
 import org.chromium.payments.mojom.PaymentDetails;
+import org.chromium.payments.mojom.PaymentItem;
 import org.chromium.payments.mojom.PaymentMethodData;
 import org.chromium.payments.mojom.PaymentOptions;
 import org.chromium.payments.mojom.PaymentRequest;
@@ -25,45 +26,22 @@
  * org.chromium.chrome.browser.payments.PaymentRequestImpl.
  */
 public class ComponentPaymentRequestImpl implements PaymentRequest {
-    private final ComponentPaymentRequestDelegate mDelegate;
     private static NativeObserverForTest sNativeObserverForTest;
+    private final BrowserPaymentRequestFactory mBrowserPaymentRequestFactory;
+    private BrowserPaymentRequest mBrowserPaymentRequest;
     private PaymentRequestClient mClient;
     private PaymentRequestLifecycleObserver mPaymentRequestLifecycleObserver;
 
-    /**
-     * The delegate of {@link ComponentPaymentRequestImpl}.
-     */
-    public interface ComponentPaymentRequestDelegate {
-        // The implementation of the same methods in {@link PaymentRequest).
-        void init(PaymentMethodData[] methodData, PaymentDetails details, PaymentOptions options,
-                boolean googlePayBridgeEligible);
-        void show(boolean isUserGesture, boolean waitForUpdatedDetails);
-        void updateWith(PaymentDetails details);
-        void onPaymentDetailsNotUpdated();
-        void abort();
-        void complete(int result);
-        void retry(PaymentValidationErrors errors);
-        void hasEnrolledInstrument(boolean perMethodQuota);
-        void canMakePayment();
-        void close();
-        void onConnectionError(MojoException e);
-
+    /** The factory that creates an instance of {@link BrowserPaymentRequest}. */
+    public interface BrowserPaymentRequestFactory {
         /**
-         * Set a weak reference to the client of this delegate.
-         * @param componentPaymentRequestImpl The client of this delegate.
+         * Create an instance of {@link BrowserPaymentRequest}, and have it working together with an
+         * instance of {@link ComponentPaymentRequestImpl}.
+         * @param componentPaymentRequestImpl The ComponentPaymentRequestImpl to work together with
+         *         the BrowserPaymentRequest instance.
          */
-        void setComponentPaymentRequestImpl(
+        BrowserPaymentRequest createBrowserPaymentRequest(
                 ComponentPaymentRequestImpl componentPaymentRequestImpl);
-
-        /**
-         * @return The JourneyLogger of PaymentRequestImpl.
-         */
-        JourneyLogger getJourneyLogger();
-
-        /**
-         * Delegate to the same method of PaymentRequestImpl.
-         */
-        void disconnectFromClientWithDebugMessage(String debugMessage);
     }
 
     /**
@@ -78,8 +56,7 @@
         void onCanMakePaymentReturned();
         void onHasEnrolledInstrumentCalled();
         void onHasEnrolledInstrumentReturned();
-        void onAppListReady(@Nullable List<EditableOption> paymentApps,
-                org.chromium.payments.mojom.PaymentItem total);
+        void onAppListReady(@Nullable List<EditableOption> paymentApps, PaymentItem total);
         void onNotSupportedError();
         void onConnectionTerminated();
         void onAbortCalled();
@@ -89,11 +66,11 @@
 
     /**
      * Build an instance of the PaymentRequest implementation.
-     * @param delegate A delegate of the instance.
+     * @param browserPaymentRequestFactory The factory that generates an instance of
+     *         BrowserPaymentRequest to work with this ComponentPaymentRequestImpl instance.
      */
-    public ComponentPaymentRequestImpl(ComponentPaymentRequestDelegate delegate) {
-        mDelegate = delegate;
-        mDelegate.setComponentPaymentRequestImpl(this);
+    public ComponentPaymentRequestImpl(BrowserPaymentRequestFactory browserPaymentRequestFactory) {
+        mBrowserPaymentRequestFactory = browserPaymentRequestFactory;
     }
 
     /**
@@ -115,75 +92,76 @@
     @Override
     public void init(PaymentRequestClient client, PaymentMethodData[] methodData,
             PaymentDetails details, PaymentOptions options, boolean googlePayBridgeEligible) {
+        mBrowserPaymentRequest = mBrowserPaymentRequestFactory.createBrowserPaymentRequest(this);
+        assert mBrowserPaymentRequest != null;
         if (mClient != null) {
-            mDelegate.getJourneyLogger().setAborted(
-                    org.chromium.components.payments.AbortReason.INVALID_DATA_FROM_RENDERER);
-            mDelegate.disconnectFromClientWithDebugMessage(
-                    org.chromium.components.payments.ErrorStrings.ATTEMPTED_INITIALIZATION_TWICE);
+            mBrowserPaymentRequest.getJourneyLogger().setAborted(
+                    AbortReason.INVALID_DATA_FROM_RENDERER);
+            mBrowserPaymentRequest.disconnectFromClientWithDebugMessage(
+                    ErrorStrings.ATTEMPTED_INITIALIZATION_TWICE);
             return;
         }
 
         if (client == null) {
-            mDelegate.getJourneyLogger().setAborted(
-                    org.chromium.components.payments.AbortReason.INVALID_DATA_FROM_RENDERER);
-            mDelegate.disconnectFromClientWithDebugMessage(
-                    org.chromium.components.payments.ErrorStrings.INVALID_STATE);
+            mBrowserPaymentRequest.getJourneyLogger().setAborted(
+                    AbortReason.INVALID_DATA_FROM_RENDERER);
+            mBrowserPaymentRequest.disconnectFromClientWithDebugMessage(ErrorStrings.INVALID_STATE);
             return;
         }
 
         mClient = client;
 
-        mDelegate.init(methodData, details, options, googlePayBridgeEligible);
+        mBrowserPaymentRequest.init(methodData, details, options, googlePayBridgeEligible);
     }
 
     @Override
     public void show(boolean isUserGesture, boolean waitForUpdatedDetails) {
-        mDelegate.show(isUserGesture, waitForUpdatedDetails);
+        mBrowserPaymentRequest.show(isUserGesture, waitForUpdatedDetails);
     }
 
     @Override
     public void updateWith(PaymentDetails details) {
-        mDelegate.updateWith(details);
+        mBrowserPaymentRequest.updateWith(details);
     }
 
     @Override
     public void onPaymentDetailsNotUpdated() {
-        mDelegate.onPaymentDetailsNotUpdated();
+        mBrowserPaymentRequest.onPaymentDetailsNotUpdated();
     }
 
     @Override
     public void abort() {
-        mDelegate.abort();
+        mBrowserPaymentRequest.abort();
     }
 
     @Override
     public void complete(int result) {
-        mDelegate.complete(result);
+        mBrowserPaymentRequest.complete(result);
     }
 
     @Override
     public void retry(PaymentValidationErrors errors) {
-        mDelegate.retry(errors);
+        mBrowserPaymentRequest.retry(errors);
     }
 
     @Override
     public void canMakePayment() {
-        mDelegate.canMakePayment();
+        mBrowserPaymentRequest.canMakePayment();
     }
 
     @Override
     public void hasEnrolledInstrument(boolean perMethodQuota) {
-        mDelegate.hasEnrolledInstrument(perMethodQuota);
+        mBrowserPaymentRequest.hasEnrolledInstrument(perMethodQuota);
     }
 
     @Override
     public void close() {
-        mDelegate.close();
+        mBrowserPaymentRequest.close();
     }
 
     @Override
     public void onConnectionError(MojoException e) {
-        mDelegate.onConnectionError(e);
+        mBrowserPaymentRequest.onConnectionError(e);
     }
 
     /**
diff --git a/components/permissions/contexts/geolocation_permission_context.cc b/components/permissions/contexts/geolocation_permission_context.cc
index b62512c..6890e7f 100644
--- a/components/permissions/contexts/geolocation_permission_context.cc
+++ b/components/permissions/contexts/geolocation_permission_context.cc
@@ -5,7 +5,7 @@
 #include "components/permissions/contexts/geolocation_permission_context.h"
 
 #include "base/bind.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/permissions/permission_request_id.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/device_service.h"
@@ -52,12 +52,12 @@
     const PermissionRequestID& id,
     const GURL& requesting_frame,
     bool allowed) {
-  content_settings::TabSpecificContentSettings* content_settings =
-      content_settings::TabSpecificContentSettings::GetForFrame(
+  content_settings::PageSpecificContentSettings* content_settings =
+      content_settings::PageSpecificContentSettings::GetForFrame(
           id.render_process_id(), id.render_frame_id());
 
   // WebContents might not exist (extensions) or no longer exist. In which case,
-  // TabSpecificContentSettings will be null.
+  // PageSpecificContentSettings will be null.
   if (content_settings)
     content_settings->OnGeolocationPermissionSet(requesting_frame.GetOrigin(),
                                                  allowed);
diff --git a/components/permissions/contexts/geolocation_permission_context_unittest.cc b/components/permissions/contexts/geolocation_permission_context_unittest.cc
index 5c4fbd1..76dc457 100644
--- a/components/permissions/contexts/geolocation_permission_context_unittest.cc
+++ b/components/permissions/contexts/geolocation_permission_context_unittest.cc
@@ -28,8 +28,8 @@
 #include "base/time/clock.h"
 #include "build/build_config.h"
 #include "components/content_settings/browser/content_settings_usages_state.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
-#include "components/content_settings/browser/test_tab_specific_content_settings_delegate.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
+#include "components/content_settings/browser/test_page_specific_content_settings_delegate.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/permissions/features.h"
 #include "components/permissions/permission_context_base.h"
@@ -253,7 +253,7 @@
     const GURL& requesting_frame,
     ContentSetting expected_content_setting) {
   auto* content_settings =
-      content_settings::TabSpecificContentSettings::GetForFrame(
+      content_settings::PageSpecificContentSettings::GetForFrame(
           web_contents()->GetMainFrame());
 
   const ContentSettingsUsagesState::StateMap& state_map =
@@ -269,10 +269,10 @@
 void GeolocationPermissionContextTests::SetUp() {
   RenderViewHostTestHarness::SetUp();
 
-  content_settings::TabSpecificContentSettings::CreateForWebContents(
+  content_settings::PageSpecificContentSettings::CreateForWebContents(
       web_contents(),
       std::make_unique<
-          content_settings::TestTabSpecificContentSettingsDelegate>(
+          content_settings::TestPageSpecificContentSettingsDelegate>(
           /*prefs=*/nullptr,
           PermissionsClient::Get()->GetSettingsMap(browser_context())));
 
diff --git a/components/policy/proto/chrome_device_policy.proto b/components/policy/proto/chrome_device_policy.proto
index 4b22f20..a0045b6 100644
--- a/components/policy/proto/chrome_device_policy.proto
+++ b/components/policy/proto/chrome_device_policy.proto
@@ -320,16 +320,19 @@
 
   // Types of channel downgrade behavior.
   enum ChannelDowngradeBehavior {
+    // Channel downgrade behavior unspecified. Default is
+    // WAIT_FOR_VERSION_CATCH_UP.
+    CHANNEL_DOWNGRADE_BEHAVIOR_UNSPECIFIED = 0;
     // On a channel downgrade, e.g. beta to stable, wait for the device's
     // version to become available on the new channel. No updates happen until
     // then. This is the default.
-    WAIT_FOR_VERSION_CATCH_UP = 0;
+    WAIT_FOR_VERSION_CATCH_UP = 1;
     // Roll back and reset the device on a channel downgrade. This does a full
     // powerwash and tries to preserve wifi and enrollment.
-    ROLLBACK = 1;
+    ROLLBACK = 2;
     // Allow the user to decide whether to wait or roll back and reset on a
     // user-initiated channel downgrade.
-    ALLOW_USER_TO_CONFIGURE = 2;
+    ALLOW_USER_TO_CONFIGURE = 3;
   }
 
   // Specifies what should happen if the device channel is downgraded.
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index 69f868b..1a4ef7a 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -9353,22 +9353,22 @@
       'type': 'int-enum',
       'schema': {
         'type': 'integer',
-        'enum': [0, 1, 2],
+        'enum': [1, 2, 3],
       },
       'items': [
         {
           'name': 'WaitForVersionCatchUp',
-          'value': 0,
+          'value': 1,
           'caption': '''Wait for the target channel to catch up on channel downgrade''',
         },
         {
           'name': 'Rollback',
-          'value': 1,
+          'value': 2,
           'caption': '''Roll back and reset the device on channel downgrade, try to preserve enrollment''',
         },
         {
           'name': 'AllowUserToConfigure',
-          'value': 2,
+          'value': 3,
           'caption': '''User decides on channel downgrade behavior''',
         },
       ],
@@ -9377,7 +9377,7 @@
       'features': {
         'dynamic_refresh': True,
       },
-      'example_value': 0,
+      'example_value': 1,
       'id': 757,
       'caption': '''Channel downgrade behavior''',
       'tags': [],
diff --git a/components/safe_browsing/core/realtime/url_lookup_service.cc b/components/safe_browsing/core/realtime/url_lookup_service.cc
index 7fc36ca..4e4edce1 100644
--- a/components/safe_browsing/core/realtime/url_lookup_service.cc
+++ b/components/safe_browsing/core/realtime/url_lookup_service.cc
@@ -145,9 +145,9 @@
         })");
 }
 
-std::string RealTimeUrlLookupService::GetDMTokenString() const {
+base::Optional<std::string> RealTimeUrlLookupService::GetDMTokenString() const {
   // DM token should only be set for enterprise requests.
-  return "";
+  return base::nullopt;
 }
 
 std::string RealTimeUrlLookupService::GetMetricSuffix() const {
diff --git a/components/safe_browsing/core/realtime/url_lookup_service.h b/components/safe_browsing/core/realtime/url_lookup_service.h
index fab556ea..11d62f7 100644
--- a/components/safe_browsing/core/realtime/url_lookup_service.h
+++ b/components/safe_browsing/core/realtime/url_lookup_service.h
@@ -79,7 +79,7 @@
   void GetAccessToken(const GURL& url,
                       RTLookupRequestCallback request_callback,
                       RTLookupResponseCallback response_callback) override;
-  std::string GetDMTokenString() const override;
+  base::Optional<std::string> GetDMTokenString() const override;
   std::string GetMetricSuffix() const override;
 
   // Called when the access token is obtained from |token_fetcher_|.
diff --git a/components/safe_browsing/core/realtime/url_lookup_service_base.cc b/components/safe_browsing/core/realtime/url_lookup_service_base.cc
index 8d4cdf8..fc7cda17 100644
--- a/components/safe_browsing/core/realtime/url_lookup_service_base.cc
+++ b/components/safe_browsing/core/realtime/url_lookup_service_base.cc
@@ -413,7 +413,10 @@
   auto request = std::make_unique<RTLookupRequest>();
   request->set_url(SanitizeURL(url).spec());
   request->set_lookup_type(RTLookupRequest::NAVIGATION);
-  request->set_dm_token(GetDMTokenString());
+  base::Optional<std::string> dm_token_string = GetDMTokenString();
+  if (dm_token_string.has_value()) {
+    request->set_dm_token(dm_token_string.value());
+  }
 
   ChromeUserPopulation* user_population = request->mutable_population();
   user_population->set_user_population(
diff --git a/components/safe_browsing/core/realtime/url_lookup_service_base.h b/components/safe_browsing/core/realtime/url_lookup_service_base.h
index 048dee0b..29fa016 100644
--- a/components/safe_browsing/core/realtime/url_lookup_service_base.h
+++ b/components/safe_browsing/core/realtime/url_lookup_service_base.h
@@ -137,7 +137,7 @@
                               RTLookupResponseCallback response_callback) = 0;
 
   // Gets a dm token string to be set in a request proto.
-  virtual std::string GetDMTokenString() const = 0;
+  virtual base::Optional<std::string> GetDMTokenString() const = 0;
 
   // Suffix for logging metrics.
   virtual std::string GetMetricSuffix() const = 0;
diff --git a/components/safe_browsing/core/realtime/url_lookup_service_unittest.cc b/components/safe_browsing/core/realtime/url_lookup_service_unittest.cc
index 9e62834a..ffb482a 100644
--- a/components/safe_browsing/core/realtime/url_lookup_service_unittest.cc
+++ b/components/safe_browsing/core/realtime/url_lookup_service_unittest.cc
@@ -602,6 +602,7 @@
       url,
       base::BindOnce(
           [](std::unique_ptr<RTLookupRequest> request, std::string token) {
+            EXPECT_FALSE(request->has_dm_token());
             // Check token is attached.
             EXPECT_EQ("access_token_string", token);
           }),
diff --git a/components/test/data/payments/payment_handler.js b/components/test/data/payments/payment_handler.js
index ac926dc..df468477 100644
--- a/components/test/data/payments/payment_handler.js
+++ b/components/test/data/payments/payment_handler.js
@@ -4,19 +4,27 @@
  * found in the LICENSE file.
  */
 
-const methodName = window.location.origin + '/pay';
-const swSrcUrl = 'payment_handler_sw.js';
+const DEFAULT_METHOD_NAME = window.location.origin + '/pay';
+const SW_SRC_URL = 'payment_handler_sw.js';
 
-/** Installs the payment handler. */
-async function install() { // eslint-disable-line no-unused-vars
+let methodName = DEFAULT_METHOD_NAME;
+var request;
+
+/** Installs the payment handler.
+ * @param {string} method - The payment method that this service worker
+ *    supports.
+ * @return {Promise<string>} - 'success' or error message on failure.
+ */
+async function install(method=DEFAULT_METHOD_NAME) { // eslint-disable-line no-unused-vars, max-len
   try {
+    methodName = method;
     let registration =
-        await navigator.serviceWorker.getRegistration(swSrcUrl);
+        await navigator.serviceWorker.getRegistration(SW_SRC_URL);
     if (registration) {
       return 'The payment handler is already installed.';
     }
 
-    await navigator.serviceWorker.register(swSrcUrl);
+    await navigator.serviceWorker.register(SW_SRC_URL);
     registration = await navigator.serviceWorker.ready;
     if (!registration.paymentManager) {
       return 'PaymentManager API not found.';
@@ -24,7 +32,7 @@
 
     await registration.paymentManager.instruments.set('instrument-id', {
       name: 'Instrument Name',
-      method: methodName,
+      method,
     });
     return 'success';
   } catch (e) {
@@ -33,15 +41,33 @@
 }
 
 /**
+ * Uninstalls the payment handler.
+ * @return {Promise<string>} - 'success' or error message on failure.
+ */
+async function uninstall() { // eslint-disable-line no-unused-vars
+  try {
+    let registration =
+        await navigator.serviceWorker.getRegistration(SW_SRC_URL);
+    if (!registration) {
+      return 'The Payment handler has not been installed yet.';
+    }
+    await registration.unregister();
+    return 'success';
+  } catch (e) {
+    return e.toString();
+  }
+}
+
+/**
  * Delegates handling of the provided options to the payment handler.
  * @param {Array<string>} delegations The list of payment options to delegate.
- * @return {string} The 'success' or error message.
+ * @return {Promise<string>} - 'success' or error message on failure.
  */
 async function enableDelegations(delegations) { // eslint-disable-line no-unused-vars, max-len
   try {
     await navigator.serviceWorker.ready;
     let registration =
-        await navigator.serviceWorker.getRegistration(swSrcUrl);
+        await navigator.serviceWorker.getRegistration(SW_SRC_URL);
     if (!registration) {
       return 'The payment handler is not installed.';
     }
@@ -61,6 +87,7 @@
 
 /**
  * Launches the payment handler.
+ * @return {Promise<string>} - 'success' or error message on failure.
  */
 async function launch() { // eslint-disable-line no-unused-vars
   try {
@@ -81,7 +108,7 @@
  */
 function launchWithoutWaitForResponse() { // eslint-disable-line no-unused-vars
   try {
-    const request = new PaymentRequest([{supportedMethods: methodName}], {
+    request = new PaymentRequest([{supportedMethods: methodName}], {
       total: {label: 'Total', amount: {currency: 'USD', value: '0.01'}},
     });
     request.show();
@@ -91,6 +118,19 @@
   }
 }
 
+/**
+ * Aborts the on-going payment request.
+ * @return {Promise<string>} - 'success' or error message on failure.
+ */
+async function abort() { // eslint-disable-line no-unused-vars
+  try {
+    await request.abort();
+    return 'success';
+  } catch (e) {
+    return e.toString();
+  }
+}
+
 var paymentOptions = null;
 
 /**
diff --git a/components/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkValidator.java b/components/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkValidator.java
index 980f8ea..aa6e6c0 100644
--- a/components/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkValidator.java
+++ b/components/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkValidator.java
@@ -49,7 +49,6 @@
     private static byte[] sExpectedSignature;
     private static byte[] sCommentSignedPublicKeyBytes;
     private static PublicKey sCommentSignedPublicKey;
-    private static boolean sDisableValidation;
     private static boolean sOverrideValidationForTesting;
 
     /**
@@ -178,8 +177,7 @@
      */
     @SuppressLint("PackageManagerGetSignatures")
     public static boolean isValidWebApk(Context context, String webappPackageName) {
-        if ((sExpectedSignature == null || sCommentSignedPublicKeyBytes == null)
-                && !sDisableValidation) {
+        if (sExpectedSignature == null || sCommentSignedPublicKeyBytes == null) {
             Log.wtf(TAG,
                     "WebApk validation failure - expected signature not set."
                             + "missing call to WebApkValidator.initWithBrowserHostSignature");
@@ -199,7 +197,7 @@
         if (isNotWebApkQuick(packageInfo)) {
             return false;
         }
-        if (sDisableValidation || sOverrideValidationForTesting) {
+        if (sOverrideValidationForTesting) {
             if (DEBUG) {
                 Log.d(TAG, "Ok! Looks like a WebApk (has start url) and validation is disabled.");
             }
@@ -417,14 +415,6 @@
     }
 
     /**
-     * Disables all validation performed by this class. This should only be called when some other
-     * means of validating WebApks is already present and otherwise should never be called.
-     */
-    public static void disableValidationUnsafe() {
-        sDisableValidation = true;
-    }
-
-    /**
      * Lazy evaluate the creation of the Public Key as the KeyFactories may not yet be initialized.
      * @return The decoded PublicKey or null
      */
diff --git a/components/webrtc/media_stream_devices_controller.h b/components/webrtc/media_stream_devices_controller.h
index 0c6cc67f..73d035a 100644
--- a/components/webrtc/media_stream_devices_controller.h
+++ b/components/webrtc/media_stream_devices_controller.h
@@ -86,11 +86,6 @@
   // Runs |callback_| with the current audio/video permission settings.
   void RunCallback(bool blocked_by_feature_policy);
 
-  // Called when the permission has been set to update the
-  // TabSpecificContentSettings.
-  void UpdateTabSpecificContentSettings(ContentSetting audio_setting,
-                                        ContentSetting video_setting) const;
-
   // Returns the content settings for the given content type and request.
   ContentSetting GetContentSetting(
       ContentSettingsType content_type,
diff --git a/content/browser/accessibility/dump_accessibility_events_browsertest.cc b/content/browser/accessibility/dump_accessibility_events_browsertest.cc
index d62c622..35b8f41 100644
--- a/content/browser/accessibility/dump_accessibility_events_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_events_browsertest.cc
@@ -545,6 +545,22 @@
 }
 
 IN_PROC_BROWSER_TEST_P(DumpAccessibilityEventsTest,
+                       AccessibilityEventsAriaHiddenDescendants) {
+  RunEventTest(FILE_PATH_LITERAL("aria-hidden-descendants.html"));
+}
+
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityEventsTest,
+                       AccessibilityEventsAriaHiddenDescendantsAlreadyIgnored) {
+  RunEventTest(
+      FILE_PATH_LITERAL("aria-hidden-descendants-already-ignored.html"));
+}
+
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityEventsTest,
+                       AccessibilityEventsCSSDisplayDescendants) {
+  RunEventTest(FILE_PATH_LITERAL("css-display-descendants.html"));
+}
+
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityEventsTest,
                        AccessibilityEventsCSSFlexTextUpdate) {
   RunEventTest(FILE_PATH_LITERAL("css-flex-text-update.html"));
 }
@@ -555,6 +571,11 @@
 }
 
 IN_PROC_BROWSER_TEST_P(DumpAccessibilityEventsTest,
+                       AccessibilityEventsCSSVisibilityDescendants) {
+  RunEventTest(FILE_PATH_LITERAL("css-visibility-descendants.html"));
+}
+
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityEventsTest,
                        AccessibilityEventsCSSCollapse) {
   RunEventTest(FILE_PATH_LITERAL("css-visibility-collapse.html"));
 }
diff --git a/content/browser/bad_message.h b/content/browser/bad_message.h
index e13d028..36e3e53b 100644
--- a/content/browser/bad_message.h
+++ b/content/browser/bad_message.h
@@ -256,6 +256,7 @@
   INPUT_ROUTER_INVALID_EVENT_SOURCE = 228,
   RFH_INACTIVE_CHECK_FROM_SPECULATIVE_RFH = 229,
   RFH_SUBFRAME_CAPTURE_ON_MAIN_FRAME = 230,
+  RFH_CSP_ATTRIBUTE = 231,
 
   // Please add new elements here. The naming convention is abbreviated class
   // name (e.g. RenderFrameHost becomes RFH) plus a unique description of the
diff --git a/content/browser/browsing_data/clear_site_data_handler.cc b/content/browser/browsing_data/clear_site_data_handler.cc
index 7c717e1..9924493 100644
--- a/content/browser/browsing_data/clear_site_data_handler.cc
+++ b/content/browser/browsing_data/clear_site_data_handler.cc
@@ -182,7 +182,7 @@
   // modification to cookies. Clear-Site-Data applies this restriction to other
   // data types as well.
   // TODO(msramek): Consider showing a blocked icon via
-  // TabSpecificContentSettings and reporting the action in the "Blocked"
+  // PageSpecificContentSettings and reporting the action in the "Blocked"
   // section of the cookies dialog in OIB.
   if (load_flags_ & net::LOAD_DO_NOT_SAVE_COOKIES) {
     delegate_->AddMessage(
diff --git a/content/browser/frame_host/ancestor_throttle.cc b/content/browser/frame_host/ancestor_throttle.cc
index 99910c1..7ba6dbc4 100644
--- a/content/browser/frame_host/ancestor_throttle.cc
+++ b/content/browser/frame_host/ancestor_throttle.cc
@@ -145,6 +145,49 @@
 
 AncestorThrottle::~AncestorThrottle() {}
 
+NavigationThrottle::ThrottleCheckResult AncestorThrottle::WillStartRequest() {
+  if (!base::FeatureList::IsEnabled(network::features::kOutOfBlinkCSPEE))
+    return NavigationThrottle::PROCEED;
+
+  NavigationRequest* request = NavigationRequest::From(navigation_handle());
+  if (request->IsInMainFrame())
+    return NavigationThrottle::PROCEED;
+
+  // TODO(antoniosartori): Probably we should have taken a snapshot of the 'csp'
+  // attribute at the beginning of the navigation and not now, since the
+  // beforeunload handlers might have modified it in the meantime.
+  std::vector<network::mojom::ContentSecurityPolicyPtr> frame_csp;
+  frame_csp.emplace_back(
+      request->frame_tree_node()->csp_attribute()
+          ? request->frame_tree_node()->csp_attribute()->Clone()
+          : nullptr);
+  const network::mojom::ContentSecurityPolicy* parent_required_csp =
+      request->frame_tree_node()->parent()->required_csp();
+
+  std::string error_message;
+  if (!network::IsValidRequiredCSPAttr(frame_csp, parent_required_csp,
+                                       error_message)) {
+    if (frame_csp[0]) {
+      navigation_handle()->GetParentFrame()->AddMessageToConsole(
+          blink::mojom::ConsoleMessageLevel::kError,
+          base::StringPrintf("The frame 'csp' attribute ('%s') is invalid and "
+                             "will be discarded: %s",
+                             frame_csp[0]->header->header_value.c_str(),
+                             error_message.c_str()));
+    }
+    if (parent_required_csp)
+      request->SetRequiredCSP(parent_required_csp->Clone());
+    // TODO(antoniosartori): Consider instead blocking the navigation here,
+    // since this seems to be insecure
+    // (cf. https://github.com/w3c/webappsec-cspee/pull/11).
+  } else {
+    // If |frame_csp| is valid then it is not null.
+    request->SetRequiredCSP(std::move(frame_csp[0]));
+  }
+
+  return NavigationThrottle::PROCEED;
+}
+
 NavigationThrottle::ThrottleCheckResult
 AncestorThrottle::WillRedirectRequest() {
   // During a redirect, we don't know which RenderFrameHost we'll end up in,
@@ -395,7 +438,8 @@
   if (!base::FeatureList::IsEnabled(network::features::kOutOfBlinkCSPEE))
     return CheckResult::PROCEED;
 
-  if (NavigationRequest::From(navigation_handle())->IsInMainFrame()) {
+  NavigationRequest* request = NavigationRequest::From(navigation_handle());
+  if (request->IsInMainFrame()) {
     // We enforce CSPEE only for frames, not for portals.
     return CheckResult::PROCEED;
   }
@@ -403,19 +447,16 @@
   RenderFrameHostImpl* frame = static_cast<RenderFrameHostImpl*>(
       navigation_handle()->GetRenderFrameHost());
 
-  const std::string& required_csp =
-      frame->frame_tree_node()->frame_owner_properties().required_csp;
-
-  if (required_csp.empty())
+  if (!request->required_csp())
     return CheckResult::PROCEED;
 
   const network::mojom::AllowCSPFromHeaderValuePtr& allow_csp_from =
-      NavigationRequest::From(navigation_handle())
-          ->response()
-          ->parsed_headers->allow_csp_from;
+      request->response()->parsed_headers->allow_csp_from;
   if (AllowsBlanketEnforcementOfRequiredCSP(
           frame->GetParent()->GetLastCommittedOrigin(),
           navigation_handle()->GetURL(), allow_csp_from)) {
+    // Enforce the required csps on the frame by passing them down to blink
+    request->ForceCSPForResponse(request->required_csp()->header->header_value);
     return CheckResult::PROCEED;
   }
 
@@ -433,9 +474,7 @@
   // TODO(antoniosartori): This is temporary, since the check in this function
   // is incomplete and will require iterations in several CLs. For now, let's
   // allow anything that has no "allow-csp-from" header.
-  if (!NavigationRequest::From(navigation_handle())
-           ->GetResponseHeaders()
-           ->HasHeader("allow-csp-from")) {
+  if (!allow_csp_from) {
     return CheckResult::PROCEED;
   }
 
@@ -447,7 +486,8 @@
           "frame neither accepts that policy using the Allow-CSP-From header "
           "nor delivers a Content Security Policy which is at least as strong "
           "as that one.",
-          sanitized_blocked_url.c_str(), required_csp.c_str()));
+          sanitized_blocked_url.c_str(),
+          request->required_csp()->header->header_value.c_str()));
 
   return CheckResult::BLOCK;
 }
diff --git a/content/browser/frame_host/ancestor_throttle.h b/content/browser/frame_host/ancestor_throttle.h
index 5122ece..7c92f0f 100644
--- a/content/browser/frame_host/ancestor_throttle.h
+++ b/content/browser/frame_host/ancestor_throttle.h
@@ -45,6 +45,7 @@
 
   ~AncestorThrottle() override;
 
+  NavigationThrottle::ThrottleCheckResult WillStartRequest() override;
   NavigationThrottle::ThrottleCheckResult WillRedirectRequest() override;
   NavigationThrottle::ThrottleCheckResult WillProcessResponse() override;
   const char* GetNameForLogging() override;
diff --git a/content/browser/frame_host/ancestor_throttle_unittest.cc b/content/browser/frame_host/ancestor_throttle_unittest.cc
index 5bfab420..a62a7ea 100644
--- a/content/browser/frame_host/ancestor_throttle_unittest.cc
+++ b/content/browser/frame_host/ancestor_throttle_unittest.cc
@@ -7,10 +7,14 @@
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "base/memory/ref_counted.h"
+#include "base/test/scoped_feature_list.h"
+#include "content/browser/frame_host/render_frame_host_impl.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/navigation_throttle.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/test_renderer_host.h"
+#include "content/test/navigation_simulator_impl.h"
+#include "content/test/test_navigation_url_loader.h"
 #include "net/http/http_response_headers.h"
 #include "services/network/public/cpp/content_security_policy/content_security_policy.h"
 #include "services/network/public/cpp/features.h"
@@ -41,6 +45,17 @@
   return headers;
 }
 
+network::mojom::ContentSecurityPolicyPtr ParsePolicy(
+    const std::string& policy) {
+  scoped_refptr<net::HttpResponseHeaders> headers(
+      new net::HttpResponseHeaders("HTTP/1.1 200 OK"));
+  headers->SetHeader("Content-Security-Policy", policy);
+  std::vector<network::mojom::ContentSecurityPolicyPtr> policies;
+  network::AddContentSecurityPolicyFromHeaders(
+      *headers, GURL("https://example.com/"), &policies);
+  return std::move(policies[0]);
+}
+
 }  // namespace
 
 // AncestorThrottleTest
@@ -132,8 +147,8 @@
 }
 
 TEST_F(AncestorThrottleTest, AllowsBlanketEnforcementOfRequiredCSP) {
-  if (!base::FeatureList::IsEnabled(network::features::kOutOfBlinkCSPEE))
-    return;
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(network::features::kOutOfBlinkCSPEE);
 
   struct TestCase {
     const char* name;
@@ -250,4 +265,155 @@
   }
 }
 
+class AncestorThrottleNavigationTest
+    : public content::RenderViewHostTestHarness {};
+
+TEST_F(AncestorThrottleNavigationTest,
+       WillStartRequestAddsSecRequiredCSPHeader) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(network::features::kOutOfBlinkCSPEE);
+
+  // Perform an initial navigation to set up everything.
+  NavigateAndCommit(GURL("https://test.com"));
+
+  // Create a frame tree with different 'csp' attributes according to the
+  // following graph:
+  //
+  // FRAME NAME                    | 'csp' attribute
+  // ------------------------------|-------------------------------------
+  // main_frame                    | (none)
+  //  ├─child_with_csp             | script-src 'none'
+  //  │  ├─grandchild_same_csp     | script-src 'none'
+  //  │  ├─grandchild_no_csp       | (none)
+  //  │  │ └─grandgrandchild       | (none)
+  //  │  ├─grandchild_invalid_csp  | report-to group
+  //  │  └─grandchild_invalid_csp2 | script-src 'none'; invalid-directive
+  //  └─sibling                    | (none)
+  //
+  // Test that the required CSP of every frame is computed/inherited correctly
+  // and that the Sec-Required-CSP header is set.
+
+  auto* main_frame = static_cast<TestRenderFrameHost*>(main_rfh());
+
+  auto* child_with_csp = static_cast<TestRenderFrameHost*>(
+      content::RenderFrameHostTester::For(main_frame)
+          ->AppendChild("child_frame"));
+  child_with_csp->frame_tree_node()->set_csp_attribute(
+      ParsePolicy("script-src 'none'"));
+
+  auto* grandchild_same_csp = static_cast<TestRenderFrameHost*>(
+      content::RenderFrameHostTester::For(child_with_csp)
+          ->AppendChild("grandchild_frame"));
+  grandchild_same_csp->frame_tree_node()->set_csp_attribute(
+      ParsePolicy("script-src 'none'"));
+
+  auto* grandchild_no_csp = static_cast<TestRenderFrameHost*>(
+      content::RenderFrameHostTester::For(child_with_csp)
+          ->AppendChild("grandchild_frame"));
+
+  auto* grandgrandchild = static_cast<TestRenderFrameHost*>(
+      content::RenderFrameHostTester::For(grandchild_no_csp)
+          ->AppendChild("grandgrandchild_frame"));
+
+  auto* grandchild_invalid_csp = static_cast<TestRenderFrameHost*>(
+      content::RenderFrameHostTester::For(child_with_csp)
+          ->AppendChild("grandchild_frame"));
+  grandchild_invalid_csp->frame_tree_node()->set_csp_attribute(
+      ParsePolicy("report-to group"));
+
+  auto* grandchild_invalid_csp2 = static_cast<TestRenderFrameHost*>(
+      content::RenderFrameHostTester::For(child_with_csp)
+          ->AppendChild("grandchild_frame"));
+  grandchild_invalid_csp2->frame_tree_node()->set_csp_attribute(
+      ParsePolicy("script-src 'none'; invalid-directive"));
+
+  auto* sibling = static_cast<TestRenderFrameHost*>(
+      content::RenderFrameHostTester::For(main_frame)
+          ->AppendChild("sibling_frame"));
+
+  struct TestCase {
+    const char* name;
+    TestRenderFrameHost* frame;
+    const char* expected_header;
+  } cases[] = {
+      {
+          "Main frame does not set header",
+          main_frame,
+          nullptr,
+      },
+      {
+          "Frame with 'csp' attribute sets correct header",
+          child_with_csp,
+          "script-src 'none'",
+      },
+      {
+          "Child with same 'csp' attribute as parent frame sets correct header",
+          grandchild_same_csp,
+          "script-src 'none'",
+      },
+      {
+          "Child without 'csp' attribute inherits from parent",
+          grandchild_no_csp,
+          "script-src 'none'",
+      },
+      {
+          "Grandchild without 'csp' attribute inherits from grandparent"
+          "header",
+          grandgrandchild,
+          "script-src 'none'",
+      },
+      {
+          "Child with invalid 'csp' attribute inherits from parent",
+          grandchild_invalid_csp,
+          "script-src 'none'",
+      },
+      {
+          "Child with invalid 'csp' attribute inherits from parent 2",
+          grandchild_invalid_csp2,
+          "script-src 'none'",
+      },
+      {
+          "Frame without 'csp' attribute does not set header",
+          sibling,
+          nullptr,
+      },
+  };
+
+  for (auto test : cases) {
+    SCOPED_TRACE(test.name);
+    std::unique_ptr<NavigationSimulator> simulator =
+        content::NavigationSimulator::CreateRendererInitiated(
+            GURL("https://www.foo.com/"), test.frame);
+    simulator->Start();
+    NavigationRequest* request =
+        NavigationRequest::From(simulator->GetNavigationHandle());
+    std::string header_value;
+    bool found = request->GetRequestHeaders().GetHeader("sec-required-csp",
+                                                        &header_value);
+    if (test.expected_header) {
+      EXPECT_TRUE(found);
+      EXPECT_EQ(test.expected_header, header_value);
+    } else {
+      EXPECT_FALSE(found);
+    }
+
+    // Complete the navigation and store the required CSP in the
+    // RenderFrameHostImpl so that the next tests can rely on this.
+    // TODO(antoniosartori): Update the NavigationSimulatorImpl so that this is
+    // done automatically on commit.
+    TestNavigationURLLoader* url_loader =
+        static_cast<TestNavigationURLLoader*>(request->loader_for_testing());
+    auto response = network::mojom::URLResponseHead::New();
+    response->headers =
+        base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK");
+    response->parsed_headers = network::mojom::ParsedHeaders::New();
+    response->parsed_headers->allow_csp_from =
+        network::mojom::AllowCSPFromHeaderValue::NewAllowStar(true);
+    url_loader->CallOnResponseStarted(std::move(response));
+    auto* new_required_csp = request->required_csp();
+    if (new_required_csp)
+      test.frame->required_csp_ = new_required_csp->Clone();
+  }
+}
+
 }  // namespace content
diff --git a/content/browser/frame_host/frame_tree_node.h b/content/browser/frame_host/frame_tree_node.h
index 54fd7c1..05c9871 100644
--- a/content/browser/frame_host/frame_tree_node.h
+++ b/content/browser/frame_host/frame_tree_node.h
@@ -248,6 +248,15 @@
     frame_owner_properties_ = frame_owner_properties;
   }
 
+  const network::mojom::ContentSecurityPolicy* csp_attribute() {
+    return csp_attribute_.get();
+  }
+
+  void set_csp_attribute(
+      network::mojom::ContentSecurityPolicyPtr parsed_csp_attribute) {
+    csp_attribute_ = std::move(parsed_csp_attribute);
+  }
+
   bool HasSameOrigin(const FrameTreeNode& node) const {
     return replication_state_.origin.IsSameOriginWith(
         node.replication_state_.origin);
@@ -514,6 +523,9 @@
   // Note that dynamic updates only take effect on the next frame navigation.
   blink::mojom::FrameOwnerProperties frame_owner_properties_;
 
+  // Contains the current parsed value of the 'csp' attribute of this frame.
+  network::mojom::ContentSecurityPolicyPtr csp_attribute_;
+
   // Owns an ongoing NavigationRequest until it is ready to commit. It will then
   // be reset and a RenderFrameHost will be responsible for the navigation.
   std::unique_ptr<NavigationRequest> navigation_request_;
diff --git a/content/browser/frame_host/navigation_controller_impl.cc b/content/browser/frame_host/navigation_controller_impl.cc
index c7e2973..2899140 100644
--- a/content/browser/frame_host/navigation_controller_impl.cc
+++ b/content/browser/frame_host/navigation_controller_impl.cc
@@ -3332,7 +3332,8 @@
           false /* origin_isolated */,
           std::vector<
               network::mojom::WebClientHintsType>() /* enabled_client_hints */,
-          false /* is_cross_browsing_instance */);
+          false /* is_cross_browsing_instance */,
+          std::vector<std::string>() /* forced_content_security_policies */);
 #if defined(OS_ANDROID)
   if (ValidateDataURLAsString(params.data_url_as_string)) {
     commit_params->data_url_as_string = params.data_url_as_string->data();
diff --git a/content/browser/frame_host/navigation_entry_impl.cc b/content/browser/frame_host/navigation_entry_impl.cc
index 0df3b800..f346d5a 100644
--- a/content/browser/frame_host/navigation_entry_impl.cc
+++ b/content/browser/frame_host/navigation_entry_impl.cc
@@ -833,7 +833,8 @@
           false /* origin_isolated */,
           std::vector<
               network::mojom::WebClientHintsType>() /* enabled_client_hints */,
-          false /* is_cross_browsing_instance */);
+          false /* is_cross_browsing_instance */,
+          std::vector<std::string>() /* forced_content_security_policies */);
 #if defined(OS_ANDROID)
   if (NavigationControllerImpl::ValidateDataURLAsString(GetDataURLAsString())) {
     commit_params->data_url_as_string = GetDataURLAsString()->data();
diff --git a/content/browser/frame_host/navigation_request.cc b/content/browser/frame_host/navigation_request.cc
index 9830bd1..b836c4a 100644
--- a/content/browser/frame_host/navigation_request.cc
+++ b/content/browser/frame_host/navigation_request.cc
@@ -937,7 +937,8 @@
           false /* origin_isolated */,
           std::vector<
               network::mojom::WebClientHintsType>() /* enabled_client_hints */,
-          false /* is_cross_browsing_instance */);
+          false /* is_cross_browsing_instance */,
+          std::vector<std::string>() /* forced_content_security_policies */);
 
   // CreateRendererInitiated() should only be triggered when the navigation is
   // initiated by a frame in the same process.
@@ -1027,10 +1028,9 @@
           std::vector<std::string>() /* force_enabled_origin_trials */,
           false /* origin_isolated */,
           std::vector<
-              network::mojom::WebClientHintsType>() /* enabled_client_hints
-                                                     */
-          ,
-          false /* is_cross_browsing_instance */
+              network::mojom::WebClientHintsType>() /* enabled_client_hints */,
+          false /* is_cross_browsing_instance */,
+          std::vector<std::string>() /* forced_content_security_policies */
       );
   mojom::BeginNavigationParamsPtr begin_params =
       mojom::BeginNavigationParams::New();
@@ -1693,6 +1693,21 @@
   return std::move(client_security_state_);
 }
 
+void NavigationRequest::SetRequiredCSP(
+    network::mojom::ContentSecurityPolicyPtr csp) {
+  DCHECK(!required_csp_);
+  required_csp_ = std::move(csp);
+  if (required_csp_) {
+    const std::string& header_value = required_csp_->header->header_value;
+    DCHECK(net::HttpUtil::IsValidHeaderValue(header_value));
+    SetRequestHeader("Sec-Required-CSP", header_value);
+  }
+}
+
+network::mojom::ContentSecurityPolicyPtr NavigationRequest::TakeRequiredCSP() {
+  return std::move(required_csp_);
+}
+
 void NavigationRequest::CreateCoepReporter(
     StoragePartition* storage_partition) {
   const auto& coep = client_security_state_->cross_origin_embedder_policy;
@@ -4732,6 +4747,10 @@
   return entry_overrides_ua_;
 }
 
+void NavigationRequest::ForceCSPForResponse(const std::string& csp) {
+  commit_params_->forced_content_security_policies.push_back(csp);
+}
+
 // static
 NavigationRequest* NavigationRequest::From(NavigationHandle* handle) {
   return static_cast<NavigationRequest*>(handle);
diff --git a/content/browser/frame_host/navigation_request.h b/content/browser/frame_host/navigation_request.h
index bc60e642..c0691276 100644
--- a/content/browser/frame_host/navigation_request.h
+++ b/content/browser/frame_host/navigation_request.h
@@ -45,6 +45,7 @@
 #include "services/metrics/public/cpp/ukm_source_id.h"
 #include "services/network/public/cpp/origin_policy.h"
 #include "services/network/public/mojom/blocked_by_response_reason.mojom-shared.h"
+#include "services/network/public/mojom/content_security_policy.mojom.h"
 #include "services/network/public/mojom/web_sandbox_flags.mojom-shared.h"
 #include "third_party/blink/public/common/loader/previews_state.h"
 
@@ -350,6 +351,8 @@
   // The NavigationRequest can be deleted while BeginNavigation() is called.
   void BeginNavigation();
 
+  void ForceCSPForResponse(const std::string& csp);
+
   const mojom::CommonNavigationParams& common_params() const {
     return *common_params_;
   }
@@ -638,6 +641,12 @@
 
   bool ua_change_requires_reload() const { return ua_change_requires_reload_; }
 
+  const network::mojom::ContentSecurityPolicy* required_csp() {
+    return required_csp_.get();
+  }
+  void SetRequiredCSP(network::mojom::ContentSecurityPolicyPtr csp);
+  network::mojom::ContentSecurityPolicyPtr TakeRequiredCSP();
+
   CrossOriginEmbedderPolicyReporter* coep_reporter() {
     return coep_reporter_.get();
   }
@@ -1399,6 +1408,10 @@
   // at the network request level.
   network::mojom::ClientSecurityStatePtr client_security_state_;
 
+  // Holds the required CSP for this navigation. This will be moved into
+  // the RenderFrameHost at DidCommitNavigation time.
+  network::mojom::ContentSecurityPolicyPtr required_csp_;
+
   std::unique_ptr<CrossOriginEmbedderPolicyReporter> coep_reporter_;
   std::unique_ptr<CrossOriginOpenerPolicyReporter> coop_reporter_;
 
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index 4e10c1cf..c53a217 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -4540,6 +4540,17 @@
       opener_frame_token.value_or(base::UnguessableToken()), GetSiteInstance());
 }
 
+void RenderFrameHostImpl::DidChangeCSPAttribute(
+    const base::UnguessableToken& child_frame_token,
+    network::mojom::ContentSecurityPolicyPtr parsed_csp_attribute) {
+  auto* child =
+      FindAndVerifyChild(child_frame_token, bad_message::RFH_CSP_ATTRIBUTE);
+  if (!child)
+    return;
+
+  child->frame_tree_node()->set_csp_attribute(std::move(parsed_csp_attribute));
+}
+
 void RenderFrameHostImpl::DidChangeFramePolicy(
     const base::UnguessableToken& child_frame_token,
     const blink::FramePolicy& frame_policy) {
@@ -8222,6 +8233,9 @@
     }
   }
 
+  network::mojom::ContentSecurityPolicyPtr required_csp =
+      navigation_request->TakeRequiredCSP();
+
   // TODO(arthursonzogni, altimin): By taking ownership and deleting the
   // NavigationRequest, this line triggers the DidFinishNavigation event. There
   // are several document's associated state assigned below when a new document
@@ -8251,6 +8265,10 @@
     coep_reporter_ = std::move(coep_reporter);
     coop_reporter_ = std::move(coop_reporter);
 
+    // Store the required CSP (it will be used by the AncestorThrottle if
+    // this frame embeds a subframe when that subframe navigates).
+    required_csp_ = std::move(required_csp);
+
     if (coep_reporter_) {
       mojo::PendingRemote<blink::mojom::ReportingObserver> remote;
       mojo::PendingReceiver<blink::mojom::ReportingObserver> receiver =
@@ -8263,7 +8281,6 @@
           base::BindOnce(&RenderFrameHostImpl::BindReportingObserver,
                          weak_ptr_factory_.GetWeakPtr(), std::move(receiver)));
     }
-
   }
 
   RecordCrossOriginIsolationMetrics(this);
diff --git a/content/browser/frame_host/render_frame_host_impl.h b/content/browser/frame_host/render_frame_host_impl.h
index 4cd841d..a2995be1 100644
--- a/content/browser/frame_host/render_frame_host_impl.h
+++ b/content/browser/frame_host/render_frame_host_impl.h
@@ -1452,6 +1452,10 @@
     return last_committed_client_security_state_;
   }
 
+  const network::mojom::ContentSecurityPolicy* required_csp() {
+    return required_csp_.get();
+  }
+
   // This function mimics DidCommitProvisionalLoad for navigations served from
   // the back-forward cache.
   void DidCommitBackForwardCacheNavigation(
@@ -1616,6 +1620,9 @@
       blink::mojom::FrameOwnerPropertiesPtr frame_owner_properties) override;
   void DidChangeOpener(
       const base::Optional<base::UnguessableToken>& opener_frame) override;
+  void DidChangeCSPAttribute(
+      const base::UnguessableToken& child_frame_token,
+      network::mojom::ContentSecurityPolicyPtr parsed_csp_attribute) override;
   void DidChangeFramePolicy(const base::UnguessableToken& child_frame_token,
                             const blink::FramePolicy& frame_policy) override;
   void CapturePaintPreviewOfSubframe(
@@ -1890,6 +1897,8 @@
                            AttemptDuplicateRenderWidgetHost);
   FRIEND_TEST_ALL_PREFIXES(RenderDocumentHostUserDataTest,
                            CheckInPendingDeletionState);
+  FRIEND_TEST_ALL_PREFIXES(AncestorThrottleNavigationTest,
+                           WillStartRequestAddsSecRequiredCSPHeader);
 
   class DroppedInterfaceRequestLogger;
 
@@ -3134,6 +3143,11 @@
   // RenderFrameHost.
   bool is_outer_delegate_frame_ = false;
 
+  // The browsing context's required CSP as defined by
+  // https://w3c.github.io/webappsec-cspee/#required-csp,
+  // stored when the frame commits the navigation.
+  network::mojom::ContentSecurityPolicyPtr required_csp_;
+
   // NOTE: This must be the last member.
   base::WeakPtrFactory<RenderFrameHostImpl> weak_ptr_factory_{this};
 
diff --git a/content/browser/renderer_host/render_view_host_impl.cc b/content/browser/renderer_host/render_view_host_impl.cc
index 0ba74f2..2aecfd92 100644
--- a/content/browser/renderer_host/render_view_host_impl.cc
+++ b/content/browser/renderer_host/render_view_host_impl.cc
@@ -113,6 +113,10 @@
 #include "content/browser/host_zoom_map_impl.h"
 #endif
 
+#if defined(USE_OZONE)
+#include "ui/base/ui_base_features.h"
+#endif
+
 using base::TimeDelta;
 
 using blink::WebConsoleMessage;
@@ -141,6 +145,20 @@
 }
 #endif  // OS_WIN
 
+#if defined(USE_OZONE) || defined(USE_X11)
+bool IsSelectionBufferAvailable() {
+#if defined(USE_OZONE)
+  if (features::IsUsingOzonePlatform())
+    return ui::Clipboard::GetForCurrentThread()->IsSelectionBufferAvailable();
+#endif
+#if defined(USE_X11)
+  return true;
+#else
+  return false;
+#endif
+}
+#endif  // defined(USE_OZONE) || defined(USE_X11)
+
 }  // namespace
 
 // static
@@ -218,6 +236,9 @@
   // TODO(crbug.com/1066605): Consider exposing this as a FIDL parameter.
   prefs->focus_ring_color = SK_AlphaTRANSPARENT;
 #endif
+#if defined(USE_OZONE) || defined(USE_X11)
+  prefs->selection_clipboard_buffer_available = IsSelectionBufferAvailable();
+#endif
 }
 
 RenderViewHostImpl::RenderViewHostImpl(
diff --git a/content/browser/web_contents/web_contents_view_aura.cc b/content/browser/web_contents/web_contents_view_aura.cc
index cc6dabc4..c313a00 100644
--- a/content/browser/web_contents/web_contents_view_aura.cc
+++ b/content/browser/web_contents/web_contents_view_aura.cc
@@ -1211,7 +1211,7 @@
     // Linux window managers like to handle raise-on-click themselves.  If we
     // raise-on-click manually, this may override user settings that prevent
     // focus-stealing.
-#if !defined(USE_X11)
+#if !defined(OS_LINUX)
     // It is possible for the web-contents to be destroyed while it is being
     // activated. Use a weak-ptr to track whether that happened or not.
     // More in https://crbug.com/1040725
diff --git a/content/browser/web_contents/web_contents_view_aura_unittest.cc b/content/browser/web_contents/web_contents_view_aura_unittest.cc
index 19e3fe9d..73d9cbd 100644
--- a/content/browser/web_contents/web_contents_view_aura_unittest.cc
+++ b/content/browser/web_contents/web_contents_view_aura_unittest.cc
@@ -186,17 +186,11 @@
                              0);
   ui::EventHandler* event_handler = GetView();
   event_handler->OnMouseEvent(&mouse_event);
-#if defined(USE_X11)
-  // The web-content is not activated during mouse-press on X11.
+#if defined(OS_LINUX)
+  // The web-content is not activated during mouse-press on Linux.
   // See comment in WebContentsViewAura::OnMouseEvent() for more details.
-  // TODO(https://crbug.com/1109695): enable for Ozone/Linux.
-  if (!features::IsUsingOzonePlatform()) {
-    EXPECT_NE(web_contents(), nullptr);
-  } else
+  EXPECT_NE(web_contents(), nullptr);
 #endif
-  {
-    EXPECT_EQ(web_contents(), nullptr);
-  }
 }
 
 TEST_F(WebContentsViewAuraTest, OccludeView) {
@@ -319,6 +313,7 @@
 #if defined(OS_WIN) || defined(USE_X11)
 TEST_F(WebContentsViewAuraTest, DragDropFilesOriginateFromRenderer) {
 #if defined(USE_X11)
+  // TODO(https://crbug.com/1109695): enable for Ozone/Linux.
   if (features::IsUsingOzonePlatform())
     return;
 #endif
diff --git a/content/common/navigation_params.mojom b/content/common/navigation_params.mojom
index e610aad..94509a73 100644
--- a/content/common/navigation_params.mojom
+++ b/content/common/navigation_params.mojom
@@ -438,4 +438,10 @@
 
   // Whether this is a cross browsing instance navigation.
   bool is_cross_browsing_instance = false;
+
+  // A list of additional Content Security Policies to be enforced by blink
+  // on the document. This is being used by Content Security Policy: Embedded
+  // Enforcement for enforcing on frames Content Security Policies required by
+  // their embedders.
+  array<string> forced_content_security_policies;
 };
diff --git a/content/public/android/java/src/org/chromium/content/browser/androidoverlay/OWNERS b/content/public/android/java/src/org/chromium/content/browser/androidoverlay/OWNERS
new file mode 100644
index 0000000..5d55ae16
--- /dev/null
+++ b/content/public/android/java/src/org/chromium/content/browser/androidoverlay/OWNERS
@@ -0,0 +1,3 @@
+file://content/browser/media/OWNERS
+
+# OS: Android
diff --git a/content/public/android/java/src/org/chromium/content/browser/input/CursorAnchorInfoController.java b/content/public/android/java/src/org/chromium/content/browser/input/CursorAnchorInfoController.java
index 67b23035..7b52265 100644
--- a/content/public/android/java/src/org/chromium/content/browser/input/CursorAnchorInfoController.java
+++ b/content/public/android/java/src/org/chromium/content/browser/input/CursorAnchorInfoController.java
@@ -97,7 +97,6 @@
                 });
     }
 
-    @VisibleForTesting
     public void setInputMethodManagerWrapper(InputMethodManagerWrapper inputMethodManagerWrapper) {
         mInputMethodManagerWrapper = inputMethodManagerWrapper;
     }
diff --git a/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapterImpl.java b/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapterImpl.java
index 5cd00b38..559be9a 100644
--- a/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapterImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapterImpl.java
@@ -835,7 +835,6 @@
         return true;
     }
 
-    @VisibleForTesting
     boolean finishComposingText() {
         if (!isValid()) return false;
         ImeAdapterImplJni.get().finishComposingText(mNativeImeAdapterAndroid, ImeAdapterImpl.this);
diff --git a/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnectionFactory.java b/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnectionFactory.java
index ae355b7..c38f4995 100644
--- a/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnectionFactory.java
+++ b/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnectionFactory.java
@@ -99,7 +99,6 @@
                 containerView.getContext(), handler, containerView, this);
     }
 
-    @VisibleForTesting
     @Override
     public void setTriggerDelayedOnCreateInputConnection(boolean trigger) {
         mTriggerDelayedOnCreateInputConnection = trigger;
diff --git a/content/public/android/java/src/org/chromium/content_public/browser/ImeAdapter.java b/content/public/android/java/src/org/chromium/content_public/browser/ImeAdapter.java
index 96d70d4..d2a9c94 100644
--- a/content/public/android/java/src/org/chromium/content_public/browser/ImeAdapter.java
+++ b/content/public/android/java/src/org/chromium/content_public/browser/ImeAdapter.java
@@ -19,7 +19,6 @@
  */
 public interface ImeAdapter {
     /** Composition key code sent when user either hit a key or hit a selection. */
-    @VisibleForTesting
     static final int COMPOSITION_KEY_CODE = 229;
 
     /**
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 9ab84b4d..1280deb 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -514,6 +514,14 @@
   for (const auto& trial : commit_params.force_enabled_origin_trials)
     web_origin_trials.emplace_back(WebString::FromASCII(trial));
   navigation_params->force_enabled_origin_trials = web_origin_trials;
+
+  WebVector<WebString> forced_content_security_policies;
+  forced_content_security_policies.reserve(
+      commit_params.forced_content_security_policies.size());
+  for (const auto& csp : commit_params.forced_content_security_policies)
+    forced_content_security_policies.emplace_back(WebString::FromASCII(csp));
+  navigation_params->forced_content_security_policies =
+      forced_content_security_policies;
 }
 
 mojom::CommonNavigationParamsPtr MakeCommonNavigationParams(
diff --git a/content/renderer/render_view_impl.cc b/content/renderer/render_view_impl.cc
index b34393f..c468b81 100644
--- a/content/renderer/render_view_impl.cc
+++ b/content/renderer/render_view_impl.cc
@@ -1663,6 +1663,11 @@
     GetWebView()->GetSettings()->SetCaretBrowsingEnabled(
         renderer_preferences_.caret_browsing_enabled);
   }
+
+#if defined(USE_X11) || defined(USE_OZONE)
+  GetWebView()->GetSettings()->SetSelectionClipboardBufferAvailable(
+      renderer_preferences_.selection_clipboard_buffer_available);
+#endif  // defined(USE_X11) || defined(USE_OZONE)
 }
 
 void RenderViewImpl::OnMoveOrResizeStarted() {
diff --git a/content/test/data/accessibility/event/aria-hidden-descendants-already-ignored-expected-win.txt b/content/test/data/accessibility/event/aria-hidden-descendants-already-ignored-expected-win.txt
new file mode 100644
index 0000000..770bdfc
--- /dev/null
+++ b/content/test/data/accessibility/event/aria-hidden-descendants-already-ignored-expected-win.txt
@@ -0,0 +1 @@
+EVENT_OBJECT_HIDE on <div#heading-root.a> role=ROLE_SYSTEM_GROUPING name="Heading" INVISIBLE level=2
diff --git a/content/test/data/accessibility/event/aria-hidden-descendants-already-ignored.html b/content/test/data/accessibility/event/aria-hidden-descendants-already-ignored.html
new file mode 100644
index 0000000..47a7b4e
--- /dev/null
+++ b/content/test/data/accessibility/event/aria-hidden-descendants-already-ignored.html
@@ -0,0 +1,27 @@
+<!--
+@WIN-DENY:IA2_EVENT_TEXT_INSERTED*
+@WIN-DENY:IA2_EVENT_TEXT_REMOVED*
+@WIN-DENY:EVENT_OBJECT_REORDER*
+-->
+<html>
+<body>
+<!-- Hide events only need to occur on the root of what's shown/hidden,
+     not for each descendant, with some descendants already ignored. -->
+<div role="toolbar">
+  <div id="heading-root" role="heading" aria-label="Heading" class="a">
+    <div> <!-- already ignored-->
+      <div> <!-- already ignored -->
+        <div id="heading-child">
+          <div id="heading-grandchild"></div>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+<script>
+  function go() {
+    document.querySelector('.a').setAttribute("aria-hidden", "true");
+  }
+</script>
+</body>
+</html>
diff --git a/content/test/data/accessibility/event/aria-hidden-descendants-expected-win.txt b/content/test/data/accessibility/event/aria-hidden-descendants-expected-win.txt
new file mode 100644
index 0000000..4ae2446b0
--- /dev/null
+++ b/content/test/data/accessibility/event/aria-hidden-descendants-expected-win.txt
@@ -0,0 +1,3 @@
+EVENT_OBJECT_HIDE on <div#heading-root.a> role=ROLE_SYSTEM_GROUPING name="Heading" INVISIBLE level=2
+EVENT_OBJECT_REORDER on <div> role=ROLE_SYSTEM_TOOLBAR IA2_STATE_HORIZONTAL
+EVENT_OBJECT_SHOW on <div#banner-root.b> role=ROLE_SYSTEM_GROUPING name="Banner"
diff --git a/content/test/data/accessibility/event/aria-hidden-descendants.html b/content/test/data/accessibility/event/aria-hidden-descendants.html
new file mode 100644
index 0000000..4a33575
--- /dev/null
+++ b/content/test/data/accessibility/event/aria-hidden-descendants.html
@@ -0,0 +1,28 @@
+<!--
+@WIN-DENY:IA2_EVENT_TEXT_INSERTED*
+@WIN-DENY:IA2_EVENT_TEXT_REMOVED*
+-->
+<html>
+<body>
+<!-- Show/hide events only need to occur on the root of what's shown/hidden,
+     not for each descendant -->
+<div role="toolbar">
+  <div id="heading-root" role="heading" aria-label="Heading" class="a">
+    <div id="heading-child">
+      <div id="heading-grandchild"></div>
+    </div>
+  </div>
+  <div id="banner-root" role="banner" aria-label="Banner" aria-hidden="true" class="b">
+    <div id="banner-child">
+      <div id="banner-grandchild"></div>
+    </div>
+  </div>
+</div>
+<script>
+  function go() {
+    document.querySelector('.a').setAttribute("aria-hidden", "true");
+    document.querySelector('.b').removeAttribute("aria-hidden");
+  }
+</script>
+</body>
+</html>
diff --git a/content/test/data/accessibility/event/css-display-descendants-expected-win.txt b/content/test/data/accessibility/event/css-display-descendants-expected-win.txt
new file mode 100644
index 0000000..ef42547
--- /dev/null
+++ b/content/test/data/accessibility/event/css-display-descendants-expected-win.txt
@@ -0,0 +1,4 @@
+EVENT_OBJECT_HIDE on <div#heading-root.a> role=ROLE_SYSTEM_GROUPING name="Heading" level=2
+EVENT_OBJECT_REORDER on <div#banner-child> role=ROLE_SYSTEM_GROUPING
+EVENT_OBJECT_REORDER on <div> role=ROLE_SYSTEM_TOOLBAR IA2_STATE_HORIZONTAL
+EVENT_OBJECT_SHOW on <div#banner-root.b> role=ROLE_SYSTEM_GROUPING name="Banner"
diff --git a/content/test/data/accessibility/event/css-display-descendants.html b/content/test/data/accessibility/event/css-display-descendants.html
new file mode 100644
index 0000000..add55ee
--- /dev/null
+++ b/content/test/data/accessibility/event/css-display-descendants.html
@@ -0,0 +1,35 @@
+<!--
+@WIN-DENY:IA2_EVENT_TEXT_INSERTED*
+@WIN-DENY:IA2_EVENT_TEXT_REMOVED*
+-->
+<html>
+<head>
+<style>
+  .a { display: block; }
+  .b { display: none; }
+  body.flip .a { display: none; }
+  body.flip .b { display: block; }
+</style>
+</head>
+<body>
+<!-- Show/hide events only need to occur on the root of what's shown/hidden,
+     not for each descendant -->
+<div role="toolbar">
+  <div id="heading-root" role="heading" aria-label="Heading" class="a">
+    <div id="heading-child">
+      <div id="heading-grandchild"></div>
+    </div>
+  </div>
+  <div id="banner-root" role="banner" aria-label="Banner" class="b">
+    <div id="banner-child">
+      <div id="banner-grandchild"></div>
+    </div>
+  </div>
+</div>
+<script>
+  function go() {
+    document.body.className = 'flip';
+  }
+</script>
+</body>
+</html>
diff --git a/content/test/data/accessibility/event/css-visibility-descendants-expected-win.txt b/content/test/data/accessibility/event/css-visibility-descendants-expected-win.txt
new file mode 100644
index 0000000..4ae2446b0
--- /dev/null
+++ b/content/test/data/accessibility/event/css-visibility-descendants-expected-win.txt
@@ -0,0 +1,3 @@
+EVENT_OBJECT_HIDE on <div#heading-root.a> role=ROLE_SYSTEM_GROUPING name="Heading" INVISIBLE level=2
+EVENT_OBJECT_REORDER on <div> role=ROLE_SYSTEM_TOOLBAR IA2_STATE_HORIZONTAL
+EVENT_OBJECT_SHOW on <div#banner-root.b> role=ROLE_SYSTEM_GROUPING name="Banner"
diff --git a/content/test/data/accessibility/event/css-visibility-descendants.html b/content/test/data/accessibility/event/css-visibility-descendants.html
new file mode 100644
index 0000000..0aafc051
--- /dev/null
+++ b/content/test/data/accessibility/event/css-visibility-descendants.html
@@ -0,0 +1,35 @@
+<!--
+@WIN-DENY:IA2_EVENT_TEXT_INSERTED*
+@WIN-DENY:IA2_EVENT_TEXT_REMOVED*
+-->
+<html>
+<head>
+<style>
+  .a { visibility: visible; }
+  .b { visibility: hidden; }
+  body.flip .a { visibility: hidden; }
+  body.flip .b { visibility: visible; }
+</style>
+</head>
+<body>
+<!-- Show/hide events only need to occur on the root of what's shown/hidden,
+     not for each descendant -->
+<div role="toolbar">
+  <div id="heading-root" role="heading" aria-label="Heading" class="a">
+    <div id="heading-child">
+      <div id="heading-grandchild"></div>
+    </div>
+  </div>
+  <div id="banner-root" role="banner" aria-label="Banner" class="b">
+    <div id="banner-child">
+      <div id="banner-grandchild"></div>
+    </div>
+  </div>
+</div>
+<script>
+  function go() {
+    document.body.className = 'flip';
+  }
+</script>
+</body>
+</html>
diff --git a/content/test/test_navigation_url_loader.cc b/content/test/test_navigation_url_loader.cc
index cb93b0f..e902ca3 100644
--- a/content/test/test_navigation_url_loader.cc
+++ b/content/test/test_navigation_url_loader.cc
@@ -72,7 +72,8 @@
 
 void TestNavigationURLLoader::CallOnResponseStarted(
     network::mojom::URLResponseHeadPtr response_head) {
-  response_head->parsed_headers = network::mojom::ParsedHeaders::New();
+  if (!response_head->parsed_headers)
+    response_head->parsed_headers = network::mojom::ParsedHeaders::New();
   // Create a bidirectionnal communication pipe between a URLLoader and a
   // URLLoaderClient. It will be closed at the end of this function. The sole
   // purpose of this is not to violate some DCHECKs when the navigation commits.
diff --git a/headless/lib/browser/headless_clipboard.cc b/headless/lib/browser/headless_clipboard.cc
index 44667f3..603f28d 100644
--- a/headless/lib/browser/headless_clipboard.cc
+++ b/headless/lib/browser/headless_clipboard.cc
@@ -176,6 +176,12 @@
     *result = it->second;
 }
 
+#if defined(USE_OZONE)
+bool HeadlessClipboard::IsSelectionBufferAvailable() const {
+  return false;
+}
+#endif  // defined(USE_OZONE)
+
 // |data_src| is not used. It's only passed to be consistent with other
 // platforms.
 void HeadlessClipboard::WritePortableRepresentations(
diff --git a/headless/lib/browser/headless_clipboard.h b/headless/lib/browser/headless_clipboard.h
index 67a40dd3..20bbf7bc 100644
--- a/headless/lib/browser/headless_clipboard.h
+++ b/headless/lib/browser/headless_clipboard.h
@@ -67,6 +67,9 @@
   void ReadData(const ui::ClipboardFormatType& format,
                 const ui::ClipboardDataEndpoint* data_dst,
                 std::string* result) const override;
+#if defined(USE_OZONE)
+  bool IsSelectionBufferAvailable() const override;
+#endif  // defined(USE_OZONE)
   void WritePortableRepresentations(
       ui::ClipboardBuffer buffer,
       const ObjectMap& objects,
diff --git a/ios/chrome/app/strings/ios_strings.grd b/ios/chrome/app/strings/ios_strings.grd
index 751773d..cb6d579 100644
--- a/ios/chrome/app/strings/ios_strings.grd
+++ b/ios/chrome/app/strings/ios_strings.grd
@@ -357,12 +357,12 @@
       <message name="IDS_IOS_AUTOFILL_SET_UP_SCREENLOCK_CONTENT" desc="Message informing the user that in order to use the password, a screen lock needs to be set up on the device. This is shown as an alert message after the user tries to view or copy the password from a settings page. [CHAR_LIMIT=100]">
         To use passwords, you must first set a passcode on your device.
       </message>
-      <message name="IDS_IOS_COPY_ACTION_TITLE" desc="Title of the action used to copy the selected piece of content to the clipboard. Targeted content type includes URL or image. [iOS only]">
-        Copy
-      </message>
       <message name="IDS_IOS_DELETE_ACTION_TITLE" desc="Title of the action used to delete a selected item. Item types include, for example, a Bookmark or an History Entry. [iOS only]">
         Delete
       </message>
+      <message name="IDS_IOS_OPEN_IN_INCOGNITO_ACTION_TITLE" desc="Title of the action used to open an URL in a new Incognito tab. [iOS only]">
+        Open in Incognito
+      </message>
       <message name="IDS_IOS_BADGE_INCOGNITO_HINT" desc="Button displayed when the user is in Incognito mode. [iOS only]">
         Current Webpage is on Incognito
       </message>
@@ -616,7 +616,7 @@
         Open
       </message>
       <message name="IDS_IOS_CONTENT_CONTEXT_OPENINNEWWINDOW" desc="The iOS menu item for opening a link in a new window. [iOS only]">
-        Open In New Window
+        Open in New Window
       </message>
       <message name="IDS_IOS_CONTENT_CONTEXT_OPENIMAGE" desc="The name of the Open Image command in the content area context menu">
         Open Image
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_CONTENT_CONTEXT_OPENINNEWWINDOW.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_CONTENT_CONTEXT_OPENINNEWWINDOW.png.sha1
index 6b4f6835..6492618 100644
--- a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_CONTENT_CONTEXT_OPENINNEWWINDOW.png.sha1
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_CONTENT_CONTEXT_OPENINNEWWINDOW.png.sha1
@@ -1 +1 @@
-2589d145f8e666dad0b925a24d010fd5bb7a342b
\ No newline at end of file
+b7f2e1d1ccbf5bb66cf754f620214caa77b38df9
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_COPY_ACTION_TITLE.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_COPY_ACTION_TITLE.png.sha1
deleted file mode 100644
index 244445a..0000000
--- a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_COPY_ACTION_TITLE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-9caa7694a2ef308a353a2ab234f4f1ba44920f86
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_OPEN_IN_INCOGNITO_ACTION_TITLE.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_OPEN_IN_INCOGNITO_ACTION_TITLE.png.sha1
new file mode 100644
index 0000000..6492618
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_OPEN_IN_INCOGNITO_ACTION_TITLE.png.sha1
@@ -0,0 +1 @@
+b7f2e1d1ccbf5bb66cf754f620214caa77b38df9
\ No newline at end of file
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm
index c3742fc..3a8f5118 100644
--- a/ios/chrome/browser/flags/about_flags.mm
+++ b/ios/chrome/browser/flags/about_flags.mm
@@ -554,9 +554,6 @@
     {"page-info-refactoring", flag_descriptions::kPageInfoRefactoringName,
      flag_descriptions::kPageInfoRefactoringDescription, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(kPageInfoRefactoring)},
-    {"contained-browser-bvc", flag_descriptions::kContainedBVCName,
-     flag_descriptions::kContainedBVCDescription, flags_ui::kOsIos,
-     FEATURE_VALUE_TYPE(kContainedBVC)},
     {"ssl-committed-interstitials",
      flag_descriptions::kSSLCommittedInterstitialsName,
      flag_descriptions::kSSLCommittedInterstitialsDescription, flags_ui::kOsIos,
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
index 0dfb506..dc867c02 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -165,11 +165,6 @@
 const char kConfirmInfobarMessagesUIDescription[] =
     "When enabled Confirm Infobars use the new Messages UI.";
 
-const char kContainedBVCName[] = "Contained Browser ViewController";
-const char kContainedBVCDescription[] =
-    "When enabled, the BrowserViewController is contained by the TabGrid "
-    "instead of being presented";
-
 const char kCrashRestoreInfobarMessagesUIName[] =
     "Crash Restore Infobars Messages UI";
 const char kCrashRestoreInfobarMessagesUIDescription[] =
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
index ebadd3a..1d1b418 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -130,11 +130,6 @@
 extern const char kConfirmInfobarMessagesUIName[];
 extern const char kConfirmInfobarMessagesUIDescription[];
 
-// Title and description for the flag that makes the Browser being contained by
-// the TabGrid instead of being presented.
-extern const char kContainedBVCName[];
-extern const char kContainedBVCDescription[];
-
 // Title and description for the flag that enables Messages UI on
 // Crash Restore Infobars.
 extern const char kCrashRestoreInfobarMessagesUIName[];
diff --git a/ios/chrome/browser/ui/main/scene_controller.mm b/ios/chrome/browser/ui/main/scene_controller.mm
index 48fa0fb..e8b7f58c 100644
--- a/ios/chrome/browser/ui/main/scene_controller.mm
+++ b/ios/chrome/browser/ui/main/scene_controller.mm
@@ -178,7 +178,7 @@
     NTPTabOpeningPostOpeningAction NTPActionAfterTabSwitcherDismissal;
 
 // TabSwitcher object -- the tab grid.
-@property(nonatomic, strong) id<TabSwitcher> tabSwitcher;
+@property(nonatomic, strong, readonly) id<TabSwitcher> tabSwitcher;
 
 // The main coordinator, lazily created the first time it is accessed. Manages
 // the main view controller. This property should not be accessed before the
@@ -270,6 +270,10 @@
          self.signinCoordinator.isSettingsViewPresented;
 }
 
+- (id<TabSwitcher>)tabSwitcher {
+  return self.mainCoordinator;
+}
+
 #pragma mark - SceneStateObserver
 
 - (void)sceneState:(SceneState*)sceneState
@@ -584,9 +588,8 @@
   // Lazy init of mainCoordinator.
   [self.mainCoordinator start];
 
-  self.tabSwitcher = self.mainCoordinator.tabSwitcher;
   // Call -restoreInternalState so that the grid shows the correct panel.
-  [_tabSwitcher
+  [self.tabSwitcher
       restoreInternalStateWithMainBrowser:self.mainInterface.browser
                                otrBrowser:self.incognitoInterface.browser
                             activeBrowser:self.currentInterface.browser];
diff --git a/ios/chrome/browser/ui/menu/BUILD.gn b/ios/chrome/browser/ui/menu/BUILD.gn
index b1549bb..2d86946 100644
--- a/ios/chrome/browser/ui/menu/BUILD.gn
+++ b/ios/chrome/browser/ui/menu/BUILD.gn
@@ -12,6 +12,7 @@
   ]
   configs += [ "//build/config/compiler:enable_arc" ]
   deps = [
+    "resources:copy_link_url",
     "//base",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/ui/commands",
@@ -30,6 +31,7 @@
   sources = [ "action_factory_unittest.mm" ]
   deps = [
     ":menu",
+    "resources:copy_link_url",
     "//base",
     "//base/test:test_support",
     "//ios/chrome/app/strings",
diff --git a/ios/chrome/browser/ui/menu/action_factory.mm b/ios/chrome/browser/ui/menu/action_factory.mm
index 370d3ac..3d334c9 100644
--- a/ios/chrome/browser/ui/menu/action_factory.mm
+++ b/ios/chrome/browser/ui/menu/action_factory.mm
@@ -59,12 +59,13 @@
 }
 
 - (UIAction*)actionToCopyURL:(const GURL)URL {
-  return [self actionWithTitle:l10n_util::GetNSString(IDS_IOS_COPY_ACTION_TITLE)
-                         image:[UIImage systemImageNamed:@"doc.on.doc"]
-                          type:MenuActionType::Copy
-                         block:^{
-                           StoreURLInPasteboard(URL);
-                         }];
+  return
+      [self actionWithTitle:l10n_util::GetNSString(IDS_IOS_CONTENT_CONTEXT_COPY)
+                      image:[UIImage imageNamed:@"copy_link_url"]
+                       type:MenuActionType::Copy
+                      block:^{
+                        StoreURLInPasteboard(URL);
+                      }];
 }
 
 - (UIAction*)actionToDeleteWithBlock:(ProceduralBlock)block {
@@ -113,12 +114,11 @@
 }
 
 - (UIAction*)actionToOpenInNewIncognitoTabWithBlock:(ProceduralBlock)block {
-  return
-      [self actionWithTitle:l10n_util::GetNSString(
-                                IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWINCOGNITOTAB)
-                      image:nil
-                       type:MenuActionType::OpenInNewIncognitoTab
-                      block:block];
+  return [self actionWithTitle:l10n_util::GetNSString(
+                                   IDS_IOS_OPEN_IN_INCOGNITO_ACTION_TITLE)
+                         image:nil
+                          type:MenuActionType::OpenInNewIncognitoTab
+                         block:block];
 }
 
 - (UIAction*)actionToOpenInNewWindowWithURL:(const GURL)URL
diff --git a/ios/chrome/browser/ui/menu/action_factory_unittest.mm b/ios/chrome/browser/ui/menu/action_factory_unittest.mm
index 2a19dc0a..74b01fd 100644
--- a/ios/chrome/browser/ui/menu/action_factory_unittest.mm
+++ b/ios/chrome/browser/ui/menu/action_factory_unittest.mm
@@ -96,8 +96,9 @@
         [[ActionFactory alloc] initWithBrowser:test_browser_.get()
                                       scenario:kTestMenuScenario];
 
-    UIImage* expectedImage = [UIImage systemImageNamed:@"doc.on.doc"];
-    NSString* expectedTitle = l10n_util::GetNSString(IDS_IOS_COPY_ACTION_TITLE);
+    UIImage* expectedImage = [UIImage imageNamed:@"copy_link_url"];
+    NSString* expectedTitle =
+        l10n_util::GetNSString(IDS_IOS_CONTENT_CONTEXT_COPY);
 
     GURL testURL = GURL("https://example.com");
 
@@ -162,7 +163,7 @@
     GURL testURL = GURL("https://example.com");
 
     NSString* expectedTitle =
-        l10n_util::GetNSString(IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWINCOGNITOTAB);
+        l10n_util::GetNSString(IDS_IOS_OPEN_IN_INCOGNITO_ACTION_TITLE);
 
     UIAction* actionWithURL =
         [factory actionToOpenInNewIncognitoTabWithURL:testURL completion:nil];
diff --git a/ios/chrome/browser/ui/menu/resources/BUILD.gn b/ios/chrome/browser/ui/menu/resources/BUILD.gn
new file mode 100644
index 0000000..fc0ae5e
--- /dev/null
+++ b/ios/chrome/browser/ui/menu/resources/BUILD.gn
@@ -0,0 +1,13 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/ios/asset_catalog.gni")
+
+imageset("copy_link_url") {
+  sources = [
+    "copy_link_url.imageset/Contents.json",
+    "copy_link_url.imageset/copy_link_url@2x.png",
+    "copy_link_url.imageset/copy_link_url@3x.png",
+  ]
+}
diff --git a/ios/chrome/browser/ui/menu/resources/copy_link_url.imageset/Contents.json b/ios/chrome/browser/ui/menu/resources/copy_link_url.imageset/Contents.json
new file mode 100644
index 0000000..280deeec
--- /dev/null
+++ b/ios/chrome/browser/ui/menu/resources/copy_link_url.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "filename" : "copy_link_url@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "copy_link_url@3x.png",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  },
+  "properties" : {
+    "template-rendering-intent": "template"
+  }
+}
diff --git a/ios/chrome/browser/ui/menu/resources/copy_link_url.imageset/copy_link_url@2x.png b/ios/chrome/browser/ui/menu/resources/copy_link_url.imageset/copy_link_url@2x.png
new file mode 100644
index 0000000..9771a14
--- /dev/null
+++ b/ios/chrome/browser/ui/menu/resources/copy_link_url.imageset/copy_link_url@2x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/menu/resources/copy_link_url.imageset/copy_link_url@3x.png b/ios/chrome/browser/ui/menu/resources/copy_link_url.imageset/copy_link_url@3x.png
new file mode 100644
index 0000000..69e9af0
--- /dev/null
+++ b/ios/chrome/browser/ui/menu/resources/copy_link_url.imageset/copy_link_url@3x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_view_controller.mm b/ios/chrome/browser/ui/omnibox/omnibox_view_controller.mm
index e3895a4a..6f6e05e 100644
--- a/ios/chrome/browser/ui/omnibox/omnibox_view_controller.mm
+++ b/ios/chrome/browser/ui/omnibox/omnibox_view_controller.mm
@@ -42,6 +42,11 @@
 
 }  // namespace
 
+#if defined(__IPHONE_14_0)
+@interface OmniboxViewController (Scribble) <UIScribbleInteractionDelegate>
+@end
+#endif  // defined(__IPHONE14_0)
+
 @interface OmniboxViewController () <OmniboxTextFieldDelegate> {
   // Weak, acts as a delegate
   OmniboxTextChangeDelegate* _textChangeDelegate;
@@ -122,6 +127,13 @@
 
   SetA11yLabelAndUiAutomationName(self.textField, IDS_ACCNAME_LOCATION,
                                   @"Address");
+
+#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0
+  if (@available(iOS 14, *)) {
+    [self.textField
+        addInteraction:[[UIScribbleInteraction alloc] initWithDelegate:self]];
+  }
+#endif  // defined(__IPHONE_14_0)
 }
 
 - (void)viewDidLoad {
@@ -227,6 +239,17 @@
   return self.view.textField;
 }
 
+- (void)prepareOmniboxForScribble {
+  [self.textField exitPreEditState];
+  [self.textField setText:[[NSAttributedString alloc] initWithString:@""]
+           userTextLength:0];
+  self.textField.placeholder = nil;
+}
+
+- (void)cleanupOmniboxAfterScribble {
+  self.textField.placeholder = l10n_util::GetNSString(IDS_OMNIBOX_EMPTY_HINT);
+}
+
 #pragma mark - OmniboxTextFieldDelegate
 
 - (BOOL)textField:(UITextField*)textField
@@ -588,4 +611,29 @@
       }));
 }
 
+#pragma mark - UIScribbleInteractionDelegate
+
+#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0
+
+- (void)scribbleInteractionWillBeginWriting:(UIScribbleInteraction*)interaction
+    API_AVAILABLE(ios(14.0)) {
+  if (self.textField.isPreEditing) {
+    [self.textField exitPreEditState];
+    [self.textField setText:[[NSAttributedString alloc] initWithString:@""]
+             userTextLength:0];
+  }
+
+  [self.textField clearAutocompleteText];
+}
+
+- (void)scribbleInteractionDidFinishWriting:(UIScribbleInteraction*)interaction
+    API_AVAILABLE(ios(14.0)) {
+  [self cleanupOmniboxAfterScribble];
+
+  // Dismiss any inline autocomplete. The user expectation is to not have it.
+  [self.textField clearAutocompleteText];
+}
+
+#endif  // defined(__IPHONE_14_0)
+
 @end
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_coordinator.mm b/ios/chrome/browser/ui/reading_list/reading_list_coordinator.mm
index 05ca688b..321ff7c6a 100644
--- a/ios/chrome/browser/ui/reading_list/reading_list_coordinator.mm
+++ b/ios/chrome/browser/ui/reading_list/reading_list_coordinator.mm
@@ -421,6 +421,8 @@
 
 - (UIContextMenuConfiguration*)contextMenuConfigurationForItem:
     (id<ReadingListListItem>)item API_AVAILABLE(ios(13.0)) {
+  __weak id<ReadingListListItemAccessibilityDelegate> accessibilityDelegate =
+      self.tableViewController;
   __weak __typeof(self) weakSelf = self;
 
   UIContextMenuActionProvider actionProvider =
@@ -468,6 +470,10 @@
 
         [menuElements addObject:[actionFactory actionToCopyURL:item.entryURL]];
 
+        [menuElements addObject:[actionFactory actionToDeleteWithBlock:^{
+                        [accessibilityDelegate deleteItem:item];
+                      }]];
+
         return [UIMenu menuWithTitle:@"" children:menuElements];
       };
 
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_list_item_accessibility_delegate.h b/ios/chrome/browser/ui/reading_list/reading_list_list_item_accessibility_delegate.h
index 4833e07..050c497 100644
--- a/ios/chrome/browser/ui/reading_list/reading_list_list_item_accessibility_delegate.h
+++ b/ios/chrome/browser/ui/reading_list/reading_list_list_item_accessibility_delegate.h
@@ -19,6 +19,7 @@
 - (void)openItemOffline:(id<ReadingListListItem>)item;
 - (void)markItemRead:(id<ReadingListListItem>)item;
 - (void)markItemUnread:(id<ReadingListListItem>)item;
+- (void)deleteItem:(id<ReadingListListItem>)item;
 
 @end
 
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_table_view_controller.mm b/ios/chrome/browser/ui/reading_list/reading_list_table_view_controller.mm
index 8e8b019..da91a67 100644
--- a/ios/chrome/browser/ui/reading_list/reading_list_table_view_controller.mm
+++ b/ios/chrome/browser/ui/reading_list/reading_list_table_view_controller.mm
@@ -441,6 +441,16 @@
   }
 }
 
+- (void)deleteItem:(id<ReadingListListItem>)item {
+  TableViewItem<ReadingListListItem>* tableViewItem =
+      base::mac::ObjCCastStrict<TableViewItem<ReadingListListItem>>(item);
+  if ([self.tableViewModel hasItem:tableViewItem]) {
+    NSIndexPath* indexPath =
+        [self.tableViewModel indexPathForItem:tableViewItem];
+    [self deleteItemsAtIndexPaths:@[ indexPath ]];
+  }
+}
+
 #pragma mark - ReadingListToolbarButtonCommands
 
 - (void)enterReadingListEditMode {
diff --git a/ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.mm b/ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.mm
index d2d41e2..efddfb3 100644
--- a/ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.mm
+++ b/ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.mm
@@ -181,9 +181,6 @@
 
 - (void)viewWillAppear:(BOOL)animated {
   [super viewWillAppear:animated];
-  if (!base::FeatureList::IsEnabled(kContainedBVC)) {
-    self.preventUpdates = NO;
-  }
   if (!self.preventUpdates) {
     // The table view might get stale while hidden, so we need to forcibly
     // refresh it here.
@@ -193,9 +190,6 @@
 }
 
 - (void)viewWillDisappear:(BOOL)animated {
-  if (!base::FeatureList::IsEnabled(kContainedBVC)) {
-    self.preventUpdates = YES;
-  }
   [super viewWillDisappear:animated];
 }
 
@@ -223,7 +217,7 @@
 
   _preventUpdates = preventUpdates;
 
-  if (preventUpdates || !base::FeatureList::IsEnabled(kContainedBVC))
+  if (preventUpdates)
     return;
   [self loadModel];
   [self.tableView reloadData];
diff --git a/ios/chrome/browser/ui/tab_grid/BUILD.gn b/ios/chrome/browser/ui/tab_grid/BUILD.gn
index ead7b36b..42582503 100644
--- a/ios/chrome/browser/ui/tab_grid/BUILD.gn
+++ b/ios/chrome/browser/ui/tab_grid/BUILD.gn
@@ -6,8 +6,6 @@
 
 source_set("tab_grid") {
   sources = [
-    "tab_grid_adaptor.h",
-    "tab_grid_adaptor.mm",
     "tab_grid_coordinator.h",
     "tab_grid_coordinator.mm",
     "tab_grid_mediator.h",
@@ -67,8 +65,6 @@
 
 source_set("tab_grid_ui") {
   sources = [
-    "legacy_tab_grid_transition_handler.h",
-    "legacy_tab_grid_transition_handler.mm",
     "tab_grid_bottom_toolbar.h",
     "tab_grid_bottom_toolbar.mm",
     "tab_grid_empty_state_view.h",
diff --git a/ios/chrome/browser/ui/tab_grid/legacy_tab_grid_transition_handler.h b/ios/chrome/browser/ui/tab_grid/legacy_tab_grid_transition_handler.h
deleted file mode 100644
index d13fdfa..0000000
--- a/ios/chrome/browser/ui/tab_grid/legacy_tab_grid_transition_handler.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_UI_TAB_GRID_LEGACY_TAB_GRID_TRANSITION_HANDLER_H_
-#define IOS_CHROME_BROWSER_UI_TAB_GRID_LEGACY_TAB_GRID_TRANSITION_HANDLER_H_
-
-#import <UIKit/UIKit.h>
-
-@protocol GridTransitionAnimationLayoutProviding;
-
-@interface LegacyTabGridTransitionHandler
-    : NSObject <UIViewControllerTransitioningDelegate>
-
-@property(nonatomic, weak) id<GridTransitionAnimationLayoutProviding> provider;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_UI_TAB_GRID_LEGACY_TAB_GRID_TRANSITION_HANDLER_H_
diff --git a/ios/chrome/browser/ui/tab_grid/legacy_tab_grid_transition_handler.mm b/ios/chrome/browser/ui/tab_grid/legacy_tab_grid_transition_handler.mm
deleted file mode 100644
index 2e242dc..0000000
--- a/ios/chrome/browser/ui/tab_grid/legacy_tab_grid_transition_handler.mm
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/chrome/browser/ui/tab_grid/legacy_tab_grid_transition_handler.h"
-
-#import "ios/chrome/browser/ui/tab_grid/transitions/grid_transition_animation_layout_providing.h"
-#import "ios/chrome/browser/ui/tab_grid/transitions/legacy_grid_to_visible_tab_animator.h"
-#import "ios/chrome/browser/ui/tab_grid/transitions/legacy_tab_to_grid_animator.h"
-#import "ios/chrome/browser/ui/tab_grid/transitions/reduced_motion_animator.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-@implementation LegacyTabGridTransitionHandler
-
-#pragma mark - UIViewControllerTransitioningDelegate
-
-- (id<UIViewControllerAnimatedTransitioning>)
-    animationControllerForPresentedController:(UIViewController*)presented
-                         presentingController:(UIViewController*)presenting
-                             sourceController:(UIViewController*)source {
-  if (!UIAccessibilityIsReduceMotionEnabled() &&
-      self.provider.selectedCellVisible) {
-    return [[LegacyGridToVisibleTabAnimator alloc]
-        initWithAnimationLayoutProvider:self.provider];
-  }
-  ReducedMotionAnimator* simpleAnimator = [[ReducedMotionAnimator alloc] init];
-  simpleAnimator.presenting = YES;
-  return simpleAnimator;
-}
-
-- (id<UIViewControllerAnimatedTransitioning>)
-    animationControllerForDismissedController:(UIViewController*)dismissed {
-  if (!UIAccessibilityIsReduceMotionEnabled()) {
-    return [[LegacyTabToGridAnimator alloc]
-        initWithAnimationLayoutProvider:self.provider];
-  }
-  ReducedMotionAnimator* simpleAnimator = [[ReducedMotionAnimator alloc] init];
-  simpleAnimator.presenting = NO;
-  return simpleAnimator;
-}
-
-@end
diff --git a/ios/chrome/browser/ui/tab_grid/tab_grid_adaptor.h b/ios/chrome/browser/ui/tab_grid/tab_grid_adaptor.h
deleted file mode 100644
index 6e051df..0000000
--- a/ios/chrome/browser/ui/tab_grid/tab_grid_adaptor.h
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_UI_TAB_GRID_TAB_GRID_ADAPTOR_H_
-#define IOS_CHROME_BROWSER_UI_TAB_GRID_TAB_GRID_ADAPTOR_H_
-
-#import <Foundation/Foundation.h>
-
-#import "ios/chrome/browser/ui/tab_grid/tab_grid_mediator.h"
-#import "ios/chrome/browser/ui/tab_grid/tab_grid_view_controller.h"
-#import "ios/chrome/browser/ui/tab_grid/tab_switcher.h"
-
-@class TabGridViewController;
-
-// An opaque adaptor for the TabSwitcher protocol into the TabGrid.
-// Consuming objects should be passed instances of this object as an
-// id<TabSwitcher>.
-// All of the methods and properties on this class are internal API fot the
-// tab grid, and external code shouldn't depend on them.
-@interface TabGridAdaptor : NSObject<TabSwitcher>
-@property(nonatomic, weak) TabGridViewController* tabGridViewController;
-// The mediator for the incognito grid.
-@property(nonatomic, weak) TabGridMediator* incognitoMediator;
-@end
-
-#endif  // IOS_CHROME_BROWSER_UI_TAB_GRID_TAB_GRID_ADAPTOR_H_
diff --git a/ios/chrome/browser/ui/tab_grid/tab_grid_adaptor.mm b/ios/chrome/browser/ui/tab_grid/tab_grid_adaptor.mm
deleted file mode 100644
index 13bc471..0000000
--- a/ios/chrome/browser/ui/tab_grid/tab_grid_adaptor.mm
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/chrome/browser/ui/tab_grid/tab_grid_adaptor.h"
-
-#include "base/check.h"
-#import "ios/chrome/browser/main/browser.h"
-#include "ios/chrome/browser/main/browser.h"
-#import "ios/chrome/browser/ui/tab_grid/tab_grid_paging.h"
-#import "ios/chrome/browser/ui/tab_grid/tab_grid_view_controller.h"
-#import "ios/chrome/browser/url_loading/url_loading_params.h"
-#import "ios/chrome/browser/web_state_list/tab_insertion_browser_agent.h"
-#import "ios/chrome/browser/web_state_list/web_state_list.h"
-#import "ios/web/public/navigation/navigation_manager.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-@implementation TabGridAdaptor
-// TabSwitcher properties.
-@synthesize delegate = _delegate;
-
-#pragma mark - TabSwitcher
-
-- (void)restoreInternalStateWithMainBrowser:(Browser*)mainBrowser
-                                 otrBrowser:(Browser*)otrBrowser
-                              activeBrowser:(Browser*)activeBrowser {
-  // The only action here is to signal to the tab grid which panel should be
-  // active.
-  if (activeBrowser == otrBrowser) {
-    self.tabGridViewController.activePage = TabGridPageIncognitoTabs;
-  } else {
-    self.tabGridViewController.activePage = TabGridPageRegularTabs;
-  }
-}
-
-- (UIViewController*)viewController {
-  return self.tabGridViewController;
-}
-
-- (void)dismissWithNewTabAnimationToBrowser:(Browser*)browser
-                          withUrlLoadParams:(const UrlLoadParams&)urlLoadParams
-                                    atIndex:(int)position {
-  int tabIndex = std::min(position, browser->GetWebStateList()->count());
-
-  TabInsertionBrowserAgent::FromBrowser(browser)->InsertWebState(
-      urlLoadParams.web_params, nil, false, tabIndex, false);
-
-  // Tell the delegate to display the tab.
-  DCHECK(self.delegate);
-  [self.delegate tabSwitcher:self
-      shouldFinishWithBrowser:browser
-                 focusOmnibox:NO];
-}
-
-- (void)setOtrBrowser:(Browser*)browser {
-  DCHECK(self.incognitoMediator);
-  self.incognitoMediator.browser = browser;
-}
-
-@end
diff --git a/ios/chrome/browser/ui/tab_grid/tab_grid_coordinator.h b/ios/chrome/browser/ui/tab_grid/tab_grid_coordinator.h
index f00c88e..7f253ee 100644
--- a/ios/chrome/browser/ui/tab_grid/tab_grid_coordinator.h
+++ b/ios/chrome/browser/ui/tab_grid/tab_grid_coordinator.h
@@ -9,14 +9,14 @@
 
 #import "base/ios/block_types.h"
 #import "ios/chrome/browser/chrome_root_coordinator.h"
+#import "ios/chrome/browser/ui/tab_grid/tab_switcher.h"
 
 @protocol ApplicationCommands;
 @protocol BrowsingDataCommands;
-@protocol TabSwitcher;
 
 class Browser;
 
-@interface TabGridCoordinator : ChromeRootCoordinator
+@interface TabGridCoordinator : ChromeRootCoordinator <TabSwitcher>
 
 - (instancetype)initWithWindow:(UIWindow*)window
      applicationCommandEndpoint:
@@ -27,7 +27,7 @@
 
 - (instancetype)initWithWindow:(UIWindow*)window NS_UNAVAILABLE;
 
-@property(nonatomic, readonly) id<TabSwitcher> tabSwitcher;
+@property(nonatomic, weak) id<TabSwitcherDelegate> delegate;
 
 @property(nonatomic, assign) Browser* regularBrowser;
 @property(nonatomic, assign) Browser* incognitoBrowser;
diff --git a/ios/chrome/browser/ui/tab_grid/tab_grid_coordinator.mm b/ios/chrome/browser/ui/tab_grid/tab_grid_coordinator.mm
index 6e395fa..c640384 100644
--- a/ios/chrome/browser/ui/tab_grid/tab_grid_coordinator.mm
+++ b/ios/chrome/browser/ui/tab_grid/tab_grid_coordinator.mm
@@ -21,8 +21,6 @@
 #import "ios/chrome/browser/ui/recent_tabs/recent_tabs_mediator.h"
 #import "ios/chrome/browser/ui/recent_tabs/recent_tabs_presentation_delegate.h"
 #import "ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.h"
-#import "ios/chrome/browser/ui/tab_grid/legacy_tab_grid_transition_handler.h"
-#import "ios/chrome/browser/ui/tab_grid/tab_grid_adaptor.h"
 #import "ios/chrome/browser/ui/tab_grid/tab_grid_mediator.h"
 #import "ios/chrome/browser/ui/tab_grid/tab_grid_paging.h"
 #import "ios/chrome/browser/ui/tab_grid/tab_grid_view_controller.h"
@@ -30,6 +28,7 @@
 #include "ios/chrome/browser/ui/ui_feature_flags.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
 #import "ios/chrome/browser/url_loading/url_loading_params.h"
+#import "ios/chrome/browser/web_state_list/tab_insertion_browser_agent.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -41,20 +40,12 @@
                                  RecentTabsPresentationDelegate>
 // Superclass property specialized for the class that this coordinator uses.
 @property(nonatomic, weak) TabGridViewController* baseViewController;
-// Pointer to the masking view used to prevent the main view controller from
-// being shown at launch.
-@property(nonatomic, strong) UIView* launchMaskView;
 // Commad dispatcher used while this coordinator's view controller is active.
 // (for compatibility with the TabSwitcher protocol).
 @property(nonatomic, strong) CommandDispatcher* dispatcher;
-// Object that internally backs the public  TabSwitcher
-@property(nonatomic, strong) TabGridAdaptor* adaptor;
 // Container view controller for the BVC to live in; this class's view
 // controller will present this.
 @property(nonatomic, strong) BVCContainerViewController* bvcContainer;
-// Transitioning delegate for the view controller.
-@property(nonatomic, strong)
-    LegacyTabGridTransitionHandler* legacyTransitionHandler;
 // Handler for the transitions between the TabGrid and the Browser.
 @property(nonatomic, strong) TabGridTransitionHandler* transitionHandler;
 // Mediator for regular Tabs.
@@ -99,10 +90,6 @@
 
 #pragma mark - Public
 
-- (id<TabSwitcher>)tabSwitcher {
-  return self.adaptor;
-}
-
 - (Browser*)regularBrowser {
   // Ensure browser which is actually used by the mediator is returned, as it
   // may have been updated.
@@ -151,16 +138,9 @@
       [[TabGridViewController alloc] init];
   baseViewController.handler =
       HandlerForProtocol(self.dispatcher, ApplicationCommands);
-  self.legacyTransitionHandler = [[LegacyTabGridTransitionHandler alloc] init];
-  self.legacyTransitionHandler.provider = baseViewController;
-  baseViewController.modalPresentationStyle = UIModalPresentationCustom;
-  baseViewController.transitioningDelegate = self.legacyTransitionHandler;
   baseViewController.tabPresentationDelegate = self;
   _baseViewController = baseViewController;
 
-  self.adaptor = [[TabGridAdaptor alloc] init];
-  self.adaptor.tabGridViewController = self.baseViewController;
-
   self.regularTabsMediator = [[TabGridMediator alloc]
       initWithConsumer:baseViewController.regularTabsConsumer];
   ChromeBrowserState* regularBrowserState =
@@ -177,7 +157,6 @@
   self.incognitoTabsMediator = [[TabGridMediator alloc]
       initWithConsumer:baseViewController.incognitoTabsConsumer];
   self.incognitoTabsMediator.browser = _incognitoBrowser;
-  self.adaptor.incognitoMediator = self.incognitoTabsMediator;
   baseViewController.regularTabsDelegate = self.regularTabsMediator;
   baseViewController.incognitoTabsDelegate = self.incognitoTabsMediator;
   baseViewController.regularTabsDragDropHandler = self.regularTabsMediator;
@@ -207,21 +186,7 @@
       WindowOpenDisposition::NEW_FOREGROUND_TAB;
   baseViewController.remoteTabsViewController.presentationDelegate = self;
 
-  if (!base::FeatureList::IsEnabled(kContainedBVC)) {
-    // Insert the launch screen view in front of this view to hide it until
-    // after launch. This should happen before |baseViewController| is made the
-    // window's root view controller.
-    NSBundle* mainBundle = base::mac::FrameworkBundle();
-    NSArray* topObjects = [mainBundle loadNibNamed:@"LaunchScreen"
-                                             owner:self
-                                           options:nil];
-    UIViewController* launchScreenController =
-        base::mac::ObjCCastStrict<UIViewController>([topObjects lastObject]);
-    self.launchMaskView = launchScreenController.view;
-    [baseViewController.view addSubview:self.launchMaskView];
-  } else {
-    self.firstPresentation = YES;
-  }
+  self.firstPresentation = YES;
 
   // TODO(crbug.com/850387) : Currently, consumer calls from the mediator
   // prematurely loads the view in |RecentTabsTableViewController|. Fix this so
@@ -265,10 +230,6 @@
 
 - (UIViewController*)activeViewController {
   if (self.bvcContainer) {
-    if (!base::FeatureList::IsEnabled(kContainedBVC)) {
-      DCHECK_EQ(self.bvcContainer,
-                self.baseViewController.presentedViewController);
-    }
     DCHECK(self.bvcContainer.currentBVC);
     return self.bvcContainer.currentBVC;
   }
@@ -290,41 +251,30 @@
 }
 
 - (void)showTabSwitcher:(id<TabSwitcher>)tabSwitcher {
-  DCHECK(tabSwitcher);
   DCHECK_EQ([tabSwitcher viewController], self.baseViewController);
-  // It's also expected that |tabSwitcher| will be |self.tabSwitcher|, but that
-  // may not be worth a DCHECK?
-
   BOOL animated = !self.animationsDisabledForTesting;
 
   // If a BVC is currently being presented, dismiss it.  This will trigger any
   // necessary animations.
   if (self.bvcContainer) {
-    if (base::FeatureList::IsEnabled(kContainedBVC)) {
-      // This is done with a dispatch to make sure that the view isn't added to
-      // the view hierarchy right away, as it is not the expectations of the
-      // API.
-      dispatch_async(dispatch_get_main_queue(), ^{
-        [self.baseViewController contentWillAppearAnimated:animated];
-        self.baseViewController.childViewControllerForStatusBarStyle = nil;
+    // This is done with a dispatch to make sure that the view isn't added to
+    // the view hierarchy right away, as it is not the expectations of the
+    // API.
+    dispatch_async(dispatch_get_main_queue(), ^{
+      [self.baseViewController contentWillAppearAnimated:animated];
+      self.baseViewController.childViewControllerForStatusBarStyle = nil;
 
-        self.transitionHandler = [[TabGridTransitionHandler alloc]
-            initWithLayoutProvider:self.baseViewController];
-        self.transitionHandler.animationDisabled = !animated;
-        [self.transitionHandler
-            transitionFromBrowser:self.bvcContainer
-                        toTabGrid:self.baseViewController
-                   withCompletion:^{
-                     self.bvcContainer = nil;
-                     [self.baseViewController contentDidAppear];
-                   }];
-      });
-    } else {
-      self.bvcContainer.transitioningDelegate = self.legacyTransitionHandler;
-      self.bvcContainer = nil;
-      [self.baseViewController dismissViewControllerAnimated:animated
-                                                  completion:nil];
-    }
+      self.transitionHandler = [[TabGridTransitionHandler alloc]
+          initWithLayoutProvider:self.baseViewController];
+      self.transitionHandler.animationDisabled = !animated;
+      [self.transitionHandler
+          transitionFromBrowser:self.bvcContainer
+                      toTabGrid:self.baseViewController
+                 withCompletion:^{
+                   self.bvcContainer = nil;
+                   [self.baseViewController contentDidAppear];
+                 }];
+    });
   }
   // Record when the tab switcher is presented.
   base::RecordAction(base::UserMetricsAction("MobileTabGridEntered"));
@@ -348,12 +298,10 @@
   }
 
   self.bvcContainer = [[BVCContainerViewController alloc] init];
-  self.bvcContainer.modalPresentationStyle = UIModalPresentationFullScreen;
   self.bvcContainer.currentBVC = viewController;
-  self.bvcContainer.transitioningDelegate = self.legacyTransitionHandler;
   BOOL animated = !self.animationsDisabledForTesting;
   // Never animate the first time.
-  if (self.launchMaskView || self.firstPresentation)
+  if (self.firstPresentation)
     animated = NO;
 
   // Extened |completion| to signal the tab switcher delegate
@@ -361,43 +309,31 @@
   // on top of the tab switcher) transition has completed.
   // Finally, the launch mask view should be removed.
   ProceduralBlock extendedCompletion = ^{
-    [self.tabSwitcher.delegate
-        tabSwitcherDismissTransitionDidEnd:self.tabSwitcher];
-    if (base::FeatureList::IsEnabled(kContainedBVC)) {
-      if (!GetFirstResponder()) {
-        // It is possible to already have a first responder (for example the
-        // omnibox). In that case, we don't want to mark BVC as first responder.
-        [self.bvcContainer.currentBVC becomeFirstResponder];
-      }
+    [self.delegate tabSwitcherDismissTransitionDidEnd:self];
+    if (!GetFirstResponder()) {
+      // It is possible to already have a first responder (for example the
+      // omnibox). In that case, we don't want to mark BVC as first responder.
+      [self.bvcContainer.currentBVC becomeFirstResponder];
     }
     if (completion) {
       completion();
     }
-    [self.launchMaskView removeFromSuperview];
-    self.launchMaskView = nil;
     self.firstPresentation = NO;
   };
 
-  if (base::FeatureList::IsEnabled(kContainedBVC)) {
-    self.baseViewController.childViewControllerForStatusBarStyle =
-        self.bvcContainer.currentBVC;
+  self.baseViewController.childViewControllerForStatusBarStyle =
+      self.bvcContainer.currentBVC;
 
-    [self.adaptor.tabGridViewController contentWillDisappearAnimated:animated];
+  [self.baseViewController contentWillDisappearAnimated:animated];
 
-    self.transitionHandler = [[TabGridTransitionHandler alloc]
-        initWithLayoutProvider:self.baseViewController];
-    self.transitionHandler.animationDisabled = !animated;
-    [self.transitionHandler transitionFromTabGrid:self.baseViewController
-                                        toBrowser:self.bvcContainer
-                                   withCompletion:^{
-                                     extendedCompletion();
-                                   }];
-
-  } else {
-    [self.baseViewController presentViewController:self.bvcContainer
-                                          animated:animated
-                                        completion:extendedCompletion];
-  }
+  self.transitionHandler = [[TabGridTransitionHandler alloc]
+      initWithLayoutProvider:self.baseViewController];
+  self.transitionHandler.animationDisabled = !animated;
+  [self.transitionHandler transitionFromTabGrid:self.baseViewController
+                                      toBrowser:self.bvcContainer
+                                 withCompletion:^{
+                                   extendedCompletion();
+                                 }];
 }
 
 #pragma mark - TabPresentationDelegate
@@ -420,11 +356,11 @@
       // Defensively early return instead of continuing.
       return;
   }
-  // Trigger the transition through the TabSwitcher delegate. This will in turn
-  // call back into this coordinator via the ViewControllerSwapping protocol.
-  [self.tabSwitcher.delegate tabSwitcher:self.tabSwitcher
-                 shouldFinishWithBrowser:activeBrowser
-                            focusOmnibox:focusOmnibox];
+  // Trigger the transition through the delegate. This will in turn call back
+  // into this coordinator via the ViewControllerSwapping protocol.
+  [self.delegate tabSwitcher:self
+      shouldFinishWithBrowser:activeBrowser
+                 focusOmnibox:focusOmnibox];
 }
 
 #pragma mark - RecentTabsPresentationDelegate
@@ -448,23 +384,56 @@
 }
 
 - (void)showActiveRegularTabFromRecentTabs {
-  [self.tabSwitcher.delegate tabSwitcher:self.tabSwitcher
-                 shouldFinishWithBrowser:self.regularBrowser
-                            focusOmnibox:NO];
+  [self.delegate tabSwitcher:self
+      shouldFinishWithBrowser:self.regularBrowser
+                 focusOmnibox:NO];
 }
 
 #pragma mark - HistoryPresentationDelegate
 
 - (void)showActiveRegularTabFromHistory {
-  [self.tabSwitcher.delegate tabSwitcher:self.tabSwitcher
-                 shouldFinishWithBrowser:self.regularBrowser
-                            focusOmnibox:NO];
+  [self.delegate tabSwitcher:self
+      shouldFinishWithBrowser:self.regularBrowser
+                 focusOmnibox:NO];
 }
 
 - (void)showActiveIncognitoTabFromHistory {
-  [self.tabSwitcher.delegate tabSwitcher:self.tabSwitcher
-                 shouldFinishWithBrowser:self.incognitoBrowser
-                            focusOmnibox:NO];
+  [self.delegate tabSwitcher:self
+      shouldFinishWithBrowser:self.incognitoBrowser
+                 focusOmnibox:NO];
+}
+
+#pragma mark - TabSwitcher
+
+- (void)restoreInternalStateWithMainBrowser:(Browser*)mainBrowser
+                                 otrBrowser:(Browser*)otrBrowser
+                              activeBrowser:(Browser*)activeBrowser {
+  // The only action here is to signal to the tab grid which panel should be
+  // active.
+  if (activeBrowser == otrBrowser) {
+    self.baseViewController.activePage = TabGridPageIncognitoTabs;
+  } else {
+    self.baseViewController.activePage = TabGridPageRegularTabs;
+  }
+}
+
+- (void)dismissWithNewTabAnimationToBrowser:(Browser*)browser
+                          withUrlLoadParams:(const UrlLoadParams&)urlLoadParams
+                                    atIndex:(int)position {
+  int tabIndex = std::min(position, browser->GetWebStateList()->count());
+
+  TabInsertionBrowserAgent::FromBrowser(browser)->InsertWebState(
+      urlLoadParams.web_params, nil, false, tabIndex, false);
+
+  // Tell the delegate to display the tab.
+  [self.delegate tabSwitcher:self
+      shouldFinishWithBrowser:browser
+                 focusOmnibox:NO];
+}
+
+- (void)setOtrBrowser:(Browser*)browser {
+  DCHECK(self.incognitoTabsMediator);
+  self.incognitoTabsMediator.browser = browser;
 }
 
 @end
diff --git a/ios/chrome/browser/ui/tab_grid/tab_grid_coordinator_unittest.mm b/ios/chrome/browser/ui/tab_grid/tab_grid_coordinator_unittest.mm
index ca65276..447b754 100644
--- a/ios/chrome/browser/ui/tab_grid/tab_grid_coordinator_unittest.mm
+++ b/ios/chrome/browser/ui/tab_grid/tab_grid_coordinator_unittest.mm
@@ -59,7 +59,7 @@
     [coordinator_ start];
 
     delegate_ = [[TestTabSwitcherDelegate alloc] init];
-    coordinator_.tabSwitcher.delegate = delegate_;
+    coordinator_.delegate = delegate_;
 
     normal_tab_view_controller_ = [[UIViewController alloc] init];
     normal_tab_view_controller_.view.frame = CGRectMake(20, 20, 10, 10);
@@ -115,11 +115,11 @@
   EXPECT_EQ(normal_tab_view_controller_, coordinator_.activeViewController);
 
   // Now setting a TabSwitcher will make the switcher active.
-  [coordinator_ showTabSwitcher:coordinator_.tabSwitcher];
+  [coordinator_ showTabSwitcher:coordinator_];
   bool tab_switcher_active = base::test::ios::WaitUntilConditionOrTimeout(
       base::test::ios::kWaitForUIElementTimeout, ^bool {
-        return [coordinator_.tabSwitcher viewController] ==
-               coordinator_.activeViewController;
+        return
+            [coordinator_ viewController] == coordinator_.activeViewController;
       });
   EXPECT_TRUE(tab_switcher_active);
 }
@@ -127,20 +127,19 @@
 // Tests that it is possible to set a TabViewController after setting a
 // TabSwitcher.
 TEST_F(TabGridCoordinatorTest, TabViewControllerAfterTabSwitcher) {
-  [coordinator_ showTabSwitcher:coordinator_.tabSwitcher];
-  EXPECT_EQ([coordinator_.tabSwitcher viewController],
-            coordinator_.activeViewController);
+  [coordinator_ showTabSwitcher:coordinator_];
+  EXPECT_EQ([coordinator_ viewController], coordinator_.activeViewController);
 
   [coordinator_ showTabViewController:normal_tab_view_controller_
                            completion:nil];
   EXPECT_EQ(normal_tab_view_controller_, coordinator_.activeViewController);
 
   // Showing the TabSwitcher again will make it active.
-  [coordinator_ showTabSwitcher:coordinator_.tabSwitcher];
+  [coordinator_ showTabSwitcher:coordinator_];
   bool tab_switcher_active = base::test::ios::WaitUntilConditionOrTimeout(
       base::test::ios::kWaitForUIElementTimeout, ^bool {
-        return [coordinator_.tabSwitcher viewController] ==
-               coordinator_.activeViewController;
+        return
+            [coordinator_ viewController] == coordinator_.activeViewController;
       });
   EXPECT_TRUE(tab_switcher_active);
 }
@@ -158,13 +157,11 @@
 
 // Tests calling showTabSwitcher twice in a row with the same VC.
 TEST_F(TabGridCoordinatorTest, ShowTabSwitcherTwice) {
-  [coordinator_ showTabSwitcher:coordinator_.tabSwitcher];
-  EXPECT_EQ([coordinator_.tabSwitcher viewController],
-            coordinator_.activeViewController);
+  [coordinator_ showTabSwitcher:coordinator_];
+  EXPECT_EQ([coordinator_ viewController], coordinator_.activeViewController);
 
-  [coordinator_ showTabSwitcher:coordinator_.tabSwitcher];
-  EXPECT_EQ([coordinator_.tabSwitcher viewController],
-            coordinator_.activeViewController);
+  [coordinator_ showTabSwitcher:coordinator_];
+  EXPECT_EQ([coordinator_ viewController], coordinator_.activeViewController);
 }
 
 // Tests calling showTabViewController twice in a row with the same VC.
@@ -182,7 +179,7 @@
 // handlers are called properly after the new view controller is made active.
 TEST_F(TabGridCoordinatorTest, CompletionHandlers) {
   // Setup: show the switcher.
-  [coordinator_ showTabSwitcher:coordinator_.tabSwitcher];
+  [coordinator_ showTabSwitcher:coordinator_];
 
   // Tests that the completion handler is called when showing a tab view
   // controller. Tests that the delegate 'didEnd' method is also called.
diff --git a/ios/chrome/browser/ui/tab_grid/tab_grid_view_controller.mm b/ios/chrome/browser/ui/tab_grid/tab_grid_view_controller.mm
index 6c8a7e9..cc6af504 100644
--- a/ios/chrome/browser/ui/tab_grid/tab_grid_view_controller.mm
+++ b/ios/chrome/browser/ui/tab_grid/tab_grid_view_controller.mm
@@ -182,33 +182,12 @@
   }
 }
 
-- (void)viewWillAppear:(BOOL)animated {
-  if (!base::FeatureList::IsEnabled(kContainedBVC)) {
-    [self contentWillAppearAnimated:animated];
-  }
-  [super viewWillAppear:animated];
-}
-
-- (void)viewDidAppear:(BOOL)animated {
-  [super viewDidAppear:animated];
-  if (!base::FeatureList::IsEnabled(kContainedBVC)) {
-    [self contentDidAppear];
-  }
-}
-
 - (void)viewDidLayoutSubviews {
   [super viewDidLayoutSubviews];
   // Modify Incognito and Regular Tabs Insets
   [self setInsetForGridViews];
 }
 
-- (void)viewWillDisappear:(BOOL)animated {
-  if (!base::FeatureList::IsEnabled(kContainedBVC)) {
-    [self contentWillDisappearAnimated:animated];
-  }
-  [super viewWillDisappear:animated];
-}
-
 - (void)viewWillTransitionToSize:(CGSize)size
        withTransitionCoordinator:
            (id<UIViewControllerTransitionCoordinator>)coordinator {
@@ -376,11 +355,9 @@
   }
   [self broadcastIncognitoContentVisibility];
 
-  if (base::FeatureList::IsEnabled(kContainedBVC)) {
-    [self.incognitoTabsViewController contentWillAppearAnimated:animated];
-    [self.regularTabsViewController contentWillAppearAnimated:animated];
-    self.remoteTabsViewController.preventUpdates = NO;
-  }
+  [self.incognitoTabsViewController contentWillAppearAnimated:animated];
+  [self.regularTabsViewController contentWillAppearAnimated:animated];
+  self.remoteTabsViewController.preventUpdates = NO;
 }
 
 - (void)contentDidAppear {
@@ -404,11 +381,9 @@
   }
   self.viewVisible = NO;
 
-  if (base::FeatureList::IsEnabled(kContainedBVC)) {
-    [self.incognitoTabsViewController contentWillDisappear];
-    [self.regularTabsViewController contentWillDisappear];
-    self.remoteTabsViewController.preventUpdates = YES;
-  }
+  [self.incognitoTabsViewController contentWillDisappear];
+  [self.regularTabsViewController contentWillDisappear];
+  self.remoteTabsViewController.preventUpdates = YES;
 }
 
 #pragma mark - Public Properties
diff --git a/ios/chrome/browser/ui/tab_grid/transitions/BUILD.gn b/ios/chrome/browser/ui/tab_grid/transitions/BUILD.gn
index f92e86b..719c02b 100644
--- a/ios/chrome/browser/ui/tab_grid/transitions/BUILD.gn
+++ b/ios/chrome/browser/ui/tab_grid/transitions/BUILD.gn
@@ -12,12 +12,6 @@
     "grid_transition_animation_layout_providing.h",
     "grid_transition_layout.h",
     "grid_transition_layout.mm",
-    "legacy_grid_to_visible_tab_animator.h",
-    "legacy_grid_to_visible_tab_animator.mm",
-    "legacy_tab_to_grid_animator.h",
-    "legacy_tab_to_grid_animator.mm",
-    "reduced_motion_animator.h",
-    "reduced_motion_animator.mm",
     "tab_grid_transition_handler.h",
     "tab_grid_transition_handler.mm",
   ]
diff --git a/ios/chrome/browser/ui/tab_grid/transitions/legacy_grid_to_visible_tab_animator.h b/ios/chrome/browser/ui/tab_grid/transitions/legacy_grid_to_visible_tab_animator.h
deleted file mode 100644
index bcc2773..0000000
--- a/ios/chrome/browser/ui/tab_grid/transitions/legacy_grid_to_visible_tab_animator.h
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_UI_TAB_GRID_TRANSITIONS_LEGACY_GRID_TO_VISIBLE_TAB_ANIMATOR_H_
-#define IOS_CHROME_BROWSER_UI_TAB_GRID_TRANSITIONS_LEGACY_GRID_TO_VISIBLE_TAB_ANIMATOR_H_
-
-#import <UIKit/UIKit.h>
-
-@protocol GridTransitionAnimationLayoutProviding;
-
-// Animator object for transitioning from a collection view of square-ish items
-// (the "grid") into a fullscreen view controller (the "tab").
-@interface LegacyGridToVisibleTabAnimator
-    : NSObject <UIViewControllerAnimatedTransitioning>
-
-// Initialize an animator object with |animationLayoutProvider| to provide
-// layout information for the transition.
-- (instancetype)initWithAnimationLayoutProvider:
-    (id<GridTransitionAnimationLayoutProviding>)animationLayoutProvider;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_UI_TAB_GRID_TRANSITIONS_LEGACY_GRID_TO_VISIBLE_TAB_ANIMATOR_H_
diff --git a/ios/chrome/browser/ui/tab_grid/transitions/legacy_grid_to_visible_tab_animator.mm b/ios/chrome/browser/ui/tab_grid/transitions/legacy_grid_to_visible_tab_animator.mm
deleted file mode 100644
index 9ca1ab1..0000000
--- a/ios/chrome/browser/ui/tab_grid/transitions/legacy_grid_to_visible_tab_animator.mm
+++ /dev/null
@@ -1,171 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/chrome/browser/ui/tab_grid/transitions/legacy_grid_to_visible_tab_animator.h"
-
-#include "ios/chrome/browser/crash_report/crash_keys_helper.h"
-#import "ios/chrome/browser/ui/tab_grid/transitions/grid_transition_animation.h"
-#import "ios/chrome/browser/ui/tab_grid/transitions/grid_transition_animation_layout_providing.h"
-#import "ios/chrome/browser/ui/tab_grid/transitions/grid_transition_layout.h"
-#import "ios/chrome/browser/ui/util/layout_guide_names.h"
-#import "ios/chrome/browser/ui/util/named_guide.h"
-#import "ios/chrome/browser/ui/util/property_animator_group.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-@interface LegacyGridToVisibleTabAnimator ()
-@property(nonatomic, weak) id<GridTransitionAnimationLayoutProviding>
-    animationLayoutProvider;
-// Animation object for this transition.
-@property(nonatomic, strong) GridTransitionAnimation* animation;
-// Transition context passed into this object when the animation is started.
-@property(nonatomic, weak) id<UIViewControllerContextTransitioning>
-    transitionContext;
-@end
-
-@implementation LegacyGridToVisibleTabAnimator
-
-- (instancetype)initWithAnimationLayoutProvider:
-    (id<GridTransitionAnimationLayoutProviding>)animationLayoutProvider {
-  if ((self = [super init])) {
-    _animationLayoutProvider = animationLayoutProvider;
-  }
-  return self;
-}
-
-- (NSTimeInterval)transitionDuration:
-    (id<UIViewControllerContextTransitioning>)transitionContext {
-  return 0.5;
-}
-
-- (void)animateTransition:
-    (id<UIViewControllerContextTransitioning>)transitionContext {
-  // Keep a pointer to the transition context for use in animation delegate
-  // callbacks.
-  self.transitionContext = transitionContext;
-
-  // Get views and view controllers for this transition.
-  UIView* containerView = [transitionContext containerView];
-  UIViewController* presentedViewController = [transitionContext
-      viewControllerForKey:UITransitionContextToViewControllerKey];
-  UIView* presentedView =
-      [transitionContext viewForKey:UITransitionContextToViewKey];
-
-  // Add the presented view to the container. This isn't just for the
-  // transition; this is how the presented view controller's view is added to
-  // the view hierarchy.
-  [containerView addSubview:presentedView];
-  presentedView.frame =
-      [transitionContext finalFrameForViewController:presentedViewController];
-
-  // Get the layout of the grid for the transition.
-  GridTransitionLayout* layout =
-      [self.animationLayoutProvider transitionLayout];
-
-  // Ask the state provider for the views to use when inserting the animation.
-  UIView* animationContainer =
-      [self.animationLayoutProvider animationViewsContainer];
-
-  // Find the rect for the animating tab by getting the content area layout
-  // guide.
-  // Conceptually this transition is presenting a tab (a BVC). However,
-  // currently the BVC instances are themselves contanted within a BVCContainer
-  // view controller. This means that the |presentedView| is not the BVC's
-  // view; rather it's the view of the view controller that contains the BVC.
-  // Unfortunatley, the layout guide needed here is attached to the BVC's view,
-  // which is the first (and only) subview of the BVCContainerViewController's
-  // view.
-  // TODO(crbug.com/860234) Clean up this arrangement.
-  UIView* viewWithNamedGuides = presentedView.subviews[0];
-  CGRect finalRect = [NamedGuide guideWithName:kContentAreaGuide
-                                          view:viewWithNamedGuides]
-                         .layoutFrame;
-
-  [layout.activeItem populateWithSnapshotsFromView:viewWithNamedGuides
-                                        middleRect:finalRect];
-
-  layout.expandedRect =
-      [animationContainer convertRect:viewWithNamedGuides.frame
-                             fromView:presentedView];
-
-  NSTimeInterval duration = [self transitionDuration:transitionContext];
-  // Create the animation view and insert it.
-  self.animation = [[GridTransitionAnimation alloc]
-      initWithLayout:layout
-            duration:duration
-           direction:GridAnimationDirectionExpanding];
-
-  UIView* bottomViewForAnimations =
-      [self.animationLayoutProvider animationViewsContainerBottomView];
-  [animationContainer insertSubview:self.animation
-                       aboveSubview:bottomViewForAnimations];
-
-  // Reparent the active cell view so that it can animate above the presenting
-  // view while the rest of the animation is embedded inside it.
-  UIView* presentingView =
-      [transitionContext viewForKey:UITransitionContextFromViewKey];
-  [containerView insertSubview:self.animation.activeCell
-                  aboveSubview:presentingView];
-
-  // Make the presented view alpha-zero; this should happen after all snapshots
-  // are taken.
-  presentedView.alpha = 0.0;
-
-  [self.animation.animator addCompletion:^(UIViewAnimatingPosition position) {
-    BOOL finished = (position == UIViewAnimatingPositionEnd);
-    [self gridTransitionAnimationDidFinish:finished];
-  }];
-
-  // Run the main animation.
-  [self.animation.animator startAnimation];
-}
-
-- (void)gridTransitionAnimationDidFinish:(BOOL)finished {
-  // Clean up the animation. First the active cell, then the animation itself.
-  // These views will not be re-used, so there's no need to reparent the
-  // active cell view.
-  [self.animation.activeCell removeFromSuperview];
-  [self.animation removeFromSuperview];
-  // If the transition was cancelled, remove the presented view.
-  // If not, remove the grid view.
-  UIView* gridView =
-      [self.transitionContext viewForKey:UITransitionContextFromViewKey];
-  UIView* presentedView =
-      [self.transitionContext viewForKey:UITransitionContextToViewKey];
-  if (self.transitionContext.transitionWasCancelled) {
-    [presentedView removeFromSuperview];
-  } else {
-    // TODO(crbug.com/850507): Have the tab view animate itself in alongside
-    // this transition instead of just setting the alpha here.
-    presentedView.alpha = 1;
-    [gridView removeFromSuperview];
-  }
-
-  // TODO(crbug.com/959774): The logging below is to better understand a crash
-  // when |-completeTransition| is called. We expect the |toViewController| to
-  // be BVC. We are testing the assumption below that there should be no
-  // presentingViewController, presentedViewController, or parentViewController.
-  UIViewController* toViewController = [self.transitionContext
-      viewControllerForKey:UITransitionContextToViewControllerKey];
-  NSString* toViewControllerName = NSStringFromClass([toViewController class]);
-  NSString* presentingViewControllerName =
-      NSStringFromClass([toViewController.presentingViewController class]);
-  NSString* presentedViewControllerName =
-      NSStringFromClass([toViewController.presentedViewController class]);
-  NSString* parentViewControllerName =
-      NSStringFromClass([toViewController.parentViewController class]);
-  crash_keys::SetGridToVisibleTabAnimation(
-      toViewControllerName, presentingViewControllerName,
-      presentedViewControllerName, parentViewControllerName);
-
-  // Mark the transition as completed.
-  [self.transitionContext completeTransition:YES];
-
-  // Remove the crash log since the presentation completed without a crash.
-  crash_keys::RemoveGridToVisibleTabAnimation();
-}
-
-@end
diff --git a/ios/chrome/browser/ui/tab_grid/transitions/legacy_tab_to_grid_animator.h b/ios/chrome/browser/ui/tab_grid/transitions/legacy_tab_to_grid_animator.h
deleted file mode 100644
index 826dead2..0000000
--- a/ios/chrome/browser/ui/tab_grid/transitions/legacy_tab_to_grid_animator.h
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_UI_TAB_GRID_TRANSITIONS_LEGACY_TAB_TO_GRID_ANIMATOR_H_
-#define IOS_CHROME_BROWSER_UI_TAB_GRID_TRANSITIONS_LEGACY_TAB_TO_GRID_ANIMATOR_H_
-
-#import <UIKit/UIKit.h>
-
-@protocol GridTransitionAnimationLayoutProviding;
-
-// Animator object for transitioning from a fullscreen view controller (the
-// "tab") into a collection view of square-ish items (the "grid").
-@interface LegacyTabToGridAnimator
-    : NSObject <UIViewControllerAnimatedTransitioning>
-
-// Initialize an animator object with |animationLayoutProvider| to provide
-// layout information for the transition.
-- (instancetype)initWithAnimationLayoutProvider:
-    (id<GridTransitionAnimationLayoutProviding>)animationLayoutProvider;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_UI_TAB_GRID_TRANSITIONS_LEGACY_TAB_TO_GRID_ANIMATOR_H_
diff --git a/ios/chrome/browser/ui/tab_grid/transitions/legacy_tab_to_grid_animator.mm b/ios/chrome/browser/ui/tab_grid/transitions/legacy_tab_to_grid_animator.mm
deleted file mode 100644
index 145d686..0000000
--- a/ios/chrome/browser/ui/tab_grid/transitions/legacy_tab_to_grid_animator.mm
+++ /dev/null
@@ -1,158 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/chrome/browser/ui/tab_grid/transitions/legacy_tab_to_grid_animator.h"
-
-#include <ostream>
-
-#include "base/check_op.h"
-#import "ios/chrome/browser/ui/tab_grid/transitions/grid_transition_animation.h"
-#import "ios/chrome/browser/ui/tab_grid/transitions/grid_transition_animation_layout_providing.h"
-#import "ios/chrome/browser/ui/tab_grid/transitions/grid_transition_layout.h"
-#import "ios/chrome/browser/ui/util/layout_guide_names.h"
-#import "ios/chrome/browser/ui/util/named_guide.h"
-#import "ios/chrome/browser/ui/util/property_animator_group.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-@interface LegacyTabToGridAnimator ()
-@property(nonatomic, weak) id<GridTransitionAnimationLayoutProviding>
-    animationLayoutProvider;
-// Animation object for this transition.
-@property(nonatomic, strong) GridTransitionAnimation* animation;
-// Transition context passed into this object when the animation is started.
-@property(nonatomic, weak) id<UIViewControllerContextTransitioning>
-    transitionContext;
-@end
-
-@implementation LegacyTabToGridAnimator
-
-- (instancetype)initWithAnimationLayoutProvider:
-    (id<GridTransitionAnimationLayoutProviding>)animationLayoutProvider {
-  if ((self = [super init])) {
-    _animationLayoutProvider = animationLayoutProvider;
-  }
-  return self;
-}
-
-- (NSTimeInterval)transitionDuration:
-    (id<UIViewControllerContextTransitioning>)transitionContext {
-  return 0.3;
-}
-
-- (void)animateTransition:
-    (id<UIViewControllerContextTransitioning>)transitionContext {
-  // Keep a pointer to the transition context for use in animation delegate
-  // callbacks.
-  self.transitionContext = transitionContext;
-
-  // Get views and view controllers for this transition.
-  UIView* containerView = [transitionContext containerView];
-  UIViewController* gridViewController = [transitionContext
-      viewControllerForKey:UITransitionContextToViewControllerKey];
-  UIView* gridView =
-      [transitionContext viewForKey:UITransitionContextToViewKey];
-  UIView* dismissingView =
-      [transitionContext viewForKey:UITransitionContextFromViewKey];
-
-  // Find the rect for the animating tab by getting the content area layout
-  // guide.
-  // Add the grid view to the container. This isn't just for the transition;
-  // this is how the grid view controller's view is added to the view
-  // hierarchy.
-  [containerView insertSubview:gridView belowSubview:dismissingView];
-  gridView.frame =
-      [transitionContext finalFrameForViewController:gridViewController];
-  // Normally this view will layout before it's displayed, but in order to build
-  // the layout for the animation, |gridView|'s layout needs to be current, so
-  // force an update here.
-  [gridView layoutIfNeeded];
-
-  // Ask the state provider for the views to use when inserting the animation.
-  UIView* animationContainer =
-      [self.animationLayoutProvider animationViewsContainer];
-
-  // Get the layout of the grid for the transition.
-  GridTransitionLayout* layout =
-      [self.animationLayoutProvider transitionLayout];
-
-  // Get the initial rect for the snapshotted content of the active tab.
-  // Conceptually this transition is dismissing a tab (a BVC). However,
-  // currently the BVC instances are themselves contanted within a BVCContainer
-  // view controller. This means that the |dismissingView| is not the BVC's
-  // view; rather it's the view of the view controller that contains the BVC.
-  // Unfortunatley, the layout guide needed here is attached to the BVC's view,
-  // which is the first (and only) subview of the BVCContainerViewController's
-  // view in most cases. However, crbug.com/1053452 shows that we cannot just
-  // blindly use the first subview. The for-loop below ensures that a layout
-  // guide is obtained from the first subview that has it.
-  // TODO(crbug.com/860234) Clean up this arrangement.
-  UIView* viewWithNamedGuides = nil;
-  CGRect initialRect;
-  for (viewWithNamedGuides in dismissingView.subviews) {
-    NamedGuide* namedGuide = [NamedGuide guideWithName:kContentAreaGuide
-                                                  view:viewWithNamedGuides];
-    if (namedGuide) {
-      initialRect = namedGuide.layoutFrame;
-      break;
-    }
-  }
-  // Prefer to crash here at the root cause, rather than crashing later where
-  // the reason is more ambiguous.
-  CHECK_NE(nil, viewWithNamedGuides);
-  [layout.activeItem populateWithSnapshotsFromView:viewWithNamedGuides
-                                        middleRect:initialRect];
-
-  layout.expandedRect =
-      [animationContainer convertRect:viewWithNamedGuides.frame
-                             fromView:dismissingView];
-
-  NSTimeInterval duration = [self transitionDuration:transitionContext];
-  // Create the animation view and insert it.
-  self.animation = [[GridTransitionAnimation alloc]
-      initWithLayout:layout
-            duration:duration
-           direction:GridAnimationDirectionContracting];
-
-  UIView* bottomViewForAnimations =
-      [self.animationLayoutProvider animationViewsContainerBottomView];
-  [animationContainer insertSubview:self.animation
-                       aboveSubview:bottomViewForAnimations];
-
-  [self.animation.animator addCompletion:^(UIViewAnimatingPosition position) {
-    BOOL finished = (position == UIViewAnimatingPositionEnd);
-    [self gridTransitionAnimationDidFinish:finished];
-  }];
-
-  // TODO(crbug.com/850507): Have the tab view animate itself out alongside this
-  // transition instead of just zeroing the alpha here.
-  dismissingView.alpha = 0;
-
-  // Run the main animation.
-  [self.animation.animator startAnimation];
-}
-
-- (void)gridTransitionAnimationDidFinish:(BOOL)finished {
-  // Clean up the animation
-  [self.animation removeFromSuperview];
-  // If the transition was cancelled, restore the dismissing view and
-  // remove the grid view.
-  // If not, remove the dismissing view.
-  UIView* gridView =
-      [self.transitionContext viewForKey:UITransitionContextToViewKey];
-  UIView* dismissingView =
-      [self.transitionContext viewForKey:UITransitionContextFromViewKey];
-  if (self.transitionContext.transitionWasCancelled) {
-    dismissingView.alpha = 1.0;
-    [gridView removeFromSuperview];
-  } else {
-    [dismissingView removeFromSuperview];
-  }
-  // Mark the transition as completed.
-  [self.transitionContext completeTransition:YES];
-}
-
-@end
diff --git a/ios/chrome/browser/ui/tab_grid/transitions/reduced_motion_animator.h b/ios/chrome/browser/ui/tab_grid/transitions/reduced_motion_animator.h
deleted file mode 100644
index ab04454..0000000
--- a/ios/chrome/browser/ui/tab_grid/transitions/reduced_motion_animator.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_UI_TAB_GRID_TRANSITIONS_REDUCED_MOTION_ANIMATOR_H_
-#define IOS_CHROME_BROWSER_UI_TAB_GRID_TRANSITIONS_REDUCED_MOTION_ANIMATOR_H_
-
-#import <UIKit/UIKit.h>
-
-// Animator object used when a simpler transition is needed.
-@interface ReducedMotionAnimator
-    : NSObject<UIViewControllerAnimatedTransitioning>
-
-// YES if the animation presents a tab; NO if it dismisses it.
-@property(nonatomic, assign) BOOL presenting;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_UI_TAB_GRID_TRANSITIONS_REDUCED_MOTION_ANIMATOR_H_
diff --git a/ios/chrome/browser/ui/tab_grid/transitions/reduced_motion_animator.mm b/ios/chrome/browser/ui/tab_grid/transitions/reduced_motion_animator.mm
deleted file mode 100644
index 77e6dfba..0000000
--- a/ios/chrome/browser/ui/tab_grid/transitions/reduced_motion_animator.mm
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/chrome/browser/ui/tab_grid/transitions/reduced_motion_animator.h"
-
-#include "ios/chrome/browser/ui/util/ui_util.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-@implementation ReducedMotionAnimator
-
-@synthesize presenting = _presenting;
-
-- (NSTimeInterval)transitionDuration:
-    (id<UIViewControllerContextTransitioning>)transitionContext {
-  return 0.25;
-}
-
-- (void)animateTransition:
-    (id<UIViewControllerContextTransitioning>)transitionContext {
-  // Get views and view controllers for this transition.
-  UIView* containerView = [transitionContext containerView];
-
-  UIViewController* appearingViewController = [transitionContext
-      viewControllerForKey:UITransitionContextToViewControllerKey];
-  // The disappearing view, already in the view hierarchy.
-  UIView* disappearingView =
-      [transitionContext viewForKey:UITransitionContextFromViewKey];
-  // The appearing view, not yet in the view hierarchy.
-  UIView* appearingView =
-      [transitionContext viewForKey:UITransitionContextToViewKey];
-
-  // Add the tab view to the container view with the correct size.
-  appearingView.frame =
-      [transitionContext finalFrameForViewController:appearingViewController];
-  // If presenting, the appearing view does in front of the disappearing view.
-  // If dismissing, the disappearing view stays in front.
-  if (self.presenting) {
-    [containerView addSubview:appearingView];
-  } else {
-    [containerView insertSubview:appearingView belowSubview:disappearingView];
-  }
-
-  // The animation here creates a simple quick zoom effect -- the tab view
-  // fades in/out as it expands/contracts. The zoom is not large (80% to 100%)
-  // and is centered on the view's final center position, so it's not directly
-  // connected to any tab grid positions.
-  UIView* animatingView;
-  CGFloat finalAnimatingViewAlpha;
-  CGAffineTransform finalAnimatingViewTransform;
-  CGFloat finalAnimatingCornerRadius;
-
-  if (self.presenting) {
-    // If presenting, the appearing view (the tab view) animates in from 0%
-    // opacity, 75% scale transform, and a 13px corner radius
-    animatingView = appearingView;
-    finalAnimatingViewAlpha = animatingView.alpha;
-    animatingView.alpha = 0;
-    finalAnimatingViewTransform = animatingView.transform;
-    animatingView.transform =
-        CGAffineTransformScale(finalAnimatingViewTransform, 0.75, 0.75);
-    finalAnimatingCornerRadius = DeviceCornerRadius();
-    animatingView.layer.cornerRadius = 26.0;
-  } else {
-    // If dismissing, the disappearing view (the tab view) animates out
-    // to 0% opacity, 75% scale, and 13px corner radius.
-    animatingView = disappearingView;
-    finalAnimatingViewAlpha = 0;
-    finalAnimatingViewTransform =
-        CGAffineTransformScale(animatingView.transform, 0.75, 0.75);
-    animatingView.layer.cornerRadius = DeviceCornerRadius();
-    finalAnimatingCornerRadius = 26.0;
-  }
-
-  // Set clipsToBounds on the animating view so its corner radius will look
-  // right.
-  BOOL oldClipsToBounds = animatingView.clipsToBounds;
-  animatingView.clipsToBounds = YES;
-
-  // Animate the animating view to final properties, then clean up by removing
-  // the disappearing view.
-  [UIView animateWithDuration:[self transitionDuration:transitionContext]
-      delay:0.0
-      options:UIViewAnimationOptionCurveEaseOut
-      animations:^{
-        animatingView.alpha = finalAnimatingViewAlpha;
-        animatingView.transform = finalAnimatingViewTransform;
-        animatingView.layer.cornerRadius = finalAnimatingCornerRadius;
-      }
-      completion:^(BOOL finished) {
-        // Restore clipping state.
-        animatingView.clipsToBounds = oldClipsToBounds;
-        // If the transition was cancelled, remove the disappearing view.
-        // If not, remove the appearing view.
-        if (transitionContext.transitionWasCancelled) {
-          [appearingView removeFromSuperview];
-        } else {
-          [disappearingView removeFromSuperview];
-        }
-        // Mark the transition as completed.
-        [transitionContext completeTransition:YES];
-      }];
-}
-
-@end
diff --git a/ios/chrome/browser/ui/ui_feature_flags.cc b/ios/chrome/browser/ui/ui_feature_flags.cc
index 4163a2f1..e3f80c0e 100644
--- a/ios/chrome/browser/ui/ui_feature_flags.cc
+++ b/ios/chrome/browser/ui/ui_feature_flags.cc
@@ -28,9 +28,6 @@
 const base::Feature kForceUnstackedTabstrip{"ForceUnstackedTabstrip",
                                             base::FEATURE_DISABLED_BY_DEFAULT};
 
-const base::Feature kContainedBVC{"ContainedBVC",
-                                  base::FEATURE_ENABLED_BY_DEFAULT};
-
 const base::Feature kTestFeature{"TestFeature",
                                  base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/ios/chrome/browser/ui/ui_feature_flags.h b/ios/chrome/browser/ui/ui_feature_flags.h
index e7f7f25..68f801e 100644
--- a/ios/chrome/browser/ui/ui_feature_flags.h
+++ b/ios/chrome/browser/ui/ui_feature_flags.h
@@ -29,10 +29,6 @@
 // Feature flag to always force an unstacked tabstrip.
 extern const base::Feature kForceUnstackedTabstrip;
 
-// Feature flag to have the Browser contained by the TabGrid instead of being
-// presented.
-extern const base::Feature kContainedBVC;
-
 // Test-only: Feature flag used to verify that EG2 can trigger flags. Must be
 // always disabled by default, because it is used to verify that enabling
 // features in tests works.
diff --git a/ios/public/provider/chrome/browser/signin/chrome_identity_service.h b/ios/public/provider/chrome/browser/signin/chrome_identity_service.h
index 905b5485..de3f185 100644
--- a/ios/public/provider/chrome/browser/signin/chrome_identity_service.h
+++ b/ios/public/provider/chrome/browser/signin/chrome_identity_service.h
@@ -154,30 +154,40 @@
   // Returns YES if |identity| is valid and if the service has it in its list of
   // identitites.
   virtual bool IsValidIdentity(ChromeIdentity* identity) const;
+  virtual bool IsValidIdentityTemporary(ChromeIdentity* identity) const;
 
   // Returns the chrome identity having the email equal to |email| or |nil| if
   // no matching identity is found.
   virtual ChromeIdentity* GetIdentityWithEmail(const std::string& email) const;
+  virtual ChromeIdentity* GetIdentityWithEmailTemporary(
+      const std::string& email) const;
 
   // Returns the chrome identity having the gaia ID equal to |gaia_id| or |nil|
   // if no matching identity is found.
   virtual ChromeIdentity* GetIdentityWithGaiaID(
       const std::string& gaia_id) const;
+  virtual ChromeIdentity* GetIdentityWithGaiaIDTemporary(
+      const std::string& gaia_id) const;
 
   // Returns the canonicalized emails for all identities.
   virtual std::vector<std::string> GetCanonicalizeEmailsForAllIdentities()
       const;
+  virtual std::vector<std::string>
+  GetCanonicalizeEmailsForAllIdentitiesTemporary() const;
 
   // Returns true if there is at least one identity.
   virtual bool HasIdentities() const;
+  virtual bool HasIdentitiesTemporary() const;
 
   // Returns all ChromeIdentity objects in an array.
   virtual NSArray* GetAllIdentities() const;
+  virtual NSArray* GetAllIdentitiesTemporary() const;
 
   // Returns all ChromeIdentity objects sorted by the ordering used in the
   // account manager, which is typically based on the keychain ordering of
   // accounts.
   virtual NSArray* GetAllIdentitiesSortedForDisplay() const;
+  virtual NSArray* GetAllIdentitiesSortedForDisplayTemporary() const;
 
   // Forgets the given identity on the device. This method logs the user out.
   // It is asynchronous because it needs to contact the server to revoke the
diff --git a/ios/public/provider/chrome/browser/signin/chrome_identity_service.mm b/ios/public/provider/chrome/browser/signin/chrome_identity_service.mm
index f0ffb74c..cdcb2b8 100644
--- a/ios/public/provider/chrome/browser/signin/chrome_identity_service.mm
+++ b/ios/public/provider/chrome/browser/signin/chrome_identity_service.mm
@@ -62,33 +62,66 @@
 }
 
 bool ChromeIdentityService::IsValidIdentity(ChromeIdentity* identity) const {
+  return IsValidIdentityTemporary(identity);
+}
+
+bool ChromeIdentityService::IsValidIdentityTemporary(
+    ChromeIdentity* identity) const {
   return false;
 }
 
 ChromeIdentity* ChromeIdentityService::GetIdentityWithEmail(
     const std::string& email) const {
+  return GetIdentityWithEmailTemporary(email);
+}
+
+ChromeIdentity* ChromeIdentityService::GetIdentityWithEmailTemporary(
+    const std::string& email) const {
   return nil;
 }
 
 ChromeIdentity* ChromeIdentityService::GetIdentityWithGaiaID(
     const std::string& gaia_id) const {
+  return GetIdentityWithGaiaIDTemporary(gaia_id);
+}
+
+ChromeIdentity* ChromeIdentityService::GetIdentityWithGaiaIDTemporary(
+    const std::string& gaia_id) const {
   return nil;
 }
 
 std::vector<std::string>
 ChromeIdentityService::GetCanonicalizeEmailsForAllIdentities() const {
+  return GetCanonicalizeEmailsForAllIdentitiesTemporary();
+}
+
+std::vector<std::string>
+ChromeIdentityService::GetCanonicalizeEmailsForAllIdentitiesTemporary() const {
   return std::vector<std::string>();
 }
 
 bool ChromeIdentityService::HasIdentities() const {
+  return HasIdentitiesTemporary();
+}
+
+bool ChromeIdentityService::HasIdentitiesTemporary() const {
   return false;
 }
 
 NSArray* ChromeIdentityService::GetAllIdentities() const {
+  return GetAllIdentitiesTemporary();
+}
+
+NSArray* ChromeIdentityService::GetAllIdentitiesTemporary() const {
   return nil;
 }
 
 NSArray* ChromeIdentityService::GetAllIdentitiesSortedForDisplay() const {
+  return GetAllIdentitiesSortedForDisplayTemporary();
+}
+
+NSArray* ChromeIdentityService::GetAllIdentitiesSortedForDisplayTemporary()
+    const {
   return nil;
 }
 
diff --git a/media/audio/audio_input_unittest.cc b/media/audio/audio_input_unittest.cc
index 5a38c8d6..f603729 100644
--- a/media/audio/audio_input_unittest.cc
+++ b/media/audio/audio_input_unittest.cc
@@ -75,12 +75,6 @@
         audio_manager_(AudioManager::CreateForTesting(
             std::make_unique<TestAudioThread>())),
         audio_input_stream_(nullptr) {
-#if defined(OS_LINUX)
-    // Due to problems with PulseAudio failing to start, use a fake audio
-    // stream. https://crbug.com/1047655#c70
-    base::CommandLine::ForCurrentProcess()->AppendSwitch(
-        switches::kDisableAudioOutput);
-#endif
     base::RunLoop().RunUntilIdle();
   }
 
diff --git a/media/audio/audio_manager_unittest.cc b/media/audio/audio_manager_unittest.cc
index 533de6c..0624621 100644
--- a/media/audio/audio_manager_unittest.cc
+++ b/media/audio/audio_manager_unittest.cc
@@ -335,12 +335,6 @@
 
  protected:
   AudioManagerTest() {
-#if defined(OS_LINUX)
-    // Due to problems with PulseAudio failing to start, use a fake audio
-    // stream. https://crbug.com/1047655#c70
-    base::CommandLine::ForCurrentProcess()->AppendSwitch(
-        switches::kDisableAudioOutput);
-#endif
     CreateAudioManagerForTesting();
   }
   ~AudioManagerTest() override { audio_manager_->Shutdown(); }
diff --git a/media/base/android/java/src/org/chromium/media/MediaCodecUtil.java b/media/base/android/java/src/org/chromium/media/MediaCodecUtil.java
index c67f6165..480c0ff 100644
--- a/media/base/android/java/src/org/chromium/media/MediaCodecUtil.java
+++ b/media/base/android/java/src/org/chromium/media/MediaCodecUtil.java
@@ -553,7 +553,7 @@
             case HWEncoder.MediatekH264:
                 return BitrateAdjuster.Type.FRAMERATE_ADJUSTMENT;
         }
-        return -1;
+        throw new IllegalArgumentException("Invalid HWEncoder decoder parameter.");
     }
 
     /**
diff --git a/media/cast/BUILD.gn b/media/cast/BUILD.gn
index 7df70b3..28aa4b7 100644
--- a/media/cast/BUILD.gn
+++ b/media/cast/BUILD.gn
@@ -408,6 +408,7 @@
       "//build/win:default_exe_manifest",
       "//media:test_support",
       "//net",
+      "//ui/base:features",
       "//ui/gfx/geometry",
     ]
 
diff --git a/media/cast/DEPS b/media/cast/DEPS
index d1cb7e6..19af2a19 100644
--- a/media/cast/DEPS
+++ b/media/cast/DEPS
@@ -4,5 +4,6 @@
   "+net",
   "+third_party/libyuv",
   "+third_party/zlib",
+  "+ui/base",
   "+ui/gfx",
 ]
diff --git a/media/cast/test/receiver.cc b/media/cast/test/receiver.cc
index d357fb0..7254c096 100644
--- a/media/cast/test/receiver.cc
+++ b/media/cast/test/receiver.cc
@@ -49,6 +49,7 @@
 #include "media/cast/test/utility/input_builder.h"
 #include "media/cast/test/utility/standalone_cast_environment.h"
 #include "net/base/ip_address.h"
+#include "ui/base/ui_base_features.h"
 
 #if defined(USE_X11)
 #include "media/cast/test/linux_output_window.h"
@@ -424,7 +425,8 @@
     if (!video_playout_queue_.empty()) {
       const scoped_refptr<VideoFrame> video_frame = PopOneVideoFrame(false);
 #if defined(USE_X11)
-      render_.RenderFrame(*video_frame);
+      if (!features::IsUsingOzonePlatform())
+        render_.RenderFrame(*video_frame);
 #endif  // defined(USE_X11)
     }
     ScheduleVideoPlayout();
@@ -595,7 +597,8 @@
   int window_width = 0;
   int window_height = 0;
 #if defined(USE_X11)
-  media::cast::GetWindowSize(&window_width, &window_height);
+  if (!features::IsUsingOzonePlatform())
+    media::cast::GetWindowSize(&window_width, &window_height);
 #endif  // defined(USE_X11)
   media::cast::NaivePlayer player(cast_environment,
                                   local_end_point,
diff --git a/media/gpu/test/video_test_environment.cc b/media/gpu/test/video_test_environment.cc
index 18733d7..1feb8cd5 100644
--- a/media/gpu/test/video_test_environment.cc
+++ b/media/gpu/test/video_test_environment.cc
@@ -17,6 +17,7 @@
 #endif
 
 #if defined(USE_OZONE)
+#include "ui/base/ui_base_features.h"
 #include "ui/ozone/public/ozone_platform.h"
 #endif
 
@@ -58,12 +59,14 @@
 #if defined(USE_OZONE)
   // Initialize Ozone. This is necessary to gain access to the GPU for hardware
   // video decode acceleration.
-  LOG(WARNING) << "Initializing Ozone Platform...\n"
-                  "If this hangs indefinitely please call 'stop ui' first!";
-  ui::OzonePlatform::InitParams params;
-  params.single_process = true;
-  ui::OzonePlatform::InitializeForUI(params);
-  ui::OzonePlatform::InitializeForGPU(params);
+  if (features::IsUsingOzonePlatform()) {
+    LOG(WARNING) << "Initializing Ozone Platform...\n"
+                    "If this hangs indefinitely please call 'stop ui' first!";
+    ui::OzonePlatform::InitParams params;
+    params.single_process = true;
+    ui::OzonePlatform::InitializeForUI(params);
+    ui::OzonePlatform::InitializeForGPU(params);
+  }
 #endif
 }
 
diff --git a/media/gpu/vaapi/BUILD.gn b/media/gpu/vaapi/BUILD.gn
index 1f3a775..5ec2c41 100644
--- a/media/gpu/vaapi/BUILD.gn
+++ b/media/gpu/vaapi/BUILD.gn
@@ -166,6 +166,7 @@
   deps = [
     ":libva_stubs",
     "//third_party/libyuv",
+    "//ui/base:features",
     "//ui/gfx:memory_buffer",
     "//ui/gl",
   ]
diff --git a/media/gpu/vaapi/vaapi_picture_factory.cc b/media/gpu/vaapi/vaapi_picture_factory.cc
index ea01697..46d4ceee 100644
--- a/media/gpu/vaapi/vaapi_picture_factory.cc
+++ b/media/gpu/vaapi/vaapi_picture_factory.cc
@@ -6,6 +6,7 @@
 
 #include "media/gpu/vaapi/vaapi_wrapper.h"
 #include "media/video/picture.h"
+#include "ui/base/ui_base_features.h"
 #include "ui/gl/gl_bindings.h"
 
 #if defined(USE_X11)
@@ -20,23 +21,18 @@
 
 namespace media {
 
-namespace {
-
-const struct {
-  gl::GLImplementation gl_impl;
-  VaapiPictureFactory::VaapiImplementation va_impl;
-} kVaapiImplementationPairs[] = {{gl::kGLImplementationEGLGLES2,
-                                  VaapiPictureFactory::kVaapiImplementationDrm}
+VaapiPictureFactory::VaapiPictureFactory() {
+  vaapi_impl_pairs_.insert(
+      std::make_pair(gl::kGLImplementationEGLGLES2,
+                     VaapiPictureFactory::kVaapiImplementationDrm));
 #if defined(USE_X11)
-                                 ,
-                                 {gl::kGLImplementationDesktopGL,
-                                  VaapiPictureFactory::kVaapiImplementationX11}
-#endif  // USE_X11
-};
-
-}  // namespace
-
-VaapiPictureFactory::VaapiPictureFactory() = default;
+  if (!features::IsUsingOzonePlatform()) {
+    vaapi_impl_pairs_.insert(
+        std::make_pair(gl::kGLImplementationDesktopGL,
+                       VaapiPictureFactory::kVaapiImplementationX11));
+  }
+#endif
+}
 
 VaapiPictureFactory::~VaapiPictureFactory() = default;
 
@@ -60,56 +56,23 @@
           ? picture_buffer.service_texture_ids()[0]
           : 0;
 
-  std::unique_ptr<VaapiPicture> picture;
-
   // Select DRM(egl) / TFP(glx) at runtime with --use-gl=egl / --use-gl=desktop
-  switch (GetVaapiImplementation(gl::GetGLImplementation())) {
+
 #if defined(USE_OZONE)
-    // We can be called without GL initialized, which is valid if we use Ozone.
-    case kVaapiImplementationNone:
-      FALLTHROUGH;
-    case kVaapiImplementationDrm:
-      picture.reset(new VaapiPictureNativePixmapOzone(
-          std::move(vaapi_wrapper), make_context_current_cb, bind_image_cb,
-          picture_buffer.id(), picture_buffer.size(), visible_size,
-          service_texture_id, client_texture_id,
-          picture_buffer.texture_target()));
-      break;
-#elif defined(USE_EGL)
-    case kVaapiImplementationDrm:
-      picture.reset(new VaapiPictureNativePixmapEgl(
-          std::move(vaapi_wrapper), make_context_current_cb, bind_image_cb,
-          picture_buffer.id(), picture_buffer.size(), visible_size,
-          service_texture_id, client_texture_id,
-          picture_buffer.texture_target()));
-      break;
+  if (features::IsUsingOzonePlatform())
+    return CreateVaapiPictureNativeForOzone(
+        vaapi_wrapper, make_context_current_cb, bind_image_cb, picture_buffer,
+        visible_size, client_texture_id, service_texture_id);
 #endif
-
-#if defined(USE_X11)
-    case kVaapiImplementationX11:
-      picture.reset(new VaapiTFPPicture(
-          std::move(vaapi_wrapper), make_context_current_cb, bind_image_cb,
-          picture_buffer.id(), picture_buffer.size(), visible_size,
-          service_texture_id, client_texture_id,
-          picture_buffer.texture_target()));
-      break;
-#endif  // USE_X11
-
-    default:
-      NOTREACHED();
-      return nullptr;
-  }
-
-  return picture;
+  return CreateVaapiPictureNative(vaapi_wrapper, make_context_current_cb,
+                                  bind_image_cb, picture_buffer, visible_size,
+                                  client_texture_id, service_texture_id);
 }
 
 VaapiPictureFactory::VaapiImplementation
 VaapiPictureFactory::GetVaapiImplementation(gl::GLImplementation gl_impl) {
-  for (const auto& implementation_pair : kVaapiImplementationPairs) {
-    if (gl_impl == implementation_pair.gl_impl)
-      return implementation_pair.va_impl;
-  }
-
+  if (base::Contains(vaapi_impl_pairs_, gl_impl))
+    return vaapi_impl_pairs_[gl_impl];
   return kVaapiImplementationNone;
 }
 
@@ -129,4 +92,73 @@
 #endif
 }
 
+#if defined(USE_OZONE)
+std::unique_ptr<VaapiPicture>
+VaapiPictureFactory::CreateVaapiPictureNativeForOzone(
+    scoped_refptr<VaapiWrapper> vaapi_wrapper,
+    const MakeGLContextCurrentCallback& make_context_current_cb,
+    const BindGLImageCallback& bind_image_cb,
+    const PictureBuffer& picture_buffer,
+    const gfx::Size& visible_size,
+    uint32_t client_texture_id,
+    uint32_t service_texture_id) {
+  DCHECK(features::IsUsingOzonePlatform());
+  switch (GetVaapiImplementation(gl::GetGLImplementation())) {
+    // We can be called without GL initialized, which is valid if we use Ozone.
+    case kVaapiImplementationNone:
+      FALLTHROUGH;
+    case kVaapiImplementationDrm:
+      return std::make_unique<VaapiPictureNativePixmapOzone>(
+          std::move(vaapi_wrapper), make_context_current_cb, bind_image_cb,
+          picture_buffer.id(), picture_buffer.size(), visible_size,
+          service_texture_id, client_texture_id,
+          picture_buffer.texture_target());
+      break;
+
+    default:
+      NOTREACHED();
+      return nullptr;
+  }
+
+  return nullptr;
+}
+#endif  // USE_OZONE
+
+std::unique_ptr<VaapiPicture> VaapiPictureFactory::CreateVaapiPictureNative(
+    scoped_refptr<VaapiWrapper> vaapi_wrapper,
+    const MakeGLContextCurrentCallback& make_context_current_cb,
+    const BindGLImageCallback& bind_image_cb,
+    const PictureBuffer& picture_buffer,
+    const gfx::Size& visible_size,
+    uint32_t client_texture_id,
+    uint32_t service_texture_id) {
+  switch (GetVaapiImplementation(gl::GetGLImplementation())) {
+#if defined(USE_EGL)
+    case kVaapiImplementationDrm:
+      return std::make_unique<VaapiPictureNativePixmapEgl>(
+          std::move(vaapi_wrapper), make_context_current_cb, bind_image_cb,
+          picture_buffer.id(), picture_buffer.size(), visible_size,
+          service_texture_id, client_texture_id,
+          picture_buffer.texture_target());
+#endif  // USE_EGL
+
+#if defined(USE_X11)
+    case kVaapiImplementationX11:
+      DCHECK(!features::IsUsingOzonePlatform());
+      return std::make_unique<VaapiTFPPicture>(
+          std::move(vaapi_wrapper), make_context_current_cb, bind_image_cb,
+          picture_buffer.id(), picture_buffer.size(), visible_size,
+          service_texture_id, client_texture_id,
+          picture_buffer.texture_target());
+      break;
+#endif  // USE_X11
+
+    default:
+      NOTREACHED();
+      return nullptr;
+  }
+
+  return nullptr;
+}
+
 }  // namespace media
diff --git a/media/gpu/vaapi/vaapi_picture_factory.h b/media/gpu/vaapi/vaapi_picture_factory.h
index 09edc66..08372fc 100644
--- a/media/gpu/vaapi/vaapi_picture_factory.h
+++ b/media/gpu/vaapi/vaapi_picture_factory.h
@@ -51,6 +51,29 @@
   // the format decoded frames in VASurfaces are converted into.
   gfx::BufferFormat GetBufferFormat();
 
+#if defined(USE_OZONE)
+  std::unique_ptr<VaapiPicture> CreateVaapiPictureNativeForOzone(
+      scoped_refptr<VaapiWrapper> vaapi_wrapper,
+      const MakeGLContextCurrentCallback& make_context_current_cb,
+      const BindGLImageCallback& bind_image_cb,
+      const PictureBuffer& picture_buffer,
+      const gfx::Size& visible_size,
+      uint32_t client_texture_id,
+      uint32_t service_texture_id);
+#endif
+
+  std::unique_ptr<VaapiPicture> CreateVaapiPictureNative(
+      scoped_refptr<VaapiWrapper> vaapi_wrapper,
+      const MakeGLContextCurrentCallback& make_context_current_cb,
+      const BindGLImageCallback& bind_image_cb,
+      const PictureBuffer& picture_buffer,
+      const gfx::Size& visible_size,
+      uint32_t client_texture_id,
+      uint32_t service_texture_id);
+
+  std::map<gl::GLImplementation, VaapiPictureFactory::VaapiImplementation>
+      vaapi_impl_pairs_;
+
  private:
   DISALLOW_COPY_AND_ASSIGN(VaapiPictureFactory);
 };
diff --git a/media/gpu/vaapi/vaapi_picture_tfp.cc b/media/gpu/vaapi/vaapi_picture_tfp.cc
index b42620d..37b821c 100644
--- a/media/gpu/vaapi/vaapi_picture_tfp.cc
+++ b/media/gpu/vaapi/vaapi_picture_tfp.cc
@@ -6,6 +6,7 @@
 
 #include "media/gpu/vaapi/va_surface.h"
 #include "media/gpu/vaapi/vaapi_wrapper.h"
+#include "ui/base/ui_base_features.h"
 #include "ui/gfx/x/x11_types.h"
 #include "ui/gl/gl_bindings.h"
 #include "ui/gl/gl_image_glx.h"
@@ -35,6 +36,7 @@
       x_display_(gfx::GetXDisplay()),
       x_pixmap_(0) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(!features::IsUsingOzonePlatform());
   DCHECK(texture_id);
   DCHECK(client_texture_id);
 }
diff --git a/media/gpu/vaapi/vaapi_video_decode_accelerator_unittest.cc b/media/gpu/vaapi/vaapi_video_decode_accelerator_unittest.cc
index 12daaefd..1eb262e 100644
--- a/media/gpu/vaapi/vaapi_video_decode_accelerator_unittest.cc
+++ b/media/gpu/vaapi/vaapi_video_decode_accelerator_unittest.cc
@@ -15,6 +15,7 @@
 #include "media/gpu/vaapi/vaapi_wrapper.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/ui_base_features.h"
 
 using base::test::RunClosure;
 using ::testing::_;
@@ -405,9 +406,11 @@
                 gl::kGLImplementationEGLGLES2));
 
 #if defined(USE_X11)
-  EXPECT_EQ(VaapiPictureFactory::kVaapiImplementationX11,
-            mock_vaapi_picture_factory_->GetVaapiImplementation(
-                gl::kGLImplementationDesktopGL));
+  if (!features::IsUsingOzonePlatform()) {
+    EXPECT_EQ(VaapiPictureFactory::kVaapiImplementationX11,
+              mock_vaapi_picture_factory_->GetVaapiImplementation(
+                  gl::kGLImplementationDesktopGL));
+  }
 #endif
 }
 
diff --git a/media/gpu/vaapi/vaapi_wrapper.cc b/media/gpu/vaapi/vaapi_wrapper.cc
index 040296d..34257d7f 100644
--- a/media/gpu/vaapi/vaapi_wrapper.cc
+++ b/media/gpu/vaapi/vaapi_wrapper.cc
@@ -37,6 +37,7 @@
 #include "base/system/sys_info.h"
 #include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
+#include "ui/base/ui_base_features.h"
 
 #include "media/base/media_switches.h"
 #include "media/base/video_frame.h"
@@ -380,13 +381,13 @@
 bool VADisplayState::Initialize() {
   base::AutoLock auto_lock(va_lock_);
 
-  if (!IsVaInitialized() ||
+  bool libraries_initialized = IsVaInitialized() && IsVa_drmInitialized();
 #if defined(USE_X11)
-      !IsVa_x11Initialized() ||
+  if (!features::IsUsingOzonePlatform())
+    libraries_initialized = libraries_initialized && IsVa_x11Initialized();
 #endif
-      !IsVa_drmInitialized()) {
+  if (!libraries_initialized)
     return false;
-  }
 
   // Manual refcounting to ensure the rest of the method is called only once.
   if (refcount_++ > 0)
@@ -409,7 +410,8 @@
       break;
     case gl::kGLImplementationDesktopGL:
 #if defined(USE_X11)
-      va_display_ = vaGetDisplay(gfx::GetXDisplay());
+      if (!features::IsUsingOzonePlatform())
+        va_display_ = vaGetDisplay(gfx::GetXDisplay());
 #else
       LOG(WARNING) << "VAAPI video acceleration not available without "
                       "DesktopGL (GLX).";
@@ -418,9 +420,11 @@
     // Cannot infer platform from GL, try all available displays
     case gl::kGLImplementationNone:
 #if defined(USE_X11)
-      va_display_ = vaGetDisplay(gfx::GetXDisplay());
-      if (vaDisplayIsValid(va_display_))
-        break;
+      if (!features::IsUsingOzonePlatform()) {
+        va_display_ = vaGetDisplay(gfx::GetXDisplay());
+        if (vaDisplayIsValid(va_display_))
+          break;
+      }
 #endif  // USE_X11
       va_display_ = vaGetDisplayDRM(drm_fd_.get());
       break;
@@ -1770,6 +1774,7 @@
 bool VaapiWrapper::PutSurfaceIntoPixmap(VASurfaceID va_surface_id,
                                         Pixmap x_pixmap,
                                         gfx::Size dest_size) {
+  DCHECK(!features::IsUsingOzonePlatform());
   base::AutoLock auto_lock(*va_lock_);
 
   VAStatus va_res = vaSyncSurface(va_display_, va_surface_id);
@@ -2143,7 +2148,8 @@
   paths[kModuleVa].push_back(std::string("libva.so.") + va_suffix);
   paths[kModuleVa_drm].push_back(std::string("libva-drm.so.") + va_suffix);
 #if defined(USE_X11)
-  paths[kModuleVa_x11].push_back(std::string("libva-x11.so.") + va_suffix);
+  if (!features::IsUsingOzonePlatform())
+    paths[kModuleVa_x11].push_back(std::string("libva-x11.so.") + va_suffix);
 #endif
 
   // InitializeStubs dlopen() VA-API libraries
diff --git a/net/cookies/README.md b/net/cookies/README.md
index 3f6384d..c4baf8e 100644
--- a/net/cookies/README.md
+++ b/net/cookies/README.md
@@ -386,14 +386,14 @@
     The `CookieAccessObserver`s forward the notifications to `WebContents`,
     which then notifies its `WebContentsObserver`s. One such
     `WebContentsObserver` that cares about this information is
-    `TabSpecificContentSettings`, which displays information about allowed and
+    `PageSpecificContentSettings`, which displays information about allowed and
     blocked cookies in UI surfaces (see next item).
 
 * [`CookiesTreeModel`](/chrome/browser/browsing_data/cookies_tree_model.h)
 
     Stores cookie information for use in settings UI (the Page Info Bubble and
     various `chrome://settings` pages). Populated with info from
-    `TabSpecificContentSettings`.
+    `PageSpecificContentSettings`.
 
 * [`CookieJar`](/third_party/blink/renderer/core/loader/cookie_jar.h)
 
diff --git a/sandbox/linux/seccomp-bpf-helpers/baseline_policy_android.cc b/sandbox/linux/seccomp-bpf-helpers/baseline_policy_android.cc
index ab194e2b..222158c 100644
--- a/sandbox/linux/seccomp-bpf-helpers/baseline_policy_android.cc
+++ b/sandbox/linux/seccomp-bpf-helpers/baseline_policy_android.cc
@@ -112,6 +112,10 @@
     case __NR_openat:
     case __NR_pwrite64:
     case __NR_rt_sigtimedwait:
+    // sched_setaffinity() is required for an experiment to schedule all
+    // Chromium threads onto LITTLE cores (crbug.com/1111789). Should be removed
+    // or reconsidered once the experiment is complete.
+    case __NR_sched_setaffinity:
     case __NR_sched_getparam:
     case __NR_sched_getscheduler:
     case __NR_sched_setscheduler:
diff --git a/services/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderAdapter.java b/services/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderAdapter.java
index c3651e74..ebdfcc7 100644
--- a/services/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderAdapter.java
+++ b/services/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderAdapter.java
@@ -6,8 +6,6 @@
 
 import android.location.Location;
 
-import androidx.annotation.VisibleForTesting;
-
 import org.chromium.base.Log;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.annotations.CalledByNative;
@@ -23,7 +21,6 @@
  * content/browser/geolocation/location_api_adapter_android.h.
  * Based on android.webkit.GeolocationService.java
  */
-@VisibleForTesting
 public class LocationProviderAdapter {
     private static final String TAG = "LocationProvider";
 
diff --git a/services/network/public/cpp/content_security_policy/content_security_policy.cc b/services/network/public/cpp/content_security_policy/content_security_policy.cc
index a879e5d..914a787 100644
--- a/services/network/public/cpp/content_security_policy/content_security_policy.cc
+++ b/services/network/public/cpp/content_security_policy/content_security_policy.cc
@@ -949,6 +949,9 @@
     const mojom::ContentSecurityPolicy* context,
     std::string& error_message) {
   DCHECK(policy.size() == 1);
+  if (!policy[0])
+    return false;
+
   if (!policy[0]->parsing_errors.empty()) {
     error_message =
         "Parsing the csp attribute into a Content-Security-Policy returned one "
diff --git a/services/network/public/cpp/content_security_policy/csp_source.cc b/services/network/public/cpp/content_security_policy/csp_source.cc
index 1bb0c8d..983b8a5 100644
--- a/services/network/public/cpp/content_security_policy/csp_source.cc
+++ b/services/network/public/cpp/content_security_policy/csp_source.cc
@@ -48,6 +48,17 @@
 };
 enum class SchemeMatchingResult { NotMatching, MatchingUpgrade, MatchingExact };
 
+SchemeMatchingResult MatchScheme(const std::string& scheme_a,
+                                 const std::string& scheme_b) {
+  if (scheme_a == scheme_b)
+    return SchemeMatchingResult::MatchingExact;
+  if ((scheme_a == url::kHttpScheme && scheme_b == url::kHttpsScheme) ||
+      (scheme_a == url::kWsScheme && scheme_b == url::kWssScheme)) {
+    return SchemeMatchingResult::MatchingUpgrade;
+  }
+  return SchemeMatchingResult::NotMatching;
+}
+
 SchemeMatchingResult SourceAllowScheme(const mojom::CSPSourcePtr& source,
                                        const GURL& url,
                                        CSPContext* context) {
@@ -60,19 +71,11 @@
   const std::string& allowed_scheme =
       source->scheme.empty() ? context->self_source()->scheme : source->scheme;
 
-  if (url.SchemeIs(allowed_scheme))
-    return SchemeMatchingResult::MatchingExact;
-
-  // Implicitly allow using a more secure version of a protocol when the
-  // non-secure one is allowed.
-  if ((allowed_scheme == url::kHttpScheme && url.SchemeIs(url::kHttpsScheme)) ||
-      (allowed_scheme == url::kWsScheme && url.SchemeIs(url::kWssScheme))) {
-    return SchemeMatchingResult::MatchingUpgrade;
-  }
-  return SchemeMatchingResult::NotMatching;
+  return MatchScheme(allowed_scheme, url.scheme());
 }
 
-bool SourceAllowHost(const mojom::CSPSourcePtr& source, const GURL& url) {
+bool SourceAllowHost(const mojom::CSPSourcePtr& source,
+                     const std::string& host) {
   if (source->is_host_wildcard) {
     if (source->host.empty())
       return true;
@@ -80,66 +83,86 @@
     // The renderer version of this function counts how many times it happens.
     // It might be useful to do it outside of blink too.
     // See third_party/blink/renderer/core/frame/csp/csp_source.cc
-    return base::EndsWith(url.host(), '.' + source->host,
+    return base::EndsWith(host, '.' + source->host,
                           base::CompareCase::INSENSITIVE_ASCII);
   } else {
-    return base::EqualsCaseInsensitiveASCII(url.host(), source->host);
+    return base::EqualsCaseInsensitiveASCII(host, source->host);
   }
 }
 
-PortMatchingResult SourceAllowPort(const mojom::CSPSourcePtr& source,
-                                   const GURL& url) {
-  int url_port = url.EffectiveIntPort();
+bool SourceAllowHost(const mojom::CSPSourcePtr& source, const GURL& url) {
+  return SourceAllowHost(source, url.host());
+}
 
+PortMatchingResult SourceAllowPort(const mojom::CSPSourcePtr& source,
+                                   int port,
+                                   const std::string& scheme) {
   if (source->is_port_wildcard)
     return PortMatchingResult::MatchingWildcard;
 
-  if (source->port == url_port) {
+  if (source->port == port) {
     if (source->port == url::PORT_UNSPECIFIED)
       return PortMatchingResult::MatchingWildcard;
     return PortMatchingResult::MatchingExact;
   }
 
   if (source->port == url::PORT_UNSPECIFIED) {
-    if (DefaultPortForScheme(url.scheme()) == url_port) {
+    if (DefaultPortForScheme(scheme) == port)
       return PortMatchingResult::MatchingWildcard;
-    }
-    return PortMatchingResult::NotMatching;
+  }
+
+  if (port == url::PORT_UNSPECIFIED) {
+    if (source->port == DefaultPortForScheme(scheme))
+      return PortMatchingResult::MatchingWildcard;
   }
 
   int source_port = source->port;
   if (source_port == url::PORT_UNSPECIFIED)
     source_port = DefaultPortForScheme(source->scheme);
 
-  if (source_port == 80 && url_port == 443)
+  if (port == url::PORT_UNSPECIFIED)
+    port = DefaultPortForScheme(scheme);
+
+  if (source_port == 80 && port == 443)
     return PortMatchingResult::MatchingUpgrade;
 
   return PortMatchingResult::NotMatching;
 }
 
+PortMatchingResult SourceAllowPort(const mojom::CSPSourcePtr& source,
+                                   const GURL& url) {
+  return SourceAllowPort(source, url.EffectiveIntPort(), url.scheme());
+}
+
+bool SourceAllowPath(const mojom::CSPSourcePtr& source,
+                     const std::string& path) {
+  std::string path_decoded;
+  if (!DecodePath(path, &path_decoded)) {
+    // TODO(arthursonzogni): try to figure out if that could happen and how to
+    // handle it.
+    return false;
+  }
+
+  if (source->path.empty() || (source->path == "/" && path_decoded.empty()))
+    return true;
+
+  // If the path represents a directory.
+  if (base::EndsWith(source->path, "/", base::CompareCase::SENSITIVE)) {
+    return base::StartsWith(path_decoded, source->path,
+                            base::CompareCase::SENSITIVE);
+  }
+
+  // The path represents a file.
+  return source->path == path_decoded;
+}
+
 bool SourceAllowPath(const mojom::CSPSourcePtr& source,
                      const GURL& url,
                      bool has_followed_redirect) {
   if (has_followed_redirect)
     return true;
 
-  if (source->path.empty())
-    return true;
-
-  std::string url_path;
-  if (!DecodePath(url.path(), &url_path)) {
-    // TODO(arthursonzogni): try to figure out if that could happen and how to
-    // handle it.
-    return false;
-  }
-
-  // If the path represents a directory.
-  if (base::EndsWith(source->path, "/", base::CompareCase::SENSITIVE))
-    return base::StartsWith(url_path, source->path,
-                            base::CompareCase::SENSITIVE);
-
-  // The path represents a file.
-  return source->path == url_path;
+  return SourceAllowPath(source, url.path());
 }
 
 bool requiresUpgrade(const PortMatchingResult result) {
@@ -181,6 +204,42 @@
          SourceAllowPath(source, url, has_followed_redirect);
 }
 
+// Check whether |source_a| subsumes |source_b|.
+bool CSPSourceSubsumes(const mojom::CSPSourcePtr& source_a,
+                       const mojom::CSPSourcePtr& source_b) {
+  // If the original source expressions didn't have a scheme, we should have
+  // filled that already with origin's scheme.
+  DCHECK(!source_a->scheme.empty());
+  DCHECK(!source_b->scheme.empty());
+
+  if (MatchScheme(source_a->scheme, source_b->scheme) ==
+      SchemeMatchingResult::NotMatching) {
+    return false;
+  }
+
+  if (IsSchemeOnly(source_a))
+    return true;
+  if (IsSchemeOnly(source_b))
+    return false;
+
+  if (!SourceAllowHost(source_a, (source_b->is_host_wildcard ? "*." : "") +
+                                     source_b->host)) {
+    return false;
+  }
+
+  if (source_b->is_port_wildcard && !source_a->is_port_wildcard)
+    return false;
+  PortMatchingResult port_matching =
+      SourceAllowPort(source_a, source_b->port, source_b->scheme);
+  if (port_matching == PortMatchingResult::NotMatching)
+    return false;
+
+  if (!SourceAllowPath(source_a, source_b->path))
+    return false;
+
+  return true;
+}
+
 std::string ToString(const mojom::CSPSourcePtr& source) {
   // scheme
   if (IsSchemeOnly(source))
diff --git a/services/network/public/cpp/content_security_policy/csp_source.h b/services/network/public/cpp/content_security_policy/csp_source.h
index 902aed3e..d0e6c05f 100644
--- a/services/network/public/cpp/content_security_policy/csp_source.h
+++ b/services/network/public/cpp/content_security_policy/csp_source.h
@@ -22,6 +22,12 @@
                     CSPContext* context,
                     bool has_followed_redirect = false);
 
+// Check if |source_a| subsumes |source_b| according to
+// https://w3c.github.io/webappsec-cspee/#subsume-source-expressions
+COMPONENT_EXPORT(NETWORK_CPP)
+bool CSPSourceSubsumes(const mojom::CSPSourcePtr& source_a,
+                       const mojom::CSPSourcePtr& source_b);
+
 // Serialize the CSPSource |source| as a string. This is used for reporting
 // violations.
 COMPONENT_EXPORT(NETWORK_CPP)
diff --git a/services/network/public/cpp/content_security_policy/csp_source_unittest.cc b/services/network/public/cpp/content_security_policy/csp_source_unittest.cc
index 8d34dac..0ff85ab 100644
--- a/services/network/public/cpp/content_security_policy/csp_source_unittest.cc
+++ b/services/network/public/cpp/content_security_policy/csp_source_unittest.cc
@@ -3,6 +3,8 @@
 // found in the LICENSE file.
 
 #include "services/network/public/cpp/content_security_policy/csp_source.h"
+#include "net/http/http_response_headers.h"
+#include "services/network/public/cpp/content_security_policy/content_security_policy.h"
 #include "services/network/public/cpp/content_security_policy/csp_context.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/origin.h"
@@ -20,6 +22,17 @@
   return CheckCSPSource(source, url, context, is_redirect);
 }
 
+network::mojom::CSPSourcePtr CSPSource(const std::string& raw) {
+  scoped_refptr<net::HttpResponseHeaders> headers(
+      new net::HttpResponseHeaders("HTTP/1.1 200 OK"));
+  headers->SetHeader("Content-Security-Policy", "script-src " + raw);
+  std::vector<mojom::ContentSecurityPolicyPtr> policies;
+  AddContentSecurityPolicyFromHeaders(*headers, GURL("https://example.com/"),
+                                      &policies);
+  return std::move(
+      policies[0]->directives[mojom::CSPDirectiveName::ScriptSrc]->sources[0]);
+}
+
 }  // namespace
 
 TEST(CSPSourceTest, BasicMatching) {
@@ -310,6 +323,184 @@
   EXPECT_FALSE(Allow(source, GURL("http://a.com:9000/foo/"), &context, false));
 }
 
+TEST(CSPSourceTest, DoesNotSubsume) {
+  struct TestCase {
+    const char* a;
+    const char* b;
+  } cases[] = {
+      // In the following test cases, neither |a| subsumes |b| nor |b| subsumes
+      // |a|.
+      // Different hosts.
+      {"http://example.com", "http://another.com"},
+      // Different schemes (wss -> http).
+      {"wss://example.com", "http://example.com"},
+      // Different schemes (wss -> about).
+      {"wss://example.com/", "about://example.com/"},
+      // Different schemes (wss -> about).
+      {"http://example.com/", "about://example.com/"},
+      // Different paths.
+      {"http://example.com/1.html", "http://example.com/2.html"},
+      // Different ports.
+      {"http://example.com:443/", "http://example.com:800/"},
+  };
+  for (const auto& test : cases) {
+    auto a = CSPSource(test.a);
+    auto b = CSPSource(test.b);
+
+    EXPECT_FALSE(CSPSourceSubsumes(a, b))
+        << test.a << " should not subsume " << test.b;
+    EXPECT_FALSE(CSPSourceSubsumes(b, a))
+        << test.b << " should not subsume " << test.a;
+  }
+}
+
+TEST(CSPSourceTest, Subsumes) {
+  struct TestCase {
+    const char* a;
+    const char* b;
+    bool expected_a_subsumes_b;
+    bool expected_b_subsumes_a;
+  } cases[] = {
+      // Equal signals.
+      {"http://a.org/", "http://a.org/", true, true},
+      {"https://a.org/", "https://a.org/", true, true},
+      {"https://a.org/page.html", "https://a.org/page.html", true, true},
+      {"http://a.org:70", "http://a.org:70", true, true},
+      {"https://a.org:70", "https://a.org:70", true, true},
+      {"https://a.org/page.html", "https://a.org/page.html", true, true},
+      {"http://a.org:70/page.html", "http://a.org:70/page.html", true, true},
+      {"https://a.org:70/page.html", "https://a.org:70/page.html", true, true},
+      {"http://a.org/", "http://a.org", true, true},
+      {"http://a.org:80", "http://a.org:80", true, true},
+      {"http://a.org:80", "https://a.org:443", true, false},
+      {"http://a.org", "https://a.org:443", true, false},
+      {"http://a.org:80", "https://a.org", true, false},
+      // One stronger signal in the first CSPSource.
+      {"http://a.org/", "https://a.org/", true, false},
+      {"http://a.org/page.html", "http://a.org/", false, true},
+      {"http://a.org:80/page.html", "http://a.org:80/", false, true},
+      {"http://a.org:80", "http://a.org/", true, true},
+      {"http://a.org:700", "http://a.org/", false, false},
+      // Two stronger signals in the first CSPSource.
+      {"https://a.org/page.html", "http://a.org/", false, true},
+      {"https://a.org:80", "http://a.org/", false, false},
+      {"http://a.org:80/page.html", "http://a.org/", false, true},
+      // Three stronger signals in the first CSPSource.
+      {"https://a.org:70/page.html", "http://a.org/", false, false},
+      // Mixed signals.
+      {"https://a.org/", "http://a.org/page.html", false, false},
+      {"https://a.org", "http://a.org:70/", false, false},
+      {"http://a.org/page.html", "http://a.org:70/", false, false},
+  };
+
+  for (const auto& test : cases) {
+    auto a = CSPSource(test.a);
+    auto b = CSPSource(test.b);
+
+    EXPECT_EQ(CSPSourceSubsumes(a, b), test.expected_a_subsumes_b)
+        << test.a << " subsumes " << test.b << " should return "
+        << test.expected_a_subsumes_b;
+    EXPECT_EQ(CSPSourceSubsumes(b, a), test.expected_b_subsumes_a)
+        << test.b << " subsumes " << test.a << " should return "
+        << test.expected_b_subsumes_a;
+
+    a->is_host_wildcard = true;
+    EXPECT_FALSE(CSPSourceSubsumes(b, a))
+        << test.b << " should not subsume " << ToString(a);
+
+    // If also |b| has a wildcard host, then the result should be the expected
+    // one.
+    b->is_host_wildcard = true;
+    EXPECT_EQ(CSPSourceSubsumes(b, a), test.expected_b_subsumes_a)
+        << ToString(b) << " subsumes " << ToString(a) << " should return "
+        << test.expected_b_subsumes_a;
+  }
+}
+
+TEST(CSPSourceTest, HostWildcardSubsumes) {
+  const char* a = "http://*.example.org";
+  const char* b = "http://www.example.org";
+  const char* c = "http://example.org";
+  const char* d = "https://*.example.org";
+
+  auto source_a = CSPSource(a);
+  auto source_b = CSPSource(b);
+  auto source_c = CSPSource(c);
+  auto source_d = CSPSource(d);
+
+  // *.example.com subsumes www.example.com.
+  EXPECT_TRUE(CSPSourceSubsumes(source_a, source_b))
+      << a << " should subsume " << b;
+  EXPECT_FALSE(CSPSourceSubsumes(source_b, source_a))
+      << b << " should not subsume " << a;
+
+  // *.example.com and example.com have no relations.
+  EXPECT_FALSE(CSPSourceSubsumes(source_a, source_c))
+      << a << " should not subsume " << c;
+  EXPECT_FALSE(CSPSourceSubsumes(source_c, source_a))
+      << c << " should not subsume " << a;
+
+  // https://*.example.com and http://www.example.com have no relations.
+  EXPECT_FALSE(CSPSourceSubsumes(source_d, source_b))
+      << d << " should not subsume " << b;
+  EXPECT_FALSE(CSPSourceSubsumes(source_b, source_d))
+      << b << " should not subsume " << d;
+}
+
+TEST(CSPSourceTest, PortWildcardSubsumes) {
+  const char* a = "http://example.org:*";
+  const char* b = "http://example.org";
+  const char* c = "https://example.org:*";
+
+  auto source_a = CSPSource(a);
+  auto source_b = CSPSource(b);
+  auto source_c = CSPSource(c);
+
+  EXPECT_TRUE(CSPSourceSubsumes(source_a, source_b))
+      << a << " should subsume " << b;
+  EXPECT_FALSE(CSPSourceSubsumes(source_b, source_a))
+      << b << " should not subsume " << a;
+
+  // https://example.com:* and http://example.com have no relations.
+  EXPECT_FALSE(CSPSourceSubsumes(source_b, source_c))
+      << b << " should not subsume " << c;
+  EXPECT_FALSE(CSPSourceSubsumes(source_c, source_b))
+      << c << " should not subsume " << b;
+}
+
+TEST(CSPSourceTest, SchemesOnlySubsumes) {
+  struct TestCase {
+    const char* a;
+    const char* b;
+    bool expected;
+  } cases[] = {
+      // HTTP.
+      {"http:", "http:", true},
+      {"http:", "https:", true},
+      {"https:", "http:", false},
+      {"https:", "https:", true},
+      // WSS.
+      {"ws:", "ws:", true},
+      {"ws:", "wss:", true},
+      {"wss:", "ws:", false},
+      {"wss:", "wss:", true},
+      // Unequal.
+      {"ws:", "http:", false},
+      {"http:", "ws:", false},
+      {"http:", "about:", false},
+      {"wss:", "https:", false},
+      {"https:", "wss:", false},
+  };
+
+  for (const auto& test : cases) {
+    auto source_a = CSPSource(test.a);
+    auto source_b = CSPSource(test.b);
+    EXPECT_EQ(CSPSourceSubsumes(source_a, source_b), test.expected)
+        << test.a << " subsumes " << test.b << " should return "
+        << test.expected;
+  }
+}
+
 TEST(CSPSourceTest, ToString) {
   {
     auto source = network::mojom::CSPSource::New(
diff --git a/testing/scripts/run_wpt_tests.py b/testing/scripts/run_wpt_tests.py
index b777ba1..7efdcd1 100755
--- a/testing/scripts/run_wpt_tests.py
+++ b/testing/scripts/run_wpt_tests.py
@@ -43,6 +43,7 @@
             "--binary-arg=--enable-experimental-web-platform-features",
             "--binary-arg=--enable-blink-features=MojoJS,MojoJSTest",
             "--webdriver-binary=../../out/Release/chromedriver",
+            "--webdriver-arg=--enable-chrome-logs",
             "--headless",
             "--no-capture-stdio",
             "--no-manifest-download",
diff --git a/testing/scripts/wpt_common.py b/testing/scripts/wpt_common.py
index bbe53e56..de1b7e7 100644
--- a/testing/scripts/wpt_common.py
+++ b/testing/scripts/wpt_common.py
@@ -106,6 +106,7 @@
             log_artifact = root_node["artifacts"].pop("log", None)
             if log_artifact:
                 artifact_subpath = self._write_log_artifact(
+                    test_failures.FILENAME_SUFFIX_ACTUAL,
                     results_dir, path_so_far, log_artifact)
                 root_node["artifacts"]["actual_text"] = [artifact_subpath]
 
@@ -117,6 +118,13 @@
                 for screenshot_key, path in screenshot_paths_dict.items():
                     root_node["artifacts"][screenshot_key] = [path]
 
+            crashlog_artifact = root_node["artifacts"].pop("wpt_crash_log",
+                                                           None)
+            if crashlog_artifact:
+                artifact_subpath = self._write_log_artifact(
+                    test_failures.FILENAME_SUFFIX_CRASH_LOG,
+                    results_dir, path_so_far, crashlog_artifact)
+
             return
 
         # We're not at a leaf node, continue traversing the trie.
@@ -128,13 +136,15 @@
             self._process_test_leaves(results_dir, delim, root_node[key],
                                       new_path)
 
-    def _write_log_artifact(self, results_dir, test_name, log_artifact):
+    def _write_log_artifact(self, suffix, results_dir, test_name, log_artifact):
         """Writes a log artifact to disk.
 
-        The log artifact contains all the output of a test. It gets written to
-        the -actual.txt file for the test.
+        A log artifact contains some form of output for a test. It is written to
+        a txt file with a suffix generated from the log type.
 
         Args:
+            suffix: str suffix of the artifact to write, e.g.
+                test_failures.FILENAME_SUFFIX_ACTUAL
             results_dir: str path to the directory that results live in
             test_name: str name of the test that this artifact is for
             log_artifact: list of strings, the log entries for this test from
@@ -146,9 +156,7 @@
         """
         log_artifact_sub_path = (
             os.path.join("layout-test-results",
-                         self.port.output_filename(
-                             test_name, test_failures.FILENAME_SUFFIX_ACTUAL,
-                             ".txt"))
+                         self.port.output_filename(test_name, suffix, ".txt"))
         )
         log_artifact_full_path = os.path.join(results_dir,
                                               log_artifact_sub_path)
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 6063dab..41a924d 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -3578,6 +3578,24 @@
             ]
         }
     ],
+    "IOSRequestDesktopByDefault": [
+        {
+            "platforms": [
+                "ios"
+            ],
+            "experiments": [
+                {
+                    "name": "DesktopEverywhere",
+                    "enable_features": [
+                        "UseDefaultUserAgentInWebClient"
+                    ],
+                    "disable_features": [
+                        "MobileGoogleSRP"
+                    ]
+                }
+            ]
+        }
+    ],
     "IOSSSLCommittedInterstitials": [
         {
             "platforms": [
@@ -7217,9 +7235,9 @@
             ],
             "experiments": [
                 {
-                    "name": "Emabled5_v1",
+                    "name": "Enabled3_v1",
                     "params": {
-                        "limit": "5"
+                        "limit": "3"
                     },
                     "enable_features": [
                         "ThrottleInstallingServiceWorker"
diff --git a/third_party/blink/common/feature_policy/policy_value.cc b/third_party/blink/common/feature_policy/policy_value.cc
index 6be24bb..7bcf38e 100644
--- a/third_party/blink/common/feature_policy/policy_value.cc
+++ b/third_party/blink/common/feature_policy/policy_value.cc
@@ -72,28 +72,6 @@
   return *this;
 }
 
-// static
-PolicyValue PolicyValue::Combine(const PolicyValue& lhs,
-                                 const PolicyValue& rhs) {
-  PolicyValue result = lhs;
-  result.Combine(rhs);
-  return result;
-}
-
-void PolicyValue::Combine(const PolicyValue& rhs) {
-  DCHECK_EQ(type_, rhs.Type());
-  switch (type_) {
-    case mojom::PolicyValueType::kBool:
-      SetBoolValue(bool_value_ && rhs.BoolValue());
-      break;
-    case mojom::PolicyValueType::kDecDouble:
-      SetDoubleValue(std::min(double_value_, rhs.DoubleValue()), type_);
-      break;
-    default:
-      NOTREACHED();
-  }
-}
-
 bool operator==(const PolicyValue& lhs, const PolicyValue& rhs) {
   if (lhs.Type() != rhs.Type())
     return false;
diff --git a/third_party/blink/public/common/feature_policy/policy_value.h b/third_party/blink/public/common/feature_policy/policy_value.h
index ba96d1b9..bcb49c9 100644
--- a/third_party/blink/public/common/feature_policy/policy_value.h
+++ b/third_party/blink/public/common/feature_policy/policy_value.h
@@ -48,10 +48,6 @@
 
   // Operater overrides
   PolicyValue& operator=(const PolicyValue& rhs);
-  // Combine a new PolicyValue to self, by taking the stricter value of the two.
-  void Combine(const PolicyValue& value);
-  // Combine two PolicyValue_s together by taking the stricter value of the two.
-  static PolicyValue Combine(const PolicyValue& lhs, const PolicyValue& rhs);
 
   void SetToMax();
   void SetToMin();
diff --git a/third_party/blink/public/mojom/BUILD.gn b/third_party/blink/public/mojom/BUILD.gn
index 11fdd68..4abca4d 100644
--- a/third_party/blink/public/mojom/BUILD.gn
+++ b/third_party/blink/public/mojom/BUILD.gn
@@ -4,6 +4,7 @@
 
 import("//mojo/public/tools/bindings/mojom.gni")
 import("//third_party/blink/public/public_features.gni")
+import("//ui/ozone/ozone.gni")
 
 if (is_android) {
   import("//build/config/android/config.gni")
@@ -257,8 +258,12 @@
     ]
   }
 
+  enabled_features = []
   if (is_linux || is_chromeos) {
-    enabled_features = [ "renderer_pref_system_font_family_name" ]
+    enabled_features += [ "renderer_pref_system_font_family_name" ]
+  }
+  if (use_x11 || use_ozone) {
+    enabled_features += [ "is_selection_clipboard_buffer_possible" ]
   }
 
   shared_cpp_typemaps = [
diff --git a/third_party/blink/public/mojom/frame/frame.mojom b/third_party/blink/public/mojom/frame/frame.mojom
index 6816b218..d3ea704 100644
--- a/third_party/blink/public/mojom/frame/frame.mojom
+++ b/third_party/blink/public/mojom/frame/frame.mojom
@@ -417,6 +417,12 @@
       mojo_base.mojom.UnguessableToken child_frame_token,
       blink.mojom.FramePolicy frame_policy);
 
+  // Notifies the browser that the frame changed the 'csp' attribute
+  // of one of its child frames.
+  DidChangeCSPAttribute(
+      mojo_base.mojom.UnguessableToken child_frame_token,
+      network.mojom.ContentSecurityPolicy? parsed_csp_attribute);
+
   // Sent by the renderer to request a paint preview of a subframe. |clip_rect|
   // is the size of the frame in it's parent. |guid| is an an identifier for
   // all the capture work (regardless of the process captures are happening in)
diff --git a/third_party/blink/public/mojom/payments/payment_request.mojom b/third_party/blink/public/mojom/payments/payment_request.mojom
index 54cb03cc..4abac44c 100644
--- a/third_party/blink/public/mojom/payments/payment_request.mojom
+++ b/third_party/blink/public/mojom/payments/payment_request.mojom
@@ -218,10 +218,17 @@
        [EnableIf=is_android] bool google_pay_bridge_eligible);
 
   // Shows the user interface with the payment details.
+  // |is_user_gesture|: Whether the show is triggered from a user gesture.
+  // |wait_for_updated_details|: It's true when merchant passed in a promise
+  // into PaymentRequest.show(), so Chrome should disregard the initial payment
+  // details and show a spinner until the promise resolves with the correct
+  // payment details.
   Show(bool is_user_gesture, bool wait_for_updated_details);
 
   // Updates the payment details in response to new shipping address or shipping
   // option.
+  // |details|: The details that the merchant provides to update the payment
+  // request.
   UpdateWith(PaymentDetails details);
 
   // Called when the merchant received a new shipping address or shipping
diff --git a/third_party/blink/public/mojom/renderer_preferences.mojom b/third_party/blink/public/mojom/renderer_preferences.mojom
index ee5a09d..f580532 100644
--- a/third_party/blink/public/mojom/renderer_preferences.mojom
+++ b/third_party/blink/public/mojom/renderer_preferences.mojom
@@ -154,4 +154,9 @@
   // The width of the arrow bitmap on a horizontal scroll bar in dips.
   [EnableIf=is_win]
   int32 arrow_bitmap_width_horizontal_scroll_bar_in_dips = 0;
+
+  // Whether the selection clipboard buffer is available.  Linux environments
+  // may support this.
+  [EnableIf=is_selection_clipboard_buffer_possible]
+  bool selection_clipboard_buffer_available = false;
 };
diff --git a/third_party/blink/public/web/web_navigation_params.h b/third_party/blink/public/web/web_navigation_params.h
index 27539d30..0df09c1b 100644
--- a/third_party/blink/public/web/web_navigation_params.h
+++ b/third_party/blink/public/web/web_navigation_params.h
@@ -401,6 +401,9 @@
 
   // Whether the navigation is cross browsing context group (browsing instance).
   bool is_cross_browsing_context_group_navigation = false;
+
+  // A list of additional content security policies to be enforced by blink.
+  WebVector<WebString> forced_content_security_policies;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/public/web/web_settings.h b/third_party/blink/public/web/web_settings.h
index ada5a91..b01fc0f1 100644
--- a/third_party/blink/public/web/web_settings.h
+++ b/third_party/blink/public/web/web_settings.h
@@ -298,6 +298,7 @@
   virtual void SetNavigationControls(NavigationControls) = 0;
   virtual void SetAriaModalPrunesAXTree(bool) = 0;
   virtual void SetUseAXMenuList(bool) = 0;
+  virtual void SetSelectionClipboardBufferAvailable(bool) = 0;
 
  protected:
   ~WebSettings() = default;
diff --git a/third_party/blink/renderer/core/animation/animation.cc b/third_party/blink/renderer/core/animation/animation.cc
index 8fd94fb..ceecbc795 100644
--- a/third_party/blink/renderer/core/animation/animation.cc
+++ b/third_party/blink/renderer/core/animation/animation.cc
@@ -171,13 +171,12 @@
 
 AtomicString GetCSSTransitionCSSPropertyName(const Animation* animation) {
   CSSPropertyID property_id =
-      To<CSSTransition>(animation)->TransitionCSSProperty().PropertyID();
+      To<CSSTransition>(animation)->TransitionCSSPropertyName().Id();
   if (property_id == CSSPropertyID::kVariable ||
       property_id == CSSPropertyID::kInvalid)
     return AtomicString();
   return To<CSSTransition>(animation)
-      ->TransitionCSSProperty()
-      .GetCSSPropertyName()
+      ->TransitionCSSPropertyName()
       .ToAtomicString();
 }
 }  // namespace
@@ -943,7 +942,9 @@
   //    * animation does not have either a pending play task or a pending pause
   //      task,
   //    then idle.
-  if (!CurrentTimeInternal() && !start_time_ && !PendingInternal())
+  // https://github.com/w3c/csswg-drafts/issues/5400
+  if (!CurrentTimeInternal() && (!start_time_ || !timeline_) &&
+      !PendingInternal())
     return kIdle;
 
   // 2. Either of the following conditions are true:
@@ -1937,16 +1938,15 @@
                                             playback_rate_);
   }
 
-  double result =
+  AnimationTimeDelta result =
       playback_rate_ > 0
-          ? content_->TimeToForwardsEffectChange().InSecondsF() / playback_rate_
-          : content_->TimeToReverseEffectChange().InSecondsF() /
-                -playback_rate_;
+          ? content_->TimeToForwardsEffectChange() / playback_rate_
+          : content_->TimeToReverseEffectChange() / -playback_rate_;
 
   return !HasActiveAnimationsOnCompositor() &&
                  content_->GetPhase() == Timing::kPhaseActive
              ? AnimationTimeDelta()
-             : AnimationTimeDelta::FromSecondsD(result);
+             : result;
 }
 
 void Animation::cancel() {
diff --git a/third_party/blink/renderer/core/animation/animation_time_delta.h b/third_party/blink/renderer/core/animation/animation_time_delta.h
index 7984ceb..0f0c00b 100644
--- a/third_party/blink/renderer/core/animation/animation_time_delta.h
+++ b/third_party/blink/renderer/core/animation/animation_time_delta.h
@@ -80,6 +80,14 @@
   AnimationTimeDelta& operator*=(V a) {
     return *this = (*this * a);
   }
+  template <typename T>
+  AnimationTimeDelta operator/(T a) const {
+    return AnimationTimeDelta(delta_ / a);
+  }
+  template <typename T>
+  AnimationTimeDelta& operator/=(T a) {
+    return *this = (*this / a);
+  }
 
  protected:
   constexpr explicit AnimationTimeDelta(double delta) : delta_(delta) {}
diff --git a/third_party/blink/renderer/core/animation/compositor_animations_test.cc b/third_party/blink/renderer/core/animation/compositor_animations_test.cc
index becf7ed9..6e30713 100644
--- a/third_party/blink/renderer/core/animation/compositor_animations_test.cc
+++ b/third_party/blink/renderer/core/animation/compositor_animations_test.cc
@@ -2120,10 +2120,8 @@
           ->Layer()
           ->GetCompositedLayerMapping();
   ASSERT_NE(nullptr, composited_layer_mapping);
-  const cc::PictureLayer* layer =
-      composited_layer_mapping->MainGraphicsLayer()->CcLayer();
-  ASSERT_NE(nullptr, layer);
-  EXPECT_FALSE(layer->should_check_backface_visibility());
+  const auto& layer = composited_layer_mapping->MainGraphicsLayer()->CcLayer();
+  EXPECT_FALSE(layer.should_check_backface_visibility());
 
   // Change the backface visibility, while the compositor animation is
   // happening.
@@ -2132,7 +2130,7 @@
   // Make sure the setting made it to both blink and all the way to CC.
   EXPECT_EQ(transform->GetBackfaceVisibilityForTesting(),
             TransformPaintPropertyNode::BackfaceVisibility::kHidden);
-  EXPECT_TRUE(layer->should_check_backface_visibility())
+  EXPECT_TRUE(layer.should_check_backface_visibility())
       << "Change to hidden did not get propagated to CC";
   // Make sure the animation state is initialized in paint properties after
   // blink pushing new paint properties without animation state change.
diff --git a/third_party/blink/renderer/core/animation/css/css_transition.h b/third_party/blink/renderer/core/animation/css/css_transition.h
index b1858af..6097b27 100644
--- a/third_party/blink/renderer/core/animation/css/css_transition.h
+++ b/third_party/blink/renderer/core/animation/css/css_transition.h
@@ -29,8 +29,8 @@
 
   uint64_t TransitionGeneration() const { return transition_generation_; }
   AtomicString transitionProperty() const;
-  const CSSProperty& TransitionCSSProperty() const {
-    return transition_property_.GetCSSProperty();
+  CSSPropertyName TransitionCSSPropertyName() const {
+    return transition_property_.GetCSSPropertyName();
   }
 
   // Animation overrides.
diff --git a/third_party/blink/renderer/core/clipboard/system_clipboard.cc b/third_party/blink/renderer/core/clipboard/system_clipboard.cc
index 4027f9b..1c201d04 100644
--- a/third_party/blink/renderer/core/clipboard/system_clipboard.cc
+++ b/third_party/blink/renderer/core/clipboard/system_clipboard.cc
@@ -12,11 +12,15 @@
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/web_drag_data.h"
 #include "third_party/blink/public/platform/web_string.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_local_frame_client.h"
 #include "third_party/blink/renderer/core/clipboard/clipboard_mime_types.h"
 #include "third_party/blink/renderer/core/clipboard/clipboard_utilities.h"
 #include "third_party/blink/renderer/core/clipboard/data_object.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
+#include "third_party/blink/renderer/core/frame/local_frame_client.h"
+#include "third_party/blink/renderer/core/frame/settings.h"
 #include "third_party/blink/renderer/platform/graphics/image.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
@@ -37,6 +41,10 @@
   frame->GetBrowserInterfaceBroker().GetInterface(
       clipboard_.BindNewPipeAndPassReceiver(
           frame->GetTaskRunner(TaskType::kUserInteraction)));
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+  is_selection_buffer_available_ =
+      frame->GetSettings()->GetSelectionClipboardBufferAvailable();
+#endif  // defined(OS_LINUX) && !defined(OS_CHROMEOS)
 }
 
 bool SystemClipboard::IsSelectionMode() const {
@@ -242,14 +250,7 @@
     case mojom::ClipboardBuffer::kStandard:
       return true;
     case mojom::ClipboardBuffer::kSelection:
-#if defined(USE_X11)
-      return true;
-#else
-      // Chrome OS and non-X11 unix builds do not support
-      // the X selection clipboard.
-      // TODO(http://crbug.com/361753): remove the need for this case.
-      return false;
-#endif
+      return is_selection_buffer_available_;
   }
   return true;
 }
diff --git a/third_party/blink/renderer/core/clipboard/system_clipboard.h b/third_party/blink/renderer/core/clipboard/system_clipboard.h
index 8f4979d..04a769dd 100644
--- a/third_party/blink/renderer/core/clipboard/system_clipboard.h
+++ b/third_party/blink/renderer/core/clipboard/system_clipboard.h
@@ -79,10 +79,14 @@
   bool IsValidBufferType(mojom::ClipboardBuffer);
 
   HeapMojoRemote<mojom::blink::ClipboardHost> clipboard_;
-  // In X11, |buffer_| may equal ClipboardBuffer::kStandard or kSelection.
-  // Outside X11, |buffer_| always equals ClipboardBuffer::kStandard.
+  // In some Linux environments, |buffer_| may equal ClipboardBuffer::kStandard
+  // or kSelection.  In other platforms |buffer_| always equals
+  // ClipboardBuffer::kStandard.
   mojom::ClipboardBuffer buffer_ = mojom::ClipboardBuffer::kStandard;
 
+  // Whether the selection buffer is available on the underlying platform.
+  bool is_selection_buffer_available_ = false;
+
   DISALLOW_COPY_AND_ASSIGN(SystemClipboard);
 };
 
diff --git a/third_party/blink/renderer/core/editing/compute_layer_selection.cc b/third_party/blink/renderer/core/editing/compute_layer_selection.cc
index dfd0ea9..75b582b 100644
--- a/third_party/blink/renderer/core/editing/compute_layer_selection.cc
+++ b/third_party/blink/renderer/core/editing/compute_layer_selection.cc
@@ -169,7 +169,7 @@
       edge_start_in_layer, layout_object, graphics_layer);
   bound.edge_end = LocalToInvalidationBackingPoint(
       edge_end_in_layer, layout_object, graphics_layer);
-  bound.layer_id = graphics_layer.CcLayer()->id();
+  bound.layer_id = graphics_layer.CcLayer().id();
   bound.hidden =
       !IsVisible(layout_object, edge_start_in_layer, edge_end_in_layer);
   return bound;
diff --git a/third_party/blink/renderer/core/exported/web_frame_test.cc b/third_party/blink/renderer/core/exported/web_frame_test.cc
index 234a5c7..dc54a49 100644
--- a/third_party/blink/renderer/core/exported/web_frame_test.cc
+++ b/third_party/blink/renderer/core/exported/web_frame_test.cc
@@ -6223,7 +6223,7 @@
     ASSERT_TRUE(layer_owner_node_for_start);
     EXPECT_EQ(GetExpectedLayerForSelection(layer_owner_node_for_start)
                   ->CcLayer()
-                  ->id(),
+                  .id(),
               selection.start.layer_id);
 
     EXPECT_EQ(start_edge_start_in_layer_x, selection.start.edge_start.x());
@@ -6236,7 +6236,7 @@
 
     ASSERT_TRUE(layer_owner_node_for_end);
     EXPECT_EQ(
-        GetExpectedLayerForSelection(layer_owner_node_for_end)->CcLayer()->id(),
+        GetExpectedLayerForSelection(layer_owner_node_for_end)->CcLayer().id(),
         selection.end.layer_id);
 
     EXPECT_EQ(end_edge_start_in_layer_x, selection.end.edge_start.x());
diff --git a/third_party/blink/renderer/core/exported/web_settings_impl.cc b/third_party/blink/renderer/core/exported/web_settings_impl.cc
index 3e58fd8..de0a67d0 100644
--- a/third_party/blink/renderer/core/exported/web_settings_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_settings_impl.cc
@@ -788,6 +788,10 @@
   settings_->SetUseAXMenuList(enabled);
 }
 
+void WebSettingsImpl::SetSelectionClipboardBufferAvailable(bool available) {
+  settings_->SetSelectionClipboardBufferAvailable(available);
+}
+
 STATIC_ASSERT_ENUM(WebSettings::ImageAnimationPolicy::kAllowed,
                    kImageAnimationPolicyAllowed);
 STATIC_ASSERT_ENUM(WebSettings::ImageAnimationPolicy::kAnimateOnce,
diff --git a/third_party/blink/renderer/core/exported/web_settings_impl.h b/third_party/blink/renderer/core/exported/web_settings_impl.h
index dcd1119..13ed538 100644
--- a/third_party/blink/renderer/core/exported/web_settings_impl.h
+++ b/third_party/blink/renderer/core/exported/web_settings_impl.h
@@ -225,6 +225,7 @@
 
   void SetAriaModalPrunesAXTree(bool) override;
   void SetUseAXMenuList(bool) override;
+  void SetSelectionClipboardBufferAvailable(bool) override;
 
   bool RenderVSyncNotificationEnabled() const {
     return render_v_sync_notification_enabled_;
diff --git a/third_party/blink/renderer/core/frame/csp/content_security_policy.cc b/third_party/blink/renderer/core/frame/csp/content_security_policy.cc
index bfc6638..b8b7a5ef 100644
--- a/third_party/blink/renderer/core/frame/csp/content_security_policy.cc
+++ b/third_party/blink/renderer/core/frame/csp/content_security_policy.cc
@@ -1048,12 +1048,13 @@
     // StripURLForUseInReport(..)
     source_url.SetQuery(String());
 
-    // TODO(arthursonzogni): |redirect_status| refers to the redirect status of
-    // the |blocked_url|. This is unrelated to |source_url|. Why using it in
-    // this case? This is obviously wrong:
+    // The |source_url| is the URL of the script that triggered the CSP
+    // violation. It is the URL pre-redirect. So it is safe to expose it in
+    // reports without leaking any new informations to the document. See
+    // https://crrev.com/c/2187792.
     String source_file =
         StripURLForUseInReport(delegate->GetSecurityOrigin(), source_url,
-                               redirect_status, effective_type);
+                               RedirectStatus::kNoRedirect, effective_type);
 
     init->setSourceFile(source_file);
     init->setLineNumber(source_location->LineNumber());
@@ -1723,6 +1724,8 @@
 // static
 bool ContentSecurityPolicy::IsValidCSPAttr(const String& attr,
                                            const String& context_required_csp) {
+  DCHECK(!base::FeatureList::IsEnabled(network::features::kOutOfBlinkCSPEE));
+
   // we don't allow any newline characters in the CSP attributes
   if (attr.Contains('\n') || attr.Contains('\r'))
     return false;
@@ -1746,9 +1749,6 @@
     return true;
   }
 
-  if (base::FeatureList::IsEnabled(network::features::kOutOfBlinkCSPEE))
-    return true;
-
   auto* context_policy = MakeGarbageCollected<ContentSecurityPolicy>();
   context_policy->AddPolicyFromHeaderValue(context_required_csp,
                                            ContentSecurityPolicyType::kEnforce,
diff --git a/third_party/blink/renderer/core/frame/csp/csp_directive_list.cc b/third_party/blink/renderer/core/frame/csp/csp_directive_list.cc
index 4f0f773..3581ff0 100644
--- a/third_party/blink/renderer/core/frame/csp/csp_directive_list.cc
+++ b/third_party/blink/renderer/core/frame/csp/csp_directive_list.cc
@@ -265,7 +265,7 @@
                            report_endpoints_, use_reporting_api_, header_,
                            header_type_, ContentSecurityPolicy::kEvalViolation,
                            std::unique_ptr<SourceLocation>(), nullptr,
-                           RedirectStatus::kFollowedRedirect, nullptr, content);
+                           RedirectStatus::kNoRedirect, nullptr, content);
 }
 
 bool CSPDirectiveList::CheckEval(SourceListDirective* directive) const {
@@ -340,7 +340,7 @@
       ContentSecurityPolicy::GetDirectiveName(
           ContentSecurityPolicy::DirectiveType::kRequireTrustedTypesFor),
       ContentSecurityPolicy::DirectiveType::kRequireTrustedTypesFor, message,
-      KURL(), RedirectStatus::kFollowedRedirect,
+      KURL(), RedirectStatus::kNoRedirect,
       ContentSecurityPolicy::kTrustedTypesSinkViolation, sample, sample_prefix);
   return IsReportOnly();
 }
diff --git a/third_party/blink/renderer/core/frame/frame_owner.h b/third_party/blink/renderer/core/frame/frame_owner.h
index cce810a3..c18dc8a4 100644
--- a/third_party/blink/renderer/core/frame/frame_owner.h
+++ b/third_party/blink/renderer/core/frame/frame_owner.h
@@ -79,6 +79,7 @@
 
  protected:
   virtual void FrameOwnerPropertiesChanged() {}
+  virtual void CSPAttributeChanged() {}
 
  private:
   virtual void SetIsSwappingFrames(bool) {}
@@ -107,6 +108,7 @@
     if (frame_owner_) {
       frame_owner_->SetIsSwappingFrames(false);
       frame_owner_->FrameOwnerPropertiesChanged();
+      frame_owner_->CSPAttributeChanged();
     }
   }
 
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 8acf297..f1d3ff00 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -2803,7 +2803,7 @@
       root,
       [](const GraphicsLayer& layer) {
         PaintArtifactCompositor::UpdateLayerDebugInfo(
-            *layer.CcLayer(),
+            layer.CcLayer(),
             PaintChunk::Id(layer, DisplayItem::kGraphicsLayerWrapper),
             layer.GetCompositingReasons(),
             layer.GetRasterInvalidationTracking());
diff --git a/third_party/blink/renderer/core/frame/settings.json5 b/third_party/blink/renderer/core/frame/settings.json5
index 23989e8..3ea132ac 100644
--- a/third_party/blink/renderer/core/frame/settings.json5
+++ b/third_party/blink/renderer/core/frame/settings.json5
@@ -1099,6 +1099,10 @@
     {
       name: "useAXMenuList",
       initial: true,
+    },
+    {
+      name: "selectionClipboardBufferAvailable",
+      initial: false,
       type: "bool",
     },
   ],
diff --git a/third_party/blink/renderer/core/html/html_frame_owner_element.cc b/third_party/blink/renderer/core/html/html_frame_owner_element.cc
index 43d129e7..706fb4e1 100644
--- a/third_party/blink/renderer/core/html/html_frame_owner_element.cc
+++ b/third_party/blink/renderer/core/html/html_frame_owner_element.cc
@@ -20,6 +20,7 @@
 
 #include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
 
+#include "services/network/public/cpp/features.h"
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/mojom/feature_policy/feature_policy.mojom-blink.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
@@ -383,6 +384,33 @@
                                      std::move(properties));
 }
 
+void HTMLFrameOwnerElement::CSPAttributeChanged() {
+  if (!base::FeatureList::IsEnabled(network::features::kOutOfBlinkCSPEE))
+    return;
+
+  // Don't notify about updates if ContentFrame() is null, for example when
+  // the subframe hasn't been created yet; or if we are in the middle of
+  // swapping one frame for another, in which case the final state
+  // will be propagated at the end of the swapping operation.
+  if (is_swapping_frames_ || !ContentFrame())
+    return;
+
+  String fake_header =
+      "HTTP/1.1 200 OK\nContent-Security-Policy: " + RequiredCsp();
+  network::mojom::blink::ParsedHeadersPtr parsed_headers =
+      ParseHeaders(fake_header, GetDocument().Url());
+
+  DCHECK_LE(parsed_headers->content_security_policy.size(), 1u);
+
+  network::mojom::blink::ContentSecurityPolicyPtr csp =
+      parsed_headers->content_security_policy.IsEmpty()
+          ? nullptr
+          : std::move(parsed_headers->content_security_policy[0]);
+
+  GetDocument().GetFrame()->GetLocalFrameHostRemote().DidChangeCSPAttribute(
+      ContentFrame()->GetFrameToken(), std::move(csp));
+}
+
 void HTMLFrameOwnerElement::AddResourceTiming(const ResourceTimingInfo& info) {
   // Resource timing info should only be reported if the subframe is attached.
   DCHECK(ContentFrame() && ContentFrame()->IsLocalFrame());
@@ -548,6 +576,9 @@
   if (!child_frame)
     return false;
 
+  // Send 'csp' attribute to the browser.
+  CSPAttributeChanged();
+
   WebFrameLoadType child_load_type = WebFrameLoadType::kReplaceCurrentItem;
   if (!GetDocument().LoadEventFinished() &&
       GetDocument().Loader()->LoadType() ==
diff --git a/third_party/blink/renderer/core/html/html_frame_owner_element.h b/third_party/blink/renderer/core/html/html_frame_owner_element.h
index 038a9c1..a103ca44 100644
--- a/third_party/blink/renderer/core/html/html_frame_owner_element.h
+++ b/third_party/blink/renderer/core/html/html_frame_owner_element.h
@@ -145,6 +145,7 @@
                               bool replace_current_item);
   bool IsKeyboardFocusable() const override;
   void FrameOwnerPropertiesChanged() override;
+  void CSPAttributeChanged() override;
 
   void DisposePluginSoon(WebPluginContainerImpl*);
 
diff --git a/third_party/blink/renderer/core/html/html_iframe_element.cc b/third_party/blink/renderer/core/html/html_iframe_element.cc
index 2fdf9bca..3d708f4 100644
--- a/third_party/blink/renderer/core/html/html_iframe_element.cc
+++ b/third_party/blink/renderer/core/html/html_iframe_element.cc
@@ -25,6 +25,7 @@
 #include "third_party/blink/renderer/core/html/html_iframe_element.h"
 
 #include "base/metrics/histogram_macros.h"
+#include "services/network/public/cpp/features.h"
 #include "services/network/public/cpp/web_sandbox_flags.h"
 #include "services/network/public/mojom/trust_tokens.mojom-blink.h"
 #include "services/network/public/mojom/web_sandbox_flags.mojom-blink.h"
@@ -231,19 +232,35 @@
       UpdateContainerPolicy();
     }
   } else if (name == html_names::kCspAttr) {
-    if (!ContentSecurityPolicy::IsValidCSPAttr(
-            value.GetString(), GetDocument().RequiredCSP().GetString())) {
-      required_csp_ = g_null_atom;
-      GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
-          mojom::ConsoleMessageSource::kOther,
-          mojom::ConsoleMessageLevel::kError,
-          "'csp' attribute is not a valid policy: " + value));
-      return;
-    }
-    if (required_csp_ != value) {
-      required_csp_ = value;
-      FrameOwnerPropertiesChanged();
-      UseCounter::Count(GetDocument(), WebFeature::kIFrameCSPAttribute);
+    if (base::FeatureList::IsEnabled(network::features::kOutOfBlinkCSPEE)) {
+      if (value.Contains('\n') || value.Contains('\r') || value.Contains(',')) {
+        required_csp_ = g_null_atom;
+        GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
+            mojom::blink::ConsoleMessageSource::kOther,
+            mojom::blink::ConsoleMessageLevel::kError,
+            "'csp' attribute is invalid: " + value));
+        return;
+      }
+      if (required_csp_ != value) {
+        required_csp_ = value;
+        CSPAttributeChanged();
+        UseCounter::Count(GetDocument(), WebFeature::kIFrameCSPAttribute);
+      }
+    } else {
+      if (!ContentSecurityPolicy::IsValidCSPAttr(
+              value.GetString(), GetDocument().RequiredCSP().GetString())) {
+        required_csp_ = g_null_atom;
+        GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
+            mojom::blink::ConsoleMessageSource::kOther,
+            mojom::blink::ConsoleMessageLevel::kError,
+            "'csp' attribute is not a valid policy: " + value));
+        return;
+      }
+      if (required_csp_ != value) {
+        required_csp_ = value;
+        FrameOwnerPropertiesChanged();
+        UseCounter::Count(GetDocument(), WebFeature::kIFrameCSPAttribute);
+      }
     }
   } else if (name == html_names::kAllowAttr) {
     if (allow_ != value) {
@@ -432,7 +449,8 @@
   auto* html_doc = DynamicTo<HTMLDocument>(GetDocument());
   if (html_doc && insertion_point.IsInDocumentTree()) {
     html_doc->AddNamedItem(name_);
-    if (!ContentSecurityPolicy::IsValidCSPAttr(
+    if (!base::FeatureList::IsEnabled(network::features::kOutOfBlinkCSPEE) &&
+        !ContentSecurityPolicy::IsValidCSPAttr(
             required_csp_, GetDocument().RequiredCSP().GetString())) {
       if (!required_csp_.IsEmpty()) {
         GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
diff --git a/third_party/blink/renderer/core/inspector/inspector_animation_agent.cc b/third_party/blink/renderer/core/inspector/inspector_animation_agent.cc
index 1a052a4..917896ec 100644
--- a/third_party/blink/renderer/core/inspector/inspector_animation_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_animation_agent.cc
@@ -406,30 +406,31 @@
 }
 
 String InspectorAnimationAgent::CreateCSSId(blink::Animation& animation) {
-  static const CSSProperty* g_animation_properties[] = {
-      &GetCSSPropertyAnimationDelay(),
-      &GetCSSPropertyAnimationDirection(),
-      &GetCSSPropertyAnimationDuration(),
-      &GetCSSPropertyAnimationFillMode(),
-      &GetCSSPropertyAnimationIterationCount(),
-      &GetCSSPropertyAnimationName(),
-      &GetCSSPropertyAnimationTimingFunction(),
+  static CSSPropertyID g_animation_properties[] = {
+      CSSPropertyID::kAnimationDelay,
+      CSSPropertyID::kAnimationDirection,
+      CSSPropertyID::kAnimationDuration,
+      CSSPropertyID::kAnimationFillMode,
+      CSSPropertyID::kAnimationIterationCount,
+      CSSPropertyID::kAnimationName,
+      CSSPropertyID::kAnimationTimingFunction,
   };
-  static const CSSProperty* g_transition_properties[] = {
-      &GetCSSPropertyTransitionDelay(), &GetCSSPropertyTransitionDuration(),
-      &GetCSSPropertyTransitionProperty(),
-      &GetCSSPropertyTransitionTimingFunction(),
+  static CSSPropertyID g_transition_properties[] = {
+      CSSPropertyID::kTransitionDelay,
+      CSSPropertyID::kTransitionDuration,
+      CSSPropertyID::kTransitionProperty,
+      CSSPropertyID::kTransitionTimingFunction,
   };
 
   auto* effect = To<KeyframeEffect>(animation.effect());
-  Vector<const CSSProperty*> css_properties;
+  Vector<CSSPropertyName> css_property_names;
   if (IsA<CSSAnimation>(animation)) {
-    for (const CSSProperty* property : g_animation_properties)
-      css_properties.push_back(property);
+    for (CSSPropertyID property : g_animation_properties)
+      css_property_names.push_back(CSSPropertyName(property));
   } else if (auto* css_transition = DynamicTo<CSSTransition>(animation)) {
-    for (const CSSProperty* property : g_transition_properties)
-      css_properties.push_back(property);
-    css_properties.push_back(&css_transition->TransitionCSSProperty());
+    for (CSSPropertyID property : g_transition_properties)
+      css_property_names.push_back(CSSPropertyName(property));
+    css_property_names.push_back(css_transition->TransitionCSSPropertyName());
   } else {
     NOTREACHED();
   }
@@ -442,14 +443,14 @@
                           ? AnimationType::CSSTransition
                           : AnimationType::CSSAnimation);
   digestor.UpdateUtf8(animation.id());
-  for (const CSSProperty* property : css_properties) {
+  for (const CSSPropertyName& name : css_property_names) {
     CSSStyleDeclaration* style =
-        css_agent_->FindEffectiveDeclaration(*property, styles);
+        css_agent_->FindEffectiveDeclaration(name, styles);
     // Ignore inline styles.
     if (!style || !style->ParentStyleSheet() || !style->parentRule() ||
         style->parentRule()->GetType() != CSSRule::kStyleRule)
       continue;
-    digestor.UpdateUtf8(property->GetPropertyNameString());
+    digestor.UpdateUtf8(name.ToAtomicString());
     digestor.UpdateUtf8(css_agent_->StyleSheetId(style->ParentStyleSheet()));
     digestor.UpdateUtf8(To<CSSStyleRule>(style->parentRule())->selectorText());
   }
diff --git a/third_party/blink/renderer/core/inspector/inspector_css_agent.cc b/third_party/blink/renderer/core/inspector/inspector_css_agent.cc
index 5a7b7db9..731b51b 100644
--- a/third_party/blink/renderer/core/inspector/inspector_css_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_css_agent.cc
@@ -37,6 +37,7 @@
 #include "third_party/blink/renderer/core/css/css_import_rule.h"
 #include "third_party/blink/renderer/core/css/css_keyframe_rule.h"
 #include "third_party/blink/renderer/core/css/css_media_rule.h"
+#include "third_party/blink/renderer/core/css/css_property_name.h"
 #include "third_party/blink/renderer/core/css/css_property_names.h"
 #include "third_party/blink/renderer/core/css/css_property_value_set.h"
 #include "third_party/blink/renderer/core/css/css_rule.h"
@@ -743,7 +744,7 @@
 
 void InspectorCSSAgent::CompleteEnabled() {
   instrumenting_agents_->AddInspectorCSSAgent(this);
-  dom_agent_->SetDOMListener(this);
+  dom_agent_->AddDOMListener(this);
   HeapVector<Member<Document>> documents = dom_agent_->Documents();
   for (Document* document : documents) {
     UpdateActiveStyleSheets(document);
@@ -774,7 +775,7 @@
 
 Response InspectorCSSAgent::disable() {
   Reset();
-  dom_agent_->SetDOMListener(nullptr);
+  dom_agent_->RemoveDOMListener(this);
   instrumenting_agents_->RemoveInspectorCSSAgent(this);
   enable_completed_ = false;
   enable_requested_.Set(false);
@@ -2320,12 +2321,12 @@
 }
 
 CSSStyleDeclaration* InspectorCSSAgent::FindEffectiveDeclaration(
-    const CSSProperty& property_class,
+    const CSSPropertyName& property_name,
     const HeapVector<Member<CSSStyleDeclaration>>& styles) {
   if (!styles.size())
     return nullptr;
 
-  String longhand = property_class.GetPropertyNameString();
+  String longhand = property_name.ToAtomicString();
   CSSStyleDeclaration* found_style = nullptr;
 
   for (unsigned i = 0; i < styles.size(); ++i) {
@@ -2357,16 +2358,13 @@
         "Can't edit a node from a non-active document");
   }
 
-  CSSPropertyID property =
-      cssPropertyID(element->GetExecutionContext(), property_name);
-  if (!isValidCSSPropertyID(property))
+  base::Optional<CSSPropertyName> css_property_name =
+      CSSPropertyName::From(element->GetExecutionContext(), property_name);
+  if (!css_property_name.has_value())
     return Response::ServerError("Invalid property name");
 
-  CSSPropertyID property_id =
-      cssPropertyID(element->GetExecutionContext(), property_name);
-  const CSSProperty& property_class = CSSProperty::Get(property_id);
   CSSStyleDeclaration* style =
-      FindEffectiveDeclaration(property_class, MatchingStyles(element));
+      FindEffectiveDeclaration(*css_property_name, MatchingStyles(element));
   if (!style)
     return Response::ServerError("Can't find a style to edit");
 
@@ -2390,13 +2388,13 @@
     return Response::ServerError("Can't find a source to edit");
 
   Vector<StylePropertyShorthand, 4> shorthands;
-  getMatchingShorthandsForLonghand(property_id, &shorthands);
+  getMatchingShorthandsForLonghand(css_property_name->Id(), &shorthands);
 
   String shorthand =
       shorthands.size() > 0
           ? CSSProperty::Get(shorthands[0].id()).GetPropertyNameString()
           : String();
-  String longhand = property_class.GetPropertyNameString();
+  String longhand = css_property_name->ToAtomicString();
 
   int found_index = -1;
   Vector<CSSPropertySourceData>& properties = source_data->property_data;
diff --git a/third_party/blink/renderer/core/inspector/inspector_css_agent.h b/third_party/blink/renderer/core/inspector/inspector_css_agent.h
index eaf2c0f..0de4ab3 100644
--- a/third_party/blink/renderer/core/inspector/inspector_css_agent.h
+++ b/third_party/blink/renderer/core/inspector/inspector_css_agent.h
@@ -50,6 +50,7 @@
 class RecalculateStyle;
 }  // namespace probe
 
+class CSSPropertyName;
 class CSSRule;
 class CSSStyleRule;
 class CSSStyleSheet;
@@ -233,7 +234,7 @@
       CSSRule*);
 
   CSSStyleDeclaration* FindEffectiveDeclaration(
-      const CSSProperty&,
+      const CSSPropertyName&,
       const HeapVector<Member<CSSStyleDeclaration>>& styles);
 
   HeapVector<Member<CSSStyleDeclaration>> MatchingStyles(Element*);
diff --git a/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc b/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc
index f8798c4..f0a7244 100644
--- a/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc
@@ -231,7 +231,6 @@
     : isolate_(isolate),
       inspected_frames_(inspected_frames),
       v8_session_(v8_session),
-      dom_listener_(nullptr),
       document_node_to_id_map_(MakeGarbageCollected<NodeToIdMap>()),
       last_node_id_(1),
       suppress_attribute_modified_event_(false),
@@ -256,8 +255,32 @@
   return result;
 }
 
-void InspectorDOMAgent::SetDOMListener(DOMListener* listener) {
-  dom_listener_ = listener;
+void InspectorDOMAgent::AddDOMListener(DOMListener* listener) {
+  dom_listeners_.insert(listener);
+}
+
+void InspectorDOMAgent::RemoveDOMListener(DOMListener* listener) {
+  dom_listeners_.erase(listener);
+}
+
+void InspectorDOMAgent::NotifyDidAddDocument(Document* document) {
+  for (DOMListener* listener : dom_listeners_)
+    listener->DidAddDocument(document);
+}
+
+void InspectorDOMAgent::NotifyDidRemoveDocument(Document* document) {
+  for (DOMListener* listener : dom_listeners_)
+    listener->DidRemoveDocument(document);
+}
+
+void InspectorDOMAgent::NotifyWillRemoveDOMNode(Node* node) {
+  for (DOMListener* listener : dom_listeners_)
+    listener->WillRemoveDOMNode(node);
+}
+
+void InspectorDOMAgent::NotifyDidModifyDOMAttr(Element* element) {
+  for (DOMListener* listener : dom_listeners_)
+    listener->DidModifyDOMAttr(element);
 }
 
 void InspectorDOMAgent::SetDocument(Document* doc) {
@@ -304,8 +327,8 @@
   id_to_node_.erase(id);
   id_to_nodes_map_.erase(id);
 
-  if (IsA<Document>(node) && dom_listener_)
-    dom_listener_->DidRemoveDocument(To<Document>(node));
+  if (IsA<Document>(node))
+    NotifyDidRemoveDocument(To<Document>(node));
 
   if (auto* frame_owner = DynamicTo<HTMLFrameOwnerElement>(node)) {
     Document* content_document = frame_owner->contentDocument();
@@ -331,8 +354,7 @@
     }
   }
 
-  if (dom_listener_)
-    dom_listener_->WillRemoveDOMNode(node);
+  NotifyWillRemoveDOMNode(node);
   document_node_to_id_map_->erase(node);
 
   bool children_requested = children_requested_.Contains(id);
@@ -1859,8 +1881,7 @@
 
 void InspectorDOMAgent::DidCommitLoad(LocalFrame*, DocumentLoader* loader) {
   Document* document = loader->GetFrame()->GetDocument();
-  if (dom_listener_)
-    dom_listener_->DidAddDocument(document);
+  NotifyDidAddDocument(document);
 
   LocalFrame* inspected_frame = inspected_frames_->Root();
   if (loader->GetFrame() != inspected_frame) {
@@ -1948,8 +1969,7 @@
   if (!id)
     return;
 
-  if (dom_listener_)
-    dom_listener_->DidModifyDOMAttr(element);
+  NotifyDidModifyDOMAttr(element);
 
   GetFrontend()->attributeModified(id, name.ToString(), value);
 }
@@ -1961,8 +1981,7 @@
   if (!id)
     return;
 
-  if (dom_listener_)
-    dom_listener_->DidModifyDOMAttr(element);
+  NotifyDidModifyDOMAttr(element);
 
   GetFrontend()->attributeRemoved(id, name.ToString());
 }
@@ -1977,8 +1996,7 @@
     if (!id)
       continue;
 
-    if (dom_listener_)
-      dom_listener_->DidModifyDOMAttr(element);
+    NotifyDidModifyDOMAttr(element);
     node_ids->emplace_back(id);
   }
   GetFrontend()->inlineStyleInvalidated(std::move(node_ids));
@@ -2374,7 +2392,7 @@
 }
 
 void InspectorDOMAgent::Trace(Visitor* visitor) const {
-  visitor->Trace(dom_listener_);
+  visitor->Trace(dom_listeners_);
   visitor->Trace(inspected_frames_);
   visitor->Trace(document_node_to_id_map_);
   visitor->Trace(dangling_node_to_id_maps_);
diff --git a/third_party/blink/renderer/core/inspector/inspector_dom_agent.h b/third_party/blink/renderer/core/inspector/inspector_dom_agent.h
index 06c6b4bf..c36df12 100644
--- a/third_party/blink/renderer/core/inspector/inspector_dom_agent.h
+++ b/third_party/blink/renderer/core/inspector/inspector_dom_agent.h
@@ -278,7 +278,8 @@
 
   Node* NodeForId(int node_id);
   int BoundNodeId(Node*);
-  void SetDOMListener(DOMListener*);
+  void AddDOMListener(DOMListener*);
+  void RemoveDOMListener(DOMListener*);
   int PushNodePathToFrontend(Node*);
   protocol::Response NodeForRemoteObjectId(const String& remote_object_id,
                                            Node*&);
@@ -317,6 +318,11 @@
   // For idempotence, call enable().
   void EnableAndReset();
 
+  void NotifyDidAddDocument(Document*);
+  void NotifyDidRemoveDocument(Document*);
+  void NotifyWillRemoveDOMNode(Node*);
+  void NotifyDidModifyDOMAttr(Element*);
+
   // Node-related methods.
   typedef HeapHashMap<Member<Node>, int> NodeToIdMap;
   int Bind(Node*, NodeToIdMap*);
@@ -367,7 +373,7 @@
   v8::Isolate* isolate_;
   Member<InspectedFrames> inspected_frames_;
   v8_inspector::V8InspectorSession* v8_session_;
-  Member<DOMListener> dom_listener_;
+  HeapHashSet<Member<DOMListener>> dom_listeners_;
   Member<NodeToIdMap> document_node_to_id_map_;
   // Owns node mappings for dangling nodes.
   HeapVector<Member<NodeToIdMap>> dangling_node_to_id_maps_;
diff --git a/third_party/blink/renderer/core/inspector/inspector_overlay_agent.cc b/third_party/blink/renderer/core/inspector/inspector_overlay_agent.cc
index a130efe..664a49d 100644
--- a/third_party/blink/renderer/core/inspector/inspector_overlay_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_overlay_agent.cc
@@ -389,6 +389,7 @@
   visitor->Trace(dom_agent_);
   visitor->Trace(inspect_tool_);
   visitor->Trace(hinge_);
+  visitor->Trace(document_to_ax_context_);
   InspectorBaseAgent::Trace(visitor);
 }
 
@@ -421,9 +422,26 @@
   }
   backend_node_id_to_inspect_ = 0;
   SetNeedsUnbufferedInput(true);
+  dom_agent_->AddDOMListener(this);
+  for (Document* document : dom_agent_->Documents())
+    DidAddDocument(document);
+
   return Response::Success();
 }
 
+void InspectorOverlayAgent::DidAddDocument(Document* document) {
+  auto context = std::make_unique<AXContext>(*document);
+  document_to_ax_context_.Set(document, std::move(context));
+}
+
+void InspectorOverlayAgent::DidRemoveDocument(Document* document) {
+  document_to_ax_context_.erase(document);
+}
+
+void InspectorOverlayAgent::WillRemoveDOMNode(Node* node) {}
+
+void InspectorOverlayAgent::DidModifyDOMAttr(Element* element) {}
+
 Response InspectorOverlayAgent::disable() {
   enabled_.Clear();
   setShowAdHighlights(false);
@@ -450,6 +468,8 @@
   frame_resource_name_ = 0;
   PickTheRightTool();
   SetNeedsUnbufferedInput(false);
+  dom_agent_->RemoveDOMListener(this);
+  document_to_ax_context_.clear();
   return Response::Success();
 }
 
@@ -855,7 +875,7 @@
                         frame_overlay_->GetDelegate())
                         ->GetLayer();
   }
-  return layer == frame_overlay_->GetGraphicsLayer()->CcLayer();
+  return layer == &frame_overlay_->GetGraphicsLayer()->CcLayer();
 }
 
 LocalFrame* InspectorOverlayAgent::GetFrame() const {
diff --git a/third_party/blink/renderer/core/inspector/inspector_overlay_agent.h b/third_party/blink/renderer/core/inspector/inspector_overlay_agent.h
index d8fff418..be4973a 100644
--- a/third_party/blink/renderer/core/inspector/inspector_overlay_agent.h
+++ b/third_party/blink/renderer/core/inspector/inspector_overlay_agent.h
@@ -35,9 +35,12 @@
 #include "base/memory/scoped_refptr.h"
 #include "third_party/blink/public/common/input/web_input_event.h"
 #include "third_party/blink/public/platform/web_input_event_result.h"
+#include "third_party/blink/renderer/core/accessibility/ax_context.h"
 #include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/dom_node_ids.h"
 #include "third_party/blink/renderer/core/inspector/inspector_base_agent.h"
+#include "third_party/blink/renderer/core/inspector/inspector_dom_agent.h"
 #include "third_party/blink/renderer/core/inspector/inspector_highlight.h"
 #include "third_party/blink/renderer/core/inspector/inspector_overlay_host.h"
 #include "third_party/blink/renderer/core/inspector/protocol/Overlay.h"
@@ -126,6 +129,7 @@
 
 class CORE_EXPORT InspectorOverlayAgent final
     : public InspectorBaseAgent<protocol::Overlay::Metainfo>,
+      public InspectorDOMAgent::DOMListener,
       public InspectorOverlayHost::Delegate {
  public:
   static std::unique_ptr<InspectorGridHighlightConfig> ToGridHighlightConfig(
@@ -233,6 +237,12 @@
   // InspectorOverlayHost::Delegate implementation.
   void Dispatch(const String& message) override;
 
+  // InspectorDOMAgent::DOMListener implementation
+  void DidAddDocument(Document*) override;
+  void DidRemoveDocument(Document*) override;
+  void WillRemoveDOMNode(Node*) override;
+  void DidModifyDOMAttr(Element*) override;
+
   bool IsEmpty();
 
   LocalFrame* OverlayMainFrame();
@@ -272,6 +282,10 @@
   std::unique_ptr<FrameOverlay> frame_overlay_;
   Member<InspectTool> inspect_tool_;
   Member<Hinge> hinge_;
+  // The agent needs to keep AXContext because it enables caching of
+  // a11y attributes shown in the inspector overlay.
+  HeapHashMap<Member<Document>, std::unique_ptr<AXContext>>
+      document_to_ax_context_;
   bool swallow_next_mouse_up_;
   DOMNodeId backend_node_id_to_inspect_;
   InspectorAgentState::Boolean enabled_;
diff --git a/third_party/blink/renderer/core/inspector/inspector_trace_events.cc b/third_party/blink/renderer/core/inspector/inspector_trace_events.cc
index 4bb4e61..69f427e 100644
--- a/third_party/blink/renderer/core/inspector/inspector_trace_events.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_trace_events.cc
@@ -1067,7 +1067,7 @@
   LocalToPageQuad(*layout_object, clip_rect, &quad);
   CreateQuad(value.get(), "clip", quad);
   SetGeneratingNodeInfo(value.get(), layout_object, "nodeId");
-  int graphics_layer_id = graphics_layer ? graphics_layer->CcLayer()->id() : 0;
+  int graphics_layer_id = graphics_layer ? graphics_layer->CcLayer().id() : 0;
   value->SetInteger("layerId", graphics_layer_id);
   SetCallStack(value.get());
   return value;
diff --git a/third_party/blink/renderer/core/loader/frame_loader.cc b/third_party/blink/renderer/core/loader/frame_loader.cc
index b9b4ead3..a035565f 100644
--- a/third_party/blink/renderer/core/loader/frame_loader.cc
+++ b/third_party/blink/renderer/core/loader/frame_loader.cc
@@ -991,6 +991,12 @@
       navigation_params->origin_policy, last_origin_window_csp_.Release(),
       commit_reason);
 
+  for (auto& csp : navigation_params->forced_content_security_policies) {
+    content_security_policy->AddPolicyFromHeaderValue(
+        csp, network::mojom::ContentSecurityPolicyType::kEnforce,
+        network::mojom::ContentSecurityPolicySource::kHTTP);
+  }
+
   base::Optional<Document::UnloadEventTiming> unload_timing;
   FrameSwapScope frame_swap_scope(frame_owner);
   {
@@ -1547,7 +1553,8 @@
     const FetchClientSettingsObject* fetch_client_settings_object,
     LocalDOMWindow* window_for_logging,
     mojom::RequestContextFrameType frame_type) const {
-  if (!RequiredCSP().IsEmpty()) {
+  if (!base::FeatureList::IsEnabled(network::features::kOutOfBlinkCSPEE) &&
+      !RequiredCSP().IsEmpty()) {
     DCHECK(
         ContentSecurityPolicy::IsValidCSPAttr(RequiredCSP().GetString(), ""));
     resource_request.SetHttpHeaderField(http_names::kSecRequiredCSP,
diff --git a/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc b/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
index 8302e1f..c66e94d 100644
--- a/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
+++ b/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
@@ -254,7 +254,7 @@
     owner = &GetLayoutObject().GetDocument();
   }
   if (owner) {
-    graphics_layer->CcLayer()->SetFrameElementId(
+    graphics_layer->CcLayer().SetFrameElementId(
         CompositorElementIdFromUniqueObjectId(
             DOMNodeIds::IdForNode(owner),
             CompositorElementIdNamespace::kDOMNodeId));
@@ -301,7 +301,7 @@
     // Determine whether the external texture layer covers the whole graphics
     // layer. This may not be the case if there are box decorations or
     // shadows.
-    if (layer && layer->bounds() == graphics_layer_->CcLayer()->bounds()) {
+    if (layer && layer->bounds() == graphics_layer_->CcLayer().bounds()) {
       // Determine whether the rendering context's external texture layer is
       // opaque.
       if (!context->CreationAttributes().alpha) {
@@ -361,11 +361,11 @@
   bool transformed_rasterization_allowed =
       !(owning_layer_.GetCompositingReasons() &
         CompositingReason::kComboTransformedRasterizationDisallowedReasons);
-  graphics_layer_->CcLayer()->SetTransformedRasterizationAllowed(
+  graphics_layer_->CcLayer().SetTransformedRasterizationAllowed(
       transformed_rasterization_allowed);
   if (non_scrolling_squashing_layer_) {
     non_scrolling_squashing_layer_->CcLayer()
-        ->SetTransformedRasterizationAllowed(true);
+        .SetTransformedRasterizationAllowed(true);
   }
 }
 
@@ -1085,7 +1085,7 @@
 
     if (scrolling_contents_layer_ &&
         scrollable_area->NeedsShowScrollbarLayers()) {
-      scrolling_contents_layer_->CcLayer()->ShowScrollbars();
+      scrolling_contents_layer_->CcLayer().ShowScrollbars();
       scrollable_area->DidShowScrollbarLayers();
     }
   }
@@ -1248,7 +1248,7 @@
           CompositorElementIdNamespace::kEffectMask);
       mask_layer_->SetElementId(element_id);
       if (GetLayoutObject().HasNonInitialBackdropFilter())
-        mask_layer_->CcLayer()->SetIsBackdropFilterMask(true);
+        mask_layer_->CcLayer().SetIsBackdropFilterMask(true);
       mask_layer_->SetHitTestable(true);
       layer_changed = true;
     }
diff --git a/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping_test.cc b/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping_test.cc
index 1e98f165..d1ca418 100644
--- a/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping_test.cc
+++ b/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping_test.cc
@@ -1343,7 +1343,7 @@
         target_layer ? target_layer->GraphicsLayerBacking() : nullptr;
     ASSERT_TRUE(target_graphics_layer);
     EXPECT_FALSE(
-        target_graphics_layer->CcLayer()->transformed_rasterization_allowed());
+        target_graphics_layer->CcLayer().transformed_rasterization_allowed());
   }
   {
     LayoutObject* target = GetLayoutObjectByElementId("target2");
@@ -1353,7 +1353,7 @@
         target_layer ? target_layer->GraphicsLayerBacking() : nullptr;
     ASSERT_TRUE(target_graphics_layer);
     EXPECT_FALSE(
-        target_graphics_layer->CcLayer()->transformed_rasterization_allowed());
+        target_graphics_layer->CcLayer().transformed_rasterization_allowed());
   }
 }
 
@@ -1376,7 +1376,7 @@
       target_layer ? target_layer->GraphicsLayerBacking() : nullptr;
   ASSERT_TRUE(target_graphics_layer);
   EXPECT_TRUE(
-      target_graphics_layer->CcLayer()->transformed_rasterization_allowed());
+      target_graphics_layer->CcLayer().transformed_rasterization_allowed());
 }
 
 TEST_F(CompositedLayerMappingTest,
@@ -1399,7 +1399,7 @@
       target_layer ? target_layer->GraphicsLayerBacking() : nullptr;
   ASSERT_TRUE(target_graphics_layer);
   EXPECT_TRUE(
-      target_graphics_layer->CcLayer()->transformed_rasterization_allowed());
+      target_graphics_layer->CcLayer().transformed_rasterization_allowed());
 }
 
 TEST_F(CompositedLayerMappingTest,
@@ -1417,7 +1417,7 @@
       target_layer ? target_layer->GraphicsLayerBacking() : nullptr;
   ASSERT_TRUE(target_graphics_layer);
   EXPECT_TRUE(
-      target_graphics_layer->CcLayer()->transformed_rasterization_allowed());
+      target_graphics_layer->CcLayer().transformed_rasterization_allowed());
 }
 
 TEST_F(CompositedLayerMappingTest,
@@ -1433,7 +1433,7 @@
       target_layer ? target_layer->GraphicsLayerBacking() : nullptr;
   ASSERT_TRUE(target_graphics_layer);
   EXPECT_TRUE(
-      target_graphics_layer->CcLayer()->transformed_rasterization_allowed());
+      target_graphics_layer->CcLayer().transformed_rasterization_allowed());
 }
 
 TEST_F(CompositedLayerMappingTest, ScrollingContainerBoundsChange) {
@@ -1676,15 +1676,15 @@
   auto* box = ToLayoutBoxModelObject(GetLayoutObjectByElementId("target"));
   auto* mapping = box->Layer()->GetCompositedLayerMapping();
 
-  const auto* layer = mapping->MainGraphicsLayer()->CcLayer();
+  const auto& layer = mapping->MainGraphicsLayer()->CcLayer();
   auto expected = gfx::Rect(0, 0, 100, 100);
-  EXPECT_EQ(layer->touch_action_region().GetAllRegions().bounds(), expected);
+  EXPECT_EQ(layer.touch_action_region().GetAllRegions().bounds(), expected);
 
   EXPECT_TRUE(mapping->MainGraphicsLayer()->PaintsHitTest());
 
   // The only painted content for the main graphics layer is the touch-action
   // rect which is not sent to cc, so the cc::layer should not draw content.
-  EXPECT_FALSE(layer->DrawsContent());
+  EXPECT_FALSE(layer.DrawsContent());
   EXPECT_FALSE(mapping->MainGraphicsLayer()->DrawsContent());
 }
 
@@ -1886,10 +1886,10 @@
   Element* child = GetDocument().getElementById("child");
   PaintLayer* child_paint_layer =
       ToLayoutBoxModelObject(child->GetLayoutObject())->Layer();
-  auto* child_layer = child_paint_layer->GraphicsLayerBacking()->CcLayer();
-  EXPECT_TRUE(child_layer->frame_element_id());
+  auto& child_layer = child_paint_layer->GraphicsLayerBacking()->CcLayer();
+  EXPECT_TRUE(child_layer.frame_element_id());
 
-  EXPECT_EQ(child_layer->frame_element_id(),
+  EXPECT_EQ(child_layer.frame_element_id(),
             CompositorElementIdFromUniqueObjectId(
                 DOMNodeIds::IdForNode(&GetDocument()),
                 CompositorElementIdNamespace::kDOMNodeId));
@@ -1901,11 +1901,11 @@
   EXPECT_TRUE(subframe);
   PaintLayer* subframe_paint_layer =
       ToLayoutBoxModelObject(subframe->GetLayoutObject())->Layer();
-  auto* subframe_layer =
+  auto& subframe_layer =
       subframe_paint_layer->GraphicsLayerBacking()->CcLayer();
-  EXPECT_TRUE(subframe_layer->frame_element_id());
+  EXPECT_TRUE(subframe_layer.frame_element_id());
 
-  EXPECT_EQ(subframe_layer->frame_element_id(),
+  EXPECT_EQ(subframe_layer.frame_element_id(),
             CompositorElementIdFromUniqueObjectId(
                 DOMNodeIds::IdForNode(subframe->contentDocument()),
                 CompositorElementIdNamespace::kDOMNodeId));
diff --git a/third_party/blink/renderer/core/paint/compositing/compositing_test.cc b/third_party/blink/renderer/core/paint/compositing/compositing_test.cc
index b3e9c6c..9a49901 100644
--- a/third_party/blink/renderer/core/paint/compositing/compositing_test.cc
+++ b/third_party/blink/renderer/core/paint/compositing/compositing_test.cc
@@ -1212,21 +1212,22 @@
 
   auto* grouped_mapping =
       GetElementById("topleft")->GetLayoutBox()->Layer()->GroupedMapping();
-  auto* squashed_layer =
+  ASSERT_TRUE(grouped_mapping);
+  ASSERT_TRUE(grouped_mapping->NonScrollingSquashingLayer());
+  auto& squashing_layer =
       grouped_mapping->NonScrollingSquashingLayer()->CcLayer();
-  ASSERT_NE(nullptr, squashed_layer);
 
   // Top left and bottom right are squashed.
   // This squashed layer should not be opaque, as it is squashing two squares
   // with some gaps between them.
-  EXPECT_FALSE(squashed_layer->contents_opaque());
+  EXPECT_FALSE(squashing_layer.contents_opaque());
   // This shouldn't DCHECK.
-  squashed_layer->SafeOpaqueBackgroundColor();
+  squashing_layer.SafeOpaqueBackgroundColor();
   // Because contents_opaque is false, the SafeOpaqueBackgroundColor() getter
   // will return SK_ColorTRANSPARENT. So we need to grab the actual color,
   // to make sure it's right.
   SkColor squashed_bg_color =
-      squashed_layer->ActualSafeOpaqueBackgroundColorForTesting();
+      squashing_layer.ActualSafeOpaqueBackgroundColorForTesting();
   // The squashed layer should have a non-transparent safe opaque background
   // color, that isn't blue. Exactly which color it is depends on heuristics,
   // but it should be one of the two colors of the elements that created it.
diff --git a/third_party/blink/renderer/core/paint/compositing/graphics_layer_tree_as_text.cc b/third_party/blink/renderer/core/paint/compositing/graphics_layer_tree_as_text.cc
index bea6453..f8c6758 100644
--- a/third_party/blink/renderer/core/paint/compositing/graphics_layer_tree_as_text.cc
+++ b/third_party/blink/renderer/core/paint/compositing/graphics_layer_tree_as_text.cc
@@ -45,7 +45,7 @@
                    PointAsJSONArray(layer->GetOffsetFromTransformNode()));
   }
 
-  layer->AppendAdditionalInfoAsJSON(flags, *layer->CcLayer(), *json.get());
+  layer->AppendAdditionalInfoAsJSON(flags, layer->CcLayer(), *json.get());
 
   return json;
 }
diff --git a/third_party/blink/renderer/core/paint/paint_and_raster_invalidation_test.cc b/third_party/blink/renderer/core/paint/paint_and_raster_invalidation_test.cc
index 8a5f1bd3..7803daa8 100644
--- a/third_party/blink/renderer/core/paint/paint_and_raster_invalidation_test.cc
+++ b/third_party/blink/renderer/core/paint/paint_and_raster_invalidation_test.cc
@@ -79,14 +79,13 @@
     <div id="target"></div>
   )HTML");
   auto* target = GetDocument().getElementById("target");
-  auto* cc_layer =
+  auto& cc_layer =
       RuntimeEnabledFeatures::CompositeAfterPaintEnabled()
-          ? GetDocument()
-                .View()
-                ->GetPaintArtifactCompositor()
-                ->RootLayer()
-                ->children()[1]
-                .get()
+          ? *GetDocument()
+                 .View()
+                 ->GetPaintArtifactCompositor()
+                 ->RootLayer()
+                 ->children()[1]
           : GetLayoutView().Layer()->GraphicsLayerBacking()->CcLayer();
 
   {
@@ -94,20 +93,20 @@
 
     target->setAttribute(html_names::kStyleAttr, "height: 200px");
     UpdateAllLifecyclePhasesForTest();
-    ASSERT_TRUE(cc_layer->debug_info());
-    EXPECT_EQ(1u, cc_layer->debug_info()->invalidations.size());
+    ASSERT_TRUE(cc_layer.debug_info());
+    EXPECT_EQ(1u, cc_layer.debug_info()->invalidations.size());
 
     target->setAttribute(html_names::kStyleAttr, "height: 200px; width: 200px");
     UpdateAllLifecyclePhasesForTest();
-    ASSERT_TRUE(cc_layer->debug_info());
-    EXPECT_EQ(2u, cc_layer->debug_info()->invalidations.size());
+    ASSERT_TRUE(cc_layer.debug_info());
+    EXPECT_EQ(2u, cc_layer.debug_info()->invalidations.size());
   }
 
   target->setAttribute(html_names::kStyleAttr, "height: 300px; width: 300px");
   UpdateAllLifecyclePhasesForTest();
-  ASSERT_TRUE(cc_layer->debug_info());
+  ASSERT_TRUE(cc_layer.debug_info());
   // No new invalidations tracked.
-  EXPECT_EQ(2u, cc_layer->debug_info()->invalidations.size());
+  EXPECT_EQ(2u, cc_layer.debug_info()->invalidations.size());
 }
 
 TEST_P(PaintAndRasterInvalidationTest, IncrementalInvalidationExpand) {
diff --git a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
index 88eafe7..f3bd433 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
@@ -284,7 +284,7 @@
 
 cc::Layer* PaintLayerScrollableArea::LayerForScrolling() const {
   if (auto* graphics_layer = GraphicsLayerForScrolling())
-    return graphics_layer->CcLayer();
+    return &graphics_layer->CcLayer();
   return nullptr;
 }
 
@@ -302,7 +302,7 @@
 
 cc::Layer* PaintLayerScrollableArea::LayerForScrollCorner() const {
   if (auto* graphics_layer = GraphicsLayerForScrollCorner())
-    return graphics_layer->CcLayer();
+    return &graphics_layer->CcLayer();
   return nullptr;
 }
 
diff --git a/third_party/blink/renderer/core/testing/fake_local_frame_host.cc b/third_party/blink/renderer/core/testing/fake_local_frame_host.cc
index 56299a0f..b381726 100644
--- a/third_party/blink/renderer/core/testing/fake_local_frame_host.cc
+++ b/third_party/blink/renderer/core/testing/fake_local_frame_host.cc
@@ -188,6 +188,10 @@
 void FakeLocalFrameHost::DidChangeOpener(
     const base::Optional<base::UnguessableToken>& opener_frame) {}
 
+void FakeLocalFrameHost::DidChangeCSPAttribute(
+    const base::UnguessableToken& child_frame_token,
+    network::mojom::blink::ContentSecurityPolicyPtr) {}
+
 void FakeLocalFrameHost::DidChangeFramePolicy(
     const base::UnguessableToken& child_frame_token,
     const FramePolicy& frame_policy) {}
diff --git a/third_party/blink/renderer/core/testing/fake_local_frame_host.h b/third_party/blink/renderer/core/testing/fake_local_frame_host.h
index b174b9e1..a34a906c 100644
--- a/third_party/blink/renderer/core/testing/fake_local_frame_host.h
+++ b/third_party/blink/renderer/core/testing/fake_local_frame_host.h
@@ -118,6 +118,9 @@
       mojom::blink::FrameOwnerPropertiesPtr frame_owner_properties) override;
   void DidChangeOpener(
       const base::Optional<base::UnguessableToken>& opener_frame) override;
+  void DidChangeCSPAttribute(const base::UnguessableToken& child_frame_token,
+                             network::mojom::blink::ContentSecurityPolicyPtr
+                                 parsed_csp_attribute) override;
   void DidChangeFramePolicy(const base::UnguessableToken& child_frame_token,
                             const FramePolicy& frame_policy) override;
   void CapturePaintPreviewOfSubframe(
diff --git a/third_party/blink/renderer/modules/native_io/native_io_file.cc b/third_party/blink/renderer/modules/native_io/native_io_file.cc
index c26d6076..5943495 100644
--- a/third_party/blink/renderer/modules/native_io/native_io_file.cc
+++ b/third_party/blink/renderer/modules/native_io/native_io_file.cc
@@ -231,6 +231,38 @@
   return resolver->Promise();
 }
 
+ScriptPromise NativeIOFile::flush(ScriptState* script_state,
+                                  ExceptionState& exception_state) {
+  // This implementation of flush attempts to physically store the data it has
+  // written on disk. This behaviour might change in the future in order to
+  // support more performant but less reliable persistency guarantees.
+  if (io_pending_) {
+    exception_state.ThrowDOMException(
+        DOMExceptionCode::kInvalidStateError,
+        "Another I/O operation is in progress on the same file");
+    return ScriptPromise();
+  }
+  if (closed_) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                      "The file was already closed");
+    return ScriptPromise();
+  }
+  io_pending_ = true;
+
+  auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
+  // CrossThreadUnretained() is safe here because the NativeIOFile::FileState
+  // instance is owned by this NativeIOFile, which is also passed to the task
+  // via WrapCrossThreadPersistent. Therefore, the FileState instance is
+  // guaranteed to remain alive during the task's execution.
+  worker_pool::PostTask(
+      FROM_HERE, {base::MayBlock(), base::ThreadPool()},
+      CrossThreadBindOnce(&DoFlush, WrapCrossThreadPersistent(this),
+                          WrapCrossThreadPersistent(resolver),
+                          CrossThreadUnretained(file_state_.get()),
+                          resolver_task_runner_));
+  return resolver->Promise();
+}
+
 void NativeIOFile::Trace(Visitor* visitor) const {
   ScriptWrappable::Trace(visitor);
   visitor->Trace(queued_close_resolver_);
@@ -462,6 +494,49 @@
   resolver->Resolve(written_bytes);
 }
 
+// static
+void NativeIOFile::DoFlush(
+    CrossThreadPersistent<NativeIOFile> native_io_file,
+    CrossThreadPersistent<ScriptPromiseResolver> resolver,
+    NativeIOFile::FileState* file_state,
+    scoped_refptr<base::SequencedTaskRunner> resolver_task_runner) {
+  DCHECK(!IsMainThread()) << "File I/O should not happen on the main thread";
+  bool success = false;
+  {
+    WTF::MutexLocker mutex_locker(file_state->mutex);
+    DCHECK(file_state->file.IsValid())
+        << "file I/O operation queued after file closed";
+    success = file_state->file.Flush();
+  }
+
+  PostCrossThreadTask(
+      *resolver_task_runner, FROM_HERE,
+      CrossThreadBindOnce(&NativeIOFile::DidFlush, std::move(native_io_file),
+                          std::move(resolver), success));
+}
+
+void NativeIOFile::DidFlush(
+    CrossThreadPersistent<ScriptPromiseResolver> resolver,
+    bool success) {
+  ScriptState* script_state = resolver->GetScriptState();
+  if (!script_state->ContextIsValid())
+    return;
+  ScriptState::Scope scope(script_state);
+
+  DCHECK(io_pending_) << "I/O operation performed without io_pending_ set";
+  io_pending_ = false;
+
+  DispatchQueuedClose();
+
+  if (!success) {
+    resolver->Reject(V8ThrowDOMException::CreateOrEmpty(
+        script_state->GetIsolate(), DOMExceptionCode::kOperationError,
+        "flush() failed"));
+    return;
+  }
+  resolver->Resolve();
+}
+
 void NativeIOFile::CloseBackingFile() {
   closed_ = true;
   file_state_->mutex.lock();
diff --git a/third_party/blink/renderer/modules/native_io/native_io_file.h b/third_party/blink/renderer/modules/native_io/native_io_file.h
index d91a8aa..7a47e2a 100644
--- a/third_party/blink/renderer/modules/native_io/native_io_file.h
+++ b/third_party/blink/renderer/modules/native_io/native_io_file.h
@@ -56,6 +56,7 @@
                       MaybeShared<DOMArrayBufferView> buffer,
                       uint64_t file_offset,
                       ExceptionState&);
+  ScriptPromise flush(ScriptState*, ExceptionState&);
 
   // GarbageCollected
   void Trace(Visitor* visitor) const override;
@@ -124,6 +125,16 @@
                 int written_bytes,
                 base::File::Error write_error);
 
+  // Performs the file I/O part of flush().
+  static void DoFlush(
+      CrossThreadPersistent<NativeIOFile> native_io_file,
+      CrossThreadPersistent<ScriptPromiseResolver> resolver,
+      NativeIOFile::FileState* file_state,
+      scoped_refptr<base::SequencedTaskRunner> file_task_runner);
+  // Performs the post file-I/O part of flush(), on the main thread.
+  void DidFlush(CrossThreadPersistent<ScriptPromiseResolver> resolver,
+                bool success);
+
   // Kicks off closing the file from the main thread.
   void CloseBackingFile();
 
diff --git a/third_party/blink/renderer/modules/native_io/native_io_file.idl b/third_party/blink/renderer/modules/native_io/native_io_file.idl
index 65594a2..38dba604 100644
--- a/third_party/blink/renderer/modules/native_io/native_io_file.idl
+++ b/third_party/blink/renderer/modules/native_io/native_io_file.idl
@@ -23,4 +23,7 @@
     CallWith=ScriptState, RaisesException
   ] Promise<unsigned long long> write([AllowShared] ArrayBufferView buffer,
                                       unsigned long long file_offset);
+  [
+    CallWith=ScriptState, RaisesException
+  ] Promise<void> flush();
 };
diff --git a/third_party/blink/renderer/modules/native_io/native_io_file_sync.cc b/third_party/blink/renderer/modules/native_io/native_io_file_sync.cc
index 6cf4151..f06f36a8 100644
--- a/third_party/blink/renderer/modules/native_io/native_io_file_sync.cc
+++ b/third_party/blink/renderer/modules/native_io/native_io_file_sync.cc
@@ -105,6 +105,22 @@
   return base::as_unsigned(written_bytes);
 }
 
+void NativeIOFileSync::flush(ExceptionState& exception_state) {
+  // This implementation of flush attempts to physically store the data it has
+  // written on disk. This behaviour might change in the future.
+  if (!backing_file_.IsValid()) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                      "The file was already closed");
+    return;
+  }
+  bool success = backing_file_.Flush();
+  if (!success) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kOperationError,
+                                      "flush() failed");
+  }
+  return;
+}
+
 void NativeIOFileSync::Trace(Visitor* visitor) const {
   visitor->Trace(backend_file_);
   ScriptWrappable::Trace(visitor);
diff --git a/third_party/blink/renderer/modules/native_io/native_io_file_sync.h b/third_party/blink/renderer/modules/native_io/native_io_file_sync.h
index d9f02f2..8f4567f 100644
--- a/third_party/blink/renderer/modules/native_io/native_io_file_sync.h
+++ b/third_party/blink/renderer/modules/native_io/native_io_file_sync.h
@@ -45,6 +45,7 @@
   uint64_t write(MaybeShared<DOMArrayBufferView> buffer,
                  uint64_t file_offset,
                  ExceptionState&);
+  void flush(ExceptionState&);
 
   // GarbageCollected
   void Trace(Visitor* visitor) const override;
diff --git a/third_party/blink/renderer/modules/native_io/native_io_file_sync.idl b/third_party/blink/renderer/modules/native_io/native_io_file_sync.idl
index 4be4baf..f4378bc7 100644
--- a/third_party/blink/renderer/modules/native_io/native_io_file_sync.idl
+++ b/third_party/blink/renderer/modules/native_io/native_io_file_sync.idl
@@ -23,4 +23,7 @@
     RaisesException
   ] unsigned long long write([AllowShared] ArrayBufferView buffer,
                              unsigned long long file_offset);
+  [
+    RaisesException
+  ] void flush();
 };
diff --git a/third_party/blink/renderer/modules/navigatorcontentutils/navigator_content_utils.cc b/third_party/blink/renderer/modules/navigatorcontentutils/navigator_content_utils.cc
index d5eb163..361d113 100644
--- a/third_party/blink/renderer/modules/navigatorcontentutils/navigator_content_utils.cc
+++ b/third_party/blink/renderer/modules/navigatorcontentutils/navigator_content_utils.cc
@@ -87,9 +87,7 @@
 static bool VerifyCustomHandlerURL(const LocalDOMWindow& window,
                                    const String& user_url,
                                    ExceptionState& exception_state) {
-  String new_url = user_url;
-  new_url.Remove(user_url.Find(kToken), base::size(kToken) - 1);
-  KURL full_url = window.CompleteURL(new_url);
+  KURL full_url = window.CompleteURL(user_url);
   KURL base_url = window.BaseURL();
   String error_message;
 
diff --git a/third_party/blink/renderer/platform/fonts/mac/font_matcher_mac.mm b/third_party/blink/renderer/platform/fonts/mac/font_matcher_mac.mm
index cf25ba02..aafddac 100644
--- a/third_party/blink/renderer/platform/fonts/mac/font_matcher_mac.mm
+++ b/third_party/blink/renderer/platform/fonts/mac/font_matcher_mac.mm
@@ -32,6 +32,8 @@
 #import <AppKit/AppKit.h>
 #import <Foundation/Foundation.h>
 #import <math.h>
+
+#include "base/bit_cast.h"
 #include "base/mac/foundation_util.h"
 #include "base/mac/scoped_cftyperef.h"
 #include "base/mac/scoped_nsobject.h"
@@ -59,16 +61,15 @@
       0x3fe3d70a40000000,  // NSFontWeightBlack
   };
   if (font_weight <= 50 || font_weight >= 950)
-    return ns_font_weights[3];
+    return bit_cast<CGFloat>(ns_font_weights[3]);
 
   size_t select_weight = roundf(font_weight / 100) - 1;
   DCHECK_GE(select_weight, 0ul);
   DCHECK_LE(select_weight, base::size(ns_font_weights));
-  CGFloat* return_weight =
-      reinterpret_cast<CGFloat*>(&ns_font_weights[select_weight]);
-  return *return_weight;
+  return bit_cast<CGFloat>(ns_font_weights[select_weight]);
 }
-}
+
+}  // namespace
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/platform/graphics/compositing/layers_as_json.cc b/third_party/blink/renderer/platform/graphics/compositing/layers_as_json.cc
index 2adb8011..a356a43 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/layers_as_json.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/layers_as_json.cc
@@ -25,48 +25,48 @@
 }  // namespace
 
 // Create a JSON version of the specified |layer|.
-std::unique_ptr<JSONObject> CCLayerAsJSON(const cc::Layer* layer,
+std::unique_ptr<JSONObject> CCLayerAsJSON(const cc::Layer& layer,
                                           LayerTreeFlags flags) {
   auto json = std::make_unique<JSONObject>();
 
   if (flags & kLayerTreeIncludesDebugInfo) {
-    json->SetString("this", PointerAsString(layer));
-    json->SetInteger("ccLayerId", layer->id());
+    json->SetString("this", PointerAsString(&layer));
+    json->SetInteger("ccLayerId", layer.id());
   }
 
-  json->SetString("name", String(layer->DebugName().c_str()));
+  json->SetString("name", String(layer.DebugName().c_str()));
 
-  if (layer->offset_to_transform_parent() != gfx::Vector2dF()) {
+  if (layer.offset_to_transform_parent() != gfx::Vector2dF()) {
     json->SetArray(
         "position",
-        PointAsJSONArray(FloatPoint(layer->offset_to_transform_parent())));
+        PointAsJSONArray(FloatPoint(layer.offset_to_transform_parent())));
   }
 
   // This is testing against gfx::Size(), *not* whether the size is empty.
-  if (layer->bounds() != gfx::Size())
-    json->SetArray("bounds", SizeAsJSONArray(IntSize(layer->bounds())));
+  if (layer.bounds() != gfx::Size())
+    json->SetArray("bounds", SizeAsJSONArray(IntSize(layer.bounds())));
 
-  if (layer->contents_opaque())
+  if (layer.contents_opaque())
     json->SetBoolean("contentsOpaque", true);
-  else if (layer->contents_opaque_for_text())
+  else if (layer.contents_opaque_for_text())
     json->SetBoolean("contentsOpaqueForText", true);
 
-  if (!layer->DrawsContent())
+  if (!layer.DrawsContent())
     json->SetBoolean("drawsContent", false);
 
-  if (layer->should_check_backface_visibility())
+  if (layer.should_check_backface_visibility())
     json->SetString("backfaceVisibility", "hidden");
 
-  if (Color(layer->background_color()).Alpha()) {
+  if (Color(layer.background_color()).Alpha()) {
     json->SetString("backgroundColor",
-                    Color(layer->background_color()).NameForLayoutTreeAsText());
+                    Color(layer.background_color()).NameForLayoutTreeAsText());
   }
 
   if (flags &
       (kLayerTreeIncludesDebugInfo | kLayerTreeIncludesCompositingReasons)) {
-    if (layer->debug_info()) {
+    if (layer.debug_info()) {
       auto compositing_reasons_json = std::make_unique<JSONArray>();
-      for (const char* name : layer->debug_info()->compositing_reasons)
+      for (const char* name : layer.debug_info()->compositing_reasons)
         compositing_reasons_json->PushString(name);
       json->SetArray("compositingReasons", std::move(compositing_reasons_json));
     }
@@ -138,7 +138,7 @@
        layer.DebugName() == "Scrolling Contents Layer"))
     return;
 
-  auto layer_json = CCLayerAsJSON(&layer, flags_);
+  auto layer_json = CCLayerAsJSON(layer, flags_);
   if (json_client) {
     json_client->AppendAdditionalInfoAsJSON(flags_, layer, *(layer_json.get()));
   }
diff --git a/third_party/blink/renderer/platform/graphics/compositing/layers_as_json.h b/third_party/blink/renderer/platform/graphics/compositing/layers_as_json.h
index 4be8eed2..aaa88b5d 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/layers_as_json.h
+++ b/third_party/blink/renderer/platform/graphics/compositing/layers_as_json.h
@@ -67,7 +67,7 @@
 };
 
 PLATFORM_EXPORT std::unique_ptr<JSONObject> CCLayerAsJSON(
-    const cc::Layer* layer,
+    const cc::Layer& layer,
     LayerTreeFlags flags);
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
index 59c328ef..562a1c8d 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
@@ -118,7 +118,7 @@
       if (display_item.IsGraphicsLayerWrapper()) {
         const GraphicsLayerDisplayItem& graphics_layer_display_item =
             static_cast<const GraphicsLayerDisplayItem&>(display_item);
-        layer = graphics_layer_display_item.GetGraphicsLayer().CcLayer();
+        layer = &graphics_layer_display_item.GetGraphicsLayer().CcLayer();
         json_client = &graphics_layer_display_item.GetGraphicsLayer();
       } else {
         DCHECK(display_item.IsForeignLayer());
@@ -176,7 +176,7 @@
       DCHECK(RuntimeEnabledFeatures::CompositeSVGEnabled());
       return nullptr;
     }
-    layer = graphics_layer.CcLayer();
+    layer = &graphics_layer.CcLayer();
     layer_offset = FloatPoint(graphics_layer_display_item.GetGraphicsLayer()
                                   .GetOffsetFromTransformNode());
   } else {
@@ -363,7 +363,7 @@
 }
 
 void PaintArtifactCompositor::UpdateTouchActionRects(
-    cc::Layer* layer,
+    cc::Layer& layer,
     const gfx::Vector2dF& layer_offset,
     const PropertyTreeState& layer_state,
     const PaintChunkSubset& paint_chunks) {
@@ -386,11 +386,11 @@
           (gfx::Rect)EnclosingIntRect(rect.Rect()));
     }
   }
-  layer->SetTouchActionRegion(std::move(touch_action_in_layer_space));
+  layer.SetTouchActionRegion(std::move(touch_action_in_layer_space));
 }
 
 void PaintArtifactCompositor::UpdateNonFastScrollableRegions(
-    cc::Layer* layer,
+    cc::Layer& layer,
     const gfx::Vector2dF& layer_offset,
     const PropertyTreeState& layer_state,
     const PaintChunkSubset& paint_chunks,
@@ -410,7 +410,7 @@
       if (const auto* scroll_translation = hit_test_data->scroll_translation) {
         const auto& scroll_node = *scroll_translation->ScrollNode();
         auto scroll_element_id = scroll_node.GetCompositorElementId();
-        if (layer->element_id() == scroll_element_id)
+        if (layer.element_id() == scroll_element_id)
           continue;
         // Ensure the cc scroll node to prepare for possible descendant nodes
         // referenced by later composited layers. This can't be done by ensuring
@@ -436,7 +436,7 @@
     non_fast_scrollable_regions_in_layer_space.Union(
         EnclosingIntRect(rect.Rect()));
   }
-  layer->SetNonFastScrollableRegion(non_fast_scrollable_regions_in_layer_space);
+  layer.SetNonFastScrollableRegion(non_fast_scrollable_regions_in_layer_space);
 }
 
 bool PaintArtifactCompositor::HasComposited(
@@ -1310,10 +1310,10 @@
     if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
       auto paint_chunks = paint_artifact->GetPaintChunkSubset(
           pending_layer.paint_chunk_indices);
-      UpdateTouchActionRects(layer.get(), layer->offset_to_transform_parent(),
+      UpdateTouchActionRects(*layer, layer->offset_to_transform_parent(),
                              property_state, paint_chunks);
       UpdateNonFastScrollableRegions(
-          layer.get(), layer->offset_to_transform_parent(), property_state,
+          *layer, layer->offset_to_transform_parent(), property_state,
           paint_chunks, &property_tree_manager);
     }
 
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h
index 2b5fb9a..cd528b3 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h
@@ -184,7 +184,7 @@
 
   // Update the cc::Layer's touch action region from the touch action rects of
   // the paint chunks.
-  static void UpdateTouchActionRects(cc::Layer*,
+  static void UpdateTouchActionRects(cc::Layer&,
                                      const gfx::Vector2dF& layer_offset,
                                      const PropertyTreeState& layer_state,
                                      const PaintChunkSubset& paint_chunks);
@@ -192,7 +192,7 @@
   // Update the cc::Layer's non-fast scrollable region from the non-fast regions
   // in the paint chunks.
   static void UpdateNonFastScrollableRegions(
-      cc::Layer*,
+      cc::Layer&,
       const gfx::Vector2dF& layer_offset,
       const PropertyTreeState& layer_state,
       const PaintChunkSubset& paint_chunks,
diff --git a/third_party/blink/renderer/platform/graphics/graphics_layer.cc b/third_party/blink/renderer/platform/graphics/graphics_layer.cc
index 9c3a4814..d5ac3c1 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_layer.cc
+++ b/third_party/blink/renderer/platform/graphics/graphics_layer.cc
@@ -87,14 +87,14 @@
   client.VerifyNotPainting();
 #endif
   layer_ = cc::PictureLayer::Create(this);
-  CcLayer()->SetIsDrawable(draws_content_ && contents_visible_);
-  CcLayer()->SetHitTestable(hit_testable_);
+  layer_->SetIsDrawable(draws_content_ && contents_visible_);
+  layer_->SetHitTestable(hit_testable_);
 
   UpdateTrackingRasterInvalidations();
 }
 
 GraphicsLayer::~GraphicsLayer() {
-  CcLayer()->ClearClient();
+  CcLayer().ClearClient();
   contents_layer_ = nullptr;
 
 #if DCHECK_IS_ON()
@@ -313,7 +313,7 @@
         GetPaintController().AppendDebugDrawingAfterCommit(std::move(record),
                                                            layer_state_->state);
         // Ensure the compositor will raster the under-invalidation overlay.
-        CcLayer()->SetNeedsDisplay();
+        CcLayer().SetNeedsDisplay();
       }
     }
   }
@@ -325,7 +325,7 @@
 void GraphicsLayer::UpdateSafeOpaqueBackgroundColor() {
   if (!DrawsContent())
     return;
-  CcLayer()->SetSafeOpaqueBackgroundColor(
+  CcLayer().SetSafeOpaqueBackgroundColor(
       GetPaintController().GetPaintArtifact().SafeOpaqueBackgroundColor(
           GetPaintController().GetPaintArtifact().PaintChunks()));
 }
@@ -403,12 +403,12 @@
   // shouldn't receive the drawsContent flag, so it is only given
   // contentsVisible.
 
-  CcLayer()->SetIsDrawable(draws_content_ && contents_visible_);
+  CcLayer().SetIsDrawable(draws_content_ && contents_visible_);
   if (contents_layer_)
     contents_layer_->SetIsDrawable(contents_visible_);
 
   if (draws_content_)
-    CcLayer()->SetNeedsDisplay();
+    CcLayer().SetNeedsDisplay();
 }
 
 void GraphicsLayer::UpdateContentsLayerBounds() {
@@ -504,18 +504,18 @@
 }
 
 const gfx::Size& GraphicsLayer::Size() const {
-  return CcLayer()->bounds();
+  return CcLayer().bounds();
 }
 
 void GraphicsLayer::SetSize(const gfx::Size& size) {
   DCHECK(size.width() >= 0 && size.height() >= 0);
 
-  if (size == CcLayer()->bounds())
+  if (size == CcLayer().bounds())
     return;
 
   Invalidate(PaintInvalidationReason::kIncremental);  // as DisplayItemClient.
 
-  CcLayer()->SetBounds(size);
+  CcLayer().SetBounds(size);
   // Note that we don't resize m_contentsLayer. It's up the caller to do that.
 }
 
@@ -547,32 +547,32 @@
 }
 
 RGBA32 GraphicsLayer::BackgroundColor() const {
-  return CcLayer()->background_color();
+  return CcLayer().background_color();
 }
 
 void GraphicsLayer::SetBackgroundColor(RGBA32 color) {
-  CcLayer()->SetBackgroundColor(color);
+  CcLayer().SetBackgroundColor(color);
 }
 
 bool GraphicsLayer::ContentsOpaque() const {
-  return CcLayer()->contents_opaque();
+  return CcLayer().contents_opaque();
 }
 
 void GraphicsLayer::SetContentsOpaque(bool opaque) {
-  CcLayer()->SetContentsOpaque(opaque);
+  CcLayer().SetContentsOpaque(opaque);
   if (contents_layer_ && !prevent_contents_opaque_changes_)
     contents_layer_->SetContentsOpaque(opaque);
 }
 
 void GraphicsLayer::SetContentsOpaqueForText(bool opaque) {
-  CcLayer()->SetContentsOpaqueForText(opaque);
+  CcLayer().SetContentsOpaqueForText(opaque);
 }
 
 void GraphicsLayer::SetHitTestable(bool should_hit_test) {
   if (hit_testable_ == should_hit_test)
     return;
   hit_testable_ = should_hit_test;
-  CcLayer()->SetHitTestable(should_hit_test);
+  CcLayer().SetHitTestable(should_hit_test);
 }
 
 void GraphicsLayer::SetContentsNeedsDisplay() {
@@ -587,7 +587,7 @@
   if (!PaintsContentOrHitTest())
     return;
 
-  CcLayer()->SetNeedsDisplay();
+  CcLayer().SetNeedsDisplay();
 
   // Invalidate the paint controller if it exists, but don't bother creating one
   // if not.
@@ -603,7 +603,7 @@
 
 void GraphicsLayer::SetNeedsDisplayInRect(const IntRect& rect) {
   DCHECK(PaintsContentOrHitTest());
-  CcLayer()->SetNeedsDisplayRect(rect);
+  CcLayer().SetNeedsDisplayRect(rect);
 }
 
 void GraphicsLayer::SetContentsRect(const IntRect& rect) {
@@ -615,10 +615,6 @@
   client_.GraphicsLayersDidChange();
 }
 
-cc::PictureLayer* GraphicsLayer::CcLayer() const {
-  return layer_.get();
-}
-
 void GraphicsLayer::SetPaintingPhase(GraphicsLayerPaintingPhase phase) {
   if (painting_phase_ == phase)
     return;
@@ -634,8 +630,7 @@
 }
 
 void GraphicsLayer::SetElementId(const CompositorElementId& id) {
-  if (cc::Layer* layer = CcLayer())
-    layer->SetElementId(id);
+  CcLayer().SetElementId(id);
 }
 
 sk_sp<PaintRecord> GraphicsLayer::CapturePaintRecord() const {
@@ -674,8 +669,7 @@
         std::make_unique<LayerState>(LayerState{layer_state, layer_offset});
   }
 
-  if (auto* layer = CcLayer())
-    layer->SetSubtreePropertyChanged();
+  CcLayer().SetSubtreePropertyChanged();
   client_.GraphicsLayersDidChange();
 }
 
diff --git a/third_party/blink/renderer/platform/graphics/graphics_layer.h b/third_party/blink/renderer/platform/graphics/graphics_layer.h
index b039fc05..6725517 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_layer.h
+++ b/third_party/blink/renderer/platform/graphics/graphics_layer.h
@@ -173,7 +173,7 @@
   const IntRect& ContentsRect() const { return contents_rect_; }
 
   // For hosting this GraphicsLayer in a native layer hierarchy.
-  cc::PictureLayer* CcLayer() const;
+  cc::PictureLayer& CcLayer() const { return *layer_; }
 
   void UpdateTrackingRasterInvalidations();
   void ResetTrackedRasterInvalidations();
diff --git a/third_party/blink/renderer/platform/graphics/paint/graphics_layer_display_item.cc b/third_party/blink/renderer/platform/graphics/paint/graphics_layer_display_item.cc
index 9f5e9538..eb71a06 100644
--- a/third_party/blink/renderer/platform/graphics/paint/graphics_layer_display_item.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/graphics_layer_display_item.cc
@@ -39,7 +39,7 @@
 #if DCHECK_IS_ON()
 void GraphicsLayerDisplayItem::PropertiesAsJSON(JSONObject& json) const {
   DisplayItem::PropertiesAsJSON(json);
-  json.SetInteger("layer", graphics_layer_.CcLayer()->id());
+  json.SetInteger("layer", graphics_layer_.CcLayer().id());
   FloatPoint offset(graphics_layer_.GetOffsetFromTransformNode());
   json.SetDouble("offset_x", offset.X());
   json.SetDouble("offset_y", offset.Y());
@@ -53,7 +53,7 @@
   // extraneous layers are still attached. In future we will disable all
   // those layer hierarchy code so we won't need this line.
   DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled());
-  graphics_layer.CcLayer()->RemoveAllChildren();
+  graphics_layer.CcLayer().RemoveAllChildren();
 
   PaintController& paint_controller = context.GetPaintController();
 
diff --git a/third_party/blink/renderer/platform/network/http_parsers.cc b/third_party/blink/renderer/platform/network/http_parsers.cc
index d3235e2..af9d9ff 100644
--- a/third_party/blink/renderer/platform/network/http_parsers.cc
+++ b/third_party/blink/renderer/platform/network/http_parsers.cc
@@ -102,25 +102,34 @@
       String::FromUTF8(header->header_value), header->type, header->source);
 }
 
+WTF::HashMap<blink::CSPDirectiveName, blink::CSPSourceListPtr> ConvertToBlink(
+    base::flat_map<CSPDirectiveName, CSPSourceListPtr> directives) {
+  WTF::HashMap<blink::CSPDirectiveName, blink::CSPSourceListPtr> out;
+
+  for (auto& list : directives) {
+    out.insert(ConvertToBlink(list.first),
+               ConvertToBlink(std::move(list.second)));
+  }
+
+  return out;
+}
+
+WTF::Vector<WTF::String> ConvertToBlink(std::vector<std::string> in) {
+  WTF::Vector<WTF::String> out;
+  for (auto& el : in)
+    out.push_back(String::FromUTF8(el));
+  return out;
+}
+
 blink::ContentSecurityPolicyPtr ConvertToBlink(
     ContentSecurityPolicyPtr policy_in) {
-  auto policy = blink::ContentSecurityPolicy::New();
-
-  policy->header = ConvertToBlink(std::move(policy_in->header));
-  policy->use_reporting_api = policy_in->use_reporting_api;
-
-  for (auto& list : policy_in->directives) {
-    policy->directives.insert(ConvertToBlink(list.first),
-                              ConvertToBlink(std::move(list.second)));
-  }
-  policy->upgrade_insecure_requests = policy_in->upgrade_insecure_requests;
-  policy->sandbox = policy_in->sandbox;
-  policy->treat_as_public_address = policy_in->treat_as_public_address;
-
-  for (auto& endpoint : policy_in->report_endpoints)
-    policy->report_endpoints.push_back(String::FromUTF8(endpoint));
-
-  return policy;
+  return blink::ContentSecurityPolicy::New(
+      ConvertToBlink(std::move(policy_in->directives)),
+      policy_in->upgrade_insecure_requests, policy_in->treat_as_public_address,
+      policy_in->sandbox, ConvertToBlink(std::move(policy_in->header)),
+      policy_in->use_reporting_api,
+      ConvertToBlink(std::move(policy_in->report_endpoints)),
+      ConvertToBlink(std::move(policy_in->parsing_errors)));
 }
 
 WTF::Vector<blink::ContentSecurityPolicyPtr> ConvertToBlink(
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc
index 5460712..5b01605 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc
@@ -308,28 +308,6 @@
   return webrtc::kVideoCodecGeneric;
 }
 
-// Populates struct webrtc::RTPFragmentationHeader for H264 codec.
-// Each entry specifies the offset and length (excluding start code) of a NALU.
-// Returns true if successful.
-bool GetRTPFragmentationHeaderH264(webrtc::RTPFragmentationHeader* header,
-                                   const uint8_t* data,
-                                   uint32_t length) {
-  std::vector<media::H264NALU> nalu_vector;
-  if (!media::H264Parser::ParseNALUs(data, length, &nalu_vector)) {
-    // H264Parser::ParseNALUs() has logged the errors already.
-    return false;
-  }
-
-  // TODO(zijiehe): Find a right place to share the following logic between
-  // //content and //remoting.
-  header->VerifyAndAllocateFragmentationHeader(nalu_vector.size());
-  for (size_t i = 0; i < nalu_vector.size(); ++i) {
-    header->fragmentationOffset[i] = nalu_vector[i].data - data;
-    header->fragmentationLength[i] = static_cast<size_t>(nalu_vector[i].size);
-  }
-  return true;
-}
-
 void RecordInitEncodeUMA(int32_t init_retval,
                          media::VideoCodecProfile profile) {
   UMA_HISTOGRAM_BOOLEAN("Media.RTCVideoEncoderInitEncodeSuccess",
@@ -1292,31 +1270,7 @@
   if (!encoded_image_callback_)
     return;
 
-  webrtc::RTPFragmentationHeader header;
-  memset(&header, 0, sizeof(header));
-  switch (video_codec_type_) {
-    case webrtc::kVideoCodecVP8:
-    case webrtc::kVideoCodecVP9:
-      // Generate a header describing a single fragment.
-      header.VerifyAndAllocateFragmentationHeader(1);
-      header.fragmentationOffset[0] = 0;
-      header.fragmentationLength[0] = image.size();
-      break;
-    case webrtc::kVideoCodecH264:
-      if (!GetRTPFragmentationHeaderH264(&header, image.data(), image.size())) {
-        DLOG(ERROR) << "Failed to get RTP fragmentation header for H264";
-        NotifyError(
-            (media::VideoEncodeAccelerator::Error)WEBRTC_VIDEO_CODEC_ERROR);
-        return;
-      }
-      break;
-    default:
-      NOTREACHED() << "Invalid video codec type";
-      return;
-  }
-
-  const auto result =
-      encoded_image_callback_->OnEncodedImage(image, &info, &header);
+  const auto result = encoded_image_callback_->OnEncodedImage(image, &info);
   if (result.error != webrtc::EncodedImageCallback::Result::OK) {
     DVLOG(2)
         << "ReturnEncodedImage(): webrtc::EncodedImageCallback::Result.error = "
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder_test.cc b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder_test.cc
index 052b6b3..edeaaed4 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder_test.cc
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder_test.cc
@@ -42,18 +42,15 @@
  public:
   using EncodedCallback = base::OnceCallback<void(
       const webrtc::EncodedImage& encoded_image,
-      const webrtc::CodecSpecificInfo* codec_specific_info,
-      const webrtc::RTPFragmentationHeader* fragmentation)>;
+      const webrtc::CodecSpecificInfo* codec_specific_info)>;
 
   EncodedImageCallbackWrapper(EncodedCallback encoded_callback)
       : encoded_callback_(std::move(encoded_callback)) {}
 
   Result OnEncodedImage(
       const webrtc::EncodedImage& encoded_image,
-      const webrtc::CodecSpecificInfo* codec_specific_info,
-      const webrtc::RTPFragmentationHeader* fragmentation) override {
-    std::move(encoded_callback_)
-        .Run(encoded_image, codec_specific_info, fragmentation);
+      const webrtc::CodecSpecificInfo* codec_specific_info) override {
+    std::move(encoded_callback_).Run(encoded_image, codec_specific_info);
     return Result(Result::OK);
   }
 
@@ -261,8 +258,7 @@
   void VerifyTimestamp(uint32_t rtp_timestamp,
                        int64_t capture_time_ms,
                        const webrtc::EncodedImage& encoded_image,
-                       const webrtc::CodecSpecificInfo* codec_specific_info,
-                       const webrtc::RTPFragmentationHeader* fragmentation) {
+                       const webrtc::CodecSpecificInfo* codec_specific_info) {
     DVLOG(3) << __func__;
     EXPECT_EQ(rtp_timestamp, encoded_image.Timestamp());
     EXPECT_EQ(capture_time_ms, encoded_image.capture_time_ms_);
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
index b0406abf..ac27142 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
@@ -1479,28 +1479,6 @@
       NOTREACHED();
   }
 
-  if (main_thread_only().prioritize_compositing_after_input) {
-    new_policy.compositor_priority() =
-        base::sequence_manager::TaskQueue::QueuePriority::kVeryHighPriority;
-  } else if (scheduling_settings_
-                 .prioritize_compositing_and_loading_during_early_loading &&
-             current_use_case() == UseCase::kEarlyLoading) {
-    new_policy.compositor_priority() =
-        base::sequence_manager::TaskQueue::QueuePriority::kHighPriority;
-    new_policy.should_prioritize_loading_with_compositing() = true;
-  } else {
-    base::Optional<TaskQueue::QueuePriority> computed_compositor_priority =
-        ComputeCompositorPriorityFromUseCase();
-    if (computed_compositor_priority) {
-      new_policy.compositor_priority() = computed_compositor_priority.value();
-    } else if (main_thread_only()
-                   .compositor_priority_experiments.IsExperimentActive()) {
-      new_policy.compositor_priority() =
-          main_thread_only()
-              .compositor_priority_experiments.GetCompositorPriority();
-    }
-  }
-
   // TODO(skyostil): Add an idle state for foreground tabs too.
   if (main_thread_only().renderer_hidden)
     new_policy.rail_mode() = RAILMode::kIdle;
@@ -1520,8 +1498,16 @@
     new_policy.timer_queue_policy().use_virtual_time = true;
   }
 
+  if (scheduling_settings_
+          .prioritize_compositing_and_loading_during_early_loading &&
+      current_use_case() == UseCase::kEarlyLoading) {
+    new_policy.should_prioritize_loading_with_compositing() = true;
+  }
+
   new_policy.should_disable_throttling() = main_thread_only().use_virtual_time;
 
+  new_policy.compositor_priority() = ComputeCompositorPriority();
+
   new_policy.find_in_page_priority() =
       find_in_page_budget_pool_controller_->CurrentTaskPriority();
 
@@ -2725,13 +2711,41 @@
   }
   main_thread_only().prioritize_compositing_after_input =
       prioritize_compositing_after_input;
-  UpdatePolicy();
+  UpdateCompositorPolicy();
 }
 
 void MainThreadSchedulerImpl::
     OnCompositorPriorityExperimentUpdateCompositorPriority() {
-  if (!ComputeCompositorPriorityFromUseCase())
-    UpdatePolicy();
+  UpdateCompositorPolicy();
+}
+
+TaskQueue::QueuePriority MainThreadSchedulerImpl::ComputeCompositorPriority()
+    const {
+  if (main_thread_only().prioritize_compositing_after_input) {
+    return TaskQueue::QueuePriority::kVeryHighPriority;
+  } else if (scheduling_settings_
+                 .prioritize_compositing_and_loading_during_early_loading &&
+             current_use_case() == UseCase::kEarlyLoading) {
+    return TaskQueue::QueuePriority::kHighPriority;
+  } else {
+    base::Optional<TaskQueue::QueuePriority> computed_compositor_priority =
+        ComputeCompositorPriorityFromUseCase();
+    if (computed_compositor_priority) {
+      return computed_compositor_priority.value();
+    } else if (main_thread_only()
+                   .compositor_priority_experiments.IsExperimentActive()) {
+      return main_thread_only()
+          .compositor_priority_experiments.GetCompositorPriority();
+    }
+  }
+  return TaskQueue::QueuePriority::kNormalPriority;
+}
+
+void MainThreadSchedulerImpl::UpdateCompositorPolicy() {
+  main_thread_only().current_policy.compositor_priority() =
+      ComputeCompositorPriority();
+  CompositorTaskQueue()->SetQueuePriority(
+      ComputePriority(CompositorTaskQueue().get()));
 }
 
 base::Optional<TaskQueue::QueuePriority>
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
index fbe13ad2..01fec36 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
@@ -772,6 +772,14 @@
   // trigger a priority update.
   bool ShouldUpdateTaskQueuePriorities(Policy new_policy) const;
 
+  // Computes compositor priority based on various experiments and
+  // the use case. Defaults to kNormalPriority.
+  TaskQueue::QueuePriority ComputeCompositorPriority() const;
+
+  // Used to update the compositor policy on the main thread when there is a
+  // change in the compositor priority.
+  void UpdateCompositorPolicy();
+
   // Computes the priority for compositing based on the current use case.
   // Returns nullopt if the use case does not need to set the priority.
   base::Optional<TaskQueue::QueuePriority>
diff --git a/third_party/blink/renderer/platform/testing/viewport_layers_setup.cc b/third_party/blink/renderer/platform/testing/viewport_layers_setup.cc
index 09f8a12c..06c5baf 100644
--- a/third_party/blink/renderer/platform/testing/viewport_layers_setup.cc
+++ b/third_party/blink/renderer/platform/testing/viewport_layers_setup.cc
@@ -23,9 +23,9 @@
   graphics_layer_->SetDrawsContent(true);
   graphics_layer_->SetHitTestable(true);
   root_layer_->AddChild(graphics_layer_.get());
-  graphics_layer_->CcLayer()->SetScrollable(root_layer_->CcLayer()->bounds());
+  graphics_layer_->CcLayer().SetScrollable(root_layer_->CcLayer().bounds());
   layer_tree_ = std::make_unique<LayerTreeHostEmbedder>();
-  layer_tree_->layer_tree_host()->SetRootLayer(root_layer_->CcLayer());
+  layer_tree_->layer_tree_host()->SetRootLayer(&root_layer_->CcLayer());
 
   layer_tree_->layer_tree_host()->SetViewportRectAndScale(
       gfx::Rect(1, 1), /*device_scale_factor=*/1.f,
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index f092222..211db6e 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -2727,9 +2727,6 @@
 crbug.com/626703 [ Linux ] external/wpt/css/css-grid/alignment/grid-item-no-aspect-ratio-stretch-5.html [ Failure ]
 crbug.com/626703 [ Mac ] external/wpt/css/css-grid/alignment/grid-item-no-aspect-ratio-stretch-5.html [ Failure ]
 crbug.com/626703 [ Win ] external/wpt/css/css-grid/alignment/grid-item-no-aspect-ratio-stretch-5.html [ Failure ]
-crbug.com/626703 [ Linux ] virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-item-no-aspect-ratio-stretch-5.html [ Failure ]
-crbug.com/626703 [ Mac ] virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-item-no-aspect-ratio-stretch-5.html [ Failure ]
-crbug.com/626703 [ Win ] virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-item-no-aspect-ratio-stretch-5.html [ Failure ]
 crbug.com/626703 [ Linux ] virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-item-no-aspect-ratio-stretch-1.html [ Failure ]
 crbug.com/626703 [ Mac ] virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-item-no-aspect-ratio-stretch-1.html [ Failure ]
 crbug.com/626703 [ Win ] virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-item-no-aspect-ratio-stretch-1.html [ Failure ]
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/wide-gamut-canvas/canvas-createImageBitmap-e_srgb.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/wide-gamut-canvas/canvas-createImageBitmap-e_srgb.html
index 4bb4475..fd9a5e96d 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/wide-gamut-canvas/canvas-createImageBitmap-e_srgb.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/wide-gamut-canvas/canvas-createImageBitmap-e_srgb.html
@@ -285,31 +285,33 @@
 
 // HTMLImageElement - Opaque sRGB
 // File formats: AVIF, Bitmap, GIF, ICO, JPEG, PNG, WEBP
-promise_test(function() {
-    return Promise.all(['avif', 'bmp', 'gif', 'ico', 'jpg', 'png', 'webp'].map(
-        ext => new Promise((resolve,reject) => {
+['avif', 'bmp', 'gif', 'ico', 'jpg', 'png', 'webp'].forEach(ext => {
+    promise_test(function() {
+        return new Promise((resolve,reject) => {
             var image = new Image();
             image.onload = function() {
                 resolve(image);
             }
             image.src = 'resources/pattern-srgb.' + ext;
-        }).then(testImageBitmapOpaque)));
-}, 'createImageBitmap in e-sRGB from an opaque sRGB HTMLImageElement (AVIF, \
-BMP, GIF, ICO, JPG, PNG, WEBP) with resize.');
+        }).then(testImageBitmapOpaque);
+    }, 'createImageBitmap in e-sRGB from an opaque sRGB HTMLImageElement (' + ext +
+        ') with resize.');
+});
 
 // HTMLImageElement - Transparent sRGB
 // File formats: AVIF, Bitmap, ICO, PNG, WEBP
-promise_test(function() {
-    return Promise.all(['avif', 'bmp', 'ico', 'png', 'webp'].map(
-        ext => new Promise((resolve,reject) => {
+['avif', 'bmp', 'ico', 'png', 'webp'].forEach(ext => {
+    promise_test(function() {
+        return new Promise((resolve,reject) => {
             var image = new Image();
             image.onload = function() {
                 resolve(image);
             }
             image.src = 'resources/pattern-srgb-transparent.' + ext;
-        }).then(testImageBitmapFromTransparentImage)));
-}, 'createImageBitmap in e-sRGB from a transparent sRGB HTMLImageElement \
-(AVIF, BMP, ICO, PNG, WEBP) with resize.');
+        }).then(testImageBitmapFromTransparentImage);
+    }, 'createImageBitmap in e-sRGB from a transparent sRGB HTMLImageElement (' + ext +
+        ') with resize.');
+});
 
 ////////////////////////////////////////////////////////////////////////////////
 
@@ -370,9 +372,9 @@
 
 // Blob from file - Opaque sRGB
 // File formats: AVIF, Bitmap, GIF, ICO, JPEG, PNG, WEBP
-promise_test(function() {
-    return Promise.all(['avif', 'bmp', 'gif', 'ico', 'jpg', 'png', 'webp'].map(
-        ext => new Promise((resolve, reject) => {
+['avif', 'bmp', 'gif', 'ico', 'jpg', 'png', 'webp'].forEach(ext => {
+    promise_test(function() {
+        return new Promise((resolve, reject) => {
             var xhr = new XMLHttpRequest();
             xhr.open("GET", 'resources/pattern-srgb.' + ext);
             xhr.responseType = 'blob';
@@ -380,15 +382,15 @@
             xhr.onload = function() {
                 resolve(xhr.response);
             };
-        }).then(testImageBitmapOpaque)));
-}, 'createImageBitmap in e-sRGB from an opaque sRGB Blob (AVIF, BMP, GIF, ICO, \
-JPG, PNG, WEBP) with resize.');
+        }).then(testImageBitmapOpaque);
+    }, 'createImageBitmap in e-sRGB from an opaque sRGB Blob (' + ext + ') with resize.');
+});
 
 // Blob form file - Transparent sRGB
 // File formats: AVIF, Bitmap, ICO, PNG, WEBP
-promise_test(function() {
-    return Promise.all(['avif', 'bmp', 'ico', 'png', 'webp'].map(
-        ext => new Promise((resolve, reject) => {
+['avif', 'bmp', 'ico', 'png', 'webp'].forEach(ext => {
+    promise_test(function() {
+        return new Promise((resolve, reject) => {
             var xhr = new XMLHttpRequest();
             xhr.open("GET", 'resources/pattern-srgb-transparent.' + ext);
             xhr.responseType = 'blob';
@@ -396,9 +398,9 @@
             xhr.onload = function() {
                 resolve(xhr.response);
             };
-        }).then(testImageBitmapFromTransparentImage)));
-}, 'createImageBitmap in e-sRGB from a transparent sRGB Blob (AVIF, BMP, ICO, \
-PNG, WEBP) with resize.');
+        }).then(testImageBitmapFromTransparentImage);
+    }, 'createImageBitmap in e-sRGB from a transparent sRGB Blob (' + ext + ') with resize.');
+});
 
 // Color managed blob from canvas
 function testCreateImageBitmapFromColorManagedBlob(pixelFormat, isTransparent) {
diff --git a/third_party/blink/web_tests/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.https.html b/third_party/blink/web_tests/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.https.html
index d8e064d..f4dca7d6 100644
--- a/third_party/blink/web_tests/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.https.html
+++ b/third_party/blink/web_tests/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.https.html
@@ -24,6 +24,10 @@
 [
   '%s',
   'foo/%s',
+  `%s${location.href}`,
+  location.href.replace(location.protocol,
+    `${location.protocol[0]}%s${location.protocol.substring(1)}`),
+  location.href.replace(location.protocol, `${location.protocol}%s`),
   location.href + '/%s',
   location.href + '#%s',
   location.href + '?foo=%s',
@@ -45,6 +49,10 @@
 [
   '',
   '%S',
+  'http://%s.com',
+  'http://%s.example.com',
+  location.href.replace(location.hostname, `%s${location.hostname}`),
+  location.href.replace(location.port, `%s${location.port}`),
   location.href + '',
   location.href + '/%',
   location.href + '/%a',
@@ -64,8 +72,6 @@
 });
 
 [
-  'http://%s.com',
-  'http://%s.example.com',
   'http://example.com/%s',
   'https://example.com/%s',
   'http://foobar.example.com/%s',
diff --git a/third_party/blink/web_tests/external/wpt/native-io/close_async.tentative.https.any.js b/third_party/blink/web_tests/external/wpt/native-io/close_async.tentative.https.any.js
index a2b692e..0338cda 100644
--- a/third_party/blink/web_tests/external/wpt/native-io/close_async.tentative.https.any.js
+++ b/third_party/blink/web_tests/external/wpt/native-io/close_async.tentative.https.any.js
@@ -95,3 +95,18 @@
   await promise_rejects_dom(testCase, 'InvalidStateError', file.getLength());
   assert_equals(await closePromise, undefined);
 }, 'NativeIOFile.getLength fails immediately after calling NativeIOFile.close');
+
+promise_test(async testCase => {
+  const file = await createFile(testCase, 'file_name');
+  assert_equals(await file.close(), undefined);
+
+  await promise_rejects_dom(testCase, 'InvalidStateError', file.flush());
+}, 'NativeIOFile.flush fails after NativeIOFile.close settles');
+
+promise_test(async testCase => {
+  const file = await createFile(testCase, 'file_name');
+  const closePromise = file.close();
+
+  await promise_rejects_dom(testCase, 'InvalidStateError', file.flush());
+  assert_equals(await closePromise, undefined);
+}, 'NativeIOFile.flush fails immediately after calling NativeIOFile.close');
diff --git a/third_party/blink/web_tests/external/wpt/native-io/close_sync.tentative.https.any.js b/third_party/blink/web_tests/external/wpt/native-io/close_sync.tentative.https.any.js
index b5fb288..834265f 100644
--- a/third_party/blink/web_tests/external/wpt/native-io/close_sync.tentative.https.any.js
+++ b/third_party/blink/web_tests/external/wpt/native-io/close_sync.tentative.https.any.js
@@ -49,3 +49,10 @@
 
   assert_throws_dom('InvalidStateError', () => file.getLength());
 }, 'NativeIOFileSync.getLength fails after NativeIOFileSync.close');
+
+test(testCase => {
+  const file = createFileSync(testCase, 'file_name');
+  assert_equals(undefined, file.close());
+
+  assert_throws_dom('InvalidStateError', () => file.flush());
+}, 'NativeIOFileSync.flush fails after NativeIOFileSync.close');
diff --git a/third_party/blink/web_tests/external/wpt/native-io/concurrent_io/concurrent_io_flush_async.tentative.https.any.js b/third_party/blink/web_tests/external/wpt/native-io/concurrent_io/concurrent_io_flush_async.tentative.https.any.js
new file mode 100644
index 0000000..7436b86
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/native-io/concurrent_io/concurrent_io_flush_async.tentative.https.any.js
@@ -0,0 +1,32 @@
+// META: title=NativeIO API: Concurrent io while flush is resolving.
+// META: global=window,worker
+// META: script=operation_helpers.js
+// META: script=../resources/support.js
+
+
+'use strict';
+
+// See documentation in operation_helpers.js
+
+for (let op of kOperations) {
+  promise_test(async testCase => {
+    const file = await createFile(testCase, 'flush_file');
+
+    const res = op.prepare();
+
+    const flushPromise = file.flush();
+    op.assertRejection(testCase, file, res);
+
+    await flushPromise;
+
+    const readSharedArrayBuffer = new SharedArrayBuffer(4);
+    const readBytes = new Uint8Array(readSharedArrayBuffer);
+    assert_equals(await file.read(readBytes, 0), 4,
+                  `NativeIOFile.read() should not fail after a rejected ` +
+                    `${op.name}() during flush()`);
+    assert_array_equals(readBytes, [64, 65, 66, 67],
+                        `Rejecting ${op.name}() during flush() should not ` +
+                          `change the file.`);
+    op.assertUnchanged(res);
+  }, `${op.name}() rejects while flush() is resolving.`);
+};
diff --git a/third_party/blink/web_tests/external/wpt/native-io/concurrent_io/operation_helpers.js b/third_party/blink/web_tests/external/wpt/native-io/concurrent_io/operation_helpers.js
index 71cbc29efa..76804ca 100644
--- a/third_party/blink/web_tests/external/wpt/native-io/concurrent_io/operation_helpers.js
+++ b/third_party/blink/web_tests/external/wpt/native-io/concurrent_io/operation_helpers.js
@@ -64,4 +64,15 @@
     assertUnchanged: () => {},
   };
   kOperations.push(kOpGetLength);
+
+  const kOpFlush = {
+    name: 'flush',
+    prepare: () => {},
+    assertRejection: async (testCase, file) => {
+      await promise_rejects_dom(testCase, 'InvalidStateError',
+                                file.flush());
+    },
+    assertUnchanged: () => {},
+  };
+  kOperations.push(kOpFlush);
 })();
diff --git a/third_party/blink/web_tests/external/wpt/native-io/flush_async_basic.tentative.https.any.js b/third_party/blink/web_tests/external/wpt/native-io/flush_async_basic.tentative.https.any.js
new file mode 100644
index 0000000..2036d22
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/native-io/flush_async_basic.tentative.https.any.js
@@ -0,0 +1,30 @@
+// META: title=Synchronous NativeIO API: Flushed data is read back.
+// META: global=window,worker
+// META: script=resources/support.js
+
+'use strict';
+
+promise_test(async testCase => {
+  const file = await nativeIO.open('test_file');
+  testCase.add_cleanup(async () => {
+    await file.close();
+    await nativeIO.delete('test_file');
+  });
+
+  const size = 1024;
+  const longarray = createLargeArray(size, /*seed = */ 103);
+  const writeSharedArrayBuffer = new SharedArrayBuffer(size);
+  const writtenBytes = new Uint8Array(writeSharedArrayBuffer);
+  writtenBytes.set(longarray);
+  const writeCount = await file.write(writtenBytes, 0);
+  assert_equals(
+      writeCount, size,
+      'NativeIOFile.write() should resolve with the number of bytes written');
+
+  await file.flush();
+  const readBytes = await readIoFile(file);
+
+  assert_array_equals(readBytes, writtenBytes,
+                      'the bytes read should match the bytes written');
+}, 'NativeIOFile.read returns bytes written by NativeIOFile.write' +
+     ' after NativeIOFile.flush');
diff --git a/third_party/blink/web_tests/external/wpt/native-io/flush_sync_basic.tentative.https.any.js b/third_party/blink/web_tests/external/wpt/native-io/flush_sync_basic.tentative.https.any.js
new file mode 100644
index 0000000..c5a1268e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/native-io/flush_sync_basic.tentative.https.any.js
@@ -0,0 +1,28 @@
+// META: title=Synchronous NativeIO API: Flushed data is read back.
+// META: global=dedicatedworker
+// META: script=resources/support.js
+
+'use strict';
+
+test(testCase => {
+  const file = nativeIO.openSync('test_file');
+  testCase.add_cleanup(() => {
+    file.close();
+    nativeIO.deleteSync('test_file');
+  });
+
+  const size = 1024;
+  const longarray = createLargeArray(size, /*seed = */ 107);
+  const writtenBytes = Uint8Array.from(longarray);
+  const writeCount = file.write(writtenBytes, 0);
+  assert_equals(
+      writeCount, size,
+      'NativeIOFile.write() should resolve with the number of bytes written');
+
+  file.flush();
+  const readBytes = readIoFileSync(file);
+
+  assert_array_equals(readBytes, writtenBytes,
+                      'the bytes read should match the bytes written');
+}, 'NativeIOFileSync.read returns bytes written by NativeIOFileSync.write' +
+     ' after NativeIOFileSync.flush');
diff --git a/third_party/blink/web_tests/external/wpt/native-io/resources/support.js b/third_party/blink/web_tests/external/wpt/native-io/resources/support.js
index 90bb912..bf7cf5a 100644
--- a/third_party/blink/web_tests/external/wpt/native-io/resources/support.js
+++ b/third_party/blink/web_tests/external/wpt/native-io/resources/support.js
@@ -24,3 +24,56 @@
 
   return file;
 }
+
+// Returns a handle to a newly created file that holds some data.
+//
+// The file will be closed and deleted when the test ends.
+function createFileSync(testCase, fileName) {
+  const file = nativeIO.openSync(fileName);
+  testCase.add_cleanup(() => {
+    file.close();
+    nativeIO.deleteSync(fileName);
+  });
+
+  const writtenBytes = Uint8Array.from([64, 65, 66, 67]);
+  const writeCount = file.write(writtenBytes, 0);
+  assert_equals(writeCount, 4);
+
+  return file;
+}
+
+// Returns an Uint8Array with pseudorandom data.
+//
+// The PRNG should be sufficient to defeat compression schemes, but it is not
+// cryptographically strong.
+function createLargeArray(size, seed) {
+  const buffer = new Uint8Array(size);
+
+  // 32-bit xorshift - the seed can't be zero
+  let state = 1000 + seed;
+
+  for (let i = 0; i < size; ++i) {
+    state ^= state << 13;
+    state ^= state >> 17;
+    state ^= state << 5;
+    buffer[i] = state & 0xff;
+  }
+
+  return buffer;
+}
+
+// Attempts to read the entire file into a buffer.
+async function readIoFile(file) {
+  const length = await file.getLength();
+  const readBuffer = new Uint8Array(new SharedArrayBuffer(length));
+  await file.read(readBuffer, 0);
+  return readBuffer;
+}
+
+// Attempts to read the entire file into a buffer.
+function readIoFileSync(file) {
+  const length = file.getLength();
+  const readBuffer = new Uint8Array(length);
+  file.read(readBuffer, 0);
+  return readBuffer;
+}
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/compat/pointerevent_mouse-on-object.html b/third_party/blink/web_tests/external/wpt/pointerevents/compat/pointerevent_mouse-on-object.html
index fa0e97f..78edfbd 100644
--- a/third_party/blink/web_tests/external/wpt/pointerevents/compat/pointerevent_mouse-on-object.html
+++ b/third_party/blink/web_tests/external/wpt/pointerevents/compat/pointerevent_mouse-on-object.html
@@ -1,9 +1,9 @@
 <!DOCTYPE HTML>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/resources/testdriver.js"></script>
-<script src="/resources/testdriver-vendor.js"></script>
-<script src="/resources/testdriver-actions.js"></script>
+<script type="text/javascript" src="/resources/testharness.js"></script>
+<script type="text/javascript" src="/resources/testharnessreport.js"></script>
+<script type="text/javascript" src="/resources/testdriver.js"></script>
+<script type="text/javascript" src="/resources/testdriver-vendor.js"></script>
+<script type="text/javascript" src="/resources/testdriver-actions.js"></script>
 <script type="text/javascript" src="../pointerevent_support.js"></script>
 
 <style>
@@ -11,61 +11,101 @@
   width: 50px;
   height: 50px;
   padding: 50px;
+  background-color: lightblue;
+  border:1px solid black;
+}
+div {
+  display: block;
 }
 </style>
 
 <h1>Verifies that mouse activities on an object fire pointerevents. It expected to get pointerup when the pointerdown happened on the object for compatibility with flash objects.</h1>
 
-<object id="obj"></object>
 
-<button id="done"></button>
+<p>
+  To test that when clicking inside the blue rectangle all compat mouse events are correct:
+  <ul>
+    <li> Click once in the blue rectangle
+    <li> Click the Done button
+  </ul>
+</p>
+<p>
+  To test that when dragging mouse outside all compat mouse events are correct:
+  <ul>
+    <li> Press left mouse button in the blue rectangle
+    <li> Drag the mouse cursor out of the blue rectangle
+    <li> Release the left mouse button
+    <li> Click the Done button
+  </ul>
+</p>
+<!-- draggable is set to false because there is a difference between auto draggable value in different browsers -->
+<object id="obj" draggable="false"></object>
+<button id="done">Done</button>
 <div id="log"></div>
-
 <script>
 var target = document.getElementById("obj");
 var done = document.getElementById("done");
+
 var rect = target.getBoundingClientRect();
-var done_clicked = false;
+var done_clicked = 0;
 var receivedEvents = [];
+var previous_done_clicked = 0;
+
 ["mousedown", "mouseup", "mousemove", "pointerdown", "pointerup", "pointermove"].forEach(function(eventName) {
   target.addEventListener(eventName, function(event) {
+    // This will clear receivedEvents once another test starts
+    if(previous_done_clicked !== done_clicked){
+      previous_done_clicked = done_clicked;
+      receivedEvents = [];
+    }
     receivedEvents.push(event.type);
   });
 });
 
-document.getElementById('done').addEventListener('click', (e) => done_clicked = true);
+document.getElementById('done').addEventListener('click', (e) => done_clicked++);
 
-promise_test(async() => {
-  done_clicked = false;
-  receivedEvents = [];
+// Need to prevent the default behaviour for firefox
+target.addEventListener("dragstart", (e)=>e.preventDefault());
 
-  await new test_driver.Actions()
-        .pointerMove(Math.ceil(rect.left+5), Math.ceil(rect.top+5))
-        .pointerDown()
-        .pointerUp()
-        .send()
-        .then(() => clickInTarget("mouse", done));
-  await resolveWhen(() => done_clicked);
+if(window.promise_test){
+  promise_test(async() => {
+    receivedEvents = [];
 
-  assert_array_equals(receivedEvents, ["pointermove", "mousemove", "pointerdown", "mousedown", "pointerup", "mouseup"],
-                      "Click on object should result in the correct sequence of events");
-}, "Normal click event sequence within object");
+    await new test_driver.Actions()
+          .pointerMove(Math.ceil(rect.left+5), Math.ceil(rect.top+5))
+          .pointerDown()
+          .pointerUp()
+          .send()
+          .then(() => clickInTarget("mouse", done));
+    await resolveWhen(() => done_clicked === 1);
 
-promise_test(async() => {
-  done_clicked = false;
-  receivedEvents = [];
+    assert_array_equals(receivedEvents.filter(isPointerEvent), ["pointermove", "pointerdown", "pointerup"],
+                        "Click on object should result in the correct sequence of pointer events");
+    assert_array_equals(receivedEvents.filter(isMouseEvent), ["mousemove", "mousedown", "mouseup"],
+                        "Click on object should result in the correct sequence of mouse events");
+    assert_true(arePointerEventsBeforeCompatMouseEvents(receivedEvents),
+                        "Click on object should result in the correct sequence of events: " + receivedEvents);
+  }, "Normal click event sequence within object");
 
-  await new test_driver.Actions()
-        .pointerMove(Math.ceil(rect.left+5), Math.ceil(rect.top+5))
-        .pointerDown()
-        .pointerMove(Math.ceil(rect.left-5), Math.ceil(rect.top-5))
-        .pointerUp()
-        .send()
-        .then(() => clickInTarget("mouse", done));
-  await resolveWhen(() => done_clicked);
+  promise_test(async() => {
+    receivedEvents = [];
 
-  assert_array_equals(receivedEvents, ["pointermove", "mousemove", "pointerdown", "mousedown", "pointermove", "mousemove", "pointerup", "mouseup"],
-                      "Drag from object should result in the correct sequence of events");
+    await new test_driver.Actions()
+          .pointerMove(Math.ceil(rect.left+5), Math.ceil(rect.top+5))
+          .pointerDown()
+          .pointerMove(Math.ceil(rect.left-5), Math.ceil(rect.top-5))
+          .pointerUp()
+          .send()
+          .then(() => clickInTarget("mouse", done));
+    await resolveWhen(() => done_clicked === 2);
 
-}, "Click and drag outside of object event sequence");
+    assert_array_equals(receivedEvents.filter(isPointerEvent), ["pointermove", "pointerdown", "pointermove", "pointerup"],
+                        "Drag from object should result in the correct sequence of pointer events");
+    assert_array_equals(receivedEvents.filter(isMouseEvent), ["mousemove", "mousedown", "mousemove", "mouseup"],
+                        "Drag from object should result in the correct sequence of mouse events");
+    assert_true(arePointerEventsBeforeCompatMouseEvents(receivedEvents),
+                        "Drag from object should result in the correct sequence of events: " + receivedEvents);
+
+  }, "Click and drag outside of object event sequence");
+}
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_support.js b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_support.js
index 7f291d7..6cbc8d6 100644
--- a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_support.js
+++ b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_support.js
@@ -402,3 +402,54 @@
   }
   return resolveWhen(next);
 }
+
+function isPointerEvent(eventName){
+  return All_Pointer_Events.includes(eventName);
+}
+
+function isMouseEvent(eventName){
+  return ["mousedown", "mouseup", "mousemove", "mouseover",
+          "mouseenter", "mouseout", "mouseleave",
+          "click", "contextmenu", "dblclick"
+         ].includes(eventName);
+}
+
+function arePointerAndMouseEventCompatible(pointerEventName, mouseEventName){
+  // e.g. compatible pointer-mouse events: pointerup - mouseup etc
+  return pointerEventName.startsWith("pointer") &&
+         mouseEventName.startsWith("mouse") &&
+         pointerEventName.substring(7) === mouseEventName.substring(5);
+}
+
+// events is a list of events fired at a target
+// checks to see if each pointer event has a corresponding mouse event in the event array
+// and the two events are in the proper order (pointer event is first)
+// see https://www.w3.org/TR/pointerevents3/#mapping-for-devices-that-support-hover
+function arePointerEventsBeforeCompatMouseEvents(events){
+  // checks to see if the pointer event is compatible with the mouse event
+  // and the pointer event happens before the mouse event
+  function arePointerAndMouseEventInProperOrder(pointerEventIndex, mouseEventIndex, events){
+    return (pointerEventIndex < mouseEventIndex && isPointerEvent(events[pointerEventIndex]) && isMouseEvent(events[mouseEventIndex])
+      && arePointerAndMouseEventCompatible(events[pointerEventIndex], events[mouseEventIndex]));
+  }
+
+  let currentPointerEventIndex = events.findIndex((event)=>isPointerEvent(event));
+  let currentMouseEventIndex = events.findIndex((event)=>isMouseEvent(event));
+
+  while(1){
+    if(currentMouseEventIndex < 0 && currentPointerEventIndex < 0)
+      return true;
+    if(currentMouseEventIndex < 0 || currentPointerEventIndex < 0)
+      return false;
+    if(!arePointerAndMouseEventInProperOrder(currentPointerEventIndex, currentMouseEventIndex, events))
+      return false;
+
+    let pointerIdx = events.slice(currentPointerEventIndex+1).findIndex(isPointerEvent);
+    let mouseIdx = events.slice(currentMouseEventIndex+1).findIndex(isMouseEvent);
+
+    currentPointerEventIndex = (pointerIdx < 0)?pointerIdx:(currentPointerEventIndex+1+pointerIdx);
+    currentMouseEventIndex = (mouseIdx < 0)?mouseIdx:(currentMouseEventIndex+1+mouseIdx);
+  }
+
+  return true;
+}
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/formatters/chromium.py b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/formatters/chromium.py
index 445063d..0c2fc40 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/formatters/chromium.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/formatters/chromium.py
@@ -38,6 +38,9 @@
         # List of tests that have failing subtests.
         self.tests_with_subtest_fails = set()
 
+        # Browser log for the current test under execution.
+        self.test_log = []
+
     def _append_test_message(self, test, subtest, status, expected, message):
         """
         Appends the message data for a test.
@@ -90,6 +93,9 @@
             self._append_artifact(cur_dict, "wpt_subtest_failure", "true")
         if wpt_actual != actual:
             self._append_artifact(cur_dict, "wpt_actual_status", wpt_actual)
+        if wpt_actual == 'CRASH':
+            for line in self.test_log:
+                self._append_artifact(cur_dict, "wpt_crash_log", line)
         for message in messages:
             self._append_artifact(cur_dict, "log", message)
 
@@ -129,13 +135,9 @@
             return "SKIP"
         if status == "EXTERNAL-TIMEOUT":
             return "TIMEOUT"
-        if status in ("ERROR", "CRASH", "PRECONDITION_FAILED"):
-            # CRASH in WPT means a browser crash, which Chromium treats as a
-            # test failure.
+        if status in ("ERROR", "PRECONDITION_FAILED"):
             return "FAIL"
         if status == "INTERNAL-ERROR":
-            # CRASH in Chromium refers to an error in the test runner not the
-            # browser.
             return "CRASH"
         # Any other status just gets returned as-is.
         return status
@@ -218,6 +220,9 @@
         # Update the count of how many tests ran with each status.
         self.num_failures_by_status[actual_status] += 1
 
+        # New test, new browser logs.
+        self.test_log = []
+
     def suite_end(self, data):
         # Create the final result dictionary
         final_result = {
@@ -230,3 +235,7 @@
             "tests": self.tests
         }
         return json.dumps(final_result)
+
+    def process_output(self, data):
+        if 'command' in data and 'chromedriver' in data['command']:
+            self.test_log.append(data['data'])
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/formatters/tests/test_chromium.py b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/formatters/tests/test_chromium.py
index efff5f0..59f39c1e 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/formatters/tests/test_chromium.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/formatters/tests/test_chromium.py
@@ -570,3 +570,56 @@
         "foo.html: DATA1",
         "foo-ref.html: DATA2",
     ]
+
+
+def test_process_output_crashing_test(capfd):
+    """Test that chromedriver logs are preserved for crashing tests"""
+
+    # Set up the handler.
+    output = StringIO()
+    logger = structuredlog.StructuredLogger("test_a")
+    logger.add_handler(handlers.StreamHandler(output, ChromiumFormatter()))
+
+    logger.suite_start(["t1", "t2", "t3"], run_info={}, time=123)
+
+    logger.test_start("t1")
+    logger.process_output(100, "This message should be recorded", "/some/path/to/chromedriver --some-flag")
+    logger.process_output(101, "This message should not be recorded", "/some/other/process --another-flag")
+    logger.process_output(100, "This message should also be recorded", "/some/path/to/chromedriver --some-flag")
+    logger.test_end("t1", status="CRASH", expected="CRASH")
+
+    logger.test_start("t2")
+    logger.process_output(100, "Another message for the second test", "/some/path/to/chromedriver --some-flag")
+    logger.test_end("t2", status="CRASH", expected="PASS")
+
+    logger.test_start("t3")
+    logger.process_output(100, "This test fails", "/some/path/to/chromedriver --some-flag")
+    logger.process_output(100, "But the output should not be captured", "/some/path/to/chromedriver --some-flag")
+    logger.process_output(100, "Because it does not crash", "/some/path/to/chromedriver --some-flag")
+    logger.test_end("t3", status="FAIL", expected="PASS")
+
+    logger.suite_end()
+
+    # check nothing got output to stdout/stderr
+    # (note that mozlog outputs exceptions during handling to stderr!)
+    captured = capfd.readouterr()
+    assert captured.out == ""
+    assert captured.err == ""
+
+    # check the actual output of the formatter
+    output.seek(0)
+    output_json = json.load(output)
+
+    test_obj = output_json["tests"]["t1"]
+    assert test_obj["artifacts"]["wpt_crash_log"] == [
+        "This message should be recorded",
+        "This message should also be recorded"
+    ]
+
+    test_obj = output_json["tests"]["t2"]
+    assert test_obj["artifacts"]["wpt_crash_log"] == [
+        "Another message for the second test"
+    ]
+
+    test_obj = output_json["tests"]["t3"]
+    assert "wpt_crash_log" not in test_obj["artifacts"]
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-source-file-path.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-source-file-path.tentative.html
index 61adb29..6e87c25a 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-source-file-path.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-source-file-path.tentative.html
@@ -38,7 +38,7 @@
 
 promise_test(async t => {
   let future_violation = futureViolation();
-  assert_throws_js(TypeError, () => {
+  assert_throws_js(TypeError, _ => {
     document.getElementById("to-be-modified").innerHTML = "'test'";
   });
   let violation = await future_violation;
@@ -64,9 +64,7 @@
   let future_violation = futureViolation();
   assert_throws_js(TypeError, () => setInnerHtml(toBeModified, "'test'"));
   let violation = await future_violation;
-  // TODO(https://crbug.com/1113163): Consider exposing the full path of the
-  // cross-origin script here instead of just its origin.
-  assert_equals(violation.sourceFile, script_origin);
+  assert_equals(violation.sourceFile, script_src);
 }, "cross-origin script")
 
 // TODO(arthursonzogni): Check what happens with redirects. Do we report the
diff --git a/third_party/blink/web_tests/external/wpt/web-animations/timing-model/animations/setting-the-timeline-of-an-animation.html b/third_party/blink/web_tests/external/wpt/web-animations/timing-model/animations/setting-the-timeline-of-an-animation.html
index 7e98ef42..dd861750 100644
--- a/third_party/blink/web_tests/external/wpt/web-animations/timing-model/animations/setting-the-timeline-of-an-animation.html
+++ b/third_party/blink/web_tests/external/wpt/web-animations/timing-model/animations/setting-the-timeline-of-an-animation.html
@@ -60,7 +60,7 @@
     new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
                   null);
   animation.startTime = document.timeline.currentTime;
-  assert_equals(animation.playState, 'running');
+  assert_equals(animation.playState, 'idle');
 
   animation.timeline = document.timeline;
 
@@ -73,7 +73,7 @@
     new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
                   null);
   animation.startTime = document.timeline.currentTime - 200 * MS_PER_SEC;
-  assert_equals(animation.playState, 'running');
+  assert_equals(animation.playState, 'idle');
 
   animation.timeline = document.timeline;
 
diff --git a/third_party/blink/web_tests/http/tests/reporting-observer/reporting-observer-worker.html b/third_party/blink/web_tests/http/tests/reporting-observer/reporting-observer-worker.html
deleted file mode 100644
index 51dcb15..0000000
--- a/third_party/blink/web_tests/http/tests/reporting-observer/reporting-observer-worker.html
+++ /dev/null
@@ -1,74 +0,0 @@
-<!doctype html>
-<html>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/serviceworker/resources/test-helpers.js"></script>
-<script>
-    const WORKER_URL
-        = new URL('resources/deprecation-worker.js', location).href;
-    function checkReport(report) {
-        assert_equals(report.type, 'deprecation');
-        assert_equals(report.url, WORKER_URL);
-        assert_equals(typeof report.body.id, 'string');
-        assert_equals(typeof report.body.anticipatedRemoval, 'string');
-        assert_equals(typeof report.body.message, 'string');
-        assert_equals(report.body.sourceFile, WORKER_URL);
-        assert_equals(typeof report.body.lineNumber, 'number');
-        assert_equals(typeof report.body.columnNumber, 'number');
-    }
-
-    promise_test(async (test) => {
-        const worker = new Worker(WORKER_URL);
-        test.add_cleanup(() => worker.terminate());
-
-        const mc = new MessageChannel();
-        worker.postMessage(mc.port2, [mc.port2]);
-        test.add_cleanup(() => mc.port1.close());
-
-        const reports = (await new Promise(r => {
-            mc.port1.onmessage = r;
-        })).data;
-
-        assert_equals(reports.length, 1);
-        checkReport(reports[0]);
-    }, 'Deprecation reports on DedicatedWorker');
-
-    promise_test(async (test) => {
-        const worker = new SharedWorker(WORKER_URL);
-
-        const mc = new MessageChannel();
-        worker.port.start();
-        worker.port.postMessage(mc.port2, [mc.port2]);
-        test.add_cleanup(() => mc.port1.close());
-
-        const reports = (await new Promise(r => {
-            mc.port1.onmessage = r;
-        })).data;
-
-        assert_equals(reports.length, 1);
-        checkReport(reports[0]);
-    }, 'Deprecation reports on SharedWorker');
-
-    promise_test(async (test) => {
-        const SCOPE = new URL('resources/empty.html', location).pathname;
-        const reg = await service_worker_unregister_and_register(test, WORKER_URL, SCOPE);
-        await wait_for_state(test, reg.installing, 'activated');
-        test.add_cleanup(() => reg.unregister());
-
-        const frame = await with_iframe(SCOPE);
-
-        const mc = new MessageChannel();
-        const worker = frame.contentWindow.navigator.serviceWorker.controller;
-        worker.postMessage(mc.port2, [mc.port2]);
-        test.add_cleanup(() => mc.port1.close());
-
-        const reports = (await new Promise(r => {
-            mc.port1.onmessage = r;
-        })).data;
-
-        assert_equals(reports.length, 1);
-        checkReport(reports[0]);
-    }, 'Deprecation reports on ServiceWorker');
-</script>
-
-</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/http/tests/reporting-observer/resources/deprecation-worker.js b/third_party/blink/web_tests/http/tests/reporting-observer/resources/deprecation-worker.js
deleted file mode 100644
index 7a9b2191..0000000
--- a/third_party/blink/web_tests/http/tests/reporting-observer/resources/deprecation-worker.js
+++ /dev/null
@@ -1,21 +0,0 @@
-function run(port) {
-  const observer = new ReportingObserver((reports) => {
-    port.postMessage(reports.map(report => report.toJSON()));
-  });
-  observer.observe();
-
-  try {
-    // This API is deprecated. This throws an exception because of a bad
-    // argument, but we don't care.
-    Atomics.wake();
-  } catch (e) {
-  }
-}
-
-// For DedicatedWorker and ServiceWorker
-self.addEventListener('message', (e) => run(e.data));
-
-// For SharedWorker
-self.addEventListener('connect', (e) => {
-  e.ports[0].onmessage = (ev) => run(ev.data);
-});
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 344d7a3..89a34803 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
@@ -922,6 +922,7 @@
     attribute @@toStringTag
     method close
     method constructor
+    method flush
     method getLength
     method read
     method write
diff --git a/third_party/blink/web_tests/inspector-protocol/css/css-set-effective-property-value-expected.txt b/third_party/blink/web_tests/inspector-protocol/css/css-set-effective-property-value-expected.txt
index 5c5d76a..28bee53e 100644
--- a/third_party/blink/web_tests/inspector-protocol/css/css-set-effective-property-value-expected.txt
+++ b/third_party/blink/web_tests/inspector-protocol/css/css-set-effective-property-value-expected.txt
@@ -3,6 +3,7 @@
 {
     padding-top: 55px;
     margin-top: 33px !important;
+    --x: foo;
 }
 Dumping matched rules: 
 *#inspected* {    regular
@@ -28,6 +29,7 @@
 {
     padding-top: 55px;
     margin-top: 33px !important;
+    --x: foo;
 }
 *#inspected* {    regular
     margin-left: 10px !important;
@@ -51,6 +53,7 @@
 {
     padding-top: 55px;
     margin-top: 33px !important;
+    --x: foo;
 }
 *#inspected* {    regular
     margin-left: 10px !important;
@@ -74,6 +77,7 @@
 {
     padding-top: 55px;
     margin-top: 33px !important;
+    --x: foo;
 }
 *#inspected* {    regular
     margin-left: 101px !important;
@@ -97,6 +101,7 @@
 {
     padding-top: 101px;
     margin-top: 33px !important;
+    --x: foo;
 }
 *#inspected* {    regular
     margin-left: 10px !important;
@@ -120,6 +125,7 @@
 {
     padding-top: 55px;
     margin-top: 101px !important;
+    --x: foo;
 }
 *#inspected* {    regular
     margin-left: 10px !important;
@@ -143,6 +149,7 @@
 {
     padding-top: 55px;
     margin-top: 33px !important;
+    --x: foo;
     margin-bottom: 101px;
 }
 *#inspected* {    regular
@@ -176,3 +183,27 @@
     padding-right: 101px;
 }
 
+Running test: testChangeCustomProperty
+{
+    padding-top: 55px;
+    margin-top: 33px !important;
+    --x: bar;
+}
+*#inspected* {    regular
+    margin-left: 10px !important;
+}
+*#inspected* {    regular
+    padding: 10px 20px 30px 40px;
+    padding-top: 50px;
+    padding-right: 20px;
+    padding-bottom: 30px;
+    padding-left: 40px;
+}
+@media (min-width: 1px)
+    *#inspected* {    regular
+        padding-left: 5px;
+        margin-left: 20px;
+        padding-left: 10px;
+        margin-top: 15px !important;
+    }
+
diff --git a/third_party/blink/web_tests/inspector-protocol/css/css-set-effective-property-value.js b/third_party/blink/web_tests/inspector-protocol/css/css-set-effective-property-value.js
index 35e51be..70c6860d 100644
--- a/third_party/blink/web_tests/inspector-protocol/css/css-set-effective-property-value.js
+++ b/third_party/blink/web_tests/inspector-protocol/css/css-set-effective-property-value.js
@@ -1,7 +1,7 @@
 (async function(testRunner) {
   var {page, session, dp} = await testRunner.startHTML(`
 <link rel='stylesheet' href='${testRunner.url('resources/set-active-property-value.css')}'/>
-<div id='inspected' style='padding-top: 55px; margin-top: 33px !important;'></div>
+<div id='inspected' style='padding-top: 55px; margin-top: 33px !important; --x:foo'></div>
 <div id='append-test' style='padding-left: 10px'/>
   `, 'The test verifies functionality of protocol method CSS.setEffectivePropertyValueForNode.');
 
@@ -55,7 +55,12 @@
       testRunner.log('Resulting styles');
       await dp.CSS.setEffectivePropertyValueForNode({nodeId, propertyName: 'padding-right', value : '101px'});
       await cssHelper.loadAndDumpInlineAndMatchingRules(documentNodeId, '#append-test', true /* omitLog */);
+    },
+
+    async function testChangeCustomProperty() {
+      await updateProperty('--x', 'bar');
     }
+
   ]);
 });
 
diff --git a/third_party/blink/web_tests/virtual/out-of-blink-cspee/external/wpt/content-security-policy/embedded-enforcement/allow_csp_from-header-expected.txt b/third_party/blink/web_tests/virtual/out-of-blink-cspee/external/wpt/content-security-policy/embedded-enforcement/allow_csp_from-header-expected.txt
index 11afaac..54921ea 100644
--- a/third_party/blink/web_tests/virtual/out-of-blink-cspee/external/wpt/content-security-policy/embedded-enforcement/allow_csp_from-header-expected.txt
+++ b/third_party/blink/web_tests/virtual/out-of-blink-cspee/external/wpt/content-security-policy/embedded-enforcement/allow_csp_from-header-expected.txt
@@ -8,7 +8,7 @@
 PASS Cross origin iframe with correct Allow-CSP-From header is allowed.
 PASS Iframe with improper Allow-CSP-From header gets blocked.
 PASS Allow-CSP-From header with a star value allows cross origin frame.
-FAIL Star Allow-CSP-From header enforces EmbeddingCSP. assert_equals: expected (string) "inline" but got (undefined) undefined
-FAIL Allow-CSP-From header enforces EmbeddingCSP. assert_equals: expected (string) "inline" but got (undefined) undefined
+PASS Star Allow-CSP-From header enforces EmbeddingCSP.
+PASS Allow-CSP-From header enforces EmbeddingCSP.
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/virtual/out-of-blink-cspee/external/wpt/content-security-policy/embedded-enforcement/required-csp-header-cascade-expected.txt b/third_party/blink/web_tests/virtual/out-of-blink-cspee/external/wpt/content-security-policy/embedded-enforcement/required-csp-header-cascade-expected.txt
deleted file mode 100644
index 454abe66..0000000
--- a/third_party/blink/web_tests/virtual/out-of-blink-cspee/external/wpt/content-security-policy/embedded-enforcement/required-csp-header-cascade-expected.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-This is a testharness.js-based test.
-PASS Test same origin: Test same policy for both iframes
-PASS Test same origin: Test more restrictive policy on second iframe
-FAIL Test same origin: Test less restrictive policy on second iframe assert_unreached: Child iframes have unexpected csp:"script-src 'unsafe-inline';" Reached unreachable code
-PASS Test same origin: Test no policy on second iframe
-PASS Test same origin: Test no policy on first iframe
-PASS Test same origin: Test invalid policy on first iframe (bad directive)
-PASS Test same origin: Test invalid policy on first iframe (report directive)
-PASS Test same origin: Test invalid policy on second iframe (bad directive)
-PASS Test same origin: Test invalid policy on second iframe (report directive)
-Harness: the test ran to completion.
-
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 5b1d11b..3789acd4 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
@@ -882,6 +882,7 @@
 [Worker]     attribute @@toStringTag
 [Worker]     method close
 [Worker]     method constructor
+[Worker]     method flush
 [Worker]     method getLength
 [Worker]     method read
 [Worker]     method write
@@ -889,6 +890,7 @@
 [Worker]     attribute @@toStringTag
 [Worker]     method close
 [Worker]     method constructor
+[Worker]     method flush
 [Worker]     method getLength
 [Worker]     method read
 [Worker]     method write
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 3b1b26a4..414350d7 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
@@ -5439,6 +5439,7 @@
     attribute @@toStringTag
     method close
     method constructor
+    method flush
     method getLength
     method read
     method write
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 3a63e8f..e7bc30e7 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
@@ -849,6 +849,7 @@
 [Worker]     attribute @@toStringTag
 [Worker]     method close
 [Worker]     method constructor
+[Worker]     method flush
 [Worker]     method getLength
 [Worker]     method read
 [Worker]     method write
diff --git a/tools/binary_size/libsupersize/path_util.py b/tools/binary_size/libsupersize/path_util.py
index 7712d54..bf123b4e 100644
--- a/tools/binary_size/libsupersize/path_util.py
+++ b/tools/binary_size/libsupersize/path_util.py
@@ -6,7 +6,6 @@
 
 import abc
 import distutils.spawn
-import json
 import logging
 import os
 
@@ -135,11 +134,11 @@
 
 
 def _LoadBuildVars(output_directory):
-  build_vars_path = os.path.join(output_directory, 'build_vars.json')
+  build_vars_path = os.path.join(output_directory, 'build_vars.txt')
   if os.path.exists(build_vars_path):
     with open(build_vars_path) as f:
-      return json.load(f)
-  return {}
+      return dict(l.rstrip().split('=', 1) for l in f if '=' in l)
+  return dict()
 
 
 def GetSrcRootFromOutputDirectory(output_directory):
diff --git a/tools/binary_size/libsupersize/testdata/mock_source_directory/out/Release/build_vars.json b/tools/binary_size/libsupersize/testdata/mock_source_directory/out/Release/build_vars.json
deleted file mode 100644
index 53e11e8e4..0000000
--- a/tools/binary_size/libsupersize/testdata/mock_source_directory/out/Release/build_vars.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-  "android_sdk_root": "../../../mock_sdk",
-  "android_tool_prefix": "../../../mock_toolchain/"
-}
diff --git a/tools/binary_size/libsupersize/testdata/mock_source_directory/out/Release/build_vars.txt b/tools/binary_size/libsupersize/testdata/mock_source_directory/out/Release/build_vars.txt
new file mode 100644
index 0000000..ab47517
--- /dev/null
+++ b/tools/binary_size/libsupersize/testdata/mock_source_directory/out/Release/build_vars.txt
@@ -0,0 +1,2 @@
+android_sdk_root=../../../mock_sdk
+android_tool_prefix=../../../mock_toolchain/
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 3a8cbff..5d31da5 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -6244,6 +6244,7 @@
   <int value="228" label="INPUT_ROUTER_INVALID_EVENT_SOURCE"/>
   <int value="229" label="RFH_INACTIVE_CHECK_FROM_SPECULATIVE_RFH"/>
   <int value="230" label="RFH_SUBFRAME_CAPTURE_ON_MAIN_FRAME"/>
+  <int value="231" label="RFH_CSP_ATTRIBUTE"/>
 </enum>
 
 <enum name="BadMessageReasonExtensions">
@@ -16010,6 +16011,44 @@
   <int value="2" label="Platform dual-screen support used"/>
 </enum>
 
+<enum name="DevtoolsExperiments">
+  <int value="0" label="applyCustomStylesheet"/>
+  <int value="1" label="captureNodeCreationStacks"/>
+  <int value="2" label="sourcesPrettyPrint"/>
+  <int value="3" label="backgroundServices"/>
+  <int value="4" label="backgroundServicesNotifications"/>
+  <int value="5" label="backgroundServicesPaymentHandler"/>
+  <int value="6" label="backgroundServicesPushMessaging"/>
+  <int value="7" label="blackboxJSFramesOnTimeline"/>
+  <int value="8" label="cssOverview"/>
+  <int value="9" label="emptySourceMapAutoStepping"/>
+  <int value="10" label="inputEventsOnTimelineOverview"/>
+  <int value="11" label="liveHeapProfile"/>
+  <int value="12" label="nativeHeapProfiler"/>
+  <int value="13" label="protocolMonitor"/>
+  <int value="14" label="issuesPane"/>
+  <int value="15" label="developerResourcesView"/>
+  <int value="16" label="recordCoverageWithPerformanceTracing"/>
+  <int value="17" label="samplingHeapProfilerTimeline"/>
+  <int value="18" label="showOptionToNotTreatGlobalObjectsAsRoots"/>
+  <int value="19" label="sourceDiff"/>
+  <int value="20" label="sourceOrderViewer"/>
+  <int value="21" label="spotlight"/>
+  <int value="22" label="webauthnPane"/>
+  <int value="23" label="customKeyboardShortcuts"/>
+  <int value="24" label="timelineEventInitiators"/>
+  <int value="25" label="timelineFlowEvents"/>
+  <int value="26" label="timelineInvalidationTracking"/>
+  <int value="27" label="timelineShowAllEvents"/>
+  <int value="28" label="timelineV8RuntimeCallStats"/>
+  <int value="29" label="timelineWebGL"/>
+  <int value="30" label="timelineReplayEvent"/>
+  <int value="31" label="wasmDWARFDebugging"/>
+  <int value="32" label="dualScreenSupport"/>
+  <int value="33" label="cssGridFeatures"/>
+  <int value="34" label="movableTabs"/>
+</enum>
+
 <enum name="DevtoolsGridSettingChanged">
   <int value="0" label="showGridBorder.none"/>
   <int value="1" label="showGridBorder.dashed"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 64e76214..e5bda54 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -39468,6 +39468,36 @@
   </summary>
 </histogram>
 
+<histogram name="DevTools.ExperimentDisabled" enum="DevtoolsExperiments"
+    expires_after="2021-07-31">
+  <owner>yangguo@chromium.org</owner>
+  <owner>brgoddar@microsoft.com</owner>
+  <owner>shanejc@microsoft.com</owner>
+  <summary>
+    Fired when a devtools experiment is disabled from the experiments tab.
+  </summary>
+</histogram>
+
+<histogram name="DevTools.ExperimentEnabled" enum="DevtoolsExperiments"
+    expires_after="2021-07-31">
+  <owner>yangguo@chromium.org</owner>
+  <owner>brgoddar@microsoft.com</owner>
+  <owner>shanejc@microsoft.com</owner>
+  <summary>
+    Fired when a devtools experiment is enabled from the experiments tab.
+  </summary>
+</histogram>
+
+<histogram name="DevTools.ExperimentEnabledAtLaunch" enum="DevtoolsExperiments"
+    expires_after="2021-07-31">
+  <owner>yangguo@chromium.org</owner>
+  <owner>brgoddar@microsoft.com</owner>
+  <owner>shanejc@microsoft.com</owner>
+  <summary>
+    Fires for each experiment that is enabled at the time of Devtools Launch.
+  </summary>
+</histogram>
+
 <histogram name="DevTools.GridSettingChanged" enum="DevtoolsGridSettingChanged"
     expires_after="M87">
   <obsolete>
@@ -135542,6 +135572,17 @@
   </summary>
 </histogram>
 
+<histogram name="Power.CpuAffinityExperiments.ProcessAffinityUpdateSuccess"
+    enum="BooleanSuccess" expires_after="2021-08-05">
+  <owner>eseckler@chromium.org</owner>
+  <owner>skyostil@chromium.org</owner>
+  <summary>
+    For clients enrolled in CPU affinity restriction experiments (e.g.
+    restricting execution to little cores only), records whether the CPU
+    affinity for a process could be succcessfully set.
+  </summary>
+</histogram>
+
 <histogram name="Power.CpuTimeSecondsPerCoreTypeAndFrequency" units="50 MHz"
     expires_after="2021-06-26">
   <owner>eseckler@chromium.org</owner>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 0082debd..27dde277 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -1,16 +1,16 @@
 {
     "trace_processor_shell": {
         "win": {
-            "hash": "4109a0936495f455a113241d44034b723778ee66",
-            "remote_path": "perfetto_binaries/trace_processor_shell/win/a4cba6578f09d606d32fe32bb0a0c6283ea6d198/trace_processor_shell.exe"
+            "hash": "9632b44419444a74efa0e5241589bf4235e4853b",
+            "remote_path": "perfetto_binaries/trace_processor_shell/win/01e0ccdc9b8b4697a343b8ef49f53fd489d79a41/trace_processor_shell.exe"
         },
         "mac": {
             "hash": "803679e20216dc532ebe9e915e84107065c4c79c",
-            "remote_path": "perfetto_binaries/trace_processor_shell/mac/a4cba6578f09d606d32fe32bb0a0c6283ea6d198/trace_processor_shell"
+            "remote_path": "perfetto_binaries/trace_processor_shell/mac/577cc158d1e2a9fc4b69c1e503f5d49f388dd856/trace_processor_shell"
         },
         "linux": {
-            "hash": "4bdc7c11640334a0bddbe287525847074cf4b576",
-            "remote_path": "perfetto_binaries/trace_processor_shell/linux/a4cba6578f09d606d32fe32bb0a0c6283ea6d198/trace_processor_shell"
+            "hash": "283e0325abb12d3cea2c5a23acbdc39cfc6dd13f",
+            "remote_path": "perfetto_binaries/trace_processor_shell/linux/01e0ccdc9b8b4697a343b8ef49f53fd489d79a41/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/win/IdleWakeups/idle_wakeups.cpp b/tools/win/IdleWakeups/idle_wakeups.cpp
index 03a0332..bbb579b 100644
--- a/tools/win/IdleWakeups/idle_wakeups.cpp
+++ b/tools/win/IdleWakeups/idle_wakeups.cpp
@@ -54,7 +54,19 @@
   }
 }
 
-// This class holds the app state and constains a number of utilities for
+// Count newly created processes: those in |processes| but not
+// |previous_processes|.
+size_t GetNumProcessesCreated(const ProcessDataMap& previous_processes,
+                              const ProcessDataMap& processes) {
+  size_t num_processes_created = 0;
+  for (auto& process : processes) {
+    if (previous_processes.find(process.first) == previous_processes.end())
+      num_processes_created++;
+  }
+  return num_processes_created;
+}
+
+// This class holds the app state and contains a number of utilities for
 // collecting and diffing snapshots of data, handling processes, etc.
 class IdleWakeups {
  public:
@@ -279,11 +291,15 @@
       system_information_sampler.TakeSnapshot();
 
   the_app.OpenProcesses(*previous_snapshot);
+  const size_t initial_number_of_processes =
+      previous_snapshot->processes.size();
+  size_t final_number_of_processes = initial_number_of_processes;
 
   ULONG cumulative_idle_wakeups_per_sec = 0;
   double cumulative_cpu_usage = 0.0;
   ULONGLONG cumulative_working_set = 0;
   double cumulative_energy = 0.0;
+  size_t cumulative_processes_created = 0;
 
   ResultVector results;
 
@@ -300,6 +316,10 @@
     std::unique_ptr<ProcessDataSnapshot> snapshot =
         system_information_sampler.TakeSnapshot();
     size_t number_of_processes = snapshot->processes.size();
+    final_number_of_processes = number_of_processes;
+
+    cumulative_processes_created += GetNumProcessesCreated(
+        previous_snapshot->processes, snapshot->processes);
 
     Result result = the_app.DiffSnapshots(*previous_snapshot, *snapshot);
     previous_snapshot = std::move(snapshot);
@@ -345,5 +365,10 @@
          median_result.idle_wakeups_per_sec, median_result.cpu_usage, '%',
          median_result.working_set / 1024.0, median_result.power);
 
+  printf("\nProcesses created:   %zu\n", cumulative_processes_created);
+  printf("Processes destroyed: %zu\n", initial_number_of_processes +
+                                           cumulative_processes_created -
+                                           final_number_of_processes);
+
   return 0;
 }
diff --git a/ui/accessibility/ax_event_generator.cc b/ui/accessibility/ax_event_generator.cc
index da9314d4..f57aa0b0 100644
--- a/ui/accessibility/ax_event_generator.cc
+++ b/ui/accessibility/ax_event_generator.cc
@@ -47,6 +47,19 @@
   }
 }
 
+// If a node toggled its ignored state, don't also fire children-changed because
+// platforms likely will do that in response to ignored-changed.
+// Suppress name- and description-changed because those can be emitted as a side
+// effect of calculating alternative text values for a newly-displayed object.
+// Ditto for text attributes such as foreground and background colors.
+void RemoveEventsDueToIgnoredChanged(
+    std::set<AXEventGenerator::EventParams>* node_events) {
+  RemoveEvent(node_events, AXEventGenerator::Event::CHILDREN_CHANGED);
+  RemoveEvent(node_events, AXEventGenerator::Event::DESCRIPTION_CHANGED);
+  RemoveEvent(node_events, AXEventGenerator::Event::NAME_CHANGED);
+  RemoveEvent(node_events, AXEventGenerator::Event::TEXT_ATTRIBUTE_CHANGED);
+}
+
 }  // namespace
 
 AXEventGenerator::EventParams::EventParams(
@@ -665,7 +678,51 @@
          data.relative_bounds.bounds.height();
 }
 
+void AXEventGenerator::TrimEventsDueToAncestorIgnoredChanged(
+    AXNode* node,
+    std::map<AXNode*, bool>& ancestor_ignored_changed_map) {
+  DCHECK(node);
+
+  // Recursively compute and cache ancestor ignored changed results in
+  // |ancestor_ignored_changed_map|, if |node|'s ancestors have become ignored
+  // and the ancestor's ignored changed results have not been cached.
+  if (node->parent() &&
+      !base::Contains(ancestor_ignored_changed_map, node->parent())) {
+    TrimEventsDueToAncestorIgnoredChanged(node->parent(),
+                                          ancestor_ignored_changed_map);
+  }
+
+  // If an ancestor of |node| changed to ignored state, update the corresponding
+  // entry in the map for |node| based on the ancestor result (i.e. if an
+  // ancestor changed to ignored state, set the entry in the map to true for the
+  // current node). If |node|'s state changed to ignored as well, we want to
+  // remove its IGNORED_CHANGED event.
+  const auto& map_iter = ancestor_ignored_changed_map.find(node->parent());
+  const auto& events_iter = tree_events_.find(node);
+  if (map_iter != ancestor_ignored_changed_map.end() && map_iter->second) {
+    ancestor_ignored_changed_map.insert(std::make_pair(node, true));
+    if (node->IsIgnored() && events_iter != tree_events_.end()) {
+      RemoveEvent(&(events_iter->second), Event::IGNORED_CHANGED);
+      RemoveEventsDueToIgnoredChanged(&(events_iter->second));
+    }
+    return;
+  }
+
+  // If ignored changed results are not cached, calculate the corresponding
+  // entry for |node| in the map using the ignored states and events of |node|.
+  if (events_iter != tree_events_.end() &&
+      HasEvent(events_iter->second, Event::IGNORED_CHANGED) &&
+      node->IsIgnored()) {
+    ancestor_ignored_changed_map.insert(std::make_pair(node, true));
+    return;
+  }
+
+  ancestor_ignored_changed_map.insert(std::make_pair(node, false));
+}
+
 void AXEventGenerator::PostprocessEvents() {
+  std::map<AXNode*, bool> ancestor_ignored_changed_map;
+
   auto iter = tree_events_.begin();
   while (iter != tree_events_.end()) {
     AXNode* node = iter->first;
@@ -678,17 +735,13 @@
       RemoveEvent(&node_events, Event::LIVE_REGION_CHANGED);
     }
 
-    // If a node toggled its ignored state, don't also fire children-changed
-    // because platforms likely will do that in response to ignored-changed.
-    // Suppress name- and description-changed because those can be emitted
-    // as a side effect of calculating alternative text values for a newly-
-    // displayed object. Ditto for text attributes such as foreground and
-    // background colors.
     if (HasEvent(node_events, Event::IGNORED_CHANGED)) {
-      RemoveEvent(&node_events, Event::CHILDREN_CHANGED);
-      RemoveEvent(&node_events, Event::DESCRIPTION_CHANGED);
-      RemoveEvent(&node_events, Event::NAME_CHANGED);
-      RemoveEvent(&node_events, Event::TEXT_ATTRIBUTE_CHANGED);
+      // If a node toggled its ignored state from show to hide, we only want to
+      // fire IGNORED_CHANGED event on the top most ancestor where this ignored
+      // state change takes place and suppress all the descendants's
+      // IGNORED_CHANGED events.
+      TrimEventsDueToAncestorIgnoredChanged(node, ancestor_ignored_changed_map);
+      RemoveEventsDueToIgnoredChanged(&node_events);
     }
 
     // When the selected option in an expanded select element changes, the
diff --git a/ui/accessibility/ax_event_generator.h b/ui/accessibility/ax_event_generator.h
index 2755932f..b068624 100644
--- a/ui/accessibility/ax_event_generator.h
+++ b/ui/accessibility/ax_event_generator.h
@@ -235,6 +235,18 @@
   void FireActiveDescendantEvents();
   void FireRelationSourceEvents(AXTree* tree, AXNode* target_node);
   bool ShouldFireLoadEvents(AXNode* node);
+  // Remove excessive events for a tree update containing node.
+  // We remove certain events on a node when it changes to IGNORED state and one
+  // of the node's ancestor has also changed to IGNORED in the same tree update.
+  // |ancestor_has_ignored_map| contains if a node's ancestor has changed to
+  // IGNORED state.
+  // Map's key is: an ax node.
+  // Map's value is:
+  // - True if an ancestor of node changed to IGNORED state.
+  // - False if no ancestor of node changed to IGNORED state.
+  void TrimEventsDueToAncestorIgnoredChanged(
+      AXNode* node,
+      std::map<AXNode*, bool>& ancestor_has_ignored_map);
   void PostprocessEvents();
   static void GetRestrictionStates(ax::mojom::Restriction restriction,
                                    bool* is_enabled,
diff --git a/ui/accessibility/ax_event_generator_unittest.cc b/ui/accessibility/ax_event_generator_unittest.cc
index 3f9eb022f..611ce33 100644
--- a/ui/accessibility/ax_event_generator_unittest.cc
+++ b/ui/accessibility/ax_event_generator_unittest.cc
@@ -1152,6 +1152,459 @@
                   HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 2)));
 }
 
+TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly1) {
+  // BEFORE
+  //   1 (IGN)
+  //  / \
+  // 2   3 (IGN)
+  // AFTER
+  //   1 (IGN)
+  //  /      \
+  // 2 (IGN)  3
+  // IGNORED_CHANGED expected on #2, #3
+
+  AXTreeUpdate initial_state;
+  initial_state.root_id = 1;
+  initial_state.nodes.resize(3);
+  initial_state.nodes[0].id = 1;
+  initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea;
+  initial_state.nodes[0].AddState(ax::mojom::State::kIgnored);
+  initial_state.nodes[0].child_ids = {2, 3};
+
+  initial_state.nodes[1].id = 2;
+  initial_state.nodes[1].role = ax::mojom::Role::kStaticText;
+
+  initial_state.nodes[2].id = 3;
+  initial_state.nodes[2].role = ax::mojom::Role::kStaticText;
+  initial_state.nodes[2].AddState(ax::mojom::State::kIgnored);
+
+  AXTree tree(initial_state);
+
+  AXEventGenerator event_generator(&tree);
+  AXTreeUpdate update = initial_state;
+  update.nodes[1].AddState(ax::mojom::State::kIgnored);
+  update.nodes[2].RemoveState(ax::mojom::State::kIgnored);
+  ASSERT_TRUE(tree.Unserialize(update));
+  EXPECT_THAT(event_generator,
+              UnorderedElementsAre(
+                  HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 2),
+                  HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 3),
+                  HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 3)));
+}
+
+TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly2) {
+  // BEFORE
+  //   1
+  //   |
+  //   2
+  //  / \
+  // 3   4 (IGN)
+  // AFTER
+  //   1
+  //   |
+  //   2 ___
+  //  /      \
+  // 3 (IGN)  4
+  // IGNORED_CHANGED expected on #3, #4
+
+  AXTreeUpdate initial_state;
+  initial_state.root_id = 1;
+  initial_state.nodes.resize(4);
+  initial_state.nodes[0].id = 1;
+  initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea;
+  initial_state.nodes[0].child_ids = {2};
+
+  initial_state.nodes[1].id = 2;
+  initial_state.nodes[1].role = ax::mojom::Role::kGroup;
+  initial_state.nodes[1].child_ids = {3, 4};
+
+  initial_state.nodes[2].id = 3;
+  initial_state.nodes[2].role = ax::mojom::Role::kStaticText;
+
+  initial_state.nodes[3].id = 4;
+  initial_state.nodes[3].role = ax::mojom::Role::kStaticText;
+  initial_state.nodes[3].AddState(ax::mojom::State::kIgnored);
+
+  AXTree tree(initial_state);
+
+  AXEventGenerator event_generator(&tree);
+  AXTreeUpdate update = initial_state;
+  update.nodes[2].AddState(ax::mojom::State::kIgnored);
+  update.nodes[3].RemoveState(ax::mojom::State::kIgnored);
+  ASSERT_TRUE(tree.Unserialize(update));
+  EXPECT_THAT(event_generator,
+              UnorderedElementsAre(
+                  HasEventAtNode(AXEventGenerator::Event::CHILDREN_CHANGED, 2),
+                  HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 3),
+                  HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 4),
+                  HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 4)));
+}
+
+TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly3) {
+  // BEFORE
+  //   1
+  //   |
+  //   2 ___
+  //  /      \
+  // 3 (IGN)  4
+  // AFTER
+  //   1 (IGN)
+  //   |
+  //   2
+  //  /  \
+  // 3    4 (IGN)
+  // IGNORED_CHANGED expected on #1, #3
+
+  AXTreeUpdate initial_state;
+  initial_state.root_id = 1;
+  initial_state.nodes.resize(4);
+  initial_state.nodes[0].id = 1;
+  initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea;
+  initial_state.nodes[0].child_ids = {2};
+
+  initial_state.nodes[1].id = 2;
+  initial_state.nodes[1].role = ax::mojom::Role::kGroup;
+  initial_state.nodes[1].child_ids = {3, 4};
+
+  initial_state.nodes[2].id = 3;
+  initial_state.nodes[2].role = ax::mojom::Role::kStaticText;
+  initial_state.nodes[2].AddState(ax::mojom::State::kIgnored);
+
+  initial_state.nodes[3].id = 4;
+  initial_state.nodes[3].role = ax::mojom::Role::kStaticText;
+
+  AXTree tree(initial_state);
+
+  AXEventGenerator event_generator(&tree);
+  AXTreeUpdate update = initial_state;
+  update.nodes[0].AddState(ax::mojom::State::kIgnored);
+  update.nodes[2].RemoveState(ax::mojom::State::kIgnored);
+  update.nodes[3].AddState(ax::mojom::State::kIgnored);
+  ASSERT_TRUE(tree.Unserialize(update));
+  EXPECT_THAT(event_generator,
+              UnorderedElementsAre(
+                  HasEventAtNode(AXEventGenerator::Event::CHILDREN_CHANGED, 2),
+                  HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 1),
+                  HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 3),
+                  HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 3)));
+}
+
+TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly4) {
+  // BEFORE
+  //         1 (IGN)
+  //         |
+  //         2
+  //         |
+  //         3 (IGN)
+  //         |
+  //         4 (IGN)
+  //         |
+  //    ____ 5  _____
+  //  /       |       \
+  // 6 (IGN)  7 (IGN)  8
+  // AFTER
+  //         1 (IGN)
+  //         |
+  //         2
+  //         |
+  //         3 (IGN)
+  //         |
+  //         4 (IGN)
+  //         |
+  //    ____ 5  _____
+  //  /       |       \
+  // 6        7        8 (IGN)
+
+  // IGNORED_CHANGED expected on #6, #7, #8
+
+  AXTreeUpdate initial_state;
+  initial_state.root_id = 1;
+  initial_state.nodes.resize(8);
+  initial_state.nodes[0].id = 1;
+  initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea;
+  initial_state.nodes[0].child_ids = {2};
+
+  initial_state.nodes[1].id = 2;
+  initial_state.nodes[1].role = ax::mojom::Role::kGroup;
+  initial_state.nodes[1].child_ids = {3};
+
+  initial_state.nodes[2].id = 3;
+  initial_state.nodes[2].role = ax::mojom::Role::kGroup;
+  initial_state.nodes[2].child_ids = {4};
+  initial_state.nodes[2].AddState(ax::mojom::State::kIgnored);
+
+  initial_state.nodes[3].id = 4;
+  initial_state.nodes[3].role = ax::mojom::Role::kGroup;
+  initial_state.nodes[3].child_ids = {5};
+  initial_state.nodes[3].AddState(ax::mojom::State::kIgnored);
+
+  initial_state.nodes[4].id = 5;
+  initial_state.nodes[4].role = ax::mojom::Role::kGroup;
+  initial_state.nodes[4].child_ids = {6, 7, 8};
+
+  initial_state.nodes[5].id = 6;
+  initial_state.nodes[5].role = ax::mojom::Role::kStaticText;
+  initial_state.nodes[5].AddState(ax::mojom::State::kIgnored);
+
+  initial_state.nodes[6].id = 7;
+  initial_state.nodes[6].role = ax::mojom::Role::kStaticText;
+  initial_state.nodes[6].AddState(ax::mojom::State::kIgnored);
+
+  initial_state.nodes[7].id = 8;
+  initial_state.nodes[7].role = ax::mojom::Role::kStaticText;
+
+  AXTree tree(initial_state);
+
+  AXEventGenerator event_generator(&tree);
+  AXTreeUpdate update = initial_state;
+  update.nodes[5].RemoveState(ax::mojom::State::kIgnored);
+  update.nodes[6].RemoveState(ax::mojom::State::kIgnored);
+  update.nodes[7].AddState(ax::mojom::State::kIgnored);
+  ASSERT_TRUE(tree.Unserialize(update));
+  EXPECT_THAT(event_generator,
+              UnorderedElementsAre(
+                  HasEventAtNode(AXEventGenerator::Event::CHILDREN_CHANGED, 5),
+                  HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 6),
+                  HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 7),
+                  HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 6),
+                  HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 7),
+                  HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 8)));
+}
+
+TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly5) {
+  // BEFORE
+  //         1
+  //         |
+  //         2
+  //         |
+  //         3 (IGN)
+  //         |
+  //         4 (IGN)
+  //         |
+  //    ____ 5  _____
+  //  /       |       \
+  // 6 (IGN)  7        8
+  // AFTER
+  //         1 (IGN)
+  //         |
+  //         2
+  //         |
+  //         3 (IGN)
+  //         |
+  //         4 (IGN)
+  //         |
+  //    ____ 5  _____
+  //  /       |       \
+  // 6        7 (IGN)  8 (IGN)
+
+  // IGNORED_CHANGED expected on #1, #6
+
+  AXTreeUpdate initial_state;
+  initial_state.root_id = 1;
+  initial_state.nodes.resize(8);
+  initial_state.nodes[0].id = 1;
+  initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea;
+  initial_state.nodes[0].child_ids = {2};
+
+  initial_state.nodes[1].id = 2;
+  initial_state.nodes[1].role = ax::mojom::Role::kGroup;
+  initial_state.nodes[1].child_ids = {3};
+
+  initial_state.nodes[2].id = 3;
+  initial_state.nodes[2].role = ax::mojom::Role::kGroup;
+  initial_state.nodes[2].child_ids = {4};
+  initial_state.nodes[2].AddState(ax::mojom::State::kIgnored);
+
+  initial_state.nodes[3].id = 4;
+  initial_state.nodes[3].role = ax::mojom::Role::kGroup;
+  initial_state.nodes[3].child_ids = {5};
+  initial_state.nodes[3].AddState(ax::mojom::State::kIgnored);
+
+  initial_state.nodes[4].id = 5;
+  initial_state.nodes[4].role = ax::mojom::Role::kGroup;
+  initial_state.nodes[4].child_ids = {6, 7, 8};
+
+  initial_state.nodes[5].id = 6;
+  initial_state.nodes[5].role = ax::mojom::Role::kStaticText;
+  initial_state.nodes[5].AddState(ax::mojom::State::kIgnored);
+
+  initial_state.nodes[6].id = 7;
+  initial_state.nodes[6].role = ax::mojom::Role::kStaticText;
+
+  initial_state.nodes[7].id = 8;
+  initial_state.nodes[7].role = ax::mojom::Role::kStaticText;
+
+  AXTree tree(initial_state);
+
+  AXEventGenerator event_generator(&tree);
+  AXTreeUpdate update = initial_state;
+  update.nodes[0].AddState(ax::mojom::State::kIgnored);
+  update.nodes[5].RemoveState(ax::mojom::State::kIgnored);
+  update.nodes[6].AddState(ax::mojom::State::kIgnored);
+  update.nodes[7].AddState(ax::mojom::State::kIgnored);
+  ASSERT_TRUE(tree.Unserialize(update));
+  EXPECT_THAT(event_generator,
+              UnorderedElementsAre(
+                  HasEventAtNode(AXEventGenerator::Event::CHILDREN_CHANGED, 5),
+                  HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 6),
+                  HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 1),
+                  HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 6)));
+}
+
+TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly6) {
+  // BEFORE
+  //         1 (IGN)
+  //         |
+  //         2
+  //         |
+  //         3
+  //         |
+  //         4
+  //         |
+  //    ____ 5  _____
+  //  /       |       \
+  // 6 (IGN)  7 (IGN)  8
+  // AFTER
+  //         1
+  //         |
+  //         2
+  //         |
+  //         3
+  //         |
+  //         4
+  //         |
+  //    ____ 5  _____
+  //  /       |       \
+  // 6        7        8 (IGN)
+
+  // IGNORED_CHANGED expected on #1, #6, #7, #8
+
+  AXTreeUpdate initial_state;
+  initial_state.root_id = 1;
+  initial_state.nodes.resize(8);
+  initial_state.nodes[0].id = 1;
+  initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea;
+  initial_state.nodes[0].child_ids = {2};
+  initial_state.nodes[0].AddState(ax::mojom::State::kIgnored);
+
+  initial_state.nodes[1].id = 2;
+  initial_state.nodes[1].role = ax::mojom::Role::kGroup;
+  initial_state.nodes[1].child_ids = {3};
+
+  initial_state.nodes[2].id = 3;
+  initial_state.nodes[2].role = ax::mojom::Role::kGroup;
+  initial_state.nodes[2].child_ids = {4};
+
+  initial_state.nodes[3].id = 4;
+  initial_state.nodes[3].role = ax::mojom::Role::kGroup;
+  initial_state.nodes[3].child_ids = {5};
+
+  initial_state.nodes[4].id = 5;
+  initial_state.nodes[4].role = ax::mojom::Role::kGroup;
+  initial_state.nodes[4].child_ids = {6, 7, 8};
+
+  initial_state.nodes[5].id = 6;
+  initial_state.nodes[5].role = ax::mojom::Role::kStaticText;
+  initial_state.nodes[5].AddState(ax::mojom::State::kIgnored);
+
+  initial_state.nodes[6].id = 7;
+  initial_state.nodes[6].role = ax::mojom::Role::kStaticText;
+  initial_state.nodes[6].AddState(ax::mojom::State::kIgnored);
+
+  initial_state.nodes[7].id = 8;
+  initial_state.nodes[7].role = ax::mojom::Role::kStaticText;
+
+  AXTree tree(initial_state);
+
+  AXEventGenerator event_generator(&tree);
+  AXTreeUpdate update = initial_state;
+  update.nodes[0].RemoveState(ax::mojom::State::kIgnored);
+  update.nodes[5].RemoveState(ax::mojom::State::kIgnored);
+  update.nodes[6].RemoveState(ax::mojom::State::kIgnored);
+  update.nodes[7].AddState(ax::mojom::State::kIgnored);
+  ASSERT_TRUE(tree.Unserialize(update));
+  EXPECT_THAT(event_generator,
+              UnorderedElementsAre(
+                  HasEventAtNode(AXEventGenerator::Event::CHILDREN_CHANGED, 5),
+                  HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 1),
+                  HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 6),
+                  HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 7),
+                  HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 1),
+                  HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 6),
+                  HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 7),
+                  HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 8)));
+}
+
+TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly7) {
+  // BEFORE
+  //       1 (IGN)
+  //       |
+  //       2 (IGN)
+  //       |
+  //       3
+  //       |
+  //    __ 4 ___
+  //  /          \
+  // 5 (IGN)      6 (IGN)
+  // AFTER
+  //       1
+  //       |
+  //       2
+  //       |
+  //       3 (IGN)
+  //       |
+  //    __ 4 (IGN)
+  //  /           \
+  // 5 (IGN)       6 (IGN)
+
+  // IGNORED_CHANGED expected on #1, #2, #3
+
+  AXTreeUpdate initial_state;
+  initial_state.root_id = 1;
+  initial_state.nodes.resize(6);
+  initial_state.nodes[0].id = 1;
+  initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea;
+  initial_state.nodes[0].child_ids = {2};
+  initial_state.nodes[0].AddState(ax::mojom::State::kIgnored);
+
+  initial_state.nodes[1].id = 2;
+  initial_state.nodes[1].role = ax::mojom::Role::kGroup;
+  initial_state.nodes[1].child_ids = {3};
+  initial_state.nodes[1].AddState(ax::mojom::State::kIgnored);
+
+  initial_state.nodes[2].id = 3;
+  initial_state.nodes[2].role = ax::mojom::Role::kGroup;
+  initial_state.nodes[2].child_ids = {4};
+
+  initial_state.nodes[3].id = 4;
+  initial_state.nodes[3].role = ax::mojom::Role::kGroup;
+  initial_state.nodes[3].child_ids = {5, 6};
+
+  initial_state.nodes[4].id = 5;
+  initial_state.nodes[4].role = ax::mojom::Role::kStaticText;
+  initial_state.nodes[4].AddState(ax::mojom::State::kIgnored);
+
+  initial_state.nodes[5].id = 6;
+  initial_state.nodes[5].role = ax::mojom::Role::kStaticText;
+  initial_state.nodes[5].AddState(ax::mojom::State::kIgnored);
+
+  AXTree tree(initial_state);
+
+  AXEventGenerator event_generator(&tree);
+  AXTreeUpdate update = initial_state;
+  update.nodes[0].RemoveState(ax::mojom::State::kIgnored);
+  update.nodes[1].RemoveState(ax::mojom::State::kIgnored);
+  update.nodes[2].AddState(ax::mojom::State::kIgnored);
+  update.nodes[3].AddState(ax::mojom::State::kIgnored);
+  ASSERT_TRUE(tree.Unserialize(update));
+  EXPECT_THAT(event_generator,
+              UnorderedElementsAre(
+                  HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 1),
+                  HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 1),
+                  HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 2),
+                  HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 3)));
+}
+
 TEST(AXEventGeneratorTest, ActiveDescendantChangeOnDescendant) {
   AXTreeUpdate initial_state;
   initial_state.root_id = 1;
diff --git a/ui/android/java/src/org/chromium/ui/DropdownPopupWindowInterface.java b/ui/android/java/src/org/chromium/ui/DropdownPopupWindowInterface.java
index 83e89fba..60ba7d5 100644
--- a/ui/android/java/src/org/chromium/ui/DropdownPopupWindowInterface.java
+++ b/ui/android/java/src/org/chromium/ui/DropdownPopupWindowInterface.java
@@ -10,12 +10,9 @@
 import android.widget.ListView;
 import android.widget.PopupWindow;
 
-import androidx.annotation.VisibleForTesting;
-
 /**
  * The interface for dropdown popup window.
  */
-@VisibleForTesting
 public interface DropdownPopupWindowInterface {
     /**
      * Sets the adapter that provides the data and the views to represent the data
diff --git a/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java b/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java
index 96e78c26..0c589c4 100644
--- a/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java
+++ b/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java
@@ -258,7 +258,6 @@
     /**
      * Set the delegate that will handle android permissions requests.
      */
-    @VisibleForTesting
     public void setAndroidPermissionDelegate(AndroidPermissionDelegate delegate) {
         mPermissionDelegate = delegate;
     }
@@ -731,7 +730,6 @@
         return mApplicationBottomInsetProvider;
     }
 
-    @VisibleForTesting
     public void setKeyboardDelegate(KeyboardVisibilityDelegate keyboardDelegate) {
         mKeyboardVisibilityDelegate = keyboardDelegate;
         // TODO(fhorschig): Remove - every caller should use the window to get the delegate.
diff --git a/ui/base/clipboard/clipboard.h b/ui/base/clipboard/clipboard.h
index 2cc065a2..62004e65 100644
--- a/ui/base/clipboard/clipboard.h
+++ b/ui/base/clipboard/clipboard.h
@@ -227,6 +227,12 @@
   // Resets the clipboard last modified time to Time::Time().
   virtual void ClearLastModifiedTime();
 
+#if defined(USE_OZONE)
+  // Returns whether the selection buffer is available.  This is true for some
+  // Linux platforms.
+  virtual bool IsSelectionBufferAvailable() const = 0;
+#endif  // defined(USE_OZONE)
+
  protected:
   // PortableFormat designates the type of data to be stored in the clipboard.
   // This designation is shared across all OSes. The system-specific designation
diff --git a/ui/base/clipboard/clipboard_non_backed.cc b/ui/base/clipboard/clipboard_non_backed.cc
index 680deff..211e952864e 100644
--- a/ui/base/clipboard/clipboard_non_backed.cc
+++ b/ui/base/clipboard/clipboard_non_backed.cc
@@ -585,6 +585,10 @@
   clipboard_internal_->ReadData(format.GetName(), result);
 }
 
+bool ClipboardNonBacked::IsSelectionBufferAvailable() const {
+  return false;
+}
+
 void ClipboardNonBacked::WritePortableRepresentations(
     ClipboardBuffer buffer,
     const ObjectMap& objects,
diff --git a/ui/base/clipboard/clipboard_non_backed.h b/ui/base/clipboard/clipboard_non_backed.h
index da427a4..77dff2cd 100644
--- a/ui/base/clipboard/clipboard_non_backed.h
+++ b/ui/base/clipboard/clipboard_non_backed.h
@@ -86,6 +86,7 @@
   void ReadData(const ClipboardFormatType& format,
                 const ClipboardDataEndpoint* data_dst,
                 std::string* result) const override;
+  bool IsSelectionBufferAvailable() const override;
   void WritePortableRepresentations(
       ClipboardBuffer buffer,
       const ObjectMap& objects,
diff --git a/ui/base/clipboard/clipboard_ozone.cc b/ui/base/clipboard/clipboard_ozone.cc
index 93db5c9a..4db9efa6 100644
--- a/ui/base/clipboard/clipboard_ozone.cc
+++ b/ui/base/clipboard/clipboard_ozone.cc
@@ -77,6 +77,7 @@
   bool IsSelectionOwner(ClipboardBuffer buffer) override { return false; }
   void SetSequenceNumberUpdateCb(
       PlatformClipboard::SequenceNumberUpdateCb cb) override {}
+  bool IsSelectionBufferAvailable() const override { return false; }
 };
 
 }  // namespace
@@ -101,6 +102,10 @@
 
   ~AsyncClipboardOzone() = default;
 
+  bool IsSelectionBufferAvailable() const {
+    return platform_clipboard_->IsSelectionBufferAvailable();
+  }
+
   base::span<uint8_t> ReadClipboardDataAndWait(ClipboardBuffer buffer,
                                                const std::string& mime_type) {
     // We can use a fastpath if we are the owner of the selection.
@@ -299,7 +304,7 @@
   base::RepeatingTimer abort_timer_;
 
   // Provides communication to a system clipboard under ozone level.
-  PlatformClipboard* platform_clipboard_ = nullptr;
+  PlatformClipboard* const platform_clipboard_ = nullptr;
 
   base::flat_map<ClipboardBuffer, uint64_t> clipboard_sequence_number_;
 
@@ -513,6 +518,10 @@
   result->assign(clipboard_data.begin(), clipboard_data.end());
 }
 
+bool ClipboardOzone::IsSelectionBufferAvailable() const {
+  return async_clipboard_ozone_->IsSelectionBufferAvailable();
+}
+
 // TODO(crbug.com/1103194): |data_src| should be supported
 void ClipboardOzone::WritePortableRepresentations(
     ClipboardBuffer buffer,
diff --git a/ui/base/clipboard/clipboard_ozone.h b/ui/base/clipboard/clipboard_ozone.h
index 65b3ac19..c254b02 100644
--- a/ui/base/clipboard/clipboard_ozone.h
+++ b/ui/base/clipboard/clipboard_ozone.h
@@ -66,6 +66,7 @@
   void ReadData(const ClipboardFormatType& format,
                 const ClipboardDataEndpoint* data_dst,
                 std::string* result) const override;
+  bool IsSelectionBufferAvailable() const override;
   void WritePortableRepresentations(
       ClipboardBuffer buffer,
       const ObjectMap& objects,
diff --git a/ui/base/clipboard/test/test_clipboard.cc b/ui/base/clipboard/test/test_clipboard.cc
index 93a37e5..5935c38 100644
--- a/ui/base/clipboard/test/test_clipboard.cc
+++ b/ui/base/clipboard/test/test_clipboard.cc
@@ -239,6 +239,12 @@
   last_modified_time_ = base::Time();
 }
 
+#if defined(USE_OZONE)
+bool TestClipboard::IsSelectionBufferAvailable() const {
+  return false;
+}
+#endif  // defined(USE_OZONE)
+
 void TestClipboard::WritePortableRepresentations(
     ClipboardBuffer buffer,
     const ObjectMap& objects,
diff --git a/ui/base/clipboard/test/test_clipboard.h b/ui/base/clipboard/test/test_clipboard.h
index 83324202..44f77a57 100644
--- a/ui/base/clipboard/test/test_clipboard.h
+++ b/ui/base/clipboard/test/test_clipboard.h
@@ -78,6 +78,9 @@
                 std::string* result) const override;
   base::Time GetLastModifiedTime() const override;
   void ClearLastModifiedTime() override;
+#if defined(USE_OZONE)
+  bool IsSelectionBufferAvailable() const override;
+#endif  // defined(USE_OZONE)
   void WritePortableRepresentations(
       ClipboardBuffer buffer,
       const ObjectMap& objects,
diff --git a/ui/base/mojom/clipboard_blink_mojom_traits.h b/ui/base/mojom/clipboard_blink_mojom_traits.h
index e993d50..b49dd29 100644
--- a/ui/base/mojom/clipboard_blink_mojom_traits.h
+++ b/ui/base/mojom/clipboard_blink_mojom_traits.h
@@ -26,12 +26,8 @@
         *out = ui::ClipboardBuffer::kCopyPaste;
         return true;
       case blink::mojom::ClipboardBuffer::kSelection:
-#if defined(USE_X11)
         *out = ui::ClipboardBuffer::kSelection;
         return true;
-#else
-        return false;
-#endif
     }
     return false;
   }
diff --git a/ui/compositor/BUILD.gn b/ui/compositor/BUILD.gn
index e71fb2cd..219d6a8f 100644
--- a/ui/compositor/BUILD.gn
+++ b/ui/compositor/BUILD.gn
@@ -208,14 +208,26 @@
   }
 
   if (use_ozone) {
-    sources += [ "test/test_compositor_host_ozone.cc" ]
+    sources += [
+      "test/test_compositor_host_ozone.cc",
+      "test/test_compositor_host_ozone.h",
+    ]
 
     deps += [
       "//ui/ozone",
       "//ui/platform_window",
     ]
-  } else if (use_x11) {
-    sources += [ "test/test_compositor_host_x11.cc" ]
+  }
+
+  if (use_x11) {
+    sources += [
+      "test/test_compositor_host_x11.cc",
+      "test/test_compositor_host_x11.h",
+    ]
+  }
+
+  if (is_linux) {
+    sources += [ "test/test_compositor_host_linux.cc" ]
   }
 }
 
diff --git a/ui/compositor/test/test_compositor_host_linux.cc b/ui/compositor/test/test_compositor_host_linux.cc
new file mode 100644
index 0000000..7dd512d
--- /dev/null
+++ b/ui/compositor/test/test_compositor_host_linux.cc
@@ -0,0 +1,35 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/compositor/test/test_compositor_host.h"
+
+#include "base/notreached.h"
+
+#if defined(USE_OZONE)
+#include "ui/base/ui_base_features.h"
+#include "ui/compositor/test/test_compositor_host_ozone.h"
+#endif
+
+#if defined(USE_X11)
+#include "ui/compositor/test/test_compositor_host_x11.h"
+#endif
+
+namespace ui {
+
+// static
+TestCompositorHost* TestCompositorHost::Create(
+    const gfx::Rect& bounds,
+    ui::ContextFactory* context_factory) {
+#if defined(USE_OZONE)
+  if (features::IsUsingOzonePlatform())
+    return new TestCompositorHostOzone(bounds, context_factory);
+#endif
+#if defined(USE_X11)
+  return new TestCompositorHostX11(bounds, context_factory);
+#endif
+  NOTREACHED();
+  return nullptr;
+}
+
+}  // namespace ui
diff --git a/ui/compositor/test/test_compositor_host_ozone.cc b/ui/compositor/test/test_compositor_host_ozone.cc
index 50b77ca7..7a6179d 100644
--- a/ui/compositor/test/test_compositor_host_ozone.cc
+++ b/ui/compositor/test/test_compositor_host_ozone.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 "ui/compositor/test/test_compositor_host.h"
+#include "ui/compositor/test/test_compositor_host_ozone.h"
 
 #include <memory>
 
@@ -13,6 +13,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
+#include "build/build_config.h"
 #include "components/viz/common/surfaces/parent_local_surface_id_allocator.h"
 #include "ui/compositor/compositor.h"
 #include "ui/gfx/geometry/rect.h"
@@ -24,11 +25,10 @@
 
 namespace ui {
 
-namespace {
-
 // Stub implementation of PlatformWindowDelegate that stores the
 // AcceleratedWidget.
-class StubPlatformWindowDelegate : public PlatformWindowDelegate {
+class TestCompositorHostOzone::StubPlatformWindowDelegate
+    : public PlatformWindowDelegate {
  public:
   StubPlatformWindowDelegate() {}
   ~StubPlatformWindowDelegate() override {}
@@ -59,26 +59,6 @@
   DISALLOW_COPY_AND_ASSIGN(StubPlatformWindowDelegate);
 };
 
-class TestCompositorHostOzone : public TestCompositorHost {
- public:
-  TestCompositorHostOzone(const gfx::Rect& bounds,
-                          ui::ContextFactory* context_factory);
-  ~TestCompositorHostOzone() override;
-
- private:
-  // Overridden from TestCompositorHost:
-  void Show() override;
-  ui::Compositor* GetCompositor() override;
-
-  gfx::Rect bounds_;
-  ui::Compositor compositor_;
-  std::unique_ptr<PlatformWindow> window_;
-  StubPlatformWindowDelegate window_delegate_;
-  viz::ParentLocalSurfaceIdAllocator allocator_;
-
-  DISALLOW_COPY_AND_ASSIGN(TestCompositorHostOzone);
-};
-
 TestCompositorHostOzone::TestCompositorHostOzone(
     const gfx::Rect& bounds,
     ui::ContextFactory* context_factory)
@@ -86,7 +66,8 @@
       compositor_(context_factory->AllocateFrameSinkId(),
                   context_factory,
                   base::ThreadTaskRunnerHandle::Get(),
-                  false /* enable_pixel_canvas */) {}
+                  false /* enable_pixel_canvas */),
+      window_delegate_(std::make_unique<StubPlatformWindowDelegate>()) {}
 
 TestCompositorHostOzone::~TestCompositorHostOzone() {
   // |window_| should be destroyed earlier than |window_delegate_| as it refers
@@ -99,12 +80,12 @@
   properties.bounds = bounds_;
   // Create a PlatformWindow to get the AcceleratedWidget backing it.
   window_ = ui::OzonePlatform::GetInstance()->CreatePlatformWindow(
-      &window_delegate_, std::move(properties));
+      window_delegate_.get(), std::move(properties));
   window_->Show();
-  DCHECK_NE(window_delegate_.widget(), gfx::kNullAcceleratedWidget);
+  DCHECK_NE(window_delegate_->widget(), gfx::kNullAcceleratedWidget);
 
   allocator_.GenerateId();
-  compositor_.SetAcceleratedWidget(window_delegate_.widget());
+  compositor_.SetAcceleratedWidget(window_delegate_->widget());
   compositor_.SetScaleAndSize(1.0f, bounds_.size(),
                               allocator_.GetCurrentLocalSurfaceIdAllocation());
   compositor_.SetVisible(true);
@@ -114,13 +95,16 @@
   return &compositor_;
 }
 
-}  // namespace
-
+// To avoid multiple definitions when use_x11 && use_ozone is true, disable this
+// factory method for OS_LINUX as Linux has a factory method that decides what
+// screen to use based on IsUsingOzonePlatform feature flag.
+#if !defined(OS_LINUX)
 // static
 TestCompositorHost* TestCompositorHost::Create(
     const gfx::Rect& bounds,
     ui::ContextFactory* context_factory) {
   return new TestCompositorHostOzone(bounds, context_factory);
 }
+#endif
 
 }  // namespace ui
diff --git a/ui/compositor/test/test_compositor_host_ozone.h b/ui/compositor/test/test_compositor_host_ozone.h
new file mode 100644
index 0000000..7b764f20
--- /dev/null
+++ b/ui/compositor/test/test_compositor_host_ozone.h
@@ -0,0 +1,44 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_COMPOSITOR_TEST_TEST_COMPOSITOR_HOST_OZONE_H_
+#define UI_COMPOSITOR_TEST_TEST_COMPOSITOR_HOST_OZONE_H_
+
+#include "ui/compositor/test/test_compositor_host.h"
+
+#include <memory>
+
+#include "components/viz/common/surfaces/parent_local_surface_id_allocator.h"
+#include "ui/compositor/compositor.h"
+#include "ui/gfx/geometry/rect.h"
+
+namespace ui {
+
+class PlatformWindow;
+
+class TestCompositorHostOzone : public TestCompositorHost {
+ public:
+  TestCompositorHostOzone(const gfx::Rect& bounds,
+                          ui::ContextFactory* context_factory);
+  TestCompositorHostOzone(const TestCompositorHostOzone&) = delete;
+  TestCompositorHostOzone& operator=(const TestCompositorHostOzone&) = delete;
+  ~TestCompositorHostOzone() override;
+
+ private:
+  class StubPlatformWindowDelegate;
+
+  // Overridden from TestCompositorHost:
+  void Show() override;
+  ui::Compositor* GetCompositor() override;
+
+  gfx::Rect bounds_;
+  ui::Compositor compositor_;
+  std::unique_ptr<PlatformWindow> window_;
+  std::unique_ptr<StubPlatformWindowDelegate> window_delegate_;
+  viz::ParentLocalSurfaceIdAllocator allocator_;
+};
+
+}  // namespace ui
+
+#endif  // UI_COMPOSITOR_TEST_TEST_COMPOSITOR_HOST_OZONE_H_
diff --git a/ui/compositor/test/test_compositor_host_x11.cc b/ui/compositor/test/test_compositor_host_x11.cc
index 1f866da..cff7ee7 100644
--- a/ui/compositor/test/test_compositor_host_x11.cc
+++ b/ui/compositor/test/test_compositor_host_x11.cc
@@ -2,50 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ui/compositor/test/test_compositor_host.h"
-
-#include <memory>
+#include "ui/compositor/test/test_compositor_host_x11.h"
 
 #include "base/bind.h"
 #include "base/compiler_specific.h"
-#include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
-#include "components/viz/common/surfaces/parent_local_surface_id_allocator.h"
-#include "ui/compositor/compositor.h"
 #include "ui/events/x/x11_window_event_manager.h"
-#include "ui/gfx/geometry/rect.h"
-#include "ui/gfx/x/x11.h"
-#include "ui/gfx/x/x11_types.h"
 
 namespace ui {
 
-class TestCompositorHostX11 : public TestCompositorHost {
- public:
-  TestCompositorHostX11(const gfx::Rect& bounds,
-                        ui::ContextFactory* context_factory);
-  ~TestCompositorHostX11() override;
-
- private:
-  // Overridden from TestCompositorHost:
-  void Show() override;
-  ui::Compositor* GetCompositor() override;
-
-  gfx::Rect bounds_;
-
-  ui::ContextFactory* context_factory_;
-
-  ui::Compositor compositor_;
-
-  x11::Window window_;
-
-  std::unique_ptr<XScopedEventSelector> window_events_;
-  viz::ParentLocalSurfaceIdAllocator allocator_;
-
-  DISALLOW_COPY_AND_ASSIGN(TestCompositorHostX11);
-};
-
 TestCompositorHostX11::TestCompositorHostX11(
     const gfx::Rect& bounds,
     ui::ContextFactory* context_factory)
@@ -88,11 +55,4 @@
   return &compositor_;
 }
 
-// static
-TestCompositorHost* TestCompositorHost::Create(
-    const gfx::Rect& bounds,
-    ui::ContextFactory* context_factory) {
-  return new TestCompositorHostX11(bounds, context_factory);
-}
-
 }  // namespace ui
diff --git a/ui/compositor/test/test_compositor_host_x11.h b/ui/compositor/test/test_compositor_host_x11.h
new file mode 100644
index 0000000..4e10912
--- /dev/null
+++ b/ui/compositor/test/test_compositor_host_x11.h
@@ -0,0 +1,49 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_COMPOSITOR_TEST_TEST_COMPOSITOR_HOST_X11_H_
+#define UI_COMPOSITOR_TEST_TEST_COMPOSITOR_HOST_X11_H_
+
+#include <memory>
+
+#include "base/time/time.h"
+#include "components/viz/common/surfaces/parent_local_surface_id_allocator.h"
+#include "ui/compositor/compositor.h"
+#include "ui/compositor/test/test_compositor_host.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/x/x11.h"
+#include "ui/gfx/x/x11_types.h"
+
+namespace ui {
+
+class XScopedEventSelector;
+
+class TestCompositorHostX11 : public TestCompositorHost {
+ public:
+  TestCompositorHostX11(const gfx::Rect& bounds,
+                        ui::ContextFactory* context_factory);
+  TestCompositorHostX11(const TestCompositorHostX11&) = delete;
+  TestCompositorHostX11& operator=(const TestCompositorHostX11&) = delete;
+  ~TestCompositorHostX11() override;
+
+ private:
+  // Overridden from TestCompositorHost:
+  void Show() override;
+  ui::Compositor* GetCompositor() override;
+
+  gfx::Rect bounds_;
+
+  ui::ContextFactory* context_factory_;
+
+  ui::Compositor compositor_;
+
+  x11::Window window_;
+
+  std::unique_ptr<XScopedEventSelector> window_events_;
+  viz::ParentLocalSurfaceIdAllocator allocator_;
+};
+
+}  // namespace ui
+
+#endif  // UI_COMPOSITOR_TEST_TEST_COMPOSITOR_HOST_X11_H_
diff --git a/ui/display/manager/display_configurator.cc b/ui/display/manager/display_configurator.cc
index 7f7c2bbc..53dc139 100644
--- a/ui/display/manager/display_configurator.cc
+++ b/ui/display/manager/display_configurator.cc
@@ -44,31 +44,14 @@
   const DisplayMode* mirror_mode = nullptr;
 };
 
-// This is used for calling either SetColorMatrix() or SetGammaCorrection()
-// depending on the given |color_correction_closure| which is run synchronously.
-// If |reset_color_space_on_success| is true and running
-// |color_correction_closure| returns true, then the color space of the display
-// with |display_id| will be reset.
-bool RunColorCorrectionClosureSync(
+// Returns whether |display_id| can be found in |display_list|,
+bool IsDisplayIdInDisplayStateList(
     int64_t display_id,
-    const DisplayConfigurator::DisplayStateList& cached_displays,
-    bool reset_color_space_on_success,
-    base::OnceCallback<bool(void)> color_correction_closure) {
-  for (DisplaySnapshot* display : cached_displays) {
-    if (display->display_id() != display_id)
-      continue;
-
-    const bool success = std::move(color_correction_closure).Run();
-
-    // Nullify the |display|s ColorSpace to avoid correcting colors twice, if
-    // we have successfully configured something.
-    if (success && reset_color_space_on_success)
-      display->reset_color_space();
-
-    return success;
-  }
-
-  return false;
+    const DisplayConfigurator::DisplayStateList& display_list) {
+  return std::find_if(display_list.begin(), display_list.end(),
+                      [display_id](DisplaySnapshot* display) {
+                        return display->display_id() == display_id;
+                      }) != display_list.end();
 }
 
 // Returns true if a platform native |mode| is equal to a |managed_mode|.
@@ -761,25 +744,19 @@
 bool DisplayConfigurator::SetColorMatrix(
     int64_t display_id,
     const std::vector<float>& color_matrix) {
-  return RunColorCorrectionClosureSync(
-      display_id, cached_displays_,
-      !color_matrix.empty() /* reset_color_space_on_success */,
-      base::BindOnce(&NativeDisplayDelegate::SetColorMatrix,
-                     base::Unretained(native_display_delegate_.get()),
-                     display_id, color_matrix));
+  if (!IsDisplayIdInDisplayStateList(display_id, cached_displays_))
+    return false;
+  return native_display_delegate_->SetColorMatrix(display_id, color_matrix);
 }
 
 bool DisplayConfigurator::SetGammaCorrection(
     int64_t display_id,
     const std::vector<GammaRampRGBEntry>& degamma_lut,
     const std::vector<GammaRampRGBEntry>& gamma_lut) {
-  const bool reset_color_space_on_success =
-      !degamma_lut.empty() || !gamma_lut.empty();
-  return RunColorCorrectionClosureSync(
-      display_id, cached_displays_, reset_color_space_on_success,
-      base::BindOnce(&NativeDisplayDelegate::SetGammaCorrection,
-                     base::Unretained(native_display_delegate_.get()),
-                     display_id, degamma_lut, gamma_lut));
+  if (!IsDisplayIdInDisplayStateList(display_id, cached_displays_))
+    return false;
+  return native_display_delegate_->SetGammaCorrection(display_id, degamma_lut,
+                                                      gamma_lut);
 }
 
 void DisplayConfigurator::SetPrivacyScreen(int64_t display_id, bool enabled) {
diff --git a/ui/display/types/display_snapshot.h b/ui/display/types/display_snapshot.h
index 22c567a..fc552e50 100644
--- a/ui/display/types/display_snapshot.h
+++ b/ui/display/types/display_snapshot.h
@@ -71,7 +71,6 @@
     return color_correction_in_linear_space_;
   }
   const gfx::ColorSpace& color_space() const { return color_space_; }
-  void reset_color_space() { color_space_ = gfx::ColorSpace(); }
   uint32_t bits_per_channel() const { return bits_per_channel_; }
   const std::string& display_name() const { return display_name_; }
   const base::FilePath& sys_path() const { return sys_path_; }
@@ -122,7 +121,7 @@
   // instead of gamma compressed one.
   const bool color_correction_in_linear_space_;
 
-  gfx::ColorSpace color_space_;
+  const gfx::ColorSpace color_space_;
 
   uint32_t bits_per_channel_;
 
diff --git a/ui/ozone/platform/wayland/common/wayland_util.cc b/ui/ozone/platform/wayland/common/wayland_util.cc
index 19d33ef4..3b0b882 100644
--- a/ui/ozone/platform/wayland/common/wayland_util.cc
+++ b/ui/ozone/platform/wayland/common/wayland_util.cc
@@ -172,4 +172,16 @@
   return wayland_surface->root_window();
 }
 
+gfx::Rect TranslateWindowBoundsToParentDIP(ui::WaylandWindow* window,
+                                           ui::WaylandWindow* parent_window) {
+  DCHECK(window);
+  DCHECK(parent_window);
+  DCHECK_EQ(window->buffer_scale(), parent_window->buffer_scale());
+  DCHECK_EQ(window->ui_scale(), parent_window->ui_scale());
+  return gfx::ScaleToRoundedRect(
+      wl::TranslateBoundsToParentCoordinates(window->GetBounds(),
+                                             parent_window->GetBounds()),
+      1.0 / window->buffer_scale());
+}
+
 }  // namespace wl
diff --git a/ui/ozone/platform/wayland/common/wayland_util.h b/ui/ozone/platform/wayland/common/wayland_util.h
index 918f4c6..582a3c7 100644
--- a/ui/ozone/platform/wayland/common/wayland_util.h
+++ b/ui/ozone/platform/wayland/common/wayland_util.h
@@ -66,6 +66,13 @@
 // Returns the root WaylandWindow for the given wl_surface.
 ui::WaylandWindow* RootWindowFromWlSurface(wl_surface* surface);
 
+// Returns bounds of the given window, adjusted to its subsurface. We need to
+// adjust bounds because WaylandWindow::GetBounds() returns absolute bounds in
+// pixels, but wl_subsurface works with bounds relative to the parent surface
+// and in DIP.
+gfx::Rect TranslateWindowBoundsToParentDIP(ui::WaylandWindow* window,
+                                           ui::WaylandWindow* parent_window);
+
 }  // namespace wl
 
 #endif  // UI_OZONE_PLATFORM_WAYLAND_COMMON_WAYLAND_UTIL_H_
diff --git a/ui/ozone/platform/wayland/host/wayland_auxiliary_window.cc b/ui/ozone/platform/wayland/host/wayland_auxiliary_window.cc
index e1859d75..c5e31fd5 100644
--- a/ui/ozone/platform/wayland/host/wayland_auxiliary_window.cc
+++ b/ui/ozone/platform/wayland/host/wayland_auxiliary_window.cc
@@ -12,21 +12,6 @@
 
 namespace ui {
 
-namespace {
-
-gfx::Rect AdjustSubsurfaceBounds(const gfx::Rect& bounds_px,
-                                 const gfx::Rect& parent_bounds_px,
-                                 float ui_scale,
-                                 int32_t buffer_scale) {
-  const auto parent_bounds_dip =
-      gfx::ScaleToRoundedRect(parent_bounds_px, 1.0 / ui_scale);
-  auto new_bounds_dip =
-      wl::TranslateBoundsToParentCoordinates(bounds_px, parent_bounds_dip);
-  return gfx::ScaleToRoundedRect(new_bounds_dip, ui_scale / buffer_scale);
-}
-
-}  // namespace
-
 WaylandAuxiliaryWindow::WaylandAuxiliaryWindow(PlatformWindowDelegate* delegate,
                                                WaylandConnection* connection)
     : WaylandWindow(delegate, connection) {}
@@ -63,11 +48,10 @@
   if (old_bounds == bounds || !parent_window())
     return;
 
-  // Translate location from screen to surface coordinates.
-  auto bounds_px = AdjustSubsurfaceBounds(
-      GetBounds(), parent_window()->GetBounds(), ui_scale(), buffer_scale());
-  wl_subsurface_set_position(subsurface_.get(), bounds_px.x() / buffer_scale(),
-                             bounds_px.y() / buffer_scale());
+  auto subsurface_bounds_dip =
+      wl::TranslateWindowBoundsToParentDIP(this, parent_window());
+  wl_subsurface_set_position(subsurface_.get(), subsurface_bounds_dip.x(),
+                             subsurface_bounds_dip.y());
   root_surface()->Commit();
   connection()->ScheduleFlush();
 }
@@ -99,16 +83,13 @@
 
   subsurface_ = root_surface()->CreateSubsurface(parent->root_surface());
 
-  // Chromium positions tooltip windows in screen coordinates, but Wayland
-  // requires them to be in local surface coordinates a.k.a relative to parent
-  // window.
-  auto bounds_px = AdjustSubsurfaceBounds(GetBounds(), parent->GetBounds(),
-                                          ui_scale(), buffer_scale());
+  auto subsurface_bounds_dip =
+      wl::TranslateWindowBoundsToParentDIP(this, parent);
 
   DCHECK(subsurface_);
   // Convert position to DIP.
-  wl_subsurface_set_position(subsurface_.get(), bounds_px.x() / buffer_scale(),
-                             bounds_px.y() / buffer_scale());
+  wl_subsurface_set_position(subsurface_.get(), subsurface_bounds_dip.x(),
+                             subsurface_bounds_dip.y());
   wl_subsurface_set_desync(subsurface_.get());
   parent->root_surface()->Commit();
   connection()->ScheduleFlush();
diff --git a/ui/ozone/platform/wayland/host/wayland_clipboard.cc b/ui/ozone/platform/wayland/host/wayland_clipboard.cc
index cd99fdd..7e1cdf1a 100644
--- a/ui/ozone/platform/wayland/host/wayland_clipboard.cc
+++ b/ui/ozone/platform/wayland/host/wayland_clipboard.cc
@@ -185,6 +185,10 @@
   std::move(callback).Run(mime_types);
 }
 
+bool WaylandClipboard::IsSelectionBufferAvailable() const {
+  return (connection_->primary_selection_device_manager() != nullptr);
+}
+
 void WaylandClipboard::SetData(PlatformClipboard::Data contents,
                                const std::string& mime_type) {
   if (!data_map_)
diff --git a/ui/ozone/platform/wayland/host/wayland_clipboard.h b/ui/ozone/platform/wayland/host/wayland_clipboard.h
index c8b724d..515c85c 100644
--- a/ui/ozone/platform/wayland/host/wayland_clipboard.h
+++ b/ui/ozone/platform/wayland/host/wayland_clipboard.h
@@ -53,6 +53,7 @@
   bool IsSelectionOwner(ClipboardBuffer buffer) override;
   void SetSequenceNumberUpdateCb(
       PlatformClipboard::SequenceNumberUpdateCb cb) override;
+  bool IsSelectionBufferAvailable() const override;
 
   // TODO(nickdiego): Get rid of these methods once DataDevice implementations
   // are decoupled from WaylandClipboard.
diff --git a/ui/ozone/platform/wayland/host/wayland_popup.cc b/ui/ozone/platform/wayland/host/wayland_popup.cc
index ec7e27e..f1f7c8d 100644
--- a/ui/ozone/platform/wayland/host/wayland_popup.cc
+++ b/ui/ozone/platform/wayland/host/wayland_popup.cc
@@ -24,10 +24,12 @@
 
   DCHECK(parent_window() && !shell_popup_);
 
-  auto bounds_px = AdjustPopupWindowPosition();
+  auto subsurface_bounds_dip =
+      wl::TranslateWindowBoundsToParentDIP(this, parent_window());
 
   ShellObjectFactory factory;
-  shell_popup_ = factory.CreateShellPopupWrapper(connection(), this, bounds_px);
+  shell_popup_ = factory.CreateShellPopupWrapper(connection(), this,
+                                                 subsurface_bounds_dip);
   if (!shell_popup_) {
     LOG(ERROR) << "Failed to create Wayland shell popup";
     return false;
@@ -143,20 +145,4 @@
   return true;
 }
 
-gfx::Rect WaylandPopup::AdjustPopupWindowPosition() {
-  auto* top_level_parent = GetRootParentWindow();
-  DCHECK(top_level_parent);
-  DCHECK(buffer_scale() == top_level_parent->buffer_scale());
-  DCHECK(ui_scale() == top_level_parent->ui_scale());
-
-  // Chromium positions windows in screen coordinates, but Wayland requires them
-  // to be in local surface coordinates a.k.a relative to parent window.
-  const gfx::Rect parent_bounds_dip =
-      gfx::ScaleToRoundedRect(parent_window()->GetBounds(), 1.0 / ui_scale());
-  gfx::Rect new_bounds_dip = wl::TranslateBoundsToParentCoordinates(
-      gfx::ScaleToRoundedRect(GetBounds(), 1.0 / ui_scale()),
-      parent_bounds_dip);
-  return gfx::ScaleToRoundedRect(new_bounds_dip, ui_scale() / buffer_scale());
-}
-
 }  // namespace ui
diff --git a/ui/ozone/platform/x11/x11_clipboard_ozone.cc b/ui/ozone/platform/x11/x11_clipboard_ozone.cc
index e23c83b..c913289 100644
--- a/ui/ozone/platform/x11/x11_clipboard_ozone.cc
+++ b/ui/ozone/platform/x11/x11_clipboard_ozone.cc
@@ -398,4 +398,8 @@
   update_sequence_cb_ = std::move(cb);
 }
 
+bool X11ClipboardOzone::IsSelectionBufferAvailable() const {
+  return true;
+}
+
 }  // namespace ui
diff --git a/ui/ozone/platform/x11/x11_clipboard_ozone.h b/ui/ozone/platform/x11/x11_clipboard_ozone.h
index 478205c..a8c0c96 100644
--- a/ui/ozone/platform/x11/x11_clipboard_ozone.h
+++ b/ui/ozone/platform/x11/x11_clipboard_ozone.h
@@ -49,6 +49,7 @@
   bool IsSelectionOwner(ClipboardBuffer buffer) override;
   void SetSequenceNumberUpdateCb(
       PlatformClipboard::SequenceNumberUpdateCb cb) override;
+  bool IsSelectionBufferAvailable() const override;
 
  private:
   struct SelectionState;
diff --git a/ui/ozone/public/platform_clipboard.h b/ui/ozone/public/platform_clipboard.h
index 7390222..941e2377 100644
--- a/ui/ozone/public/platform_clipboard.h
+++ b/ui/ozone/public/platform_clipboard.h
@@ -90,6 +90,9 @@
 
   // See comment above SequenceNumberUpdateCb. Can be called once.
   virtual void SetSequenceNumberUpdateCb(SequenceNumberUpdateCb cb) = 0;
+
+  // Returns whether the kSelection buffer is available.
+  virtual bool IsSelectionBufferAvailable() const = 0;
 };
 
 }  // namespace ui
diff --git a/weblayer/BUILD.gn b/weblayer/BUILD.gn
index 8805b11..c9ab2ff 100644
--- a/weblayer/BUILD.gn
+++ b/weblayer/BUILD.gn
@@ -214,6 +214,8 @@
     "browser/no_state_prefetch/prerender_processor_impl_delegate_impl.h",
     "browser/page_load_metrics_initialize.cc",
     "browser/page_load_metrics_initialize.h",
+    "browser/page_specific_content_settings_delegate.cc",
+    "browser/page_specific_content_settings_delegate.h",
     "browser/password_manager_driver_factory.cc",
     "browser/password_manager_driver_factory.h",
     "browser/permissions/geolocation_permission_context_delegate.cc",
@@ -248,8 +250,6 @@
     "browser/system_network_context_manager.h",
     "browser/tab_impl.cc",
     "browser/tab_impl.h",
-    "browser/tab_specific_content_settings_delegate.cc",
-    "browser/tab_specific_content_settings_delegate.h",
     "browser/translate_accept_languages_factory.cc",
     "browser/translate_accept_languages_factory.h",
     "browser/translate_client_impl.cc",
diff --git a/weblayer/browser/content_browser_client_impl.cc b/weblayer/browser/content_browser_client_impl.cc
index c73a95e..47928a3 100644
--- a/weblayer/browser/content_browser_client_impl.cc
+++ b/weblayer/browser/content_browser_client_impl.cc
@@ -76,13 +76,13 @@
 #include "weblayer/browser/i18n_util.h"
 #include "weblayer/browser/navigation_controller_impl.h"
 #include "weblayer/browser/navigation_error_navigation_throttle.h"
+#include "weblayer/browser/page_specific_content_settings_delegate.h"
 #include "weblayer/browser/password_manager_driver_factory.h"
 #include "weblayer/browser/popup_navigation_delegate_impl.h"
 #include "weblayer/browser/profile_impl.h"
 #include "weblayer/browser/signin_url_loader_throttle.h"
 #include "weblayer/browser/system_network_context_manager.h"
 #include "weblayer/browser/tab_impl.h"
-#include "weblayer/browser/tab_specific_content_settings_delegate.h"
 #include "weblayer/browser/user_agent.h"
 #include "weblayer/browser/web_contents_view_delegate_impl.h"
 #include "weblayer/browser/weblayer_browser_interface_binders.h"
@@ -692,7 +692,7 @@
       /*can_persist_data*/ true,
       /*force_to_support_secure_codecs*/ false));
 #endif
-  TabSpecificContentSettingsDelegate::UpdateRendererContentSettingRules(host);
+  PageSpecificContentSettingsDelegate::UpdateRendererContentSettingRules(host);
 }
 
 scoped_refptr<content::QuotaPermissionContext>
diff --git a/weblayer/browser/tab_specific_content_settings_delegate.cc b/weblayer/browser/page_specific_content_settings_delegate.cc
similarity index 67%
rename from weblayer/browser/tab_specific_content_settings_delegate.cc
rename to weblayer/browser/page_specific_content_settings_delegate.cc
index 9db88a2..e07b644a 100644
--- a/weblayer/browser/tab_specific_content_settings_delegate.cc
+++ b/weblayer/browser/page_specific_content_settings_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 "weblayer/browser/tab_specific_content_settings_delegate.h"
+#include "weblayer/browser/page_specific_content_settings_delegate.h"
 
 #include "base/bind_helpers.h"
 #include "components/content_settings/core/common/content_settings.h"
@@ -25,15 +25,15 @@
 
 }  // namespace
 
-TabSpecificContentSettingsDelegate::TabSpecificContentSettingsDelegate(
+PageSpecificContentSettingsDelegate::PageSpecificContentSettingsDelegate(
     content::WebContents* web_contents)
     : web_contents_(web_contents) {}
 
-TabSpecificContentSettingsDelegate::~TabSpecificContentSettingsDelegate() =
+PageSpecificContentSettingsDelegate::~PageSpecificContentSettingsDelegate() =
     default;
 
 // static
-void TabSpecificContentSettingsDelegate::UpdateRendererContentSettingRules(
+void PageSpecificContentSettingsDelegate::UpdateRendererContentSettingRules(
     content::RenderProcessHost* process) {
   RendererContentSettingRules rules;
   GetRendererContentSettingRules(
@@ -43,25 +43,25 @@
   weblayer::SetContentSettingRules(process, rules);
 }
 
-void TabSpecificContentSettingsDelegate::UpdateLocationBar() {}
+void PageSpecificContentSettingsDelegate::UpdateLocationBar() {}
 
-void TabSpecificContentSettingsDelegate::SetContentSettingRules(
+void PageSpecificContentSettingsDelegate::SetContentSettingRules(
     content::RenderProcessHost* process,
     const RendererContentSettingRules& rules) {
   weblayer::SetContentSettingRules(process, rules);
 }
 
-PrefService* TabSpecificContentSettingsDelegate::GetPrefs() {
+PrefService* PageSpecificContentSettingsDelegate::GetPrefs() {
   return static_cast<BrowserContextImpl*>(web_contents_->GetBrowserContext())
       ->pref_service();
 }
 
-HostContentSettingsMap* TabSpecificContentSettingsDelegate::GetSettingsMap() {
+HostContentSettingsMap* PageSpecificContentSettingsDelegate::GetSettingsMap() {
   return HostContentSettingsMapFactory::GetForBrowserContext(
       web_contents_->GetBrowserContext());
 }
 
-ContentSetting TabSpecificContentSettingsDelegate::GetEmbargoSetting(
+ContentSetting PageSpecificContentSettingsDelegate::GetEmbargoSetting(
     const GURL& request_origin,
     ContentSettingsType permission) {
   return PermissionDecisionAutoBlockerFactory::GetForBrowserContext(
@@ -71,33 +71,33 @@
 }
 
 std::vector<storage::FileSystemType>
-TabSpecificContentSettingsDelegate::GetAdditionalFileSystemTypes() {
+PageSpecificContentSettingsDelegate::GetAdditionalFileSystemTypes() {
   return {};
 }
 
 browsing_data::CookieHelper::IsDeletionDisabledCallback
-TabSpecificContentSettingsDelegate::GetIsDeletionDisabledCallback() {
+PageSpecificContentSettingsDelegate::GetIsDeletionDisabledCallback() {
   return base::NullCallback();
 }
 
-bool TabSpecificContentSettingsDelegate::IsMicrophoneCameraStateChanged(
-    content_settings::TabSpecificContentSettings::MicrophoneCameraState
+bool PageSpecificContentSettingsDelegate::IsMicrophoneCameraStateChanged(
+    content_settings::PageSpecificContentSettings::MicrophoneCameraState
         microphone_camera_state,
     const std::string& media_stream_selected_audio_device,
     const std::string& media_stream_selected_video_device) {
   return false;
 }
 
-content_settings::TabSpecificContentSettings::MicrophoneCameraState
-TabSpecificContentSettingsDelegate::GetMicrophoneCameraState() {
-  return content_settings::TabSpecificContentSettings::
+content_settings::PageSpecificContentSettings::MicrophoneCameraState
+PageSpecificContentSettingsDelegate::GetMicrophoneCameraState() {
+  return content_settings::PageSpecificContentSettings::
       MICROPHONE_CAMERA_NOT_ACCESSED;
 }
 
-void TabSpecificContentSettingsDelegate::OnContentBlocked(
+void PageSpecificContentSettingsDelegate::OnContentBlocked(
     ContentSettingsType type) {}
 
-void TabSpecificContentSettingsDelegate::OnCookieAccessAllowed(
+void PageSpecificContentSettingsDelegate::OnCookieAccessAllowed(
     const net::CookieList& accessed_cookies) {}
 
 }  // namespace weblayer
diff --git a/weblayer/browser/page_specific_content_settings_delegate.h b/weblayer/browser/page_specific_content_settings_delegate.h
new file mode 100644
index 0000000..29dd868
--- /dev/null
+++ b/weblayer/browser/page_specific_content_settings_delegate.h
@@ -0,0 +1,55 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBLAYER_BROWSER_PAGE_SPECIFIC_CONTENT_SETTINGS_DELEGATE_H_
+#define WEBLAYER_BROWSER_PAGE_SPECIFIC_CONTENT_SETTINGS_DELEGATE_H_
+
+#include "components/content_settings/browser/page_specific_content_settings.h"
+
+namespace weblayer {
+
+// Called by PageSpecificContentSettings to handle WebLayer specific logic.
+class PageSpecificContentSettingsDelegate
+    : public content_settings::PageSpecificContentSettings::Delegate {
+ public:
+  explicit PageSpecificContentSettingsDelegate(
+      content::WebContents* web_contents);
+  ~PageSpecificContentSettingsDelegate() override;
+  PageSpecificContentSettingsDelegate(
+      const PageSpecificContentSettingsDelegate&) = delete;
+  PageSpecificContentSettingsDelegate& operator=(
+      const PageSpecificContentSettingsDelegate&) = delete;
+
+  static void UpdateRendererContentSettingRules(
+      content::RenderProcessHost* process);
+
+ private:
+  // PageSpecificContentSettings::Delegate:
+  void UpdateLocationBar() override;
+  void SetContentSettingRules(
+      content::RenderProcessHost* process,
+      const RendererContentSettingRules& rules) override;
+  PrefService* GetPrefs() override;
+  HostContentSettingsMap* GetSettingsMap() override;
+  ContentSetting GetEmbargoSetting(const GURL& request_origin,
+                                   ContentSettingsType permission) override;
+  std::vector<storage::FileSystemType> GetAdditionalFileSystemTypes() override;
+  browsing_data::CookieHelper::IsDeletionDisabledCallback
+  GetIsDeletionDisabledCallback() override;
+  bool IsMicrophoneCameraStateChanged(
+      content_settings::PageSpecificContentSettings::MicrophoneCameraState
+          microphone_camera_state,
+      const std::string& media_stream_selected_audio_device,
+      const std::string& media_stream_selected_video_device) override;
+  content_settings::PageSpecificContentSettings::MicrophoneCameraState
+  GetMicrophoneCameraState() override;
+  void OnContentBlocked(ContentSettingsType type) override;
+  void OnCookieAccessAllowed(const net::CookieList& accessed_cookies) override;
+
+  content::WebContents* web_contents_;
+};
+
+}  // namespace weblayer
+
+#endif  // WEBLAYER_BROWSER_PAGE_SPECIFIC_CONTENT_SETTINGS_DELEGATE_H_
diff --git a/weblayer/browser/tab_impl.cc b/weblayer/browser/tab_impl.cc
index 34afe11..535ca4c 100644
--- a/weblayer/browser/tab_impl.cc
+++ b/weblayer/browser/tab_impl.cc
@@ -21,7 +21,7 @@
 #include "components/blocked_content/popup_opener_tab_helper.h"
 #include "components/blocked_content/popup_tracker.h"
 #include "components/captive_portal/core/buildflags.h"
-#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/find_in_page/find_tab_helper.h"
 #include "components/find_in_page/find_types.h"
 #include "components/js_injection/browser/js_communication_host.h"
@@ -63,12 +63,12 @@
 #include "weblayer/browser/js_communication/web_message_host_factory_wrapper.h"
 #include "weblayer/browser/navigation_controller_impl.h"
 #include "weblayer/browser/page_load_metrics_initialize.h"
+#include "weblayer/browser/page_specific_content_settings_delegate.h"
 #include "weblayer/browser/password_manager_driver_factory.h"
 #include "weblayer/browser/permissions/permission_manager_factory.h"
 #include "weblayer/browser/persistence/browser_persister.h"
 #include "weblayer/browser/popup_navigation_delegate_impl.h"
 #include "weblayer/browser/profile_impl.h"
-#include "weblayer/browser/tab_specific_content_settings_delegate.h"
 #include "weblayer/browser/translate_client_impl.h"
 #include "weblayer/browser/weblayer_features.h"
 #include "weblayer/common/isolated_world_ids.h"
@@ -305,9 +305,10 @@
 
   permissions::PermissionRequestManager::CreateForWebContents(
       web_contents_.get());
-  content_settings::TabSpecificContentSettings::CreateForWebContents(
-      web_contents_.get(), std::make_unique<TabSpecificContentSettingsDelegate>(
-                               web_contents_.get()));
+  content_settings::PageSpecificContentSettings::CreateForWebContents(
+      web_contents_.get(),
+      std::make_unique<PageSpecificContentSettingsDelegate>(
+          web_contents_.get()));
   blocked_content::PopupBlockerTabHelper::CreateForWebContents(
       web_contents_.get());
   blocked_content::PopupOpenerTabHelper::CreateForWebContents(
diff --git a/weblayer/browser/tab_specific_content_settings_delegate.h b/weblayer/browser/tab_specific_content_settings_delegate.h
deleted file mode 100644
index b987b745..0000000
--- a/weblayer/browser/tab_specific_content_settings_delegate.h
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef WEBLAYER_BROWSER_TAB_SPECIFIC_CONTENT_SETTINGS_DELEGATE_H_
-#define WEBLAYER_BROWSER_TAB_SPECIFIC_CONTENT_SETTINGS_DELEGATE_H_
-
-#include "components/content_settings/browser/tab_specific_content_settings.h"
-
-namespace weblayer {
-
-// Called by TabSpecificContentSettings to handle WebLayer specific logic.
-class TabSpecificContentSettingsDelegate
-    : public content_settings::TabSpecificContentSettings::Delegate {
- public:
-  explicit TabSpecificContentSettingsDelegate(
-      content::WebContents* web_contents);
-  ~TabSpecificContentSettingsDelegate() override;
-  TabSpecificContentSettingsDelegate(
-      const TabSpecificContentSettingsDelegate&) = delete;
-  TabSpecificContentSettingsDelegate& operator=(
-      const TabSpecificContentSettingsDelegate&) = delete;
-
-  static void UpdateRendererContentSettingRules(
-      content::RenderProcessHost* process);
-
- private:
-  // TabSpecificContentSettings::Delegate:
-  void UpdateLocationBar() override;
-  void SetContentSettingRules(
-      content::RenderProcessHost* process,
-      const RendererContentSettingRules& rules) override;
-  PrefService* GetPrefs() override;
-  HostContentSettingsMap* GetSettingsMap() override;
-  ContentSetting GetEmbargoSetting(const GURL& request_origin,
-                                   ContentSettingsType permission) override;
-  std::vector<storage::FileSystemType> GetAdditionalFileSystemTypes() override;
-  browsing_data::CookieHelper::IsDeletionDisabledCallback
-  GetIsDeletionDisabledCallback() override;
-  bool IsMicrophoneCameraStateChanged(
-      content_settings::TabSpecificContentSettings::MicrophoneCameraState
-          microphone_camera_state,
-      const std::string& media_stream_selected_audio_device,
-      const std::string& media_stream_selected_video_device) override;
-  content_settings::TabSpecificContentSettings::MicrophoneCameraState
-  GetMicrophoneCameraState() override;
-  void OnContentBlocked(ContentSettingsType type) override;
-  void OnCookieAccessAllowed(const net::CookieList& accessed_cookies) override;
-
-  content::WebContents* web_contents_;
-};
-
-}  // namespace weblayer
-
-#endif  // WEBLAYER_BROWSER_TAB_SPECIFIC_CONTENT_SETTINGS_DELEGATE_H_
diff --git a/weblayer/browser/url_bar/page_info_browsertest.cc b/weblayer/browser/url_bar/page_info_browsertest.cc
index dc4ad64..6c59719 100644
--- a/weblayer/browser/url_bar/page_info_browsertest.cc
+++ b/weblayer/browser/url_bar/page_info_browsertest.cc
@@ -76,11 +76,11 @@
 }
 
 IN_PROC_BROWSER_TEST_F(PageInfoBrowserTest,
-                       TabSpecificContentSettingsDelegate) {
+                       PageSpecificContentSettingsDelegate) {
   std::unique_ptr<PageInfoDelegate> page_info_delegate =
       page_info::GetPageInfoClient()->CreatePageInfoDelegate(GetWebContents());
   ASSERT_TRUE(page_info_delegate);
-  EXPECT_TRUE(page_info_delegate->GetTabSpecificContentSettingsDelegate());
+  EXPECT_TRUE(page_info_delegate->GetPageSpecificContentSettingsDelegate());
 }
 
 IN_PROC_BROWSER_TEST_F(PageInfoBrowserTest, EmbedderNameSet) {
diff --git a/weblayer/browser/url_bar/page_info_delegate_impl.cc b/weblayer/browser/url_bar/page_info_delegate_impl.cc
index f189430f..b9ac734 100644
--- a/weblayer/browser/url_bar/page_info_delegate_impl.cc
+++ b/weblayer/browser/url_bar/page_info_delegate_impl.cc
@@ -10,10 +10,10 @@
 #include "components/security_state/content/content_utils.h"
 #include "content/public/browser/browser_context.h"
 #include "weblayer/browser/host_content_settings_map_factory.h"
+#include "weblayer/browser/page_specific_content_settings_delegate.h"
 #include "weblayer/browser/permissions/permission_decision_auto_blocker_factory.h"
 #include "weblayer/browser/permissions/permission_manager_factory.h"
 #include "weblayer/browser/stateful_ssl_host_state_delegate_factory.h"
-#include "weblayer/browser/tab_specific_content_settings_delegate.h"
 
 #if defined(OS_ANDROID)
 #include "weblayer/browser/weblayer_impl_android.h"
@@ -108,9 +108,9 @@
   return *security_state::GetVisibleSecurityState(web_contents_);
 }
 
-std::unique_ptr<content_settings::TabSpecificContentSettings::Delegate>
-PageInfoDelegateImpl::GetTabSpecificContentSettingsDelegate() {
-  return std::make_unique<TabSpecificContentSettingsDelegate>(web_contents_);
+std::unique_ptr<content_settings::PageSpecificContentSettings::Delegate>
+PageInfoDelegateImpl::GetPageSpecificContentSettingsDelegate() {
+  return std::make_unique<PageSpecificContentSettingsDelegate>(web_contents_);
 }
 
 #if defined(OS_ANDROID)
diff --git a/weblayer/browser/url_bar/page_info_delegate_impl.h b/weblayer/browser/url_bar/page_info_delegate_impl.h
index 65939b3..e12a4cc45 100644
--- a/weblayer/browser/url_bar/page_info_delegate_impl.h
+++ b/weblayer/browser/url_bar/page_info_delegate_impl.h
@@ -43,8 +43,8 @@
       override;
   StatefulSSLHostStateDelegate* GetStatefulSSLHostStateDelegate() override;
   HostContentSettingsMap* GetContentSettings() override;
-  std::unique_ptr<content_settings::TabSpecificContentSettings::Delegate>
-  GetTabSpecificContentSettingsDelegate() override;
+  std::unique_ptr<content_settings::PageSpecificContentSettings::Delegate>
+  GetPageSpecificContentSettingsDelegate() override;
   bool IsContentDisplayedInVrHeadset() override;
   security_state::SecurityLevel GetSecurityLevel() override;
   security_state::VisibleSecurityState GetVisibleSecurityState() override;